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::{CellRange, FlowElement, SelectionKind, 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
25use crate::document::get_main_frame_id;
26
27fn max_cursor_position(stats: &frontend::document_inspection::DocumentStatsDto) -> usize {
33 let chars = to_usize(stats.character_count);
34 let blocks = to_usize(stats.block_count);
35 if blocks > 1 {
36 chars + blocks - 1
37 } else {
38 chars
39 }
40}
41
42pub struct TextCursor {
50 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
51 pub(crate) data: Arc<Mutex<CursorData>>,
52}
53
54impl Clone for TextCursor {
55 fn clone(&self) -> Self {
56 let (position, anchor) = {
57 let d = self.data.lock();
58 (d.position, d.anchor)
59 };
60 let data = {
61 let mut inner = self.doc.lock();
62 let data = Arc::new(Mutex::new(CursorData {
63 position,
64 anchor,
65 cell_selection_override: None,
66 }));
67 inner.cursors.push(Arc::downgrade(&data));
68 data
69 };
70 TextCursor {
71 doc: self.doc.clone(),
72 data,
73 }
74 }
75}
76
77impl TextCursor {
78 fn read_cursor(&self) -> (usize, usize) {
81 let d = self.data.lock();
82 (d.position, d.anchor)
83 }
84
85 fn finish_edit(
89 &self,
90 inner: &mut TextDocumentInner,
91 edit_pos: usize,
92 removed: usize,
93 new_pos: usize,
94 blocks_affected: usize,
95 ) -> QueuedEvents {
96 self.finish_edit_ext(inner, edit_pos, removed, new_pos, blocks_affected, true)
97 }
98
99 fn finish_edit_ext(
100 &self,
101 inner: &mut TextDocumentInner,
102 edit_pos: usize,
103 removed: usize,
104 new_pos: usize,
105 blocks_affected: usize,
106 flow_may_change: bool,
107 ) -> QueuedEvents {
108 let added = new_pos - edit_pos;
109 inner.adjust_cursors(edit_pos, removed, added);
110 {
111 let mut d = self.data.lock();
112 d.position = new_pos;
113 d.anchor = new_pos;
114 }
115 inner.modified = true;
116 inner.invalidate_text_cache();
117 inner.rehighlight_affected(edit_pos);
118 inner.queue_event(DocumentEvent::ContentsChanged {
119 position: edit_pos,
120 chars_removed: removed,
121 chars_added: added,
122 blocks_affected,
123 });
124 inner.check_block_count_changed();
125 if flow_may_change {
126 inner.check_flow_changed();
127 }
128 self.queue_undo_redo_event(inner)
129 }
130
131 pub fn position(&self) -> usize {
135 self.data.lock().position
136 }
137
138 pub fn anchor(&self) -> usize {
140 self.data.lock().anchor
141 }
142
143 pub fn has_selection(&self) -> bool {
145 let d = self.data.lock();
146 d.position != d.anchor
147 }
148
149 pub fn selection_start(&self) -> usize {
151 let d = self.data.lock();
152 d.position.min(d.anchor)
153 }
154
155 pub fn selection_end(&self) -> usize {
157 let d = self.data.lock();
158 d.position.max(d.anchor)
159 }
160
161 pub fn selected_text(&self) -> Result<String> {
163 let (pos, anchor) = self.read_cursor();
164 if pos == anchor {
165 return Ok(String::new());
166 }
167 let start = pos.min(anchor);
168 let len = pos.max(anchor) - start;
169 let inner = self.doc.lock();
170 let dto = frontend::document_inspection::GetTextAtPositionDto {
171 position: to_i64(start),
172 length: to_i64(len),
173 };
174 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
175 Ok(result.text)
176 }
177
178 pub fn clear_selection(&self) {
180 let mut d = self.data.lock();
181 d.anchor = d.position;
182 }
183
184 pub fn at_block_start(&self) -> bool {
188 let pos = self.position();
189 let inner = self.doc.lock();
190 let dto = frontend::document_inspection::GetBlockAtPositionDto {
191 position: to_i64(pos),
192 };
193 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
194 pos == to_usize(info.block_start)
195 } else {
196 false
197 }
198 }
199
200 pub fn at_block_end(&self) -> bool {
202 let pos = self.position();
203 let inner = self.doc.lock();
204 let dto = frontend::document_inspection::GetBlockAtPositionDto {
205 position: to_i64(pos),
206 };
207 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
208 pos == to_usize(info.block_start) + to_usize(info.block_length)
209 } else {
210 false
211 }
212 }
213
214 pub fn at_start(&self) -> bool {
216 self.data.lock().position == 0
217 }
218
219 pub fn at_end(&self) -> bool {
221 let pos = self.position();
222 let inner = self.doc.lock();
223 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
224 frontend::document_inspection::DocumentStatsDto {
225 character_count: 0,
226 word_count: 0,
227 block_count: 0,
228 frame_count: 0,
229 image_count: 0,
230 list_count: 0,
231 table_count: 0,
232 }
233 });
234 pos >= max_cursor_position(&stats)
235 }
236
237 pub fn block_number(&self) -> usize {
239 let pos = self.position();
240 let inner = self.doc.lock();
241 let dto = frontend::document_inspection::GetBlockAtPositionDto {
242 position: to_i64(pos),
243 };
244 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
245 .map(|info| to_usize(info.block_number))
246 .unwrap_or(0)
247 }
248
249 pub fn position_in_block(&self) -> usize {
251 let pos = self.position();
252 let inner = self.doc.lock();
253 let dto = frontend::document_inspection::GetBlockAtPositionDto {
254 position: to_i64(pos),
255 };
256 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
257 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
258 .unwrap_or(0)
259 }
260
261 pub fn set_position(&self, position: usize, mode: MoveMode) {
275 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 pos = position.min(end);
283
284 if mode == MoveMode::KeepAnchor {
288 let anchor = self.data.lock().anchor;
289 let pos_cell = self.table_cell_at(pos);
290 let anchor_cell = self.table_cell_at(anchor);
291 match (&pos_cell, &anchor_cell) {
292 (Some(tc), None) => {
293 let before = anchor < pos;
295 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
296 pos = boundary;
297 }
298 }
299 (None, Some(tc)) => {
300 let before = pos < anchor;
303 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
304 pos = boundary;
305 }
306 }
307 _ => {}
308 }
309 }
310
311 let mut d = self.data.lock();
312 d.position = pos;
313 if mode == MoveMode::MoveAnchor {
314 d.anchor = pos;
315 }
316 d.cell_selection_override = None;
317 }
318
319 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
325 let old_pos = self.position();
326 let target = self.resolve_move(operation, n);
327 self.set_position(target, mode);
328 self.position() != old_pos
329 }
330
331 pub fn select(&self, selection: SelectionType) {
333 match selection {
334 SelectionType::Document => {
335 let end = {
336 let inner = self.doc.lock();
337 document_inspection_commands::get_document_stats(&inner.ctx)
338 .map(|s| max_cursor_position(&s))
339 .unwrap_or(0)
340 };
341 let mut d = self.data.lock();
342 d.anchor = 0;
343 d.position = end;
344 d.cell_selection_override = None;
345 }
346 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
347 let pos = self.position();
348 let inner = self.doc.lock();
349 let dto = frontend::document_inspection::GetBlockAtPositionDto {
350 position: to_i64(pos),
351 };
352 if let Ok(info) =
353 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
354 {
355 let start = to_usize(info.block_start);
356 let end = start + to_usize(info.block_length);
357 drop(inner);
358 let mut d = self.data.lock();
359 d.anchor = start;
360 d.position = end;
361 d.cell_selection_override = None;
362 }
363 }
364 SelectionType::WordUnderCursor => {
365 let pos = self.position();
366 let (word_start, word_end) = self.find_word_boundaries(pos);
367 let mut d = self.data.lock();
368 d.anchor = word_start;
369 d.position = word_end;
370 d.cell_selection_override = None;
371 }
372 }
373 }
374
375 pub fn insert_text(&self, text: &str) -> Result<()> {
379 let (pos, anchor) = self.read_cursor();
380
381 let dto = frontend::document_editing::InsertTextDto {
383 position: to_i64(pos),
384 anchor: to_i64(anchor),
385 text: text.into(),
386 };
387
388 let queued = {
389 let mut inner = self.doc.lock();
390 let result = match document_editing_commands::insert_text(
391 &inner.ctx,
392 Some(inner.stack_id),
393 &dto,
394 ) {
395 Ok(r) => r,
396 Err(_) if pos != anchor => {
397 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
399
400 let del_dto = frontend::document_editing::DeleteTextDto {
401 position: to_i64(pos),
402 anchor: to_i64(anchor),
403 };
404 let del_result = document_editing_commands::delete_text(
405 &inner.ctx,
406 Some(inner.stack_id),
407 &del_dto,
408 )?;
409 let del_pos = to_usize(del_result.new_position);
410
411 let ins_dto = frontend::document_editing::InsertTextDto {
412 position: to_i64(del_pos),
413 anchor: to_i64(del_pos),
414 text: text.into(),
415 };
416 let ins_result = document_editing_commands::insert_text(
417 &inner.ctx,
418 Some(inner.stack_id),
419 &ins_dto,
420 )?;
421
422 undo_redo_commands::end_composite(&inner.ctx);
423 ins_result
424 }
425 Err(e) => return Err(e),
426 };
427
428 let edit_pos = pos.min(anchor);
429 let removed = pos.max(anchor) - edit_pos;
430 self.finish_edit_ext(
431 &mut inner,
432 edit_pos,
433 removed,
434 to_usize(result.new_position),
435 to_usize(result.blocks_affected),
436 false,
437 )
438 };
439 crate::inner::dispatch_queued_events(queued);
440 Ok(())
441 }
442
443 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
445 let (pos, anchor) = self.read_cursor();
446
447 let make_dto = |p: usize, a: usize| frontend::document_editing::InsertFormattedTextDto {
448 position: to_i64(p),
449 anchor: to_i64(a),
450 text: text.into(),
451 font_family: format.font_family.clone().unwrap_or_default(),
452 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
453 font_bold: format.font_bold.unwrap_or(false),
454 font_italic: format.font_italic.unwrap_or(false),
455 font_underline: format.font_underline.unwrap_or(false),
456 font_strikeout: format.font_strikeout.unwrap_or(false),
457 };
458
459 let queued = {
460 let mut inner = self.doc.lock();
461 let result = match document_editing_commands::insert_formatted_text(
462 &inner.ctx,
463 Some(inner.stack_id),
464 &make_dto(pos, anchor),
465 ) {
466 Ok(r) => r,
467 Err(_) if pos != anchor => {
468 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
470
471 let del_dto = frontend::document_editing::DeleteTextDto {
472 position: to_i64(pos),
473 anchor: to_i64(anchor),
474 };
475 let del_result = document_editing_commands::delete_text(
476 &inner.ctx,
477 Some(inner.stack_id),
478 &del_dto,
479 )?;
480 let del_pos = to_usize(del_result.new_position);
481
482 let ins_result = document_editing_commands::insert_formatted_text(
483 &inner.ctx,
484 Some(inner.stack_id),
485 &make_dto(del_pos, del_pos),
486 )?;
487
488 undo_redo_commands::end_composite(&inner.ctx);
489 ins_result
490 }
491 Err(e) => return Err(e),
492 };
493
494 let edit_pos = pos.min(anchor);
495 let removed = pos.max(anchor) - edit_pos;
496 self.finish_edit_ext(
497 &mut inner,
498 edit_pos,
499 removed,
500 to_usize(result.new_position),
501 1,
502 false,
503 )
504 };
505 crate::inner::dispatch_queued_events(queued);
506 Ok(())
507 }
508
509 pub fn insert_block(&self) -> Result<()> {
511 let (pos, anchor) = self.read_cursor();
512 let queued = {
513 let mut inner = self.doc.lock();
514
515 let (insert_pos, removed) = if pos != anchor {
516 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
518 let del_dto = frontend::document_editing::DeleteTextDto {
519 position: to_i64(pos),
520 anchor: to_i64(anchor),
521 };
522 let del_result = document_editing_commands::delete_text(
523 &inner.ctx,
524 Some(inner.stack_id),
525 &del_dto,
526 )?;
527 (
528 to_usize(del_result.new_position),
529 pos.max(anchor) - pos.min(anchor),
530 )
531 } else {
532 (pos, 0)
533 };
534
535 let dto = frontend::document_editing::InsertBlockDto {
536 position: to_i64(insert_pos),
537 anchor: to_i64(insert_pos),
538 };
539 let result =
540 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
541
542 if pos != anchor {
543 undo_redo_commands::end_composite(&inner.ctx);
544 }
545
546 let edit_pos = pos.min(anchor);
547 self.finish_edit(
548 &mut inner,
549 edit_pos,
550 removed,
551 to_usize(result.new_position),
552 2,
553 )
554 };
555 crate::inner::dispatch_queued_events(queued);
556 Ok(())
557 }
558
559 pub fn insert_html(&self, html: &str) -> Result<()> {
561 let frag = DocumentFragment::from_html(html);
563 self.insert_fragment(&frag)
564 }
565
566 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
568 let frag = DocumentFragment::from_markdown(markdown);
569 self.insert_fragment(&frag)
570 }
571
572 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
574 let (pos, anchor) = self.read_cursor();
575 let queued = {
576 let mut inner = self.doc.lock();
577
578 let (insert_pos, removed) = if pos != anchor {
579 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
580 let del_dto = frontend::document_editing::DeleteTextDto {
581 position: to_i64(pos),
582 anchor: to_i64(anchor),
583 };
584 let del_result = document_editing_commands::delete_text(
585 &inner.ctx,
586 Some(inner.stack_id),
587 &del_dto,
588 )?;
589 (
590 to_usize(del_result.new_position),
591 pos.max(anchor) - pos.min(anchor),
592 )
593 } else {
594 (pos, 0)
595 };
596
597 let dto = frontend::document_editing::InsertFragmentDto {
598 position: to_i64(insert_pos),
599 anchor: to_i64(insert_pos),
600 fragment_data: fragment.raw_data().into(),
601 };
602 let result =
603 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
604
605 if pos != anchor {
606 undo_redo_commands::end_composite(&inner.ctx);
607 }
608
609 let edit_pos = pos.min(anchor);
610 self.finish_edit(
611 &mut inner,
612 edit_pos,
613 removed,
614 to_usize(result.new_position),
615 to_usize(result.blocks_added),
616 )
617 };
618 crate::inner::dispatch_queued_events(queued);
619 Ok(())
620 }
621
622 pub fn selection(&self) -> DocumentFragment {
624 let (pos, anchor) = self.read_cursor();
625
626 let (extract_pos, extract_anchor) = match self.selection_kind() {
629 SelectionKind::Cells(ref range) => match self.cell_range_positions(range) {
630 Some((start, end)) => (start, end),
631 None => return DocumentFragment::new(),
632 },
633 SelectionKind::Mixed {
634 ref cell_range,
635 text_before,
636 text_after,
637 } => {
638 let (cell_start, cell_end) = match self.cell_range_positions(cell_range) {
639 Some(p) => p,
640 None => return DocumentFragment::new(),
641 };
642 let start = if text_before {
643 pos.min(anchor)
644 } else {
645 cell_start
646 };
647 let end = if text_after {
648 pos.max(anchor)
649 } else {
650 cell_end
651 };
652 (start.min(cell_start), end.max(cell_end))
653 }
654 SelectionKind::None => return DocumentFragment::new(),
655 SelectionKind::Text => (pos, anchor),
656 };
657
658 if extract_pos == extract_anchor {
659 return DocumentFragment::new();
660 }
661
662 let inner = self.doc.lock();
663 let dto = frontend::document_inspection::ExtractFragmentDto {
664 position: to_i64(extract_pos),
665 anchor: to_i64(extract_anchor),
666 };
667 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
668 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
669 Err(_) => DocumentFragment::new(),
670 }
671 }
672
673 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
675 let (pos, anchor) = self.read_cursor();
676 let queued = {
677 let mut inner = self.doc.lock();
678
679 let (insert_pos, removed) = if pos != anchor {
680 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
681 let del_dto = frontend::document_editing::DeleteTextDto {
682 position: to_i64(pos),
683 anchor: to_i64(anchor),
684 };
685 let del_result = document_editing_commands::delete_text(
686 &inner.ctx,
687 Some(inner.stack_id),
688 &del_dto,
689 )?;
690 (
691 to_usize(del_result.new_position),
692 pos.max(anchor) - pos.min(anchor),
693 )
694 } else {
695 (pos, 0)
696 };
697
698 let dto = frontend::document_editing::InsertImageDto {
699 position: to_i64(insert_pos),
700 anchor: to_i64(insert_pos),
701 image_name: name.into(),
702 width: width as i64,
703 height: height as i64,
704 };
705 let result =
706 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
707
708 if pos != anchor {
709 undo_redo_commands::end_composite(&inner.ctx);
710 }
711
712 let edit_pos = pos.min(anchor);
713 self.finish_edit_ext(
714 &mut inner,
715 edit_pos,
716 removed,
717 to_usize(result.new_position),
718 1,
719 false,
720 )
721 };
722 crate::inner::dispatch_queued_events(queued);
723 Ok(())
724 }
725
726 pub fn insert_frame(&self) -> Result<()> {
728 let (pos, anchor) = self.read_cursor();
729 let queued = {
730 let mut inner = self.doc.lock();
731 let dto = frontend::document_editing::InsertFrameDto {
732 position: to_i64(pos),
733 anchor: to_i64(anchor),
734 };
735 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
736 inner.modified = true;
739 inner.invalidate_text_cache();
740 inner.rehighlight_affected(pos.min(anchor));
741 inner.queue_event(DocumentEvent::ContentsChanged {
742 position: pos.min(anchor),
743 chars_removed: 0,
744 chars_added: 0,
745 blocks_affected: 1,
746 });
747 inner.check_block_count_changed();
748 inner.check_flow_changed();
749 self.queue_undo_redo_event(&mut inner)
750 };
751 crate::inner::dispatch_queued_events(queued);
752 Ok(())
753 }
754
755 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
761 let (pos, anchor) = self.read_cursor();
762 let (table_id, queued) = {
763 let mut inner = self.doc.lock();
764 let dto = frontend::document_editing::InsertTableDto {
765 position: to_i64(pos),
766 anchor: to_i64(anchor),
767 rows: to_i64(rows),
768 columns: to_i64(columns),
769 };
770 let result =
771 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
772 let new_pos = to_usize(result.new_position);
773 let table_id = to_usize(result.table_id);
774 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
775 {
776 let mut d = self.data.lock();
777 d.position = new_pos;
778 d.anchor = new_pos;
779 }
780 inner.modified = true;
781 inner.invalidate_text_cache();
782 inner.rehighlight_affected(pos.min(anchor));
783 inner.queue_event(DocumentEvent::ContentsChanged {
784 position: pos.min(anchor),
785 chars_removed: 0,
786 chars_added: new_pos - pos.min(anchor),
787 blocks_affected: 1,
788 });
789 inner.check_block_count_changed();
790 inner.check_flow_changed();
791 (table_id, self.queue_undo_redo_event(&mut inner))
792 };
793 crate::inner::dispatch_queued_events(queued);
794 Ok(TextTable {
795 doc: self.doc.clone(),
796 table_id,
797 })
798 }
799
800 pub fn current_table(&self) -> Option<TextTable> {
805 self.current_table_cell().map(|c| c.table)
806 }
807
808 pub fn current_table_cell(&self) -> Option<TableCellRef> {
813 let pos = self.position();
814 let inner = self.doc.lock();
815 let dto = frontend::document_inspection::GetBlockAtPositionDto {
817 position: to_i64(pos),
818 };
819 let block_info =
820 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
821
822 let block_id = if to_i64(pos) < block_info.block_start && pos > 0 {
826 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
827 position: to_i64(pos - 1),
828 };
829 let prev_info =
830 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
831 prev_info.block_id as usize
832 } else {
833 block_info.block_id as usize
834 };
835
836 let block = crate::text_block::TextBlock {
837 doc: self.doc.clone(),
838 block_id,
839 };
840 drop(inner);
842 block.table_cell()
843 }
844
845 pub fn remove_table(&self, table_id: usize) -> Result<()> {
849 let queued = {
850 let mut inner = self.doc.lock();
851 let dto = frontend::document_editing::RemoveTableDto {
852 table_id: to_i64(table_id),
853 };
854 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
855 inner.modified = true;
856 inner.invalidate_text_cache();
857 inner.rehighlight_all();
858 inner.check_block_count_changed();
859 inner.check_flow_changed();
860 self.queue_undo_redo_event(&mut inner)
861 };
862 crate::inner::dispatch_queued_events(queued);
863 Ok(())
864 }
865
866 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
868 let queued = {
869 let mut inner = self.doc.lock();
870 let dto = frontend::document_editing::InsertTableRowDto {
871 table_id: to_i64(table_id),
872 row_index: to_i64(row_index),
873 };
874 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
875 inner.modified = true;
876 inner.invalidate_text_cache();
877 inner.rehighlight_all();
878 inner.check_block_count_changed();
879 self.queue_undo_redo_event(&mut inner)
880 };
881 crate::inner::dispatch_queued_events(queued);
882 Ok(())
883 }
884
885 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
887 let queued = {
888 let mut inner = self.doc.lock();
889 let dto = frontend::document_editing::InsertTableColumnDto {
890 table_id: to_i64(table_id),
891 column_index: to_i64(column_index),
892 };
893 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
894 inner.modified = true;
895 inner.invalidate_text_cache();
896 inner.rehighlight_all();
897 inner.check_block_count_changed();
898 self.queue_undo_redo_event(&mut inner)
899 };
900 crate::inner::dispatch_queued_events(queued);
901 Ok(())
902 }
903
904 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
906 let queued = {
907 let mut inner = self.doc.lock();
908 let dto = frontend::document_editing::RemoveTableRowDto {
909 table_id: to_i64(table_id),
910 row_index: to_i64(row_index),
911 };
912 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
913 inner.modified = true;
914 inner.invalidate_text_cache();
915 inner.rehighlight_all();
916 inner.check_block_count_changed();
917 self.queue_undo_redo_event(&mut inner)
918 };
919 crate::inner::dispatch_queued_events(queued);
920 Ok(())
921 }
922
923 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
925 let queued = {
926 let mut inner = self.doc.lock();
927 let dto = frontend::document_editing::RemoveTableColumnDto {
928 table_id: to_i64(table_id),
929 column_index: to_i64(column_index),
930 };
931 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
932 inner.modified = true;
933 inner.invalidate_text_cache();
934 inner.rehighlight_all();
935 inner.check_block_count_changed();
936 self.queue_undo_redo_event(&mut inner)
937 };
938 crate::inner::dispatch_queued_events(queued);
939 Ok(())
940 }
941
942 pub fn merge_table_cells(
944 &self,
945 table_id: usize,
946 start_row: usize,
947 start_column: usize,
948 end_row: usize,
949 end_column: usize,
950 ) -> Result<()> {
951 let queued = {
952 let mut inner = self.doc.lock();
953 let dto = frontend::document_editing::MergeTableCellsDto {
954 table_id: to_i64(table_id),
955 start_row: to_i64(start_row),
956 start_column: to_i64(start_column),
957 end_row: to_i64(end_row),
958 end_column: to_i64(end_column),
959 };
960 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
961 inner.modified = true;
962 inner.invalidate_text_cache();
963 inner.rehighlight_all();
964 inner.check_block_count_changed();
965 self.queue_undo_redo_event(&mut inner)
966 };
967 crate::inner::dispatch_queued_events(queued);
968 Ok(())
969 }
970
971 pub fn split_table_cell(
973 &self,
974 cell_id: usize,
975 split_rows: usize,
976 split_columns: usize,
977 ) -> Result<()> {
978 let queued = {
979 let mut inner = self.doc.lock();
980 let dto = frontend::document_editing::SplitTableCellDto {
981 cell_id: to_i64(cell_id),
982 split_rows: to_i64(split_rows),
983 split_columns: to_i64(split_columns),
984 };
985 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
986 inner.modified = true;
987 inner.invalidate_text_cache();
988 inner.rehighlight_all();
989 inner.check_block_count_changed();
990 self.queue_undo_redo_event(&mut inner)
991 };
992 crate::inner::dispatch_queued_events(queued);
993 Ok(())
994 }
995
996 pub fn set_table_format(
1000 &self,
1001 table_id: usize,
1002 format: &crate::flow::TableFormat,
1003 ) -> Result<()> {
1004 let queued = {
1005 let mut inner = self.doc.lock();
1006 let dto = format.to_set_dto(table_id);
1007 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1008 inner.modified = true;
1009 inner.queue_event(DocumentEvent::FormatChanged {
1010 position: 0,
1011 length: 0,
1012 kind: crate::flow::FormatChangeKind::Block,
1013 });
1014 self.queue_undo_redo_event(&mut inner)
1015 };
1016 crate::inner::dispatch_queued_events(queued);
1017 Ok(())
1018 }
1019
1020 pub fn set_table_cell_format(
1022 &self,
1023 cell_id: usize,
1024 format: &crate::flow::CellFormat,
1025 ) -> Result<()> {
1026 let queued = {
1027 let mut inner = self.doc.lock();
1028 let dto = format.to_set_dto(cell_id);
1029 document_formatting_commands::set_table_cell_format(
1030 &inner.ctx,
1031 Some(inner.stack_id),
1032 &dto,
1033 )?;
1034 inner.modified = true;
1035 inner.queue_event(DocumentEvent::FormatChanged {
1036 position: 0,
1037 length: 0,
1038 kind: crate::flow::FormatChangeKind::Block,
1039 });
1040 self.queue_undo_redo_event(&mut inner)
1041 };
1042 crate::inner::dispatch_queued_events(queued);
1043 Ok(())
1044 }
1045
1046 pub fn remove_current_table(&self) -> Result<()> {
1051 let table = self
1052 .current_table()
1053 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1054 self.remove_table(table.id())
1055 }
1056
1057 pub fn insert_row_above(&self) -> Result<()> {
1060 let cell_ref = self
1061 .current_table_cell()
1062 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1063 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
1064 }
1065
1066 pub fn insert_row_below(&self) -> Result<()> {
1069 let cell_ref = self
1070 .current_table_cell()
1071 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1072 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
1073 }
1074
1075 pub fn insert_column_before(&self) -> Result<()> {
1078 let cell_ref = self
1079 .current_table_cell()
1080 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1081 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
1082 }
1083
1084 pub fn insert_column_after(&self) -> Result<()> {
1087 let cell_ref = self
1088 .current_table_cell()
1089 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1090 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
1091 }
1092
1093 pub fn remove_current_row(&self) -> Result<()> {
1096 let cell_ref = self
1097 .current_table_cell()
1098 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1099 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
1100 }
1101
1102 pub fn remove_current_column(&self) -> Result<()> {
1105 let cell_ref = self
1106 .current_table_cell()
1107 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1108 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
1109 }
1110
1111 pub fn merge_selected_cells(&self) -> Result<()> {
1118 let pos_cell = self
1119 .current_table_cell()
1120 .ok_or_else(|| anyhow::anyhow!("cursor position is not inside a table"))?;
1121
1122 let (_pos, anchor) = self.read_cursor();
1124 let anchor_cell = {
1125 let inner = self.doc.lock();
1127 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1128 position: to_i64(anchor),
1129 };
1130 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1131 .map_err(|_| anyhow::anyhow!("cursor anchor is not inside a table"))?;
1132 let block = crate::text_block::TextBlock {
1133 doc: self.doc.clone(),
1134 block_id: block_info.block_id as usize,
1135 };
1136 drop(inner);
1137 block
1138 .table_cell()
1139 .ok_or_else(|| anyhow::anyhow!("cursor anchor is not inside a table"))?
1140 };
1141
1142 if pos_cell.table.id() != anchor_cell.table.id() {
1143 return Err(anyhow::anyhow!(
1144 "position and anchor are in different tables"
1145 ));
1146 }
1147
1148 let start_row = pos_cell.row.min(anchor_cell.row);
1149 let start_col = pos_cell.column.min(anchor_cell.column);
1150 let end_row = pos_cell.row.max(anchor_cell.row);
1151 let end_col = pos_cell.column.max(anchor_cell.column);
1152
1153 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
1154 }
1155
1156 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1159 let cell_ref = self
1160 .current_table_cell()
1161 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1162 let cell = cell_ref
1164 .table
1165 .cell(cell_ref.row, cell_ref.column)
1166 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1167 self.split_table_cell(cell.id(), split_rows, split_columns)
1169 }
1170
1171 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1174 let table = self
1175 .current_table()
1176 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1177 self.set_table_format(table.id(), format)
1178 }
1179
1180 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1183 let cell_ref = self
1184 .current_table_cell()
1185 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1186 let cell = cell_ref
1187 .table
1188 .cell(cell_ref.row, cell_ref.column)
1189 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1190 self.set_table_cell_format(cell.id(), format)
1191 }
1192
1193 pub fn selection_kind(&self) -> crate::flow::SelectionKind {
1201 use crate::flow::{CellRange, SelectionKind};
1202
1203 {
1205 let d = self.data.lock();
1206 if let Some(ref range) = d.cell_selection_override {
1207 return SelectionKind::Cells(range.clone());
1208 }
1209 if d.position == d.anchor {
1210 return SelectionKind::None;
1211 }
1212 }
1213
1214 let (pos, anchor) = self.read_cursor();
1215
1216 let pos_cell = self.table_cell_at(pos);
1218 let anchor_cell = self.table_cell_at(anchor);
1219
1220 match (&pos_cell, &anchor_cell) {
1221 (None, None) => {
1222 let (start, end) = (pos.min(anchor), pos.max(anchor));
1226 if let Some(t) = self.find_table_between(start, end) {
1227 let table_id = t.id();
1228 let rows = t.rows();
1229 let cols = t.columns();
1230 let range = CellRange {
1231 table_id,
1232 start_row: 0,
1233 start_col: 0,
1234 end_row: if rows > 0 { rows - 1 } else { 0 },
1235 end_col: if cols > 0 { cols - 1 } else { 0 },
1236 };
1237 let spans = self.collect_cell_spans(table_id);
1238 SelectionKind::Mixed {
1239 cell_range: range.expand_for_spans(&spans),
1240 text_before: true,
1241 text_after: true,
1242 }
1243 } else {
1244 SelectionKind::Text
1245 }
1246 }
1247 (Some(pc), Some(ac)) => {
1248 if pc.table.id() != ac.table.id() {
1249 return SelectionKind::Text;
1251 }
1252 if pc.row == ac.row && pc.column == ac.column {
1253 return SelectionKind::Text;
1255 }
1256 let range = CellRange {
1258 table_id: pc.table.id(),
1259 start_row: pc.row.min(ac.row),
1260 start_col: pc.column.min(ac.column),
1261 end_row: pc.row.max(ac.row),
1262 end_col: pc.column.max(ac.column),
1263 };
1264 let spans = self.collect_cell_spans(pc.table.id());
1265 SelectionKind::Cells(range.expand_for_spans(&spans))
1266 }
1267 (Some(tc), None) | (None, Some(tc)) => {
1268 let table_id = tc.table.id();
1272 let rows = tc.table.rows();
1273 let cols = tc.table.columns();
1274
1275 let inside_pos = if pos_cell.is_some() { pos } else { anchor };
1276 let outside_pos = if pos_cell.is_some() { anchor } else { pos };
1277
1278 let text_before = outside_pos < inside_pos;
1279 let text_after = !text_before;
1280
1281 let range = CellRange {
1282 table_id,
1283 start_row: 0,
1284 start_col: 0,
1285 end_row: if rows > 0 { rows - 1 } else { 0 },
1286 end_col: if cols > 0 { cols - 1 } else { 0 },
1287 };
1288 let spans = self.collect_cell_spans(table_id);
1289 SelectionKind::Mixed {
1290 cell_range: range.expand_for_spans(&spans),
1291 text_before,
1292 text_after,
1293 }
1294 }
1295 }
1296 }
1297
1298 pub fn is_cell_selection(&self) -> bool {
1300 matches!(
1301 self.selection_kind(),
1302 crate::flow::SelectionKind::Cells(_) | crate::flow::SelectionKind::Mixed { .. }
1303 )
1304 }
1305
1306 pub fn selected_cell_range(&self) -> Option<crate::flow::CellRange> {
1308 match self.selection_kind() {
1309 crate::flow::SelectionKind::Cells(r) => Some(r),
1310 crate::flow::SelectionKind::Mixed { cell_range, .. } => Some(cell_range),
1311 _ => None,
1312 }
1313 }
1314
1315 pub fn selected_cells(&self) -> Vec<TableCellRef> {
1317 let range = match self.selected_cell_range() {
1318 Some(r) => r,
1319 None => return Vec::new(),
1320 };
1321 let table = TextTable {
1322 doc: self.doc.clone(),
1323 table_id: range.table_id,
1324 };
1325 let mut cells = Vec::new();
1326 for row in range.start_row..=range.end_row {
1327 for col in range.start_col..=range.end_col {
1328 if table.cell(row, col).is_some() {
1329 cells.push(TableCellRef {
1330 table: table.clone(),
1331 row,
1332 column: col,
1333 });
1334 }
1335 }
1336 }
1337 cells
1338 }
1339
1340 pub fn select_table_cell(&self, table_id: usize, row: usize, col: usize) {
1344 let mut d = self.data.lock();
1345 d.cell_selection_override = Some(crate::flow::CellRange {
1346 table_id,
1347 start_row: row,
1348 start_col: col,
1349 end_row: row,
1350 end_col: col,
1351 });
1352 }
1353
1354 pub fn select_cell_range(
1356 &self,
1357 table_id: usize,
1358 start_row: usize,
1359 start_col: usize,
1360 end_row: usize,
1361 end_col: usize,
1362 ) {
1363 let range = crate::flow::CellRange {
1364 table_id,
1365 start_row,
1366 start_col,
1367 end_row,
1368 end_col,
1369 };
1370 let spans = self.collect_cell_spans(table_id);
1371 let mut d = self.data.lock();
1372 d.cell_selection_override = Some(range.expand_for_spans(&spans));
1373 }
1374
1375 pub fn clear_cell_selection(&self) {
1377 let mut d = self.data.lock();
1378 d.cell_selection_override = None;
1379 }
1380
1381 fn cell_range_positions(&self, range: &CellRange) -> Option<(usize, usize)> {
1384 let inner = self.doc.lock();
1385 let main_frame_id = get_main_frame_id(&inner);
1386 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1387 drop(inner);
1388
1389 let table = flow.into_iter().find_map(|e| match e {
1391 FlowElement::Table(t) if t.id() == range.table_id => Some(t),
1392 _ => None,
1393 })?;
1394
1395 let mut min_pos = usize::MAX;
1396 let mut max_pos = 0usize;
1397
1398 for row in range.start_row..=range.end_row {
1399 for col in range.start_col..=range.end_col {
1400 if let Some(cell) = table.cell(row, col) {
1401 for block in cell.blocks() {
1402 let bp = block.position();
1403 let bl = block.length();
1404 min_pos = min_pos.min(bp);
1405 max_pos = max_pos.max(bp + bl);
1406 }
1407 }
1408 }
1409 }
1410
1411 if min_pos == usize::MAX {
1412 return None;
1413 }
1414
1415 Some((min_pos, max_pos + 1))
1417 }
1418
1419 fn table_cell_at(&self, position: usize) -> Option<TableCellRef> {
1423 let inner = self.doc.lock();
1424 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1425 position: to_i64(position),
1426 };
1427 let block_info =
1428 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1429
1430 let block_id = if to_i64(position) < block_info.block_start && position > 0 {
1431 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1432 position: to_i64(position - 1),
1433 };
1434 let prev_info =
1435 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
1436 prev_info.block_id as usize
1437 } else {
1438 block_info.block_id as usize
1439 };
1440
1441 let block = crate::text_block::TextBlock {
1442 doc: self.doc.clone(),
1443 block_id,
1444 };
1445 drop(inner);
1446 block.table_cell()
1447 }
1448
1449 fn table_boundary_position(&self, table_id: usize, before: bool) -> Option<usize> {
1460 let inner = self.doc.lock();
1461 let main_frame_id = get_main_frame_id(&inner);
1462 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1463 drop(inner);
1464
1465 let idx = flow
1467 .iter()
1468 .position(|e| matches!(e, FlowElement::Table(t) if t.id() == table_id))?;
1469
1470 if before {
1471 for i in (0..idx).rev() {
1473 if let FlowElement::Block(b) = &flow[i] {
1474 return Some(b.position() + b.length());
1475 }
1476 }
1477 } else {
1478 for item in flow.iter().skip(idx + 1) {
1480 if let FlowElement::Block(b) = item {
1481 return Some(b.position());
1482 }
1483 }
1484 }
1485 None
1486 }
1487
1488 fn find_table_between(&self, start: usize, end: usize) -> Option<TextTable> {
1490 let inner = self.doc.lock();
1491 let main_frame_id = get_main_frame_id(&inner);
1492 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1493 drop(inner);
1494
1495 for elem in flow {
1496 if let FlowElement::Table(t) = elem {
1497 if let Some(first_cell) = t.cell(0, 0) {
1500 let blocks = first_cell.blocks();
1501 if let Some(fb) = blocks.first() {
1502 let p = fb.position();
1503 if p > start && p < end {
1504 return Some(t);
1505 }
1506 }
1507 }
1508 }
1509 }
1510 None
1511 }
1512
1513 fn collect_cell_spans(&self, table_id: usize) -> Vec<(usize, usize, usize, usize)> {
1515 let inner = self.doc.lock();
1516 let table_dto =
1517 match frontend::commands::table_commands::get_table(&inner.ctx, &(table_id as u64))
1518 .ok()
1519 .flatten()
1520 {
1521 Some(t) => t,
1522 None => return Vec::new(),
1523 };
1524
1525 let mut spans = Vec::with_capacity(table_dto.cells.len());
1526 for &cell_id in &table_dto.cells {
1527 if let Some(cell) =
1528 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &cell_id)
1529 .ok()
1530 .flatten()
1531 {
1532 spans.push((
1533 cell.row as usize,
1534 cell.column as usize,
1535 cell.row_span.max(1) as usize,
1536 cell.column_span.max(1) as usize,
1537 ));
1538 }
1539 }
1540 spans
1541 }
1542
1543 pub fn delete_char(&self) -> Result<()> {
1545 let (pos, anchor) = self.read_cursor();
1546 let (del_pos, del_anchor) = if pos != anchor {
1547 (pos, anchor)
1548 } else {
1549 let end = {
1551 let inner = self.doc.lock();
1552 document_inspection_commands::get_document_stats(&inner.ctx)
1553 .map(|s| max_cursor_position(&s))
1554 .unwrap_or(0)
1555 };
1556 if pos >= end {
1557 return Ok(());
1558 }
1559 (pos, pos + 1)
1560 };
1561 self.do_delete(del_pos, del_anchor)
1562 }
1563
1564 pub fn delete_previous_char(&self) -> Result<()> {
1566 let (pos, anchor) = self.read_cursor();
1567 let (del_pos, del_anchor) = if pos != anchor {
1568 (pos, anchor)
1569 } else if pos > 0 {
1570 (pos - 1, pos)
1571 } else {
1572 return Ok(());
1573 };
1574 self.do_delete(del_pos, del_anchor)
1575 }
1576
1577 pub fn remove_selected_text(&self) -> Result<String> {
1579 let (pos, anchor) = self.read_cursor();
1580 if pos == anchor {
1581 return Ok(String::new());
1582 }
1583 let queued = {
1584 let mut inner = self.doc.lock();
1585 let dto = frontend::document_editing::DeleteTextDto {
1586 position: to_i64(pos),
1587 anchor: to_i64(anchor),
1588 };
1589 let result =
1590 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1591 let edit_pos = pos.min(anchor);
1592 let removed = pos.max(anchor) - edit_pos;
1593 let new_pos = to_usize(result.new_position);
1594 inner.adjust_cursors(edit_pos, removed, 0);
1595 {
1596 let mut d = self.data.lock();
1597 d.position = new_pos;
1598 d.anchor = new_pos;
1599 }
1600 inner.modified = true;
1601 inner.invalidate_text_cache();
1602 inner.rehighlight_affected(edit_pos);
1603 inner.queue_event(DocumentEvent::ContentsChanged {
1604 position: edit_pos,
1605 chars_removed: removed,
1606 chars_added: 0,
1607 blocks_affected: 1,
1608 });
1609 inner.check_block_count_changed();
1610 inner.check_flow_changed();
1611 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1613 };
1614 crate::inner::dispatch_queued_events(queued.1);
1615 Ok(queued.0)
1616 }
1617
1618 pub fn current_list(&self) -> Option<crate::TextList> {
1623 let pos = self.position();
1624 let inner = self.doc.lock();
1625 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1626 position: to_i64(pos),
1627 };
1628 let block_info =
1629 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1630 let block = crate::text_block::TextBlock {
1631 doc: self.doc.clone(),
1632 block_id: block_info.block_id as usize,
1633 };
1634 drop(inner);
1635 block.list()
1636 }
1637
1638 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1640 let (pos, anchor) = self.read_cursor();
1641 let queued = {
1642 let mut inner = self.doc.lock();
1643 let dto = frontend::document_editing::CreateListDto {
1644 position: to_i64(pos),
1645 anchor: to_i64(anchor),
1646 style: style.clone(),
1647 };
1648 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1649 inner.modified = true;
1650 inner.rehighlight_affected(pos.min(anchor));
1651 inner.queue_event(DocumentEvent::ContentsChanged {
1652 position: pos.min(anchor),
1653 chars_removed: 0,
1654 chars_added: 0,
1655 blocks_affected: 1,
1656 });
1657 self.queue_undo_redo_event(&mut inner)
1658 };
1659 crate::inner::dispatch_queued_events(queued);
1660 Ok(())
1661 }
1662
1663 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
1665 let (pos, anchor) = self.read_cursor();
1666 let queued = {
1667 let mut inner = self.doc.lock();
1668 let dto = frontend::document_editing::InsertListDto {
1669 position: to_i64(pos),
1670 anchor: to_i64(anchor),
1671 style: style.clone(),
1672 };
1673 let result =
1674 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1675 let edit_pos = pos.min(anchor);
1676 let removed = pos.max(anchor) - edit_pos;
1677 self.finish_edit_ext(
1678 &mut inner,
1679 edit_pos,
1680 removed,
1681 to_usize(result.new_position),
1682 1,
1683 false,
1684 )
1685 };
1686 crate::inner::dispatch_queued_events(queued);
1687 Ok(())
1688 }
1689
1690 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
1692 let queued = {
1693 let mut inner = self.doc.lock();
1694 let dto = format.to_set_dto(list_id);
1695 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1696 inner.modified = true;
1697 inner.queue_event(DocumentEvent::FormatChanged {
1698 position: 0,
1699 length: 0,
1700 kind: crate::flow::FormatChangeKind::List,
1701 });
1702 self.queue_undo_redo_event(&mut inner)
1703 };
1704 crate::inner::dispatch_queued_events(queued);
1705 Ok(())
1706 }
1707
1708 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
1711 let list = self
1712 .current_list()
1713 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a list"))?;
1714 self.set_list_format(list.id(), format)
1715 }
1716
1717 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
1719 let queued = {
1720 let mut inner = self.doc.lock();
1721 let dto = frontend::document_editing::AddBlockToListDto {
1722 block_id: to_i64(block_id),
1723 list_id: to_i64(list_id),
1724 };
1725 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1726 inner.modified = true;
1727 inner.queue_event(DocumentEvent::ContentsChanged {
1728 position: 0,
1729 chars_removed: 0,
1730 chars_added: 0,
1731 blocks_affected: 1,
1732 });
1733 self.queue_undo_redo_event(&mut inner)
1734 };
1735 crate::inner::dispatch_queued_events(queued);
1736 Ok(())
1737 }
1738
1739 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
1741 let pos = self.position();
1742 let inner = self.doc.lock();
1743 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1744 position: to_i64(pos),
1745 };
1746 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1747 drop(inner);
1748 self.add_block_to_list(block_info.block_id as usize, list_id)
1749 }
1750
1751 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
1753 let queued = {
1754 let mut inner = self.doc.lock();
1755 let dto = frontend::document_editing::RemoveBlockFromListDto {
1756 block_id: to_i64(block_id),
1757 };
1758 document_editing_commands::remove_block_from_list(
1759 &inner.ctx,
1760 Some(inner.stack_id),
1761 &dto,
1762 )?;
1763 inner.modified = true;
1764 inner.queue_event(DocumentEvent::ContentsChanged {
1765 position: 0,
1766 chars_removed: 0,
1767 chars_added: 0,
1768 blocks_affected: 1,
1769 });
1770 self.queue_undo_redo_event(&mut inner)
1771 };
1772 crate::inner::dispatch_queued_events(queued);
1773 Ok(())
1774 }
1775
1776 pub fn remove_current_block_from_list(&self) -> Result<()> {
1779 let pos = self.position();
1780 let inner = self.doc.lock();
1781 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1782 position: to_i64(pos),
1783 };
1784 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1785 drop(inner);
1786 self.remove_block_from_list(block_info.block_id as usize)
1787 }
1788
1789 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
1792 let list = crate::text_list::TextList {
1793 doc: self.doc.clone(),
1794 list_id,
1795 };
1796 let block = list
1797 .item(index)
1798 .ok_or_else(|| anyhow::anyhow!("list item index {index} out of range"))?;
1799 self.remove_block_from_list(block.id())
1800 }
1801
1802 pub fn char_format(&self) -> Result<TextFormat> {
1806 let pos = self.position();
1807 let inner = self.doc.lock();
1808 let dto = frontend::document_inspection::GetTextAtPositionDto {
1809 position: to_i64(pos),
1810 length: 1,
1811 };
1812 let text_info = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
1813 let element_id = text_info.element_id as u64;
1814 let element = inline_element_commands::get_inline_element(&inner.ctx, &element_id)?
1815 .ok_or_else(|| anyhow::anyhow!("element not found at position"))?;
1816 Ok(TextFormat::from(&element))
1817 }
1818
1819 pub fn block_format(&self) -> Result<BlockFormat> {
1821 let pos = self.position();
1822 let inner = self.doc.lock();
1823 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1824 position: to_i64(pos),
1825 };
1826 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1827 let block_id = block_info.block_id as u64;
1828 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
1829 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
1830 Ok(BlockFormat::from(&block))
1831 }
1832
1833 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
1837 let (pos, anchor) = self.read_cursor();
1838 let queued = {
1839 let mut inner = self.doc.lock();
1840 let dto = format.to_set_dto(pos, anchor);
1841 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1842 let start = pos.min(anchor);
1843 let length = pos.max(anchor) - start;
1844 inner.modified = true;
1845 inner.queue_event(DocumentEvent::FormatChanged {
1846 position: start,
1847 length,
1848 kind: crate::flow::FormatChangeKind::Character,
1849 });
1850 self.queue_undo_redo_event(&mut inner)
1851 };
1852 crate::inner::dispatch_queued_events(queued);
1853 Ok(())
1854 }
1855
1856 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
1858 let (pos, anchor) = self.read_cursor();
1859 let queued = {
1860 let mut inner = self.doc.lock();
1861 let dto = format.to_merge_dto(pos, anchor);
1862 document_formatting_commands::merge_text_format(
1863 &inner.ctx,
1864 Some(inner.stack_id),
1865 &dto,
1866 )?;
1867 let start = pos.min(anchor);
1868 let length = pos.max(anchor) - start;
1869 inner.modified = true;
1870 inner.queue_event(DocumentEvent::FormatChanged {
1871 position: start,
1872 length,
1873 kind: crate::flow::FormatChangeKind::Character,
1874 });
1875 self.queue_undo_redo_event(&mut inner)
1876 };
1877 crate::inner::dispatch_queued_events(queued);
1878 Ok(())
1879 }
1880
1881 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
1883 let (pos, anchor) = self.read_cursor();
1884 let queued = {
1885 let mut inner = self.doc.lock();
1886 let dto = format.to_set_dto(pos, anchor);
1887 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1888 let start = pos.min(anchor);
1889 let length = pos.max(anchor) - start;
1890 inner.modified = true;
1891 inner.queue_event(DocumentEvent::FormatChanged {
1892 position: start,
1893 length,
1894 kind: crate::flow::FormatChangeKind::Block,
1895 });
1896 self.queue_undo_redo_event(&mut inner)
1897 };
1898 crate::inner::dispatch_queued_events(queued);
1899 Ok(())
1900 }
1901
1902 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
1904 let (pos, anchor) = self.read_cursor();
1905 let queued = {
1906 let mut inner = self.doc.lock();
1907 let dto = format.to_set_dto(pos, anchor, frame_id);
1908 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1909 let start = pos.min(anchor);
1910 let length = pos.max(anchor) - start;
1911 inner.modified = true;
1912 inner.queue_event(DocumentEvent::FormatChanged {
1913 position: start,
1914 length,
1915 kind: crate::flow::FormatChangeKind::Block,
1916 });
1917 self.queue_undo_redo_event(&mut inner)
1918 };
1919 crate::inner::dispatch_queued_events(queued);
1920 Ok(())
1921 }
1922
1923 pub fn begin_edit_block(&self) {
1927 let inner = self.doc.lock();
1928 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1929 }
1930
1931 pub fn end_edit_block(&self) {
1933 let inner = self.doc.lock();
1934 undo_redo_commands::end_composite(&inner.ctx);
1935 }
1936
1937 pub fn join_previous_edit_block(&self) {
1944 self.begin_edit_block();
1945 }
1946
1947 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
1951 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1952 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1953 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1954 inner.take_queued_events()
1955 }
1956
1957 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
1958 let queued = {
1959 let mut inner = self.doc.lock();
1960 let dto = frontend::document_editing::DeleteTextDto {
1961 position: to_i64(pos),
1962 anchor: to_i64(anchor),
1963 };
1964 let result =
1965 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1966 let edit_pos = pos.min(anchor);
1967 let removed = pos.max(anchor) - edit_pos;
1968 let new_pos = to_usize(result.new_position);
1969 inner.adjust_cursors(edit_pos, removed, 0);
1970 {
1971 let mut d = self.data.lock();
1972 d.position = new_pos;
1973 d.anchor = new_pos;
1974 }
1975 inner.modified = true;
1976 inner.invalidate_text_cache();
1977 inner.rehighlight_affected(edit_pos);
1978 inner.queue_event(DocumentEvent::ContentsChanged {
1979 position: edit_pos,
1980 chars_removed: removed,
1981 chars_added: 0,
1982 blocks_affected: 1,
1983 });
1984 inner.check_block_count_changed();
1985 inner.check_flow_changed();
1986 self.queue_undo_redo_event(&mut inner)
1987 };
1988 crate::inner::dispatch_queued_events(queued);
1989 Ok(())
1990 }
1991
1992 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
1994 let pos = self.position();
1995 match op {
1996 MoveOperation::NoMove => pos,
1997 MoveOperation::Start => 0,
1998 MoveOperation::End => {
1999 let inner = self.doc.lock();
2000 document_inspection_commands::get_document_stats(&inner.ctx)
2001 .map(|s| max_cursor_position(&s))
2002 .unwrap_or(pos)
2003 }
2004 MoveOperation::NextCharacter | MoveOperation::Right => pos + n,
2005 MoveOperation::PreviousCharacter | MoveOperation::Left => pos.saturating_sub(n),
2006 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
2007 let inner = self.doc.lock();
2008 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2009 position: to_i64(pos),
2010 };
2011 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2012 .map(|info| to_usize(info.block_start))
2013 .unwrap_or(pos)
2014 }
2015 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
2016 let inner = self.doc.lock();
2017 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2018 position: to_i64(pos),
2019 };
2020 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2021 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
2022 .unwrap_or(pos)
2023 }
2024 MoveOperation::NextBlock => {
2025 let inner = self.doc.lock();
2026 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2027 position: to_i64(pos),
2028 };
2029 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2030 .map(|info| {
2031 to_usize(info.block_start) + to_usize(info.block_length) + 1
2033 })
2034 .unwrap_or(pos)
2035 }
2036 MoveOperation::PreviousBlock => {
2037 let inner = self.doc.lock();
2038 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2039 position: to_i64(pos),
2040 };
2041 let block_start =
2042 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2043 .map(|info| to_usize(info.block_start))
2044 .unwrap_or(pos);
2045 if block_start >= 2 {
2046 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2048 position: to_i64(block_start - 2),
2049 };
2050 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
2051 .map(|info| to_usize(info.block_start))
2052 .unwrap_or(0)
2053 } else {
2054 0
2055 }
2056 }
2057 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
2058 let (_, end) = self.find_word_boundaries(pos);
2059 if end == pos {
2061 let inner = self.doc.lock();
2063 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
2064 .map(|s| max_cursor_position(&s))
2065 .unwrap_or(0);
2066 let scan_len = max_pos.saturating_sub(pos).min(64);
2067 if scan_len == 0 {
2068 return pos;
2069 }
2070 let dto = frontend::document_inspection::GetTextAtPositionDto {
2071 position: to_i64(pos),
2072 length: to_i64(scan_len),
2073 };
2074 if let Ok(r) =
2075 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
2076 {
2077 for (i, ch) in r.text.chars().enumerate() {
2078 if ch.is_alphanumeric() || ch == '_' {
2079 let word_pos = pos + i;
2081 drop(inner);
2082 let (_, word_end) = self.find_word_boundaries(word_pos);
2083 return word_end;
2084 }
2085 }
2086 }
2087 pos + scan_len
2088 } else {
2089 end
2090 }
2091 }
2092 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
2093 let (start, _) = self.find_word_boundaries(pos);
2094 if start < pos {
2095 start
2096 } else if pos > 0 {
2097 let mut search = pos - 1;
2100 loop {
2101 let (ws, we) = self.find_word_boundaries(search);
2102 if ws < we {
2103 break ws;
2105 }
2106 if search == 0 {
2108 break 0;
2109 }
2110 search -= 1;
2111 }
2112 } else {
2113 0
2114 }
2115 }
2116 MoveOperation::Up | MoveOperation::Down => {
2117 if matches!(op, MoveOperation::Up) {
2120 self.resolve_move(MoveOperation::PreviousBlock, 1)
2121 } else {
2122 self.resolve_move(MoveOperation::NextBlock, 1)
2123 }
2124 }
2125 }
2126 }
2127
2128 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
2134 let inner = self.doc.lock();
2135 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2137 position: to_i64(pos),
2138 };
2139 let block_info =
2140 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2141 Ok(info) => info,
2142 Err(_) => return (pos, pos),
2143 };
2144
2145 let block_start = to_usize(block_info.block_start);
2146 let block_length = to_usize(block_info.block_length);
2147 if block_length == 0 {
2148 return (pos, pos);
2149 }
2150
2151 let dto = frontend::document_inspection::GetTextAtPositionDto {
2152 position: to_i64(block_start),
2153 length: to_i64(block_length),
2154 };
2155 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
2156 Ok(r) => r.text,
2157 Err(_) => return (pos, pos),
2158 };
2159
2160 let cursor_offset = pos.saturating_sub(block_start);
2162
2163 let mut last_char_start = 0;
2165 let mut last_char_end = 0;
2166
2167 for (word_byte_start, word) in text.unicode_word_indices() {
2168 let word_char_start = text[..word_byte_start].chars().count();
2170 let word_char_len = word.chars().count();
2171 let word_char_end = word_char_start + word_char_len;
2172
2173 last_char_start = word_char_start;
2174 last_char_end = word_char_end;
2175
2176 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
2177 return (block_start + word_char_start, block_start + word_char_end);
2178 }
2179 }
2180
2181 if cursor_offset == last_char_end && last_char_start < last_char_end {
2183 return (block_start + last_char_start, block_start + last_char_end);
2184 }
2185
2186 (pos, pos)
2187 }
2188}