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, HtmlImportResult, MarkdownImportResult, Operation,
25};
26use crate::{BlockFormat, BlockInfo, DocumentStats, FindMatch, FindOptions};
27
28#[derive(Clone)]
39pub struct TextDocument {
40 pub(crate) inner: Arc<Mutex<TextDocumentInner>>,
41}
42
43impl TextDocument {
46 #[doc(hidden)]
47 pub fn rope_store_for_test(&self) -> std::sync::Arc<common::database::Store> {
48 let inner = self.inner.lock();
49 std::sync::Arc::clone(inner.ctx.db_context.get_store())
50 }
51}
52
53impl TextDocument {
54 pub fn new() -> Self {
63 Self::try_new().expect("failed to initialize document")
64 }
65
66 pub fn try_new() -> Result<Self> {
68 let ctx = frontend::AppContext::new();
69 let doc_inner = TextDocumentInner::initialize(ctx)?;
70 let inner = Arc::new(Mutex::new(doc_inner));
71
72 Self::subscribe_long_operation_events(&inner);
74
75 Ok(Self { inner })
76 }
77
78 fn subscribe_long_operation_events(inner: &Arc<Mutex<TextDocumentInner>>) {
80 use frontend::common::event::{LongOperationEvent as LOE, Origin};
81
82 let weak = Arc::downgrade(inner);
83 let mut locked = inner.lock();
84
85 let w = weak.clone();
87 let progress_tok =
88 locked
89 .event_client
90 .subscribe(Origin::LongOperation(LOE::Progress), move |event| {
91 if let Some(inner) = w.upgrade() {
92 let (op_id, percent, message) = parse_progress_data(&event.data);
93 let mut inner = inner.lock();
94 inner.queue_event(DocumentEvent::LongOperationProgress {
95 operation_id: op_id,
96 percent,
97 message,
98 });
99 }
100 });
101
102 let w = weak.clone();
104 let completed_tok =
105 locked
106 .event_client
107 .subscribe(Origin::LongOperation(LOE::Completed), move |event| {
108 if let Some(inner) = w.upgrade() {
109 let op_id = parse_id_data(&event.data);
110 let mut inner = inner.lock();
111 inner.queue_event(DocumentEvent::DocumentReset);
112 inner.check_block_count_changed();
113 inner.reset_cached_child_order();
114 inner.queue_event(DocumentEvent::LongOperationFinished {
115 operation_id: op_id,
116 success: true,
117 error: None,
118 });
119 }
120 });
121
122 let w = weak.clone();
124 let cancelled_tok =
125 locked
126 .event_client
127 .subscribe(Origin::LongOperation(LOE::Cancelled), move |event| {
128 if let Some(inner) = w.upgrade() {
129 let op_id = parse_id_data(&event.data);
130 let mut inner = inner.lock();
131 inner.queue_event(DocumentEvent::LongOperationFinished {
132 operation_id: op_id,
133 success: false,
134 error: Some("cancelled".into()),
135 });
136 }
137 });
138
139 let failed_tok =
141 locked
142 .event_client
143 .subscribe(Origin::LongOperation(LOE::Failed), move |event| {
144 if let Some(inner) = weak.upgrade() {
145 let (op_id, error) = parse_failed_data(&event.data);
146 let mut inner = inner.lock();
147 inner.queue_event(DocumentEvent::LongOperationFinished {
148 operation_id: op_id,
149 success: false,
150 error: Some(error),
151 });
152 }
153 });
154
155 locked.long_op_subscriptions.extend([
156 progress_tok,
157 completed_tok,
158 cancelled_tok,
159 failed_tok,
160 ]);
161 }
162
163 pub fn set_plain_text(&self, text: &str) -> Result<()> {
167 let queued = {
168 let mut inner = self.inner.lock();
169 let dto = frontend::document_io::ImportPlainTextDto {
170 plain_text: text.into(),
171 };
172 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
173 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
174 inner.invalidate_text_cache();
175 inner.rehighlight_all();
176 inner.queue_event(DocumentEvent::DocumentReset);
177 inner.check_block_count_changed();
178 inner.reset_cached_child_order();
179 inner.queue_event(DocumentEvent::UndoRedoChanged {
180 can_undo: false,
181 can_redo: false,
182 });
183 inner.take_queued_events()
184 };
185 crate::inner::dispatch_queued_events(queued);
186 Ok(())
187 }
188
189 pub fn to_plain_text(&self) -> Result<String> {
191 let mut inner = self.inner.lock();
192 Ok(inner.plain_text()?.to_string())
193 }
194
195 pub fn set_markdown(&self, markdown: &str) -> Result<Operation<MarkdownImportResult>> {
199 let mut inner = self.inner.lock();
200 inner.invalidate_text_cache();
201 let dto = frontend::document_io::ImportMarkdownDto {
202 markdown_text: markdown.into(),
203 };
204 let op_id = document_io_commands::import_markdown(&inner.ctx, &dto)?;
205 Ok(Operation::new(
206 op_id,
207 &inner.ctx,
208 Box::new(|ctx, id| {
209 document_io_commands::get_import_markdown_result(ctx, id)
210 .ok()
211 .flatten()
212 .map(|r| {
213 Ok(MarkdownImportResult {
214 block_count: to_usize(r.block_count),
215 })
216 })
217 }),
218 ))
219 }
220
221 pub fn to_markdown(&self) -> Result<String> {
223 let inner = self.inner.lock();
224 let dto = document_io_commands::export_markdown(&inner.ctx)?;
225 Ok(dto.markdown_text)
226 }
227
228 pub fn set_djot(&self, djot: &str) -> Result<Operation<DjotImportResult>> {
232 self.set_djot_with_options(djot, DjotImportOptions::default())
233 }
234
235 pub fn set_djot_with_options(
241 &self,
242 djot: &str,
243 options: DjotImportOptions,
244 ) -> Result<Operation<DjotImportResult>> {
245 let mut inner = self.inner.lock();
246 inner.invalidate_text_cache();
247 let dto = frontend::document_io::ImportDjotDto {
248 djot_text: djot.into(),
249 options,
250 };
251 let op_id = document_io_commands::import_djot(&inner.ctx, &dto)?;
252 Ok(Operation::new(
253 op_id,
254 &inner.ctx,
255 Box::new(|ctx, id| {
256 document_io_commands::get_import_djot_result(ctx, id)
257 .ok()
258 .flatten()
259 .map(|r| {
260 Ok(DjotImportResult {
261 block_count: to_usize(r.block_count),
262 })
263 })
264 }),
265 ))
266 }
267
268 pub fn to_djot(&self) -> Result<String> {
270 self.to_djot_with_options(DjotExportOptions::default())
271 }
272
273 pub fn to_djot_with_options(&self, options: DjotExportOptions) -> Result<String> {
277 let inner = self.inner.lock();
278 let dto = document_io_commands::export_djot(&inner.ctx, &options)?;
279 Ok(dto.djot_text)
280 }
281
282 pub fn set_html(&self, html: &str) -> Result<Operation<HtmlImportResult>> {
286 let mut inner = self.inner.lock();
287 inner.invalidate_text_cache();
288 let dto = frontend::document_io::ImportHtmlDto {
289 html_text: html.into(),
290 };
291 let op_id = document_io_commands::import_html(&inner.ctx, &dto)?;
292 Ok(Operation::new(
293 op_id,
294 &inner.ctx,
295 Box::new(|ctx, id| {
296 document_io_commands::get_import_html_result(ctx, id)
297 .ok()
298 .flatten()
299 .map(|r| {
300 Ok(HtmlImportResult {
301 block_count: to_usize(r.block_count),
302 })
303 })
304 }),
305 ))
306 }
307
308 pub fn to_html(&self) -> Result<String> {
310 let inner = self.inner.lock();
311 let dto = document_io_commands::export_html(&inner.ctx)?;
312 Ok(dto.html_text)
313 }
314
315 pub fn to_latex(&self, document_class: &str, include_preamble: bool) -> Result<String> {
317 let inner = self.inner.lock();
318 let dto = frontend::document_io::ExportLatexDto {
319 document_class: document_class.into(),
320 include_preamble,
321 };
322 let result = document_io_commands::export_latex(&inner.ctx, &dto)?;
323 Ok(result.latex_text)
324 }
325
326 pub fn to_docx(&self, output_path: &str) -> Result<Operation<DocxExportResult>> {
330 let inner = self.inner.lock();
331 let dto = frontend::document_io::ExportDocxDto {
332 output_path: output_path.into(),
333 };
334 let op_id = document_io_commands::export_docx(&inner.ctx, &dto)?;
335 Ok(Operation::new(
336 op_id,
337 &inner.ctx,
338 Box::new(|ctx, id| {
339 document_io_commands::get_export_docx_result(ctx, id)
340 .ok()
341 .flatten()
342 .map(|r| {
343 Ok(DocxExportResult {
344 file_path: r.file_path,
345 paragraph_count: to_usize(r.paragraph_count),
346 })
347 })
348 }),
349 ))
350 }
351
352 pub fn clear(&self) -> Result<()> {
354 let queued = {
355 let mut inner = self.inner.lock();
356 let dto = frontend::document_io::ImportPlainTextDto {
357 plain_text: String::new(),
358 };
359 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
360 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
361 inner.invalidate_text_cache();
362 inner.rehighlight_all();
363 inner.queue_event(DocumentEvent::DocumentReset);
364 inner.check_block_count_changed();
365 inner.reset_cached_child_order();
366 inner.queue_event(DocumentEvent::UndoRedoChanged {
367 can_undo: false,
368 can_redo: false,
369 });
370 inner.take_queued_events()
371 };
372 crate::inner::dispatch_queued_events(queued);
373 Ok(())
374 }
375
376 pub fn cursor(&self) -> TextCursor {
380 self.cursor_at(0)
381 }
382
383 pub fn cursor_at(&self, position: usize) -> TextCursor {
389 let data = {
390 let mut inner = self.inner.lock();
391 inner.register_cursor(position)
392 };
393 let cursor = TextCursor {
394 doc: self.inner.clone(),
395 data,
396 };
397 cursor.snap_position_to_grapheme_boundary();
398 cursor
399 }
400
401 pub fn stats(&self) -> DocumentStats {
405 let inner = self.inner.lock();
406 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
407 .expect("get_document_stats should not fail");
408 DocumentStats::from(&dto)
409 }
410
411 pub fn character_count(&self) -> usize {
413 let inner = self.inner.lock();
414 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
415 .expect("get_document_stats should not fail");
416 to_usize(dto.character_count)
417 }
418
419 pub fn block_count(&self) -> usize {
421 let inner = self.inner.lock();
422 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
423 .expect("get_document_stats should not fail");
424 to_usize(dto.block_count)
425 }
426
427 pub fn is_empty(&self) -> bool {
429 self.character_count() == 0
430 }
431
432 pub fn text_at(&self, position: usize, length: usize) -> Result<String> {
434 let inner = self.inner.lock();
435 let dto = frontend::document_inspection::GetTextAtPositionDto {
436 position: to_i64(position),
437 length: to_i64(length),
438 };
439 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
440 Ok(result.text)
441 }
442
443 pub fn find_element_at_position(&self, position: usize) -> Option<(u64, usize, usize)> {
458 let block_info = self.block_at(position).ok()?;
459 let block_start = block_info.start;
460 let offset_in_block = position.checked_sub(block_start)?;
461 let block = crate::text_block::TextBlock {
462 doc: std::sync::Arc::clone(&self.inner),
463 block_id: block_info.block_id,
464 };
465 let frags = block.fragments();
466 let mut last_text: Option<(u64, usize, usize, usize)> = None; for frag in &frags {
472 match frag {
473 crate::flow::FragmentContent::Text {
474 offset,
475 length,
476 element_id,
477 ..
478 } => {
479 let frag_start = *offset;
480 let frag_end = frag_start + *length;
481 if offset_in_block >= frag_start && offset_in_block < frag_end {
482 let abs_start = block_start + frag_start;
483 let offset_within = offset_in_block - frag_start;
484 return Some((*element_id, abs_start, offset_within));
485 }
486 if offset_in_block == frag_end {
489 last_text =
490 Some((*element_id, block_start + frag_start, frag_start, *length));
491 }
492 }
493 crate::flow::FragmentContent::Image {
494 offset, element_id, ..
495 } => {
496 if offset_in_block == *offset {
497 return Some((*element_id, block_start + offset, 0));
498 }
499 }
500 }
501 }
502 last_text.map(|(id, abs_start, _, length)| (id, abs_start, length))
505 }
506
507 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
509 let inner = self.inner.lock();
510 let dto = frontend::document_inspection::GetBlockAtPositionDto {
511 position: to_i64(position),
512 };
513 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
514 Ok(BlockInfo::from(&result))
515 }
516
517 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
519 let inner = self.inner.lock();
520 let dto = frontend::document_inspection::GetBlockAtPositionDto {
521 position: to_i64(position),
522 };
523 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
524 let block_id = block_info.block_id;
525 let block_id = block_id as u64;
526 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
527 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
528 Ok(BlockFormat::from(&block_dto))
529 }
530
531 pub fn flow(&self) -> Vec<crate::flow::FlowElement> {
542 let inner = self.inner.lock();
543 let main_frame_id = get_main_frame_id(&inner);
544 crate::text_frame::build_flow_elements(&inner, &self.inner, main_frame_id)
545 }
546
547 pub fn block_by_id(&self, block_id: usize) -> Option<crate::text_block::TextBlock> {
552 let inner = self.inner.lock();
553 let exists = frontend::commands::block_commands::get_block(&inner.ctx, &(block_id as u64))
554 .ok()
555 .flatten()
556 .is_some();
557
558 if exists {
559 Some(crate::text_block::TextBlock {
560 doc: self.inner.clone(),
561 block_id,
562 })
563 } else {
564 None
565 }
566 }
567
568 pub fn snapshot_block_at_position(
574 &self,
575 position: usize,
576 ) -> Option<crate::flow::BlockSnapshot> {
577 self.snapshot_block_at_position_impl(position, true)
578 }
579
580 pub fn snapshot_block_at_position_without_highlights(
585 &self,
586 position: usize,
587 ) -> Option<crate::flow::BlockSnapshot> {
588 self.snapshot_block_at_position_impl(position, false)
589 }
590
591 fn snapshot_block_at_position_impl(
592 &self,
593 position: usize,
594 apply_highlights: bool,
595 ) -> Option<crate::flow::BlockSnapshot> {
596 let inner = self.inner.lock();
597 let effective_kind = if apply_highlights {
598 inner.highlight_kind
599 } else {
600 crate::highlight::HighlighterKind::None
601 };
602 let main_frame_id = get_main_frame_id(&inner);
603 let store = inner.ctx.db_context.get_store();
604
605 if common::database::rope_helpers::rope_positions_match_flow(store)
613 && let Some((block_id, _, _)) =
614 common::database::rope_helpers::find_block_at_char_position(store, position as i64)
615 {
616 return crate::text_block::build_block_snapshot(&inner, block_id, effective_kind);
617 }
618
619 let ordered_block_ids = collect_frame_block_ids(&inner, main_frame_id)?;
621
622 let pos = position as i64;
624 let mut running_pos: i64 = 0;
625 for &block_id in &ordered_block_ids {
626 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
627 .ok()
628 .flatten()?;
629 let entity: common::entities::Block = block_dto.clone().into();
630 let block_end =
631 running_pos + common::database::rope_helpers::block_char_length(&entity, store);
632 if pos >= running_pos && pos <= block_end {
633 return crate::text_block::build_block_snapshot_with_position(
634 &inner,
635 block_id,
636 Some(running_pos as usize),
637 effective_kind,
638 );
639 }
640 running_pos = block_end + 1;
641 }
642
643 if let Some(&last_id) = ordered_block_ids.last() {
645 return crate::text_block::build_block_snapshot(&inner, last_id, effective_kind);
646 }
647 None
648 }
649
650 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
653 let inner = self.inner.lock();
654 let dto = frontend::document_inspection::GetBlockAtPositionDto {
655 position: to_i64(position),
656 };
657 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
658 Some(crate::text_block::TextBlock {
659 doc: self.inner.clone(),
660 block_id: result.block_id as usize,
661 })
662 }
663
664 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
673 let inner = self.inner.lock();
674 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
675 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
676 let store = inner.ctx.db_context.get_store();
677 crate::inner::refresh_block_positions(&mut sorted, store);
678 sorted.sort_by_key(|b| b.document_position);
679
680 sorted
681 .get(block_number)
682 .map(|b| crate::text_block::TextBlock {
683 doc: self.inner.clone(),
684 block_id: b.id as usize,
685 })
686 }
687
688 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
694 let inner = self.inner.lock();
695 let all_blocks =
696 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
697 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
698 let store = inner.ctx.db_context.get_store();
699 crate::inner::refresh_block_positions(&mut sorted, store);
700 sorted.sort_by_key(|b| b.document_position);
701 sorted
702 .iter()
703 .map(|b| crate::text_block::TextBlock {
704 doc: self.inner.clone(),
705 block_id: b.id as usize,
706 })
707 .collect()
708 }
709
710 pub fn blocks_in_range(
717 &self,
718 position: usize,
719 length: usize,
720 ) -> Vec<crate::text_block::TextBlock> {
721 let inner = self.inner.lock();
722 let all_blocks =
723 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
724 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
725 let store = inner.ctx.db_context.get_store();
726 crate::inner::refresh_block_positions(&mut sorted, store);
727 sorted.sort_by_key(|b| b.document_position);
728
729 let range_start = position;
730 let range_end = position + length;
731 sorted
732 .iter()
733 .filter(|b| {
734 let block_start = b.document_position.max(0) as usize;
735 let entity: common::entities::Block = (*b).clone().into();
736 let block_end = block_start
737 + common::database::rope_helpers::block_char_length(&entity, store).max(0)
738 as usize;
739 if length == 0 {
741 range_start >= block_start && range_start < block_end
743 } else {
744 block_start < range_end && block_end > range_start
745 }
746 })
747 .map(|b| crate::text_block::TextBlock {
748 doc: self.inner.clone(),
749 block_id: b.id as usize,
750 })
751 .collect()
752 }
753
754 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
759 let inner = self.inner.lock();
760 let main_frame_id = get_main_frame_id(&inner);
761 let elements =
762 crate::text_frame::build_flow_snapshot(&inner, main_frame_id, inner.highlight_kind);
763 crate::flow::FlowSnapshot { elements }
764 }
765
766 pub fn snapshot_flow_without_highlights(&self) -> crate::flow::FlowSnapshot {
776 let inner = self.inner.lock();
777 let main_frame_id = get_main_frame_id(&inner);
778 let elements = crate::text_frame::build_flow_snapshot(
779 &inner,
780 main_frame_id,
781 crate::highlight::HighlighterKind::None,
782 );
783 crate::flow::FlowSnapshot { elements }
784 }
785
786 pub fn find(
790 &self,
791 query: &str,
792 from: usize,
793 options: &FindOptions,
794 ) -> Result<Option<FindMatch>> {
795 let inner = self.inner.lock();
796 let dto = options.to_find_text_dto(query, from);
797 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
798 Ok(convert::find_result_to_match(&result))
799 }
800
801 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
803 let inner = self.inner.lock();
804 let dto = options.to_find_all_dto(query);
805 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
806 Ok(convert::find_all_to_matches(&result))
807 }
808
809 pub fn replace_text(
811 &self,
812 query: &str,
813 replacement: &str,
814 replace_all: bool,
815 options: &FindOptions,
816 ) -> Result<usize> {
817 let (count, queued) = {
818 let mut inner = self.inner.lock();
819 let dto = options.to_replace_dto(query, replacement, replace_all);
820 let result =
821 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
822 let count = to_usize(result.replacements_count);
823 inner.invalidate_text_cache();
824 if count > 0 {
825 inner.modified = true;
826 inner.rehighlight_all();
827 inner.queue_event(DocumentEvent::ContentsChanged {
832 position: 0,
833 chars_removed: 0,
834 chars_added: 0,
835 blocks_affected: count,
836 });
837 inner.check_block_count_changed();
838 inner.check_flow_changed();
839 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
840 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
841 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
842 }
843 (count, inner.take_queued_events())
844 };
845 crate::inner::dispatch_queued_events(queued);
846 Ok(count)
847 }
848
849 pub fn add_resource(
853 &self,
854 resource_type: ResourceType,
855 name: &str,
856 mime_type: &str,
857 data: &[u8],
858 ) -> Result<()> {
859 let mut inner = self.inner.lock();
860 let dto = frontend::resource::dtos::CreateResourceDto {
861 created_at: Default::default(),
862 updated_at: Default::default(),
863 resource_type,
864 name: name.into(),
865 url: String::new(),
866 mime_type: mime_type.into(),
867 data_base64: BASE64.encode(data),
868 };
869 let created = resource_commands::create_resource(
870 &inner.ctx,
871 Some(inner.stack_id),
872 &dto,
873 inner.document_id,
874 -1,
875 )?;
876 inner.resource_cache.insert(name.to_string(), created.id);
877 Ok(())
878 }
879
880 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
884 let mut inner = self.inner.lock();
885
886 if let Some(&id) = inner.resource_cache.get(name) {
888 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
889 let bytes = BASE64
890 .decode(&r.data_base64)
891 .map_err(|e| DocumentError::Internal(e.into()))?;
892 return Ok(Some(bytes));
893 }
894 inner.resource_cache.remove(name);
896 }
897
898 let all = resource_commands::get_all_resource(&inner.ctx)?;
900 for r in &all {
901 if r.name == name {
902 inner.resource_cache.insert(name.to_string(), r.id);
903 let bytes = BASE64
904 .decode(&r.data_base64)
905 .map_err(|e| DocumentError::Internal(e.into()))?;
906 return Ok(Some(bytes));
907 }
908 }
909 Ok(None)
910 }
911
912 pub fn undo(&self) -> Result<()> {
916 let queued = {
917 let mut inner = self.inner.lock();
918 let before = capture_block_state(&inner);
919 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
920 inner.invalidate_text_cache();
921 result?;
922 inner.rehighlight_all();
923 emit_undo_redo_change_events(&mut inner, &before);
924 inner.check_block_count_changed();
925 inner.check_flow_changed();
926 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
927 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
928 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
929 inner.take_queued_events()
930 };
931 crate::inner::dispatch_queued_events(queued);
932 Ok(())
933 }
934
935 pub fn redo(&self) -> Result<()> {
937 let queued = {
938 let mut inner = self.inner.lock();
939 let before = capture_block_state(&inner);
940 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
941 inner.invalidate_text_cache();
942 result?;
943 inner.rehighlight_all();
944 emit_undo_redo_change_events(&mut inner, &before);
945 inner.check_block_count_changed();
946 inner.check_flow_changed();
947 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
948 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
949 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
950 inner.take_queued_events()
951 };
952 crate::inner::dispatch_queued_events(queued);
953 Ok(())
954 }
955
956 pub fn can_undo(&self) -> bool {
958 let inner = self.inner.lock();
959 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
960 }
961
962 pub fn can_redo(&self) -> bool {
964 let inner = self.inner.lock();
965 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
966 }
967
968 pub fn clear_undo_redo(&self) {
970 let inner = self.inner.lock();
971 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
972 }
973
974 pub fn is_modified(&self) -> bool {
978 self.inner.lock().modified
979 }
980
981 pub fn set_modified(&self, modified: bool) {
983 let queued = {
984 let mut inner = self.inner.lock();
985 if inner.modified != modified {
986 inner.modified = modified;
987 inner.queue_event(DocumentEvent::ModificationChanged(modified));
988 }
989 inner.take_queued_events()
990 };
991 crate::inner::dispatch_queued_events(queued);
992 }
993
994 pub fn title(&self) -> String {
998 let inner = self.inner.lock();
999 document_commands::get_document(&inner.ctx, &inner.document_id)
1000 .ok()
1001 .flatten()
1002 .map(|d| d.title)
1003 .unwrap_or_default()
1004 }
1005
1006 pub fn set_title(&self, title: &str) -> Result<()> {
1008 let inner = self.inner.lock();
1009 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
1010 .ok_or_else(|| DocumentError::NotFound("document not found".into()))?;
1011 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
1012 update.title = title.into();
1013 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
1014 Ok(())
1015 }
1016
1017 pub fn text_direction(&self) -> TextDirection {
1019 let inner = self.inner.lock();
1020 document_commands::get_document(&inner.ctx, &inner.document_id)
1021 .ok()
1022 .flatten()
1023 .map(|d| d.text_direction)
1024 .unwrap_or(TextDirection::LeftToRight)
1025 }
1026
1027 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
1029 let inner = self.inner.lock();
1030 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
1031 .ok_or_else(|| DocumentError::NotFound("document not found".into()))?;
1032 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
1033 update.text_direction = direction;
1034 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
1035 Ok(())
1036 }
1037
1038 pub fn default_wrap_mode(&self) -> WrapMode {
1040 let inner = self.inner.lock();
1041 document_commands::get_document(&inner.ctx, &inner.document_id)
1042 .ok()
1043 .flatten()
1044 .map(|d| d.default_wrap_mode)
1045 .unwrap_or(WrapMode::WordWrap)
1046 }
1047
1048 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
1050 let inner = self.inner.lock();
1051 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
1052 .ok_or_else(|| DocumentError::NotFound("document not found".into()))?;
1053 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
1054 update.default_wrap_mode = mode;
1055 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
1056 Ok(())
1057 }
1058
1059 pub fn default_language(&self) -> String {
1063 let inner = self.inner.lock();
1064 document_commands::get_document(&inner.ctx, &inner.document_id)
1065 .ok()
1066 .flatten()
1067 .and_then(|d| d.default_language)
1068 .unwrap_or_else(|| "en".to_string())
1069 }
1070
1071 pub fn set_default_language(&self, language: &str) -> Result<()> {
1074 let inner = self.inner.lock();
1075 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
1076 .ok_or_else(|| DocumentError::NotFound("document not found".into()))?;
1077 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
1078 update.default_language = Some(language.to_string());
1079 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
1080 Ok(())
1081 }
1082
1083 pub fn on_change<F>(&self, callback: F) -> Subscription
1102 where
1103 F: Fn(DocumentEvent) + Send + Sync + 'static,
1104 {
1105 let mut inner = self.inner.lock();
1106 events::subscribe_inner(&mut inner, callback)
1107 }
1108
1109 pub fn poll_events(&self) -> Vec<DocumentEvent> {
1115 let mut inner = self.inner.lock();
1116 inner.drain_poll_events()
1117 }
1118
1119 pub fn set_syntax_highlighter(&self, highlighter: Option<Arc<dyn crate::SyntaxHighlighter>>) {
1127 let queued = {
1128 let mut inner = self.inner.lock();
1129 let prev_kind = inner.highlight_kind;
1130 match highlighter {
1131 Some(hl) => {
1132 inner.highlight = Some(crate::highlight::HighlightData {
1133 highlighter: hl,
1134 blocks: std::collections::HashMap::new(),
1135 });
1136 inner.rehighlight_all(); }
1138 None => {
1139 inner.highlight = None;
1140 inner.recompute_highlight_kind(); }
1142 }
1143 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1144 inner.take_queued_events()
1145 };
1146 crate::inner::dispatch_queued_events(queued);
1147 }
1148
1149 pub fn rehighlight(&self) {
1154 let queued = {
1155 let mut inner = self.inner.lock();
1156 let prev_kind = inner.highlight_kind;
1157 inner.rehighlight_all();
1158 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1159 inner.take_queued_events()
1160 };
1161 crate::inner::dispatch_queued_events(queued);
1162 }
1163
1164 pub fn rehighlight_block(&self, block_id: usize) {
1167 let queued = {
1168 let mut inner = self.inner.lock();
1169 let prev_kind = inner.highlight_kind;
1170 inner.rehighlight_from_block(block_id);
1171 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1172 inner.take_queued_events()
1173 };
1174 crate::inner::dispatch_queued_events(queued);
1175 }
1176
1177 fn queue_highlight_changed(
1197 inner: &mut TextDocumentInner,
1198 position: usize,
1199 length: usize,
1200 prev_kind: crate::highlight::HighlighterKind,
1201 ) {
1202 use crate::highlight::HighlighterKind::{Metric, None as KNone, PaintOnly};
1203 let new_kind = inner.highlight_kind;
1204 let event = match (prev_kind, new_kind) {
1205 (KNone, KNone) => return,
1207 (PaintOnly, PaintOnly) | (KNone, PaintOnly) | (PaintOnly, KNone) => {
1209 DocumentEvent::HighlightPaintChanged { position, length }
1210 }
1211 (KNone, Metric)
1213 | (Metric, Metric)
1214 | (Metric, PaintOnly)
1215 | (Metric, KNone)
1216 | (PaintOnly, Metric) => DocumentEvent::FormatChanged {
1217 position,
1218 length,
1219 kind: crate::flow::FormatChangeKind::Character,
1220 },
1221 };
1222 inner.queue_event(event);
1223 }
1224}
1225
1226impl Default for TextDocument {
1227 fn default() -> Self {
1228 Self::new()
1229 }
1230}
1231
1232struct UndoBlockState {
1236 id: u64,
1237 position: i64,
1238 text_length: i64,
1239 plain_text: String,
1240 format: BlockFormat,
1241}
1242
1243fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
1245 let mut all_blocks =
1246 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
1247 let store = inner.ctx.db_context.get_store();
1248 crate::inner::refresh_block_positions(&mut all_blocks, store);
1249 let mut states: Vec<UndoBlockState> = all_blocks
1250 .into_iter()
1251 .map(|b| {
1252 let format = BlockFormat::from(&b);
1253 let entity: common::entities::Block = b.clone().into();
1254 let plain_text =
1255 common::database::rope_helpers::block_content_via_store(&entity, store);
1256 let text_length = common::database::rope_helpers::block_char_length(&entity, store);
1257 UndoBlockState {
1258 id: b.id,
1259 position: b.document_position,
1260 text_length,
1261 plain_text,
1262 format,
1263 }
1264 })
1265 .collect();
1266 states.sort_by_key(|s| s.position);
1267 states
1268}
1269
1270fn build_doc_text(states: &[UndoBlockState]) -> String {
1272 states
1273 .iter()
1274 .map(|s| s.plain_text.as_str())
1275 .collect::<Vec<_>>()
1276 .join("\n")
1277}
1278
1279fn compute_text_edit(before: &str, after: &str) -> (usize, usize, usize) {
1282 let before_chars: Vec<char> = before.chars().collect();
1283 let after_chars: Vec<char> = after.chars().collect();
1284
1285 let prefix_len = before_chars
1287 .iter()
1288 .zip(after_chars.iter())
1289 .take_while(|(a, b)| a == b)
1290 .count();
1291
1292 let before_remaining = before_chars.len() - prefix_len;
1294 let after_remaining = after_chars.len() - prefix_len;
1295 let suffix_len = before_chars
1296 .iter()
1297 .rev()
1298 .zip(after_chars.iter().rev())
1299 .take(before_remaining.min(after_remaining))
1300 .take_while(|(a, b)| a == b)
1301 .count();
1302
1303 let removed = before_remaining - suffix_len;
1304 let added = after_remaining - suffix_len;
1305
1306 (prefix_len, removed, added)
1307}
1308
1309fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
1312 let after = capture_block_state(inner);
1313
1314 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
1316 before.iter().map(|s| (s.id, s)).collect();
1317 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
1318 after.iter().map(|s| (s.id, s)).collect();
1319
1320 let mut content_changed = false;
1322 let mut earliest_pos: Option<usize> = None;
1323 let mut old_end: usize = 0;
1324 let mut new_end: usize = 0;
1325 let mut blocks_affected: usize = 0;
1326
1327 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
1331 if let Some(before_state) = before_map.get(&after_state.id) {
1332 let text_changed = before_state.plain_text != after_state.plain_text
1333 || before_state.text_length != after_state.text_length;
1334 let format_changed = before_state.format != after_state.format;
1335
1336 if text_changed {
1337 content_changed = true;
1338 blocks_affected += 1;
1339 let pos = after_state.position.max(0) as usize;
1340 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1341 old_end = old_end.max(
1342 before_state.position.max(0) as usize
1343 + before_state.text_length.max(0) as usize,
1344 );
1345 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1346 } else if format_changed {
1347 let pos = after_state.position.max(0) as usize;
1348 let len = after_state.text_length.max(0) as usize;
1349 format_only_changes.push((pos, len));
1350 }
1351 } else {
1352 content_changed = true;
1354 blocks_affected += 1;
1355 let pos = after_state.position.max(0) as usize;
1356 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1357 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1358 }
1359 }
1360
1361 for before_state in before {
1363 if !after_map.contains_key(&before_state.id) {
1364 content_changed = true;
1365 blocks_affected += 1;
1366 let pos = before_state.position.max(0) as usize;
1367 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1368 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
1369 }
1370 }
1371
1372 if content_changed {
1373 let position = earliest_pos.unwrap_or(0);
1374 let chars_removed = old_end.saturating_sub(position);
1375 let chars_added = new_end.saturating_sub(position);
1376
1377 let before_text = build_doc_text(before);
1380 let after_text = build_doc_text(&after);
1381 let (edit_offset, precise_removed, precise_added) =
1382 compute_text_edit(&before_text, &after_text);
1383 if precise_removed > 0 || precise_added > 0 {
1384 inner.adjust_cursors(edit_offset, precise_removed, precise_added);
1385 }
1386
1387 inner.queue_event(DocumentEvent::ContentsChanged {
1388 position,
1389 chars_removed,
1390 chars_added,
1391 blocks_affected,
1392 });
1393 }
1394
1395 for (position, length) in format_only_changes {
1397 inner.queue_event(DocumentEvent::FormatChanged {
1398 position,
1399 length,
1400 kind: FormatChangeKind::Block,
1401 });
1402 }
1403}
1404
1405fn collect_frame_block_ids(
1411 inner: &TextDocumentInner,
1412 frame_id: frontend::common::types::EntityId,
1413) -> Option<Vec<u64>> {
1414 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
1415 .ok()
1416 .flatten()?;
1417
1418 if !frame_dto.child_order.is_empty() {
1419 let mut block_ids = Vec::new();
1420 for &entry in &frame_dto.child_order {
1421 if entry > 0 {
1422 block_ids.push(entry as u64);
1423 } else if entry < 0 {
1424 let sub_frame_id = (-entry) as u64;
1425 let sub_frame = frame_commands::get_frame(&inner.ctx, &sub_frame_id)
1426 .ok()
1427 .flatten();
1428 if let Some(ref sf) = sub_frame {
1429 if let Some(table_id) = sf.table {
1430 if let Some(table_dto) = table_commands::get_table(&inner.ctx, &table_id)
1433 .ok()
1434 .flatten()
1435 {
1436 let mut cell_dtos: Vec<_> = table_dto
1437 .cells
1438 .iter()
1439 .filter_map(|&cid| {
1440 table_cell_commands::get_table_cell(&inner.ctx, &cid)
1441 .ok()
1442 .flatten()
1443 })
1444 .collect();
1445 cell_dtos
1446 .sort_by(|a, b| a.row.cmp(&b.row).then(a.column.cmp(&b.column)));
1447 for cell_dto in &cell_dtos {
1448 if let Some(cf_id) = cell_dto.cell_frame
1449 && let Some(cf_ids) = collect_frame_block_ids(inner, cf_id)
1450 {
1451 block_ids.extend(cf_ids);
1452 }
1453 }
1454 }
1455 } else if let Some(sub_ids) = collect_frame_block_ids(inner, sub_frame_id) {
1456 block_ids.extend(sub_ids);
1457 }
1458 }
1459 }
1460 }
1461 Some(block_ids)
1462 } else {
1463 Some(frame_dto.blocks.to_vec())
1464 }
1465}
1466
1467pub(crate) fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
1468 let frames = frontend::commands::document_commands::get_document_relationship(
1470 &inner.ctx,
1471 &inner.document_id,
1472 &frontend::document::dtos::DocumentRelationshipField::Frames,
1473 )
1474 .unwrap_or_default();
1475
1476 frames.first().copied().unwrap_or(0)
1477}
1478
1479fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
1483 let Some(json) = data else {
1484 return (String::new(), 0.0, String::new());
1485 };
1486 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1487 let id = v["id"].as_str().unwrap_or_default().to_string();
1488 let pct = v["percentage"].as_f64().unwrap_or(0.0);
1489 let msg = v["message"].as_str().unwrap_or_default().to_string();
1490 (id, pct, msg)
1491}
1492
1493fn parse_id_data(data: &Option<String>) -> String {
1495 let Some(json) = data else {
1496 return String::new();
1497 };
1498 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1499 v["id"].as_str().unwrap_or_default().to_string()
1500}
1501
1502fn parse_failed_data(data: &Option<String>) -> (String, String) {
1504 let Some(json) = data else {
1505 return (String::new(), "unknown error".into());
1506 };
1507 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1508 let id = v["id"].as_str().unwrap_or_default().to_string();
1509 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
1510 (id, error)
1511}