@@ -6,18 +6,19 @@ use helix_lsp::{
66};
77use tui::text::{Span, Spans};
88
9−use super::{align_view, push_jump, Align, Context, Editor};
9+use super::{align_view, push_jump, Align, Context, Editor, Open};
1010
1111use helix_core::{path, Selection};
1212use helix_view::{editor::Action, theme::Style};
1313
1414use crate::{
1515 compositor::{self, Compositor},
16− ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent},
16+ ui::{
17+ self, lsp::SignatureHelp, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent,
18+ },
1719};
1820
19−use std::collections::BTreeMap;
20−use std::{borrow::Cow, path::PathBuf};
21+use std::{borrow::Cow, collections::BTreeMap, path::PathBuf, sync::Arc};
2122
2223/// Gets the language server that is attached to a document, and
2324/// if it's not active displays a status message. Using this macro
@@ -805,31 +806,116 @@ pub fn goto_reference(cx: &mut Context) {
805806 );
806807}
807808
809+#[derive(PartialEq)]
810+pub enum SignatureHelpInvoked {
811+ Manual,
812+ Automatic,
813+}
814+
808815pub fn signature_help(cx: &mut Context) {
816+ signature_help_impl(cx, SignatureHelpInvoked::Manual)
817+}
818+
819+pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
809820 let (view, doc) = current!(cx.editor);
810− let language_server = language_server!(cx.editor, doc);
821+ let was_manually_invoked = invoked == SignatureHelpInvoked::Manual;
822+
823+ let language_server = match doc.language_server() {
824+ Some(language_server) => language_server,
825+ None => {
826+ // Do not show the message if signature help was invoked
827+ // automatically on backspace, trigger characters, etc.
828+ if was_manually_invoked {
829+ cx.editor
830+ .set_status("Language server not active for current buffer");
831+ }
832+ return;
833+ }
834+ };
811835 let offset_encoding = language_server.offset_encoding();
812836
813837 let pos = doc.position(view.id, offset_encoding);
814838
815− let future = language_server.text_document_signature_help(doc.identifier(), pos, None);
839+ let future = match language_server.text_document_signature_help(doc.identifier(), pos, None) {
840+ Some(f) => f,
841+ None => return,
842+ };
816843
817844 cx.callback(
818845 future,
819− move |_editor, _compositor, response: Option<lsp::SignatureHelp>| {
820− if let Some(signature_help) = response {
821− log::info!("{:?}", signature_help);
822− // signatures
823− // active_signature
824− // active_parameter
825− // render as:
826−
827− // signature
828− // ----------
829− // doc
830−
831− // with active param highlighted
846+ move |editor, compositor, response: Option<lsp::SignatureHelp>| {
847+ let config = &editor.config();
848+
849+ if !(config.lsp.auto_signature_help
850+ || SignatureHelp::visible_popup(compositor).is_some()
851+ || was_manually_invoked)
852+ {
853+ return;
832854 }
855+
856+ let response = match response {
857+ // According to the spec the response should be None if there
858+ // are no signatures, but some servers don't follow this.
859+ Some(s) if !s.signatures.is_empty() => s,
860+ _ => {
861+ compositor.remove(SignatureHelp::ID);
862+ return;
863+ }
864+ };
865+ let doc = doc!(editor);
866+ let language = doc
867+ .language()
868+ .and_then(|scope| scope.strip_prefix("source."))
869+ .unwrap_or("");
870+
871+ let signature = match response
872+ .signatures
873+ .get(response.active_signature.unwrap_or(0) as usize)
874+ {
875+ Some(s) => s,
876+ None => return,
877+ };
878+ let mut contents = SignatureHelp::new(
879+ signature.label.clone(),
880+ language.to_string(),
881+ Arc::clone(&editor.syn_loader),
882+ );
883+
884+ let signature_doc = if config.lsp.display_signature_help_docs {
885+ signature.documentation.as_ref().map(|doc| match doc {
886+ lsp::Documentation::String(s) => s.clone(),
887+ lsp::Documentation::MarkupContent(markup) => markup.value.clone(),
888+ })
889+ } else {
890+ None
891+ };
892+
893+ contents.set_signature_doc(signature_doc);
894+
895+ let active_param_range = || -> Option<(usize, usize)> {
896+ let param_idx = signature
897+ .active_parameter
898+ .or(response.active_parameter)
899+ .unwrap_or(0) as usize;
900+ let param = signature.parameters.as_ref()?.get(param_idx)?;
901+ match ¶m.label {
902+ lsp::ParameterLabel::Simple(string) => {
903+ let start = signature.label.find(string.as_str())?;
904+ Some((start, start + string.len()))
905+ }
906+ lsp::ParameterLabel::LabelOffsets([start, end]) => {
907+ Some((*start as usize, *end as usize))
908+ }
909+ }
910+ };
911+ contents.set_active_param_range(active_param_range());
912+
913+ let old_popup = compositor.find_id::<Popup<SignatureHelp>>(SignatureHelp::ID);
914+ let popup = Popup::new(SignatureHelp::ID, contents)
915+ .position(old_popup.and_then(|p| p.get_position()))
916+ .position_bias(Open::Above)
917+ .ignore_escape_key(true);
918+ compositor.replace_or_push(SignatureHelp::ID, popup);
833919 },
834920 );
835921}