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 document_commands, document_inspection_commands, document_io_commands,
14 document_search_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::inner::TextDocumentInner;
21use crate::operation::{DocxExportResult, HtmlImportResult, MarkdownImportResult, Operation};
22use crate::{BlockFormat, BlockInfo, DocumentStats, FindMatch, FindOptions};
23
24#[derive(Clone)]
35pub struct TextDocument {
36 pub(crate) inner: Arc<Mutex<TextDocumentInner>>,
37}
38
39impl TextDocument {
40 pub fn new() -> Self {
49 Self::try_new().expect("failed to initialize document")
50 }
51
52 pub fn try_new() -> Result<Self> {
54 let ctx = frontend::AppContext::new();
55 let doc_inner = TextDocumentInner::initialize(ctx)?;
56 let inner = Arc::new(Mutex::new(doc_inner));
57
58 Self::subscribe_long_operation_events(&inner);
60
61 Ok(Self { inner })
62 }
63
64 fn subscribe_long_operation_events(inner: &Arc<Mutex<TextDocumentInner>>) {
66 use frontend::common::event::{LongOperationEvent as LOE, Origin};
67
68 let weak = Arc::downgrade(inner);
69 {
70 let locked = inner.lock();
71 let w = weak.clone();
73 locked
74 .event_client
75 .subscribe(Origin::LongOperation(LOE::Progress), move |event| {
76 if let Some(inner) = w.upgrade() {
77 let (op_id, percent, message) = parse_progress_data(&event.data);
78 let mut inner = inner.lock();
79 inner.queue_event(DocumentEvent::LongOperationProgress {
80 operation_id: op_id,
81 percent,
82 message,
83 });
84 }
85 });
86
87 let w = weak.clone();
89 locked
90 .event_client
91 .subscribe(Origin::LongOperation(LOE::Completed), move |event| {
92 if let Some(inner) = w.upgrade() {
93 let op_id = parse_id_data(&event.data);
94 let mut inner = inner.lock();
95 inner.queue_event(DocumentEvent::DocumentReset);
96 inner.check_block_count_changed();
97 inner.reset_cached_child_order();
98 inner.queue_event(DocumentEvent::LongOperationFinished {
99 operation_id: op_id,
100 success: true,
101 error: None,
102 });
103 }
104 });
105
106 let w = weak.clone();
108 locked
109 .event_client
110 .subscribe(Origin::LongOperation(LOE::Cancelled), move |event| {
111 if let Some(inner) = w.upgrade() {
112 let op_id = parse_id_data(&event.data);
113 let mut inner = inner.lock();
114 inner.queue_event(DocumentEvent::LongOperationFinished {
115 operation_id: op_id,
116 success: false,
117 error: Some("cancelled".into()),
118 });
119 }
120 });
121
122 locked
124 .event_client
125 .subscribe(Origin::LongOperation(LOE::Failed), move |event| {
126 if let Some(inner) = weak.upgrade() {
127 let (op_id, error) = parse_failed_data(&event.data);
128 let mut inner = inner.lock();
129 inner.queue_event(DocumentEvent::LongOperationFinished {
130 operation_id: op_id,
131 success: false,
132 error: Some(error),
133 });
134 }
135 });
136 }
137 }
138
139 pub fn set_plain_text(&self, text: &str) -> Result<()> {
143 let queued = {
144 let mut inner = self.inner.lock();
145 let dto = frontend::document_io::ImportPlainTextDto {
146 plain_text: text.into(),
147 };
148 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
149 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
150 inner.invalidate_text_cache();
151 inner.queue_event(DocumentEvent::DocumentReset);
152 inner.check_block_count_changed();
153 inner.reset_cached_child_order();
154 inner.queue_event(DocumentEvent::UndoRedoChanged {
155 can_undo: false,
156 can_redo: false,
157 });
158 inner.take_queued_events()
159 };
160 crate::inner::dispatch_queued_events(queued);
161 Ok(())
162 }
163
164 pub fn to_plain_text(&self) -> Result<String> {
166 let mut inner = self.inner.lock();
167 Ok(inner.plain_text()?.to_string())
168 }
169
170 pub fn set_markdown(&self, markdown: &str) -> Result<Operation<MarkdownImportResult>> {
174 let mut inner = self.inner.lock();
175 inner.invalidate_text_cache();
176 let dto = frontend::document_io::ImportMarkdownDto {
177 markdown_text: markdown.into(),
178 };
179 let op_id = document_io_commands::import_markdown(&inner.ctx, &dto)?;
180 Ok(Operation::new(
181 op_id,
182 &inner.ctx,
183 Box::new(|ctx, id| {
184 document_io_commands::get_import_markdown_result(ctx, id)
185 .ok()
186 .flatten()
187 .map(|r| {
188 Ok(MarkdownImportResult {
189 block_count: to_usize(r.block_count),
190 })
191 })
192 }),
193 ))
194 }
195
196 pub fn to_markdown(&self) -> Result<String> {
198 let inner = self.inner.lock();
199 let dto = document_io_commands::export_markdown(&inner.ctx)?;
200 Ok(dto.markdown_text)
201 }
202
203 pub fn set_html(&self, html: &str) -> Result<Operation<HtmlImportResult>> {
207 let mut inner = self.inner.lock();
208 inner.invalidate_text_cache();
209 let dto = frontend::document_io::ImportHtmlDto {
210 html_text: html.into(),
211 };
212 let op_id = document_io_commands::import_html(&inner.ctx, &dto)?;
213 Ok(Operation::new(
214 op_id,
215 &inner.ctx,
216 Box::new(|ctx, id| {
217 document_io_commands::get_import_html_result(ctx, id)
218 .ok()
219 .flatten()
220 .map(|r| {
221 Ok(HtmlImportResult {
222 block_count: to_usize(r.block_count),
223 })
224 })
225 }),
226 ))
227 }
228
229 pub fn to_html(&self) -> Result<String> {
231 let inner = self.inner.lock();
232 let dto = document_io_commands::export_html(&inner.ctx)?;
233 Ok(dto.html_text)
234 }
235
236 pub fn to_latex(&self, document_class: &str, include_preamble: bool) -> Result<String> {
238 let inner = self.inner.lock();
239 let dto = frontend::document_io::ExportLatexDto {
240 document_class: document_class.into(),
241 include_preamble,
242 };
243 let result = document_io_commands::export_latex(&inner.ctx, &dto)?;
244 Ok(result.latex_text)
245 }
246
247 pub fn to_docx(&self, output_path: &str) -> Result<Operation<DocxExportResult>> {
251 let inner = self.inner.lock();
252 let dto = frontend::document_io::ExportDocxDto {
253 output_path: output_path.into(),
254 };
255 let op_id = document_io_commands::export_docx(&inner.ctx, &dto)?;
256 Ok(Operation::new(
257 op_id,
258 &inner.ctx,
259 Box::new(|ctx, id| {
260 document_io_commands::get_export_docx_result(ctx, id)
261 .ok()
262 .flatten()
263 .map(|r| {
264 Ok(DocxExportResult {
265 file_path: r.file_path,
266 paragraph_count: to_usize(r.paragraph_count),
267 })
268 })
269 }),
270 ))
271 }
272
273 pub fn clear(&self) -> Result<()> {
275 let queued = {
276 let mut inner = self.inner.lock();
277 let dto = frontend::document_io::ImportPlainTextDto {
278 plain_text: String::new(),
279 };
280 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
281 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
282 inner.invalidate_text_cache();
283 inner.queue_event(DocumentEvent::DocumentReset);
284 inner.check_block_count_changed();
285 inner.reset_cached_child_order();
286 inner.queue_event(DocumentEvent::UndoRedoChanged {
287 can_undo: false,
288 can_redo: false,
289 });
290 inner.take_queued_events()
291 };
292 crate::inner::dispatch_queued_events(queued);
293 Ok(())
294 }
295
296 pub fn cursor(&self) -> TextCursor {
300 self.cursor_at(0)
301 }
302
303 pub fn cursor_at(&self, position: usize) -> TextCursor {
305 let data = {
306 let mut inner = self.inner.lock();
307 inner.register_cursor(position)
308 };
309 TextCursor {
310 doc: self.inner.clone(),
311 data,
312 }
313 }
314
315 pub fn stats(&self) -> DocumentStats {
319 let inner = self.inner.lock();
320 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
321 .expect("get_document_stats should not fail");
322 DocumentStats::from(&dto)
323 }
324
325 pub fn character_count(&self) -> usize {
327 let inner = self.inner.lock();
328 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
329 .expect("get_document_stats should not fail");
330 to_usize(dto.character_count)
331 }
332
333 pub fn block_count(&self) -> usize {
335 let inner = self.inner.lock();
336 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
337 .expect("get_document_stats should not fail");
338 to_usize(dto.block_count)
339 }
340
341 pub fn is_empty(&self) -> bool {
343 self.character_count() == 0
344 }
345
346 pub fn text_at(&self, position: usize, length: usize) -> Result<String> {
348 let inner = self.inner.lock();
349 let dto = frontend::document_inspection::GetTextAtPositionDto {
350 position: to_i64(position),
351 length: to_i64(length),
352 };
353 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
354 Ok(result.text)
355 }
356
357 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
359 let inner = self.inner.lock();
360 let dto = frontend::document_inspection::GetBlockAtPositionDto {
361 position: to_i64(position),
362 };
363 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
364 Ok(BlockInfo::from(&result))
365 }
366
367 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
369 let inner = self.inner.lock();
370 let dto = frontend::document_inspection::GetBlockAtPositionDto {
371 position: to_i64(position),
372 };
373 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
374 let block_id = block_info.block_id;
375 let block_id = block_id as u64;
376 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
377 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
378 Ok(BlockFormat::from(&block_dto))
379 }
380
381 pub fn flow(&self) -> Vec<crate::flow::FlowElement> {
392 let inner = self.inner.lock();
393 let main_frame_id = get_main_frame_id(&inner);
394 crate::text_frame::build_flow_elements(&inner, &self.inner, main_frame_id)
395 }
396
397 pub fn block_by_id(&self, block_id: usize) -> Option<crate::text_block::TextBlock> {
402 let inner = self.inner.lock();
403 let exists = frontend::commands::block_commands::get_block(&inner.ctx, &(block_id as u64))
404 .ok()
405 .flatten()
406 .is_some();
407
408 if exists {
409 Some(crate::text_block::TextBlock {
410 doc: self.inner.clone(),
411 block_id,
412 })
413 } else {
414 None
415 }
416 }
417
418 pub fn block_at_position(&self, position: usize) -> Option<crate::text_block::TextBlock> {
421 let inner = self.inner.lock();
422 let dto = frontend::document_inspection::GetBlockAtPositionDto {
423 position: to_i64(position),
424 };
425 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
426 Some(crate::text_block::TextBlock {
427 doc: self.inner.clone(),
428 block_id: result.block_id as usize,
429 })
430 }
431
432 pub fn block_by_number(&self, block_number: usize) -> Option<crate::text_block::TextBlock> {
441 let inner = self.inner.lock();
442 let all_blocks = frontend::commands::block_commands::get_all_block(&inner.ctx).ok()?;
443 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
444 sorted.sort_by_key(|b| b.document_position);
445
446 sorted
447 .get(block_number)
448 .map(|b| crate::text_block::TextBlock {
449 doc: self.inner.clone(),
450 block_id: b.id as usize,
451 })
452 }
453
454 pub fn snapshot_flow(&self) -> crate::flow::FlowSnapshot {
459 let inner = self.inner.lock();
460 let main_frame_id = get_main_frame_id(&inner);
461 let elements = crate::text_frame::build_flow_snapshot(&inner, main_frame_id);
462 crate::flow::FlowSnapshot { elements }
463 }
464
465 pub fn find(
469 &self,
470 query: &str,
471 from: usize,
472 options: &FindOptions,
473 ) -> Result<Option<FindMatch>> {
474 let inner = self.inner.lock();
475 let dto = options.to_find_text_dto(query, from);
476 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
477 Ok(convert::find_result_to_match(&result))
478 }
479
480 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
482 let inner = self.inner.lock();
483 let dto = options.to_find_all_dto(query);
484 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
485 Ok(convert::find_all_to_matches(&result))
486 }
487
488 pub fn replace_text(
490 &self,
491 query: &str,
492 replacement: &str,
493 replace_all: bool,
494 options: &FindOptions,
495 ) -> Result<usize> {
496 let (count, queued) = {
497 let mut inner = self.inner.lock();
498 let dto = options.to_replace_dto(query, replacement, replace_all);
499 let result =
500 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
501 let count = to_usize(result.replacements_count);
502 inner.invalidate_text_cache();
503 if count > 0 {
504 inner.modified = true;
505 inner.queue_event(DocumentEvent::ContentsChanged {
510 position: 0,
511 chars_removed: 0,
512 chars_added: 0,
513 blocks_affected: count,
514 });
515 inner.check_block_count_changed();
516 inner.check_flow_changed();
517 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
518 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
519 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
520 }
521 (count, inner.take_queued_events())
522 };
523 crate::inner::dispatch_queued_events(queued);
524 Ok(count)
525 }
526
527 pub fn add_resource(
531 &self,
532 resource_type: ResourceType,
533 name: &str,
534 mime_type: &str,
535 data: &[u8],
536 ) -> Result<()> {
537 let mut inner = self.inner.lock();
538 let dto = frontend::resource::dtos::CreateResourceDto {
539 created_at: Default::default(),
540 updated_at: Default::default(),
541 resource_type,
542 name: name.into(),
543 url: String::new(),
544 mime_type: mime_type.into(),
545 data_base64: BASE64.encode(data),
546 };
547 let created = resource_commands::create_resource(
548 &inner.ctx,
549 Some(inner.stack_id),
550 &dto,
551 inner.document_id,
552 -1,
553 )?;
554 inner.resource_cache.insert(name.to_string(), created.id);
555 Ok(())
556 }
557
558 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
562 let mut inner = self.inner.lock();
563
564 if let Some(&id) = inner.resource_cache.get(name) {
566 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
567 let bytes = BASE64.decode(&r.data_base64)?;
568 return Ok(Some(bytes));
569 }
570 inner.resource_cache.remove(name);
572 }
573
574 let all = resource_commands::get_all_resource(&inner.ctx)?;
576 for r in &all {
577 if r.name == name {
578 inner.resource_cache.insert(name.to_string(), r.id);
579 let bytes = BASE64.decode(&r.data_base64)?;
580 return Ok(Some(bytes));
581 }
582 }
583 Ok(None)
584 }
585
586 pub fn undo(&self) -> Result<()> {
590 let queued = {
591 let mut inner = self.inner.lock();
592 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
593 inner.invalidate_text_cache();
594 result?;
595 inner.check_block_count_changed();
596 inner.check_flow_changed();
597 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
598 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
599 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
600 inner.take_queued_events()
601 };
602 crate::inner::dispatch_queued_events(queued);
603 Ok(())
604 }
605
606 pub fn redo(&self) -> Result<()> {
608 let queued = {
609 let mut inner = self.inner.lock();
610 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
611 inner.invalidate_text_cache();
612 result?;
613 inner.check_block_count_changed();
614 inner.check_flow_changed();
615 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
616 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
617 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
618 inner.take_queued_events()
619 };
620 crate::inner::dispatch_queued_events(queued);
621 Ok(())
622 }
623
624 pub fn can_undo(&self) -> bool {
626 let inner = self.inner.lock();
627 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
628 }
629
630 pub fn can_redo(&self) -> bool {
632 let inner = self.inner.lock();
633 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
634 }
635
636 pub fn clear_undo_redo(&self) {
638 let inner = self.inner.lock();
639 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
640 }
641
642 pub fn is_modified(&self) -> bool {
646 self.inner.lock().modified
647 }
648
649 pub fn set_modified(&self, modified: bool) {
651 let queued = {
652 let mut inner = self.inner.lock();
653 if inner.modified != modified {
654 inner.modified = modified;
655 inner.queue_event(DocumentEvent::ModificationChanged(modified));
656 }
657 inner.take_queued_events()
658 };
659 crate::inner::dispatch_queued_events(queued);
660 }
661
662 pub fn title(&self) -> String {
666 let inner = self.inner.lock();
667 document_commands::get_document(&inner.ctx, &inner.document_id)
668 .ok()
669 .flatten()
670 .map(|d| d.title)
671 .unwrap_or_default()
672 }
673
674 pub fn set_title(&self, title: &str) -> Result<()> {
676 let inner = self.inner.lock();
677 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
678 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
679 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
680 update.title = title.into();
681 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
682 Ok(())
683 }
684
685 pub fn text_direction(&self) -> TextDirection {
687 let inner = self.inner.lock();
688 document_commands::get_document(&inner.ctx, &inner.document_id)
689 .ok()
690 .flatten()
691 .map(|d| d.text_direction)
692 .unwrap_or(TextDirection::LeftToRight)
693 }
694
695 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
697 let inner = self.inner.lock();
698 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
699 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
700 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
701 update.text_direction = direction;
702 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
703 Ok(())
704 }
705
706 pub fn default_wrap_mode(&self) -> WrapMode {
708 let inner = self.inner.lock();
709 document_commands::get_document(&inner.ctx, &inner.document_id)
710 .ok()
711 .flatten()
712 .map(|d| d.default_wrap_mode)
713 .unwrap_or(WrapMode::WordWrap)
714 }
715
716 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
718 let inner = self.inner.lock();
719 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
720 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
721 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
722 update.default_wrap_mode = mode;
723 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
724 Ok(())
725 }
726
727 pub fn on_change<F>(&self, callback: F) -> Subscription
746 where
747 F: Fn(DocumentEvent) + Send + Sync + 'static,
748 {
749 let mut inner = self.inner.lock();
750 events::subscribe_inner(&mut inner, callback)
751 }
752
753 pub fn poll_events(&self) -> Vec<DocumentEvent> {
759 let mut inner = self.inner.lock();
760 inner.drain_poll_events()
761 }
762}
763
764impl Default for TextDocument {
765 fn default() -> Self {
766 Self::new()
767 }
768}
769
770fn get_main_frame_id(inner: &TextDocumentInner) -> frontend::common::types::EntityId {
774 let frames = frontend::commands::document_commands::get_document_relationship(
776 &inner.ctx,
777 &inner.document_id,
778 &frontend::document::dtos::DocumentRelationshipField::Frames,
779 )
780 .unwrap_or_default();
781
782 frames.first().copied().unwrap_or(0)
783}
784
785fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
789 let Some(json) = data else {
790 return (String::new(), 0.0, String::new());
791 };
792 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
793 let id = v["id"].as_str().unwrap_or_default().to_string();
794 let pct = v["percentage"].as_f64().unwrap_or(0.0);
795 let msg = v["message"].as_str().unwrap_or_default().to_string();
796 (id, pct, msg)
797}
798
799fn parse_id_data(data: &Option<String>) -> String {
801 let Some(json) = data else {
802 return String::new();
803 };
804 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
805 v["id"].as_str().unwrap_or_default().to_string()
806}
807
808fn parse_failed_data(data: &Option<String>) -> (String, String) {
810 let Some(json) = data else {
811 return (String::new(), "unknown error".into());
812 };
813 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
814 let id = v["id"].as_str().unwrap_or_default().to_string();
815 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
816 (id, error)
817}