editor: Expand selection to word under cursor before expanding to next enclosing syntax node (#28864)
Closes #27995 For strings in any language and Markdown, `select_larger_syntax_node` will first select the word and then expand from there if: - The cursor is on the word. - The selection is inside the word. It will not select the word and will directly proceed to expand if: - The word is already selected. - Multiple partial words are selected. Todo: - [x] Tests Release Notes: - Fixed `select_larger_syntax_node` to first expand to the word within a string, and then to the larger syntax node.
This commit is contained in:
parent
cb79420773
commit
0d8f77b5de
2 changed files with 220 additions and 1 deletions
|
@ -12519,6 +12519,45 @@ impl Editor {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|selection| {
|
.map(|selection| {
|
||||||
let old_range = selection.start..selection.end;
|
let old_range = selection.start..selection.end;
|
||||||
|
|
||||||
|
if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
|
||||||
|
// manually select word at selection
|
||||||
|
if ["string_content", "inline"].contains(&node.kind()) {
|
||||||
|
let word_range = {
|
||||||
|
let display_point = buffer
|
||||||
|
.offset_to_point(old_range.start)
|
||||||
|
.to_display_point(&display_map);
|
||||||
|
let Range { start, end } =
|
||||||
|
movement::surrounding_word(&display_map, display_point);
|
||||||
|
start.to_point(&display_map).to_offset(&buffer)
|
||||||
|
..end.to_point(&display_map).to_offset(&buffer)
|
||||||
|
};
|
||||||
|
// ignore if word is already selected
|
||||||
|
if !word_range.is_empty() && old_range != word_range {
|
||||||
|
let last_word_range = {
|
||||||
|
let display_point = buffer
|
||||||
|
.offset_to_point(old_range.end)
|
||||||
|
.to_display_point(&display_map);
|
||||||
|
let Range { start, end } =
|
||||||
|
movement::surrounding_word(&display_map, display_point);
|
||||||
|
start.to_point(&display_map).to_offset(&buffer)
|
||||||
|
..end.to_point(&display_map).to_offset(&buffer)
|
||||||
|
};
|
||||||
|
// only select word if start and end point belongs to same word
|
||||||
|
if word_range == last_word_range {
|
||||||
|
selected_larger_node = true;
|
||||||
|
return Selection {
|
||||||
|
id: selection.id,
|
||||||
|
start: word_range.start,
|
||||||
|
end: word_range.end,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
reversed: selection.reversed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut new_range = old_range.clone();
|
let mut new_range = old_range.clone();
|
||||||
let mut new_node = None;
|
let mut new_node = None;
|
||||||
while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
|
while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
|
||||||
|
|
|
@ -6309,7 +6309,187 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
|
||||||
use mod1::mod2::«{mod3, mod4}ˇ»;
|
use mod1::mod2::«{mod3, mod4}ˇ»;
|
||||||
|
|
||||||
fn fn_1«ˇ(param1: bool, param2: &str)» {
|
fn fn_1«ˇ(param1: bool, param2: &str)» {
|
||||||
«ˇlet var1 = "text";»
|
let var1 = "«ˇtext»";
|
||||||
|
}
|
||||||
|
"#},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig::default(),
|
||||||
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
|
));
|
||||||
|
|
||||||
|
let text = r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
let var1 = "hello world";
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
|
||||||
|
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
|
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
|
||||||
|
|
||||||
|
editor
|
||||||
|
.condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Test 1: Cursor on a letter of a string word
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
editor.change_selections(None, window, cx, |s| {
|
||||||
|
s.select_display_ranges([
|
||||||
|
DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
assert_text_with_selections(
|
||||||
|
editor,
|
||||||
|
indoc! {r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
let var1 = "hˇello world";
|
||||||
|
}
|
||||||
|
"#},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
|
||||||
|
assert_text_with_selections(
|
||||||
|
editor,
|
||||||
|
indoc! {r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
let var1 = "«ˇhello» world";
|
||||||
|
}
|
||||||
|
"#},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2: Partial selection within a word
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
editor.change_selections(None, window, cx, |s| {
|
||||||
|
s.select_display_ranges([
|
||||||
|
DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
assert_text_with_selections(
|
||||||
|
editor,
|
||||||
|
indoc! {r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
let var1 = "h«elˇ»lo world";
|
||||||
|
}
|
||||||
|
"#},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
|
||||||
|
assert_text_with_selections(
|
||||||
|
editor,
|
||||||
|
indoc! {r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
let var1 = "«ˇhello» world";
|
||||||
|
}
|
||||||
|
"#},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 3: Complete word already selected
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
editor.change_selections(None, window, cx, |s| {
|
||||||
|
s.select_display_ranges([
|
||||||
|
DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
assert_text_with_selections(
|
||||||
|
editor,
|
||||||
|
indoc! {r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
let var1 = "«helloˇ» world";
|
||||||
|
}
|
||||||
|
"#},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
|
||||||
|
assert_text_with_selections(
|
||||||
|
editor,
|
||||||
|
indoc! {r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
let var1 = "«hello worldˇ»";
|
||||||
|
}
|
||||||
|
"#},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 4: Selection spanning across words
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
editor.change_selections(None, window, cx, |s| {
|
||||||
|
s.select_display_ranges([
|
||||||
|
DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
assert_text_with_selections(
|
||||||
|
editor,
|
||||||
|
indoc! {r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
let var1 = "hel«lo woˇ»rld";
|
||||||
|
}
|
||||||
|
"#},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
|
||||||
|
assert_text_with_selections(
|
||||||
|
editor,
|
||||||
|
indoc! {r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
let var1 = "«ˇhello world»";
|
||||||
|
}
|
||||||
|
"#},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 5: Expansion beyond string
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
|
||||||
|
editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
|
||||||
|
assert_text_with_selections(
|
||||||
|
editor,
|
||||||
|
indoc! {r#"
|
||||||
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
fn fn_1(param1: bool, param2: &str) {
|
||||||
|
«ˇlet var1 = "hello world";»
|
||||||
}
|
}
|
||||||
"#},
|
"#},
|
||||||
cx,
|
cx,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue