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 find_element_at_position(&self, position: usize) -> Option<(u64, usize, usize)> {
375 let block_info = self.block_at(position).ok()?;
376 let block_start = block_info.start;
377 let offset_in_block = position.checked_sub(block_start)?;
378 let block = crate::text_block::TextBlock {
379 doc: std::sync::Arc::clone(&self.inner),
380 block_id: block_info.block_id,
381 };
382 let frags = block.fragments();
383 let mut last_text: Option<(u64, usize, usize, usize)> = None; for frag in &frags {
389 match frag {
390 crate::flow::FragmentContent::Text {
391 offset,
392 length,
393 element_id,
394 ..
395 } => {
396 let frag_start = *offset;
397 let frag_end = frag_start + *length;
398 if offset_in_block >= frag_start && offset_in_block < frag_end {
399 let abs_start = block_start + frag_start;
400 let offset_within = offset_in_block - frag_start;
401 return Some((*element_id, abs_start, offset_within));
402 }
403 if offset_in_block == frag_end {
406 last_text =
407 Some((*element_id, block_start + frag_start, frag_start, *length));
408 }
409 }
410 crate::flow::FragmentContent::Image {
411 offset, element_id, ..
412 } => {
413 if offset_in_block == *offset {
414 return Some((*element_id, block_start + offset, 0));
415 }
416 }
417 }
418 }
419 last_text.map(|(id, abs_start, _, length)| (id, abs_start, length))
422 }
423
424 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
426 let inner = self.inner.lock();
427 let dto = frontend::document_inspection::GetBlockAtPositionDto {
428 position: to_i64(position),
429 };
430 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
431 Ok(BlockInfo::from(&result))
432 }
433
434 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
436 let inner = self.inner.lock();
437 let dto = frontend::document_inspection::GetBlockAtPositionDto {
438 position: to_i64(position),
439 };
440 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
441 let block_id = block_info.block_id;
442 let block_id = block_id as u64;
443 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
444 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
445 Ok(BlockFormat::from(&block_dto))
446 }
447
448 pub fn flow(&self) -> Vec<crate::flow::FlowElement> {
459 let inner = self.inner.lock();
460 let main_frame_id = get_main_frame_id(&inner);
461 crate::text_frame::build_flow_elements(&inner, &self.inner, main_frame_id)
462 }
463
464 pub fn block_by_id(&self, block_id: usize) -> Option<crate::text_block::TextBlock> {
469 let inner = self.inner.lock();
470 let exists = frontend::commands::block_commands::get_block(&inner.ctx, &(block_id as u64))
471 .ok()
472 .flatten()
473 .is_some();
474
475 if exists {
476 Some(crate::text_block::TextBlock {
477 doc: self.inner.clone(),
478 block_id,
479 })
480 } else {
481 None
482 }
483 }
484
485 pub fn snapshot_block_at_position(
491 &self,
492 position: usize,
493 ) -> Option<crate::flow::BlockSnapshot> {
494 let inner = self.inner.lock();
495 let main_frame_id = get_main_frame_id(&inner);
496
497 let ordered_block_ids = collect_frame_block_ids(&inner, main_frame_id)?;
499
500 let pos = position as i64;
502 let mut running_pos: i64 = 0;
503 for &block_id in &ordered_block_ids {
504 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
505 .ok()
506 .flatten()?;
507 let block_end = running_pos + block_dto.text_length;
508 if pos >= running_pos && pos <= block_end {
509 return crate::text_block::build_block_snapshot_with_position(
510 &inner,
511 block_id,
512 Some(running_pos as usize),
513 );
514 }
515 running_pos = block_end + 1;
516 }
517
518 if let Some(&last_id) = ordered_block_ids.last() {
520 return crate::text_block::build_block_snapshot(&inner, last_id);
521 }
522 None
523 }
524
525 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
528 let inner = self.inner.lock();
529 let dto = frontend::document_inspection::GetBlockAtPositionDto {
530 position: to_i64(position),
531 };
532 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
533 Some(crate::text_block::TextBlock {
534 doc: self.inner.clone(),
535 block_id: result.block_id as usize,
536 })
537 }
538
539 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
548 let inner = self.inner.lock();
549 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
550 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
551 sorted.sort_by_key(|b| b.document_position);
552
553 sorted
554 .get(block_number)
555 .map(|b| crate::text_block::TextBlock {
556 doc: self.inner.clone(),
557 block_id: b.id as usize,
558 })
559 }
560
561 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
567 let inner = self.inner.lock();
568 let all_blocks =
569 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
570 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
571 sorted.sort_by_key(|b| b.document_position);
572 sorted
573 .iter()
574 .map(|b| crate::text_block::TextBlock {
575 doc: self.inner.clone(),
576 block_id: b.id as usize,
577 })
578 .collect()
579 }
580
581 pub fn blocks_in_range(
588 &self,
589 position: usize,
590 length: usize,
591 ) -> Vec<crate::text_block::TextBlock> {
592 let inner = self.inner.lock();
593 let all_blocks =
594 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
595 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
596 sorted.sort_by_key(|b| b.document_position);
597
598 let range_start = position;
599 let range_end = position + length;
600
601 sorted
602 .iter()
603 .filter(|b| {
604 let block_start = b.document_position.max(0) as usize;
605 let block_end = block_start + b.text_length.max(0) as usize;
606 if length == 0 {
608 range_start >= block_start && range_start < block_end
610 } else {
611 block_start < range_end && block_end > range_start
612 }
613 })
614 .map(|b| crate::text_block::TextBlock {
615 doc: self.inner.clone(),
616 block_id: b.id as usize,
617 })
618 .collect()
619 }
620
621 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
626 let inner = self.inner.lock();
627 let main_frame_id = get_main_frame_id(&inner);
628 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id);
629 crate::flow::FlowSnapshot { elements }
630 }
631
632 pub fn find(
636 &self,
637 query: &str,
638 from: usize,
639 options: &FindOptions,
640 ) -> Result<Option<FindMatch>> {
641 let inner = self.inner.lock();
642 let dto = options.to_find_text_dto(query, from);
643 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
644 Ok(convert::find_result_to_match(&result))
645 }
646
647 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
649 let inner = self.inner.lock();
650 let dto = options.to_find_all_dto(query);
651 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
652 Ok(convert::find_all_to_matches(&result))
653 }
654
655 pub fn replace_text(
657 &self,
658 query: &str,
659 replacement: &str,
660 replace_all: bool,
661 options: &FindOptions,
662 ) -> Result<usize> {
663 let (count, queued) = {
664 let mut inner = self.inner.lock();
665 let dto = options.to_replace_dto(query, replacement, replace_all);
666 let result =
667 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
668 let count = to_usize(result.replacements_count);
669 inner.invalidate_text_cache();
670 if count > 0 {
671 inner.modified = true;
672 inner.rehighlight_all();
673 inner.queue_event(DocumentEvent::ContentsChanged {
678 position: 0,
679 chars_removed: 0,
680 chars_added: 0,
681 blocks_affected: count,
682 });
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 }
689 (count, inner.take_queued_events())
690 };
691 crate::inner::dispatch_queued_events(queued);
692 Ok(count)
693 }
694
695 pub fn add_resource(
699 &self,
700 resource_type: ResourceType,
701 name: &str,
702 mime_type: &str,
703 data: &[u8],
704 ) -> Result<()> {
705 let mut inner = self.inner.lock();
706 let dto = frontend::resource::dtos::CreateResourceDto {
707 created_at: Default::default(),
708 updated_at: Default::default(),
709 resource_type,
710 name: name.into(),
711 url: String::new(),
712 mime_type: mime_type.into(),
713 data_base64: BASE64.encode(data),
714 };
715 let created = resource_commands::create_resource(
716 &inner.ctx,
717 Some(inner.stack_id),
718 &dto,
719 inner.document_id,
720 -1,
721 )?;
722 inner.resource_cache.insert(name.to_string(), created.id);
723 Ok(())
724 }
725
726 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
730 let mut inner = self.inner.lock();
731
732 if let Some(&id) = inner.resource_cache.get(name) {
734 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
735 let bytes = BASE64.decode(&r.data_base64)?;
736 return Ok(Some(bytes));
737 }
738 inner.resource_cache.remove(name);
740 }
741
742 let all = resource_commands::get_all_resource(&inner.ctx)?;
744 for r in &all {
745 if r.name == name {
746 inner.resource_cache.insert(name.to_string(), r.id);
747 let bytes = BASE64.decode(&r.data_base64)?;
748 return Ok(Some(bytes));
749 }
750 }
751 Ok(None)
752 }
753
754 pub fn undo(&self) -> Result<()> {
758 let queued = {
759 let mut inner = self.inner.lock();
760 let before = capture_block_state(&inner);
761 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
762 inner.invalidate_text_cache();
763 result?;
764 inner.rehighlight_all();
765 emit_undo_redo_change_events(&mut inner, &before);
766 inner.check_block_count_changed();
767 inner.check_flow_changed();
768 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
769 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
770 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
771 inner.take_queued_events()
772 };
773 crate::inner::dispatch_queued_events(queued);
774 Ok(())
775 }
776
777 pub fn redo(&self) -> Result<()> {
779 let queued = {
780 let mut inner = self.inner.lock();
781 let before = capture_block_state(&inner);
782 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
783 inner.invalidate_text_cache();
784 result?;
785 inner.rehighlight_all();
786 emit_undo_redo_change_events(&mut inner, &before);
787 inner.check_block_count_changed();
788 inner.check_flow_changed();
789 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
790 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
791 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
792 inner.take_queued_events()
793 };
794 crate::inner::dispatch_queued_events(queued);
795 Ok(())
796 }
797
798 pub fn can_undo(&self) -> bool {
800 let inner = self.inner.lock();
801 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
802 }
803
804 pub fn can_redo(&self) -> bool {
806 let inner = self.inner.lock();
807 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
808 }
809
810 pub fn clear_undo_redo(&self) {
812 let inner = self.inner.lock();
813 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
814 }
815
816 pub fn is_modified(&self) -> bool {
820 self.inner.lock().modified
821 }
822
823 pub fn set_modified(&self, modified: bool) {
825 let queued = {
826 let mut inner = self.inner.lock();
827 if inner.modified != modified {
828 inner.modified = modified;
829 inner.queue_event(DocumentEvent::ModificationChanged(modified));
830 }
831 inner.take_queued_events()
832 };
833 crate::inner::dispatch_queued_events(queued);
834 }
835
836 pub fn title(&self) -> String {
840 let inner = self.inner.lock();
841 document_commands::get_document(&inner.ctx, &inner.document_id)
842 .ok()
843 .flatten()
844 .map(|d| d.title)
845 .unwrap_or_default()
846 }
847
848 pub fn set_title(&self, title: &str) -> Result<()> {
850 let inner = self.inner.lock();
851 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
852 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
853 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
854 update.title = title.into();
855 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
856 Ok(())
857 }
858
859 pub fn text_direction(&self) -> TextDirection {
861 let inner = self.inner.lock();
862 document_commands::get_document(&inner.ctx, &inner.document_id)
863 .ok()
864 .flatten()
865 .map(|d| d.text_direction)
866 .unwrap_or(TextDirection::LeftToRight)
867 }
868
869 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
871 let inner = self.inner.lock();
872 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
873 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
874 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
875 update.text_direction = direction;
876 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
877 Ok(())
878 }
879
880 pub fn default_wrap_mode(&self) -> WrapMode {
882 let inner = self.inner.lock();
883 document_commands::get_document(&inner.ctx, &inner.document_id)
884 .ok()
885 .flatten()
886 .map(|d| d.default_wrap_mode)
887 .unwrap_or(WrapMode::WordWrap)
888 }
889
890 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
892 let inner = self.inner.lock();
893 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
894 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
895 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
896 update.default_wrap_mode = mode;
897 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
898 Ok(())
899 }
900
901 pub fn on_change<F>(&self, callback: F) -> Subscription
920 where
921 F: Fn(DocumentEvent) + Send + Sync + 'static,
922 {
923 let mut inner = self.inner.lock();
924 events::subscribe_inner(&mut inner, callback)
925 }
926
927 pub fn poll_events(&self) -> Vec<DocumentEvent> {
933 let mut inner = self.inner.lock();
934 inner.drain_poll_events()
935 }
936
937 pub fn set_syntax_highlighter(&self, highlighter: Option<Arc<dyn crate::SyntaxHighlighter>>) {
945 let mut inner = self.inner.lock();
946 match highlighter {
947 Some(hl) => {
948 inner.highlight = Some(crate::highlight::HighlightData {
949 highlighter: hl,
950 blocks: std::collections::HashMap::new(),
951 });
952 inner.rehighlight_all();
953 }
954 None => {
955 inner.highlight = None;
956 }
957 }
958 }
959
960 pub fn rehighlight(&self) {
965 let mut inner = self.inner.lock();
966 inner.rehighlight_all();
967 }
968
969 pub fn rehighlight_block(&self, block_id: usize) {
972 let mut inner = self.inner.lock();
973 inner.rehighlight_from_block(block_id);
974 }
975}
976
977impl Default for TextDocument {
978 fn default() -> Self {
979 Self::new()
980 }
981}
982
983struct UndoBlockState {
987 id: u64,
988 position: i64,
989 text_length: i64,
990 plain_text: String,
991 format: BlockFormat,
992}
993
994fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
996 let all_blocks =
997 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
998 let mut states: Vec<UndoBlockState> = all_blocks
999 .into_iter()
1000 .map(|b| UndoBlockState {
1001 id: b.id,
1002 position: b.document_position,
1003 text_length: b.text_length,
1004 plain_text: b.plain_text.clone(),
1005 format: BlockFormat::from(&b),
1006 })
1007 .collect();
1008 states.sort_by_key(|s| s.position);
1009 states
1010}
1011
1012fn build_doc_text(states: &[UndoBlockState]) -> String {
1014 states
1015 .iter()
1016 .map(|s| s.plain_text.as_str())
1017 .collect::<Vec<_>>()
1018 .join("\n")
1019}
1020
1021fn compute_text_edit(before: &str, after: &str) -> (usize, usize, usize) {
1024 let before_chars: Vec<char> = before.chars().collect();
1025 let after_chars: Vec<char> = after.chars().collect();
1026
1027 let prefix_len = before_chars
1029 .iter()
1030 .zip(after_chars.iter())
1031 .take_while(|(a, b)| a == b)
1032 .count();
1033
1034 let before_remaining = before_chars.len() - prefix_len;
1036 let after_remaining = after_chars.len() - prefix_len;
1037 let suffix_len = before_chars
1038 .iter()
1039 .rev()
1040 .zip(after_chars.iter().rev())
1041 .take(before_remaining.min(after_remaining))
1042 .take_while(|(a, b)| a == b)
1043 .count();
1044
1045 let removed = before_remaining - suffix_len;
1046 let added = after_remaining - suffix_len;
1047
1048 (prefix_len, removed, added)
1049}
1050
1051fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
1054 let after = capture_block_state(inner);
1055
1056 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
1058 before.iter().map(|s| (s.id, s)).collect();
1059 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
1060 after.iter().map(|s| (s.id, s)).collect();
1061
1062 let mut content_changed = false;
1064 let mut earliest_pos: Option<usize> = None;
1065 let mut old_end: usize = 0;
1066 let mut new_end: usize = 0;
1067 let mut blocks_affected: usize = 0;
1068
1069 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
1073 if let Some(before_state) = before_map.get(&after_state.id) {
1074 let text_changed = before_state.plain_text != after_state.plain_text
1075 || before_state.text_length != after_state.text_length;
1076 let format_changed = before_state.format != after_state.format;
1077
1078 if text_changed {
1079 content_changed = true;
1080 blocks_affected += 1;
1081 let pos = after_state.position.max(0) as usize;
1082 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1083 old_end = old_end.max(
1084 before_state.position.max(0) as usize
1085 + before_state.text_length.max(0) as usize,
1086 );
1087 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1088 } else if format_changed {
1089 let pos = after_state.position.max(0) as usize;
1090 let len = after_state.text_length.max(0) as usize;
1091 format_only_changes.push((pos, len));
1092 }
1093 } else {
1094 content_changed = true;
1096 blocks_affected += 1;
1097 let pos = after_state.position.max(0) as usize;
1098 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1099 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1100 }
1101 }
1102
1103 for before_state in before {
1105 if !after_map.contains_key(&before_state.id) {
1106 content_changed = true;
1107 blocks_affected += 1;
1108 let pos = before_state.position.max(0) as usize;
1109 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1110 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
1111 }
1112 }
1113
1114 if content_changed {
1115 let position = earliest_pos.unwrap_or(0);
1116 let chars_removed = old_end.saturating_sub(position);
1117 let chars_added = new_end.saturating_sub(position);
1118
1119 let before_text = build_doc_text(before);
1122 let after_text = build_doc_text(&after);
1123 let (edit_offset, precise_removed, precise_added) =
1124 compute_text_edit(&before_text, &after_text);
1125 if precise_removed > 0 || precise_added > 0 {
1126 inner.adjust_cursors(edit_offset, precise_removed, precise_added);
1127 }
1128
1129 inner.queue_event(DocumentEvent::ContentsChanged {
1130 position,
1131 chars_removed,
1132 chars_added,
1133 blocks_affected,
1134 });
1135 }
1136
1137 for (position, length) in format_only_changes {
1139 inner.queue_event(DocumentEvent::FormatChanged {
1140 position,
1141 length,
1142 kind: FormatChangeKind::Block,
1143 });
1144 }
1145}
1146
1147fn collect_frame_block_ids(
1153 inner: &TextDocumentInner,
1154 frame_id: frontend::common::types::EntityId,
1155) -> Option<Vec<u64>> {
1156 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
1157 .ok()
1158 .flatten()?;
1159
1160 if !frame_dto.child_order.is_empty() {
1161 let mut block_ids = Vec::new();
1162 for &entry in &frame_dto.child_order {
1163 if entry > 0 {
1164 block_ids.push(entry as u64);
1165 } else if entry < 0 {
1166 let sub_frame_id = (-entry) as u64;
1167 let sub_frame = frame_commands::get_frame(&inner.ctx, &sub_frame_id)
1168 .ok()
1169 .flatten();
1170 if let Some(ref sf) = sub_frame {
1171 if let Some(table_id) = sf.table {
1172 if let Some(table_dto) = table_commands::get_table(&inner.ctx, &table_id)
1175 .ok()
1176 .flatten()
1177 {
1178 let mut cell_dtos: Vec<_> = table_dto
1179 .cells
1180 .iter()
1181 .filter_map(|&cid| {
1182 table_cell_commands::get_table_cell(&inner.ctx, &cid)
1183 .ok()
1184 .flatten()
1185 })
1186 .collect();
1187 cell_dtos
1188 .sort_by(|a, b| a.row.cmp(&b.row).then(a.column.cmp(&b.column)));
1189 for cell_dto in &cell_dtos {
1190 if let Some(cf_id) = cell_dto.cell_frame
1191 && let Some(cf_ids) = collect_frame_block_ids(inner, cf_id)
1192 {
1193 block_ids.extend(cf_ids);
1194 }
1195 }
1196 }
1197 } else if let Some(sub_ids) = collect_frame_block_ids(inner, sub_frame_id) {
1198 block_ids.extend(sub_ids);
1199 }
1200 }
1201 }
1202 }
1203 Some(block_ids)
1204 } else {
1205 Some(frame_dto.blocks.to_vec())
1206 }
1207}
1208
1209pub(crate) fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
1210 let frames = frontend::commands::document_commands::get_document_relationship(
1212 &inner.ctx,
1213 &inner.document_id,
1214 &frontend::document::dtos::DocumentRelationshipField::Frames,
1215 )
1216 .unwrap_or_default();
1217
1218 frames.first().copied().unwrap_or(0)
1219}
1220
1221fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
1225 let Some(json) = data else {
1226 return (String::new(), 0.0, String::new());
1227 };
1228 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1229 let id = v["id"].as_str().unwrap_or_default().to_string();
1230 let pct = v["percentage"].as_f64().unwrap_or(0.0);
1231 let msg = v["message"].as_str().unwrap_or_default().to_string();
1232 (id, pct, msg)
1233}
1234
1235fn parse_id_data(data: &Option<String>) -> String {
1237 let Some(json) = data else {
1238 return String::new();
1239 };
1240 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1241 v["id"].as_str().unwrap_or_default().to_string()
1242}
1243
1244fn parse_failed_data(data: &Option<String>) -> (String, String) {
1246 let Some(json) = data else {
1247 return (String::new(), "unknown error".into());
1248 };
1249 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1250 let id = v["id"].as_str().unwrap_or_default().to_string();
1251 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
1252 (id, error)
1253}