on master help sign in
lsp: Hover documentation draft.
commit7162632eb739557ededaee902b23e48a577817b4 authorBlaž Hrastnik <blaz@mxxn.io> dateThu 25 Feb 2021, 18:07 (6 years ago) parent8289bd1
6 files changed+213−10
helix-lsp/src/client.rs+53−5 helix-term/src/commands.rs+58−1 helix-term/src/keymap.rs+2 helix-term/src/ui/editor.rs+3−4 helix-term/src/ui/mod.rs+2 helix-term/src/ui/popup.rs+95
helix-lsp/src/client.rs+53−5
@@ -201,11 +201,12 @@ impl Client {
201201 context_support: None, // additional context information Some(true)
202202 ..Default::default()
203203 }),
204− // { completion: {
205− // dynamic_registration: bool
206− // completion_item: { snippet, documentation_format, ... }
207− // completion_item_kind: { }
208− // } }
204+ hover: Some(lsp::HoverClientCapabilities {
205+ // if not specified, rust-analyzer returns plaintext marked as markdown but
206+ // badly formatted.
207+ content_format: Some(vec![lsp::MarkupKind::Markdown]),
208+ ..Default::default()
209+ }),
209210 ..Default::default()
210211 }),
211212 ..Default::default()
@@ -458,4 +459,51 @@ impl Client {
458459
459460 Ok(items)
460461 }
462+
463+ pub async fn text_document_signature_help(
464+ &self,
465+ text_document: lsp::TextDocumentIdentifier,
466+ position: lsp::Position,
467+ ) -> anyhow::Result<Option<lsp::SignatureHelp>> {
468+ let params = lsp::SignatureHelpParams {
469+ text_document_position_params: lsp::TextDocumentPositionParams {
470+ text_document,
471+ position,
472+ },
473+ // TODO: support these tokens
474+ work_done_progress_params: lsp::WorkDoneProgressParams {
475+ work_done_token: None,
476+ },
477+ context: None,
478+ // lsp::SignatureHelpContext
479+ };
480+
481+ let response = self
482+ .request::<lsp::request::SignatureHelpRequest>(params)
483+ .await?;
484+
485+ Ok(response)
486+ }
487+
488+ pub async fn text_document_hover(
489+ &self,
490+ text_document: lsp::TextDocumentIdentifier,
491+ position: lsp::Position,
492+ ) -> anyhow::Result<Option<lsp::Hover>> {
493+ let params = lsp::HoverParams {
494+ text_document_position_params: lsp::TextDocumentPositionParams {
495+ text_document,
496+ position,
497+ },
498+ // TODO: support these tokens
499+ work_done_progress_params: lsp::WorkDoneProgressParams {
500+ work_done_token: None,
501+ },
502+ // lsp::SignatureHelpContext
503+ };
504+
505+ let response = self.request::<lsp::request::HoverRequest>(params).await?;
506+
507+ Ok(response)
508+ }
461509}
helix-term/src/commands.rs+58−1
@@ -11,7 +11,7 @@ use helix_core::{
1111use once_cell::sync::Lazy;
1212
1313use crate::compositor::Compositor;
14−use crate::ui::{self, Prompt, PromptEvent};
14+use crate::ui::{self, Popup, Prompt, PromptEvent};
1515
1616use helix_view::{
1717 document::Mode,
@@ -1000,6 +1000,63 @@ pub fn completion(cx: &mut Context) {
10001000 }
10011001}
10021002
1003+pub fn hover(cx: &mut Context) {
1004+ use helix_lsp::lsp;
1005+
1006+ let doc = cx.doc();
1007+
1008+ let language_server = match doc.language_server.as_ref() {
1009+ Some(language_server) => language_server,
1010+ None => return,
1011+ };
1012+
1013+ // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
1014+
1015+ // TODO: blocking here is not ideal, make commands async fn?
1016+ // not like we can process additional input meanwhile though
1017+ let pos = helix_lsp::util::pos_to_lsp_pos(doc.text().slice(..), doc.selection().cursor());
1018+
1019+ // TODO: handle fails
1020+ let res = smol::block_on(language_server.text_document_hover(doc.identifier(), pos))
1021+ .unwrap_or_default();
1022+
1023+ if let Some(hover) = res {
1024+ // hover.contents / .range <- used for visualizing
1025+ let contents = match hover.contents {
1026+ lsp::HoverContents::Scalar(contents) => {
1027+ // markedstring(string/languagestring to be highlighted)
1028+ // TODO
1029+ unimplemented!("{:?}", contents)
1030+ }
1031+ lsp::HoverContents::Array(contents) => {
1032+ unimplemented!("{:?}", contents)
1033+ }
1034+ // TODO: render markdown
1035+ lsp::HoverContents::Markup(contents) => contents.value,
1036+ };
1037+
1038+ // skip if contents empty
1039+
1040+ // Popup: box frame + Box<Component> for internal content.
1041+ // it will use the contents.size_hint/required size to figure out sizing & positioning
1042+ // can also use render_buffer to render the content.
1043+ // render_buffer(highlights/scopes, text, surface, theme)
1044+ //
1045+ let mut popup = Popup::new(contents);
1046+
1047+ cx.callback = Some(Box::new(
1048+ move |compositor: &mut Compositor, editor: &mut Editor| {
1049+ let area = tui::layout::Rect::default(); // TODO: unused remove from cursor_position
1050+ let mut pos = compositor.cursor_position(area, editor);
1051+ pos.row += 1; // shift down by one row
1052+ popup.set_position(pos);
1053+
1054+ compositor.push(Box::new(popup));
1055+ },
1056+ ));
1057+ }
1058+}
1059+
10031060// view movements
10041061pub fn next_view(cx: &mut Context) {
10051062 cx.editor.tree.focus_next()
helix-term/src/keymap.rs+2
@@ -203,6 +203,8 @@ pub fn default() -> Keymaps {
203203
204204 // move under <space>c
205205 vec![ctrl!('c')] => commands::toggle_comments,
206+ // was K, figure out a key
207+ vec![ctrl!('k')] => commands::hover,
206208 ),
207209 Mode::Insert => hashmap!(
208210 vec![Key {
helix-term/src/ui/editor.rs+3−4
@@ -100,6 +100,7 @@ impl EditorView {
100100 let mut spans = Vec::new();
101101 let mut visual_x = 0;
102102 let mut line = 0u16;
103+ let text = view.doc.text();
103104
104105 'outer: for event in highlights {
105106 match event.unwrap() {
@@ -113,7 +114,6 @@ impl EditorView {
113114 // TODO: filter out spans out of viewport for now..
114115
115116 // TODO: do these before iterating
116− let text = view.doc.text();
117117 let start = text.byte_to_char(start);
118118 let end = text.byte_to_char(end);
119119
@@ -160,8 +160,7 @@ impl EditorView {
160160 let grapheme = Cow::from(grapheme);
161161 let width = grapheme_width(&grapheme) as u16;
162162
163− // ugh, improve with a traverse method
164− // or interleave highlight spans with selection and diagnostic spans
163+ // ugh,interleave highlight spans with diagnostic spans
165164 let is_diagnostic = view.doc.diagnostics.iter().any(|diagnostic| {
166165 diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
167166 });
@@ -191,12 +190,12 @@ impl EditorView {
191190 // render selections
192191
193192 if is_focused {
194− let text = view.doc.text().slice(..);
195193 let screen = {
196194 let start = text.line_to_char(view.first_line);
197195 let end = text.line_to_char(last_line + 1);
198196 Range::new(start, end)
199197 };
198+ let text = text.slice(..);
200199 let cursor_style = Style::default().bg(Color::Rgb(255, 255, 255));
201200
202201 // cedar
helix-term/src/ui/mod.rs+2
@@ -1,11 +1,13 @@
11mod editor;
22mod menu;
33mod picker;
4+mod popup;
45mod prompt;
56
67pub use editor::EditorView;
78pub use menu::Menu;
89pub use picker::Picker;
10+pub use popup::Popup;
911pub use prompt::{Prompt, PromptEvent};
1012
1113pub use tui::layout::Rect;
helix-term/src/ui/popup.rsnew file+95
@@ -0,0 +1,95 @@
1+use crate::compositor::{Component, Compositor, Context, EventResult};
2+use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
3+use tui::buffer::Buffer as Surface;
4+use tui::{
5+ layout::Rect,
6+ style::{Color, Style},
7+ widgets::{Block, Borders},
8+};
9+
10+use std::borrow::Cow;
11+
12+use helix_core::Position;
13+use helix_view::Editor;
14+
15+pub struct Popup {
16+ contents: String,
17+ position: Position,
18+}
19+
20+impl Popup {
21+ // TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
22+ // rendering)
23+ pub fn new(contents: String) -> Self {
24+ Self {
25+ contents,
26+ position: Position::default(),
27+ }
28+ }
29+
30+ pub fn set_position(&mut self, pos: Position) {
31+ self.position = pos;
32+ }
33+}
34+
35+impl Component for Popup {
36+ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
37+ let event = match event {
38+ Event::Key(event) => event,
39+ _ => return EventResult::Ignored,
40+ };
41+
42+ let close_fn = EventResult::Consumed(Some(Box::new(
43+ |compositor: &mut Compositor, editor: &mut Editor| {
44+ // remove the layer
45+ compositor.pop();
46+ },
47+ )));
48+
49+ match event {
50+ // esc or ctrl-c aborts the completion and closes the menu
51+ KeyEvent {
52+ code: KeyCode::Esc, ..
53+ }
54+ | KeyEvent {
55+ code: KeyCode::Char('c'),
56+ modifiers: KeyModifiers::CONTROL,
57+ } => {
58+ return close_fn;
59+ }
60+ _ => (),
61+ }
62+ // for some events, we want to process them but send ignore, specifically all input except
63+ // tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
64+ // EventResult::Consumed(None)
65+ EventResult::Consumed(None)
66+ }
67+ fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
68+ // render a box at x, y. Width equal to max width of item.
69+ const MAX: usize = 15;
70+ let rows = std::cmp::min(self.contents.lines().count(), MAX) as u16; // inefficient
71+ let area = Rect::new(self.position.col as u16, self.position.row as u16, 80, rows);
72+
73+ // clear area
74+ let background = cx.editor.theme.get("ui.popup");
75+ for y in area.top()..area.bottom() {
76+ for x in area.left()..area.right() {
77+ let cell = surface.get_mut(x, y);
78+ cell.reset();
79+ // cell.symbol.clear();
80+ cell.set_style(background);
81+ }
82+ }
83+
84+ // -- Render the contents:
85+
86+ let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
87+
88+ use tui::text::Text;
89+ use tui::widgets::{Paragraph, Widget, Wrap};
90+ let contents = Text::from(self.contents.clone());
91+ let par = Paragraph::new(contents).wrap(Wrap { trim: false });
92+
93+ par.render(area, surface);
94+ }
95+}