Refactor editor's select inside/around delimiter actions (#59472)
# Objective Both https://github.com/zed-industries/zed/pull/59005 and https://github.com/zed-industries/zed/pull/53789 introduced actions, at the editor context, which allow to select the content between brackets/delimiters, with the latter also adding an action to include the brackets/delimiters too. Shipping both would mean we end up with three distinct, but similar actions: * `editor: select inside enclosing bracket` * `editor: select inside delimiters` * `editor: select around delimiters` As such, in order to simplify this, the changes in this Pull Request remove the first action, while updating the logic for both `editor: select inside delimiters` and `editor: select around delimiters` in order to support continuous outwards expansion, like `editor: select inside enclosing bracket` supported. ## Solution * Remove `editor::SelectInsideEnclosingBracket` action, it's related method and tests * Updated `editor::selection::Editor::select_delimiters_impl` to use `MultiBufferSnapshot::enclosing_bracket_ranges` instead of `MultiBufferSnapshot::innermost_enclosing_bracket_ranges` so we can traverse all bracket ranges and expand to the next one ## Testing Besides manual testing, a new test case was added specifically for the selection expansion support, namely `editor::editor_tests::test_select_delimiters_expansion`. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - N/A
This commit is contained in:
5 files changed
+75
-150
No files matched your search
@@ -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.
|
||||
|
||||
@@ -7529,51 +7529,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_inside_delimiters(
|
||||
&mut self,
|
||||
_: &SelectInsideDelimiters,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.select_delimiters_impl(false, window, cx);
|
||||
}
|
||||
|
||||
pub fn select_around_delimiters(
|
||||
&mut self,
|
||||
_: &SelectAroundDelimiters,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.select_delimiters_impl(true, window, cx);
|
||||
}
|
||||
|
||||
fn select_delimiters_impl(
|
||||
&mut self,
|
||||
include_brackets: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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,
|
||||
|
||||
@@ -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 }ˇ»);");
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>,
|
||||
) {
|
||||
self.select_delimiters_impl(false, window, cx);
|
||||
}
|
||||
|
||||
pub fn select_around_delimiters(
|
||||
&mut self,
|
||||
_: &SelectAroundDelimiters,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.select_delimiters_impl(true, window, cx);
|
||||
}
|
||||
|
||||
fn select_delimiters_impl(
|
||||
&mut self,
|
||||
include_brackets: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -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);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user