Unnamed repository; edit this file 'description' to name the repository.
Cycle through hover results from multiple language servers (#10122)
Co-authored-by: Vladyslav Karasov <[email protected]> Co-authored-by: Michael Davis <[email protected]>
kyfanc 2025-01-27
parent 7c907e6 · commit 9829ac0
-rw-r--r--helix-term/src/commands/lsp.rs89
-rw-r--r--helix-term/src/handlers/completion.rs2
-rw-r--r--helix-term/src/handlers/signature_help.rs2
-rw-r--r--helix-term/src/ui/lsp.rs211
-rw-r--r--helix-term/src/ui/lsp/hover.rs189
-rw-r--r--helix-term/src/ui/lsp/signature_help.rs209
6 files changed, 450 insertions, 252 deletions
diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs
index e52b2bf4..a7151b40 100644
--- a/helix-term/src/commands/lsp.rs
+++ b/helix-term/src/commands/lsp.rs
@@ -1009,54 +1009,61 @@ pub fn signature_help(cx: &mut Context) {
}
pub fn hover(cx: &mut Context) {
+ use ui::lsp::hover::Hover;
+
let (view, doc) = current!(cx.editor);
+ if doc
+ .language_servers_with_feature(LanguageServerFeature::Hover)
+ .count()
+ == 0
+ {
+ cx.editor
+ .set_error("No configured language server supports hover");
+ return;
+ }
- // TODO support multiple language servers (merge UI somehow)
- let language_server =
- language_server_with_feature!(cx.editor, doc, LanguageServerFeature::Hover);
- // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
- let pos = doc.position(view.id, language_server.offset_encoding());
- let future = language_server
- .text_document_hover(doc.identifier(), pos, None)
- .unwrap();
+ let mut seen_language_servers = HashSet::new();
+ let mut futures: FuturesOrdered<_> = doc
+ .language_servers_with_feature(LanguageServerFeature::Hover)
+ .filter(|ls| seen_language_servers.insert(ls.id()))
+ .map(|language_server| {
+ let server_name = language_server.name().to_string();
+ // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
+ let pos = doc.position(view.id, language_server.offset_encoding());
+ let request = language_server
+ .text_document_hover(doc.identifier(), pos, None)
+ .unwrap();
- cx.callback(
- future,
- move |editor, compositor, response: Option<lsp::Hover>| {
- if let Some(hover) = response {
- // hover.contents / .range <- used for visualizing
-
- fn marked_string_to_markdown(contents: lsp::MarkedString) -> String {
- match contents {
- lsp::MarkedString::String(contents) => contents,
- lsp::MarkedString::LanguageString(string) => {
- if string.language == "markdown" {
- string.value
- } else {
- format!("```{}\n{}\n```", string.language, string.value)
- }
- }
- }
- }
+ async move {
+ let json = request.await?;
+ let response = serde_json::from_value::<Option<lsp::Hover>>(json)?;
+ anyhow::Ok((server_name, response))
+ }
+ })
+ .collect();
- let contents = match hover.contents {
- lsp::HoverContents::Scalar(contents) => marked_string_to_markdown(contents),
- lsp::HoverContents::Array(contents) => contents
- .into_iter()
- .map(marked_string_to_markdown)
- .collect::<Vec<_>>()
- .join("\n\n"),
- lsp::HoverContents::Markup(contents) => contents.value,
- };
+ cx.jobs.callback(async move {
+ let mut hovers: Vec<(String, lsp::Hover)> = Vec::new();
- // skip if contents empty
+ while let Some((server_name, hover)) = futures.try_next().await? {
+ if let Some(hover) = hover {
+ hovers.push((server_name, hover));
+ }
+ }
- let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
- let popup = Popup::new("hover", contents).auto_close(true);
- compositor.replace_or_push("hover", popup);
+ let call = move |editor: &mut Editor, compositor: &mut Compositor| {
+ if hovers.is_empty() {
+ editor.set_status("No hover results available.");
+ return;
}
- },
- );
+
+ // create new popup
+ let contents = Hover::new(hovers, editor.syn_loader.clone());
+ let popup = Popup::new(Hover::ID, contents).auto_close(true);
+ compositor.replace_or_push(Hover::ID, popup);
+ };
+ Ok(Callback::EditorCompositor(Box::new(call)))
+ });
}
pub fn rename_symbol(cx: &mut Context) {
diff --git a/helix-term/src/handlers/completion.rs b/helix-term/src/handlers/completion.rs
index 3b12bd0a..6f0b73d7 100644
--- a/helix-term/src/handlers/completion.rs
+++ b/helix-term/src/handlers/completion.rs
@@ -26,7 +26,7 @@ use crate::events::{OnModeSwitch, PostCommand, PostInsertChar};
use crate::job::{dispatch, dispatch_blocking};
use crate::keymap::MappableCommand;
use crate::ui::editor::InsertEvent;
-use crate::ui::lsp::SignatureHelp;
+use crate::ui::lsp::signature_help::SignatureHelp;
use crate::ui::{self, Popup};
use super::Handlers;
diff --git a/helix-term/src/handlers/signature_help.rs b/helix-term/src/handlers/signature_help.rs
index cc7392e3..33c9e16c 100644
--- a/helix-term/src/handlers/signature_help.rs
+++ b/helix-term/src/handlers/signature_help.rs
@@ -16,7 +16,7 @@ use crate::commands::Open;
use crate::compositor::Compositor;
use crate::events::{OnModeSwitch, PostInsertChar};
use crate::handlers::Handlers;
-use crate::ui::lsp::{Signature, SignatureHelp};
+use crate::ui::lsp::signature_help::{Signature, SignatureHelp};
use crate::ui::Popup;
use crate::{job, ui};
diff --git a/helix-term/src/ui/lsp.rs b/helix-term/src/ui/lsp.rs
index 48862630..e71cf095 100644
--- a/helix-term/src/ui/lsp.rs
+++ b/helix-term/src/ui/lsp.rs
@@ -1,209 +1,2 @@
-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
- .get(self.active_signature)
- .unwrap_or_else(|| &self.signatures[0]);
-
- 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 signature = self
- .signatures
- .get(self.active_signature)
- .unwrap_or_else(|| &self.signatures[0]);
-
- let sig_text = crate::ui::markdown::highlighted_code_block(
- signature.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 signature.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 &signature.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 signature = self
- .signatures
- .get(self.active_signature)
- .unwrap_or_else(|| &self.signatures[0]);
-
- let max_text_width = viewport.0.saturating_sub(PADDING).clamp(10, 120);
-
- let signature_text = crate::ui::markdown::highlighted_code_block(
- signature.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 signature.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))
- }
-}
+pub mod hover;
+pub mod signature_help;
diff --git a/helix-term/src/ui/lsp/hover.rs b/helix-term/src/ui/lsp/hover.rs
new file mode 100644
index 00000000..bc50037b
--- /dev/null
+++ b/helix-term/src/ui/lsp/hover.rs
@@ -0,0 +1,189 @@
+use std::sync::Arc;
+
+use arc_swap::ArcSwap;
+use helix_core::syntax;
+use helix_lsp::lsp;
+use helix_view::graphics::{Margin, Rect, Style};
+use helix_view::input::Event;
+use once_cell::sync::OnceCell;
+use tui::buffer::Buffer;
+use tui::widgets::{BorderType, Paragraph, Widget, Wrap};
+
+use crate::compositor::{Component, Context, EventResult};
+
+use crate::alt;
+use crate::ui::Markdown;
+
+pub struct Hover {
+ hovers: Vec<(String, lsp::Hover)>,
+ active_index: usize,
+ config_loader: Arc<ArcSwap<syntax::Loader>>,
+
+ content: OnceCell<(Option<Markdown>, Markdown)>,
+}
+
+impl Hover {
+ pub const ID: &'static str = "hover";
+
+ pub fn new(
+ hovers: Vec<(String, lsp::Hover)>,
+ config_loader: Arc<ArcSwap<syntax::Loader>>,
+ ) -> Self {
+ Self {
+ hovers,
+ active_index: usize::default(),
+ config_loader,
+ content: OnceCell::new(),
+ }
+ }
+
+ fn content(&self) -> &(Option<Markdown>, Markdown) {
+ self.content.get_or_init(|| {
+ let (server_name, hover) = &self.hovers[self.active_index];
+ // Only render the header when there is more than one hover response.
+ let header = (self.hovers.len() > 1).then(|| {
+ Markdown::new(
+ format!(
+ "**[{}/{}] {}**",
+ self.active_index + 1,
+ self.hovers.len(),
+ server_name
+ ),
+ self.config_loader.clone(),
+ )
+ });
+ let body = Markdown::new(
+ hover_contents_to_string(&hover.contents),
+ self.config_loader.clone(),
+ );
+ (header, body)
+ })
+ }
+
+ fn set_index(&mut self, index: usize) {
+ assert!((0..self.hovers.len()).contains(&index));
+ self.active_index = index;
+ // Reset the cached markdown:
+ self.content.take();
+ }
+}
+
+const PADDING_HORIZONTAL: u16 = 2;
+const PADDING_TOP: u16 = 1;
+const PADDING_BOTTOM: u16 = 1;
+const HEADER_HEIGHT: u16 = 1;
+const SEPARATOR_HEIGHT: u16 = 1;
+
+impl Component for Hover {
+ fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
+ let margin = Margin::all(1);
+ let area = area.inner(margin);
+
+ let (header, contents) = self.content();
+
+ // show header and border only when more than one results
+ if let Some(header) = header {
+ // header LSP Name
+ let header = header.parse(Some(&cx.editor.theme));
+ let header = Paragraph::new(&header);
+ header.render(area.with_height(HEADER_HEIGHT), surface);
+
+ // border
+ let sep_style = Style::default();
+ let borders = BorderType::line_symbols(BorderType::Plain);
+ for x in area.left()..area.right() {
+ if let Some(cell) = surface.get_mut(x, area.top() + HEADER_HEIGHT) {
+ cell.set_symbol(borders.horizontal).set_style(sep_style);
+ }
+ }
+ }
+
+ // hover content
+ let contents = contents.parse(Some(&cx.editor.theme));
+ let contents_area = area
+ .clip_top(if self.hovers.len() > 1 {
+ HEADER_HEIGHT + SEPARATOR_HEIGHT
+ } else {
+ 0
+ })
+ .clip_bottom(u16::from(cx.editor.popup_border()));
+ let contents_para = Paragraph::new(&contents)
+ .wrap(Wrap { trim: false })
+ .scroll((cx.scroll.unwrap_or_default() as u16, 0));
+ contents_para.render(contents_area, surface);
+ }
+
+ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
+ let max_text_width = viewport.0.saturating_sub(PADDING_HORIZONTAL).clamp(10, 120);
+
+ let (header, contents) = self.content();
+
+ let header_width = header
+ .as_ref()
+ .map(|header| {
+ let header = header.parse(None);
+ let (width, _height) = crate::ui::text::required_size(&header, max_text_width);
+ width
+ })
+ .unwrap_or_default();
+
+ let contents = contents.parse(None);
+ let (content_width, content_height) =
+ crate::ui::text::required_size(&contents, max_text_width);
+
+ let width = PADDING_HORIZONTAL + header_width.max(content_width);
+ let height = if self.hovers.len() > 1 {
+ PADDING_TOP + HEADER_HEIGHT + SEPARATOR_HEIGHT + content_height + PADDING_BOTTOM
+ } else {
+ PADDING_TOP + content_height + PADDING_BOTTOM
+ };
+
+ Some((width, height))
+ }
+
+ fn handle_event(&mut self, event: &Event, _ctx: &mut Context) -> EventResult {
+ let Event::Key(event) = event else {
+ return EventResult::Ignored(None);
+ };
+
+ match event {
+ alt!('p') => {
+ let index = self
+ .active_index
+ .checked_sub(1)
+ .unwrap_or(self.hovers.len() - 1);
+ self.set_index(index);
+ EventResult::Consumed(None)
+ }
+ alt!('n') => {
+ self.set_index((self.active_index + 1) % self.hovers.len());
+ EventResult::Consumed(None)
+ }
+ _ => EventResult::Ignored(None),
+ }
+ }
+}
+
+fn hover_contents_to_string(contents: &lsp::HoverContents) -> String {
+ fn marked_string_to_markdown(contents: &lsp::MarkedString) -> String {
+ match contents {
+ lsp::MarkedString::String(contents) => contents.clone(),
+ lsp::MarkedString::LanguageString(string) => {
+ if string.language == "markdown" {
+ string.value.clone()
+ } else {
+ format!("```{}\n{}\n```", string.language, string.value)
+ }
+ }
+ }
+ }
+ match contents {
+ lsp::HoverContents::Scalar(contents) => marked_string_to_markdown(contents),
+ lsp::HoverContents::Array(contents) => contents
+ .iter()
+ .map(marked_string_to_markdown)
+ .collect::<Vec<_>>()
+ .join("\n\n"),
+ lsp::HoverContents::Markup(contents) => contents.value.clone(),
+ }
+}
diff --git a/helix-term/src/ui/lsp/signature_help.rs b/helix-term/src/ui/lsp/signature_help.rs
new file mode 100644
index 00000000..2dee8124
--- /dev/null
+++ b/helix-term/src/ui/lsp/signature_help.rs
@@ -0,0 +1,209 @@
+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 crate::ui::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
+ .get(self.active_signature)
+ .unwrap_or_else(|| &self.signatures[0]);
+
+ 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 signature = self
+ .signatures
+ .get(self.active_signature)
+ .unwrap_or_else(|| &self.signatures[0]);
+
+ let sig_text = crate::ui::markdown::highlighted_code_block(
+ signature.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 signature.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 &signature.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 signature = self
+ .signatures
+ .get(self.active_signature)
+ .unwrap_or_else(|| &self.signatures[0]);
+
+ let max_text_width = viewport.0.saturating_sub(PADDING).clamp(10, 120);
+
+ let signature_text = crate::ui::markdown::highlighted_code_block(
+ signature.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 signature.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))
+ }
+}