Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/lsp.rs')
-rw-r--r--helix-term/src/ui/lsp.rs201
1 files changed, 199 insertions, 2 deletions
diff --git a/helix-term/src/ui/lsp.rs b/helix-term/src/ui/lsp.rs
index e71cf095..b6491085 100644
--- a/helix-term/src/ui/lsp.rs
+++ b/helix-term/src/ui/lsp.rs
@@ -1,2 +1,199 @@
-pub mod hover;
-pub mod signature_help;
+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::layout::Alignment;
+use tui::text::Text;
+use tui::widgets::{BorderType, Paragraph, Widget, Wrap};
+
+use crate::compositor::{Component, Compositor, Context, EventResult};
+
+use crate::alt;
+use crate::ui::Markdown;
+
+use super::Popup;
+
+pub struct Signature {
+ pub signature: String,
+ pub signature_doc: Option<String>,
+ /// Part of signature text
+ pub active_param_range: Option<(usize, usize)>,
+}
+
+pub struct SignatureHelp {
+ language: String,
+ config_loader: Arc<ArcSwap<syntax::Loader>>,
+ active_signature: usize,
+ lsp_signature: Option<usize>,
+ signatures: Vec<Signature>,
+}
+
+impl SignatureHelp {
+ pub const ID: &'static str = "signature-help";
+
+ pub fn new(
+ language: String,
+ config_loader: Arc<ArcSwap<syntax::Loader>>,
+ active_signature: usize,
+ lsp_signature: Option<usize>,
+ signatures: Vec<Signature>,
+ ) -> Self {
+ Self {
+ language,
+ config_loader,
+ active_signature,
+ lsp_signature,
+ signatures,
+ }
+ }
+
+ pub fn active_signature(&self) -> usize {
+ self.active_signature
+ }
+
+ pub fn lsp_signature(&self) -> Option<usize> {
+ self.lsp_signature
+ }
+
+ pub fn visible_popup(compositor: &mut Compositor) -> Option<&mut Popup<Self>> {
+ compositor.find_id::<Popup<Self>>(Self::ID)
+ }
+
+ fn signature_index(&self) -> String {
+ format!("({}/{})", self.active_signature + 1, self.signatures.len())
+ }
+}
+
+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) {
+ let margin = Margin::horizontal(1);
+
+ let signature = &self.signatures[self.active_signature];
+
+ let active_param_span = signature.active_param_range.map(|(start, end)| {
+ vec![(
+ cx.editor
+ .theme
+ .find_scope_index_exact("ui.selection")
+ .unwrap(),
+ start..end,
+ )]
+ });
+
+ let sig = &self.signatures[self.active_signature];
+ let sig_text = crate::ui::markdown::highlighted_code_block(
+ 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 = 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_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 sig.signature_doc.is_none() {
+ return;
+ }
+
+ let sep_style = Style::default();
+ let borders = BorderType::line_symbols(BorderType::Plain);
+ for x in sig_text_area.left()..sig_text_area.right() {
+ if let Some(cell) = surface.get_mut(x, sig_text_area.bottom()) {
+ cell.set_symbol(borders.horizontal).set_style(sep_style);
+ }
+ }
+
+ let sig_doc = match &sig.signature_doc {
+ None => return,
+ Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
+ };
+ let sig_doc = sig_doc.parse(Some(&cx.editor.theme));
+ let sig_doc_area = area
+ .clip_top(sig_text_area.height + 2)
+ .clip_bottom(u16::from(cx.editor.popup_border()));
+ let sig_doc_para = Paragraph::new(&sig_doc)
+ .wrap(Wrap { trim: false })
+ .scroll((cx.scroll.unwrap_or_default() as u16, 0));
+ sig_doc_para.render(sig_doc_area.inner(margin), surface);
+ }
+
+ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
+ const PADDING: u16 = 2;
+ const SEPARATOR_HEIGHT: u16 = 1;
+
+ let sig = &self.signatures[self.active_signature];
+
+ let max_text_width = viewport.0.saturating_sub(PADDING).clamp(10, 120);
+
+ let signature_text = crate::ui::markdown::highlighted_code_block(
+ sig.signature.as_str(),
+ &self.language,
+ None,
+ Arc::clone(&self.config_loader),
+ None,
+ );
+ let (sig_width, sig_height) =
+ crate::ui::text::required_size(&signature_text, max_text_width);
+
+ 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);
+ let (doc_width, doc_height) =
+ crate::ui::text::required_size(&doc_text, max_text_width);
+ (
+ sig_width.max(doc_width),
+ sig_height + SEPARATOR_HEIGHT + doc_height,
+ )
+ }
+ None => (sig_width, sig_height),
+ };
+
+ 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))
+ }
+}