mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-27 12:54:42 +00:00
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:
commit
bdd3e77e02
3 changed files with 92 additions and 12 deletions
|
@ -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"))]
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>)> {
|
||||||
|
|
Loading…
Reference in a new issue