diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 830333706e..2f7ee8aea5 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -654,11 +654,16 @@ actions!( MoveToBeginning, /// Moves cursor to the enclosing bracket. MoveToEnclosingBracket, - /// Selects the content inside the nearest enclosing delimiters - /// (brackets, braces, parentheses, or quotes). + /// Selects the content within the nearest enclosing delimiters + /// (brackets, braces, parentheses, or quotes), excluding the + /// delimiters. + /// Repeating the action expands the selection to the next enclosing + /// pair. SelectInsideDelimiters, - /// Selects the content inside the nearest surrounding delimiters, - /// including the delimiters themselves. + /// Selects the nearest enclosing delimiters (brackets, braces, + /// parentheses, or quotes) together with the content between them. + /// Repeating the action expands the selection to the next enclosing + /// pair. SelectAroundDelimiters, /// Moves cursor to the end of the document. MoveToEnd, @@ -779,8 +784,6 @@ actions!( SelectDown, /// Selects the enclosing symbol. SelectEnclosingSymbol, - /// Selects inside the innermost enclosing bracket pair. - SelectInsideEnclosingBracket, /// Selects to the start of the next larger syntax node. SelectToStartOfLargerSyntaxNode, /// Selects to the end of the next larger syntax node. diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c45e298f5c..e165c765f2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7529,51 +7529,6 @@ impl Editor { } } - pub fn select_inside_delimiters( - &mut self, - _: &SelectInsideDelimiters, - window: &mut Window, - cx: &mut Context, - ) { - self.select_delimiters_impl(false, window, cx); - } - - pub fn select_around_delimiters( - &mut self, - _: &SelectAroundDelimiters, - window: &mut Window, - cx: &mut Context, - ) { - self.select_delimiters_impl(true, window, cx); - } - - fn select_delimiters_impl( - &mut self, - include_brackets: bool, - window: &mut Window, - cx: &mut Context, - ) { - self.change_selections(Default::default(), window, cx, |s| { - s.move_offsets_with(&mut |snapshot, selection| { - let Some((open, close)) = snapshot - .innermost_enclosing_bracket_ranges(selection.start..selection.end, None) - else { - return; - }; - - let (start, end) = if include_brackets { - (open.start, close.end) - } else { - (open.end, close.start) - }; - selection.start = start; - selection.end = end; - selection.reversed = false; - selection.goal = SelectionGoal::None; - }) - }); - } - pub fn rename( &mut self, _: &Rename, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 1305a2ad4a..21b62d96ab 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -21303,88 +21303,6 @@ async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) { ); } -#[gpui::test] -async fn test_select_inside_enclosing_bracket(cx: &mut TestAppContext) { - init_test(cx, |_| {}); - - let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await; - - #[track_caller] - fn assert_after_runs(before: &str, after: &str, runs: usize, cx: &mut EditorLspTestContext) { - let _state_context = cx.set_state(before); - cx.run_until_parked(); - for _ in 0..runs { - cx.update_editor(|editor, window, cx| { - editor.select_inside_enclosing_bracket(&SelectInsideEnclosingBracket, window, cx) - }); - } - cx.run_until_parked(); - cx.assert_editor_state(after); - } - - #[track_caller] - fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) { - assert_after_runs(before, after, 1, cx); - } - - assert("console.log(ˇvar);", "console.log(«varˇ»);", &mut cx); - assert("console.logˇ(var);", "console.log(«varˇ»);", &mut cx); - assert("console.log(var)ˇ;", "console.log(«varˇ»);", &mut cx); - assert( - "let numbers = [1, ˇ2, 3];", - "let numbers = [«1, 2, 3ˇ»];", - &mut cx, - ); - assert( - "const object = { foo: ˇbar };", - "const object = {« foo: bar ˇ»};", - &mut cx, - ); - assert( - r#"const doubleQuoted = "foo ˇbar";"#, - r#"const doubleQuoted = "«foo barˇ»";"#, - &mut cx, - ); - assert( - "const singleQuoted = 'foo ˇbar';", - "const singleQuoted = '«foo barˇ»';", - &mut cx, - ); - assert( - "const template = `foo ˇbar`;", - "const template = `«foo barˇ»`;", - &mut cx, - ); - assert( - "let result = foo(bar(ˇbaz));", - "let result = foo(bar(«bazˇ»));", - &mut cx, - ); - assert( - "let result = foo(«barˇ»(baz));", - "let result = foo(«bar(baz)ˇ»);", - &mut cx, - ); - assert_after_runs( - "let result = foo(bar(ˇbaz));", - "let result = foo(«bar(baz)ˇ»);", - 2, - &mut cx, - ); - assert_after_runs( - r#"let result = (xx[xxx{xxˇx}] xx"xxx"xx);"#, - r#"let result = («xx[xxx{xxx}] xx"xxx"xxˇ»);"#, - 3, - &mut cx, - ); - assert("let plain = ˇvalue;", "let plain = ˇvalue;", &mut cx); - assert( - "foo(ˇone); bar(ˇtwo);", - "foo(«oneˇ»); bar(«twoˇ»);", - &mut cx, - ); -} - #[gpui::test] async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -38423,17 +38341,13 @@ async fn test_toggle_markdown_block_quote(cx: &mut TestAppContext) { #[track_caller] fn assert_select_delimiters(around: bool, before: &str, after: &str, cx: &mut EditorTestContext) { let _state_context = cx.set_state(before); - cx.run_until_parked(); - cx.update_editor(|editor, window, cx| { - if around { - editor.select_around_delimiters(&SelectAroundDelimiters, window, cx); - } else { - editor.select_inside_delimiters(&SelectInsideDelimiters, window, cx); - } - }); + if around { + cx.dispatch_action(SelectAroundDelimiters); + } else { + cx.dispatch_action(SelectInsideDelimiters); + } - cx.run_until_parked(); cx.assert_editor_state(after); } @@ -38572,3 +38486,31 @@ async fn test_select_delimiters_in_markdown(cx: &mut TestAppContext) { &mut cx, ); } + +#[gpui::test] +async fn test_select_delimiters_expansion(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await; + + let _state_context = cx.set_state("foo([1, ˇ2, 3]);"); + cx.dispatch_action(SelectInsideDelimiters); + cx.assert_editor_state("foo([«1, 2, 3ˇ»]);"); + cx.dispatch_action(SelectInsideDelimiters); + cx.assert_editor_state("foo(«[1, 2, 3]ˇ»);"); + + let _state_context = cx.set_state("foo([1, ˇ2, 3]);"); + cx.dispatch_action(SelectInsideDelimiters); + cx.assert_editor_state("foo([«1, 2, 3ˇ»]);"); + cx.dispatch_action(SelectAroundDelimiters); + cx.assert_editor_state("foo(«[1, 2, 3]ˇ»);"); + cx.dispatch_action(SelectAroundDelimiters); + cx.assert_editor_state("foo«([1, 2, 3])ˇ»;"); + + let _state_context = cx.set_state("foo(x, { ˇa: 1 });"); + cx.dispatch_action(SelectInsideDelimiters); + cx.assert_editor_state("foo(x, {« a: 1 ˇ»});"); + cx.dispatch_action(SelectAroundDelimiters); + cx.assert_editor_state("foo(x, «{ a: 1 }ˇ»);"); + cx.dispatch_action(SelectInsideDelimiters); + cx.assert_editor_state("foo(«x, { a: 1 }ˇ»);"); +} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7cfd1b9ce7..06717e8978 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -335,7 +335,6 @@ impl EditorElement { register_action(editor, window, Editor::move_to_start_of_larger_syntax_node); register_action(editor, window, Editor::move_to_end_of_larger_syntax_node); register_action(editor, window, Editor::select_enclosing_symbol); - register_action(editor, window, Editor::select_inside_enclosing_bracket); register_action(editor, window, Editor::move_to_enclosing_bracket); register_action(editor, window, Editor::select_inside_delimiters); register_action(editor, window, Editor::select_around_delimiters); diff --git a/crates/editor/src/selection.rs b/crates/editor/src/selection.rs index 11892a39a7..1d5ba4b526 100644 --- a/crates/editor/src/selection.rs +++ b/crates/editor/src/selection.rs @@ -966,9 +966,27 @@ impl Editor { self.select_to_syntax_nodes(window, cx, true); } - pub fn select_inside_enclosing_bracket( + pub fn select_inside_delimiters( &mut self, - _: &SelectInsideEnclosingBracket, + _: &SelectInsideDelimiters, + window: &mut Window, + cx: &mut Context, + ) { + self.select_delimiters_impl(false, window, cx); + } + + pub fn select_around_delimiters( + &mut self, + _: &SelectAroundDelimiters, + window: &mut Window, + cx: &mut Context, + ) { + self.select_delimiters_impl(true, window, cx); + } + + fn select_delimiters_impl( + &mut self, + include_brackets: bool, window: &mut Window, cx: &mut Context, ) { @@ -984,20 +1002,28 @@ impl Editor { let mut best_length = usize::MAX; for (open, close) in enclosing_bracket_ranges { - let inside = open.end..close.start; - if inside == (selection.start..selection.end) { + let range = if include_brackets { + open.start..close.end + } else { + open.end..close.start + }; + + // Skip any bracket pair that is already covered by the + // selection so repeated uses of delimiters selection only + // evere expands outwards to the next pair. + if (selection.start..selection.end).contains_inclusive(&range) { continue; } let length = close.end - open.start; if length < best_length { best_length = length; - best = Some(inside); + best = Some(range); } } - if let Some(inside) = best { - selection.set_head_tail(inside.end, inside.start, SelectionGoal::None); + if let Some(range) = best { + selection.set_head_tail(range.end, range.start, SelectionGoal::None); } }) });