hover ui component

pull/10122/head
kyfanc 7 months ago
parent 7e44329196
commit f450b0d7b9

@ -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, CompletionItem, Popup};
use super::Handlers;

@ -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::{Signature, SignatureHelp};
use crate::ui::lsp::signature_help::{Signature, SignatureHelp};
use crate::ui::Popup;
use crate::{job, ui};

@ -0,0 +1,2 @@
pub mod hover;
pub mod signature_help;

@ -0,0 +1,203 @@
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 tui::buffer::Buffer;
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 Hover {
hovers: Vec<(String, lsp::Hover)>,
active_index: usize,
config_loader: Arc<ArcSwap<syntax::Loader>>,
header: Option<Markdown>,
contents: Option<Markdown>,
}
impl Hover {
pub const ID: &'static str = "hover";
pub fn new(
hovers: Vec<(String, lsp::Hover)>,
config_loader: Arc<ArcSwap<syntax::Loader>>,
) -> Self {
let mut hover = Self {
hovers,
active_index: usize::default(),
config_loader,
header: None,
contents: None,
};
hover.set_index(hover.active_index);
hover
}
fn prepare_markdowns(&mut self) {
let Some((lsp_name, hover)) = self.hovers.get(self.active_index) else {
log::info!("prepare_markdowns: failed \nindex:{}\ncount:{}", self.active_index, self.hovers.len());
return
};
self.header = Some(Markdown::new(
format!(
"**[{}/{}] {}**",
self.active_index + 1,
self.hovers.len(),
lsp_name
),
self.config_loader.clone(),
));
let contents = hover_contents_to_string(&hover.contents);
self.contents = Some(Markdown::new(contents, self.config_loader.clone()));
}
pub fn set_hover(&mut self, hovers: Vec<(String, lsp::Hover)>) {
self.hovers = hovers;
self.set_index(usize::default());
}
fn set_index(&mut self, index: usize) {
self.active_index = index;
self.prepare_markdowns();
}
pub fn next_hover(&mut self) {
let index = if self.active_index < self.hovers.len() - 1 {
self.active_index + 1
} else {
usize::default()
};
self.set_index(index);
}
pub fn previous_hover(&mut self) {
let index = if self.active_index > 0 {
self.active_index - 1
} else {
self.hovers.len() - 1
};
self.set_index(index);
}
pub fn visible_popup(compositor: &mut Compositor) -> Option<&mut Popup<Self>> {
compositor.find_id::<Popup<Self>>(Self::ID)
}
}
const PADDING: u16 = 2;
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::horizontal(1);
let area = area.inner(&margin);
let (Some(header), Some(contents)) = (self.header.as_ref(), self.contents.as_ref()) else {
log::info!("markdown not ready");
return;
};
// 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(2)
.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)> {
if PADDING >= viewport.1 || PADDING >= viewport.0 {
return None;
}
let max_text_width = (viewport.0 - PADDING).min(120);
let (Some(header), Some(contents)) = (self.header.as_ref(), self.contents.as_ref()) else {
log::info!("markdown not ready");
return None;
};
let header = header.parse(None);
let (header_width, _header_height) =
crate::ui::text::required_size(&header, max_text_width);
let contents = contents.parse(None);
let (content_width, content_height) =
crate::ui::text::required_size(&contents, max_text_width);
let (width, height) = (
header_width.max(content_width),
HEADER_HEIGHT + SEPARATOR_HEIGHT + content_height,
);
Some((width + PADDING, height + PADDING))
}
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') => {
self.previous_hover();
EventResult::Consumed(None)
}
alt!('n') => {
self.next_hover();
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(),
}
}

@ -14,7 +14,7 @@ use crate::compositor::{Component, Compositor, Context, EventResult};
use crate::alt;
use crate::ui::Markdown;
use super::Popup;
use crate::ui::Popup;
pub struct Signature {
pub signature: String,

Loading…
Cancel
Save