aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src
diff options
context:
space:
mode:
authorMichael Davis2022-11-22 02:52:23 +0000
committerGitHub2022-11-22 02:52:23 +0000
commit9059c65a5385f6d3cc0bc7f6e3f835ae542635a5 (patch)
tree6fffacee213769a669c045f1c5c2a07bdf41ec02 /helix-term/src
parent1db01caec7c91e270a23fd4f85955bb235ffbcb7 (diff)
lsp: Check server provider capabilities (#3554)
Language Servers may signal that they do not support a method in the initialization result (server capabilities). We can check these when making LSP requests and hint in the status line when a method is not supported by the server. This can also prevent crashes in servers which assume that clients do not send requests for methods which are disabled in the server capabilities. There is an existing pattern the LSP client module where a method returns `Option<impl Future<Output = Result<_>>>` with `None` signaling no support in the server. This change extends this pattern to the rest of the client functions. And we log an error to the statusline for manually triggered LSP calls which return `None`.
Diffstat (limited to 'helix-term/src')
-rw-r--r--helix-term/src/commands.rs29
-rw-r--r--helix-term/src/commands/lsp.rs115
-rw-r--r--helix-term/src/ui/completion.rs17
3 files changed, 125 insertions, 36 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 5b44775b..8af5a7e3 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -3801,15 +3801,21 @@ fn format_selections(cx: &mut Context) {
let range = ranges[0];
- let edits = tokio::task::block_in_place(|| {
- helix_lsp::block_on(language_server.text_document_range_formatting(
- doc.identifier(),
- range,
- lsp::FormattingOptions::default(),
- None,
- ))
- })
- .unwrap_or_default();
+ let request = match language_server.text_document_range_formatting(
+ doc.identifier(),
+ range,
+ lsp::FormattingOptions::default(),
+ None,
+ ) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support range formatting");
+ return;
+ }
+ };
+
+ let edits = tokio::task::block_in_place(|| helix_lsp::block_on(request)).unwrap_or_default();
let transaction = helix_lsp::util::generate_transaction_from_edits(
doc.text(),
@@ -3953,7 +3959,10 @@ pub fn completion(cx: &mut Context) {
let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
- let future = language_server.completion(doc.identifier(), pos, None);
+ let future = match language_server.completion(doc.identifier(), pos, None) {
+ Some(future) => future,
+ None => return,
+ };
let trigger_offset = cursor;
diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs
index dcabfab1..8faf1d08 100644
--- a/helix-term/src/commands/lsp.rs
+++ b/helix-term/src/commands/lsp.rs
@@ -333,7 +333,14 @@ pub fn symbol_picker(cx: &mut Context) {
let current_url = doc.url();
let offset_encoding = language_server.offset_encoding();
- let future = language_server.document_symbols(doc.identifier());
+ let future = match language_server.document_symbols(doc.identifier()) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support document symbols");
+ return;
+ }
+ };
cx.callback(
future,
@@ -365,7 +372,14 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
let current_url = doc.url();
let language_server = language_server!(cx.editor, doc);
let offset_encoding = language_server.offset_encoding();
- let future = language_server.workspace_symbols("".to_string());
+ let future = match language_server.workspace_symbols("".to_string()) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support workspace symbols");
+ return;
+ }
+ };
cx.callback(
future,
@@ -493,7 +507,7 @@ pub fn code_action(cx: &mut Context) {
let range = range_to_lsp_range(doc.text(), selection_range, offset_encoding);
- let future = language_server.code_actions(
+ let future = match language_server.code_actions(
doc.identifier(),
range,
// Filter and convert overlapping diagnostics
@@ -509,7 +523,14 @@ pub fn code_action(cx: &mut Context) {
.collect(),
only: None,
},
- );
+ ) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support code actions");
+ return;
+ }
+ };
cx.callback(
future,
@@ -617,9 +638,16 @@ pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
// the command is executed on the server and communicated back
// to the client asynchronously using workspace edits
- let command_future = language_server.command(cmd);
+ let future = match language_server.command(cmd) {
+ Some(future) => future,
+ None => {
+ editor.set_error("Language server does not support executing commands");
+ return;
+ }
+ };
+
tokio::spawn(async move {
- let res = command_future.await;
+ let res = future.await;
if let Err(e) = res {
log::error!("execute LSP command: {}", e);
@@ -853,7 +881,14 @@ pub fn goto_definition(cx: &mut Context) {
let pos = doc.position(view.id, offset_encoding);
- let future = language_server.goto_definition(doc.identifier(), pos, None);
+ let future = match language_server.goto_definition(doc.identifier(), pos, None) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support goto-definition");
+ return;
+ }
+ };
cx.callback(
future,
@@ -871,7 +906,14 @@ pub fn goto_type_definition(cx: &mut Context) {
let pos = doc.position(view.id, offset_encoding);
- let future = language_server.goto_type_definition(doc.identifier(), pos, None);
+ let future = match language_server.goto_type_definition(doc.identifier(), pos, None) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support goto-type-definition");
+ return;
+ }
+ };
cx.callback(
future,
@@ -889,7 +931,14 @@ pub fn goto_implementation(cx: &mut Context) {
let pos = doc.position(view.id, offset_encoding);
- let future = language_server.goto_implementation(doc.identifier(), pos, None);
+ let future = match language_server.goto_implementation(doc.identifier(), pos, None) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support goto-implementation");
+ return;
+ }
+ };
cx.callback(
future,
@@ -907,7 +956,14 @@ pub fn goto_reference(cx: &mut Context) {
let pos = doc.position(view.id, offset_encoding);
- let future = language_server.goto_reference(doc.identifier(), pos, None);
+ let future = match language_server.goto_reference(doc.identifier(), pos, None) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support goto-reference");
+ return;
+ }
+ };
cx.callback(
future,
@@ -950,7 +1006,13 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
let future = match language_server.text_document_signature_help(doc.identifier(), pos, None) {
Some(f) => f,
- None => return,
+ None => {
+ if was_manually_invoked {
+ cx.editor
+ .set_error("Language server does not support signature-help");
+ }
+ return;
+ }
};
cx.callback(
@@ -1051,7 +1113,14 @@ pub fn hover(cx: &mut Context) {
let pos = doc.position(view.id, offset_encoding);
- let future = language_server.text_document_hover(doc.identifier(), pos, None);
+ let future = match language_server.text_document_hover(doc.identifier(), pos, None) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support hover");
+ return;
+ }
+ };
cx.callback(
future,
@@ -1121,8 +1190,16 @@ pub fn rename_symbol(cx: &mut Context) {
let pos = doc.position(view.id, offset_encoding);
- let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string());
- match block_on(task) {
+ let future =
+ match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support symbol renaming");
+ return;
+ }
+ };
+ match block_on(future) {
Ok(edits) => apply_workspace_edit(cx.editor, offset_encoding, &edits),
Err(err) => cx.editor.set_error(err.to_string()),
}
@@ -1137,7 +1214,15 @@ pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
let pos = doc.position(view.id, offset_encoding);
- let future = language_server.text_document_document_highlight(doc.identifier(), pos, None);
+ let future = match language_server.text_document_document_highlight(doc.identifier(), pos, None)
+ {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support document highlight");
+ return;
+ }
+ };
cx.callback(
future,
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 4e6ee424..ebb4fb46 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -245,21 +245,13 @@ impl Completion {
completion_item: lsp::CompletionItem,
) -> Option<CompletionItem> {
let language_server = doc.language_server()?;
- let completion_resolve_provider = language_server
- .capabilities()
- .completion_provider
- .as_ref()?
- .resolve_provider;
- if completion_resolve_provider != Some(true) {
- return None;
- }
- let future = language_server.resolve_completion_item(completion_item);
+ let future = language_server.resolve_completion_item(completion_item)?;
let response = helix_lsp::block_on(future);
match response {
Ok(value) => serde_json::from_value(value).ok(),
Err(err) => {
- log::error!("execute LSP command: {}", err);
+ log::error!("Failed to resolve completion item: {}", err);
None
}
}
@@ -330,7 +322,10 @@ impl Completion {
};
// This method should not block the compositor so we handle the response asynchronously.
- let future = language_server.resolve_completion_item(current_item.clone());
+ let future = match language_server.resolve_completion_item(current_item.clone()) {
+ Some(future) => future,
+ None => return false,
+ };
cx.callback(
future,