From 7aede4a3488d60781f1cb3ff5db8e9ba2ca0b3e0 Mon Sep 17 00:00:00 2001 From: 0x2CA <2478557459@qq.com> Date: Tue, 24 Dec 2024 11:47:02 +0800 Subject: [PATCH] add subword --- assets/keymaps/vim.json | 3 + crates/vim/src/object.rs | 117 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 417916db4d6ca..7c1067df293f1 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -389,6 +389,9 @@ "bindings": { "w": "vim::Word", "shift-w": ["vim::Word", { "ignorePunctuation": true }], + // Subword TextObject + // "w": "vim::Subword", + // "shift-w": ["vim::Subword", { "ignorePunctuation": true }], "t": "vim::Tag", "s": "vim::Sentence", "p": "vim::Paragraph", diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index c63cb0e843066..c95139bd9d442 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -21,6 +21,7 @@ use serde::Deserialize; #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] pub enum Object { Word { ignore_punctuation: bool }, + Subword { ignore_punctuation: bool }, Sentence, Paragraph, Quotes, @@ -47,12 +48,18 @@ struct Word { } #[derive(Clone, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] +struct Subword { + #[serde(default)] + ignore_punctuation: bool, +} +#[derive(Clone, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] struct IndentObj { #[serde(default)] include_below: bool, } -impl_actions!(vim, [Word, IndentObj]); +impl_actions!(vim, [Word, Subword, IndentObj]); actions!( vim, @@ -83,6 +90,13 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext) { vim.object(Object::Word { ignore_punctuation }, cx) }, ); + Vim::action( + editor, + cx, + |vim, &Subword { ignore_punctuation }: &Subword, cx| { + vim.object(Object::Subword { ignore_punctuation }, cx) + }, + ); Vim::action(editor, cx, |vim, _: &Tag, cx| vim.object(Object::Tag, cx)); Vim::action(editor, cx, |vim, _: &Sentence, cx| { vim.object(Object::Sentence, cx) @@ -154,6 +168,7 @@ impl Object { pub fn is_multiline(self) -> bool { match self { Object::Word { .. } + | Object::Subword { .. } | Object::Quotes | Object::BackQuotes | Object::VerticalBars @@ -176,6 +191,7 @@ impl Object { pub fn always_expands_both_ways(self) -> bool { match self { Object::Word { .. } + | Object::Subword { .. } | Object::Sentence | Object::Paragraph | Object::Argument @@ -198,6 +214,7 @@ impl Object { pub fn target_visual_mode(self, current_mode: Mode, around: bool) -> Mode { match self { Object::Word { .. } + | Object::Subword { .. } | Object::Sentence | Object::Quotes | Object::BackQuotes @@ -243,6 +260,13 @@ impl Object { in_word(map, relative_to, ignore_punctuation) } } + Object::Subword { ignore_punctuation } => { + if around { + around_subword(map, relative_to, ignore_punctuation) + } else { + in_subword(map, relative_to, ignore_punctuation) + } + } Object::Sentence => sentence(map, relative_to, around), Object::Paragraph => paragraph(map, relative_to, around), Object::Quotes => { @@ -350,6 +374,63 @@ fn in_word( Some(start..end) } +fn in_subword( + map: &DisplaySnapshot, + relative_to: DisplayPoint, + ignore_punctuation: bool, +) -> Option> { + let offset = relative_to.to_offset(map, Bias::Left); + // Use motion::right so that we consider the character under the cursor when looking for the start + let classifier = map + .buffer_snapshot + .char_classifier_at(relative_to.to_point(map)) + .ignore_punctuation(ignore_punctuation); + let in_subword = map + .buffer_chars_at(offset) + .next() + .map(|(c, _)| { + if classifier.is_word('-') { + !classifier.is_whitespace(c) && c != '_' && c != '-' + } else { + !classifier.is_whitespace(c) && c != '_' + } + }) + .unwrap_or(false); + + let start = if in_subword { + movement::find_preceding_boundary_display_point( + map, + right(map, relative_to, 1), + movement::FindRange::SingleLine, + |left, right| { + let is_word_start = classifier.kind(left) != classifier.kind(right); + let is_subword_start = classifier.is_word('-') && left == '-' && right != '-' + || left == '_' && right != '_' + || left.is_lowercase() && right.is_uppercase(); + is_word_start || is_subword_start + }, + ) + } else { + movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| { + let is_word_start = classifier.kind(left) != classifier.kind(right); + let is_subword_start = classifier.is_word('-') && left == '-' && right != '-' + || left == '_' && right != '_' + || left.is_lowercase() && right.is_uppercase(); + is_word_start || is_subword_start + }) + }; + + let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| { + let is_word_end = classifier.kind(left) != classifier.kind(right); + let is_subword_end = classifier.is_word('-') && left != '-' && right == '-' + || left != '_' && right == '_' + || left.is_lowercase() && right.is_uppercase(); + is_word_end || is_subword_end + }); + + Some(start..end) +} + pub fn surrounding_html_tag( map: &DisplaySnapshot, head: DisplayPoint, @@ -461,6 +542,40 @@ fn around_word( } } +fn around_subword( + map: &DisplaySnapshot, + relative_to: DisplayPoint, + ignore_punctuation: bool, +) -> Option> { + // Use motion::right so that we consider the character under the cursor when looking for the start + let classifier = map + .buffer_snapshot + .char_classifier_at(relative_to.to_point(map)) + .ignore_punctuation(ignore_punctuation); + let start = movement::find_preceding_boundary_display_point( + map, + right(map, relative_to, 1), + movement::FindRange::SingleLine, + |left, right| { + let is_word_start = classifier.kind(left) != classifier.kind(right); + let is_subword_start = classifier.is_word('-') && left != '-' && right == '-' + || left != '_' && right == '_' + || left.is_lowercase() && right.is_uppercase(); + is_word_start || is_subword_start + }, + ); + + let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| { + let is_word_end = classifier.kind(left) != classifier.kind(right); + let is_subword_end = classifier.is_word('-') && left != '-' && right == '-' + || left != '_' && right == '_' + || left.is_lowercase() && right.is_uppercase(); + is_word_end || is_subword_end + }); + + Some(start..end) +} + fn around_containing_word( map: &DisplaySnapshot, relative_to: DisplayPoint,