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
|
@ -25,6 +25,7 @@ pub enum Object {
|
|||
Paragraph,
|
||||
Quotes,
|
||||
BackQuotes,
|
||||
AnyQuotes,
|
||||
DoubleQuotes,
|
||||
VerticalBars,
|
||||
Parentheses,
|
||||
|
@ -61,6 +62,7 @@ actions!(
|
|||
Paragraph,
|
||||
Quotes,
|
||||
BackQuotes,
|
||||
AnyQuotes,
|
||||
DoubleQuotes,
|
||||
VerticalBars,
|
||||
Parentheses,
|
||||
|
@ -96,6 +98,9 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
|||
Vim::action(editor, cx, |vim, _: &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.object(Object::DoubleQuotes, cx)
|
||||
});
|
||||
|
@ -156,6 +161,7 @@ impl Object {
|
|||
Object::Word { .. }
|
||||
| Object::Quotes
|
||||
| Object::BackQuotes
|
||||
| Object::AnyQuotes
|
||||
| Object::VerticalBars
|
||||
| Object::DoubleQuotes => false,
|
||||
Object::Sentence
|
||||
|
@ -182,6 +188,7 @@ impl Object {
|
|||
| Object::IndentObj { .. } => false,
|
||||
Object::Quotes
|
||||
| Object::BackQuotes
|
||||
| Object::AnyQuotes
|
||||
| Object::DoubleQuotes
|
||||
| Object::VerticalBars
|
||||
| Object::Parentheses
|
||||
|
@ -200,6 +207,7 @@ impl Object {
|
|||
Object::Word { .. }
|
||||
| Object::Sentence
|
||||
| Object::Quotes
|
||||
| Object::AnyQuotes
|
||||
| Object::BackQuotes
|
||||
| Object::DoubleQuotes => {
|
||||
if current_mode == Mode::VisualBlock {
|
||||
|
@ -251,6 +259,35 @@ impl Object {
|
|||
Object::BackQuotes => {
|
||||
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 => {
|
||||
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]
|
||||
async fn test_tags(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new_html(cx).await;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue