diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index f6e148bcaef40fb2d54345ac05f22e46b58a8c1d..e76d5a07144ffb8ec480aa8e150633673e9abf6e 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -19,7 +19,7 @@ use crate::{ indent::IndentDirection, motion::{self, Motion, first_non_whitespace, next_line_end, right}, object::Object, - state::{Mark, Mode, Operator}, + state::{Mark, Mode, ObjectScope, Operator}, surrounds::SurroundsType, }; use collections::BTreeSet; @@ -454,64 +454,82 @@ impl Vim { ) { let mut waiting_operator: Option = None; match self.maybe_pop_operator() { - Some(Operator::Object { around, whitespace }) => match self.maybe_pop_operator() { - Some(Operator::Change) => self.change_object(object, around, times, window, cx), - Some(Operator::Delete) => { - self.delete_object(object, around, whitespace, times, window, cx) - } - Some(Operator::Yank) => self.yank_object(object, around, times, window, cx), - Some(Operator::Indent) => { - self.indent_object(object, around, IndentDirection::In, times, window, cx) - } - Some(Operator::Outdent) => { - self.indent_object(object, around, IndentDirection::Out, times, window, cx) - } - Some(Operator::AutoIndent) => { - self.indent_object(object, around, IndentDirection::Auto, times, window, cx) - } - Some(Operator::ShellCommand) => { - self.shell_command_object(object, around, window, cx); - } - Some(Operator::Rewrap) => self.rewrap_object(object, around, times, window, cx), - Some(Operator::Lowercase) => { - self.convert_object(object, around, ConvertTarget::LowerCase, times, window, cx) - } - Some(Operator::Uppercase) => { - self.convert_object(object, around, ConvertTarget::UpperCase, times, window, cx) - } - Some(Operator::OppositeCase) => self.convert_object( - object, - around, - ConvertTarget::OppositeCase, - times, - window, - cx, - ), - Some(Operator::Rot13) => { - self.convert_object(object, around, ConvertTarget::Rot13, times, window, cx) - } - Some(Operator::Rot47) => { - self.convert_object(object, around, ConvertTarget::Rot47, times, window, cx) - } - Some(Operator::AddSurrounds { target: None }) => { - waiting_operator = Some(Operator::AddSurrounds { - target: Some(SurroundsType::Object(object, around)), - }); - } - Some(Operator::ToggleComments) => { - self.toggle_comments_object(object, around, times, window, cx) - } - Some(Operator::ReplaceWithRegister) => { - self.replace_with_register_object(object, around, window, cx) - } - Some(Operator::Exchange) => self.exchange_object(object, around, window, cx), - Some(Operator::HelixMatch) => { - self.select_current_object(object, around, window, cx) - } - _ => { - // Can't do anything for namespace operators. Ignoring + Some(Operator::Object { scope }) => { + let (around, whitespace) = match scope { + ObjectScope::Inside => (false, false), + ObjectScope::Around => (true, true), + ObjectScope::AroundTrimmed => (true, false), + }; + + match self.maybe_pop_operator() { + Some(Operator::Change) => self.change_object(object, around, times, window, cx), + Some(Operator::Delete) => { + self.delete_object(object, around, whitespace, times, window, cx) + } + Some(Operator::Yank) => self.yank_object(object, around, times, window, cx), + Some(Operator::Indent) => { + self.indent_object(object, around, IndentDirection::In, times, window, cx) + } + Some(Operator::Outdent) => { + self.indent_object(object, around, IndentDirection::Out, times, window, cx) + } + Some(Operator::AutoIndent) => { + self.indent_object(object, around, IndentDirection::Auto, times, window, cx) + } + Some(Operator::ShellCommand) => { + self.shell_command_object(object, around, window, cx); + } + Some(Operator::Rewrap) => self.rewrap_object(object, around, times, window, cx), + Some(Operator::Lowercase) => self.convert_object( + object, + around, + ConvertTarget::LowerCase, + times, + window, + cx, + ), + Some(Operator::Uppercase) => self.convert_object( + object, + around, + ConvertTarget::UpperCase, + times, + window, + cx, + ), + Some(Operator::OppositeCase) => self.convert_object( + object, + around, + ConvertTarget::OppositeCase, + times, + window, + cx, + ), + Some(Operator::Rot13) => { + self.convert_object(object, around, ConvertTarget::Rot13, times, window, cx) + } + Some(Operator::Rot47) => { + self.convert_object(object, around, ConvertTarget::Rot47, times, window, cx) + } + Some(Operator::AddSurrounds { target: None }) => { + waiting_operator = Some(Operator::AddSurrounds { + target: Some(SurroundsType::Object(object, around)), + }); + } + Some(Operator::ToggleComments) => { + self.toggle_comments_object(object, around, times, window, cx) + } + Some(Operator::ReplaceWithRegister) => { + self.replace_with_register_object(object, around, window, cx) + } + Some(Operator::Exchange) => self.exchange_object(object, around, window, cx), + Some(Operator::HelixMatch) => { + self.select_current_object(object, around, window, cx) + } + _ => { + // Can't do anything for namespace operators. Ignoring + } } - }, + } Some(Operator::HelixNext { around }) => { self.select_next_object(object, around, window, cx); } diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index f9d6fb8d107449764b8db622c94d65fe746ed5d6..222403d9701dcccbf180b5f0f05c3c5ec464c757 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -3,7 +3,7 @@ use std::ops::Range; use crate::{ Vim, motion::right, - state::{Mode, Operator}, + state::{Mode, ObjectScope, Operator}, }; use editor::{ Bias, DisplayPoint, Editor, ToOffset, @@ -408,8 +408,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { if !matches!(vim.active_operator(), Some(Operator::Object { .. })) { vim.push_operator( Operator::Object { - around: true, - whitespace: true, + scope: ObjectScope::Around, }, window, cx, diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 1cf777b9319d94e6f309b25c456ad6d2a9e024ce..f6d6334201186151ccd7bc733760b0c5eef1fb02 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -87,8 +87,7 @@ pub enum Operator { Yank, Replace, Object { - around: bool, - whitespace: bool, + scope: ObjectScope, }, FindForward { before: bool, @@ -150,6 +149,28 @@ pub enum Operator { }, } +/// Controls how the object interacts with its delimiters and the surrounding +/// whitespace. +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum ObjectScope { + /// Inside the delimiters, excluding whitespace. + /// + /// Used by the `i` operator (e.g., `diw` for "delete inner word"). + /// Selects only the content between delimiters without including + /// the delimiters themselves or surrounding whitespace. + Inside, + /// Around the delimiters, including surrounding whitespace. + /// + /// Used by the `a` operator (e.g., `daw` for "delete a word"). + /// Selects the content, the delimiters, and any surrounding whitespace. + Around, + /// Around the delimiters, excluding surrounding whitespace. + /// + /// Similar to `Around`, but does not include whitespace adjacent to + /// the delimiters. + AroundTrimmed, +} + #[derive(Default, Clone, Debug)] pub enum RecordedSelection { #[default] @@ -997,8 +1018,12 @@ pub struct SearchState { impl Operator { pub fn id(&self) -> &'static str { match self { - Operator::Object { around: false, .. } => "i", - Operator::Object { around: true, .. } => "a", + Operator::Object { + scope: ObjectScope::Inside, + } => "i", + Operator::Object { + scope: ObjectScope::Around | ObjectScope::AroundTrimmed, + } => "a", Operator::Change => "c", Operator::Delete => "d", Operator::Yank => "y", diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 216f63d31ca3268092aa6e702f823590ce69016f..ae13c5af706f8f9a1cda4dd6238c51aba1b71115 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -42,7 +42,7 @@ use serde::Deserialize; pub use settings::{ ModeContent, Settings, SettingsStore, UseSystemClipboard, update_settings_file, }; -use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals}; +use state::{Mode, ObjectScope, Operator, RecordedSelection, SearchState, VimGlobals}; use std::{mem, ops::Range, sync::Arc}; use surrounds::SurroundsType; use theme::ThemeSettings; @@ -662,14 +662,13 @@ impl Vim { Vim::globals(cx).forced_motion = true; }); Vim::action(editor, cx, |vim, action: &PushObject, window, cx| { - vim.push_operator( - Operator::Object { - around: action.around, - whitespace: action.whitespace, - }, - window, - cx, - ) + let scope = match (action.around, action.whitespace) { + (false, _) => ObjectScope::Inside, + (true, true) => ObjectScope::Around, + (true, false) => ObjectScope::AroundTrimmed, + }; + + vim.push_operator(Operator::Object { scope }, window, cx) }); Vim::action(editor, cx, |vim, action: &PushFindForward, window, cx| { diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 74895d13edfe24edf623007ca0fbed3726d180e9..dab49aac81a4ad950d68c6fab61b130519be4bd0 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -17,7 +17,7 @@ use crate::{ Vim, motion::{Motion, MotionKind, first_non_whitespace, next_line_end, start_of_line}, object::Object, - state::{Mark, Mode, Operator}, + state::{Mark, Mode, ObjectScope, Operator}, }; actions!( @@ -426,7 +426,12 @@ impl Vim { window: &mut Window, cx: &mut Context, ) { - if let Some(Operator::Object { around, .. }) = self.active_operator() { + if let Some(Operator::Object { scope }) = self.active_operator() { + let around = match scope { + ObjectScope::Around | ObjectScope::AroundTrimmed => true, + ObjectScope::Inside => false, + }; + self.pop_operator(window, cx); let current_mode = self.mode; let target_mode = object.target_visual_mode(current_mode, around);