diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index 88ef505053..2bc7962267 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -50,14 +50,17 @@ pub enum NotifyWhenAgentWaiting { #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] pub enum AgentEditorMode { - Vim, - VimInsert, - Helix, - Default, + EditorModeOverride(EditorMode), #[default] Inherit, } +impl From for AgentEditorMode { + fn from(b: EditorMode) -> Self { + AgentEditorMode::EditorModeOverride(b) + } +} + #[derive(Default, Clone, Debug)] pub struct AgentSettings { pub enabled: bool, diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index dda0f9f04b..d3bacf2227 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -112,6 +112,14 @@ impl MessageEditor { range: Cell::new(None), }); let mention_set = MentionSet::default(); + + let editor_mode = match settings.editor_mode { + agent_settings::AgentEditorMode::EditorModeOverride(mode) => mode, + agent_settings::AgentEditorMode::Inherit => { + vim_mode_setting::EditorModeSetting::get_global(cx).0 + } + }; + let editor = cx.new(|cx| { let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx)); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); @@ -120,7 +128,7 @@ impl MessageEditor { editor.set_placeholder_text(placeholder, cx); editor.set_show_indent_guides(false, cx); editor.set_soft_wrap(); - editor.set_use_modal_editing(true); + editor.set_use_modal_editing(editor_mode); editor.set_completion_provider(Some(Rc::new(completion_provider))); editor.set_context_menu_options(ContextMenuOptions { min_entries_visible: 12, diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index 7f0254564f..a5f1830e97 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -117,10 +117,7 @@ pub(crate) fn create_editor( let settings = agent_settings::AgentSettings::get_global(cx); let editor_mode = match settings.editor_mode { - agent_settings::AgentEditorMode::Vim => vim_mode_setting::EditorMode::Vim, - agent_settings::AgentEditorMode::VimInsert => vim_mode_setting::EditorMode::VimInsert, - agent_settings::AgentEditorMode::Helix => vim_mode_setting::EditorMode::Helix, - agent_settings::AgentEditorMode::Default => vim_mode_setting::EditorMode::Default, + agent_settings::AgentEditorMode::EditorModeOverride(mode) => mode, agent_settings::AgentEditorMode::Inherit => { vim_mode_setting::EditorModeSetting::get_global(cx).0 } @@ -139,7 +136,6 @@ pub(crate) fn create_editor( editor.set_placeholder_text("Message the agent – @ to include context", cx); editor.set_show_indent_guides(false, cx); editor.set_soft_wrap(); - editor.set_use_modal_editing(true); editor.set_default_editor_mode(editor_mode); editor.set_context_menu_options(ContextMenuOptions { min_entries_visible: 12, diff --git a/crates/debugger_ui/Cargo.toml b/crates/debugger_ui/Cargo.toml index df4125860f..2543102dd8 100644 --- a/crates/debugger_ui/Cargo.toml +++ b/crates/debugger_ui/Cargo.toml @@ -76,6 +76,7 @@ util.workspace = true workspace-hack.workspace = true workspace.workspace = true zed_actions.workspace = true +vim_mode_settings.workspace = true [dev-dependencies] dap = { workspace = true, features = ["test-support"] } diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index a801cedd26..26c2ad3477 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -26,6 +26,7 @@ use std::{cell::RefCell, ops::Range, rc::Rc, usize}; use theme::{Theme, ThemeSettings}; use ui::{ContextMenu, Divider, PopoverMenu, SplitButton, Tooltip, prelude::*}; use util::ResultExt; +use vim_mode_settings::EditorMode; actions!( console, @@ -74,7 +75,7 @@ impl Console { editor.set_show_wrap_guides(false, cx); editor.set_show_indent_guides(false, cx); editor.set_show_edit_predictions(Some(false), window, cx); - editor.set_use_modal_editing(false); + editor.set_default_editor_mode(EditorMode::Default); editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); editor }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0412ee1764..29e8d26ac7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -165,7 +165,6 @@ use project::{ }; use rand::{seq::SliceRandom, thread_rng}; use rpc::{ErrorCode, ErrorExt, proto::PeerId}; -use schemars::JsonSchema; use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide}; use selections_collection::{ MutableSelectionsCollection, SelectionsCollection, resolve_selections, @@ -202,7 +201,6 @@ use ui::{ IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, }; use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc}; -use vim_mode_setting::EditorModeSetting; use workspace::{ CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast, @@ -2185,7 +2183,6 @@ impl Editor { collapse_matches: false, workspace: None, input_enabled: !is_minimap, - use_modal_editing: full_mode, read_only: is_minimap, use_autoclose: true, use_auto_surround: true, @@ -22940,6 +22937,7 @@ pub enum EditorEvent { anchor: Anchor, is_deactivate: bool, }, + EditorModeChanged, } impl EventEmitter for Editor {} diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 1b3aa0d222..456f043771 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -406,7 +406,7 @@ pub(crate) fn commit_message_editor( commit_editor.set_collaboration_hub(Box::new(project)); commit_editor.set_use_autoclose(false); commit_editor.set_show_gutter(false, cx); - commit_editor.set_use_modal_editing(true); + // commit_editor.set_use_modal_editing(true); TODO commit_editor.set_show_wrap_guides(false, cx); commit_editor.set_show_indent_guides(false, cx); let placeholder = placeholder.unwrap_or("Enter commit message".into()); diff --git a/crates/repl/src/repl_sessions_ui.rs b/crates/repl/src/repl_sessions_ui.rs index 493b8aa950..de7cbf2b0e 100644 --- a/crates/repl/src/repl_sessions_ui.rs +++ b/crates/repl/src/repl_sessions_ui.rs @@ -75,7 +75,8 @@ pub fn init(cx: &mut App) { return; }; - if !editor.use_modal_editing() || !editor.buffer().read(cx).is_singleton() { + if !editor.default_editor_mode().is_modal() || !editor.buffer().read(cx).is_singleton() + { return; } diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs index 5ad3996e78..9bacadfcbf 100644 --- a/crates/rules_library/src/rules_library.rs +++ b/crates/rules_library/src/rules_library.rs @@ -637,7 +637,7 @@ impl RulesLibrary { editor.set_show_gutter(false, cx); editor.set_show_wrap_guides(false, cx); editor.set_show_indent_guides(false, cx); - editor.set_use_modal_editing(false); + editor.set_default_editor_mode(EditorMode::Default); editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); editor.set_completion_provider(Some(make_completion_provider())); if focus { diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index c0176cb12c..9df762e52f 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -32,49 +32,10 @@ use ui::{ StyledTypography, Window, h_flex, rems, }; use util::ResultExt; +use vim_mode_setting::ModalMode; use workspace::searchable::Direction; use workspace::{Workspace, WorkspaceDb, WorkspaceId}; -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] -pub enum Mode { - Normal, - Insert, - Replace, - Visual, - VisualLine, - VisualBlock, - HelixNormal, -} - -impl Display for Mode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Mode::Normal => write!(f, "NORMAL"), - Mode::Insert => write!(f, "INSERT"), - Mode::Replace => write!(f, "REPLACE"), - Mode::Visual => write!(f, "VISUAL"), - Mode::VisualLine => write!(f, "VISUAL LINE"), - Mode::VisualBlock => write!(f, "VISUAL BLOCK"), - Mode::HelixNormal => write!(f, "HELIX NORMAL"), - } - } -} - -impl Mode { - pub fn is_visual(&self) -> bool { - match self { - Self::Visual | Self::VisualLine | Self::VisualBlock => true, - Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false, - } - } -} - -impl Default for Mode { - fn default() -> Self { - Self::Normal - } -} - #[derive(Clone, Debug, PartialEq)] pub enum Operator { Change, @@ -976,7 +937,7 @@ pub struct SearchState { pub prior_selections: Vec>, pub prior_operator: Option, - pub prior_mode: Mode, + pub prior_mode: ModalMode, } impl Operator { @@ -1043,7 +1004,7 @@ impl Operator { } } - pub fn is_waiting(&self, mode: Mode) -> bool { + pub fn is_waiting(&self, mode: ModalMode) -> bool { match self { Operator::AddSurrounds { target } => target.is_some() || mode.is_visual(), Operator::FindForward { .. } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 4b89539f50..984e11b495 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -40,7 +40,7 @@ use schemars::JsonSchema; use serde::Deserialize; use serde_derive::Serialize; use settings::{Settings, SettingsSources, SettingsStore, update_settings_file}; -use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals}; +use state::{Operator, RecordedSelection, SearchState, VimGlobals}; use std::{mem, ops::Range, sync::Arc}; use surrounds::SurroundsType; use theme::ThemeSettings; @@ -360,8 +360,8 @@ impl editor::Addon for VimAddon { /// The state pertaining to Vim mode. pub(crate) struct Vim { - pub(crate) mode: Mode, - pub last_mode: Mode, + pub(crate) mode: ModalMode, + pub last_mode: ModalMode, pub temp_mode: bool, pub status_label: Option, pub exit_temporary_mode: bool, @@ -369,11 +369,11 @@ pub(crate) struct Vim { operator_stack: Vec, pub(crate) replacements: Vec<(Range, String)>, - pub(crate) stored_visual_mode: Option<(Mode, Vec)>, + pub(crate) stored_visual_mode: Option<(ModalMode, Vec)>, pub(crate) current_tx: Option, pub(crate) current_anchor: Option>, - pub(crate) undo_modes: HashMap, + pub(crate) undo_modes: HashMap, pub(crate) undo_last_line_tx: Option, selected_register: Option, @@ -407,16 +407,9 @@ impl Vim { pub fn new(window: &mut Window, cx: &mut Context) -> Entity { let editor = cx.entity(); - let mut initial_mode = VimSettings::get_global(cx).default_mode; - if initial_mode == Mode::Normal - && matches!(EditorModeSetting::get_global(cx).0, EditorMode::Helix) - { - initial_mode = Mode::HelixNormal; - } - cx.new(|cx| Vim { - mode: initial_mode, - last_mode: Mode::Normal, + mode: ModalMode::Normal, + last_mode: ModalMode::Normal, temp_mode: false, exit_temporary_mode: false, operator_stack: Vec::new(), @@ -450,56 +443,45 @@ impl Vim { return; }; - if !editor.use_modal_editing() { + if !editor.default_editor_mode().is_modal() { return; } - if editor.default_editor_mode() == EditorMode::Default { - return; - } - - let mut was_enabled = Vim::enabled(cx); let mut was_toggle = VimSettings::get_global(cx).toggle_relative_line_numbers; cx.observe_global_in::(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 != was_toggle { if toggle { let is_relative = editor .addon::() - .map(|vim| vim.entity.read(cx).mode != Mode::Insert); + .map(|vim| vim.entity.read(cx).mode != ModalMode::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) - } + + let mut was_enabled = true; + cx.observe::(window, move |editor, window, cx| {}) + .detach(); + + Self::activate(editor, window, cx) } fn activate(editor: &mut Editor, window: &mut Window, cx: &mut Context) { let vim = Vim::new(window, cx); - let default_editor_mode = editor.default_editor_mode(); - - if default_editor_mode == EditorMode::VimInsert { - vim.update(cx, |vim, _| { - vim.mode = Mode::Insert; - }); - } + vim.update(cx, |vim, _| { + let initial_mode = match editor.default_editor_mode() { + EditorMode::Default => return, + EditorMode::Vim(modal_mode) => modal_mode, + EditorMode::Helix(modal_mode) => modal_mode, + }; + vim.mode = initial_mode; + }); editor.register_addon(VimAddon { entity: vim.clone(), @@ -511,34 +493,34 @@ impl Vim { cx, move |vim, _: &SwitchToNormalMode, window, cx| { if matches!(default_editor_mode, EditorMode::Helix) { - vim.switch_mode(Mode::HelixNormal, false, window, cx) + vim.switch_mode(ModalMode::HelixNormal, false, window, cx) } else { - vim.switch_mode(Mode::Normal, false, window, cx) + vim.switch_mode(ModalMode::Normal, false, window, cx) } }, ); Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| { - vim.switch_mode(Mode::Insert, false, window, cx) + vim.switch_mode(ModalMode::Insert, false, window, cx) }); Vim::action(editor, cx, |vim, _: &SwitchToReplaceMode, window, cx| { - vim.switch_mode(Mode::Replace, false, window, cx) + vim.switch_mode(ModalMode::Replace, false, window, cx) }); Vim::action(editor, cx, |vim, _: &SwitchToVisualMode, window, cx| { - vim.switch_mode(Mode::Visual, false, window, cx) + vim.switch_mode(ModalMode::Visual, false, window, cx) }); Vim::action(editor, cx, |vim, _: &SwitchToVisualLineMode, window, cx| { - vim.switch_mode(Mode::VisualLine, false, window, cx) + vim.switch_mode(ModalMode::VisualLine, false, window, cx) }); Vim::action( editor, cx, |vim, _: &SwitchToVisualBlockMode, window, cx| { - vim.switch_mode(Mode::VisualBlock, false, window, cx) + vim.switch_mode(ModalMode::VisualBlock, false, window, cx) }, ); @@ -546,7 +528,7 @@ impl Vim { editor, cx, |vim, _: &SwitchToHelixNormalMode, window, cx| { - vim.switch_mode(Mode::HelixNormal, false, window, cx) + vim.switch_mode(ModalMode::HelixNormal, false, window, cx) }, ); Vim::action(editor, cx, |_, _: &PushForcedMotion, _, cx| { @@ -766,8 +748,8 @@ impl Vim { // displayed (and performed by `accept_edit_prediction`). This switches to // insert mode so that the prediction is displayed after the jump. match vim.mode { - Mode::Replace => {} - _ => vim.switch_mode(Mode::Insert, true, window, cx), + ModalMode::Replace => {} + _ => vim.switch_mode(ModalMode::Insert, true, window, cx), }; }, ); @@ -859,7 +841,7 @@ impl Vim { { return; } - self.switch_mode(Mode::Insert, false, window, cx) + self.switch_mode(ModalMode::Insert, false, window, cx) } if let Some(action) = keystroke_event.action.as_ref() { // Keystroke is handled by the vim system, so continue forward @@ -935,6 +917,24 @@ impl Vim { vim.set_mark(mark, vec![*anchor], editor.buffer(), window, cx); }); } + EditorEvent::EditorModeChanged => { + self.update_editor(cx, |vim, editor, cx| { + let enabled = editor.default_editor_mode().is_modal(); + if was_enabled == enabled { + return; + } + if !enabled { + editor.set_relative_line_number(None, cx); + } + was_enabled = enabled; + if enabled { + Self::activate(editor, window, cx) + } else { + Self::deactivate(editor, cx) + } + // + }); + } _ => {} } } @@ -961,18 +961,21 @@ impl Vim { pub fn switch_mode( &mut self, - mode: Mode, + mode: ModalMode, leave_selections: bool, window: &mut Window, cx: &mut Context, ) { - if self.temp_mode && mode == Mode::Normal { + if self.temp_mode && mode == ModalMode::Normal { self.temp_mode = false; - self.switch_mode(Mode::Normal, leave_selections, window, cx); - self.switch_mode(Mode::Insert, false, window, cx); + self.switch_mode(ModalMode::Normal, leave_selections, window, cx); + self.switch_mode(ModalMode::Insert, false, window, cx); return; } else if self.temp_mode - && !matches!(mode, Mode::Visual | Mode::VisualLine | Mode::VisualBlock) + && !matches!( + mode, + ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock + ) { self.temp_mode = false; } @@ -986,7 +989,7 @@ impl Vim { self.operator_stack.clear(); self.selected_register.take(); self.cancel_running_command(window, cx); - if mode == Mode::Normal || mode != last_mode { + if mode == ModalMode::Normal || mode != last_mode { self.current_tx.take(); self.current_anchor.take(); self.update_editor(cx, |_, editor, _| { @@ -994,7 +997,7 @@ impl Vim { }); } Vim::take_forced_motion(cx); - if mode != Mode::Insert && mode != Mode::Replace { + if mode != ModalMode::Insert && mode != ModalMode::Replace { Vim::take_count(cx); } @@ -1003,10 +1006,10 @@ impl Vim { if VimSettings::get_global(cx).toggle_relative_line_numbers && self.mode != self.last_mode - && (self.mode == Mode::Insert || self.last_mode == Mode::Insert) + && (self.mode == ModalMode::Insert || self.last_mode == ModalMode::Insert) { self.update_editor(cx, |vim, editor, cx| { - let is_relative = vim.mode != Mode::Insert; + let is_relative = vim.mode != ModalMode::Insert; editor.set_relative_line_number(Some(is_relative), cx) }); } @@ -1021,13 +1024,15 @@ impl Vim { // Adjust selections self.update_editor(cx, |vim, editor, cx| { - if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock + if last_mode != ModalMode::VisualBlock + && last_mode.is_visual() + && mode == ModalMode::VisualBlock { vim.visual_block_motion(true, editor, window, cx, |_, point, goal| { Some((point, goal)) }) } - if (last_mode == Mode::Insert || last_mode == Mode::Replace) + if (last_mode == ModalMode::Insert || last_mode == ModalMode::Replace) && let Some(prior_tx) = prior_tx { editor.group_until_transaction(prior_tx, cx) @@ -1037,15 +1042,15 @@ impl Vim { // we cheat with visual block mode and use multiple cursors. // the cost of this cheat is we need to convert back to a single // cursor whenever vim would. - if last_mode == Mode::VisualBlock - && (mode != Mode::VisualBlock && mode != Mode::Insert) + if last_mode == ModalMode::VisualBlock + && (mode != ModalMode::VisualBlock && mode != ModalMode::Insert) { let tail = s.oldest_anchor().tail(); let head = s.newest_anchor().head(); s.select_anchor_ranges(vec![tail..head]); - } else if last_mode == Mode::Insert - && prior_mode == Mode::VisualBlock - && mode != Mode::VisualBlock + } else if last_mode == ModalMode::Insert + && prior_mode == ModalMode::VisualBlock + && mode != ModalMode::VisualBlock { let pos = s.first_anchor().head(); s.select_anchor_ranges(vec![pos..pos]) @@ -1110,7 +1115,7 @@ impl Vim { pub fn cursor_shape(&self, cx: &mut App) -> CursorShape { let cursor_shape = VimSettings::get_global(cx).cursor_shape; match self.mode { - Mode::Normal => { + ModalMode::Normal => { if let Some(operator) = self.operator_stack.last() { match operator { // Navigation operators -> Block cursor @@ -1129,12 +1134,12 @@ impl Vim { cursor_shape.normal.unwrap_or(CursorShape::Block) } } - Mode::HelixNormal => cursor_shape.normal.unwrap_or(CursorShape::Block), - Mode::Replace => cursor_shape.replace.unwrap_or(CursorShape::Underline), - Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { + ModalMode::HelixNormal => cursor_shape.normal.unwrap_or(CursorShape::Block), + ModalMode::Replace => cursor_shape.replace.unwrap_or(CursorShape::Underline), + ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock => { cursor_shape.visual.unwrap_or(CursorShape::Block) } - Mode::Insert => cursor_shape.insert.unwrap_or({ + ModalMode::Insert => cursor_shape.insert.unwrap_or({ let editor_settings = EditorSettings::get_global(cx); editor_settings.cursor_shape.unwrap_or_default() }), @@ -1143,45 +1148,45 @@ impl Vim { pub fn editor_input_enabled(&self) -> bool { match self.mode { - Mode::Insert => { + ModalMode::Insert => { if let Some(operator) = self.operator_stack.last() { !operator.is_waiting(self.mode) } else { true } } - Mode::Normal - | Mode::HelixNormal - | Mode::Replace - | Mode::Visual - | Mode::VisualLine - | Mode::VisualBlock => false, + ModalMode::Normal + | ModalMode::HelixNormal + | ModalMode::Replace + | ModalMode::Visual + | ModalMode::VisualLine + | ModalMode::VisualBlock => false, } } pub fn should_autoindent(&self) -> bool { - !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock) + !(self.mode == ModalMode::Insert && self.last_mode == ModalMode::VisualBlock) } pub fn clip_at_line_ends(&self) -> bool { match self.mode { - Mode::Insert - | Mode::Visual - | Mode::VisualLine - | Mode::VisualBlock - | Mode::Replace - | Mode::HelixNormal => false, - Mode::Normal => true, + ModalMode::Insert + | ModalMode::Visual + | ModalMode::VisualLine + | ModalMode::VisualBlock + | ModalMode::Replace + | ModalMode::HelixNormal => false, + ModalMode::Normal => true, } } pub fn extend_key_context(&self, context: &mut KeyContext, cx: &App) { let mut mode = match self.mode { - Mode::Normal => "normal", - Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual", - Mode::Insert => "insert", - Mode::Replace => "replace", - Mode::HelixNormal => "helix_normal", + ModalMode::Normal => "normal", + ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock => "visual", + ModalMode::Insert => "insert", + ModalMode::Replace => "replace", + ModalMode::HelixNormal => "helix_normal", } .to_string(); @@ -1226,12 +1231,12 @@ impl Vim { if editor_mode.is_full() && !newest_selection_empty - && self.mode == Mode::Normal + && self.mode == ModalMode::Normal // When following someone, don't switch vim mode. && editor.leader_id().is_none() { if preserve_selection { - self.switch_mode(Mode::Visual, true, window, cx); + self.switch_mode(ModalMode::Visual, true, window, cx); } else { self.update_editor(cx, |_, editor, cx| { editor.set_clip_at_line_ends(false, cx); @@ -1257,13 +1262,13 @@ impl Vim { }); self.update_editor(cx, |vim, editor, cx| { - let is_relative = vim.mode != Mode::Insert; + let is_relative = vim.mode != ModalMode::Insert; editor.set_relative_line_number(Some(is_relative), cx) }); } } else { self.update_editor(cx, |vim, editor, cx| { - let is_relative = vim.mode != Mode::Insert; + let is_relative = vim.mode != ModalMode::Insert; editor.set_relative_line_number(Some(is_relative), cx) }); } @@ -1351,19 +1356,19 @@ impl Vim { if let Some((oldest, newest)) = selections { globals.recorded_selection = match self.mode { - Mode::Visual if newest.end.row == newest.start.row => { + ModalMode::Visual if newest.end.row == newest.start.row => { RecordedSelection::SingleLine { cols: newest.end.column - newest.start.column, } } - Mode::Visual => RecordedSelection::Visual { + ModalMode::Visual => RecordedSelection::Visual { rows: newest.end.row - newest.start.row, cols: newest.end.column, }, - Mode::VisualLine => RecordedSelection::VisualLine { + ModalMode::VisualLine => RecordedSelection::VisualLine { rows: newest.end.row - newest.start.row, }, - Mode::VisualBlock => RecordedSelection::VisualBlock { + ModalMode::VisualBlock => RecordedSelection::VisualBlock { rows: newest.end.row.abs_diff(oldest.start.row), cols: newest.end.column.abs_diff(oldest.start.column), }, @@ -1480,9 +1485,9 @@ impl Vim { _window: &mut Window, _: &mut Context, ) { - let mode = if (self.mode == Mode::Insert - || self.mode == Mode::Replace - || self.mode == Mode::Normal) + let mode = if (self.mode == ModalMode::Insert + || self.mode == ModalMode::Replace + || self.mode == ModalMode::Normal) && self.current_tx.is_none() { self.current_tx = Some(transaction_id); @@ -1490,7 +1495,7 @@ impl Vim { } else { self.mode }; - if mode == Mode::VisualLine || mode == Mode::VisualBlock { + if mode == ModalMode::VisualLine || mode == ModalMode::VisualBlock { self.undo_modes.insert(transaction_id, mode); } } @@ -1502,12 +1507,12 @@ impl Vim { cx: &mut Context, ) { match self.mode { - Mode::VisualLine | Mode::VisualBlock | Mode::Visual => { + ModalMode::VisualLine | ModalMode::VisualBlock | ModalMode::Visual => { self.update_editor(cx, |vim, editor, cx| { let original_mode = vim.undo_modes.get(transaction_id); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { match original_mode { - Some(Mode::VisualLine) => { + Some(ModalMode::VisualLine) => { s.move_with(|map, selection| { selection.collapse_to( map.prev_line_boundary(selection.start.to_point(map)).1, @@ -1515,7 +1520,7 @@ impl Vim { ) }); } - Some(Mode::VisualBlock) => { + Some(ModalMode::VisualBlock) => { let mut first = s.first_anchor(); first.collapse_to(first.start, first.goal); s.select_anchors(vec![first]); @@ -1531,9 +1536,9 @@ impl Vim { } }); }); - self.switch_mode(Mode::Normal, true, window, cx) + self.switch_mode(ModalMode::Normal, true, window, cx) } - Mode::Normal => { + ModalMode::Normal => { self.update_editor(cx, |_, editor, cx| { editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { @@ -1543,7 +1548,7 @@ impl Vim { }) }); } - Mode::Insert | Mode::Replace | Mode::HelixNormal => {} + ModalMode::Insert | ModalMode::Replace | ModalMode::HelixNormal => {} } } @@ -1556,7 +1561,7 @@ impl Vim { let newest = editor.read(cx).selections.newest_anchor().clone(); let is_multicursor = editor.read(cx).selections.count() > 1; - if self.mode == Mode::Insert && self.current_tx.is_some() { + if self.mode == ModalMode::Insert && self.current_tx.is_some() { if self.current_anchor.is_none() { self.current_anchor = Some(newest); } else if self.current_anchor.as_ref().unwrap() != &newest @@ -1566,17 +1571,22 @@ impl Vim { editor.group_until_transaction(tx_id, cx) }); } - } else if self.mode == Mode::Normal && newest.start != newest.end { + } else if self.mode == ModalMode::Normal && newest.start != newest.end { if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) { - self.switch_mode(Mode::VisualBlock, false, window, cx); + self.switch_mode(ModalMode::VisualBlock, false, window, cx); } else { - self.switch_mode(Mode::Visual, false, window, cx) + self.switch_mode(ModalMode::Visual, false, window, cx) } } else if newest.start == newest.end && !is_multicursor - && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&self.mode) + && [ + ModalMode::Visual, + ModalMode::VisualLine, + ModalMode::VisualBlock, + ] + .contains(&self.mode) { - self.switch_mode(Mode::Normal, true, window, cx); + self.switch_mode(ModalMode::Normal, true, window, cx); } } @@ -1649,11 +1659,11 @@ impl Vim { } } Some(Operator::Replace) => match self.mode { - Mode::Normal => self.normal_replace(text, window, cx), - Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { + ModalMode::Normal => self.normal_replace(text, window, cx), + ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock => { self.visual_replace(text, window, cx) } - Mode::HelixNormal => self.helix_replace(&text, window, cx), + ModalMode::HelixNormal => self.helix_replace(&text, window, cx), _ => self.clear_operator(window, cx), }, Some(Operator::Digraph { first_char }) => { @@ -1671,20 +1681,20 @@ impl Vim { self.handle_literal_input(prefix.unwrap_or_default(), &text, window, cx) } Some(Operator::AddSurrounds { target }) => match self.mode { - Mode::Normal => { + ModalMode::Normal => { if let Some(target) = target { self.add_surrounds(text, target, window, cx); self.clear_operator(window, cx); } } - Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { + ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock => { self.add_surrounds(text, SurroundsType::Selection, window, cx); self.clear_operator(window, cx); } _ => self.clear_operator(window, cx), }, Some(Operator::ChangeSurrounds { target }) => match self.mode { - Mode::Normal => { + ModalMode::Normal => { if let Some(target) = target { self.change_surrounds(text, target, window, cx); self.clear_operator(window, cx); @@ -1693,7 +1703,7 @@ impl Vim { _ => self.clear_operator(window, cx), }, Some(Operator::DeleteSurrounds) => match self.mode { - Mode::Normal => { + ModalMode::Normal => { self.delete_surrounds(text, window, cx); self.clear_operator(window, cx); } @@ -1707,7 +1717,7 @@ impl Vim { self.replay_register(text.chars().next().unwrap(), window, cx) } Some(Operator::Register) => match self.mode { - Mode::Insert => { + ModalMode::Insert => { self.update_editor(cx, |_, editor, cx| { if let Some(register) = Vim::update_globals(cx, |globals, cx| { globals.read_register(text.chars().next(), Some(editor), cx) @@ -1729,11 +1739,11 @@ impl Vim { }, Some(Operator::Jump { line }) => self.jump(text, line, true, window, cx), _ => { - if self.mode == Mode::Replace { + if self.mode == ModalMode::Replace { self.multi_replace(text, window, cx) } - if self.mode == Mode::Normal { + if self.mode == ModalMode::Normal { self.update_editor(cx, |_, editor, cx| { editor.accept_edit_prediction( &editor::actions::AcceptEditPrediction {}, @@ -1753,9 +1763,9 @@ impl Vim { editor.set_collapse_matches(true); editor.set_input_enabled(vim.editor_input_enabled()); editor.set_autoindent(vim.should_autoindent()); - editor.selections.line_mode = matches!(vim.mode, Mode::VisualLine); + editor.selections.line_mode = matches!(vim.mode, ModalMode::VisualLine); - let hide_edit_predictions = !matches!(vim.mode, Mode::Insert | Mode::Replace); + let hide_edit_predictions = !matches!(vim.mode, ModalMode::Insert | ModalMode::Replace); editor.set_edit_predictions_hidden_for_vim_mode(hide_edit_predictions, window, cx); }); cx.notify() @@ -1797,7 +1807,6 @@ struct CursorShapeSettings { #[derive(Deserialize)] struct VimSettings { - pub default_mode: Mode, pub toggle_relative_line_numbers: bool, pub use_system_clipboard: UseSystemClipboard, pub use_smartcase_find: bool, @@ -1808,7 +1817,6 @@ struct VimSettings { #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] struct VimSettingsContent { - pub default_mode: Option, pub toggle_relative_line_numbers: Option, pub use_system_clipboard: Option, pub use_smartcase_find: Option, @@ -1817,33 +1825,6 @@ struct VimSettingsContent { pub cursor_shape: Option, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ModeContent { - #[default] - Normal, - Insert, - Replace, - Visual, - VisualLine, - VisualBlock, - HelixNormal, -} - -impl From for Mode { - fn from(mode: ModeContent) -> Self { - match mode { - ModeContent::Normal => Self::Normal, - ModeContent::Insert => Self::Insert, - ModeContent::Replace => Self::Replace, - ModeContent::Visual => Self::Visual, - ModeContent::VisualLine => Self::VisualLine, - ModeContent::VisualBlock => Self::VisualBlock, - ModeContent::HelixNormal => Self::HelixNormal, - } - } -} - impl Settings for VimSettings { const KEY: Option<&'static str> = Some("vim"); @@ -1853,10 +1834,6 @@ impl Settings for VimSettings { let settings: VimSettingsContent = sources.json_merge()?; Ok(Self { - default_mode: settings - .default_mode - .ok_or_else(Self::missing_default)? - .into(), toggle_relative_line_numbers: settings .toggle_relative_line_numbers .ok_or_else(Self::missing_default)?, diff --git a/crates/vim_mode_setting/src/vim_mode_setting.rs b/crates/vim_mode_setting/src/vim_mode_setting.rs index c8d05a1af6..4ee6a6288b 100644 --- a/crates/vim_mode_setting/src/vim_mode_setting.rs +++ b/crates/vim_mode_setting/src/vim_mode_setting.rs @@ -7,8 +7,10 @@ use anyhow::Result; use gpui::App; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use serde::de::Error; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use settings::{Settings, SettingsSources}; +use std::fmt::Display; /// Initializes the `vim_mode_setting` crate. pub fn init(cx: &mut App) { @@ -20,13 +22,93 @@ pub fn init(cx: &mut App) { /// Default: `EditMode::Default` pub struct EditorModeSetting(pub EditorMode); -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] pub enum EditorMode { - Vim, - VimInsert, - Helix, #[default] Default, + Vim(ModalMode), + Helix(ModalMode), +} + +impl<'de> Deserialize<'de> for EditorMode { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.as_str() { + "default" => Ok(EditorMode::Default), + "vim" => Ok(EditorMode::Vim(ModalMode::Normal)), + "vim_normal" => Ok(EditorMode::Vim(ModalMode::Normal)), + "vim_insert" => Ok(EditorMode::Vim(ModalMode::Insert)), + "vim_replace" => Ok(EditorMode::Vim(ModalMode::Replace)), + "vim_visual" => Ok(EditorMode::Vim(ModalMode::Visual)), + "vim_visual_line" => Ok(EditorMode::Vim(ModalMode::VisualLine)), + "vim_visual_block" => Ok(EditorMode::Vim(ModalMode::VisualBlock)), + "helix_experimental" => Ok(EditorMode::Helix(ModalMode::HelixNormal)), + _ => Err(D::Error::custom(format!("Unknown editor mode: {}", s))), + } + } +} + +impl Serialize for EditorMode { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = match self { + EditorMode::Default => "default", + EditorMode::Vim(ModalMode::Normal) => "vim", + EditorMode::Vim(ModalMode::Insert) => "vim_insert", + EditorMode::Vim(ModalMode::Replace) => "vim_replace", + EditorMode::Vim(ModalMode::Visual) => "vim_visual", + EditorMode::Vim(ModalMode::VisualLine) => "vim_visual_line", + EditorMode::Vim(ModalMode::VisualBlock) => "vim_visual_block", + EditorMode::Helix(ModalMode::Normal) => "helix_experimental", + _ => return Err(serde::ser::Error::custom("unsupported editor mode variant")), + }; + serializer.serialize_str(s) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub enum ModalMode { + Normal, + Insert, + Replace, + Visual, + VisualLine, + VisualBlock, + HelixNormal, +} + +impl Display for ModalMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ModalMode::Normal => write!(f, "NORMAL"), + ModalMode::Insert => write!(f, "INSERT"), + ModalMode::Replace => write!(f, "REPLACE"), + ModalMode::Visual => write!(f, "VISUAL"), + ModalMode::VisualLine => write!(f, "VISUAL LINE"), + ModalMode::VisualBlock => write!(f, "VISUAL BLOCK"), + ModalMode::HelixNormal => write!(f, "HELIX NORMAL"), + } + } +} + +impl ModalMode { + pub fn is_visual(&self) -> bool { + match self { + Self::Visual | Self::VisualLine | Self::VisualBlock => true, + Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false, + } + } +} + +impl Default for ModalMode { + fn default() -> Self { + Self::Normal + } } impl Settings for EditorModeSetting { @@ -49,3 +131,9 @@ impl Settings for EditorModeSetting { // TODO: could possibly check if any of the `vim.` keys are set? } } + +impl EditorMode { + pub fn is_modal(&self) -> bool { + matches!(self, EditorMode::Vim(_) | EditorMode::Helix(_)) + } +}