1use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use anyhow::Result;
8use base64::Engine;
9use base64::engine::general_purpose::STANDARD as BASE64;
10
11use crate::{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::{DocxExportResult, HtmlImportResult, MarkdownImportResult, Operation};
24use crate::{BlockFormat, BlockInfo, DocumentStats, FindMatch, FindOptions};
25
26#[derive(Clone)]
37pub struct TextDocument {
38 pub(crate) inner: Arc<Mutex<TextDocumentInner>>,
39}
40
41impl TextDocument {
44 #[doc(hidden)]
45 pub fn rope_store_for_test(&self) -> std::sync::Arc<common::database::Store> {
46 let inner = self.inner.lock();
47 std::sync::Arc::clone(inner.ctx.db_context.get_store())
48 }
49}
50
51impl TextDocument {
52 pub fn new() -> Self {
61 Self::try_new().expect("failed to initialize document")
62 }
63
64 pub fn try_new() -> Result<Self> {
66 let ctx = frontend::AppContext::new();
67 let doc_inner = TextDocumentInner::initialize(ctx)?;
68 let inner = Arc::new(Mutex::new(doc_inner));
69
70 Self::subscribe_long_operation_events(&inner);
72
73 Ok(Self { inner })
74 }
75
76 fn subscribe_long_operation_events(inner: &Arc<Mutex<TextDocumentInner>>) {
78 use frontend::common::event::{LongOperationEvent as LOE, Origin};
79
80 let weak = Arc::downgrade(inner);
81 let mut locked = inner.lock();
82
83 let w = weak.clone();
85 let progress_tok =
86 locked
87 .event_client
88 .subscribe(Origin::LongOperation(LOE::Progress), move |event| {
89 if let Some(inner) = w.upgrade() {
90 let (op_id, percent, message) = parse_progress_data(&event.data);
91 let mut inner = inner.lock();
92 inner.queue_event(DocumentEvent::LongOperationProgress {
93 operation_id: op_id,
94 percent,
95 message,
96 });
97 }
98 });
99
100 let w = weak.clone();
102 let completed_tok =
103 locked
104 .event_client
105 .subscribe(Origin::LongOperation(LOE::Completed), move |event| {
106 if let Some(inner) = w.upgrade() {
107 let op_id = parse_id_data(&event.data);
108 let mut inner = inner.lock();
109 inner.queue_event(DocumentEvent::DocumentReset);
110 inner.check_block_count_changed();
111 inner.reset_cached_child_order();
112 inner.queue_event(DocumentEvent::LongOperationFinished {
113 operation_id: op_id,
114 success: true,
115 error: None,
116 });
117 }
118 });
119
120 let w = weak.clone();
122 let cancelled_tok =
123 locked
124 .event_client
125 .subscribe(Origin::LongOperation(LOE::Cancelled), move |event| {
126 if let Some(inner) = w.upgrade() {
127 let op_id = parse_id_data(&event.data);
128 let mut inner = inner.lock();
129 inner.queue_event(DocumentEvent::LongOperationFinished {
130 operation_id: op_id,
131 success: false,
132 error: Some("cancelled".into()),
133 });
134 }
135 });
136
137 let failed_tok =
139 locked
140 .event_client
141 .subscribe(Origin::LongOperation(LOE::Failed), move |event| {
142 if let Some(inner) = weak.upgrade() {
143 let (op_id, error) = parse_failed_data(&event.data);
144 let mut inner = inner.lock();
145 inner.queue_event(DocumentEvent::LongOperationFinished {
146 operation_id: op_id,
147 success: false,
148 error: Some(error),
149 });
150 }
151 });
152
153 locked.long_op_subscriptions.extend([
154 progress_tok,
155 completed_tok,
156 cancelled_tok,
157 failed_tok,
158 ]);
159 }
160
161 pub fn set_plain_text(&self, text: &str) -> Result<()> {
165 let queued = {
166 let mut inner = self.inner.lock();
167 let dto = frontend::document_io::ImportPlainTextDto {
168 plain_text: text.into(),
169 };
170 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
171 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
172 inner.invalidate_text_cache();
173 inner.rehighlight_all();
174 inner.queue_event(DocumentEvent::DocumentReset);
175 inner.check_block_count_changed();
176 inner.reset_cached_child_order();
177 inner.queue_event(DocumentEvent::UndoRedoChanged {
178 can_undo: false,
179 can_redo: false,
180 });
181 inner.take_queued_events()
182 };
183 crate::inner::dispatch_queued_events(queued);
184 Ok(())
185 }
186
187 pub fn to_plain_text(&self) -> Result<String> {
189 let mut inner = self.inner.lock();
190 Ok(inner.plain_text()?.to_string())
191 }
192
193 pub fn set_markdown(&self, markdown: &str) -> Result<Operation<MarkdownImportResult>> {
197 let mut inner = self.inner.lock();
198 inner.invalidate_text_cache();
199 let dto = frontend::document_io::ImportMarkdownDto {
200 markdown_text: markdown.into(),
201 };
202 let op_id = document_io_commands::import_markdown(&inner.ctx, &dto)?;
203 Ok(Operation::new(
204 op_id,
205 &inner.ctx,
206 Box::new(|ctx, id| {
207 document_io_commands::get_import_markdown_result(ctx, id)
208 .ok()
209 .flatten()
210 .map(|r| {
211 Ok(MarkdownImportResult {
212 block_count: to_usize(r.block_count),
213 })
214 })
215 }),
216 ))
217 }
218
219 pub fn to_markdown(&self) -> Result<String> {
221 let inner = self.inner.lock();
222 let dto = document_io_commands::export_markdown(&inner.ctx)?;
223 Ok(dto.markdown_text)
224 }
225
226 pub fn set_html(&self, html: &str) -> Result<Operation<HtmlImportResult>> {
230 let mut inner = self.inner.lock();
231 inner.invalidate_text_cache();
232 let dto = frontend::document_io::ImportHtmlDto {
233 html_text: html.into(),
234 };
235 let op_id = document_io_commands::import_html(&inner.ctx, &dto)?;
236 Ok(Operation::new(
237 op_id,
238 &inner.ctx,
239 Box::new(|ctx, id| {
240 document_io_commands::get_import_html_result(ctx, id)
241 .ok()
242 .flatten()
243 .map(|r| {
244 Ok(HtmlImportResult {
245 block_count: to_usize(r.block_count),
246 })
247 })
248 }),
249 ))
250 }
251
252 pub fn to_html(&self) -> Result<String> {
254 let inner = self.inner.lock();
255 let dto = document_io_commands::export_html(&inner.ctx)?;
256 Ok(dto.html_text)
257 }
258
259 pub fn to_latex(&self, document_class: &str, include_preamble: bool) -> Result<String> {
261 let inner = self.inner.lock();
262 let dto = frontend::document_io::ExportLatexDto {
263 document_class: document_class.into(),
264 include_preamble,
265 };
266 let result = document_io_commands::export_latex(&inner.ctx, &dto)?;
267 Ok(result.latex_text)
268 }
269
270 pub fn to_docx(&self, output_path: &str) -> Result<Operation<DocxExportResult>> {
274 let inner = self.inner.lock();
275 let dto = frontend::document_io::ExportDocxDto {
276 output_path: output_path.into(),
277 };
278 let op_id = document_io_commands::export_docx(&inner.ctx, &dto)?;
279 Ok(Operation::new(
280 op_id,
281 &inner.ctx,
282 Box::new(|ctx, id| {
283 document_io_commands::get_export_docx_result(ctx, id)
284 .ok()
285 .flatten()
286 .map(|r| {
287 Ok(DocxExportResult {
288 file_path: r.file_path,
289 paragraph_count: to_usize(r.paragraph_count),
290 })
291 })
292 }),
293 ))
294 }
295
296 pub fn clear(&self) -> Result<()> {
298 let queued = {
299 let mut inner = self.inner.lock();
300 let dto = frontend::document_io::ImportPlainTextDto {
301 plain_text: String::new(),
302 };
303 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
304 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
305 inner.invalidate_text_cache();
306 inner.rehighlight_all();
307 inner.queue_event(DocumentEvent::DocumentReset);
308 inner.check_block_count_changed();
309 inner.reset_cached_child_order();
310 inner.queue_event(DocumentEvent::UndoRedoChanged {
311 can_undo: false,
312 can_redo: false,
313 });
314 inner.take_queued_events()
315 };
316 crate::inner::dispatch_queued_events(queued);
317 Ok(())
318 }
319
320 pub fn cursor(&self) -> TextCursor {
324 self.cursor_at(0)
325 }
326
327 pub fn cursor_at(&self, position: usize) -> TextCursor {
333 let data = {
334 let mut inner = self.inner.lock();
335 inner.register_cursor(position)
336 };
337 let cursor = TextCursor {
338 doc: self.inner.clone(),
339 data,
340 };
341 cursor.snap_position_to_grapheme_boundary();
342 cursor
343 }
344
345 pub fn stats(&self) -> DocumentStats {
349 let inner = self.inner.lock();
350 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
351 .expect("get_document_stats should not fail");
352 DocumentStats::from(&dto)
353 }
354
355 pub fn character_count(&self) -> usize {
357 let inner = self.inner.lock();
358 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
359 .expect("get_document_stats should not fail");
360 to_usize(dto.character_count)
361 }
362
363 pub fn block_count(&self) -> usize {
365 let inner = self.inner.lock();
366 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
367 .expect("get_document_stats should not fail");
368 to_usize(dto.block_count)
369 }
370
371 pub fn is_empty(&self) -> bool {
373 self.character_count() == 0
374 }
375
376 pub fn text_at(&self, position: usize, length: usize) -> Result<String> {
378 let inner = self.inner.lock();
379 let dto = frontend::document_inspection::GetTextAtPositionDto {
380 position: to_i64(position),
381 length: to_i64(length),
382 };
383 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
384 Ok(result.text)
385 }
386
387 pub fn find_element_at_position(&self, position: usize) -> Option<(u64, usize, usize)> {
402 let block_info = self.block_at(position).ok()?;
403 let block_start = block_info.start;
404 let offset_in_block = position.checked_sub(block_start)?;
405 let block = crate::text_block::TextBlock {
406 doc: std::sync::Arc::clone(&self.inner),
407 block_id: block_info.block_id,
408 };
409 let frags = block.fragments();
410 let mut last_text: Option<(u64, usize, usize, usize)> = None; for frag in &frags {
416 match frag {
417 crate::flow::FragmentContent::Text {
418 offset,
419 length,
420 element_id,
421 ..
422 } => {
423 let frag_start = *offset;
424 let frag_end = frag_start + *length;
425 if offset_in_block >= frag_start && offset_in_block < frag_end {
426 let abs_start = block_start + frag_start;
427 let offset_within = offset_in_block - frag_start;
428 return Some((*element_id, abs_start, offset_within));
429 }
430 if offset_in_block == frag_end {
433 last_text =
434 Some((*element_id, block_start + frag_start, frag_start, *length));
435 }
436 }
437 crate::flow::FragmentContent::Image {
438 offset, element_id, ..
439 } => {
440 if offset_in_block == *offset {
441 return Some((*element_id, block_start + offset, 0));
442 }
443 }
444 }
445 }
446 last_text.map(|(id, abs_start, _, length)| (id, abs_start, length))
449 }
450
451 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
453 let inner = self.inner.lock();
454 let dto = frontend::document_inspection::GetBlockAtPositionDto {
455 position: to_i64(position),
456 };
457 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
458 Ok(BlockInfo::from(&result))
459 }
460
461 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
463 let inner = self.inner.lock();
464 let dto = frontend::document_inspection::GetBlockAtPositionDto {
465 position: to_i64(position),
466 };
467 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
468 let block_id = block_info.block_id;
469 let block_id = block_id as u64;
470 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
471 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
472 Ok(BlockFormat::from(&block_dto))
473 }
474
475 pub fn flow(&self) -> Vec<crate::flow::FlowElement> {
486 let inner = self.inner.lock();
487 let main_frame_id = get_main_frame_id(&inner);
488 crate::text_frame::build_flow_elements(&inner, &self.inner, main_frame_id)
489 }
490
491 pub fn block_by_id(&self, block_id: usize) -> Option<crate::text_block::TextBlock> {
496 let inner = self.inner.lock();
497 let exists = frontend::commands::block_commands::get_block(&inner.ctx, &(block_id as u64))
498 .ok()
499 .flatten()
500 .is_some();
501
502 if exists {
503 Some(crate::text_block::TextBlock {
504 doc: self.inner.clone(),
505 block_id,
506 })
507 } else {
508 None
509 }
510 }
511
512 pub fn snapshot_block_at_position(
518 &self,
519 position: usize,
520 ) -> Option<crate::flow::BlockSnapshot> {
521 self.snapshot_block_at_position_impl(position, true)
522 }
523
524 pub fn snapshot_block_at_position_without_highlights(
529 &self,
530 position: usize,
531 ) -> Option<crate::flow::BlockSnapshot> {
532 self.snapshot_block_at_position_impl(position, false)
533 }
534
535 fn snapshot_block_at_position_impl(
536 &self,
537 position: usize,
538 apply_highlights: bool,
539 ) -> Option<crate::flow::BlockSnapshot> {
540 let inner = self.inner.lock();
541 let effective_kind = if apply_highlights {
542 inner.highlight_kind
543 } else {
544 crate::highlight::HighlighterKind::None
545 };
546 let main_frame_id = get_main_frame_id(&inner);
547 let store = inner.ctx.db_context.get_store();
548
549 if common::database::rope_helpers::rope_positions_match_flow(store)
557 && let Some((block_id, _, _)) =
558 common::database::rope_helpers::find_block_at_char_position(store, position as i64)
559 {
560 return crate::text_block::build_block_snapshot(&inner, block_id, effective_kind);
561 }
562
563 let ordered_block_ids = collect_frame_block_ids(&inner, main_frame_id)?;
565
566 let pos = position as i64;
568 let mut running_pos: i64 = 0;
569 for &block_id in &ordered_block_ids {
570 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
571 .ok()
572 .flatten()?;
573 let entity: common::entities::Block = block_dto.clone().into();
574 let block_end =
575 running_pos + common::database::rope_helpers::block_char_length(&entity, store);
576 if pos >= running_pos && pos <= block_end {
577 return crate::text_block::build_block_snapshot_with_position(
578 &inner,
579 block_id,
580 Some(running_pos as usize),
581 effective_kind,
582 );
583 }
584 running_pos = block_end + 1;
585 }
586
587 if let Some(&last_id) = ordered_block_ids.last() {
589 return crate::text_block::build_block_snapshot(&inner, last_id, effective_kind);
590 }
591 None
592 }
593
594 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
597 let inner = self.inner.lock();
598 let dto = frontend::document_inspection::GetBlockAtPositionDto {
599 position: to_i64(position),
600 };
601 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
602 Some(crate::text_block::TextBlock {
603 doc: self.inner.clone(),
604 block_id: result.block_id as usize,
605 })
606 }
607
608 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
617 let inner = self.inner.lock();
618 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
619 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
620 let store = inner.ctx.db_context.get_store();
621 crate::inner::refresh_block_positions(&mut sorted, store);
622 sorted.sort_by_key(|b| b.document_position);
623
624 sorted
625 .get(block_number)
626 .map(|b| crate::text_block::TextBlock {
627 doc: self.inner.clone(),
628 block_id: b.id as usize,
629 })
630 }
631
632 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
638 let inner = self.inner.lock();
639 let all_blocks =
640 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
641 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
642 let store = inner.ctx.db_context.get_store();
643 crate::inner::refresh_block_positions(&mut sorted, store);
644 sorted.sort_by_key(|b| b.document_position);
645 sorted
646 .iter()
647 .map(|b| crate::text_block::TextBlock {
648 doc: self.inner.clone(),
649 block_id: b.id as usize,
650 })
651 .collect()
652 }
653
654 pub fn blocks_in_range(
661 &self,
662 position: usize,
663 length: usize,
664 ) -> Vec<crate::text_block::TextBlock> {
665 let inner = self.inner.lock();
666 let all_blocks =
667 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
668 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
669 let store = inner.ctx.db_context.get_store();
670 crate::inner::refresh_block_positions(&mut sorted, store);
671 sorted.sort_by_key(|b| b.document_position);
672
673 let range_start = position;
674 let range_end = position + length;
675 sorted
676 .iter()
677 .filter(|b| {
678 let block_start = b.document_position.max(0) as usize;
679 let entity: common::entities::Block = (*b).clone().into();
680 let block_end = block_start
681 + common::database::rope_helpers::block_char_length(&entity, store).max(0)
682 as usize;
683 if length == 0 {
685 range_start >= block_start && range_start < block_end
687 } else {
688 block_start < range_end && block_end > range_start
689 }
690 })
691 .map(|b| crate::text_block::TextBlock {
692 doc: self.inner.clone(),
693 block_id: b.id as usize,
694 })
695 .collect()
696 }
697
698 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
703 let inner = self.inner.lock();
704 let main_frame_id = get_main_frame_id(&inner);
705 let elements =
706 crate::text_frame::build_flow_snapshot(&inner, main_frame_id, inner.highlight_kind);
707 crate::flow::FlowSnapshot { elements }
708 }
709
710 pub fn snapshot_flow_without_highlights(&self) -> crate::flow::FlowSnapshot {
720 let inner = self.inner.lock();
721 let main_frame_id = get_main_frame_id(&inner);
722 let elements = crate::text_frame::build_flow_snapshot(
723 &inner,
724 main_frame_id,
725 crate::highlight::HighlighterKind::None,
726 );
727 crate::flow::FlowSnapshot { elements }
728 }
729
730 pub fn find(
734 &self,
735 query: &str,
736 from: usize,
737 options: &FindOptions,
738 ) -> Result<Option<FindMatch>> {
739 let inner = self.inner.lock();
740 let dto = options.to_find_text_dto(query, from);
741 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
742 Ok(convert::find_result_to_match(&result))
743 }
744
745 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
747 let inner = self.inner.lock();
748 let dto = options.to_find_all_dto(query);
749 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
750 Ok(convert::find_all_to_matches(&result))
751 }
752
753 pub fn replace_text(
755 &self,
756 query: &str,
757 replacement: &str,
758 replace_all: bool,
759 options: &FindOptions,
760 ) -> Result<usize> {
761 let (count, queued) = {
762 let mut inner = self.inner.lock();
763 let dto = options.to_replace_dto(query, replacement, replace_all);
764 let result =
765 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
766 let count = to_usize(result.replacements_count);
767 inner.invalidate_text_cache();
768 if count > 0 {
769 inner.modified = true;
770 inner.rehighlight_all();
771 inner.queue_event(DocumentEvent::ContentsChanged {
776 position: 0,
777 chars_removed: 0,
778 chars_added: 0,
779 blocks_affected: count,
780 });
781 inner.check_block_count_changed();
782 inner.check_flow_changed();
783 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
784 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
785 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
786 }
787 (count, inner.take_queued_events())
788 };
789 crate::inner::dispatch_queued_events(queued);
790 Ok(count)
791 }
792
793 pub fn add_resource(
797 &self,
798 resource_type: ResourceType,
799 name: &str,
800 mime_type: &str,
801 data: &[u8],
802 ) -> Result<()> {
803 let mut inner = self.inner.lock();
804 let dto = frontend::resource::dtos::CreateResourceDto {
805 created_at: Default::default(),
806 updated_at: Default::default(),
807 resource_type,
808 name: name.into(),
809 url: String::new(),
810 mime_type: mime_type.into(),
811 data_base64: BASE64.encode(data),
812 };
813 let created = resource_commands::create_resource(
814 &inner.ctx,
815 Some(inner.stack_id),
816 &dto,
817 inner.document_id,
818 -1,
819 )?;
820 inner.resource_cache.insert(name.to_string(), created.id);
821 Ok(())
822 }
823
824 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
828 let mut inner = self.inner.lock();
829
830 if let Some(&id) = inner.resource_cache.get(name) {
832 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
833 let bytes = BASE64.decode(&r.data_base64)?;
834 return Ok(Some(bytes));
835 }
836 inner.resource_cache.remove(name);
838 }
839
840 let all = resource_commands::get_all_resource(&inner.ctx)?;
842 for r in &all {
843 if r.name == name {
844 inner.resource_cache.insert(name.to_string(), r.id);
845 let bytes = BASE64.decode(&r.data_base64)?;
846 return Ok(Some(bytes));
847 }
848 }
849 Ok(None)
850 }
851
852 pub fn undo(&self) -> Result<()> {
856 let queued = {
857 let mut inner = self.inner.lock();
858 let before = capture_block_state(&inner);
859 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
860 inner.invalidate_text_cache();
861 result?;
862 inner.rehighlight_all();
863 emit_undo_redo_change_events(&mut inner, &before);
864 inner.check_block_count_changed();
865 inner.check_flow_changed();
866 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
867 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
868 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
869 inner.take_queued_events()
870 };
871 crate::inner::dispatch_queued_events(queued);
872 Ok(())
873 }
874
875 pub fn redo(&self) -> Result<()> {
877 let queued = {
878 let mut inner = self.inner.lock();
879 let before = capture_block_state(&inner);
880 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
881 inner.invalidate_text_cache();
882 result?;
883 inner.rehighlight_all();
884 emit_undo_redo_change_events(&mut inner, &before);
885 inner.check_block_count_changed();
886 inner.check_flow_changed();
887 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
888 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
889 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
890 inner.take_queued_events()
891 };
892 crate::inner::dispatch_queued_events(queued);
893 Ok(())
894 }
895
896 pub fn can_undo(&self) -> bool {
898 let inner = self.inner.lock();
899 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
900 }
901
902 pub fn can_redo(&self) -> bool {
904 let inner = self.inner.lock();
905 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
906 }
907
908 pub fn clear_undo_redo(&self) {
910 let inner = self.inner.lock();
911 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
912 }
913
914 pub fn is_modified(&self) -> bool {
918 self.inner.lock().modified
919 }
920
921 pub fn set_modified(&self, modified: bool) {
923 let queued = {
924 let mut inner = self.inner.lock();
925 if inner.modified != modified {
926 inner.modified = modified;
927 inner.queue_event(DocumentEvent::ModificationChanged(modified));
928 }
929 inner.take_queued_events()
930 };
931 crate::inner::dispatch_queued_events(queued);
932 }
933
934 pub fn title(&self) -> String {
938 let inner = self.inner.lock();
939 document_commands::get_document(&inner.ctx, &inner.document_id)
940 .ok()
941 .flatten()
942 .map(|d| d.title)
943 .unwrap_or_default()
944 }
945
946 pub fn set_title(&self, title: &str) -> Result<()> {
948 let inner = self.inner.lock();
949 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
950 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
951 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
952 update.title = title.into();
953 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
954 Ok(())
955 }
956
957 pub fn text_direction(&self) -> TextDirection {
959 let inner = self.inner.lock();
960 document_commands::get_document(&inner.ctx, &inner.document_id)
961 .ok()
962 .flatten()
963 .map(|d| d.text_direction)
964 .unwrap_or(TextDirection::LeftToRight)
965 }
966
967 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
969 let inner = self.inner.lock();
970 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
971 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
972 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
973 update.text_direction = direction;
974 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
975 Ok(())
976 }
977
978 pub fn default_wrap_mode(&self) -> WrapMode {
980 let inner = self.inner.lock();
981 document_commands::get_document(&inner.ctx, &inner.document_id)
982 .ok()
983 .flatten()
984 .map(|d| d.default_wrap_mode)
985 .unwrap_or(WrapMode::WordWrap)
986 }
987
988 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
990 let inner = self.inner.lock();
991 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
992 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
993 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
994 update.default_wrap_mode = mode;
995 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
996 Ok(())
997 }
998
999 pub fn on_change<F>(&self, callback: F) -> Subscription
1018 where
1019 F: Fn(DocumentEvent) + Send + Sync + 'static,
1020 {
1021 let mut inner = self.inner.lock();
1022 events::subscribe_inner(&mut inner, callback)
1023 }
1024
1025 pub fn poll_events(&self) -> Vec<DocumentEvent> {
1031 let mut inner = self.inner.lock();
1032 inner.drain_poll_events()
1033 }
1034
1035 pub fn set_syntax_highlighter(&self, highlighter: Option<Arc<dyn crate::SyntaxHighlighter>>) {
1043 let queued = {
1044 let mut inner = self.inner.lock();
1045 let prev_kind = inner.highlight_kind;
1046 match highlighter {
1047 Some(hl) => {
1048 inner.highlight = Some(crate::highlight::HighlightData {
1049 highlighter: hl,
1050 blocks: std::collections::HashMap::new(),
1051 });
1052 inner.rehighlight_all(); }
1054 None => {
1055 inner.highlight = None;
1056 inner.recompute_highlight_kind(); }
1058 }
1059 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1060 inner.take_queued_events()
1061 };
1062 crate::inner::dispatch_queued_events(queued);
1063 }
1064
1065 pub fn rehighlight(&self) {
1070 let queued = {
1071 let mut inner = self.inner.lock();
1072 let prev_kind = inner.highlight_kind;
1073 inner.rehighlight_all();
1074 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1075 inner.take_queued_events()
1076 };
1077 crate::inner::dispatch_queued_events(queued);
1078 }
1079
1080 pub fn rehighlight_block(&self, block_id: usize) {
1083 let queued = {
1084 let mut inner = self.inner.lock();
1085 let prev_kind = inner.highlight_kind;
1086 inner.rehighlight_from_block(block_id);
1087 Self::queue_highlight_changed(&mut inner, 0, 0, prev_kind);
1088 inner.take_queued_events()
1089 };
1090 crate::inner::dispatch_queued_events(queued);
1091 }
1092
1093 fn queue_highlight_changed(
1113 inner: &mut TextDocumentInner,
1114 position: usize,
1115 length: usize,
1116 prev_kind: crate::highlight::HighlighterKind,
1117 ) {
1118 use crate::highlight::HighlighterKind::{Metric, None as KNone, PaintOnly};
1119 let new_kind = inner.highlight_kind;
1120 let event = match (prev_kind, new_kind) {
1121 (KNone, KNone) => return,
1123 (PaintOnly, PaintOnly) | (KNone, PaintOnly) | (PaintOnly, KNone) => {
1125 DocumentEvent::HighlightPaintChanged { position, length }
1126 }
1127 (KNone, Metric)
1129 | (Metric, Metric)
1130 | (Metric, PaintOnly)
1131 | (Metric, KNone)
1132 | (PaintOnly, Metric) => DocumentEvent::FormatChanged {
1133 position,
1134 length,
1135 kind: crate::flow::FormatChangeKind::Character,
1136 },
1137 };
1138 inner.queue_event(event);
1139 }
1140}
1141
1142impl Default for TextDocument {
1143 fn default() -> Self {
1144 Self::new()
1145 }
1146}
1147
1148struct UndoBlockState {
1152 id: u64,
1153 position: i64,
1154 text_length: i64,
1155 plain_text: String,
1156 format: BlockFormat,
1157}
1158
1159fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
1161 let mut all_blocks =
1162 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
1163 let store = inner.ctx.db_context.get_store();
1164 crate::inner::refresh_block_positions(&mut all_blocks, store);
1165 let mut states: Vec<UndoBlockState> = all_blocks
1166 .into_iter()
1167 .map(|b| {
1168 let format = BlockFormat::from(&b);
1169 let entity: common::entities::Block = b.clone().into();
1170 let plain_text =
1171 common::database::rope_helpers::block_content_via_store(&entity, store);
1172 let text_length = common::database::rope_helpers::block_char_length(&entity, store);
1173 UndoBlockState {
1174 id: b.id,
1175 position: b.document_position,
1176 text_length,
1177 plain_text,
1178 format,
1179 }
1180 })
1181 .collect();
1182 states.sort_by_key(|s| s.position);
1183 states
1184}
1185
1186fn build_doc_text(states: &[UndoBlockState]) -> String {
1188 states
1189 .iter()
1190 .map(|s| s.plain_text.as_str())
1191 .collect::<Vec<_>>()
1192 .join("\n")
1193}
1194
1195fn compute_text_edit(before: &str, after: &str) -> (usize, usize, usize) {
1198 let before_chars: Vec<char> = before.chars().collect();
1199 let after_chars: Vec<char> = after.chars().collect();
1200
1201 let prefix_len = before_chars
1203 .iter()
1204 .zip(after_chars.iter())
1205 .take_while(|(a, b)| a == b)
1206 .count();
1207
1208 let before_remaining = before_chars.len() - prefix_len;
1210 let after_remaining = after_chars.len() - prefix_len;
1211 let suffix_len = before_chars
1212 .iter()
1213 .rev()
1214 .zip(after_chars.iter().rev())
1215 .take(before_remaining.min(after_remaining))
1216 .take_while(|(a, b)| a == b)
1217 .count();
1218
1219 let removed = before_remaining - suffix_len;
1220 let added = after_remaining - suffix_len;
1221
1222 (prefix_len, removed, added)
1223}
1224
1225fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
1228 let after = capture_block_state(inner);
1229
1230 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
1232 before.iter().map(|s| (s.id, s)).collect();
1233 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
1234 after.iter().map(|s| (s.id, s)).collect();
1235
1236 let mut content_changed = false;
1238 let mut earliest_pos: Option<usize> = None;
1239 let mut old_end: usize = 0;
1240 let mut new_end: usize = 0;
1241 let mut blocks_affected: usize = 0;
1242
1243 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
1247 if let Some(before_state) = before_map.get(&after_state.id) {
1248 let text_changed = before_state.plain_text != after_state.plain_text
1249 || before_state.text_length != after_state.text_length;
1250 let format_changed = before_state.format != after_state.format;
1251
1252 if text_changed {
1253 content_changed = true;
1254 blocks_affected += 1;
1255 let pos = after_state.position.max(0) as usize;
1256 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1257 old_end = old_end.max(
1258 before_state.position.max(0) as usize
1259 + before_state.text_length.max(0) as usize,
1260 );
1261 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1262 } else if format_changed {
1263 let pos = after_state.position.max(0) as usize;
1264 let len = after_state.text_length.max(0) as usize;
1265 format_only_changes.push((pos, len));
1266 }
1267 } else {
1268 content_changed = true;
1270 blocks_affected += 1;
1271 let pos = after_state.position.max(0) as usize;
1272 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1273 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1274 }
1275 }
1276
1277 for before_state in before {
1279 if !after_map.contains_key(&before_state.id) {
1280 content_changed = true;
1281 blocks_affected += 1;
1282 let pos = before_state.position.max(0) as usize;
1283 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1284 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
1285 }
1286 }
1287
1288 if content_changed {
1289 let position = earliest_pos.unwrap_or(0);
1290 let chars_removed = old_end.saturating_sub(position);
1291 let chars_added = new_end.saturating_sub(position);
1292
1293 let before_text = build_doc_text(before);
1296 let after_text = build_doc_text(&after);
1297 let (edit_offset, precise_removed, precise_added) =
1298 compute_text_edit(&before_text, &after_text);
1299 if precise_removed > 0 || precise_added > 0 {
1300 inner.adjust_cursors(edit_offset, precise_removed, precise_added);
1301 }
1302
1303 inner.queue_event(DocumentEvent::ContentsChanged {
1304 position,
1305 chars_removed,
1306 chars_added,
1307 blocks_affected,
1308 });
1309 }
1310
1311 for (position, length) in format_only_changes {
1313 inner.queue_event(DocumentEvent::FormatChanged {
1314 position,
1315 length,
1316 kind: FormatChangeKind::Block,
1317 });
1318 }
1319}
1320
1321fn collect_frame_block_ids(
1327 inner: &TextDocumentInner,
1328 frame_id: frontend::common::types::EntityId,
1329) -> Option<Vec<u64>> {
1330 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
1331 .ok()
1332 .flatten()?;
1333
1334 if !frame_dto.child_order.is_empty() {
1335 let mut block_ids = Vec::new();
1336 for &entry in &frame_dto.child_order {
1337 if entry > 0 {
1338 block_ids.push(entry as u64);
1339 } else if entry < 0 {
1340 let sub_frame_id = (-entry) as u64;
1341 let sub_frame = frame_commands::get_frame(&inner.ctx, &sub_frame_id)
1342 .ok()
1343 .flatten();
1344 if let Some(ref sf) = sub_frame {
1345 if let Some(table_id) = sf.table {
1346 if let Some(table_dto) = table_commands::get_table(&inner.ctx, &table_id)
1349 .ok()
1350 .flatten()
1351 {
1352 let mut cell_dtos: Vec<_> = table_dto
1353 .cells
1354 .iter()
1355 .filter_map(|&cid| {
1356 table_cell_commands::get_table_cell(&inner.ctx, &cid)
1357 .ok()
1358 .flatten()
1359 })
1360 .collect();
1361 cell_dtos
1362 .sort_by(|a, b| a.row.cmp(&b.row).then(a.column.cmp(&b.column)));
1363 for cell_dto in &cell_dtos {
1364 if let Some(cf_id) = cell_dto.cell_frame
1365 && let Some(cf_ids) = collect_frame_block_ids(inner, cf_id)
1366 {
1367 block_ids.extend(cf_ids);
1368 }
1369 }
1370 }
1371 } else if let Some(sub_ids) = collect_frame_block_ids(inner, sub_frame_id) {
1372 block_ids.extend(sub_ids);
1373 }
1374 }
1375 }
1376 }
1377 Some(block_ids)
1378 } else {
1379 Some(frame_dto.blocks.to_vec())
1380 }
1381}
1382
1383pub(crate) fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
1384 let frames = frontend::commands::document_commands::get_document_relationship(
1386 &inner.ctx,
1387 &inner.document_id,
1388 &frontend::document::dtos::DocumentRelationshipField::Frames,
1389 )
1390 .unwrap_or_default();
1391
1392 frames.first().copied().unwrap_or(0)
1393}
1394
1395fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
1399 let Some(json) = data else {
1400 return (String::new(), 0.0, String::new());
1401 };
1402 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1403 let id = v["id"].as_str().unwrap_or_default().to_string();
1404 let pct = v["percentage"].as_f64().unwrap_or(0.0);
1405 let msg = v["message"].as_str().unwrap_or_default().to_string();
1406 (id, pct, msg)
1407}
1408
1409fn parse_id_data(data: &Option<String>) -> String {
1411 let Some(json) = data else {
1412 return String::new();
1413 };
1414 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1415 v["id"].as_str().unwrap_or_default().to_string()
1416}
1417
1418fn parse_failed_data(data: &Option<String>) -> (String, String) {
1420 let Some(json) = data else {
1421 return (String::new(), "unknown error".into());
1422 };
1423 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1424 let id = v["id"].as_str().unwrap_or_default().to_string();
1425 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
1426 (id, error)
1427}