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 let spans = crate::highlight::merged_spans_for_block(
315 &inner,
316 self.block_id,
317 &crate::highlight::HighlightMask::ALL,
318 );
319 if !spans.is_empty() {
320 return crate::highlight::merge_highlight_spans(fragments, &spans);
321 }
322 fragments
323 }
324
325 pub fn list(&self) -> Option<TextList> {
329 let inner = self.doc.lock();
330 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
331 .ok()
332 .flatten()?;
333 let list_id = block_dto.list?;
334 Some(TextList {
335 doc: Arc::clone(&self.doc),
336 list_id: list_id as usize,
337 })
338 }
339
340 pub fn list_item_index(&self) -> Option<usize> {
342 let inner = self.doc.lock();
343 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
344 .ok()
345 .flatten()?;
346 let list_id = block_dto.list?;
347 Some(compute_list_item_index(
348 &inner,
349 list_id,
350 self.block_id as u64,
351 ))
352 }
353
354 pub fn snapshot(&self) -> BlockSnapshot {
358 let inner = self.doc.lock();
359 build_block_snapshot(
360 &inner,
361 self.block_id as u64,
362 crate::highlight::SnapshotHighlights {
363 kind: inner.highlight_kind,
364 mask: &crate::highlight::HighlightMask::ALL,
365 suppress_paint: false,
366 },
367 )
368 .unwrap_or_else(|| BlockSnapshot {
369 block_id: self.block_id,
370 position: 0,
371 length: 0,
372 text: String::new(),
373 fragments: Vec::new(),
374 block_format: BlockFormat::default(),
375 list_info: None,
376 parent_frame_id: None,
377 table_cell: None,
378 paint_highlights: Vec::new(),
379 })
380 }
381}
382
383pub(crate) fn find_parent_frame(inner: &TextDocumentInner, block_id: u64) -> Option<EntityId> {
389 let all_frames = frame_commands::get_all_frame(&inner.ctx).ok()?;
390 let block_entity_id = block_id as EntityId;
391 for frame in &all_frames {
392 if frame.blocks.contains(&block_entity_id) {
393 return Some(frame.id as EntityId);
394 }
395 }
396 None
397}
398
399fn document_has_no_tables(inner: &TextDocumentInner) -> bool {
404 inner.ctx.db_context.get_store().tables.read().is_empty()
405}
406
407fn find_table_cell_context(inner: &TextDocumentInner, block_id: u64) -> Option<TableCellContext> {
410 if document_has_no_tables(inner) {
414 return None;
415 }
416 let frame_id = find_parent_frame(inner, block_id)?;
417
418 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
419 .ok()
420 .flatten()?;
421
422 if let Some(table_entity_id) = frame_dto.table {
424 let table_dto =
425 frontend::commands::table_commands::get_table(&inner.ctx, &{ table_entity_id })
426 .ok()
427 .flatten()?;
428 for &cell_id in &table_dto.cells {
429 if let Some(cell_dto) =
430 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
431 .ok()
432 .flatten()
433 && cell_dto.cell_frame == Some(frame_id)
434 {
435 return Some(TableCellContext {
436 table_id: table_entity_id as usize,
437 row: to_usize(cell_dto.row),
438 column: to_usize(cell_dto.column),
439 });
440 }
441 }
442 }
443
444 let all_tables =
446 frontend::commands::table_commands::get_all_table(&inner.ctx).unwrap_or_default();
447 for table_dto in &all_tables {
448 for &cell_id in &table_dto.cells {
449 if let Some(cell_dto) =
450 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
451 .ok()
452 .flatten()
453 && cell_dto.cell_frame == Some(frame_id)
454 {
455 return Some(TableCellContext {
456 table_id: table_dto.id as usize,
457 row: to_usize(cell_dto.row),
458 column: to_usize(cell_dto.column),
459 });
460 }
461 }
462 }
463
464 None
465}
466
467fn compute_block_number(inner: &TextDocumentInner, block_id: u64) -> usize {
469 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
470 let store = inner.ctx.db_context.get_store();
471 crate::inner::refresh_block_positions(&mut all_blocks, store);
472 let mut sorted: Vec<_> = all_blocks.iter().collect();
473 sorted.sort_by_key(|b| b.document_position);
474 sorted.iter().position(|b| b.id == block_id).unwrap_or(0)
475}
476
477pub(crate) fn build_fragments(inner: &TextDocumentInner, block_id: u64) -> Vec<FragmentContent> {
480 build_fragments_with_text(
481 inner,
482 block_id,
483 None,
484 crate::highlight::SnapshotHighlights {
485 kind: inner.highlight_kind,
486 mask: &crate::highlight::HighlightMask::ALL,
487 suppress_paint: false,
488 },
489 )
490}
491
492pub(crate) fn build_fragments_with_text(
498 inner: &TextDocumentInner,
499 block_id: u64,
500 prefetched_text: Option<&str>,
501 hl: crate::highlight::SnapshotHighlights,
502) -> Vec<FragmentContent> {
503 let fragments = build_raw_fragments(inner, block_id, prefetched_text);
504
505 if hl.kind == crate::highlight::HighlighterKind::Metric {
511 let spans = crate::highlight::merged_spans_for_block(inner, block_id as usize, hl.mask);
512 if !spans.is_empty() {
513 return crate::highlight::merge_highlight_spans(fragments, &spans);
514 }
515 }
516
517 fragments
518}
519
520fn build_raw_fragments(
535 inner: &TextDocumentInner,
536 block_id: u64,
537 prefetched_text: Option<&str>,
538) -> Vec<FragmentContent> {
539 let _block_dto = match block_commands::get_block(&inner.ctx, &block_id)
540 .ok()
541 .flatten()
542 {
543 Some(b) => b,
544 None => return Vec::new(),
545 };
546
547 let plain_owned;
548 let plain: &str = match prefetched_text {
549 Some(t) => t,
550 None => {
551 let entity: common::entities::Block = _block_dto.clone().into();
552 plain_owned = common::database::rope_helpers::block_content_via_store(
553 &entity,
554 inner.ctx.db_context.get_store(),
555 );
556 &plain_owned
557 }
558 };
559
560 let (runs, images) = {
561 let store = inner.ctx.db_context.get_store();
562 let runs: Vec<FormatRun> = store
563 .format_runs
564 .read()
565 .get(&block_id)
566 .cloned()
567 .unwrap_or_default();
568 let images: Vec<ImageAnchor> = store
569 .block_images
570 .read()
571 .get(&block_id)
572 .cloned()
573 .unwrap_or_default();
574 (runs, images)
575 };
576
577 let mut fragments = Vec::with_capacity(runs.len() + images.len() + 1);
578 let mut char_offset: usize = 0;
579 let mut byte_cursor: u32 = 0;
580 let mut img_iter = images.iter().peekable();
581
582 fn emit_default_text(
585 fragments: &mut Vec<FragmentContent>,
586 plain: &str,
587 block_id: u64,
588 byte_a: u32,
589 byte_b: u32,
590 char_offset: &mut usize,
591 byte_cursor: &mut u32,
592 ) {
593 if byte_a >= byte_b {
594 return;
595 }
596 let text = &plain[byte_a as usize..byte_b as usize];
597 let length = text.chars().count();
598 let word_starts = compute_word_starts(text);
599 fragments.push(FragmentContent::Text {
600 text: text.to_string(),
601 format: TextFormat::default(),
602 offset: *char_offset,
603 length,
604 element_id: synth_element_id(block_id, byte_a),
605 word_starts,
606 });
607 *char_offset += length;
608 *byte_cursor = byte_b;
609 }
610
611 #[allow(clippy::too_many_arguments)]
615 fn emit_run_text(
616 fragments: &mut Vec<FragmentContent>,
617 plain: &str,
618 block_id: u64,
619 byte_a: u32,
620 byte_b: u32,
621 run_format: &frontend::common::format_runs::CharacterFormat,
622 char_offset: &mut usize,
623 byte_cursor: &mut u32,
624 ) {
625 if byte_a >= byte_b {
626 return;
627 }
628 let text = &plain[byte_a as usize..byte_b as usize];
629 let length = text.chars().count();
630 let word_starts = compute_word_starts(text);
631 fragments.push(FragmentContent::Text {
632 text: text.to_string(),
633 format: TextFormat::from(run_format),
634 offset: *char_offset,
635 length,
636 element_id: synth_element_id(block_id, byte_a),
637 word_starts,
638 });
639 *char_offset += length;
640 *byte_cursor = byte_b;
641 }
642
643 for run in &runs {
644 let mut run_cursor = run.byte_start;
645
646 while let Some(img) = img_iter.peek() {
650 if img.byte_offset < run.byte_start {
651 emit_default_text(
653 &mut fragments,
654 plain,
655 block_id,
656 byte_cursor,
657 img.byte_offset,
658 &mut char_offset,
659 &mut byte_cursor,
660 );
661 fragments.push(FragmentContent::Image {
662 name: img.name.clone(),
663 width: img.width as u32,
664 height: img.height as u32,
665 quality: img.quality as u32,
666 format: TextFormat::from(&img.format),
667 offset: char_offset,
668 element_id: synth_element_id(block_id, img.byte_offset),
669 });
670 char_offset += 1;
671 img_iter.next();
672 } else if img.byte_offset <= run.byte_end {
673 emit_default_text(
676 &mut fragments,
677 plain,
678 block_id,
679 byte_cursor,
680 run_cursor,
681 &mut char_offset,
682 &mut byte_cursor,
683 );
684 emit_run_text(
686 &mut fragments,
687 plain,
688 block_id,
689 run_cursor,
690 img.byte_offset,
691 &run.format,
692 &mut char_offset,
693 &mut byte_cursor,
694 );
695 fragments.push(FragmentContent::Image {
697 name: img.name.clone(),
698 width: img.width as u32,
699 height: img.height as u32,
700 quality: img.quality as u32,
701 format: TextFormat::from(&img.format),
702 offset: char_offset,
703 element_id: synth_element_id(block_id, img.byte_offset),
704 });
705 char_offset += 1;
706 run_cursor = img.byte_offset;
707 byte_cursor = img.byte_offset;
708 img_iter.next();
709 } else {
710 break;
711 }
712 }
713
714 emit_default_text(
717 &mut fragments,
718 plain,
719 block_id,
720 byte_cursor,
721 run_cursor,
722 &mut char_offset,
723 &mut byte_cursor,
724 );
725
726 emit_run_text(
728 &mut fragments,
729 plain,
730 block_id,
731 run_cursor,
732 run.byte_end,
733 &run.format,
734 &mut char_offset,
735 &mut byte_cursor,
736 );
737 }
738
739 for img in img_iter {
741 emit_default_text(
742 &mut fragments,
743 plain,
744 block_id,
745 byte_cursor,
746 img.byte_offset,
747 &mut char_offset,
748 &mut byte_cursor,
749 );
750 fragments.push(FragmentContent::Image {
751 name: img.name.clone(),
752 width: img.width as u32,
753 height: img.height as u32,
754 quality: img.quality as u32,
755 format: TextFormat::from(&img.format),
756 offset: char_offset,
757 element_id: synth_element_id(block_id, img.byte_offset),
758 });
759 char_offset += 1;
760 }
761
762 emit_default_text(
764 &mut fragments,
765 plain,
766 block_id,
767 byte_cursor,
768 plain.len() as u32,
769 &mut char_offset,
770 &mut byte_cursor,
771 );
772
773 fragments
774}
775
776fn compute_word_starts(text: &str) -> Vec<u8> {
782 use unicode_segmentation::UnicodeSegmentation;
783 let mut result = Vec::new();
784 let mut byte_to_char: Vec<(usize, usize)> = Vec::new();
788 for (ci, (bi, _)) in text.char_indices().enumerate() {
789 byte_to_char.push((bi, ci));
790 }
791 for (byte_off, _word) in text.unicode_word_indices() {
792 let char_idx = byte_to_char
793 .iter()
794 .find(|(bi, _)| *bi == byte_off)
795 .map(|(_, ci)| *ci)
796 .unwrap_or(0);
797 if let Ok(idx) = u8::try_from(char_idx) {
804 result.push(idx);
805 } else {
806 break;
807 }
808 }
809 result
810}
811
812fn compute_list_item_index(inner: &TextDocumentInner, list_id: EntityId, block_id: u64) -> usize {
814 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
815 let store = inner.ctx.db_context.get_store();
816 crate::inner::refresh_block_positions(&mut all_blocks, store);
817 let mut list_blocks: Vec<_> = all_blocks
818 .iter()
819 .filter(|b| b.list == Some(list_id))
820 .collect();
821 list_blocks.sort_by_key(|b| b.document_position);
822 list_blocks
823 .iter()
824 .position(|b| b.id == block_id)
825 .unwrap_or(0)
826}
827
828pub(crate) fn format_list_marker(
830 list_dto: &frontend::list::dtos::ListDto,
831 item_index: usize,
832) -> String {
833 let number = item_index + 1; let marker_body = match list_dto.style {
835 ListStyle::Disc => "\u{2022}".to_string(), ListStyle::Circle => "\u{25E6}".to_string(), ListStyle::Square => "\u{25AA}".to_string(), ListStyle::Decimal => format!("{number}"),
839 ListStyle::LowerAlpha => {
840 if number <= 26 {
841 ((b'a' + (number as u8 - 1)) as char).to_string()
842 } else {
843 format!("{number}")
844 }
845 }
846 ListStyle::UpperAlpha => {
847 if number <= 26 {
848 ((b'A' + (number as u8 - 1)) as char).to_string()
849 } else {
850 format!("{number}")
851 }
852 }
853 ListStyle::LowerRoman => to_roman_lower(number),
854 ListStyle::UpperRoman => to_roman_upper(number),
855 };
856 format!("{}{marker_body}{}", list_dto.prefix, list_dto.suffix)
857}
858
859fn to_roman_upper(mut n: usize) -> String {
860 const VALUES: &[(usize, &str)] = &[
861 (1000, "M"),
862 (900, "CM"),
863 (500, "D"),
864 (400, "CD"),
865 (100, "C"),
866 (90, "XC"),
867 (50, "L"),
868 (40, "XL"),
869 (10, "X"),
870 (9, "IX"),
871 (5, "V"),
872 (4, "IV"),
873 (1, "I"),
874 ];
875 let mut result = String::new();
876 for &(val, sym) in VALUES {
877 while n >= val {
878 result.push_str(sym);
879 n -= val;
880 }
881 }
882 result
883}
884
885fn to_roman_lower(n: usize) -> String {
886 to_roman_upper(n).to_lowercase()
887}
888
889fn build_list_info(
891 inner: &TextDocumentInner,
892 block_dto: &frontend::block::dtos::BlockDto,
893) -> Option<ListInfo> {
894 let list_id = block_dto.list?;
895 let list_dto = list_commands::get_list(&inner.ctx, &{ list_id })
896 .ok()
897 .flatten()?;
898
899 let item_index = compute_list_item_index(inner, list_id, block_dto.id);
900 let marker = format_list_marker(&list_dto, item_index);
901
902 Some(ListInfo {
903 list_id: list_id as usize,
904 style: list_dto.style.clone(),
905 indent: list_dto.indent as u8,
906 marker,
907 item_index,
908 })
909}
910
911pub(crate) fn build_block_snapshot(
913 inner: &TextDocumentInner,
914 block_id: u64,
915 hl: crate::highlight::SnapshotHighlights,
916) -> Option<BlockSnapshot> {
917 build_block_snapshot_with_position_and_parent(inner, block_id, None, None, hl)
918}
919
920pub(crate) fn build_block_snapshot_with_position(
924 inner: &TextDocumentInner,
925 block_id: u64,
926 computed_position: Option<usize>,
927 hl: crate::highlight::SnapshotHighlights,
928) -> Option<BlockSnapshot> {
929 build_block_snapshot_with_position_and_parent(inner, block_id, computed_position, None, hl)
930}
931
932pub(crate) fn build_block_snapshot_with_position_and_parent(
939 inner: &TextDocumentInner,
940 block_id: u64,
941 computed_position: Option<usize>,
942 parent_frame_hint: Option<EntityId>,
943 hl: crate::highlight::SnapshotHighlights,
944) -> Option<BlockSnapshot> {
945 let mut block_dto = block_commands::get_block(&inner.ctx, &block_id)
946 .ok()
947 .flatten()?;
948 let store_for_pos = inner.ctx.db_context.get_store();
949 crate::inner::refresh_block_position(&mut block_dto, store_for_pos);
950
951 let mut block_format = BlockFormat::from(&block_dto);
952 if block_format.language.is_none() {
956 block_format.language = document_commands::get_document(&inner.ctx, &inner.document_id)
957 .ok()
958 .flatten()
959 .and_then(|d| d.default_language);
960 }
961 let list_info = build_list_info(inner, &block_dto);
962
963 let parent_frame_id = parent_frame_hint
964 .or_else(|| find_parent_frame(inner, block_id))
965 .map(|id| id as usize);
966 let table_cell = find_table_cell_context(inner, block_id);
967
968 let position = if common::database::rope_helpers::rope_positions_match_flow(store_for_pos) {
979 to_usize(block_dto.document_position)
980 } else {
981 computed_position.unwrap_or_else(|| to_usize(block_dto.document_position))
982 };
983
984 let entity: common::entities::Block = block_dto.clone().into();
988 let store = inner.ctx.db_context.get_store();
989 let text = common::database::rope_helpers::block_content_via_store(&entity, store);
990 let length = to_usize(common::database::rope_helpers::block_char_length(
991 &entity, store,
992 ));
993 let fragments = build_fragments_with_text(inner, block_id, Some(&text), hl);
994
995 let paint_highlights =
1000 if hl.kind == crate::highlight::HighlighterKind::PaintOnly && !hl.suppress_paint {
1001 let spans = crate::highlight::merged_spans_for_block(inner, block_id as usize, hl.mask);
1002 crate::highlight::extract_paint_spans(&spans, length)
1003 } else {
1004 Vec::new()
1005 };
1006
1007 Some(BlockSnapshot {
1008 block_id: block_id as usize,
1009 position,
1010 length,
1011 text,
1012 fragments,
1013 block_format,
1014 list_info,
1015 parent_frame_id,
1016 table_cell,
1017 paint_highlights,
1018 })
1019}
1020
1021pub(crate) fn build_blocks_snapshot_for_frame(
1023 inner: &TextDocumentInner,
1024 frame_id: u64,
1025 hl: crate::highlight::SnapshotHighlights,
1026) -> Vec<BlockSnapshot> {
1027 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
1028 .ok()
1029 .flatten()
1030 {
1031 Some(f) => f,
1032 None => return Vec::new(),
1033 };
1034
1035 let mut block_dtos: Vec<_> = frame_dto
1036 .blocks
1037 .iter()
1038 .filter_map(|&id| {
1039 block_commands::get_block(&inner.ctx, &{ id })
1040 .ok()
1041 .flatten()
1042 })
1043 .collect();
1044 let store = inner.ctx.db_context.get_store();
1045 crate::inner::refresh_block_positions(&mut block_dtos, store);
1046 block_dtos.sort_by_key(|b| b.document_position);
1047
1048 block_dtos
1049 .iter()
1050 .filter_map(|b| build_block_snapshot(inner, b.id, hl))
1051 .collect()
1052}
1053
1054pub(crate) fn build_blocks_snapshot_for_frame_with_positions(
1060 inner: &TextDocumentInner,
1061 frame_id: u64,
1062 start_pos: usize,
1063 hl: crate::highlight::SnapshotHighlights,
1064) -> (Vec<BlockSnapshot>, usize) {
1065 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
1066 .ok()
1067 .flatten()
1068 {
1069 Some(f) => f,
1070 None => return (Vec::new(), start_pos),
1071 };
1072
1073 let mut block_dtos: Vec<_> = frame_dto
1074 .blocks
1075 .iter()
1076 .filter_map(|&id| {
1077 block_commands::get_block(&inner.ctx, &{ id })
1078 .ok()
1079 .flatten()
1080 })
1081 .collect();
1082 let store = inner.ctx.db_context.get_store();
1083 crate::inner::refresh_block_positions(&mut block_dtos, store);
1084 block_dtos.sort_by_key(|b| b.document_position);
1085
1086 let mut running_pos = start_pos;
1087 let mut snapshots = Vec::with_capacity(block_dtos.len());
1088 for b in &block_dtos {
1089 if let Some(snap) = build_block_snapshot_with_position(inner, b.id, Some(running_pos), hl) {
1090 running_pos += snap.length + 1; snapshots.push(snap);
1092 }
1093 }
1094 (snapshots, running_pos)
1095}