From 8d3f42de52753d8d317d4c0fa5dd4959e223e055 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 2 Nov 2021 17:41:01 -0700 Subject: [PATCH] Start language servers based on buffers' languages Co-Authored-By: Nathan Sobo --- Cargo.lock | 1 + crates/language/Cargo.toml | 8 +- crates/language/src/language.rs | 31 ++++++- crates/language/src/lib.rs | 5 +- crates/language/src/tests.rs | 8 +- crates/lsp/src/lib.rs | 7 +- crates/project/Cargo.toml | 1 + crates/project/src/lib.rs | 9 +- crates/project/src/worktree.rs | 159 +++++++++++++++++++------------- crates/server/src/rpc.rs | 59 +++++++----- crates/workspace/src/items.rs | 14 ++- crates/zed/src/language.rs | 2 +- 12 files changed, 197 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 530a2aff6e69a21cebd6d6dc3e7fef96062d86ee..3d70d53b05eddc194203581b3bbbcb28d5ab4531 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3831,6 +3831,7 @@ dependencies = [ "rpc", "serde 1.0.125", "serde_json 1.0.64", + "simplelog", "smol", "sum_tree", "tempdir", diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index b0a384e16a7667323bbbac702956b0b5992b7011..39423268e7b9489c12f319c746434a5e7f22bb8f 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -4,7 +4,12 @@ version = "0.1.0" edition = "2018" [features] -test-support = ["rand", "buffer/test-support", "lsp/test-support"] +test-support = [ + "rand", + "buffer/test-support", + "lsp/test-support", + "tree-sitter-rust", +] [dependencies] buffer = { path = "../buffer" } @@ -25,6 +30,7 @@ serde = { version = "1", features = ["derive"] } similar = "1.3" smol = "1.2" tree-sitter = "0.19.5" +tree-sitter-rust = { version = "0.19.0", optional = true } [dev-dependencies] buffer = { path = "../buffer", features = ["test-support"] } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 7b42f5dcbc041dadf3e5907f87c406bc177a690d..1f949961237627d5d0809513e734375908318de8 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1,6 +1,7 @@ use crate::HighlightMap; use anyhow::Result; -use gpui::AppContext; +use gpui::{executor::Background, AppContext}; +use lsp::LanguageServer; use parking_lot::Mutex; use serde::Deserialize; use std::{collections::HashSet, path::Path, str, sync::Arc}; @@ -16,10 +17,13 @@ pub struct LanguageConfig { pub language_server: Option, } -#[derive(Deserialize)] +#[derive(Default, Deserialize)] pub struct LanguageServerConfig { pub binary: String, pub disk_based_diagnostic_sources: HashSet, + #[cfg(any(test, feature = "test-support"))] + #[serde(skip)] + pub fake_server: Option<(Arc, Arc)>, } #[derive(Clone, Debug, Deserialize)] @@ -117,6 +121,12 @@ impl Language { cx: &AppContext, ) -> Result>> { if let Some(config) = &self.config.language_server { + #[cfg(any(test, feature = "test-support"))] + if let Some((server, started)) = &config.fake_server { + started.store(true, std::sync::atomic::Ordering::SeqCst); + return Ok(Some(server.clone())); + } + const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE"); let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? { cx.platform() @@ -151,6 +161,23 @@ impl Language { } } +#[cfg(any(test, feature = "test-support"))] +impl LanguageServerConfig { + pub async fn fake(executor: Arc) -> (Self, lsp::FakeLanguageServer) { + let (server, fake) = lsp::LanguageServer::fake(executor).await; + fake.started + .store(false, std::sync::atomic::Ordering::SeqCst); + let started = fake.started.clone(); + ( + Self { + fake_server: Some((server, started)), + ..Default::default() + }, + fake, + ) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index 735bd0f3f080000411e3f2dd26b44e024374013b..893dc164a66d21f5b980f4b0f4c1075528a59d6c 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -6,7 +6,7 @@ mod tests; pub use self::{ highlight_map::{HighlightId, HighlightMap}, - language::{BracketPair, Language, LanguageConfig, LanguageRegistry}, + language::{BracketPair, Language, LanguageConfig, LanguageRegistry, LanguageServerConfig}, }; use anyhow::{anyhow, Result}; pub use buffer::{Buffer as TextBuffer, Operation as _, *}; @@ -37,6 +37,9 @@ use std::{ use tree_sitter::{InputEdit, Parser, QueryCursor, Tree}; use util::{post_inc, TryFutureExt as _}; +#[cfg(any(test, feature = "test-support"))] +pub use tree_sitter_rust; + pub use lsp::DiagnosticSeverity; thread_local! { diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 5ab67362995437dc0e9c6e53c2ec97e25d7231de..8ee3beebf48d5f577136bea1317ce7017ed9f0e3 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -1,7 +1,6 @@ use super::*; -use crate::language::LanguageServerConfig; use gpui::{ModelHandle, MutableAppContext}; -use std::{iter::FromIterator, rc::Rc}; +use std::rc::Rc; use unindent::Unindent as _; #[gpui::test] @@ -676,10 +675,7 @@ fn rust_lang() -> Option> { LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], - language_server: Some(LanguageServerConfig { - binary: "rust-analyzer".to_string(), - disk_based_diagnostic_sources: HashSet::from_iter(vec!["rustc".to_string()]), - }), + language_server: None, ..Default::default() }, tree_sitter_rust::language(), diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 81c9431093f447cd35aa26a558e53a8707a9bc32..ef5435d80c59491f7c271311f6e8a3847a53bab6 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -16,7 +16,7 @@ use std::{ io::Write, str::FromStr, sync::{ - atomic::{AtomicUsize, Ordering::SeqCst}, + atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}, Arc, }, }; @@ -427,6 +427,7 @@ pub struct FakeLanguageServer { buffer: Vec, stdin: smol::io::BufReader, stdout: smol::io::BufWriter, + pub started: Arc, } #[cfg(any(test, feature = "test-support"))] @@ -444,6 +445,7 @@ impl LanguageServer { stdin: smol::io::BufReader::new(stdin.1), stdout: smol::io::BufWriter::new(stdout.0), buffer: Vec::new(), + started: Arc::new(AtomicBool::new(true)), }; let server = Self::new_internal(stdin.0, stdout.1, Path::new("/"), executor).unwrap(); @@ -460,6 +462,9 @@ impl LanguageServer { #[cfg(any(test, feature = "test-support"))] impl FakeLanguageServer { pub async fn notify(&mut self, params: T::Params) { + if !self.started.load(std::sync::atomic::Ordering::SeqCst) { + panic!("can't simulate an LSP notification before the server has been started"); + } let message = serde_json::to_vec(&Notification { jsonrpc: JSON_RPC_VERSION, method: T::METHOD, diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index bb00812251b95651c67874d46940b6d673a103fd..b19516055e0e8fda8f39fa8c9f28ffee78320603 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -40,5 +40,6 @@ lsp = { path = "../lsp", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } rand = "0.8.3" +simplelog = "0.9" tempdir = { version = "0.3.7" } unindent = "0.1.7" diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index 458e7bf3637e342218a276ebf5839d73e64ff7b8..3e129c8fb8a1d67feb7b7abdebb1bc89c0c5c1fe 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -12,7 +12,7 @@ use std::{ path::Path, sync::{atomic::AtomicBool, Arc}, }; -use util::{ResultExt, TryFutureExt as _}; +use util::TryFutureExt as _; pub use fs::*; pub use worktree::*; @@ -73,13 +73,8 @@ impl Project { let rpc = self.client.clone(); let languages = self.languages.clone(); let path = Arc::from(abs_path); - let language_server = languages - .get_language("Rust") - .map(|language| language.start_server(&path, cx)); cx.spawn(|this, mut cx| async move { - let language_server = language_server.and_then(|language| language.log_err().flatten()); - let worktree = - Worktree::open_local(rpc, path, fs, languages, language_server, &mut cx).await?; + let worktree = Worktree::open_local(rpc, path, fs, languages, &mut cx).await?; this.update(&mut cx, |this, cx| { this.add_worktree(worktree.clone(), cx); }); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index d8ae0e3aa86cd8832a74e56a221f08703354181a..13f33bfdb14e0d84d7950ebf72f6ee9f22d542ac 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -12,7 +12,7 @@ use gpui::{ executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle, }; -use language::{Buffer, LanguageRegistry, Operation, Rope}; +use language::{Buffer, Language, LanguageRegistry, Operation, Rope}; use lazy_static::lazy_static; use lsp::LanguageServer; use parking_lot::Mutex; @@ -98,17 +98,21 @@ impl Entity for Worktree { ) -> Option>>> { use futures::FutureExt; - if let Some(server) = self.language_server() { - if let Some(shutdown) = server.shutdown() { - return Some( - async move { - shutdown.await.log_err(); - } - .boxed(), - ); - } + if let Self::Local(worktree) = self { + let shutdown_futures = worktree + .language_servers + .drain() + .filter_map(|(_, server)| server.shutdown()) + .collect::>(); + Some( + async move { + futures::future::join_all(shutdown_futures).await; + } + .boxed(), + ) + } else { + None } - None } } @@ -118,11 +122,10 @@ impl Worktree { path: impl Into>, fs: Arc, languages: Arc, - language_server: Option>, cx: &mut AsyncAppContext, ) -> Result> { let (tree, scan_states_tx) = - LocalWorktree::new(rpc, path, fs.clone(), languages, language_server, cx).await?; + LocalWorktree::new(rpc, path, fs.clone(), languages, cx).await?; tree.update(cx, |tree, cx| { let tree = tree.as_local_mut().unwrap(); let abs_path = tree.snapshot.abs_path.clone(); @@ -315,13 +318,6 @@ impl Worktree { } } - pub fn language_server(&self) -> Option<&Arc> { - match self { - Worktree::Local(worktree) => worktree.language_server.as_ref(), - Worktree::Remote(_) => None, - } - } - pub fn handle_add_peer( &mut self, envelope: TypedEnvelope, @@ -781,7 +777,7 @@ pub struct LocalWorktree { languages: Arc, rpc: Arc, fs: Arc, - language_server: Option>, + language_servers: HashMap>, } #[derive(Default, Deserialize)] @@ -795,7 +791,6 @@ impl LocalWorktree { path: impl Into>, fs: Arc, languages: Arc, - language_server: Option>, cx: &mut AsyncAppContext, ) -> Result<(ModelHandle, Sender)> { let abs_path = path.into(); @@ -896,7 +891,7 @@ impl LocalWorktree { languages, rpc, fs, - language_server, + language_servers: Default::default(), }; cx.spawn_weak(|this, mut cx| async move { @@ -926,33 +921,57 @@ impl LocalWorktree { }) .detach(); - if let Some(language_server) = &tree.language_server { - let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); - language_server - .on_notification::(move |params| { - smol::block_on(diagnostics_tx.send(params)).ok(); - }) - .detach(); - cx.spawn_weak(|this, mut cx| async move { - while let Ok(diagnostics) = diagnostics_rx.recv().await { - if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - handle.update(&mut cx, |this, cx| { - this.update_diagnostics(diagnostics, cx).log_err(); - }); - } else { - break; - } - } - }) - .detach(); - } - Worktree::Local(tree) }); Ok((tree, scan_states_tx)) } + pub fn languages(&self) -> &LanguageRegistry { + &self.languages + } + + pub fn ensure_language_server( + &mut self, + language: &Language, + cx: &mut ModelContext, + ) -> Option> { + if let Some(server) = self.language_servers.get(language.name()) { + return Some(server.clone()); + } + + if let Some(language_server) = language + .start_server(self.abs_path(), cx) + .log_err() + .flatten() + { + let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); + language_server + .on_notification::(move |params| { + smol::block_on(diagnostics_tx.send(params)).ok(); + }) + .detach(); + cx.spawn_weak(|this, mut cx| async move { + while let Ok(diagnostics) = diagnostics_rx.recv().await { + if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { + handle.update(&mut cx, |this, cx| { + this.update_diagnostics(diagnostics, cx).log_err(); + }); + } else { + break; + } + } + }) + .detach(); + + self.language_servers + .insert(language.name().to_string(), language_server.clone()); + Some(language_server.clone()) + } else { + None + } + } + pub fn open_buffer( &mut self, path: &Path, @@ -976,7 +995,6 @@ impl LocalWorktree { }); let path = Arc::from(path); - let language_server = self.language_server.clone(); cx.spawn(|this, mut cx| async move { if let Some(existing_buffer) = existing_buffer { Ok(existing_buffer) @@ -988,11 +1006,14 @@ impl LocalWorktree { use language::File; this.languages().select_language(file.full_path()).cloned() }); - let diagnostics = this.update(&mut cx, |this, _| { - this.as_local_mut() - .unwrap() - .diagnostics - .remove(path.as_ref()) + let (diagnostics, language_server) = this.update(&mut cx, |this, cx| { + let this = this.as_local_mut().unwrap(); + ( + this.diagnostics.remove(path.as_ref()), + language + .as_ref() + .and_then(|language| this.ensure_language_server(language, cx)), + ) }); let buffer = cx.add_model(|cx| { let mut buffer = Buffer::from_file(0, contents, Box::new(file), cx); @@ -2925,7 +2946,8 @@ mod tests { use buffer::Point; use client::test::FakeServer; use fs::RealFs; - use language::Diagnostic; + use language::{tree_sitter_rust, LanguageServerConfig}; + use language::{Diagnostic, LanguageConfig}; use lsp::Url; use rand::prelude::*; use serde_json::json; @@ -2957,7 +2979,6 @@ mod tests { Arc::from(Path::new("/root")), Arc::new(fs), Default::default(), - None, &mut cx.to_async(), ) .await @@ -2990,7 +3011,6 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), - None, &mut cx.to_async(), ) .await @@ -3021,7 +3041,6 @@ mod tests { file_path.clone(), Arc::new(RealFs), Default::default(), - None, &mut cx.to_async(), ) .await @@ -3068,7 +3087,6 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), - None, &mut cx.to_async(), ) .await @@ -3229,7 +3247,6 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), - None, &mut cx.to_async(), ) .await @@ -3284,7 +3301,6 @@ mod tests { "/path/to/the-dir".as_ref(), fs, Default::default(), - None, &mut cx.to_async(), ) .await @@ -3333,7 +3349,6 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), - None, &mut cx.to_async(), ) .await @@ -3467,7 +3482,6 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), - None, &mut cx.to_async(), ) .await @@ -3555,7 +3569,21 @@ mod tests { #[gpui::test] async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) { - let (language_server, mut fake_lsp) = LanguageServer::fake(cx.background()).await; + simplelog::SimpleLogger::init(log::LevelFilter::Info, Default::default()).unwrap(); + + let (language_server_config, mut fake_server) = + LanguageServerConfig::fake(cx.background()).await; + let mut languages = LanguageRegistry::new(); + languages.add(Arc::new(Language::new( + LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + language_server: Some(language_server_config), + ..Default::default() + }, + tree_sitter_rust::language(), + ))); + let dir = temp_tree(json!({ "a.rs": "fn a() { A }", "b.rs": "const y: i32 = 1", @@ -3565,8 +3593,7 @@ mod tests { Client::new(), dir.path(), Arc::new(RealFs), - Default::default(), - Some(language_server), + Arc::new(languages), &mut cx.to_async(), ) .await @@ -3574,7 +3601,13 @@ mod tests { cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; - fake_lsp + // Cause worktree to start the fake language server + let _buffer = tree + .update(&mut cx, |tree, cx| tree.open_buffer("b.rs", cx)) + .await + .unwrap(); + + fake_server .notify::(lsp::PublishDiagnosticsParams { uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(), version: None, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 2139b5560ebde1b37a47490437f4c1885c5da947..aebebc589177910c7dd31a7992f3d7c084b39bfb 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -982,7 +982,10 @@ mod tests { }, editor::{Editor, EditorSettings, Input}, fs::{FakeFs, Fs as _}, - language::{Diagnostic, LanguageRegistry, Point}, + language::{ + tree_sitter_rust, Diagnostic, Language, LanguageConfig, LanguageRegistry, + LanguageServerConfig, Point, + }, lsp, people_panel::JoinWorktree, project::{ProjectPath, Worktree}, @@ -1017,7 +1020,6 @@ mod tests { "/a".as_ref(), fs, lang_registry.clone(), - None, &mut cx_a.to_async(), ) .await @@ -1126,7 +1128,6 @@ mod tests { "/a".as_ref(), fs, lang_registry.clone(), - None, &mut cx_a.to_async(), ) .await @@ -1219,7 +1220,6 @@ mod tests { "/a".as_ref(), fs.clone(), lang_registry.clone(), - None, &mut cx_a.to_async(), ) .await @@ -1356,7 +1356,6 @@ mod tests { "/dir".as_ref(), fs, lang_registry.clone(), - None, &mut cx_a.to_async(), ) .await @@ -1441,7 +1440,6 @@ mod tests { "/dir".as_ref(), fs, lang_registry.clone(), - None, &mut cx_a.to_async(), ) .await @@ -1508,7 +1506,6 @@ mod tests { "/dir".as_ref(), fs, lang_registry.clone(), - None, &mut cx_a.to_async(), ) .await @@ -1570,7 +1567,6 @@ mod tests { "/a".as_ref(), fs, lang_registry.clone(), - None, &mut cx_a.to_async(), ) .await @@ -1609,9 +1605,20 @@ mod tests { mut cx_b: TestAppContext, ) { cx_a.foreground().forbid_parking(); - let lang_registry = Arc::new(LanguageRegistry::new()); + let (language_server_config, mut fake_language_server) = + LanguageServerConfig::fake(cx_a.background()).await; + let mut lang_registry = LanguageRegistry::new(); + lang_registry.add(Arc::new(Language::new( + LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + language_server: Some(language_server_config), + ..Default::default() + }, + tree_sitter_rust::language(), + ))); - let (language_server, mut fake_lsp) = lsp::LanguageServer::fake(cx_a.background()).await; + let lang_registry = Arc::new(lang_registry); // Connect to a server as 2 clients. let mut server = TestServer::start().await; @@ -1624,8 +1631,8 @@ mod tests { "/a", json!({ ".zed.toml": r#"collaborators = ["user_b"]"#, - "a.txt": "one two three", - "b.txt": "b-contents", + "a.rs": "let one = two", + "other.rs": "", }), ) .await; @@ -1634,7 +1641,6 @@ mod tests { "/a".as_ref(), fs, lang_registry.clone(), - Some(language_server), &mut cx_a.to_async(), ) .await @@ -1647,21 +1653,33 @@ mod tests { .await .unwrap(); + // Cause language server to start. + let _ = cx_a + .background() + .spawn(worktree_a.update(&mut cx_a, |worktree, cx| { + worktree.open_buffer("other.rs", cx) + })) + .await + .unwrap(); + // Simulate a language server reporting errors for a file. - fake_lsp + fake_language_server .notify::(lsp::PublishDiagnosticsParams { - uri: lsp::Url::from_file_path("/a/a.txt").unwrap(), + uri: lsp::Url::from_file_path("/a/a.rs").unwrap(), version: None, diagnostics: vec![ lsp::Diagnostic { severity: Some(lsp::DiagnosticSeverity::ERROR), - range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 3)), + range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)), message: "message 1".to_string(), ..Default::default() }, lsp::Diagnostic { severity: Some(lsp::DiagnosticSeverity::WARNING), - range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 13)), + range: lsp::Range::new( + lsp::Position::new(0, 10), + lsp::Position::new(0, 13), + ), message: "message 2".to_string(), ..Default::default() }, @@ -1682,7 +1700,7 @@ mod tests { // Open the file with the errors. let buffer_b = cx_b .background() - .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.txt", cx))) + .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx))) .await .unwrap(); @@ -1693,14 +1711,14 @@ mod tests { .collect::>(), &[ ( - Point::new(0, 0)..Point::new(0, 3), + Point::new(0, 4)..Point::new(0, 7), &Diagnostic { message: "message 1".to_string(), severity: lsp::DiagnosticSeverity::ERROR, } ), ( - Point { row: 0, column: 8 }..Point { row: 0, column: 13 }, + Point::new(0, 10)..Point::new(0, 13), &Diagnostic { severity: lsp::DiagnosticSeverity::WARNING, message: "message 2".to_string() @@ -2149,7 +2167,6 @@ mod tests { "/a".as_ref(), fs.clone(), lang_registry.clone(), - None, &mut cx_a.to_async(), ) .await diff --git a/crates/workspace/src/items.rs b/crates/workspace/src/items.rs index e9411e93cc1a93fd5e42ba20e21c8a8aa57210c8..0b4b5f0d51719843d6ff61fc6ca5720784e8f196 100644 --- a/crates/workspace/src/items.rs +++ b/crates/workspace/src/items.rs @@ -127,10 +127,16 @@ impl ItemView for Editor { cx.spawn(|buffer, mut cx| async move { save_as.await.map(|new_file| { - let (language, language_server) = worktree.read_with(&cx, |worktree, _| { - let language = worktree.languages().select_language(new_file.full_path()); - let language_server = worktree.language_server(); - (language.cloned(), language_server.cloned()) + let (language, language_server) = worktree.update(&mut cx, |worktree, cx| { + let worktree = worktree.as_local_mut().unwrap(); + let language = worktree + .languages() + .select_language(new_file.full_path()) + .cloned(); + let language_server = language + .as_ref() + .and_then(|language| worktree.ensure_language_server(language, cx)); + (language, language_server.clone()) }); buffer.update(&mut cx, |buffer, cx| { diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index 2c60ddd92c81d458ce7267a76844a97de131c41a..3b77a0cf3ad82dff5d14de8b167174669b072068 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -1,4 +1,4 @@ -pub use language::{Buffer, Diagnostic, Language, LanguageRegistry, Point}; +pub use language::*; use rust_embed::RustEmbed; use std::borrow::Cow; use std::{str, sync::Arc};