From 791bf7e50a19bcf7612788deb7514847089cb976 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Tue, 19 Jul 2022 07:58:24 +0530 Subject: Add lsp signature help (#1755) * Add lsp signature help * Do not move signature help popup on multiple triggers * Highlight current parameter in signature help * Auto close signature help * Position signature help above to not block completion * Update signature help on backspace/insert mode delete * Add lsp.auto-signature-help config option * Add serde default annotation for LspConfig * Show LSP inactive message only if signature help is invoked manually * Do not assume valid signature help response from LSP Malformed LSP responses are common, and these should not crash the editor. * Check signature help capability before sending request * Reuse Open enum for PositionBias in popup * Close signature popup and exit insert mode on escape * Add config to control signature help docs display * Use new Margin api in signature help * Invoke signature help on changing to insert mode--- helix-term/src/commands/lsp.rs | 124 +++++++++++++++++++++++++++++++++------ helix-term/src/commands/typed.rs | 8 +-- 2 files changed, 108 insertions(+), 24 deletions(-) (limited to 'helix-term/src/commands') diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index a91e3792..1785a50c 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -6,18 +6,19 @@ use helix_lsp::{ }; use tui::text::{Span, Spans}; -use super::{align_view, push_jump, Align, Context, Editor}; +use super::{align_view, push_jump, Align, Context, Editor, Open}; use helix_core::{path, Selection}; use helix_view::{editor::Action, theme::Style}; use crate::{ compositor::{self, Compositor}, - ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent}, + ui::{ + self, lsp::SignatureHelp, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent, + }, }; -use std::collections::BTreeMap; -use std::{borrow::Cow, path::PathBuf}; +use std::{borrow::Cow, collections::BTreeMap, path::PathBuf, sync::Arc}; /// Gets the language server that is attached to a document, and /// if it's not active displays a status message. Using this macro @@ -805,31 +806,116 @@ pub fn goto_reference(cx: &mut Context) { ); } +#[derive(PartialEq)] +pub enum SignatureHelpInvoked { + Manual, + Automatic, +} + pub fn signature_help(cx: &mut Context) { + signature_help_impl(cx, SignatureHelpInvoked::Manual) +} + +pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) { let (view, doc) = current!(cx.editor); - let language_server = language_server!(cx.editor, doc); + let was_manually_invoked = invoked == SignatureHelpInvoked::Manual; + + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => { + // Do not show the message if signature help was invoked + // automatically on backspace, trigger characters, etc. + if was_manually_invoked { + cx.editor + .set_status("Language server not active for current buffer"); + } + return; + } + }; let offset_encoding = language_server.offset_encoding(); let pos = doc.position(view.id, offset_encoding); - let future = language_server.text_document_signature_help(doc.identifier(), pos, None); + let future = match language_server.text_document_signature_help(doc.identifier(), pos, None) { + Some(f) => f, + None => return, + }; cx.callback( future, - move |_editor, _compositor, response: Option| { - if let Some(signature_help) = response { - log::info!("{:?}", signature_help); - // signatures - // active_signature - // active_parameter - // render as: - - // signature - // ---------- - // doc - - // with active param highlighted + move |editor, compositor, response: Option| { + let config = &editor.config(); + + if !(config.lsp.auto_signature_help + || SignatureHelp::visible_popup(compositor).is_some() + || was_manually_invoked) + { + return; } + + let response = match response { + // According to the spec the response should be None if there + // are no signatures, but some servers don't follow this. + Some(s) if !s.signatures.is_empty() => s, + _ => { + compositor.remove(SignatureHelp::ID); + return; + } + }; + let doc = doc!(editor); + let language = doc + .language() + .and_then(|scope| scope.strip_prefix("source.")) + .unwrap_or(""); + + let signature = match response + .signatures + .get(response.active_signature.unwrap_or(0) as usize) + { + Some(s) => s, + None => return, + }; + let mut contents = SignatureHelp::new( + signature.label.clone(), + language.to_string(), + Arc::clone(&editor.syn_loader), + ); + + let signature_doc = if config.lsp.display_signature_help_docs { + signature.documentation.as_ref().map(|doc| match doc { + lsp::Documentation::String(s) => s.clone(), + lsp::Documentation::MarkupContent(markup) => markup.value.clone(), + }) + } else { + None + }; + + contents.set_signature_doc(signature_doc); + + let active_param_range = || -> Option<(usize, usize)> { + let param_idx = signature + .active_parameter + .or(response.active_parameter) + .unwrap_or(0) as usize; + let param = signature.parameters.as_ref()?.get(param_idx)?; + match ¶m.label { + lsp::ParameterLabel::Simple(string) => { + let start = signature.label.find(string.as_str())?; + Some((start, start + string.len())) + } + lsp::ParameterLabel::LabelOffsets([start, end]) => { + Some((*start as usize, *end as usize)) + } + } + }; + contents.set_active_param_range(active_param_range()); + + let old_popup = compositor.find_id::>(SignatureHelp::ID); + let popup = Popup::new(SignatureHelp::ID, contents) + .position(old_popup.and_then(|p| p.get_position())) + .position_bias(Open::Above) + .ignore_escape_key(true); + compositor.replace_or_push(SignatureHelp::ID, popup); }, ); } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 4e1ac0da..fb03af44 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1501,11 +1501,9 @@ fn run_shell_command( format!("```sh\n{}\n```", output), editor.syn_loader.clone(), ); - let mut popup = Popup::new("shell", contents); - popup.set_position(Some(helix_core::Position::new( - editor.cursor().0.unwrap_or_default().row, - 2, - ))); + let popup = Popup::new("shell", contents).position(Some( + helix_core::Position::new(editor.cursor().0.unwrap_or_default().row, 2), + )); compositor.replace_or_push("shell", popup); }); Ok(call) -- cgit v1.2.3-70-g09d2