@@ -2103,12 +2103,12 @@ impl Editor {
for (selection, autoclose_region) in
self.selections_with_autoclose_regions(selections, &snapshot)
{
- if let Some(language) = snapshot.language_scope_at(selection.head()) {
+ if let Some(scope) = snapshot.language_scope_at(selection.head()) {
// Determine if the inserted text matches the opening or closing
// bracket of any of this language's bracket pairs.
let mut bracket_pair = None;
let mut is_bracket_pair_start = false;
- for (pair, enabled) in language.brackets() {
+ for (pair, enabled) in scope.brackets() {
if enabled && pair.close && pair.start.ends_with(text.as_ref()) {
bracket_pair = Some(pair.clone());
is_bracket_pair_start = true;
@@ -2130,7 +2130,7 @@ impl Editor {
let following_text_allows_autoclose = snapshot
.chars_at(selection.start)
.next()
- .map_or(true, |c| language.should_autoclose_before(c));
+ .map_or(true, |c| scope.should_autoclose_before(c));
let preceding_text_matches_prefix = prefix_len == 0
|| (selection.start.column >= (prefix_len as u32)
&& snapshot.contains_str_at(
@@ -2145,27 +2145,46 @@ impl BufferSnapshot {
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
let offset = position.to_offset(self);
- let mut range = 0..self.len();
- let mut scope = self.language.clone().map(|language| LanguageScope {
- language,
- override_id: None,
- });
+ let mut scope = None;
+ let mut smallest_range: Option<Range<usize>> = None;
// Use the layer that has the smallest node intersecting the given point.
for layer in self.syntax.layers_for_range(offset..offset, &self.text) {
let mut cursor = layer.node().walk();
- while cursor.goto_first_child_for_byte(offset).is_some() {}
- let node_range = cursor.node().byte_range();
- if node_range.to_inclusive().contains(&offset) && node_range.len() < range.len() {
- range = node_range;
- scope = Some(LanguageScope {
- language: layer.language.clone(),
- override_id: layer.override_id(offset, &self.text),
- });
+
+ let mut range = None;
+ loop {
+ let child_range = cursor.node().byte_range();
+ if !child_range.to_inclusive().contains(&offset) {
+ break;
+ }
+
+ range = Some(child_range);
+ if cursor.goto_first_child_for_byte(offset).is_none() {
+ break;
+ }
+ }
+
+ if let Some(range) = range {
+ if smallest_range
+ .as_ref()
+ .map_or(true, |smallest_range| range.len() < smallest_range.len())
+ {
+ smallest_range = Some(range);
+ scope = Some(LanguageScope {
+ language: layer.language.clone(),
+ override_id: layer.override_id(offset, &self.text),
+ });
+ }
}
}
- scope
+ scope.or_else(|| {
+ self.language.clone().map(|language| LanguageScope {
+ language,
+ override_id: None,
+ })
+ })
}
pub fn surrounding_word<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
@@ -1631,7 +1631,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
}
#[gpui::test]
-fn test_language_scope_at(cx: &mut AppContext) {
+fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
@@ -1718,6 +1718,73 @@ fn test_language_scope_at(cx: &mut AppContext) {
});
}
+#[gpui::test]
+fn test_language_scope_at_with_rust(cx: &mut AppContext) {
+ init_settings(cx, |_| {});
+
+ cx.add_model(|cx| {
+ let language = Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ brackets: BracketPairConfig {
+ pairs: vec![
+ BracketPair {
+ start: "{".into(),
+ end: "}".into(),
+ close: true,
+ newline: false,
+ },
+ BracketPair {
+ start: "'".into(),
+ end: "'".into(),
+ close: true,
+ newline: false,
+ },
+ ],
+ disabled_scopes_by_bracket_ix: vec![
+ Vec::new(), //
+ vec!["string".into()],
+ ],
+ },
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ )
+ .with_override_query(
+ r#"
+ (string_literal) @string
+ "#,
+ )
+ .unwrap();
+
+ let text = r#"
+ const S: &'static str = "hello";
+ "#
+ .unindent();
+
+ let buffer = Buffer::new(0, text.clone(), cx).with_language(Arc::new(language), cx);
+ let snapshot = buffer.snapshot();
+
+ // By default, all brackets are enabled
+ let config = snapshot.language_scope_at(0).unwrap();
+ assert_eq!(
+ config.brackets().map(|e| e.1).collect::<Vec<_>>(),
+ &[true, true]
+ );
+
+ // Within a string, the quotation brackets are disabled.
+ let string_config = snapshot
+ .language_scope_at(text.find("ello").unwrap())
+ .unwrap();
+ assert_eq!(
+ string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
+ &[true, false]
+ );
+
+ buffer
+ });
+}
+
#[gpui::test]
fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
init_settings(cx, |_| {});