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.queue_event(DocumentEvent::LongOperationFinished {
98 operation_id: op_id,
99 success: true,
100 error: None,
101 });
102 }
103 });
104
105 let w = weak.clone();
107 locked
108 .event_client
109 .subscribe(Origin::LongOperation(LOE::Cancelled), move |event| {
110 if let Some(inner) = w.upgrade() {
111 let op_id = parse_id_data(&event.data);
112 let mut inner = inner.lock();
113 inner.queue_event(DocumentEvent::LongOperationFinished {
114 operation_id: op_id,
115 success: false,
116 error: Some("cancelled".into()),
117 });
118 }
119 });
120
121 locked
123 .event_client
124 .subscribe(Origin::LongOperation(LOE::Failed), move |event| {
125 if let Some(inner) = weak.upgrade() {
126 let (op_id, error) = parse_failed_data(&event.data);
127 let mut inner = inner.lock();
128 inner.queue_event(DocumentEvent::LongOperationFinished {
129 operation_id: op_id,
130 success: false,
131 error: Some(error),
132 });
133 }
134 });
135 }
136 }
137
138 pub fn set_plain_text(&self, text: &str) -> Result<()> {
142 let queued = {
143 let mut inner = self.inner.lock();
144 let dto = frontend::document_io::ImportPlainTextDto {
145 plain_text: text.into(),
146 };
147 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
148 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
149 inner.invalidate_text_cache();
150 inner.queue_event(DocumentEvent::DocumentReset);
151 inner.check_block_count_changed();
152 inner.queue_event(DocumentEvent::UndoRedoChanged {
153 can_undo: false,
154 can_redo: false,
155 });
156 inner.take_queued_events()
157 };
158 crate::inner::dispatch_queued_events(queued);
159 Ok(())
160 }
161
162 pub fn to_plain_text(&self) -> Result<String> {
164 let mut inner = self.inner.lock();
165 Ok(inner.plain_text()?.to_string())
166 }
167
168 pub fn set_markdown(&self, markdown: &str) -> Result<Operation<MarkdownImportResult>> {
172 let mut inner = self.inner.lock();
173 inner.invalidate_text_cache();
174 let dto = frontend::document_io::ImportMarkdownDto {
175 markdown_text: markdown.into(),
176 };
177 let op_id = document_io_commands::import_markdown(&inner.ctx, &dto)?;
178 Ok(Operation::new(
179 op_id,
180 &inner.ctx,
181 Box::new(|ctx, id| {
182 document_io_commands::get_import_markdown_result(ctx, id)
183 .ok()
184 .flatten()
185 .map(|r| {
186 Ok(MarkdownImportResult {
187 block_count: to_usize(r.block_count),
188 })
189 })
190 }),
191 ))
192 }
193
194 pub fn to_markdown(&self) -> Result<String> {
196 let inner = self.inner.lock();
197 let dto = document_io_commands::export_markdown(&inner.ctx)?;
198 Ok(dto.markdown_text)
199 }
200
201 pub fn set_html(&self, html: &str) -> Result<Operation<HtmlImportResult>> {
205 let mut inner = self.inner.lock();
206 inner.invalidate_text_cache();
207 let dto = frontend::document_io::ImportHtmlDto {
208 html_text: html.into(),
209 };
210 let op_id = document_io_commands::import_html(&inner.ctx, &dto)?;
211 Ok(Operation::new(
212 op_id,
213 &inner.ctx,
214 Box::new(|ctx, id| {
215 document_io_commands::get_import_html_result(ctx, id)
216 .ok()
217 .flatten()
218 .map(|r| {
219 Ok(HtmlImportResult {
220 block_count: to_usize(r.block_count),
221 })
222 })
223 }),
224 ))
225 }
226
227 pub fn to_html(&self) -> Result<String> {
229 let inner = self.inner.lock();
230 let dto = document_io_commands::export_html(&inner.ctx)?;
231 Ok(dto.html_text)
232 }
233
234 pub fn to_latex(&self, document_class: &str, include_preamble: bool) -> Result<String> {
236 let inner = self.inner.lock();
237 let dto = frontend::document_io::ExportLatexDto {
238 document_class: document_class.into(),
239 include_preamble,
240 };
241 let result = document_io_commands::export_latex(&inner.ctx, &dto)?;
242 Ok(result.latex_text)
243 }
244
245 pub fn to_docx(&self, output_path: &str) -> Result<Operation<DocxExportResult>> {
249 let inner = self.inner.lock();
250 let dto = frontend::document_io::ExportDocxDto {
251 output_path: output_path.into(),
252 };
253 let op_id = document_io_commands::export_docx(&inner.ctx, &dto)?;
254 Ok(Operation::new(
255 op_id,
256 &inner.ctx,
257 Box::new(|ctx, id| {
258 document_io_commands::get_export_docx_result(ctx, id)
259 .ok()
260 .flatten()
261 .map(|r| {
262 Ok(DocxExportResult {
263 file_path: r.file_path,
264 paragraph_count: to_usize(r.paragraph_count),
265 })
266 })
267 }),
268 ))
269 }
270
271 pub fn clear(&self) -> Result<()> {
273 let queued = {
274 let mut inner = self.inner.lock();
275 let dto = frontend::document_io::ImportPlainTextDto {
276 plain_text: String::new(),
277 };
278 document_io_commands::import_plain_text(&inner.ctx, &dto)?;
279 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
280 inner.invalidate_text_cache();
281 inner.queue_event(DocumentEvent::DocumentReset);
282 inner.check_block_count_changed();
283 inner.queue_event(DocumentEvent::UndoRedoChanged {
284 can_undo: false,
285 can_redo: false,
286 });
287 inner.take_queued_events()
288 };
289 crate::inner::dispatch_queued_events(queued);
290 Ok(())
291 }
292
293 pub fn cursor(&self) -> TextCursor {
297 self.cursor_at(0)
298 }
299
300 pub fn cursor_at(&self, position: usize) -> TextCursor {
302 let data = {
303 let mut inner = self.inner.lock();
304 inner.register_cursor(position)
305 };
306 TextCursor {
307 doc: self.inner.clone(),
308 data,
309 }
310 }
311
312 pub fn stats(&self) -> DocumentStats {
316 let inner = self.inner.lock();
317 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
318 .expect("get_document_stats should not fail");
319 DocumentStats::from(&dto)
320 }
321
322 pub fn character_count(&self) -> usize {
324 let inner = self.inner.lock();
325 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
326 .expect("get_document_stats should not fail");
327 to_usize(dto.character_count)
328 }
329
330 pub fn block_count(&self) -> usize {
332 let inner = self.inner.lock();
333 let dto = document_inspection_commands::get_document_stats(&inner.ctx)
334 .expect("get_document_stats should not fail");
335 to_usize(dto.block_count)
336 }
337
338 pub fn is_empty(&self) -> bool {
340 self.character_count() == 0
341 }
342
343 pub fn text_at(&self, position: usize, length: usize) -> Result<String> {
345 let inner = self.inner.lock();
346 let dto = frontend::document_inspection::GetTextAtPositionDto {
347 position: to_i64(position),
348 length: to_i64(length),
349 };
350 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
351 Ok(result.text)
352 }
353
354 pub fn block_at(&self, position: usize) -> Result<BlockInfo> {
356 let inner = self.inner.lock();
357 let dto = frontend::document_inspection::GetBlockAtPositionDto {
358 position: to_i64(position),
359 };
360 let result = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
361 Ok(BlockInfo::from(&result))
362 }
363
364 pub fn block_format_at(&self, position: usize) -> Result<BlockFormat> {
366 let inner = self.inner.lock();
367 let dto = frontend::document_inspection::GetBlockAtPositionDto {
368 position: to_i64(position),
369 };
370 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
371 let block_id = block_info.block_id;
372 let block_id = block_id as u64;
373 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
374 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
375 Ok(BlockFormat::from(&block_dto))
376 }
377
378 pub fn find(
382 &self,
383 query: &str,
384 from: usize,
385 options: &FindOptions,
386 ) -> Result<Option<FindMatch>> {
387 let inner = self.inner.lock();
388 let dto = options.to_find_text_dto(query, from);
389 let result = document_search_commands::find_text(&inner.ctx, &dto)?;
390 Ok(convert::find_result_to_match(&result))
391 }
392
393 pub fn find_all(&self, query: &str, options: &FindOptions) -> Result<Vec<FindMatch>> {
395 let inner = self.inner.lock();
396 let dto = options.to_find_all_dto(query);
397 let result = document_search_commands::find_all(&inner.ctx, &dto)?;
398 Ok(convert::find_all_to_matches(&result))
399 }
400
401 pub fn replace_text(
403 &self,
404 query: &str,
405 replacement: &str,
406 replace_all: bool,
407 options: &FindOptions,
408 ) -> Result<usize> {
409 let (count, queued) = {
410 let mut inner = self.inner.lock();
411 let dto = options.to_replace_dto(query, replacement, replace_all);
412 let result =
413 document_search_commands::replace_text(&inner.ctx, Some(inner.stack_id), &dto)?;
414 let count = to_usize(result.replacements_count);
415 inner.invalidate_text_cache();
416 if count > 0 {
417 inner.modified = true;
418 inner.queue_event(DocumentEvent::ContentsChanged {
419 position: 0,
420 chars_removed: 0,
421 chars_added: 0,
422 blocks_affected: 0,
423 });
424 inner.check_block_count_changed();
425 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
426 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
427 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
428 }
429 (count, inner.take_queued_events())
430 };
431 crate::inner::dispatch_queued_events(queued);
432 Ok(count)
433 }
434
435 pub fn add_resource(
439 &self,
440 resource_type: ResourceType,
441 name: &str,
442 mime_type: &str,
443 data: &[u8],
444 ) -> Result<()> {
445 let mut inner = self.inner.lock();
446 let dto = frontend::resource::dtos::CreateResourceDto {
447 created_at: Default::default(),
448 updated_at: Default::default(),
449 resource_type,
450 name: name.into(),
451 url: String::new(),
452 mime_type: mime_type.into(),
453 data_base64: BASE64.encode(data),
454 };
455 let created = resource_commands::create_resource(
456 &inner.ctx,
457 Some(inner.stack_id),
458 &dto,
459 inner.document_id,
460 -1,
461 )?;
462 inner.resource_cache.insert(name.to_string(), created.id);
463 Ok(())
464 }
465
466 pub fn resource(&self, name: &str) -> Result<Option<Vec<u8>>> {
470 let mut inner = self.inner.lock();
471
472 if let Some(&id) = inner.resource_cache.get(name) {
474 if let Some(r) = resource_commands::get_resource(&inner.ctx, &id)? {
475 let bytes = BASE64.decode(&r.data_base64)?;
476 return Ok(Some(bytes));
477 }
478 inner.resource_cache.remove(name);
480 }
481
482 let all = resource_commands::get_all_resource(&inner.ctx)?;
484 for r in &all {
485 if r.name == name {
486 inner.resource_cache.insert(name.to_string(), r.id);
487 let bytes = BASE64.decode(&r.data_base64)?;
488 return Ok(Some(bytes));
489 }
490 }
491 Ok(None)
492 }
493
494 pub fn undo(&self) -> Result<()> {
498 let queued = {
499 let mut inner = self.inner.lock();
500 let result = undo_redo_commands::undo(&inner.ctx, Some(inner.stack_id));
501 inner.invalidate_text_cache();
502 result?;
503 inner.check_block_count_changed();
504 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
505 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
506 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
507 inner.take_queued_events()
508 };
509 crate::inner::dispatch_queued_events(queued);
510 Ok(())
511 }
512
513 pub fn redo(&self) -> Result<()> {
515 let queued = {
516 let mut inner = self.inner.lock();
517 let result = undo_redo_commands::redo(&inner.ctx, Some(inner.stack_id));
518 inner.invalidate_text_cache();
519 result?;
520 inner.check_block_count_changed();
521 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
522 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
523 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
524 inner.take_queued_events()
525 };
526 crate::inner::dispatch_queued_events(queued);
527 Ok(())
528 }
529
530 pub fn can_undo(&self) -> bool {
532 let inner = self.inner.lock();
533 undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id))
534 }
535
536 pub fn can_redo(&self) -> bool {
538 let inner = self.inner.lock();
539 undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id))
540 }
541
542 pub fn clear_undo_redo(&self) {
544 let inner = self.inner.lock();
545 undo_redo_commands::clear_stack(&inner.ctx, inner.stack_id);
546 }
547
548 pub fn is_modified(&self) -> bool {
552 self.inner.lock().modified
553 }
554
555 pub fn set_modified(&self, modified: bool) {
557 let queued = {
558 let mut inner = self.inner.lock();
559 if inner.modified != modified {
560 inner.modified = modified;
561 inner.queue_event(DocumentEvent::ModificationChanged(modified));
562 }
563 inner.take_queued_events()
564 };
565 crate::inner::dispatch_queued_events(queued);
566 }
567
568 pub fn title(&self) -> String {
572 let inner = self.inner.lock();
573 document_commands::get_document(&inner.ctx, &inner.document_id)
574 .ok()
575 .flatten()
576 .map(|d| d.title)
577 .unwrap_or_default()
578 }
579
580 pub fn set_title(&self, title: &str) -> Result<()> {
582 let inner = self.inner.lock();
583 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
584 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
585 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
586 update.title = title.into();
587 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
588 Ok(())
589 }
590
591 pub fn text_direction(&self) -> TextDirection {
593 let inner = self.inner.lock();
594 document_commands::get_document(&inner.ctx, &inner.document_id)
595 .ok()
596 .flatten()
597 .map(|d| d.text_direction)
598 .unwrap_or(TextDirection::LeftToRight)
599 }
600
601 pub fn set_text_direction(&self, direction: TextDirection) -> Result<()> {
603 let inner = self.inner.lock();
604 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
605 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
606 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
607 update.text_direction = direction;
608 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
609 Ok(())
610 }
611
612 pub fn default_wrap_mode(&self) -> WrapMode {
614 let inner = self.inner.lock();
615 document_commands::get_document(&inner.ctx, &inner.document_id)
616 .ok()
617 .flatten()
618 .map(|d| d.default_wrap_mode)
619 .unwrap_or(WrapMode::WordWrap)
620 }
621
622 pub fn set_default_wrap_mode(&self, mode: WrapMode) -> Result<()> {
624 let inner = self.inner.lock();
625 let doc = document_commands::get_document(&inner.ctx, &inner.document_id)?
626 .ok_or_else(|| anyhow::anyhow!("document not found"))?;
627 let mut update: frontend::document::dtos::UpdateDocumentDto = doc.into();
628 update.default_wrap_mode = mode;
629 document_commands::update_document(&inner.ctx, Some(inner.stack_id), &update)?;
630 Ok(())
631 }
632
633 pub fn on_change<F>(&self, callback: F) -> Subscription
652 where
653 F: Fn(DocumentEvent) + Send + Sync + 'static,
654 {
655 let mut inner = self.inner.lock();
656 events::subscribe_inner(&mut inner, callback)
657 }
658
659 pub fn poll_events(&self) -> Vec<DocumentEvent> {
665 let mut inner = self.inner.lock();
666 inner.drain_poll_events()
667 }
668}
669
670impl Default for TextDocument {
671 fn default() -> Self {
672 Self::new()
673 }
674}
675
676fn parse_progress_data(data: &Option<String>) -> (String, f64, String) {
680 let Some(json) = data else {
681 return (String::new(), 0.0, String::new());
682 };
683 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
684 let id = v["id"].as_str().unwrap_or_default().to_string();
685 let pct = v["percentage"].as_f64().unwrap_or(0.0);
686 let msg = v["message"].as_str().unwrap_or_default().to_string();
687 (id, pct, msg)
688}
689
690fn parse_id_data(data: &Option<String>) -> String {
692 let Some(json) = data else {
693 return String::new();
694 };
695 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
696 v["id"].as_str().unwrap_or_default().to_string()
697}
698
699fn parse_failed_data(data: &Option<String>) -> (String, String) {
701 let Some(json) = data else {
702 return (String::new(), "unknown error".into());
703 };
704 let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
705 let id = v["id"].as_str().unwrap_or_default().to_string();
706 let error = v["error"].as_str().unwrap_or("unknown error").to_string();
707 (id, error)
708}