994 lines · 30.4 KB · Rust
RawDownload
use std::{cmp, sync::Arc}; use editor::{    char_kind,    display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},    movement::{self, FindRange},    Bias, CharKind, DisplayPoint, ToOffset,};use gpui::{actions, impl_actions, AppContext, WindowContext};use language::{Point, Selection, SelectionGoal};use serde::Deserialize;use workspace::Workspace; use crate::{    normal::normal_motion,    state::{Mode, Operator},    visual::visual_motion,    Vim,}; #[derive(Clone, Debug, PartialEq, Eq)]pub enum Motion {    Left,    Backspace,    Down { display_lines: bool },    Up { display_lines: bool },    Right,    NextWordStart { ignore_punctuation: bool },    NextWordEnd { ignore_punctuation: bool },    PreviousWordStart { ignore_punctuation: bool },    FirstNonWhitespace { display_lines: bool },    CurrentLine,    StartOfLine { display_lines: bool },    EndOfLine { display_lines: bool },    StartOfParagraph,    EndOfParagraph,    StartOfDocument,    EndOfDocument,    Matching,    FindForward { before: bool, text: Arc<str> },    FindBackward { after: bool, text: Arc<str> },    NextLineStart,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]struct NextWordStart {    #[serde(default)]    ignore_punctuation: bool,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]struct NextWordEnd {    #[serde(default)]    ignore_punctuation: bool,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]struct PreviousWordStart {    #[serde(default)]    ignore_punctuation: bool,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]struct Up {    #[serde(default)]    display_lines: bool,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]struct Down {    #[serde(default)]    display_lines: bool,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]struct FirstNonWhitespace {    #[serde(default)]    display_lines: bool,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]struct EndOfLine {    #[serde(default)]    display_lines: bool,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]struct StartOfLine {    #[serde(default)]    display_lines: bool,} #[derive(Clone, Deserialize, PartialEq)]struct RepeatFind {    #[serde(default)]    backwards: bool,} actions!(    vim,    [        Left,        Backspace,        Right,        CurrentLine,        StartOfParagraph,        EndOfParagraph,        StartOfDocument,        EndOfDocument,        Matching,        NextLineStart,    ]);impl_actions!(    vim,    [        NextWordStart,        NextWordEnd,        PreviousWordStart,        RepeatFind,        Up,        Down,        FirstNonWhitespace,        EndOfLine,        StartOfLine,    ]); pub fn init(cx: &mut AppContext) {    cx.add_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));    cx.add_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));    cx.add_action(|_: &mut Workspace, action: &Down, cx: _| {        motion(            Motion::Down {                display_lines: action.display_lines,            },            cx,        )    });    cx.add_action(|_: &mut Workspace, action: &Up, cx: _| {        motion(            Motion::Up {                display_lines: action.display_lines,            },            cx,        )    });    cx.add_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));    cx.add_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {        motion(            Motion::FirstNonWhitespace {                display_lines: action.display_lines,            },            cx,        )    });    cx.add_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {        motion(            Motion::StartOfLine {                display_lines: action.display_lines,            },            cx,        )    });    cx.add_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {        motion(            Motion::EndOfLine {                display_lines: action.display_lines,            },            cx,        )    });    cx.add_action(|_: &mut Workspace, _: &CurrentLine, cx: _| motion(Motion::CurrentLine, cx));    cx.add_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {        motion(Motion::StartOfParagraph, cx)    });    cx.add_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {        motion(Motion::EndOfParagraph, cx)    });    cx.add_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {        motion(Motion::StartOfDocument, cx)    });    cx.add_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| motion(Motion::EndOfDocument, cx));    cx.add_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));     cx.add_action(        |_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {            motion(Motion::NextWordStart { ignore_punctuation }, cx)        },    );    cx.add_action(        |_: &mut Workspace, &NextWordEnd { ignore_punctuation }: &NextWordEnd, cx: _| {            motion(Motion::NextWordEnd { ignore_punctuation }, cx)        },    );    cx.add_action(        |_: &mut Workspace,         &PreviousWordStart { ignore_punctuation }: &PreviousWordStart,         cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },    );    cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx));    cx.add_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {        repeat_motion(action.backwards, cx)    })} pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {    if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) =        Vim::read(cx).active_operator()    {        Vim::update(cx, |vim, cx| vim.pop_operator(cx));    }     let times = Vim::update(cx, |vim, cx| vim.pop_number_operator(cx));    let operator = Vim::read(cx).active_operator();    match Vim::read(cx).state().mode {        Mode::Normal => normal_motion(motion, operator, times, cx),        Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_motion(motion, times, cx),        Mode::Insert => {            // Shouldn't execute a motion in insert mode. Ignoring        }    }    Vim::update(cx, |vim, cx| vim.clear_operator(cx));} fn repeat_motion(backwards: bool, cx: &mut WindowContext) {    let find = match Vim::read(cx).workspace_state.last_find.clone() {        Some(Motion::FindForward { before, text }) => {            if backwards {                Motion::FindBackward {                    after: before,                    text,                }            } else {                Motion::FindForward { before, text }            }        }         Some(Motion::FindBackward { after, text }) => {            if backwards {                Motion::FindForward {                    before: after,                    text,                }            } else {                Motion::FindBackward { after, text }            }        }        _ => return,    };     motion(find, cx)} // Motion handling is specified here:// https://github.com/vim/vim/blob/master/runtime/doc/motion.txtimpl Motion {    pub fn linewise(&self) -> bool {        use Motion::*;        match self {            Down { .. }            | Up { .. }            | StartOfDocument            | EndOfDocument            | CurrentLine            | NextLineStart            | StartOfParagraph            | EndOfParagraph => true,            EndOfLine { .. }            | NextWordEnd { .. }            | Matching            | FindForward { .. }            | Left            | Backspace            | Right            | StartOfLine { .. }            | NextWordStart { .. }            | PreviousWordStart { .. }            | FirstNonWhitespace { .. }            | FindBackward { .. } => false,        }    }     pub fn infallible(&self) -> bool {        use Motion::*;        match self {            StartOfDocument | EndOfDocument | CurrentLine => true,            Down { .. }            | Up { .. }            | EndOfLine { .. }            | NextWordEnd { .. }            | Matching            | FindForward { .. }            | Left            | Backspace            | Right            | StartOfLine { .. }            | StartOfParagraph            | EndOfParagraph            | NextWordStart { .. }            | PreviousWordStart { .. }            | FirstNonWhitespace { .. }            | FindBackward { .. }            | NextLineStart => false,        }    }     pub fn inclusive(&self) -> bool {        use Motion::*;        match self {            Down { .. }            | Up { .. }            | StartOfDocument            | EndOfDocument            | CurrentLine            | EndOfLine { .. }            | NextWordEnd { .. }            | Matching            | FindForward { .. }            | NextLineStart => true,            Left            | Backspace            | Right            | StartOfLine { .. }            | StartOfParagraph            | EndOfParagraph            | NextWordStart { .. }            | PreviousWordStart { .. }            | FirstNonWhitespace { .. }            | FindBackward { .. } => false,        }    }     pub fn move_point(        &self,        map: &DisplaySnapshot,        point: DisplayPoint,        goal: SelectionGoal,        maybe_times: Option<usize>,    ) -> Option<(DisplayPoint, SelectionGoal)> {        let times = maybe_times.unwrap_or(1);        use Motion::*;        let infallible = self.infallible();        let (new_point, goal) = match self {            Left => (left(map, point, times), SelectionGoal::None),            Backspace => (backspace(map, point, times), SelectionGoal::None),            Down {                display_lines: false,            } => down(map, point, goal, times),            Down {                display_lines: true,            } => down_display(map, point, goal, times),            Up {                display_lines: false,            } => up(map, point, goal, times),            Up {                display_lines: true,            } => up_display(map, point, goal, times),            Right => (right(map, point, times), SelectionGoal::None),            NextWordStart { ignore_punctuation } => (                next_word_start(map, point, *ignore_punctuation, times),                SelectionGoal::None,            ),            NextWordEnd { ignore_punctuation } => (                next_word_end(map, point, *ignore_punctuation, times),                SelectionGoal::None,            ),            PreviousWordStart { ignore_punctuation } => (                previous_word_start(map, point, *ignore_punctuation, times),                SelectionGoal::None,            ),            FirstNonWhitespace { display_lines } => (                first_non_whitespace(map, *display_lines, point),                SelectionGoal::None,            ),            StartOfLine { display_lines } => (                start_of_line(map, *display_lines, point),                SelectionGoal::None,            ),            EndOfLine { display_lines } => {                (end_of_line(map, *display_lines, point), SelectionGoal::None)            }            StartOfParagraph => (                movement::start_of_paragraph(map, point, times),                SelectionGoal::None,            ),            EndOfParagraph => (                map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),                SelectionGoal::None,            ),            CurrentLine => (end_of_line(map, false, point), SelectionGoal::None),            StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),            EndOfDocument => (                end_of_document(map, point, maybe_times),                SelectionGoal::None,            ),            Matching => (matching(map, point), SelectionGoal::None),            FindForward { before, text } => (                find_forward(map, point, *before, text.clone(), times),                SelectionGoal::None,            ),            FindBackward { after, text } => (                find_backward(map, point, *after, text.clone(), times),                SelectionGoal::None,            ),            NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),        };         (new_point != point || infallible).then_some((new_point, goal))    }     // Expands a selection using self motion for an operator    pub fn expand_selection(        &self,        map: &DisplaySnapshot,        selection: &mut Selection<DisplayPoint>,        times: Option<usize>,        expand_to_surrounding_newline: bool,    ) -> bool {        if let Some((new_head, goal)) =            self.move_point(map, selection.head(), selection.goal, times)        {            selection.set_head(new_head, goal);             if self.linewise() {                selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;                 if expand_to_surrounding_newline {                    if selection.end.row() < map.max_point().row() {                        *selection.end.row_mut() += 1;                        *selection.end.column_mut() = 0;                        selection.end = map.clip_point(selection.end, Bias::Right);                        // Don't reset the end here                        return true;                    } else if selection.start.row() > 0 {                        *selection.start.row_mut() -= 1;                        *selection.start.column_mut() = map.line_len(selection.start.row());                        selection.start = map.clip_point(selection.start, Bias::Left);                    }                }                 (_, selection.end) = map.next_line_boundary(selection.end.to_point(map));            } else {                // If the motion is exclusive and the end of the motion is in column 1, the                // end of the motion is moved to the end of the previous line and the motion                // becomes inclusive. Example: "}" moves to the first line after a paragraph,                // but "d}" will not include that line.                let mut inclusive = self.inclusive();                if !inclusive                    && self != &Motion::Backspace                    && selection.end.row() > selection.start.row()                    && selection.end.column() == 0                {                    inclusive = true;                    *selection.end.row_mut() -= 1;                    *selection.end.column_mut() = 0;                    selection.end = map.clip_point(                        map.next_line_boundary(selection.end.to_point(map)).1,                        Bias::Left,                    );                }                 if inclusive && selection.end.column() < map.line_len(selection.end.row()) {                    *selection.end.column_mut() += 1;                }            }            true        } else {            false        }    }} fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {    for _ in 0..times {        point = movement::saturating_left(map, point);        if point.column() == 0 {            break;        }    }    point} fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {    for _ in 0..times {        point = movement::left(map, point);    }    point} fn down(    map: &DisplaySnapshot,

Lines 1–500 of 994.