mirror of https://github.com/helix-editor/helix
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
parent
7775b35cba
commit
69e08d9e91
|
@ -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 ¶m.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 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 {
|
let signature_doc = if config.lsp.display_signature_help_docs {
|
||||||
signature.documentation.as_ref().map(|doc| match doc {
|
s.documentation.map(|doc| match doc {
|
||||||
lsp::Documentation::String(s) => s.clone(),
|
lsp::Documentation::String(s) => s,
|
||||||
lsp::Documentation::MarkupContent(markup) => markup.value.clone(),
|
lsp::Documentation::MarkupContent(markup) => markup.value,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Signature {
|
||||||
|
signature: s.label,
|
||||||
|
signature_doc,
|
||||||
|
active_param_range,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else {
|
.collect();
|
||||||
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]) => {
|
|
||||||
// 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 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(),
|
||||||
|
Arc::clone(&editor.syn_loader),
|
||||||
|
active_signature,
|
||||||
|
signatures,
|
||||||
|
);
|
||||||
|
|
||||||
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…
Reference in New Issue