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.queue_event(DocumentEvent::DocumentReset);
153 inner.check_block_count_changed();
154 inner.reset_cached_child_order();
155 inner.queue_event(DocumentEvent::UndoRedoChanged {
156 can_undo: false,
157 can_redo: false,
158 });
159 inner.take_queued_events()
160 };
161 crate::inner::dispatch_queued_events(queued);
162 Ok(())
163 }
164
165 pub fn to_plain_text(&self) -> Result<String> {
167 let mut inner = self.inner.lock();
168 Ok(inner.plain_text()?.to_string())
169 }
170
171 pub fn set_markdown(&self, markdown: &str) -> Result<Operation<MarkdownImportResult>> {
175 let mut inner = self.inner.lock();
176 inner.invalidate_text_cache();
177 let dto = frontend::document_io::ImportMarkdownDto {
178 markdown_text: markdown.into(),
179 };
180 let op_id = document_io_commands::import_markdown(&inner.ctx, &dto)?;
181 Ok(Operation::new(
182 op_id,
183 &inner.ctx,
184 Box::new(|ctx, id| {
185 document_io_commands::get_import_markdown_result(ctx, id)
186 .ok()
187 .flatten()
188 .map(|r| {
189 Ok(MarkdownImportResult {
190 block_count: to_usize(r.block_count),
191 })
192 })
193 }),
194 ))
195 }
196
197 pub fn to_markdown(&self) -> Result<String> {
199 let inner = self.inner.lock();
200 let dto = document_io_commands::export_markdown(&inner.ctx)?;
201 Ok(dto.markdown_text)
202 }
203
204 pub fn set_html(&self, html: &str) -> Result<Operation<HtmlImportResult>> {
208 let mut inner = self.inner.lock();
209 inner.invalidate_text_cache();
210 let dto = frontend::document_io::ImportHtmlDto {
211 html_text: html.into(),
212 };
213 let op_id = document_io_commands::import_html(&inner.ctx, &dto)?;
214 Ok(Operation::new(
215 op_id,
216 &inner.ctx,
217 Box::new(|ctx, id| {
218 document_io_commands::get_import_html_result(ctx, id)
219 .ok()
220 .flatten()
221 .map(|r| {
222 Ok(HtmlImportResult {
223 block_count: to_usize(r.block_count),
224 })
225 })
226 }),
227 ))
228 }
229
230 pub fn to_html(&self) -> Result<String> {
232 let inner = self.inner.lock();
233 let dto = document_io_commands::export_html(&inner.ctx)?;
234 Ok(dto.html_text)
235 }
236
237 pub fn to_latex(&self, document_class: &str, include_preamble: bool) -> Result<String> {
239 let inner = self.inner.lock();
240 let dto = frontend::document_io::ExportLatexDto {
241 document_class: document_class.into(),
242 include_preamble,
243 };
244 let result = document_io_commands::export_latex(&inner.ctx, &dto)?;
245 Ok(result.latex_text)
246 }
247
248 pub fn to_docx(&self, output_path: &str) -> Result<Operation<DocxExportResult>> {
252 let inner = self.inner.lock();
253 let dto = frontend::document_io::ExportDocxDto {
254 output_path: output_path.into(),
255 };
256 let op_id = document_io_commands::export_docx(&inner.ctx, &dto)?;
257 Ok(Operation::new(
258 op_id,
259 &inner.ctx,
260 Box::new(|ctx, id| {
261 document_io_commands::get_export_docx_result(ctx, id)
262 .ok()
263 .flatten()
264 .map(|r| {
265 Ok(DocxExportResult {
266 file_path: r.file_path,
267 paragraph_count: to_usize(r.paragraph_count),
268 })
269 })
270 }),
271 ))
272 }
273
274 pub fn clear(&self) -> Result<()> {
276 let queued = {
277 let mut inner = self.inner.lock();
278 let dto = frontend::document_io::ImportPlainTextDto {
279 plain_text: String::new(),
280 };
281 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
282 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
283 inner.invalidate_text_cache();
284 inner.queue_event(DocumentEvent::DocumentReset);
285 inner.check_block_count_changed();
286 inner.reset_cached_child_order();
287 inner.queue_event(DocumentEvent::UndoRedoChanged {
288 can_undo: false,
289 can_redo: false,
290 });
291 inner.take_queued_events()
292 };
293 crate::inner::dispatch_queued_events(queued);
294 Ok(())
295 }
296
297 pub fn cursor(&self) -> TextCursor {
301 self.cursor_at(0)
302 }
303
304 pub fn cursor_at(&self, position: usize) -> TextCursor {
306 let data = {
307 let mut inner = self.inner.lock();
308 inner.register_cursor(position)
309 };
310 TextCursor {
311 doc: self.inner.clone(),
312 data,
313 }
314 }
315
316 pub fn stats(&self) -> DocumentStats {
320 let inner = self.inner.lock();
321 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
322 .expect("get_document_stats should not fail");
323 DocumentStats::from(&dto)
324 }
325
326 pub fn character_count(&self) -> usize {
328 let inner = self.inner.lock();
329 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
330 .expect("get_document_stats should not fail");
331 to_usize(dto.character_count)
332 }
333
334 pub fn block_count(&self) -> usize {
336 let inner = self.inner.lock();
337 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
338 .expect("get_document_stats should not fail");
339 to_usize(dto.block_count)
340 }
341
342 pub fn is_empty(&self) -> bool {
344 self.character_count() == 0
345 }
346
347 pub fn text_at(&self, position: usize, length: usize) -> Result<String> {
349 let inner = self.inner.lock();
350 let dto = frontend::document_inspection::GetTextAtPositionDto {
351 position: to_i64(position),
352 length: to_i64(length),
353 };
354 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
355 Ok(result.text)
356 }
357
358 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
360 let inner = self.inner.lock();
361 let dto = frontend::document_inspection::GetBlockAtPositionDto {
362 position: to_i64(position),
363 };
364 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
365 Ok(BlockInfo::from(&result))
366 }
367
368 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
370 let inner = self.inner.lock();
371 let dto = frontend::document_inspection::GetBlockAtPositionDto {
372 position: to_i64(position),
373 };
374 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
375 let block_id = block_info.block_id;
376 let block_id = block_id as u64;
377 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
378 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
379 Ok(BlockFormat::from(&block_dto))
380 }
381
382 pub fn flow(&self) -> Vec<crate::flow::FlowElement> {
393 let inner = self.inner.lock();
394 let main_frame_id = get_main_frame_id(&inner);
395 crate::text_frame::build_flow_elements(&inner, &self.inner, main_frame_id)
396 }
397
398 pub fn block_by_id(&self, block_id: usize) -> Option<crate::text_block::TextBlock> {
403 let inner = self.inner.lock();
404 let exists = frontend::commands::block_commands::get_block(&inner.ctx, &(block_id as u64))
405 .ok()
406 .flatten()
407 .is_some();
408
409 if exists {
410 Some(crate::text_block::TextBlock {
411 doc: self.inner.clone(),
412 block_id,
413 })
414 } else {
415 None
416 }
417 }
418
419 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
422 let inner = self.inner.lock();
423 let dto = frontend::document_inspection::GetBlockAtPositionDto {
424 position: to_i64(position),
425 };
426 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
427 Some(crate::text_block::TextBlock {
428 doc: self.inner.clone(),
429 block_id: result.block_id as usize,
430 })
431 }
432
433 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
442 let inner = self.inner.lock();
443 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
444 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
445 sorted.sort_by_key(|b| b.document_position);
446
447 sorted
448 .get(block_number)
449 .map(|b| crate::text_block::TextBlock {
450 doc: self.inner.clone(),
451 block_id: b.id as usize,
452 })
453 }
454
455 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
461 let inner = self.inner.lock();
462 let all_blocks =
463 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
464 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
465 sorted.sort_by_key(|b| b.document_position);
466 sorted
467 .iter()
468 .map(|b| crate::text_block::TextBlock {
469 doc: self.inner.clone(),
470 block_id: b.id as usize,
471 })
472 .collect()
473 }
474
475 pub fn blocks_in_range(
482 &self,
483 position: usize,
484 length: usize,
485 ) -> Vec<crate::text_block::TextBlock> {
486 let inner = self.inner.lock();
487 let all_blocks =
488 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
489 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
490 sorted.sort_by_key(|b| b.document_position);
491
492 let range_start = position;
493 let range_end = position + length;
494
495 sorted
496 .iter()
497 .filter(|b| {
498 let block_start = b.document_position.max(0) as usize;
499 let block_end = block_start + b.text_length.max(0) as usize;
500 if length == 0 {
502 range_start >= block_start && range_start < block_end
504 } else {
505 block_start < range_end && block_end > range_start
506 }
507 })
508 .map(|b| crate::text_block::TextBlock {
509 doc: self.inner.clone(),
510 block_id: b.id as usize,
511 })
512 .collect()
513 }
514
515 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
520 let inner = self.inner.lock();
521 let main_frame_id = get_main_frame_id(&inner);
522 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id);
523 crate::flow::FlowSnapshot { elements }
524 }
525
526 pub fn find(
530 &self,
531 query: &str,
532 from: usize,
533 options: &FindOptions,
534 ) -> Result<Option<FindMatch>> {
535 let inner = self.inner.lock();
536 let dto = options.to_find_text_dto(query, from);
537 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
538 Ok(convert::find_result_to_match(&result))
539 }
540
541 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
543 let inner = self.inner.lock();
544 let dto = options.to_find_all_dto(query);
545 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
546 Ok(convert::find_all_to_matches(&result))
547 }
548
549 pub fn replace_text(
551 &self,
552 query: &str,
553 replacement: &str,
554 replace_all: bool,
555 options: &FindOptions,
556 ) -> Result<usize> {
557 let (count, queued) = {
558 let mut inner = self.inner.lock();
559 let dto = options.to_replace_dto(query, replacement, replace_all);
560 let result =
561 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
562 let count = to_usize(result.replacements_count);
563 inner.invalidate_text_cache();
564 if count > 0 {
565 inner.modified = true;
566 inner.queue_event(DocumentEvent::ContentsChanged {
571 position: 0,
572 chars_removed: 0,
573 chars_added: 0,
574 blocks_affected: count,
575 });
576 inner.check_block_count_changed();
577 inner.check_flow_changed();
578 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
579 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
580 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
581 }
582 (count, inner.take_queued_events())
583 };
584 crate::inner::dispatch_queued_events(queued);
585 Ok(count)
586 }
587
588 pub fn add_resource(
592 &self,
593 resource_type: ResourceType,
594 name: &str,
595 mime_type: &str,
596 data: &[u8],
597 ) -> Result<()> {
598 let mut inner = self.inner.lock();
599 let dto = frontend::resource::dtos::CreateResourceDto {
600 created_at: Default::default(),
601 updated_at: Default::default(),
602 resource_type,
603 name: name.into(),
604 url: String::new(),
605 mime_type: mime_type.into(),
606 data_base64: BASE64.encode(data),
607 };
608 let created = resource_commands::create_resource(
609 &inner.ctx,
610 Some(inner.stack_id),
611 &dto,
612 inner.document_id,
613 -1,
614 )?;
615 inner.resource_cache.insert(name.to_string(), created.id);
616 Ok(())
617 }
618
619 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
623 let mut inner = self.inner.lock();
624
625 if let Some(&id) = inner.resource_cache.get(name) {
627 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
628 let bytes = BASE64.decode(&r.data_base64)?;
629 return Ok(Some(bytes));
630 }
631 inner.resource_cache.remove(name);
633 }
634
635 let all = resource_commands::get_all_resource(&inner.ctx)?;
637 for r in &all {
638 if r.name == name {
639 inner.resource_cache.insert(name.to_string(), r.id);
640 let bytes = BASE64.decode(&r.data_base64)?;
641 return Ok(Some(bytes));
642 }
643 }
644 Ok(None)
645 }
646
647 pub fn undo(&self) -> Result<()> {
651 let queued = {
652 let mut inner = self.inner.lock();
653 let before = capture_block_state(&inner);
654 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
655 inner.invalidate_text_cache();
656 result?;
657 emit_undo_redo_change_events(&mut inner, &before);
658 inner.check_block_count_changed();
659 inner.check_flow_changed();
660 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
661 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
662 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
663 inner.take_queued_events()
664 };
665 crate::inner::dispatch_queued_events(queued);
666 Ok(())
667 }
668
669 pub fn redo(&self) -> Result<()> {
671 let queued = {
672 let mut inner = self.inner.lock();
673 let before = capture_block_state(&inner);
674 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
675 inner.invalidate_text_cache();
676 result?;
677 emit_undo_redo_change_events(&mut inner, &before);
678 inner.check_block_count_changed();
679 inner.check_flow_changed();
680 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
681 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
682 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
683 inner.take_queued_events()
684 };
685 crate::inner::dispatch_queued_events(queued);
686 Ok(())
687 }
688
689 pub fn can_undo(&self) -> bool {
691 let inner = self.inner.lock();
692 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
693 }
694
695 pub fn can_redo(&self) -> bool {
697 let inner = self.inner.lock();
698 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
699 }
700
701 pub fn clear_undo_redo(&self) {
703 let inner = self.inner.lock();
704 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
705 }
706
707 pub fn is_modified(&self) -> bool {
711 self.inner.lock().modified
712 }
713
714 pub fn set_modified(&self, modified: bool) {
716 let queued = {
717 let mut inner = self.inner.lock();
718 if inner.modified != modified {
719 inner.modified = modified;
720 inner.queue_event(DocumentEvent::ModificationChanged(modified));
721 }
722 inner.take_queued_events()
723 };
724 crate::inner::dispatch_queued_events(queued);
725 }
726
727 pub fn title(&self) -> String {
731 let inner = self.inner.lock();
732 document_commands::get_document(&inner.ctx, &inner.document_id)
733 .ok()
734 .flatten()
735 .map(|d| d.title)
736 .unwrap_or_default()
737 }
738
739 pub fn set_title(&self, title: &str) -> Result<()> {
741 let inner = self.inner.lock();
742 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
743 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
744 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
745 update.title = title.into();
746 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
747 Ok(())
748 }
749
750 pub fn text_direction(&self) -> TextDirection {
752 let inner = self.inner.lock();
753 document_commands::get_document(&inner.ctx, &inner.document_id)
754 .ok()
755 .flatten()
756 .map(|d| d.text_direction)
757 .unwrap_or(TextDirection::LeftToRight)
758 }
759
760 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
762 let inner = self.inner.lock();
763 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
764 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
765 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
766 update.text_direction = direction;
767 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
768 Ok(())
769 }
770
771 pub fn default_wrap_mode(&self) -> WrapMode {
773 let inner = self.inner.lock();
774 document_commands::get_document(&inner.ctx, &inner.document_id)
775 .ok()
776 .flatten()
777 .map(|d| d.default_wrap_mode)
778 .unwrap_or(WrapMode::WordWrap)
779 }
780
781 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
783 let inner = self.inner.lock();
784 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
785 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
786 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
787 update.default_wrap_mode = mode;
788 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
789 Ok(())
790 }
791
792 pub fn on_change<F>(&self, callback: F) -> Subscription
811 where
812 F: Fn(DocumentEvent) + Send + Sync + 'static,
813 {
814 let mut inner = self.inner.lock();
815 events::subscribe_inner(&mut inner, callback)
816 }
817
818 pub fn poll_events(&self) -> Vec<DocumentEvent> {
824 let mut inner = self.inner.lock();
825 inner.drain_poll_events()
826 }
827}
828
829impl Default for TextDocument {
830 fn default() -> Self {
831 Self::new()
832 }
833}
834
835struct UndoBlockState {
839 id: u64,
840 position: i64,
841 text_length: i64,
842 plain_text: String,
843 format: BlockFormat,
844}
845
846fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
848 let all_blocks =
849 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
850 let mut states: Vec<UndoBlockState> = all_blocks
851 .into_iter()
852 .map(|b| UndoBlockState {
853 id: b.id,
854 position: b.document_position,
855 text_length: b.text_length,
856 plain_text: b.plain_text.clone(),
857 format: BlockFormat::from(&b),
858 })
859 .collect();
860 states.sort_by_key(|s| s.position);
861 states
862}
863
864fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
867 let after = capture_block_state(inner);
868
869 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
871 before.iter().map(|s| (s.id, s)).collect();
872 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
873 after.iter().map(|s| (s.id, s)).collect();
874
875 let mut content_changed = false;
877 let mut earliest_pos: Option<usize> = None;
878 let mut old_end: usize = 0;
879 let mut new_end: usize = 0;
880 let mut blocks_affected: usize = 0;
881
882 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
886 if let Some(before_state) = before_map.get(&after_state.id) {
887 let text_changed = before_state.plain_text != after_state.plain_text
888 || before_state.text_length != after_state.text_length;
889 let format_changed = before_state.format != after_state.format;
890
891 if text_changed {
892 content_changed = true;
893 blocks_affected += 1;
894 let pos = after_state.position.max(0) as usize;
895 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
896 old_end = old_end.max(
897 before_state.position.max(0) as usize
898 + before_state.text_length.max(0) as usize,
899 );
900 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
901 } else if format_changed {
902 let pos = after_state.position.max(0) as usize;
903 let len = after_state.text_length.max(0) as usize;
904 format_only_changes.push((pos, len));
905 }
906 } else {
907 content_changed = true;
909 blocks_affected += 1;
910 let pos = after_state.position.max(0) as usize;
911 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
912 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
913 }
914 }
915
916 for before_state in before {
918 if !after_map.contains_key(&before_state.id) {
919 content_changed = true;
920 blocks_affected += 1;
921 let pos = before_state.position.max(0) as usize;
922 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
923 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
924 }
925 }
926
927 if content_changed {
928 let position = earliest_pos.unwrap_or(0);
929 inner.queue_event(DocumentEvent::ContentsChanged {
930 position,
931 chars_removed: old_end.saturating_sub(position),
932 chars_added: new_end.saturating_sub(position),
933 blocks_affected,
934 });
935 }
936
937 for (position, length) in format_only_changes {
939 inner.queue_event(DocumentEvent::FormatChanged {
940 position,
941 length,
942 kind: FormatChangeKind::Block,
943 });
944 }
945}
946
947fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
951 let frames = frontend::commands::document_commands::get_document_relationship(
953 &inner.ctx,
954 &inner.document_id,
955 &frontend::document::dtos::DocumentRelationshipField::Frames,
956 )
957 .unwrap_or_default();
958
959 frames.first().copied().unwrap_or(0)
960}
961
962fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
966 let Some(json) = data else {
967 return (String::new(), 0.0, String::new());
968 };
969 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
970 let id = v["id"].as_str().unwrap_or_default().to_string();
971 let pct = v["percentage"].as_f64().unwrap_or(0.0);
972 let msg = v["message"].as_str().unwrap_or_default().to_string();
973 (id, pct, msg)
974}
975
976fn parse_id_data(data: &Option<String>) -> String {
978 let Some(json) = data else {
979 return String::new();
980 };
981 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
982 v["id"].as_str().unwrap_or_default().to_string()
983}
984
985fn parse_failed_data(data: &Option<String>) -> (String, String) {
987 let Some(json) = data else {
988 return (String::new(), "unknown error".into());
989 };
990 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
991 let id = v["id"].as_str().unwrap_or_default().to_string();
992 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
993 (id, error)
994}