From 56bd762ac7e6fef32c421bab2b0800173ad7a914 Mon Sep 17 00:00:00 2001 From: dino Date: Tue, 28 Oct 2025 12:52:38 +0000 Subject: [PATCH] chore(vim): introduce enum to control object scope In order to avoid having to pass around the values that determine whether an operator should be applied to inside the object or include the object as well as the surrounding whitespace, this commit introduces a new enum, `vim::state::ObjectScope`, which we'll use to pass this information around. As such, `vim::state::Operator::Object` has already been updated so that it now only has a `scope` field with the `ObejctScope`. Existing functionality has been migrated so as to calculate the `around` and `whitespace` values from this scope, but a future commit will update it so that all dependencies start operating on the `ObjectScope` instead. --- crates/vim/src/normal.rs | 134 ++++++++++++++++++++++----------------- crates/vim/src/object.rs | 5 +- crates/vim/src/state.rs | 33 ++++++++-- crates/vim/src/vim.rs | 17 +++-- crates/vim/src/visual.rs | 9 ++- 5 files changed, 122 insertions(+), 76 deletions(-) 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);