Skip to content

Commit

Permalink
add :lsp-restart command
Browse files Browse the repository at this point in the history
  • Loading branch information
mangas committed Aug 14, 2022
1 parent 8a75795 commit 68be73a
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 38 deletions.
121 changes: 83 additions & 38 deletions helix-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ pub use lsp::{Position, Url};
pub use lsp_types as lsp;

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::{
collections::{hash_map::Entry, HashMap},
Expand Down Expand Up @@ -320,56 +321,51 @@ impl Registry {
.map(|(_, client)| client.as_ref())
}

pub fn restart(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
let config = match &language_config.language_server {
Some(config) => config,
None => {
return Err(Error::LspNotDefined);
}
};

let scope = language_config.scope.clone();

match self.inner.entry(scope) {
Entry::Vacant(_) => Err(Error::LspNotDefined),
Entry::Occupied(mut entry) => {
// initialize a new client
let id = self.counter.fetch_add(1, Ordering::Relaxed);

let (client, incoming) = start_client(id, language_config, config)?;
self.incoming.push(UnboundedReceiverStream::new(incoming));

entry.insert((id, client.clone()));

Ok(client)
}
}
}

pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
let config = match &language_config.language_server {
Some(config) => config,
None => return Err(Error::LspNotDefined),
};

match self.inner.entry(language_config.scope.clone()) {
let scope = language_config.scope.clone();

match self.inner.entry(scope) {
Entry::Occupied(entry) => Ok(entry.get().1.clone()),
Entry::Vacant(entry) => {
// initialize a new client
let id = self.counter.fetch_add(1, Ordering::Relaxed);
let (client, incoming, initialize_notify) = Client::start(
&config.command,
&config.args,
language_config.config.clone(),
&language_config.roots,
id,
config.timeout,
)?;

let NewClientResult(client, incoming) = start_client(id, language_config, config)?;
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()));

Ok(client)
}
}
Expand All @@ -380,6 +376,55 @@ impl Registry {
}
}

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, specifically, start_client shouldn't care whether.
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((client, incoming))
}

#[derive(Debug)]
pub enum ProgressStatus {
Created,
Expand Down
41 changes: 41 additions & 0 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::ops::Deref;

use super::*;

use helix_core::surround::Error;
use helix_view::editor::{Action, ConfigEvent};
use ui::completers::{self, Completer};

Expand Down Expand Up @@ -982,6 +983,39 @@ 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()
.ok_or(anyhow!("LSP not defined for current document"))?;

let scope = config.scope.clone();
cx.editor.language_servers.restart(config)?;

let to_refresh: Vec<DocumentId> = cx
.editor
.documents_mut()
.filter_map(|doc| match doc.language_config() {
Some(config) if config.scope.eq(&scope) => Some(doc.id()),
_ => None,
})
.collect();

to_refresh.into_iter().for_each(|id| {
cx.editor.refresh_language_server(id);
});

Ok(())
}

fn tree_sitter_scopes(
cx: &mut compositor::Context,
_args: &[Cow<str>],
Expand Down Expand Up @@ -1827,6 +1861,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: reload,
completer: None,
},
TypableCommand {
name: "lsp-reload",
aliases: &[],
doc: "Reload the LSP the is in use by the current doc",
fun: lsp_restart,
completer: None,
},
TypableCommand {
name: "tree-sitter-scopes",
aliases: &[],
Expand Down

0 comments on commit 68be73a

Please sign in to comment.