diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index f32918c4cad6f623f2c9a9584c7d42dc4a367d77..a33a21cb0fcc945c054cd3607de8cbd7b434366f 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -2120,8 +2120,8 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { }, ], disabled_scopes_by_bracket_ix: vec![ - Vec::new(), // - vec!["string".into()], + Vec::new(), // + vec!["string".into(), "comment".into()], // single quotes disabled ], }, overrides: [( @@ -2142,6 +2142,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { r#" (jsx_element) @element (string) @string + (comment) @comment.inclusive [ (jsx_opening_element) (jsx_closing_element) @@ -2155,7 +2156,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { a["b"] = { g() } - ; + ; // a comment "# .unindent(); @@ -2170,6 +2171,14 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { &[true, true] ); + let comment_config = snapshot + .language_scope_at(text.find("comment").unwrap() + "comment".len()) + .unwrap(); + assert_eq!( + comment_config.brackets().map(|e| e.1).collect::>(), + &[true, false] + ); + let string_config = snapshot .language_scope_at(text.find("b\"").unwrap()) .unwrap(); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index cf267f69616dec2a6d3f7cc03c292ff392ad8a05..48501114b006ecab1ad4f326c2f34e5834c0626b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -945,7 +945,14 @@ struct RunnableConfig { struct OverrideConfig { query: Query, - values: HashMap, + values: HashMap, +} + +#[derive(Debug)] +struct OverrideEntry { + name: String, + range_is_inclusive: bool, + value: LanguageConfigOverride, } #[derive(Default, Clone)] @@ -1265,58 +1272,66 @@ impl Language { }; let mut override_configs_by_id = HashMap::default(); - for (ix, name) in query.capture_names().iter().enumerate() { - if !name.starts_with('_') { - let value = self.config.overrides.remove(*name).unwrap_or_default(); - for server_name in &value.opt_into_language_servers { - if !self - .config - .scope_opt_in_language_servers - .contains(server_name) - { - util::debug_panic!("Server {server_name:?} has been opted-in by scope {name:?} but has not been marked as an opt-in server"); - } - } + for (ix, mut name) in query.capture_names().iter().copied().enumerate() { + let mut range_is_inclusive = false; + if name.starts_with('_') { + continue; + } + if let Some(prefix) = name.strip_suffix(".inclusive") { + name = prefix; + range_is_inclusive = true; + } - override_configs_by_id.insert(ix as u32, (name.to_string(), value)); + let value = self.config.overrides.get(name).cloned().unwrap_or_default(); + for server_name in &value.opt_into_language_servers { + if !self + .config + .scope_opt_in_language_servers + .contains(server_name) + { + util::debug_panic!("Server {server_name:?} has been opted-in by scope {name:?} but has not been marked as an opt-in server"); + } } - } - if !self.config.overrides.is_empty() { - let keys = self.config.overrides.keys().collect::>(); - Err(anyhow!( - "language {:?} has overrides in config not in query: {keys:?}", - self.config.name - ))?; + override_configs_by_id.insert( + ix as u32, + OverrideEntry { + name: name.to_string(), + range_is_inclusive, + value, + }, + ); } - for disabled_scope_name in self - .config - .brackets - .disabled_scopes_by_bracket_ix - .iter() - .flatten() - { + let referenced_override_names = self.config.overrides.keys().chain( + self.config + .brackets + .disabled_scopes_by_bracket_ix + .iter() + .flatten(), + ); + + for referenced_name in referenced_override_names { if !override_configs_by_id .values() - .any(|(scope_name, _)| scope_name == disabled_scope_name) + .any(|entry| entry.name == *referenced_name) { Err(anyhow!( - "language {:?} has overrides in config not in query: {disabled_scope_name:?}", + "language {:?} has overrides in config not in query: {referenced_name:?}", self.config.name ))?; } } - for (name, override_config) in override_configs_by_id.values_mut() { - override_config.disabled_bracket_ixs = self + for entry in override_configs_by_id.values_mut() { + entry.value.disabled_bracket_ixs = self .config .brackets .disabled_scopes_by_bracket_ix .iter() .enumerate() .filter_map(|(ix, disabled_scope_names)| { - if disabled_scope_names.contains(name) { + if disabled_scope_names.contains(&entry.name) { Some(ix as u16) } else { None @@ -1534,14 +1549,14 @@ impl LanguageScope { let id = self.override_id?; let grammar = self.language.grammar.as_ref()?; let override_config = grammar.override_config.as_ref()?; - override_config.values.get(&id).map(|e| e.0.as_str()) + override_config.values.get(&id).map(|e| e.name.as_str()) } fn config_override(&self) -> Option<&LanguageConfigOverride> { let id = self.override_id?; let grammar = self.language.grammar.as_ref()?; let override_config = grammar.override_config.as_ref()?; - override_config.values.get(&id).map(|e| &e.1) + override_config.values.get(&id).map(|e| &e.value) } } diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 8617696cc471ecd50cd160b35698e72328cc0179..12089255420d35e567ecb1da1f97dda18f11cdef 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1520,18 +1520,24 @@ impl<'a> SyntaxLayer<'a> { let config = self.language.grammar.as_ref()?.override_config.as_ref()?; let mut query_cursor = QueryCursorHandle::new(); - query_cursor.set_byte_range(offset..offset); + query_cursor.set_byte_range(offset.saturating_sub(1)..offset.saturating_add(1)); let mut smallest_match: Option<(u32, Range)> = None; for mat in query_cursor.matches(&config.query, self.node(), text) { for capture in mat.captures { - if !config.values.contains_key(&capture.index) { + let Some(override_entry) = config.values.get(&capture.index) else { continue; - } + }; let range = capture.node.byte_range(); - if offset <= range.start || offset >= range.end { - continue; + if override_entry.range_is_inclusive { + if offset < range.start || offset > range.end { + continue; + } + } else { + if offset <= range.start || offset >= range.end { + continue; + } } if let Some((_, smallest_range)) = &smallest_match { diff --git a/crates/languages/src/c/overrides.scm b/crates/languages/src/c/overrides.scm index 178355c67c9797b371be81de98c23cba9373c38d..36473eb300fd01370e1947873435a821e2d6417a 100644 --- a/crates/languages/src/c/overrides.scm +++ b/crates/languages/src/c/overrides.scm @@ -1,2 +1,2 @@ -(comment) @comment +(comment) @comment.inclusive (string_literal) @string diff --git a/crates/languages/src/cpp/overrides.scm b/crates/languages/src/cpp/overrides.scm index 178355c67c9797b371be81de98c23cba9373c38d..36473eb300fd01370e1947873435a821e2d6417a 100644 --- a/crates/languages/src/cpp/overrides.scm +++ b/crates/languages/src/cpp/overrides.scm @@ -1,2 +1,2 @@ -(comment) @comment +(comment) @comment.inclusive (string_literal) @string diff --git a/crates/languages/src/css/overrides.scm b/crates/languages/src/css/overrides.scm index c0db9fe3274a7746ebb479618efbca117129bc60..e5eade479723c33894b6165085603631bdfe8c64 100644 --- a/crates/languages/src/css/overrides.scm +++ b/crates/languages/src/css/overrides.scm @@ -1,2 +1,2 @@ -(comment) @comment +(comment) @comment.inclusive (string_value) @string diff --git a/crates/languages/src/go/overrides.scm b/crates/languages/src/go/overrides.scm index 9eb287df3f448b20848572f2bb81b9b4e9f80d3d..aae1520301bbb2a04b04f930b747d290051bc9cc 100644 --- a/crates/languages/src/go/overrides.scm +++ b/crates/languages/src/go/overrides.scm @@ -1,4 +1,4 @@ -(comment) @comment +(comment) @comment.inclusive [ (interpreted_string_literal) (raw_string_literal) diff --git a/crates/languages/src/javascript/overrides.scm b/crates/languages/src/javascript/overrides.scm index d133898768ac61905640df15f24178e30e524d6e..d93c8b5aea27b1fced6d021c68403348a97bb9e9 100644 --- a/crates/languages/src/javascript/overrides.scm +++ b/crates/languages/src/javascript/overrides.scm @@ -1,4 +1,4 @@ -(comment) @comment +(comment) @comment.inclusive [ (string) diff --git a/crates/languages/src/python/overrides.scm b/crates/languages/src/python/overrides.scm index 8a58e304e5c5185166a09bc78eb835527a246301..81fec9a5f57b28fc67b4781ec37df43559e21dc9 100644 --- a/crates/languages/src/python/overrides.scm +++ b/crates/languages/src/python/overrides.scm @@ -1,2 +1,2 @@ -(comment) @comment +(comment) @comment.inclusive (string) @string diff --git a/crates/languages/src/rust/overrides.scm b/crates/languages/src/rust/overrides.scm index 216a3951476509b79e35bec2f79b3feb9a1afa44..91fa6139d387db97676cd32a84433b16f3c8e94e 100644 --- a/crates/languages/src/rust/overrides.scm +++ b/crates/languages/src/rust/overrides.scm @@ -5,4 +5,4 @@ [ (line_comment) (block_comment) -] @comment +] @comment.inclusive diff --git a/crates/languages/src/tsx/overrides.scm b/crates/languages/src/tsx/overrides.scm index d133898768ac61905640df15f24178e30e524d6e..d93c8b5aea27b1fced6d021c68403348a97bb9e9 100644 --- a/crates/languages/src/tsx/overrides.scm +++ b/crates/languages/src/tsx/overrides.scm @@ -1,4 +1,4 @@ -(comment) @comment +(comment) @comment.inclusive [ (string) diff --git a/crates/languages/src/typescript/overrides.scm b/crates/languages/src/typescript/overrides.scm index 8a58e304e5c5185166a09bc78eb835527a246301..81fec9a5f57b28fc67b4781ec37df43559e21dc9 100644 --- a/crates/languages/src/typescript/overrides.scm +++ b/crates/languages/src/typescript/overrides.scm @@ -1,2 +1,2 @@ -(comment) @comment +(comment) @comment.inclusive (string) @string diff --git a/docs/src/extensions/languages.md b/docs/src/extensions/languages.md index 174a27c6e61d1a76a69d2fe55194c6a12d587579..b7e0cb44823084e9b6e75fe5a43ad2aca4bde737 100644 --- a/docs/src/extensions/languages.md +++ b/docs/src/extensions/languages.md @@ -218,15 +218,44 @@ Note that we couldn't use JSON as an example here because it doesn't support lan ### Syntax overrides -The `overrides.scm` file defines syntax overrides. +The `overrides.scm` file defines syntactic _scopes_ that can be used to override certain editor settings within specific language constructs. -Here's an example from an `overrides.scm` file for JSON: +For example, there is a language-specific setting called `word_characters` that controls which non-alphabetic characters are considered part of a word, for filtering autocomplete suggestions. In JavaScript, "$" and "#" are considered word characters. But when your cursor is within a _string_ in JavaScript, "-" is _also_ considered a word character. To achieve this, the JavaScript `overrides.scm` file contains the following pattern: ```scheme -(string) @string +[ + (string) + (template_string) +] @string +``` + +And the JavaScript `config.toml` contains this setting: + +```toml +word_characters = ["#", "$"] + +[overrides.string] +word_characters = ["-"] ``` -This query explicitly marks strings for highlighting, potentially overriding default behavior. For a complete list of supported captures, refer to the [Syntax highlighting](#syntax-highlighting) section above. +You can also disable certain auto-closing brackets in a specific scope. For example, to prevent auto-closing `'` within strings, you could put the following in the JavaScript `config.toml`: + +```toml +brackets = [ + { start = "'", end = "'", close = true, newline = false, not_in = ["string"] }, + # other pairs... +] +``` + +#### Range inclusivity + +By default, the ranges defined in `overrides.scm` are _exclusive_. So in the case above, if you cursor was _outside_ the quotation marks delimiting the string, the `string` scope would not take effect. Sometimes, you may want to make the range _inclusive_. You can do this by adding the `.inclusive` suffix to the capture name in the query. + +For example, in JavaScript, we also disable auto-closing of single quotes within comments. And the comment scope must extend all the way to the newline after a line comment. To achieve this, the JavaScript `overrides.scm` contains the following pattern: + +```scheme +(comment) @comment.inclusive +``` ### Text redactions diff --git a/extensions/astro/languages/astro/overrides.scm b/extensions/astro/languages/astro/overrides.scm index 3749716d55003d4c3f16435e09bdee54fcff73d5..e84d1a3e604246b9a2f07e5d5c837c928493dbee 100644 --- a/extensions/astro/languages/astro/overrides.scm +++ b/extensions/astro/languages/astro/overrides.scm @@ -3,4 +3,4 @@ (quoted_attribute_value) ] @string -(comment) @comment +(comment) @comment.inclusive diff --git a/extensions/elixir/languages/elixir/overrides.scm b/extensions/elixir/languages/elixir/overrides.scm index 181254018126d5ee0faaad24911c3b71b1e30c5b..9e77c17ec558ec469b25d23f72a34f56afe47117 100644 --- a/extensions/elixir/languages/elixir/overrides.scm +++ b/extensions/elixir/languages/elixir/overrides.scm @@ -1,2 +1,2 @@ -(comment) @comment +(comment) @comment.inclusive [(string) (charlist)] @string diff --git a/extensions/scheme/languages/scheme/overrides.scm b/extensions/scheme/languages/scheme/overrides.scm index 8c0d41b046c6dbe72937d662a7c8ebdb023fe49e..5fd3083adc57e71f6e505e351a5915375a0ec8a5 100644 --- a/extensions/scheme/languages/scheme/overrides.scm +++ b/extensions/scheme/languages/scheme/overrides.scm @@ -2,5 +2,5 @@ (comment) (block_comment) (directive) -] @comment +] @comment.inclusive (string) @string diff --git a/extensions/toml/languages/toml/overrides.scm b/extensions/toml/languages/toml/overrides.scm index 8a58e304e5c5185166a09bc78eb835527a246301..81fec9a5f57b28fc67b4781ec37df43559e21dc9 100644 --- a/extensions/toml/languages/toml/overrides.scm +++ b/extensions/toml/languages/toml/overrides.scm @@ -1,2 +1,2 @@ -(comment) @comment +(comment) @comment.inclusive (string) @string