1use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use frontend::commands::{block_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 if offset == *frag_offset {
277 return Some(format.clone());
278 }
279 }
280 }
281 }
282 None
283 }
284
285 pub fn fragments(&self) -> Vec<FragmentContent> {
298 let inner = self.doc.lock();
299 build_fragments(&inner, self.block_id as u64)
300 }
301
302 pub fn display_fragments(&self) -> Vec<FragmentContent> {
310 let inner = self.doc.lock();
311 let fragments = build_raw_fragments(&inner, self.block_id as u64, None);
312 if let Some(ref hl) = inner.highlight
313 && let Some(block_hl) = hl.blocks.get(&{ self.block_id })
314 && !block_hl.spans.is_empty()
315 {
316 return crate::highlight::merge_highlight_spans(fragments, &block_hl.spans);
317 }
318 fragments
319 }
320
321 pub fn list(&self) -> Option<TextList> {
325 let inner = self.doc.lock();
326 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
327 .ok()
328 .flatten()?;
329 let list_id = block_dto.list?;
330 Some(TextList {
331 doc: Arc::clone(&self.doc),
332 list_id: list_id as usize,
333 })
334 }
335
336 pub fn list_item_index(&self) -> Option<usize> {
338 let inner = self.doc.lock();
339 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
340 .ok()
341 .flatten()?;
342 let list_id = block_dto.list?;
343 Some(compute_list_item_index(
344 &inner,
345 list_id,
346 self.block_id as u64,
347 ))
348 }
349
350 pub fn snapshot(&self) -> BlockSnapshot {
354 let inner = self.doc.lock();
355 build_block_snapshot(&inner, self.block_id as u64, inner.highlight_kind).unwrap_or_else(
356 || BlockSnapshot {
357 block_id: self.block_id,
358 position: 0,
359 length: 0,
360 text: String::new(),
361 fragments: Vec::new(),
362 block_format: BlockFormat::default(),
363 list_info: None,
364 parent_frame_id: None,
365 table_cell: None,
366 paint_highlights: Vec::new(),
367 },
368 )
369 }
370}
371
372fn find_parent_frame(inner: &TextDocumentInner, block_id: u64) -> Option<EntityId> {
378 let all_frames = frame_commands::get_all_frame(&inner.ctx).ok()?;
379 let block_entity_id = block_id as EntityId;
380 for frame in &all_frames {
381 if frame.blocks.contains(&block_entity_id) {
382 return Some(frame.id as EntityId);
383 }
384 }
385 None
386}
387
388fn document_has_no_tables(inner: &TextDocumentInner) -> bool {
393 inner
394 .ctx
395 .db_context
396 .get_store()
397 .tables
398 .read()
399 .unwrap()
400 .is_empty()
401}
402
403fn find_table_cell_context(inner: &TextDocumentInner, block_id: u64) -> Option<TableCellContext> {
406 if document_has_no_tables(inner) {
410 return None;
411 }
412 let frame_id = find_parent_frame(inner, block_id)?;
413
414 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
415 .ok()
416 .flatten()?;
417
418 if let Some(table_entity_id) = frame_dto.table {
420 let table_dto =
421 frontend::commands::table_commands::get_table(&inner.ctx, &{ table_entity_id })
422 .ok()
423 .flatten()?;
424 for &cell_id in &table_dto.cells {
425 if let Some(cell_dto) =
426 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
427 .ok()
428 .flatten()
429 && cell_dto.cell_frame == Some(frame_id)
430 {
431 return Some(TableCellContext {
432 table_id: table_entity_id as usize,
433 row: to_usize(cell_dto.row),
434 column: to_usize(cell_dto.column),
435 });
436 }
437 }
438 }
439
440 let all_tables =
442 frontend::commands::table_commands::get_all_table(&inner.ctx).unwrap_or_default();
443 for table_dto in &all_tables {
444 for &cell_id in &table_dto.cells {
445 if let Some(cell_dto) =
446 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
447 .ok()
448 .flatten()
449 && cell_dto.cell_frame == Some(frame_id)
450 {
451 return Some(TableCellContext {
452 table_id: table_dto.id as usize,
453 row: to_usize(cell_dto.row),
454 column: to_usize(cell_dto.column),
455 });
456 }
457 }
458 }
459
460 None
461}
462
463fn compute_block_number(inner: &TextDocumentInner, block_id: u64) -> usize {
465 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
466 let store = inner.ctx.db_context.get_store();
467 crate::inner::refresh_block_positions(&mut all_blocks, store);
468 let mut sorted: Vec<_> = all_blocks.iter().collect();
469 sorted.sort_by_key(|b| b.document_position);
470 sorted.iter().position(|b| b.id == block_id).unwrap_or(0)
471}
472
473pub(crate) fn build_fragments(inner: &TextDocumentInner, block_id: u64) -> Vec<FragmentContent> {
476 build_fragments_with_text(inner, block_id, None, inner.highlight_kind)
477}
478
479pub(crate) fn build_fragments_with_text(
485 inner: &TextDocumentInner,
486 block_id: u64,
487 prefetched_text: Option<&str>,
488 effective_kind: crate::highlight::HighlighterKind,
489) -> Vec<FragmentContent> {
490 let fragments = build_raw_fragments(inner, block_id, prefetched_text);
491
492 if effective_kind == crate::highlight::HighlighterKind::Metric
500 && let Some(ref hl) = inner.highlight
501 && let Some(block_hl) = hl.blocks.get(&(block_id as usize))
502 && !block_hl.spans.is_empty()
503 {
504 return crate::highlight::merge_highlight_spans(fragments, &block_hl.spans);
505 }
506
507 fragments
508}
509
510fn build_raw_fragments(
525 inner: &TextDocumentInner,
526 block_id: u64,
527 prefetched_text: Option<&str>,
528) -> Vec<FragmentContent> {
529 let _block_dto = match block_commands::get_block(&inner.ctx, &block_id)
530 .ok()
531 .flatten()
532 {
533 Some(b) => b,
534 None => return Vec::new(),
535 };
536
537 let plain_owned;
538 let plain: &str = match prefetched_text {
539 Some(t) => t,
540 None => {
541 let entity: common::entities::Block = _block_dto.clone().into();
542 plain_owned = common::database::rope_helpers::block_content_via_store(
543 &entity,
544 inner.ctx.db_context.get_store(),
545 );
546 &plain_owned
547 }
548 };
549
550 let (runs, images) = {
551 let store = inner.ctx.db_context.get_store();
552 let runs: Vec<FormatRun> = store
553 .format_runs
554 .read()
555 .unwrap()
556 .get(&block_id)
557 .cloned()
558 .unwrap_or_default();
559 let images: Vec<ImageAnchor> = store
560 .block_images
561 .read()
562 .unwrap()
563 .get(&block_id)
564 .cloned()
565 .unwrap_or_default();
566 (runs, images)
567 };
568
569 let mut fragments = Vec::with_capacity(runs.len() + images.len() + 1);
570 let mut char_offset: usize = 0;
571 let mut byte_cursor: u32 = 0;
572 let mut img_iter = images.iter().peekable();
573
574 fn emit_default_text(
577 fragments: &mut Vec<FragmentContent>,
578 plain: &str,
579 block_id: u64,
580 byte_a: u32,
581 byte_b: u32,
582 char_offset: &mut usize,
583 byte_cursor: &mut u32,
584 ) {
585 if byte_a >= byte_b {
586 return;
587 }
588 let text = &plain[byte_a as usize..byte_b as usize];
589 let length = text.chars().count();
590 let word_starts = compute_word_starts(text);
591 fragments.push(FragmentContent::Text {
592 text: text.to_string(),
593 format: TextFormat::default(),
594 offset: *char_offset,
595 length,
596 element_id: synth_element_id(block_id, byte_a),
597 word_starts,
598 });
599 *char_offset += length;
600 *byte_cursor = byte_b;
601 }
602
603 #[allow(clippy::too_many_arguments)]
607 fn emit_run_text(
608 fragments: &mut Vec<FragmentContent>,
609 plain: &str,
610 block_id: u64,
611 byte_a: u32,
612 byte_b: u32,
613 run_format: &frontend::common::format_runs::CharacterFormat,
614 char_offset: &mut usize,
615 byte_cursor: &mut u32,
616 ) {
617 if byte_a >= byte_b {
618 return;
619 }
620 let text = &plain[byte_a as usize..byte_b as usize];
621 let length = text.chars().count();
622 let word_starts = compute_word_starts(text);
623 fragments.push(FragmentContent::Text {
624 text: text.to_string(),
625 format: TextFormat::from(run_format),
626 offset: *char_offset,
627 length,
628 element_id: synth_element_id(block_id, byte_a),
629 word_starts,
630 });
631 *char_offset += length;
632 *byte_cursor = byte_b;
633 }
634
635 for run in &runs {
636 let mut run_cursor = run.byte_start;
637
638 while let Some(img) = img_iter.peek() {
642 if img.byte_offset < run.byte_start {
643 emit_default_text(
645 &mut fragments,
646 plain,
647 block_id,
648 byte_cursor,
649 img.byte_offset,
650 &mut char_offset,
651 &mut byte_cursor,
652 );
653 fragments.push(FragmentContent::Image {
654 name: img.name.clone(),
655 width: img.width as u32,
656 height: img.height as u32,
657 quality: img.quality as u32,
658 format: TextFormat::from(&img.format),
659 offset: char_offset,
660 element_id: synth_element_id(block_id, img.byte_offset),
661 });
662 char_offset += 1;
663 img_iter.next();
664 } else if img.byte_offset <= run.byte_end {
665 emit_default_text(
668 &mut fragments,
669 plain,
670 block_id,
671 byte_cursor,
672 run_cursor,
673 &mut char_offset,
674 &mut byte_cursor,
675 );
676 emit_run_text(
678 &mut fragments,
679 plain,
680 block_id,
681 run_cursor,
682 img.byte_offset,
683 &run.format,
684 &mut char_offset,
685 &mut byte_cursor,
686 );
687 fragments.push(FragmentContent::Image {
689 name: img.name.clone(),
690 width: img.width as u32,
691 height: img.height as u32,
692 quality: img.quality as u32,
693 format: TextFormat::from(&img.format),
694 offset: char_offset,
695 element_id: synth_element_id(block_id, img.byte_offset),
696 });
697 char_offset += 1;
698 run_cursor = img.byte_offset;
699 byte_cursor = img.byte_offset;
700 img_iter.next();
701 } else {
702 break;
703 }
704 }
705
706 emit_default_text(
709 &mut fragments,
710 plain,
711 block_id,
712 byte_cursor,
713 run_cursor,
714 &mut char_offset,
715 &mut byte_cursor,
716 );
717
718 emit_run_text(
720 &mut fragments,
721 plain,
722 block_id,
723 run_cursor,
724 run.byte_end,
725 &run.format,
726 &mut char_offset,
727 &mut byte_cursor,
728 );
729 }
730
731 for img in img_iter {
733 emit_default_text(
734 &mut fragments,
735 plain,
736 block_id,
737 byte_cursor,
738 img.byte_offset,
739 &mut char_offset,
740 &mut byte_cursor,
741 );
742 fragments.push(FragmentContent::Image {
743 name: img.name.clone(),
744 width: img.width as u32,
745 height: img.height as u32,
746 quality: img.quality as u32,
747 format: TextFormat::from(&img.format),
748 offset: char_offset,
749 element_id: synth_element_id(block_id, img.byte_offset),
750 });
751 char_offset += 1;
752 }
753
754 emit_default_text(
756 &mut fragments,
757 plain,
758 block_id,
759 byte_cursor,
760 plain.len() as u32,
761 &mut char_offset,
762 &mut byte_cursor,
763 );
764
765 fragments
766}
767
768fn compute_word_starts(text: &str) -> Vec<u8> {
774 use unicode_segmentation::UnicodeSegmentation;
775 let mut result = Vec::new();
776 let mut byte_to_char: Vec<(usize, usize)> = Vec::new();
780 for (ci, (bi, _)) in text.char_indices().enumerate() {
781 byte_to_char.push((bi, ci));
782 }
783 for (byte_off, _word) in text.unicode_word_indices() {
784 let char_idx = byte_to_char
785 .iter()
786 .find(|(bi, _)| *bi == byte_off)
787 .map(|(_, ci)| *ci)
788 .unwrap_or(0);
789 if let Ok(idx) = u8::try_from(char_idx) {
796 result.push(idx);
797 } else {
798 break;
799 }
800 }
801 result
802}
803
804fn compute_list_item_index(inner: &TextDocumentInner, list_id: EntityId, block_id: u64) -> usize {
806 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
807 let store = inner.ctx.db_context.get_store();
808 crate::inner::refresh_block_positions(&mut all_blocks, store);
809 let mut list_blocks: Vec<_> = all_blocks
810 .iter()
811 .filter(|b| b.list == Some(list_id))
812 .collect();
813 list_blocks.sort_by_key(|b| b.document_position);
814 list_blocks
815 .iter()
816 .position(|b| b.id == block_id)
817 .unwrap_or(0)
818}
819
820pub(crate) fn format_list_marker(
822 list_dto: &frontend::list::dtos::ListDto,
823 item_index: usize,
824) -> String {
825 let number = item_index + 1; let marker_body = match list_dto.style {
827 ListStyle::Disc => "\u{2022}".to_string(), ListStyle::Circle => "\u{25E6}".to_string(), ListStyle::Square => "\u{25AA}".to_string(), ListStyle::Decimal => format!("{number}"),
831 ListStyle::LowerAlpha => {
832 if number <= 26 {
833 ((b'a' + (number as u8 - 1)) as char).to_string()
834 } else {
835 format!("{number}")
836 }
837 }
838 ListStyle::UpperAlpha => {
839 if number <= 26 {
840 ((b'A' + (number as u8 - 1)) as char).to_string()
841 } else {
842 format!("{number}")
843 }
844 }
845 ListStyle::LowerRoman => to_roman_lower(number),
846 ListStyle::UpperRoman => to_roman_upper(number),
847 };
848 format!("{}{marker_body}{}", list_dto.prefix, list_dto.suffix)
849}
850
851fn to_roman_upper(mut n: usize) -> String {
852 const VALUES: &[(usize, &str)] = &[
853 (1000, "M"),
854 (900, "CM"),
855 (500, "D"),
856 (400, "CD"),
857 (100, "C"),
858 (90, "XC"),
859 (50, "L"),
860 (40, "XL"),
861 (10, "X"),
862 (9, "IX"),
863 (5, "V"),
864 (4, "IV"),
865 (1, "I"),
866 ];
867 let mut result = String::new();
868 for &(val, sym) in VALUES {
869 while n >= val {
870 result.push_str(sym);
871 n -= val;
872 }
873 }
874 result
875}
876
877fn to_roman_lower(n: usize) -> String {
878 to_roman_upper(n).to_lowercase()
879}
880
881fn build_list_info(
883 inner: &TextDocumentInner,
884 block_dto: &frontend::block::dtos::BlockDto,
885) -> Option<ListInfo> {
886 let list_id = block_dto.list?;
887 let list_dto = list_commands::get_list(&inner.ctx, &{ list_id })
888 .ok()
889 .flatten()?;
890
891 let item_index = compute_list_item_index(inner, list_id, block_dto.id);
892 let marker = format_list_marker(&list_dto, item_index);
893
894 Some(ListInfo {
895 list_id: list_id as usize,
896 style: list_dto.style.clone(),
897 indent: list_dto.indent as u8,
898 marker,
899 item_index,
900 })
901}
902
903pub(crate) fn build_block_snapshot(
905 inner: &TextDocumentInner,
906 block_id: u64,
907 effective_kind: crate::highlight::HighlighterKind,
908) -> Option<BlockSnapshot> {
909 build_block_snapshot_with_position_and_parent(inner, block_id, None, None, effective_kind)
910}
911
912pub(crate) fn build_block_snapshot_with_position(
916 inner: &TextDocumentInner,
917 block_id: u64,
918 computed_position: Option<usize>,
919 effective_kind: crate::highlight::HighlighterKind,
920) -> Option<BlockSnapshot> {
921 build_block_snapshot_with_position_and_parent(
922 inner,
923 block_id,
924 computed_position,
925 None,
926 effective_kind,
927 )
928}
929
930pub(crate) fn build_block_snapshot_with_position_and_parent(
937 inner: &TextDocumentInner,
938 block_id: u64,
939 computed_position: Option<usize>,
940 parent_frame_hint: Option<EntityId>,
941 effective_kind: crate::highlight::HighlighterKind,
942) -> Option<BlockSnapshot> {
943 let mut block_dto = block_commands::get_block(&inner.ctx, &block_id)
944 .ok()
945 .flatten()?;
946 let store_for_pos = inner.ctx.db_context.get_store();
947 crate::inner::refresh_block_position(&mut block_dto, store_for_pos);
948
949 let block_format = BlockFormat::from(&block_dto);
950 let list_info = build_list_info(inner, &block_dto);
951
952 let parent_frame_id = parent_frame_hint
953 .or_else(|| find_parent_frame(inner, block_id))
954 .map(|id| id as usize);
955 let table_cell = find_table_cell_context(inner, block_id);
956
957 let position = if common::database::rope_helpers::rope_positions_match_flow(store_for_pos) {
968 to_usize(block_dto.document_position)
969 } else {
970 computed_position.unwrap_or_else(|| to_usize(block_dto.document_position))
971 };
972
973 let entity: common::entities::Block = block_dto.clone().into();
977 let store = inner.ctx.db_context.get_store();
978 let text = common::database::rope_helpers::block_content_via_store(&entity, store);
979 let length = to_usize(common::database::rope_helpers::block_char_length(
980 &entity, store,
981 ));
982 let fragments = build_fragments_with_text(inner, block_id, Some(&text), effective_kind);
983
984 let paint_highlights = if effective_kind == crate::highlight::HighlighterKind::PaintOnly {
989 inner
990 .highlight
991 .as_ref()
992 .and_then(|hl| hl.blocks.get(&(block_id as usize)))
993 .map(|bd| crate::highlight::extract_paint_spans(&bd.spans, length))
994 .unwrap_or_default()
995 } else {
996 Vec::new()
997 };
998
999 Some(BlockSnapshot {
1000 block_id: block_id as usize,
1001 position,
1002 length,
1003 text,
1004 fragments,
1005 block_format,
1006 list_info,
1007 parent_frame_id,
1008 table_cell,
1009 paint_highlights,
1010 })
1011}
1012
1013pub(crate) fn build_blocks_snapshot_for_frame(
1015 inner: &TextDocumentInner,
1016 frame_id: u64,
1017 effective_kind: crate::highlight::HighlighterKind,
1018) -> Vec<BlockSnapshot> {
1019 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
1020 .ok()
1021 .flatten()
1022 {
1023 Some(f) => f,
1024 None => return Vec::new(),
1025 };
1026
1027 let mut block_dtos: Vec<_> = frame_dto
1028 .blocks
1029 .iter()
1030 .filter_map(|&id| {
1031 block_commands::get_block(&inner.ctx, &{ id })
1032 .ok()
1033 .flatten()
1034 })
1035 .collect();
1036 let store = inner.ctx.db_context.get_store();
1037 crate::inner::refresh_block_positions(&mut block_dtos, store);
1038 block_dtos.sort_by_key(|b| b.document_position);
1039
1040 block_dtos
1041 .iter()
1042 .filter_map(|b| build_block_snapshot(inner, b.id, effective_kind))
1043 .collect()
1044}
1045
1046pub(crate) fn build_blocks_snapshot_for_frame_with_positions(
1052 inner: &TextDocumentInner,
1053 frame_id: u64,
1054 start_pos: usize,
1055 effective_kind: crate::highlight::HighlighterKind,
1056) -> (Vec<BlockSnapshot>, usize) {
1057 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
1058 .ok()
1059 .flatten()
1060 {
1061 Some(f) => f,
1062 None => return (Vec::new(), start_pos),
1063 };
1064
1065 let mut block_dtos: Vec<_> = frame_dto
1066 .blocks
1067 .iter()
1068 .filter_map(|&id| {
1069 block_commands::get_block(&inner.ctx, &{ id })
1070 .ok()
1071 .flatten()
1072 })
1073 .collect();
1074 let store = inner.ctx.db_context.get_store();
1075 crate::inner::refresh_block_positions(&mut block_dtos, store);
1076 block_dtos.sort_by_key(|b| b.document_position);
1077
1078 let mut running_pos = start_pos;
1079 let mut snapshots = Vec::with_capacity(block_dtos.len());
1080 for b in &block_dtos {
1081 if let Some(snap) =
1082 build_block_snapshot_with_position(inner, b.id, Some(running_pos), effective_kind)
1083 {
1084 running_pos += snap.length + 1; snapshots.push(snap);
1086 }
1087 }
1088 (snapshots, running_pos)
1089}