Live-reload tree-sitter queries in development (#2578)

This PR adds live reloading of Tree-sitter queries when running in debug
mode, similar to what we do for the themes. This way, you can change a
highlighting query or an outline query, and immediately see the result
in the app.

Release Notes:

- N/A
This commit is contained in:
Max Brunsfeld 2023-06-06 14:30:28 -07:00 committed by GitHub
commit bdd3e77e02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 12 deletions

View file

@ -34,7 +34,7 @@ use std::{
fmt::Debug, fmt::Debug,
hash::Hash, hash::Hash,
mem, mem,
ops::Range, ops::{Not, Range},
path::{Path, PathBuf}, path::{Path, PathBuf},
str, str,
sync::{ sync::{
@ -500,6 +500,7 @@ struct AvailableLanguage {
grammar: tree_sitter::Language, grammar: tree_sitter::Language,
lsp_adapters: Vec<Arc<dyn LspAdapter>>, lsp_adapters: Vec<Arc<dyn LspAdapter>>,
get_queries: fn(&str) -> LanguageQueries, get_queries: fn(&str) -> LanguageQueries,
loaded: bool,
} }
pub struct LanguageRegistry { pub struct LanguageRegistry {
@ -527,6 +528,7 @@ struct LanguageRegistryState {
subscription: (watch::Sender<()>, watch::Receiver<()>), subscription: (watch::Sender<()>, watch::Receiver<()>),
theme: Option<Arc<Theme>>, theme: Option<Arc<Theme>>,
version: usize, version: usize,
reload_count: usize,
} }
pub struct PendingLanguageServer { pub struct PendingLanguageServer {
@ -547,6 +549,7 @@ impl LanguageRegistry {
subscription: watch::channel(), subscription: watch::channel(),
theme: Default::default(), theme: Default::default(),
version: 0, version: 0,
reload_count: 0,
}), }),
language_server_download_dir: None, language_server_download_dir: None,
lsp_binary_statuses_tx, lsp_binary_statuses_tx,
@ -566,6 +569,14 @@ impl LanguageRegistry {
self.executor = Some(executor); self.executor = Some(executor);
} }
/// Clear out all of the loaded languages and reload them from scratch.
///
/// This is useful in development, when queries have changed.
#[cfg(debug_assertions)]
pub fn reload(&self) {
self.state.write().reload();
}
pub fn register( pub fn register(
&self, &self,
path: &'static str, path: &'static str,
@ -582,6 +593,7 @@ impl LanguageRegistry {
grammar, grammar,
lsp_adapters, lsp_adapters,
get_queries, get_queries,
loaded: false,
}); });
} }
@ -590,7 +602,7 @@ impl LanguageRegistry {
let mut result = state let mut result = state
.available_languages .available_languages
.iter() .iter()
.map(|l| l.config.name.to_string()) .filter_map(|l| l.loaded.not().then_some(l.config.name.to_string()))
.chain(state.languages.iter().map(|l| l.config.name.to_string())) .chain(state.languages.iter().map(|l| l.config.name.to_string()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
result.sort_unstable_by_key(|language_name| language_name.to_lowercase()); result.sort_unstable_by_key(|language_name| language_name.to_lowercase());
@ -603,6 +615,7 @@ impl LanguageRegistry {
state state
.available_languages .available_languages
.iter() .iter()
.filter(|l| !l.loaded)
.flat_map(|l| l.lsp_adapters.clone()) .flat_map(|l| l.lsp_adapters.clone())
.chain( .chain(
state state
@ -639,10 +652,17 @@ impl LanguageRegistry {
self.state.read().subscription.1.clone() self.state.read().subscription.1.clone()
} }
/// The number of times that the registry has been changed,
/// by adding languages or reloading.
pub fn version(&self) -> usize { pub fn version(&self) -> usize {
self.state.read().version self.state.read().version
} }
/// The number of times that the registry has been reloaded.
pub fn reload_count(&self) -> usize {
self.state.read().reload_count
}
pub fn set_theme(&self, theme: Arc<Theme>) { pub fn set_theme(&self, theme: Arc<Theme>) {
let mut state = self.state.write(); let mut state = self.state.write();
state.theme = Some(theme.clone()); state.theme = Some(theme.clone());
@ -721,7 +741,7 @@ impl LanguageRegistry {
if let Some(language) = state if let Some(language) = state
.available_languages .available_languages
.iter() .iter()
.find(|l| callback(&l.config)) .find(|l| !l.loaded && callback(&l.config))
.cloned() .cloned()
{ {
let txs = state let txs = state
@ -743,9 +763,7 @@ impl LanguageRegistry {
let language = Arc::new(language); let language = Arc::new(language);
let mut state = this.state.write(); let mut state = this.state.write();
state.add(language.clone()); state.add(language.clone());
state state.mark_language_loaded(id);
.available_languages
.retain(|language| language.id != id);
if let Some(mut txs) = state.loading_languages.remove(&id) { if let Some(mut txs) = state.loading_languages.remove(&id) {
for tx in txs.drain(..) { for tx in txs.drain(..) {
let _ = tx.send(Ok(language.clone())); let _ = tx.send(Ok(language.clone()));
@ -754,9 +772,7 @@ impl LanguageRegistry {
} }
Err(err) => { Err(err) => {
let mut state = this.state.write(); let mut state = this.state.write();
state state.mark_language_loaded(id);
.available_languages
.retain(|language| language.id != id);
if let Some(mut txs) = state.loading_languages.remove(&id) { if let Some(mut txs) = state.loading_languages.remove(&id) {
for tx in txs.drain(..) { for tx in txs.drain(..) {
let _ = tx.send(Err(anyhow!( let _ = tx.send(Err(anyhow!(
@ -905,6 +921,28 @@ impl LanguageRegistryState {
self.version += 1; self.version += 1;
*self.subscription.0.borrow_mut() = (); *self.subscription.0.borrow_mut() = ();
} }
#[cfg(debug_assertions)]
fn reload(&mut self) {
self.languages.clear();
self.version += 1;
self.reload_count += 1;
for language in &mut self.available_languages {
language.loaded = false;
}
*self.subscription.0.borrow_mut() = ();
}
/// Mark the given language a having been loaded, so that the
/// language registry won't try to load it again.
fn mark_language_loaded(&mut self, id: AvailableLanguageId) {
for language in &mut self.available_languages {
if language.id == id {
language.loaded = true;
break;
}
}
}
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]

View file

@ -523,7 +523,7 @@ impl Project {
_subscriptions: vec![ _subscriptions: vec![
cx.observe_global::<SettingsStore, _>(Self::on_settings_changed) cx.observe_global::<SettingsStore, _>(Self::on_settings_changed)
], ],
_maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx), _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
_maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx), _maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx),
active_entry: None, active_entry: None,
languages, languages,
@ -592,7 +592,7 @@ impl Project {
active_entry: None, active_entry: None,
collaborators: Default::default(), collaborators: Default::default(),
join_project_response_message_id: response.message_id, join_project_response_message_id: response.message_id,
_maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx), _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
_maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx), _maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx),
languages, languages,
user_store: user_store.clone(), user_store: user_store.clone(),
@ -2238,13 +2238,34 @@ impl Project {
} }
fn maintain_buffer_languages( fn maintain_buffer_languages(
languages: &LanguageRegistry, languages: Arc<LanguageRegistry>,
cx: &mut ModelContext<Project>, cx: &mut ModelContext<Project>,
) -> Task<()> { ) -> Task<()> {
let mut subscription = languages.subscribe(); let mut subscription = languages.subscribe();
let mut prev_reload_count = languages.reload_count();
cx.spawn_weak(|project, mut cx| async move { cx.spawn_weak(|project, mut cx| async move {
while let Some(()) = subscription.next().await { while let Some(()) = subscription.next().await {
if let Some(project) = project.upgrade(&cx) { if let Some(project) = project.upgrade(&cx) {
// If the language registry has been reloaded, then remove and
// re-assign the languages on all open buffers.
let reload_count = languages.reload_count();
if reload_count > prev_reload_count {
prev_reload_count = reload_count;
project.update(&mut cx, |this, cx| {
let buffers = this
.opened_buffers
.values()
.filter_map(|b| b.upgrade(cx))
.collect::<Vec<_>>();
for buffer in buffers {
if let Some(f) = File::from_dyn(buffer.read(cx).file()).cloned() {
this.unregister_buffer_from_language_servers(&buffer, &f, cx);
buffer.update(cx, |buffer, cx| buffer.set_language(None, cx));
}
}
});
}
project.update(&mut cx, |project, cx| { project.update(&mut cx, |project, cx| {
let mut plain_text_buffers = Vec::new(); let mut plain_text_buffers = Vec::new();
let mut buffers_with_unknown_injections = Vec::new(); let mut buffers_with_unknown_injections = Vec::new();

View file

@ -160,6 +160,8 @@ fn main() {
ai::init(cx); ai::init(cx);
cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach(); cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach();
cx.spawn(|_| watch_languages(fs.clone(), languages.clone()))
.detach();
languages.set_theme(theme::current(cx).clone()); languages.set_theme(theme::current(cx).clone());
cx.observe_global::<SettingsStore, _>({ cx.observe_global::<SettingsStore, _>({
@ -660,11 +662,30 @@ async fn watch_themes(fs: Arc<dyn Fs>, mut cx: AsyncAppContext) -> Option<()> {
Some(()) Some(())
} }
#[cfg(debug_assertions)]
async fn watch_languages(fs: Arc<dyn Fs>, languages: Arc<LanguageRegistry>) -> Option<()> {
let mut events = fs
.watch(
"crates/zed/src/languages".as_ref(),
Duration::from_millis(100),
)
.await;
while (events.next().await).is_some() {
languages.reload();
}
Some(())
}
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
async fn watch_themes(_fs: Arc<dyn Fs>, _cx: AsyncAppContext) -> Option<()> { async fn watch_themes(_fs: Arc<dyn Fs>, _cx: AsyncAppContext) -> Option<()> {
None None
} }
#[cfg(not(debug_assertions))]
async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
None
}
fn connect_to_cli( fn connect_to_cli(
server_name: &str, server_name: &str,
) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> { ) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {