mirror of https://github.com/helix-editor/helix
Add lsp signature help (#1755)
* Add lsp signature help * Do not move signature help popup on multiple triggers * Highlight current parameter in signature help * Auto close signature help * Position signature help above to not block completion * Update signature help on backspace/insert mode delete * Add lsp.auto-signature-help config option * Add serde default annotation for LspConfig * Show LSP inactive message only if signature help is invoked manually * Do not assume valid signature help response from LSP Malformed LSP responses are common, and these should not crash the editor. * Check signature help capability before sending request * Reuse Open enum for PositionBias in popup * Close signature popup and exit insert mode on escape * Add config to control signature help docs display * Use new Margin api in signature help * Invoke signature help on changing to insert modepull/3104/head
parent
02f0099210
commit
791bf7e50a
@ -0,0 +1,133 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use helix_core::syntax;
|
||||
use helix_view::graphics::{Margin, Rect, Style};
|
||||
use tui::buffer::Buffer;
|
||||
use tui::widgets::{BorderType, Paragraph, Widget, Wrap};
|
||||
|
||||
use crate::compositor::{Component, Compositor, Context};
|
||||
|
||||
use crate::ui::Markdown;
|
||||
|
||||
use super::Popup;
|
||||
|
||||
pub struct SignatureHelp {
|
||||
signature: String,
|
||||
signature_doc: Option<String>,
|
||||
/// Part of signature text
|
||||
active_param_range: Option<(usize, usize)>,
|
||||
|
||||
language: String,
|
||||
config_loader: Arc<syntax::Loader>,
|
||||
}
|
||||
|
||||
impl SignatureHelp {
|
||||
pub const ID: &'static str = "signature-help";
|
||||
|
||||
pub fn new(signature: String, language: String, config_loader: Arc<syntax::Loader>) -> Self {
|
||||
Self {
|
||||
signature,
|
||||
signature_doc: None,
|
||||
active_param_range: None,
|
||||
language,
|
||||
config_loader,
|
||||
}
|
||||
}
|
||||
|
||||
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 visible_popup(compositor: &mut Compositor) -> Option<&mut Popup<Self>> {
|
||||
compositor.find_id::<Popup<Self>>(Self::ID)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SignatureHelp {
|
||||
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)| {
|
||||
vec![(
|
||||
cx.editor.theme.find_scope_index("ui.selection").unwrap(),
|
||||
start..end,
|
||||
)]
|
||||
});
|
||||
|
||||
let sig_text = crate::ui::markdown::highlighted_code_block(
|
||||
self.signature.clone(),
|
||||
&self.language,
|
||||
Some(&cx.editor.theme),
|
||||
Arc::clone(&self.config_loader),
|
||||
active_param_span,
|
||||
);
|
||||
|
||||
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_para = Paragraph::new(sig_text).wrap(Wrap { trim: false });
|
||||
sig_text_para.render(sig_text_area.inner(&margin), surface);
|
||||
|
||||
if self.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 &self.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);
|
||||
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;
|
||||
|
||||
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.clone(),
|
||||
&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 self.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),
|
||||
};
|
||||
|
||||
Some((width + PADDING, height + PADDING))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue