mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-25 02:46:17 +04:00
Feat: LSP Type Hints (#5934)
* misc: missing inline, outdated link * doc: Add new theme keys and config option to book * fix: don't panic in Tree::try_get(view_id) Necessary for later, where we could be receiving an LSP response for a closed window, in which case we don't want to crash while checking for its existence * fix: reset idle timer on all mouse events * refacto: Introduce Overlay::new and InlineAnnotation::new * refacto: extract make_job_callback from Context::callback * feat: add LSP display_inlay_hint option to config * feat: communicate inlay hints support capabilities of helix to LSP server * feat: Add function to request range of inlay hint from LSP * feat: Save inlay hints in document, per view * feat: Update inlay hints on document changes * feat: Compute inlay hints on idle timeout * nit: Add todo's about inlay hints for later * fix: compute text annotations for current view in view.rs, not document.rs * doc: Improve Document::text_annotations() description * nit: getters don't use 'get_' in front * fix: Drop inlay hints annotations on config refresh if necessary * fix: padding theming for LSP inlay hints * fix: tracking of outdated inlay hints should not be dependant on document revision (because of undos and such) * fix: follow LSP spec and don't highlight padding as virtual text * config: add some LSP inlay hint configs
This commit is contained in:
parent
3d230e701d
commit
bdcd4d9411
@ -120,9 +120,12 @@ ### `[editor.lsp]` Section
|
||||
| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
|
||||
| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |
|
||||
| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
|
||||
| `display-inlay-hints` | Display inlay hints[^2] | `false` |
|
||||
| `display-signature-help-docs` | Display docs under signature help popup | `true` |
|
||||
|
||||
[^1]: By default, a progress spinner is shown in the statusline beside the file path.
|
||||
[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix.
|
||||
Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances, please report any bugs you see so we can fix them!
|
||||
|
||||
### `[editor.cursor-shape]` Section
|
||||
|
||||
|
@ -262,58 +262,61 @@ #### Interface
|
||||
- `hover` - for hover popup UI
|
||||
|
||||
|
||||
| Key | Notes |
|
||||
| --- | --- |
|
||||
| `ui.background` | |
|
||||
| `ui.background.separator` | Picker separator below input line |
|
||||
| `ui.cursor` | |
|
||||
| `ui.cursor.normal` | |
|
||||
| `ui.cursor.insert` | |
|
||||
| `ui.cursor.select` | |
|
||||
| `ui.cursor.match` | Matching bracket etc. |
|
||||
| `ui.cursor.primary` | Cursor with primary selection |
|
||||
| `ui.cursor.primary.normal` | |
|
||||
| `ui.cursor.primary.insert` | |
|
||||
| `ui.cursor.primary.select` | |
|
||||
| `ui.gutter` | Gutter |
|
||||
| `ui.gutter.selected` | Gutter for the line the cursor is on |
|
||||
| `ui.linenr` | Line numbers |
|
||||
| `ui.linenr.selected` | Line number for the line the cursor is on |
|
||||
| `ui.statusline` | Statusline |
|
||||
| `ui.statusline.inactive` | Statusline (unfocused document) |
|
||||
| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.separator` | Separator character in statusline |
|
||||
| `ui.popup` | Documentation popups (e.g. Space + k) |
|
||||
| `ui.popup.info` | Prompt for multiple key options |
|
||||
| `ui.window` | Borderlines separating splits |
|
||||
| `ui.help` | Description box for commands |
|
||||
| `ui.text` | Command prompts, popup text, etc. |
|
||||
| `ui.text.focus` | |
|
||||
| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
|
||||
| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
|
||||
| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
|
||||
| `ui.virtual.whitespace` | Visible whitespace characters |
|
||||
| `ui.virtual.indent-guide` | Vertical indent width guides |
|
||||
| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
|
||||
| `ui.menu` | Code and command completion menus |
|
||||
| `ui.menu.selected` | Selected autocomplete item |
|
||||
| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
|
||||
| `ui.selection` | For selections in the editing area |
|
||||
| `ui.selection.primary` | |
|
||||
| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
|
||||
| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
|
||||
| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
|
||||
| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
|
||||
| `warning` | Diagnostics warning (gutter) |
|
||||
| `error` | Diagnostics error (gutter) |
|
||||
| `info` | Diagnostics info (gutter) |
|
||||
| `hint` | Diagnostics hint (gutter) |
|
||||
| `diagnostic` | Diagnostics fallback style (editing area) |
|
||||
| `diagnostic.hint` | Diagnostics hint (editing area) |
|
||||
| `diagnostic.info` | Diagnostics info (editing area) |
|
||||
| `diagnostic.warning` | Diagnostics warning (editing area) |
|
||||
| `diagnostic.error` | Diagnostics error (editing area) |
|
||||
| Key | Notes |
|
||||
| --- | --- |
|
||||
| `ui.background` | |
|
||||
| `ui.background.separator` | Picker separator below input line |
|
||||
| `ui.cursor` | |
|
||||
| `ui.cursor.normal` | |
|
||||
| `ui.cursor.insert` | |
|
||||
| `ui.cursor.select` | |
|
||||
| `ui.cursor.match` | Matching bracket etc. |
|
||||
| `ui.cursor.primary` | Cursor with primary selection |
|
||||
| `ui.cursor.primary.normal` | |
|
||||
| `ui.cursor.primary.insert` | |
|
||||
| `ui.cursor.primary.select` | |
|
||||
| `ui.gutter` | Gutter |
|
||||
| `ui.gutter.selected` | Gutter for the line the cursor is on |
|
||||
| `ui.linenr` | Line numbers |
|
||||
| `ui.linenr.selected` | Line number for the line the cursor is on |
|
||||
| `ui.statusline` | Statusline |
|
||||
| `ui.statusline.inactive` | Statusline (unfocused document) |
|
||||
| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.separator` | Separator character in statusline |
|
||||
| `ui.popup` | Documentation popups (e.g. Space + k) |
|
||||
| `ui.popup.info` | Prompt for multiple key options |
|
||||
| `ui.window` | Borderlines separating splits |
|
||||
| `ui.help` | Description box for commands |
|
||||
| `ui.text` | Command prompts, popup text, etc. |
|
||||
| `ui.text.focus` | |
|
||||
| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
|
||||
| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
|
||||
| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
|
||||
| `ui.virtual.whitespace` | Visible whitespace characters |
|
||||
| `ui.virtual.indent-guide` | Vertical indent width guides |
|
||||
| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds |
|
||||
| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (LSPs are not required to set a kind) |
|
||||
| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (LSPs are not required to set a kind) |
|
||||
| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
|
||||
| `ui.menu` | Code and command completion menus |
|
||||
| `ui.menu.selected` | Selected autocomplete item |
|
||||
| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
|
||||
| `ui.selection` | For selections in the editing area |
|
||||
| `ui.selection.primary` | |
|
||||
| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
|
||||
| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
|
||||
| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
|
||||
| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
|
||||
| `warning` | Diagnostics warning (gutter) |
|
||||
| `error` | Diagnostics error (gutter) |
|
||||
| `info` | Diagnostics info (gutter) |
|
||||
| `hint` | Diagnostics hint (gutter) |
|
||||
| `diagnostic` | Diagnostics fallback style (editing area) |
|
||||
| `diagnostic.hint` | Diagnostics hint (editing area) |
|
||||
| `diagnostic.info` | Diagnostics info (editing area) |
|
||||
| `diagnostic.warning` | Diagnostics warning (editing area) |
|
||||
| `diagnostic.error` | Diagnostics error (editing area) |
|
||||
|
||||
[editor-section]: ./configuration.md#editor-section
|
||||
|
@ -35,7 +35,7 @@ pub enum DiagnosticTag {
|
||||
Deprecated,
|
||||
}
|
||||
|
||||
/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html)
|
||||
/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.94.0/lsp_types/struct.Diagnostic.html)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Diagnostic {
|
||||
pub range: Range,
|
||||
|
@ -119,16 +119,7 @@ fn overlay() {
|
||||
"foobar",
|
||||
0,
|
||||
false,
|
||||
&[
|
||||
Overlay {
|
||||
char_idx: 0,
|
||||
grapheme: "X".into(),
|
||||
},
|
||||
Overlay {
|
||||
char_idx: 2,
|
||||
grapheme: "\t".into(),
|
||||
},
|
||||
]
|
||||
&[Overlay::new(0, "X"), Overlay::new(2, "\t")],
|
||||
),
|
||||
"Xo bar "
|
||||
);
|
||||
@ -138,18 +129,9 @@ fn overlay() {
|
||||
0,
|
||||
true,
|
||||
&[
|
||||
Overlay {
|
||||
char_idx: 2,
|
||||
grapheme: "\t".into(),
|
||||
},
|
||||
Overlay {
|
||||
char_idx: 5,
|
||||
grapheme: "\t".into(),
|
||||
},
|
||||
Overlay {
|
||||
char_idx: 16,
|
||||
grapheme: "X".into(),
|
||||
},
|
||||
Overlay::new(2, "\t"),
|
||||
Overlay::new(5, "\t"),
|
||||
Overlay::new(16, "X"),
|
||||
]
|
||||
),
|
||||
"fo f o foo \n.foo Xoo foo foo \n.foo foo foo "
|
||||
@ -170,24 +152,14 @@ fn annotate_text(text: &str, softwrap: bool, annotations: &[InlineAnnotation]) -
|
||||
#[test]
|
||||
fn annotation() {
|
||||
assert_eq!(
|
||||
annotate_text(
|
||||
"bar",
|
||||
false,
|
||||
&[InlineAnnotation {
|
||||
char_idx: 0,
|
||||
text: "foo".into(),
|
||||
}]
|
||||
),
|
||||
annotate_text("bar", false, &[InlineAnnotation::new(0, "foo")]),
|
||||
"foobar "
|
||||
);
|
||||
assert_eq!(
|
||||
annotate_text(
|
||||
&"foo ".repeat(10),
|
||||
true,
|
||||
&[InlineAnnotation {
|
||||
char_idx: 0,
|
||||
text: "foo ".into(),
|
||||
}]
|
||||
&[InlineAnnotation::new(0, "foo ")]
|
||||
),
|
||||
"foo foo foo foo \n.foo foo foo foo \n.foo foo foo "
|
||||
);
|
||||
@ -199,20 +171,8 @@ fn annotation_and_overlay() {
|
||||
"bbar".into(),
|
||||
&TextFormat::new_test(false),
|
||||
TextAnnotations::default()
|
||||
.add_inline_annotations(
|
||||
Rc::new([InlineAnnotation {
|
||||
char_idx: 0,
|
||||
text: "fooo".into(),
|
||||
}]),
|
||||
None
|
||||
)
|
||||
.add_overlay(
|
||||
Rc::new([Overlay {
|
||||
char_idx: 0,
|
||||
grapheme: "\t".into(),
|
||||
}]),
|
||||
None
|
||||
),
|
||||
.add_inline_annotations(Rc::new([InlineAnnotation::new(0, "fooo")]), None)
|
||||
.add_overlay(Rc::new([Overlay::new(0, "\t")]), None),
|
||||
0,
|
||||
)
|
||||
.0
|
||||
|
@ -15,6 +15,15 @@ pub struct InlineAnnotation {
|
||||
pub char_idx: usize,
|
||||
}
|
||||
|
||||
impl InlineAnnotation {
|
||||
pub fn new(char_idx: usize, text: impl Into<Tendril>) -> Self {
|
||||
Self {
|
||||
char_idx,
|
||||
text: text.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a **single Grapheme** that is part of the document
|
||||
/// that start at `char_idx` that will be replaced with
|
||||
/// a different `grapheme`.
|
||||
@ -33,22 +42,13 @@ pub struct InlineAnnotation {
|
||||
/// use helix_core::text_annotations::Overlay;
|
||||
///
|
||||
/// // replaces a
|
||||
/// Overlay {
|
||||
/// char_idx: 0,
|
||||
/// grapheme: "X".into(),
|
||||
/// };
|
||||
/// Overlay::new(0, "X");
|
||||
///
|
||||
/// // replaces X͎̊͢͜͝͡
|
||||
/// Overlay{
|
||||
/// char_idx: 1,
|
||||
/// grapheme: "\t".into(),
|
||||
/// };
|
||||
/// Overlay::new(1, "\t");
|
||||
///
|
||||
/// // replaces b
|
||||
/// Overlay{
|
||||
/// char_idx: 6,
|
||||
/// grapheme: "X̢̢̟͖̲͌̋̇͑͝".into(),
|
||||
/// };
|
||||
/// Overlay::new(6, "X̢̢̟͖̲͌̋̇͑͝");
|
||||
/// ```
|
||||
///
|
||||
/// The following examples are invalid uses
|
||||
@ -57,16 +57,10 @@ pub struct InlineAnnotation {
|
||||
/// use helix_core::text_annotations::Overlay;
|
||||
///
|
||||
/// // overlay is not aligned at grapheme boundary
|
||||
/// Overlay{
|
||||
/// char_idx: 3,
|
||||
/// grapheme: "x".into(),
|
||||
/// };
|
||||
/// Overlay::new(3, "x");
|
||||
///
|
||||
/// // overlay contains multiple graphemes
|
||||
/// Overlay{
|
||||
/// char_idx: 0,
|
||||
/// grapheme: "xy".into(),
|
||||
/// };
|
||||
/// Overlay::new(0, "xy");
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Overlay {
|
||||
@ -74,6 +68,15 @@ pub struct Overlay {
|
||||
pub grapheme: Tendril,
|
||||
}
|
||||
|
||||
impl Overlay {
|
||||
pub fn new(char_idx: usize, grapheme: impl Into<Tendril>) -> Self {
|
||||
Self {
|
||||
char_idx,
|
||||
grapheme: grapheme.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Line annotations allow for virtual text between normal
|
||||
/// text lines. They cause `height` empty lines to be inserted
|
||||
/// below the document line that contains `anchor_char_idx`.
|
||||
|
@ -315,6 +315,9 @@ pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
|
||||
execute_command: Some(lsp::DynamicRegistrationClientCapabilities {
|
||||
dynamic_registration: Some(false),
|
||||
}),
|
||||
inlay_hint: Some(lsp::InlayHintWorkspaceClientCapabilities {
|
||||
refresh_support: Some(false),
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
text_document: Some(lsp::TextDocumentClientCapabilities {
|
||||
@ -386,6 +389,10 @@ pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
|
||||
publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
|
||||
..Default::default()
|
||||
}),
|
||||
inlay_hint: Some(lsp::InlayHintClientCapabilities {
|
||||
dynamic_registration: Some(false),
|
||||
resolve_support: None,
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
window: Some(lsp::WindowClientCapabilities {
|
||||
@ -726,6 +733,31 @@ pub fn text_document_signature_help(
|
||||
Some(self.call::<lsp::request::SignatureHelpRequest>(params))
|
||||
}
|
||||
|
||||
pub fn text_document_range_inlay_hints(
|
||||
&self,
|
||||
text_document: lsp::TextDocumentIdentifier,
|
||||
range: lsp::Range,
|
||||
work_done_token: Option<lsp::ProgressToken>,
|
||||
) -> Option<impl Future<Output = Result<Value>>> {
|
||||
let capabilities = self.capabilities.get().unwrap();
|
||||
|
||||
match capabilities.inlay_hint_provider {
|
||||
Some(
|
||||
lsp::OneOf::Left(true)
|
||||
| lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_)),
|
||||
) => (),
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
let params = lsp::InlayHintParams {
|
||||
text_document,
|
||||
range,
|
||||
work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
|
||||
};
|
||||
|
||||
Some(self.call::<lsp::request::InlayHintRequest>(params))
|
||||
}
|
||||
|
||||
pub fn text_document_hover(
|
||||
&self,
|
||||
text_document: lsp::TextDocumentIdentifier,
|
||||
|
@ -114,17 +114,7 @@ pub fn callback<T, F>(
|
||||
T: for<'de> serde::Deserialize<'de> + Send + 'static,
|
||||
F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,
|
||||
{
|
||||
let callback = Box::pin(async move {
|
||||
let json = call.await?;
|
||||
let response = serde_json::from_value(json)?;
|
||||
let call: job::Callback = Callback::EditorCompositor(Box::new(
|
||||
move |editor: &mut Editor, compositor: &mut Compositor| {
|
||||
callback(editor, compositor, response)
|
||||
},
|
||||
));
|
||||
Ok(call)
|
||||
});
|
||||
self.jobs.callback(callback);
|
||||
self.jobs.callback(make_job_callback(call, callback));
|
||||
}
|
||||
|
||||
/// Returns 1 if no explicit count was provided
|
||||
@ -134,6 +124,27 @@ pub fn count(&self) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn make_job_callback<T, F>(
|
||||
call: impl Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
|
||||
callback: F,
|
||||
) -> std::pin::Pin<Box<impl Future<Output = Result<Callback, anyhow::Error>>>>
|
||||
where
|
||||
T: for<'de> serde::Deserialize<'de> + Send + 'static,
|
||||
F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,
|
||||
{
|
||||
Box::pin(async move {
|
||||
let json = call.await?;
|
||||
let response = serde_json::from_value(json)?;
|
||||
let call: job::Callback = Callback::EditorCompositor(Box::new(
|
||||
move |editor: &mut Editor, compositor: &mut Compositor| {
|
||||
callback(editor, compositor, response)
|
||||
},
|
||||
));
|
||||
Ok(call)
|
||||
})
|
||||
}
|
||||
|
||||
use helix_view::{align_view, Align};
|
||||
|
||||
/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
|
||||
|
@ -15,8 +15,13 @@
|
||||
|
||||
use super::{align_view, push_jump, Align, Context, Editor, Open};
|
||||
|
||||
use helix_core::{path, Selection};
|
||||
use helix_view::{document::Mode, editor::Action, theme::Style};
|
||||
use helix_core::{path, text_annotations::InlineAnnotation, Selection};
|
||||
use helix_view::{
|
||||
document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
|
||||
editor::Action,
|
||||
theme::Style,
|
||||
Document, View,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
compositor::{self, Compositor},
|
||||
@ -27,7 +32,8 @@
|
||||
};
|
||||
|
||||
use std::{
|
||||
borrow::Cow, cmp::Ordering, collections::BTreeMap, fmt::Write, path::PathBuf, sync::Arc,
|
||||
borrow::Cow, cmp::Ordering, collections::BTreeMap, fmt::Write, future::Future, path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Gets the language server that is attached to a document, and
|
||||
@ -1391,3 +1397,174 @@ pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn compute_inlay_hints_for_all_views(editor: &mut Editor, jobs: &mut crate::job::Jobs) {
|
||||
if !editor.config().lsp.display_inlay_hints {
|
||||
return;
|
||||
}
|
||||
|
||||
for (view, _) in editor.tree.views() {
|
||||
let doc = match editor.documents.get(&view.doc) {
|
||||
Some(doc) => doc,
|
||||
None => continue,
|
||||
};
|
||||
if let Some(callback) = compute_inlay_hints_for_view(view, doc) {
|
||||
jobs.callback(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_inlay_hints_for_view(
|
||||
view: &View,
|
||||
doc: &Document,
|
||||
) -> Option<std::pin::Pin<Box<impl Future<Output = Result<crate::job::Callback, anyhow::Error>>>>> {
|
||||
let view_id = view.id;
|
||||
let doc_id = view.doc;
|
||||
|
||||
let language_server = doc.language_server()?;
|
||||
|
||||
let capabilities = language_server.capabilities();
|
||||
|
||||
let (future, new_doc_inlay_hints_id) = match capabilities.inlay_hint_provider {
|
||||
Some(
|
||||
lsp::OneOf::Left(true)
|
||||
| lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_)),
|
||||
) => {
|
||||
let doc_text = doc.text();
|
||||
let len_lines = doc_text.len_lines();
|
||||
|
||||
// Compute ~3 times the current view height of inlay hints, that way some scrolling
|
||||
// will not show half the view with hints and half without while still being faster
|
||||
// than computing all the hints for the full file (which could be dozens of time
|
||||
// longer than the view is).
|
||||
let view_height = view.inner_height();
|
||||
let first_visible_line = doc_text.char_to_line(view.offset.anchor);
|
||||
let first_line = first_visible_line.saturating_sub(view_height);
|
||||
let last_line = first_visible_line
|
||||
.saturating_add(view_height.saturating_mul(2))
|
||||
.min(len_lines);
|
||||
|
||||
let new_doc_inlay_hint_id = DocumentInlayHintsId {
|
||||
first_line,
|
||||
last_line,
|
||||
};
|
||||
// Don't recompute the annotations in case nothing has changed about the view
|
||||
if !doc.inlay_hints_oudated
|
||||
&& doc
|
||||
.inlay_hints(view_id)
|
||||
.map_or(false, |dih| dih.id == new_doc_inlay_hint_id)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let doc_slice = doc_text.slice(..);
|
||||
let first_char_in_range = doc_slice.line_to_char(first_line);
|
||||
let last_char_in_range = doc_slice.line_to_char(last_line);
|
||||
|
||||
let range = helix_lsp::util::range_to_lsp_range(
|
||||
doc_text,
|
||||
helix_core::Range::new(first_char_in_range, last_char_in_range),
|
||||
language_server.offset_encoding(),
|
||||
);
|
||||
|
||||
(
|
||||
language_server.text_document_range_inlay_hints(doc.identifier(), range, None),
|
||||
new_doc_inlay_hint_id,
|
||||
)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let callback = super::make_job_callback(
|
||||
future?,
|
||||
move |editor, _compositor, response: Option<Vec<lsp::InlayHint>>| {
|
||||
// The config was modified or the window was closed while the request was in flight
|
||||
if !editor.config().lsp.display_inlay_hints || editor.tree.try_get(view_id).is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add annotations to relevant document, not the current one (it may have changed in between)
|
||||
let doc = match editor.documents.get_mut(&doc_id) {
|
||||
Some(doc) => doc,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// If we have neither hints nor an LSP, empty the inlay hints since they're now oudated
|
||||
let (mut hints, offset_encoding) = match (response, doc.language_server()) {
|
||||
(Some(h), Some(ls)) if !h.is_empty() => (h, ls.offset_encoding()),
|
||||
_ => {
|
||||
doc.set_inlay_hints(
|
||||
view_id,
|
||||
DocumentInlayHints::empty_with_id(new_doc_inlay_hints_id),
|
||||
);
|
||||
doc.inlay_hints_oudated = false;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Most language servers will already send them sorted but ensure this is the case to
|
||||
// avoid errors on our end.
|
||||
hints.sort_unstable_by_key(|inlay_hint| inlay_hint.position);
|
||||
|
||||
let mut padding_before_inlay_hints = Vec::new();
|
||||
let mut type_inlay_hints = Vec::new();
|
||||
let mut parameter_inlay_hints = Vec::new();
|
||||
let mut other_inlay_hints = Vec::new();
|
||||
let mut padding_after_inlay_hints = Vec::new();
|
||||
|
||||
let doc_text = doc.text();
|
||||
|
||||
for hint in hints {
|
||||
let char_idx =
|
||||
match helix_lsp::util::lsp_pos_to_pos(doc_text, hint.position, offset_encoding)
|
||||
{
|
||||
Some(pos) => pos,
|
||||
// Skip inlay hints that have no "real" position
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let label = match hint.label {
|
||||
lsp::InlayHintLabel::String(s) => s,
|
||||
lsp::InlayHintLabel::LabelParts(parts) => parts
|
||||
.into_iter()
|
||||
.map(|p| p.value)
|
||||
.collect::<Vec<_>>()
|
||||
.join(""),
|
||||
};
|
||||
|
||||
let inlay_hints_vec = match hint.kind {
|
||||
Some(lsp::InlayHintKind::TYPE) => &mut type_inlay_hints,
|
||||
Some(lsp::InlayHintKind::PARAMETER) => &mut parameter_inlay_hints,
|
||||
// We can't warn on unknown kind here since LSPs are free to set it or not, for
|
||||
// example Rust Analyzer does not: every kind will be `None`.
|
||||
_ => &mut other_inlay_hints,
|
||||
};
|
||||
|
||||
if let Some(true) = hint.padding_left {
|
||||
padding_before_inlay_hints.push(InlineAnnotation::new(char_idx, " "));
|
||||
}
|
||||
|
||||
inlay_hints_vec.push(InlineAnnotation::new(char_idx, label));
|
||||
|
||||
if let Some(true) = hint.padding_right {
|
||||
padding_after_inlay_hints.push(InlineAnnotation::new(char_idx, " "));
|
||||
}
|
||||
}
|
||||
|
||||
doc.set_inlay_hints(
|
||||
view_id,
|
||||
DocumentInlayHints {
|
||||
id: new_doc_inlay_hints_id,
|
||||
type_inlay_hints: type_inlay_hints.into(),
|
||||
parameter_inlay_hints: parameter_inlay_hints.into(),
|
||||
other_inlay_hints: other_inlay_hints.into(),
|
||||
padding_before_inlay_hints: padding_before_inlay_hints.into(),
|
||||
padding_after_inlay_hints: padding_after_inlay_hints.into(),
|
||||
},
|
||||
);
|
||||
doc.inlay_hints_oudated = false;
|
||||
},
|
||||
);
|
||||
|
||||
Some(callback)
|
||||
}
|
||||
|
@ -990,6 +990,8 @@ pub fn clear_completion(&mut self, editor: &mut Editor) {
|
||||
}
|
||||
|
||||
pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult {
|
||||
commands::compute_inlay_hints_for_all_views(cx.editor, cx.jobs);
|
||||
|
||||
if let Some(completion) = &mut self.completion {
|
||||
return if completion.ensure_item_resolved(cx) {
|
||||
EventResult::Consumed(None)
|
||||
@ -1014,6 +1016,10 @@ fn handle_mouse_event(
|
||||
event: &MouseEvent,
|
||||
cxt: &mut commands::Context,
|
||||
) -> EventResult {
|
||||
if event.kind != MouseEventKind::Moved {
|
||||
cxt.editor.reset_idle_timer();
|
||||
}
|
||||
|
||||
let config = cxt.editor.config();
|
||||
let MouseEvent {
|
||||
kind,
|
||||
|
@ -225,6 +225,9 @@ fn handle_idle_timeout(&mut self, cx: &mut Context) -> EventResult {
|
||||
let loader = cx.editor.syn_loader.clone();
|
||||
doc.detect_language(loader);
|
||||
}
|
||||
|
||||
// QUESTION: do we want to compute inlay hints in pickers too ? Probably not for now
|
||||
// but it could be interesting in the future
|
||||
}
|
||||
|
||||
EventResult::Consumed(None)
|
||||
@ -339,6 +342,7 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||
inner,
|
||||
doc,
|
||||
offset,
|
||||
// TODO: compute text annotations asynchronously here (like inlay hints)
|
||||
&TextAnnotations::default(),
|
||||
highlights,
|
||||
&cx.editor.theme,
|
||||
|
@ -6,7 +6,7 @@
|
||||
use helix_core::auto_pairs::AutoPairs;
|
||||
use helix_core::doc_formatter::TextFormat;
|
||||
use helix_core::syntax::Highlight;
|
||||
use helix_core::text_annotations::TextAnnotations;
|
||||
use helix_core::text_annotations::{InlineAnnotation, TextAnnotations};
|
||||
use helix_core::Range;
|
||||
use helix_vcs::{DiffHandle, DiffProviderRegistry};
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
use std::fmt::Display;
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::SystemTime;
|
||||
@ -119,6 +120,14 @@ pub struct Document {
|
||||
text: Rope,
|
||||
selections: HashMap<ViewId, Selection>,
|
||||
|
||||
/// Inlay hints annotations for the document, by view.
|
||||
///
|
||||
/// To know if they're up-to-date, check the `id` field in `DocumentInlayHints`.
|
||||
pub(crate) inlay_hints: HashMap<ViewId, DocumentInlayHints>,
|
||||
/// Set to `true` when the document is updated, reset to `false` on the next inlay hints
|
||||
/// update from the LSP
|
||||
pub inlay_hints_oudated: bool,
|
||||
|
||||
path: Option<PathBuf>,
|
||||
encoding: &'static encoding::Encoding,
|
||||
|
||||
@ -162,6 +171,73 @@ pub struct Document {
|
||||
version_control_head: Option<Arc<ArcSwap<Box<str>>>>,
|
||||
}
|
||||
|
||||
/// Inlay hints for a single `(Document, View)` combo.
|
||||
///
|
||||
/// There are `*_inlay_hints` field for each kind of hints an LSP can send since we offer the
|
||||
/// option to style theme differently in the theme according to the (currently supported) kinds
|
||||
/// (`type`, `parameter` and the rest).
|
||||
///
|
||||
/// Inlay hints are always `InlineAnnotation`s, not overlays or line-ones: LSP may choose to place
|
||||
/// them anywhere in the text and will sometime offer config options to move them where the user
|
||||
/// wants them but it shouldn't be Helix who decides that so we use the most precise positioning.
|
||||
///
|
||||
/// The padding for inlay hints needs to be stored separately for before and after (the LSP spec
|
||||
/// uses 'left' and 'right' but not all text is left to right so let's be correct) padding because
|
||||
/// the 'before' padding must be added to a layer *before* the regular inlay hints and the 'after'
|
||||
/// padding comes ... after.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DocumentInlayHints {
|
||||
/// Identifier for the inlay hints stored in this structure. To be checked to know if they have
|
||||
/// to be recomputed on idle or not.
|
||||
pub id: DocumentInlayHintsId,
|
||||
|
||||
/// Inlay hints of `TYPE` kind, if any.
|
||||
pub type_inlay_hints: Rc<[InlineAnnotation]>,
|
||||
|
||||
/// Inlay hints of `PARAMETER` kind, if any.
|
||||
pub parameter_inlay_hints: Rc<[InlineAnnotation]>,
|
||||
|
||||
/// Inlay hints that are neither `TYPE` nor `PARAMETER`.
|
||||
///
|
||||
/// LSPs are not required to associate a kind to their inlay hints, for example Rust-Analyzer
|
||||
/// currently never does (February 2023) and the LSP spec may add new kinds in the future that
|
||||
/// we want to display even if we don't have some special highlighting for them.
|
||||
pub other_inlay_hints: Rc<[InlineAnnotation]>,
|
||||
|
||||
/// Inlay hint padding. When creating the final `TextAnnotations`, the `before` padding must be
|
||||
/// added first, then the regular inlay hints, then the `after` padding.
|
||||
pub padding_before_inlay_hints: Rc<[InlineAnnotation]>,
|
||||
pub padding_after_inlay_hints: Rc<[InlineAnnotation]>,
|
||||
}
|
||||
|
||||
impl DocumentInlayHints {
|
||||
/// Generate an empty list of inlay hints with the given ID.
|
||||
pub fn empty_with_id(id: DocumentInlayHintsId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
type_inlay_hints: Rc::new([]),
|
||||
parameter_inlay_hints: Rc::new([]),
|
||||
other_inlay_hints: Rc::new([]),
|
||||
padding_before_inlay_hints: Rc::new([]),
|
||||
padding_after_inlay_hints: Rc::new([]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Associated with a [`Document`] and [`ViewId`], uniquely identifies the state of inlay hints for
|
||||
/// for that document and view: if this changed since the last save, the inlay hints for the view
|
||||
/// should be recomputed.
|
||||
///
|
||||
/// We can't store the `ViewOffset` instead of the first and last asked-for lines because if
|
||||
/// softwrapping changes, the `ViewOffset` may not change while the displayed lines will.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct DocumentInlayHintsId {
|
||||
/// First line for which the inlay hints were requested.
|
||||
pub first_line: usize,
|
||||
/// Last line for which the inlay hints were requested.
|
||||
pub last_line: usize,
|
||||
}
|
||||
|
||||
use std::{fmt, mem};
|
||||
impl fmt::Debug for Document {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@ -169,6 +245,8 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
.field("id", &self.id)
|
||||
.field("text", &self.text)
|
||||
.field("selections", &self.selections)
|
||||
.field("inlay_hints_oudated", &self.inlay_hints_oudated)
|
||||
.field("text_annotations", &self.inlay_hints)
|
||||
.field("path", &self.path)
|
||||
.field("encoding", &self.encoding)
|
||||
.field("restore_cursor", &self.restore_cursor)
|
||||
@ -187,6 +265,15 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DocumentInlayHintsId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Much more agreable to read when debugging
|
||||
f.debug_struct("DocumentInlayHintsId")
|
||||
.field("lines", &(self.first_line..self.last_line))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// The documentation and implementation of this function should be up-to-date with
|
||||
// its sibling function, `to_writer()`.
|
||||
//
|
||||
@ -389,6 +476,8 @@ pub fn from(
|
||||
encoding,
|
||||
text,
|
||||
selections: HashMap::default(),
|
||||
inlay_hints: HashMap::default(),
|
||||
inlay_hints_oudated: false,
|
||||
indent_style: DEFAULT_INDENT,
|
||||
line_ending: DEFAULT_LINE_ENDING,
|
||||
restore_cursor: false,
|
||||
@ -819,13 +908,16 @@ pub fn ensure_view_init(&mut self, view_id: ViewId) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a view's selection from this document.
|
||||
/// Remove a view's selection and inlay hints from this document.
|
||||
pub fn remove_view(&mut self, view_id: ViewId) {
|
||||
self.selections.remove(&view_id);
|
||||
self.inlay_hints.remove(&view_id);
|
||||
}
|
||||
|
||||
/// Apply a [`Transaction`] to the [`Document`] to change its text.
|
||||
fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
|
||||
use helix_core::Assoc;
|
||||
|
||||
let old_doc = self.text().clone();
|
||||
|
||||
let success = transaction.changes().apply(&mut self.text);
|
||||
@ -881,10 +973,10 @@ fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let changes = transaction.changes();
|
||||
|
||||
// map state.diagnostics over changes::map_pos too
|
||||
for diagnostic in &mut self.diagnostics {
|
||||
use helix_core::Assoc;
|
||||
let changes = transaction.changes();
|
||||
diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
|
||||
diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
|
||||
diagnostic.line = self.text.char_to_line(diagnostic.range.start);
|
||||
@ -892,13 +984,40 @@ fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
|
||||
self.diagnostics
|
||||
.sort_unstable_by_key(|diagnostic| diagnostic.range);
|
||||
|
||||
// Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
|
||||
let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| {
|
||||
if let Some(data) = Rc::get_mut(annotations) {
|
||||
for inline in data.iter_mut() {
|
||||
inline.char_idx = changes.map_pos(inline.char_idx, Assoc::After);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.inlay_hints_oudated = true;
|
||||
for text_annotation in self.inlay_hints.values_mut() {
|
||||
let DocumentInlayHints {
|
||||
id: _,
|
||||
type_inlay_hints,
|
||||
parameter_inlay_hints,
|
||||
other_inlay_hints,
|
||||
padding_before_inlay_hints,
|
||||
padding_after_inlay_hints,
|
||||
} = text_annotation;
|
||||
|
||||
apply_inlay_hint_changes(padding_before_inlay_hints);
|
||||
apply_inlay_hint_changes(type_inlay_hints);
|
||||
apply_inlay_hint_changes(parameter_inlay_hints);
|
||||
apply_inlay_hint_changes(other_inlay_hints);
|
||||
apply_inlay_hint_changes(padding_after_inlay_hints);
|
||||
}
|
||||
|
||||
// emit lsp notification
|
||||
if let Some(language_server) = self.language_server() {
|
||||
let notify = language_server.text_document_did_change(
|
||||
self.versioned_identifier(),
|
||||
&old_doc,
|
||||
self.text(),
|
||||
transaction.changes(),
|
||||
changes,
|
||||
);
|
||||
|
||||
if let Some(notify) = notify {
|
||||
@ -1217,6 +1336,7 @@ pub fn selection(&self, view_id: ViewId) -> &Selection {
|
||||
&self.selections[&view_id]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn selections(&self) -> &HashMap<ViewId, Selection> {
|
||||
&self.selections
|
||||
}
|
||||
@ -1355,9 +1475,27 @@ pub fn text_format(&self, mut viewport_width: u16, theme: Option<&Theme>) -> Tex
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the text annotations that apply to the whole document, those that do not apply to any
|
||||
/// specific view.
|
||||
pub fn text_annotations(&self, _theme: Option<&Theme>) -> TextAnnotations {
|
||||
TextAnnotations::default()
|
||||
}
|
||||
|
||||
/// Set the inlay hints for this document and `view_id`.
|
||||
pub fn set_inlay_hints(&mut self, view_id: ViewId, inlay_hints: DocumentInlayHints) {
|
||||
self.inlay_hints.insert(view_id, inlay_hints);
|
||||
}
|
||||
|
||||
/// Get the inlay hints for this document and `view_id`.
|
||||
pub fn inlay_hints(&self, view_id: ViewId) -> Option<&DocumentInlayHints> {
|
||||
self.inlay_hints.get(&view_id)
|
||||
}
|
||||
|
||||
/// Completely removes all the inlay hints saved for the document, dropping them to free memory
|
||||
/// (since it often means inlay hints have been fully deactivated).
|
||||
pub fn reset_all_inlay_hints(&mut self) {
|
||||
self.inlay_hints = Default::default();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -345,6 +345,8 @@ pub struct LspConfig {
|
||||
pub auto_signature_help: bool,
|
||||
/// Display docs under signature help popup
|
||||
pub display_signature_help_docs: bool,
|
||||
/// Display inlay hints
|
||||
pub display_inlay_hints: bool,
|
||||
}
|
||||
|
||||
impl Default for LspConfig {
|
||||
@ -354,6 +356,7 @@ fn default() -> Self {
|
||||
display_messages: false,
|
||||
auto_signature_help: true,
|
||||
display_signature_help_docs: true,
|
||||
display_inlay_hints: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1133,6 +1136,19 @@ fn launch_language_server(&mut self, doc_id: DocumentId) -> Option<()> {
|
||||
|
||||
fn _refresh(&mut self) {
|
||||
let config = self.config();
|
||||
|
||||
// Reset the inlay hints annotations *before* updating the views, that way we ensure they
|
||||
// will disappear during the `.sync_change(doc)` call below.
|
||||
//
|
||||
// We can't simply check this config when rendering because inlay hints are only parts of
|
||||
// the possible annotations, and others could still be active, so we need to selectively
|
||||
// drop the inlay hints.
|
||||
if !config.lsp.display_inlay_hints {
|
||||
for doc in self.documents_mut() {
|
||||
doc.reset_all_inlay_hints();
|
||||
}
|
||||
}
|
||||
|
||||
for (view, _) in self.tree.views_mut() {
|
||||
let doc = doc_mut!(self, &view.doc);
|
||||
view.sync_changes(doc);
|
||||
|
@ -278,16 +278,15 @@ pub fn get(&self, index: ViewId) -> &View {
|
||||
self.try_get(index).unwrap()
|
||||
}
|
||||
|
||||
/// Try to get reference to a [View] by index. Returns `None` if node content is not a [Content::View]
|
||||
/// # Panics
|
||||
/// Try to get reference to a [View] by index. Returns `None` if node content is not a [`Content::View`].
|
||||
///
|
||||
/// Panics if `index` is not in self.nodes. This can be checked with [Self::contains]
|
||||
/// Does not panic if the view does not exists anymore.
|
||||
pub fn try_get(&self, index: ViewId) -> Option<&View> {
|
||||
match &self.nodes[index] {
|
||||
Node {
|
||||
match self.nodes.get(index) {
|
||||
Some(Node {
|
||||
content: Content::View(view),
|
||||
..
|
||||
} => Some(view),
|
||||
}) => Some(view),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,21 @@
|
||||
use crate::{
|
||||
align_view,
|
||||
document::DocumentInlayHints,
|
||||
editor::{GutterConfig, GutterType},
|
||||
graphics::Rect,
|
||||
Align, Document, DocumentId, Theme, ViewId,
|
||||
};
|
||||
|
||||
use helix_core::{
|
||||
char_idx_at_visual_offset, doc_formatter::TextFormat, text_annotations::TextAnnotations,
|
||||
visual_offset_from_anchor, visual_offset_from_block, Position, RopeSlice, Selection,
|
||||
Transaction,
|
||||
char_idx_at_visual_offset, doc_formatter::TextFormat, syntax::Highlight,
|
||||
text_annotations::TextAnnotations, visual_offset_from_anchor, visual_offset_from_block,
|
||||
Position, RopeSlice, Selection, Transaction,
|
||||
};
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
const JUMP_LIST_CAPACITY: usize = 30;
|
||||
@ -402,9 +404,50 @@ pub fn screen_coords_at_pos(
|
||||
Some(pos)
|
||||
}
|
||||
|
||||
/// Get the text annotations to display in the current view for the given document and theme.
|
||||
pub fn text_annotations(&self, doc: &Document, theme: Option<&Theme>) -> TextAnnotations {
|
||||
// TODO custom annotations for custom views like side by side diffs
|
||||
doc.text_annotations(theme)
|
||||
|
||||
let mut text_annotations = doc.text_annotations(theme);
|
||||
|
||||
let DocumentInlayHints {
|
||||
id: _,
|
||||
type_inlay_hints,
|
||||
parameter_inlay_hints,
|
||||
other_inlay_hints,
|
||||
padding_before_inlay_hints,
|
||||
padding_after_inlay_hints,
|
||||
} = match doc.inlay_hints.get(&self.id) {
|
||||
Some(doc_inlay_hints) => doc_inlay_hints,
|
||||
None => return text_annotations,
|
||||
};
|
||||
|
||||
let type_style = theme
|
||||
.and_then(|t| t.find_scope_index("ui.virtual.inlay-hint.type"))
|
||||
.map(Highlight);
|
||||
let parameter_style = theme
|
||||
.and_then(|t| t.find_scope_index("ui.virtual.inlay-hint.parameter"))
|
||||
.map(Highlight);
|
||||
let other_style = theme
|
||||
.and_then(|t| t.find_scope_index("ui.virtual.inlay-hint"))
|
||||
.map(Highlight);
|
||||
|
||||
let mut add_annotations = |annotations: &Rc<[_]>, style| {
|
||||
if !annotations.is_empty() {
|
||||
text_annotations.add_inline_annotations(Rc::clone(annotations), style);
|
||||
}
|
||||
};
|
||||
|
||||
// Overlapping annotations are ignored apart from the first so the order here is not random:
|
||||
// types -> parameters -> others should hopefully be the "correct" order for most use cases,
|
||||
// with the padding coming before and after as expected.
|
||||
add_annotations(padding_before_inlay_hints, None);
|
||||
add_annotations(type_inlay_hints, type_style);
|
||||
add_annotations(parameter_inlay_hints, parameter_style);
|
||||
add_annotations(other_inlay_hints, other_style);
|
||||
add_annotations(padding_after_inlay_hints, None);
|
||||
|
||||
text_annotations
|
||||
}
|
||||
|
||||
pub fn text_pos_at_screen_coords(
|
||||
|
@ -19,6 +19,14 @@ indent = { tab-width = 4, unit = " " }
|
||||
'"' = '"'
|
||||
'`' = '`'
|
||||
|
||||
[language.config]
|
||||
inlayHints.bindingModeHints.enable = false
|
||||
inlayHints.closingBraceHints.minLines = 10
|
||||
inlayHints.closureReturnTypeHints.enable = "with_block"
|
||||
inlayHints.discriminantHints.enable = "fieldless"
|
||||
inlayHints.lifetimeElisionHints.enable = "skip_trivial"
|
||||
inlayHints.typeHints.hideClosureInitialization = false
|
||||
|
||||
[language.debugger]
|
||||
name = "lldb-vscode"
|
||||
transport = "stdio"
|
||||
@ -291,6 +299,14 @@ language-server = { command = "gopls" }
|
||||
# TODO: gopls needs utf-8 offsets?
|
||||
indent = { tab-width = 4, unit = "\t" }
|
||||
|
||||
[language.config.hints]
|
||||
assignVariableTypes = true
|
||||
compositeLiteralFields = true
|
||||
constantValues = true
|
||||
functionTypeParameters = true
|
||||
parameterNames = true
|
||||
rangeVariableTypes = true
|
||||
|
||||
[language.debugger]
|
||||
name = "go"
|
||||
transport = "tcp"
|
||||
@ -382,6 +398,18 @@ comment-token = "//"
|
||||
language-server = { command = "typescript-language-server", args = ["--stdio"], language-id = "javascript" }
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[language.config]
|
||||
hostInfo = "helix"
|
||||
|
||||
[language.config.javascript.inlayHints]
|
||||
includeInlayEnumMemberValueHints = true
|
||||
includeInlayFunctionLikeReturnTypeHints = true
|
||||
includeInlayFunctionParameterTypeHints = true
|
||||
includeInlayParameterNameHints = "all"
|
||||
includeInlayParameterNameHintsWhenArgumentMatchesName = true
|
||||
includeInlayPropertyDeclarationTypeHints = true
|
||||
includeInlayVariableTypeHints = true
|
||||
|
||||
[language.debugger]
|
||||
name = "node-debug2"
|
||||
transport = "stdio"
|
||||
@ -409,6 +437,18 @@ language-server = { command = "typescript-language-server", args = ["--stdio"],
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
grammar = "javascript"
|
||||
|
||||
[language.config]
|
||||
hostInfo = "helix"
|
||||
|
||||
[language.config.javascript.inlayHints]
|
||||
includeInlayEnumMemberValueHints = true
|
||||
includeInlayFunctionLikeReturnTypeHints = true
|
||||
includeInlayFunctionParameterTypeHints = true
|
||||
includeInlayParameterNameHints = "all"
|
||||
includeInlayParameterNameHintsWhenArgumentMatchesName = true
|
||||
includeInlayPropertyDeclarationTypeHints = true
|
||||
includeInlayVariableTypeHints = true
|
||||
|
||||
[[language]]
|
||||
name = "typescript"
|
||||
scope = "source.ts"
|
||||
@ -420,6 +460,18 @@ roots = []
|
||||
language-server = { command = "typescript-language-server", args = ["--stdio"], language-id = "typescript"}
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[language.config]
|
||||
hostInfo = "helix"
|
||||
|
||||
[language.config.typescript.inlayHints]
|
||||
includeInlayEnumMemberValueHints = true
|
||||
includeInlayFunctionLikeReturnTypeHints = true
|
||||
includeInlayFunctionParameterTypeHints = true
|
||||
includeInlayParameterNameHints = "all"
|
||||
includeInlayParameterNameHintsWhenArgumentMatchesName = true
|
||||
includeInlayPropertyDeclarationTypeHints = true
|
||||
includeInlayVariableTypeHints = true
|
||||
|
||||
[[grammar]]
|
||||
name = "typescript"
|
||||
source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "6aac031ad88dd6317f02ac0bb27d099a553a7d8c", subpath = "typescript" }
|
||||
@ -434,6 +486,18 @@ roots = []
|
||||
language-server = { command = "typescript-language-server", args = ["--stdio"], language-id = "typescriptreact" }
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[language.config]
|
||||
hostInfo = "helix"
|
||||
|
||||
[language.config.typescript.inlayHints]
|
||||
includeInlayEnumMemberValueHints = true
|
||||
includeInlayFunctionLikeReturnTypeHints = true
|
||||
includeInlayFunctionParameterTypeHints = true
|
||||
includeInlayParameterNameHints = "all"
|
||||
includeInlayParameterNameHintsWhenArgumentMatchesName = true
|
||||
includeInlayPropertyDeclarationTypeHints = true
|
||||
includeInlayVariableTypeHints = true
|
||||
|
||||
[[grammar]]
|
||||
name = "tsx"
|
||||
source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "6aac031ad88dd6317f02ac0bb27d099a553a7d8c", subpath = "tsx" }
|
||||
@ -740,6 +804,14 @@ comment-token = "--"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
language-server = { command = "lua-language-server", args = [] }
|
||||
|
||||
[language.config.Lua.hint]
|
||||
enable = true
|
||||
arrayIndex = "Enable"
|
||||
setType = true
|
||||
paramName = "All"
|
||||
paramType = true
|
||||
await = true
|
||||
|
||||
[[grammar]]
|
||||
name = "lua"
|
||||
source = { git = "https://github.com/MunifTanjim/tree-sitter-lua", rev = "887dfd4e83c469300c279314ff1619b1d0b85b91" }
|
||||
|
Loading…
Reference in New Issue
Block a user