From 24e3ccc31b0ace22ce4298cae61b24ddf71e2419 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Thu, 20 Feb 2025 17:45:19 -0500 Subject: [PATCH] Add the `syn_loader` to `Document` This type also exists on `Editor`. This change brings it to the `Document` as well because the replacement for `Syntax` in the child commits will eliminate `Syntax`'s copy of `syn_loader`. `Syntax` will also be responsible for returning the highlighter and query iterators (which will borrow the loader), so the loader must be separated from that type. In the long run, when we make a larger refactor to have `Document::apply` be a function of the `Editor` instead of the `Document`, we will be able to drop this field on `Document` - it is currently only necessary for `Document::apply`. Once we make that refactor, we will be able to eliminate the surrounding `Arc` in `Arc>` and use the `ArcSwap` directly instead. --- helix-core/src/syntax.rs | 11 ++++- helix-term/src/application.rs | 3 +- helix-term/src/commands/typed.rs | 5 ++- helix-term/src/ui/picker.rs | 9 +++- helix-view/src/document.rs | 76 ++++++++++++++++++-------------- helix-view/src/editor.rs | 20 ++++++--- helix-view/src/gutter.rs | 7 ++- helix-view/src/view.rs | 7 ++- 8 files changed, 94 insertions(+), 44 deletions(-) diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 6a2c28d1e..dfc323429 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -274,6 +274,15 @@ struct FileTypeGlobMatcher { file_types: Vec, } +impl Default for FileTypeGlobMatcher { + fn default() -> Self { + Self { + matcher: globset::GlobSet::empty(), + file_types: Default::default(), + } + } +} + impl FileTypeGlobMatcher { fn new(file_types: Vec) -> Result { let mut builder = globset::GlobSetBuilder::new(); @@ -299,7 +308,7 @@ impl FileTypeGlobMatcher { // Expose loader as Lazy<> global since it's always static? -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Loader { // highlight_names ? language_configs: Vec>, diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 16a26cb26..6f3485a87 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -389,8 +389,9 @@ impl Application { let lang_loader = helix_core::config::user_lang_loader()?; self.editor.syn_loader.store(Arc::new(lang_loader)); + let loader = self.editor.syn_loader.load(); for document in self.editor.documents.values_mut() { - document.detect_language(self.editor.syn_loader.clone()); + document.detect_language(&loader); let diagnostics = Editor::doc_diagnostics( &self.editor.language_servers, &self.editor.diagnostics, diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index c35ff714a..db2942eae 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2090,10 +2090,11 @@ fn language(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> any let doc = doc_mut!(cx.editor); + let loader = cx.editor.syn_loader.load(); if &args[0] == DEFAULT_LANGUAGE_NAME { - doc.set_language(None, None) + doc.set_language(None, &loader) } else { - doc.set_language_by_language_id(&args[0], cx.editor.syn_loader.clone())?; + doc.set_language_by_language_id(&args[0], &loader)?; } doc.detect_indent_and_line_ending(); diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index a6ce91a67..5a4b3afb5 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -624,7 +624,14 @@ impl Picker { if content_type.is_binary() { return Ok(CachedPreview::Binary); } - Document::open(&path, None, None, editor.config.clone()).map_or( + Document::open( + &path, + None, + false, + editor.config.clone(), + editor.syn_loader.clone(), + ) + .map_or( Err(std::io::Error::new( std::io::ErrorKind::NotFound, "Cannot open document", diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 417483e06..34a3df827 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -209,6 +209,11 @@ pub struct Document { // NOTE: ideally this would live on the handler for color swatches. This is blocked on a // large refactor that would make `&mut Editor` available on the `DocumentDidChange` event. pub color_swatch_controller: TaskController, + + // NOTE: this field should eventually go away - we should use the Editor's syn_loader instead + // of storing a copy on every doc. Then we can remove the surrounding `Arc` and use the + // `ArcSwap` directly. + syn_loader: Arc>, } #[derive(Debug, Clone, Default)] @@ -679,6 +684,7 @@ impl Document { text: Rope, encoding_with_bom_info: Option<(&'static Encoding, bool)>, config: Arc>, + syn_loader: Arc>, ) -> Self { let (encoding, has_bom) = encoding_with_bom_info.unwrap_or((encoding::UTF_8, false)); let line_ending = config.load().default_line_ending.into(); @@ -721,13 +727,17 @@ impl Document { jump_labels: HashMap::new(), color_swatches: None, color_swatch_controller: TaskController::new(), + syn_loader, } } - pub fn default(config: Arc>) -> Self { + pub fn default( + config: Arc>, + syn_loader: Arc>, + ) -> Self { let line_ending: LineEnding = config.load().default_line_ending.into(); let text = Rope::from(line_ending.as_str()); - Self::from(text, None, config) + Self::from(text, None, config, syn_loader) } // TODO: async fn? @@ -736,8 +746,9 @@ impl Document { pub fn open( path: &Path, mut encoding: Option<&'static Encoding>, - config_loader: Option>>, + detect_language: bool, config: Arc>, + syn_loader: Arc>, ) -> Result { // If the path is not a regular file (e.g.: /dev/random) it should not be opened. if path.metadata().is_ok_and(|metadata| !metadata.is_file()) { @@ -763,12 +774,13 @@ impl Document { (Rope::from(line_ending.as_str()), encoding, false) }; - let mut doc = Self::from(rope, Some((encoding, has_bom)), config); + let loader = syn_loader.load(); + let mut doc = Self::from(rope, Some((encoding, has_bom)), config, syn_loader); // set the path and try detecting the language doc.set_path(Some(path)); - if let Some(loader) = config_loader { - doc.detect_language(loader); + if detect_language { + doc.detect_language(&loader); } doc.editor_config = editor_config; @@ -1122,12 +1134,8 @@ impl Document { } /// Detect the programming language based on the file type. - pub fn detect_language(&mut self, config_loader: Arc>) { - let loader = config_loader.load(); - self.set_language( - self.detect_language_config(&loader), - Some(Arc::clone(&config_loader)), - ); + pub fn detect_language(&mut self, loader: &syntax::Loader) { + self.set_language(self.detect_language_config(loader), loader); } /// Detect the programming language based on the file type. @@ -1277,20 +1285,20 @@ impl Document { pub fn set_language( &mut self, language_config: Option>, - loader: Option>>, + loader: &syntax::Loader, ) { - if let (Some(language_config), Some(loader)) = (language_config, loader) { - if let Some(highlight_config) = - language_config.highlight_config(&(*loader).load().scopes()) - { - self.syntax = Syntax::new(self.text.slice(..), highlight_config, loader); - } - - self.language = Some(language_config); - } else { - self.syntax = None; - self.language = None; - }; + self.language = language_config; + self.syntax = self + .language + .as_ref() + .and_then(|config| config.highlight_config(&loader.scopes())) + .and_then(|highlight_config| { + Syntax::new( + self.text.slice(..), + highlight_config, + self.syn_loader.clone(), + ) + }); } /// Set the programming language for the file if you know the language but don't have the @@ -1298,13 +1306,12 @@ impl Document { pub fn set_language_by_language_id( &mut self, language_id: &str, - config_loader: Arc>, + loader: &syntax::Loader, ) -> anyhow::Result<()> { - let language_config = (*config_loader) - .load() + let language_config = loader .language_config_for_language_id(language_id) .ok_or_else(|| anyhow!("invalid language id: {}", language_id))?; - self.set_language(Some(language_config), Some(config_loader)); + self.set_language(Some(language_config), loader); Ok(()) } @@ -2319,6 +2326,7 @@ mod test { text, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); let view = ViewId::default(); doc.set_selection(view, Selection::single(0, 0)); @@ -2357,6 +2365,7 @@ mod test { text, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); let view = ViewId::default(); doc.set_selection(view, Selection::single(5, 5)); @@ -2470,9 +2479,12 @@ mod test { #[test] fn test_line_ending() { assert_eq!( - Document::default(Arc::new(ArcSwap::new(Arc::new(Config::default())))) - .text() - .to_string(), + Document::default( + Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())) + ) + .text() + .to_string(), helix_core::NATIVE_LINE_ENDING.as_str() ); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 5509bba34..ab763a962 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1478,9 +1478,9 @@ impl Editor { } pub fn refresh_doc_language(&mut self, doc_id: DocumentId) { - let loader = self.syn_loader.clone(); + let loader = self.syn_loader.load(); let doc = doc_mut!(self, &doc_id); - doc.detect_language(loader); + doc.detect_language(&loader); doc.detect_editor_config(); doc.detect_indent_and_line_ending(); self.refresh_language_servers(doc_id); @@ -1740,7 +1740,10 @@ impl Editor { } pub fn new_file(&mut self, action: Action) -> DocumentId { - self.new_file_from_document(action, Document::default(self.config.clone())) + self.new_file_from_document( + action, + Document::default(self.config.clone(), self.syn_loader.clone()), + ) } pub fn new_file_from_stdin(&mut self, action: Action) -> Result { @@ -1749,6 +1752,7 @@ impl Editor { helix_core::Rope::default(), Some((encoding, has_bom)), self.config.clone(), + self.syn_loader.clone(), ); let doc_id = self.new_file_from_document(action, doc); let doc = doc_mut!(self, &doc_id); @@ -1777,8 +1781,9 @@ impl Editor { let mut doc = Document::open( &path, None, - Some(self.syn_loader.clone()), + true, self.config.clone(), + self.syn_loader.clone(), )?; let diagnostics = @@ -1874,7 +1879,12 @@ impl Editor { .iter() .map(|(&doc_id, _)| doc_id) .next() - .unwrap_or_else(|| self.new_document(Document::default(self.config.clone()))); + .unwrap_or_else(|| { + self.new_document(Document::default( + self.config.clone(), + self.syn_loader.clone(), + )) + }); let view = View::new(doc_id, self.config().gutters.clone()); let view_id = self.tree.insert(view); let doc = doc_mut!(self, &doc_id); diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index bc87d836f..c2cbc0da5 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -334,7 +334,7 @@ mod tests { use crate::graphics::Rect; use crate::DocumentId; use arc_swap::ArcSwap; - use helix_core::Rope; + use helix_core::{syntax, Rope}; #[test] fn test_default_gutter_widths() { @@ -346,6 +346,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); assert_eq!(view.gutters.layout.len(), 5); @@ -371,6 +372,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); assert_eq!(view.gutters.layout.len(), 1); @@ -389,6 +391,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); assert_eq!(view.gutters.layout.len(), 2); @@ -411,6 +414,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); let rope = Rope::from_str("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np"); @@ -418,6 +422,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); assert_eq!(view.gutters.layout.len(), 2); diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index d6f10753a..6d237e203 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -699,7 +699,7 @@ mod tests { use super::*; use arc_swap::ArcSwap; - use helix_core::Rope; + use helix_core::{syntax, Rope}; // 1 diagnostic + 1 spacer + 3 linenr (< 1000 lines) + 1 spacer + 1 diff const DEFAULT_GUTTER_OFFSET: u16 = 7; @@ -719,6 +719,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); doc.ensure_view_init(view.id); @@ -894,6 +895,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); doc.ensure_view_init(view.id); assert_eq!( @@ -924,6 +926,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); doc.ensure_view_init(view.id); assert_eq!( @@ -948,6 +951,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); doc.ensure_view_init(view.id); @@ -1032,6 +1036,7 @@ mod tests { rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), + Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), ); doc.ensure_view_init(view.id);