Reload grammars in extensions when they are updated on disk (#7531)

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-02-07 16:39:11 -08:00 committed by GitHub
parent f2a4dbaf7f
commit 7b03e977e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 111 additions and 57 deletions

View file

@ -8772,6 +8772,10 @@ impl Editor {
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() }) cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
} }
multi_buffer::Event::Reparsed => cx.emit(EditorEvent::Reparsed), multi_buffer::Event::Reparsed => cx.emit(EditorEvent::Reparsed),
multi_buffer::Event::LanguageChanged => {
cx.emit(EditorEvent::Reparsed);
cx.notify();
}
multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged), multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved), multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => { multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::{Context as _, Result};
use collections::{HashMap, HashSet}; use collections::HashMap;
use fs::Fs; use fs::Fs;
use futures::StreamExt as _; use futures::StreamExt as _;
use gpui::{actions, AppContext, Context, Global, Model, ModelContext, Task}; use gpui::{actions, AppContext, Context, Global, Model, ModelContext, Task};
@ -36,7 +36,7 @@ impl Global for GlobalExtensionStore {}
#[derive(Deserialize, Serialize, Default)] #[derive(Deserialize, Serialize, Default)]
pub struct Manifest { pub struct Manifest {
pub grammars: HashMap<String, GrammarManifestEntry>, pub grammars: HashMap<Arc<str>, GrammarManifestEntry>,
pub languages: HashMap<Arc<str>, LanguageManifestEntry>, pub languages: HashMap<Arc<str>, LanguageManifestEntry>,
pub themes: HashMap<String, ThemeManifestEntry>, pub themes: HashMap<String, ThemeManifestEntry>,
} }
@ -52,6 +52,7 @@ pub struct LanguageManifestEntry {
extension: String, extension: String,
path: PathBuf, path: PathBuf,
matcher: LanguageMatcher, matcher: LanguageMatcher,
grammar: Option<Arc<str>>,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
@ -152,6 +153,7 @@ impl ExtensionStore {
self.language_registry.register_extension( self.language_registry.register_extension(
language_path.into(), language_path.into(),
language_name.clone(), language_name.clone(),
language.grammar.clone(),
language.matcher.clone(), language.matcher.clone(),
load_plugin_queries, load_plugin_queries,
); );
@ -188,19 +190,29 @@ impl ExtensionStore {
let events_task = cx.background_executor().spawn(async move { let events_task = cx.background_executor().spawn(async move {
let mut events = fs.watch(&extensions_dir, Duration::from_millis(250)).await; let mut events = fs.watch(&extensions_dir, Duration::from_millis(250)).await;
while let Some(events) = events.next().await { while let Some(events) = events.next().await {
let mut changed_languages = HashSet::default(); let mut changed_grammars = Vec::default();
let mut changed_themes = HashSet::default(); let mut changed_languages = Vec::default();
let mut changed_themes = Vec::default();
{ {
let manifest = manifest.read(); let manifest = manifest.read();
for event in events { for event in events {
for (grammar_name, grammar) in &manifest.grammars {
let mut grammar_path = extensions_dir.clone();
grammar_path
.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
if event.path.starts_with(&grammar_path) || event.path == grammar_path {
changed_grammars.push(grammar_name.clone());
}
}
for (language_name, language) in &manifest.languages { for (language_name, language) in &manifest.languages {
let mut language_path = extensions_dir.clone(); let mut language_path = extensions_dir.clone();
language_path language_path
.extend([language.extension.as_ref(), language.path.as_path()]); .extend([language.extension.as_ref(), language.path.as_path()]);
if event.path.starts_with(&language_path) || event.path == language_path if event.path.starts_with(&language_path) || event.path == language_path
{ {
changed_languages.insert(language_name.clone()); changed_languages.push(language_name.clone());
} }
} }
@ -208,18 +220,19 @@ impl ExtensionStore {
let mut theme_path = extensions_dir.clone(); let mut theme_path = extensions_dir.clone();
theme_path.extend([theme.extension.as_ref(), theme.path.as_path()]); theme_path.extend([theme.extension.as_ref(), theme.path.as_path()]);
if event.path.starts_with(&theme_path) || event.path == theme_path { if event.path.starts_with(&theme_path) || event.path == theme_path {
changed_themes.insert(theme_path.clone()); changed_themes.push(theme_path.clone());
} }
} }
} }
} }
language_registry.reload_languages(&changed_languages); language_registry.reload_languages(&changed_languages, &changed_grammars);
for theme_path in &changed_themes { for theme_path in &changed_themes {
theme_registry theme_registry
.load_user_theme(&theme_path, fs.clone()) .load_user_theme(&theme_path, fs.clone())
.await .await
.context("failed to load user theme")
.log_err(); .log_err();
} }
@ -253,7 +266,10 @@ impl ExtensionStore {
.spawn(async move { .spawn(async move {
let mut manifest = Manifest::default(); let mut manifest = Manifest::default();
let mut extension_paths = fs.read_dir(&extensions_dir).await?; let mut extension_paths = fs
.read_dir(&extensions_dir)
.await
.context("failed to read extensions directory")?;
while let Some(extension_dir) = extension_paths.next().await { while let Some(extension_dir) = extension_paths.next().await {
let extension_dir = extension_dir?; let extension_dir = extension_dir?;
let Some(extension_name) = let Some(extension_name) =
@ -305,6 +321,7 @@ impl ExtensionStore {
extension: extension_name.into(), extension: extension_name.into(),
path: relative_path.into(), path: relative_path.into(),
matcher: config.matcher, matcher: config.matcher,
grammar: config.grammar,
}, },
); );
} }
@ -345,7 +362,8 @@ impl ExtensionStore {
&serde_json::to_string_pretty(&manifest)?.as_str().into(), &serde_json::to_string_pretty(&manifest)?.as_str().into(),
Default::default(), Default::default(),
) )
.await?; .await
.context("failed to save extension manifest")?;
anyhow::Ok(manifest) anyhow::Ok(manifest)
}) })

View file

@ -106,6 +106,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
LanguageManifestEntry { LanguageManifestEntry {
extension: "zed-ruby".into(), extension: "zed-ruby".into(),
path: "languages/erb".into(), path: "languages/erb".into(),
grammar: Some("embedded_template".into()),
matcher: LanguageMatcher { matcher: LanguageMatcher {
path_suffixes: vec!["erb".into()], path_suffixes: vec!["erb".into()],
first_line_pattern: None, first_line_pattern: None,
@ -117,6 +118,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
LanguageManifestEntry { LanguageManifestEntry {
extension: "zed-ruby".into(), extension: "zed-ruby".into(),
path: "languages/ruby".into(), path: "languages/ruby".into(),
grammar: Some("ruby".into()),
matcher: LanguageMatcher { matcher: LanguageMatcher {
path_suffixes: vec!["rb".into()], path_suffixes: vec!["rb".into()],
first_line_pattern: None, first_line_pattern: None,

View file

@ -758,6 +758,7 @@ impl Buffer {
/// Assign a language to the buffer. /// Assign a language to the buffer.
pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) { pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
self.parse_count += 1;
self.syntax_map.lock().clear(); self.syntax_map.lock().clear();
self.language = language; self.language = language;
self.reparse(cx); self.reparse(cx);

View file

@ -740,14 +740,16 @@ type AvailableLanguageId = usize;
struct AvailableLanguage { struct AvailableLanguage {
id: AvailableLanguageId, id: AvailableLanguageId,
name: Arc<str>, name: Arc<str>,
grammar: Option<Arc<str>>,
source: AvailableLanguageSource, source: AvailableLanguageSource,
lsp_adapters: Vec<Arc<dyn LspAdapter>>, lsp_adapters: Vec<Arc<dyn LspAdapter>>,
loaded: bool, loaded: bool,
} }
enum AvailableGrammar { enum AvailableGrammar {
Loaded(tree_sitter::Language), Native(tree_sitter::Language),
Loading(Vec<oneshot::Sender<Result<tree_sitter::Language>>>), Loaded(PathBuf, tree_sitter::Language),
Loading(PathBuf, Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
Unloaded(PathBuf), Unloaded(PathBuf),
} }
@ -781,7 +783,7 @@ struct LanguageRegistryState {
next_language_server_id: usize, next_language_server_id: usize,
languages: Vec<Arc<Language>>, languages: Vec<Arc<Language>>,
available_languages: Vec<AvailableLanguage>, available_languages: Vec<AvailableLanguage>,
grammars: HashMap<String, AvailableGrammar>, grammars: HashMap<Arc<str>, AvailableGrammar>,
next_available_language_id: AvailableLanguageId, next_available_language_id: AvailableLanguageId,
loading_languages: HashMap<AvailableLanguageId, 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<()>),
@ -834,8 +836,8 @@ impl LanguageRegistry {
} }
/// Clear out the given languages and reload them from scratch. /// Clear out the given languages and reload them from scratch.
pub fn reload_languages(&self, languages: &HashSet<Arc<str>>) { pub fn reload_languages(&self, languages: &[Arc<str>], grammars: &[Arc<str>]) {
self.state.write().reload_languages(languages); self.state.write().reload_languages(languages, grammars);
} }
pub fn register( pub fn register(
@ -849,6 +851,7 @@ impl LanguageRegistry {
state.available_languages.push(AvailableLanguage { state.available_languages.push(AvailableLanguage {
id: post_inc(&mut state.next_available_language_id), id: post_inc(&mut state.next_available_language_id),
name: config.name.clone(), name: config.name.clone(),
grammar: config.grammar.clone(),
source: AvailableLanguageSource::BuiltIn { source: AvailableLanguageSource::BuiltIn {
config, config,
get_queries, get_queries,
@ -863,6 +866,7 @@ impl LanguageRegistry {
&self, &self,
path: Arc<Path>, path: Arc<Path>,
name: Arc<str>, name: Arc<str>,
grammar_name: Option<Arc<str>>,
matcher: LanguageMatcher, matcher: LanguageMatcher,
get_queries: fn(&Path) -> LanguageQueries, get_queries: fn(&Path) -> LanguageQueries,
) { ) {
@ -885,6 +889,7 @@ impl LanguageRegistry {
} }
state.available_languages.push(AvailableLanguage { state.available_languages.push(AvailableLanguage {
id: post_inc(&mut state.next_available_language_id), id: post_inc(&mut state.next_available_language_id),
grammar: grammar_name,
name, name,
source, source,
lsp_adapters: Vec::new(), lsp_adapters: Vec::new(),
@ -894,16 +899,16 @@ impl LanguageRegistry {
pub fn add_grammars( pub fn add_grammars(
&self, &self,
grammars: impl IntoIterator<Item = (impl Into<String>, tree_sitter::Language)>, grammars: impl IntoIterator<Item = (impl Into<Arc<str>>, tree_sitter::Language)>,
) { ) {
self.state.write().grammars.extend( self.state.write().grammars.extend(
grammars grammars
.into_iter() .into_iter()
.map(|(name, grammar)| (name.into(), AvailableGrammar::Loaded(grammar))), .map(|(name, grammar)| (name.into(), AvailableGrammar::Native(grammar))),
); );
} }
pub fn register_grammar(&self, name: String, path: PathBuf) { pub fn register_grammar(&self, name: Arc<str>, path: PathBuf) {
self.state self.state
.write() .write()
.grammars .grammars
@ -1124,18 +1129,19 @@ impl LanguageRegistry {
if let Some(grammar) = state.grammars.get_mut(name.as_ref()) { if let Some(grammar) = state.grammars.get_mut(name.as_ref()) {
match grammar { match grammar {
AvailableGrammar::Loaded(grammar) => { AvailableGrammar::Native(grammar) | AvailableGrammar::Loaded(_, grammar) => {
tx.send(Ok(grammar.clone())).ok(); tx.send(Ok(grammar.clone())).ok();
} }
AvailableGrammar::Loading(txs) => { AvailableGrammar::Loading(_, txs) => {
txs.push(tx); txs.push(tx);
} }
AvailableGrammar::Unloaded(wasm_path) => { AvailableGrammar::Unloaded(wasm_path) => {
if let Some(executor) = &self.executor { if let Some(executor) = &self.executor {
let this = self.clone(); let this = self.clone();
let wasm_path = wasm_path.clone();
executor executor
.spawn(async move { .spawn({
let wasm_path = wasm_path.clone();
async move {
let wasm_bytes = std::fs::read(&wasm_path)?; let wasm_bytes = std::fs::read(&wasm_path)?;
let grammar_name = wasm_path let grammar_name = wasm_path
.file_stem() .file_stem()
@ -1144,15 +1150,16 @@ impl LanguageRegistry {
let grammar = PARSER.with(|parser| { let grammar = PARSER.with(|parser| {
let mut parser = parser.borrow_mut(); let mut parser = parser.borrow_mut();
let mut store = parser.take_wasm_store().unwrap(); let mut store = parser.take_wasm_store().unwrap();
let grammar = store.load_language(&grammar_name, &wasm_bytes); let grammar =
store.load_language(&grammar_name, &wasm_bytes);
parser.set_wasm_store(store).unwrap(); parser.set_wasm_store(store).unwrap();
grammar grammar
})?; })?;
if let Some(AvailableGrammar::Loading(txs)) = if let Some(AvailableGrammar::Loading(_, txs)) =
this.state.write().grammars.insert( this.state.write().grammars.insert(
name.to_string(), name,
AvailableGrammar::Loaded(grammar.clone()), AvailableGrammar::Loaded(wasm_path, grammar.clone()),
) )
{ {
for tx in txs { for tx in txs {
@ -1161,9 +1168,10 @@ impl LanguageRegistry {
} }
anyhow::Ok(()) anyhow::Ok(())
}
}) })
.detach(); .detach();
*grammar = AvailableGrammar::Loading(vec![tx]); *grammar = AvailableGrammar::Loading(wasm_path.clone(), vec![tx]);
} }
} }
} }
@ -1357,16 +1365,42 @@ impl LanguageRegistryState {
*self.subscription.0.borrow_mut() = (); *self.subscription.0.borrow_mut() = ();
} }
fn reload_languages(&mut self, languages: &HashSet<Arc<str>>) { fn reload_languages(
self.languages &mut self,
.retain(|language| !languages.contains(&language.config.name)); languages_to_reload: &[Arc<str>],
self.version += 1; grammars_to_reload: &[Arc<str>],
self.reload_count += 1; ) {
for (name, grammar) in self.grammars.iter_mut() {
if grammars_to_reload.contains(name) {
if let AvailableGrammar::Loaded(path, _) = grammar {
*grammar = AvailableGrammar::Unloaded(path.clone());
}
}
}
self.languages.retain(|language| {
let should_reload = languages_to_reload.contains(&language.config.name)
|| language
.config
.grammar
.as_ref()
.map_or(false, |grammar| grammars_to_reload.contains(&grammar));
!should_reload
});
for language in &mut self.available_languages { for language in &mut self.available_languages {
if languages.contains(&language.name) { if languages_to_reload.contains(&language.name)
|| language
.grammar
.as_ref()
.map_or(false, |grammar| grammars_to_reload.contains(grammar))
{
language.loaded = false; language.loaded = false;
} }
} }
self.version += 1;
self.reload_count += 1;
*self.subscription.0.borrow_mut() = (); *self.subscription.0.borrow_mut() = ();
} }

View file

@ -23,15 +23,10 @@ lazy_static::lazy_static! {
CONFIG_DIR.join("support") CONFIG_DIR.join("support")
}; };
pub static ref EXTENSIONS_DIR: PathBuf = if cfg!(target_os="macos") { pub static ref EXTENSIONS_DIR: PathBuf = if cfg!(target_os="macos") {
HOME.join("Library/Application Support/Zed") HOME.join("Library/Application Support/Zed/extensions")
} else { } else {
CONFIG_DIR.join("extensions") CONFIG_DIR.join("extensions")
}; };
pub static ref PLUGINS_DIR: PathBuf = if cfg!(target_os="macos") {
HOME.join("Library/Application Support/Zed/plugins")
} else {
CONFIG_DIR.join("plugins")
};
pub static ref LANGUAGES_DIR: PathBuf = if cfg!(target_os="macos") { pub static ref LANGUAGES_DIR: PathBuf = if cfg!(target_os="macos") {
HOME.join("Library/Application Support/Zed/languages") HOME.join("Library/Application Support/Zed/languages")
} else { } else {