From 22148a3639096dc3c004d41370a1a0bd99f865fa Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 23 Mar 2022 15:37:28 -0700 Subject: [PATCH] Fix extending selections starting at ends of other nodes Fixes #478 --- crates/language/src/buffer.rs | 12 +++++++++-- crates/language/src/tests.rs | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index f32028002a..28f2c0510d 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1621,8 +1621,13 @@ impl BufferSnapshot { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut cursor = tree.root_node().walk(); - // Descend to smallest leaf that touches or exceeds the start of the range. - while cursor.goto_first_child_for_byte(range.start).is_some() {} + // Descend to the first leaf that touches the start of the range, + // and if the range is non-empty, extends beyond the start. + while cursor.goto_first_child_for_byte(range.start).is_some() { + if !range.is_empty() && cursor.node().end_byte() == range.start { + cursor.goto_next_sibling(); + } + } // Ascend to the smallest ancestor that strictly contains the range. loop { @@ -1656,6 +1661,9 @@ impl BufferSnapshot { } } + // If there is a candidate node on both sides of the (empty) range, then + // decide between the two by favoring a named node over an anonymous token. + // If both nodes are the same in that regard, favor the right one. if let Some(right_node) = right_node { if right_node.is_named() || !left_node.is_named() { return Some(right_node.byte_range()); diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index d36771c44f..19b1a1cf73 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -458,6 +458,44 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { ); } +#[gpui::test] +fn test_range_for_syntax_ancestor(cx: &mut MutableAppContext) { + cx.add_model(|cx| { + let text = "fn a() { b(|c| {}) }"; + let buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + let snapshot = buffer.snapshot(); + + assert_eq!( + snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")), + Some(range_of(text, "|")) + ); + assert_eq!( + snapshot.range_for_syntax_ancestor(range_of(text, "|")), + Some(range_of(text, "|c|")) + ); + assert_eq!( + snapshot.range_for_syntax_ancestor(range_of(text, "|c|")), + Some(range_of(text, "|c| {}")) + ); + assert_eq!( + snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")), + Some(range_of(text, "(|c| {})")) + ); + + buffer + }); + + fn empty_range_at(text: &str, part: &str) -> Range { + let start = text.find(part).unwrap(); + start..start + } + + fn range_of(text: &str, part: &str) -> Range { + let start = text.find(part).unwrap(); + start..start + part.len() + } +} + #[gpui::test] fn test_edit_with_autoindent(cx: &mut MutableAppContext) { cx.add_model(|cx| {