Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/text_annotations.rs')
| -rw-r--r-- | helix-core/src/text_annotations.rs | 424 |
1 files changed, 0 insertions, 424 deletions
diff --git a/helix-core/src/text_annotations.rs b/helix-core/src/text_annotations.rs deleted file mode 100644 index 0f492b8b..00000000 --- a/helix-core/src/text_annotations.rs +++ /dev/null @@ -1,424 +0,0 @@ -use std::cell::Cell; -use std::cmp::Ordering; -use std::fmt::Debug; -use std::ops::Range; -use std::ptr::NonNull; - -use crate::doc_formatter::FormattedGrapheme; -use crate::syntax::{Highlight, OverlayHighlights}; -use crate::{Position, Tendril}; - -/// An inline annotation is continuous text shown -/// on the screen before the grapheme that starts at -/// `char_idx` -#[derive(Debug, Clone)] -pub struct InlineAnnotation { - pub text: Tendril, - 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`. -/// If `grapheme` contains multiple graphemes the text -/// will render incorrectly. -/// If you want to overlay multiple graphemes simply -/// use multiple `Overlays`. -/// -/// # Examples -/// -/// The following examples are valid overlays for the following text: -/// -/// `aX͎̊͢͜͝͡bc` -/// -/// ``` -/// use helix_core::text_annotations::Overlay; -/// -/// // replaces a -/// Overlay::new(0, "X"); -/// -/// // replaces X͎̊͢͜͝͡ -/// Overlay::new(1, "\t"); -/// -/// // replaces b -/// Overlay::new(6, "X̢̢̟͖̲͌̋̇͑͝"); -/// ``` -/// -/// The following examples are invalid uses -/// -/// ``` -/// use helix_core::text_annotations::Overlay; -/// -/// // overlay is not aligned at grapheme boundary -/// Overlay::new(3, "x"); -/// -/// // overlay contains multiple graphemes -/// Overlay::new(0, "xy"); -/// ``` -#[derive(Debug, Clone)] -pub struct Overlay { - pub char_idx: usize, - 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 inserting virtual text lines between normal text -/// lines. These lines can be filled with text in the rendering code as their -/// contents have no effect beyond visual appearance. -/// -/// The height of virtual text is usually not known ahead of time as virtual -/// text often requires softwrapping. Furthermore the height of some virtual -/// text like side-by-side diffs depends on the height of the text (again -/// influenced by softwrap) and other virtual text. Therefore line annotations -/// are computed on the fly instead of ahead of time like other annotations. -/// -/// The core of this trait `insert_virtual_lines` function. It is called at the -/// end of every visual line and allows the `LineAnnotation` to insert empty -/// virtual lines. Apart from that the `LineAnnotation` trait has multiple -/// methods that allow it to track anchors in the document. -/// -/// When a new traversal of a document starts `reset_pos` is called. Afterwards -/// the other functions are called with indices that are larger then the -/// one passed to `reset_pos`. This allows performing a binary search (use -/// `partition_point`) in `reset_pos` once and then to only look at the next -/// anchor during each method call. -/// -/// The `reset_pos`, `skip_conceal` and `process_anchor` functions all return a -/// `char_idx` anchor. This anchor is stored when transversing the document and -/// when the grapheme at the anchor is traversed the `process_anchor` function -/// is called. -/// -/// # Note -/// -/// All functions only receive immutable references to `self`. -/// `LineAnnotation`s that want to store an internal position or -/// state of some kind should use `Cell`. Using interior mutability for -/// caches is preferable as otherwise a lot of lifetimes become invariant -/// which complicates APIs a lot. -pub trait LineAnnotation { - /// Resets the internal position to `char_idx`. This function is called - /// when a new traversal of a document starts. - /// - /// All `char_idx` passed to `insert_virtual_lines` are strictly monotonically increasing - /// with the first `char_idx` greater or equal to the `char_idx` - /// passed to this function. - /// - /// # Returns - /// - /// The `char_idx` of the next anchor this `LineAnnotation` is interested in, - /// replaces the currently registered anchor. Return `usize::MAX` to ignore - fn reset_pos(&mut self, _char_idx: usize) -> usize { - usize::MAX - } - - /// Called when a text is concealed that contains an anchor registered by this `LineAnnotation`. - /// In this case the line decorations **must** ensure that virtual text anchored within that - /// char range is skipped. - /// - /// # Returns - /// - /// The `char_idx` of the next anchor this `LineAnnotation` is interested in, - /// **after the end of conceal_end_char_idx** - /// replaces the currently registered anchor. Return `usize::MAX` to ignore - fn skip_concealed_anchors(&mut self, conceal_end_char_idx: usize) -> usize { - self.reset_pos(conceal_end_char_idx) - } - - /// Process an anchor (horizontal position is provided) and returns the next anchor. - /// - /// # Returns - /// - /// The `char_idx` of the next anchor this `LineAnnotation` is interested in, - /// replaces the currently registered anchor. Return `usize::MAX` to ignore - fn process_anchor(&mut self, _grapheme: &FormattedGrapheme) -> usize { - usize::MAX - } - - /// This function is called at the end of a visual line to insert virtual text - /// - /// # Returns - /// - /// The number of additional virtual lines to reserve - /// - /// # Note - /// - /// The `line_end_visual_pos` parameter indicates the visual vertical distance - /// from the start of block where the traversal starts. This includes the offset - /// from other `LineAnnotations`. This allows inline annotations to consider - /// the height of the text and "align" two different documents (like for side - /// by side diffs). These annotations that want to "align" two documents should - /// therefore be added last so that other virtual text is also considered while aligning - fn insert_virtual_lines( - &mut self, - line_end_char_idx: usize, - line_end_visual_pos: Position, - doc_line: usize, - ) -> Position; -} - -#[derive(Debug)] -struct Layer<'a, A, M> { - annotations: &'a [A], - current_index: Cell<usize>, - metadata: M, -} - -impl<A, M: Clone> Clone for Layer<'_, A, M> { - fn clone(&self) -> Self { - Layer { - annotations: self.annotations, - current_index: self.current_index.clone(), - metadata: self.metadata.clone(), - } - } -} - -impl<A, M> Layer<'_, A, M> { - pub fn reset_pos(&self, char_idx: usize, get_char_idx: impl Fn(&A) -> usize) { - let new_index = self - .annotations - .partition_point(|annot| get_char_idx(annot) < char_idx); - self.current_index.set(new_index); - } - - pub fn consume(&self, char_idx: usize, get_char_idx: impl Fn(&A) -> usize) -> Option<&A> { - let annot = self.annotations.get(self.current_index.get())?; - debug_assert!(get_char_idx(annot) >= char_idx); - if get_char_idx(annot) == char_idx { - self.current_index.set(self.current_index.get() + 1); - Some(annot) - } else { - None - } - } -} - -impl<'a, A, M> From<(&'a [A], M)> for Layer<'a, A, M> { - fn from((annotations, metadata): (&'a [A], M)) -> Layer<'a, A, M> { - Layer { - annotations, - current_index: Cell::new(0), - metadata, - } - } -} - -fn reset_pos<A, M>(layers: &[Layer<A, M>], pos: usize, get_pos: impl Fn(&A) -> usize) { - for layer in layers { - layer.reset_pos(pos, &get_pos) - } -} - -/// Safety: We store LineAnnotation in a NonNull pointer. This is necessary to work -/// around an unfortunate inconsistency in rusts variance system that unnnecesarily -/// makes the lifetime invariant if implemented with safe code. This makes the -/// DocFormatter API very cumbersome/basically impossible to work with. -/// -/// Normally object types `dyn Foo + 'a` are covariant so if we used `Box<dyn LineAnnotation + 'a>` below -/// everything would be alright. However we want to use `Cell<Box<dyn LineAnnotation + 'a>>` -/// to be able to call the mutable function on `LineAnnotation`. The problem is that -/// some types like `Cell` make all their arguments invariant. This is important for soundness -/// normally for the same reasons that `&'a mut T` is invariant over `T` -/// (see <https://doc.rust-lang.org/nomicon/subtyping.html>). However for `&'a mut` (`dyn Foo + 'b`) -/// there is a specical rule in the language to make `'b` covariant (otherwise trait objects would be -/// super annoying to use). See <https://users.rust-lang.org/t/solved-variance-of-dyn-trait-a> for -/// why this is sound. Sadly that rule doesn't apply to `Cell<Box<(dyn Foo + 'a)>` -/// (or other invariant types like `UnsafeCell` or `*mut (dyn Foo + 'a)`). -/// -/// We sidestep the problem by using `NonNull` which is covariant. In the -/// special case of trait objects this is sound (easily checked by adding a -/// `PhantomData<&'a mut Foo + 'a)>` field). We don't need an explicit `Cell` -/// type here because we never hand out any refereces to the trait objects. That -/// means any reference to the pointer can create a valid multable reference -/// that is covariant over `'a` (or in other words it's a raw pointer, as long as -/// we don't hand out references we are free to do whatever we want). -struct RawBox<T: ?Sized>(NonNull<T>); - -impl<T: ?Sized> RawBox<T> { - /// Safety: Only a single mutable reference - /// created by this function may exist at a given time. - #[allow(clippy::mut_from_ref)] - unsafe fn get(&self) -> &mut T { - &mut *self.0.as_ptr() - } -} -impl<T: ?Sized> From<Box<T>> for RawBox<T> { - fn from(box_: Box<T>) -> Self { - // obviously safe because Box::into_raw never returns null - unsafe { Self(NonNull::new_unchecked(Box::into_raw(box_))) } - } -} - -impl<T: ?Sized> Drop for RawBox<T> { - fn drop(&mut self) { - unsafe { drop(Box::from_raw(self.0.as_ptr())) } - } -} - -/// Annotations that change that is displayed when the document is render. -/// Also commonly called virtual text. -#[derive(Default)] -pub struct TextAnnotations<'a> { - inline_annotations: Vec<Layer<'a, InlineAnnotation, Option<Highlight>>>, - overlays: Vec<Layer<'a, Overlay, Option<Highlight>>>, - line_annotations: Vec<(Cell<usize>, RawBox<dyn LineAnnotation + 'a>)>, -} - -impl Debug for TextAnnotations<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TextAnnotations") - .field("inline_annotations", &self.inline_annotations) - .field("overlays", &self.overlays) - .finish_non_exhaustive() - } -} - -impl<'a> TextAnnotations<'a> { - /// Prepare the TextAnnotations for iteration starting at char_idx - pub fn reset_pos(&self, char_idx: usize) { - reset_pos(&self.inline_annotations, char_idx, |annot| annot.char_idx); - reset_pos(&self.overlays, char_idx, |annot| annot.char_idx); - for (next_anchor, layer) in &self.line_annotations { - next_anchor.set(unsafe { layer.get().reset_pos(char_idx) }); - } - } - - pub fn collect_overlay_highlights(&self, char_range: Range<usize>) -> OverlayHighlights { - let mut highlights = Vec::new(); - self.reset_pos(char_range.start); - for char_idx in char_range { - if let Some((_, Some(highlight))) = self.overlay_at(char_idx) { - // we don't know the number of chars the original grapheme takes - // however it doesn't matter as highlight boundaries are automatically - // aligned to grapheme boundaries in the rendering code - highlights.push((highlight, char_idx..char_idx + 1)); - } - } - - OverlayHighlights::Heterogenous { highlights } - } - - /// Add new inline annotations. - /// - /// The annotations grapheme will be rendered with `highlight` - /// patched on top of `ui.text`. - /// - /// The annotations **must be sorted** by their `char_idx`. - /// Multiple annotations with the same `char_idx` are allowed, - /// they will be display in the order that they are present in the layer. - /// - /// If multiple layers contain annotations at the same position - /// the annotations that belong to the layers added first will be shown first. - pub fn add_inline_annotations( - &mut self, - layer: &'a [InlineAnnotation], - highlight: Option<Highlight>, - ) -> &mut Self { - if !layer.is_empty() { - self.inline_annotations.push((layer, highlight).into()); - } - self - } - - /// Add new grapheme overlays. - /// - /// The overlaid grapheme will be rendered with `highlight` - /// patched on top of `ui.text`. - /// - /// The overlays **must be sorted** by their `char_idx`. - /// Multiple overlays with the same `char_idx` **are allowed**. - /// - /// If multiple layers contain overlay at the same position - /// the overlay from the layer added last will be show. - pub fn add_overlay(&mut self, layer: &'a [Overlay], highlight: Option<Highlight>) -> &mut Self { - if !layer.is_empty() { - self.overlays.push((layer, highlight).into()); - } - self - } - - /// Add new annotation lines. - /// - /// The line annotations **must be sorted** by their `char_idx`. - /// Multiple line annotations with the same `char_idx` **are not allowed**. - pub fn add_line_annotation(&mut self, layer: Box<dyn LineAnnotation + 'a>) -> &mut Self { - self.line_annotations - .push((Cell::new(usize::MAX), layer.into())); - self - } - - /// Removes all line annotations, useful for vertical motions - /// so that virtual text lines are automatically skipped. - pub fn clear_line_annotations(&mut self) { - self.line_annotations.clear(); - } - - pub(crate) fn next_inline_annotation_at( - &self, - char_idx: usize, - ) -> Option<(&InlineAnnotation, Option<Highlight>)> { - self.inline_annotations.iter().find_map(|layer| { - let annotation = layer.consume(char_idx, |annot| annot.char_idx)?; - Some((annotation, layer.metadata)) - }) - } - - pub(crate) fn overlay_at(&self, char_idx: usize) -> Option<(&Overlay, Option<Highlight>)> { - let mut overlay = None; - for layer in &self.overlays { - while let Some(new_overlay) = layer.consume(char_idx, |annot| annot.char_idx) { - overlay = Some((new_overlay, layer.metadata)); - } - } - overlay - } - - pub(crate) fn process_virtual_text_anchors(&self, grapheme: &FormattedGrapheme) { - for (next_anchor, layer) in &self.line_annotations { - loop { - match next_anchor.get().cmp(&grapheme.char_idx) { - Ordering::Less => next_anchor - .set(unsafe { layer.get().skip_concealed_anchors(grapheme.char_idx) }), - Ordering::Equal => { - next_anchor.set(unsafe { layer.get().process_anchor(grapheme) }) - } - Ordering::Greater => break, - }; - } - } - } - - pub(crate) fn virtual_lines_at( - &self, - char_idx: usize, - line_end_visual_pos: Position, - doc_line: usize, - ) -> usize { - let mut virt_off = Position::new(0, 0); - for (_, layer) in &self.line_annotations { - virt_off += unsafe { - layer - .get() - .insert_virtual_lines(char_idx, line_end_visual_pos + virt_off, doc_line) - }; - } - virt_off.row - } -} |