1use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use frontend::commands::{block_commands, document_commands, frame_commands, list_commands};
8use frontend::common::format_runs::{FormatRun, ImageAnchor, synth_element_id};
9use frontend::common::types::EntityId;
10
11use crate::convert::to_usize;
12use crate::flow::{BlockSnapshot, FragmentContent, ListInfo, TableCellContext, TableCellRef};
13use crate::inner::TextDocumentInner;
14use crate::text_frame::TextFrame;
15use crate::text_list::TextList;
16use crate::text_table::TextTable;
17use crate::{BlockFormat, ListStyle, TextFormat};
18
19#[derive(Clone)]
26pub struct TextBlock {
27 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
28 pub(crate) block_id: usize,
29}
30
31impl TextBlock {
32 pub fn text(&self) -> String {
36 let inner = self.doc.lock();
37 let store = inner.ctx.db_context.get_store();
38 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
39 .ok()
40 .flatten()
41 .map(|b| {
42 let entity: common::entities::Block = b.into();
43 common::database::rope_helpers::block_content_via_store(&entity, store)
44 })
45 .unwrap_or_default()
46 }
47
48 pub fn length(&self) -> usize {
50 let inner = self.doc.lock();
51 let store = inner.ctx.db_context.get_store();
52 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
53 .ok()
54 .flatten()
55 .map(|b| {
56 let entity: common::entities::Block = b.into();
57 to_usize(common::database::rope_helpers::block_char_length(
58 &entity, store,
59 ))
60 })
61 .unwrap_or(0)
62 }
63
64 pub fn is_empty(&self) -> bool {
66 let inner = self.doc.lock();
67 let store = inner.ctx.db_context.get_store();
68 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
69 .ok()
70 .flatten()
71 .map(|b| {
72 let entity: common::entities::Block = b.into();
73 common::database::rope_helpers::block_char_length(&entity, store) == 0
74 })
75 .unwrap_or(true)
76 }
77
78 pub fn is_valid(&self) -> bool {
80 let inner = self.doc.lock();
81 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
82 .ok()
83 .flatten()
84 .is_some()
85 }
86
87 pub fn id(&self) -> usize {
91 self.block_id
92 }
93
94 pub fn position(&self) -> usize {
98 let inner = self.doc.lock();
99 let Some(mut dto) = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
100 .ok()
101 .flatten()
102 else {
103 return 0;
104 };
105 let store = inner.ctx.db_context.get_store();
106 crate::inner::refresh_block_position(&mut dto, store);
107 to_usize(dto.document_position)
108 }
109
110 pub fn block_number(&self) -> usize {
114 let inner = self.doc.lock();
115 compute_block_number(&inner, self.block_id as u64)
116 }
117
118 pub fn next(&self) -> Option<TextBlock> {
121 let inner = self.doc.lock();
122 let all_blocks = block_commands::get_all_block(&inner.ctx).ok()?;
123 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
124 let store = inner.ctx.db_context.get_store();
125 crate::inner::refresh_block_positions(&mut sorted, store);
126 sorted.sort_by_key(|b| b.document_position);
127 let idx = sorted.iter().position(|b| b.id == self.block_id as u64)?;
128 sorted.get(idx + 1).map(|b| TextBlock {
129 doc: Arc::clone(&self.doc),
130 block_id: b.id as usize,
131 })
132 }
133
134 pub fn previous(&self) -> Option<TextBlock> {
137 let inner = self.doc.lock();
138 let all_blocks = block_commands::get_all_block(&inner.ctx).ok()?;
139 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
140 let store = inner.ctx.db_context.get_store();
141 crate::inner::refresh_block_positions(&mut sorted, store);
142 sorted.sort_by_key(|b| b.document_position);
143 let idx = sorted.iter().position(|b| b.id == self.block_id as u64)?;
144 if idx == 0 {
145 return None;
146 }
147 sorted.get(idx - 1).map(|b| TextBlock {
148 doc: Arc::clone(&self.doc),
149 block_id: b.id as usize,
150 })
151 }
152
153 pub fn frame(&self) -> TextFrame {
157 let inner = self.doc.lock();
158 let frame_id = find_parent_frame(&inner, self.block_id as u64);
159 TextFrame {
160 doc: Arc::clone(&self.doc),
161 frame_id: frame_id.map(|id| id as usize).unwrap_or(0),
162 }
163 }
164
165 pub fn table_cell(&self) -> Option<TableCellRef> {
171 let inner = self.doc.lock();
172 let frame_id = find_parent_frame(&inner, self.block_id as u64)?;
173
174 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
177 .ok()
178 .flatten()?;
179
180 if let Some(table_entity_id) = frame_dto.table {
181 let table_dto =
185 frontend::commands::table_commands::get_table(&inner.ctx, &{ table_entity_id })
186 .ok()
187 .flatten()?;
188 for &cell_id in &table_dto.cells {
189 if let Some(cell_dto) =
190 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{
191 cell_id
192 })
193 .ok()
194 .flatten()
195 && cell_dto.cell_frame == Some(frame_id)
196 {
197 return Some(TableCellRef {
198 table: TextTable {
199 doc: Arc::clone(&self.doc),
200 table_id: table_entity_id as usize,
201 },
202 row: to_usize(cell_dto.row),
203 column: to_usize(cell_dto.column),
204 });
205 }
206 }
207 }
208
209 let all_tables =
212 frontend::commands::table_commands::get_all_table(&inner.ctx).unwrap_or_default();
213 for table_dto in &all_tables {
214 for &cell_id in &table_dto.cells {
215 if let Some(cell_dto) =
216 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{
217 cell_id
218 })
219 .ok()
220 .flatten()
221 && cell_dto.cell_frame == Some(frame_id)
222 {
223 return Some(TableCellRef {
224 table: TextTable {
225 doc: Arc::clone(&self.doc),
226 table_id: table_dto.id as usize,
227 },
228 row: to_usize(cell_dto.row),
229 column: to_usize(cell_dto.column),
230 });
231 }
232 }
233 }
234
235 None
236 }
237
238 pub fn block_format(&self) -> BlockFormat {
242 let inner = self.doc.lock();
243 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
244 .ok()
245 .flatten()
246 .map(|b| BlockFormat::from(&b))
247 .unwrap_or_default()
248 }
249
250 pub fn char_format_at(&self, offset: usize) -> Option<TextFormat> {
257 let inner = self.doc.lock();
258 let fragments = build_fragments(&inner, self.block_id as u64);
259 for frag in &fragments {
260 match frag {
261 FragmentContent::Text {
262 format,
263 offset: frag_offset,
264 length,
265 ..
266 } => {
267 if offset >= *frag_offset && offset < frag_offset + length {
268 return Some(format.clone());
269 }
270 }
271 FragmentContent::Image {
272 format,
273 offset: frag_offset,
274 ..
275 }
276 | FragmentContent::FootnoteReference {
277 format,
278 offset: frag_offset,
279 ..
280 } => {
281 if offset == *frag_offset {
282 return Some(format.clone());
283 }
284 }
285 }
286 }
287 None
288 }
289
290 pub fn fragments(&self) -> Vec<FragmentContent> {
303 let inner = self.doc.lock();
304 build_fragments(&inner, self.block_id as u64)
305 }
306
307 pub fn display_fragments(&self) -> Vec<FragmentContent> {
315 let inner = self.doc.lock();
316 let fragments = build_raw_fragments(&inner, self.block_id as u64, None);
317 let spans = crate::highlight::merged_spans_for_block(
320 &inner,
321 self.block_id,
322 &crate::highlight::HighlightMask::ALL,
323 );
324 if !spans.is_empty() {
325 return crate::highlight::merge_highlight_spans(fragments, &spans);
326 }
327 fragments
328 }
329
330 pub fn list(&self) -> Option<TextList> {
334 let inner = self.doc.lock();
335 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
336 .ok()
337 .flatten()?;
338 let list_id = block_dto.list?;
339 Some(TextList {
340 doc: Arc::clone(&self.doc),
341 list_id: list_id as usize,
342 })
343 }
344
345 pub fn list_item_index(&self) -> Option<usize> {
347 let inner = self.doc.lock();
348 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
349 .ok()
350 .flatten()?;
351 let list_id = block_dto.list?;
352 Some(compute_list_item_index(
353 &inner,
354 list_id,
355 self.block_id as u64,
356 ))
357 }
358
359 pub fn snapshot(&self) -> BlockSnapshot {
363 let inner = self.doc.lock();
364 build_block_snapshot(
365 &inner,
366 self.block_id as u64,
367 crate::highlight::SnapshotHighlights {
368 kind: inner.highlight_kind,
369 mask: &crate::highlight::HighlightMask::ALL,
370 suppress_paint: false,
371 },
372 )
373 .unwrap_or_else(|| BlockSnapshot {
374 block_id: self.block_id,
375 position: 0,
376 length: 0,
377 text: String::new(),
378 fragments: Vec::new(),
379 block_format: BlockFormat::default(),
380 list_info: None,
381 parent_frame_id: None,
382 table_cell: None,
383 paint_highlights: Vec::new(),
384 })
385 }
386}
387
388pub(crate) fn find_parent_frame(inner: &TextDocumentInner, block_id: u64) -> Option<EntityId> {
394 let all_frames = frame_commands::get_all_frame(&inner.ctx).ok()?;
395 let block_entity_id = block_id as EntityId;
396 for frame in &all_frames {
397 if frame.blocks.contains(&block_entity_id) {
398 return Some(frame.id as EntityId);
399 }
400 }
401 None
402}
403
404fn document_has_no_tables(inner: &TextDocumentInner) -> bool {
409 inner.ctx.db_context.get_store().tables.read().is_empty()
410}
411
412fn find_table_cell_context(inner: &TextDocumentInner, block_id: u64) -> Option<TableCellContext> {
415 if document_has_no_tables(inner) {
419 return None;
420 }
421 let frame_id = find_parent_frame(inner, block_id)?;
422
423 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
424 .ok()
425 .flatten()?;
426
427 if let Some(table_entity_id) = frame_dto.table {
429 let table_dto =
430 frontend::commands::table_commands::get_table(&inner.ctx, &{ table_entity_id })
431 .ok()
432 .flatten()?;
433 for &cell_id in &table_dto.cells {
434 if let Some(cell_dto) =
435 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
436 .ok()
437 .flatten()
438 && cell_dto.cell_frame == Some(frame_id)
439 {
440 return Some(TableCellContext {
441 table_id: table_entity_id as usize,
442 row: to_usize(cell_dto.row),
443 column: to_usize(cell_dto.column),
444 });
445 }
446 }
447 }
448
449 let all_tables =
451 frontend::commands::table_commands::get_all_table(&inner.ctx).unwrap_or_default();
452 for table_dto in &all_tables {
453 for &cell_id in &table_dto.cells {
454 if let Some(cell_dto) =
455 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
456 .ok()
457 .flatten()
458 && cell_dto.cell_frame == Some(frame_id)
459 {
460 return Some(TableCellContext {
461 table_id: table_dto.id as usize,
462 row: to_usize(cell_dto.row),
463 column: to_usize(cell_dto.column),
464 });
465 }
466 }
467 }
468
469 None
470}
471
472fn compute_block_number(inner: &TextDocumentInner, block_id: u64) -> usize {
474 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
475 let store = inner.ctx.db_context.get_store();
476 crate::inner::refresh_block_positions(&mut all_blocks, store);
477 let mut sorted: Vec<_> = all_blocks.iter().collect();
478 sorted.sort_by_key(|b| b.document_position);
479 sorted.iter().position(|b| b.id == block_id).unwrap_or(0)
480}
481
482pub(crate) fn build_fragments(inner: &TextDocumentInner, block_id: u64) -> Vec<FragmentContent> {
485 build_fragments_with_text(
486 inner,
487 block_id,
488 None,
489 crate::highlight::SnapshotHighlights {
490 kind: inner.highlight_kind,
491 mask: &crate::highlight::HighlightMask::ALL,
492 suppress_paint: false,
493 },
494 )
495}
496
497pub(crate) fn build_fragments_with_text(
503 inner: &TextDocumentInner,
504 block_id: u64,
505 prefetched_text: Option<&str>,
506 hl: crate::highlight::SnapshotHighlights,
507) -> Vec<FragmentContent> {
508 let fragments = build_raw_fragments(inner, block_id, prefetched_text);
509
510 if hl.kind == crate::highlight::HighlighterKind::Metric {
516 let spans = crate::highlight::merged_spans_for_block(inner, block_id as usize, hl.mask);
517 if !spans.is_empty() {
518 return crate::highlight::merge_highlight_spans(fragments, &spans);
519 }
520 }
521
522 fragments
523}
524
525fn document_self_footnote_numbers(
550 store: &common::database::Store,
551) -> std::collections::HashMap<String, usize> {
552 let definition_blocks: std::collections::HashSet<common::types::EntityId> = store
553 .frames
554 .read()
555 .values()
556 .filter(|f| f.footnote_label.is_some())
557 .flat_map(|f| f.child_order.iter().copied())
558 .filter(|child| *child > 0)
559 .map(|child| child as common::types::EntityId)
560 .collect();
561
562 let mut ordered: Vec<(i64, common::types::EntityId)> = store
563 .blocks
564 .read()
565 .values()
566 .filter(|b| !definition_blocks.contains(&b.id))
567 .map(|b| (b.document_position, b.id))
568 .collect();
569 ordered.sort_unstable();
570
571 let refs = store.block_footnote_refs.read();
572 let mut numbers: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
573 let mut next = 1usize;
574 for (_, block_id) in ordered {
575 let Some(anchors) = refs.get(&block_id) else {
576 continue;
577 };
578 let mut in_block: Vec<_> = anchors.iter().collect();
579 in_block.sort_by_key(|a| a.byte_offset);
580 for anchor in in_block {
581 numbers.entry(anchor.label.clone()).or_insert_with(|| {
582 let n = next;
583 next += 1;
584 n
585 });
586 }
587 }
588 numbers
589}
590
591fn build_raw_fragments(
606 inner: &TextDocumentInner,
607 block_id: u64,
608 prefetched_text: Option<&str>,
609) -> Vec<FragmentContent> {
610 let _block_dto = match block_commands::get_block(&inner.ctx, &block_id)
611 .ok()
612 .flatten()
613 {
614 Some(b) => b,
615 None => return Vec::new(),
616 };
617
618 let plain_owned;
619 let plain: &str = match prefetched_text {
620 Some(t) => t,
621 None => {
622 let entity: common::entities::Block = _block_dto.clone().into();
623 plain_owned = common::database::rope_helpers::block_content_via_store(
624 &entity,
625 inner.ctx.db_context.get_store(),
626 );
627 &plain_owned
628 }
629 };
630
631 let (runs, images, notes, markers) = {
632 let store = inner.ctx.db_context.get_store();
633 let runs: Vec<FormatRun> = store
634 .format_runs
635 .read()
636 .get(&block_id)
637 .cloned()
638 .unwrap_or_default();
639 let images: Vec<ImageAnchor> = store
640 .block_images
641 .read()
642 .get(&block_id)
643 .cloned()
644 .unwrap_or_default();
645 let notes = store
646 .block_footnote_refs
647 .read()
648 .get(&block_id)
649 .cloned()
650 .unwrap_or_default();
651 let markers = if notes.is_empty() {
655 std::collections::HashMap::new()
656 } else {
657 store.footnote_markers.read().clone()
658 };
659 (runs, images, notes, markers)
660 };
661
662 let anchors = frontend::common::format_runs::block_anchors(&images, ¬es);
667 let pieces = frontend::common::format_runs::merge_runs_and_anchors(plain, &runs, &anchors);
668
669 let mut fragments = Vec::with_capacity(pieces.len());
670 let mut char_offset: usize = 0;
671 let mut self_numbers: Option<std::collections::HashMap<String, usize>> = None;
676
677 for piece in pieces {
678 match piece {
679 frontend::common::format_runs::InlinePiece::Text { start, end, format } => {
680 let text = &plain[start as usize..end as usize];
681 let length = text.chars().count();
682 let word_starts = compute_word_starts(text);
683 fragments.push(FragmentContent::Text {
684 text: text.to_string(),
685 format: format.map(TextFormat::from).unwrap_or_default(),
686 offset: char_offset,
687 length,
688 element_id: synth_element_id(block_id, start),
689 word_starts,
690 });
691 char_offset += length;
692 }
693 frontend::common::format_runs::InlinePiece::FootnoteRef(note) => {
694 fragments.push(FragmentContent::FootnoteReference {
695 label: note.label.clone(),
696 marker: markers.get(¬e.label).cloned().unwrap_or_else(|| {
713 self_numbers
714 .get_or_insert_with(|| {
715 document_self_footnote_numbers(inner.ctx.db_context.get_store())
716 })
717 .get(¬e.label)
718 .map(|n| n.to_string())
719 .unwrap_or_else(|| note.label.clone())
720 }),
721 format: TextFormat::from(¬e.format),
722 offset: char_offset,
723 element_id: synth_element_id(block_id, note.byte_offset),
724 });
725 char_offset += 1;
728 }
729 frontend::common::format_runs::InlinePiece::Image(img) => {
730 fragments.push(FragmentContent::Image {
731 name: img.name.clone(),
732 alt: img.alt.clone(),
733 width: img.width as u32,
734 height: img.height as u32,
735 quality: img.quality as u32,
736 format: TextFormat::from(&img.format),
737 offset: char_offset,
738 element_id: synth_element_id(block_id, img.byte_offset),
739 });
740 char_offset += 1;
743 }
744 }
745 }
746
747 fragments
748}
749
750fn compute_word_starts(text: &str) -> Vec<u8> {
756 use unicode_segmentation::UnicodeSegmentation;
757 let mut result = Vec::new();
758 let mut byte_to_char: Vec<(usize, usize)> = Vec::new();
762 for (ci, (bi, _)) in text.char_indices().enumerate() {
763 byte_to_char.push((bi, ci));
764 }
765 for (byte_off, _word) in text.unicode_word_indices() {
766 let char_idx = byte_to_char
767 .iter()
768 .find(|(bi, _)| *bi == byte_off)
769 .map(|(_, ci)| *ci)
770 .unwrap_or(0);
771 if let Ok(idx) = u8::try_from(char_idx) {
778 result.push(idx);
779 } else {
780 break;
781 }
782 }
783 result
784}
785
786fn compute_list_item_index(inner: &TextDocumentInner, list_id: EntityId, block_id: u64) -> usize {
788 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
789 let store = inner.ctx.db_context.get_store();
790 crate::inner::refresh_block_positions(&mut all_blocks, store);
791 let mut list_blocks: Vec<_> = all_blocks
792 .iter()
793 .filter(|b| b.list == Some(list_id))
794 .collect();
795 list_blocks.sort_by_key(|b| b.document_position);
796 list_blocks
797 .iter()
798 .position(|b| b.id == block_id)
799 .unwrap_or(0)
800}
801
802pub(crate) fn format_list_marker(
804 list_dto: &frontend::list::dtos::ListDto,
805 item_index: usize,
806) -> String {
807 let number = item_index + 1; let marker_body = match list_dto.style {
809 ListStyle::Disc => "\u{2022}".to_string(), ListStyle::Circle => "\u{25E6}".to_string(), ListStyle::Square => "\u{25AA}".to_string(), ListStyle::Decimal => format!("{number}"),
813 ListStyle::LowerAlpha => {
814 if number <= 26 {
815 ((b'a' + (number as u8 - 1)) as char).to_string()
816 } else {
817 format!("{number}")
818 }
819 }
820 ListStyle::UpperAlpha => {
821 if number <= 26 {
822 ((b'A' + (number as u8 - 1)) as char).to_string()
823 } else {
824 format!("{number}")
825 }
826 }
827 ListStyle::LowerRoman => to_roman_lower(number),
828 ListStyle::UpperRoman => to_roman_upper(number),
829 };
830 format!("{}{marker_body}{}", list_dto.prefix, list_dto.suffix)
831}
832
833fn to_roman_upper(mut n: usize) -> String {
834 const VALUES: &[(usize, &str)] = &[
835 (1000, "M"),
836 (900, "CM"),
837 (500, "D"),
838 (400, "CD"),
839 (100, "C"),
840 (90, "XC"),
841 (50, "L"),
842 (40, "XL"),
843 (10, "X"),
844 (9, "IX"),
845 (5, "V"),
846 (4, "IV"),
847 (1, "I"),
848 ];
849 let mut result = String::new();
850 for &(val, sym) in VALUES {
851 while n >= val {
852 result.push_str(sym);
853 n -= val;
854 }
855 }
856 result
857}
858
859fn to_roman_lower(n: usize) -> String {
860 to_roman_upper(n).to_lowercase()
861}
862
863fn build_list_info(
865 inner: &TextDocumentInner,
866 block_dto: &frontend::block::dtos::BlockDto,
867) -> Option<ListInfo> {
868 let list_id = block_dto.list?;
869 let list_dto = list_commands::get_list(&inner.ctx, &{ list_id })
870 .ok()
871 .flatten()?;
872
873 let item_index = compute_list_item_index(inner, list_id, block_dto.id);
874 let marker = format_list_marker(&list_dto, item_index);
875
876 Some(ListInfo {
877 list_id: list_id as usize,
878 style: list_dto.style.clone(),
879 indent: list_dto.indent as u8,
880 marker,
881 item_index,
882 })
883}
884
885pub(crate) fn build_block_snapshot(
887 inner: &TextDocumentInner,
888 block_id: u64,
889 hl: crate::highlight::SnapshotHighlights,
890) -> Option<BlockSnapshot> {
891 build_block_snapshot_with_position_and_parent(inner, block_id, None, None, hl)
892}
893
894pub(crate) fn build_block_snapshot_with_position(
898 inner: &TextDocumentInner,
899 block_id: u64,
900 computed_position: Option<usize>,
901 hl: crate::highlight::SnapshotHighlights,
902) -> Option<BlockSnapshot> {
903 build_block_snapshot_with_position_and_parent(inner, block_id, computed_position, None, hl)
904}
905
906pub(crate) fn build_block_snapshot_with_position_and_parent(
913 inner: &TextDocumentInner,
914 block_id: u64,
915 computed_position: Option<usize>,
916 parent_frame_hint: Option<EntityId>,
917 hl: crate::highlight::SnapshotHighlights,
918) -> Option<BlockSnapshot> {
919 let mut block_dto = block_commands::get_block(&inner.ctx, &block_id)
920 .ok()
921 .flatten()?;
922 let store_for_pos = inner.ctx.db_context.get_store();
923 crate::inner::refresh_block_position(&mut block_dto, store_for_pos);
924
925 let mut block_format = BlockFormat::from(&block_dto);
926 if block_format.language.is_none() {
930 block_format.language = document_commands::get_document(&inner.ctx, &inner.document_id)
931 .ok()
932 .flatten()
933 .and_then(|d| d.default_language);
934 }
935 let list_info = build_list_info(inner, &block_dto);
936
937 let parent_frame_id = parent_frame_hint
938 .or_else(|| find_parent_frame(inner, block_id))
939 .map(|id| id as usize);
940 let table_cell = find_table_cell_context(inner, block_id);
941
942 let position = if common::database::rope_helpers::rope_positions_match_flow(store_for_pos) {
953 to_usize(block_dto.document_position)
954 } else {
955 computed_position.unwrap_or_else(|| to_usize(block_dto.document_position))
956 };
957
958 let entity: common::entities::Block = block_dto.clone().into();
962 let store = inner.ctx.db_context.get_store();
963 let text = common::database::rope_helpers::block_content_via_store(&entity, store);
964 let length = to_usize(common::database::rope_helpers::block_char_length(
965 &entity, store,
966 ));
967 let fragments = build_fragments_with_text(inner, block_id, Some(&text), hl);
968
969 let paint_highlights =
974 if hl.kind == crate::highlight::HighlighterKind::PaintOnly && !hl.suppress_paint {
975 let spans = crate::highlight::merged_spans_for_block(inner, block_id as usize, hl.mask);
976 crate::highlight::extract_paint_spans(&spans, length)
977 } else {
978 Vec::new()
979 };
980
981 Some(BlockSnapshot {
982 block_id: block_id as usize,
983 position,
984 length,
985 text,
986 fragments,
987 block_format,
988 list_info,
989 parent_frame_id,
990 table_cell,
991 paint_highlights,
992 })
993}
994
995pub(crate) fn build_blocks_snapshot_for_frame(
997 inner: &TextDocumentInner,
998 frame_id: u64,
999 hl: crate::highlight::SnapshotHighlights,
1000) -> Vec<BlockSnapshot> {
1001 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
1002 .ok()
1003 .flatten()
1004 {
1005 Some(f) => f,
1006 None => return Vec::new(),
1007 };
1008
1009 let mut block_dtos: Vec<_> = frame_dto
1010 .blocks
1011 .iter()
1012 .filter_map(|&id| {
1013 block_commands::get_block(&inner.ctx, &{ id })
1014 .ok()
1015 .flatten()
1016 })
1017 .collect();
1018 let store = inner.ctx.db_context.get_store();
1019 crate::inner::refresh_block_positions(&mut block_dtos, store);
1020 block_dtos.sort_by_key(|b| b.document_position);
1021
1022 block_dtos
1023 .iter()
1024 .filter_map(|b| build_block_snapshot(inner, b.id, hl))
1025 .collect()
1026}
1027
1028pub(crate) fn build_blocks_snapshot_for_frame_with_positions(
1034 inner: &TextDocumentInner,
1035 frame_id: u64,
1036 start_pos: usize,
1037 hl: crate::highlight::SnapshotHighlights,
1038) -> (Vec<BlockSnapshot>, usize) {
1039 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
1040 .ok()
1041 .flatten()
1042 {
1043 Some(f) => f,
1044 None => return (Vec::new(), start_pos),
1045 };
1046
1047 let mut block_dtos: Vec<_> = frame_dto
1048 .blocks
1049 .iter()
1050 .filter_map(|&id| {
1051 block_commands::get_block(&inner.ctx, &{ id })
1052 .ok()
1053 .flatten()
1054 })
1055 .collect();
1056 let store = inner.ctx.db_context.get_store();
1057 crate::inner::refresh_block_positions(&mut block_dtos, store);
1058 block_dtos.sort_by_key(|b| b.document_position);
1059
1060 let mut running_pos = start_pos;
1061 let mut snapshots = Vec::with_capacity(block_dtos.len());
1062 for b in &block_dtos {
1063 if let Some(snap) = build_block_snapshot_with_position(inner, b.id, Some(running_pos), hl) {
1064 running_pos += snap.length + 1; snapshots.push(snap);
1066 }
1067 }
1068 (snapshots, running_pos)
1069}