1use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use crate::{DocumentError, Result};
8use base64::Engine;
9use base64::engine::general_purpose::STANDARD as BASE64;
10
11use crate::{DjotExportOptions, DjotImportOptions, ResourceType, TextDirection, WrapMode};
12use frontend::commands::{
13 block_commands, document_commands, document_inspection_commands, document_io_commands,
14 document_search_commands, frame_commands, resource_commands, table_cell_commands,
15 table_commands, undo_redo_commands,
16};
17
18use crate::convert::{self, to_i64, to_usize};
19use crate::cursor::TextCursor;
20use crate::events::{self, DocumentEvent, Subscription};
21use crate::flow::FormatChangeKind;
22use crate::inner::TextDocumentInner;
23use crate::operation::{
24 DjotImportResult, DocxExportResult, EpubExportResult, HtmlImportResult, MarkdownImportResult,
25 Operation, PdfExportResult,
26};
27use crate::{BlockFormat, BlockInfo, DocumentStats, FindMatch, FindOptions, ReplaceRange};
28
29#[derive(Clone)]
40pub struct TextDocument {
41 pub(crate) inner: Arc<Mutex<TextDocumentInner>>,
42}
43
44impl TextDocument {
47 #[doc(hidden)]
48 pub fn rope_store_for_test(&self) -> std::sync::Arc<common::database::Store> {
49 let inner = self.inner.lock();
50 std::sync::Arc::clone(inner.ctx.db_context.get_store())
51 }
52}
53
54impl TextDocument {
55 pub fn new() -> Self {
64 Self::try_new().expect("failed to initialize document")
65 }
66
67 pub fn try_new() -> Result<Self> {
69 let ctx = frontend::AppContext::new();
70 let doc_inner = TextDocumentInner::initialize(ctx)?;
71 let inner = Arc::new(Mutex::new(doc_inner));
72
73 Self::subscribe_long_operation_events(&inner);
75
76 Ok(Self { inner })
77 }
78
79 fn subscribe_long_operation_events(inner: &Arc<Mutex<TextDocumentInner>>) {
81 use frontend::common::event::{LongOperationEvent as LOE, Origin};
82
83 let weak = Arc::downgrade(inner);
84 let mut locked = inner.lock();
85
86 let w = weak.clone();
88 let progress_tok =
89 locked
90 .event_client
91 .subscribe(Origin::LongOperation(LOE::Progress), move |event| {
92 if let Some(inner) = w.upgrade() {
93 let (op_id, percent, message) = parse_progress_data(&event.data);
94 let mut inner = inner.lock();
95 inner.queue_event(DocumentEvent::LongOperationProgress {
96 operation_id: op_id,
97 percent,
98 message,
99 });
100 }
101 });
102
103 let w = weak.clone();
105 let completed_tok =
106 locked
107 .event_client
108 .subscribe(Origin::LongOperation(LOE::Completed), move |event| {
109 if let Some(inner) = w.upgrade() {
110 let op_id = parse_id_data(&event.data);
111 let mut inner = inner.lock();
112 inner.queue_event(DocumentEvent::DocumentReset);
113 inner.check_block_count_changed();
114 inner.reset_cached_child_order();
115 inner.queue_event(DocumentEvent::LongOperationFinished {
116 operation_id: op_id,
117 success: true,
118 error: None,
119 });
120 }
121 });
122
123 let w = weak.clone();
125 let cancelled_tok =
126 locked
127 .event_client
128 .subscribe(Origin::LongOperation(LOE::Cancelled), move |event| {
129 if let Some(inner) = w.upgrade() {
130 let op_id = parse_id_data(&event.data);
131 let mut inner = inner.lock();
132 inner.queue_event(DocumentEvent::LongOperationFinished {
133 operation_id: op_id,
134 success: false,
135 error: Some("cancelled".into()),
136 });
137 }
138 });
139
140 let failed_tok =
142 locked
143 .event_client
144 .subscribe(Origin::LongOperation(LOE::Failed), move |event| {
145 if let Some(inner) = weak.upgrade() {
146 let (op_id, error) = parse_failed_data(&event.data);
147 let mut inner = inner.lock();
148 inner.queue_event(DocumentEvent::LongOperationFinished {
149 operation_id: op_id,
150 success: false,
151 error: Some(error),
152 });
153 }
154 });
155
156 locked.long_op_subscriptions.extend([
157 progress_tok,
158 completed_tok,
159 cancelled_tok,
160 failed_tok,
161 ]);
162 }
163
164 pub fn set_plain_text(&self, text: &str) -> Result<()> {
168 let queued = {
169 let mut inner = self.inner.lock();
170 let dto = frontend::document_io::ImportPlainTextDto {
171 plain_text: text.into(),
172 };
173 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
174 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
175 inner.invalidate_text_cache();
176 inner.rehighlight_all();
177 inner.queue_event(DocumentEvent::DocumentReset);
178 inner.check_block_count_changed();
179 inner.reset_cached_child_order();
180 inner.queue_event(DocumentEvent::UndoRedoChanged {
181 can_undo: false,
182 can_redo: false,
183 });
184 inner.take_queued_events()
185 };
186 crate::inner::dispatch_queued_events(queued);
187 Ok(())
188 }
189
190 pub fn to_plain_text(&self) -> Result<String> {
211 let mut inner = self.inner.lock();
212 Ok(inner.plain_text()?.to_string())
213 }
214
215 pub fn set_markdown(&self, markdown: &str) -> Result<Operation<MarkdownImportResult>> {
219 let mut inner = self.inner.lock();
220 inner.invalidate_text_cache();
221 let dto = frontend::document_io::ImportMarkdownDto {
222 markdown_text: markdown.into(),
223 };
224 let op_id = document_io_commands::import_markdown(&inner.ctx, &dto)?;
225 Ok(Operation::new(
226 op_id,
227 &inner.ctx,
228 Box::new(|ctx, id| {
229 document_io_commands::get_import_markdown_result(ctx, id)
230 .ok()
231 .flatten()
232 .map(|r| {
233 Ok(MarkdownImportResult {
234 block_count: to_usize(r.block_count),
235 })
236 })
237 }),
238 ))
239 }
240
241 pub fn to_markdown(&self) -> Result<String> {
243 let inner = self.inner.lock();
244 let dto = document_io_commands::export_markdown(&inner.ctx)?;
245 Ok(dto.markdown_text)
246 }
247
248 pub fn set_djot(&self, djot: &str) -> Result<Operation<DjotImportResult>> {
252 self.set_djot_with_options(djot, DjotImportOptions::default())
253 }
254
255 pub fn set_djot_with_options(
261 &self,
262 djot: &str,
263 options: DjotImportOptions,
264 ) -> Result<Operation<DjotImportResult>> {
265 let mut inner = self.inner.lock();
266 inner.invalidate_text_cache();
267 let dto = frontend::document_io::ImportDjotDto {
268 djot_text: djot.into(),
269 options,
270 };
271 let op_id = document_io_commands::import_djot(&inner.ctx, &dto)?;
272 Ok(Operation::new(
273 op_id,
274 &inner.ctx,
275 Box::new(|ctx, id| {
276 document_io_commands::get_import_djot_result(ctx, id)
277 .ok()
278 .flatten()
279 .map(|r| {
280 Ok(DjotImportResult {
281 block_count: to_usize(r.block_count),
282 })
283 })
284 }),
285 ))
286 }
287
288 pub fn set_djot_sync(&self, djot: &str) -> Result<DjotImportResult> {
308 self.set_djot_sync_with_options(djot, DjotImportOptions::default())
309 }
310
311 pub fn set_djot_sync_with_options(
314 &self,
315 djot: &str,
316 options: DjotImportOptions,
317 ) -> Result<DjotImportResult> {
318 let (queued, block_count) = {
319 let mut inner = self.inner.lock();
320 inner.invalidate_text_cache();
321 let dto = frontend::document_io::ImportDjotDto {
322 djot_text: djot.into(),
323 options,
324 };
325 let result = document_io_commands::import_djot_sync(&inner.ctx, &dto)?;
326 inner.queue_event(DocumentEvent::DocumentReset);
330 inner.check_block_count_changed();
331 inner.reset_cached_child_order();
332 (inner.take_queued_events(), result.block_count)
333 };
334 crate::inner::dispatch_queued_events(queued);
336 Ok(DjotImportResult {
337 block_count: to_usize(block_count),
338 })
339 }
340
341 pub fn to_djot(&self) -> Result<String> {
343 self.to_djot_with_options(DjotExportOptions::default())
344 }
345
346 pub fn to_djot_with_options(&self, options: DjotExportOptions) -> Result<String> {
350 let inner = self.inner.lock();
351 let dto = document_io_commands::export_djot(&inner.ctx, &options)?;
352 Ok(dto.djot_text)
353 }
354
355 pub fn set_html(&self, html: &str) -> Result<Operation<HtmlImportResult>> {
359 let mut inner = self.inner.lock();
360 inner.invalidate_text_cache();
361 let dto = frontend::document_io::ImportHtmlDto {
362 html_text: html.into(),
363 };
364 let op_id = document_io_commands::import_html(&inner.ctx, &dto)?;
365 Ok(Operation::new(
366 op_id,
367 &inner.ctx,
368 Box::new(|ctx, id| {
369 document_io_commands::get_import_html_result(ctx, id)
370 .ok()
371 .flatten()
372 .map(|r| {
373 Ok(HtmlImportResult {
374 block_count: to_usize(r.block_count),
375 })
376 })
377 }),
378 ))
379 }
380
381 pub fn to_html(&self) -> Result<String> {
383 let inner = self.inner.lock();
384 let dto = document_io_commands::export_html(&inner.ctx)?;
385 Ok(dto.html_text)
386 }
387
388 pub fn to_latex(&self, document_class: &str, include_preamble: bool) -> Result<String> {
390 let inner = self.inner.lock();
391 let dto = frontend::document_io::ExportLatexDto {
392 document_class: document_class.into(),
393 include_preamble,
394 };
395 let result = document_io_commands::export_latex(&inner.ctx, &dto)?;
396 Ok(result.latex_text)
397 }
398
399 pub fn to_docx(&self, output_path: &str) -> Result<Operation<DocxExportResult>> {
403 self.to_docx_with_options(output_path, crate::DocxExportOptions::default())
404 }
405
406 pub fn to_docx_with_options(
411 &self,
412 output_path: &str,
413 options: crate::DocxExportOptions,
414 ) -> Result<Operation<DocxExportResult>> {
415 let inner = self.inner.lock();
416 let dto = frontend::document_io::ExportDocxDto {
417 output_path: output_path.into(),
418 options,
419 };
420 let op_id = document_io_commands::export_docx(&inner.ctx, &dto)?;
421 Ok(Operation::new(
422 op_id,
423 &inner.ctx,
424 Box::new(|ctx, id| {
425 document_io_commands::get_export_docx_result(ctx, id)
426 .ok()
427 .flatten()
428 .map(|r| {
429 Ok(DocxExportResult {
430 file_path: r.file_path,
431 paragraph_count: to_usize(r.paragraph_count),
432 })
433 })
434 }),
435 ))
436 }
437
438 pub fn to_epub(&self, output_path: &str) -> Result<Operation<EpubExportResult>> {
442 self.to_epub_with_options(output_path, crate::EpubExportOptions::default())
443 }
444
445 pub fn to_epub_with_options(
450 &self,
451 output_path: &str,
452 options: crate::EpubExportOptions,
453 ) -> Result<Operation<EpubExportResult>> {
454 let inner = self.inner.lock();
455 let dto = frontend::document_io::ExportEpubDto {
456 output_path: output_path.into(),
457 options,
458 };
459 let op_id = document_io_commands::export_epub(&inner.ctx, &dto)?;
460 Ok(Operation::new(
461 op_id,
462 &inner.ctx,
463 Box::new(|ctx, id| {
464 document_io_commands::get_export_epub_result(ctx, id)
465 .ok()
466 .flatten()
467 .map(|r| {
468 Ok(EpubExportResult {
469 file_path: r.file_path,
470 chapter_count: to_usize(r.chapter_count),
471 })
472 })
473 }),
474 ))
475 }
476
477 pub fn to_pdf(
487 &self,
488 output_path: &str,
489 options: crate::PdfExportOptions,
490 ) -> Result<Operation<PdfExportResult>> {
491 self.to_pdf_with_options(output_path, options)
492 }
493
494 #[cfg(feature = "pdf")]
499 pub fn to_pdf_with_options(
500 &self,
501 output_path: &str,
502 options: crate::PdfExportOptions,
503 ) -> Result<Operation<PdfExportResult>> {
504 let inner = self.inner.lock();
505 let dto = frontend::document_io::ExportPdfDto {
506 output_path: output_path.into(),
507 options,
508 };
509 let op_id = document_io_commands::export_pdf(&inner.ctx, &dto)?;
510 Ok(Operation::new(
511 op_id,
512 &inner.ctx,
513 Box::new(|ctx, id| {
514 document_io_commands::get_export_pdf_result(ctx, id)
515 .ok()
516 .flatten()
517 .map(|r| {
518 Ok(PdfExportResult {
519 file_path: r.file_path,
520 page_count: to_usize(r.page_count),
521 })
522 })
523 }),
524 ))
525 }
526
527 #[cfg(not(feature = "pdf"))]
531 pub fn to_pdf_with_options(
532 &self,
533 _output_path: &str,
534 _options: crate::PdfExportOptions,
535 ) -> Result<Operation<PdfExportResult>> {
536 Err(DocumentError::Unsupported(
537 "PDF export requires the `pdf` cargo feature on the `text-document` crate".into(),
538 ))
539 }
540
541 pub fn clear(&self) -> Result<()> {
543 let queued = {
544 let mut inner = self.inner.lock();
545 let dto = frontend::document_io::ImportPlainTextDto {
546 plain_text: String::new(),
547 };
548 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
549 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
550 inner.invalidate_text_cache();
551 inner.rehighlight_all();
552 inner.queue_event(DocumentEvent::DocumentReset);
553 inner.check_block_count_changed();
554 inner.reset_cached_child_order();
555 inner.queue_event(DocumentEvent::UndoRedoChanged {
556 can_undo: false,
557 can_redo: false,
558 });
559 inner.take_queued_events()
560 };
561 crate::inner::dispatch_queued_events(queued);
562 Ok(())
563 }
564
565 pub fn cursor(&self) -> TextCursor {
569 self.cursor_at(0)
570 }
571
572 pub fn cursor_at(&self, position: usize) -> TextCursor {
578 let data = {
579 let mut inner = self.inner.lock();
580 inner.register_cursor(position)
581 };
582 let cursor = TextCursor {
583 doc: self.inner.clone(),
584 data,
585 };
586 cursor.snap_position_to_grapheme_boundary();
587 cursor
588 }
589
590 pub fn stats(&self) -> DocumentStats {
594 let inner = self.inner.lock();
595 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
596 .expect("get_document_stats should not fail");
597 DocumentStats::from(&dto)
598 }
599
600 pub fn character_count(&self) -> usize {
602 let inner = self.inner.lock();
603 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
604 .expect("get_document_stats should not fail");
605 to_usize(dto.character_count)
606 }
607
608 pub fn block_count(&self) -> usize {
610 let inner = self.inner.lock();
611 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
612 .expect("get_document_stats should not fail");
613 to_usize(dto.block_count)
614 }
615
616 pub fn is_empty(&self) -> bool {
618 self.character_count() == 0
619 }
620
621 pub fn text_at(&self, position: usize, length: usize) -> Result<String> {
623 let inner = self.inner.lock();
624 let dto = frontend::document_inspection::GetTextAtPositionDto {
625 position: to_i64(position),
626 length: to_i64(length),
627 };
628 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
629 Ok(result.text)
630 }
631
632 pub fn find_element_at_position(&self, position: usize) -> Option<(u64, usize, usize)> {
647 let block_info = self.block_at(position).ok()?;
648 let block_start = block_info.start;
649 let offset_in_block = position.checked_sub(block_start)?;
650 let block = crate::text_block::TextBlock {
651 doc: std::sync::Arc::clone(&self.inner),
652 block_id: block_info.block_id,
653 };
654 let frags = block.fragments();
655 let mut last_text: Option<(u64, usize, usize, usize)> = None; for frag in &frags {
661 match frag {
662 crate::flow::FragmentContent::Text {
663 offset,
664 length,
665 element_id,
666 ..
667 } => {
668 let frag_start = *offset;
669 let frag_end = frag_start + *length;
670 if offset_in_block >= frag_start && offset_in_block < frag_end {
671 let abs_start = block_start + frag_start;
672 let offset_within = offset_in_block - frag_start;
673 return Some((*element_id, abs_start, offset_within));
674 }
675 if offset_in_block == frag_end {
678 last_text =
679 Some((*element_id, block_start + frag_start, frag_start, *length));
680 }
681 }
682 crate::flow::FragmentContent::Image {
683 offset, element_id, ..
684 } => {
685 if offset_in_block == *offset {
686 return Some((*element_id, block_start + offset, 0));
687 }
688 }
689 }
690 }
691 last_text.map(|(id, abs_start, _, length)| (id, abs_start, length))
694 }
695
696 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
698 let inner = self.inner.lock();
699 let dto = frontend::document_inspection::GetBlockAtPositionDto {
700 position: to_i64(position),
701 };
702 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
703 Ok(BlockInfo::from(&result))
704 }
705
706 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
708 let inner = self.inner.lock();
709 let dto = frontend::document_inspection::GetBlockAtPositionDto {
710 position: to_i64(position),
711 };
712 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
713 let block_id = block_info.block_id;
714 let block_id = block_id as u64;
715 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
716 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
717 Ok(BlockFormat::from(&block_dto))
718 }
719
720 pub fn flow(&self) -> Vec<crate::flow::FlowElement> {
731 let inner = self.inner.lock();
732 let main_frame_id = get_main_frame_id(&inner);
733 crate::text_frame::build_flow_elements(&inner, &self.inner, main_frame_id)
734 }
735
736 pub fn block_by_id(&self, block_id: usize) -> Option<crate::text_block::TextBlock> {
741 let inner = self.inner.lock();
742 let exists = frontend::commands::block_commands::get_block(&inner.ctx, &(block_id as u64))
743 .ok()
744 .flatten()
745 .is_some();
746
747 if exists {
748 Some(crate::text_block::TextBlock {
749 doc: self.inner.clone(),
750 block_id,
751 })
752 } else {
753 None
754 }
755 }
756
757 pub fn snapshot_block_at_position(
763 &self,
764 position: usize,
765 ) -> Option<crate::flow::BlockSnapshot> {
766 self.snapshot_block_at_position_masked(position, &crate::highlight::HighlightMask::all())
767 }
768
769 pub fn snapshot_block_at_position_without_highlights(
774 &self,
775 position: usize,
776 ) -> Option<crate::flow::BlockSnapshot> {
777 self.snapshot_block_at_position_masked(position, &crate::highlight::HighlightMask::none())
778 }
779
780 pub fn snapshot_block_at_position_masked(
785 &self,
786 position: usize,
787 mask: &crate::highlight::HighlightMask,
788 ) -> Option<crate::flow::BlockSnapshot> {
789 let inner = self.inner.lock();
790 let hl = crate::highlight::SnapshotHighlights {
793 kind: inner.highlights.effective_kind(mask),
794 mask,
795 suppress_paint: false,
796 };
797 let main_frame_id = get_main_frame_id(&inner);
798 let store = inner.ctx.db_context.get_store();
799
800 if common::database::rope_helpers::rope_positions_match_flow(store)
808 && let Some((block_id, _, _)) =
809 common::database::rope_helpers::find_block_at_char_position(store, position as i64)
810 {
811 return crate::text_block::build_block_snapshot(&inner, block_id, hl);
812 }
813
814 let ordered_block_ids = collect_frame_block_ids(&inner, main_frame_id)?;
816
817 let pos = position as i64;
819 let mut running_pos: i64 = 0;
820 for &block_id in &ordered_block_ids {
821 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
822 .ok()
823 .flatten()?;
824 let entity: common::entities::Block = block_dto.clone().into();
825 let block_end =
826 running_pos + common::database::rope_helpers::block_char_length(&entity, store);
827 if pos >= running_pos && pos <= block_end {
828 return crate::text_block::build_block_snapshot_with_position(
829 &inner,
830 block_id,
831 Some(running_pos as usize),
832 hl,
833 );
834 }
835 running_pos = block_end + 1;
836 }
837
838 if let Some(&last_id) = ordered_block_ids.last() {
840 return crate::text_block::build_block_snapshot(&inner, last_id, hl);
841 }
842 None
843 }
844
845 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
848 let inner = self.inner.lock();
849 let dto = frontend::document_inspection::GetBlockAtPositionDto {
850 position: to_i64(position),
851 };
852 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
853 Some(crate::text_block::TextBlock {
854 doc: self.inner.clone(),
855 block_id: result.block_id as usize,
856 })
857 }
858
859 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
868 let inner = self.inner.lock();
869 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
870 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
871 let store = inner.ctx.db_context.get_store();
872 crate::inner::refresh_block_positions(&mut sorted, store);
873 sorted.sort_by_key(|b| b.document_position);
874
875 sorted
876 .get(block_number)
877 .map(|b| crate::text_block::TextBlock {
878 doc: self.inner.clone(),
879 block_id: b.id as usize,
880 })
881 }
882
883 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
889 let inner = self.inner.lock();
890 let all_blocks =
891 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
892 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
893 let store = inner.ctx.db_context.get_store();
894 crate::inner::refresh_block_positions(&mut sorted, store);
895 sorted.sort_by_key(|b| b.document_position);
896 sorted
897 .iter()
898 .map(|b| crate::text_block::TextBlock {
899 doc: self.inner.clone(),
900 block_id: b.id as usize,
901 })
902 .collect()
903 }
904
905 pub fn blocks_in_range(
912 &self,
913 position: usize,
914 length: usize,
915 ) -> Vec<crate::text_block::TextBlock> {
916 let inner = self.inner.lock();
917 let all_blocks =
918 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
919 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
920 let store = inner.ctx.db_context.get_store();
921 crate::inner::refresh_block_positions(&mut sorted, store);
922 sorted.sort_by_key(|b| b.document_position);
923
924 let range_start = position;
925 let range_end = position + length;
926 sorted
927 .iter()
928 .filter(|b| {
929 let block_start = b.document_position.max(0) as usize;
930 let entity: common::entities::Block = (*b).clone().into();
931 let block_end = block_start
932 + common::database::rope_helpers::block_char_length(&entity, store).max(0)
933 as usize;
934 if length == 0 {
936 range_start >= block_start && range_start < block_end
938 } else {
939 block_start < range_end && block_end > range_start
940 }
941 })
942 .map(|b| crate::text_block::TextBlock {
943 doc: self.inner.clone(),
944 block_id: b.id as usize,
945 })
946 .collect()
947 }
948
949 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
954 self.snapshot_flow_masked(&crate::highlight::HighlightMask::all())
955 }
956
957 pub fn snapshot_flow_without_highlights(&self) -> crate::flow::FlowSnapshot {
967 self.snapshot_flow_masked(&crate::highlight::HighlightMask::none())
968 }
969
970 pub fn snapshot_flow_masked(
979 &self,
980 mask: &crate::highlight::HighlightMask,
981 ) -> crate::flow::FlowSnapshot {
982 let inner = self.inner.lock();
983 let main_frame_id = get_main_frame_id(&inner);
984 let hl = crate::highlight::SnapshotHighlights {
985 kind: inner.highlights.effective_kind(mask),
986 mask,
987 suppress_paint: false,
988 };
989 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id, hl);
990 crate::flow::FlowSnapshot { elements }
991 }
992
993 pub fn snapshot_flow_masked_no_paint(
1006 &self,
1007 mask: &crate::highlight::HighlightMask,
1008 ) -> crate::flow::FlowSnapshot {
1009 let inner = self.inner.lock();
1010 let main_frame_id = get_main_frame_id(&inner);
1011 let hl = crate::highlight::SnapshotHighlights {
1012 kind: inner.highlights.effective_kind(mask),
1013 mask,
1014 suppress_paint: true,
1015 };
1016 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id, hl);
1017 crate::flow::FlowSnapshot { elements }
1018 }
1019
1020 pub fn find(
1024 &self,
1025 query: &str,
1026 from: usize,
1027 options: &FindOptions,
1028 ) -> Result<Option<FindMatch>> {
1029 let inner = self.inner.lock();
1030 let dto = options.to_find_text_dto(query, from);
1031 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
1032 Ok(convert::find_result_to_match(&result))
1033 }
1034
1035 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
1037 let inner = self.inner.lock();
1038 let dto = options.to_find_all_dto(query);
1039 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
1040 Ok(convert::find_all_to_matches(&result))
1041 }
1042
1043 pub fn replace_text(
1051 &self,
1052 query: &str,
1053 replacement: &str,
1054 replace_all: bool,
1055 options: &crate::ReplaceOptions,
1056 ) -> Result<usize> {
1057 let (count, queued) = {
1058 let mut inner = self.inner.lock();
1059 let dto = options.to_replace_dto(query, replacement, replace_all);
1060 let result =
1061 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1062 let count = to_usize(result.replacements_count);
1063 inner.invalidate_text_cache();
1064 if count > 0 {
1065 inner.modified = true;
1066 inner.rehighlight_all();
1067 inner.queue_event(DocumentEvent::ContentsChanged {
1072 position: 0,
1073 chars_removed: 0,
1074 chars_added: 0,
1075 blocks_affected: count,
1076 });
1077 inner.check_block_count_changed();
1078 inner.check_flow_changed();
1079 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1080 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1081 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1082 }
1083 (count, inner.take_queued_events())
1084 };
1085 crate::inner::dispatch_queued_events(queued);
1086 Ok(count)
1087 }
1088
1089 pub fn replace_ranges(
1105 &self,
1106 ranges: &[ReplaceRange],
1107 options: &crate::ReplaceOptions,
1108 ) -> Result<usize> {
1109 let (count, queued) = {
1110 let mut inner = self.inner.lock();
1111 let count = Self::replace_ranges_locked(&mut inner, ranges, options)?;
1112 (count, inner.take_queued_events())
1113 };
1114 crate::inner::dispatch_queued_events(queued);
1115 Ok(count)
1116 }
1117
1118 pub fn find_and_replace(
1144 &self,
1145 query: &str,
1146 options: &crate::ReplaceOptions,
1147 mut decide: impl FnMut(&str, usize) -> Option<String>,
1148 ) -> Result<usize> {
1149 let (count, queued) = {
1150 let mut inner = self.inner.lock();
1151
1152 let found = {
1159 let dto = options.find.to_find_all_dto(query);
1160 document_search_commands::find_all(&inner.ctx, &dto)?
1161 };
1162
1163 let mut ranges: Vec<ReplaceRange> = Vec::new();
1165 for (i, ((&position, &length), matched)) in found
1166 .positions
1167 .iter()
1168 .zip(found.lengths.iter())
1169 .zip(found.matched_texts.iter())
1170 .enumerate()
1171 {
1172 if let Some(replacement) = decide(matched, i) {
1173 ranges.push(ReplaceRange {
1174 position: to_usize(position),
1175 length: to_usize(length),
1176 replacement,
1177 });
1178 }
1179 }
1180
1181 let count = if ranges.is_empty() {
1183 0
1184 } else {
1185 Self::replace_ranges_locked(&mut inner, &ranges, options)?
1186 };
1187 (count, inner.take_queued_events())
1188 };
1189 crate::inner::dispatch_queued_events(queued);
1190 Ok(count)
1191 }
1192
1193 fn replace_ranges_locked(
1196 inner: &mut crate::inner::TextDocumentInner,
1197 ranges: &[ReplaceRange],
1198 options: &crate::ReplaceOptions,
1199 ) -> Result<usize> {
1200 let dto = options.to_replace_ranges_dto(ranges);
1201 let result =
1202 document_search_commands::replace_ranges(&inner.ctx, Some(inner.stack_id), &dto)?;
1203 let count = to_usize(result.replacements_count);
1204
1205 inner.invalidate_text_cache();
1206 if count > 0 {
1207 inner.modified = true;
1208 inner.rehighlight_all();
1209 inner.queue_event(DocumentEvent::ContentsChanged {
1210 position: 0,
1211 chars_removed: 0,
1212 chars_added: 0,
1213 blocks_affected: count,
1214 });
1215 inner.check_block_count_changed();
1216 inner.check_flow_changed();
1217 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1218 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1219 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1220 }
1221 Ok(count)
1222 }
1223
1224 pub fn add_resource(
1228 &self,
1229 resource_type: ResourceType,
1230 name: &str,
1231 mime_type: &str,
1232 data: &[u8],
1233 ) -> Result<()> {
1234 let mut inner = self.inner.lock();
1235 let dto = frontend::resource::dtos::CreateResourceDto {
1236 created_at: Default::default(),
1237 updated_at: Default::default(),
1238 resource_type,
1239 name: name.into(),
1240 url: String::new(),
1241 mime_type: mime_type.into(),
1242 data_base64: BASE64.encode(data),
1243 };
1244 let created = resource_commands::create_resource(
1245 &inner.ctx,
1246 Some(inner.stack_id),
1247 &dto,
1248 inner.document_id,
1249 -1,
1250 )?;
1251 inner.resource_cache.insert(name.to_string(), created.id);
1252 Ok(())
1253 }
1254
1255 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
1259 let mut inner = self.inner.lock();
1260
1261 if let Some(&id) = inner.resource_cache.get(name) {
1263 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
1264 let bytes = BASE64
1265 .decode(&r.data_base64)
1266 .map_err(|e| DocumentError::Internal(e.into()))?;
1267 return Ok(Some(bytes));
1268 }
1269 inner.resource_cache.remove(name);
1271 }
1272
1273 let all = resource_commands::get_all_resource(&inner.ctx)?;
1275 for r in &all {
1276 if r.name == name {
1277 inner.resource_cache.insert(name.to_string(), r.id);
1278 let bytes = BASE64
1279 .decode(&r.data_base64)
1280 .map_err(|e| DocumentError::Internal(e.into()))?;
1281 return Ok(Some(bytes));
1282 }
1283 }
1284 Ok(None)
1285 }
1286
1287 pub fn undo(&self) -> Result<()> {
1291 let queued = {
1292 let mut inner = self.inner.lock();
1293 let before = capture_block_state(&inner);
1294 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
1295 inner.invalidate_text_cache();
1296 result?;
1297 inner.rehighlight_all();
1298 emit_undo_redo_change_events(&mut inner, &before);
1299 inner.check_block_count_changed();
1300 inner.check_flow_changed();
1301 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1302 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1303 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1304 inner.take_queued_events()
1305 };
1306 crate::inner::dispatch_queued_events(queued);
1307 Ok(())
1308 }
1309
1310 pub fn redo(&self) -> Result<()> {
1312 let queued = {
1313 let mut inner = self.inner.lock();
1314 let before = capture_block_state(&inner);
1315 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
1316 inner.invalidate_text_cache();
1317 result?;
1318 inner.rehighlight_all();
1319 emit_undo_redo_change_events(&mut inner, &before);
1320 inner.check_block_count_changed();
1321 inner.check_flow_changed();
1322 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1323 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1324 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1325 inner.take_queued_events()
1326 };
1327 crate::inner::dispatch_queued_events(queued);
1328 Ok(())
1329 }
1330
1331 pub fn can_undo(&self) -> bool {
1333 let inner = self.inner.lock();
1334 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
1335 }
1336
1337 pub fn can_redo(&self) -> bool {
1339 let inner = self.inner.lock();
1340 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
1341 }
1342
1343 pub fn clear_undo_redo(&self) {
1345 let inner = self.inner.lock();
1346 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
1347 }
1348
1349 pub fn is_modified(&self) -> bool {
1353 self.inner.lock().modified
1354 }
1355
1356 pub fn set_modified(&self, modified: bool) {
1358 let queued = {
1359 let mut inner = self.inner.lock();
1360 if inner.modified != modified {
1361 inner.modified = modified;
1362 inner.queue_event(DocumentEvent::ModificationChanged(modified));
1363 }
1364 inner.take_queued_events()
1365 };
1366 crate::inner::dispatch_queued_events(queued);
1367 }
1368
1369 pub fn title(&self) -> String {
1373 let inner = self.inner.lock();
1374 document_commands::get_document(&inner.ctx, &inner.document_id)
1375 .ok()
1376 .flatten()
1377 .map(|d| d.title)
1378 .unwrap_or_default()
1379 }
1380
1381 pub fn set_title(&self, title: &str) -> Result<()> {
1383 let inner = self.inner.lock();
1384 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
1385 .ok_or_else(|| DocumentError::NotFound("document not found".into()))?;
1386 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
1387 update.title = title.into();
1388 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
1389 Ok(())
1390 }
1391
1392 pub fn text_direction(&self) -> TextDirection {
1394 let inner = self.inner.lock();
1395 document_commands::get_document(&inner.ctx, &inner.document_id)
1396 .ok()
1397 .flatten()
1398 .map(|d| d.text_direction)
1399 .unwrap_or(TextDirection::LeftToRight)
1400 }
1401
1402 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
1404 let inner = self.inner.lock();
1405 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
1406 .ok_or_else(|| DocumentError::NotFound("document not found".into()))?;
1407 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
1408 update.text_direction = direction;
1409 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
1410 Ok(())
1411 }
1412
1413 pub fn default_wrap_mode(&self) -> WrapMode {
1415 let inner = self.inner.lock();
1416 document_commands::get_document(&inner.ctx, &inner.document_id)
1417 .ok()
1418 .flatten()
1419 .map(|d| d.default_wrap_mode)
1420 .unwrap_or(WrapMode::WordWrap)
1421 }
1422
1423 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
1425 let inner = self.inner.lock();
1426 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
1427 .ok_or_else(|| DocumentError::NotFound("document not found".into()))?;
1428 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
1429 update.default_wrap_mode = mode;
1430 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
1431 Ok(())
1432 }
1433
1434 pub fn default_language(&self) -> String {
1438 let inner = self.inner.lock();
1439 document_commands::get_document(&inner.ctx, &inner.document_id)
1440 .ok()
1441 .flatten()
1442 .and_then(|d| d.default_language)
1443 .unwrap_or_else(|| "en".to_string())
1444 }
1445
1446 pub fn set_default_language(&self, language: &str) -> Result<()> {
1449 let inner = self.inner.lock();
1450 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
1451 .ok_or_else(|| DocumentError::NotFound("document not found".into()))?;
1452 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
1453 update.default_language = Some(language.to_string());
1454 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
1455 Ok(())
1456 }
1457
1458 pub fn on_change<F>(&self, callback: F) -> Subscription
1477 where
1478 F: Fn(DocumentEvent) + Send + Sync + 'static,
1479 {
1480 let mut inner = self.inner.lock();
1481 events::subscribe_inner(&mut inner, callback)
1482 }
1483
1484 pub fn poll_events(&self) -> Vec<DocumentEvent> {
1490 let mut inner = self.inner.lock();
1491 inner.drain_poll_events()
1492 }
1493
1494 pub fn set_syntax_highlighter(&self, highlighter: Option<Arc<dyn crate::SyntaxHighlighter>>) {
1508 let queued = {
1509 let mut inner = self.inner.lock();
1510 let prev_kind = inner.highlight_kind;
1511 let installed = highlighter.is_some();
1512 inner.highlights.set_shim(highlighter);
1513 if installed {
1514 inner.rehighlight_all(); } else {
1516 inner.recompute_highlight_kind();
1517 }
1518 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1519 inner.take_queued_events()
1520 };
1521 crate::inner::dispatch_queued_events(queued);
1522 }
1523
1524 pub fn add_syntax_session(
1533 &self,
1534 highlighter: Arc<dyn crate::SyntaxHighlighter>,
1535 ) -> crate::highlight::SessionId {
1536 let (id, queued) = {
1537 let mut inner = self.inner.lock();
1538 let prev_kind = inner.highlight_kind;
1539 let id = inner.highlights.add_syntax(highlighter);
1540 inner.rehighlight_all();
1541 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1542 (id, inner.take_queued_events())
1543 };
1544 crate::inner::dispatch_queued_events(queued);
1545 id
1546 }
1547
1548 pub fn add_range_session(&self) -> crate::highlight::SessionId {
1555 let mut inner = self.inner.lock();
1556 inner.highlights.add_range()
1557 }
1559
1560 pub fn set_session_ranges(
1567 &self,
1568 id: crate::highlight::SessionId,
1569 ranges: Vec<crate::highlight::RangeHighlight>,
1570 ) -> bool {
1571 let (ok, queued) = {
1572 let mut inner = self.inner.lock();
1573 let prev_kind = inner.highlight_kind;
1574 let block_positions = crate::highlight::ordered_block_positions(&inner);
1579 let ok = inner.highlights.set_ranges(id, ranges, &block_positions);
1580 if ok {
1581 inner.recompute_highlight_kind();
1582 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1583 }
1584 (ok, inner.take_queued_events())
1585 };
1586 crate::inner::dispatch_queued_events(queued);
1587 ok
1588 }
1589
1590 pub fn remove_session(&self, id: crate::highlight::SessionId) -> bool {
1592 let (existed, queued) = {
1593 let mut inner = self.inner.lock();
1594 let prev_kind = inner.highlight_kind;
1595 let existed = inner.highlights.remove(id);
1596 if existed {
1597 inner.recompute_highlight_kind();
1598 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1599 }
1600 (existed, inner.take_queued_events())
1601 };
1602 crate::inner::dispatch_queued_events(queued);
1603 existed
1604 }
1605
1606 pub fn rehighlight(&self) {
1611 let queued = {
1612 let mut inner = self.inner.lock();
1613 let prev_kind = inner.highlight_kind;
1614 inner.rehighlight_all();
1615 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1616 inner.take_queued_events()
1617 };
1618 crate::inner::dispatch_queued_events(queued);
1619 }
1620
1621 pub fn rehighlight_block(&self, block_id: usize) {
1624 let queued = {
1625 let mut inner = self.inner.lock();
1626 let prev_kind = inner.highlight_kind;
1627 inner.rehighlight_from_block(block_id);
1628 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1629 inner.take_queued_events()
1630 };
1631 crate::inner::dispatch_queued_events(queued);
1632 }
1633
1634 fn queue_highlight_changed(
1654 inner: &mut TextDocumentInner,
1655 position: usize,
1656 length: usize,
1657 prev_kind: crate::highlight::HighlighterKind,
1658 ) {
1659 use crate::highlight::HighlighterKind::{Metric, None as KNone, PaintOnly};
1660 let new_kind = inner.highlight_kind;
1661 let event = match (prev_kind, new_kind) {
1662 (KNone, KNone) => return,
1664 (PaintOnly, PaintOnly) | (KNone, PaintOnly) | (PaintOnly, KNone) => {
1666 DocumentEvent::HighlightPaintChanged { position, length }
1667 }
1668 (KNone, Metric)
1670 | (Metric, Metric)
1671 | (Metric, PaintOnly)
1672 | (Metric, KNone)
1673 | (PaintOnly, Metric) => DocumentEvent::FormatChanged {
1674 position,
1675 length,
1676 kind: crate::flow::FormatChangeKind::Character,
1677 },
1678 };
1679 inner.queue_event(event);
1680 }
1681}
1682
1683impl Default for TextDocument {
1684 fn default() -> Self {
1685 Self::new()
1686 }
1687}
1688
1689struct UndoBlockState {
1693 id: u64,
1694 position: i64,
1695 text_length: i64,
1696 plain_text: String,
1697 format: BlockFormat,
1698}
1699
1700fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
1702 let mut all_blocks =
1703 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
1704 let store = inner.ctx.db_context.get_store();
1705 crate::inner::refresh_block_positions(&mut all_blocks, store);
1706 let mut states: Vec<UndoBlockState> = all_blocks
1707 .into_iter()
1708 .map(|b| {
1709 let format = BlockFormat::from(&b);
1710 let entity: common::entities::Block = b.clone().into();
1711 let plain_text =
1712 common::database::rope_helpers::block_content_via_store(&entity, store);
1713 let text_length = common::database::rope_helpers::block_char_length(&entity, store);
1714 UndoBlockState {
1715 id: b.id,
1716 position: b.document_position,
1717 text_length,
1718 plain_text,
1719 format,
1720 }
1721 })
1722 .collect();
1723 states.sort_by_key(|s| s.position);
1724 states
1725}
1726
1727fn build_doc_text(states: &[UndoBlockState]) -> String {
1729 states
1730 .iter()
1731 .map(|s| s.plain_text.as_str())
1732 .collect::<Vec<_>>()
1733 .join("\n")
1734}
1735
1736fn compute_text_edit(before: &str, after: &str) -> (usize, usize, usize) {
1739 let before_chars: Vec<char> = before.chars().collect();
1740 let after_chars: Vec<char> = after.chars().collect();
1741
1742 let prefix_len = before_chars
1744 .iter()
1745 .zip(after_chars.iter())
1746 .take_while(|(a, b)| a == b)
1747 .count();
1748
1749 let before_remaining = before_chars.len() - prefix_len;
1751 let after_remaining = after_chars.len() - prefix_len;
1752 let suffix_len = before_chars
1753 .iter()
1754 .rev()
1755 .zip(after_chars.iter().rev())
1756 .take(before_remaining.min(after_remaining))
1757 .take_while(|(a, b)| a == b)
1758 .count();
1759
1760 let removed = before_remaining - suffix_len;
1761 let added = after_remaining - suffix_len;
1762
1763 (prefix_len, removed, added)
1764}
1765
1766fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
1769 let after = capture_block_state(inner);
1770
1771 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
1773 before.iter().map(|s| (s.id, s)).collect();
1774 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
1775 after.iter().map(|s| (s.id, s)).collect();
1776
1777 let mut content_changed = false;
1779 let mut earliest_pos: Option<usize> = None;
1780 let mut old_end: usize = 0;
1781 let mut new_end: usize = 0;
1782 let mut blocks_affected: usize = 0;
1783
1784 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
1788 if let Some(before_state) = before_map.get(&after_state.id) {
1789 let text_changed = before_state.plain_text != after_state.plain_text
1790 || before_state.text_length != after_state.text_length;
1791 let format_changed = before_state.format != after_state.format;
1792
1793 if text_changed {
1794 content_changed = true;
1795 blocks_affected += 1;
1796 let pos = after_state.position.max(0) as usize;
1797 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1798 old_end = old_end.max(
1799 before_state.position.max(0) as usize
1800 + before_state.text_length.max(0) as usize,
1801 );
1802 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1803 } else if format_changed {
1804 let pos = after_state.position.max(0) as usize;
1805 let len = after_state.text_length.max(0) as usize;
1806 format_only_changes.push((pos, len));
1807 }
1808 } else {
1809 content_changed = true;
1811 blocks_affected += 1;
1812 let pos = after_state.position.max(0) as usize;
1813 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1814 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1815 }
1816 }
1817
1818 for before_state in before {
1820 if !after_map.contains_key(&before_state.id) {
1821 content_changed = true;
1822 blocks_affected += 1;
1823 let pos = before_state.position.max(0) as usize;
1824 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1825 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
1826 }
1827 }
1828
1829 if content_changed {
1830 let position = earliest_pos.unwrap_or(0);
1831 let chars_removed = old_end.saturating_sub(position);
1832 let chars_added = new_end.saturating_sub(position);
1833
1834 let before_text = build_doc_text(before);
1837 let after_text = build_doc_text(&after);
1838 let (edit_offset, precise_removed, precise_added) =
1839 compute_text_edit(&before_text, &after_text);
1840 if precise_removed > 0 || precise_added > 0 {
1841 inner.adjust_cursors(edit_offset, precise_removed, precise_added);
1842 }
1843
1844 inner.queue_event(DocumentEvent::ContentsChanged {
1845 position,
1846 chars_removed,
1847 chars_added,
1848 blocks_affected,
1849 });
1850 }
1851
1852 for (position, length) in format_only_changes {
1854 inner.queue_event(DocumentEvent::FormatChanged {
1855 position,
1856 length,
1857 kind: FormatChangeKind::Block,
1858 });
1859 }
1860}
1861
1862fn collect_frame_block_ids(
1868 inner: &TextDocumentInner,
1869 frame_id: frontend::common::types::EntityId,
1870) -> Option<Vec<u64>> {
1871 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
1872 .ok()
1873 .flatten()?;
1874
1875 if !frame_dto.child_order.is_empty() {
1876 let mut block_ids = Vec::new();
1877 for &entry in &frame_dto.child_order {
1878 if entry > 0 {
1879 block_ids.push(entry as u64);
1880 } else if entry < 0 {
1881 let sub_frame_id = (-entry) as u64;
1882 let sub_frame = frame_commands::get_frame(&inner.ctx, &sub_frame_id)
1883 .ok()
1884 .flatten();
1885 if let Some(ref sf) = sub_frame {
1886 if let Some(table_id) = sf.table {
1887 if let Some(table_dto) = table_commands::get_table(&inner.ctx, &table_id)
1890 .ok()
1891 .flatten()
1892 {
1893 let mut cell_dtos: Vec<_> = table_dto
1894 .cells
1895 .iter()
1896 .filter_map(|&cid| {
1897 table_cell_commands::get_table_cell(&inner.ctx, &cid)
1898 .ok()
1899 .flatten()
1900 })
1901 .collect();
1902 cell_dtos
1903 .sort_by(|a, b| a.row.cmp(&b.row).then(a.column.cmp(&b.column)));
1904 for cell_dto in &cell_dtos {
1905 if let Some(cf_id) = cell_dto.cell_frame
1906 && let Some(cf_ids) = collect_frame_block_ids(inner, cf_id)
1907 {
1908 block_ids.extend(cf_ids);
1909 }
1910 }
1911 }
1912 } else if let Some(sub_ids) = collect_frame_block_ids(inner, sub_frame_id) {
1913 block_ids.extend(sub_ids);
1914 }
1915 }
1916 }
1917 }
1918 Some(block_ids)
1919 } else {
1920 Some(frame_dto.blocks.to_vec())
1921 }
1922}
1923
1924pub(crate) fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
1925 let frames = frontend::commands::document_commands::get_document_relationship(
1927 &inner.ctx,
1928 &inner.document_id,
1929 &frontend::document::dtos::DocumentRelationshipField::Frames,
1930 )
1931 .unwrap_or_default();
1932
1933 frames.first().copied().unwrap_or(0)
1934}
1935
1936fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
1940 let Some(json) = data else {
1941 return (String::new(), 0.0, String::new());
1942 };
1943 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1944 let id = v["id"].as_str().unwrap_or_default().to_string();
1945 let pct = v["percentage"].as_f64().unwrap_or(0.0);
1946 let msg = v["message"].as_str().unwrap_or_default().to_string();
1947 (id, pct, msg)
1948}
1949
1950fn parse_id_data(data: &Option<String>) -> String {
1952 let Some(json) = data else {
1953 return String::new();
1954 };
1955 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1956 v["id"].as_str().unwrap_or_default().to_string()
1957}
1958
1959fn parse_failed_data(data: &Option<String>) -> (String, String) {
1961 let Some(json) = data else {
1962 return (String::new(), "unknown error".into());
1963 };
1964 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1965 let id = v["id"].as_str().unwrap_or_default().to_string();
1966 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
1967 (id, error)
1968}