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::{
13 AddressablePiece, BlockSnapshot, FragmentContent, ListInfo, TableCellContext, TableCellRef,
14};
15use crate::inner::TextDocumentInner;
16use crate::text_frame::TextFrame;
17use crate::text_list::TextList;
18use crate::text_table::TextTable;
19use crate::{BlockFormat, ListStyle, TextFormat};
20
21#[derive(Clone)]
28pub struct TextBlock {
29 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
30 pub(crate) block_id: usize,
31}
32
33impl TextBlock {
34 pub fn text(&self) -> String {
38 let inner = self.doc.lock();
39 let store = inner.ctx.db_context.get_store();
40 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
41 .ok()
42 .flatten()
43 .map(|b| {
44 let entity: common::entities::Block = b.into();
45 common::database::rope_helpers::block_content_via_store(&entity, store)
46 })
47 .unwrap_or_default()
48 }
49
50 pub fn length(&self) -> usize {
52 let inner = self.doc.lock();
53 let store = inner.ctx.db_context.get_store();
54 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
55 .ok()
56 .flatten()
57 .map(|b| {
58 let entity: common::entities::Block = b.into();
59 to_usize(common::database::rope_helpers::block_char_length(
60 &entity, store,
61 ))
62 })
63 .unwrap_or(0)
64 }
65
66 pub fn is_empty(&self) -> bool {
68 let inner = self.doc.lock();
69 let store = inner.ctx.db_context.get_store();
70 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
71 .ok()
72 .flatten()
73 .map(|b| {
74 let entity: common::entities::Block = b.into();
75 common::database::rope_helpers::block_char_length(&entity, store) == 0
76 })
77 .unwrap_or(true)
78 }
79
80 pub fn is_valid(&self) -> bool {
82 let inner = self.doc.lock();
83 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
84 .ok()
85 .flatten()
86 .is_some()
87 }
88
89 pub fn id(&self) -> usize {
93 self.block_id
94 }
95
96 pub fn position(&self) -> usize {
100 let inner = self.doc.lock();
101 let Some(mut dto) = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
102 .ok()
103 .flatten()
104 else {
105 return 0;
106 };
107 let store = inner.ctx.db_context.get_store();
108 crate::inner::refresh_block_position(&mut dto, store);
109 to_usize(dto.document_position)
110 }
111
112 pub fn block_number(&self) -> usize {
116 let inner = self.doc.lock();
117 compute_block_number(&inner, self.block_id as u64)
118 }
119
120 pub fn next(&self) -> Option<TextBlock> {
123 let inner = self.doc.lock();
124 let all_blocks = block_commands::get_all_block(&inner.ctx).ok()?;
125 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
126 let store = inner.ctx.db_context.get_store();
127 crate::inner::refresh_block_positions(&mut sorted, store);
128 sorted.sort_by_key(|b| b.document_position);
129 let idx = sorted.iter().position(|b| b.id == self.block_id as u64)?;
130 sorted.get(idx + 1).map(|b| TextBlock {
131 doc: Arc::clone(&self.doc),
132 block_id: b.id as usize,
133 })
134 }
135
136 pub fn previous(&self) -> Option<TextBlock> {
139 let inner = self.doc.lock();
140 let all_blocks = block_commands::get_all_block(&inner.ctx).ok()?;
141 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
142 let store = inner.ctx.db_context.get_store();
143 crate::inner::refresh_block_positions(&mut sorted, store);
144 sorted.sort_by_key(|b| b.document_position);
145 let idx = sorted.iter().position(|b| b.id == self.block_id as u64)?;
146 if idx == 0 {
147 return None;
148 }
149 sorted.get(idx - 1).map(|b| TextBlock {
150 doc: Arc::clone(&self.doc),
151 block_id: b.id as usize,
152 })
153 }
154
155 pub fn frame(&self) -> TextFrame {
159 let inner = self.doc.lock();
160 let frame_id = find_parent_frame(&inner, self.block_id as u64);
161 TextFrame {
162 doc: Arc::clone(&self.doc),
163 frame_id: frame_id.map(|id| id as usize).unwrap_or(0),
164 }
165 }
166
167 pub fn table_cell(&self) -> Option<TableCellRef> {
173 let inner = self.doc.lock();
174 let frame_id = find_parent_frame(&inner, self.block_id as u64)?;
175
176 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
179 .ok()
180 .flatten()?;
181
182 if let Some(table_entity_id) = frame_dto.table {
183 let table_dto =
187 frontend::commands::table_commands::get_table(&inner.ctx, &{ table_entity_id })
188 .ok()
189 .flatten()?;
190 for &cell_id in &table_dto.cells {
191 if let Some(cell_dto) =
192 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{
193 cell_id
194 })
195 .ok()
196 .flatten()
197 && cell_dto.cell_frame == Some(frame_id)
198 {
199 return Some(TableCellRef {
200 table: TextTable {
201 doc: Arc::clone(&self.doc),
202 table_id: table_entity_id as usize,
203 },
204 row: to_usize(cell_dto.row),
205 column: to_usize(cell_dto.column),
206 });
207 }
208 }
209 }
210
211 let all_tables =
214 frontend::commands::table_commands::get_all_table(&inner.ctx).unwrap_or_default();
215 for table_dto in &all_tables {
216 for &cell_id in &table_dto.cells {
217 if let Some(cell_dto) =
218 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{
219 cell_id
220 })
221 .ok()
222 .flatten()
223 && cell_dto.cell_frame == Some(frame_id)
224 {
225 return Some(TableCellRef {
226 table: TextTable {
227 doc: Arc::clone(&self.doc),
228 table_id: table_dto.id as usize,
229 },
230 row: to_usize(cell_dto.row),
231 column: to_usize(cell_dto.column),
232 });
233 }
234 }
235 }
236
237 None
238 }
239
240 pub fn block_format(&self) -> BlockFormat {
244 let inner = self.doc.lock();
245 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
246 .ok()
247 .flatten()
248 .map(|b| BlockFormat::from(&b))
249 .unwrap_or_default()
250 }
251
252 pub fn char_format_at(&self, offset: usize) -> Option<TextFormat> {
259 let inner = self.doc.lock();
260 let fragments = build_fragments(&inner, self.block_id as u64);
261 for frag in &fragments {
262 match frag {
263 FragmentContent::Text {
264 format,
265 offset: frag_offset,
266 length,
267 ..
268 } => {
269 if offset >= *frag_offset && offset < frag_offset + length {
270 return Some(format.clone());
271 }
272 }
273 FragmentContent::Image {
274 format,
275 offset: frag_offset,
276 ..
277 }
278 | FragmentContent::FootnoteReference {
279 format,
280 offset: frag_offset,
281 ..
282 } => {
283 if offset == *frag_offset {
284 return Some(format.clone());
285 }
286 }
287 }
288 }
289 None
290 }
291
292 pub fn fragments(&self) -> Vec<FragmentContent> {
305 let inner = self.doc.lock();
306 build_fragments(&inner, self.block_id as u64)
307 }
308
309 pub fn display_fragments(&self) -> Vec<FragmentContent> {
317 let inner = self.doc.lock();
318 let fragments = build_raw_fragments(&inner, self.block_id as u64, None);
319 let spans = crate::highlight::merged_spans_for_block(
322 &inner,
323 self.block_id,
324 &crate::highlight::HighlightMask::ALL,
325 );
326 if !spans.is_empty() {
327 return crate::highlight::merge_highlight_spans(fragments, &spans);
328 }
329 fragments
330 }
331
332 pub fn addressable_inline_pieces(&self) -> Vec<AddressablePiece> {
351 let inner = self.doc.lock();
352 let Some(block_dto) = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
353 .ok()
354 .flatten()
355 else {
356 return Vec::new();
357 };
358 let entity: common::entities::Block = block_dto.into();
359 let store = inner.ctx.db_context.get_store();
360 let plain_text = common::database::rope_helpers::block_content_via_store(&entity, store);
361 common::format_runs_query::addressable_inline_pieces_for_block(store, &entity, &plain_text)
362 .into_iter()
363 .map(|p| AddressablePiece {
364 start: to_usize(p.start as i64),
365 end: to_usize(p.end as i64),
366 content: p.content,
367 format: TextFormat::from(&p.format),
368 })
369 .collect()
370 }
371
372 pub fn list(&self) -> Option<TextList> {
376 let inner = self.doc.lock();
377 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
378 .ok()
379 .flatten()?;
380 let list_id = block_dto.list?;
381 Some(TextList {
382 doc: Arc::clone(&self.doc),
383 list_id: list_id as usize,
384 })
385 }
386
387 pub fn list_item_index(&self) -> Option<usize> {
389 let inner = self.doc.lock();
390 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
391 .ok()
392 .flatten()?;
393 let list_id = block_dto.list?;
394 Some(compute_list_item_index(
395 &inner,
396 list_id,
397 self.block_id as u64,
398 ))
399 }
400
401 pub fn snapshot(&self) -> BlockSnapshot {
405 let inner = self.doc.lock();
406 build_block_snapshot(
407 &inner,
408 self.block_id as u64,
409 crate::highlight::SnapshotHighlights {
410 kind: inner.highlight_kind,
411 mask: &crate::highlight::HighlightMask::ALL,
412 suppress_paint: false,
413 },
414 )
415 .unwrap_or_else(|| BlockSnapshot {
416 block_id: self.block_id,
417 position: 0,
418 length: 0,
419 text: String::new(),
420 fragments: Vec::new(),
421 block_format: BlockFormat::default(),
422 list_info: None,
423 parent_frame_id: None,
424 table_cell: None,
425 paint_highlights: Vec::new(),
426 })
427 }
428}
429
430pub(crate) fn find_parent_frame(inner: &TextDocumentInner, block_id: u64) -> Option<EntityId> {
436 let all_frames = frame_commands::get_all_frame(&inner.ctx).ok()?;
437 let block_entity_id = block_id as EntityId;
438 for frame in &all_frames {
439 if frame.blocks.contains(&block_entity_id) {
440 return Some(frame.id as EntityId);
441 }
442 }
443 None
444}
445
446fn document_has_no_tables(inner: &TextDocumentInner) -> bool {
451 inner.ctx.db_context.get_store().tables.read().is_empty()
452}
453
454fn find_table_cell_context(inner: &TextDocumentInner, block_id: u64) -> Option<TableCellContext> {
457 if document_has_no_tables(inner) {
461 return None;
462 }
463 let frame_id = find_parent_frame(inner, block_id)?;
464
465 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
466 .ok()
467 .flatten()?;
468
469 if let Some(table_entity_id) = frame_dto.table {
471 let table_dto =
472 frontend::commands::table_commands::get_table(&inner.ctx, &{ table_entity_id })
473 .ok()
474 .flatten()?;
475 for &cell_id in &table_dto.cells {
476 if let Some(cell_dto) =
477 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
478 .ok()
479 .flatten()
480 && cell_dto.cell_frame == Some(frame_id)
481 {
482 return Some(TableCellContext {
483 table_id: table_entity_id as usize,
484 row: to_usize(cell_dto.row),
485 column: to_usize(cell_dto.column),
486 });
487 }
488 }
489 }
490
491 let all_tables =
493 frontend::commands::table_commands::get_all_table(&inner.ctx).unwrap_or_default();
494 for table_dto in &all_tables {
495 for &cell_id in &table_dto.cells {
496 if let Some(cell_dto) =
497 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
498 .ok()
499 .flatten()
500 && cell_dto.cell_frame == Some(frame_id)
501 {
502 return Some(TableCellContext {
503 table_id: table_dto.id as usize,
504 row: to_usize(cell_dto.row),
505 column: to_usize(cell_dto.column),
506 });
507 }
508 }
509 }
510
511 None
512}
513
514fn compute_block_number(inner: &TextDocumentInner, block_id: u64) -> usize {
516 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
517 let store = inner.ctx.db_context.get_store();
518 crate::inner::refresh_block_positions(&mut all_blocks, store);
519 let mut sorted: Vec<_> = all_blocks.iter().collect();
520 sorted.sort_by_key(|b| b.document_position);
521 sorted.iter().position(|b| b.id == block_id).unwrap_or(0)
522}
523
524pub(crate) fn build_fragments(inner: &TextDocumentInner, block_id: u64) -> Vec<FragmentContent> {
527 build_fragments_with_text(
528 inner,
529 block_id,
530 None,
531 crate::highlight::SnapshotHighlights {
532 kind: inner.highlight_kind,
533 mask: &crate::highlight::HighlightMask::ALL,
534 suppress_paint: false,
535 },
536 )
537}
538
539pub(crate) fn build_fragments_with_text(
545 inner: &TextDocumentInner,
546 block_id: u64,
547 prefetched_text: Option<&str>,
548 hl: crate::highlight::SnapshotHighlights,
549) -> Vec<FragmentContent> {
550 let fragments = build_raw_fragments(inner, block_id, prefetched_text);
551
552 if hl.kind == crate::highlight::HighlighterKind::Metric {
558 let spans = crate::highlight::merged_spans_for_block(inner, block_id as usize, hl.mask);
559 if !spans.is_empty() {
560 return crate::highlight::merge_highlight_spans(fragments, &spans);
561 }
562 }
563
564 fragments
565}
566
567fn document_self_footnote_numbers(
592 store: &common::database::Store,
593) -> std::collections::HashMap<String, usize> {
594 let definition_blocks: std::collections::HashSet<common::types::EntityId> = store
595 .frames
596 .read()
597 .values()
598 .filter(|f| f.footnote_label.is_some())
599 .flat_map(|f| f.child_order.iter().copied())
600 .filter(|child| *child > 0)
601 .map(|child| child as common::types::EntityId)
602 .collect();
603
604 let mut ordered: Vec<(i64, common::types::EntityId)> = store
605 .blocks
606 .read()
607 .values()
608 .filter(|b| !definition_blocks.contains(&b.id))
609 .map(|b| (b.document_position, b.id))
610 .collect();
611 ordered.sort_unstable();
612
613 let refs = store.block_footnote_refs.read();
614 let mut numbers: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
615 let mut next = 1usize;
616 for (_, block_id) in ordered {
617 let Some(anchors) = refs.get(&block_id) else {
618 continue;
619 };
620 let mut in_block: Vec<_> = anchors.iter().collect();
621 in_block.sort_by_key(|a| a.byte_offset);
622 for anchor in in_block {
623 numbers.entry(anchor.label.clone()).or_insert_with(|| {
624 let n = next;
625 next += 1;
626 n
627 });
628 }
629 }
630 numbers
631}
632
633fn build_raw_fragments(
648 inner: &TextDocumentInner,
649 block_id: u64,
650 prefetched_text: Option<&str>,
651) -> Vec<FragmentContent> {
652 let _block_dto = match block_commands::get_block(&inner.ctx, &block_id)
653 .ok()
654 .flatten()
655 {
656 Some(b) => b,
657 None => return Vec::new(),
658 };
659
660 let plain_owned;
661 let plain: &str = match prefetched_text {
662 Some(t) => t,
663 None => {
664 let entity: common::entities::Block = _block_dto.clone().into();
665 plain_owned = common::database::rope_helpers::block_content_via_store(
666 &entity,
667 inner.ctx.db_context.get_store(),
668 );
669 &plain_owned
670 }
671 };
672
673 let (runs, images, notes, markers) = {
674 let store = inner.ctx.db_context.get_store();
675 let runs: Vec<FormatRun> = store
676 .format_runs
677 .read()
678 .get(&block_id)
679 .cloned()
680 .unwrap_or_default();
681 let images: Vec<ImageAnchor> = store
682 .block_images
683 .read()
684 .get(&block_id)
685 .cloned()
686 .unwrap_or_default();
687 let notes = store
688 .block_footnote_refs
689 .read()
690 .get(&block_id)
691 .cloned()
692 .unwrap_or_default();
693 let markers = if notes.is_empty() {
697 std::collections::HashMap::new()
698 } else {
699 store.footnote_markers.read().clone()
700 };
701 (runs, images, notes, markers)
702 };
703
704 let anchors = frontend::common::format_runs::block_anchors(&images, ¬es);
709 let pieces = frontend::common::format_runs::merge_runs_and_anchors(plain, &runs, &anchors);
710
711 let mut fragments = Vec::with_capacity(pieces.len());
712 let mut char_offset: usize = 0;
713 let mut self_numbers: Option<std::collections::HashMap<String, usize>> = None;
718
719 for piece in pieces {
720 match piece {
721 frontend::common::format_runs::InlinePiece::Text { start, end, format } => {
722 let text = &plain[start as usize..end as usize];
723 let length = text.chars().count();
724 let word_starts = compute_word_starts(text);
725 fragments.push(FragmentContent::Text {
726 text: text.to_string(),
727 format: format.map(TextFormat::from).unwrap_or_default(),
728 offset: char_offset,
729 length,
730 element_id: synth_element_id(block_id, start),
731 word_starts,
732 });
733 char_offset += length;
734 }
735 frontend::common::format_runs::InlinePiece::FootnoteRef(note) => {
736 fragments.push(FragmentContent::FootnoteReference {
737 label: note.label.clone(),
738 marker: markers.get(¬e.label).cloned().unwrap_or_else(|| {
755 self_numbers
756 .get_or_insert_with(|| {
757 document_self_footnote_numbers(inner.ctx.db_context.get_store())
758 })
759 .get(¬e.label)
760 .map(|n| n.to_string())
761 .unwrap_or_else(|| note.label.clone())
762 }),
763 format: TextFormat::from(¬e.format),
764 offset: char_offset,
765 element_id: synth_element_id(block_id, note.byte_offset),
766 });
767 char_offset += 1;
770 }
771 frontend::common::format_runs::InlinePiece::Image(img) => {
772 fragments.push(FragmentContent::Image {
773 name: img.name.clone(),
774 alt: img.alt.clone(),
775 width: img.width as u32,
776 height: img.height as u32,
777 quality: img.quality as u32,
778 format: TextFormat::from(&img.format),
779 offset: char_offset,
780 element_id: synth_element_id(block_id, img.byte_offset),
781 });
782 char_offset += 1;
785 }
786 }
787 }
788
789 fragments
790}
791
792fn compute_word_starts(text: &str) -> Vec<u8> {
798 use unicode_segmentation::UnicodeSegmentation;
799 let mut result = Vec::new();
800 let mut byte_to_char: Vec<(usize, usize)> = Vec::new();
804 for (ci, (bi, _)) in text.char_indices().enumerate() {
805 byte_to_char.push((bi, ci));
806 }
807 for (byte_off, _word) in text.unicode_word_indices() {
808 let char_idx = byte_to_char
809 .iter()
810 .find(|(bi, _)| *bi == byte_off)
811 .map(|(_, ci)| *ci)
812 .unwrap_or(0);
813 if let Ok(idx) = u8::try_from(char_idx) {
820 result.push(idx);
821 } else {
822 break;
823 }
824 }
825 result
826}
827
828fn compute_list_item_index(inner: &TextDocumentInner, list_id: EntityId, block_id: u64) -> usize {
830 let mut all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
831 let store = inner.ctx.db_context.get_store();
832 crate::inner::refresh_block_positions(&mut all_blocks, store);
833 let mut list_blocks: Vec<_> = all_blocks
834 .iter()
835 .filter(|b| b.list == Some(list_id))
836 .collect();
837 list_blocks.sort_by_key(|b| b.document_position);
838 list_blocks
839 .iter()
840 .position(|b| b.id == block_id)
841 .unwrap_or(0)
842}
843
844pub(crate) fn format_list_marker(
846 list_dto: &frontend::list::dtos::ListDto,
847 item_index: usize,
848) -> String {
849 let number = item_index + 1; let marker_body = match list_dto.style {
851 ListStyle::Disc => "\u{2022}".to_string(), ListStyle::Circle => "\u{25E6}".to_string(), ListStyle::Square => "\u{25AA}".to_string(), ListStyle::Decimal => format!("{number}"),
855 ListStyle::LowerAlpha => {
856 if number <= 26 {
857 ((b'a' + (number as u8 - 1)) as char).to_string()
858 } else {
859 format!("{number}")
860 }
861 }
862 ListStyle::UpperAlpha => {
863 if number <= 26 {
864 ((b'A' + (number as u8 - 1)) as char).to_string()
865 } else {
866 format!("{number}")
867 }
868 }
869 ListStyle::LowerRoman => to_roman_lower(number),
870 ListStyle::UpperRoman => to_roman_upper(number),
871 };
872 format!("{}{marker_body}{}", list_dto.prefix, list_dto.suffix)
873}
874
875fn to_roman_upper(mut n: usize) -> String {
876 const VALUES: &[(usize, &str)] = &[
877 (1000, "M"),
878 (900, "CM"),
879 (500, "D"),
880 (400, "CD"),
881 (100, "C"),
882 (90, "XC"),
883 (50, "L"),
884 (40, "XL"),
885 (10, "X"),
886 (9, "IX"),
887 (5, "V"),
888 (4, "IV"),
889 (1, "I"),
890 ];
891 let mut result = String::new();
892 for &(val, sym) in VALUES {
893 while n >= val {
894 result.push_str(sym);
895 n -= val;
896 }
897 }
898 result
899}
900
901fn to_roman_lower(n: usize) -> String {
902 to_roman_upper(n).to_lowercase()
903}
904
905fn build_list_info(
907 inner: &TextDocumentInner,
908 block_dto: &frontend::block::dtos::BlockDto,
909) -> Option<ListInfo> {
910 let list_id = block_dto.list?;
911 let list_dto = list_commands::get_list(&inner.ctx, &{ list_id })
912 .ok()
913 .flatten()?;
914
915 let item_index = compute_list_item_index(inner, list_id, block_dto.id);
916 let marker = format_list_marker(&list_dto, item_index);
917
918 Some(ListInfo {
919 list_id: list_id as usize,
920 style: list_dto.style.clone(),
921 indent: list_dto.indent as u8,
922 marker,
923 item_index,
924 })
925}
926
927pub(crate) fn build_block_snapshot(
929 inner: &TextDocumentInner,
930 block_id: u64,
931 hl: crate::highlight::SnapshotHighlights,
932) -> Option<BlockSnapshot> {
933 build_block_snapshot_with_position_and_parent(inner, block_id, None, None, hl)
934}
935
936pub(crate) fn build_block_snapshot_with_position(
940 inner: &TextDocumentInner,
941 block_id: u64,
942 computed_position: Option<usize>,
943 hl: crate::highlight::SnapshotHighlights,
944) -> Option<BlockSnapshot> {
945 build_block_snapshot_with_position_and_parent(inner, block_id, computed_position, None, hl)
946}
947
948pub(crate) fn build_block_snapshot_with_position_and_parent(
955 inner: &TextDocumentInner,
956 block_id: u64,
957 computed_position: Option<usize>,
958 parent_frame_hint: Option<EntityId>,
959 hl: crate::highlight::SnapshotHighlights,
960) -> Option<BlockSnapshot> {
961 let mut block_dto = block_commands::get_block(&inner.ctx, &block_id)
962 .ok()
963 .flatten()?;
964 let store_for_pos = inner.ctx.db_context.get_store();
965 crate::inner::refresh_block_position(&mut block_dto, store_for_pos);
966
967 let mut block_format = BlockFormat::from(&block_dto);
968 if block_format.language.is_none() {
972 block_format.language = document_commands::get_document(&inner.ctx, &inner.document_id)
973 .ok()
974 .flatten()
975 .and_then(|d| d.default_language);
976 }
977 let list_info = build_list_info(inner, &block_dto);
978
979 let parent_frame_id = parent_frame_hint
980 .or_else(|| find_parent_frame(inner, block_id))
981 .map(|id| id as usize);
982 let table_cell = find_table_cell_context(inner, block_id);
983
984 let position = if common::database::rope_helpers::rope_positions_match_flow(store_for_pos) {
995 to_usize(block_dto.document_position)
996 } else {
997 computed_position.unwrap_or_else(|| to_usize(block_dto.document_position))
998 };
999
1000 let entity: common::entities::Block = block_dto.clone().into();
1004 let store = inner.ctx.db_context.get_store();
1005 let text = common::database::rope_helpers::block_content_via_store(&entity, store);
1006 let length = to_usize(common::database::rope_helpers::block_char_length(
1007 &entity, store,
1008 ));
1009 let fragments = build_fragments_with_text(inner, block_id, Some(&text), hl);
1010
1011 let paint_highlights =
1016 if hl.kind == crate::highlight::HighlighterKind::PaintOnly && !hl.suppress_paint {
1017 let spans = crate::highlight::merged_spans_for_block(inner, block_id as usize, hl.mask);
1018 crate::highlight::extract_paint_spans(&spans, length)
1019 } else {
1020 Vec::new()
1021 };
1022
1023 Some(BlockSnapshot {
1024 block_id: block_id as usize,
1025 position,
1026 length,
1027 text,
1028 fragments,
1029 block_format,
1030 list_info,
1031 parent_frame_id,
1032 table_cell,
1033 paint_highlights,
1034 })
1035}
1036
1037pub(crate) fn build_blocks_snapshot_for_frame(
1039 inner: &TextDocumentInner,
1040 frame_id: u64,
1041 hl: crate::highlight::SnapshotHighlights,
1042) -> Vec<BlockSnapshot> {
1043 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
1044 .ok()
1045 .flatten()
1046 {
1047 Some(f) => f,
1048 None => return Vec::new(),
1049 };
1050
1051 let mut block_dtos: Vec<_> = frame_dto
1052 .blocks
1053 .iter()
1054 .filter_map(|&id| {
1055 block_commands::get_block(&inner.ctx, &{ id })
1056 .ok()
1057 .flatten()
1058 })
1059 .collect();
1060 let store = inner.ctx.db_context.get_store();
1061 crate::inner::refresh_block_positions(&mut block_dtos, store);
1062 block_dtos.sort_by_key(|b| b.document_position);
1063
1064 block_dtos
1065 .iter()
1066 .filter_map(|b| build_block_snapshot(inner, b.id, hl))
1067 .collect()
1068}
1069
1070pub(crate) fn build_blocks_snapshot_for_frame_with_positions(
1076 inner: &TextDocumentInner,
1077 frame_id: u64,
1078 start_pos: usize,
1079 hl: crate::highlight::SnapshotHighlights,
1080) -> (Vec<BlockSnapshot>, usize) {
1081 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
1082 .ok()
1083 .flatten()
1084 {
1085 Some(f) => f,
1086 None => return (Vec::new(), start_pos),
1087 };
1088
1089 let mut block_dtos: Vec<_> = frame_dto
1090 .blocks
1091 .iter()
1092 .filter_map(|&id| {
1093 block_commands::get_block(&inner.ctx, &{ id })
1094 .ok()
1095 .flatten()
1096 })
1097 .collect();
1098 let store = inner.ctx.db_context.get_store();
1099 crate::inner::refresh_block_positions(&mut block_dtos, store);
1100 block_dtos.sort_by_key(|b| b.document_position);
1101
1102 let mut running_pos = start_pos;
1103 let mut snapshots = Vec::with_capacity(block_dtos.len());
1104 for b in &block_dtos {
1105 if let Some(snap) = build_block_snapshot_with_position(inner, b.id, Some(running_pos), hl) {
1106 running_pos += snap.length + 1; snapshots.push(snap);
1108 }
1109 }
1110 (snapshots, running_pos)
1111}