|
1 | 1 | use std::cell::Cell;
|
| 2 | +use std::cmp::Ordering; |
| 3 | +use std::fmt::Debug; |
2 | 4 | use std::ops::Range;
|
| 5 | +use std::ptr::NonNull; |
3 | 6 |
|
| 7 | +use crate::doc_formatter::FormattedGrapheme; |
4 | 8 | use crate::syntax::Highlight;
|
5 |
| -use crate::Tendril; |
| 9 | +use crate::{Position, Tendril}; |
6 | 10 |
|
7 | 11 | /// An inline annotation is continuous text shown
|
8 | 12 | /// on the screen before the grapheme that starts at
|
@@ -75,19 +79,98 @@ impl Overlay {
|
75 | 79 | }
|
76 | 80 | }
|
77 | 81 |
|
78 |
| -/// Line annotations allow for virtual text between normal |
79 |
| -/// text lines. They cause `height` empty lines to be inserted |
80 |
| -/// below the document line that contains `anchor_char_idx`. |
| 82 | +/// Line annotations allow inserting virtual text lines between normal text |
| 83 | +/// lines. These lines can be filled with text in the rendering code as their |
| 84 | +/// contents have no effect beyond visual appearance. |
81 | 85 | ///
|
82 |
| -/// These lines can be filled with text in the rendering code |
83 |
| -/// as their contents have no effect beyond visual appearance. |
| 86 | +/// The height of virtual text is usually not known ahead of time as virtual |
| 87 | +/// text often requires softwrapping. Furthermore the height of some virtual |
| 88 | +/// text like side-by-side diffs depends on the height of the text (again |
| 89 | +/// influenced by softwrap) and other virtual text. Therefore line annotations |
| 90 | +/// are computed on the fly instead of ahead of time like other annotations. |
84 | 91 | ///
|
85 |
| -/// To insert a line after a document line simply set |
86 |
| -/// `anchor_char_idx` to `doc.line_to_char(line_idx)` |
87 |
| -#[derive(Debug, Clone)] |
88 |
| -pub struct LineAnnotation { |
89 |
| - pub anchor_char_idx: usize, |
90 |
| - pub height: usize, |
| 92 | +/// The core of this trait `insert_virtual_lines` function. It is called at the |
| 93 | +/// end of every visual line and allows the `LineAnnotation` to insert empty |
| 94 | +/// virtual lines. Apart from that the `LineAnnotation` trait has multiple |
| 95 | +/// methods that allow it to track anchors in the document. |
| 96 | +/// |
| 97 | +/// When a new traversal of a document starts `reset_pos` is called. Afterwards |
| 98 | +/// the other functions are called with indices that are larger then the |
| 99 | +/// one passed to `reset_pos`. This allows performing a binary search (use |
| 100 | +/// `partition_point`) in `reset_pos` once and then to only look at the next |
| 101 | +/// anchor during each method call. |
| 102 | +/// |
| 103 | +/// The `reset_pos`, `skip_conceal` and `process_anchor` functions all return a |
| 104 | +/// `char_idx` anchor. This anchor is stored when transversing the document and |
| 105 | +/// when the grapheme at the anchor is traversed the `process_anchor` function |
| 106 | +/// is called. |
| 107 | +/// |
| 108 | +/// # Note |
| 109 | +/// |
| 110 | +/// All functions only receive immutable references to `self`. |
| 111 | +/// `LineAnnotation`s that want to store an internal position or |
| 112 | +/// state of some kind should use `Cell`. Using interior mutability for |
| 113 | +/// caches is preferable as otherwise a lot of lifetimes become invariant |
| 114 | +/// which complicates APIs a lot. |
| 115 | +pub trait LineAnnotation { |
| 116 | + /// Resets the internal position to `char_idx`. This function is called |
| 117 | + //// when a new transveral of a document starts. |
| 118 | + /// |
| 119 | + /// All `char_idx` passed to `insert_virtual_lines` are strictly monotonically increasing |
| 120 | + /// with the first `char_idx` greater or equal to the `char_idx` |
| 121 | + /// passed to this function. |
| 122 | + /// |
| 123 | + /// # Returns |
| 124 | + /// |
| 125 | + /// The `char_idx` of the next anchor this `LineAnnotation` is interested in, |
| 126 | + /// replaces the currently registered anchor. Return `usize::MAX` to ignore |
| 127 | + fn reset_pos(&mut self, _char_idx: usize) -> usize { |
| 128 | + usize::MAX |
| 129 | + } |
| 130 | + |
| 131 | + /// Called when a text is concealed that contains an anchor registered by this `LineAnnotation` |
| 132 | + ///. In this case the line decorations **must** ensure that virtual text anchored within that |
| 133 | + /// char range is skipped. |
| 134 | + /// |
| 135 | + /// # Returns |
| 136 | + /// |
| 137 | + /// The `char_idx` of the next anchor this `LineAnnotation` is interested in, |
| 138 | + /// **after the end of conceal_end_char_idx** |
| 139 | + /// replaces the currently registered anchor. Return `usize::MAX` to ignore |
| 140 | + fn skip_concealed_anchors(&mut self, conceal_end_char_idx: usize) -> usize { |
| 141 | + self.reset_pos(conceal_end_char_idx) |
| 142 | + } |
| 143 | + |
| 144 | + /// Process an anchor (horizontal position is provided) and returns the next anchor. |
| 145 | + /// |
| 146 | + /// # Returns |
| 147 | + /// |
| 148 | + /// The `char_idx` of the next anchor this `LineAnnotation` is interested in, |
| 149 | + /// replaces the currently registered anchor. Return `usize::MAX` to ignore |
| 150 | + fn process_anchor(&mut self, _grapheme: &FormattedGrapheme) -> usize { |
| 151 | + usize::MAX |
| 152 | + } |
| 153 | + |
| 154 | + /// This function is called at the end of a visual line to insert virtual text |
| 155 | + /// |
| 156 | + /// # Returns |
| 157 | + /// |
| 158 | + /// The number of additional virtual lines to reserve |
| 159 | + /// |
| 160 | + /// # Note |
| 161 | + /// |
| 162 | + /// The `line_end_visual_pos` paramater indiciates the visual vertical distance |
| 163 | + /// from the start of block where the transversal start. This includes the offset |
| 164 | + /// from other `LineAnnotations`. This allows inline annotations to consider |
| 165 | + /// the height of the text and "align" two different documents (like for side |
| 166 | + /// by side diffs). These annotations that want to "align" two documents should |
| 167 | + /// therefore be added last so that other virtual text is also considered while aligning |
| 168 | + fn insert_virtual_lines( |
| 169 | + &mut self, |
| 170 | + line_end_char_idx: usize, |
| 171 | + line_end_visual_pos: Position, |
| 172 | + doc_line: usize, |
| 173 | + ) -> Position; |
91 | 174 | }
|
92 | 175 |
|
93 | 176 | #[derive(Debug)]
|
@@ -143,23 +226,77 @@ fn reset_pos<A, M>(layers: &[Layer<A, M>], pos: usize, get_pos: impl Fn(&A) -> u
|
143 | 226 | }
|
144 | 227 | }
|
145 | 228 |
|
| 229 | +/// Safety: We store LineAnnotation in a NonNull pointer. This is necessary to work |
| 230 | +/// around an unfortunate inconsistency in rusts variance system that unnnecesarily |
| 231 | +/// makes the lifetime invariant if implemented with safe code. This makes the |
| 232 | +/// DocFormatter API very cumbersome/basically impossible to work with. |
| 233 | +/// |
| 234 | +/// Normally object types dyn Foo + 'a so if we used Box<dyn LineAnnotation + 'a> below |
| 235 | +/// everything would be alright. However we want to use `Cell<Box<dyn LineAnnotation + 'a>>` |
| 236 | +/// to be able to call the mutable function on `LineAnnotation`. The problem is that |
| 237 | +/// some types like `Cell` make all their arguments invariant. This is important for soundness |
| 238 | +/// normally for the same reasons that &'a mut T is invariant over T |
| 239 | +/// (see <https://doc.rust-lang.org/nomicon/subtyping.html>). However for &'a mut (dyn Foo + 'b) |
| 240 | +/// there is a specical rule in the language to make 'b covariant (otherwise trait objects would be |
| 241 | +/// super annoying to use). See <https://users.rust-lang.org/t/solved-variance-of-dyn-trait-a> for |
| 242 | +/// why this is sound. Sadly that rule doesn't apply to Cell<Box<(dyn Foo + 'a)> |
| 243 | +/// (or other invariant types like `UnsafeCell` or `*mut (dyn Foo + 'a)`). |
| 244 | +/// |
| 245 | +/// We sidestep the problem by using `NonNull` which is covariant. In the specical case |
| 246 | +/// of trait objects this is sound (easily checked by adding a `PhantomData<&'a mut Foo + 'a)>` field). |
| 247 | +/// We don't need an explicit Cell type here because we never hand out any refereces |
| 248 | +/// to the trait objects so even without guarding mutable access to the pointer data behind a `` |
| 249 | +/// are covariant over 'a (or in other words it's a raw pointer, as long as we don't hand out |
| 250 | +/// references we are free to do whatever we want). |
| 251 | +struct RawBox<T: ?Sized>(NonNull<T>); |
| 252 | + |
| 253 | +impl<T: ?Sized> RawBox<T> { |
| 254 | + /// Safety: Only a single mutable reference |
| 255 | + /// created by this function may exist at a given time. |
| 256 | + #[allow(clippy::mut_from_ref)] |
| 257 | + unsafe fn get(&self) -> &mut T { |
| 258 | + &mut *self.0.as_ptr() |
| 259 | + } |
| 260 | +} |
| 261 | +impl<T: ?Sized> From<Box<T>> for RawBox<T> { |
| 262 | + fn from(box_: Box<T>) -> Self { |
| 263 | + // obviously safe because Box::into_raw never returns null |
| 264 | + unsafe { Self(NonNull::new_unchecked(Box::into_raw(box_))) } |
| 265 | + } |
| 266 | +} |
| 267 | + |
| 268 | +impl<T: ?Sized> Drop for RawBox<T> { |
| 269 | + fn drop(&mut self) { |
| 270 | + unsafe { drop(Box::from_raw(self.0.as_ptr())) } |
| 271 | + } |
| 272 | +} |
| 273 | + |
146 | 274 | /// Annotations that change that is displayed when the document is render.
|
147 | 275 | /// Also commonly called virtual text.
|
148 |
| -#[derive(Default, Debug, Clone)] |
| 276 | +#[derive(Default)] |
149 | 277 | pub struct TextAnnotations<'a> {
|
150 | 278 | inline_annotations: Vec<Layer<'a, InlineAnnotation, Option<Highlight>>>,
|
151 | 279 | overlays: Vec<Layer<'a, Overlay, Option<Highlight>>>,
|
152 |
| - line_annotations: Vec<Layer<'a, LineAnnotation, ()>>, |
| 280 | + line_annotations: Vec<(Cell<usize>, RawBox<dyn LineAnnotation + 'a>)>, |
| 281 | +} |
| 282 | + |
| 283 | +impl Debug for TextAnnotations<'_> { |
| 284 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 285 | + f.debug_struct("TextAnnotations") |
| 286 | + .field("inline_annotations", &self.inline_annotations) |
| 287 | + .field("overlays", &self.overlays) |
| 288 | + .finish_non_exhaustive() |
| 289 | + } |
153 | 290 | }
|
154 | 291 |
|
155 | 292 | impl<'a> TextAnnotations<'a> {
|
156 | 293 | /// Prepare the TextAnnotations for iteration starting at char_idx
|
157 | 294 | pub fn reset_pos(&self, char_idx: usize) {
|
158 | 295 | reset_pos(&self.inline_annotations, char_idx, |annot| annot.char_idx);
|
159 | 296 | reset_pos(&self.overlays, char_idx, |annot| annot.char_idx);
|
160 |
| - reset_pos(&self.line_annotations, char_idx, |annot| { |
161 |
| - annot.anchor_char_idx |
162 |
| - }); |
| 297 | + for (next_anchor, layer) in &self.line_annotations { |
| 298 | + next_anchor.set(unsafe { layer.get().reset_pos(char_idx) }); |
| 299 | + } |
163 | 300 | }
|
164 | 301 |
|
165 | 302 | pub fn collect_overlay_highlights(
|
@@ -219,8 +356,9 @@ impl<'a> TextAnnotations<'a> {
|
219 | 356 | ///
|
220 | 357 | /// The line annotations **must be sorted** by their `char_idx`.
|
221 | 358 | /// Multiple line annotations with the same `char_idx` **are not allowed**.
|
222 |
| - pub fn add_line_annotation(&mut self, layer: &'a [LineAnnotation]) -> &mut Self { |
223 |
| - self.line_annotations.push((layer, ()).into()); |
| 359 | + pub fn add_line_annotation(&mut self, layer: Box<dyn LineAnnotation + 'a>) -> &mut Self { |
| 360 | + self.line_annotations |
| 361 | + .push((Cell::new(usize::MAX), layer.into())); |
224 | 362 | self
|
225 | 363 | }
|
226 | 364 |
|
@@ -250,21 +388,35 @@ impl<'a> TextAnnotations<'a> {
|
250 | 388 | overlay
|
251 | 389 | }
|
252 | 390 |
|
253 |
| - pub(crate) fn annotation_lines_at(&self, char_idx: usize) -> usize { |
254 |
| - self.line_annotations |
255 |
| - .iter() |
256 |
| - .map(|layer| { |
257 |
| - let mut lines = 0; |
258 |
| - while let Some(annot) = layer.annotations.get(layer.current_index.get()) { |
259 |
| - if annot.anchor_char_idx == char_idx { |
260 |
| - layer.current_index.set(layer.current_index.get() + 1); |
261 |
| - lines += annot.height |
262 |
| - } else { |
263 |
| - break; |
| 391 | + pub(crate) fn process_virtual_text_anchors(&self, grapheme: &FormattedGrapheme) { |
| 392 | + for (next_anchor, layer) in &self.line_annotations { |
| 393 | + loop { |
| 394 | + match next_anchor.get().cmp(&grapheme.char_idx) { |
| 395 | + Ordering::Less => next_anchor |
| 396 | + .set(unsafe { layer.get().skip_concealed_anchors(grapheme.char_idx) }), |
| 397 | + Ordering::Equal => { |
| 398 | + next_anchor.set(unsafe { layer.get().process_anchor(grapheme) }) |
264 | 399 | }
|
265 |
| - } |
266 |
| - lines |
267 |
| - }) |
268 |
| - .sum() |
| 400 | + Ordering::Greater => break, |
| 401 | + }; |
| 402 | + } |
| 403 | + } |
| 404 | + } |
| 405 | + |
| 406 | + pub(crate) fn virtual_lines_at( |
| 407 | + &self, |
| 408 | + char_idx: usize, |
| 409 | + line_end_visual_pos: Position, |
| 410 | + doc_line: usize, |
| 411 | + ) -> usize { |
| 412 | + let mut virt_off = Position::new(0, 0); |
| 413 | + for (_, layer) in &self.line_annotations { |
| 414 | + virt_off += unsafe { |
| 415 | + layer |
| 416 | + .get() |
| 417 | + .insert_virtual_lines(char_idx, line_end_visual_pos + virt_off, doc_line) |
| 418 | + }; |
| 419 | + } |
| 420 | + virt_off.row |
269 | 421 | }
|
270 | 422 | }
|
0 commit comments