Unload languages when uninstalling their extension (#7743)

Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-02-13 12:25:54 -08:00 committed by GitHub
parent a2144faf9c
commit e73e93f333
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 120 additions and 28 deletions

View file

@ -311,6 +311,13 @@ impl ExtensionStore {
} }
fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) { fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) {
let old_manifest = self.manifest.read();
let language_names = old_manifest.languages.keys().cloned().collect::<Vec<_>>();
let grammar_names = old_manifest.grammars.keys().cloned().collect::<Vec<_>>();
drop(old_manifest);
self.language_registry
.remove_languages(&language_names, &grammar_names);
self.language_registry self.language_registry
.register_wasm_grammars(manifest.grammars.iter().map(|(grammar_name, grammar)| { .register_wasm_grammars(manifest.grammars.iter().map(|(grammar_name, grammar)| {
let mut grammar_path = self.extensions_dir.clone(); let mut grammar_path = self.extensions_dir.clone();

View file

@ -302,6 +302,10 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_registry.language_names(), language_registry.language_names(),
["ERB", "Plain Text", "Ruby"] ["ERB", "Plain Text", "Ruby"]
); );
assert_eq!(
language_registry.grammar_names(),
["embedded_template".into(), "ruby".into()]
);
assert_eq!( assert_eq!(
theme_registry.list_names(false), 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.read_dir_call_count(), prev_fs_read_dir_call_count);
assert_eq!(fs.metadata_call_count(), prev_fs_metadata_call_count + 2); 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(), []);
});
} }

View file

@ -83,8 +83,8 @@ thread_local! {
} }
lazy_static! { lazy_static! {
static ref NEXT_LANGUAGE_ID: AtomicUsize = Default::default();
static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default(); static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default(); static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
/// A shared grammar for plain text, exposed for reuse by downstream crates. /// A shared grammar for plain text, exposed for reuse by downstream crates.
@ -613,7 +613,17 @@ pub struct BracketPair {
pub newline: bool, 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 struct Language {
pub(crate) id: LanguageId,
pub(crate) config: LanguageConfig, pub(crate) config: LanguageConfig,
pub(crate) grammar: Option<Arc<Grammar>>, pub(crate) grammar: Option<Arc<Grammar>>,
pub(crate) adapters: Vec<Arc<CachedLspAdapter>>, pub(crate) adapters: Vec<Arc<CachedLspAdapter>>,
@ -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 { pub struct Grammar {
id: usize, id: GrammarId,
pub ts_language: tree_sitter::Language, pub ts_language: tree_sitter::Language,
pub(crate) error_query: Query, pub(crate) error_query: Query,
pub(crate) highlights_query: Option<Query>, pub(crate) highlights_query: Option<Query>,
@ -697,11 +716,24 @@ struct BracketConfig {
impl Language { impl Language {
pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self { pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> 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<tree_sitter::Language>,
) -> Self {
Self { Self {
id,
config, config,
grammar: ts_language.map(|ts_language| { grammar: ts_language.map(|ts_language| {
Arc::new(Grammar { Arc::new(Grammar {
id: NEXT_GRAMMAR_ID.fetch_add(1, SeqCst), id: GrammarId::new(),
highlights_query: None, highlights_query: None,
brackets_config: None, brackets_config: None,
outline_config: None, outline_config: None,
@ -726,10 +758,6 @@ impl Language {
&self.adapters &self.adapters
} }
pub fn id(&self) -> Option<usize> {
self.grammar.as_ref().map(|g| g.id)
}
pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> { pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
if let Some(query) = queries.highlights { if let Some(query) = queries.highlights {
self = self self = self
@ -1237,13 +1265,13 @@ impl LanguageScope {
impl Hash for Language { impl Hash for Language {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id().hash(state) self.id.hash(state)
} }
} }
impl PartialEq for Language { impl PartialEq for Language {
fn eq(&self, other: &Self) -> bool { 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 { impl Grammar {
pub fn id(&self) -> usize { pub fn id(&self) -> GrammarId {
self.id self.id
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
CachedLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName, LspAdapter, CachedLspAdapter, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName,
LspAdapterDelegate, PARSER, PLAIN_TEXT, LspAdapter, LspAdapterDelegate, PARSER, PLAIN_TEXT,
}; };
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use collections::{hash_map, HashMap}; use collections::{hash_map, HashMap};
@ -43,8 +43,7 @@ struct LanguageRegistryState {
languages: Vec<Arc<Language>>, languages: Vec<Arc<Language>>,
available_languages: Vec<AvailableLanguage>, available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>, grammars: HashMap<Arc<str>, AvailableGrammar>,
next_available_language_id: AvailableLanguageId, loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
subscription: (watch::Sender<()>, watch::Receiver<()>), subscription: (watch::Sender<()>, watch::Receiver<()>),
theme: Option<Arc<Theme>>, theme: Option<Arc<Theme>>,
version: usize, version: usize,
@ -68,7 +67,7 @@ pub struct PendingLanguageServer {
#[derive(Clone)] #[derive(Clone)]
struct AvailableLanguage { struct AvailableLanguage {
id: AvailableLanguageId, id: LanguageId,
name: Arc<str>, name: Arc<str>,
grammar: Option<Arc<str>>, grammar: Option<Arc<str>>,
matcher: LanguageMatcher, matcher: LanguageMatcher,
@ -77,8 +76,6 @@ struct AvailableLanguage {
loaded: bool, loaded: bool,
} }
type AvailableLanguageId = usize;
enum AvailableGrammar { enum AvailableGrammar {
Native(tree_sitter::Language), Native(tree_sitter::Language),
Loaded(PathBuf, tree_sitter::Language), Loaded(PathBuf, tree_sitter::Language),
@ -126,7 +123,6 @@ impl LanguageRegistry {
languages: vec![PLAIN_TEXT.clone()], languages: vec![PLAIN_TEXT.clone()],
available_languages: Default::default(), available_languages: Default::default(),
grammars: Default::default(), grammars: Default::default(),
next_available_language_id: 0,
loading_languages: Default::default(), loading_languages: Default::default(),
subscription: watch::channel(), subscription: watch::channel(),
theme: Default::default(), theme: Default::default(),
@ -160,6 +156,17 @@ impl LanguageRegistry {
self.state.write().reload_languages(languages, grammars); 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<str>],
grammars_to_remove: &[Arc<str>],
) {
self.state
.write()
.remove_languages(languages_to_remove, grammars_to_remove)
}
#[cfg(any(feature = "test-support", test))] #[cfg(any(feature = "test-support", test))]
pub fn register_test_language(&self, config: LanguageConfig) { pub fn register_test_language(&self, config: LanguageConfig) {
self.register_language( self.register_language(
@ -194,7 +201,7 @@ impl LanguageRegistry {
} }
state.available_languages.push(AvailableLanguage { state.available_languages.push(AvailableLanguage {
id: post_inc(&mut state.next_available_language_id), id: LanguageId::new(),
name, name,
grammar: grammar_name, grammar: grammar_name,
matcher, matcher,
@ -241,6 +248,13 @@ impl LanguageRegistry {
result result
} }
pub fn grammar_names(&self) -> Vec<Arc<str>> {
let state = self.state.read();
let mut result = state.grammars.keys().cloned().collect::<Vec<_>>();
result.sort_unstable_by_key(|grammar_name| grammar_name.to_lowercase());
result
}
pub fn add(&self, language: Arc<Language>) { pub fn add(&self, language: Arc<Language>) {
self.state.write().add(language); self.state.write().add(language);
} }
@ -358,7 +372,7 @@ impl LanguageRegistry {
None None
}; };
Language::new(config, grammar) Language::new_with_id(id, config, grammar)
.with_lsp_adapters(language.lsp_adapters) .with_lsp_adapters(language.lsp_adapters)
.await .await
.with_queries(queries) .with_queries(queries)
@ -660,6 +674,22 @@ impl LanguageRegistryState {
*self.subscription.0.borrow_mut() = (); *self.subscription.0.borrow_mut() = ();
} }
fn remove_languages(
&mut self,
languages_to_remove: &[Arc<str>],
grammars_to_remove: &[Arc<str>],
) {
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( fn reload_languages(
&mut self, &mut self,
languages_to_reload: &[Arc<str>], languages_to_reload: &[Arc<str>],
@ -701,7 +731,7 @@ impl LanguageRegistryState {
/// Mark the given language as having been loaded, so that the /// Mark the given language as having been loaded, so that the
/// language registry won't try to load it again. /// 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 { for language in &mut self.available_languages {
if language.id == id { if language.id == id {
language.loaded = true; language.loaded = true;

View file

@ -1,7 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod syntax_map_tests; mod syntax_map_tests;
use crate::{Grammar, InjectionConfig, Language, LanguageRegistry}; use crate::{Grammar, InjectionConfig, Language, LanguageId, LanguageRegistry};
use collections::HashMap; use collections::HashMap;
use futures::FutureExt; use futures::FutureExt;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -102,9 +102,9 @@ enum SyntaxLayerContent {
} }
impl SyntaxLayerContent { impl SyntaxLayerContent {
fn language_id(&self) -> Option<usize> { fn language_id(&self) -> Option<LanguageId> {
match self { match self {
SyntaxLayerContent::Parsed { language, .. } => language.id(), SyntaxLayerContent::Parsed { language, .. } => Some(language.id),
SyntaxLayerContent::Pending { .. } => None, SyntaxLayerContent::Pending { .. } => None,
} }
} }
@ -144,7 +144,7 @@ struct SyntaxLayerSummary {
max_depth: usize, max_depth: usize,
range: Range<Anchor>, range: Range<Anchor>,
last_layer_range: Range<Anchor>, last_layer_range: Range<Anchor>,
last_layer_language: Option<usize>, last_layer_language: Option<LanguageId>,
contains_unknown_injections: bool, contains_unknown_injections: bool,
} }
@ -152,7 +152,7 @@ struct SyntaxLayerSummary {
struct SyntaxLayerPosition { struct SyntaxLayerPosition {
depth: usize, depth: usize,
range: Range<Anchor>, range: Range<Anchor>,
language: Option<usize>, language: Option<LanguageId>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -182,9 +182,9 @@ enum ParseStepLanguage {
} }
impl ParseStepLanguage { impl ParseStepLanguage {
fn id(&self) -> Option<usize> { fn id(&self) -> Option<LanguageId> {
match self { match self {
ParseStepLanguage::Loaded { language } => language.id(), ParseStepLanguage::Loaded { language } => Some(language.id),
ParseStepLanguage::Pending { .. } => None, ParseStepLanguage::Pending { .. } => None,
} }
} }