From bdcd4d9411655ab69245d803e88f88cc278127da Mon Sep 17 00:00:00 2001
From: Poliorcetics <poliorcetics@users.noreply.github.com>
Date: Sat, 11 Mar 2023 03:32:14 +0100
Subject: [PATCH] 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
---
 book/src/configuration.md            |   3 +
 book/src/themes.md                   | 109 ++++++++--------
 helix-core/src/diagnostic.rs         |   2 +-
 helix-core/src/doc_formatter/test.rs |  56 ++------
 helix-core/src/text_annotations.rs   |  43 ++++---
 helix-lsp/src/client.rs              |  32 +++++
 helix-term/src/commands.rs           |  33 +++--
 helix-term/src/commands/lsp.rs       | 183 ++++++++++++++++++++++++++-
 helix-term/src/ui/editor.rs          |   6 +
 helix-term/src/ui/picker.rs          |   4 +
 helix-view/src/document.rs           | 148 +++++++++++++++++++++-
 helix-view/src/editor.rs             |  16 +++
 helix-view/src/tree.rs               |  11 +-
 helix-view/src/view.rs               |  51 +++++++-
 languages.toml                       |  72 +++++++++++
 15 files changed, 618 insertions(+), 151 deletions(-)

diff --git a/book/src/configuration.md b/book/src/configuration.md
index e698646b..ec692cab 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -120,9 +120,12 @@ The following statusline elements can be configured:
 | `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
 
diff --git a/book/src/themes.md b/book/src/themes.md
index 5ddd4f2c..929f821e 100644
--- a/book/src/themes.md
+++ b/book/src/themes.md
@@ -262,58 +262,61 @@ These scopes are used for theming the editor 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
diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs
index 6b5da17e..58ddb038 100644
--- a/helix-core/src/diagnostic.rs
+++ b/helix-core/src/diagnostic.rs
@@ -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,
diff --git a/helix-core/src/doc_formatter/test.rs b/helix-core/src/doc_formatter/test.rs
index e68b31fd..ac8918bb 100644
--- a/helix-core/src/doc_formatter/test.rs
+++ b/helix-core/src/doc_formatter/test.rs
@@ -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
diff --git a/helix-core/src/text_annotations.rs b/helix-core/src/text_annotations.rs
index 1956f6b5..3e48de4d 100644
--- a/helix-core/src/text_annotations.rs
+++ b/helix-core/src/text_annotations.rs
@@ -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`.
diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs
index 9fa118fb..9cb7c147 100644
--- a/helix-lsp/src/client.rs
+++ b/helix-lsp/src/client.rs
@@ -315,6 +315,9 @@ impl Client {
                     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 @@ impl Client {
                     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 @@ impl Client {
         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,
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 803f4051..1c1edece 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -114,17 +114,7 @@ impl<'a> Context<'a> {
         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 @@ impl<'a> Context<'a> {
     }
 }
 
+#[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
diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs
index 08519366..f9d9856f 100644
--- a/helix-term/src/commands/lsp.rs
+++ b/helix-term/src/commands/lsp.rs
@@ -15,8 +15,13 @@ use tui::{
 
 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 crate::{
 };
 
 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)
+}
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 4abbe01e..7c22df74 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -990,6 +990,8 @@ impl EditorView {
     }
 
     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 @@ impl EditorView {
         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,
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index ec8b1c7f..bc2f98ee 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -225,6 +225,9 @@ impl<T: Item> FilePicker<T> {
                 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 @@ impl<T: Item + 'static> Component for FilePicker<T> {
                 inner,
                 doc,
                 offset,
+                // TODO: compute text annotations asynchronously here (like inlay hints)
                 &TextAnnotations::default(),
                 highlights,
                 &cx.editor.theme,
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index b2a9ddec..19220f28 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -6,7 +6,7 @@ use futures_util::FutureExt;
 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::collections::HashMap;
 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 @@ impl fmt::Debug for Document {
             .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 @@ impl fmt::Debug for Document {
     }
 }
 
+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 @@ impl Document {
             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 @@ impl Document {
         }
     }
 
-    /// 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 @@ impl Document {
                     .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 @@ impl Document {
             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 @@ impl Document {
         &self.selections[&view_id]
     }
 
+    #[inline]
     pub fn selections(&self) -> &HashMap<ViewId, Selection> {
         &self.selections
     }
@@ -1355,9 +1475,27 @@ impl Document {
         }
     }
 
+    /// 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)]
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 41aa707f..bbed58d6 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -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 @@ impl Default for LspConfig {
             display_messages: false,
             auto_signature_help: true,
             display_signature_help_docs: true,
+            display_inlay_hints: false,
         }
     }
 }
@@ -1133,6 +1136,19 @@ impl Editor {
 
     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);
diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs
index 5ec2773d..e8afd204 100644
--- a/helix-view/src/tree.rs
+++ b/helix-view/src/tree.rs
@@ -278,16 +278,15 @@ impl Tree {
         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,
         }
     }
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 7bfbb241..0ac7ca3b 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -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 @@ impl View {
         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(
diff --git a/languages.toml b/languages.toml
index 86f4a64d..83a09b0b 100644
--- a/languages.toml
+++ b/languages.toml
@@ -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" }