diff --git a/crates/extension/src/extension_store.rs b/crates/extension/src/extension_store.rs index 844f57b8b8..1a9e430bf2 100644 --- a/crates/extension/src/extension_store.rs +++ b/crates/extension/src/extension_store.rs @@ -311,6 +311,13 @@ impl ExtensionStore { } fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext) { + let old_manifest = self.manifest.read(); + let language_names = old_manifest.languages.keys().cloned().collect::>(); + let grammar_names = old_manifest.grammars.keys().cloned().collect::>(); + drop(old_manifest); + self.language_registry + .remove_languages(&language_names, &grammar_names); + self.language_registry .register_wasm_grammars(manifest.grammars.iter().map(|(grammar_name, grammar)| { let mut grammar_path = self.extensions_dir.clone(); diff --git a/crates/extension/src/extension_store_test.rs b/crates/extension/src/extension_store_test.rs index 42a54bfd86..cf8f659ef8 100644 --- a/crates/extension/src/extension_store_test.rs +++ b/crates/extension/src/extension_store_test.rs @@ -302,6 +302,10 @@ async fn test_extension_store(cx: &mut TestAppContext) { language_registry.language_names(), ["ERB", "Plain Text", "Ruby"] ); + assert_eq!( + language_registry.grammar_names(), + ["embedded_template".into(), "ruby".into()] + ); assert_eq!( theme_registry.list_names(false), [ @@ -319,4 +323,27 @@ async fn test_extension_store(cx: &mut TestAppContext) { assert_eq!(fs.read_dir_call_count(), prev_fs_read_dir_call_count); assert_eq!(fs.metadata_call_count(), prev_fs_metadata_call_count + 2); }); + + store + .update(cx, |store, cx| { + store.uninstall_extension("zed-ruby".into(), cx) + }) + .await + .unwrap(); + + expected_manifest.extensions.remove("zed-ruby"); + expected_manifest.languages.remove("Ruby"); + expected_manifest.languages.remove("ERB"); + expected_manifest.grammars.remove("ruby"); + expected_manifest.grammars.remove("embedded_template"); + + store.read_with(cx, |store, _| { + let manifest = store.manifest.read(); + assert_eq!(manifest.grammars, expected_manifest.grammars); + assert_eq!(manifest.languages, expected_manifest.languages); + assert_eq!(manifest.themes, expected_manifest.themes); + + assert_eq!(language_registry.language_names(), ["Plain Text"]); + assert_eq!(language_registry.grammar_names(), []); + }); } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c3438f867e..e9c1a83bed 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -83,8 +83,8 @@ thread_local! { } lazy_static! { + static ref NEXT_LANGUAGE_ID: AtomicUsize = Default::default(); static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default(); - static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default(); /// A shared grammar for plain text, exposed for reuse by downstream crates. @@ -613,7 +613,17 @@ pub struct BracketPair { pub newline: bool, } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub(crate) struct LanguageId(usize); + +impl LanguageId { + pub(crate) fn new() -> Self { + Self(NEXT_LANGUAGE_ID.fetch_add(1, SeqCst)) + } +} + pub struct Language { + pub(crate) id: LanguageId, pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, pub(crate) adapters: Vec>, @@ -625,8 +635,17 @@ pub struct Language { )>, } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct GrammarId(pub usize); + +impl GrammarId { + pub(crate) fn new() -> Self { + Self(NEXT_GRAMMAR_ID.fetch_add(1, SeqCst)) + } +} + pub struct Grammar { - id: usize, + id: GrammarId, pub ts_language: tree_sitter::Language, pub(crate) error_query: Query, pub(crate) highlights_query: Option, @@ -697,11 +716,24 @@ struct BracketConfig { impl Language { pub fn new(config: LanguageConfig, ts_language: Option) -> Self { + Self::new_with_id( + LanguageId(NEXT_LANGUAGE_ID.fetch_add(1, SeqCst)), + config, + ts_language, + ) + } + + fn new_with_id( + id: LanguageId, + config: LanguageConfig, + ts_language: Option, + ) -> Self { Self { + id, config, grammar: ts_language.map(|ts_language| { Arc::new(Grammar { - id: NEXT_GRAMMAR_ID.fetch_add(1, SeqCst), + id: GrammarId::new(), highlights_query: None, brackets_config: None, outline_config: None, @@ -726,10 +758,6 @@ impl Language { &self.adapters } - pub fn id(&self) -> Option { - self.grammar.as_ref().map(|g| g.id) - } - pub fn with_queries(mut self, queries: LanguageQueries) -> Result { if let Some(query) = queries.highlights { self = self @@ -1237,13 +1265,13 @@ impl LanguageScope { impl Hash for Language { fn hash(&self, state: &mut H) { - self.id().hash(state) + self.id.hash(state) } } impl PartialEq for Language { fn eq(&self, other: &Self) -> bool { - self.id().eq(&other.id()) + self.id.eq(&other.id) } } @@ -1258,7 +1286,7 @@ impl Debug for Language { } impl Grammar { - pub fn id(&self) -> usize { + pub fn id(&self) -> GrammarId { self.id } diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 55987b565d..a571b9af05 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -1,6 +1,6 @@ use crate::{ - CachedLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName, LspAdapter, - LspAdapterDelegate, PARSER, PLAIN_TEXT, + CachedLspAdapter, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName, + LspAdapter, LspAdapterDelegate, PARSER, PLAIN_TEXT, }; use anyhow::{anyhow, Context as _, Result}; use collections::{hash_map, HashMap}; @@ -43,8 +43,7 @@ struct LanguageRegistryState { languages: Vec>, available_languages: Vec, grammars: HashMap, AvailableGrammar>, - next_available_language_id: AvailableLanguageId, - loading_languages: HashMap>>>>, + loading_languages: HashMap>>>>, subscription: (watch::Sender<()>, watch::Receiver<()>), theme: Option>, version: usize, @@ -68,7 +67,7 @@ pub struct PendingLanguageServer { #[derive(Clone)] struct AvailableLanguage { - id: AvailableLanguageId, + id: LanguageId, name: Arc, grammar: Option>, matcher: LanguageMatcher, @@ -77,8 +76,6 @@ struct AvailableLanguage { loaded: bool, } -type AvailableLanguageId = usize; - enum AvailableGrammar { Native(tree_sitter::Language), Loaded(PathBuf, tree_sitter::Language), @@ -126,7 +123,6 @@ impl LanguageRegistry { languages: vec![PLAIN_TEXT.clone()], available_languages: Default::default(), grammars: Default::default(), - next_available_language_id: 0, loading_languages: Default::default(), subscription: watch::channel(), theme: Default::default(), @@ -160,6 +156,17 @@ impl LanguageRegistry { self.state.write().reload_languages(languages, grammars); } + /// Removes the specified languages and grammars from the registry. + pub fn remove_languages( + &self, + languages_to_remove: &[Arc], + grammars_to_remove: &[Arc], + ) { + self.state + .write() + .remove_languages(languages_to_remove, grammars_to_remove) + } + #[cfg(any(feature = "test-support", test))] pub fn register_test_language(&self, config: LanguageConfig) { self.register_language( @@ -194,7 +201,7 @@ impl LanguageRegistry { } state.available_languages.push(AvailableLanguage { - id: post_inc(&mut state.next_available_language_id), + id: LanguageId::new(), name, grammar: grammar_name, matcher, @@ -241,6 +248,13 @@ impl LanguageRegistry { result } + pub fn grammar_names(&self) -> Vec> { + let state = self.state.read(); + let mut result = state.grammars.keys().cloned().collect::>(); + result.sort_unstable_by_key(|grammar_name| grammar_name.to_lowercase()); + result + } + pub fn add(&self, language: Arc) { self.state.write().add(language); } @@ -358,7 +372,7 @@ impl LanguageRegistry { None }; - Language::new(config, grammar) + Language::new_with_id(id, config, grammar) .with_lsp_adapters(language.lsp_adapters) .await .with_queries(queries) @@ -660,6 +674,22 @@ impl LanguageRegistryState { *self.subscription.0.borrow_mut() = (); } + fn remove_languages( + &mut self, + languages_to_remove: &[Arc], + grammars_to_remove: &[Arc], + ) { + self.languages + .retain(|language| !languages_to_remove.contains(&language.name())); + self.available_languages + .retain(|language| !languages_to_remove.contains(&language.name)); + self.grammars + .retain(|name, _| !grammars_to_remove.contains(&name)); + self.version += 1; + self.reload_count += 1; + *self.subscription.0.borrow_mut() = (); + } + fn reload_languages( &mut self, languages_to_reload: &[Arc], @@ -701,7 +731,7 @@ impl LanguageRegistryState { /// Mark the given language as having been loaded, so that the /// language registry won't try to load it again. - fn mark_language_loaded(&mut self, id: AvailableLanguageId) { + fn mark_language_loaded(&mut self, id: LanguageId) { for language in &mut self.available_languages { if language.id == id { language.loaded = true; diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 4c20a32a12..045e475f07 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod syntax_map_tests; -use crate::{Grammar, InjectionConfig, Language, LanguageRegistry}; +use crate::{Grammar, InjectionConfig, Language, LanguageId, LanguageRegistry}; use collections::HashMap; use futures::FutureExt; use parking_lot::Mutex; @@ -102,9 +102,9 @@ enum SyntaxLayerContent { } impl SyntaxLayerContent { - fn language_id(&self) -> Option { + fn language_id(&self) -> Option { match self { - SyntaxLayerContent::Parsed { language, .. } => language.id(), + SyntaxLayerContent::Parsed { language, .. } => Some(language.id), SyntaxLayerContent::Pending { .. } => None, } } @@ -144,7 +144,7 @@ struct SyntaxLayerSummary { max_depth: usize, range: Range, last_layer_range: Range, - last_layer_language: Option, + last_layer_language: Option, contains_unknown_injections: bool, } @@ -152,7 +152,7 @@ struct SyntaxLayerSummary { struct SyntaxLayerPosition { depth: usize, range: Range, - language: Option, + language: Option, } #[derive(Clone, Debug)] @@ -182,9 +182,9 @@ enum ParseStepLanguage { } impl ParseStepLanguage { - fn id(&self) -> Option { + fn id(&self) -> Option { match self { - ParseStepLanguage::Loaded { language } => language.id(), + ParseStepLanguage::Loaded { language } => Some(language.id), ParseStepLanguage::Pending { .. } => None, } }