Detect LSP startup failure

This commit is contained in:
Julia 2023-06-07 14:01:50 -04:00
parent 0a8d67c4ca
commit ec0409a3d1
3 changed files with 351 additions and 291 deletions

View file

@ -887,8 +887,8 @@ impl LanguageRegistry {
}) })
.clone(); .clone();
drop(lock); drop(lock);
let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
let server = lsp::LanguageServer::new( let server = lsp::LanguageServer::new(
server_id, server_id,
&binary.path, &binary.path,

View file

@ -167,6 +167,7 @@ impl LanguageServer {
if let Some(name) = binary_path.file_name() { if let Some(name) = binary_path.file_name() {
server.name = name.to_string_lossy().to_string(); server.name = name.to_string_lossy().to_string();
} }
Ok(server) Ok(server)
} }

View file

@ -2383,16 +2383,9 @@ impl Project {
language: Arc<Language>, language: Arc<Language>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
if !language_settings( let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx));
Some(&language), let settings = language_settings(Some(&language), root_file.map(|f| f as _).as_ref(), cx);
worktree if !settings.enable_language_server {
.update(cx, |tree, cx| tree.root_file(cx))
.map(|f| f as _)
.as_ref(),
cx,
)
.enable_language_server
{
return; return;
} }
@ -2414,9 +2407,8 @@ impl Project {
None => continue, None => continue,
}; };
let lsp = settings::get::<ProjectSettings>(cx) let project_settings = settings::get::<ProjectSettings>(cx);
.lsp let lsp = project_settings.lsp.get(&adapter.name.0);
.get(&adapter.name.0);
let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten();
let mut initialization_options = adapter.initialization_options.clone(); let mut initialization_options = adapter.initialization_options.clone();
@ -2429,34 +2421,98 @@ impl Project {
} }
let server_id = pending_server.server_id; let server_id = pending_server.server_id;
let state = self.setup_pending_language_server( let state = LanguageServerState::Starting({
let server_name = adapter.name.0.clone();
let adapter = adapter.clone();
let language = language.clone();
let languages = self.languages.clone();
let key = key.clone();
cx.spawn_weak(|this, cx| async move {
let result = Self::setup_and_insert_language_server(
this,
initialization_options, initialization_options,
pending_server, pending_server,
adapter.clone(), adapter,
language.clone(), languages,
key.clone(), language,
server_id,
key,
cx, cx,
); )
.await;
match result {
Ok(server) => Some(server),
Err(err) => {
log::warn!("Error starting language server {:?}: {}", server_name, err);
// TODO: Prompt installation validity check
None
}
}
})
});
self.language_servers.insert(server_id, state); self.language_servers.insert(server_id, state);
self.language_server_ids.insert(key.clone(), server_id); self.language_server_ids.insert(key, server_id);
} }
} }
fn setup_pending_language_server( async fn setup_and_insert_language_server(
&mut self, this: WeakModelHandle<Self>,
initialization_options: Option<serde_json::Value>, initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer, pending_server: PendingLanguageServer,
adapter: Arc<CachedLspAdapter>, adapter: Arc<CachedLspAdapter>,
languages: Arc<LanguageRegistry>,
language: Arc<Language>, language: Arc<Language>,
server_id: LanguageServerId,
key: (WorktreeId, LanguageServerName), key: (WorktreeId, LanguageServerName),
cx: &mut ModelContext<Project>, mut cx: AsyncAppContext,
) -> LanguageServerState { ) -> Result<Arc<LanguageServer>> {
let server_id = pending_server.server_id; let language_server = Self::setup_pending_language_server(
let languages = self.languages.clone(); this,
initialization_options,
pending_server,
adapter.clone(),
languages,
server_id,
&mut cx,
)
.await?;
LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { let this = match this.upgrade(&mut cx) {
Some(this) => this,
None => return Err(anyhow!("failed to upgrade project handle")),
};
this.update(&mut cx, |this, cx| {
this.insert_newly_running_language_server(
language,
adapter,
language_server.clone(),
server_id,
key,
cx,
)
})?;
Ok(language_server)
}
async fn setup_pending_language_server(
this: WeakModelHandle<Self>,
initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
adapter: Arc<CachedLspAdapter>,
languages: Arc<LanguageRegistry>,
server_id: LanguageServerId,
cx: &mut AsyncAppContext,
) -> Result<Arc<LanguageServer>> {
let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await;
let language_server = pending_server.task.await.log_err()?;
let language_server = pending_server.task.await?;
let language_server = language_server.initialize(initialization_options).await?;
language_server language_server
.on_notification::<lsp::notification::LogMessage, _>({ .on_notification::<lsp::notification::LogMessage, _>({
@ -2474,6 +2530,7 @@ impl Project {
.on_notification::<lsp::notification::PublishDiagnostics, _>({ .on_notification::<lsp::notification::PublishDiagnostics, _>({
let adapter = adapter.clone(); let adapter = adapter.clone();
move |mut params, cx| { move |mut params, cx| {
let this = this;
let adapter = adapter.clone(); let adapter = adapter.clone();
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
adapter.process_diagnostics(&mut params).await; adapter.process_diagnostics(&mut params).await;
@ -2529,8 +2586,7 @@ impl Project {
move |params, mut cx| async move { move |params, mut cx| async move {
if let Some(this) = this.upgrade(&cx) { if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, _| { this.update(&mut cx, |this, _| {
if let Some(status) = if let Some(status) = this.language_server_statuses.get_mut(&server_id)
this.language_server_statuses.get_mut(&server_id)
{ {
if let lsp::NumberOrString::String(token) = params.token { if let lsp::NumberOrString::String(token) = params.token {
status.progress_tokens.insert(token); status.progress_tokens.insert(token);
@ -2543,7 +2599,7 @@ impl Project {
) )
.detach(); .detach();
language_server language_server
.on_request::<lsp::request::RegisterCapability, _, _>( .on_request::<lsp::request::RegisterCapability, _, _>({
move |params, mut cx| async move { move |params, mut cx| async move {
let this = this let this = this
.upgrade(&cx) .upgrade(&cx)
@ -2553,16 +2609,14 @@ impl Project {
if let Some(options) = reg.register_options { if let Some(options) = reg.register_options {
let options = serde_json::from_value(options)?; let options = serde_json::from_value(options)?;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.on_lsp_did_change_watched_files( this.on_lsp_did_change_watched_files(server_id, options, cx);
server_id, options, cx,
);
}); });
} }
} }
} }
Ok(()) Ok(())
}, }
) })
.detach(); .detach();
language_server language_server
@ -2578,8 +2632,7 @@ impl Project {
adapter.disk_based_diagnostics_progress_token.clone(); adapter.disk_based_diagnostics_progress_token.clone();
language_server language_server
.on_notification::<lsp::notification::Progress, _>({ .on_notification::<lsp::notification::Progress, _>(move |params, mut cx| {
move |params, mut cx| {
if let Some(this) = this.upgrade(&cx) { if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.on_lsp_progress( this.on_lsp_progress(
@ -2590,14 +2643,9 @@ impl Project {
); );
}); });
} }
}
}) })
.detach(); .detach();
let language_server = language_server
.initialize(initialization_options)
.await
.log_err()?;
language_server language_server
.notify::<lsp::notification::DidChangeConfiguration>( .notify::<lsp::notification::DidChangeConfiguration>(
lsp::DidChangeConfigurationParams { lsp::DidChangeConfigurationParams {
@ -2606,22 +2654,32 @@ impl Project {
) )
.ok(); .ok();
let this = this.upgrade(&cx)?; Ok(language_server)
this.update(&mut cx, |this, cx| { }
fn insert_newly_running_language_server(
&mut self,
language: Arc<Language>,
adapter: Arc<CachedLspAdapter>,
language_server: Arc<LanguageServer>,
server_id: LanguageServerId,
key: (WorktreeId, LanguageServerName),
cx: &mut ModelContext<Self>,
) -> Result<()> {
// If the language server for this key doesn't match the server id, don't store the // If the language server for this key doesn't match the server id, don't store the
// server. Which will cause it to be dropped, killing the process // server. Which will cause it to be dropped, killing the process
if this if self
.language_server_ids .language_server_ids
.get(&key) .get(&key)
.map(|id| id != &server_id) .map(|id| id != &server_id)
.unwrap_or(false) .unwrap_or(false)
{ {
return None; return Ok(());
} }
// Update language_servers collection with Running variant of LanguageServerState // Update language_servers collection with Running variant of LanguageServerState
// indicating that the server is up and running and ready // indicating that the server is up and running and ready
this.language_servers.insert( self.language_servers.insert(
server_id, server_id,
LanguageServerState::Running { LanguageServerState::Running {
adapter: adapter.clone(), adapter: adapter.clone(),
@ -2631,7 +2689,8 @@ impl Project {
simulate_disk_based_diagnostics_completion: None, simulate_disk_based_diagnostics_completion: None,
}, },
); );
this.language_server_statuses.insert(
self.language_server_statuses.insert(
server_id, server_id,
LanguageServerStatus { LanguageServerStatus {
name: language_server.name().to_string(), name: language_server.name().to_string(),
@ -2643,20 +2702,18 @@ impl Project {
cx.emit(Event::LanguageServerAdded(server_id)); cx.emit(Event::LanguageServerAdded(server_id));
if let Some(project_id) = this.remote_id() { if let Some(project_id) = self.remote_id() {
this.client self.client.send(proto::StartLanguageServer {
.send(proto::StartLanguageServer {
project_id, project_id,
server: Some(proto::LanguageServer { server: Some(proto::LanguageServer {
id: server_id.0 as u64, id: server_id.0 as u64,
name: language_server.name().to_string(), name: language_server.name().to_string(),
}), }),
}) })?;
.log_err();
} }
// Tell the language server about every open buffer in the worktree that matches the language. // Tell the language server about every open buffer in the worktree that matches the language.
for buffer in this.opened_buffers.values() { for buffer in self.opened_buffers.values() {
if let Some(buffer_handle) = buffer.upgrade(cx) { if let Some(buffer_handle) = buffer.upgrade(cx) {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
let file = match File::from_dyn(buffer.file()) { let file = match File::from_dyn(buffer.file()) {
@ -2674,8 +2731,12 @@ impl Project {
continue; continue;
} }
let file = file.as_local()?; let file = match file.as_local() {
let versions = this Some(file) => file,
None => continue,
};
let versions = self
.buffer_snapshots .buffer_snapshots
.entry(buffer.remote_id()) .entry(buffer.remote_id())
.or_default() .or_default()
@ -2691,8 +2752,7 @@ impl Project {
let version = snapshot.version; let version = snapshot.version;
let initial_snapshot = &snapshot.snapshot; let initial_snapshot = &snapshot.snapshot;
let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
language_server language_server.notify::<lsp::notification::DidOpenTextDocument>(
.notify::<lsp::notification::DidOpenTextDocument>(
lsp::DidOpenTextDocumentParams { lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem::new( text_document: lsp::TextDocumentItem::new(
uri, uri,
@ -2705,8 +2765,8 @@ impl Project {
initial_snapshot.text(), initial_snapshot.text(),
), ),
}, },
) )?;
.log_err()?;
buffer_handle.update(cx, |buffer, cx| { buffer_handle.update(cx, |buffer, cx| {
buffer.set_completion_triggers( buffer.set_completion_triggers(
language_server language_server
@ -2722,9 +2782,7 @@ impl Project {
} }
cx.notify(); cx.notify();
Some(language_server) Ok(())
})
}))
} }
// Returns a list of all of the worktrees which no longer have a language server and the root path // Returns a list of all of the worktrees which no longer have a language server and the root path
@ -2776,6 +2834,7 @@ impl Project {
Some(LanguageServerState::Starting(started_language_server)) => { Some(LanguageServerState::Starting(started_language_server)) => {
started_language_server.await started_language_server.await
} }
Some(LanguageServerState::Running { server, .. }) => Some(server), Some(LanguageServerState::Running { server, .. }) => Some(server),
None => None, None => None,
}; };