mirror of https://github.com/helix-editor/helix
lsp: Hover documentation draft.
parent
8289bd1cb0
commit
7162632eb7
|
@ -201,11 +201,12 @@ impl Client {
|
||||||
context_support: None, // additional context information Some(true)
|
context_support: None, // additional context information Some(true)
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
// { completion: {
|
hover: Some(lsp::HoverClientCapabilities {
|
||||||
// dynamic_registration: bool
|
// if not specified, rust-analyzer returns plaintext marked as markdown but
|
||||||
// completion_item: { snippet, documentation_format, ... }
|
// badly formatted.
|
||||||
// completion_item_kind: { }
|
content_format: Some(vec![lsp::MarkupKind::Markdown]),
|
||||||
// } }
|
..Default::default()
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -458,4 +459,51 @@ impl Client {
|
||||||
|
|
||||||
Ok(items)
|
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 once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::compositor::Compositor;
|
use crate::compositor::Compositor;
|
||||||
use crate::ui::{self, Prompt, PromptEvent};
|
use crate::ui::{self, Popup, Prompt, PromptEvent};
|
||||||
|
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::Mode,
|
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
|
// view movements
|
||||||
pub fn next_view(cx: &mut Context) {
|
pub fn next_view(cx: &mut Context) {
|
||||||
cx.editor.tree.focus_next()
|
cx.editor.tree.focus_next()
|
||||||
|
|
|
@ -203,6 +203,8 @@ pub fn default() -> Keymaps {
|
||||||
|
|
||||||
// move under <space>c
|
// move under <space>c
|
||||||
vec![ctrl!('c')] => commands::toggle_comments,
|
vec![ctrl!('c')] => commands::toggle_comments,
|
||||||
|
// was K, figure out a key
|
||||||
|
vec![ctrl!('k')] => commands::hover,
|
||||||
),
|
),
|
||||||
Mode::Insert => hashmap!(
|
Mode::Insert => hashmap!(
|
||||||
vec![Key {
|
vec![Key {
|
||||||
|
|
|
@ -100,6 +100,7 @@ impl EditorView {
|
||||||
let mut spans = Vec::new();
|
let mut spans = Vec::new();
|
||||||
let mut visual_x = 0;
|
let mut visual_x = 0;
|
||||||
let mut line = 0u16;
|
let mut line = 0u16;
|
||||||
|
let text = view.doc.text();
|
||||||
|
|
||||||
'outer: for event in highlights {
|
'outer: for event in highlights {
|
||||||
match event.unwrap() {
|
match event.unwrap() {
|
||||||
|
@ -113,7 +114,6 @@ impl EditorView {
|
||||||
// TODO: filter out spans out of viewport for now..
|
// TODO: filter out spans out of viewport for now..
|
||||||
|
|
||||||
// TODO: do these before iterating
|
// TODO: do these before iterating
|
||||||
let text = view.doc.text();
|
|
||||||
let start = text.byte_to_char(start);
|
let start = text.byte_to_char(start);
|
||||||
let end = text.byte_to_char(end);
|
let end = text.byte_to_char(end);
|
||||||
|
|
||||||
|
@ -160,8 +160,7 @@ impl EditorView {
|
||||||
let grapheme = Cow::from(grapheme);
|
let grapheme = Cow::from(grapheme);
|
||||||
let width = grapheme_width(&grapheme) as u16;
|
let width = grapheme_width(&grapheme) as u16;
|
||||||
|
|
||||||
// ugh, improve with a traverse method
|
// ugh,interleave highlight spans with diagnostic spans
|
||||||
// or interleave highlight spans with selection and diagnostic spans
|
|
||||||
let is_diagnostic = view.doc.diagnostics.iter().any(|diagnostic| {
|
let is_diagnostic = view.doc.diagnostics.iter().any(|diagnostic| {
|
||||||
diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
|
diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
|
||||||
});
|
});
|
||||||
|
@ -191,12 +190,12 @@ impl EditorView {
|
||||||
// render selections
|
// render selections
|
||||||
|
|
||||||
if is_focused {
|
if is_focused {
|
||||||
let text = view.doc.text().slice(..);
|
|
||||||
let screen = {
|
let screen = {
|
||||||
let start = text.line_to_char(view.first_line);
|
let start = text.line_to_char(view.first_line);
|
||||||
let end = text.line_to_char(last_line + 1);
|
let end = text.line_to_char(last_line + 1);
|
||||||
Range::new(start, end)
|
Range::new(start, end)
|
||||||
};
|
};
|
||||||
|
let text = text.slice(..);
|
||||||
let cursor_style = Style::default().bg(Color::Rgb(255, 255, 255));
|
let cursor_style = Style::default().bg(Color::Rgb(255, 255, 255));
|
||||||
|
|
||||||
// cedar
|
// cedar
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
mod editor;
|
mod editor;
|
||||||
mod menu;
|
mod menu;
|
||||||
mod picker;
|
mod picker;
|
||||||
|
mod popup;
|
||||||
mod prompt;
|
mod prompt;
|
||||||
|
|
||||||
pub use editor::EditorView;
|
pub use editor::EditorView;
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
pub use picker::Picker;
|
pub use picker::Picker;
|
||||||
|
pub use popup::Popup;
|
||||||
pub use prompt::{Prompt, PromptEvent};
|
pub use prompt::{Prompt, PromptEvent};
|
||||||
|
|
||||||
pub use tui::layout::Rect;
|
pub use tui::layout::Rect;
|
||||||
|
|
|
@ -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 New Issue