From b2065acdadbe66a2e9c23d634c95487b848755af Mon Sep 17 00:00:00 2001 From: "Ben Fekih, Hichem" Date: Tue, 2 Apr 2024 23:41:46 +0200 Subject: [PATCH] feat: cycle through function signatures/overloads 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 --- helix-term/src/handlers/signature_help.rs | 101 +++++++++++++++++++--- helix-term/src/ui/lsp.rs | 40 ++++++++- helix-view/src/handlers/lsp.rs | 8 +- 3 files changed, 134 insertions(+), 15 deletions(-) diff --git a/helix-term/src/handlers/signature_help.rs b/helix-term/src/handlers/signature_help.rs index 3c746548ac8c4..c6f782271ae8c 100644 --- a/helix-term/src/handlers/signature_help.rs +++ b/helix-term/src/handlers/signature_help.rs @@ -24,7 +24,7 @@ use crate::{job, ui}; #[derive(Debug)] enum State { - Open, + Open { signature_count: usize }, Closed, Pending { request: CancelTx }, } @@ -37,6 +37,7 @@ const TIMEOUT: u64 = 120; pub(super) struct SignatureHelpHandler { trigger: Option, state: State, + active_signature: Option, } impl SignatureHelpHandler { @@ -44,8 +45,41 @@ impl SignatureHelpHandler { SignatureHelpHandler { trigger: None, state: State::Closed, + active_signature: None, } } + + fn next_signature(&mut self) { + let State::Open { signature_count } = self.state else { + return; + }; + + let value = self + .active_signature + .map(|x| if x >= signature_count - 1 { 0 } else { x + 1 }) + .unwrap_or_default(); + + self.active_signature.replace(value); + } + + fn previous_signature(&mut self) { + let State::Open { signature_count } = self.state else { + return; + }; + + let value = self + .active_signature + .map(|x| if x == 0 { signature_count - 1 } else { x - 1 }) + .unwrap_or(signature_count - 1); + + self.active_signature.replace(value); + } + + /// set the state to closed and clear active_signature + fn close(&mut self) { + self.state = State::Closed; + self.active_signature = None; + } } impl helix_event::AsyncHook for SignatureHelpHandler { @@ -59,7 +93,7 @@ impl helix_event::AsyncHook for SignatureHelpHandler { match event { SignatureHelpEvent::Invoked => { self.trigger = Some(SignatureHelpInvoked::Manual); - self.state = State::Closed; + self.close(); self.finish_debounce(); return None; } @@ -71,19 +105,35 @@ impl helix_event::AsyncHook for SignatureHelpHandler { } } SignatureHelpEvent::Cancel => { - self.state = State::Closed; + self.close(); return None; } - SignatureHelpEvent::RequestComplete { open } => { + SignatureHelpEvent::RequestComplete { + open, + signature_count, + active_signature, + } => { // don't cancel rerequest that was already triggered if let State::Pending { request } = &self.state { if !request.is_closed() { return timeout; } } - self.state = if open { State::Open } else { State::Closed }; + if open { + if self.active_signature.is_none() + || self.active_signature.unwrap() >= signature_count + { + self.active_signature = active_signature; + } + self.state = State::Open { signature_count }; + } else { + self.close(); + } + return timeout; } + SignatureHelpEvent::Next => self.next_signature(), + SignatureHelpEvent::Previous => self.previous_signature(), } if self.trigger.is_none() { self.trigger = Some(SignatureHelpInvoked::Automatic) @@ -95,7 +145,11 @@ impl helix_event::AsyncHook for SignatureHelpHandler { let invocation = self.trigger.take().unwrap(); let (tx, rx) = cancelation(); self.state = State::Pending { request: tx }; - job::dispatch_blocking(move |editor, _| request_signature_help(editor, invocation, rx)) + let active_signature = self.active_signature; + + job::dispatch_blocking(move |editor, _| { + request_signature_help(editor, invocation, rx, active_signature) + }) } } @@ -103,6 +157,7 @@ pub fn request_signature_help( editor: &mut Editor, invoked: SignatureHelpInvoked, cancel: CancelRx, + signature_index: Option, ) { let (view, doc) = current!(editor); @@ -128,7 +183,7 @@ pub fn request_signature_help( match cancelable_future(future, cancel).await { Some(Ok(res)) => { job::dispatch(move |editor, compositor| { - show_signature_help(editor, compositor, invoked, res) + show_signature_help(editor, compositor, invoked, res, signature_index) }) .await } @@ -143,6 +198,7 @@ pub fn show_signature_help( compositor: &mut Compositor, invoked: SignatureHelpInvoked, response: Option, + signature_index: Option, ) { let config = &editor.config(); @@ -170,7 +226,11 @@ pub fn show_signature_help( _ => { send_blocking( &editor.handlers.signature_hints, - SignatureHelpEvent::RequestComplete { open: false }, + SignatureHelpEvent::RequestComplete { + open: false, + signature_count: 0, + active_signature: None, + }, ); compositor.remove(SignatureHelp::ID); return; @@ -178,16 +238,20 @@ pub fn show_signature_help( }; send_blocking( &editor.handlers.signature_hints, - SignatureHelpEvent::RequestComplete { open: true }, + SignatureHelpEvent::RequestComplete { + open: true, + signature_count: response.signatures.len(), + active_signature: response.active_signature.map(|x| x as usize), + }, ); let doc = doc!(editor); let language = doc.language_name().unwrap_or(""); - let signature = match response - .signatures - .get(response.active_signature.unwrap_or(0) as usize) - { + let signature_index = + signature_index.unwrap_or_else(|| response.active_signature.unwrap_or_default() as usize); + + let signature = match response.signatures.get(signature_index) { Some(s) => s, None => return, }; @@ -195,8 +259,19 @@ pub fn show_signature_help( signature.label.clone(), language.to_string(), Arc::clone(&editor.syn_loader), + |editor, event: SignatureHelpEvent| { + send_blocking(&editor.handlers.signature_hints, event); + }, ); + if response.signatures.len() > 1 { + contents.set_signature_index(format!( + "{}/{}", + signature_index + 1, + response.signatures.len() + )); + } + let signature_doc = if config.lsp.display_signature_help_docs { signature.documentation.as_ref().map(|doc| match doc { lsp::Documentation::String(s) => s.clone(), diff --git a/helix-term/src/ui/lsp.rs b/helix-term/src/ui/lsp.rs index a3698e38d8795..42190c97f7bd8 100644 --- a/helix-term/src/ui/lsp.rs +++ b/helix-term/src/ui/lsp.rs @@ -3,23 +3,29 @@ use std::sync::Arc; use arc_swap::ArcSwap; use helix_core::syntax; use helix_view::graphics::{Margin, Rect, Style}; +use helix_view::handlers::lsp::SignatureHelpEvent; +use helix_view::input::Event; +use helix_view::Editor; 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, + signature_index: Option, /// Part of signature text active_param_range: Option<(usize, usize)>, language: String, config_loader: Arc>, + callback: fn(&mut Editor, SignatureHelpEvent), } impl SignatureHelp { @@ -29,6 +35,7 @@ impl SignatureHelp { signature: String, language: String, config_loader: Arc>, + callback: fn(&mut Editor, SignatureHelpEvent), ) -> Self { Self { signature, @@ -36,6 +43,8 @@ impl SignatureHelp { active_param_range: None, language, config_loader, + signature_index: None, + callback, } } @@ -50,9 +59,31 @@ impl SignatureHelp { pub fn visible_popup(compositor: &mut Compositor) -> Option<&mut Popup> { compositor.find_id::>(Self::ID) } + + pub fn set_signature_index(&mut self, signature_index: String) { + self.signature_index = Some(signature_index); + } } 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); + }; + + match event { + shift!(Tab) | key!(Up) | ctrl!('p') => { + (self.callback)(cx.editor, SignatureHelpEvent::Previous); + EventResult::Consumed(None) + } + key!(Tab) | key!(Down) | ctrl!('n') => { + (self.callback)(cx.editor, SignatureHelpEvent::Next); + EventResult::Consumed(None) + } + _ => EventResult::Ignored(None), + } + } + fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) { let margin = Margin::horizontal(1); @@ -74,6 +105,13 @@ impl Component for SignatureHelp { active_param_span, ); + if let Some(signature_index) = &self.signature_index { + let sig_index = Markdown::new(signature_index.clone(), 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); diff --git a/helix-view/src/handlers/lsp.rs b/helix-view/src/handlers/lsp.rs index beb106b2bf83e..de036e08f2125 100644 --- a/helix-view/src/handlers/lsp.rs +++ b/helix-view/src/handlers/lsp.rs @@ -41,7 +41,13 @@ pub enum SignatureHelpEvent { Trigger, ReTrigger, Cancel, - RequestComplete { open: bool }, + RequestComplete { + open: bool, + signature_count: usize, + active_signature: Option, + }, + Next, + Previous, } #[derive(Debug)]