1423 lines · 47.0 KB · Rust
RawDownload
mod boundary;mod duplicate;mod object;mod paste;mod select; use editor::display_map::DisplaySnapshot;use editor::{    DisplayPoint, Editor, EditorSettings, HideMouseCursorOrigin, 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;use workspace::searchable::FilteredSearchRange; 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,    ]); 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);} 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| {                s.move_with(|map, selection| {                    let current_head = selection.head();                     let Some((new_head, goal)) = motion.move_point(                        map,                        current_head,                        selection.goal,                        times,                        &text_layout_details,                    ) else {                        return;                    };                     selection.set_head(new_head, goal);                })            });        });    }     /// 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)                })            });        });    }     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, |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                })            }            Motion::NextWordEnd { ignore_punctuation } => {                self.helix_find_range_forward(times, window, cx, |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                })            }            Motion::PreviousWordStart { ignore_punctuation } => {                self.helix_find_range_backward(times, window, cx, |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                })            }            Motion::PreviousWordEnd { ignore_punctuation } => {                self.helix_find_range_backward(times, window, cx, |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                })            }            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);                    }                });            });        });        self.switch_mode(Mode::Insert, false, window, cx);    }     fn helix_select_regex(        &mut self,        _: &HelixSelectRegex,        window: &mut Window,        cx: &mut Context<Self>,    ) {        Vim::take_forced_motion(cx);        let Some(pane) = self.pane(window, cx) else {            return;        };        let prior_selections = self.editor_selections(window, cx);        pane.update(cx, |pane, cx| {            if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {                search_bar.update(cx, |search_bar, cx| {                    if !search_bar.show(window, cx) {                        return;                    }                     search_bar.select_query(window, cx);                    cx.focus_self(window);                     search_bar.set_replacement(None, cx);                    let mut options = SearchOptions::NONE;                    options |= SearchOptions::REGEX;                    if EditorSettings::get_global(cx).search.case_sensitive {                        options |= SearchOptions::CASE_SENSITIVE;                    }                    search_bar.set_search_options(options, cx);                    if let Some(search) = search_bar.set_search_within_selection(                        Some(FilteredSearchRange::Selection),                        window,                        cx,                    ) {                        cx.spawn_in(window, async move |search_bar, cx| {                            if search.await.is_ok() {                                search_bar.update_in(cx, |search_bar, window, cx| {                                    search_bar.activate_current_match(window, cx)                                })                            } else {                                Ok(())                            }                        })                        .detach_and_log_err(cx);                    }                    self.search = SearchState {                        direction: searchable::Direction::Next,                        count: 1,                        prior_selections,                        prior_operator: self.operator_stack.last().cloned(),                        prior_mode: self.mode,                        is_helix_regex_search: true,                    }                });            }        });        self.start_recording(cx);    }     fn helix_append(&mut self, _: &HelixAppend, window: &mut Window, cx: &mut Context<Self>) {        self.start_recording(cx);        self.switch_mode(Mode::Insert, false, window, cx);        self.update_editor(cx, |_, editor, cx| {            editor.change_selections(Default::default(), window, cx, |s| {                s.move_with(|map, selection| {                    let point = if selection.is_empty() {                        right(map, selection.head(), 1)                    } else {                        selection.end                    };                    selection.collapse_to(point, SelectionGoal::None);                });            });        });    }     pub fn helix_replace(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {        self.update_editor(cx, |_, editor, cx| {            editor.transact(window, cx, |editor, window, cx| {                let display_map = editor.display_snapshot(cx);                let selections = editor.selections.all_display(&display_map);                 // Store selection info for positioning after edit                let selection_info: Vec<_> = selections                    .iter()                    .map(|selection| {                        let range = selection.range();                        let start_offset = range.start.to_offset(&display_map, Bias::Left);                        let end_offset = range.end.to_offset(&display_map, Bias::Left);                        let was_empty = range.is_empty();                        let was_reversed = selection.reversed;                        (                            display_map.buffer_snapshot().anchor_before(start_offset),                            end_offset - start_offset,                            was_empty,                            was_reversed,                        )                    })                    .collect();

Lines 1–500 of 1423.