1use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use anyhow::Result;
8
9use crate::ListStyle;
10use frontend::commands::{
11 document_editing_commands, document_formatting_commands, document_inspection_commands,
12 inline_element_commands, undo_redo_commands,
13};
14
15use unicode_segmentation::UnicodeSegmentation;
16
17use crate::convert::{to_i64, to_usize};
18use crate::events::DocumentEvent;
19use crate::flow::TableCellRef;
20use crate::fragment::DocumentFragment;
21use crate::inner::{CursorData, QueuedEvents, TextDocumentInner};
22use crate::text_table::TextTable;
23use crate::{BlockFormat, FrameFormat, MoveMode, MoveOperation, SelectionType, TextFormat};
24
25fn max_cursor_position(stats: &frontend::document_inspection::DocumentStatsDto) -> usize {
31 let chars = to_usize(stats.character_count);
32 let blocks = to_usize(stats.block_count);
33 if blocks > 1 {
34 chars + blocks - 1
35 } else {
36 chars
37 }
38}
39
40pub struct TextCursor {
48 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
49 pub(crate) data: Arc<Mutex<CursorData>>,
50}
51
52impl Clone for TextCursor {
53 fn clone(&self) -> Self {
54 let (position, anchor) = {
55 let d = self.data.lock();
56 (d.position, d.anchor)
57 };
58 let data = {
59 let mut inner = self.doc.lock();
60 let data = Arc::new(Mutex::new(CursorData { position, anchor }));
61 inner.cursors.push(Arc::downgrade(&data));
62 data
63 };
64 TextCursor {
65 doc: self.doc.clone(),
66 data,
67 }
68 }
69}
70
71impl TextCursor {
72 fn read_cursor(&self) -> (usize, usize) {
75 let d = self.data.lock();
76 (d.position, d.anchor)
77 }
78
79 fn finish_edit(
83 &self,
84 inner: &mut TextDocumentInner,
85 edit_pos: usize,
86 removed: usize,
87 new_pos: usize,
88 blocks_affected: usize,
89 ) -> QueuedEvents {
90 let added = new_pos - edit_pos;
91 inner.adjust_cursors(edit_pos, removed, added);
92 {
93 let mut d = self.data.lock();
94 d.position = new_pos;
95 d.anchor = new_pos;
96 }
97 inner.modified = true;
98 inner.invalidate_text_cache();
99 inner.rehighlight_affected(edit_pos);
100 inner.queue_event(DocumentEvent::ContentsChanged {
101 position: edit_pos,
102 chars_removed: removed,
103 chars_added: added,
104 blocks_affected,
105 });
106 inner.check_block_count_changed();
107 inner.check_flow_changed();
108 self.queue_undo_redo_event(inner)
109 }
110
111 pub fn position(&self) -> usize {
115 self.data.lock().position
116 }
117
118 pub fn anchor(&self) -> usize {
120 self.data.lock().anchor
121 }
122
123 pub fn has_selection(&self) -> bool {
125 let d = self.data.lock();
126 d.position != d.anchor
127 }
128
129 pub fn selection_start(&self) -> usize {
131 let d = self.data.lock();
132 d.position.min(d.anchor)
133 }
134
135 pub fn selection_end(&self) -> usize {
137 let d = self.data.lock();
138 d.position.max(d.anchor)
139 }
140
141 pub fn selected_text(&self) -> Result<String> {
143 let (pos, anchor) = self.read_cursor();
144 if pos == anchor {
145 return Ok(String::new());
146 }
147 let start = pos.min(anchor);
148 let len = pos.max(anchor) - start;
149 let inner = self.doc.lock();
150 let dto = frontend::document_inspection::GetTextAtPositionDto {
151 position: to_i64(start),
152 length: to_i64(len),
153 };
154 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
155 Ok(result.text)
156 }
157
158 pub fn clear_selection(&self) {
160 let mut d = self.data.lock();
161 d.anchor = d.position;
162 }
163
164 pub fn at_block_start(&self) -> bool {
168 let pos = self.position();
169 let inner = self.doc.lock();
170 let dto = frontend::document_inspection::GetBlockAtPositionDto {
171 position: to_i64(pos),
172 };
173 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
174 pos == to_usize(info.block_start)
175 } else {
176 false
177 }
178 }
179
180 pub fn at_block_end(&self) -> bool {
182 let pos = self.position();
183 let inner = self.doc.lock();
184 let dto = frontend::document_inspection::GetBlockAtPositionDto {
185 position: to_i64(pos),
186 };
187 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
188 pos == to_usize(info.block_start) + to_usize(info.block_length)
189 } else {
190 false
191 }
192 }
193
194 pub fn at_start(&self) -> bool {
196 self.data.lock().position == 0
197 }
198
199 pub fn at_end(&self) -> bool {
201 let pos = self.position();
202 let inner = self.doc.lock();
203 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
204 frontend::document_inspection::DocumentStatsDto {
205 character_count: 0,
206 word_count: 0,
207 block_count: 0,
208 frame_count: 0,
209 image_count: 0,
210 list_count: 0,
211 table_count: 0,
212 }
213 });
214 pos >= max_cursor_position(&stats)
215 }
216
217 pub fn block_number(&self) -> usize {
219 let pos = self.position();
220 let inner = self.doc.lock();
221 let dto = frontend::document_inspection::GetBlockAtPositionDto {
222 position: to_i64(pos),
223 };
224 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
225 .map(|info| to_usize(info.block_number))
226 .unwrap_or(0)
227 }
228
229 pub fn position_in_block(&self) -> usize {
231 let pos = self.position();
232 let inner = self.doc.lock();
233 let dto = frontend::document_inspection::GetBlockAtPositionDto {
234 position: to_i64(pos),
235 };
236 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
237 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
238 .unwrap_or(0)
239 }
240
241 pub fn set_position(&self, position: usize, mode: MoveMode) {
245 let end = {
247 let inner = self.doc.lock();
248 document_inspection_commands::get_document_stats(&inner.ctx)
249 .map(|s| max_cursor_position(&s))
250 .unwrap_or(0)
251 };
252 let pos = position.min(end);
253 let mut d = self.data.lock();
254 d.position = pos;
255 if mode == MoveMode::MoveAnchor {
256 d.anchor = pos;
257 }
258 }
259
260 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
266 let old_pos = self.position();
267 let target = self.resolve_move(operation, n);
268 self.set_position(target, mode);
269 self.position() != old_pos
270 }
271
272 pub fn select(&self, selection: SelectionType) {
274 match selection {
275 SelectionType::Document => {
276 let end = {
277 let inner = self.doc.lock();
278 document_inspection_commands::get_document_stats(&inner.ctx)
279 .map(|s| max_cursor_position(&s))
280 .unwrap_or(0)
281 };
282 let mut d = self.data.lock();
283 d.anchor = 0;
284 d.position = end;
285 }
286 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
287 let pos = self.position();
288 let inner = self.doc.lock();
289 let dto = frontend::document_inspection::GetBlockAtPositionDto {
290 position: to_i64(pos),
291 };
292 if let Ok(info) =
293 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
294 {
295 let start = to_usize(info.block_start);
296 let end = start + to_usize(info.block_length);
297 drop(inner);
298 let mut d = self.data.lock();
299 d.anchor = start;
300 d.position = end;
301 }
302 }
303 SelectionType::WordUnderCursor => {
304 let pos = self.position();
305 let (word_start, word_end) = self.find_word_boundaries(pos);
306 let mut d = self.data.lock();
307 d.anchor = word_start;
308 d.position = word_end;
309 }
310 }
311 }
312
313 pub fn insert_text(&self, text: &str) -> Result<()> {
317 let (pos, anchor) = self.read_cursor();
318
319 let dto = frontend::document_editing::InsertTextDto {
321 position: to_i64(pos),
322 anchor: to_i64(anchor),
323 text: text.into(),
324 };
325
326 let queued = {
327 let mut inner = self.doc.lock();
328 let result = match document_editing_commands::insert_text(
329 &inner.ctx,
330 Some(inner.stack_id),
331 &dto,
332 ) {
333 Ok(r) => r,
334 Err(_) if pos != anchor => {
335 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
337
338 let del_dto = frontend::document_editing::DeleteTextDto {
339 position: to_i64(pos),
340 anchor: to_i64(anchor),
341 };
342 let del_result = document_editing_commands::delete_text(
343 &inner.ctx,
344 Some(inner.stack_id),
345 &del_dto,
346 )?;
347 let del_pos = to_usize(del_result.new_position);
348
349 let ins_dto = frontend::document_editing::InsertTextDto {
350 position: to_i64(del_pos),
351 anchor: to_i64(del_pos),
352 text: text.into(),
353 };
354 let ins_result = document_editing_commands::insert_text(
355 &inner.ctx,
356 Some(inner.stack_id),
357 &ins_dto,
358 )?;
359
360 undo_redo_commands::end_composite(&inner.ctx);
361 ins_result
362 }
363 Err(e) => return Err(e),
364 };
365
366 let edit_pos = pos.min(anchor);
367 let removed = pos.max(anchor) - edit_pos;
368 self.finish_edit(
369 &mut inner,
370 edit_pos,
371 removed,
372 to_usize(result.new_position),
373 to_usize(result.blocks_affected),
374 )
375 };
376 crate::inner::dispatch_queued_events(queued);
377 Ok(())
378 }
379
380 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
382 let (pos, anchor) = self.read_cursor();
383 let queued = {
384 let mut inner = self.doc.lock();
385 let dto = frontend::document_editing::InsertFormattedTextDto {
386 position: to_i64(pos),
387 anchor: to_i64(anchor),
388 text: text.into(),
389 font_family: format.font_family.clone().unwrap_or_default(),
390 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
391 font_bold: format.font_bold.unwrap_or(false),
392 font_italic: format.font_italic.unwrap_or(false),
393 font_underline: format.font_underline.unwrap_or(false),
394 font_strikeout: format.font_strikeout.unwrap_or(false),
395 };
396 let result = document_editing_commands::insert_formatted_text(
397 &inner.ctx,
398 Some(inner.stack_id),
399 &dto,
400 )?;
401 let edit_pos = pos.min(anchor);
402 let removed = pos.max(anchor) - edit_pos;
403 self.finish_edit(
404 &mut inner,
405 edit_pos,
406 removed,
407 to_usize(result.new_position),
408 1,
409 )
410 };
411 crate::inner::dispatch_queued_events(queued);
412 Ok(())
413 }
414
415 pub fn insert_block(&self) -> Result<()> {
417 let (pos, anchor) = self.read_cursor();
418 let queued = {
419 let mut inner = self.doc.lock();
420 let dto = frontend::document_editing::InsertBlockDto {
421 position: to_i64(pos),
422 anchor: to_i64(anchor),
423 };
424 let result =
425 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
426 let edit_pos = pos.min(anchor);
427 let removed = pos.max(anchor) - edit_pos;
428 self.finish_edit(
429 &mut inner,
430 edit_pos,
431 removed,
432 to_usize(result.new_position),
433 2,
434 )
435 };
436 crate::inner::dispatch_queued_events(queued);
437 Ok(())
438 }
439
440 pub fn insert_html(&self, html: &str) -> Result<()> {
442 let (pos, anchor) = self.read_cursor();
443 let queued = {
444 let mut inner = self.doc.lock();
445 let dto = frontend::document_editing::InsertHtmlAtPositionDto {
446 position: to_i64(pos),
447 anchor: to_i64(anchor),
448 html: html.into(),
449 };
450 let result = document_editing_commands::insert_html_at_position(
451 &inner.ctx,
452 Some(inner.stack_id),
453 &dto,
454 )?;
455 let edit_pos = pos.min(anchor);
456 let removed = pos.max(anchor) - edit_pos;
457 self.finish_edit(
458 &mut inner,
459 edit_pos,
460 removed,
461 to_usize(result.new_position),
462 to_usize(result.blocks_added),
463 )
464 };
465 crate::inner::dispatch_queued_events(queued);
466 Ok(())
467 }
468
469 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
471 let (pos, anchor) = self.read_cursor();
472 let queued = {
473 let mut inner = self.doc.lock();
474 let dto = frontend::document_editing::InsertMarkdownAtPositionDto {
475 position: to_i64(pos),
476 anchor: to_i64(anchor),
477 markdown: markdown.into(),
478 };
479 let result = document_editing_commands::insert_markdown_at_position(
480 &inner.ctx,
481 Some(inner.stack_id),
482 &dto,
483 )?;
484 let edit_pos = pos.min(anchor);
485 let removed = pos.max(anchor) - edit_pos;
486 self.finish_edit(
487 &mut inner,
488 edit_pos,
489 removed,
490 to_usize(result.new_position),
491 to_usize(result.blocks_added),
492 )
493 };
494 crate::inner::dispatch_queued_events(queued);
495 Ok(())
496 }
497
498 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
500 let (pos, anchor) = self.read_cursor();
501 let queued = {
502 let mut inner = self.doc.lock();
503 let dto = frontend::document_editing::InsertFragmentDto {
504 position: to_i64(pos),
505 anchor: to_i64(anchor),
506 fragment_data: fragment.raw_data().into(),
507 };
508 let result =
509 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
510 let edit_pos = pos.min(anchor);
511 let removed = pos.max(anchor) - edit_pos;
512 self.finish_edit(
513 &mut inner,
514 edit_pos,
515 removed,
516 to_usize(result.new_position),
517 to_usize(result.blocks_added),
518 )
519 };
520 crate::inner::dispatch_queued_events(queued);
521 Ok(())
522 }
523
524 pub fn selection(&self) -> DocumentFragment {
526 let (pos, anchor) = self.read_cursor();
527 if pos == anchor {
528 return DocumentFragment::new();
529 }
530 let inner = self.doc.lock();
531 let dto = frontend::document_inspection::ExtractFragmentDto {
532 position: to_i64(pos),
533 anchor: to_i64(anchor),
534 };
535 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
536 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
537 Err(_) => DocumentFragment::new(),
538 }
539 }
540
541 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
543 let (pos, anchor) = self.read_cursor();
544 let queued = {
545 let mut inner = self.doc.lock();
546 let dto = frontend::document_editing::InsertImageDto {
547 position: to_i64(pos),
548 anchor: to_i64(anchor),
549 image_name: name.into(),
550 width: width as i64,
551 height: height as i64,
552 };
553 let result =
554 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
555 let edit_pos = pos.min(anchor);
556 let removed = pos.max(anchor) - edit_pos;
557 self.finish_edit(
558 &mut inner,
559 edit_pos,
560 removed,
561 to_usize(result.new_position),
562 1,
563 )
564 };
565 crate::inner::dispatch_queued_events(queued);
566 Ok(())
567 }
568
569 pub fn insert_frame(&self) -> Result<()> {
571 let (pos, anchor) = self.read_cursor();
572 let queued = {
573 let mut inner = self.doc.lock();
574 let dto = frontend::document_editing::InsertFrameDto {
575 position: to_i64(pos),
576 anchor: to_i64(anchor),
577 };
578 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
579 inner.modified = true;
582 inner.invalidate_text_cache();
583 inner.rehighlight_affected(pos.min(anchor));
584 inner.queue_event(DocumentEvent::ContentsChanged {
585 position: pos.min(anchor),
586 chars_removed: 0,
587 chars_added: 0,
588 blocks_affected: 1,
589 });
590 inner.check_block_count_changed();
591 inner.check_flow_changed();
592 self.queue_undo_redo_event(&mut inner)
593 };
594 crate::inner::dispatch_queued_events(queued);
595 Ok(())
596 }
597
598 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
604 let (pos, anchor) = self.read_cursor();
605 let (table_id, queued) = {
606 let mut inner = self.doc.lock();
607 let dto = frontend::document_editing::InsertTableDto {
608 position: to_i64(pos),
609 anchor: to_i64(anchor),
610 rows: to_i64(rows),
611 columns: to_i64(columns),
612 };
613 let result =
614 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
615 let new_pos = to_usize(result.new_position);
616 let table_id = to_usize(result.table_id);
617 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
618 {
619 let mut d = self.data.lock();
620 d.position = new_pos;
621 d.anchor = new_pos;
622 }
623 inner.modified = true;
624 inner.invalidate_text_cache();
625 inner.rehighlight_affected(pos.min(anchor));
626 inner.queue_event(DocumentEvent::ContentsChanged {
627 position: pos.min(anchor),
628 chars_removed: 0,
629 chars_added: new_pos - pos.min(anchor),
630 blocks_affected: 1,
631 });
632 inner.check_block_count_changed();
633 inner.check_flow_changed();
634 (table_id, self.queue_undo_redo_event(&mut inner))
635 };
636 crate::inner::dispatch_queued_events(queued);
637 Ok(TextTable {
638 doc: self.doc.clone(),
639 table_id,
640 })
641 }
642
643 pub fn current_table(&self) -> Option<TextTable> {
648 self.current_table_cell().map(|c| c.table)
649 }
650
651 pub fn current_table_cell(&self) -> Option<TableCellRef> {
656 let pos = self.position();
657 let inner = self.doc.lock();
658 let dto = frontend::document_inspection::GetBlockAtPositionDto {
660 position: to_i64(pos),
661 };
662 let block_info =
663 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
664 let block = crate::text_block::TextBlock {
665 doc: self.doc.clone(),
666 block_id: block_info.block_id as usize,
667 };
668 drop(inner);
670 block.table_cell()
671 }
672
673 pub fn remove_table(&self, table_id: usize) -> Result<()> {
677 let queued = {
678 let mut inner = self.doc.lock();
679 let dto = frontend::document_editing::RemoveTableDto {
680 table_id: to_i64(table_id),
681 };
682 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
683 inner.modified = true;
684 inner.invalidate_text_cache();
685 inner.rehighlight_all();
686 inner.check_block_count_changed();
687 inner.check_flow_changed();
688 self.queue_undo_redo_event(&mut inner)
689 };
690 crate::inner::dispatch_queued_events(queued);
691 Ok(())
692 }
693
694 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
696 let queued = {
697 let mut inner = self.doc.lock();
698 let dto = frontend::document_editing::InsertTableRowDto {
699 table_id: to_i64(table_id),
700 row_index: to_i64(row_index),
701 };
702 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
703 inner.modified = true;
704 inner.invalidate_text_cache();
705 inner.rehighlight_all();
706 inner.check_block_count_changed();
707 self.queue_undo_redo_event(&mut inner)
708 };
709 crate::inner::dispatch_queued_events(queued);
710 Ok(())
711 }
712
713 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
715 let queued = {
716 let mut inner = self.doc.lock();
717 let dto = frontend::document_editing::InsertTableColumnDto {
718 table_id: to_i64(table_id),
719 column_index: to_i64(column_index),
720 };
721 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
722 inner.modified = true;
723 inner.invalidate_text_cache();
724 inner.rehighlight_all();
725 inner.check_block_count_changed();
726 self.queue_undo_redo_event(&mut inner)
727 };
728 crate::inner::dispatch_queued_events(queued);
729 Ok(())
730 }
731
732 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
734 let queued = {
735 let mut inner = self.doc.lock();
736 let dto = frontend::document_editing::RemoveTableRowDto {
737 table_id: to_i64(table_id),
738 row_index: to_i64(row_index),
739 };
740 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
741 inner.modified = true;
742 inner.invalidate_text_cache();
743 inner.rehighlight_all();
744 inner.check_block_count_changed();
745 self.queue_undo_redo_event(&mut inner)
746 };
747 crate::inner::dispatch_queued_events(queued);
748 Ok(())
749 }
750
751 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
753 let queued = {
754 let mut inner = self.doc.lock();
755 let dto = frontend::document_editing::RemoveTableColumnDto {
756 table_id: to_i64(table_id),
757 column_index: to_i64(column_index),
758 };
759 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
760 inner.modified = true;
761 inner.invalidate_text_cache();
762 inner.rehighlight_all();
763 inner.check_block_count_changed();
764 self.queue_undo_redo_event(&mut inner)
765 };
766 crate::inner::dispatch_queued_events(queued);
767 Ok(())
768 }
769
770 pub fn merge_table_cells(
772 &self,
773 table_id: usize,
774 start_row: usize,
775 start_column: usize,
776 end_row: usize,
777 end_column: usize,
778 ) -> Result<()> {
779 let queued = {
780 let mut inner = self.doc.lock();
781 let dto = frontend::document_editing::MergeTableCellsDto {
782 table_id: to_i64(table_id),
783 start_row: to_i64(start_row),
784 start_column: to_i64(start_column),
785 end_row: to_i64(end_row),
786 end_column: to_i64(end_column),
787 };
788 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
789 inner.modified = true;
790 inner.invalidate_text_cache();
791 inner.rehighlight_all();
792 inner.check_block_count_changed();
793 self.queue_undo_redo_event(&mut inner)
794 };
795 crate::inner::dispatch_queued_events(queued);
796 Ok(())
797 }
798
799 pub fn split_table_cell(
801 &self,
802 cell_id: usize,
803 split_rows: usize,
804 split_columns: usize,
805 ) -> Result<()> {
806 let queued = {
807 let mut inner = self.doc.lock();
808 let dto = frontend::document_editing::SplitTableCellDto {
809 cell_id: to_i64(cell_id),
810 split_rows: to_i64(split_rows),
811 split_columns: to_i64(split_columns),
812 };
813 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
814 inner.modified = true;
815 inner.invalidate_text_cache();
816 inner.rehighlight_all();
817 inner.check_block_count_changed();
818 self.queue_undo_redo_event(&mut inner)
819 };
820 crate::inner::dispatch_queued_events(queued);
821 Ok(())
822 }
823
824 pub fn set_table_format(
828 &self,
829 table_id: usize,
830 format: &crate::flow::TableFormat,
831 ) -> Result<()> {
832 let queued = {
833 let mut inner = self.doc.lock();
834 let dto = format.to_set_dto(table_id);
835 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
836 inner.modified = true;
837 inner.queue_event(DocumentEvent::FormatChanged {
838 position: 0,
839 length: 0,
840 kind: crate::flow::FormatChangeKind::Block,
841 });
842 self.queue_undo_redo_event(&mut inner)
843 };
844 crate::inner::dispatch_queued_events(queued);
845 Ok(())
846 }
847
848 pub fn set_table_cell_format(
850 &self,
851 cell_id: usize,
852 format: &crate::flow::CellFormat,
853 ) -> Result<()> {
854 let queued = {
855 let mut inner = self.doc.lock();
856 let dto = format.to_set_dto(cell_id);
857 document_formatting_commands::set_table_cell_format(
858 &inner.ctx,
859 Some(inner.stack_id),
860 &dto,
861 )?;
862 inner.modified = true;
863 inner.queue_event(DocumentEvent::FormatChanged {
864 position: 0,
865 length: 0,
866 kind: crate::flow::FormatChangeKind::Block,
867 });
868 self.queue_undo_redo_event(&mut inner)
869 };
870 crate::inner::dispatch_queued_events(queued);
871 Ok(())
872 }
873
874 pub fn remove_current_table(&self) -> Result<()> {
879 let table = self
880 .current_table()
881 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
882 self.remove_table(table.id())
883 }
884
885 pub fn insert_row_above(&self) -> Result<()> {
888 let cell_ref = self
889 .current_table_cell()
890 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
891 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
892 }
893
894 pub fn insert_row_below(&self) -> Result<()> {
897 let cell_ref = self
898 .current_table_cell()
899 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
900 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
901 }
902
903 pub fn insert_column_before(&self) -> Result<()> {
906 let cell_ref = self
907 .current_table_cell()
908 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
909 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
910 }
911
912 pub fn insert_column_after(&self) -> Result<()> {
915 let cell_ref = self
916 .current_table_cell()
917 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
918 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
919 }
920
921 pub fn remove_current_row(&self) -> Result<()> {
924 let cell_ref = self
925 .current_table_cell()
926 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
927 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
928 }
929
930 pub fn remove_current_column(&self) -> Result<()> {
933 let cell_ref = self
934 .current_table_cell()
935 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
936 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
937 }
938
939 pub fn merge_selected_cells(&self) -> Result<()> {
946 let pos_cell = self
947 .current_table_cell()
948 .ok_or_else(|| anyhow::anyhow!("cursor position is not inside a table"))?;
949
950 let (_pos, anchor) = self.read_cursor();
952 let anchor_cell = {
953 let inner = self.doc.lock();
955 let dto = frontend::document_inspection::GetBlockAtPositionDto {
956 position: to_i64(anchor),
957 };
958 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
959 .map_err(|_| anyhow::anyhow!("cursor anchor is not inside a table"))?;
960 let block = crate::text_block::TextBlock {
961 doc: self.doc.clone(),
962 block_id: block_info.block_id as usize,
963 };
964 drop(inner);
965 block
966 .table_cell()
967 .ok_or_else(|| anyhow::anyhow!("cursor anchor is not inside a table"))?
968 };
969
970 if pos_cell.table.id() != anchor_cell.table.id() {
971 return Err(anyhow::anyhow!(
972 "position and anchor are in different tables"
973 ));
974 }
975
976 let start_row = pos_cell.row.min(anchor_cell.row);
977 let start_col = pos_cell.column.min(anchor_cell.column);
978 let end_row = pos_cell.row.max(anchor_cell.row);
979 let end_col = pos_cell.column.max(anchor_cell.column);
980
981 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
982 }
983
984 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
987 let cell_ref = self
988 .current_table_cell()
989 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
990 let cell = cell_ref
992 .table
993 .cell(cell_ref.row, cell_ref.column)
994 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
995 self.split_table_cell(cell.id(), split_rows, split_columns)
997 }
998
999 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1002 let table = self
1003 .current_table()
1004 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1005 self.set_table_format(table.id(), format)
1006 }
1007
1008 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1011 let cell_ref = self
1012 .current_table_cell()
1013 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1014 let cell = cell_ref
1015 .table
1016 .cell(cell_ref.row, cell_ref.column)
1017 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1018 self.set_table_cell_format(cell.id(), format)
1019 }
1020
1021 pub fn delete_char(&self) -> Result<()> {
1023 let (pos, anchor) = self.read_cursor();
1024 let (del_pos, del_anchor) = if pos != anchor {
1025 (pos, anchor)
1026 } else {
1027 (pos, pos + 1)
1028 };
1029 self.do_delete(del_pos, del_anchor)
1030 }
1031
1032 pub fn delete_previous_char(&self) -> Result<()> {
1034 let (pos, anchor) = self.read_cursor();
1035 let (del_pos, del_anchor) = if pos != anchor {
1036 (pos, anchor)
1037 } else if pos > 0 {
1038 (pos - 1, pos)
1039 } else {
1040 return Ok(());
1041 };
1042 self.do_delete(del_pos, del_anchor)
1043 }
1044
1045 pub fn remove_selected_text(&self) -> Result<String> {
1047 let (pos, anchor) = self.read_cursor();
1048 if pos == anchor {
1049 return Ok(String::new());
1050 }
1051 let queued = {
1052 let mut inner = self.doc.lock();
1053 let dto = frontend::document_editing::DeleteTextDto {
1054 position: to_i64(pos),
1055 anchor: to_i64(anchor),
1056 };
1057 let result =
1058 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1059 let edit_pos = pos.min(anchor);
1060 let removed = pos.max(anchor) - edit_pos;
1061 let new_pos = to_usize(result.new_position);
1062 inner.adjust_cursors(edit_pos, removed, 0);
1063 {
1064 let mut d = self.data.lock();
1065 d.position = new_pos;
1066 d.anchor = new_pos;
1067 }
1068 inner.modified = true;
1069 inner.invalidate_text_cache();
1070 inner.rehighlight_affected(edit_pos);
1071 inner.queue_event(DocumentEvent::ContentsChanged {
1072 position: edit_pos,
1073 chars_removed: removed,
1074 chars_added: 0,
1075 blocks_affected: 1,
1076 });
1077 inner.check_block_count_changed();
1078 inner.check_flow_changed();
1079 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1081 };
1082 crate::inner::dispatch_queued_events(queued.1);
1083 Ok(queued.0)
1084 }
1085
1086 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1090 let (pos, anchor) = self.read_cursor();
1091 let queued = {
1092 let mut inner = self.doc.lock();
1093 let dto = frontend::document_editing::CreateListDto {
1094 position: to_i64(pos),
1095 anchor: to_i64(anchor),
1096 style: style.clone(),
1097 };
1098 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1099 inner.modified = true;
1100 inner.rehighlight_affected(pos.min(anchor));
1101 inner.queue_event(DocumentEvent::ContentsChanged {
1102 position: pos.min(anchor),
1103 chars_removed: 0,
1104 chars_added: 0,
1105 blocks_affected: 1,
1106 });
1107 self.queue_undo_redo_event(&mut inner)
1108 };
1109 crate::inner::dispatch_queued_events(queued);
1110 Ok(())
1111 }
1112
1113 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
1115 let (pos, anchor) = self.read_cursor();
1116 let queued = {
1117 let mut inner = self.doc.lock();
1118 let dto = frontend::document_editing::InsertListDto {
1119 position: to_i64(pos),
1120 anchor: to_i64(anchor),
1121 style: style.clone(),
1122 };
1123 let result =
1124 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1125 let edit_pos = pos.min(anchor);
1126 let removed = pos.max(anchor) - edit_pos;
1127 self.finish_edit(
1128 &mut inner,
1129 edit_pos,
1130 removed,
1131 to_usize(result.new_position),
1132 1,
1133 )
1134 };
1135 crate::inner::dispatch_queued_events(queued);
1136 Ok(())
1137 }
1138
1139 pub fn char_format(&self) -> Result<TextFormat> {
1143 let pos = self.position();
1144 let inner = self.doc.lock();
1145 let dto = frontend::document_inspection::GetTextAtPositionDto {
1146 position: to_i64(pos),
1147 length: 1,
1148 };
1149 let text_info = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
1150 let element_id = text_info.element_id as u64;
1151 let element = inline_element_commands::get_inline_element(&inner.ctx, &element_id)?
1152 .ok_or_else(|| anyhow::anyhow!("element not found at position"))?;
1153 Ok(TextFormat::from(&element))
1154 }
1155
1156 pub fn block_format(&self) -> Result<BlockFormat> {
1158 let pos = self.position();
1159 let inner = self.doc.lock();
1160 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1161 position: to_i64(pos),
1162 };
1163 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1164 let block_id = block_info.block_id as u64;
1165 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
1166 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
1167 Ok(BlockFormat::from(&block))
1168 }
1169
1170 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
1174 let (pos, anchor) = self.read_cursor();
1175 let queued = {
1176 let mut inner = self.doc.lock();
1177 let dto = format.to_set_dto(pos, anchor);
1178 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1179 let start = pos.min(anchor);
1180 let length = pos.max(anchor) - start;
1181 inner.modified = true;
1182 inner.queue_event(DocumentEvent::FormatChanged {
1183 position: start,
1184 length,
1185 kind: crate::flow::FormatChangeKind::Character,
1186 });
1187 self.queue_undo_redo_event(&mut inner)
1188 };
1189 crate::inner::dispatch_queued_events(queued);
1190 Ok(())
1191 }
1192
1193 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
1195 let (pos, anchor) = self.read_cursor();
1196 let queued = {
1197 let mut inner = self.doc.lock();
1198 let dto = format.to_merge_dto(pos, anchor);
1199 document_formatting_commands::merge_text_format(
1200 &inner.ctx,
1201 Some(inner.stack_id),
1202 &dto,
1203 )?;
1204 let start = pos.min(anchor);
1205 let length = pos.max(anchor) - start;
1206 inner.modified = true;
1207 inner.queue_event(DocumentEvent::FormatChanged {
1208 position: start,
1209 length,
1210 kind: crate::flow::FormatChangeKind::Character,
1211 });
1212 self.queue_undo_redo_event(&mut inner)
1213 };
1214 crate::inner::dispatch_queued_events(queued);
1215 Ok(())
1216 }
1217
1218 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
1220 let (pos, anchor) = self.read_cursor();
1221 let queued = {
1222 let mut inner = self.doc.lock();
1223 let dto = format.to_set_dto(pos, anchor);
1224 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1225 let start = pos.min(anchor);
1226 let length = pos.max(anchor) - start;
1227 inner.modified = true;
1228 inner.queue_event(DocumentEvent::FormatChanged {
1229 position: start,
1230 length,
1231 kind: crate::flow::FormatChangeKind::Block,
1232 });
1233 self.queue_undo_redo_event(&mut inner)
1234 };
1235 crate::inner::dispatch_queued_events(queued);
1236 Ok(())
1237 }
1238
1239 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
1241 let (pos, anchor) = self.read_cursor();
1242 let queued = {
1243 let mut inner = self.doc.lock();
1244 let dto = format.to_set_dto(pos, anchor, frame_id);
1245 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1246 let start = pos.min(anchor);
1247 let length = pos.max(anchor) - start;
1248 inner.modified = true;
1249 inner.queue_event(DocumentEvent::FormatChanged {
1250 position: start,
1251 length,
1252 kind: crate::flow::FormatChangeKind::Block,
1253 });
1254 self.queue_undo_redo_event(&mut inner)
1255 };
1256 crate::inner::dispatch_queued_events(queued);
1257 Ok(())
1258 }
1259
1260 pub fn begin_edit_block(&self) {
1264 let inner = self.doc.lock();
1265 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1266 }
1267
1268 pub fn end_edit_block(&self) {
1270 let inner = self.doc.lock();
1271 undo_redo_commands::end_composite(&inner.ctx);
1272 }
1273
1274 pub fn join_previous_edit_block(&self) {
1281 self.begin_edit_block();
1282 }
1283
1284 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
1288 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1289 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1290 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1291 inner.take_queued_events()
1292 }
1293
1294 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
1295 let queued = {
1296 let mut inner = self.doc.lock();
1297 let dto = frontend::document_editing::DeleteTextDto {
1298 position: to_i64(pos),
1299 anchor: to_i64(anchor),
1300 };
1301 let result =
1302 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1303 let edit_pos = pos.min(anchor);
1304 let removed = pos.max(anchor) - edit_pos;
1305 let new_pos = to_usize(result.new_position);
1306 inner.adjust_cursors(edit_pos, removed, 0);
1307 {
1308 let mut d = self.data.lock();
1309 d.position = new_pos;
1310 d.anchor = new_pos;
1311 }
1312 inner.modified = true;
1313 inner.invalidate_text_cache();
1314 inner.rehighlight_affected(edit_pos);
1315 inner.queue_event(DocumentEvent::ContentsChanged {
1316 position: edit_pos,
1317 chars_removed: removed,
1318 chars_added: 0,
1319 blocks_affected: 1,
1320 });
1321 inner.check_block_count_changed();
1322 inner.check_flow_changed();
1323 self.queue_undo_redo_event(&mut inner)
1324 };
1325 crate::inner::dispatch_queued_events(queued);
1326 Ok(())
1327 }
1328
1329 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
1331 let pos = self.position();
1332 match op {
1333 MoveOperation::NoMove => pos,
1334 MoveOperation::Start => 0,
1335 MoveOperation::End => {
1336 let inner = self.doc.lock();
1337 document_inspection_commands::get_document_stats(&inner.ctx)
1338 .map(|s| max_cursor_position(&s))
1339 .unwrap_or(pos)
1340 }
1341 MoveOperation::NextCharacter | MoveOperation::Right => pos + n,
1342 MoveOperation::PreviousCharacter | MoveOperation::Left => pos.saturating_sub(n),
1343 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
1344 let inner = self.doc.lock();
1345 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1346 position: to_i64(pos),
1347 };
1348 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1349 .map(|info| to_usize(info.block_start))
1350 .unwrap_or(pos)
1351 }
1352 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
1353 let inner = self.doc.lock();
1354 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1355 position: to_i64(pos),
1356 };
1357 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1358 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
1359 .unwrap_or(pos)
1360 }
1361 MoveOperation::NextBlock => {
1362 let inner = self.doc.lock();
1363 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1364 position: to_i64(pos),
1365 };
1366 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1367 .map(|info| {
1368 to_usize(info.block_start) + to_usize(info.block_length) + 1
1370 })
1371 .unwrap_or(pos)
1372 }
1373 MoveOperation::PreviousBlock => {
1374 let inner = self.doc.lock();
1375 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1376 position: to_i64(pos),
1377 };
1378 let block_start =
1379 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1380 .map(|info| to_usize(info.block_start))
1381 .unwrap_or(pos);
1382 if block_start >= 2 {
1383 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1385 position: to_i64(block_start - 2),
1386 };
1387 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
1388 .map(|info| to_usize(info.block_start))
1389 .unwrap_or(0)
1390 } else {
1391 0
1392 }
1393 }
1394 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
1395 let (_, end) = self.find_word_boundaries(pos);
1396 if end == pos {
1398 let inner = self.doc.lock();
1400 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
1401 .map(|s| max_cursor_position(&s))
1402 .unwrap_or(0);
1403 let scan_len = max_pos.saturating_sub(pos).min(64);
1404 if scan_len == 0 {
1405 return pos;
1406 }
1407 let dto = frontend::document_inspection::GetTextAtPositionDto {
1408 position: to_i64(pos),
1409 length: to_i64(scan_len),
1410 };
1411 if let Ok(r) =
1412 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
1413 {
1414 for (i, ch) in r.text.chars().enumerate() {
1415 if ch.is_alphanumeric() || ch == '_' {
1416 let word_pos = pos + i;
1418 drop(inner);
1419 let (_, word_end) = self.find_word_boundaries(word_pos);
1420 return word_end;
1421 }
1422 }
1423 }
1424 pos + scan_len
1425 } else {
1426 end
1427 }
1428 }
1429 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
1430 let (start, _) = self.find_word_boundaries(pos);
1431 if start < pos {
1432 start
1433 } else if pos > 0 {
1434 let mut search = pos - 1;
1437 loop {
1438 let (ws, we) = self.find_word_boundaries(search);
1439 if ws < we {
1440 break ws;
1442 }
1443 if search == 0 {
1445 break 0;
1446 }
1447 search -= 1;
1448 }
1449 } else {
1450 0
1451 }
1452 }
1453 MoveOperation::Up | MoveOperation::Down => {
1454 if matches!(op, MoveOperation::Up) {
1457 self.resolve_move(MoveOperation::PreviousBlock, 1)
1458 } else {
1459 self.resolve_move(MoveOperation::NextBlock, 1)
1460 }
1461 }
1462 }
1463 }
1464
1465 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
1471 let inner = self.doc.lock();
1472 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
1474 position: to_i64(pos),
1475 };
1476 let block_info =
1477 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
1478 Ok(info) => info,
1479 Err(_) => return (pos, pos),
1480 };
1481
1482 let block_start = to_usize(block_info.block_start);
1483 let block_length = to_usize(block_info.block_length);
1484 if block_length == 0 {
1485 return (pos, pos);
1486 }
1487
1488 let dto = frontend::document_inspection::GetTextAtPositionDto {
1489 position: to_i64(block_start),
1490 length: to_i64(block_length),
1491 };
1492 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
1493 Ok(r) => r.text,
1494 Err(_) => return (pos, pos),
1495 };
1496
1497 let cursor_offset = pos.saturating_sub(block_start);
1499
1500 let mut last_char_start = 0;
1502 let mut last_char_end = 0;
1503
1504 for (word_byte_start, word) in text.unicode_word_indices() {
1505 let word_char_start = text[..word_byte_start].chars().count();
1507 let word_char_len = word.chars().count();
1508 let word_char_end = word_char_start + word_char_len;
1509
1510 last_char_start = word_char_start;
1511 last_char_end = word_char_end;
1512
1513 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
1514 return (block_start + word_char_start, block_start + word_char_end);
1515 }
1516 }
1517
1518 if cursor_offset == last_char_end && last_char_start < last_char_end {
1520 return (block_start + last_char_start, block_start + last_char_end);
1521 }
1522
1523 (pos, pos)
1524 }
1525}