on master help sign in
Handle language server termination (#4797)

This change handles a language server exiting. This was a UX sore-spot: if a language server crashed, Helix did not recognize the exit and continued to send requests to it. All requests would timeout since they would not receive responses. This would also hold-up Helix closing itself down since it would try to gracefully shutdown the server which is implemented in the LSP spec as a request. We could attempt to automatically restart the language server on crash. I left this for future work since that change will need to be slightly complicated: it will need to cover the case of a language server repeatedly crashing.

commit8be2d1dcbfeff88f47b8bfb9685f2ab45a72efb5 authorMichael Davis <mcarsondavis@gmail.com> dateFri 18 Nov 2022, 22:14 (4 years ago) parent598bd8b
3 files changed+53
helix-lsp/src/lib.rs+7 helix-lsp/src/transport.rs+20 helix-term/src/application.rs+26
helix-lsp/src/lib.rs+7
@@ -282,6 +282,8 @@ impl MethodCall {
282282pub enum Notification {
283283 // we inject this notification to signal the LSP is ready
284284 Initialized,
285+ // and this notification to signal that the LSP exited
286+ Exit,
285287 PublishDiagnostics(lsp::PublishDiagnosticsParams),
286288 ShowMessage(lsp::ShowMessageParams),
287289 LogMessage(lsp::LogMessageParams),
@@ -294,6 +296,7 @@ impl Notification {
294296
295297 let notification = match method {
296298 lsp::notification::Initialized::METHOD => Self::Initialized,
299+ lsp::notification::Exit::METHOD => Self::Exit,
297300 lsp::notification::PublishDiagnostics::METHOD => {
298301 let params: lsp::PublishDiagnosticsParams = params.parse()?;
299302 Self::PublishDiagnostics(params)
@@ -350,6 +353,10 @@ impl Registry {
350353 .map(|(_, client)| client.as_ref())
351354 }
352355
356+ pub fn remove_by_id(&mut self, id: usize) {
357+ self.inner.retain(|_, (client_id, _)| client_id != &id)
358+ }
359+
353360 pub fn restart(
354361 &mut self,
355362 language_config: &LanguageConfiguration,
helix-lsp/src/transport.rs+20
@@ -250,6 +250,26 @@ impl Transport {
250250 }
251251 };
252252 }
253+ Err(Error::StreamClosed) => {
254+ // Hack: inject a terminated notification so we trigger code that needs to happen after exit
255+ use lsp_types::notification::Notification as _;
256+ let notification =
257+ ServerMessage::Call(jsonrpc::Call::Notification(jsonrpc::Notification {
258+ jsonrpc: None,
259+ method: lsp_types::notification::Exit::METHOD.to_string(),
260+ params: jsonrpc::Params::None,
261+ }));
262+ match transport
263+ .process_server_message(&client_tx, notification)
264+ .await
265+ {
266+ Ok(_) => {}
267+ Err(err) => {
268+ error!("err: <- {:?}", err);
269+ }
270+ }
271+ break;
272+ }
253273 Err(err) => {
254274 error!("err: <- {:?}", err);
255275 break;
helix-term/src/application.rs+26
@@ -871,6 +871,32 @@ impl Application {
871871 Notification::ProgressMessage(_params) => {
872872 // do nothing
873873 }
874+ Notification::Exit => {
875+ self.editor.set_status("Language server exited");
876+
877+ // Clear any diagnostics for documents with this server open.
878+ let urls: Vec<_> = self
879+ .editor
880+ .documents_mut()
881+ .filter_map(|doc| {
882+ if doc.language_server().map(|server| server.id())
883+ == Some(server_id)
884+ {
885+ doc.set_diagnostics(Vec::new());
886+ doc.url()
887+ } else {
888+ None
889+ }
890+ })
891+ .collect();
892+
893+ for url in urls {
894+ self.editor.diagnostics.remove(&url);
895+ }
896+
897+ // Remove the language server from the registry.
898+ self.editor.language_servers.remove_by_id(server_id);
899+ }
874900 }
875901 }
876902 Call::MethodCall(helix_lsp::jsonrpc::MethodCall {