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 document_commands, document_inspection_commands, document_io_commands,
14 document_search_commands, resource_commands, undo_redo_commands,
15};
16
17use crate::convert::{self, to_i64, to_usize};
18use crate::cursor::TextCursor;
19use crate::events::{self, DocumentEvent, Subscription};
20use crate::flow::FormatChangeKind;
21use crate::inner::TextDocumentInner;
22use crate::operation::{DocxExportResult, HtmlImportResult, MarkdownImportResult, Operation};
23use crate::{BlockFormat, BlockInfo, DocumentStats, FindMatch, FindOptions};
24
25#[derive(Clone)]
36pub struct TextDocument {
37 pub(crate) inner: Arc<Mutex<TextDocumentInner>>,
38}
39
40impl TextDocument {
41 pub fn new() -> Self {
50 Self::try_new().expect("failed to initialize document")
51 }
52
53 pub fn try_new() -> Result<Self> {
55 let ctx = frontend::AppContext::new();
56 let doc_inner = TextDocumentInner::initialize(ctx)?;
57 let inner = Arc::new(Mutex::new(doc_inner));
58
59 Self::subscribe_long_operation_events(&inner);
61
62 Ok(Self { inner })
63 }
64
65 fn subscribe_long_operation_events(inner: &Arc<Mutex<TextDocumentInner>>) {
67 use frontend::common::event::{LongOperationEvent as LOE, Origin};
68
69 let weak = Arc::downgrade(inner);
70 {
71 let locked = inner.lock();
72 let w = weak.clone();
74 locked
75 .event_client
76 .subscribe(Origin::LongOperation(LOE::Progress), move |event| {
77 if let Some(inner) = w.upgrade() {
78 let (op_id, percent, message) = parse_progress_data(&event.data);
79 let mut inner = inner.lock();
80 inner.queue_event(DocumentEvent::LongOperationProgress {
81 operation_id: op_id,
82 percent,
83 message,
84 });
85 }
86 });
87
88 let w = weak.clone();
90 locked
91 .event_client
92 .subscribe(Origin::LongOperation(LOE::Completed), move |event| {
93 if let Some(inner) = w.upgrade() {
94 let op_id = parse_id_data(&event.data);
95 let mut inner = inner.lock();
96 inner.queue_event(DocumentEvent::DocumentReset);
97 inner.check_block_count_changed();
98 inner.reset_cached_child_order();
99 inner.queue_event(DocumentEvent::LongOperationFinished {
100 operation_id: op_id,
101 success: true,
102 error: None,
103 });
104 }
105 });
106
107 let w = weak.clone();
109 locked
110 .event_client
111 .subscribe(Origin::LongOperation(LOE::Cancelled), move |event| {
112 if let Some(inner) = w.upgrade() {
113 let op_id = parse_id_data(&event.data);
114 let mut inner = inner.lock();
115 inner.queue_event(DocumentEvent::LongOperationFinished {
116 operation_id: op_id,
117 success: false,
118 error: Some("cancelled".into()),
119 });
120 }
121 });
122
123 locked
125 .event_client
126 .subscribe(Origin::LongOperation(LOE::Failed), move |event| {
127 if let Some(inner) = weak.upgrade() {
128 let (op_id, error) = parse_failed_data(&event.data);
129 let mut inner = inner.lock();
130 inner.queue_event(DocumentEvent::LongOperationFinished {
131 operation_id: op_id,
132 success: false,
133 error: Some(error),
134 });
135 }
136 });
137 }
138 }
139
140 pub fn set_plain_text(&self, text: &str) -> Result<()> {
144 let queued = {
145 let mut inner = self.inner.lock();
146 let dto = frontend::document_io::ImportPlainTextDto {
147 plain_text: text.into(),
148 };
149 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
150 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
151 inner.invalidate_text_cache();
152 inner.rehighlight_all();
153 inner.queue_event(DocumentEvent::DocumentReset);
154 inner.check_block_count_changed();
155 inner.reset_cached_child_order();
156 inner.queue_event(DocumentEvent::UndoRedoChanged {
157 can_undo: false,
158 can_redo: false,
159 });
160 inner.take_queued_events()
161 };
162 crate::inner::dispatch_queued_events(queued);
163 Ok(())
164 }
165
166 pub fn to_plain_text(&self) -> Result<String> {
168 let mut inner = self.inner.lock();
169 Ok(inner.plain_text()?.to_string())
170 }
171
172 pub fn set_markdown(&self, markdown: &str) -> Result<Operation<MarkdownImportResult>> {
176 let mut inner = self.inner.lock();
177 inner.invalidate_text_cache();
178 let dto = frontend::document_io::ImportMarkdownDto {
179 markdown_text: markdown.into(),
180 };
181 let op_id = document_io_commands::import_markdown(&inner.ctx, &dto)?;
182 Ok(Operation::new(
183 op_id,
184 &inner.ctx,
185 Box::new(|ctx, id| {
186 document_io_commands::get_import_markdown_result(ctx, id)
187 .ok()
188 .flatten()
189 .map(|r| {
190 Ok(MarkdownImportResult {
191 block_count: to_usize(r.block_count),
192 })
193 })
194 }),
195 ))
196 }
197
198 pub fn to_markdown(&self) -> Result<String> {
200 let inner = self.inner.lock();
201 let dto = document_io_commands::export_markdown(&inner.ctx)?;
202 Ok(dto.markdown_text)
203 }
204
205 pub fn set_html(&self, html: &str) -> Result<Operation<HtmlImportResult>> {
209 let mut inner = self.inner.lock();
210 inner.invalidate_text_cache();
211 let dto = frontend::document_io::ImportHtmlDto {
212 html_text: html.into(),
213 };
214 let op_id = document_io_commands::import_html(&inner.ctx, &dto)?;
215 Ok(Operation::new(
216 op_id,
217 &inner.ctx,
218 Box::new(|ctx, id| {
219 document_io_commands::get_import_html_result(ctx, id)
220 .ok()
221 .flatten()
222 .map(|r| {
223 Ok(HtmlImportResult {
224 block_count: to_usize(r.block_count),
225 })
226 })
227 }),
228 ))
229 }
230
231 pub fn to_html(&self) -> Result<String> {
233 let inner = self.inner.lock();
234 let dto = document_io_commands::export_html(&inner.ctx)?;
235 Ok(dto.html_text)
236 }
237
238 pub fn to_latex(&self, document_class: &str, include_preamble: bool) -> Result<String> {
240 let inner = self.inner.lock();
241 let dto = frontend::document_io::ExportLatexDto {
242 document_class: document_class.into(),
243 include_preamble,
244 };
245 let result = document_io_commands::export_latex(&inner.ctx, &dto)?;
246 Ok(result.latex_text)
247 }
248
249 pub fn to_docx(&self, output_path: &str) -> Result<Operation<DocxExportResult>> {
253 let inner = self.inner.lock();
254 let dto = frontend::document_io::ExportDocxDto {
255 output_path: output_path.into(),
256 };
257 let op_id = document_io_commands::export_docx(&inner.ctx, &dto)?;
258 Ok(Operation::new(
259 op_id,
260 &inner.ctx,
261 Box::new(|ctx, id| {
262 document_io_commands::get_export_docx_result(ctx, id)
263 .ok()
264 .flatten()
265 .map(|r| {
266 Ok(DocxExportResult {
267 file_path: r.file_path,
268 paragraph_count: to_usize(r.paragraph_count),
269 })
270 })
271 }),
272 ))
273 }
274
275 pub fn clear(&self) -> Result<()> {
277 let queued = {
278 let mut inner = self.inner.lock();
279 let dto = frontend::document_io::ImportPlainTextDto {
280 plain_text: String::new(),
281 };
282 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
283 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
284 inner.invalidate_text_cache();
285 inner.rehighlight_all();
286 inner.queue_event(DocumentEvent::DocumentReset);
287 inner.check_block_count_changed();
288 inner.reset_cached_child_order();
289 inner.queue_event(DocumentEvent::UndoRedoChanged {
290 can_undo: false,
291 can_redo: false,
292 });
293 inner.take_queued_events()
294 };
295 crate::inner::dispatch_queued_events(queued);
296 Ok(())
297 }
298
299 pub fn cursor(&self) -> TextCursor {
303 self.cursor_at(0)
304 }
305
306 pub fn cursor_at(&self, position: usize) -> TextCursor {
308 let data = {
309 let mut inner = self.inner.lock();
310 inner.register_cursor(position)
311 };
312 TextCursor {
313 doc: self.inner.clone(),
314 data,
315 }
316 }
317
318 pub fn stats(&self) -> DocumentStats {
322 let inner = self.inner.lock();
323 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
324 .expect("get_document_stats should not fail");
325 DocumentStats::from(&dto)
326 }
327
328 pub fn character_count(&self) -> usize {
330 let inner = self.inner.lock();
331 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
332 .expect("get_document_stats should not fail");
333 to_usize(dto.character_count)
334 }
335
336 pub fn block_count(&self) -> usize {
338 let inner = self.inner.lock();
339 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
340 .expect("get_document_stats should not fail");
341 to_usize(dto.block_count)
342 }
343
344 pub fn is_empty(&self) -> bool {
346 self.character_count() == 0
347 }
348
349 pub fn text_at(&self, position: usize, length: usize) -> Result<String> {
351 let inner = self.inner.lock();
352 let dto = frontend::document_inspection::GetTextAtPositionDto {
353 position: to_i64(position),
354 length: to_i64(length),
355 };
356 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
357 Ok(result.text)
358 }
359
360 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
362 let inner = self.inner.lock();
363 let dto = frontend::document_inspection::GetBlockAtPositionDto {
364 position: to_i64(position),
365 };
366 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
367 Ok(BlockInfo::from(&result))
368 }
369
370 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
372 let inner = self.inner.lock();
373 let dto = frontend::document_inspection::GetBlockAtPositionDto {
374 position: to_i64(position),
375 };
376 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
377 let block_id = block_info.block_id;
378 let block_id = block_id as u64;
379 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
380 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
381 Ok(BlockFormat::from(&block_dto))
382 }
383
384 pub fn flow(&self) -> Vec<crate::flow::FlowElement> {
395 let inner = self.inner.lock();
396 let main_frame_id = get_main_frame_id(&inner);
397 crate::text_frame::build_flow_elements(&inner, &self.inner, main_frame_id)
398 }
399
400 pub fn block_by_id(&self, block_id: usize) -> Option<crate::text_block::TextBlock> {
405 let inner = self.inner.lock();
406 let exists = frontend::commands::block_commands::get_block(&inner.ctx, &(block_id as u64))
407 .ok()
408 .flatten()
409 .is_some();
410
411 if exists {
412 Some(crate::text_block::TextBlock {
413 doc: self.inner.clone(),
414 block_id,
415 })
416 } else {
417 None
418 }
419 }
420
421 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
424 let inner = self.inner.lock();
425 let dto = frontend::document_inspection::GetBlockAtPositionDto {
426 position: to_i64(position),
427 };
428 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
429 Some(crate::text_block::TextBlock {
430 doc: self.inner.clone(),
431 block_id: result.block_id as usize,
432 })
433 }
434
435 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
444 let inner = self.inner.lock();
445 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
446 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
447 sorted.sort_by_key(|b| b.document_position);
448
449 sorted
450 .get(block_number)
451 .map(|b| crate::text_block::TextBlock {
452 doc: self.inner.clone(),
453 block_id: b.id as usize,
454 })
455 }
456
457 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
463 let inner = self.inner.lock();
464 let all_blocks =
465 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
466 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
467 sorted.sort_by_key(|b| b.document_position);
468 sorted
469 .iter()
470 .map(|b| crate::text_block::TextBlock {
471 doc: self.inner.clone(),
472 block_id: b.id as usize,
473 })
474 .collect()
475 }
476
477 pub fn blocks_in_range(
484 &self,
485 position: usize,
486 length: usize,
487 ) -> Vec<crate::text_block::TextBlock> {
488 let inner = self.inner.lock();
489 let all_blocks =
490 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
491 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
492 sorted.sort_by_key(|b| b.document_position);
493
494 let range_start = position;
495 let range_end = position + length;
496
497 sorted
498 .iter()
499 .filter(|b| {
500 let block_start = b.document_position.max(0) as usize;
501 let block_end = block_start + b.text_length.max(0) as usize;
502 if length == 0 {
504 range_start >= block_start && range_start < block_end
506 } else {
507 block_start < range_end && block_end > range_start
508 }
509 })
510 .map(|b| crate::text_block::TextBlock {
511 doc: self.inner.clone(),
512 block_id: b.id as usize,
513 })
514 .collect()
515 }
516
517 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
522 let inner = self.inner.lock();
523 let main_frame_id = get_main_frame_id(&inner);
524 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id);
525 crate::flow::FlowSnapshot { elements }
526 }
527
528 pub fn find(
532 &self,
533 query: &str,
534 from: usize,
535 options: &FindOptions,
536 ) -> Result<Option<FindMatch>> {
537 let inner = self.inner.lock();
538 let dto = options.to_find_text_dto(query, from);
539 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
540 Ok(convert::find_result_to_match(&result))
541 }
542
543 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
545 let inner = self.inner.lock();
546 let dto = options.to_find_all_dto(query);
547 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
548 Ok(convert::find_all_to_matches(&result))
549 }
550
551 pub fn replace_text(
553 &self,
554 query: &str,
555 replacement: &str,
556 replace_all: bool,
557 options: &FindOptions,
558 ) -> Result<usize> {
559 let (count, queued) = {
560 let mut inner = self.inner.lock();
561 let dto = options.to_replace_dto(query, replacement, replace_all);
562 let result =
563 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
564 let count = to_usize(result.replacements_count);
565 inner.invalidate_text_cache();
566 if count > 0 {
567 inner.modified = true;
568 inner.rehighlight_all();
569 inner.queue_event(DocumentEvent::ContentsChanged {
574 position: 0,
575 chars_removed: 0,
576 chars_added: 0,
577 blocks_affected: count,
578 });
579 inner.check_block_count_changed();
580 inner.check_flow_changed();
581 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
582 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
583 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
584 }
585 (count, inner.take_queued_events())
586 };
587 crate::inner::dispatch_queued_events(queued);
588 Ok(count)
589 }
590
591 pub fn add_resource(
595 &self,
596 resource_type: ResourceType,
597 name: &str,
598 mime_type: &str,
599 data: &[u8],
600 ) -> Result<()> {
601 let mut inner = self.inner.lock();
602 let dto = frontend::resource::dtos::CreateResourceDto {
603 created_at: Default::default(),
604 updated_at: Default::default(),
605 resource_type,
606 name: name.into(),
607 url: String::new(),
608 mime_type: mime_type.into(),
609 data_base64: BASE64.encode(data),
610 };
611 let created = resource_commands::create_resource(
612 &inner.ctx,
613 Some(inner.stack_id),
614 &dto,
615 inner.document_id,
616 -1,
617 )?;
618 inner.resource_cache.insert(name.to_string(), created.id);
619 Ok(())
620 }
621
622 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
626 let mut inner = self.inner.lock();
627
628 if let Some(&id) = inner.resource_cache.get(name) {
630 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
631 let bytes = BASE64.decode(&r.data_base64)?;
632 return Ok(Some(bytes));
633 }
634 inner.resource_cache.remove(name);
636 }
637
638 let all = resource_commands::get_all_resource(&inner.ctx)?;
640 for r in &all {
641 if r.name == name {
642 inner.resource_cache.insert(name.to_string(), r.id);
643 let bytes = BASE64.decode(&r.data_base64)?;
644 return Ok(Some(bytes));
645 }
646 }
647 Ok(None)
648 }
649
650 pub fn undo(&self) -> Result<()> {
654 let queued = {
655 let mut inner = self.inner.lock();
656 let before = capture_block_state(&inner);
657 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
658 inner.invalidate_text_cache();
659 result?;
660 inner.rehighlight_all();
661 emit_undo_redo_change_events(&mut inner, &before);
662 inner.check_block_count_changed();
663 inner.check_flow_changed();
664 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
665 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
666 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
667 inner.take_queued_events()
668 };
669 crate::inner::dispatch_queued_events(queued);
670 Ok(())
671 }
672
673 pub fn redo(&self) -> Result<()> {
675 let queued = {
676 let mut inner = self.inner.lock();
677 let before = capture_block_state(&inner);
678 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
679 inner.invalidate_text_cache();
680 result?;
681 inner.rehighlight_all();
682 emit_undo_redo_change_events(&mut inner, &before);
683 inner.check_block_count_changed();
684 inner.check_flow_changed();
685 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
686 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
687 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
688 inner.take_queued_events()
689 };
690 crate::inner::dispatch_queued_events(queued);
691 Ok(())
692 }
693
694 pub fn can_undo(&self) -> bool {
696 let inner = self.inner.lock();
697 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
698 }
699
700 pub fn can_redo(&self) -> bool {
702 let inner = self.inner.lock();
703 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
704 }
705
706 pub fn clear_undo_redo(&self) {
708 let inner = self.inner.lock();
709 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
710 }
711
712 pub fn is_modified(&self) -> bool {
716 self.inner.lock().modified
717 }
718
719 pub fn set_modified(&self, modified: bool) {
721 let queued = {
722 let mut inner = self.inner.lock();
723 if inner.modified != modified {
724 inner.modified = modified;
725 inner.queue_event(DocumentEvent::ModificationChanged(modified));
726 }
727 inner.take_queued_events()
728 };
729 crate::inner::dispatch_queued_events(queued);
730 }
731
732 pub fn title(&self) -> String {
736 let inner = self.inner.lock();
737 document_commands::get_document(&inner.ctx, &inner.document_id)
738 .ok()
739 .flatten()
740 .map(|d| d.title)
741 .unwrap_or_default()
742 }
743
744 pub fn set_title(&self, title: &str) -> Result<()> {
746 let inner = self.inner.lock();
747 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
748 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
749 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
750 update.title = title.into();
751 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
752 Ok(())
753 }
754
755 pub fn text_direction(&self) -> TextDirection {
757 let inner = self.inner.lock();
758 document_commands::get_document(&inner.ctx, &inner.document_id)
759 .ok()
760 .flatten()
761 .map(|d| d.text_direction)
762 .unwrap_or(TextDirection::LeftToRight)
763 }
764
765 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
767 let inner = self.inner.lock();
768 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
769 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
770 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
771 update.text_direction = direction;
772 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
773 Ok(())
774 }
775
776 pub fn default_wrap_mode(&self) -> WrapMode {
778 let inner = self.inner.lock();
779 document_commands::get_document(&inner.ctx, &inner.document_id)
780 .ok()
781 .flatten()
782 .map(|d| d.default_wrap_mode)
783 .unwrap_or(WrapMode::WordWrap)
784 }
785
786 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
788 let inner = self.inner.lock();
789 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
790 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
791 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
792 update.default_wrap_mode = mode;
793 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
794 Ok(())
795 }
796
797 pub fn on_change<F>(&self, callback: F) -> Subscription
816 where
817 F: Fn(DocumentEvent) + Send + Sync + 'static,
818 {
819 let mut inner = self.inner.lock();
820 events::subscribe_inner(&mut inner, callback)
821 }
822
823 pub fn poll_events(&self) -> Vec<DocumentEvent> {
829 let mut inner = self.inner.lock();
830 inner.drain_poll_events()
831 }
832
833 pub fn set_syntax_highlighter(&self, highlighter: Option<Arc<dyn crate::SyntaxHighlighter>>) {
841 let mut inner = self.inner.lock();
842 match highlighter {
843 Some(hl) => {
844 inner.highlight = Some(crate::highlight::HighlightData {
845 highlighter: hl,
846 blocks: std::collections::HashMap::new(),
847 });
848 inner.rehighlight_all();
849 }
850 None => {
851 inner.highlight = None;
852 }
853 }
854 }
855
856 pub fn rehighlight(&self) {
861 let mut inner = self.inner.lock();
862 inner.rehighlight_all();
863 }
864
865 pub fn rehighlight_block(&self, block_id: usize) {
868 let mut inner = self.inner.lock();
869 inner.rehighlight_from_block(block_id);
870 }
871}
872
873impl Default for TextDocument {
874 fn default() -> Self {
875 Self::new()
876 }
877}
878
879struct UndoBlockState {
883 id: u64,
884 position: i64,
885 text_length: i64,
886 plain_text: String,
887 format: BlockFormat,
888}
889
890fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
892 let all_blocks =
893 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
894 let mut states: Vec<UndoBlockState> = all_blocks
895 .into_iter()
896 .map(|b| UndoBlockState {
897 id: b.id,
898 position: b.document_position,
899 text_length: b.text_length,
900 plain_text: b.plain_text.clone(),
901 format: BlockFormat::from(&b),
902 })
903 .collect();
904 states.sort_by_key(|s| s.position);
905 states
906}
907
908fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
911 let after = capture_block_state(inner);
912
913 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
915 before.iter().map(|s| (s.id, s)).collect();
916 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
917 after.iter().map(|s| (s.id, s)).collect();
918
919 let mut content_changed = false;
921 let mut earliest_pos: Option<usize> = None;
922 let mut old_end: usize = 0;
923 let mut new_end: usize = 0;
924 let mut blocks_affected: usize = 0;
925
926 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
930 if let Some(before_state) = before_map.get(&after_state.id) {
931 let text_changed = before_state.plain_text != after_state.plain_text
932 || before_state.text_length != after_state.text_length;
933 let format_changed = before_state.format != after_state.format;
934
935 if text_changed {
936 content_changed = true;
937 blocks_affected += 1;
938 let pos = after_state.position.max(0) as usize;
939 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
940 old_end = old_end.max(
941 before_state.position.max(0) as usize
942 + before_state.text_length.max(0) as usize,
943 );
944 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
945 } else if format_changed {
946 let pos = after_state.position.max(0) as usize;
947 let len = after_state.text_length.max(0) as usize;
948 format_only_changes.push((pos, len));
949 }
950 } else {
951 content_changed = true;
953 blocks_affected += 1;
954 let pos = after_state.position.max(0) as usize;
955 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
956 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
957 }
958 }
959
960 for before_state in before {
962 if !after_map.contains_key(&before_state.id) {
963 content_changed = true;
964 blocks_affected += 1;
965 let pos = before_state.position.max(0) as usize;
966 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
967 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
968 }
969 }
970
971 if content_changed {
972 let position = earliest_pos.unwrap_or(0);
973 inner.queue_event(DocumentEvent::ContentsChanged {
974 position,
975 chars_removed: old_end.saturating_sub(position),
976 chars_added: new_end.saturating_sub(position),
977 blocks_affected,
978 });
979 }
980
981 for (position, length) in format_only_changes {
983 inner.queue_event(DocumentEvent::FormatChanged {
984 position,
985 length,
986 kind: FormatChangeKind::Block,
987 });
988 }
989}
990
991fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
995 let frames = frontend::commands::document_commands::get_document_relationship(
997 &inner.ctx,
998 &inner.document_id,
999 &frontend::document::dtos::DocumentRelationshipField::Frames,
1000 )
1001 .unwrap_or_default();
1002
1003 frames.first().copied().unwrap_or(0)
1004}
1005
1006fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
1010 let Some(json) = data else {
1011 return (String::new(), 0.0, String::new());
1012 };
1013 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1014 let id = v["id"].as_str().unwrap_or_default().to_string();
1015 let pct = v["percentage"].as_f64().unwrap_or(0.0);
1016 let msg = v["message"].as_str().unwrap_or_default().to_string();
1017 (id, pct, msg)
1018}
1019
1020fn parse_id_data(data: &Option<String>) -> String {
1022 let Some(json) = data else {
1023 return String::new();
1024 };
1025 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1026 v["id"].as_str().unwrap_or_default().to_string()
1027}
1028
1029fn parse_failed_data(data: &Option<String>) -> (String, String) {
1031 let Some(json) = data else {
1032 return (String::new(), "unknown error".into());
1033 };
1034 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1035 let id = v["id"].as_str().unwrap_or_default().to_string();
1036 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
1037 (id, error)
1038}