diff options
Diffstat (limited to 'helix-term/src/commands/lsp.rs')
-rw-r--r-- | helix-term/src/commands/lsp.rs | 124 |
1 files changed, 105 insertions, 19 deletions
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<lsp::SignatureHelp>| { - 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<lsp::SignatureHelp>| { + 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::<Popup<SignatureHelp>>(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); }, ); } |