//! 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. Load more
//! 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,