lsp: Hover documentation draft.

This commit is contained in:
Blaž Hrastnik 2021-02-25 18:07:47 +09:00
parent 8289bd1cb0
commit 7162632eb7
6 changed files with 213 additions and 10 deletions

View file

@ -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)
}
}

View file

@ -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()

View file

@ -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 {

View file

@ -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

View file

@ -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;

View 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);
}
}