diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 81ab73e24e..0d62c1f625 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1737,7 +1737,7 @@ impl Editor { for (selection, autoclose_region) in self.selections_with_autoclose_regions(selections, &snapshot) { - if let Some(language) = snapshot.language_config_at(selection.head()) { + if let Some(language) = 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; @@ -1898,7 +1898,7 @@ impl Editor { let end = selection.end; let mut insert_extra_newline = false; - if let Some(language) = buffer.language_config_at(start) { + if let Some(language) = buffer.language_scope_at(start) { let leading_whitespace_len = buffer .reversed_chars_at(start) .take_while(|c| c.is_whitespace() && *c != '\n') @@ -4535,7 +4535,7 @@ impl Editor { for selection in &mut selections { let start_column = snapshot.indent_size_for_line(selection.start.row).len; let language = if let Some(language) = - snapshot.language_config_at(Point::new(selection.start.row, start_column)) + snapshot.language_scope_at(Point::new(selection.start.row, start_column)) { language } else { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 57fe5411cd..7079d197f9 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, - DiagnosticEntry, IndentSize, Language, LanguageConfigYeet, OffsetRangeExt, OffsetUtf16, - Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, - ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, + DiagnosticEntry, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, + OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, + ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; use std::{ borrow::Cow, @@ -2691,9 +2691,9 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_at(offset)) } - pub fn language_config_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { self.point_to_buffer_offset(point) - .and_then(|(buffer, offset)| buffer.language_config_at(offset)) + .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) } pub fn is_dirty(&self) -> bool { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5593241e70..7ef7845952 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -9,7 +9,7 @@ use crate::{ syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, }, - CodeLabel, LanguageConfigYeet, Outline, + CodeLabel, LanguageScope, Outline, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -2015,7 +2015,7 @@ impl BufferSnapshot { .or(self.language.as_ref()) } - pub fn language_config_at(&self, position: D) -> Option { + pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); if let Some(layer_info) = self @@ -2024,12 +2024,12 @@ impl BufferSnapshot { .filter(|l| l.node.end_byte() > offset) .last() { - Some(LanguageConfigYeet { + Some(LanguageScope { language: layer_info.language.clone(), override_id: layer_info.override_id(offset, &self.text), }) } else { - self.language.clone().map(|language| LanguageConfigYeet { + self.language.clone().map(|language| LanguageScope { language, override_id: None, }) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 5aa9152205..07198e8090 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1369,6 +1369,89 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut MutableAppContext) { }); } +#[gpui::test] +fn test_language_config_at(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let language = Language::new( + LanguageConfig { + name: "JavaScript".into(), + line_comment: Some("// ".into()), + brackets: vec![ + BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }, + BracketPair { + start: "'".into(), + end: "'".into(), + close: true, + newline: false, + }, + ], + overrides: [ + ( + "element".into(), + LanguageConfigOverride { + line_comment: Override::Remove { remove: true }, + block_comment: Override::Set(("{/*".into(), "*/}".into())), + ..Default::default() + }, + ), + ( + "string".into(), + LanguageConfigOverride { + brackets: Override::Set(vec![BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }]), + ..Default::default() + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }, + Some(tree_sitter_javascript::language()), + ) + .with_override_query( + r#" + (jsx_element) @override.element + (string) @override.string + "#, + ) + .unwrap(); + + let text = r#"a["b"] = ;"#; + + let buffer = Buffer::new(0, text, cx).with_language(Arc::new(language), cx); + let snapshot = buffer.snapshot(); + + let config = snapshot.language_scope_at(0).unwrap(); + assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!(config.brackets().len(), 2); + + let string_config = snapshot.language_scope_at(3).unwrap(); + assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!(string_config.brackets().len(), 1); + + let element_config = snapshot.language_scope_at(10).unwrap(); + assert_eq!(element_config.line_comment_prefix(), None); + assert_eq!( + element_config.block_comment_delimiters(), + Some((&"{/*".into(), &"*/}".into())) + ); + assert_eq!(element_config.brackets().len(), 2); + + buffer + }); +} + #[gpui::test] fn test_serialization(cx: &mut gpui::MutableAppContext) { let mut now = Instant::now(); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 3c8b2b987b..928e208627 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -22,10 +22,7 @@ use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; -use serde::{ - de::{self}, - Deserialize, Deserializer, -}; +use serde::{de, Deserialize, Deserializer}; use serde_json::Value; use std::{ any::Any, @@ -251,20 +248,22 @@ pub struct LanguageConfig { } #[derive(Clone)] -pub struct LanguageConfigYeet { +pub struct LanguageScope { language: Arc, override_id: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Default, Debug)] pub struct LanguageConfigOverride { #[serde(default)] pub line_comment: Override>, #[serde(default)] pub block_comment: Override<(Arc, Arc)>, + #[serde(default)] + pub brackets: Override>, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(untagged)] pub enum Override { Remove { remove: bool }, @@ -278,11 +277,11 @@ impl Default for Override { } impl Override { - fn as_option<'a>(this: Option<&'a Self>, original: &'a Option) -> Option<&'a T> { + fn as_option<'a>(this: Option<&'a Self>, original: Option<&'a T>) -> Option<&'a T> { match this { Some(Self::Set(value)) => Some(value), Some(Self::Remove { remove: true }) => None, - Some(Self::Remove { remove: false }) | None => original.as_ref(), + Some(Self::Remove { remove: false }) | None => original, } } } @@ -966,40 +965,39 @@ impl Language { } } -impl LanguageConfigYeet { +impl LanguageScope { pub fn line_comment_prefix(&self) -> Option<&Arc> { Override::as_option( - self.over_ride().map(|o| &o.line_comment), - &self.language.config.line_comment, + self.config_override().map(|o| &o.line_comment), + self.language.config.line_comment.as_ref(), ) } pub fn block_comment_delimiters(&self) -> Option<(&Arc, &Arc)> { Override::as_option( - self.over_ride().map(|o| &o.block_comment), - &self.language.config.block_comment, + self.config_override().map(|o| &o.block_comment), + self.language.config.block_comment.as_ref(), ) .map(|e| (&e.0, &e.1)) } pub fn brackets(&self) -> &[BracketPair] { - &self.language.config.brackets + Override::as_option( + self.config_override().map(|o| &o.brackets), + Some(&self.language.config.brackets), + ) + .map_or(&[], Vec::as_slice) } pub fn should_autoclose_before(&self, c: char) -> bool { c.is_whitespace() || self.language.config.autoclose_before.contains(c) } - fn over_ride(&self) -> Option<&LanguageConfigOverride> { - self.override_id.and_then(|id| { - self.language - .grammar - .as_ref()? - .override_config - .as_ref()? - .values - .get(&id) - }) + 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) } } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 0250c53684..d7fa67b1d4 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -173,6 +173,11 @@ pub(crate) fn language( .with_injection_query(query.as_ref()) .expect("failed to load injection query"); } + if let Some(query) = load_query(name, "/overrides") { + language = language + .with_override_query(query.as_ref()) + .expect("failed to load override query"); + } if let Some(lsp_adapter) = lsp_adapter { language = language.with_lsp_adapter(lsp_adapter) } diff --git a/crates/zed/src/languages/tsx/overrides.scm b/crates/zed/src/languages/tsx/overrides.scm index aa617b3086..183f7b4f98 100644 --- a/crates/zed/src/languages/tsx/overrides.scm +++ b/crates/zed/src/languages/tsx/overrides.scm @@ -1,2 +1,7 @@ -(jsx_element) @override.element +[ + (jsx_element) + (jsx_fragment) + (jsx_self_closing_element) + (jsx_expression) +] @override.element (string) @override.string