From bdd0607e05919e9e504150bf8c0e15485467e36d Mon Sep 17 00:00:00 2001 From: myself <2467315534@qq.com> Date: Wed, 10 Jun 2026 16:16:23 +0800 Subject: [PATCH] Add action to select inside enclosing brackets --- crates/editor/src/actions.rs | 2 + crates/editor/src/editor_tests.rs | 82 +++++++++++++++++++++++++++++++ crates/editor/src/element.rs | 1 + crates/editor/src/selection.rs | 37 ++++++++++++++ 4 files changed, 122 insertions(+) diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index e4e280851d54ef..906f4b00f26398 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -773,6 +773,8 @@ 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_tests.rs b/crates/editor/src/editor_tests.rs index b476b9bcdabd2c..10dee6038d6d96 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -21266,6 +21266,88 @@ 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, |_| {}); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e3d634b4a0a459..ec693e96f01689 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -334,6 +334,7 @@ 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::undo_selection); register_action(editor, window, Editor::redo_selection); diff --git a/crates/editor/src/selection.rs b/crates/editor/src/selection.rs index e6c5a8bb1fbe56..d3a4b80cd1e741 100644 --- a/crates/editor/src/selection.rs +++ b/crates/editor/src/selection.rs @@ -966,6 +966,43 @@ impl Editor { self.select_to_syntax_nodes(window, cx, true); } + pub fn select_inside_enclosing_bracket( + &mut self, + _: &SelectInsideEnclosingBracket, + window: &mut Window, + cx: &mut Context, + ) { + self.change_selections(Default::default(), window, cx, |s| { + s.move_offsets_with(&mut |snapshot, selection| { + let Some(enclosing_bracket_ranges) = + snapshot.enclosing_bracket_ranges(selection.start..selection.end) + else { + return; + }; + + let mut best = None; + 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) { + continue; + } + + let length = close.end - open.start; + if length < best_length { + best_length = length; + best = Some(inside); + } + } + + if let Some(inside) = best { + selection.set_head_tail(inside.end, inside.start, SelectionGoal::None); + } + }) + }); + } + pub fn move_to_enclosing_bracket( &mut self, _: &MoveToEnclosingBracket,