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 let mut locked = inner.lock();
72
73 let w = weak.clone();
75 let progress_tok =
76 locked
77 .event_client
78 .subscribe(Origin::LongOperation(LOE::Progress), move |event| {
79 if let Some(inner) = w.upgrade() {
80 let (op_id, percent, message) = parse_progress_data(&event.data);
81 let mut inner = inner.lock();
82 inner.queue_event(DocumentEvent::LongOperationProgress {
83 operation_id: op_id,
84 percent,
85 message,
86 });
87 }
88 });
89
90 let w = weak.clone();
92 let completed_tok =
93 locked
94 .event_client
95 .subscribe(Origin::LongOperation(LOE::Completed), move |event| {
96 if let Some(inner) = w.upgrade() {
97 let op_id = parse_id_data(&event.data);
98 let mut inner = inner.lock();
99 inner.queue_event(DocumentEvent::DocumentReset);
100 inner.check_block_count_changed();
101 inner.reset_cached_child_order();
102 inner.queue_event(DocumentEvent::LongOperationFinished {
103 operation_id: op_id,
104 success: true,
105 error: None,
106 });
107 }
108 });
109
110 let w = weak.clone();
112 let cancelled_tok =
113 locked
114 .event_client
115 .subscribe(Origin::LongOperation(LOE::Cancelled), move |event| {
116 if let Some(inner) = w.upgrade() {
117 let op_id = parse_id_data(&event.data);
118 let mut inner = inner.lock();
119 inner.queue_event(DocumentEvent::LongOperationFinished {
120 operation_id: op_id,
121 success: false,
122 error: Some("cancelled".into()),
123 });
124 }
125 });
126
127 let failed_tok =
129 locked
130 .event_client
131 .subscribe(Origin::LongOperation(LOE::Failed), move |event| {
132 if let Some(inner) = weak.upgrade() {
133 let (op_id, error) = parse_failed_data(&event.data);
134 let mut inner = inner.lock();
135 inner.queue_event(DocumentEvent::LongOperationFinished {
136 operation_id: op_id,
137 success: false,
138 error: Some(error),
139 });
140 }
141 });
142
143 locked.long_op_subscriptions.extend([
144 progress_tok,
145 completed_tok,
146 cancelled_tok,
147 failed_tok,
148 ]);
149 }
150
151 pub fn set_plain_text(&self, text: &str) -> Result<()> {
155 let queued = {
156 let mut inner = self.inner.lock();
157 let dto = frontend::document_io::ImportPlainTextDto {
158 plain_text: text.into(),
159 };
160 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
161 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
162 inner.invalidate_text_cache();
163 inner.rehighlight_all();
164 inner.queue_event(DocumentEvent::DocumentReset);
165 inner.check_block_count_changed();
166 inner.reset_cached_child_order();
167 inner.queue_event(DocumentEvent::UndoRedoChanged {
168 can_undo: false,
169 can_redo: false,
170 });
171 inner.take_queued_events()
172 };
173 crate::inner::dispatch_queued_events(queued);
174 Ok(())
175 }
176
177 pub fn to_plain_text(&self) -> Result<String> {
179 let mut inner = self.inner.lock();
180 Ok(inner.plain_text()?.to_string())
181 }
182
183 pub fn set_markdown(&self, markdown: &str) -> Result<Operation<MarkdownImportResult>> {
187 let mut inner = self.inner.lock();
188 inner.invalidate_text_cache();
189 let dto = frontend::document_io::ImportMarkdownDto {
190 markdown_text: markdown.into(),
191 };
192 let op_id = document_io_commands::import_markdown(&inner.ctx, &dto)?;
193 Ok(Operation::new(
194 op_id,
195 &inner.ctx,
196 Box::new(|ctx, id| {
197 document_io_commands::get_import_markdown_result(ctx, id)
198 .ok()
199 .flatten()
200 .map(|r| {
201 Ok(MarkdownImportResult {
202 block_count: to_usize(r.block_count),
203 })
204 })
205 }),
206 ))
207 }
208
209 pub fn to_markdown(&self) -> Result<String> {
211 let inner = self.inner.lock();
212 let dto = document_io_commands::export_markdown(&inner.ctx)?;
213 Ok(dto.markdown_text)
214 }
215
216 pub fn set_html(&self, html: &str) -> Result<Operation<HtmlImportResult>> {
220 let mut inner = self.inner.lock();
221 inner.invalidate_text_cache();
222 let dto = frontend::document_io::ImportHtmlDto {
223 html_text: html.into(),
224 };
225 let op_id = document_io_commands::import_html(&inner.ctx, &dto)?;
226 Ok(Operation::new(
227 op_id,
228 &inner.ctx,
229 Box::new(|ctx, id| {
230 document_io_commands::get_import_html_result(ctx, id)
231 .ok()
232 .flatten()
233 .map(|r| {
234 Ok(HtmlImportResult {
235 block_count: to_usize(r.block_count),
236 })
237 })
238 }),
239 ))
240 }
241
242 pub fn to_html(&self) -> Result<String> {
244 let inner = self.inner.lock();
245 let dto = document_io_commands::export_html(&inner.ctx)?;
246 Ok(dto.html_text)
247 }
248
249 pub fn to_latex(&self, document_class: &str, include_preamble: bool) -> Result<String> {
251 let inner = self.inner.lock();
252 let dto = frontend::document_io::ExportLatexDto {
253 document_class: document_class.into(),
254 include_preamble,
255 };
256 let result = document_io_commands::export_latex(&inner.ctx, &dto)?;
257 Ok(result.latex_text)
258 }
259
260 pub fn to_docx(&self, output_path: &str) -> Result<Operation<DocxExportResult>> {
264 let inner = self.inner.lock();
265 let dto = frontend::document_io::ExportDocxDto {
266 output_path: output_path.into(),
267 };
268 let op_id = document_io_commands::export_docx(&inner.ctx, &dto)?;
269 Ok(Operation::new(
270 op_id,
271 &inner.ctx,
272 Box::new(|ctx, id| {
273 document_io_commands::get_export_docx_result(ctx, id)
274 .ok()
275 .flatten()
276 .map(|r| {
277 Ok(DocxExportResult {
278 file_path: r.file_path,
279 paragraph_count: to_usize(r.paragraph_count),
280 })
281 })
282 }),
283 ))
284 }
285
286 pub fn clear(&self) -> Result<()> {
288 let queued = {
289 let mut inner = self.inner.lock();
290 let dto = frontend::document_io::ImportPlainTextDto {
291 plain_text: String::new(),
292 };
293 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
294 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
295 inner.invalidate_text_cache();
296 inner.rehighlight_all();
297 inner.queue_event(DocumentEvent::DocumentReset);
298 inner.check_block_count_changed();
299 inner.reset_cached_child_order();
300 inner.queue_event(DocumentEvent::UndoRedoChanged {
301 can_undo: false,
302 can_redo: false,
303 });
304 inner.take_queued_events()
305 };
306 crate::inner::dispatch_queued_events(queued);
307 Ok(())
308 }
309
310 pub fn cursor(&self) -> TextCursor {
314 self.cursor_at(0)
315 }
316
317 pub fn cursor_at(&self, position: usize) -> TextCursor {
319 let data = {
320 let mut inner = self.inner.lock();
321 inner.register_cursor(position)
322 };
323 TextCursor {
324 doc: self.inner.clone(),
325 data,
326 }
327 }
328
329 pub fn stats(&self) -> DocumentStats {
333 let inner = self.inner.lock();
334 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
335 .expect("get_document_stats should not fail");
336 DocumentStats::from(&dto)
337 }
338
339 pub fn character_count(&self) -> usize {
341 let inner = self.inner.lock();
342 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
343 .expect("get_document_stats should not fail");
344 to_usize(dto.character_count)
345 }
346
347 pub fn block_count(&self) -> usize {
349 let inner = self.inner.lock();
350 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
351 .expect("get_document_stats should not fail");
352 to_usize(dto.block_count)
353 }
354
355 pub fn is_empty(&self) -> bool {
357 self.character_count() == 0
358 }
359
360 pub fn text_at(&self, position: usize, length: usize) -> Result<String> {
362 let inner = self.inner.lock();
363 let dto = frontend::document_inspection::GetTextAtPositionDto {
364 position: to_i64(position),
365 length: to_i64(length),
366 };
367 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
368 Ok(result.text)
369 }
370
371 pub fn find_element_at_position(&self, position: usize) -> Option<(u64, usize, usize)> {
385 let block_info = self.block_at(position).ok()?;
386 let block_start = block_info.start;
387 let offset_in_block = position.checked_sub(block_start)?;
388 let block = crate::text_block::TextBlock {
389 doc: std::sync::Arc::clone(&self.inner),
390 block_id: block_info.block_id,
391 };
392 let frags = block.fragments();
393 let mut last_text: Option<(u64, usize, usize, usize)> = None; for frag in &frags {
399 match frag {
400 crate::flow::FragmentContent::Text {
401 offset,
402 length,
403 element_id,
404 ..
405 } => {
406 let frag_start = *offset;
407 let frag_end = frag_start + *length;
408 if offset_in_block >= frag_start && offset_in_block < frag_end {
409 let abs_start = block_start + frag_start;
410 let offset_within = offset_in_block - frag_start;
411 return Some((*element_id, abs_start, offset_within));
412 }
413 if offset_in_block == frag_end {
416 last_text =
417 Some((*element_id, block_start + frag_start, frag_start, *length));
418 }
419 }
420 crate::flow::FragmentContent::Image {
421 offset, element_id, ..
422 } => {
423 if offset_in_block == *offset {
424 return Some((*element_id, block_start + offset, 0));
425 }
426 }
427 }
428 }
429 last_text.map(|(id, abs_start, _, length)| (id, abs_start, length))
432 }
433
434 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
436 let inner = self.inner.lock();
437 let dto = frontend::document_inspection::GetBlockAtPositionDto {
438 position: to_i64(position),
439 };
440 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
441 Ok(BlockInfo::from(&result))
442 }
443
444 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
446 let inner = self.inner.lock();
447 let dto = frontend::document_inspection::GetBlockAtPositionDto {
448 position: to_i64(position),
449 };
450 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
451 let block_id = block_info.block_id;
452 let block_id = block_id as u64;
453 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
454 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
455 Ok(BlockFormat::from(&block_dto))
456 }
457
458 pub fn flow(&self) -> Vec<crate::flow::FlowElement> {
469 let inner = self.inner.lock();
470 let main_frame_id = get_main_frame_id(&inner);
471 crate::text_frame::build_flow_elements(&inner, &self.inner, main_frame_id)
472 }
473
474 pub fn block_by_id(&self, block_id: usize) -> Option<crate::text_block::TextBlock> {
479 let inner = self.inner.lock();
480 let exists = frontend::commands::block_commands::get_block(&inner.ctx, &(block_id as u64))
481 .ok()
482 .flatten()
483 .is_some();
484
485 if exists {
486 Some(crate::text_block::TextBlock {
487 doc: self.inner.clone(),
488 block_id,
489 })
490 } else {
491 None
492 }
493 }
494
495 pub fn snapshot_block_at_position(
501 &self,
502 position: usize,
503 ) -> Option<crate::flow::BlockSnapshot> {
504 let inner = self.inner.lock();
505 let main_frame_id = get_main_frame_id(&inner);
506
507 let ordered_block_ids = collect_frame_block_ids(&inner, main_frame_id)?;
509
510 let pos = position as i64;
512 let mut running_pos: i64 = 0;
513 for &block_id in &ordered_block_ids {
514 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
515 .ok()
516 .flatten()?;
517 let block_end = running_pos + block_dto.text_length;
518 if pos >= running_pos && pos <= block_end {
519 return crate::text_block::build_block_snapshot_with_position(
520 &inner,
521 block_id,
522 Some(running_pos as usize),
523 );
524 }
525 running_pos = block_end + 1;
526 }
527
528 if let Some(&last_id) = ordered_block_ids.last() {
530 return crate::text_block::build_block_snapshot(&inner, last_id);
531 }
532 None
533 }
534
535 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
538 let inner = self.inner.lock();
539 let dto = frontend::document_inspection::GetBlockAtPositionDto {
540 position: to_i64(position),
541 };
542 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
543 Some(crate::text_block::TextBlock {
544 doc: self.inner.clone(),
545 block_id: result.block_id as usize,
546 })
547 }
548
549 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
558 let inner = self.inner.lock();
559 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
560 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
561 sorted.sort_by_key(|b| b.document_position);
562
563 sorted
564 .get(block_number)
565 .map(|b| crate::text_block::TextBlock {
566 doc: self.inner.clone(),
567 block_id: b.id as usize,
568 })
569 }
570
571 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
577 let inner = self.inner.lock();
578 let all_blocks =
579 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
580 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
581 sorted.sort_by_key(|b| b.document_position);
582 sorted
583 .iter()
584 .map(|b| crate::text_block::TextBlock {
585 doc: self.inner.clone(),
586 block_id: b.id as usize,
587 })
588 .collect()
589 }
590
591 pub fn blocks_in_range(
598 &self,
599 position: usize,
600 length: usize,
601 ) -> Vec<crate::text_block::TextBlock> {
602 let inner = self.inner.lock();
603 let all_blocks =
604 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
605 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
606 sorted.sort_by_key(|b| b.document_position);
607
608 let range_start = position;
609 let range_end = position + length;
610
611 sorted
612 .iter()
613 .filter(|b| {
614 let block_start = b.document_position.max(0) as usize;
615 let block_end = block_start + b.text_length.max(0) as usize;
616 if length == 0 {
618 range_start >= block_start && range_start < block_end
620 } else {
621 block_start < range_end && block_end > range_start
622 }
623 })
624 .map(|b| crate::text_block::TextBlock {
625 doc: self.inner.clone(),
626 block_id: b.id as usize,
627 })
628 .collect()
629 }
630
631 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
636 let inner = self.inner.lock();
637 let main_frame_id = get_main_frame_id(&inner);
638 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id);
639 crate::flow::FlowSnapshot { elements }
640 }
641
642 pub fn find(
646 &self,
647 query: &str,
648 from: usize,
649 options: &FindOptions,
650 ) -> Result<Option<FindMatch>> {
651 let inner = self.inner.lock();
652 let dto = options.to_find_text_dto(query, from);
653 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
654 Ok(convert::find_result_to_match(&result))
655 }
656
657 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
659 let inner = self.inner.lock();
660 let dto = options.to_find_all_dto(query);
661 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
662 Ok(convert::find_all_to_matches(&result))
663 }
664
665 pub fn replace_text(
667 &self,
668 query: &str,
669 replacement: &str,
670 replace_all: bool,
671 options: &FindOptions,
672 ) -> Result<usize> {
673 let (count, queued) = {
674 let mut inner = self.inner.lock();
675 let dto = options.to_replace_dto(query, replacement, replace_all);
676 let result =
677 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
678 let count = to_usize(result.replacements_count);
679 inner.invalidate_text_cache();
680 if count > 0 {
681 inner.modified = true;
682 inner.rehighlight_all();
683 inner.queue_event(DocumentEvent::ContentsChanged {
688 position: 0,
689 chars_removed: 0,
690 chars_added: 0,
691 blocks_affected: count,
692 });
693 inner.check_block_count_changed();
694 inner.check_flow_changed();
695 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
696 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
697 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
698 }
699 (count, inner.take_queued_events())
700 };
701 crate::inner::dispatch_queued_events(queued);
702 Ok(count)
703 }
704
705 pub fn add_resource(
709 &self,
710 resource_type: ResourceType,
711 name: &str,
712 mime_type: &str,
713 data: &[u8],
714 ) -> Result<()> {
715 let mut inner = self.inner.lock();
716 let dto = frontend::resource::dtos::CreateResourceDto {
717 created_at: Default::default(),
718 updated_at: Default::default(),
719 resource_type,
720 name: name.into(),
721 url: String::new(),
722 mime_type: mime_type.into(),
723 data_base64: BASE64.encode(data),
724 };
725 let created = resource_commands::create_resource(
726 &inner.ctx,
727 Some(inner.stack_id),
728 &dto,
729 inner.document_id,
730 -1,
731 )?;
732 inner.resource_cache.insert(name.to_string(), created.id);
733 Ok(())
734 }
735
736 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
740 let mut inner = self.inner.lock();
741
742 if let Some(&id) = inner.resource_cache.get(name) {
744 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
745 let bytes = BASE64.decode(&r.data_base64)?;
746 return Ok(Some(bytes));
747 }
748 inner.resource_cache.remove(name);
750 }
751
752 let all = resource_commands::get_all_resource(&inner.ctx)?;
754 for r in &all {
755 if r.name == name {
756 inner.resource_cache.insert(name.to_string(), r.id);
757 let bytes = BASE64.decode(&r.data_base64)?;
758 return Ok(Some(bytes));
759 }
760 }
761 Ok(None)
762 }
763
764 pub fn undo(&self) -> Result<()> {
768 let queued = {
769 let mut inner = self.inner.lock();
770 let before = capture_block_state(&inner);
771 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
772 inner.invalidate_text_cache();
773 result?;
774 inner.rehighlight_all();
775 emit_undo_redo_change_events(&mut inner, &before);
776 inner.check_block_count_changed();
777 inner.check_flow_changed();
778 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
779 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
780 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
781 inner.take_queued_events()
782 };
783 crate::inner::dispatch_queued_events(queued);
784 Ok(())
785 }
786
787 pub fn redo(&self) -> Result<()> {
789 let queued = {
790 let mut inner = self.inner.lock();
791 let before = capture_block_state(&inner);
792 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
793 inner.invalidate_text_cache();
794 result?;
795 inner.rehighlight_all();
796 emit_undo_redo_change_events(&mut inner, &before);
797 inner.check_block_count_changed();
798 inner.check_flow_changed();
799 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
800 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
801 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
802 inner.take_queued_events()
803 };
804 crate::inner::dispatch_queued_events(queued);
805 Ok(())
806 }
807
808 pub fn can_undo(&self) -> bool {
810 let inner = self.inner.lock();
811 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
812 }
813
814 pub fn can_redo(&self) -> bool {
816 let inner = self.inner.lock();
817 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
818 }
819
820 pub fn clear_undo_redo(&self) {
822 let inner = self.inner.lock();
823 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
824 }
825
826 pub fn is_modified(&self) -> bool {
830 self.inner.lock().modified
831 }
832
833 pub fn set_modified(&self, modified: bool) {
835 let queued = {
836 let mut inner = self.inner.lock();
837 if inner.modified != modified {
838 inner.modified = modified;
839 inner.queue_event(DocumentEvent::ModificationChanged(modified));
840 }
841 inner.take_queued_events()
842 };
843 crate::inner::dispatch_queued_events(queued);
844 }
845
846 pub fn title(&self) -> String {
850 let inner = self.inner.lock();
851 document_commands::get_document(&inner.ctx, &inner.document_id)
852 .ok()
853 .flatten()
854 .map(|d| d.title)
855 .unwrap_or_default()
856 }
857
858 pub fn set_title(&self, title: &str) -> Result<()> {
860 let inner = self.inner.lock();
861 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
862 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
863 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
864 update.title = title.into();
865 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
866 Ok(())
867 }
868
869 pub fn text_direction(&self) -> TextDirection {
871 let inner = self.inner.lock();
872 document_commands::get_document(&inner.ctx, &inner.document_id)
873 .ok()
874 .flatten()
875 .map(|d| d.text_direction)
876 .unwrap_or(TextDirection::LeftToRight)
877 }
878
879 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
881 let inner = self.inner.lock();
882 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
883 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
884 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
885 update.text_direction = direction;
886 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
887 Ok(())
888 }
889
890 pub fn default_wrap_mode(&self) -> WrapMode {
892 let inner = self.inner.lock();
893 document_commands::get_document(&inner.ctx, &inner.document_id)
894 .ok()
895 .flatten()
896 .map(|d| d.default_wrap_mode)
897 .unwrap_or(WrapMode::WordWrap)
898 }
899
900 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
902 let inner = self.inner.lock();
903 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
904 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
905 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
906 update.default_wrap_mode = mode;
907 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
908 Ok(())
909 }
910
911 pub fn on_change<F>(&self, callback: F) -> Subscription
930 where
931 F: Fn(DocumentEvent) + Send + Sync + 'static,
932 {
933 let mut inner = self.inner.lock();
934 events::subscribe_inner(&mut inner, callback)
935 }
936
937 pub fn poll_events(&self) -> Vec<DocumentEvent> {
943 let mut inner = self.inner.lock();
944 inner.drain_poll_events()
945 }
946
947 pub fn set_syntax_highlighter(&self, highlighter: Option<Arc<dyn crate::SyntaxHighlighter>>) {
955 let mut inner = self.inner.lock();
956 match highlighter {
957 Some(hl) => {
958 inner.highlight = Some(crate::highlight::HighlightData {
959 highlighter: hl,
960 blocks: std::collections::HashMap::new(),
961 });
962 inner.rehighlight_all();
963 }
964 None => {
965 inner.highlight = None;
966 }
967 }
968 }
969
970 pub fn rehighlight(&self) {
975 let mut inner = self.inner.lock();
976 inner.rehighlight_all();
977 }
978
979 pub fn rehighlight_block(&self, block_id: usize) {
982 let mut inner = self.inner.lock();
983 inner.rehighlight_from_block(block_id);
984 }
985}
986
987impl Default for TextDocument {
988 fn default() -> Self {
989 Self::new()
990 }
991}
992
993struct UndoBlockState {
997 id: u64,
998 position: i64,
999 text_length: i64,
1000 plain_text: String,
1001 format: BlockFormat,
1002}
1003
1004fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
1006 let all_blocks =
1007 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
1008 let mut states: Vec<UndoBlockState> = all_blocks
1009 .into_iter()
1010 .map(|b| UndoBlockState {
1011 id: b.id,
1012 position: b.document_position,
1013 text_length: b.text_length,
1014 plain_text: b.plain_text.clone(),
1015 format: BlockFormat::from(&b),
1016 })
1017 .collect();
1018 states.sort_by_key(|s| s.position);
1019 states
1020}
1021
1022fn build_doc_text(states: &[UndoBlockState]) -> String {
1024 states
1025 .iter()
1026 .map(|s| s.plain_text.as_str())
1027 .collect::<Vec<_>>()
1028 .join("\n")
1029}
1030
1031fn compute_text_edit(before: &str, after: &str) -> (usize, usize, usize) {
1034 let before_chars: Vec<char> = before.chars().collect();
1035 let after_chars: Vec<char> = after.chars().collect();
1036
1037 let prefix_len = before_chars
1039 .iter()
1040 .zip(after_chars.iter())
1041 .take_while(|(a, b)| a == b)
1042 .count();
1043
1044 let before_remaining = before_chars.len() - prefix_len;
1046 let after_remaining = after_chars.len() - prefix_len;
1047 let suffix_len = before_chars
1048 .iter()
1049 .rev()
1050 .zip(after_chars.iter().rev())
1051 .take(before_remaining.min(after_remaining))
1052 .take_while(|(a, b)| a == b)
1053 .count();
1054
1055 let removed = before_remaining - suffix_len;
1056 let added = after_remaining - suffix_len;
1057
1058 (prefix_len, removed, added)
1059}
1060
1061fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
1064 let after = capture_block_state(inner);
1065
1066 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
1068 before.iter().map(|s| (s.id, s)).collect();
1069 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
1070 after.iter().map(|s| (s.id, s)).collect();
1071
1072 let mut content_changed = false;
1074 let mut earliest_pos: Option<usize> = None;
1075 let mut old_end: usize = 0;
1076 let mut new_end: usize = 0;
1077 let mut blocks_affected: usize = 0;
1078
1079 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
1083 if let Some(before_state) = before_map.get(&after_state.id) {
1084 let text_changed = before_state.plain_text != after_state.plain_text
1085 || before_state.text_length != after_state.text_length;
1086 let format_changed = before_state.format != after_state.format;
1087
1088 if text_changed {
1089 content_changed = true;
1090 blocks_affected += 1;
1091 let pos = after_state.position.max(0) as usize;
1092 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1093 old_end = old_end.max(
1094 before_state.position.max(0) as usize
1095 + before_state.text_length.max(0) as usize,
1096 );
1097 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1098 } else if format_changed {
1099 let pos = after_state.position.max(0) as usize;
1100 let len = after_state.text_length.max(0) as usize;
1101 format_only_changes.push((pos, len));
1102 }
1103 } else {
1104 content_changed = true;
1106 blocks_affected += 1;
1107 let pos = after_state.position.max(0) as usize;
1108 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1109 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1110 }
1111 }
1112
1113 for before_state in before {
1115 if !after_map.contains_key(&before_state.id) {
1116 content_changed = true;
1117 blocks_affected += 1;
1118 let pos = before_state.position.max(0) as usize;
1119 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1120 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
1121 }
1122 }
1123
1124 if content_changed {
1125 let position = earliest_pos.unwrap_or(0);
1126 let chars_removed = old_end.saturating_sub(position);
1127 let chars_added = new_end.saturating_sub(position);
1128
1129 let before_text = build_doc_text(before);
1132 let after_text = build_doc_text(&after);
1133 let (edit_offset, precise_removed, precise_added) =
1134 compute_text_edit(&before_text, &after_text);
1135 if precise_removed > 0 || precise_added > 0 {
1136 inner.adjust_cursors(edit_offset, precise_removed, precise_added);
1137 }
1138
1139 inner.queue_event(DocumentEvent::ContentsChanged {
1140 position,
1141 chars_removed,
1142 chars_added,
1143 blocks_affected,
1144 });
1145 }
1146
1147 for (position, length) in format_only_changes {
1149 inner.queue_event(DocumentEvent::FormatChanged {
1150 position,
1151 length,
1152 kind: FormatChangeKind::Block,
1153 });
1154 }
1155}
1156
1157fn collect_frame_block_ids(
1163 inner: &TextDocumentInner,
1164 frame_id: frontend::common::types::EntityId,
1165) -> Option<Vec<u64>> {
1166 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
1167 .ok()
1168 .flatten()?;
1169
1170 if !frame_dto.child_order.is_empty() {
1171 let mut block_ids = Vec::new();
1172 for &entry in &frame_dto.child_order {
1173 if entry > 0 {
1174 block_ids.push(entry as u64);
1175 } else if entry < 0 {
1176 let sub_frame_id = (-entry) as u64;
1177 let sub_frame = frame_commands::get_frame(&inner.ctx, &sub_frame_id)
1178 .ok()
1179 .flatten();
1180 if let Some(ref sf) = sub_frame {
1181 if let Some(table_id) = sf.table {
1182 if let Some(table_dto) = table_commands::get_table(&inner.ctx, &table_id)
1185 .ok()
1186 .flatten()
1187 {
1188 let mut cell_dtos: Vec<_> = table_dto
1189 .cells
1190 .iter()
1191 .filter_map(|&cid| {
1192 table_cell_commands::get_table_cell(&inner.ctx, &cid)
1193 .ok()
1194 .flatten()
1195 })
1196 .collect();
1197 cell_dtos
1198 .sort_by(|a, b| a.row.cmp(&b.row).then(a.column.cmp(&b.column)));
1199 for cell_dto in &cell_dtos {
1200 if let Some(cf_id) = cell_dto.cell_frame
1201 && let Some(cf_ids) = collect_frame_block_ids(inner, cf_id)
1202 {
1203 block_ids.extend(cf_ids);
1204 }
1205 }
1206 }
1207 } else if let Some(sub_ids) = collect_frame_block_ids(inner, sub_frame_id) {
1208 block_ids.extend(sub_ids);
1209 }
1210 }
1211 }
1212 }
1213 Some(block_ids)
1214 } else {
1215 Some(frame_dto.blocks.to_vec())
1216 }
1217}
1218
1219pub(crate) fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
1220 let frames = frontend::commands::document_commands::get_document_relationship(
1222 &inner.ctx,
1223 &inner.document_id,
1224 &frontend::document::dtos::DocumentRelationshipField::Frames,
1225 )
1226 .unwrap_or_default();
1227
1228 frames.first().copied().unwrap_or(0)
1229}
1230
1231fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
1235 let Some(json) = data else {
1236 return (String::new(), 0.0, String::new());
1237 };
1238 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1239 let id = v["id"].as_str().unwrap_or_default().to_string();
1240 let pct = v["percentage"].as_f64().unwrap_or(0.0);
1241 let msg = v["message"].as_str().unwrap_or_default().to_string();
1242 (id, pct, msg)
1243}
1244
1245fn parse_id_data(data: &Option<String>) -> String {
1247 let Some(json) = data else {
1248 return String::new();
1249 };
1250 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1251 v["id"].as_str().unwrap_or_default().to_string()
1252}
1253
1254fn parse_failed_data(data: &Option<String>) -> (String, String) {
1256 let Some(json) = data else {
1257 return (String::new(), "unknown error".into());
1258 };
1259 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1260 let id = v["id"].as_str().unwrap_or_default().to_string();
1261 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
1262 (id, error)
1263}