diff --git a/Cargo.lock b/Cargo.lock index 299526596c..41dcf21797 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2605,6 +2605,7 @@ name = "language" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "clock", "collections", "ctor", diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index b662044553..d88315fff7 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -162,12 +162,12 @@ impl ItemView for Editor { let (language, language_server) = worktree.update(&mut cx, |worktree, cx| { let worktree = worktree.as_local_mut().unwrap(); let language = worktree - .languages() + .language_registry() .select_language(new_file.full_path()) .cloned(); let language_server = language .as_ref() - .and_then(|language| worktree.ensure_language_server(language, cx)); + .and_then(|language| worktree.register_language(language, cx)); (language, language_server.clone()) }); diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index a9a781e604..f8d5c1e836 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -27,6 +27,7 @@ text = { path = "../text" } theme = { path = "../theme" } util = { path = "../util" } anyhow = "1.0.38" +async-trait = "0.1" futures = "0.3" lazy_static = "1.4" log = "0.4" diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index fe832929a9..1c369e738b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -6,6 +6,7 @@ pub mod proto; mod tests; use anyhow::{anyhow, Result}; +use async_trait::async_trait; pub use buffer::Operation; pub use buffer::*; use collections::HashSet; @@ -15,7 +16,11 @@ use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Deserialize; -use std::{path::Path, str, sync::Arc}; +use std::{ + path::{Path, PathBuf}, + str, + sync::Arc, +}; use theme::SyntaxTheme; use tree_sitter::{self, Query}; pub use tree_sitter::{Parser, Tree}; @@ -59,9 +64,18 @@ pub struct BracketPair { pub newline: bool, } +#[async_trait] +pub trait DiagnosticSource: 'static + Send + Sync { + async fn diagnose( + &self, + path: Arc, + ) -> Result>)>>; +} + pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, + pub(crate) diagnostic_source: Option>, } pub struct Grammar { @@ -126,6 +140,7 @@ impl Language { highlight_map: Default::default(), }) }), + diagnostic_source: None, } } @@ -159,6 +174,11 @@ impl Language { Ok(self) } + pub fn with_diagnostic_source(mut self, source: impl DiagnosticSource) -> Self { + self.diagnostic_source = Some(Arc::new(source)); + self + } + pub fn name(&self) -> &str { self.config.name.as_str() } @@ -192,6 +212,10 @@ impl Language { } } + pub fn diagnostic_source(&self) -> Option<&Arc> { + self.diagnostic_source.as_ref() + } + pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet> { self.config .language_server diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index ef5435d80c..769922523c 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -226,7 +226,11 @@ impl LanguageServer { process_id: Default::default(), root_path: Default::default(), root_uri: Some(root_uri), - initialization_options: Default::default(), + initialization_options: Some(json!({ + "checkOnSave": { + "enable": false + }, + })), capabilities: lsp_types::ClientCapabilities { experimental: Some(json!({ "serverStatusNotification": true, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d0adbff793..02789e2f27 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -11,14 +11,14 @@ use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; -use language::{Buffer, DiagnosticEntry, LanguageRegistry}; +use language::{Buffer, DiagnosticEntry, Language, LanguageRegistry}; use lsp::DiagnosticSeverity; use postage::{prelude::Stream, watch}; use std::{ path::Path, sync::{atomic::AtomicBool, Arc}, }; -use util::TryFutureExt as _; +use util::{ResultExt, TryFutureExt as _}; pub use fs::*; pub use worktree::*; @@ -503,6 +503,31 @@ impl Project { } } + pub fn diagnose(&self, cx: &mut ModelContext) { + for worktree_handle in &self.worktrees { + if let Some(worktree) = worktree_handle.read(cx).as_local() { + for language in worktree.languages() { + if let Some(diagnostic_source) = language.diagnostic_source().cloned() { + let worktree_path = worktree.abs_path().clone(); + let worktree_handle = worktree_handle.downgrade(); + cx.spawn_weak(|_, cx| async move { + if let Some(diagnostics) = + diagnostic_source.diagnose(worktree_path).await.log_err() + { + if let Some(worktree_handle) = worktree_handle.upgrade(&cx) { + worktree_handle.update(&mut cx, |worktree, cx| { + for (path, diagnostics) in diagnostics {} + }) + } + } + }) + .detach(); + } + } + } + } + } + pub fn diagnostic_summaries<'a>( &'a self, cx: &'a AppContext, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 9fb753b9d2..cef2c35e35 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -291,7 +291,7 @@ impl Worktree { pub fn languages(&self) -> &Arc { match self { - Worktree::Local(worktree) => &worktree.languages, + Worktree::Local(worktree) => &worktree.language_registry, Worktree::Remote(worktree) => &worktree.languages, } } @@ -853,10 +853,11 @@ pub struct LocalWorktree { diagnostics: HashMap, Vec>>, diagnostic_summaries: BTreeMap, DiagnosticSummary>, queued_operations: Vec<(u64, Operation)>, - languages: Arc, + language_registry: Arc, client: Arc, user_store: ModelHandle, fs: Arc, + languages: Vec>, language_servers: HashMap>, } @@ -960,10 +961,11 @@ impl LocalWorktree { diagnostics: Default::default(), diagnostic_summaries: Default::default(), queued_operations: Default::default(), - languages, + language_registry: languages, client, user_store, fs, + languages: Default::default(), language_servers: Default::default(), }; @@ -1004,15 +1006,23 @@ impl LocalWorktree { self.config.collaborators.clone() } - pub fn languages(&self) -> &LanguageRegistry { + pub fn language_registry(&self) -> &LanguageRegistry { + &self.language_registry + } + + pub fn languages(&self) -> &[Arc] { &self.languages } - pub fn ensure_language_server( + pub fn register_language( &mut self, - language: &Language, + language: &Arc, cx: &mut ModelContext, ) -> Option> { + if !self.languages.iter().any(|l| Arc::ptr_eq(l, language)) { + self.languages.push(language.clone()); + } + if let Some(server) = self.language_servers.get(language.name()) { return Some(server.clone()); } @@ -1090,10 +1100,13 @@ impl LocalWorktree { let (diagnostics, language, language_server) = this.update(&mut cx, |this, cx| { let this = this.as_local_mut().unwrap(); let diagnostics = this.diagnostics.remove(&path); - let language = this.languages.select_language(file.full_path()).cloned(); + let language = this + .language_registry + .select_language(file.full_path()) + .cloned(); let server = language .as_ref() - .and_then(|language| this.ensure_language_server(language, cx)); + .and_then(|language| this.register_language(language, cx)); (diagnostics, language, server) }); @@ -1191,8 +1204,8 @@ impl LocalWorktree { self.snapshot.clone() } - pub fn abs_path(&self) -> &Path { - self.snapshot.abs_path.as_ref() + pub fn abs_path(&self) -> &Arc { + &self.snapshot.abs_path } pub fn contains_abs_path(&self, path: &Path) -> bool {