diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c587a61620..c5fce0da01 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -12519,6 +12519,45 @@ impl Editor { .iter() .map(|selection| { 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_node = None; while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone()) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 3d55e20a8a..c1f154a4f1 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -6309,7 +6309,187 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) { use mod1::mod2::«{mod3, mod4}ˇ»; 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::(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,