on master help sign in
add :lsp-restart command (#3435)
385ccdfFilipe Azevedo
book/src/generated/typable-cmd.md+1
@@ -44,6 +44,7 @@
44| `:show-directory`, `:pwd` | Show the current working directory. |
44| `:show-directory`, `:pwd` | Show the current working directory. |
45| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
45| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
46| `:reload` | Discard changes and reload from the source file. |
46| `:reload` | Discard changes and reload from the source file. |
47+| `:lsp-restart` | Restarts the Language Server that is in use by the current doc |
47| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
48| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
48| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
49| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
49| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
50| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
helix-lsp/src/lib.rs+81−37
@@ -9,7 +9,8 @@ pub use lsp::{Position, Url};
9pub use lsp_types as lsp;
9pub use lsp_types as lsp;
10
10
11use futures_util::stream::select_all::SelectAll;
11use futures_util::stream::select_all::SelectAll;
12−use helix_core::syntax::LanguageConfiguration;
12+use helix_core::syntax::{LanguageConfiguration, LanguageServerConfiguration};
13+use tokio::sync::mpsc::UnboundedReceiver;
13
14
14use std::{
15use std::{
15 collections::{hash_map::Entry, HashMap},
16 collections::{hash_map::Entry, HashMap},
@@ -335,6 +336,33 @@ impl Registry {
335 .map(|(_, client)| client.as_ref())
336 .map(|(_, client)| client.as_ref())
336 }
337 }
337
338
339+ pub fn restart(
340+ &mut self,
341+ language_config: &LanguageConfiguration,
342+ ) -> Result<Option<Arc<Client>>> {
343+ let config = match &language_config.language_server {
344+ Some(config) => config,
345+ None => return Ok(None),
346+ };
347+
348+ let scope = language_config.scope.clone();
349+
350+ match self.inner.entry(scope) {
351+ Entry::Vacant(_) => Ok(None),
352+ Entry::Occupied(mut entry) => {
353+ // initialize a new client
354+ let id = self.counter.fetch_add(1, Ordering::Relaxed);
355+
356+ let NewClientResult(client, incoming) = start_client(id, language_config, config)?;
357+ self.incoming.push(UnboundedReceiverStream::new(incoming));
358+
359+ entry.insert((id, client.clone()));
360+
361+ Ok(Some(client))
362+ }
363+ }
364+ }
365+
338 pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Option<Arc<Client>>> {
366 pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Option<Arc<Client>>> {
339 let config = match &language_config.language_server {
367 let config = match &language_config.language_server {
340 Some(config) => config,
368 Some(config) => config,
@@ -346,43 +374,9 @@ impl Registry {
346 Entry::Vacant(entry) => {
374 Entry::Vacant(entry) => {
347 // initialize a new client
375 // initialize a new client
348 let id = self.counter.fetch_add(1, Ordering::Relaxed);
376 let id = self.counter.fetch_add(1, Ordering::Relaxed);
349− let (client, incoming, initialize_notify) = Client::start(
377+
350− &config.command,
378+ let NewClientResult(client, incoming) = start_client(id, language_config, config)?;
351− &config.args,
352− language_config.config.clone(),
353− &language_config.roots,
354− id,
355− config.timeout,
356− )?;
357 self.incoming.push(UnboundedReceiverStream::new(incoming));
379 self.incoming.push(UnboundedReceiverStream::new(incoming));
358− let client = Arc::new(client);
359−
360− // Initialize the client asynchronously
361− let _client = client.clone();
362− tokio::spawn(async move {
363− use futures_util::TryFutureExt;
364− let value = _client
365− .capabilities
366− .get_or_try_init(|| {
367− _client
368− .initialize()
369− .map_ok(|response| response.capabilities)
370− })
371− .await;
372−
373− if let Err(e) = value {
374− log::error!("failed to initialize language server: {}", e);
375− return;
376− }
377−
378− // next up, notify<initialized>
379− _client
380− .notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
381− .await
382− .unwrap();
383−
384− initialize_notify.notify_one();
385− });
386
380
387 entry.insert((id, client.clone()));
381 entry.insert((id, client.clone()));
388 Ok(Some(client))
382 Ok(Some(client))
@@ -473,6 +467,56 @@ impl LspProgressMap {
473 }
467 }
474}
468}
475
469
470+struct NewClientResult(Arc<Client>, UnboundedReceiver<(usize, Call)>);
471+
472+/// start_client takes both a LanguageConfiguration and a LanguageServerConfiguration to ensure that
473+/// it is only called when it makes sense.
474+fn start_client(
475+ id: usize,
476+ config: &LanguageConfiguration,
477+ ls_config: &LanguageServerConfiguration,
478+) -> Result<NewClientResult> {
479+ let (client, incoming, initialize_notify) = Client::start(
480+ &ls_config.command,
481+ &ls_config.args,
482+ config.config.clone(),
483+ &config.roots,
484+ id,
485+ ls_config.timeout,
486+ )?;
487+
488+ let client = Arc::new(client);
489+
490+ // Initialize the client asynchronously
491+ let _client = client.clone();
492+ tokio::spawn(async move {
493+ use futures_util::TryFutureExt;
494+ let value = _client
495+ .capabilities
496+ .get_or_try_init(|| {
497+ _client
498+ .initialize()
499+ .map_ok(|response| response.capabilities)
500+ })
501+ .await;
502+
503+ if let Err(e) = value {
504+ log::error!("failed to initialize language server: {}", e);
505+ return;
506+ }
507+
508+ // next up, notify<initialized>
509+ _client
510+ .notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
511+ .await
512+ .unwrap();
513+
514+ initialize_notify.notify_one();
515+ });
516+
517+ Ok(NewClientResult(client, incoming))
518+}
519+
476#[cfg(test)]
520#[cfg(test)]
477mod tests {
521mod tests {
478 use super::{lsp, util::*, OffsetEncoding};
522 use super::{lsp, util::*, OffsetEncoding};
helix-term/src/commands/typed.rs+41
@@ -985,6 +985,40 @@ fn reload(
985 })
985 })
986}
986}
987
987
988+fn lsp_restart(
989+ cx: &mut compositor::Context,
990+ _args: &[Cow<str>],
991+ event: PromptEvent,
992+) -> anyhow::Result<()> {
993+ if event != PromptEvent::Validate {
994+ return Ok(());
995+ }
996+
997+ let (_view, doc) = current!(cx.editor);
998+ let config = doc
999+ .language_config()
1000+ .context("LSP not defined for the current document")?;
1001+
1002+ let scope = config.scope.clone();
1003+ cx.editor.language_servers.restart(config)?;
1004+
1005+ // This collect is needed because refresh_language_server would need to re-borrow editor.
1006+ let document_ids_to_refresh: Vec<DocumentId> = cx
1007+ .editor
1008+ .documents()
1009+ .filter_map(|doc| match doc.language_config() {
1010+ Some(config) if config.scope.eq(&scope) => Some(doc.id()),
1011+ _ => None,
1012+ })
1013+ .collect();
1014+
1015+ for document_id in document_ids_to_refresh {
1016+ cx.editor.refresh_language_server(document_id);
1017+ }
1018+
1019+ Ok(())
1020+}
1021+
988fn tree_sitter_scopes(
1022fn tree_sitter_scopes(
989 cx: &mut compositor::Context,
1023 cx: &mut compositor::Context,
990 _args: &[Cow<str>],
1024 _args: &[Cow<str>],
@@ -1837,6 +1871,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1837 fun: reload,
1871 fun: reload,
1838 completer: None,
1872 completer: None,
1839 },
1873 },
1874+ TypableCommand {
1875+ name: "lsp-restart",
1876+ aliases: &[],
1877+ doc: "Restarts the Language Server that is in use by the current doc",
1878+ fun: lsp_restart,
1879+ completer: None,
1880+ },
1840 TypableCommand {
1881 TypableCommand {
1841 name: "tree-sitter-scopes",
1882 name: "tree-sitter-scopes",
1842 aliases: &[],
1883 aliases: &[],