summaryrefslogtreecommitdiff
path: root/helix-view/src/document.rs
diff options
context:
space:
mode:
authorPoliorcetics2023-03-11 02:32:14 +0000
committerGitHub2023-03-11 02:32:14 +0000
commitbdcd4d9411655ab69245d803e88f88cc278127da (patch)
tree3131cca198bec2520a2fccc7d4c47cd3d4eddedf /helix-view/src/document.rs
parent3d230e701d4771377a6b3f3b8c68527af29ee066 (diff)
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
Diffstat (limited to 'helix-view/src/document.rs')
-rw-r--r--helix-view/src/document.rs148
1 files changed, 143 insertions, 5 deletions
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)]