1037 lines · 40.0 KB · Rust
RawDownload
use vim::vim::{    BracketChar, CharacterMotion, Direction, FindCharMotion, FirstNonWhitespaceMotion,    InsertPosition, LineMotion, ModeTransition, MotionType, TextObjectType, VimHandler, VimMode,    VimMotion, VimOperand, VimOperator, VimTextObject, WordMotion,};use warp_editor::content::buffer::{    AutoScrollBehavior, BufferEditAction, EditOrigin, SelectionOffsets, VimInsertPoint,};use warp_editor::model::{CoreEditorModel, PlainTextEditorModel};use warp_editor::render::model::AutoScrollMode;use warp_editor::selection::{TextDirection, TextUnit};use warpui::units::IntoPixels;use warpui::{SingletonEntity, ViewContext}; use super::{CodeEditorEvent, CodeEditorView};use crate::code::editor::find::view::Event as FindViewEvent;use crate::code::editor::model::{CaseTransform, CodeEditorModel, LineBound};use crate::view_components::find::FindDirection;use crate::vim_registers::{RegisterContent, VimRegisters}; impl VimHandler for CodeEditorView {    fn insert_char(&mut self, c: char, ctx: &mut ViewContext<Self>) {        self.user_insert(&c.to_string(), ctx);    }     fn keyword_prg(&mut self, _ctx: &mut ViewContext<Self>) {        // no-op    }     fn navigate_char(        &mut self,        count: u32,        character_motion: &CharacterMotion,        ctx: &mut ViewContext<Self>,    ) {        self.model.update(ctx, |model, ctx| match character_motion {            CharacterMotion::Right => {                model.vim_move_horizontal_by_offset(count, &Direction::Forward, false, true, ctx);            }            CharacterMotion::Up => {                model.vim_move_vertical_by_offset(count, TextDirection::Backwards, false, ctx);            }            CharacterMotion::Down => {                model.vim_move_vertical_by_offset(count, TextDirection::Forwards, false, ctx);            }            CharacterMotion::Left => {                model.vim_move_horizontal_by_offset(count, &Direction::Backward, false, true, ctx);            }            CharacterMotion::WrappingLeft => {                model.vim_move_horizontal_by_offset(count, &Direction::Backward, false, false, ctx);            }            CharacterMotion::WrappingRight => {                model.vim_move_horizontal_by_offset(count, &Direction::Forward, false, false, ctx);            }        });    }     fn navigate_word(&mut self, count: u32, word_motion: &WordMotion, ctx: &mut ViewContext<Self>) {        let WordMotion {            direction,            bound,            word_type,        } = word_motion;         self.model.update(ctx, |model, ctx| {            model.vim_navigate_word(*direction, *bound, *word_type, count, ctx);        });    }     fn navigate_line(&mut self, line_count: u32, motion: &LineMotion, ctx: &mut ViewContext<Self>) {        self.model.update(ctx, |model, ctx| {            match motion {                LineMotion::Start => model.vim_move_to_line_bound(LineBound::Start, false, ctx),                LineMotion::FirstNonWhitespace => model.vim_move_to_first_nonwhitespace(false, ctx),                LineMotion::End => {                    // Only moving to the end of the line ($) uses number-repeat (the line-count var)                    model.vim_move_vertical_by_offset(                        line_count.saturating_sub(1),                        TextDirection::Forwards,                        false,                        ctx,                    );                    model.vim_move_to_line_bound(LineBound::End, false, ctx);                }            }        })    }     fn first_nonwhitespace_motion(        &mut self,        count: u32,        motion: &FirstNonWhitespaceMotion,        ctx: &mut ViewContext<Self>,    ) {        self.model.update(ctx, |model, ctx| {            match motion {                FirstNonWhitespaceMotion::Up => {                    model.vim_move_vertical_by_offset(count, TextDirection::Backwards, false, ctx);                }                FirstNonWhitespaceMotion::Down => {                    model.vim_move_vertical_by_offset(count, TextDirection::Forwards, false, ctx)                }                FirstNonWhitespaceMotion::DownMinusOne => model.vim_move_vertical_by_offset(                    count - 1,                    TextDirection::Forwards,                    false,                    ctx,                ),            }             model.vim_move_to_first_nonwhitespace(false, ctx);        })    }     fn find_char(        &mut self,        occurrence_count: u32,        find_char_motion: &FindCharMotion,        ctx: &mut ViewContext<Self>,    ) {        self.model.update(ctx, |model, ctx| {            model.vim_find_char(                false, /* keep_selection */                occurrence_count,                find_char_motion,                ctx,            );        });    }     fn navigate_paragraph(        &mut self,        count: u32,        direction: &Direction,        ctx: &mut ViewContext<Self>,    ) {        self.model.update(ctx, |model, ctx| {            model.vim_move_by_paragraph(count, direction, false, ctx);        });    }     fn operation(        &mut self,        operator: &VimOperator,        operand_count: u32,        operand: &VimOperand,        register_name: char,        replacement_text: &str,        ctx: &mut ViewContext<Self>,    ) {        // Selection logic is almost the same for all operators, so capture that in a closure first.        let selection_change =            |model: &mut CodeEditorModel, ctx: &mut warpui::ModelContext<CodeEditorModel>| {                match operand {                    VimOperand::Motion {                        motion,                        motion_type,                    } => {                        match motion {                            VimMotion::Character(char_motion) => {                                model.vim_select_for_char_motion(                                    char_motion,                                    motion_type,                                    operator,                                    operand_count,                                    ctx,                                );                            }                            VimMotion::Word(word_motion) => {                                model.vim_select_for_word_motion(                                    word_motion,                                    operand_count,                                    motion_type,                                    operator,                                    ctx,                                );                            }                            VimMotion::Line(line_motion) => {                                model.vim_select_for_line_motion(                                    line_motion,                                    operand_count,                                    motion_type,                                    operator,                                    ctx,                                );                            }                            VimMotion::FirstNonWhitespace(nonws_motion) => {                                model.vim_select_for_first_nonwhitespace_motion(                                    nonws_motion,                                    motion_type,                                    operator,                                    operand_count,                                    ctx,                                );                            }                            VimMotion::Paragraph(direction) => {                                model.vim_move_by_paragraph(operand_count, direction, true, ctx);                                if *motion_type == MotionType::Linewise {                                    model.vim_extend_selection_linewise(                                        operator.includes_trailing_newline(),                                        *operator == VimOperator::Delete,                                        ctx,                                    );                                }                            }                            VimMotion::JumpToLastLine => {                                model.vim_select_to_buffer_end(ctx);                                if *motion_type == MotionType::Linewise {                                    model.vim_extend_selection_linewise(                                        operator.includes_trailing_newline(),                                        *operator == VimOperator::Delete,                                        ctx,                                    );                                }                            }                            VimMotion::JumpToFirstLine => {                                model.vim_select_to_buffer_start(ctx);                                if *motion_type == MotionType::Linewise {                                    model.vim_extend_selection_linewise(                                        operator.includes_trailing_newline(),                                        *operator == VimOperator::Delete,                                        ctx,                                    );                                }                            }                            VimMotion::FindChar(m) => {                                // Extend selection to the found character according to the motion                                model.vim_find_char(                                    true, /* keep_selection */                                    operand_count,                                    m,                                    ctx,                                );                            }                            VimMotion::JumpToLine(line_number) => {                                model.vim_select_to_line(*line_number, motion_type, operator, ctx);                            }                            VimMotion::JumpToMatchingBracket => {                                model.vim_select_for_matching_bracket(ctx);                            }                            VimMotion::JumpToUnmatchedBracket(bracket) => {                                model.vim_jump_to_unmatched_bracket(bracket, true, ctx);                            }                        }                    }                    VimOperand::Line => {                        // Extend selection down by count-1 lines                        if operand_count > 1 {                            model.vim_move_vertical_by_offset(                                operand_count - 1,                                TextDirection::Forwards,                                true,                                ctx,                            );                        }                         let include_newline = operator.includes_trailing_newline()                            && *operator != VimOperator::ToggleComment;                        model.vim_extend_selection_linewise(                            include_newline,                            *operator == VimOperator::Delete,                            ctx,                        );                    }                    VimOperand::TextObject(text_object) => {                        model.vim_select_text_object(text_object, Some(operator), ctx);                    }                }            };         let motion_type = match operand {            VimOperand::Motion { motion_type, .. } => *motion_type,            VimOperand::TextObject(text_object) => match text_object {                VimTextObject {                    object_type: TextObjectType::Paragraph,                    ..                } => MotionType::Linewise,                _ => MotionType::Charwise,            },            VimOperand::Line => MotionType::Linewise,        };         match operator {            VimOperator::Delete | VimOperator::Change => {                self.model.update(ctx, |model, ctx| {                    selection_change(model, ctx);                     // Copy selection to vim register before modifying                    let buffer = model.content().as_ref(ctx);                    let selection_model = model.buffer_selection_model().clone();                    let selected_text = buffer                        .selected_text_as_plain_text(selection_model, ctx)                        .into_string();                    let register_text =                        if selected_text.is_empty() && motion_type == MotionType::Linewise {                            Some("\n".to_owned())                        } else {                            (!selected_text.is_empty()).then(|| selected_text.clone())                        };                    if let Some(register_text) = register_text {                        VimRegisters::handle(ctx).update(ctx, |registers, ctx| {                            registers.write_to_register(                                register_name,                                register_text,                                motion_type,                                ctx,                            );                        });                    }                     if !selected_text.is_empty() {                        if *operator == VimOperator::Change && motion_type == MotionType::Linewise {                            // Use smart indent to position the cursor when changing the entire                            // line.                            model.vim_change_line_with_smart_indent(ctx);                        } else {                            model.delete(TextDirection::Forwards, TextUnit::Character, false, ctx);                            // Insert replacement text if provided                            if *operator == VimOperator::Change && !replacement_text.is_empty() {                                model.insert(replacement_text, EditOrigin::UserInitiated, ctx);                            }                            if motion_type == MotionType::Linewise {                                model.vim_move_to_line_bound(LineBound::Start, false, ctx);                            }                        }                    }                });            }            VimOperator::Yank => {                self.model.update(ctx, |model, ctx| {                    // Store existing selections to restore after yank                    let existing_selections = model.selections(ctx).clone();                    selection_change(model, ctx);                     // Copy selection to vim register                    let buffer = model.content().as_ref(ctx);                    let selection_model = model.buffer_selection_model().clone();                    let selected_text = buffer                        .selected_text_as_plain_text(selection_model, ctx)                        .into_string();                    let register_text =                        if selected_text.is_empty() && motion_type == MotionType::Linewise {                            Some("\n".to_owned())                        } else {                            (!selected_text.is_empty()).then_some(selected_text)                        };                    if let Some(register_text) = register_text {                        VimRegisters::handle(ctx).update(ctx, |registers, ctx| {                            registers.write_to_register(                                register_name,                                register_text,                                motion_type,                                ctx,                            );                        });                    }                     match operand {                        VimOperand::TextObject(_) => {                            // For text objects, move to the start (min) of the selected range                            let starts = model                                .buffer_selection_model()                                .as_ref(ctx)                                .selection_offsets()                                .mapped(|selection| {                                    let start = selection.head.min(selection.tail);                                    SelectionOffsets {                                        head: start,                                        tail: start,                                    }                                });                            model.vim_set_selections(starts, AutoScrollBehavior::None, ctx);                        }                        _ => {                            model.vim_set_selections(                                existing_selections,                                AutoScrollBehavior::None,                                ctx,                            );                        }                    }                });            }            VimOperator::ToggleCase => {                self.model.update(ctx, |model, ctx| {                    model.apply_case_transformation_with_selection_change(                        selection_change,                        CaseTransform::Toggle,                        ctx,                    );                });            }            VimOperator::Uppercase => {                self.model.update(ctx, |model, ctx| {                    model.apply_case_transformation_with_selection_change(                        selection_change,                        CaseTransform::Uppercase,                        ctx,                    );                });            }            VimOperator::Lowercase => {                self.model.update(ctx, |model, ctx| {                    model.apply_case_transformation_with_selection_change(                        selection_change,                        CaseTransform::Lowercase,                        ctx,                    );                });            }            VimOperator::ToggleComment => {                self.model.update(ctx, |model, ctx| {                    let existing_selections = model.selections(ctx).clone();                    selection_change(model, ctx);                    model.toggle_comments(ctx);                     if motion_type == MotionType::Linewise {                        model.vim_move_to_first_nonwhitespace(false, ctx);                    } else {                        model.vim_set_selections(                            existing_selections,                            AutoScrollBehavior::None,                            ctx,                        );                    }                });            }            VimOperator::Indent | VimOperator::Dedent => {                self.model.update(ctx, |model, ctx| {                    selection_change(model, ctx);                    let shift = *operator == VimOperator::Dedent;                    model.indent(shift, ctx);                    model.vim_move_to_first_nonwhitespace(false, ctx);                });            }        }    }     fn replace_char(        &mut self,        c: char,        char_count: u32,        advance: bool,        ctx: &mut ViewContext<Self>,    ) {        self.model.update(ctx, |model, ctx| {            if advance {                model.vim_replace_text(&c.to_string(), ctx);            } else {                model.replace_char(c, char_count, ctx);            }        });         // Explicit call to ctx.notify() in the case that we don't make any updates to the model        ctx.notify();    }     fn replace_text(        &mut self,        text: &str,        count: u32,        already_applied: bool,        ctx: &mut ViewContext<Self>,    ) {        let repeat_count = count.saturating_sub(u32::from(already_applied));        self.model.update(ctx, |model, ctx| {            if repeat_count > 0 {                model.vim_replace_text(&text.repeat(repeat_count as usize), ctx);            }            if !text.is_empty() {                model.vim_move_horizontal_by_offset(1, &Direction::Backward, false, true, ctx);            }        });        ctx.notify();    }     fn search(&mut self, direction: &Direction, ctx: &mut ViewContext<Self>) {        self.last_search_direction = *direction;        self.show_find_bar(ctx);    }     fn cycle_search(&mut self, direction: &Direction, ctx: &mut ViewContext<Self>) {        let Some(find_bar) = &self.find_bar else {            return;        };         if !self.searcher.as_ref(ctx).has_query() {            return;        }         if !find_bar.as_ref(ctx).is_open() {            find_bar.update(ctx, |find_bar, _| find_bar.set_open(true));        }         // Vim-like behavior:        // 'n' (Forward) repeats in the same direction        // 'N' (Backward) reverses the last direction        let effective_dir = match (direction, self.last_search_direction) {            (Direction::Forward, dir) => dir,            (Direction::Backward, Direction::Backward) => Direction::Forward,

Lines 1–500 of 1037.