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, 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 snapshot_block_at_position(
427 &self,
428 position: usize,
429 ) -> Option<crate::flow::BlockSnapshot> {
430 let inner = self.inner.lock();
431 let main_frame_id = get_main_frame_id(&inner);
432
433 let ordered_block_ids = collect_frame_block_ids(&inner, main_frame_id)?;
435
436 let pos = position as i64;
438 let mut running_pos: i64 = 0;
439 for &block_id in &ordered_block_ids {
440 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
441 .ok()
442 .flatten()?;
443 let block_end = running_pos + block_dto.text_length;
444 if pos >= running_pos && pos <= block_end {
445 return crate::text_block::build_block_snapshot_with_position(
446 &inner,
447 block_id,
448 Some(running_pos as usize),
449 );
450 }
451 running_pos = block_end + 1;
452 }
453
454 if let Some(&last_id) = ordered_block_ids.last() {
456 return crate::text_block::build_block_snapshot(&inner, last_id);
457 }
458 None
459 }
460
461 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
464 let inner = self.inner.lock();
465 let dto = frontend::document_inspection::GetBlockAtPositionDto {
466 position: to_i64(position),
467 };
468 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
469 Some(crate::text_block::TextBlock {
470 doc: self.inner.clone(),
471 block_id: result.block_id as usize,
472 })
473 }
474
475 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
484 let inner = self.inner.lock();
485 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
486 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
487 sorted.sort_by_key(|b| b.document_position);
488
489 sorted
490 .get(block_number)
491 .map(|b| crate::text_block::TextBlock {
492 doc: self.inner.clone(),
493 block_id: b.id as usize,
494 })
495 }
496
497 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
503 let inner = self.inner.lock();
504 let all_blocks =
505 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
506 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
507 sorted.sort_by_key(|b| b.document_position);
508 sorted
509 .iter()
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 blocks_in_range(
524 &self,
525 position: usize,
526 length: usize,
527 ) -> Vec<crate::text_block::TextBlock> {
528 let inner = self.inner.lock();
529 let all_blocks =
530 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
531 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
532 sorted.sort_by_key(|b| b.document_position);
533
534 let range_start = position;
535 let range_end = position + length;
536
537 sorted
538 .iter()
539 .filter(|b| {
540 let block_start = b.document_position.max(0) as usize;
541 let block_end = block_start + b.text_length.max(0) as usize;
542 if length == 0 {
544 range_start >= block_start && range_start < block_end
546 } else {
547 block_start < range_end && block_end > range_start
548 }
549 })
550 .map(|b| crate::text_block::TextBlock {
551 doc: self.inner.clone(),
552 block_id: b.id as usize,
553 })
554 .collect()
555 }
556
557 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
562 let inner = self.inner.lock();
563 let main_frame_id = get_main_frame_id(&inner);
564 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id);
565 crate::flow::FlowSnapshot { elements }
566 }
567
568 pub fn find(
572 &self,
573 query: &str,
574 from: usize,
575 options: &FindOptions,
576 ) -> Result<Option<FindMatch>> {
577 let inner = self.inner.lock();
578 let dto = options.to_find_text_dto(query, from);
579 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
580 Ok(convert::find_result_to_match(&result))
581 }
582
583 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
585 let inner = self.inner.lock();
586 let dto = options.to_find_all_dto(query);
587 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
588 Ok(convert::find_all_to_matches(&result))
589 }
590
591 pub fn replace_text(
593 &self,
594 query: &str,
595 replacement: &str,
596 replace_all: bool,
597 options: &FindOptions,
598 ) -> Result<usize> {
599 let (count, queued) = {
600 let mut inner = self.inner.lock();
601 let dto = options.to_replace_dto(query, replacement, replace_all);
602 let result =
603 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
604 let count = to_usize(result.replacements_count);
605 inner.invalidate_text_cache();
606 if count > 0 {
607 inner.modified = true;
608 inner.rehighlight_all();
609 inner.queue_event(DocumentEvent::ContentsChanged {
614 position: 0,
615 chars_removed: 0,
616 chars_added: 0,
617 blocks_affected: count,
618 });
619 inner.check_block_count_changed();
620 inner.check_flow_changed();
621 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
622 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
623 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
624 }
625 (count, inner.take_queued_events())
626 };
627 crate::inner::dispatch_queued_events(queued);
628 Ok(count)
629 }
630
631 pub fn add_resource(
635 &self,
636 resource_type: ResourceType,
637 name: &str,
638 mime_type: &str,
639 data: &[u8],
640 ) -> Result<()> {
641 let mut inner = self.inner.lock();
642 let dto = frontend::resource::dtos::CreateResourceDto {
643 created_at: Default::default(),
644 updated_at: Default::default(),
645 resource_type,
646 name: name.into(),
647 url: String::new(),
648 mime_type: mime_type.into(),
649 data_base64: BASE64.encode(data),
650 };
651 let created = resource_commands::create_resource(
652 &inner.ctx,
653 Some(inner.stack_id),
654 &dto,
655 inner.document_id,
656 -1,
657 )?;
658 inner.resource_cache.insert(name.to_string(), created.id);
659 Ok(())
660 }
661
662 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
666 let mut inner = self.inner.lock();
667
668 if let Some(&id) = inner.resource_cache.get(name) {
670 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
671 let bytes = BASE64.decode(&r.data_base64)?;
672 return Ok(Some(bytes));
673 }
674 inner.resource_cache.remove(name);
676 }
677
678 let all = resource_commands::get_all_resource(&inner.ctx)?;
680 for r in &all {
681 if r.name == name {
682 inner.resource_cache.insert(name.to_string(), r.id);
683 let bytes = BASE64.decode(&r.data_base64)?;
684 return Ok(Some(bytes));
685 }
686 }
687 Ok(None)
688 }
689
690 pub fn undo(&self) -> Result<()> {
694 let queued = {
695 let mut inner = self.inner.lock();
696 let before = capture_block_state(&inner);
697 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
698 inner.invalidate_text_cache();
699 result?;
700 inner.rehighlight_all();
701 emit_undo_redo_change_events(&mut inner, &before);
702 inner.check_block_count_changed();
703 inner.check_flow_changed();
704 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
705 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
706 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
707 inner.take_queued_events()
708 };
709 crate::inner::dispatch_queued_events(queued);
710 Ok(())
711 }
712
713 pub fn redo(&self) -> Result<()> {
715 let queued = {
716 let mut inner = self.inner.lock();
717 let before = capture_block_state(&inner);
718 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
719 inner.invalidate_text_cache();
720 result?;
721 inner.rehighlight_all();
722 emit_undo_redo_change_events(&mut inner, &before);
723 inner.check_block_count_changed();
724 inner.check_flow_changed();
725 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
726 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
727 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
728 inner.take_queued_events()
729 };
730 crate::inner::dispatch_queued_events(queued);
731 Ok(())
732 }
733
734 pub fn can_undo(&self) -> bool {
736 let inner = self.inner.lock();
737 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
738 }
739
740 pub fn can_redo(&self) -> bool {
742 let inner = self.inner.lock();
743 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
744 }
745
746 pub fn clear_undo_redo(&self) {
748 let inner = self.inner.lock();
749 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
750 }
751
752 pub fn is_modified(&self) -> bool {
756 self.inner.lock().modified
757 }
758
759 pub fn set_modified(&self, modified: bool) {
761 let queued = {
762 let mut inner = self.inner.lock();
763 if inner.modified != modified {
764 inner.modified = modified;
765 inner.queue_event(DocumentEvent::ModificationChanged(modified));
766 }
767 inner.take_queued_events()
768 };
769 crate::inner::dispatch_queued_events(queued);
770 }
771
772 pub fn title(&self) -> String {
776 let inner = self.inner.lock();
777 document_commands::get_document(&inner.ctx, &inner.document_id)
778 .ok()
779 .flatten()
780 .map(|d| d.title)
781 .unwrap_or_default()
782 }
783
784 pub fn set_title(&self, title: &str) -> Result<()> {
786 let inner = self.inner.lock();
787 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
788 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
789 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
790 update.title = title.into();
791 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
792 Ok(())
793 }
794
795 pub fn text_direction(&self) -> TextDirection {
797 let inner = self.inner.lock();
798 document_commands::get_document(&inner.ctx, &inner.document_id)
799 .ok()
800 .flatten()
801 .map(|d| d.text_direction)
802 .unwrap_or(TextDirection::LeftToRight)
803 }
804
805 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
807 let inner = self.inner.lock();
808 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
809 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
810 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
811 update.text_direction = direction;
812 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
813 Ok(())
814 }
815
816 pub fn default_wrap_mode(&self) -> WrapMode {
818 let inner = self.inner.lock();
819 document_commands::get_document(&inner.ctx, &inner.document_id)
820 .ok()
821 .flatten()
822 .map(|d| d.default_wrap_mode)
823 .unwrap_or(WrapMode::WordWrap)
824 }
825
826 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
828 let inner = self.inner.lock();
829 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
830 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
831 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
832 update.default_wrap_mode = mode;
833 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
834 Ok(())
835 }
836
837 pub fn on_change<F>(&self, callback: F) -> Subscription
856 where
857 F: Fn(DocumentEvent) + Send + Sync + 'static,
858 {
859 let mut inner = self.inner.lock();
860 events::subscribe_inner(&mut inner, callback)
861 }
862
863 pub fn poll_events(&self) -> Vec<DocumentEvent> {
869 let mut inner = self.inner.lock();
870 inner.drain_poll_events()
871 }
872
873 pub fn set_syntax_highlighter(&self, highlighter: Option<Arc<dyn crate::SyntaxHighlighter>>) {
881 let mut inner = self.inner.lock();
882 match highlighter {
883 Some(hl) => {
884 inner.highlight = Some(crate::highlight::HighlightData {
885 highlighter: hl,
886 blocks: std::collections::HashMap::new(),
887 });
888 inner.rehighlight_all();
889 }
890 None => {
891 inner.highlight = None;
892 }
893 }
894 }
895
896 pub fn rehighlight(&self) {
901 let mut inner = self.inner.lock();
902 inner.rehighlight_all();
903 }
904
905 pub fn rehighlight_block(&self, block_id: usize) {
908 let mut inner = self.inner.lock();
909 inner.rehighlight_from_block(block_id);
910 }
911}
912
913impl Default for TextDocument {
914 fn default() -> Self {
915 Self::new()
916 }
917}
918
919struct UndoBlockState {
923 id: u64,
924 position: i64,
925 text_length: i64,
926 plain_text: String,
927 format: BlockFormat,
928}
929
930fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
932 let all_blocks =
933 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
934 let mut states: Vec<UndoBlockState> = all_blocks
935 .into_iter()
936 .map(|b| UndoBlockState {
937 id: b.id,
938 position: b.document_position,
939 text_length: b.text_length,
940 plain_text: b.plain_text.clone(),
941 format: BlockFormat::from(&b),
942 })
943 .collect();
944 states.sort_by_key(|s| s.position);
945 states
946}
947
948fn build_doc_text(states: &[UndoBlockState]) -> String {
950 states
951 .iter()
952 .map(|s| s.plain_text.as_str())
953 .collect::<Vec<_>>()
954 .join("\n")
955}
956
957fn compute_text_edit(before: &str, after: &str) -> (usize, usize, usize) {
960 let before_chars: Vec<char> = before.chars().collect();
961 let after_chars: Vec<char> = after.chars().collect();
962
963 let prefix_len = before_chars
965 .iter()
966 .zip(after_chars.iter())
967 .take_while(|(a, b)| a == b)
968 .count();
969
970 let before_remaining = before_chars.len() - prefix_len;
972 let after_remaining = after_chars.len() - prefix_len;
973 let suffix_len = before_chars
974 .iter()
975 .rev()
976 .zip(after_chars.iter().rev())
977 .take(before_remaining.min(after_remaining))
978 .take_while(|(a, b)| a == b)
979 .count();
980
981 let removed = before_remaining - suffix_len;
982 let added = after_remaining - suffix_len;
983
984 (prefix_len, removed, added)
985}
986
987fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
990 let after = capture_block_state(inner);
991
992 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
994 before.iter().map(|s| (s.id, s)).collect();
995 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
996 after.iter().map(|s| (s.id, s)).collect();
997
998 let mut content_changed = false;
1000 let mut earliest_pos: Option<usize> = None;
1001 let mut old_end: usize = 0;
1002 let mut new_end: usize = 0;
1003 let mut blocks_affected: usize = 0;
1004
1005 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
1009 if let Some(before_state) = before_map.get(&after_state.id) {
1010 let text_changed = before_state.plain_text != after_state.plain_text
1011 || before_state.text_length != after_state.text_length;
1012 let format_changed = before_state.format != after_state.format;
1013
1014 if text_changed {
1015 content_changed = true;
1016 blocks_affected += 1;
1017 let pos = after_state.position.max(0) as usize;
1018 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1019 old_end = old_end.max(
1020 before_state.position.max(0) as usize
1021 + before_state.text_length.max(0) as usize,
1022 );
1023 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1024 } else if format_changed {
1025 let pos = after_state.position.max(0) as usize;
1026 let len = after_state.text_length.max(0) as usize;
1027 format_only_changes.push((pos, len));
1028 }
1029 } else {
1030 content_changed = true;
1032 blocks_affected += 1;
1033 let pos = after_state.position.max(0) as usize;
1034 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1035 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1036 }
1037 }
1038
1039 for before_state in before {
1041 if !after_map.contains_key(&before_state.id) {
1042 content_changed = true;
1043 blocks_affected += 1;
1044 let pos = before_state.position.max(0) as usize;
1045 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1046 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
1047 }
1048 }
1049
1050 if content_changed {
1051 let position = earliest_pos.unwrap_or(0);
1052 let chars_removed = old_end.saturating_sub(position);
1053 let chars_added = new_end.saturating_sub(position);
1054
1055 let before_text = build_doc_text(before);
1058 let after_text = build_doc_text(&after);
1059 let (edit_offset, precise_removed, precise_added) =
1060 compute_text_edit(&before_text, &after_text);
1061 if precise_removed > 0 || precise_added > 0 {
1062 inner.adjust_cursors(edit_offset, precise_removed, precise_added);
1063 }
1064
1065 inner.queue_event(DocumentEvent::ContentsChanged {
1066 position,
1067 chars_removed,
1068 chars_added,
1069 blocks_affected,
1070 });
1071 }
1072
1073 for (position, length) in format_only_changes {
1075 inner.queue_event(DocumentEvent::FormatChanged {
1076 position,
1077 length,
1078 kind: FormatChangeKind::Block,
1079 });
1080 }
1081}
1082
1083fn collect_frame_block_ids(
1089 inner: &TextDocumentInner,
1090 frame_id: frontend::common::types::EntityId,
1091) -> Option<Vec<u64>> {
1092 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
1093 .ok()
1094 .flatten()?;
1095
1096 if !frame_dto.child_order.is_empty() {
1097 let mut block_ids = Vec::new();
1098 for &entry in &frame_dto.child_order {
1099 if entry > 0 {
1100 block_ids.push(entry as u64);
1101 } else if entry < 0 {
1102 let sub_frame_id = (-entry) as u64;
1103 if let Some(sub_ids) = collect_frame_block_ids(inner, sub_frame_id) {
1104 block_ids.extend(sub_ids);
1105 }
1106 }
1107 }
1108 Some(block_ids)
1109 } else {
1110 Some(frame_dto.blocks.to_vec())
1111 }
1112}
1113
1114fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
1115 let frames = frontend::commands::document_commands::get_document_relationship(
1117 &inner.ctx,
1118 &inner.document_id,
1119 &frontend::document::dtos::DocumentRelationshipField::Frames,
1120 )
1121 .unwrap_or_default();
1122
1123 frames.first().copied().unwrap_or(0)
1124}
1125
1126fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
1130 let Some(json) = data else {
1131 return (String::new(), 0.0, String::new());
1132 };
1133 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1134 let id = v["id"].as_str().unwrap_or_default().to_string();
1135 let pct = v["percentage"].as_f64().unwrap_or(0.0);
1136 let msg = v["message"].as_str().unwrap_or_default().to_string();
1137 (id, pct, msg)
1138}
1139
1140fn parse_id_data(data: &Option<String>) -> String {
1142 let Some(json) = data else {
1143 return String::new();
1144 };
1145 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1146 v["id"].as_str().unwrap_or_default().to_string()
1147}
1148
1149fn parse_failed_data(data: &Option<String>) -> (String, String) {
1151 let Some(json) = data else {
1152 return (String::new(), "unknown error".into());
1153 };
1154 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1155 let id = v["id"].as_str().unwrap_or_default().to_string();
1156 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
1157 (id, error)
1158}