mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 02:46:43 +00:00
Make language server initialization asynchronous
This commit is contained in:
parent
b8523509da
commit
1ca50d0134
4 changed files with 153 additions and 148 deletions
|
@ -8,7 +8,7 @@ mod tests;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use gpui::AppContext;
|
use gpui::{AppContext, Task};
|
||||||
use highlight_map::HighlightMap;
|
use highlight_map::HighlightMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -229,9 +229,9 @@ impl Language {
|
||||||
|
|
||||||
pub fn start_server(
|
pub fn start_server(
|
||||||
&self,
|
&self,
|
||||||
root_path: &Path,
|
root_path: Arc<Path>,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Result<Option<Arc<lsp::LanguageServer>>> {
|
) -> Task<Result<Option<Arc<lsp::LanguageServer>>>> {
|
||||||
if let Some(config) = &self.config.language_server {
|
if let Some(config) = &self.config.language_server {
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
if let Some(fake_config) = &config.fake_config {
|
if let Some(fake_config) = &config.fake_config {
|
||||||
|
@ -255,19 +255,19 @@ impl Language {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
return Ok(Some(server.clone()));
|
return Task::ready(Ok(Some(server.clone())));
|
||||||
}
|
}
|
||||||
|
|
||||||
const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
|
let background = cx.background().clone();
|
||||||
let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
|
let server = lsp::LanguageServer::new(
|
||||||
cx.platform()
|
Path::new(&config.binary),
|
||||||
.path_for_resource(Some(&config.binary), None)?
|
&root_path,
|
||||||
|
cx.background().clone(),
|
||||||
|
)
|
||||||
|
.map(Some);
|
||||||
|
cx.background().spawn(async move { server })
|
||||||
} else {
|
} else {
|
||||||
Path::new(&config.binary).to_path_buf()
|
Task::ready(Ok(None))
|
||||||
};
|
|
||||||
lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,8 @@ pub struct Project {
|
||||||
active_entry: Option<ProjectEntry>,
|
active_entry: Option<ProjectEntry>,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
|
language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
|
||||||
|
loading_language_servers:
|
||||||
|
HashMap<(WorktreeId, String), watch::Receiver<Option<Arc<LanguageServer>>>>,
|
||||||
client: Arc<client::Client>,
|
client: Arc<client::Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
@ -258,6 +260,7 @@ impl Project {
|
||||||
fs,
|
fs,
|
||||||
language_servers_with_diagnostics_running: 0,
|
language_servers_with_diagnostics_running: 0,
|
||||||
language_servers: Default::default(),
|
language_servers: Default::default(),
|
||||||
|
loading_language_servers: Default::default(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -309,6 +312,7 @@ impl Project {
|
||||||
},
|
},
|
||||||
language_servers_with_diagnostics_running: 0,
|
language_servers_with_diagnostics_running: 0,
|
||||||
language_servers: Default::default(),
|
language_servers: Default::default(),
|
||||||
|
loading_language_servers: Default::default(),
|
||||||
};
|
};
|
||||||
for worktree in worktrees {
|
for worktree in worktrees {
|
||||||
this.add_worktree(&worktree, cx);
|
this.add_worktree(&worktree, cx);
|
||||||
|
@ -776,7 +780,7 @@ impl Project {
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the buffer has a language, set it and start/assign the language server
|
// If the buffer has a language, set it and start/assign the language server
|
||||||
if let Some(language) = self.languages.select_language(&full_path) {
|
if let Some(language) = self.languages.select_language(&full_path).cloned() {
|
||||||
buffer.update(cx, |buffer, cx| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
buffer.set_language(Some(language.clone()), cx);
|
buffer.set_language(Some(language.clone()), cx);
|
||||||
});
|
});
|
||||||
|
@ -786,26 +790,22 @@ impl Project {
|
||||||
if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
|
if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
|
||||||
let worktree_id = local_worktree.id();
|
let worktree_id = local_worktree.id();
|
||||||
let worktree_abs_path = local_worktree.abs_path().clone();
|
let worktree_abs_path = local_worktree.abs_path().clone();
|
||||||
|
let buffer = buffer.downgrade();
|
||||||
|
let language_server =
|
||||||
|
self.start_language_server(worktree_id, worktree_abs_path, language, cx);
|
||||||
|
|
||||||
let language_server = match self
|
cx.spawn_weak(|_, mut cx| async move {
|
||||||
.language_servers
|
if let Some(language_server) = language_server.await {
|
||||||
.entry((worktree_id, language.name().to_string()))
|
if let Some(buffer) = buffer.upgrade(&cx) {
|
||||||
{
|
buffer.update(&mut cx, |buffer, cx| {
|
||||||
hash_map::Entry::Occupied(e) => Some(e.get().clone()),
|
buffer.set_language_server(Some(language_server), cx);
|
||||||
hash_map::Entry::Vacant(e) => Self::start_language_server(
|
|
||||||
self.client.clone(),
|
|
||||||
language.clone(),
|
|
||||||
&worktree_abs_path,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.map(|server| e.insert(server).clone()),
|
|
||||||
};
|
|
||||||
|
|
||||||
buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer.set_language_server(language_server, cx);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
|
if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
|
||||||
if let Some(diagnostics) = local_worktree.diagnostics_for_path(&path) {
|
if let Some(diagnostics) = local_worktree.diagnostics_for_path(&path) {
|
||||||
|
@ -819,21 +819,46 @@ impl Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_language_server(
|
fn start_language_server(
|
||||||
rpc: Arc<Client>,
|
&mut self,
|
||||||
|
worktree_id: WorktreeId,
|
||||||
|
worktree_path: Arc<Path>,
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
worktree_path: &Path,
|
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<Arc<LanguageServer>> {
|
) -> Task<Option<Arc<LanguageServer>>> {
|
||||||
enum LspEvent {
|
enum LspEvent {
|
||||||
DiagnosticsStart,
|
DiagnosticsStart,
|
||||||
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
||||||
DiagnosticsFinish,
|
DiagnosticsFinish,
|
||||||
}
|
}
|
||||||
|
|
||||||
let language_server = language
|
let key = (worktree_id, language.name().to_string());
|
||||||
.start_server(worktree_path, cx)
|
if let Some(language_server) = self.language_servers.get(&key) {
|
||||||
.log_err()
|
return Task::ready(Some(language_server.clone()));
|
||||||
.flatten()?;
|
} else if let Some(mut language_server) = self.loading_language_servers.get(&key).cloned() {
|
||||||
|
return cx
|
||||||
|
.foreground()
|
||||||
|
.spawn(async move { language_server.recv().await.flatten() });
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut language_server_tx, language_server_rx) = watch::channel();
|
||||||
|
self.loading_language_servers
|
||||||
|
.insert(key.clone(), language_server_rx);
|
||||||
|
let language_server = language.start_server(worktree_path, cx);
|
||||||
|
let rpc = self.client.clone();
|
||||||
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
|
let language_server = language_server.await.log_err().flatten();
|
||||||
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
this.loading_language_servers.remove(&key);
|
||||||
|
if let Some(language_server) = language_server.clone() {
|
||||||
|
this.language_servers.insert(key, language_server);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let language_server = language_server?;
|
||||||
|
*language_server_tx.borrow_mut() = Some(language_server.clone());
|
||||||
|
|
||||||
let disk_based_sources = language
|
let disk_based_sources = language
|
||||||
.disk_based_diagnostic_sources()
|
.disk_based_diagnostic_sources()
|
||||||
.cloned()
|
.cloned()
|
||||||
|
@ -876,13 +901,15 @@ impl Project {
|
||||||
lsp::WorkDoneProgress::Begin(_) => {
|
lsp::WorkDoneProgress::Begin(_) => {
|
||||||
running_jobs_for_this_server += 1;
|
running_jobs_for_this_server += 1;
|
||||||
if running_jobs_for_this_server == 1 {
|
if running_jobs_for_this_server == 1 {
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
|
block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart))
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lsp::WorkDoneProgress::End(_) => {
|
lsp::WorkDoneProgress::End(_) => {
|
||||||
running_jobs_for_this_server -= 1;
|
running_jobs_for_this_server -= 1;
|
||||||
if running_jobs_for_this_server == 0 {
|
if running_jobs_for_this_server == 0 {
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
|
block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish))
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -893,7 +920,7 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
// Process all the LSP events.
|
// Process all the LSP events.
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
while let Ok(message) = diagnostics_rx.recv().await {
|
while let Ok(message) = diagnostics_rx.recv().await {
|
||||||
let this = this.upgrade(&cx)?;
|
let this = this.upgrade(&cx)?;
|
||||||
match message {
|
match message {
|
||||||
|
@ -929,6 +956,7 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
Some(language_server)
|
Some(language_server)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
|
|
|
@ -21,9 +21,6 @@ cargo build --release --target aarch64-apple-darwin
|
||||||
# Replace the bundle's binary with a "fat binary" that combines the two architecture-specific binaries
|
# Replace the bundle's binary with a "fat binary" that combines the two architecture-specific binaries
|
||||||
lipo -create target/x86_64-apple-darwin/release/Zed target/aarch64-apple-darwin/release/Zed -output target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed
|
lipo -create target/x86_64-apple-darwin/release/Zed target/aarch64-apple-darwin/release/Zed -output target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed
|
||||||
|
|
||||||
# Bundle rust-analyzer
|
|
||||||
cp vendor/bin/rust-analyzer target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/Resources/
|
|
||||||
|
|
||||||
# Sign the app bundle with an ad-hoc signature so it runs on the M1. We need a real certificate but this works for now.
|
# Sign the app bundle with an ad-hoc signature so it runs on the M1. We need a real certificate but this works for now.
|
||||||
if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then
|
if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then
|
||||||
echo "Signing bundle with Apple-issued certificate"
|
echo "Signing bundle with Apple-issued certificate"
|
||||||
|
@ -34,7 +31,6 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR
|
||||||
security import /tmp/zed-certificate.p12 -k zed.keychain -P $MACOS_CERTIFICATE_PASSWORD -T /usr/bin/codesign
|
security import /tmp/zed-certificate.p12 -k zed.keychain -P $MACOS_CERTIFICATE_PASSWORD -T /usr/bin/codesign
|
||||||
rm /tmp/zed-certificate.p12
|
rm /tmp/zed-certificate.p12
|
||||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $MACOS_CERTIFICATE_PASSWORD zed.keychain
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $MACOS_CERTIFICATE_PASSWORD zed.keychain
|
||||||
/usr/bin/codesign --force --deep --timestamp --options runtime --sign "Zed Industries, Inc." target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/Resources/rust-analyzer -v
|
|
||||||
/usr/bin/codesign --force --deep --timestamp --options runtime --sign "Zed Industries, Inc." target/x86_64-apple-darwin/release/bundle/osx/Zed.app -v
|
/usr/bin/codesign --force --deep --timestamp --options runtime --sign "Zed Industries, Inc." target/x86_64-apple-darwin/release/bundle/osx/Zed.app -v
|
||||||
security default-keychain -s login.keychain
|
security default-keychain -s login.keychain
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
export RUST_ANALYZER_URL="https://github.com/rust-analyzer/rust-analyzer/releases/download/2022-01-24/"
|
|
||||||
|
|
||||||
function download {
|
|
||||||
local filename="rust-analyzer-$1"
|
|
||||||
curl -L $RUST_ANALYZER_URL/$filename.gz | gunzip > vendor/bin/$filename
|
|
||||||
chmod +x vendor/bin/$filename
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdir -p vendor/bin
|
|
||||||
download "x86_64-apple-darwin"
|
|
||||||
download "aarch64-apple-darwin"
|
|
||||||
|
|
||||||
cd vendor/bin
|
|
||||||
lipo -create rust-analyzer-* -output rust-analyzer
|
|
||||||
rm rust-analyzer-*
|
|
Loading…
Reference in a new issue