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 frame_dto = frame_commands::get_frame(&inner.ctx, &main_frame_id)
435 .ok()
436 .flatten()?;
437
438 let ordered_block_ids: Vec<u64> = if !frame_dto.child_order.is_empty() {
439 frame_dto
440 .child_order
441 .iter()
442 .filter(|&&id| id > 0)
443 .map(|&id| id as u64)
444 .collect()
445 } else {
446 frame_dto.blocks.to_vec()
447 };
448
449 let pos = position as i64;
451 let mut running_pos: i64 = 0;
452 for &block_id in &ordered_block_ids {
453 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
454 .ok()
455 .flatten()?;
456 let block_end = running_pos + block_dto.text_length;
457 if pos >= running_pos && pos <= block_end {
458 return crate::text_block::build_block_snapshot_with_position(
459 &inner,
460 block_id,
461 Some(running_pos as usize),
462 );
463 }
464 running_pos = block_end + 1;
465 }
466
467 if let Some(&last_id) = ordered_block_ids.last() {
469 return crate::text_block::build_block_snapshot(&inner, last_id);
470 }
471 None
472 }
473
474 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
477 let inner = self.inner.lock();
478 let dto = frontend::document_inspection::GetBlockAtPositionDto {
479 position: to_i64(position),
480 };
481 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
482 Some(crate::text_block::TextBlock {
483 doc: self.inner.clone(),
484 block_id: result.block_id as usize,
485 })
486 }
487
488 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
497 let inner = self.inner.lock();
498 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
499 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
500 sorted.sort_by_key(|b| b.document_position);
501
502 sorted
503 .get(block_number)
504 .map(|b| crate::text_block::TextBlock {
505 doc: self.inner.clone(),
506 block_id: b.id as usize,
507 })
508 }
509
510 pub fn blocks(&self) -> Vec<crate::text_block::TextBlock> {
516 let inner = self.inner.lock();
517 let all_blocks =
518 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
519 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
520 sorted.sort_by_key(|b| b.document_position);
521 sorted
522 .iter()
523 .map(|b| crate::text_block::TextBlock {
524 doc: self.inner.clone(),
525 block_id: b.id as usize,
526 })
527 .collect()
528 }
529
530 pub fn blocks_in_range(
537 &self,
538 position: usize,
539 length: usize,
540 ) -> Vec<crate::text_block::TextBlock> {
541 let inner = self.inner.lock();
542 let all_blocks =
543 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
544 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
545 sorted.sort_by_key(|b| b.document_position);
546
547 let range_start = position;
548 let range_end = position + length;
549
550 sorted
551 .iter()
552 .filter(|b| {
553 let block_start = b.document_position.max(0) as usize;
554 let block_end = block_start + b.text_length.max(0) as usize;
555 if length == 0 {
557 range_start >= block_start && range_start < block_end
559 } else {
560 block_start < range_end && block_end > range_start
561 }
562 })
563 .map(|b| crate::text_block::TextBlock {
564 doc: self.inner.clone(),
565 block_id: b.id as usize,
566 })
567 .collect()
568 }
569
570 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
575 let inner = self.inner.lock();
576 let main_frame_id = get_main_frame_id(&inner);
577 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id);
578 crate::flow::FlowSnapshot { elements }
579 }
580
581 pub fn find(
585 &self,
586 query: &str,
587 from: usize,
588 options: &FindOptions,
589 ) -> Result<Option<FindMatch>> {
590 let inner = self.inner.lock();
591 let dto = options.to_find_text_dto(query, from);
592 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
593 Ok(convert::find_result_to_match(&result))
594 }
595
596 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
598 let inner = self.inner.lock();
599 let dto = options.to_find_all_dto(query);
600 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
601 Ok(convert::find_all_to_matches(&result))
602 }
603
604 pub fn replace_text(
606 &self,
607 query: &str,
608 replacement: &str,
609 replace_all: bool,
610 options: &FindOptions,
611 ) -> Result<usize> {
612 let (count, queued) = {
613 let mut inner = self.inner.lock();
614 let dto = options.to_replace_dto(query, replacement, replace_all);
615 let result =
616 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
617 let count = to_usize(result.replacements_count);
618 inner.invalidate_text_cache();
619 if count > 0 {
620 inner.modified = true;
621 inner.rehighlight_all();
622 inner.queue_event(DocumentEvent::ContentsChanged {
627 position: 0,
628 chars_removed: 0,
629 chars_added: 0,
630 blocks_affected: count,
631 });
632 inner.check_block_count_changed();
633 inner.check_flow_changed();
634 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
635 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
636 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
637 }
638 (count, inner.take_queued_events())
639 };
640 crate::inner::dispatch_queued_events(queued);
641 Ok(count)
642 }
643
644 pub fn add_resource(
648 &self,
649 resource_type: ResourceType,
650 name: &str,
651 mime_type: &str,
652 data: &[u8],
653 ) -> Result<()> {
654 let mut inner = self.inner.lock();
655 let dto = frontend::resource::dtos::CreateResourceDto {
656 created_at: Default::default(),
657 updated_at: Default::default(),
658 resource_type,
659 name: name.into(),
660 url: String::new(),
661 mime_type: mime_type.into(),
662 data_base64: BASE64.encode(data),
663 };
664 let created = resource_commands::create_resource(
665 &inner.ctx,
666 Some(inner.stack_id),
667 &dto,
668 inner.document_id,
669 -1,
670 )?;
671 inner.resource_cache.insert(name.to_string(), created.id);
672 Ok(())
673 }
674
675 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
679 let mut inner = self.inner.lock();
680
681 if let Some(&id) = inner.resource_cache.get(name) {
683 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
684 let bytes = BASE64.decode(&r.data_base64)?;
685 return Ok(Some(bytes));
686 }
687 inner.resource_cache.remove(name);
689 }
690
691 let all = resource_commands::get_all_resource(&inner.ctx)?;
693 for r in &all {
694 if r.name == name {
695 inner.resource_cache.insert(name.to_string(), r.id);
696 let bytes = BASE64.decode(&r.data_base64)?;
697 return Ok(Some(bytes));
698 }
699 }
700 Ok(None)
701 }
702
703 pub fn undo(&self) -> Result<()> {
707 let queued = {
708 let mut inner = self.inner.lock();
709 let before = capture_block_state(&inner);
710 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
711 inner.invalidate_text_cache();
712 result?;
713 inner.rehighlight_all();
714 emit_undo_redo_change_events(&mut inner, &before);
715 inner.check_block_count_changed();
716 inner.check_flow_changed();
717 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
718 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
719 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
720 inner.take_queued_events()
721 };
722 crate::inner::dispatch_queued_events(queued);
723 Ok(())
724 }
725
726 pub fn redo(&self) -> Result<()> {
728 let queued = {
729 let mut inner = self.inner.lock();
730 let before = capture_block_state(&inner);
731 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
732 inner.invalidate_text_cache();
733 result?;
734 inner.rehighlight_all();
735 emit_undo_redo_change_events(&mut inner, &before);
736 inner.check_block_count_changed();
737 inner.check_flow_changed();
738 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
739 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
740 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
741 inner.take_queued_events()
742 };
743 crate::inner::dispatch_queued_events(queued);
744 Ok(())
745 }
746
747 pub fn can_undo(&self) -> bool {
749 let inner = self.inner.lock();
750 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
751 }
752
753 pub fn can_redo(&self) -> bool {
755 let inner = self.inner.lock();
756 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
757 }
758
759 pub fn clear_undo_redo(&self) {
761 let inner = self.inner.lock();
762 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
763 }
764
765 pub fn is_modified(&self) -> bool {
769 self.inner.lock().modified
770 }
771
772 pub fn set_modified(&self, modified: bool) {
774 let queued = {
775 let mut inner = self.inner.lock();
776 if inner.modified != modified {
777 inner.modified = modified;
778 inner.queue_event(DocumentEvent::ModificationChanged(modified));
779 }
780 inner.take_queued_events()
781 };
782 crate::inner::dispatch_queued_events(queued);
783 }
784
785 pub fn title(&self) -> String {
789 let inner = self.inner.lock();
790 document_commands::get_document(&inner.ctx, &inner.document_id)
791 .ok()
792 .flatten()
793 .map(|d| d.title)
794 .unwrap_or_default()
795 }
796
797 pub fn set_title(&self, title: &str) -> Result<()> {
799 let inner = self.inner.lock();
800 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
801 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
802 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
803 update.title = title.into();
804 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
805 Ok(())
806 }
807
808 pub fn text_direction(&self) -> TextDirection {
810 let inner = self.inner.lock();
811 document_commands::get_document(&inner.ctx, &inner.document_id)
812 .ok()
813 .flatten()
814 .map(|d| d.text_direction)
815 .unwrap_or(TextDirection::LeftToRight)
816 }
817
818 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
820 let inner = self.inner.lock();
821 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
822 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
823 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
824 update.text_direction = direction;
825 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
826 Ok(())
827 }
828
829 pub fn default_wrap_mode(&self) -> WrapMode {
831 let inner = self.inner.lock();
832 document_commands::get_document(&inner.ctx, &inner.document_id)
833 .ok()
834 .flatten()
835 .map(|d| d.default_wrap_mode)
836 .unwrap_or(WrapMode::WordWrap)
837 }
838
839 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
841 let inner = self.inner.lock();
842 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
843 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
844 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
845 update.default_wrap_mode = mode;
846 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
847 Ok(())
848 }
849
850 pub fn on_change<F>(&self, callback: F) -> Subscription
869 where
870 F: Fn(DocumentEvent) + Send + Sync + 'static,
871 {
872 let mut inner = self.inner.lock();
873 events::subscribe_inner(&mut inner, callback)
874 }
875
876 pub fn poll_events(&self) -> Vec<DocumentEvent> {
882 let mut inner = self.inner.lock();
883 inner.drain_poll_events()
884 }
885
886 pub fn set_syntax_highlighter(&self, highlighter: Option<Arc<dyn crate::SyntaxHighlighter>>) {
894 let mut inner = self.inner.lock();
895 match highlighter {
896 Some(hl) => {
897 inner.highlight = Some(crate::highlight::HighlightData {
898 highlighter: hl,
899 blocks: std::collections::HashMap::new(),
900 });
901 inner.rehighlight_all();
902 }
903 None => {
904 inner.highlight = None;
905 }
906 }
907 }
908
909 pub fn rehighlight(&self) {
914 let mut inner = self.inner.lock();
915 inner.rehighlight_all();
916 }
917
918 pub fn rehighlight_block(&self, block_id: usize) {
921 let mut inner = self.inner.lock();
922 inner.rehighlight_from_block(block_id);
923 }
924}
925
926impl Default for TextDocument {
927 fn default() -> Self {
928 Self::new()
929 }
930}
931
932struct UndoBlockState {
936 id: u64,
937 position: i64,
938 text_length: i64,
939 plain_text: String,
940 format: BlockFormat,
941}
942
943fn capture_block_state(inner: &TextDocumentInner) -> Vec<UndoBlockState> {
945 let all_blocks =
946 frontend::commands::block_commands::get_all_block(&inner.ctx).unwrap_or_default();
947 let mut states: Vec<UndoBlockState> = all_blocks
948 .into_iter()
949 .map(|b| UndoBlockState {
950 id: b.id,
951 position: b.document_position,
952 text_length: b.text_length,
953 plain_text: b.plain_text.clone(),
954 format: BlockFormat::from(&b),
955 })
956 .collect();
957 states.sort_by_key(|s| s.position);
958 states
959}
960
961fn build_doc_text(states: &[UndoBlockState]) -> String {
963 states
964 .iter()
965 .map(|s| s.plain_text.as_str())
966 .collect::<Vec<_>>()
967 .join("\n")
968}
969
970fn compute_text_edit(before: &str, after: &str) -> (usize, usize, usize) {
973 let before_chars: Vec<char> = before.chars().collect();
974 let after_chars: Vec<char> = after.chars().collect();
975
976 let prefix_len = before_chars
978 .iter()
979 .zip(after_chars.iter())
980 .take_while(|(a, b)| a == b)
981 .count();
982
983 let before_remaining = before_chars.len() - prefix_len;
985 let after_remaining = after_chars.len() - prefix_len;
986 let suffix_len = before_chars
987 .iter()
988 .rev()
989 .zip(after_chars.iter().rev())
990 .take(before_remaining.min(after_remaining))
991 .take_while(|(a, b)| a == b)
992 .count();
993
994 let removed = before_remaining - suffix_len;
995 let added = after_remaining - suffix_len;
996
997 (prefix_len, removed, added)
998}
999
1000fn emit_undo_redo_change_events(inner: &mut TextDocumentInner, before: &[UndoBlockState]) {
1003 let after = capture_block_state(inner);
1004
1005 let before_map: std::collections::HashMap<u64, &UndoBlockState> =
1007 before.iter().map(|s| (s.id, s)).collect();
1008 let after_map: std::collections::HashMap<u64, &UndoBlockState> =
1009 after.iter().map(|s| (s.id, s)).collect();
1010
1011 let mut content_changed = false;
1013 let mut earliest_pos: Option<usize> = None;
1014 let mut old_end: usize = 0;
1015 let mut new_end: usize = 0;
1016 let mut blocks_affected: usize = 0;
1017
1018 let mut format_only_changes: Vec<(usize, usize)> = Vec::new(); for after_state in &after {
1022 if let Some(before_state) = before_map.get(&after_state.id) {
1023 let text_changed = before_state.plain_text != after_state.plain_text
1024 || before_state.text_length != after_state.text_length;
1025 let format_changed = before_state.format != after_state.format;
1026
1027 if text_changed {
1028 content_changed = true;
1029 blocks_affected += 1;
1030 let pos = after_state.position.max(0) as usize;
1031 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1032 old_end = old_end.max(
1033 before_state.position.max(0) as usize
1034 + before_state.text_length.max(0) as usize,
1035 );
1036 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1037 } else if format_changed {
1038 let pos = after_state.position.max(0) as usize;
1039 let len = after_state.text_length.max(0) as usize;
1040 format_only_changes.push((pos, len));
1041 }
1042 } else {
1043 content_changed = true;
1045 blocks_affected += 1;
1046 let pos = after_state.position.max(0) as usize;
1047 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1048 new_end = new_end.max(pos + after_state.text_length.max(0) as usize);
1049 }
1050 }
1051
1052 for before_state in before {
1054 if !after_map.contains_key(&before_state.id) {
1055 content_changed = true;
1056 blocks_affected += 1;
1057 let pos = before_state.position.max(0) as usize;
1058 earliest_pos = Some(earliest_pos.map_or(pos, |p: usize| p.min(pos)));
1059 old_end = old_end.max(pos + before_state.text_length.max(0) as usize);
1060 }
1061 }
1062
1063 if content_changed {
1064 let position = earliest_pos.unwrap_or(0);
1065 let chars_removed = old_end.saturating_sub(position);
1066 let chars_added = new_end.saturating_sub(position);
1067
1068 let before_text = build_doc_text(before);
1071 let after_text = build_doc_text(&after);
1072 let (edit_offset, precise_removed, precise_added) =
1073 compute_text_edit(&before_text, &after_text);
1074 if precise_removed > 0 || precise_added > 0 {
1075 inner.adjust_cursors(edit_offset, precise_removed, precise_added);
1076 }
1077
1078 inner.queue_event(DocumentEvent::ContentsChanged {
1079 position,
1080 chars_removed,
1081 chars_added,
1082 blocks_affected,
1083 });
1084 }
1085
1086 for (position, length) in format_only_changes {
1088 inner.queue_event(DocumentEvent::FormatChanged {
1089 position,
1090 length,
1091 kind: FormatChangeKind::Block,
1092 });
1093 }
1094}
1095
1096fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
1100 let frames = frontend::commands::document_commands::get_document_relationship(
1102 &inner.ctx,
1103 &inner.document_id,
1104 &frontend::document::dtos::DocumentRelationshipField::Frames,
1105 )
1106 .unwrap_or_default();
1107
1108 frames.first().copied().unwrap_or(0)
1109}
1110
1111fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
1115 let Some(json) = data else {
1116 return (String::new(), 0.0, String::new());
1117 };
1118 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1119 let id = v["id"].as_str().unwrap_or_default().to_string();
1120 let pct = v["percentage"].as_f64().unwrap_or(0.0);
1121 let msg = v["message"].as_str().unwrap_or_default().to_string();
1122 (id, pct, msg)
1123}
1124
1125fn parse_id_data(data: &Option<String>) -> String {
1127 let Some(json) = data else {
1128 return String::new();
1129 };
1130 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1131 v["id"].as_str().unwrap_or_default().to_string()
1132}
1133
1134fn parse_failed_data(data: &Option<String>) -> (String, String) {
1136 let Some(json) = data else {
1137 return (String::new(), "unknown error".into());
1138 };
1139 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
1140 let id = v["id"].as_str().unwrap_or_default().to_string();
1141 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
1142 (id, error)
1143}