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 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
372pub(crate) fn 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.ctx.db_context.get_store().tables.read().is_empty()
394}
395
396fn find_table_cell_context(inner: &TextDocumentInner, block_id: u64) -> Option<TableCellContext> {
399 if document_has_no_tables(inner) {
403 return None;
404 }
405 let frame_id = find_parent_frame(inner, block_id)?;
406
407 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
408 .ok()
409 .flatten()?;
410
411 if let Some(table_entity_id) = frame_dto.table {
413 let table_dto =
414 frontend::commands::table_commands::get_table(&inner.ctx, &{ table_entity_id })
415 .ok()
416 .flatten()?;
417 for &cell_id in &table_dto.cells {
418 if let Some(cell_dto) =
419 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
420 .ok()
421 .flatten()
422 && cell_dto.cell_frame == Some(frame_id)
423 {
424 return Some(TableCellContext {
425 table_id: table_entity_id as usize,
426 row: to_usize(cell_dto.row),
427 column: to_usize(cell_dto.column),
428 });
429 }
430 }
431 }
432
433 let all_tables =
435 frontend::commands::table_commands::get_all_table(&inner.ctx).unwrap_or_default();
436 for table_dto in &all_tables {
437 for &cell_id in &table_dto.cells {
438 if let Some(cell_dto) =
439 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
440 .ok()
441 .flatten()
442 && cell_dto.cell_frame == Some(frame_id)
443 {
444 return Some(TableCellContext {
445 table_id: table_dto.id as usize,
446 row: to_usize(cell_dto.row),
447 column: to_usize(cell_dto.column),
448 });
449 }
450 }
451 }
452
453 None
454}
455
456fn compute_block_number(inner: &TextDocumentInner, block_id: u64) -> usize {
458 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
459 let store = inner.ctx.db_context.get_store();
460 crate::inner::refresh_block_positions(&mut all_blocks, store);
461 let mut sorted: Vec<_> = all_blocks.iter().collect();
462 sorted.sort_by_key(|b| b.document_position);
463 sorted.iter().position(|b| b.id == block_id).unwrap_or(0)
464}
465
466pub(crate) fn build_fragments(inner: &TextDocumentInner, block_id: u64) -> Vec<FragmentContent> {
469 build_fragments_with_text(inner, block_id, None, inner.highlight_kind)
470}
471
472pub(crate) fn build_fragments_with_text(
478 inner: &TextDocumentInner,
479 block_id: u64,
480 prefetched_text: Option<&str>,
481 effective_kind: crate::highlight::HighlighterKind,
482) -> Vec<FragmentContent> {
483 let fragments = build_raw_fragments(inner, block_id, prefetched_text);
484
485 if effective_kind == crate::highlight::HighlighterKind::Metric
493 && let Some(ref hl) = inner.highlight
494 && let Some(block_hl) = hl.blocks.get(&(block_id as usize))
495 && !block_hl.spans.is_empty()
496 {
497 return crate::highlight::merge_highlight_spans(fragments, &block_hl.spans);
498 }
499
500 fragments
501}
502
503fn build_raw_fragments(
518 inner: &TextDocumentInner,
519 block_id: u64,
520 prefetched_text: Option<&str>,
521) -> Vec<FragmentContent> {
522 let _block_dto = match block_commands::get_block(&inner.ctx, &block_id)
523 .ok()
524 .flatten()
525 {
526 Some(b) => b,
527 None => return Vec::new(),
528 };
529
530 let plain_owned;
531 let plain: &str = match prefetched_text {
532 Some(t) => t,
533 None => {
534 let entity: common::entities::Block = _block_dto.clone().into();
535 plain_owned = common::database::rope_helpers::block_content_via_store(
536 &entity,
537 inner.ctx.db_context.get_store(),
538 );
539 &plain_owned
540 }
541 };
542
543 let (runs, images) = {
544 let store = inner.ctx.db_context.get_store();
545 let runs: Vec<FormatRun> = store
546 .format_runs
547 .read()
548 .get(&block_id)
549 .cloned()
550 .unwrap_or_default();
551 let images: Vec<ImageAnchor> = store
552 .block_images
553 .read()
554 .get(&block_id)
555 .cloned()
556 .unwrap_or_default();
557 (runs, images)
558 };
559
560 let mut fragments = Vec::with_capacity(runs.len() + images.len() + 1);
561 let mut char_offset: usize = 0;
562 let mut byte_cursor: u32 = 0;
563 let mut img_iter = images.iter().peekable();
564
565 fn emit_default_text(
568 fragments: &mut Vec<FragmentContent>,
569 plain: &str,
570 block_id: u64,
571 byte_a: u32,
572 byte_b: u32,
573 char_offset: &mut usize,
574 byte_cursor: &mut u32,
575 ) {
576 if byte_a >= byte_b {
577 return;
578 }
579 let text = &plain[byte_a as usize..byte_b as usize];
580 let length = text.chars().count();
581 let word_starts = compute_word_starts(text);
582 fragments.push(FragmentContent::Text {
583 text: text.to_string(),
584 format: TextFormat::default(),
585 offset: *char_offset,
586 length,
587 element_id: synth_element_id(block_id, byte_a),
588 word_starts,
589 });
590 *char_offset += length;
591 *byte_cursor = byte_b;
592 }
593
594 #[allow(clippy::too_many_arguments)]
598 fn emit_run_text(
599 fragments: &mut Vec<FragmentContent>,
600 plain: &str,
601 block_id: u64,
602 byte_a: u32,
603 byte_b: u32,
604 run_format: &frontend::common::format_runs::CharacterFormat,
605 char_offset: &mut usize,
606 byte_cursor: &mut u32,
607 ) {
608 if byte_a >= byte_b {
609 return;
610 }
611 let text = &plain[byte_a as usize..byte_b as usize];
612 let length = text.chars().count();
613 let word_starts = compute_word_starts(text);
614 fragments.push(FragmentContent::Text {
615 text: text.to_string(),
616 format: TextFormat::from(run_format),
617 offset: *char_offset,
618 length,
619 element_id: synth_element_id(block_id, byte_a),
620 word_starts,
621 });
622 *char_offset += length;
623 *byte_cursor = byte_b;
624 }
625
626 for run in &runs {
627 let mut run_cursor = run.byte_start;
628
629 while let Some(img) = img_iter.peek() {
633 if img.byte_offset < run.byte_start {
634 emit_default_text(
636 &mut fragments,
637 plain,
638 block_id,
639 byte_cursor,
640 img.byte_offset,
641 &mut char_offset,
642 &mut byte_cursor,
643 );
644 fragments.push(FragmentContent::Image {
645 name: img.name.clone(),
646 width: img.width as u32,
647 height: img.height as u32,
648 quality: img.quality as u32,
649 format: TextFormat::from(&img.format),
650 offset: char_offset,
651 element_id: synth_element_id(block_id, img.byte_offset),
652 });
653 char_offset += 1;
654 img_iter.next();
655 } else if img.byte_offset <= run.byte_end {
656 emit_default_text(
659 &mut fragments,
660 plain,
661 block_id,
662 byte_cursor,
663 run_cursor,
664 &mut char_offset,
665 &mut byte_cursor,
666 );
667 emit_run_text(
669 &mut fragments,
670 plain,
671 block_id,
672 run_cursor,
673 img.byte_offset,
674 &run.format,
675 &mut char_offset,
676 &mut byte_cursor,
677 );
678 fragments.push(FragmentContent::Image {
680 name: img.name.clone(),
681 width: img.width as u32,
682 height: img.height as u32,
683 quality: img.quality as u32,
684 format: TextFormat::from(&img.format),
685 offset: char_offset,
686 element_id: synth_element_id(block_id, img.byte_offset),
687 });
688 char_offset += 1;
689 run_cursor = img.byte_offset;
690 byte_cursor = img.byte_offset;
691 img_iter.next();
692 } else {
693 break;
694 }
695 }
696
697 emit_default_text(
700 &mut fragments,
701 plain,
702 block_id,
703 byte_cursor,
704 run_cursor,
705 &mut char_offset,
706 &mut byte_cursor,
707 );
708
709 emit_run_text(
711 &mut fragments,
712 plain,
713 block_id,
714 run_cursor,
715 run.byte_end,
716 &run.format,
717 &mut char_offset,
718 &mut byte_cursor,
719 );
720 }
721
722 for img in img_iter {
724 emit_default_text(
725 &mut fragments,
726 plain,
727 block_id,
728 byte_cursor,
729 img.byte_offset,
730 &mut char_offset,
731 &mut byte_cursor,
732 );
733 fragments.push(FragmentContent::Image {
734 name: img.name.clone(),
735 width: img.width as u32,
736 height: img.height as u32,
737 quality: img.quality as u32,
738 format: TextFormat::from(&img.format),
739 offset: char_offset,
740 element_id: synth_element_id(block_id, img.byte_offset),
741 });
742 char_offset += 1;
743 }
744
745 emit_default_text(
747 &mut fragments,
748 plain,
749 block_id,
750 byte_cursor,
751 plain.len() as u32,
752 &mut char_offset,
753 &mut byte_cursor,
754 );
755
756 fragments
757}
758
759fn compute_word_starts(text: &str) -> Vec<u8> {
765 use unicode_segmentation::UnicodeSegmentation;
766 let mut result = Vec::new();
767 let mut byte_to_char: Vec<(usize, usize)> = Vec::new();
771 for (ci, (bi, _)) in text.char_indices().enumerate() {
772 byte_to_char.push((bi, ci));
773 }
774 for (byte_off, _word) in text.unicode_word_indices() {
775 let char_idx = byte_to_char
776 .iter()
777 .find(|(bi, _)| *bi == byte_off)
778 .map(|(_, ci)| *ci)
779 .unwrap_or(0);
780 if let Ok(idx) = u8::try_from(char_idx) {
787 result.push(idx);
788 } else {
789 break;
790 }
791 }
792 result
793}
794
795fn compute_list_item_index(inner: &TextDocumentInner, list_id: EntityId, block_id: u64) -> usize {
797 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
798 let store = inner.ctx.db_context.get_store();
799 crate::inner::refresh_block_positions(&mut all_blocks, store);
800 let mut list_blocks: Vec<_> = all_blocks
801 .iter()
802 .filter(|b| b.list == Some(list_id))
803 .collect();
804 list_blocks.sort_by_key(|b| b.document_position);
805 list_blocks
806 .iter()
807 .position(|b| b.id == block_id)
808 .unwrap_or(0)
809}
810
811pub(crate) fn format_list_marker(
813 list_dto: &frontend::list::dtos::ListDto,
814 item_index: usize,
815) -> String {
816 let number = item_index + 1; let marker_body = match list_dto.style {
818 ListStyle::Disc => "\u{2022}".to_string(), ListStyle::Circle => "\u{25E6}".to_string(), ListStyle::Square => "\u{25AA}".to_string(), ListStyle::Decimal => format!("{number}"),
822 ListStyle::LowerAlpha => {
823 if number <= 26 {
824 ((b'a' + (number as u8 - 1)) as char).to_string()
825 } else {
826 format!("{number}")
827 }
828 }
829 ListStyle::UpperAlpha => {
830 if number <= 26 {
831 ((b'A' + (number as u8 - 1)) as char).to_string()
832 } else {
833 format!("{number}")
834 }
835 }
836 ListStyle::LowerRoman => to_roman_lower(number),
837 ListStyle::UpperRoman => to_roman_upper(number),
838 };
839 format!("{}{marker_body}{}", list_dto.prefix, list_dto.suffix)
840}
841
842fn to_roman_upper(mut n: usize) -> String {
843 const VALUES: &[(usize, &str)] = &[
844 (1000, "M"),
845 (900, "CM"),
846 (500, "D"),
847 (400, "CD"),
848 (100, "C"),
849 (90, "XC"),
850 (50, "L"),
851 (40, "XL"),
852 (10, "X"),
853 (9, "IX"),
854 (5, "V"),
855 (4, "IV"),
856 (1, "I"),
857 ];
858 let mut result = String::new();
859 for &(val, sym) in VALUES {
860 while n >= val {
861 result.push_str(sym);
862 n -= val;
863 }
864 }
865 result
866}
867
868fn to_roman_lower(n: usize) -> String {
869 to_roman_upper(n).to_lowercase()
870}
871
872fn build_list_info(
874 inner: &TextDocumentInner,
875 block_dto: &frontend::block::dtos::BlockDto,
876) -> Option<ListInfo> {
877 let list_id = block_dto.list?;
878 let list_dto = list_commands::get_list(&inner.ctx, &{ list_id })
879 .ok()
880 .flatten()?;
881
882 let item_index = compute_list_item_index(inner, list_id, block_dto.id);
883 let marker = format_list_marker(&list_dto, item_index);
884
885 Some(ListInfo {
886 list_id: list_id as usize,
887 style: list_dto.style.clone(),
888 indent: list_dto.indent as u8,
889 marker,
890 item_index,
891 })
892}
893
894pub(crate) fn build_block_snapshot(
896 inner: &TextDocumentInner,
897 block_id: u64,
898 effective_kind: crate::highlight::HighlighterKind,
899) -> Option<BlockSnapshot> {
900 build_block_snapshot_with_position_and_parent(inner, block_id, None, None, effective_kind)
901}
902
903pub(crate) fn build_block_snapshot_with_position(
907 inner: &TextDocumentInner,
908 block_id: u64,
909 computed_position: Option<usize>,
910 effective_kind: crate::highlight::HighlighterKind,
911) -> Option<BlockSnapshot> {
912 build_block_snapshot_with_position_and_parent(
913 inner,
914 block_id,
915 computed_position,
916 None,
917 effective_kind,
918 )
919}
920
921pub(crate) fn build_block_snapshot_with_position_and_parent(
928 inner: &TextDocumentInner,
929 block_id: u64,
930 computed_position: Option<usize>,
931 parent_frame_hint: Option<EntityId>,
932 effective_kind: crate::highlight::HighlighterKind,
933) -> Option<BlockSnapshot> {
934 let mut block_dto = block_commands::get_block(&inner.ctx, &block_id)
935 .ok()
936 .flatten()?;
937 let store_for_pos = inner.ctx.db_context.get_store();
938 crate::inner::refresh_block_position(&mut block_dto, store_for_pos);
939
940 let mut block_format = BlockFormat::from(&block_dto);
941 if block_format.language.is_none() {
945 block_format.language = document_commands::get_document(&inner.ctx, &inner.document_id)
946 .ok()
947 .flatten()
948 .and_then(|d| d.default_language);
949 }
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}