1734 lines · 62.0 KB · Rust
RawDownload
//! Vim support for Zed. #[cfg(test)]mod test; mod change_list;mod command;mod digraph;mod helix;mod indent;mod insert;mod mode_indicator;mod motion;mod normal;mod object;mod replace;mod rewrap;mod state;mod surrounds;mod visual; use anyhow::Result;use collections::HashMap;use editor::{    movement::{self, FindRange},    Anchor, Bias, Editor, EditorEvent, EditorMode, EditorSettings, ToPoint,};use gpui::{    actions, impl_actions, Action, App, AppContext as _, Axis, Context, Entity, EventEmitter,    KeyContext, KeystrokeEvent, Render, Subscription, Task, WeakEntity, Window,};use insert::{NormalBefore, TemporaryNormal};use language::{CharKind, CursorShape, Point, Selection, SelectionGoal, TransactionId};pub use mode_indicator::ModeIndicator;use motion::Motion;use normal::search::SearchSubmit;use object::Object;use schemars::JsonSchema;use serde::Deserialize;use serde_derive::Serialize;use settings::{update_settings_file, Settings, SettingsSources, SettingsStore};use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};use std::{mem, ops::Range, sync::Arc};use surrounds::SurroundsType;use theme::ThemeSettings;use ui::{px, IntoElement, SharedString};use vim_mode_setting::VimModeSetting;use workspace::{self, Pane, Workspace}; use crate::state::ReplayableAction; /// Number is used to manage vim's count. Pushing a digit/// multiplies the current value by 10 and adds the digit.#[derive(Clone, Deserialize, JsonSchema, PartialEq)]struct Number(usize); #[derive(Clone, Deserialize, JsonSchema, PartialEq)]struct SelectRegister(String); #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushObject {    around: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushFindForward {    before: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushFindBackward {    after: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushSneak {    first_char: Option<char>,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushSneakBackward {    first_char: Option<char>,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushAddSurrounds {} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushChangeSurrounds {    target: Option<Object>,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushJump {    line: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushDigraph {    first_char: Option<char>,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct PushLiteral {    prefix: Option<String>,} actions!(    vim,    [        SwitchToNormalMode,        SwitchToInsertMode,        SwitchToReplaceMode,        SwitchToVisualMode,        SwitchToVisualLineMode,        SwitchToVisualBlockMode,        SwitchToHelixNormalMode,        ClearOperators,        ClearExchange,        Tab,        Enter,        InnerObject,        MaximizePane,        OpenDefaultKeymap,        ResetPaneSizes,        ResizePaneRight,        ResizePaneLeft,        ResizePaneUp,        ResizePaneDown,        PushChange,        PushDelete,        Exchange,        PushYank,        PushReplace,        PushDeleteSurrounds,        PushMark,        PushIndent,        PushOutdent,        PushAutoIndent,        PushRewrap,        PushShellCommand,        PushLowercase,        PushUppercase,        PushOppositeCase,        ToggleRegistersView,        PushRegister,        PushRecordRegister,        PushReplayRegister,        PushReplaceWithRegister,        PushToggleComments,    ]); // in the workspace namespace so it's not filtered out when vim is disabled.actions!(workspace, [ToggleVimMode,]); impl_actions!(    vim,    [        Number,        SelectRegister,        PushObject,        PushFindForward,        PushFindBackward,        PushSneak,        PushSneakBackward,        PushAddSurrounds,        PushChangeSurrounds,        PushJump,        PushDigraph,        PushLiteral    ]); /// Initializes the `vim` crate.pub fn init(cx: &mut App) {    vim_mode_setting::init(cx);    VimSettings::register(cx);    VimGlobals::register(cx);     cx.observe_new(Vim::register).detach();     cx.observe_new(|workspace: &mut Workspace, _, _| {        workspace.register_action(|workspace, _: &ToggleVimMode, _, cx| {            let fs = workspace.app_state().fs.clone();            let currently_enabled = Vim::enabled(cx);            update_settings_file::<VimModeSetting>(fs, cx, move |setting, _| {                *setting = Some(!currently_enabled)            })        });         workspace.register_action(|_, _: &OpenDefaultKeymap, _, cx| {            cx.emit(workspace::Event::OpenBundledFile {                text: settings::vim_keymap(),                title: "Default Vim Bindings",                language: "JSON",            });        });         workspace.register_action(|workspace, _: &ResetPaneSizes, _, cx| {            workspace.reset_pane_sizes(cx);        });         workspace.register_action(|workspace, _: &MaximizePane, window, cx| {            let pane = workspace.active_pane();            let Some(size) = workspace.bounding_box_for_pane(&pane) else {                return;            };             let theme = ThemeSettings::get_global(cx);            let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();             let desired_size = if let Some(count) = Vim::take_count(cx) {                height * count            } else {                px(10000.)            };            workspace.resize_pane(Axis::Vertical, desired_size - size.size.height, window, cx)        });         workspace.register_action(|workspace, _: &ResizePaneRight, window, cx| {            let count = Vim::take_count(cx).unwrap_or(1) as f32;            let theme = ThemeSettings::get_global(cx);            let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {                return;            };            let Ok(width) = window                .text_system()                .advance(font_id, theme.buffer_font_size(cx), 'm')            else {                return;            };            workspace.resize_pane(Axis::Horizontal, width.width * count, window, cx);        });         workspace.register_action(|workspace, _: &ResizePaneLeft, window, cx| {            let count = Vim::take_count(cx).unwrap_or(1) as f32;            let theme = ThemeSettings::get_global(cx);            let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {                return;            };            let Ok(width) = window                .text_system()                .advance(font_id, theme.buffer_font_size(cx), 'm')            else {                return;            };            workspace.resize_pane(Axis::Horizontal, -width.width * count, window, cx);        });         workspace.register_action(|workspace, _: &ResizePaneUp, window, cx| {            let count = Vim::take_count(cx).unwrap_or(1) as f32;            let theme = ThemeSettings::get_global(cx);            let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();            workspace.resize_pane(Axis::Vertical, height * count, window, cx);        });         workspace.register_action(|workspace, _: &ResizePaneDown, window, cx| {            let count = Vim::take_count(cx).unwrap_or(1) as f32;            let theme = ThemeSettings::get_global(cx);            let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();            workspace.resize_pane(Axis::Vertical, -height * count, window, cx);        });         workspace.register_action(|workspace, _: &SearchSubmit, window, cx| {            let vim = workspace                .focused_pane(window, cx)                .read(cx)                .active_item()                .and_then(|item| item.act_as::<Editor>(cx))                .and_then(|editor| editor.read(cx).addon::<VimAddon>().cloned());            let Some(vim) = vim else { return };            vim.entity.update(cx, |_, cx| {                cx.defer_in(window, |vim, window, cx| vim.search_submit(window, cx))            })        });    })    .detach();} #[derive(Clone)]pub(crate) struct VimAddon {    pub(crate) entity: Entity<Vim>,} impl editor::Addon for VimAddon {    fn extend_key_context(&self, key_context: &mut KeyContext, cx: &App) {        self.entity.read(cx).extend_key_context(key_context, cx)    }     fn to_any(&self) -> &dyn std::any::Any {        self    }} /// The state pertaining to Vim mode.pub(crate) struct Vim {    pub(crate) mode: Mode,    pub last_mode: Mode,    pub temp_mode: bool,    pub status_label: Option<SharedString>,    pub exit_temporary_mode: bool,     operator_stack: Vec<Operator>,    pub(crate) replacements: Vec<(Range<editor::Anchor>, String)>,     pub(crate) marks: HashMap<String, Vec<Anchor>>,    pub(crate) stored_visual_mode: Option<(Mode, Vec<bool>)>,    pub(crate) change_list: Vec<Vec<Anchor>>,    pub(crate) change_list_position: Option<usize>,     pub(crate) current_tx: Option<TransactionId>,    pub(crate) current_anchor: Option<Selection<Anchor>>,    pub(crate) undo_modes: HashMap<TransactionId, Mode>,     selected_register: Option<char>,    pub search: SearchState,     editor: WeakEntity<Editor>,     last_command: Option<String>,    running_command: Option<Task<()>>,    _subscriptions: Vec<Subscription>,} // Hack: Vim intercepts events dispatched to a window and updates the view in response.// This means it needs a VisualContext. The easiest way to satisfy that constraint is// to make Vim a "View" that is just never actually rendered.impl Render for Vim {    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {        gpui::Empty    }} enum VimEvent {    Focused,}impl EventEmitter<VimEvent> for Vim {} impl Vim {    /// The namespace for Vim actions.    const NAMESPACE: &'static str = "vim";     pub fn new(window: &mut Window, cx: &mut Context<Editor>) -> Entity<Self> {        let editor = cx.entity().clone();         cx.new(|cx| Vim {            mode: VimSettings::get_global(cx).default_mode,            last_mode: Mode::Normal,            temp_mode: false,            exit_temporary_mode: false,            operator_stack: Vec::new(),            replacements: Vec::new(),             marks: HashMap::default(),            stored_visual_mode: None,            change_list: Vec::new(),            change_list_position: None,            current_tx: None,            current_anchor: None,            undo_modes: HashMap::default(),             status_label: None,            selected_register: None,            search: SearchState::default(),             last_command: None,            running_command: None,             editor: editor.downgrade(),            _subscriptions: vec![                cx.observe_keystrokes(Self::observe_keystrokes),                cx.subscribe_in(&editor, window, |this, _, event, window, cx| {                    this.handle_editor_event(event, window, cx)                }),            ],        })    }     fn register(editor: &mut Editor, window: Option<&mut Window>, cx: &mut Context<Editor>) {        let Some(window) = window else {            return;        };         if !editor.use_modal_editing() {            return;        }         let mut was_enabled = Vim::enabled(cx);        let mut was_toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;        cx.observe_global_in::<SettingsStore>(window, move |editor, window, cx| {            let enabled = Vim::enabled(cx);            let toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;            if enabled && was_enabled && (toggle != was_toggle) {                if toggle {                    let is_relative = editor                        .addon::<VimAddon>()                        .map(|vim| vim.entity.read(cx).mode != Mode::Insert);                    editor.set_relative_line_number(is_relative, cx)                } else {                    editor.set_relative_line_number(None, cx)                }            }            was_toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;            if was_enabled == enabled {                return;            }            was_enabled = enabled;            if enabled {                Self::activate(editor, window, cx)            } else {                Self::deactivate(editor, cx)            }        })        .detach();        if was_enabled {            Self::activate(editor, window, cx)        }    }     fn activate(editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>) {        let vim = Vim::new(window, cx);         editor.register_addon(VimAddon {            entity: vim.clone(),        });         vim.update(cx, |_, cx| {            Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| {                vim.switch_mode(Mode::Normal, false, window, cx)            });             Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| {                vim.switch_mode(Mode::Insert, false, window, cx)            });             Vim::action(editor, cx, |vim, _: &SwitchToReplaceMode, window, cx| {                vim.switch_mode(Mode::Replace, false, window, cx)            });             Vim::action(editor, cx, |vim, _: &SwitchToVisualMode, window, cx| {                vim.switch_mode(Mode::Visual, false, window, cx)            });             Vim::action(editor, cx, |vim, _: &SwitchToVisualLineMode, window, cx| {                vim.switch_mode(Mode::VisualLine, false, window, cx)            });             Vim::action(                editor,                cx,                |vim, _: &SwitchToVisualBlockMode, window, cx| {                    vim.switch_mode(Mode::VisualBlock, false, window, cx)                },            );             Vim::action(                editor,                cx,                |vim, _: &SwitchToHelixNormalMode, window, cx| {                    vim.switch_mode(Mode::HelixNormal, false, window, cx)                },            );             Vim::action(editor, cx, |vim, action: &PushObject, window, cx| {                vim.push_operator(                    Operator::Object {                        around: action.around,                    },                    window,                    cx,                )            });             Vim::action(editor, cx, |vim, action: &PushFindForward, window, cx| {                vim.push_operator(                    Operator::FindForward {                        before: action.before,                    },                    window,                    cx,                )            });             Vim::action(editor, cx, |vim, action: &PushFindBackward, window, cx| {                vim.push_operator(                    Operator::FindBackward {                        after: action.after,                    },                    window,

Lines 1–500 of 1734.