1670 lines · 56.3 KB · Rust
RawDownload
mod boundary;mod duplicate;mod object;mod paste;mod select; use editor::display_map::DisplaySnapshot;use editor::{    DisplayPoint, Editor, EditorSettings, HideMouseCursorOrigin, MultiBufferOffset,    SelectionEffects, ToOffset, ToPoint, movement,};use gpui::actions;use gpui::{Context, Window};use language::{CharClassifier, CharKind, Point};use search::{BufferSearchBar, SearchOptions};use settings::Settings;use text::{Bias, SelectionGoal};use workspace::searchable::FilteredSearchRange;use workspace::searchable::{self, Direction}; use crate::motion::{self, MotionKind};use crate::state::SearchState;use crate::{    Vim,    motion::{Motion, right},    state::Mode,}; actions!(    vim,    [        /// Yanks the current selection or character if no selection.        HelixYank,        /// Inserts at the beginning of the selection.        HelixInsert,        /// Appends at the end of the selection.        HelixAppend,        /// Goes to the location of the last modification.        HelixGotoLastModification,        /// Select entire line or multiple lines, extending downwards.        HelixSelectLine,        /// Select all matches of a given pattern within the current selection.        HelixSelectRegex,        /// Removes all but the one selection that was created last.        /// `Newest` can eventually be `Primary`.        HelixKeepNewestSelection,        /// Copies all selections below.        HelixDuplicateBelow,        /// Copies all selections above.        HelixDuplicateAbove,        /// Delete the selection and enter edit mode.        HelixSubstitute,        /// Delete the selection and enter edit mode, without yanking the selection.        HelixSubstituteNoYank,        /// Delete the selection and enter edit mode.        HelixSelectNext,        /// Delete the selection and enter edit mode, without yanking the selection.        HelixSelectPrevious,    ]); pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {    Vim::action(editor, cx, Vim::helix_select_lines);    Vim::action(editor, cx, Vim::helix_insert);    Vim::action(editor, cx, Vim::helix_append);    Vim::action(editor, cx, Vim::helix_yank);    Vim::action(editor, cx, Vim::helix_goto_last_modification);    Vim::action(editor, cx, Vim::helix_paste);    Vim::action(editor, cx, Vim::helix_select_regex);    Vim::action(editor, cx, Vim::helix_keep_newest_selection);    Vim::action(editor, cx, |vim, _: &HelixDuplicateBelow, window, cx| {        let times = Vim::take_count(cx);        vim.helix_duplicate_selections_below(times, window, cx);    });    Vim::action(editor, cx, |vim, _: &HelixDuplicateAbove, window, cx| {        let times = Vim::take_count(cx);        vim.helix_duplicate_selections_above(times, window, cx);    });    Vim::action(editor, cx, Vim::helix_substitute);    Vim::action(editor, cx, Vim::helix_substitute_no_yank);    Vim::action(editor, cx, Vim::helix_select_next);    Vim::action(editor, cx, Vim::helix_select_previous);} impl Vim {    pub fn helix_normal_motion(        &mut self,        motion: Motion,        times: Option<usize>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.helix_move_cursor(motion, times, window, cx);    }     pub fn helix_select_motion(        &mut self,        motion: Motion,        times: Option<usize>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.update_editor(cx, |_, editor, cx| {            let text_layout_details = editor.text_layout_details(window);            editor.change_selections(Default::default(), window, cx, |s| {                if let Motion::ZedSearchResult { new_selections, .. } = &motion {                    s.select_anchor_ranges(new_selections.clone());                    return;                };                 s.move_with(|map, selection| {                    let was_reversed = selection.reversed;                    let mut current_head = selection.head();                     // our motions assume the current character is after the cursor,                    // but in (forward) visual mode the current character is just                    // before the end of the selection.                     // If the file ends with a newline (which is common) we don't do this.                    // so that if you go to the end of such a file you can use "up" to go                    // to the previous line and have it work somewhat as expected.                    if !selection.reversed                        && !selection.is_empty()                        && !(selection.end.column() == 0 && selection.end == map.max_point())                    {                        current_head = movement::left(map, selection.end)                    }                     let (new_head, goal) = match motion {                        // Going to next word start is special cased                        // since Vim differs from Helix in that motion                        // Vim: `w` goes to the first character of a word                        // Helix: `w` goes to the character before a word                        Motion::NextWordStart { ignore_punctuation } => {                            let mut head = movement::right(map, current_head);                            let classifier =                                map.buffer_snapshot().char_classifier_at(head.to_point(map));                            for _ in 0..times.unwrap_or(1) {                                let (_, new_head) =                                    movement::find_boundary_trail(map, head, |left, right| {                                        Self::is_boundary_right(ignore_punctuation)(                                            left,                                            right,                                            &classifier,                                        )                                    });                                head = new_head;                            }                            head = movement::left(map, head);                            (head, SelectionGoal::None)                        }                        _ => motion                            .move_point(                                map,                                current_head,                                selection.goal,                                times,                                &text_layout_details,                            )                            .unwrap_or((current_head, selection.goal)),                    };                     selection.set_head(new_head, goal);                     // ensure the current character is included in the selection.                    if !selection.reversed {                        let next_point = movement::right(map, selection.end);                         if !(next_point.column() == 0 && next_point == map.max_point()) {                            selection.end = next_point;                        }                    }                     // vim always ensures the anchor character stays selected.                    // if our selection has reversed, we need to move the opposite end                    // to ensure the anchor is still selected.                    if was_reversed && !selection.reversed {                        selection.start = movement::left(map, selection.start);                    } else if !was_reversed && selection.reversed {                        selection.end = movement::right(map, selection.end);                    }                })            });        });    }     /// Updates all selections based on where the cursors are.    fn helix_new_selections(        &mut self,        window: &mut Window,        cx: &mut Context<Self>,        mut change: impl FnMut(            // the start of the cursor            DisplayPoint,            &DisplaySnapshot,        ) -> Option<(DisplayPoint, DisplayPoint)>,    ) {        self.update_editor(cx, |_, editor, cx| {            editor.change_selections(Default::default(), window, cx, |s| {                s.move_with(|map, selection| {                    let cursor_start = if selection.reversed || selection.is_empty() {                        selection.head()                    } else {                        movement::left(map, selection.head())                    };                    let Some((head, tail)) = change(cursor_start, map) else {                        return;                    };                     selection.set_head_tail(head, tail, SelectionGoal::None);                });            });        });    }     fn helix_find_range_forward(        &mut self,        times: Option<usize>,        window: &mut Window,        cx: &mut Context<Self>,        mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,    ) {        let times = times.unwrap_or(1);        self.helix_new_selections(window, cx, |cursor, map| {            let mut head = movement::right(map, cursor);            let mut tail = cursor;            let classifier = map.buffer_snapshot().char_classifier_at(head.to_point(map));            if head == map.max_point() {                return None;            }            for _ in 0..times {                let (maybe_next_tail, next_head) =                    movement::find_boundary_trail(map, head, |left, right| {                        is_boundary(left, right, &classifier)                    });                 if next_head == head && maybe_next_tail.unwrap_or(next_head) == tail {                    break;                }                 head = next_head;                if let Some(next_tail) = maybe_next_tail {                    tail = next_tail;                }            }            Some((head, tail))        });    }     fn helix_find_range_backward(        &mut self,        times: Option<usize>,        window: &mut Window,        cx: &mut Context<Self>,        mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,    ) {        let times = times.unwrap_or(1);        self.helix_new_selections(window, cx, |cursor, map| {            let mut head = cursor;            // The original cursor was one character wide,            // but the search starts from the left side of it,            // so to include that space the selection must end one character to the right.            let mut tail = movement::right(map, cursor);            let classifier = map.buffer_snapshot().char_classifier_at(head.to_point(map));            if head == DisplayPoint::zero() {                return None;            }            for _ in 0..times {                let (maybe_next_tail, next_head) =                    movement::find_preceding_boundary_trail(map, head, |left, right| {                        is_boundary(left, right, &classifier)                    });                 if next_head == head && maybe_next_tail.unwrap_or(next_head) == tail {                    break;                }                 head = next_head;                if let Some(next_tail) = maybe_next_tail {                    tail = next_tail;                }            }            Some((head, tail))        });    }     pub fn helix_move_and_collapse(        &mut self,        motion: Motion,        times: Option<usize>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.update_editor(cx, |_, editor, cx| {            let text_layout_details = editor.text_layout_details(window);            editor.change_selections(Default::default(), window, cx, |s| {                s.move_with(|map, selection| {                    let goal = selection.goal;                    let cursor = if selection.is_empty() || selection.reversed {                        selection.head()                    } else {                        movement::left(map, selection.head())                    };                     let (point, goal) = motion                        .move_point(map, cursor, selection.goal, times, &text_layout_details)                        .unwrap_or((cursor, goal));                     selection.collapse_to(point, goal)                })            });        });    }     fn is_boundary_right(        ignore_punctuation: bool,    ) -> impl FnMut(char, char, &CharClassifier) -> bool {        move |left, right, classifier| {            let left_kind = classifier.kind_with(left, ignore_punctuation);            let right_kind = classifier.kind_with(right, ignore_punctuation);            let at_newline = (left == '\n') ^ (right == '\n');             (left_kind != right_kind && right_kind != CharKind::Whitespace) || at_newline        }    }     fn is_boundary_left(        ignore_punctuation: bool,    ) -> impl FnMut(char, char, &CharClassifier) -> bool {        move |left, right, classifier| {            let left_kind = classifier.kind_with(left, ignore_punctuation);            let right_kind = classifier.kind_with(right, ignore_punctuation);            let at_newline = (left == '\n') ^ (right == '\n');             (left_kind != right_kind && left_kind != CharKind::Whitespace) || at_newline        }    }     pub fn helix_move_cursor(        &mut self,        motion: Motion,        times: Option<usize>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        match motion {            Motion::NextWordStart { ignore_punctuation } => self.helix_find_range_forward(                times,                window,                cx,                Self::is_boundary_right(ignore_punctuation),            ),            Motion::NextWordEnd { ignore_punctuation } => self.helix_find_range_forward(                times,                window,                cx,                Self::is_boundary_left(ignore_punctuation),            ),            Motion::PreviousWordStart { ignore_punctuation } => self.helix_find_range_backward(                times,                window,                cx,                Self::is_boundary_left(ignore_punctuation),            ),            Motion::PreviousWordEnd { ignore_punctuation } => self.helix_find_range_backward(                times,                window,                cx,                Self::is_boundary_right(ignore_punctuation),            ),            Motion::EndOfLine { .. } => {                // In Helix mode, EndOfLine should position cursor ON the last character,                // not after it. We therefore need special handling for it.                self.update_editor(cx, |_, editor, cx| {                    let text_layout_details = editor.text_layout_details(window);                    editor.change_selections(Default::default(), window, cx, |s| {                        s.move_with(|map, selection| {                            let goal = selection.goal;                            let cursor = if selection.is_empty() || selection.reversed {                                selection.head()                            } else {                                movement::left(map, selection.head())                            };                             let (point, _goal) = motion                                .move_point(map, cursor, goal, times, &text_layout_details)                                .unwrap_or((cursor, goal));                             // Move left by one character to position on the last character                            let adjusted_point = movement::saturating_left(map, point);                            selection.collapse_to(adjusted_point, SelectionGoal::None)                        })                    });                });            }            Motion::FindForward {                before,                char,                mode,                smartcase,            } => {                self.helix_new_selections(window, cx, |cursor, map| {                    let start = cursor;                    let mut last_boundary = start;                    for _ in 0..times.unwrap_or(1) {                        last_boundary = movement::find_boundary(                            map,                            movement::right(map, last_boundary),                            mode,                            |left, right| {                                let current_char = if before { right } else { left };                                motion::is_character_match(char, current_char, smartcase)                            },                        );                    }                    Some((last_boundary, start))                });            }            Motion::FindBackward {                after,                char,                mode,                smartcase,            } => {                self.helix_new_selections(window, cx, |cursor, map| {                    let start = cursor;                    let mut last_boundary = start;                    for _ in 0..times.unwrap_or(1) {                        last_boundary = movement::find_preceding_boundary_display_point(                            map,                            last_boundary,                            mode,                            |left, right| {                                let current_char = if after { left } else { right };                                motion::is_character_match(char, current_char, smartcase)                            },                        );                    }                    // The original cursor was one character wide,                    // but the search started from the left side of it,                    // so to include that space the selection must end one character to the right.                    Some((last_boundary, movement::right(map, start)))                });            }            _ => self.helix_move_and_collapse(motion, times, window, cx),        }    }     pub fn helix_yank(&mut self, _: &HelixYank, window: &mut Window, cx: &mut Context<Self>) {        self.update_editor(cx, |vim, editor, cx| {            let has_selection = editor                .selections                .all_adjusted(&editor.display_snapshot(cx))                .iter()                .any(|selection| !selection.is_empty());             if !has_selection {                // If no selection, expand to current character (like 'v' does)                editor.change_selections(Default::default(), window, cx, |s| {                    s.move_with(|map, selection| {                        let head = selection.head();                        let new_head = movement::saturating_right(map, head);                        selection.set_tail(head, SelectionGoal::None);                        selection.set_head(new_head, SelectionGoal::None);                    });                });                vim.yank_selections_content(                    editor,                    crate::motion::MotionKind::Exclusive,                    window,                    cx,                );                editor.change_selections(Default::default(), window, cx, |s| {                    s.move_with(|_map, selection| {                        selection.collapse_to(selection.start, SelectionGoal::None);                    });                });            } else {                // Yank the selection(s)                vim.yank_selections_content(                    editor,                    crate::motion::MotionKind::Exclusive,                    window,                    cx,                );            }        });         // Drop back to normal mode after yanking        self.switch_mode(Mode::HelixNormal, true, window, cx);    }     fn helix_insert(&mut self, _: &HelixInsert, window: &mut Window, cx: &mut Context<Self>) {        self.start_recording(cx);        self.update_editor(cx, |_, editor, cx| {            editor.change_selections(Default::default(), window, cx, |s| {                s.move_with(|_map, selection| {                    // In helix normal mode, move cursor to start of selection and collapse                    if !selection.is_empty() {                        selection.collapse_to(selection.start, SelectionGoal::None);

Lines 1–500 of 1670.