add :lsp-restart command (#3435)
This commit is contained in:
parent
aa00a470f3
commit
385ccdfc9c
3 changed files with 123 additions and 37 deletions
|
@ -44,6 +44,7 @@
|
||||||
| `:show-directory`, `:pwd` | Show the current working directory. |
|
| `:show-directory`, `:pwd` | Show the current working directory. |
|
||||||
| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
|
| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
|
||||||
| `:reload` | Discard changes and reload from the source file. |
|
| `:reload` | Discard changes and reload from the source file. |
|
||||||
|
| `:lsp-restart` | Restarts the Language Server that is in use by the current doc |
|
||||||
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
|
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
|
||||||
| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
|
| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
|
||||||
| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
|
| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
|
||||||
|
|
|
@ -9,7 +9,8 @@ pub use lsp::{Position, Url};
|
||||||
pub use lsp_types as lsp;
|
pub use lsp_types as lsp;
|
||||||
|
|
||||||
use futures_util::stream::select_all::SelectAll;
|
use futures_util::stream::select_all::SelectAll;
|
||||||
use helix_core::syntax::LanguageConfiguration;
|
use helix_core::syntax::{LanguageConfiguration, LanguageServerConfiguration};
|
||||||
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{hash_map::Entry, HashMap},
|
collections::{hash_map::Entry, HashMap},
|
||||||
|
@ -335,6 +336,33 @@ impl Registry {
|
||||||
.map(|(_, client)| client.as_ref())
|
.map(|(_, client)| client.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn restart(
|
||||||
|
&mut self,
|
||||||
|
language_config: &LanguageConfiguration,
|
||||||
|
) -> Result<Option<Arc<Client>>> {
|
||||||
|
let config = match &language_config.language_server {
|
||||||
|
Some(config) => config,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let scope = language_config.scope.clone();
|
||||||
|
|
||||||
|
match self.inner.entry(scope) {
|
||||||
|
Entry::Vacant(_) => Ok(None),
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
// initialize a new client
|
||||||
|
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let NewClientResult(client, incoming) = start_client(id, language_config, config)?;
|
||||||
|
self.incoming.push(UnboundedReceiverStream::new(incoming));
|
||||||
|
|
||||||
|
entry.insert((id, client.clone()));
|
||||||
|
|
||||||
|
Ok(Some(client))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Option<Arc<Client>>> {
|
pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Option<Arc<Client>>> {
|
||||||
let config = match &language_config.language_server {
|
let config = match &language_config.language_server {
|
||||||
Some(config) => config,
|
Some(config) => config,
|
||||||
|
@ -346,43 +374,9 @@ impl Registry {
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
// initialize a new client
|
// initialize a new client
|
||||||
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||||
let (client, incoming, initialize_notify) = Client::start(
|
|
||||||
&config.command,
|
let NewClientResult(client, incoming) = start_client(id, language_config, config)?;
|
||||||
&config.args,
|
|
||||||
language_config.config.clone(),
|
|
||||||
&language_config.roots,
|
|
||||||
id,
|
|
||||||
config.timeout,
|
|
||||||
)?;
|
|
||||||
self.incoming.push(UnboundedReceiverStream::new(incoming));
|
self.incoming.push(UnboundedReceiverStream::new(incoming));
|
||||||
let client = Arc::new(client);
|
|
||||||
|
|
||||||
// Initialize the client asynchronously
|
|
||||||
let _client = client.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
use futures_util::TryFutureExt;
|
|
||||||
let value = _client
|
|
||||||
.capabilities
|
|
||||||
.get_or_try_init(|| {
|
|
||||||
_client
|
|
||||||
.initialize()
|
|
||||||
.map_ok(|response| response.capabilities)
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Err(e) = value {
|
|
||||||
log::error!("failed to initialize language server: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// next up, notify<initialized>
|
|
||||||
_client
|
|
||||||
.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
initialize_notify.notify_one();
|
|
||||||
});
|
|
||||||
|
|
||||||
entry.insert((id, client.clone()));
|
entry.insert((id, client.clone()));
|
||||||
Ok(Some(client))
|
Ok(Some(client))
|
||||||
|
@ -473,6 +467,56 @@ impl LspProgressMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NewClientResult(Arc<Client>, UnboundedReceiver<(usize, Call)>);
|
||||||
|
|
||||||
|
/// start_client takes both a LanguageConfiguration and a LanguageServerConfiguration to ensure that
|
||||||
|
/// it is only called when it makes sense.
|
||||||
|
fn start_client(
|
||||||
|
id: usize,
|
||||||
|
config: &LanguageConfiguration,
|
||||||
|
ls_config: &LanguageServerConfiguration,
|
||||||
|
) -> Result<NewClientResult> {
|
||||||
|
let (client, incoming, initialize_notify) = Client::start(
|
||||||
|
&ls_config.command,
|
||||||
|
&ls_config.args,
|
||||||
|
config.config.clone(),
|
||||||
|
&config.roots,
|
||||||
|
id,
|
||||||
|
ls_config.timeout,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let client = Arc::new(client);
|
||||||
|
|
||||||
|
// Initialize the client asynchronously
|
||||||
|
let _client = client.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
use futures_util::TryFutureExt;
|
||||||
|
let value = _client
|
||||||
|
.capabilities
|
||||||
|
.get_or_try_init(|| {
|
||||||
|
_client
|
||||||
|
.initialize()
|
||||||
|
.map_ok(|response| response.capabilities)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(e) = value {
|
||||||
|
log::error!("failed to initialize language server: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// next up, notify<initialized>
|
||||||
|
_client
|
||||||
|
.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
initialize_notify.notify_one();
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(NewClientResult(client, incoming))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{lsp, util::*, OffsetEncoding};
|
use super::{lsp, util::*, OffsetEncoding};
|
||||||
|
|
|
@ -985,6 +985,40 @@ fn reload(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lsp_restart(
|
||||||
|
cx: &mut compositor::Context,
|
||||||
|
_args: &[Cow<str>],
|
||||||
|
event: PromptEvent,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if event != PromptEvent::Validate {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (_view, doc) = current!(cx.editor);
|
||||||
|
let config = doc
|
||||||
|
.language_config()
|
||||||
|
.context("LSP not defined for the current document")?;
|
||||||
|
|
||||||
|
let scope = config.scope.clone();
|
||||||
|
cx.editor.language_servers.restart(config)?;
|
||||||
|
|
||||||
|
// This collect is needed because refresh_language_server would need to re-borrow editor.
|
||||||
|
let document_ids_to_refresh: Vec<DocumentId> = cx
|
||||||
|
.editor
|
||||||
|
.documents()
|
||||||
|
.filter_map(|doc| match doc.language_config() {
|
||||||
|
Some(config) if config.scope.eq(&scope) => Some(doc.id()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for document_id in document_ids_to_refresh {
|
||||||
|
cx.editor.refresh_language_server(document_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn tree_sitter_scopes(
|
fn tree_sitter_scopes(
|
||||||
cx: &mut compositor::Context,
|
cx: &mut compositor::Context,
|
||||||
_args: &[Cow<str>],
|
_args: &[Cow<str>],
|
||||||
|
@ -1837,6 +1871,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
|
||||||
fun: reload,
|
fun: reload,
|
||||||
completer: None,
|
completer: None,
|
||||||
},
|
},
|
||||||
|
TypableCommand {
|
||||||
|
name: "lsp-restart",
|
||||||
|
aliases: &[],
|
||||||
|
doc: "Restarts the Language Server that is in use by the current doc",
|
||||||
|
fun: lsp_restart,
|
||||||
|
completer: None,
|
||||||
|
},
|
||||||
TypableCommand {
|
TypableCommand {
|
||||||
name: "tree-sitter-scopes",
|
name: "tree-sitter-scopes",
|
||||||
aliases: &[],
|
aliases: &[],
|
||||||
|
|
Loading…
Add table
Reference in a new issue