on master help sign in
Feat: LSP Type Hints (#5934)
bdcd4d9Poliorcetics
book/src/configuration.md+3
@@ -120,9 +120,12 @@ The following statusline elements can be configured:
120| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
120| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
121| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |
121| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |
122| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
122| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
123+| `display-inlay-hints` | Display inlay hints[^2] | `false` |
123| `display-signature-help-docs` | Display docs under signature help popup | `true` |
124| `display-signature-help-docs` | Display docs under signature help popup | `true` |
124
125
125[^1]: By default, a progress spinner is shown in the statusline beside the file path.
126[^1]: By default, a progress spinner is shown in the statusline beside the file path.
127+[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix.
128+ 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!
126
129
127### `[editor.cursor-shape]` Section
130### `[editor.cursor-shape]` Section
128
131
book/src/themes.md+56−53
@@ -262,58 +262,61 @@ These scopes are used for theming the editor interface:
262 - `hover` - for hover popup UI
262 - `hover` - for hover popup UI
263
263
264
264
265−| Key | Notes |
265+| Key | Notes |
266−| --- | --- |
266+| --- | --- |
267−| `ui.background` | |
267+| `ui.background` | |
268−| `ui.background.separator` | Picker separator below input line |
268+| `ui.background.separator` | Picker separator below input line |
269−| `ui.cursor` | |
269+| `ui.cursor` | |
270−| `ui.cursor.normal` | |
270+| `ui.cursor.normal` | |
271−| `ui.cursor.insert` | |
271+| `ui.cursor.insert` | |
272−| `ui.cursor.select` | |
272+| `ui.cursor.select` | |
273−| `ui.cursor.match` | Matching bracket etc. |
273+| `ui.cursor.match` | Matching bracket etc. |
274−| `ui.cursor.primary` | Cursor with primary selection |
274+| `ui.cursor.primary` | Cursor with primary selection |
275−| `ui.cursor.primary.normal` | |
275+| `ui.cursor.primary.normal` | |
276−| `ui.cursor.primary.insert` | |
276+| `ui.cursor.primary.insert` | |
277−| `ui.cursor.primary.select` | |
277+| `ui.cursor.primary.select` | |
278−| `ui.gutter` | Gutter |
278+| `ui.gutter` | Gutter |
279−| `ui.gutter.selected` | Gutter for the line the cursor is on |
279+| `ui.gutter.selected` | Gutter for the line the cursor is on |
280−| `ui.linenr` | Line numbers |
280+| `ui.linenr` | Line numbers |
281−| `ui.linenr.selected` | Line number for the line the cursor is on |
281+| `ui.linenr.selected` | Line number for the line the cursor is on |
282−| `ui.statusline` | Statusline |
282+| `ui.statusline` | Statusline |
283−| `ui.statusline.inactive` | Statusline (unfocused document) |
283+| `ui.statusline.inactive` | Statusline (unfocused document) |
284−| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
284+| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
285−| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
285+| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
286−| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
286+| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
287−| `ui.statusline.separator` | Separator character in statusline |
287+| `ui.statusline.separator` | Separator character in statusline |
288−| `ui.popup` | Documentation popups (e.g. Space + k) |
288+| `ui.popup` | Documentation popups (e.g. Space + k) |
289−| `ui.popup.info` | Prompt for multiple key options |
289+| `ui.popup.info` | Prompt for multiple key options |
290−| `ui.window` | Borderlines separating splits |
290+| `ui.window` | Borderlines separating splits |
291−| `ui.help` | Description box for commands |
291+| `ui.help` | Description box for commands |
292−| `ui.text` | Command prompts, popup text, etc. |
292+| `ui.text` | Command prompts, popup text, etc. |
293−| `ui.text.focus` | |
293+| `ui.text.focus` | |
294−| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
294+| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
295−| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
295+| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
296−| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
296+| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
297−| `ui.virtual.whitespace` | Visible whitespace characters |
297+| `ui.virtual.whitespace` | Visible whitespace characters |
298−| `ui.virtual.indent-guide` | Vertical indent width guides |
298+| `ui.virtual.indent-guide` | Vertical indent width guides |
299+| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds |
300+| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (LSPs are not required to set a kind) |
301+| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (LSPs are not required to set a kind) |
299−| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
302+| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
300−| `ui.menu` | Code and command completion menus |
303+| `ui.menu` | Code and command completion menus |
301−| `ui.menu.selected` | Selected autocomplete item |
304+| `ui.menu.selected` | Selected autocomplete item |
302−| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
305+| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
303−| `ui.selection` | For selections in the editing area |
306+| `ui.selection` | For selections in the editing area |
304−| `ui.selection.primary` | |
307+| `ui.selection.primary` | |
305−| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
308+| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
306−| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
309+| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
307−| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
310+| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
308−| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
311+| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
309−| `warning` | Diagnostics warning (gutter) |
312+| `warning` | Diagnostics warning (gutter) |
310−| `error` | Diagnostics error (gutter) |
313+| `error` | Diagnostics error (gutter) |
311−| `info` | Diagnostics info (gutter) |
314+| `info` | Diagnostics info (gutter) |
312−| `hint` | Diagnostics hint (gutter) |
315+| `hint` | Diagnostics hint (gutter) |
313−| `diagnostic` | Diagnostics fallback style (editing area) |
316+| `diagnostic` | Diagnostics fallback style (editing area) |
314−| `diagnostic.hint` | Diagnostics hint (editing area) |
317+| `diagnostic.hint` | Diagnostics hint (editing area) |
315−| `diagnostic.info` | Diagnostics info (editing area) |
318+| `diagnostic.info` | Diagnostics info (editing area) |
316−| `diagnostic.warning` | Diagnostics warning (editing area) |
319+| `diagnostic.warning` | Diagnostics warning (editing area) |
317−| `diagnostic.error` | Diagnostics error (editing area) |
320+| `diagnostic.error` | Diagnostics error (editing area) |
318
321
319[editor-section]: ./configuration.md#editor-section
322[editor-section]: ./configuration.md#editor-section
helix-core/src/diagnostic.rs+1−1
@@ -35,7 +35,7 @@ pub enum DiagnosticTag {
35 Deprecated,
35 Deprecated,
36}
36}
37
37
38−/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html)
38+/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.94.0/lsp_types/struct.Diagnostic.html)
39#[derive(Debug, Clone)]
39#[derive(Debug, Clone)]
40pub struct Diagnostic {
40pub struct Diagnostic {
41 pub range: Range,
41 pub range: Range,
helix-core/src/doc_formatter/test.rs+8−48
@@ -119,16 +119,7 @@ fn overlay() {
119 "foobar",
119 "foobar",
120 0,
120 0,
121 false,
121 false,
122− &[
123− Overlay {
122+ &[Overlay::new(0, "X"), Overlay::new(2, "\t")],
124− char_idx: 0,
125− grapheme: "X".into(),
126− },
127− Overlay {
128− char_idx: 2,
129− grapheme: "\t".into(),
130− },
131− ]
132 ),
123 ),
133 "Xo bar "
124 "Xo bar "
134 );
125 );
@@ -138,18 +129,9 @@ fn overlay() {
138 0,
129 0,
139 true,
130 true,
140 &[
131 &[
141− Overlay {
132+ Overlay::new(2, "\t"),
142− char_idx: 2,
143− grapheme: "\t".into(),
144− },
145− Overlay {
133+ Overlay::new(5, "\t"),
146− char_idx: 5,
147− grapheme: "\t".into(),
148− },
149− Overlay {
134+ Overlay::new(16, "X"),
150− char_idx: 16,
151− grapheme: "X".into(),
152− },
153 ]
135 ]
154 ),
136 ),
155 "fo f o foo \n.foo Xoo foo foo \n.foo foo foo "
137 "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]) -
170#[test]
152#[test]
171fn annotation() {
153fn annotation() {
172 assert_eq!(
154 assert_eq!(
173− annotate_text(
174− "bar",
175− false,
176− &[InlineAnnotation {
155+ annotate_text("bar", false, &[InlineAnnotation::new(0, "foo")]),
177− char_idx: 0,
178− text: "foo".into(),
179− }]
180− ),
181 "foobar "
156 "foobar "
182 );
157 );
183 assert_eq!(
158 assert_eq!(
184 annotate_text(
159 annotate_text(
185 &"foo ".repeat(10),
160 &"foo ".repeat(10),
186 true,
161 true,
187− &[InlineAnnotation {
162+ &[InlineAnnotation::new(0, "foo ")]
188− char_idx: 0,
189− text: "foo ".into(),
190− }]
191 ),
163 ),
192 "foo foo foo foo \n.foo foo foo foo \n.foo foo foo "
164 "foo foo foo foo \n.foo foo foo foo \n.foo foo foo "
193 );
165 );
@@ -199,20 +171,8 @@ fn annotation_and_overlay() {
199 "bbar".into(),
171 "bbar".into(),
200 &TextFormat::new_test(false),
172 &TextFormat::new_test(false),
201 TextAnnotations::default()
173 TextAnnotations::default()
202− .add_inline_annotations(
174+ .add_inline_annotations(Rc::new([InlineAnnotation::new(0, "fooo")]), None)
203− Rc::new([InlineAnnotation {
204− char_idx: 0,
205− text: "fooo".into(),
206− }]),
207− None
208− )
209− .add_overlay(
210− Rc::new([Overlay {
175+ .add_overlay(Rc::new([Overlay::new(0, "\t")]), None),
211− char_idx: 0,
212− grapheme: "\t".into(),
213− }]),
214− None
215− ),
216 0,
176 0,
217 )
177 )
218 .0
178 .0
helix-core/src/text_annotations.rs+23−20
@@ -15,6 +15,15 @@ pub struct InlineAnnotation {
15 pub char_idx: usize,
15 pub char_idx: usize,
16}
16}
17
17
18+impl InlineAnnotation {
19+ pub fn new(char_idx: usize, text: impl Into<Tendril>) -> Self {
20+ Self {
21+ char_idx,
22+ text: text.into(),
23+ }
24+ }
25+}
26+
18/// Represents a **single Grapheme** that is part of the document
27/// Represents a **single Grapheme** that is part of the document
19/// that start at `char_idx` that will be replaced with
28/// that start at `char_idx` that will be replaced with
20/// a different `grapheme`.
29/// a different `grapheme`.
@@ -33,22 +42,13 @@ pub struct InlineAnnotation {
33/// use helix_core::text_annotations::Overlay;
42/// use helix_core::text_annotations::Overlay;
34///
43///
35/// // replaces a
44/// // replaces a
36−/// Overlay {
45+/// Overlay::new(0, "X");
37−/// char_idx: 0,
38−/// grapheme: "X".into(),
39−/// };
40///
46///
41/// // replaces X͎̊͢͜͝͡
47/// // replaces X͎̊͢͜͝͡
42−/// Overlay{
48+/// Overlay::new(1, "\t");
43−/// char_idx: 1,
44−/// grapheme: "\t".into(),
45−/// };
46///
49///
47/// // replaces b
50/// // replaces b
48−/// Overlay{
51+/// Overlay::new(6, "X̢̢̟͖̲͌̋̇͑͝");
49−/// char_idx: 6,
50−/// grapheme: "X̢̢̟͖̲͌̋̇͑͝".into(),
51−/// };
52/// ```
52/// ```
53///
53///
54/// The following examples are invalid uses
54/// The following examples are invalid uses
@@ -57,16 +57,10 @@ pub struct InlineAnnotation {
57/// use helix_core::text_annotations::Overlay;
57/// use helix_core::text_annotations::Overlay;
58///
58///
59/// // overlay is not aligned at grapheme boundary
59/// // overlay is not aligned at grapheme boundary
60−/// Overlay{
60+/// Overlay::new(3, "x");
61−/// char_idx: 3,
62−/// grapheme: "x".into(),
63−/// };
64///
61///
65/// // overlay contains multiple graphemes
62/// // overlay contains multiple graphemes
66−/// Overlay{
63+/// Overlay::new(0, "xy");
67−/// char_idx: 0,
68−/// grapheme: "xy".into(),
69−/// };
70/// ```
64/// ```
71#[derive(Debug, Clone)]
65#[derive(Debug, Clone)]
72pub struct Overlay {
66pub struct Overlay {
@@ -74,6 +68,15 @@ pub struct Overlay {
74 pub grapheme: Tendril,
68 pub grapheme: Tendril,
75}
69}
76
70
71+impl Overlay {
72+ pub fn new(char_idx: usize, grapheme: impl Into<Tendril>) -> Self {
73+ Self {
74+ char_idx,
75+ grapheme: grapheme.into(),
76+ }
77+ }
78+}
79+
77/// Line annotations allow for virtual text between normal
80/// Line annotations allow for virtual text between normal
78/// text lines. They cause `height` empty lines to be inserted
81/// text lines. They cause `height` empty lines to be inserted
79/// below the document line that contains `anchor_char_idx`.
82/// below the document line that contains `anchor_char_idx`.
helix-lsp/src/client.rs+32
@@ -315,6 +315,9 @@ impl Client {
315 execute_command: Some(lsp::DynamicRegistrationClientCapabilities {
315 execute_command: Some(lsp::DynamicRegistrationClientCapabilities {
316 dynamic_registration: Some(false),
316 dynamic_registration: Some(false),
317 }),
317 }),
318+ inlay_hint: Some(lsp::InlayHintWorkspaceClientCapabilities {
319+ refresh_support: Some(false),
320+ }),
318 ..Default::default()
321 ..Default::default()
319 }),
322 }),
320 text_document: Some(lsp::TextDocumentClientCapabilities {
323 text_document: Some(lsp::TextDocumentClientCapabilities {
@@ -386,6 +389,10 @@ impl Client {
386 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
389 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
387 ..Default::default()
390 ..Default::default()
388 }),
391 }),
392+ inlay_hint: Some(lsp::InlayHintClientCapabilities {
393+ dynamic_registration: Some(false),
394+ resolve_support: None,
395+ }),
389 ..Default::default()
396 ..Default::default()
390 }),
397 }),
391 window: Some(lsp::WindowClientCapabilities {
398 window: Some(lsp::WindowClientCapabilities {
@@ -726,6 +733,31 @@ impl Client {
726 Some(self.call::<lsp::request::SignatureHelpRequest>(params))
733 Some(self.call::<lsp::request::SignatureHelpRequest>(params))
727 }
734 }
728
735
736+ pub fn text_document_range_inlay_hints(
737+ &self,
738+ text_document: lsp::TextDocumentIdentifier,
739+ range: lsp::Range,
740+ work_done_token: Option<lsp::ProgressToken>,
741+ ) -> Option<impl Future<Output = Result<Value>>> {
742+ let capabilities = self.capabilities.get().unwrap();
743+
744+ match capabilities.inlay_hint_provider {
745+ Some(
746+ lsp::OneOf::Left(true)
747+ | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_)),
748+ ) => (),
749+ _ => return None,
750+ }
751+
752+ let params = lsp::InlayHintParams {
753+ text_document,
754+ range,
755+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
756+ };
757+
758+ Some(self.call::<lsp::request::InlayHintRequest>(params))
759+ }
760+
729 pub fn text_document_hover(
761 pub fn text_document_hover(
730 &self,
762 &self,
731 text_document: lsp::TextDocumentIdentifier,
763 text_document: lsp::TextDocumentIdentifier,
helix-term/src/commands.rs+22−11
@@ -114,17 +114,7 @@ impl<'a> Context<'a> {
114 T: for<'de> serde::Deserialize<'de> + Send + 'static,
114 T: for<'de> serde::Deserialize<'de> + Send + 'static,
115 F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,
115 F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,
116 {
116 {
117− let callback = Box::pin(async move {
118− let json = call.await?;
119− let response = serde_json::from_value(json)?;
120− let call: job::Callback = Callback::EditorCompositor(Box::new(
121− move |editor: &mut Editor, compositor: &mut Compositor| {
122− callback(editor, compositor, response)
123− },
124− ));
125− Ok(call)
126− });
127− self.jobs.callback(callback);
117+ self.jobs.callback(make_job_callback(call, callback));
128 }
118 }
129
119
130 /// Returns 1 if no explicit count was provided
120 /// Returns 1 if no explicit count was provided
@@ -134,6 +124,27 @@ impl<'a> Context<'a> {
134 }
124 }
135}
125}
136
126
127+#[inline]
128+fn make_job_callback<T, F>(
129+ call: impl Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
130+ callback: F,
131+) -> std::pin::Pin<Box<impl Future<Output = Result<Callback, anyhow::Error>>>>
132+where
133+ T: for<'de> serde::Deserialize<'de> + Send + 'static,
134+ F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,
135+{
136+ Box::pin(async move {
137+ let json = call.await?;
138+ let response = serde_json::from_value(json)?;
139+ let call: job::Callback = Callback::EditorCompositor(Box::new(
140+ move |editor: &mut Editor, compositor: &mut Compositor| {
141+ callback(editor, compositor, response)
142+ },
143+ ));
144+ Ok(call)
145+ })
146+}
147+
137use helix_view::{align_view, Align};
148use helix_view::{align_view, Align};
138
149
139/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
150/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
helix-term/src/commands/lsp.rs+180−3
@@ -15,8 +15,13 @@ use tui::{
15
15
16use super::{align_view, push_jump, Align, Context, Editor, Open};
16use super::{align_view, push_jump, Align, Context, Editor, Open};
17
17
18−use helix_core::{path, Selection};
18+use helix_core::{path, text_annotations::InlineAnnotation, Selection};
19−use helix_view::{document::Mode, editor::Action, theme::Style};
19+use helix_view::{
20+ document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
21+ editor::Action,
22+ theme::Style,
23+ Document, View,
24+};
20
25
21use crate::{
26use crate::{
22 compositor::{self, Compositor},
27 compositor::{self, Compositor},
@@ -27,7 +32,8 @@ use crate::{
27};
32};
28
33
29use std::{
34use std::{
30− borrow::Cow, cmp::Ordering, collections::BTreeMap, fmt::Write, path::PathBuf, sync::Arc,
35+ borrow::Cow, cmp::Ordering, collections::BTreeMap, fmt::Write, future::Future, path::PathBuf,
36+ sync::Arc,
31};
37};
32
38
33/// Gets the language server that is attached to a document, and
39/// 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) {
1391 },
1397 },
1392 );
1398 );
1393}
1399}
1400+
1401+pub fn compute_inlay_hints_for_all_views(editor: &mut Editor, jobs: &mut crate::job::Jobs) {
1402+ if !editor.config().lsp.display_inlay_hints {
1403+ return;
1404+ }
1405+
1406+ for (view, _) in editor.tree.views() {
1407+ let doc = match editor.documents.get(&view.doc) {
1408+ Some(doc) => doc,
1409+ None => continue,
1410+ };
1411+ if let Some(callback) = compute_inlay_hints_for_view(view, doc) {
1412+ jobs.callback(callback);
1413+ }
1414+ }
1415+}
1416+
1417+fn compute_inlay_hints_for_view(
1418+ view: &View,
1419+ doc: &Document,
1420+) -> Option<std::pin::Pin<Box<impl Future<Output = Result<crate::job::Callback, anyhow::Error>>>>> {
1421+ let view_id = view.id;
1422+ let doc_id = view.doc;
1423+
1424+ let language_server = doc.language_server()?;
1425+
1426+ let capabilities = language_server.capabilities();
1427+
1428+ let (future, new_doc_inlay_hints_id) = match capabilities.inlay_hint_provider {
1429+ Some(
1430+ lsp::OneOf::Left(true)
1431+ | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_)),
1432+ ) => {
1433+ let doc_text = doc.text();
1434+ let len_lines = doc_text.len_lines();
1435+
1436+ // Compute ~3 times the current view height of inlay hints, that way some scrolling
1437+ // will not show half the view with hints and half without while still being faster
1438+ // than computing all the hints for the full file (which could be dozens of time
1439+ // longer than the view is).
1440+ let view_height = view.inner_height();
1441+ let first_visible_line = doc_text.char_to_line(view.offset.anchor);
1442+ let first_line = first_visible_line.saturating_sub(view_height);
1443+ let last_line = first_visible_line
1444+ .saturating_add(view_height.saturating_mul(2))
1445+ .min(len_lines);
1446+
1447+ let new_doc_inlay_hint_id = DocumentInlayHintsId {
1448+ first_line,
1449+ last_line,
1450+ };
1451+ // Don't recompute the annotations in case nothing has changed about the view
1452+ if !doc.inlay_hints_oudated
1453+ && doc
1454+ .inlay_hints(view_id)
1455+ .map_or(false, |dih| dih.id == new_doc_inlay_hint_id)
1456+ {
1457+ return None;
1458+ }
1459+
1460+ let doc_slice = doc_text.slice(..);
1461+ let first_char_in_range = doc_slice.line_to_char(first_line);
1462+ let last_char_in_range = doc_slice.line_to_char(last_line);
1463+
1464+ let range = helix_lsp::util::range_to_lsp_range(
1465+ doc_text,
1466+ helix_core::Range::new(first_char_in_range, last_char_in_range),
1467+ language_server.offset_encoding(),
1468+ );
1469+
1470+ (
1471+ language_server.text_document_range_inlay_hints(doc.identifier(), range, None),
1472+ new_doc_inlay_hint_id,
1473+ )
1474+ }
1475+ _ => return None,
1476+ };
1477+
1478+ let callback = super::make_job_callback(
1479+ future?,
1480+ move |editor, _compositor, response: Option<Vec<lsp::InlayHint>>| {
1481+ // The config was modified or the window was closed while the request was in flight
1482+ if !editor.config().lsp.display_inlay_hints || editor.tree.try_get(view_id).is_none() {
1483+ return;
1484+ }
1485+
1486+ // Add annotations to relevant document, not the current one (it may have changed in between)
1487+ let doc = match editor.documents.get_mut(&doc_id) {
1488+ Some(doc) => doc,
1489+ None => return,
1490+ };
1491+
1492+ // If we have neither hints nor an LSP, empty the inlay hints since they're now oudated
1493+ let (mut hints, offset_encoding) = match (response, doc.language_server()) {
1494+ (Some(h), Some(ls)) if !h.is_empty() => (h, ls.offset_encoding()),
1495+ _ => {
1496+ doc.set_inlay_hints(
1497+ view_id,
1498+ DocumentInlayHints::empty_with_id(new_doc_inlay_hints_id),
1499+ );
1500+ doc.inlay_hints_oudated = false;
1501+ return;
1502+ }
1503+ };
1504+
1505+ // Most language servers will already send them sorted but ensure this is the case to
1506+ // avoid errors on our end.
1507+ hints.sort_unstable_by_key(|inlay_hint| inlay_hint.position);
1508+
1509+ let mut padding_before_inlay_hints = Vec::new();
1510+ let mut type_inlay_hints = Vec::new();
1511+ let mut parameter_inlay_hints = Vec::new();
1512+ let mut other_inlay_hints = Vec::new();
1513+ let mut padding_after_inlay_hints = Vec::new();
1514+
1515+ let doc_text = doc.text();
1516+
1517+ for hint in hints {
1518+ let char_idx =
1519+ match helix_lsp::util::lsp_pos_to_pos(doc_text, hint.position, offset_encoding)
1520+ {
1521+ Some(pos) => pos,
1522+ // Skip inlay hints that have no "real" position
1523+ None => continue,
1524+ };
1525+
1526+ let label = match hint.label {
1527+ lsp::InlayHintLabel::String(s) => s,
1528+ lsp::InlayHintLabel::LabelParts(parts) => parts
1529+ .into_iter()
1530+ .map(|p| p.value)
1531+ .collect::<Vec<_>>()
1532+ .join(""),
1533+ };
1534+
1535+ let inlay_hints_vec = match hint.kind {
1536+ Some(lsp::InlayHintKind::TYPE) => &mut type_inlay_hints,
1537+ Some(lsp::InlayHintKind::PARAMETER) => &mut parameter_inlay_hints,
1538+ // We can't warn on unknown kind here since LSPs are free to set it or not, for
1539+ // example Rust Analyzer does not: every kind will be `None`.
1540+ _ => &mut other_inlay_hints,
1541+ };
1542+
1543+ if let Some(true) = hint.padding_left {
1544+ padding_before_inlay_hints.push(InlineAnnotation::new(char_idx, " "));
1545+ }
1546+
1547+ inlay_hints_vec.push(InlineAnnotation::new(char_idx, label));
1548+
1549+ if let Some(true) = hint.padding_right {
1550+ padding_after_inlay_hints.push(InlineAnnotation::new(char_idx, " "));
1551+ }
1552+ }
1553+
1554+ doc.set_inlay_hints(
1555+ view_id,
1556+ DocumentInlayHints {
1557+ id: new_doc_inlay_hints_id,
1558+ type_inlay_hints: type_inlay_hints.into(),
1559+ parameter_inlay_hints: parameter_inlay_hints.into(),
1560+ other_inlay_hints: other_inlay_hints.into(),
1561+ padding_before_inlay_hints: padding_before_inlay_hints.into(),
1562+ padding_after_inlay_hints: padding_after_inlay_hints.into(),
1563+ },
1564+ );
1565+ doc.inlay_hints_oudated = false;
1566+ },
1567+ );
1568+
1569+ Some(callback)
1570+}
helix-term/src/ui/editor.rs+6
@@ -990,6 +990,8 @@ impl EditorView {
990 }
990 }
991
991
992 pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult {
992 pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult {
993+ commands::compute_inlay_hints_for_all_views(cx.editor, cx.jobs);
994+
993 if let Some(completion) = &mut self.completion {
995 if let Some(completion) = &mut self.completion {
994 return if completion.ensure_item_resolved(cx) {
996 return if completion.ensure_item_resolved(cx) {
995 EventResult::Consumed(None)
997 EventResult::Consumed(None)
@@ -1014,6 +1016,10 @@ impl EditorView {
1014 event: &MouseEvent,
1016 event: &MouseEvent,
1015 cxt: &mut commands::Context,
1017 cxt: &mut commands::Context,
1016 ) -> EventResult {
1018 ) -> EventResult {
1019+ if event.kind != MouseEventKind::Moved {
1020+ cxt.editor.reset_idle_timer();
1021+ }
1022+
1017 let config = cxt.editor.config();
1023 let config = cxt.editor.config();
1018 let MouseEvent {
1024 let MouseEvent {
1019 kind,
1025 kind,
helix-term/src/ui/picker.rs+4
@@ -225,6 +225,9 @@ impl<T: Item> FilePicker<T> {
225 let loader = cx.editor.syn_loader.clone();
225 let loader = cx.editor.syn_loader.clone();
226 doc.detect_language(loader);
226 doc.detect_language(loader);
227 }
227 }
228+
229+ // QUESTION: do we want to compute inlay hints in pickers too ? Probably not for now
230+ // but it could be interesting in the future
228 }
231 }
229
232
230 EventResult::Consumed(None)
233 EventResult::Consumed(None)
@@ -339,6 +342,7 @@ impl<T: Item + 'static> Component for FilePicker<T> {
339 inner,
342 inner,
340 doc,
343 doc,
341 offset,
344 offset,
345+ // TODO: compute text annotations asynchronously here (like inlay hints)
342 &TextAnnotations::default(),
346 &TextAnnotations::default(),
343 highlights,
347 highlights,
344 &cx.editor.theme,
348 &cx.editor.theme,
helix-view/src/document.rs+143−5
@@ -6,7 +6,7 @@ use futures_util::FutureExt;
6use helix_core::auto_pairs::AutoPairs;
6use helix_core::auto_pairs::AutoPairs;
7use helix_core::doc_formatter::TextFormat;
7use helix_core::doc_formatter::TextFormat;
8use helix_core::syntax::Highlight;
8use helix_core::syntax::Highlight;
9−use helix_core::text_annotations::TextAnnotations;
9+use helix_core::text_annotations::{InlineAnnotation, TextAnnotations};
10use helix_core::Range;
10use helix_core::Range;
11use helix_vcs::{DiffHandle, DiffProviderRegistry};
11use helix_vcs::{DiffHandle, DiffProviderRegistry};
12
12
@@ -19,6 +19,7 @@ use std::collections::HashMap;
19use std::fmt::Display;
19use std::fmt::Display;
20use std::future::Future;
20use std::future::Future;
21use std::path::{Path, PathBuf};
21use std::path::{Path, PathBuf};
22+use std::rc::Rc;
22use std::str::FromStr;
23use std::str::FromStr;
23use std::sync::{Arc, Weak};
24use std::sync::{Arc, Weak};
24use std::time::SystemTime;
25use std::time::SystemTime;
@@ -119,6 +120,14 @@ pub struct Document {
119 text: Rope,
120 text: Rope,
120 selections: HashMap<ViewId, Selection>,
121 selections: HashMap<ViewId, Selection>,
121
122
123+ /// Inlay hints annotations for the document, by view.
124+ ///
125+ /// To know if they're up-to-date, check the `id` field in `DocumentInlayHints`.
126+ pub(crate) inlay_hints: HashMap<ViewId, DocumentInlayHints>,
127+ /// Set to `true` when the document is updated, reset to `false` on the next inlay hints
128+ /// update from the LSP
129+ pub inlay_hints_oudated: bool,
130+
122 path: Option<PathBuf>,
131 path: Option<PathBuf>,
123 encoding: &'static encoding::Encoding,
132 encoding: &'static encoding::Encoding,
124
133
@@ -162,6 +171,73 @@ pub struct Document {
162 version_control_head: Option<Arc<ArcSwap<Box<str>>>>,
171 version_control_head: Option<Arc<ArcSwap<Box<str>>>>,
163}
172}
164
173
174+/// Inlay hints for a single `(Document, View)` combo.
175+///
176+/// There are `*_inlay_hints` field for each kind of hints an LSP can send since we offer the
177+/// option to style theme differently in the theme according to the (currently supported) kinds
178+/// (`type`, `parameter` and the rest).
179+///
180+/// Inlay hints are always `InlineAnnotation`s, not overlays or line-ones: LSP may choose to place
181+/// them anywhere in the text and will sometime offer config options to move them where the user
182+/// wants them but it shouldn't be Helix who decides that so we use the most precise positioning.
183+///
184+/// The padding for inlay hints needs to be stored separately for before and after (the LSP spec
185+/// uses 'left' and 'right' but not all text is left to right so let's be correct) padding because
186+/// the 'before' padding must be added to a layer *before* the regular inlay hints and the 'after'
187+/// padding comes ... after.
188+#[derive(Debug, Clone)]
189+pub struct DocumentInlayHints {
190+ /// Identifier for the inlay hints stored in this structure. To be checked to know if they have
191+ /// to be recomputed on idle or not.
192+ pub id: DocumentInlayHintsId,
193+
194+ /// Inlay hints of `TYPE` kind, if any.
195+ pub type_inlay_hints: Rc<[InlineAnnotation]>,
196+
197+ /// Inlay hints of `PARAMETER` kind, if any.
198+ pub parameter_inlay_hints: Rc<[InlineAnnotation]>,
199+
200+ /// Inlay hints that are neither `TYPE` nor `PARAMETER`.
201+ ///
202+ /// LSPs are not required to associate a kind to their inlay hints, for example Rust-Analyzer
203+ /// currently never does (February 2023) and the LSP spec may add new kinds in the future that
204+ /// we want to display even if we don't have some special highlighting for them.
205+ pub other_inlay_hints: Rc<[InlineAnnotation]>,
206+
207+ /// Inlay hint padding. When creating the final `TextAnnotations`, the `before` padding must be
208+ /// added first, then the regular inlay hints, then the `after` padding.
209+ pub padding_before_inlay_hints: Rc<[InlineAnnotation]>,
210+ pub padding_after_inlay_hints: Rc<[InlineAnnotation]>,
211+}
212+
213+impl DocumentInlayHints {
214+ /// Generate an empty list of inlay hints with the given ID.
215+ pub fn empty_with_id(id: DocumentInlayHintsId) -> Self {
216+ Self {
217+ id,
218+ type_inlay_hints: Rc::new([]),
219+ parameter_inlay_hints: Rc::new([]),
220+ other_inlay_hints: Rc::new([]),
221+ padding_before_inlay_hints: Rc::new([]),
222+ padding_after_inlay_hints: Rc::new([]),
223+ }
224+ }
225+}
226+
227+/// Associated with a [`Document`] and [`ViewId`], uniquely identifies the state of inlay hints for
228+/// for that document and view: if this changed since the last save, the inlay hints for the view
229+/// should be recomputed.
230+///
231+/// We can't store the `ViewOffset` instead of the first and last asked-for lines because if
232+/// softwrapping changes, the `ViewOffset` may not change while the displayed lines will.
233+#[derive(Copy, Clone, PartialEq, Eq)]
234+pub struct DocumentInlayHintsId {
235+ /// First line for which the inlay hints were requested.
236+ pub first_line: usize,
237+ /// Last line for which the inlay hints were requested.
238+ pub last_line: usize,
239+}
240+
165use std::{fmt, mem};
241use std::{fmt, mem};
166impl fmt::Debug for Document {
242impl fmt::Debug for Document {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -169,6 +245,8 @@ impl fmt::Debug for Document {
169 .field("id", &self.id)
245 .field("id", &self.id)
170 .field("text", &self.text)
246 .field("text", &self.text)
171 .field("selections", &self.selections)
247 .field("selections", &self.selections)
248+ .field("inlay_hints_oudated", &self.inlay_hints_oudated)
249+ .field("text_annotations", &self.inlay_hints)
172 .field("path", &self.path)
250 .field("path", &self.path)
173 .field("encoding", &self.encoding)
251 .field("encoding", &self.encoding)
174 .field("restore_cursor", &self.restore_cursor)
252 .field("restore_cursor", &self.restore_cursor)
@@ -187,6 +265,15 @@ impl fmt::Debug for Document {
187 }
265 }
188}
266}
189
267
268+impl fmt::Debug for DocumentInlayHintsId {
269+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270+ // Much more agreable to read when debugging
271+ f.debug_struct("DocumentInlayHintsId")
272+ .field("lines", &(self.first_line..self.last_line))
273+ .finish()
274+ }
275+}
276+
190// The documentation and implementation of this function should be up-to-date with
277// The documentation and implementation of this function should be up-to-date with
191// its sibling function, `to_writer()`.
278// its sibling function, `to_writer()`.
192//
279//
@@ -389,6 +476,8 @@ impl Document {
389 encoding,
476 encoding,
390 text,
477 text,
391 selections: HashMap::default(),
478 selections: HashMap::default(),
479+ inlay_hints: HashMap::default(),
480+ inlay_hints_oudated: false,
392 indent_style: DEFAULT_INDENT,
481 indent_style: DEFAULT_INDENT,
393 line_ending: DEFAULT_LINE_ENDING,
482 line_ending: DEFAULT_LINE_ENDING,
394 restore_cursor: false,
483 restore_cursor: false,
@@ -819,13 +908,16 @@ impl Document {
819 }
908 }
820 }
909 }
821
910
822− /// Remove a view's selection from this document.
911+ /// Remove a view's selection and inlay hints from this document.
823 pub fn remove_view(&mut self, view_id: ViewId) {
912 pub fn remove_view(&mut self, view_id: ViewId) {
824 self.selections.remove(&view_id);
913 self.selections.remove(&view_id);
914+ self.inlay_hints.remove(&view_id);
825 }
915 }
826
916
827 /// Apply a [`Transaction`] to the [`Document`] to change its text.
917 /// Apply a [`Transaction`] to the [`Document`] to change its text.
828 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
918 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
919+ use helix_core::Assoc;
920+
829 let old_doc = self.text().clone();
921 let old_doc = self.text().clone();
830
922
831 let success = transaction.changes().apply(&mut self.text);
923 let success = transaction.changes().apply(&mut self.text);
@@ -881,10 +973,10 @@ impl Document {
881 .unwrap();
973 .unwrap();
882 }
974 }
883
975
976+ let changes = transaction.changes();
977+
884 // map state.diagnostics over changes::map_pos too
978 // map state.diagnostics over changes::map_pos too
885 for diagnostic in &mut self.diagnostics {
979 for diagnostic in &mut self.diagnostics {
886− use helix_core::Assoc;
887− let changes = transaction.changes();
888 diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
980 diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
889 diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
981 diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
890 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
982 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
@@ -892,13 +984,40 @@ impl Document {
892 self.diagnostics
984 self.diagnostics
893 .sort_unstable_by_key(|diagnostic| diagnostic.range);
985 .sort_unstable_by_key(|diagnostic| diagnostic.range);
894
986
987+ // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
988+ let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| {
989+ if let Some(data) = Rc::get_mut(annotations) {
990+ for inline in data.iter_mut() {
991+ inline.char_idx = changes.map_pos(inline.char_idx, Assoc::After);
992+ }
993+ }
994+ };
995+
996+ self.inlay_hints_oudated = true;
997+ for text_annotation in self.inlay_hints.values_mut() {
998+ let DocumentInlayHints {
999+ id: _,
1000+ type_inlay_hints,
1001+ parameter_inlay_hints,
1002+ other_inlay_hints,
1003+ padding_before_inlay_hints,
1004+ padding_after_inlay_hints,
1005+ } = text_annotation;
1006+
1007+ apply_inlay_hint_changes(padding_before_inlay_hints);
1008+ apply_inlay_hint_changes(type_inlay_hints);
1009+ apply_inlay_hint_changes(parameter_inlay_hints);
1010+ apply_inlay_hint_changes(other_inlay_hints);
1011+ apply_inlay_hint_changes(padding_after_inlay_hints);
1012+ }
1013+
895 // emit lsp notification
1014 // emit lsp notification
896 if let Some(language_server) = self.language_server() {
1015 if let Some(language_server) = self.language_server() {
897 let notify = language_server.text_document_did_change(
1016 let notify = language_server.text_document_did_change(
898 self.versioned_identifier(),
1017 self.versioned_identifier(),
899 &old_doc,
1018 &old_doc,
900 self.text(),
1019 self.text(),
901− transaction.changes(),
1020+ changes,
902 );
1021 );
903
1022
904 if let Some(notify) = notify {
1023 if let Some(notify) = notify {
@@ -1217,6 +1336,7 @@ impl Document {
1217 &self.selections[&view_id]
1336 &self.selections[&view_id]
1218 }
1337 }
1219
1338
1339+ #[inline]
1220 pub fn selections(&self) -> &HashMap<ViewId, Selection> {
1340 pub fn selections(&self) -> &HashMap<ViewId, Selection> {
1221 &self.selections
1341 &self.selections
1222 }
1342 }
@@ -1355,9 +1475,27 @@ impl Document {
1355 }
1475 }
1356 }
1476 }
1357
1477
1478+ /// Get the text annotations that apply to the whole document, those that do not apply to any
1479+ /// specific view.
1358 pub fn text_annotations(&self, _theme: Option<&Theme>) -> TextAnnotations {
1480 pub fn text_annotations(&self, _theme: Option<&Theme>) -> TextAnnotations {
1359 TextAnnotations::default()
1481 TextAnnotations::default()
1360 }
1482 }
1483+
1484+ /// Set the inlay hints for this document and `view_id`.
1485+ pub fn set_inlay_hints(&mut self, view_id: ViewId, inlay_hints: DocumentInlayHints) {
1486+ self.inlay_hints.insert(view_id, inlay_hints);
1487+ }
1488+
1489+ /// Get the inlay hints for this document and `view_id`.
1490+ pub fn inlay_hints(&self, view_id: ViewId) -> Option<&DocumentInlayHints> {
1491+ self.inlay_hints.get(&view_id)
1492+ }
1493+
1494+ /// Completely removes all the inlay hints saved for the document, dropping them to free memory
1495+ /// (since it often means inlay hints have been fully deactivated).
1496+ pub fn reset_all_inlay_hints(&mut self) {
1497+ self.inlay_hints = Default::default();
1498+ }
1361}
1499}
1362
1500
1363#[derive(Clone, Debug)]
1501#[derive(Clone, Debug)]
helix-view/src/editor.rs+16
@@ -345,6 +345,8 @@ pub struct LspConfig {
345 pub auto_signature_help: bool,
345 pub auto_signature_help: bool,
346 /// Display docs under signature help popup
346 /// Display docs under signature help popup
347 pub display_signature_help_docs: bool,
347 pub display_signature_help_docs: bool,
348+ /// Display inlay hints
349+ pub display_inlay_hints: bool,
348}
350}
349
351
350impl Default for LspConfig {
352impl Default for LspConfig {
@@ -354,6 +356,7 @@ impl Default for LspConfig {
354 display_messages: false,
356 display_messages: false,
355 auto_signature_help: true,
357 auto_signature_help: true,
356 display_signature_help_docs: true,
358 display_signature_help_docs: true,
359+ display_inlay_hints: false,
357 }
360 }
358 }
361 }
359}
362}
@@ -1133,6 +1136,19 @@ impl Editor {
1133
1136
1134 fn _refresh(&mut self) {
1137 fn _refresh(&mut self) {
1135 let config = self.config();
1138 let config = self.config();
1139+
1140+ // Reset the inlay hints annotations *before* updating the views, that way we ensure they
1141+ // will disappear during the `.sync_change(doc)` call below.
1142+ //
1143+ // We can't simply check this config when rendering because inlay hints are only parts of
1144+ // the possible annotations, and others could still be active, so we need to selectively
1145+ // drop the inlay hints.
1146+ if !config.lsp.display_inlay_hints {
1147+ for doc in self.documents_mut() {
1148+ doc.reset_all_inlay_hints();
1149+ }
1150+ }
1151+
1136 for (view, _) in self.tree.views_mut() {
1152 for (view, _) in self.tree.views_mut() {
1137 let doc = doc_mut!(self, &view.doc);
1153 let doc = doc_mut!(self, &view.doc);
1138 view.sync_changes(doc);
1154 view.sync_changes(doc);
helix-view/src/tree.rs+5−6
@@ -278,16 +278,15 @@ impl Tree {
278 self.try_get(index).unwrap()
278 self.try_get(index).unwrap()
279 }
279 }
280
280
281− /// Try to get reference to a [View] by index. Returns `None` if node content is not a [Content::View]
281+ /// Try to get reference to a [View] by index. Returns `None` if node content is not a [`Content::View`].
282− /// # Panics
283 ///
282 ///
284− /// Panics if `index` is not in self.nodes. This can be checked with [Self::contains]
283+ /// Does not panic if the view does not exists anymore.
285 pub fn try_get(&self, index: ViewId) -> Option<&View> {
284 pub fn try_get(&self, index: ViewId) -> Option<&View> {
286− match &self.nodes[index] {
285+ match self.nodes.get(index) {
287− Node {
286+ Some(Node {
288 content: Content::View(view),
287 content: Content::View(view),
289 ..
288 ..
290− } => Some(view),
289+ }) => Some(view),
291 _ => None,
290 _ => None,
292 }
291 }
293 }
292 }
helix-view/src/view.rs+47−4
@@ -1,19 +1,21 @@
1use crate::{
1use crate::{
2 align_view,
2 align_view,
3+ document::DocumentInlayHints,
3 editor::{GutterConfig, GutterType},
4 editor::{GutterConfig, GutterType},
4 graphics::Rect,
5 graphics::Rect,
5 Align, Document, DocumentId, Theme, ViewId,
6 Align, Document, DocumentId, Theme, ViewId,
6};
7};
7
8
8use helix_core::{
9use helix_core::{
9− char_idx_at_visual_offset, doc_formatter::TextFormat, text_annotations::TextAnnotations,
10+ char_idx_at_visual_offset, doc_formatter::TextFormat, syntax::Highlight,
10− visual_offset_from_anchor, visual_offset_from_block, Position, RopeSlice, Selection,
11+ text_annotations::TextAnnotations, visual_offset_from_anchor, visual_offset_from_block,
11− Transaction,
12+ Position, RopeSlice, Selection, Transaction,
12};
13};
13
14
14use std::{
15use std::{
15 collections::{HashMap, VecDeque},
16 collections::{HashMap, VecDeque},
16 fmt,
17 fmt,
18+ rc::Rc,
17};
19};
18
20
19const JUMP_LIST_CAPACITY: usize = 30;
21const JUMP_LIST_CAPACITY: usize = 30;
@@ -402,9 +404,50 @@ impl View {
402 Some(pos)
404 Some(pos)
403 }
405 }
404
406
407+ /// Get the text annotations to display in the current view for the given document and theme.
405 pub fn text_annotations(&self, doc: &Document, theme: Option<&Theme>) -> TextAnnotations {
408 pub fn text_annotations(&self, doc: &Document, theme: Option<&Theme>) -> TextAnnotations {
406 // TODO custom annotations for custom views like side by side diffs
409 // TODO custom annotations for custom views like side by side diffs
410+
407− doc.text_annotations(theme)
411+ let mut text_annotations = doc.text_annotations(theme);
412+
413+ let DocumentInlayHints {
414+ id: _,
415+ type_inlay_hints,
416+ parameter_inlay_hints,
417+ other_inlay_hints,
418+ padding_before_inlay_hints,
419+ padding_after_inlay_hints,
420+ } = match doc.inlay_hints.get(&self.id) {
421+ Some(doc_inlay_hints) => doc_inlay_hints,
422+ None => return text_annotations,
423+ };
424+
425+ let type_style = theme
426+ .and_then(|t| t.find_scope_index("ui.virtual.inlay-hint.type"))
427+ .map(Highlight);
428+ let parameter_style = theme
429+ .and_then(|t| t.find_scope_index("ui.virtual.inlay-hint.parameter"))
430+ .map(Highlight);
431+ let other_style = theme
432+ .and_then(|t| t.find_scope_index("ui.virtual.inlay-hint"))
433+ .map(Highlight);
434+
435+ let mut add_annotations = |annotations: &Rc<[_]>, style| {
436+ if !annotations.is_empty() {
437+ text_annotations.add_inline_annotations(Rc::clone(annotations), style);
438+ }
439+ };
440+
441+ // Overlapping annotations are ignored apart from the first so the order here is not random:
442+ // types -> parameters -> others should hopefully be the "correct" order for most use cases,
443+ // with the padding coming before and after as expected.
444+ add_annotations(padding_before_inlay_hints, None);
445+ add_annotations(type_inlay_hints, type_style);
446+ add_annotations(parameter_inlay_hints, parameter_style);
447+ add_annotations(other_inlay_hints, other_style);
448+ add_annotations(padding_after_inlay_hints, None);
449+
450+ text_annotations
408 }
451 }
409
452
410 pub fn text_pos_at_screen_coords(
453 pub fn text_pos_at_screen_coords(
languages.toml+72
@@ -19,6 +19,14 @@ indent = { tab-width = 4, unit = " " }
19'"' = '"'
19'"' = '"'
20'`' = '`'
20'`' = '`'
21
21
22+[language.config]
23+inlayHints.bindingModeHints.enable = false
24+inlayHints.closingBraceHints.minLines = 10
25+inlayHints.closureReturnTypeHints.enable = "with_block"
26+inlayHints.discriminantHints.enable = "fieldless"
27+inlayHints.lifetimeElisionHints.enable = "skip_trivial"
28+inlayHints.typeHints.hideClosureInitialization = false
29+
22[language.debugger]
30[language.debugger]
23name = "lldb-vscode"
31name = "lldb-vscode"
24transport = "stdio"
32transport = "stdio"
@@ -291,6 +299,14 @@ language-server = { command = "gopls" }
291# TODO: gopls needs utf-8 offsets?
299# TODO: gopls needs utf-8 offsets?
292indent = { tab-width = 4, unit = "\t" }
300indent = { tab-width = 4, unit = "\t" }
293
301
302+[language.config.hints]
303+assignVariableTypes = true
304+compositeLiteralFields = true
305+constantValues = true
306+functionTypeParameters = true
307+parameterNames = true
308+rangeVariableTypes = true
309+
294[language.debugger]
310[language.debugger]
295name = "go"
311name = "go"
296transport = "tcp"
312transport = "tcp"
@@ -382,6 +398,18 @@ comment-token = "//"
382language-server = { command = "typescript-language-server", args = ["--stdio"], language-id = "javascript" }
398language-server = { command = "typescript-language-server", args = ["--stdio"], language-id = "javascript" }
383indent = { tab-width = 2, unit = " " }
399indent = { tab-width = 2, unit = " " }
384
400
401+[language.config]
402+hostInfo = "helix"
403+
404+[language.config.javascript.inlayHints]
405+includeInlayEnumMemberValueHints = true
406+includeInlayFunctionLikeReturnTypeHints = true
407+includeInlayFunctionParameterTypeHints = true
408+includeInlayParameterNameHints = "all"
409+includeInlayParameterNameHintsWhenArgumentMatchesName = true
410+includeInlayPropertyDeclarationTypeHints = true
411+includeInlayVariableTypeHints = true
412+
385[language.debugger]
413[language.debugger]
386name = "node-debug2"
414name = "node-debug2"
387transport = "stdio"
415transport = "stdio"
@@ -409,6 +437,18 @@ language-server = { command = "typescript-language-server", args = ["--stdio"],
409indent = { tab-width = 2, unit = " " }
437indent = { tab-width = 2, unit = " " }
410grammar = "javascript"
438grammar = "javascript"
411
439
440+[language.config]
441+hostInfo = "helix"
442+
443+[language.config.javascript.inlayHints]
444+includeInlayEnumMemberValueHints = true
445+includeInlayFunctionLikeReturnTypeHints = true
446+includeInlayFunctionParameterTypeHints = true
447+includeInlayParameterNameHints = "all"
448+includeInlayParameterNameHintsWhenArgumentMatchesName = true
449+includeInlayPropertyDeclarationTypeHints = true
450+includeInlayVariableTypeHints = true
451+
412[[language]]
452[[language]]
413name = "typescript"
453name = "typescript"
414scope = "source.ts"
454scope = "source.ts"
@@ -420,6 +460,18 @@ roots = []
420language-server = { command = "typescript-language-server", args = ["--stdio"], language-id = "typescript"}
460language-server = { command = "typescript-language-server", args = ["--stdio"], language-id = "typescript"}
421indent = { tab-width = 2, unit = " " }
461indent = { tab-width = 2, unit = " " }
422
462
463+[language.config]
464+hostInfo = "helix"
465+
466+[language.config.typescript.inlayHints]
467+includeInlayEnumMemberValueHints = true
468+includeInlayFunctionLikeReturnTypeHints = true
469+includeInlayFunctionParameterTypeHints = true
470+includeInlayParameterNameHints = "all"
471+includeInlayParameterNameHintsWhenArgumentMatchesName = true
472+includeInlayPropertyDeclarationTypeHints = true
473+includeInlayVariableTypeHints = true
474+
423[[grammar]]
475[[grammar]]
424name = "typescript"
476name = "typescript"
425source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "6aac031ad88dd6317f02ac0bb27d099a553a7d8c", subpath = "typescript" }
477source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "6aac031ad88dd6317f02ac0bb27d099a553a7d8c", subpath = "typescript" }
@@ -434,6 +486,18 @@ roots = []
434language-server = { command = "typescript-language-server", args = ["--stdio"], language-id = "typescriptreact" }
486language-server = { command = "typescript-language-server", args = ["--stdio"], language-id = "typescriptreact" }
435indent = { tab-width = 2, unit = " " }
487indent = { tab-width = 2, unit = " " }
436
488
489+[language.config]
490+hostInfo = "helix"
491+
492+[language.config.typescript.inlayHints]
493+includeInlayEnumMemberValueHints = true
494+includeInlayFunctionLikeReturnTypeHints = true
495+includeInlayFunctionParameterTypeHints = true
496+includeInlayParameterNameHints = "all"
497+includeInlayParameterNameHintsWhenArgumentMatchesName = true
498+includeInlayPropertyDeclarationTypeHints = true
499+includeInlayVariableTypeHints = true
500+
437[[grammar]]
501[[grammar]]
438name = "tsx"
502name = "tsx"
439source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "6aac031ad88dd6317f02ac0bb27d099a553a7d8c", subpath = "tsx" }
503source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "6aac031ad88dd6317f02ac0bb27d099a553a7d8c", subpath = "tsx" }
@@ -740,6 +804,14 @@ comment-token = "--"
740indent = { tab-width = 2, unit = " " }
804indent = { tab-width = 2, unit = " " }
741language-server = { command = "lua-language-server", args = [] }
805language-server = { command = "lua-language-server", args = [] }
742
806
807+[language.config.Lua.hint]
808+enable = true
809+arrayIndex = "Enable"
810+setType = true
811+paramName = "All"
812+paramType = true
813+await = true
814+
743[[grammar]]
815[[grammar]]
744name = "lua"
816name = "lua"
745source = { git = "https://github.com/MunifTanjim/tree-sitter-lua", rev = "887dfd4e83c469300c279314ff1619b1d0b85b91" }
817source = { git = "https://github.com/MunifTanjim/tree-sitter-lua", rev = "887dfd4e83c469300c279314ff1619b1d0b85b91" }