allow cycling through function signatures/overloads (#9974)

implement handle_event to cycle through the function signatures.

To change the signature press alt+p/n .

Signed-off-by: Ben Fekih, Hichem <hichem.f@live.de>
pull/10484/head
Hichem 7 months ago committed by GitHub
parent 7775b35cba
commit 69e08d9e91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -5,7 +5,7 @@ use helix_core::syntax::LanguageServerFeature;
use helix_event::{ use helix_event::{
cancelable_future, cancelation, register_hook, send_blocking, CancelRx, CancelTx, cancelable_future, cancelation, register_hook, send_blocking, CancelRx, CancelTx,
}; };
use helix_lsp::lsp; use helix_lsp::lsp::{self, SignatureInformation};
use helix_stdx::rope::RopeSliceExt; use helix_stdx::rope::RopeSliceExt;
use helix_view::document::Mode; use helix_view::document::Mode;
use helix_view::events::{DocumentDidChange, SelectionDidChange}; use helix_view::events::{DocumentDidChange, SelectionDidChange};
@ -18,7 +18,7 @@ use crate::commands::Open;
use crate::compositor::Compositor; use crate::compositor::Compositor;
use crate::events::{OnModeSwitch, PostInsertChar}; use crate::events::{OnModeSwitch, PostInsertChar};
use crate::handlers::Handlers; use crate::handlers::Handlers;
use crate::ui::lsp::SignatureHelp; use crate::ui::lsp::{Signature, SignatureHelp};
use crate::ui::Popup; use crate::ui::Popup;
use crate::{job, ui}; use crate::{job, ui};
@ -82,6 +82,7 @@ impl helix_event::AsyncHook for SignatureHelpHandler {
} }
} }
self.state = if open { State::Open } else { State::Closed }; self.state = if open { State::Open } else { State::Closed };
return timeout; return timeout;
} }
} }
@ -138,6 +139,31 @@ pub fn request_signature_help(
}); });
} }
fn active_param_range(
signature: &SignatureInformation,
response_active_parameter: Option<u32>,
) -> 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 &param.label {
lsp::ParameterLabel::Simple(string) => {
let start = signature.label.find(string.as_str())?;
Some((start, start + string.len()))
}
lsp::ParameterLabel::LabelOffsets([start, end]) => {
// LS sends offsets based on utf-16 based string representation
// but highlighting in helix is done using byte offset.
use helix_core::str_utils::char_to_byte_idx;
let from = char_to_byte_idx(&signature.label, *start as usize);
let to = char_to_byte_idx(&signature.label, *end as usize);
Some((from, to))
}
}
}
pub fn show_signature_help( pub fn show_signature_help(
editor: &mut Editor, editor: &mut Editor,
compositor: &mut Compositor, compositor: &mut Compositor,
@ -184,54 +210,50 @@ pub fn show_signature_help(
let doc = doc!(editor); let doc = doc!(editor);
let language = doc.language_name().unwrap_or(""); let language = doc.language_name().unwrap_or("");
let signature = match response if response.signatures.is_empty() {
return;
}
let signatures: Vec<Signature> = response
.signatures .signatures
.get(response.active_signature.unwrap_or(0) as usize) .into_iter()
{ .map(|s| {
Some(s) => s, let active_param_range = active_param_range(&s, response.active_parameter);
None => return,
}; let signature_doc = if config.lsp.display_signature_help_docs {
let mut contents = SignatureHelp::new( s.documentation.map(|doc| match doc {
signature.label.clone(), lsp::Documentation::String(s) => s,
lsp::Documentation::MarkupContent(markup) => markup.value,
})
} else {
None
};
Signature {
signature: s.label,
signature_doc,
active_param_range,
}
})
.collect();
let old_popup = compositor.find_id::<Popup<SignatureHelp>>(SignatureHelp::ID);
let mut active_signature = old_popup
.as_ref()
.map(|popup| popup.contents().active_signature())
.unwrap_or_else(|| response.active_signature.unwrap_or_default() as usize);
if active_signature >= signatures.len() {
active_signature = signatures.len() - 1;
}
let contents = SignatureHelp::new(
language.to_string(), language.to_string(),
Arc::clone(&editor.syn_loader), Arc::clone(&editor.syn_loader),
active_signature,
signatures,
); );
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 &param.label {
lsp::ParameterLabel::Simple(string) => {
let start = signature.label.find(string.as_str())?;
Some((start, start + string.len()))
}
lsp::ParameterLabel::LabelOffsets([start, end]) => {
// LS sends offsets based on utf-16 based string representation
// but highlighting in helix is done using byte offset.
use helix_core::str_utils::char_to_byte_idx;
let from = char_to_byte_idx(&signature.label, *start as usize);
let to = char_to_byte_idx(&signature.label, *end as usize);
Some((from, to))
}
}
};
contents.set_active_param_range(active_param_range());
let old_popup = compositor.find_id::<Popup<SignatureHelp>>(SignatureHelp::ID);
let mut popup = Popup::new(SignatureHelp::ID, contents) let mut popup = Popup::new(SignatureHelp::ID, contents)
.position(old_popup.and_then(|p| p.get_position())) .position(old_popup.and_then(|p| p.get_position()))
.position_bias(Open::Above) .position_bias(Open::Above)

@ -3,60 +3,95 @@ use std::sync::Arc;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use helix_core::syntax; use helix_core::syntax;
use helix_view::graphics::{Margin, Rect, Style}; use helix_view::graphics::{Margin, Rect, Style};
use helix_view::input::Event;
use tui::buffer::Buffer; use tui::buffer::Buffer;
use tui::layout::Alignment;
use tui::text::Text;
use tui::widgets::{BorderType, Paragraph, Widget, Wrap}; use tui::widgets::{BorderType, Paragraph, Widget, Wrap};
use crate::compositor::{Component, Compositor, Context}; use crate::compositor::{Component, Compositor, Context, EventResult};
use crate::alt;
use crate::ui::Markdown; use crate::ui::Markdown;
use super::Popup; use super::Popup;
pub struct SignatureHelp { pub struct Signature {
signature: String, pub signature: String,
signature_doc: Option<String>, pub signature_doc: Option<String>,
/// Part of signature text /// Part of signature text
active_param_range: Option<(usize, usize)>, pub active_param_range: Option<(usize, usize)>,
}
pub struct SignatureHelp {
language: String, language: String,
config_loader: Arc<ArcSwap<syntax::Loader>>, config_loader: Arc<ArcSwap<syntax::Loader>>,
active_signature: usize,
signatures: Vec<Signature>,
} }
impl SignatureHelp { impl SignatureHelp {
pub const ID: &'static str = "signature-help"; pub const ID: &'static str = "signature-help";
pub fn new( pub fn new(
signature: String,
language: String, language: String,
config_loader: Arc<ArcSwap<syntax::Loader>>, config_loader: Arc<ArcSwap<syntax::Loader>>,
active_signature: usize,
signatures: Vec<Signature>,
) -> Self { ) -> Self {
Self { Self {
signature,
signature_doc: None,
active_param_range: None,
language, language,
config_loader, config_loader,
active_signature,
signatures,
} }
} }
pub fn set_signature_doc(&mut self, signature_doc: Option<String>) { pub fn active_signature(&self) -> usize {
self.signature_doc = signature_doc; self.active_signature
}
pub fn set_active_param_range(&mut self, offset: Option<(usize, usize)>) {
self.active_param_range = offset;
} }
pub fn visible_popup(compositor: &mut Compositor) -> Option<&mut Popup<Self>> { pub fn visible_popup(compositor: &mut Compositor) -> Option<&mut Popup<Self>> {
compositor.find_id::<Popup<Self>>(Self::ID) compositor.find_id::<Popup<Self>>(Self::ID)
} }
fn signature_index(&self) -> String {
format!("({}/{})", self.active_signature + 1, self.signatures.len())
}
} }
impl Component for SignatureHelp { impl Component for SignatureHelp {
fn handle_event(&mut self, event: &Event, _cx: &mut Context) -> EventResult {
let Event::Key(event) = event else {
return EventResult::Ignored(None);
};
if self.signatures.len() <= 1 {
return EventResult::Ignored(None);
}
match event {
alt!('p') => {
self.active_signature = self
.active_signature
.checked_sub(1)
.unwrap_or(self.signatures.len() - 1);
EventResult::Consumed(None)
}
alt!('n') => {
self.active_signature = (self.active_signature + 1) % self.signatures.len();
EventResult::Consumed(None)
}
_ => EventResult::Ignored(None),
}
}
fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
let margin = Margin::horizontal(1); let margin = Margin::horizontal(1);
let active_param_span = self.active_param_range.map(|(start, end)| { let signature = &self.signatures[self.active_signature];
let active_param_span = signature.active_param_range.map(|(start, end)| {
vec![( vec![(
cx.editor cx.editor
.theme .theme
@ -66,21 +101,29 @@ impl Component for SignatureHelp {
)] )]
}); });
let sig = &self.signatures[self.active_signature];
let sig_text = crate::ui::markdown::highlighted_code_block( let sig_text = crate::ui::markdown::highlighted_code_block(
&self.signature, sig.signature.as_str(),
&self.language, &self.language,
Some(&cx.editor.theme), Some(&cx.editor.theme),
Arc::clone(&self.config_loader), Arc::clone(&self.config_loader),
active_param_span, active_param_span,
); );
if self.signatures.len() > 1 {
let signature_index = self.signature_index();
let text = Text::from(signature_index);
let paragraph = Paragraph::new(&text).alignment(Alignment::Right);
paragraph.render(area.clip_top(1).with_height(1).clip_right(1), surface);
}
let (_, sig_text_height) = crate::ui::text::required_size(&sig_text, area.width); let (_, sig_text_height) = crate::ui::text::required_size(&sig_text, area.width);
let sig_text_area = area.clip_top(1).with_height(sig_text_height); let sig_text_area = area.clip_top(1).with_height(sig_text_height);
let sig_text_area = sig_text_area.inner(&margin).intersection(surface.area); let sig_text_area = sig_text_area.inner(&margin).intersection(surface.area);
let sig_text_para = Paragraph::new(&sig_text).wrap(Wrap { trim: false }); let sig_text_para = Paragraph::new(&sig_text).wrap(Wrap { trim: false });
sig_text_para.render(sig_text_area, surface); sig_text_para.render(sig_text_area, surface);
if self.signature_doc.is_none() { if sig.signature_doc.is_none() {
return; return;
} }
@ -92,7 +135,7 @@ impl Component for SignatureHelp {
} }
} }
let sig_doc = match &self.signature_doc { let sig_doc = match &sig.signature_doc {
None => return, None => return,
Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)), Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
}; };
@ -110,13 +153,15 @@ impl Component for SignatureHelp {
const PADDING: u16 = 2; const PADDING: u16 = 2;
const SEPARATOR_HEIGHT: u16 = 1; const SEPARATOR_HEIGHT: u16 = 1;
let sig = &self.signatures[self.active_signature];
if PADDING >= viewport.1 || PADDING >= viewport.0 { if PADDING >= viewport.1 || PADDING >= viewport.0 {
return None; return None;
} }
let max_text_width = (viewport.0 - PADDING).min(120); let max_text_width = (viewport.0 - PADDING).min(120);
let signature_text = crate::ui::markdown::highlighted_code_block( let signature_text = crate::ui::markdown::highlighted_code_block(
&self.signature, sig.signature.as_str(),
&self.language, &self.language,
None, None,
Arc::clone(&self.config_loader), Arc::clone(&self.config_loader),
@ -125,7 +170,7 @@ impl Component for SignatureHelp {
let (sig_width, sig_height) = let (sig_width, sig_height) =
crate::ui::text::required_size(&signature_text, max_text_width); crate::ui::text::required_size(&signature_text, max_text_width);
let (width, height) = match self.signature_doc { let (width, height) = match sig.signature_doc {
Some(ref doc) => { Some(ref doc) => {
let doc_md = Markdown::new(doc.clone(), Arc::clone(&self.config_loader)); let doc_md = Markdown::new(doc.clone(), Arc::clone(&self.config_loader));
let doc_text = doc_md.parse(None); let doc_text = doc_md.parse(None);
@ -139,6 +184,12 @@ impl Component for SignatureHelp {
None => (sig_width, sig_height), None => (sig_width, sig_height),
}; };
Some((width + PADDING, height + PADDING)) let sig_index_width = if self.signatures.len() > 1 {
self.signature_index().len() + 1
} else {
0
};
Some((width + PADDING + sig_index_width as u16, height + PADDING))
} }
} }

Loading…
Cancel
Save