Skip to content

Commit

Permalink
feat: cycle through function signatures/overloads
Browse files Browse the repository at this point in the history
implement handle_event to cycle through the function signatures.

by pressing up/down, ctrl + p/n or tab/shift+tab the function signature
will be changed. (similar behavior as the completion popup)

Signed-off-by: Ben Fekih, Hichem <[email protected]>
  • Loading branch information
karthago1 committed Apr 5, 2024
1 parent f773972 commit 1976d71
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 66 deletions.
112 changes: 67 additions & 45 deletions helix-term/src/handlers/signature_help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use helix_core::syntax::LanguageServerFeature;
use helix_event::{
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_view::document::Mode;
use helix_view::events::{DocumentDidChange, SelectionDidChange};
Expand All @@ -18,7 +18,7 @@ use crate::commands::Open;
use crate::compositor::Compositor;
use crate::events::{OnModeSwitch, PostInsertChar};
use crate::handlers::Handlers;
use crate::ui::lsp::SignatureHelp;
use crate::ui::lsp::{Signature, SignatureHelp};
use crate::ui::Popup;
use crate::{job, ui};

Expand Down Expand Up @@ -82,6 +82,7 @@ impl helix_event::AsyncHook for SignatureHelpHandler {
}
}
self.state = if open { State::Open } else { State::Closed };

return timeout;
}
}
Expand Down Expand Up @@ -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(
editor: &mut Editor,
compositor: &mut Compositor,
Expand Down Expand Up @@ -184,54 +210,50 @@ pub fn show_signature_help(
let doc = doc!(editor);
let language = doc.language_name().unwrap_or("");

let signature = match response
if response.signatures.is_empty() {
return;
}

let signatures: Vec<Signature> = response
.signatures
.get(response.active_signature.unwrap_or(0) as usize)
{
Some(s) => s,
None => return,
};
let mut contents = SignatureHelp::new(
signature.label.clone(),
.into_iter()
.map(|s| {
let active_param_range = active_param_range(&s, response.active_parameter);

let signature_doc = if config.lsp.display_signature_help_docs {
s.documentation.map(|doc| match doc {
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(),
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)
.position(old_popup.and_then(|p| p.get_position()))
.position_bias(Open::Above)
Expand Down
88 changes: 67 additions & 21 deletions helix-term/src/ui/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,50 @@ use std::sync::Arc;
use arc_swap::ArcSwap;
use helix_core::syntax;
use helix_view::graphics::{Margin, Rect, Style};
use helix_view::input::Event;
use tui::buffer::Buffer;
use tui::widgets::{BorderType, Paragraph, Widget, Wrap};

use crate::compositor::{Component, Compositor, Context};
use crate::compositor::{Component, Compositor, Context, EventResult};

use crate::ui::Markdown;
use crate::{ctrl, key, shift};

use super::Popup;

pub struct SignatureHelp {
signature: String,
signature_doc: Option<String>,
pub struct Signature {
pub signature: String,
pub signature_doc: Option<String>,
/// Part of signature text
active_param_range: Option<(usize, usize)>,
pub active_param_range: Option<(usize, usize)>,
}

pub struct SignatureHelp {
language: String,
config_loader: Arc<ArcSwap<syntax::Loader>>,
active_signature: usize,
signatures: Vec<Signature>,
}

impl SignatureHelp {
pub const ID: &'static str = "signature-help";

pub fn new(
signature: String,
language: String,
config_loader: Arc<ArcSwap<syntax::Loader>>,
active_signature: usize,
signatures: Vec<Signature>,
) -> Self {
Self {
signature,
signature_doc: None,
active_param_range: None,
language,
config_loader,
active_signature,
signatures,
}
}

pub fn set_signature_doc(&mut self, signature_doc: Option<String>) {
self.signature_doc = signature_doc;
}

pub fn set_active_param_range(&mut self, offset: Option<(usize, usize)>) {
self.active_param_range = offset;
pub fn active_signature(&self) -> usize {
self.active_signature
}

pub fn visible_popup(compositor: &mut Compositor) -> Option<&mut Popup<Self>> {
Expand All @@ -53,10 +55,42 @@ impl 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 {
shift!(Tab) | key!(Up) | ctrl!('p') => {
self.active_signature = if self.active_signature == 0 {
self.signatures.len() - 1
} else {
self.active_signature - 1
};
EventResult::Consumed(None)
}
key!(Tab) | key!(Down) | ctrl!('n') => {
self.active_signature = if self.active_signature >= self.signatures.len() - 1 {
0
} else {
self.active_signature + 1
};
EventResult::Consumed(None)
}
_ => EventResult::Ignored(None),
}
}

fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
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![(
cx.editor
.theme
Expand All @@ -66,21 +100,31 @@ impl Component for SignatureHelp {
)]
});

let sig = &self.signatures[self.active_signature];
let sig_text = crate::ui::markdown::highlighted_code_block(
&self.signature,
sig.signature.as_str(),
&self.language,
Some(&cx.editor.theme),
Arc::clone(&self.config_loader),
active_param_span,
);

if self.signatures.len() > 1 {
let signature_index =
format!("[{}/{}]", self.active_signature + 1, self.signatures.len());
let sig_index = Markdown::new(signature_index, self.config_loader.clone());
let sig_index = sig_index.parse(Some(&cx.editor.theme));
let sig_index_para = Paragraph::new(&sig_index);
sig_index_para.render(area.with_height(1).clip_left(1), surface);
}

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 = sig_text_area.inner(&margin).intersection(surface.area);
let sig_text_para = Paragraph::new(&sig_text).wrap(Wrap { trim: false });
sig_text_para.render(sig_text_area, surface);

if self.signature_doc.is_none() {
if sig.signature_doc.is_none() {
return;
}

Expand All @@ -92,7 +136,7 @@ impl Component for SignatureHelp {
}
}

let sig_doc = match &self.signature_doc {
let sig_doc = match &sig.signature_doc {
None => return,
Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
};
Expand All @@ -110,13 +154,15 @@ impl Component for SignatureHelp {
const PADDING: u16 = 2;
const SEPARATOR_HEIGHT: u16 = 1;

let sig = &self.signatures[self.active_signature];

if PADDING >= viewport.1 || PADDING >= viewport.0 {
return None;
}
let max_text_width = (viewport.0 - PADDING).min(120);

let signature_text = crate::ui::markdown::highlighted_code_block(
&self.signature,
&sig.signature.as_str(),
&self.language,
None,
Arc::clone(&self.config_loader),
Expand All @@ -125,7 +171,7 @@ impl Component for SignatureHelp {
let (sig_width, sig_height) =
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) => {
let doc_md = Markdown::new(doc.clone(), Arc::clone(&self.config_loader));
let doc_text = doc_md.parse(None);
Expand Down

0 comments on commit 1976d71

Please sign in to comment.