lsp: Hover documentation draft.
This commit is contained in:
parent
8289bd1cb0
commit
7162632eb7
6 changed files with 213 additions and 10 deletions
|
@ -201,11 +201,12 @@ impl Client {
|
|||
context_support: None, // additional context information Some(true)
|
||||
..Default::default()
|
||||
}),
|
||||
// { completion: {
|
||||
// dynamic_registration: bool
|
||||
// completion_item: { snippet, documentation_format, ... }
|
||||
// completion_item_kind: { }
|
||||
// } }
|
||||
hover: Some(lsp::HoverClientCapabilities {
|
||||
// if not specified, rust-analyzer returns plaintext marked as markdown but
|
||||
// badly formatted.
|
||||
content_format: Some(vec![lsp::MarkupKind::Markdown]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
|
@ -458,4 +459,51 @@ impl Client {
|
|||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
pub async fn text_document_signature_help(
|
||||
&self,
|
||||
text_document: lsp::TextDocumentIdentifier,
|
||||
position: lsp::Position,
|
||||
) -> anyhow::Result<Option<lsp::SignatureHelp>> {
|
||||
let params = lsp::SignatureHelpParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document,
|
||||
position,
|
||||
},
|
||||
// TODO: support these tokens
|
||||
work_done_progress_params: lsp::WorkDoneProgressParams {
|
||||
work_done_token: None,
|
||||
},
|
||||
context: None,
|
||||
// lsp::SignatureHelpContext
|
||||
};
|
||||
|
||||
let response = self
|
||||
.request::<lsp::request::SignatureHelpRequest>(params)
|
||||
.await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn text_document_hover(
|
||||
&self,
|
||||
text_document: lsp::TextDocumentIdentifier,
|
||||
position: lsp::Position,
|
||||
) -> anyhow::Result<Option<lsp::Hover>> {
|
||||
let params = lsp::HoverParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document,
|
||||
position,
|
||||
},
|
||||
// TODO: support these tokens
|
||||
work_done_progress_params: lsp::WorkDoneProgressParams {
|
||||
work_done_token: None,
|
||||
},
|
||||
// lsp::SignatureHelpContext
|
||||
};
|
||||
|
||||
let response = self.request::<lsp::request::HoverRequest>(params).await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use helix_core::{
|
|||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::compositor::Compositor;
|
||||
use crate::ui::{self, Prompt, PromptEvent};
|
||||
use crate::ui::{self, Popup, Prompt, PromptEvent};
|
||||
|
||||
use helix_view::{
|
||||
document::Mode,
|
||||
|
@ -1000,6 +1000,63 @@ pub fn completion(cx: &mut Context) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn hover(cx: &mut Context) {
|
||||
use helix_lsp::lsp;
|
||||
|
||||
let doc = cx.doc();
|
||||
|
||||
let language_server = match doc.language_server.as_ref() {
|
||||
Some(language_server) => language_server,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
|
||||
|
||||
// TODO: blocking here is not ideal, make commands async fn?
|
||||
// not like we can process additional input meanwhile though
|
||||
let pos = helix_lsp::util::pos_to_lsp_pos(doc.text().slice(..), doc.selection().cursor());
|
||||
|
||||
// TODO: handle fails
|
||||
let res = smol::block_on(language_server.text_document_hover(doc.identifier(), pos))
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(hover) = res {
|
||||
// hover.contents / .range <- used for visualizing
|
||||
let contents = match hover.contents {
|
||||
lsp::HoverContents::Scalar(contents) => {
|
||||
// markedstring(string/languagestring to be highlighted)
|
||||
// TODO
|
||||
unimplemented!("{:?}", contents)
|
||||
}
|
||||
lsp::HoverContents::Array(contents) => {
|
||||
unimplemented!("{:?}", contents)
|
||||
}
|
||||
// TODO: render markdown
|
||||
lsp::HoverContents::Markup(contents) => contents.value,
|
||||
};
|
||||
|
||||
// skip if contents empty
|
||||
|
||||
// Popup: box frame + Box<Component> for internal content.
|
||||
// it will use the contents.size_hint/required size to figure out sizing & positioning
|
||||
// can also use render_buffer to render the content.
|
||||
// render_buffer(highlights/scopes, text, surface, theme)
|
||||
//
|
||||
let mut popup = Popup::new(contents);
|
||||
|
||||
cx.callback = Some(Box::new(
|
||||
move |compositor: &mut Compositor, editor: &mut Editor| {
|
||||
let area = tui::layout::Rect::default(); // TODO: unused remove from cursor_position
|
||||
let mut pos = compositor.cursor_position(area, editor);
|
||||
pos.row += 1; // shift down by one row
|
||||
popup.set_position(pos);
|
||||
|
||||
compositor.push(Box::new(popup));
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// view movements
|
||||
pub fn next_view(cx: &mut Context) {
|
||||
cx.editor.tree.focus_next()
|
||||
|
|
|
@ -203,6 +203,8 @@ pub fn default() -> Keymaps {
|
|||
|
||||
// move under <space>c
|
||||
vec![ctrl!('c')] => commands::toggle_comments,
|
||||
// was K, figure out a key
|
||||
vec![ctrl!('k')] => commands::hover,
|
||||
),
|
||||
Mode::Insert => hashmap!(
|
||||
vec![Key {
|
||||
|
|
|
@ -100,6 +100,7 @@ impl EditorView {
|
|||
let mut spans = Vec::new();
|
||||
let mut visual_x = 0;
|
||||
let mut line = 0u16;
|
||||
let text = view.doc.text();
|
||||
|
||||
'outer: for event in highlights {
|
||||
match event.unwrap() {
|
||||
|
@ -113,7 +114,6 @@ impl EditorView {
|
|||
// TODO: filter out spans out of viewport for now..
|
||||
|
||||
// TODO: do these before iterating
|
||||
let text = view.doc.text();
|
||||
let start = text.byte_to_char(start);
|
||||
let end = text.byte_to_char(end);
|
||||
|
||||
|
@ -160,8 +160,7 @@ impl EditorView {
|
|||
let grapheme = Cow::from(grapheme);
|
||||
let width = grapheme_width(&grapheme) as u16;
|
||||
|
||||
// ugh, improve with a traverse method
|
||||
// or interleave highlight spans with selection and diagnostic spans
|
||||
// ugh,interleave highlight spans with diagnostic spans
|
||||
let is_diagnostic = view.doc.diagnostics.iter().any(|diagnostic| {
|
||||
diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
|
||||
});
|
||||
|
@ -191,12 +190,12 @@ impl EditorView {
|
|||
// render selections
|
||||
|
||||
if is_focused {
|
||||
let text = view.doc.text().slice(..);
|
||||
let screen = {
|
||||
let start = text.line_to_char(view.first_line);
|
||||
let end = text.line_to_char(last_line + 1);
|
||||
Range::new(start, end)
|
||||
};
|
||||
let text = text.slice(..);
|
||||
let cursor_style = Style::default().bg(Color::Rgb(255, 255, 255));
|
||||
|
||||
// cedar
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
mod editor;
|
||||
mod menu;
|
||||
mod picker;
|
||||
mod popup;
|
||||
mod prompt;
|
||||
|
||||
pub use editor::EditorView;
|
||||
pub use menu::Menu;
|
||||
pub use picker::Picker;
|
||||
pub use popup::Popup;
|
||||
pub use prompt::{Prompt, PromptEvent};
|
||||
|
||||
pub use tui::layout::Rect;
|
||||
|
|
95
helix-term/src/ui/popup.rs
Normal file
95
helix-term/src/ui/popup.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use crate::compositor::{Component, Compositor, Context, EventResult};
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
|
||||
use tui::buffer::Buffer as Surface;
|
||||
use tui::{
|
||||
layout::Rect,
|
||||
style::{Color, Style},
|
||||
widgets::{Block, Borders},
|
||||
};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use helix_core::Position;
|
||||
use helix_view::Editor;
|
||||
|
||||
pub struct Popup {
|
||||
contents: String,
|
||||
position: Position,
|
||||
}
|
||||
|
||||
impl Popup {
|
||||
// TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
|
||||
// rendering)
|
||||
pub fn new(contents: String) -> Self {
|
||||
Self {
|
||||
contents,
|
||||
position: Position::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_position(&mut self, pos: Position) {
|
||||
self.position = pos;
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Popup {
|
||||
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
let event = match event {
|
||||
Event::Key(event) => event,
|
||||
_ => return EventResult::Ignored,
|
||||
};
|
||||
|
||||
let close_fn = EventResult::Consumed(Some(Box::new(
|
||||
|compositor: &mut Compositor, editor: &mut Editor| {
|
||||
// remove the layer
|
||||
compositor.pop();
|
||||
},
|
||||
)));
|
||||
|
||||
match event {
|
||||
// esc or ctrl-c aborts the completion and closes the menu
|
||||
KeyEvent {
|
||||
code: KeyCode::Esc, ..
|
||||
}
|
||||
| KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
} => {
|
||||
return close_fn;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
// for some events, we want to process them but send ignore, specifically all input except
|
||||
// tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
|
||||
// EventResult::Consumed(None)
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||
// render a box at x, y. Width equal to max width of item.
|
||||
const MAX: usize = 15;
|
||||
let rows = std::cmp::min(self.contents.lines().count(), MAX) as u16; // inefficient
|
||||
let area = Rect::new(self.position.col as u16, self.position.row as u16, 80, rows);
|
||||
|
||||
// clear area
|
||||
let background = cx.editor.theme.get("ui.popup");
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
let cell = surface.get_mut(x, y);
|
||||
cell.reset();
|
||||
// cell.symbol.clear();
|
||||
cell.set_style(background);
|
||||
}
|
||||
}
|
||||
|
||||
// -- Render the contents:
|
||||
|
||||
let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
|
||||
|
||||
use tui::text::Text;
|
||||
use tui::widgets::{Paragraph, Widget, Wrap};
|
||||
let contents = Text::from(self.contents.clone());
|
||||
let par = Paragraph::new(contents).wrap(Wrap { trim: false });
|
||||
|
||||
par.render(area, surface);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue