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 {
42 pub fn new() -> Self {
51 Self::try_new().expect("failed to initialize document")
52 }
53
54 pub fn try_new() -> Result<Self> {
56 let ctx = frontend::AppContext::new();
57 let doc_inner = TextDocumentInner::initialize(ctx)?;
58 let inner = Arc::new(Mutex::new(doc_inner));
59
60 Self::subscribe_long_operation_events(&inner);
62
63 Ok(Self { inner })
64 }
65
66 fn subscribe_long_operation_events(inner: &Arc<Mutex<TextDocumentInner>>) {
68 use frontend::common::event::{LongOperationEvent as LOE, Origin};
69
70 let weak = Arc::downgrade(inner);
71 {
72 let locked = inner.lock();
73 let w = weak.clone();
75 locked
76 .event_client
77 .subscribe(Origin::LongOperation(LOE::Progress), move |event| {
78 if let Some(inner) = w.upgrade() {
79 let (op_id, percent, message) = parse_progress_data(&event.data);
80 let mut inner = inner.lock();
81 inner.queue_event(DocumentEvent::LongOperationProgress {
82 operation_id: op_id,
83 percent,
84 message,
85 });
86 }
87 });
88
89 let w = weak.clone();
91 locked
92 .event_client
93 .subscribe(Origin::LongOperation(LOE::Completed), move |event| {
94 if let Some(inner) = w.upgrade() {
95 let op_id = parse_id_data(&event.data);
96 let mut inner = inner.lock();
97 inner.queue_event(DocumentEvent::DocumentReset);
98 inner.check_block_count_changed();
99 inner.reset_cached_child_order();
100 inner.queue_event(DocumentEvent::LongOperationFinished {
101 operation_id: op_id,
102 success: true,
103 error: None,
104 });
105 }
106 });
107
108 let w = weak.clone();
110 locked
111 .event_client
112 .subscribe(Origin::LongOperation(LOE::Cancelled), move |event| {
113 if let Some(inner) = w.upgrade() {
114 let op_id = parse_id_data(&event.data);
115 let mut inner = inner.lock();
116 inner.queue_event(DocumentEvent::LongOperationFinished {
117 operation_id: op_id,
118 success: false,
119 error: Some("cancelled".into()),
120 });
121 }
122 });
123
124 locked
126 .event_client
127 .subscribe(Origin::LongOperation(LOE::Failed), move |event| {
128 if let Some(inner) = weak.upgrade() {
129 let (op_id, error) = parse_failed_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(error),
135 });
136 }
137 });
138 }
139 }
140
141 pub fn set_plain_text(&self, text: &str) -> Result<()> {
145 let queued = {
146 let mut inner = self.inner.lock();
147 let dto = frontend::document_io::ImportPlainTextDto {
148 plain_text: text.into(),
149 };
150 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
151 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
152 inner.invalidate_text_cache();
153 inner.rehighlight_all();
154 inner.queue_event(DocumentEvent::DocumentReset);
155 inner.check_block_count_changed();
156 inner.reset_cached_child_order();
157 inner.queue_event(DocumentEvent::UndoRedoChanged {
158 can_undo: false,
159 can_redo: false,
160 });
161 inner.take_queued_events()
162 };
163 crate::inner::dispatch_queued_events(queued);
164 Ok(())
165 }
166
167 pub fn to_plain_text(&self) -> Result<String> {
169 let mut inner = self.inner.lock();
170 Ok(inner.plain_text()?.to_string())
171 }
172
173 pub fn set_markdown(&self, markdown: &str) -> Result<Operation<MarkdownImportResult>> {
177 let mut inner = self.inner.lock();
178 inner.invalidate_text_cache();
179 let dto = frontend::document_io::ImportMarkdownDto {
180 markdown_text: markdown.into(),
181 };
182 let op_id = document_io_commands::import_markdown(&inner.ctx, &dto)?;
183 Ok(Operation::new(
184 op_id,
185 &inner.ctx,
186 Box::new(|ctx, id| {
187 document_io_commands::get_import_markdown_result(ctx, id)
188 .ok()
189 .flatten()
190 .map(|r| {
191 Ok(MarkdownImportResult {
192 block_count: to_usize(r.block_count),
193 })
194 })
195 }),
196 ))
197 }
198
199 pub fn to_markdown(&self) -> Result<String> {
201 let inner = self.inner.lock();
202 let dto = document_io_commands::export_markdown(&inner.ctx)?;
203 Ok(dto.markdown_text)
204 }
205
206 pub fn set_html(&self, html: &str) -> Result<Operation<HtmlImportResult>> {
210 let mut inner = self.inner.lock();
211 inner.invalidate_text_cache();
212 let dto = frontend::document_io::ImportHtmlDto {
213 html_text: html.into(),
214 };
215 let op_id = document_io_commands::import_html(&inner.ctx, &dto)?;
216 Ok(Operation::new(
217 op_id,
218 &inner.ctx,
219 Box::new(|ctx, id| {
220 document_io_commands::get_import_html_result(ctx, id)
221 .ok()
222 .flatten()
223 .map(|r| {
224 Ok(HtmlImportResult {
225 block_count: to_usize(r.block_count),
226 })
227 })
228 }),
229 ))
230 }
231
232 pub fn to_html(&self) -> Result<String> {
234 let inner = self.inner.lock();
235 let dto = document_io_commands::export_html(&inner.ctx)?;
236 Ok(dto.html_text)
237 }
238
239 pub fn to_latex(&self, document_class: &str, include_preamble: bool) -> Result<String> {
241 let inner = self.inner.lock();
242 let dto = frontend::document_io::ExportLatexDto {
243 document_class: document_class.into(),
244 include_preamble,
245 };
246 let result = document_io_commands::export_latex(&inner.ctx, &dto)?;
247 Ok(result.latex_text)
248 }
249
250 pub fn to_docx(&self, output_path: &str) -> Result<Operation<DocxExportResult>> {
254 let inner = self.inner.lock();
255 let dto = frontend::document_io::ExportDocxDto {
256 output_path: output_path.into(),
257 };
258 let op_id = document_io_commands::export_docx(&inner.ctx, &dto)?;
259 Ok(Operation::new(
260 op_id,
261 &inner.ctx,
262 Box::new(|ctx, id| {
263 document_io_commands::get_export_docx_result(ctx, id)
264 .ok()
265 .flatten()
266 .map(|r| {
267 Ok(DocxExportResult {
268 file_path: r.file_path,
269 paragraph_count: to_usize(r.paragraph_count),
270 })
271 })
272 }),
273 ))
274 }
275
276 pub fn clear(&self) -> Result<()> {
278 let queued = {
279 let mut inner = self.inner.lock();
280 let dto = frontend::document_io::ImportPlainTextDto {
281 plain_text: String::new(),
282 };
283 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
284 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
285 inner.invalidate_text_cache();
286 inner.rehighlight_all();
287 inner.queue_event(DocumentEvent::DocumentReset);
288 inner.check_block_count_changed();
289 inner.reset_cached_child_order();
290 inner.queue_event(DocumentEvent::UndoRedoChanged {
291 can_undo: false,
292 can_redo: false,
293 });
294 inner.take_queued_events()
295 };
296 crate::inner::dispatch_queued_events(queued);
297 Ok(())
298 }
299
300 pub fn cursor(&self) -> TextCursor {
304 self.cursor_at(0)
305 }
306
307 pub fn cursor_at(&self, position: usize) -> TextCursor {
309 let data = {
310 let mut inner = self.inner.lock();
311 inner.register_cursor(position)
312 };
313 TextCursor {
314 doc: self.inner.clone(),
315 data,
316 }
317 }
318
319 pub fn stats(&self) -> DocumentStats {
323 let inner = self.inner.lock();
324 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
325 .expect("get_document_stats should not fail");
326 DocumentStats::from(&dto)
327 }
328
329 pub fn character_count(&self) -> usize {
331 let inner = self.inner.lock();
332 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
333 .expect("get_document_stats should not fail");
334 to_usize(dto.character_count)
335 }
336
337 pub fn block_count(&self) -> usize {
339 let inner = self.inner.lock();
340 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
341 .expect("get_document_stats should not fail");
342 to_usize(dto.block_count)
343 }
344
345 pub fn is_empty(&self) -> bool {
347 self.character_count() == 0
348 }
349
350 pub fn text_at(&self, position: usize, length: usize) -> Result<String> {
352 let inner = self.inner.lock();
353 let dto = frontend::document_inspection::GetTextAtPositionDto {
354 position: to_i64(position),
355 length: to_i64(length),
356 };
357 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
358 Ok(result.text)
359 }
360
361 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
363 let inner = self.inner.lock();
364 let dto = frontend::document_inspection::GetBlockAtPositionDto {
365 position: to_i64(position),
366 };
367 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
368 Ok(BlockInfo::from(&result))
369 }
370
371 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
373 let inner = self.inner.lock();
374 let dto = frontend::document_inspection::GetBlockAtPositionDto {
375 position: to_i64(position),
376 };
377 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
378 let block_id = block_info.block_id;
379 let block_id = block_id as u64;
380 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
381 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
382 Ok(BlockFormat::from(&block_dto))
383 }
384
385 pub fn flow(&self) -> Vec<crate::flow::FlowElement> {
396 let inner = self.inner.lock();
397 let main_frame_id = get_main_frame_id(&inner);
398 crate::text_frame::build_flow_elements(&inner, &self.inner, main_frame_id)
399 }
400
401 pub fn block_by_id(&self, block_id: usize) -> Option<crate::text_block::TextBlock> {
406 let inner = self.inner.lock();
407 let exists = frontend::commands::block_commands::get_block(&inner.ctx, &(block_id as u64))
408 .ok()
409 .flatten()
410 .is_some();
411
412 if exists {
413 Some(crate::text_block::TextBlock {
414 doc: self.inner.clone(),
415 block_id,
416 })
417 } else {
418 None
419 }
420 }
421
422 pub fn snapshot_block_at_position(
428 &self,
429 position: usize,
430 ) -> Option<crate::flow::BlockSnapshot> {
431 let inner = self.inner.lock();
432 let main_frame_id = get_main_frame_id(&inner);
433
434 let ordered_block_ids = collect_frame_block_ids(&inner, main_frame_id)?;
436
437 let pos = position as i64;
439 let mut running_pos: i64 = 0;
440 for &block_id in &ordered_block_ids {
441 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
442 .ok()
443 .flatten()?;
444 let block_end = running_pos + block_dto.text_length;
445 if pos >= running_pos && pos <= block_end {
446 return crate::text_block::build_block_snapshot_with_position(
447 &inner,
448 block_id,
449 Some(running_pos as usize),
450 );
451 }
452 running_pos = block_end + 1;
453 }
454
455 if let Some(&last_id) = ordered_block_ids.last() {
457 return crate::text_block::build_block_snapshot(&inner, last_id);
458 }
459 None
460 }
461
462 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
465 let inner = self.inner.lock();
466 let dto = frontend::document_inspection::GetBlockAtPositionDto {
467 position: to_i64(position),
468 };
469 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
470 Some(crate::text_block::TextBlock {
471 doc: self.inner.clone(),
472 block_id: result.block_id as usize,
473 })
474 }
475
476 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
485 let inner = self.inner.lock();
486 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
487 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
488 sorted.sort_by_key(|b| b.document_position);
489
490 sorted
491 .get(block_number)
492 .map(|b| crate::text_block::TextBlock {
493 doc: self.inner.clone(),
494 block_id: b.id as usize,
495 })
496 }
497
498 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
504 let inner = self.inner.lock();
505 let all_blocks =
506 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
507 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
508 sorted.sort_by_key(|b| b.document_position);
509 sorted
510 .iter()
511 .map(|b| crate::text_block::TextBlock {
512 doc: self.inner.clone(),
513 block_id: b.id as usize,
514 })
515 .collect()
516 }
517
518 pub fn blocks_in_range(
525 &self,
526 position: usize,
527 length: usize,
528 ) -> Vec<crate::text_block::TextBlock> {
529 let inner = self.inner.lock();
530 let all_blocks =
531 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
532 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
533 sorted.sort_by_key(|b| b.document_position);
534
535 let range_start = position;
536 let range_end = position + length;
537
538 sorted
539 .iter()
540 .filter(|b| {
541 let block_start = b.document_position.max(0) as usize;
542 let block_end = block_start + b.text_length.max(0) as usize;
543 if length == 0 {
545 range_start >= block_start && range_start < block_end
547 } else {
548 block_start < range_end && block_end > range_start
549 }
550 })
551 .map(|b| crate::text_block::TextBlock {
552 doc: self.inner.clone(),
553 block_id: b.id as usize,
554 })
555 .collect()
556 }
557
558 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
563 let inner = self.inner.lock();
564 let main_frame_id = get_main_frame_id(&inner);
565 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id);
566 crate::flow::FlowSnapshot { elements }
567 }
568
569 pub fn find(
573 &self,
574 query: &str,
575 from: usize,
576 options: &FindOptions,
577 ) -> Result<Option<FindMatch>> {
578 let inner = self.inner.lock();
579 let dto = options.to_find_text_dto(query, from);
580 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
581 Ok(convert::find_result_to_match(&result))
582 }
583
584 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
586 let inner = self.inner.lock();
587 let dto = options.to_find_all_dto(query);
588 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
589 Ok(convert::find_all_to_matches(&result))
590 }
591
592 pub fn replace_text(
594 &self,
595 query: &str,
596 replacement: &str,
597 replace_all: bool,
598 options: &FindOptions,
599 ) -> Result<usize> {
600 let (count, queued) = {
601 let mut inner = self.inner.lock();
602 let dto = options.to_replace_dto(query, replacement, replace_all);
603 let result =
604 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
605 let count = to_usize(result.replacements_count);
606 inner.invalidate_text_cache();
607 if count > 0 {
608 inner.modified = true;
609 inner.rehighlight_all();
610 inner.queue_event(DocumentEvent::ContentsChanged {
615 position: 0,
616 chars_removed: 0,
617 chars_added: 0,
618 blocks_affected: count,
619 });
620 inner.check_block_count_changed();
621 inner.check_flow_changed();
622 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
623 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
624 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
625 }
626 (count, inner.take_queued_events())
627 };
628 crate::inner::dispatch_queued_events(queued);
629 Ok(count)
630 }
631
632 pub fn add_resource(
636 &self,
637 resource_type: ResourceType,
638 name: &str,
639 mime_type: &str,
640 data: &[u8],
641 ) -> Result<()> {
642 let mut inner = self.inner.lock();
643 let dto = frontend::resource::dtos::CreateResourceDto {
644 created_at: Default::default(),
645 updated_at: Default::default(),
646 resource_type,
647 name: name.into(),
648 url: String::new(),
649 mime_type: mime_type.into(),
650 data_base64: BASE64.encode(data),
651 };
652 let created = resource_commands::create_resource(
653 &inner.ctx,
654 Some(inner.stack_id),
655 &dto,
656 inner.document_id,
657 -1,
658 )?;
659 inner.resource_cache.insert(name.to_string(), created.id);
660 Ok(())
661 }
662
663 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
667 let mut inner = self.inner.lock();
668
669 if let Some(&id) = inner.resource_cache.get(name) {
671 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
672 let bytes = BASE64.decode(&r.data_base64)?;
673 return Ok(Some(bytes));
674 }
675 inner.resource_cache.remove(name);
677 }
678
679 let all = resource_commands::get_all_resource(&inner.ctx)?;
681 for r in &all {
682 if r.name == name {
683 inner.resource_cache.insert(name.to_string(), r.id);
684 let bytes = BASE64.decode(&r.data_base64)?;
685 return Ok(Some(bytes));
686 }
687 }
688 Ok(None)
689 }
690
691 pub fn undo(&self) -> Result<()> {
695 let queued = {
696 let mut inner = self.inner.lock();
697 let before = capture_block_state(&inner);
698 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
699 inner.invalidate_text_cache();
700 result?;
701 inner.rehighlight_all();
702 emit_undo_redo_change_events(&mut inner, &before);
703 inner.check_block_count_changed();
704 inner.check_flow_changed();
705 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
706 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
707 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
708 inner.take_queued_events()
709 };
710 crate::inner::dispatch_queued_events(queued);
711 Ok(())
712 }
713
714 pub fn redo(&self) -> Result<()> {
716 let queued = {
717 let mut inner = self.inner.lock();
718 let before = capture_block_state(&inner);
719 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
720 inner.invalidate_text_cache();
721 result?;
722 inner.rehighlight_all();
723 emit_undo_redo_change_events(&mut inner, &before);
724 inner.check_block_count_changed();
725 inner.check_flow_changed();
726 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
727 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
728 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
729 inner.take_queued_events()
730 };
731 crate::inner::dispatch_queued_events(queued);
732 Ok(())
733 }
734
735 pub fn can_undo(&self) -> bool {
737 let inner = self.inner.lock();
738 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
739 }
740
741 pub fn can_redo(&self) -> bool {
743 let inner = self.inner.lock();
744 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
745 }
746
747 pub fn clear_undo_redo(&self) {
749 let inner = self.inner.lock();
750 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
751 }
752
753 pub fn is_modified(&self) -> bool {
757 self.inner.lock().modified
758 }
759
760 pub fn set_modified(&self, modified: bool) {
762 let queued = {
763 let mut inner = self.inner.lock();
764 if inner.modified != modified {
765 inner.modified = modified;
766 inner.queue_event(DocumentEvent::ModificationChanged(modified));
767 }
768 inner.take_queued_events()
769 };
770 crate::inner::dispatch_queued_events(queued);
771 }
772
773 pub fn title(&self) -> String {
777 let inner = self.inner.lock();
778 document_commands::get_document(&inner.ctx, &inner.document_id)
779 .ok()
780 .flatten()
781 .map(|d| d.title)
782 .unwrap_or_default()
783 }
784
785 pub fn set_title(&self, title: &str) -> Result<()> {
787 let inner = self.inner.lock();
788 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
789 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
790 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
791 update.title = title.into();
792 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
793 Ok(())
794 }
795
796 pub fn text_direction(&self) -> TextDirection {
798 let inner = self.inner.lock();
799 document_commands::get_document(&inner.ctx, &inner.document_id)
800 .ok()
801 .flatten()
802 .map(|d| d.text_direction)
803 .unwrap_or(TextDirection::LeftToRight)
804 }
805
806 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
808 let inner = self.inner.lock();
809 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
810 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
811 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
812 update.text_direction = direction;
813 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
814 Ok(())
815 }
816
817 pub fn default_wrap_mode(&self) -> WrapMode {
819 let inner = self.inner.lock();
820 document_commands::get_document(&inner.ctx, &inner.document_id)
821 .ok()
822 .flatten()
823 .map(|d| d.default_wrap_mode)
824 .unwrap_or(WrapMode::WordWrap)
825 }
826
827 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
829 let inner = self.inner.lock();
830 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
831 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
832 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
833 update.default_wrap_mode = mode;
834 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
835 Ok(())
836 }
837
838 pub fn on_change<F>(&self, callback: F) -> Subscription
857 where
858 F: Fn(DocumentEvent) + Send + Sync + 'static,
859 {
860 let mut inner = self.inner.lock();
861 events::subscribe_inner(&mut inner, callback)
862 }
863
864 pub fn poll_events(&self) -> Vec<DocumentEvent> {
870 let mut inner = self.inner.lock();
871 inner.drain_poll_events()
872 }
873
874 pub fn set_syntax_highlighter(&self, highlighter: Option<Arc<dyn crate::SyntaxHighlighter>>) {
882 let mut inner = self.inner.lock();
883 match highlighter {
884 Some(hl) => {
885 inner.highlight = Some(crate::highlight::HighlightData {
886 highlighter: hl,
887 blocks: std::collections::HashMap::new(),
888 });
889 inner.rehighlight_all();
890 }
891 None => {
892 inner.highlight = None;
893 }
894 }
895 }
896
897 pub fn rehighlight(&self) {
902 let mut inner = self.inner.lock();
903 inner.rehighlight_all();
904 }
905
906 pub fn rehighlight_block(&self, block_id: usize) {
909 let mut inner = self.inner.lock();
910 inner.rehighlight_from_block(block_id);
911 }
912}
913
914impl Default for TextDocument {
915 fn default() -> Self {
916 Self::new()
917 }
918}
919
920struct UndoBlockState {
924 id: u64,
925 position: i64,
926 text_length: i64,
927 plain_text: String,
928 format: BlockFormat,
929}
930
931fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
933 let all_blocks =
934 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
935 let mut states: Vec<UndoBlockState> = all_blocks
936 .into_iter()
937 .map(|b| UndoBlockState {
938 id: b.id,
939 position: b.document_position,
940 text_length: b.text_length,
941 plain_text: b.plain_text.clone(),
942 format: BlockFormat::from(&b),
943 })
944 .collect();
945 states.sort_by_key(|s| s.position);
946 states
947}
948
949fn build_doc_text(states: &[UndoBlockState]) -> String {
951 states
952 .iter()
953 .map(|s| s.plain_text.as_str())
954 .collect::<Vec<_>>()
955 .join("\n")
956}
957
958fn compute_text_edit(before: &str, after: &str) -> (usize, usize, usize) {
961 let before_chars: Vec<char> = before.chars().collect();
962 let after_chars: Vec<char> = after.chars().collect();
963
964 let prefix_len = before_chars
966 .iter()
967 .zip(after_chars.iter())
968 .take_while(|(a, b)| a == b)
969 .count();
970
971 let before_remaining = before_chars.len() - prefix_len;
973 let after_remaining = after_chars.len() - prefix_len;
974 let suffix_len = before_chars
975 .iter()
976 .rev()
977 .zip(after_chars.iter().rev())
978 .take(before_remaining.min(after_remaining))
979 .take_while(|(a, b)| a == b)
980 .count();
981
982 let removed = before_remaining - suffix_len;
983 let added = after_remaining - suffix_len;
984
985 (prefix_len, removed, added)
986}
987
988fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
991 let after = capture_block_state(inner);
992
993 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
995 before.iter().map(|s| (s.id, s)).collect();
996 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
997 after.iter().map(|s| (s.id, s)).collect();
998
999 let mut content_changed = false;
1001 let mut earliest_pos: Option<usize> = None;
1002 let mut old_end: usize = 0;
1003 let mut new_end: usize = 0;
1004 let mut blocks_affected: usize = 0;
1005
1006 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
1010 if let Some(before_state) = before_map.get(&after_state.id) {
1011 let text_changed = before_state.plain_text != after_state.plain_text
1012 || before_state.text_length != after_state.text_length;
1013 let format_changed = before_state.format != after_state.format;
1014
1015 if text_changed {
1016 content_changed = true;
1017 blocks_affected += 1;
1018 let pos = after_state.position.max(0) as usize;
1019 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1020 old_end = old_end.max(
1021 before_state.position.max(0) as usize
1022 + before_state.text_length.max(0) as usize,
1023 );
1024 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1025 } else if format_changed {
1026 let pos = after_state.position.max(0) as usize;
1027 let len = after_state.text_length.max(0) as usize;
1028 format_only_changes.push((pos, len));
1029 }
1030 } else {
1031 content_changed = true;
1033 blocks_affected += 1;
1034 let pos = after_state.position.max(0) as usize;
1035 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1036 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1037 }
1038 }
1039
1040 for before_state in before {
1042 if !after_map.contains_key(&before_state.id) {
1043 content_changed = true;
1044 blocks_affected += 1;
1045 let pos = before_state.position.max(0) as usize;
1046 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1047 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
1048 }
1049 }
1050
1051 if content_changed {
1052 let position = earliest_pos.unwrap_or(0);
1053 let chars_removed = old_end.saturating_sub(position);
1054 let chars_added = new_end.saturating_sub(position);
1055
1056 let before_text = build_doc_text(before);
1059 let after_text = build_doc_text(&after);
1060 let (edit_offset, precise_removed, precise_added) =
1061 compute_text_edit(&before_text, &after_text);
1062 if precise_removed > 0 || precise_added > 0 {
1063 inner.adjust_cursors(edit_offset, precise_removed, precise_added);
1064 }
1065
1066 inner.queue_event(DocumentEvent::ContentsChanged {
1067 position,
1068 chars_removed,
1069 chars_added,
1070 blocks_affected,
1071 });
1072 }
1073
1074 for (position, length) in format_only_changes {
1076 inner.queue_event(DocumentEvent::FormatChanged {
1077 position,
1078 length,
1079 kind: FormatChangeKind::Block,
1080 });
1081 }
1082}
1083
1084fn collect_frame_block_ids(
1090 inner: &TextDocumentInner,
1091 frame_id: frontend::common::types::EntityId,
1092) -> Option<Vec<u64>> {
1093 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
1094 .ok()
1095 .flatten()?;
1096
1097 if !frame_dto.child_order.is_empty() {
1098 let mut block_ids = Vec::new();
1099 for &entry in &frame_dto.child_order {
1100 if entry > 0 {
1101 block_ids.push(entry as u64);
1102 } else if entry < 0 {
1103 let sub_frame_id = (-entry) as u64;
1104 let sub_frame = frame_commands::get_frame(&inner.ctx, &sub_frame_id)
1105 .ok()
1106 .flatten();
1107 if let Some(ref sf) = sub_frame {
1108 if let Some(table_id) = sf.table {
1109 if let Some(table_dto) = table_commands::get_table(&inner.ctx, &table_id)
1112 .ok()
1113 .flatten()
1114 {
1115 let mut cell_dtos: Vec<_> = table_dto
1116 .cells
1117 .iter()
1118 .filter_map(|&cid| {
1119 table_cell_commands::get_table_cell(&inner.ctx, &cid)
1120 .ok()
1121 .flatten()
1122 })
1123 .collect();
1124 cell_dtos
1125 .sort_by(|a, b| a.row.cmp(&b.row).then(a.column.cmp(&b.column)));
1126 for cell_dto in &cell_dtos {
1127 if let Some(cf_id) = cell_dto.cell_frame
1128 && let Some(cf_ids) = collect_frame_block_ids(inner, cf_id)
1129 {
1130 block_ids.extend(cf_ids);
1131 }
1132 }
1133 }
1134 } else if let Some(sub_ids) = collect_frame_block_ids(inner, sub_frame_id) {
1135 block_ids.extend(sub_ids);
1136 }
1137 }
1138 }
1139 }
1140 Some(block_ids)
1141 } else {
1142 Some(frame_dto.blocks.to_vec())
1143 }
1144}
1145
1146pub(crate) fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
1147 let frames = frontend::commands::document_commands::get_document_relationship(
1149 &inner.ctx,
1150 &inner.document_id,
1151 &frontend::document::dtos::DocumentRelationshipField::Frames,
1152 )
1153 .unwrap_or_default();
1154
1155 frames.first().copied().unwrap_or(0)
1156}
1157
1158fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
1162 let Some(json) = data else {
1163 return (String::new(), 0.0, String::new());
1164 };
1165 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1166 let id = v["id"].as_str().unwrap_or_default().to_string();
1167 let pct = v["percentage"].as_f64().unwrap_or(0.0);
1168 let msg = v["message"].as_str().unwrap_or_default().to_string();
1169 (id, pct, msg)
1170}
1171
1172fn parse_id_data(data: &Option<String>) -> String {
1174 let Some(json) = data else {
1175 return String::new();
1176 };
1177 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1178 v["id"].as_str().unwrap_or_default().to_string()
1179}
1180
1181fn parse_failed_data(data: &Option<String>) -> (String, String) {
1183 let Some(json) = data else {
1184 return (String::new(), "unknown error".into());
1185 };
1186 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1187 let id = v["id"].as_str().unwrap_or_default().to_string();
1188 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
1189 (id, error)
1190}