2194 lines · 80.2 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 crate::normal::paste::Paste as VimPaste;use collections::HashMap;use editor::{    Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, MultiBufferOffset,    SelectionEffects,    actions::Paste,    display_map::ToDisplayPoint,    movement::{self, FindRange},};use gpui::{    Action, App, AppContext, Axis, Context, Entity, EventEmitter, KeyContext, KeystrokeEvent,    Render, Subscription, Task, WeakEntity, Window, actions,};use insert::{NormalBefore, TemporaryNormal};use language::{    CharKind, CharScopeContext, CursorShape, Point, Selection, SelectionGoal, TransactionId,};pub use mode_indicator::ModeIndicator;use motion::Motion;use multi_buffer::ToPoint as _;use normal::search::SearchSubmit;use object::Object;use schemars::JsonSchema;use search::BufferSearchBar;use serde::Deserialize;use settings::RegisterSetting;pub use settings::{    ModeContent, Settings, SettingsStore, UseSystemClipboard, update_settings_file,};use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};use std::{mem, ops::Range, sync::Arc};use surrounds::SurroundsType;use theme::ThemeSettings;use ui::{IntoElement, SharedString, px};use vim_mode_setting::HelixModeSetting;use vim_mode_setting::VimModeSetting;use workspace::{self, Pane, Workspace}; use crate::{    normal::{GoToPreviousTab, GoToTab},    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, Action)]#[action(namespace = vim)]struct Number(usize); #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]struct SelectRegister(String); #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushObject {    around: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushFindForward {    before: bool,    multiline: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushFindBackward {    after: bool,    multiline: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]/// Selects the next object.struct PushHelixNext {    around: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]/// Selects the previous object.struct PushHelixPrevious {    around: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushSneak {    first_char: Option<char>,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushSneakBackward {    first_char: Option<char>,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushAddSurrounds; #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushChangeSurrounds {    target: Option<Object>,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushJump {    line: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushDigraph {    first_char: Option<char>,} #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct PushLiteral {    prefix: Option<String>,} actions!(    vim,    [        /// Switches to normal mode.        SwitchToNormalMode,        /// Switches to insert mode.        SwitchToInsertMode,        /// Switches to replace mode.        SwitchToReplaceMode,        /// Switches to visual mode.        SwitchToVisualMode,        /// Switches to visual line mode.        SwitchToVisualLineMode,        /// Switches to visual block mode.        SwitchToVisualBlockMode,        /// Switches to Helix-style normal mode.        SwitchToHelixNormalMode,        /// Clears any pending operators.        ClearOperators,        /// Clears the exchange register.        ClearExchange,        /// Inserts a tab character.        Tab,        /// Inserts a newline.        Enter,        /// Selects inner text object.        InnerObject,        /// Maximizes the current pane.        MaximizePane,        /// Resets all pane sizes to default.        ResetPaneSizes,        /// Resizes the pane to the right.        ResizePaneRight,        /// Resizes the pane to the left.        ResizePaneLeft,        /// Resizes the pane upward.        ResizePaneUp,        /// Resizes the pane downward.        ResizePaneDown,        /// Starts a change operation.        PushChange,        /// Starts a delete operation.        PushDelete,        /// Exchanges text regions.        Exchange,        /// Starts a yank operation.        PushYank,        /// Starts a replace operation.        PushReplace,        /// Deletes surrounding characters.        PushDeleteSurrounds,        /// Sets a mark at the current position.        PushMark,        /// Toggles the marks view.        ToggleMarksView,        /// Starts a forced motion.        PushForcedMotion,        /// Starts an indent operation.        PushIndent,        /// Starts an outdent operation.        PushOutdent,        /// Starts an auto-indent operation.        PushAutoIndent,        /// Starts a rewrap operation.        PushRewrap,        /// Starts a shell command operation.        PushShellCommand,        /// Converts to lowercase.        PushLowercase,        /// Converts to uppercase.        PushUppercase,        /// Toggles case.        PushOppositeCase,        /// Applies ROT13 encoding.        PushRot13,        /// Applies ROT47 encoding.        PushRot47,        /// Toggles the registers view.        ToggleRegistersView,        /// Selects a register.        PushRegister,        /// Starts recording to a register.        PushRecordRegister,        /// Replays a register.        PushReplayRegister,        /// Replaces with register contents.        PushReplaceWithRegister,        /// Toggles comments.        PushToggleComments,        /// Selects (count) next menu item        MenuSelectNext,        /// Selects (count) previous menu item        MenuSelectPrevious,        /// Clears count or toggles project panel focus        ToggleProjectPanelFocus,        /// Starts a match operation.        PushHelixMatch,        /// Adds surrounding characters in Helix mode.        PushHelixSurroundAdd,        /// Replaces surrounding characters in Helix mode.        PushHelixSurroundReplace,        /// Deletes surrounding characters in Helix mode.        PushHelixSurroundDelete,    ]); // in the workspace namespace so it's not filtered out when vim is disabled.actions!(    workspace,    [        /// Toggles Vim mode on or off.        ToggleVimMode,        /// Toggles Helix mode on or off.        ToggleHelixMode,    ]); /// Initializes the `vim` crate.pub fn init(cx: &mut App) {    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 = VimModeSetting::get_global(cx).0;            update_settings_file(fs, cx, move |setting, _| {                setting.vim_mode = Some(!currently_enabled);                if let Some(helix_mode) = &mut setting.helix_mode {                    *helix_mode = false;                }            })        });         workspace.register_action(|workspace, _: &ToggleHelixMode, _, cx| {            let fs = workspace.app_state().fs.clone();            let currently_enabled = HelixModeSetting::get_global(cx).0;            update_settings_file(fs, cx, move |setting, _| {                setting.helix_mode = Some(!currently_enabled);                if let Some(vim_mode) = &mut setting.vim_mode {                    *vim_mode = false;                }            })        });         workspace.register_action(|_, _: &MenuSelectNext, window, cx| {            let count = Vim::take_count(cx).unwrap_or(1);             for _ in 0..count {                window.dispatch_action(menu::SelectNext.boxed_clone(), cx);            }        });         workspace.register_action(|_, _: &MenuSelectPrevious, window, cx| {            let count = Vim::take_count(cx).unwrap_or(1);             for _ in 0..count {                window.dispatch_action(menu::SelectPrevious.boxed_clone(), cx);            }        });         workspace.register_action(|_, _: &ToggleProjectPanelFocus, window, cx| {            if Vim::take_count(cx).is_none() {                window.dispatch_action(zed_actions::project_panel::ToggleFocus.boxed_clone(), cx);            }        });         workspace.register_action(|workspace, n: &Number, 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());            if let Some(vim) = vim {                let digit = n.0;                vim.entity.update(cx, |_, cx| {                    cx.defer_in(window, move |vim, window, cx| {                        vim.push_count_digit(digit, window, cx)                    })                });            } else {                let count = Vim::globals(cx).pre_count.unwrap_or(0);                Vim::globals(cx).pre_count = Some(                    count                        .checked_mul(10)                        .and_then(|c| c.checked_add(n.0))                        .unwrap_or(count),                );            };        });         workspace.register_action(|_, _: &zed_actions::vim::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;            Vim::take_forced_motion(cx);            let theme = ThemeSettings::get_global(cx);            let font_id = window.text_system().resolve_font(&theme.buffer_font);            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;            Vim::take_forced_motion(cx);            let theme = ThemeSettings::get_global(cx);            let font_id = window.text_system().resolve_font(&theme.buffer_font);            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;            Vim::take_forced_motion(cx);            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;            Vim::take_forced_motion(cx);            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))            })        });        workspace.register_action(|_, _: &GoToTab, window, cx| {            let count = Vim::take_count(cx);            Vim::take_forced_motion(cx);             if let Some(tab_index) = count {                // <count>gt goes to tab <count> (1-based).                let zero_based_index = tab_index.saturating_sub(1);                window.dispatch_action(                    workspace::pane::ActivateItem(zero_based_index).boxed_clone(),                    cx,                );            } else {                // If no count is provided, go to the next tab.                window.dispatch_action(workspace::pane::ActivateNextItem.boxed_clone(), cx);            }        });         workspace.register_action(|workspace, _: &GoToPreviousTab, window, cx| {            let count = Vim::take_count(cx);            Vim::take_forced_motion(cx);             if let Some(count) = count {                // gT with count goes back that many tabs with wraparound (not the same as gt!).                let pane = workspace.active_pane().read(cx);                let item_count = pane.items().count();                if item_count > 0 {                    let current_index = pane.active_item_index();                    let target_index = (current_index as isize - count as isize)                        .rem_euclid(item_count as isize)                        as usize;                    window.dispatch_action(                        workspace::pane::ActivateItem(target_index).boxed_clone(),                        cx,                    );                }            } else {                // No count provided, go to the previous tab.                window.dispatch_action(workspace::pane::ActivatePreviousItem.boxed_clone(), 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,

Lines 1–500 of 2194.