1#[cfg(not(target_family = "wasm"))]
22use std::time::Instant;
23use std::{
24 ops::Range,
25 sync::{Arc, Mutex, OnceLock},
26 time::Duration,
27};
28#[cfg(target_family = "wasm")]
29use web_time::Instant;
30
31use gpui::{Bounds, EntityId, Hsla, Pixels, SharedString};
32
33use super::{
34 document::ParsedDocument,
35 node::{BlockNode, Paragraph, SourceSegment},
36 stream_fade::{TextLeaf, TextLeafKey, text_leaves},
37};
38
39#[derive(Clone)]
58pub struct RenderedText {
59 owner: EntityId,
60 revision: usize,
61 document: ParsedDocument,
62 index: Arc<OnceLock<RenderedIndex>>,
63}
64
65impl std::fmt::Debug for RenderedText {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.debug_struct("RenderedText")
68 .field("owner", &self.owner)
69 .field("revision", &self.revision)
70 .finish_non_exhaustive()
71 }
72}
73
74impl RenderedText {
75 pub(super) fn new(
77 owner: EntityId,
78 revision: usize,
79 document: ParsedDocument,
80 index: Arc<OnceLock<RenderedIndex>>,
81 ) -> Self {
82 Self {
83 owner,
84 revision,
85 document,
86 index,
87 }
88 }
89
90 pub fn as_str(&self) -> &str {
92 &self.index().text
93 }
94
95 pub fn len(&self) -> usize {
97 self.index().text.len()
98 }
99
100 pub fn is_empty(&self) -> bool {
102 self.index().text.is_empty()
103 }
104
105 pub fn source(&self) -> &str {
114 &self.document.source
115 }
116
117 pub fn range_for_source(&self, source: Range<usize>) -> Option<Range<usize>> {
149 let text = self.source();
150 if source.start >= source.end
151 || source.end > text.len()
152 || !text.is_char_boundary(source.start)
153 || !text.is_char_boundary(source.end)
154 {
155 return None;
156 }
157 self.index().range_for_source(&source)
158 }
159
160 pub(super) fn index(&self) -> &RenderedIndex {
161 self.index
162 .get_or_init(|| RenderedIndex::new(&self.document))
163 }
164}
165
166impl PartialEq for RenderedText {
167 fn eq(&self, other: &Self) -> bool {
168 self.owner == other.owner && self.revision == other.revision
169 }
170}
171
172impl Eq for RenderedText {}
173
174#[derive(Clone, Debug, PartialEq)]
179pub struct RangeHighlight {
180 range: Range<usize>,
181 background: Hsla,
182}
183
184impl RangeHighlight {
185 pub fn new(range: Range<usize>, background: impl Into<Hsla>) -> Self {
187 Self {
188 range,
189 background: background.into(),
190 }
191 }
192
193 pub fn range(&self) -> Range<usize> {
194 self.range.clone()
195 }
196
197 pub fn background(&self) -> Hsla {
198 self.background
199 }
200}
201
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
205#[non_exhaustive]
206pub enum RangeHighlightError {
207 Unsupported,
210 InvalidRange(usize),
213}
214
215impl std::fmt::Display for RangeHighlightError {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 match self {
218 Self::Unsupported => f.write_str("HTML views do not support ranges of their text"),
219 Self::InvalidRange(ix) => write!(f, "range {ix} is not a range of the text"),
220 }
221 }
222}
223
224impl std::error::Error for RangeHighlightError {}
225
226#[derive(Debug, Default)]
229pub(super) struct RenderedIndex {
230 text: SharedString,
231 leaves: Vec<LeafSpan>,
233 blocks: Vec<Range<usize>>,
235 surface_customs: Vec<(Range<usize>, TextLeafKey)>,
238 source_map: Vec<SourceSegment>,
242}
243
244#[derive(Debug)]
245struct LeafSpan {
246 range: Range<usize>,
248 key: TextLeafKey,
249 objects: Vec<Range<usize>>,
252}
253
254impl LeafSpan {
255 fn text_offset_near(&self, offset: usize) -> Option<usize> {
259 let object_at = |offset: usize| self.objects.iter().find(|object| object.contains(&offset));
260 let mut after = offset;
261 while let Some(object) = object_at(after) {
262 after = object.end;
263 }
264 if after < self.range.len() {
265 return Some(after);
266 }
267 let mut before = offset;
268 while let Some(object) = object_at(before) {
269 before = object.start.checked_sub(1)?;
270 }
271 Some(before)
272 }
273}
274
275impl RenderedIndex {
276 pub(super) fn new(document: &ParsedDocument) -> Self {
277 let mut builder = IndexBuilder::default();
278 let mut blocks = Vec::with_capacity(document.blocks.len());
279 for block in document.blocks.iter() {
280 let start = builder.text.len();
281 builder.push_block(block);
282 blocks.push(start..builder.text.len());
283 }
284 let index = Self {
285 text: builder.text.into(),
286 leaves: builder.leaves,
287 blocks,
288 surface_customs: builder.surface_customs,
289 source_map: builder.source_map,
290 };
291 debug_assert_eq!(index.text.as_ref(), document.text());
292 index
293 }
294
295 fn resolve(&self, range: &Range<usize>) -> Option<Vec<(TextLeafKey, Range<usize>)>> {
298 if range.start > range.end
299 || range.end > self.text.len()
300 || !self.text.is_char_boundary(range.start)
301 || !self.text.is_char_boundary(range.end)
302 {
303 return None;
304 }
305
306 let first = self
307 .leaves
308 .partition_point(|leaf| leaf.range.end <= range.start);
309 let mut pieces = Vec::new();
310 for leaf in &self.leaves[first..] {
311 if leaf.range.start >= range.end {
312 break;
313 }
314 let end = range.end.min(leaf.range.end) - leaf.range.start;
315 let mut cursor = range.start.max(leaf.range.start) - leaf.range.start;
316 for object in &leaf.objects {
317 if object.start >= end {
318 break;
319 }
320 if object.end <= cursor {
321 continue;
322 }
323 if object.start > cursor {
324 pieces.push((leaf.key, cursor..object.start));
325 }
326 cursor = object.end;
327 }
328 if cursor < end {
329 pieces.push((leaf.key, cursor..end));
330 }
331 }
332 Some(pieces)
333 }
334
335 fn locate(&self, range: &Range<usize>) -> Option<RevealTarget> {
340 if let Some((key, leaf_range)) = self.resolve(range)?.into_iter().next() {
341 return Some(RevealTarget::Line {
342 key,
343 offset: leaf_range.start,
344 });
345 }
346 if let Some((_, key)) = self
347 .surface_customs
348 .iter()
349 .find(|(text, _)| text.contains(&range.start))
350 {
351 return Some(RevealTarget::Line {
352 key: *key,
353 offset: 0,
354 });
355 }
356 let block_ix = self
357 .blocks
358 .partition_point(|block| block.end <= range.start)
359 .min(self.blocks.len().checked_sub(1)?);
360 let block_start = self.blocks[block_ix].start;
361 let ix = self
362 .leaves
363 .partition_point(|leaf| leaf.range.end <= range.start);
364 let leaf_offset = match self.leaves.get(ix) {
365 Some(leaf) if leaf.range.contains(&range.start) => {
366 Some((leaf, range.start - leaf.range.start))
367 }
368 _ => ix
372 .checked_sub(1)
373 .and_then(|ix| self.leaves.get(ix))
374 .filter(|leaf| leaf.range.start >= block_start)
375 .and_then(|leaf| {
376 let (last, _) = self.text[leaf.range.clone()].char_indices().last()?;
377 Some((leaf, last))
378 }),
379 };
380 if let Some((leaf, offset)) = leaf_offset
381 && let Some(offset) = leaf.text_offset_near(offset)
382 {
383 return Some(RevealTarget::Line {
384 key: leaf.key,
385 offset,
386 });
387 }
388 Some(RevealTarget::Block { ix: block_ix })
389 }
390
391 fn range_for_source(&self, source: &Range<usize>) -> Option<Range<usize>> {
394 self.source_map
395 .iter()
396 .filter(|segment| {
397 segment.source.start < source.end && segment.source.end > source.start
398 })
399 .map(|segment| {
400 if !segment.linear {
401 return segment.rendered.clone();
402 }
403 let start = segment.source.start.max(source.start) - segment.source.start;
404 let end = segment.source.end.min(source.end) - segment.source.start;
405 self.text
406 .floor_char_boundary(segment.rendered.start + start)
407 ..self.text.ceil_char_boundary(segment.rendered.start + end)
408 })
409 .reduce(|found, range| found.start.min(range.start)..found.end.max(range.end))
410 }
411}
412
413#[derive(Default)]
416struct IndexBuilder {
417 text: String,
418 leaves: Vec<LeafSpan>,
419 source_map: Vec<SourceSegment>,
420 in_surface: bool,
421 surface_customs: Vec<(Range<usize>, TextLeafKey)>,
422}
423
424impl IndexBuilder {
425 fn push_block(&mut self, block: &BlockNode) {
426 let start = self.text.len();
427 match block {
428 BlockNode::Root { children, .. } | BlockNode::Blockquote { children, .. } => {
429 for child in children {
430 self.push_block(child);
431 }
432 }
433 BlockNode::Surface { children, .. } => {
434 self.in_surface = true;
435 for child in children {
436 self.push_block(child);
437 }
438 self.in_surface = false;
439 return;
440 }
441 BlockNode::List { children, .. } | BlockNode::ListItem { children, .. } => {
442 for child in children {
443 self.push_block(child);
444 }
445 return;
446 }
447 BlockNode::Paragraph(paragraph) => {
448 self.push_paragraph(
449 paragraph,
450 paragraph.span.map(|span| TextLeafKey::block(span.start)),
451 );
452 }
453 BlockNode::Heading { children, span, .. } => {
454 self.push_paragraph(children, span.map(|span| TextLeafKey::block(span.start)));
455 }
456 BlockNode::Table(table) => {
457 let mut ordinal = 0;
458 for row in table.children.iter().filter(|row| !row.children.is_empty()) {
459 for (ix, cell) in row.children.iter().enumerate() {
460 if ix > 0 {
461 self.text.push(' ');
462 }
463 self.push_paragraph(
464 &cell.children,
465 table
466 .span
467 .map(|span| TextLeafKey::table_cell(span.start, ordinal)),
468 );
469 ordinal += 1;
470 }
471 self.text.push('\n');
472 }
473 }
474 BlockNode::CodeBlock(code_block) => {
475 let code = code_block.code();
476 self.push_source_segments(self.text.len(), &code, &code_block.source_segments);
477 self.push_leaf(
478 &code,
479 code_block.span.map(|span| TextLeafKey::block(span.start)),
480 Vec::new(),
481 );
482 }
483 BlockNode::Custom { node, .. } => {
484 self.push_object_source(self.text.len(), node.as_text(), node.source_range());
485 if self.in_surface
486 && let Some(source) = node.source_range()
487 {
488 self.surface_customs.push((
489 self.text.len()..self.text.len() + node.as_text().len(),
490 TextLeafKey::block(source.start),
491 ));
492 }
493 self.text.push_str(node.as_text());
494 }
495 BlockNode::Definition { .. }
496 | BlockNode::Break { .. }
497 | BlockNode::HorizontalRule { .. }
498 | BlockNode::Unknown => {}
499 }
500 if self.text.len() > start {
501 self.text.push('\n');
502 }
503 }
504
505 fn push_paragraph(&mut self, paragraph: &Paragraph, key: Option<TextLeafKey>) {
506 let start = self.text.len();
507 let mut text = String::new();
508 let mut objects = Vec::new();
509 for child in ¶graph.children {
510 let offset = start + text.len();
511 if let Some(custom) = &child.custom {
512 objects.push(text.len()..text.len() + child.text.len());
513 self.push_object_source(offset, &child.text, custom.source_range());
514 } else {
515 self.push_source_segments(offset, &child.text, &child.source_segments);
516 }
517 text.push_str(&child.text);
518 }
519 self.push_leaf(&text, key, objects);
520 }
521
522 fn push_source_segments(&mut self, offset: usize, text: &str, segments: &[SourceSegment]) {
527 self.source_map.extend(
528 segments
529 .iter()
530 .filter(|segment| {
531 !segment.rendered.is_empty()
532 && !segment.source.is_empty()
533 && segment.rendered.end <= text.len()
534 && text.is_char_boundary(segment.rendered.start)
535 && text.is_char_boundary(segment.rendered.end)
536 })
537 .map(|segment| SourceSegment {
538 rendered: offset + segment.rendered.start..offset + segment.rendered.end,
539 ..segment.clone()
540 }),
541 );
542 }
543
544 fn push_object_source(&mut self, offset: usize, text: &str, source: Option<Range<usize>>) {
547 if let Some(source) = source {
548 self.push_source_segments(
549 offset,
550 text,
551 &[SourceSegment {
552 rendered: 0..text.len(),
553 source,
554 linear: false,
555 }],
556 );
557 }
558 }
559
560 fn push_leaf(&mut self, text: &str, key: Option<TextLeafKey>, objects: Vec<Range<usize>>) {
561 let start = self.text.len();
562 self.text.push_str(text);
563 if let Some(key) = key
564 && !text.is_empty()
565 {
566 self.leaves.push(LeafSpan {
567 range: start..self.text.len(),
568 key,
569 objects,
570 });
571 }
572 }
573}
574
575fn table_row_source_ends(blocks: &[BlockNode], rows: &mut Vec<(TextLeafKey, Option<usize>)>) {
578 for block in blocks {
579 match block {
580 BlockNode::Table(table) => {
581 let Some(span) = table.span else {
582 continue;
583 };
584 let mut cell_count = 0;
585 for row in &table.children {
586 if row.children.is_empty() {
587 continue;
588 }
589 cell_count += row.children.len();
590 let end = row
591 .children
592 .iter()
593 .filter_map(|cell| paragraph_source_end(&cell.children))
594 .max();
595 rows.push((TextLeafKey::table_cell(span.start, cell_count - 1), end));
596 }
597 }
598 BlockNode::Root { children, .. }
599 | BlockNode::Blockquote { children, .. }
600 | BlockNode::Surface { children, .. }
601 | BlockNode::List { children, .. }
602 | BlockNode::ListItem { children, .. } => table_row_source_ends(children, rows),
603 _ => {}
604 }
605 }
606}
607
608fn paragraph_source_end(paragraph: &Paragraph) -> Option<usize> {
610 paragraph
611 .children
612 .iter()
613 .flat_map(|node| {
614 node.source_segments
615 .iter()
616 .map(|segment| segment.source.end)
617 .chain(
618 node.custom
619 .as_ref()
620 .and_then(|custom| custom.source_range())
621 .map(|range| range.end),
622 )
623 })
624 .max()
625}
626
627#[derive(Debug, Default)]
630pub(crate) struct RangeHighlightFrame {
631 leaves: Vec<(TextLeafKey, Vec<(Range<usize>, Hsla)>)>,
634}
635
636impl RangeHighlightFrame {
637 pub(super) fn new(
639 text: &RenderedText,
640 highlights: impl IntoIterator<Item = RangeHighlight>,
641 ) -> Result<Option<Self>, RangeHighlightError> {
642 let mut pieces = Vec::new();
643 for (ix, highlight) in highlights.into_iter().enumerate() {
644 let leaf_ranges = text
645 .index()
646 .resolve(&highlight.range)
647 .ok_or(RangeHighlightError::InvalidRange(ix))?;
648 pieces.extend(
649 leaf_ranges
650 .into_iter()
651 .map(|(key, range)| (key, range, highlight.background)),
652 );
653 }
654
655 pieces.sort_by_key(|(key, _, _)| *key);
657 let mut leaves: Vec<(TextLeafKey, Vec<(Range<usize>, Hsla)>)> = Vec::new();
658 for (key, range, background) in pieces {
659 match leaves.last_mut() {
660 Some((last, backgrounds)) if *last == key => backgrounds.push((range, background)),
661 _ => leaves.push((key, vec![(range, background)])),
662 }
663 }
664 Ok((!leaves.is_empty()).then_some(Self { leaves }))
665 }
666
667 pub(crate) fn backgrounds(&self, key: TextLeafKey) -> &[(Range<usize>, Hsla)] {
669 self.leaves
670 .binary_search_by_key(&key, |(leaf, _)| *leaf)
671 .map_or(&[], |ix| self.leaves[ix].1.as_slice())
672 }
673
674 pub(super) fn remap(&self, remap: &LeafRemap) -> Option<Self> {
678 let mut leaves = self
679 .leaves
680 .iter()
681 .filter_map(|(key, backgrounds)| {
682 let (new_key, unchanged) = remap.leaf(*key)?;
683 let clipped = backgrounds
684 .iter()
685 .filter(|(range, _)| range.start < unchanged)
686 .map(|(range, background)| (range.start..range.end.min(unchanged), *background))
687 .collect::<Vec<_>>();
688 (!clipped.is_empty()).then_some((new_key, clipped))
689 })
690 .collect::<Vec<_>>();
691 leaves.sort_by_key(|(key, _)| *key);
693 (!leaves.is_empty()).then_some(Self { leaves })
694 }
695}
696
697pub(super) struct LeafRemap<'a> {
705 old_len: usize,
706 new_len: usize,
707 tail_start: Option<usize>,
710 unchanged_prefix: usize,
711 unchanged_suffix: usize,
712 old_leaves: Vec<(TextLeafKey, TextLeaf<'a>)>,
713 new_leaves: Vec<(TextLeafKey, TextLeaf<'a>)>,
714 table_rows: Vec<(TextLeafKey, Option<usize>)>,
716}
717
718impl<'a> LeafRemap<'a> {
719 pub(super) fn new(old: &'a ParsedDocument, new: &'a ParsedDocument, tail_only: bool) -> Self {
722 let (old_len, new_len) = (old.source.len(), new.source.len());
723 let tail_start = tail_only.then(|| {
726 old.blocks
727 .last()
728 .and_then(BlockNode::span)
729 .map_or(old_len, |span| span.start)
730 });
731 let (unchanged_prefix, unchanged_suffix) = if tail_only {
732 (old_len, 0)
733 } else {
734 let prefix = old
735 .source
736 .bytes()
737 .zip(new.source.bytes())
738 .take_while(|(old, new)| old == new)
739 .count();
740 let shorter = old_len.min(new_len);
741 if prefix == shorter {
742 (prefix, 0)
744 } else {
745 let suffix = old
749 .source
750 .bytes()
751 .rev()
752 .zip(new.source.bytes().rev())
753 .take_while(|(old, new)| old == new)
754 .count()
755 .min(shorter);
756 (prefix.min(shorter - suffix), suffix)
757 }
758 };
759
760 fn leaves_from(
761 document: &ParsedDocument,
762 tail_start: Option<usize>,
763 ) -> Vec<(TextLeafKey, TextLeaf<'_>)> {
764 let mut leaves = Vec::new();
765 for block in document.blocks.iter().rev() {
766 if let Some(tail_start) = tail_start
767 && block.span().is_none_or(|span| span.start < tail_start)
768 {
769 break;
770 }
771 text_leaves(block, &mut leaves);
772 }
773 leaves.sort_by_key(|(key, _)| *key);
774 leaves
775 }
776
777 let mut table_rows = Vec::new();
778 if !tail_only {
779 table_row_source_ends(&old.blocks, &mut table_rows);
780 table_rows.sort_by_key(|(key, _)| *key);
781 }
782
783 Self {
784 old_len,
785 new_len,
786 tail_start,
787 unchanged_prefix,
788 unchanged_suffix,
789 old_leaves: leaves_from(old, tail_start),
790 new_leaves: leaves_from(new, tail_start),
791 table_rows,
792 }
793 }
794
795 pub(super) fn leaf(&self, key: TextLeafKey) -> Option<(TextLeafKey, usize)> {
798 if self
799 .tail_start
800 .is_some_and(|tail_start| key.block_start() < tail_start)
801 {
802 return Some((key, usize::MAX));
803 }
804 let new_key = key.moved_to(self.moved(key.block_start())?);
805 let old_leaf = Self::find(&self.old_leaves, key)?;
806 if key.cell_ix().is_some()
810 && key.block_start() < self.unchanged_prefix
811 && self.tail_start.is_none()
812 && self
813 .row_source_end(key)
814 .is_none_or(|end| end > self.unchanged_prefix)
815 {
816 return None;
817 }
818 let new_leaf = Self::find(&self.new_leaves, new_key)?;
819 Some((new_key, new_leaf.common_prefix_len(old_leaf)))
820 }
821
822 fn row_source_end(&self, key: TextLeafKey) -> Option<usize> {
823 let ix = self.table_rows.partition_point(|(last, _)| *last < key);
824 let (last, end) = self.table_rows.get(ix)?;
825 if last.block_start() != key.block_start() {
826 return None;
827 }
828 *end
829 }
830
831 fn moved(&self, start: usize) -> Option<usize> {
834 if start < self.unchanged_prefix {
835 Some(start)
836 } else if start >= self.old_len - self.unchanged_suffix {
837 Some(start + self.new_len - self.old_len)
838 } else {
839 None
840 }
841 }
842
843 fn find<'b>(
844 leaves: &'b [(TextLeafKey, TextLeaf<'a>)],
845 key: TextLeafKey,
846 ) -> Option<&'b TextLeaf<'a>> {
847 let ix = leaves.binary_search_by_key(&key, |(leaf, _)| *leaf).ok()?;
848 Some(&leaves[ix].1)
849 }
850}
851
852#[cfg(test)]
853mod tests {
854 use std::{ops::Range, sync::Arc};
855
856 use gpui::{EntityId, hsla};
857
858 use super::{LeafRemap, RangeHighlight, RangeHighlightFrame, RenderedText, TextLeafKey};
859 use crate::text::{
860 document::ParsedDocument,
861 format,
862 node::{BlockNode, CodeBlock, NodeContext, Paragraph},
863 };
864
865 fn parse(markdown: &str) -> ParsedDocument {
866 format::markdown::parse(markdown, &mut NodeContext::default()).expect("parse Markdown")
867 }
868
869 #[test]
870 fn table_row_index_keeps_empty_cells_in_their_row() {
871 let source = "| a | b |\n|---|---|\n| é | |\n| | |\n| c | d |\n";
872 let document = parse(source);
873 let remap = LeafRemap::new(&document, &document, false);
874 assert_eq!(remap.table_rows.len(), 4);
875 let ends = [Some("b"), Some("é"), None, Some("d")];
876 for (row, text) in ends.into_iter().enumerate() {
877 let expected = text.map(|text| source.find(text).unwrap() + text.len());
878 for column in 0..2 {
879 let key = TextLeafKey::table_cell(0, row * 2 + column);
880 assert_eq!(remap.row_source_end(key), expected, "{key:?}");
881 }
882 }
883 assert_eq!(remap.row_source_end(TextLeafKey::table_cell(0, 8)), None);
884 }
885
886 #[test]
887 fn table_row_index_finds_nested_tables_without_crossing_between_them() {
888 let source = concat!(
889 "| a |\n|---|\n| b |\n\n",
890 "> | c |\n> |---|\n> | d |\n\n",
891 "- | e |\n |---|\n | f |\n",
892 );
893 let document = parse(source);
894 let remap = LeafRemap::new(&document, &document, false);
895 assert_eq!(remap.table_rows.len(), 6);
896 for (header, body) in [("a", "b"), ("c", "d"), ("e", "f")] {
897 let start = source.find(&format!("| {header} |")).unwrap();
898 for (cell, text) in [header, body].into_iter().enumerate() {
899 let key = TextLeafKey::table_cell(start, cell);
900 assert_eq!(
901 remap.row_source_end(key),
902 Some(source.find(text).unwrap() + text.len()),
903 );
904 }
905 assert_eq!(
906 remap.row_source_end(TextLeafKey::table_cell(start, 2)),
907 None
908 );
909 assert_eq!(
910 remap.row_source_end(TextLeafKey::table_cell(start + 1, 0)),
911 None
912 );
913 }
914 }
915
916 #[test]
917 fn long_and_wide_tables_remap_highlights_by_whole_rows() {
918 for (rows, columns) in [(4096, 1), (2, 1024), (64, 16)] {
919 let row = format!("|{}\n", " x |".repeat(columns));
920 let separator = format!("|{}\n", "---|".repeat(columns));
921 let source = format!("{row}{separator}{}", row.repeat(rows));
922 let old = parse(&source);
923 let mut changed = source.clone();
924 let edit = row.len() + separator.len() + row.rfind('x').unwrap();
927 changed.replace_range(edit..edit + 1, "y");
928 let new = parse(&changed);
929 let remap = LeafRemap::new(&old, &new, false);
930 assert_eq!(remap.table_rows.len(), rows + 1);
931 let frame = RangeHighlightFrame {
932 leaves: remap
933 .old_leaves
934 .iter()
935 .map(|(key, _)| (*key, vec![(0..1, hsla(0.15, 1., 0.5, 0.4))]))
936 .collect(),
937 };
938 assert_eq!(frame.leaves.len(), (rows + 1) * columns);
939 let kept = frame.remap(&remap).unwrap();
940 assert_eq!(kept.leaves.len(), columns);
941 for column in 0..columns {
942 assert_eq!(
943 kept.backgrounds(TextLeafKey::table_cell(0, column)),
944 &[(0..1, hsla(0.15, 1., 0.5, 0.4))],
945 );
946 }
947 }
948 }
949
950 #[test]
951 fn append_remapping_does_not_build_a_table_row_index() {
952 let source = "| a |\n|---|\n| b |\n\n| c |\n|---|\n| d |\n";
953 let old = parse(source);
954 let new = parse(&format!("{source}| e |\n"));
955 let remap = LeafRemap::new(&old, &new, true);
956 assert!(remap.table_rows.is_empty());
957 let first = TextLeafKey::table_cell(0, 0);
958 assert_eq!(remap.leaf(first), Some((first, usize::MAX)));
959 let last_start = source.find("| c |").unwrap();
960 for cell in 0..2 {
961 let key = TextLeafKey::table_cell(last_start, cell);
962 assert_eq!(remap.leaf(key), Some((key, 1)));
963 }
964 }
965
966 #[test]
967 fn a_position_in_an_inline_object_moves_onto_text() {
968 use super::{LeafSpan, TextLeafKey};
969 let leaf = |len: usize| LeafSpan {
971 range: 10..10 + len,
972 key: TextLeafKey::block(0),
973 objects: vec![2..4, 4..6],
974 };
975 assert_eq!(leaf(8).text_offset_near(1), Some(1));
976 assert_eq!(leaf(8).text_offset_near(3), Some(6));
978 assert_eq!(leaf(8).text_offset_near(5), Some(6));
979 assert_eq!(leaf(6).text_offset_near(5), Some(1));
981 }
982
983 #[test]
984 fn range_highlight_requires_a_background() {
985 let color = hsla(0.15, 1., 0.5, 0.4);
986 let highlight = RangeHighlight::new(2..5, color);
987 assert_eq!(highlight.range(), 2..5);
988 assert_eq!(highlight.background(), color);
989 }
990
991 fn rendered(document: ParsedDocument) -> RenderedText {
992 RenderedText::new(EntityId::from(1), 0, document, Arc::default())
993 }
994
995 fn nth(markdown: &str, needle: &str, nth: usize) -> Range<usize> {
998 let (start, _) = markdown
999 .match_indices(needle)
1000 .nth(nth)
1001 .unwrap_or_else(|| panic!("{needle:?} occurs {nth} times in {markdown:?}"));
1002 start..start + needle.len()
1003 }
1004
1005 fn converted(markdown: &str, source: Range<usize>) -> Option<String> {
1007 let text = rendered(parse(markdown));
1008 let range = text.range_for_source(source)?;
1009 Some(text.as_str()[range].to_string())
1010 }
1011
1012 fn converted_needle(markdown: &str, needle: &str) -> Option<String> {
1014 converted(markdown, nth(markdown, needle, 0))
1015 }
1016
1017 #[test]
1018 fn source_rendering_nothing_converts_to_none() {
1019 for (markdown, needle) in [
1020 ("# Title", "# "),
1021 ("hello **world**", "**"),
1022 ("a ~~b~~ c", "~~"),
1023 ("- item", "- "),
1024 ("1. item", "1. "),
1025 ("- [x] done", "[x] "),
1026 ("> quote", "> "),
1027 ("```rust\nlet x\n```", "```rust\n"),
1028 ("```rust\nlet x\n```", "\n```"),
1029 ("| a | b |\n|---|---|\n| c | d |", "|---|---|"),
1030 ("| a | b |\n|---|---|\n| c | d |", " | "),
1031 (
1032 "see [docs](https://example.com) now",
1033 "(https://example.com)",
1034 ),
1035 ("a  b", ""),
1036 ("a\n\n---\n\nb", "---"),
1037 (
1038 "para\n\n[ref]: https://example.com",
1039 "[ref]: https://example.com",
1040 ),
1041 ] {
1042 assert_eq!(
1043 converted_needle(markdown, needle),
1044 None,
1045 "{needle:?} of {markdown:?}"
1046 );
1047 }
1048 }
1049
1050 #[test]
1051 fn delimiters_in_a_range_add_nothing() {
1052 let markdown = "hello **world** and `code`";
1053 assert_eq!(
1054 converted(markdown, 0..markdown.len()).as_deref(),
1055 Some("hello world and code")
1056 );
1057 assert_eq!(
1058 converted_needle(markdown, "**world**").as_deref(),
1059 Some("world")
1060 );
1061 assert_eq!(
1062 converted_needle(markdown, "o **wor").as_deref(),
1063 Some("o wor")
1064 );
1065 assert_eq!(
1066 converted_needle(markdown, "`code`").as_deref(),
1067 Some("code")
1068 );
1069 assert_eq!(
1070 converted_needle("# **Title**", "# **Title**").as_deref(),
1071 Some("Title")
1072 );
1073 assert_eq!(
1074 converted_needle("see [the docs](https://x.y) now", "[the docs](https://x.y)")
1075 .as_deref(),
1076 Some("the docs")
1077 );
1078 }
1079
1080 #[test]
1081 fn repeated_text_converts_to_the_occurrence_addressed() {
1082 let markdown = "foo **foo** foo";
1083 let text = rendered(parse(markdown));
1084 assert_eq!(text.as_str(), "foo foo foo\n");
1085 assert_eq!(text.range_for_source(nth(markdown, "foo", 0)), Some(0..3));
1086 assert_eq!(text.range_for_source(nth(markdown, "foo", 1)), Some(4..7));
1087 assert_eq!(text.range_for_source(nth(markdown, "foo", 2)), Some(8..11));
1088 }
1089
1090 #[test]
1091 fn fenced_code_backslashes_convert_individually() {
1092 let markdown = "```\na\\\\b\n```";
1093 let text = rendered(parse(markdown));
1094 assert_eq!(text.range_for_source(5..6), Some(1..2));
1095 assert_eq!(text.range_for_source(6..7), Some(2..3));
1096 assert_eq!(text.range_for_source(5..7), Some(1..3));
1097 }
1098
1099 #[test]
1100 fn literal_code_escapes_convert_individually() {
1101 for (markdown, first, second, rendered_start) in [
1102 ("`a\\\\b`", 2, 3, 1),
1103 (r"`a\*b`", 2, 3, 1),
1104 (" one\n a\\\\b", 13, 14, 5),
1105 ("- ```\n a\\\\b\n ```", 9, 10, 1),
1106 ("> ```\n> a\\\\b\n> ```", 9, 10, 1),
1107 ("```\na\\*b\n```", 5, 6, 1),
1108 ("```\na\\\\b\r\n```", 5, 6, 1),
1109 ("```\na\\\\\nb\n```", 5, 6, 1),
1110 ] {
1111 let text = rendered(parse(markdown));
1112 assert_eq!(
1113 text.range_for_source(first..first + 1),
1114 Some(rendered_start..rendered_start + 1),
1115 "{markdown:?}"
1116 );
1117 assert_eq!(
1118 text.range_for_source(second..second + 1),
1119 Some(rendered_start + 1..rendered_start + 2),
1120 "{markdown:?}"
1121 );
1122 }
1123 let text = rendered(parse(r"a\*b"));
1124 assert_eq!(text.range_for_source(1..2), Some(1..2));
1125 assert_eq!(text.range_for_source(2..3), Some(1..2));
1126 let text = rendered(parse("```\na\\\\\nb\n```"));
1127 assert_eq!(text.range_for_source(7..8), Some(3..4));
1128 assert_eq!(text.range_for_source(8..9), Some(4..5));
1129 }
1130
1131 #[test]
1132 fn a_character_converts_when_any_of_its_source_is_in_the_range() {
1133 assert_eq!(converted_needle("a & b", "amp").as_deref(), Some("&"));
1135 assert_eq!(converted_needle("a & b", "&").as_deref(), Some("&"));
1136 assert_eq!(
1137 converted_needle("AB", "B").as_deref(),
1138 Some("B")
1139 );
1140 assert_eq!(converted_needle(r"a \* b", r"\").as_deref(), Some("*"));
1142 assert_eq!(converted_needle(r"a \* b", "*").as_deref(), Some("*"));
1143 assert_eq!(converted_needle(r"\*abc", "b").as_deref(), Some("b"));
1146 assert_eq!(converted_needle(r"**\*abc**", "*a").as_deref(), Some("*a"));
1147 assert_eq!(converted_needle("soft\nbreak", "\n").as_deref(), Some(" "));
1149 assert_eq!(
1150 converted_needle("soft\r\nbreak", "\r\n").as_deref(),
1151 Some(" ")
1152 );
1153 assert_eq!(
1154 converted_needle("soft\r\nbreak", "\n").as_deref(),
1155 Some(" ")
1156 );
1157 }
1158
1159 #[test]
1160 fn a_decoded_entity_converts_only_as_a_whole() {
1161 let markdown = "a ∾̳ b";
1164 let text = rendered(parse(markdown));
1165 assert_eq!(text.as_str(), "a ∾̳ b\n");
1166 assert_eq!(converted_needle(markdown, "a").as_deref(), Some("a"));
1167 assert_eq!(converted_needle(markdown, "b").as_deref(), Some("b"));
1168 assert_eq!(converted_needle(markdown, "cE").as_deref(), Some("∾̳"));
1169 assert_eq!(converted_needle(markdown, "∾̳").as_deref(), Some("∾̳"));
1170 assert_eq!(converted_needle(markdown, "a &a").as_deref(), Some("a ∾̳"));
1171 }
1172
1173 #[test]
1174 fn multibyte_text_converts_on_character_boundaries() {
1175 let markdown = "中文 **粗体** 🎉 é";
1176 assert_eq!(converted_needle(markdown, "粗").as_deref(), Some("粗"));
1177 assert_eq!(
1178 converted_needle(markdown, "文 **粗").as_deref(),
1179 Some("文 粗")
1180 );
1181 assert_eq!(converted_needle(markdown, "🎉").as_deref(), Some("🎉"));
1182 assert_eq!(converted_needle(markdown, "é").as_deref(), Some("é"));
1183 let split = nth(markdown, "粗", 0);
1185 assert_eq!(converted(markdown, split.start..split.start + 1), None);
1186 assert_eq!(converted(markdown, split.start + 1..split.end), None);
1187 }
1188
1189 #[test]
1190 fn ranges_that_are_not_ranges_of_the_source_convert_to_none() {
1191 let text = rendered(parse("hello world"));
1192 assert_eq!(text.range_for_source(3..3), None);
1193 assert_eq!(text.range_for_source(Range { start: 5, end: 3 }), None);
1194 assert_eq!(text.range_for_source(0..12), None);
1195 assert_eq!(text.range_for_source(20..30), None);
1196 assert_eq!(text.range_for_source(0..11), Some(0..11));
1197
1198 let empty = rendered(parse(""));
1199 assert_eq!(empty.source(), "");
1200 assert_eq!(empty.range_for_source(0..0), None);
1201 }
1202
1203 #[test]
1204 fn a_range_across_blocks_holds_the_separators_between_them() {
1205 let markdown = "ab\n\ncd\n\n# ef";
1206 assert_eq!(
1208 converted_needle(markdown, "b\n\ncd\n\n# e").as_deref(),
1209 Some("b\ncd\ne")
1210 );
1211
1212 let table = "| a | b |\n|---|---|\n| c | d |";
1213 assert_eq!(
1214 converted_needle(table, "b |\n|---|---|\n| c").as_deref(),
1215 Some("b\nc")
1216 );
1217 assert_eq!(converted_needle(table, "c | d").as_deref(), Some("c d"));
1218
1219 let list = "- one\n- two\n - three";
1220 assert_eq!(
1221 converted_needle(list, "ne\n- two\n - th").as_deref(),
1222 Some("ne\ntwo\nth")
1223 );
1224 }
1225
1226 #[test]
1227 fn code_blocks_convert_their_body() {
1228 let fenced = "```rust\nlet x = 1;\nlet y = 2;\n```";
1229 assert_eq!(
1230 converted_needle(fenced, "x = 1;\nlet y").as_deref(),
1231 Some("x = 1;\nlet y")
1232 );
1233 assert_eq!(
1234 converted(fenced, 0..fenced.len()).as_deref(),
1235 Some("let x = 1;\nlet y = 2;")
1236 );
1237 let indented = " let x = 1;\n let y = 2;";
1238 assert_eq!(
1239 converted_needle(indented, "x = 1").as_deref(),
1240 Some("x = 1")
1241 );
1242 let tilde = "~~~\nwavy\n~~~";
1243 assert_eq!(converted(tilde, 0..tilde.len()).as_deref(), Some("wavy"));
1244 }
1245
1246 #[test]
1247 fn an_image_between_texts_keeps_the_range_contiguous() {
1248 let markdown = "a  b";
1249 let text = rendered(parse(markdown));
1250 let range = text.range_for_source(0..markdown.len()).unwrap();
1251 assert_eq!(&text.as_str()[range], "a b");
1252 }
1253
1254 #[test]
1255 fn html_text_converts_nothing() {
1256 let html = "<p>one <b>two</b></p>";
1257 let document = format::html::parse(html, &mut NodeContext::default()).expect("parse HTML");
1258 let text = rendered(document);
1259 assert_eq!(text.source(), html);
1260 assert!(!text.is_empty());
1261 for start in 0..html.len() {
1262 for end in start + 1..=html.len() {
1263 assert_eq!(text.range_for_source(start..end), None, "{start}..{end}");
1264 }
1265 }
1266 }
1267
1268 #[test]
1269 fn custom_blocks_convert_whole() {
1270 let extensions = crate::text::MarkdownExtensions::default().block_parser(|node, cx| {
1271 let markdown::mdast::Node::Paragraph(paragraph) = node else {
1272 return None;
1273 };
1274 let [markdown::mdast::Node::Text(text)] = paragraph.children.as_slice() else {
1275 return None;
1276 };
1277 text.value.starts_with('$').then(|| {
1278 crate::text::MarkdownNode::new("ticker", ())
1279 .text(text.value.clone())
1280 .markdown(cx.node_source(node).unwrap_or_default())
1281 })
1282 });
1283 let markdown = "before\n\n$TSLA.US\n\nafter";
1284 let mut cx = NodeContext {
1285 markdown_extensions: extensions.into(),
1286 ..NodeContext::default()
1287 };
1288 let text = rendered(format::markdown::parse(markdown, &mut cx).unwrap());
1289
1290 assert_eq!(text.as_str(), "before\n$TSLA.US\nafter\n");
1291 let ticker = nth(markdown, "$TSLA.US", 0);
1292 assert_eq!(text.range_for_source(ticker.clone()), Some(7..15));
1293 assert_eq!(
1295 text.range_for_source(ticker.start + 1..ticker.start + 3),
1296 Some(7..15)
1297 );
1298 assert_eq!(
1299 text.range_for_source(nth(markdown, "re\n\n$T", 0)),
1300 Some(4..15)
1301 );
1302 }
1303
1304 enum LeafNode<'a> {
1306 Paragraph(&'a Paragraph),
1307 Code(&'a CodeBlock),
1308 }
1309
1310 impl LeafNode<'_> {
1311 fn text(&self) -> String {
1312 match self {
1313 Self::Paragraph(paragraph) => paragraph
1314 .children
1315 .iter()
1316 .map(|child| child.text.as_ref())
1317 .collect(),
1318 Self::Code(code_block) => code_block.code().to_string(),
1319 }
1320 }
1321
1322 fn select(&self, range: Option<Range<usize>>) {
1328 match self {
1329 Self::Paragraph(paragraph) => {
1330 let text = self.text();
1331 let select_run = |state: &std::sync::Mutex<_>, run: Range<usize>| {
1332 let mut state: std::sync::MutexGuard<'_, crate::text::inline::InlineState> =
1333 state.lock().unwrap();
1334 state.set_text(text[run.clone()].to_string().into());
1335 state.selection = range
1336 .as_ref()
1337 .map(|range| range.start.max(run.start)..range.end.min(run.end))
1338 .filter(|selected| selected.start < selected.end)
1339 .map(|selected| {
1340 (selected.start - run.start..selected.end - run.start).into()
1341 });
1342 };
1343 let (mut run_start, mut offset) = (0, 0);
1344 for child in ¶graph.children {
1345 assert!(child.custom.is_none(), "inline objects select on their own");
1346 if child.image.is_some() {
1347 select_run(&child.state, run_start..offset);
1348 run_start = offset;
1349 }
1350 offset += child.text.len();
1351 }
1352 select_run(¶graph.state, run_start..offset);
1353 }
1354 Self::Code(code_block) => match range {
1355 Some(range) => code_block.set_selection(range),
1356 None => code_block.clear_selection(),
1357 },
1358 }
1359 }
1360 }
1361
1362 fn leaf_nodes<'a>(blocks: &'a [BlockNode], leaves: &mut Vec<LeafNode<'a>>) {
1364 for block in blocks {
1365 match block {
1366 BlockNode::Root { children, .. }
1367 | BlockNode::Blockquote { children, .. }
1368 | BlockNode::Surface { children, .. }
1369 | BlockNode::List { children, .. }
1370 | BlockNode::ListItem { children, .. } => leaf_nodes(children, leaves),
1371 BlockNode::Paragraph(paragraph) => leaves.push(LeafNode::Paragraph(paragraph)),
1372 BlockNode::Heading { children, .. } => leaves.push(LeafNode::Paragraph(children)),
1373 BlockNode::Table(table) => {
1374 for row in &table.children {
1375 for cell in &row.children {
1376 leaves.push(LeafNode::Paragraph(&cell.children));
1377 }
1378 }
1379 }
1380 BlockNode::CodeBlock(code_block) => leaves.push(LeafNode::Code(code_block)),
1381 _ => {}
1382 }
1383 }
1384 leaves.retain(|leaf| !leaf.text().is_empty());
1385 }
1386
1387 fn assert_selections_round_trip(markdown: &str) {
1394 let document = parse(markdown);
1395 let text = rendered(document.clone());
1396 for segment in text
1399 .index()
1400 .source_map
1401 .iter()
1402 .filter(|segment| !segment.linear)
1403 {
1404 let rendered = &text.as_str()[segment.rendered.clone()];
1405 let source = &markdown[segment.source.clone()];
1406 assert!(
1407 rendered.chars().count() == 1 || (source.starts_with('&') && source.ends_with(';')),
1408 "{markdown:?}: {rendered:?} maps from {source:?} only as a whole"
1409 );
1410 }
1411 let mut leaves = Vec::new();
1412 leaf_nodes(&document.blocks, &mut leaves);
1413 let spans = &text.index().leaves;
1414 assert_eq!(leaves.len(), spans.len(), "leaves of {markdown:?}");
1415 let leaves = leaves
1416 .into_iter()
1417 .zip(spans.iter().map(|span| span.range.clone()))
1418 .collect::<Vec<_>>();
1419 for (_, range) in &leaves {
1420 assert!(text.as_str().is_char_boundary(range.start));
1421 }
1422
1423 let boundaries = leaves
1425 .iter()
1426 .enumerate()
1427 .flat_map(|(ix, (_, range))| {
1428 let leaf_text = &text.as_str()[range.clone()];
1429 leaf_text
1430 .char_indices()
1431 .map(|(offset, _)| offset)
1432 .chain([leaf_text.len()])
1433 .map(move |offset| (ix, range.start + offset))
1434 })
1435 .collect::<Vec<_>>();
1436
1437 for (start_ix, &(first, start)) in boundaries.iter().enumerate() {
1438 if start == leaves[first].1.end {
1439 continue;
1440 }
1441 for &(last, end) in &boundaries[start_ix + 1..] {
1442 if end == leaves[last].1.start || end <= start {
1443 continue;
1444 }
1445 for (ix, (leaf, range)) in leaves.iter().enumerate() {
1446 let selected = (first..=last).contains(&ix).then(|| {
1447 start.max(range.start) - range.start..end.min(range.end) - range.start
1448 });
1449 leaf.select(selected);
1450 }
1451 let source = document.selected_source_range().unwrap_or_else(|| {
1452 panic!("{markdown:?}: selecting {start}..{end} maps to no source")
1453 });
1454 let expected = text
1457 .index()
1458 .source_map
1459 .iter()
1460 .filter(|segment| {
1461 !segment.linear
1462 && segment.rendered.start < end
1463 && segment.rendered.end > start
1464 })
1465 .fold(start..end, |range, segment| {
1466 range.start.min(segment.rendered.start)..range.end.max(segment.rendered.end)
1467 });
1468 assert_eq!(
1469 text.range_for_source(source.clone()),
1470 Some(expected),
1471 "{markdown:?}: selecting {:?} ({start}..{end}) reports source {:?} ({source:?})",
1472 &text.as_str()[start..end],
1473 &markdown[source.clone()],
1474 );
1475 }
1476 }
1477 for (leaf, _) in &leaves {
1478 leaf.select(None);
1479 }
1480 }
1481
1482 const ROUND_TRIP_CORPUS: &[&str] = &[
1483 "plain text",
1484 "hello **world** and *em* and ~~del~~ and `code`",
1485 "nested **bold *and italic* text** and ***both***",
1486 "`` code with ` backtick `` then text",
1487 "# Heading with **bold**",
1488 "Setext heading\n===",
1489 "> quoted **text**\n> continued\n>\n> second paragraph",
1490 "- item one\n- item **two**\n - nested *item*\n\n continued item",
1491 "3. third\n4. fourth",
1492 "- [x] done\n- [ ] todo",
1493 "| a | **b** |\n|---|:---:|\n| c `d` | e |\n| | f |",
1494 "```rust\nlet x = 1;\n\nlet y = 2;\n```",
1495 " indented code\n more",
1496 "~~~\ntilde fence\n~~~",
1497 "a [link](https://example.com \"title\") b",
1498 "a [reference] b\n\n[reference]: https://example.com",
1499 "<https://auto.example> and https://gfm.example",
1500 "soft\nbreak\nlines",
1501 "hard \nbreak\\\nagain",
1502 "trailing spaces \nnext line",
1503 "escapes \\* \\_ \\` \\\\ and \\[not a link\\]",
1504 "\\*starts escaped and **\\*bold** and *\\_em*",
1505 "entity ∾̳ as long as its characters",
1506 "entities & < A B © end",
1507 "中文 **粗体** 和 `代码` 🎉 é and e\u{301}",
1508 "crlf\r\nlines\r\n\r\nnext paragraph",
1509 "image  between",
1510 "one\n\ntwo\n\n---\n\nthree",
1511 "# Title\n\nIntro with **bold**.\n\n- first\n- second\n\n| a | b |\n|---|---|\n| c | d |\n\n```\ncode\n```\n\n> quote",
1512 ];
1513
1514 #[test]
1515 fn selections_round_trip_through_source_ranges() {
1516 for markdown in ROUND_TRIP_CORPUS {
1517 assert_selections_round_trip(markdown);
1518 }
1519 }
1520}
1521
1522#[derive(Clone, Copy, Debug, PartialEq)]
1524enum RevealTarget {
1525 Line { key: TextLeafKey, offset: usize },
1527 Block { ix: usize },
1529}
1530
1531const REVEAL_TIMEOUT: Duration = Duration::from_secs(1);
1535
1536const REVEAL_ATTEMPTS: usize = 8;
1539
1540#[derive(Clone, Copy, Debug)]
1543struct RevealReport {
1544 line: Bounds<Pixels>,
1545 visible: bool,
1546}
1547
1548#[derive(Debug)]
1556pub(super) struct PendingReveal {
1557 target: RevealTarget,
1558 requested_at: Instant,
1559 report: Arc<Mutex<Option<RevealReport>>>,
1562 attempts: usize,
1563}
1564
1565pub(super) enum RevealProgress {
1567 Shown,
1569 Hidden(Bounds<Pixels>),
1571 NotLaidOut,
1573}
1574
1575impl PendingReveal {
1576 pub(super) fn new(text: &RenderedText, range: &Range<usize>, now: Instant) -> Option<Self> {
1579 Some(Self {
1580 target: text.index().locate(range)?,
1581 requested_at: now,
1582 report: Arc::default(),
1583 attempts: 0,
1584 })
1585 }
1586
1587 pub(super) fn is_expired(&self, now: Instant) -> bool {
1588 now.saturating_duration_since(self.requested_at) > REVEAL_TIMEOUT
1589 || self.attempts >= REVEAL_ATTEMPTS
1590 }
1591
1592 pub(super) fn is_block(&self) -> bool {
1594 matches!(self.target, RevealTarget::Block { .. })
1595 }
1596
1597 pub(super) fn block_ix(&self, document: &ParsedDocument) -> Option<usize> {
1599 match self.target {
1600 RevealTarget::Line { key, .. } => document.blocks.iter().rposition(|block| {
1601 block
1602 .span()
1603 .is_some_and(|span| span.start <= key.block_start())
1604 }),
1605 RevealTarget::Block { ix } => (ix < document.blocks.len()).then_some(ix),
1606 }
1607 }
1608
1609 pub(super) fn was_laid_out(&self) -> bool {
1611 self.report.lock().is_ok_and(|report| report.is_some())
1612 }
1613
1614 pub(super) fn request(&self) -> Option<RevealRequest> {
1617 if let Ok(mut report) = self.report.lock() {
1618 *report = None;
1619 }
1620 let RevealTarget::Line { key, offset } = self.target else {
1621 return None;
1622 };
1623 Some(RevealRequest {
1624 key,
1625 offset,
1626 report: self.report.clone(),
1627 })
1628 }
1629
1630 pub(super) fn progress(&mut self) -> RevealProgress {
1633 let report = self.report.lock().ok().and_then(|report| *report);
1634 match report {
1635 Some(report) if report.visible => RevealProgress::Shown,
1636 Some(report) => {
1637 self.attempts += 1;
1638 RevealProgress::Hidden(report.line)
1639 }
1640 None => RevealProgress::NotLaidOut,
1641 }
1642 }
1643
1644 pub(super) fn remap(mut self, remap: &LeafRemap) -> Option<Self> {
1647 let RevealTarget::Line { key, offset } = self.target else {
1648 return None;
1649 };
1650 let (key, unchanged) = remap.leaf(key)?;
1651 (offset < unchanged).then_some(())?;
1652 self.target = RevealTarget::Line { key, offset };
1653 Some(self)
1654 }
1655}
1656
1657#[derive(Clone, Debug)]
1660pub(crate) struct RevealRequest {
1661 key: TextLeafKey,
1662 offset: usize,
1663 report: Arc<Mutex<Option<RevealReport>>>,
1664}
1665
1666impl RevealRequest {
1667 pub(crate) fn at(
1670 &self,
1671 key: Option<TextLeafKey>,
1672 start: usize,
1673 end: usize,
1674 ) -> Option<RevealAt> {
1675 if key != Some(self.key) {
1676 return None;
1677 }
1678 RevealAt {
1679 offset: self.offset,
1680 report: self.report.clone(),
1681 }
1682 .rebase(start, end)
1683 }
1684}
1685
1686#[derive(Clone, Debug)]
1688pub(crate) struct RevealAt {
1689 offset: usize,
1690 report: Arc<Mutex<Option<RevealReport>>>,
1691}
1692
1693impl RevealAt {
1694 pub(crate) fn offset(&self) -> usize {
1695 self.offset
1696 }
1697
1698 pub(crate) fn rebase(&self, start: usize, end: usize) -> Option<Self> {
1701 (start..end).contains(&self.offset).then(|| Self {
1702 offset: self.offset - start,
1703 report: self.report.clone(),
1704 })
1705 }
1706
1707 pub(crate) fn clamp(&self, start: usize, end: usize) -> Self {
1711 Self {
1712 offset: self.offset.clamp(start, end) - start,
1713 report: self.report.clone(),
1714 }
1715 }
1716
1717 pub(crate) fn report(&self, line: Bounds<Pixels>, visible: bool) {
1720 if let Ok(mut report) = self.report.lock() {
1721 *report = Some(RevealReport { line, visible });
1722 }
1723 }
1724}