@@ -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;