vim: Add AnyQuotes support for unified quote handling similar to mini.ai nvim (#22263)
### Edit 1: I tested it locally and it works! ### IMPORTANT: **Feedback and suggestions for improvement are greatly appreciated!** This commit introduces a new AnyQuotes text object to handle text surrounded by single quotes ('), double quotes ("), or back quotes (`) seamlessly. The following changes are included: - Added AnyQuotes to the Object enum to represent the new feature. - Registered AnyQuotes as an action in the actions! macro and register function to ensure proper integration with Vim actions like ci, ca, di, and da. - Extended Object::range to check for surrounding single, double, or back quotes sequentially. - Updated methods like is_multiline and always_expands_both_ways to ensure consistent behavior with other text objects. - Added support in surrounding_markers to evaluate any of the quote types when AnyQuotes is invoked. - This enhancement provides users with a flexible and unified way to interact with text objects enclosed by different types of quotes. Release Notes: - vim: Add `aq`/`iq` "any quote" text objects that are the smallest of `a"`, `a'` or <code>a`</code>
This commit is contained in:
parent
811b872f4e
commit
222b04548d
2 changed files with 152 additions and 0 deletions
|
@ -397,6 +397,7 @@
|
||||||
"'": "vim::Quotes",
|
"'": "vim::Quotes",
|
||||||
"`": "vim::BackQuotes",
|
"`": "vim::BackQuotes",
|
||||||
"\"": "vim::DoubleQuotes",
|
"\"": "vim::DoubleQuotes",
|
||||||
|
"q": "vim::AnyQuotes",
|
||||||
"|": "vim::VerticalBars",
|
"|": "vim::VerticalBars",
|
||||||
"(": "vim::Parentheses",
|
"(": "vim::Parentheses",
|
||||||
")": "vim::Parentheses",
|
")": "vim::Parentheses",
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub enum Object {
|
||||||
Paragraph,
|
Paragraph,
|
||||||
Quotes,
|
Quotes,
|
||||||
BackQuotes,
|
BackQuotes,
|
||||||
|
AnyQuotes,
|
||||||
DoubleQuotes,
|
DoubleQuotes,
|
||||||
VerticalBars,
|
VerticalBars,
|
||||||
Parentheses,
|
Parentheses,
|
||||||
|
@ -61,6 +62,7 @@ actions!(
|
||||||
Paragraph,
|
Paragraph,
|
||||||
Quotes,
|
Quotes,
|
||||||
BackQuotes,
|
BackQuotes,
|
||||||
|
AnyQuotes,
|
||||||
DoubleQuotes,
|
DoubleQuotes,
|
||||||
VerticalBars,
|
VerticalBars,
|
||||||
Parentheses,
|
Parentheses,
|
||||||
|
@ -96,6 +98,9 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||||
Vim::action(editor, cx, |vim, _: &BackQuotes, cx| {
|
Vim::action(editor, cx, |vim, _: &BackQuotes, cx| {
|
||||||
vim.object(Object::BackQuotes, cx)
|
vim.object(Object::BackQuotes, cx)
|
||||||
});
|
});
|
||||||
|
Vim::action(editor, cx, |vim, _: &AnyQuotes, cx| {
|
||||||
|
vim.object(Object::AnyQuotes, cx)
|
||||||
|
});
|
||||||
Vim::action(editor, cx, |vim, _: &DoubleQuotes, cx| {
|
Vim::action(editor, cx, |vim, _: &DoubleQuotes, cx| {
|
||||||
vim.object(Object::DoubleQuotes, cx)
|
vim.object(Object::DoubleQuotes, cx)
|
||||||
});
|
});
|
||||||
|
@ -156,6 +161,7 @@ impl Object {
|
||||||
Object::Word { .. }
|
Object::Word { .. }
|
||||||
| Object::Quotes
|
| Object::Quotes
|
||||||
| Object::BackQuotes
|
| Object::BackQuotes
|
||||||
|
| Object::AnyQuotes
|
||||||
| Object::VerticalBars
|
| Object::VerticalBars
|
||||||
| Object::DoubleQuotes => false,
|
| Object::DoubleQuotes => false,
|
||||||
Object::Sentence
|
Object::Sentence
|
||||||
|
@ -182,6 +188,7 @@ impl Object {
|
||||||
| Object::IndentObj { .. } => false,
|
| Object::IndentObj { .. } => false,
|
||||||
Object::Quotes
|
Object::Quotes
|
||||||
| Object::BackQuotes
|
| Object::BackQuotes
|
||||||
|
| Object::AnyQuotes
|
||||||
| Object::DoubleQuotes
|
| Object::DoubleQuotes
|
||||||
| Object::VerticalBars
|
| Object::VerticalBars
|
||||||
| Object::Parentheses
|
| Object::Parentheses
|
||||||
|
@ -200,6 +207,7 @@ impl Object {
|
||||||
Object::Word { .. }
|
Object::Word { .. }
|
||||||
| Object::Sentence
|
| Object::Sentence
|
||||||
| Object::Quotes
|
| Object::Quotes
|
||||||
|
| Object::AnyQuotes
|
||||||
| Object::BackQuotes
|
| Object::BackQuotes
|
||||||
| Object::DoubleQuotes => {
|
| Object::DoubleQuotes => {
|
||||||
if current_mode == Mode::VisualBlock {
|
if current_mode == Mode::VisualBlock {
|
||||||
|
@ -251,6 +259,35 @@ impl Object {
|
||||||
Object::BackQuotes => {
|
Object::BackQuotes => {
|
||||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '`', '`')
|
surrounding_markers(map, relative_to, around, self.is_multiline(), '`', '`')
|
||||||
}
|
}
|
||||||
|
Object::AnyQuotes => {
|
||||||
|
let quote_types = ['\'', '"', '`']; // Types of quotes to handle
|
||||||
|
let relative_offset = relative_to.to_offset(map, Bias::Left) as isize;
|
||||||
|
|
||||||
|
// Find the closest matching quote range
|
||||||
|
quote_types
|
||||||
|
.iter()
|
||||||
|
.flat_map(|"e| {
|
||||||
|
// Get ranges for each quote type
|
||||||
|
surrounding_markers(
|
||||||
|
map,
|
||||||
|
relative_to,
|
||||||
|
around,
|
||||||
|
self.is_multiline(),
|
||||||
|
quote,
|
||||||
|
quote,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.min_by_key(|range| {
|
||||||
|
// Calculate proximity of ranges to the cursor
|
||||||
|
let start_distance = (relative_offset
|
||||||
|
- range.start.to_offset(map, Bias::Left) as isize)
|
||||||
|
.abs();
|
||||||
|
let end_distance = (relative_offset
|
||||||
|
- range.end.to_offset(map, Bias::Right) as isize)
|
||||||
|
.abs();
|
||||||
|
start_distance + end_distance
|
||||||
|
})
|
||||||
|
}
|
||||||
Object::DoubleQuotes => {
|
Object::DoubleQuotes => {
|
||||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')
|
surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')
|
||||||
}
|
}
|
||||||
|
@ -1751,6 +1788,120 @@ mod test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_anyquotes_object(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = VimTestContext::new(cx, true).await;
|
||||||
|
|
||||||
|
const TEST_CASES: &[(&str, &str, &str, Mode)] = &[
|
||||||
|
// Single quotes
|
||||||
|
(
|
||||||
|
"c i q",
|
||||||
|
"This is a 'qˇuote' example.",
|
||||||
|
"This is a 'ˇ' example.",
|
||||||
|
Mode::Insert,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"c a q",
|
||||||
|
"This is a 'qˇuote' example.",
|
||||||
|
"This is a ˇexample.",
|
||||||
|
Mode::Insert,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"d i q",
|
||||||
|
"This is a 'qˇuote' example.",
|
||||||
|
"This is a 'ˇ' example.",
|
||||||
|
Mode::Normal,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"d a q",
|
||||||
|
"This is a 'qˇuote' example.",
|
||||||
|
"This is a ˇexample.",
|
||||||
|
Mode::Normal,
|
||||||
|
),
|
||||||
|
// Double quotes
|
||||||
|
(
|
||||||
|
"c i q",
|
||||||
|
"This is a \"qˇuote\" example.",
|
||||||
|
"This is a \"ˇ\" example.",
|
||||||
|
Mode::Insert,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"c a q",
|
||||||
|
"This is a \"qˇuote\" example.",
|
||||||
|
"This is a ˇexample.",
|
||||||
|
Mode::Insert,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"d i q",
|
||||||
|
"This is a \"qˇuote\" example.",
|
||||||
|
"This is a \"ˇ\" example.",
|
||||||
|
Mode::Normal,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"d a q",
|
||||||
|
"This is a \"qˇuote\" example.",
|
||||||
|
"This is a ˇexample.",
|
||||||
|
Mode::Normal,
|
||||||
|
),
|
||||||
|
// Back quotes
|
||||||
|
(
|
||||||
|
"c i q",
|
||||||
|
"This is a `qˇuote` example.",
|
||||||
|
"This is a `ˇ` example.",
|
||||||
|
Mode::Insert,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"c a q",
|
||||||
|
"This is a `qˇuote` example.",
|
||||||
|
"This is a ˇexample.",
|
||||||
|
Mode::Insert,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"d i q",
|
||||||
|
"This is a `qˇuote` example.",
|
||||||
|
"This is a `ˇ` example.",
|
||||||
|
Mode::Normal,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"d a q",
|
||||||
|
"This is a `qˇuote` example.",
|
||||||
|
"This is a ˇexample.",
|
||||||
|
Mode::Normal,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (keystrokes, initial_state, expected_state, expected_mode) in TEST_CASES {
|
||||||
|
cx.set_state(initial_state, Mode::Normal);
|
||||||
|
|
||||||
|
cx.simulate_keystrokes(keystrokes);
|
||||||
|
|
||||||
|
cx.assert_state(expected_state, *expected_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const INVALID_CASES: &[(&str, &str, Mode)] = &[
|
||||||
|
("c i q", "this is a 'qˇuote example.", Mode::Normal), // Missing closing simple quote
|
||||||
|
("c a q", "this is a 'qˇuote example.", Mode::Normal), // Missing closing simple quote
|
||||||
|
("d i q", "this is a 'qˇuote example.", Mode::Normal), // Missing closing simple quote
|
||||||
|
("d a q", "this is a 'qˇuote example.", Mode::Normal), // Missing closing simple quote
|
||||||
|
("c i q", "this is a \"qˇuote example.", Mode::Normal), // Missing closing double quote
|
||||||
|
("c a q", "this is a \"qˇuote example.", Mode::Normal), // Missing closing double quote
|
||||||
|
("d i q", "this is a \"qˇuote example.", Mode::Normal), // Missing closing double quote
|
||||||
|
("d a q", "this is a \"qˇuote example.", Mode::Normal), // Missing closing back quote
|
||||||
|
("c i q", "this is a `qˇuote example.", Mode::Normal), // Missing closing back quote
|
||||||
|
("c a q", "this is a `qˇuote example.", Mode::Normal), // Missing closing back quote
|
||||||
|
("d i q", "this is a `qˇuote example.", Mode::Normal), // Missing closing back quote
|
||||||
|
("d a q", "this is a `qˇuote example.", Mode::Normal), // Missing closing back quote
|
||||||
|
];
|
||||||
|
|
||||||
|
for (keystrokes, initial_state, mode) in INVALID_CASES {
|
||||||
|
cx.set_state(initial_state, Mode::Normal);
|
||||||
|
|
||||||
|
cx.simulate_keystrokes(keystrokes);
|
||||||
|
|
||||||
|
cx.assert_state(initial_state, *mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_tags(cx: &mut gpui::TestAppContext) {
|
async fn test_tags(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = VimTestContext::new_html(cx).await;
|
let mut cx = VimTestContext::new_html(cx).await;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue