mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 02:46:43 +00:00
vim: Add Subword Textobject (#22387)
Some checks are pending
CI / check_docs_only (push) Waiting to run
CI / Check Postgres and Protobuf migrations, mergability (push) Waiting to run
CI / Check formatting and spelling (push) Waiting to run
CI / (macOS) Run Clippy and tests (push) Blocked by required conditions
CI / (Linux) Run Clippy and tests (push) Blocked by required conditions
CI / (Linux) Build Remote Server (push) Blocked by required conditions
CI / (Windows) Run Clippy and tests (push) Blocked by required conditions
CI / Create a macOS bundle (push) Blocked by required conditions
CI / Create a Linux bundle (push) Blocked by required conditions
CI / Create arm64 Linux bundle (push) Blocked by required conditions
CI / Auto release preview (push) Blocked by required conditions
Deploy Docs / Deploy Docs (push) Waiting to run
Docs / Check formatting (push) Waiting to run
Script / ShellCheck Scripts (push) Waiting to run
Some checks are pending
CI / check_docs_only (push) Waiting to run
CI / Check Postgres and Protobuf migrations, mergability (push) Waiting to run
CI / Check formatting and spelling (push) Waiting to run
CI / (macOS) Run Clippy and tests (push) Blocked by required conditions
CI / (Linux) Run Clippy and tests (push) Blocked by required conditions
CI / (Linux) Build Remote Server (push) Blocked by required conditions
CI / (Windows) Run Clippy and tests (push) Blocked by required conditions
CI / Create a macOS bundle (push) Blocked by required conditions
CI / Create a Linux bundle (push) Blocked by required conditions
CI / Create arm64 Linux bundle (push) Blocked by required conditions
CI / Auto release preview (push) Blocked by required conditions
Deploy Docs / Deploy Docs (push) Waiting to run
Docs / Check formatting (push) Waiting to run
Script / ShellCheck Scripts (push) Waiting to run
Closes #22761 [Vim: subword text object? #22280](https://github.com/zed-industries/zed/discussions/22280) Release Notes: - Added Vim SubWord TextObject --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
03c99e39f9
commit
26be440d99
2 changed files with 119 additions and 1 deletions
|
@ -391,6 +391,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",
|
||||
|
|
|
@ -20,6 +20,7 @@ use serde::Deserialize;
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]
|
||||
pub enum Object {
|
||||
Word { ignore_punctuation: bool },
|
||||
Subword { ignore_punctuation: bool },
|
||||
Sentence,
|
||||
Paragraph,
|
||||
Quotes,
|
||||
|
@ -46,6 +47,12 @@ struct Word {
|
|||
ignore_punctuation: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Subword {
|
||||
#[serde(default)]
|
||||
ignore_punctuation: bool,
|
||||
}
|
||||
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct IndentObj {
|
||||
|
@ -53,7 +60,7 @@ struct IndentObj {
|
|||
include_below: bool,
|
||||
}
|
||||
|
||||
impl_actions!(vim, [Word, IndentObj]);
|
||||
impl_actions!(vim, [Word, Subword, IndentObj]);
|
||||
|
||||
actions!(
|
||||
vim,
|
||||
|
@ -85,6 +92,13 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
|||
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)
|
||||
|
@ -159,6 +173,7 @@ impl Object {
|
|||
pub fn is_multiline(self) -> bool {
|
||||
match self {
|
||||
Object::Word { .. }
|
||||
| Object::Subword { .. }
|
||||
| Object::Quotes
|
||||
| Object::BackQuotes
|
||||
| Object::AnyQuotes
|
||||
|
@ -182,6 +197,7 @@ impl Object {
|
|||
pub fn always_expands_both_ways(self) -> bool {
|
||||
match self {
|
||||
Object::Word { .. }
|
||||
| Object::Subword { .. }
|
||||
| Object::Sentence
|
||||
| Object::Paragraph
|
||||
| Object::Argument
|
||||
|
@ -205,6 +221,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::AnyQuotes
|
||||
|
@ -251,6 +268,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 => {
|
||||
|
@ -387,6 +411,63 @@ fn in_word(
|
|||
Some(start..end)
|
||||
}
|
||||
|
||||
fn in_subword(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
ignore_punctuation: bool,
|
||||
) -> Option<Range<DisplayPoint>> {
|
||||
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,
|
||||
|
@ -498,6 +579,40 @@ fn around_word(
|
|||
}
|
||||
}
|
||||
|
||||
fn around_subword(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
ignore_punctuation: bool,
|
||||
) -> Option<Range<DisplayPoint>> {
|
||||
// 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,
|
||||
|
|
Loading…
Reference in a new issue