1#[cfg(not(target_family = "wasm"))]
16use std::time::Instant;
17use std::{
18 ops::Range,
19 sync::{Arc, Mutex, OnceLock},
20 time::Duration,
21};
22#[cfg(target_family = "wasm")]
23use web_time::Instant;
24
25use gpui::{Bounds, EntityId, Hsla, Pixels, SharedString};
26
27use super::{
28 document::ParsedDocument,
29 node::{BlockNode, Paragraph},
30 stream_fade::{TextLeaf, TextLeafKey, text_leaves},
31};
32
33#[derive(Clone)]
52pub struct RenderedText {
53 owner: EntityId,
54 revision: usize,
55 document: ParsedDocument,
56 index: Arc<OnceLock<RenderedIndex>>,
57}
58
59impl std::fmt::Debug for RenderedText {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("RenderedText")
62 .field("owner", &self.owner)
63 .field("revision", &self.revision)
64 .finish_non_exhaustive()
65 }
66}
67
68impl RenderedText {
69 pub(super) fn new(
71 owner: EntityId,
72 revision: usize,
73 document: ParsedDocument,
74 index: Arc<OnceLock<RenderedIndex>>,
75 ) -> Self {
76 Self {
77 owner,
78 revision,
79 document,
80 index,
81 }
82 }
83
84 pub fn as_str(&self) -> &str {
86 &self.index().text
87 }
88
89 pub fn len(&self) -> usize {
91 self.index().text.len()
92 }
93
94 pub fn is_empty(&self) -> bool {
96 self.index().text.is_empty()
97 }
98
99 pub(super) fn index(&self) -> &RenderedIndex {
100 self.index
101 .get_or_init(|| RenderedIndex::new(&self.document))
102 }
103}
104
105impl PartialEq for RenderedText {
106 fn eq(&self, other: &Self) -> bool {
107 self.owner == other.owner && self.revision == other.revision
108 }
109}
110
111impl Eq for RenderedText {}
112
113#[derive(Clone, Debug, PartialEq)]
118pub struct RangeHighlight {
119 range: Range<usize>,
120 background: Hsla,
121}
122
123impl RangeHighlight {
124 pub fn new(range: Range<usize>, background: impl Into<Hsla>) -> Self {
126 Self {
127 range,
128 background: background.into(),
129 }
130 }
131
132 pub fn range(&self) -> Range<usize> {
133 self.range.clone()
134 }
135
136 pub fn background(&self) -> Hsla {
137 self.background
138 }
139}
140
141#[derive(Clone, Copy, Debug, Eq, PartialEq)]
144#[non_exhaustive]
145pub enum RangeHighlightError {
146 Unsupported,
149 InvalidRange(usize),
152}
153
154impl std::fmt::Display for RangeHighlightError {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 match self {
157 Self::Unsupported => f.write_str("HTML views do not support ranges of their text"),
158 Self::InvalidRange(ix) => write!(f, "range {ix} is not a range of the text"),
159 }
160 }
161}
162
163impl std::error::Error for RangeHighlightError {}
164
165#[derive(Debug, Default)]
168pub(super) struct RenderedIndex {
169 text: SharedString,
170 leaves: Vec<LeafSpan>,
172 blocks: Vec<Range<usize>>,
174}
175
176#[derive(Debug)]
177struct LeafSpan {
178 range: Range<usize>,
180 key: TextLeafKey,
181 objects: Vec<Range<usize>>,
184}
185
186impl LeafSpan {
187 fn text_offset_near(&self, offset: usize) -> Option<usize> {
191 let object_at = |offset: usize| self.objects.iter().find(|object| object.contains(&offset));
192 let mut after = offset;
193 while let Some(object) = object_at(after) {
194 after = object.end;
195 }
196 if after < self.range.len() {
197 return Some(after);
198 }
199 let mut before = offset;
200 while let Some(object) = object_at(before) {
201 before = object.start.checked_sub(1)?;
202 }
203 Some(before)
204 }
205}
206
207impl RenderedIndex {
208 pub(super) fn new(document: &ParsedDocument) -> Self {
209 let mut builder = IndexBuilder::default();
210 let mut blocks = Vec::with_capacity(document.blocks.len());
211 for block in document.blocks.iter() {
212 let start = builder.text.len();
213 builder.push_block(block);
214 blocks.push(start..builder.text.len());
215 }
216 let index = Self {
217 text: builder.text.into(),
218 leaves: builder.leaves,
219 blocks,
220 };
221 debug_assert_eq!(index.text.as_ref(), document.text());
222 index
223 }
224
225 fn resolve(&self, range: &Range<usize>) -> Option<Vec<(TextLeafKey, Range<usize>)>> {
228 if range.start > range.end
229 || range.end > self.text.len()
230 || !self.text.is_char_boundary(range.start)
231 || !self.text.is_char_boundary(range.end)
232 {
233 return None;
234 }
235
236 let first = self
237 .leaves
238 .partition_point(|leaf| leaf.range.end <= range.start);
239 let mut pieces = Vec::new();
240 for leaf in &self.leaves[first..] {
241 if leaf.range.start >= range.end {
242 break;
243 }
244 let end = range.end.min(leaf.range.end) - leaf.range.start;
245 let mut cursor = range.start.max(leaf.range.start) - leaf.range.start;
246 for object in &leaf.objects {
247 if object.start >= end {
248 break;
249 }
250 if object.end <= cursor {
251 continue;
252 }
253 if object.start > cursor {
254 pieces.push((leaf.key, cursor..object.start));
255 }
256 cursor = object.end;
257 }
258 if cursor < end {
259 pieces.push((leaf.key, cursor..end));
260 }
261 }
262 Some(pieces)
263 }
264
265 fn locate(&self, range: &Range<usize>) -> Option<RevealTarget> {
270 if let Some((key, leaf_range)) = self.resolve(range)?.into_iter().next() {
271 return Some(RevealTarget::Line {
272 key,
273 offset: leaf_range.start,
274 });
275 }
276 let block_ix = self
277 .blocks
278 .partition_point(|block| block.end <= range.start)
279 .min(self.blocks.len().checked_sub(1)?);
280 let block_start = self.blocks[block_ix].start;
281 let ix = self
282 .leaves
283 .partition_point(|leaf| leaf.range.end <= range.start);
284 let leaf_offset = match self.leaves.get(ix) {
285 Some(leaf) if leaf.range.contains(&range.start) => {
286 Some((leaf, range.start - leaf.range.start))
287 }
288 _ => ix
292 .checked_sub(1)
293 .and_then(|ix| self.leaves.get(ix))
294 .filter(|leaf| leaf.range.start >= block_start)
295 .and_then(|leaf| {
296 let (last, _) = self.text[leaf.range.clone()].char_indices().last()?;
297 Some((leaf, last))
298 }),
299 };
300 if let Some((leaf, offset)) = leaf_offset
301 && let Some(offset) = leaf.text_offset_near(offset)
302 {
303 return Some(RevealTarget::Line {
304 key: leaf.key,
305 offset,
306 });
307 }
308 Some(RevealTarget::Block { ix: block_ix })
309 }
310}
311
312#[derive(Default)]
315struct IndexBuilder {
316 text: String,
317 leaves: Vec<LeafSpan>,
318}
319
320impl IndexBuilder {
321 fn push_block(&mut self, block: &BlockNode) {
322 let start = self.text.len();
323 match block {
324 BlockNode::Root { children, .. } | BlockNode::Blockquote { children, .. } => {
325 for child in children {
326 self.push_block(child);
327 }
328 }
329 BlockNode::List { children, .. } | BlockNode::ListItem { children, .. } => {
330 for child in children {
331 self.push_block(child);
332 }
333 return;
334 }
335 BlockNode::Paragraph(paragraph) => {
336 self.push_paragraph(
337 paragraph,
338 paragraph.span.map(|span| TextLeafKey::block(span.start)),
339 );
340 }
341 BlockNode::Heading { children, span, .. } => {
342 self.push_paragraph(children, span.map(|span| TextLeafKey::block(span.start)));
343 }
344 BlockNode::Table(table) => {
345 let mut ordinal = 0;
346 for row in table.children.iter().filter(|row| !row.children.is_empty()) {
347 for (ix, cell) in row.children.iter().enumerate() {
348 if ix > 0 {
349 self.text.push(' ');
350 }
351 self.push_paragraph(
352 &cell.children,
353 table
354 .span
355 .map(|span| TextLeafKey::table_cell(span.start, ordinal)),
356 );
357 ordinal += 1;
358 }
359 self.text.push('\n');
360 }
361 }
362 BlockNode::CodeBlock(code_block) => {
363 self.push_leaf(
364 &code_block.code(),
365 code_block.span.map(|span| TextLeafKey::block(span.start)),
366 Vec::new(),
367 );
368 }
369 BlockNode::Custom(node) => self.text.push_str(node.as_text()),
370 BlockNode::Definition { .. }
371 | BlockNode::Break { .. }
372 | BlockNode::HorizontalRule { .. }
373 | BlockNode::Unknown => {}
374 }
375 if self.text.len() > start {
376 self.text.push('\n');
377 }
378 }
379
380 fn push_paragraph(&mut self, paragraph: &Paragraph, key: Option<TextLeafKey>) {
381 let mut text = String::new();
382 let mut objects = Vec::new();
383 for child in ¶graph.children {
384 if child.custom.is_some() {
385 objects.push(text.len()..text.len() + child.text.len());
386 }
387 text.push_str(&child.text);
388 }
389 self.push_leaf(&text, key, objects);
390 }
391
392 fn push_leaf(&mut self, text: &str, key: Option<TextLeafKey>, objects: Vec<Range<usize>>) {
393 let start = self.text.len();
394 self.text.push_str(text);
395 if let Some(key) = key
396 && !text.is_empty()
397 {
398 self.leaves.push(LeafSpan {
399 range: start..self.text.len(),
400 key,
401 objects,
402 });
403 }
404 }
405}
406
407fn table_row_source_ends(blocks: &[BlockNode], rows: &mut Vec<(TextLeafKey, Option<usize>)>) {
410 for block in blocks {
411 match block {
412 BlockNode::Table(table) => {
413 let Some(span) = table.span else {
414 continue;
415 };
416 let mut cell_count = 0;
417 for row in &table.children {
418 if row.children.is_empty() {
419 continue;
420 }
421 cell_count += row.children.len();
422 let end = row
423 .children
424 .iter()
425 .filter_map(|cell| paragraph_source_end(&cell.children))
426 .max();
427 rows.push((TextLeafKey::table_cell(span.start, cell_count - 1), end));
428 }
429 }
430 BlockNode::Root { children, .. }
431 | BlockNode::Blockquote { children, .. }
432 | BlockNode::List { children, .. }
433 | BlockNode::ListItem { children, .. } => table_row_source_ends(children, rows),
434 _ => {}
435 }
436 }
437}
438
439fn paragraph_source_end(paragraph: &Paragraph) -> Option<usize> {
441 paragraph
442 .children
443 .iter()
444 .flat_map(|node| {
445 node.source_segments
446 .iter()
447 .map(|segment| segment.source.end)
448 .chain(
449 node.custom
450 .as_ref()
451 .and_then(|custom| custom.source_range())
452 .map(|range| range.end),
453 )
454 })
455 .max()
456}
457
458#[derive(Debug, Default)]
461pub(crate) struct RangeHighlightFrame {
462 leaves: Vec<(TextLeafKey, Vec<(Range<usize>, Hsla)>)>,
465}
466
467impl RangeHighlightFrame {
468 pub(super) fn new(
470 text: &RenderedText,
471 highlights: impl IntoIterator<Item = RangeHighlight>,
472 ) -> Result<Option<Self>, RangeHighlightError> {
473 let mut pieces = Vec::new();
474 for (ix, highlight) in highlights.into_iter().enumerate() {
475 let leaf_ranges = text
476 .index()
477 .resolve(&highlight.range)
478 .ok_or(RangeHighlightError::InvalidRange(ix))?;
479 pieces.extend(
480 leaf_ranges
481 .into_iter()
482 .map(|(key, range)| (key, range, highlight.background)),
483 );
484 }
485
486 pieces.sort_by_key(|(key, _, _)| *key);
488 let mut leaves: Vec<(TextLeafKey, Vec<(Range<usize>, Hsla)>)> = Vec::new();
489 for (key, range, background) in pieces {
490 match leaves.last_mut() {
491 Some((last, backgrounds)) if *last == key => backgrounds.push((range, background)),
492 _ => leaves.push((key, vec![(range, background)])),
493 }
494 }
495 Ok((!leaves.is_empty()).then_some(Self { leaves }))
496 }
497
498 pub(crate) fn backgrounds(&self, key: TextLeafKey) -> &[(Range<usize>, Hsla)] {
500 self.leaves
501 .binary_search_by_key(&key, |(leaf, _)| *leaf)
502 .map_or(&[], |ix| self.leaves[ix].1.as_slice())
503 }
504
505 pub(super) fn remap(&self, remap: &LeafRemap) -> Option<Self> {
509 let mut leaves = self
510 .leaves
511 .iter()
512 .filter_map(|(key, backgrounds)| {
513 let (new_key, unchanged) = remap.leaf(*key)?;
514 let clipped = backgrounds
515 .iter()
516 .filter(|(range, _)| range.start < unchanged)
517 .map(|(range, background)| (range.start..range.end.min(unchanged), *background))
518 .collect::<Vec<_>>();
519 (!clipped.is_empty()).then_some((new_key, clipped))
520 })
521 .collect::<Vec<_>>();
522 leaves.sort_by_key(|(key, _)| *key);
524 (!leaves.is_empty()).then_some(Self { leaves })
525 }
526}
527
528pub(super) struct LeafRemap<'a> {
536 old_len: usize,
537 new_len: usize,
538 tail_start: Option<usize>,
541 unchanged_prefix: usize,
542 unchanged_suffix: usize,
543 old_leaves: Vec<(TextLeafKey, TextLeaf<'a>)>,
544 new_leaves: Vec<(TextLeafKey, TextLeaf<'a>)>,
545 table_rows: Vec<(TextLeafKey, Option<usize>)>,
547}
548
549impl<'a> LeafRemap<'a> {
550 pub(super) fn new(old: &'a ParsedDocument, new: &'a ParsedDocument, tail_only: bool) -> Self {
553 let (old_len, new_len) = (old.source.len(), new.source.len());
554 let tail_start = tail_only.then(|| {
557 old.blocks
558 .last()
559 .and_then(BlockNode::span)
560 .map_or(old_len, |span| span.start)
561 });
562 let (unchanged_prefix, unchanged_suffix) = if tail_only {
563 (old_len, 0)
564 } else {
565 let prefix = old
566 .source
567 .bytes()
568 .zip(new.source.bytes())
569 .take_while(|(old, new)| old == new)
570 .count();
571 let shorter = old_len.min(new_len);
572 if prefix == shorter {
573 (prefix, 0)
575 } else {
576 let suffix = old
580 .source
581 .bytes()
582 .rev()
583 .zip(new.source.bytes().rev())
584 .take_while(|(old, new)| old == new)
585 .count()
586 .min(shorter);
587 (prefix.min(shorter - suffix), suffix)
588 }
589 };
590
591 fn leaves_from(
592 document: &ParsedDocument,
593 tail_start: Option<usize>,
594 ) -> Vec<(TextLeafKey, TextLeaf<'_>)> {
595 let mut leaves = Vec::new();
596 for block in document.blocks.iter().rev() {
597 if let Some(tail_start) = tail_start
598 && block.span().is_none_or(|span| span.start < tail_start)
599 {
600 break;
601 }
602 text_leaves(block, &mut leaves);
603 }
604 leaves.sort_by_key(|(key, _)| *key);
605 leaves
606 }
607
608 let mut table_rows = Vec::new();
609 if !tail_only {
610 table_row_source_ends(&old.blocks, &mut table_rows);
611 table_rows.sort_by_key(|(key, _)| *key);
612 }
613
614 Self {
615 old_len,
616 new_len,
617 tail_start,
618 unchanged_prefix,
619 unchanged_suffix,
620 old_leaves: leaves_from(old, tail_start),
621 new_leaves: leaves_from(new, tail_start),
622 table_rows,
623 }
624 }
625
626 pub(super) fn leaf(&self, key: TextLeafKey) -> Option<(TextLeafKey, usize)> {
629 if self
630 .tail_start
631 .is_some_and(|tail_start| key.block_start() < tail_start)
632 {
633 return Some((key, usize::MAX));
634 }
635 let new_key = key.moved_to(self.moved(key.block_start())?);
636 let old_leaf = Self::find(&self.old_leaves, key)?;
637 if key.cell_ix().is_some()
641 && key.block_start() < self.unchanged_prefix
642 && self.tail_start.is_none()
643 && self
644 .row_source_end(key)
645 .is_none_or(|end| end > self.unchanged_prefix)
646 {
647 return None;
648 }
649 let new_leaf = Self::find(&self.new_leaves, new_key)?;
650 Some((new_key, new_leaf.common_prefix_len(old_leaf)))
651 }
652
653 fn row_source_end(&self, key: TextLeafKey) -> Option<usize> {
654 let ix = self.table_rows.partition_point(|(last, _)| *last < key);
655 let (last, end) = self.table_rows.get(ix)?;
656 if last.block_start() != key.block_start() {
657 return None;
658 }
659 *end
660 }
661
662 fn moved(&self, start: usize) -> Option<usize> {
665 if start < self.unchanged_prefix {
666 Some(start)
667 } else if start >= self.old_len - self.unchanged_suffix {
668 Some(start + self.new_len - self.old_len)
669 } else {
670 None
671 }
672 }
673
674 fn find<'b>(
675 leaves: &'b [(TextLeafKey, TextLeaf<'a>)],
676 key: TextLeafKey,
677 ) -> Option<&'b TextLeaf<'a>> {
678 let ix = leaves.binary_search_by_key(&key, |(leaf, _)| *leaf).ok()?;
679 Some(&leaves[ix].1)
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use gpui::hsla;
686
687 use super::{LeafRemap, RangeHighlight, RangeHighlightFrame, TextLeafKey};
688 use crate::text::{document::ParsedDocument, format::markdown, node::NodeContext};
689
690 fn parse(source: &str) -> ParsedDocument {
691 markdown::parse(source, &mut NodeContext::default()).unwrap()
692 }
693
694 #[test]
695 fn table_row_index_keeps_empty_cells_in_their_row() {
696 let source = "| a | b |\n|---|---|\n| é | |\n| | |\n| c | d |\n";
697 let document = parse(source);
698 let remap = LeafRemap::new(&document, &document, false);
699 assert_eq!(remap.table_rows.len(), 4);
700 let ends = [Some("b"), Some("é"), None, Some("d")];
701 for (row, text) in ends.into_iter().enumerate() {
702 let expected = text.map(|text| source.find(text).unwrap() + text.len());
703 for column in 0..2 {
704 let key = TextLeafKey::table_cell(0, row * 2 + column);
705 assert_eq!(remap.row_source_end(key), expected, "{key:?}");
706 }
707 }
708 assert_eq!(remap.row_source_end(TextLeafKey::table_cell(0, 8)), None);
709 }
710
711 #[test]
712 fn table_row_index_finds_nested_tables_without_crossing_between_them() {
713 let source = concat!(
714 "| a |\n|---|\n| b |\n\n",
715 "> | c |\n> |---|\n> | d |\n\n",
716 "- | e |\n |---|\n | f |\n",
717 );
718 let document = parse(source);
719 let remap = LeafRemap::new(&document, &document, false);
720 assert_eq!(remap.table_rows.len(), 6);
721 for (header, body) in [("a", "b"), ("c", "d"), ("e", "f")] {
722 let start = source.find(&format!("| {header} |")).unwrap();
723 for (cell, text) in [header, body].into_iter().enumerate() {
724 let key = TextLeafKey::table_cell(start, cell);
725 assert_eq!(
726 remap.row_source_end(key),
727 Some(source.find(text).unwrap() + text.len()),
728 );
729 }
730 assert_eq!(
731 remap.row_source_end(TextLeafKey::table_cell(start, 2)),
732 None
733 );
734 assert_eq!(
735 remap.row_source_end(TextLeafKey::table_cell(start + 1, 0)),
736 None
737 );
738 }
739 }
740
741 #[test]
742 fn long_and_wide_tables_remap_highlights_by_whole_rows() {
743 for (rows, columns) in [(4096, 1), (2, 1024), (64, 16)] {
744 let row = format!("|{}\n", " x |".repeat(columns));
745 let separator = format!("|{}\n", "---|".repeat(columns));
746 let source = format!("{row}{separator}{}", row.repeat(rows));
747 let old = parse(&source);
748 let mut changed = source.clone();
749 let edit = row.len() + separator.len() + row.rfind('x').unwrap();
752 changed.replace_range(edit..edit + 1, "y");
753 let new = parse(&changed);
754 let remap = LeafRemap::new(&old, &new, false);
755 assert_eq!(remap.table_rows.len(), rows + 1);
756 let frame = RangeHighlightFrame {
757 leaves: remap
758 .old_leaves
759 .iter()
760 .map(|(key, _)| (*key, vec![(0..1, hsla(0.15, 1., 0.5, 0.4))]))
761 .collect(),
762 };
763 assert_eq!(frame.leaves.len(), (rows + 1) * columns);
764 let kept = frame.remap(&remap).unwrap();
765 assert_eq!(kept.leaves.len(), columns);
766 for column in 0..columns {
767 assert_eq!(
768 kept.backgrounds(TextLeafKey::table_cell(0, column)),
769 &[(0..1, hsla(0.15, 1., 0.5, 0.4))],
770 );
771 }
772 }
773 }
774
775 #[test]
776 fn append_remapping_does_not_build_a_table_row_index() {
777 let source = "| a |\n|---|\n| b |\n\n| c |\n|---|\n| d |\n";
778 let old = parse(source);
779 let new = parse(&format!("{source}| e |\n"));
780 let remap = LeafRemap::new(&old, &new, true);
781 assert!(remap.table_rows.is_empty());
782 let first = TextLeafKey::table_cell(0, 0);
783 assert_eq!(remap.leaf(first), Some((first, usize::MAX)));
784 let last_start = source.find("| c |").unwrap();
785 for cell in 0..2 {
786 let key = TextLeafKey::table_cell(last_start, cell);
787 assert_eq!(remap.leaf(key), Some((key, 1)));
788 }
789 }
790
791 #[test]
792 fn a_position_in_an_inline_object_moves_onto_text() {
793 use super::{LeafSpan, TextLeafKey};
794 let leaf = |len: usize| LeafSpan {
796 range: 10..10 + len,
797 key: TextLeafKey::block(0),
798 objects: vec![2..4, 4..6],
799 };
800 assert_eq!(leaf(8).text_offset_near(1), Some(1));
801 assert_eq!(leaf(8).text_offset_near(3), Some(6));
803 assert_eq!(leaf(8).text_offset_near(5), Some(6));
804 assert_eq!(leaf(6).text_offset_near(5), Some(1));
806 }
807
808 #[test]
809 fn range_highlight_requires_a_background() {
810 let color = hsla(0.15, 1., 0.5, 0.4);
811 let highlight = RangeHighlight::new(2..5, color);
812 assert_eq!(highlight.range(), 2..5);
813 assert_eq!(highlight.background(), color);
814 }
815}
816
817#[derive(Clone, Copy, Debug, PartialEq)]
819enum RevealTarget {
820 Line { key: TextLeafKey, offset: usize },
822 Block { ix: usize },
824}
825
826const REVEAL_TIMEOUT: Duration = Duration::from_secs(1);
830
831const REVEAL_ATTEMPTS: usize = 8;
834
835#[derive(Clone, Copy, Debug)]
838struct RevealReport {
839 line: Bounds<Pixels>,
840 visible: bool,
841}
842
843#[derive(Debug)]
851pub(super) struct PendingReveal {
852 target: RevealTarget,
853 requested_at: Instant,
854 report: Arc<Mutex<Option<RevealReport>>>,
857 attempts: usize,
858}
859
860pub(super) enum RevealProgress {
862 Shown,
864 Hidden(Bounds<Pixels>),
866 NotLaidOut,
868}
869
870impl PendingReveal {
871 pub(super) fn new(text: &RenderedText, range: &Range<usize>, now: Instant) -> Option<Self> {
874 Some(Self {
875 target: text.index().locate(range)?,
876 requested_at: now,
877 report: Arc::default(),
878 attempts: 0,
879 })
880 }
881
882 pub(super) fn is_expired(&self, now: Instant) -> bool {
883 now.saturating_duration_since(self.requested_at) > REVEAL_TIMEOUT
884 || self.attempts >= REVEAL_ATTEMPTS
885 }
886
887 pub(super) fn is_block(&self) -> bool {
889 matches!(self.target, RevealTarget::Block { .. })
890 }
891
892 pub(super) fn block_ix(&self, document: &ParsedDocument) -> Option<usize> {
894 match self.target {
895 RevealTarget::Line { key, .. } => document.blocks.iter().rposition(|block| {
896 block
897 .span()
898 .is_some_and(|span| span.start <= key.block_start())
899 }),
900 RevealTarget::Block { ix } => (ix < document.blocks.len()).then_some(ix),
901 }
902 }
903
904 pub(super) fn was_laid_out(&self) -> bool {
906 self.report.lock().is_ok_and(|report| report.is_some())
907 }
908
909 pub(super) fn request(&self) -> Option<RevealRequest> {
912 if let Ok(mut report) = self.report.lock() {
913 *report = None;
914 }
915 let RevealTarget::Line { key, offset } = self.target else {
916 return None;
917 };
918 Some(RevealRequest {
919 key,
920 offset,
921 report: self.report.clone(),
922 })
923 }
924
925 pub(super) fn progress(&mut self) -> RevealProgress {
928 let report = self.report.lock().ok().and_then(|report| *report);
929 match report {
930 Some(report) if report.visible => RevealProgress::Shown,
931 Some(report) => {
932 self.attempts += 1;
933 RevealProgress::Hidden(report.line)
934 }
935 None => RevealProgress::NotLaidOut,
936 }
937 }
938
939 pub(super) fn remap(mut self, remap: &LeafRemap) -> Option<Self> {
942 let RevealTarget::Line { key, offset } = self.target else {
943 return None;
944 };
945 let (key, unchanged) = remap.leaf(key)?;
946 (offset < unchanged).then_some(())?;
947 self.target = RevealTarget::Line { key, offset };
948 Some(self)
949 }
950}
951
952#[derive(Clone, Debug)]
955pub(crate) struct RevealRequest {
956 key: TextLeafKey,
957 offset: usize,
958 report: Arc<Mutex<Option<RevealReport>>>,
959}
960
961impl RevealRequest {
962 pub(crate) fn at(
965 &self,
966 key: Option<TextLeafKey>,
967 start: usize,
968 end: usize,
969 ) -> Option<RevealAt> {
970 if key != Some(self.key) {
971 return None;
972 }
973 RevealAt {
974 offset: self.offset,
975 report: self.report.clone(),
976 }
977 .rebase(start, end)
978 }
979}
980
981#[derive(Clone, Debug)]
983pub(crate) struct RevealAt {
984 offset: usize,
985 report: Arc<Mutex<Option<RevealReport>>>,
986}
987
988impl RevealAt {
989 pub(crate) fn offset(&self) -> usize {
990 self.offset
991 }
992
993 pub(crate) fn rebase(&self, start: usize, end: usize) -> Option<Self> {
996 (start..end).contains(&self.offset).then(|| Self {
997 offset: self.offset - start,
998 report: self.report.clone(),
999 })
1000 }
1001
1002 pub(crate) fn clamp(&self, start: usize, end: usize) -> Self {
1006 Self {
1007 offset: self.offset.clamp(start, end) - start,
1008 report: self.report.clone(),
1009 }
1010 }
1011
1012 pub(crate) fn report(&self, line: Bounds<Pixels>, visible: bool) {
1015 if let Ok(mut report) = self.report.lock() {
1016 *report = Some(RevealReport { line, visible });
1017 }
1018 }
1019}