1use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use crate::{DocumentError, Result};
8
9use crate::ListStyle;
10use frontend::commands::{
11 document_editing_commands, document_formatting_commands, document_inspection_commands,
12 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, FrameRef, 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.saturating_sub(edit_pos);
115 inner.adjust_cursors(edit_pos, removed, added);
116 {
117 let mut d = self.data.lock();
118 d.position = new_pos;
119 d.anchor = new_pos;
120 }
121 inner.modified = true;
122 inner.invalidate_text_cache();
123 inner.rehighlight_affected(edit_pos);
124 inner.queue_event(DocumentEvent::ContentsChanged {
125 position: edit_pos,
126 chars_removed: removed,
127 chars_added: added,
128 blocks_affected,
129 });
130 inner.check_block_count_changed();
131 if flow_may_change {
132 inner.check_flow_changed();
133 }
134 self.queue_undo_redo_event(inner)
135 }
136
137 pub fn position(&self) -> usize {
141 self.data.lock().position
142 }
143
144 pub fn anchor(&self) -> usize {
146 self.data.lock().anchor
147 }
148
149 pub fn has_selection(&self) -> bool {
151 let d = self.data.lock();
152 d.position != d.anchor
153 }
154
155 pub fn selection_start(&self) -> usize {
157 let d = self.data.lock();
158 d.position.min(d.anchor)
159 }
160
161 pub fn selection_end(&self) -> usize {
163 let d = self.data.lock();
164 d.position.max(d.anchor)
165 }
166
167 pub fn selected_text(&self) -> Result<String> {
169 let (pos, anchor) = self.read_cursor();
170 if pos == anchor {
171 return Ok(String::new());
172 }
173 let start = pos.min(anchor);
174 let len = pos.max(anchor) - start;
175 let inner = self.doc.lock();
176 let dto = frontend::document_inspection::GetTextAtPositionDto {
177 position: to_i64(start),
178 length: to_i64(len),
179 };
180 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
181 Ok(result.text)
182 }
183
184 pub fn clear_selection(&self) {
186 let mut d = self.data.lock();
187 d.anchor = d.position;
188 }
189
190 pub fn at_block_start(&self) -> bool {
194 let pos = self.position();
195 let inner = self.doc.lock();
196 let dto = frontend::document_inspection::GetBlockAtPositionDto {
197 position: to_i64(pos),
198 };
199 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
200 pos == to_usize(info.block_start)
201 } else {
202 false
203 }
204 }
205
206 pub fn at_block_end(&self) -> bool {
208 let pos = self.position();
209 let inner = self.doc.lock();
210 let dto = frontend::document_inspection::GetBlockAtPositionDto {
211 position: to_i64(pos),
212 };
213 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
214 pos == to_usize(info.block_start) + to_usize(info.block_length)
215 } else {
216 false
217 }
218 }
219
220 pub fn at_start(&self) -> bool {
222 self.data.lock().position == 0
223 }
224
225 pub fn at_end(&self) -> bool {
227 let pos = self.position();
228 let inner = self.doc.lock();
229 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
230 frontend::document_inspection::DocumentStatsDto {
231 character_count: 0,
232 word_count: 0,
233 block_count: 0,
234 frame_count: 0,
235 image_count: 0,
236 list_count: 0,
237 table_count: 0,
238 }
239 });
240 pos >= max_cursor_position(&stats)
241 }
242
243 pub fn block_number(&self) -> usize {
245 let pos = self.position();
246 let inner = self.doc.lock();
247 let dto = frontend::document_inspection::GetBlockAtPositionDto {
248 position: to_i64(pos),
249 };
250 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
251 .map(|info| to_usize(info.block_number))
252 .unwrap_or(0)
253 }
254
255 pub fn position_in_block(&self) -> usize {
257 let pos = self.position();
258 let inner = self.doc.lock();
259 let dto = frontend::document_inspection::GetBlockAtPositionDto {
260 position: to_i64(pos),
261 };
262 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
263 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
264 .unwrap_or(0)
265 }
266
267 pub fn set_position(&self, position: usize, mode: MoveMode) {
281 let end = {
283 let inner = self.doc.lock();
284 document_inspection_commands::get_document_stats(&inner.ctx)
285 .map(|s| max_cursor_position(&s))
286 .unwrap_or(0)
287 };
288 let mut pos = position.min(end);
289
290 if mode == MoveMode::KeepAnchor {
294 let anchor = self.data.lock().anchor;
295 let pos_cell = self.table_cell_at(pos);
296 let anchor_cell = self.table_cell_at(anchor);
297 match (&pos_cell, &anchor_cell) {
298 (Some(tc), None) => {
299 let before = anchor < pos;
301 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
302 pos = boundary;
303 }
304 }
305 (None, Some(tc)) => {
306 let before = pos < anchor;
309 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
310 pos = boundary;
311 }
312 }
313 _ => {}
314 }
315 }
316
317 {
318 let mut d = self.data.lock();
319 d.position = pos;
320 if mode == MoveMode::MoveAnchor {
321 d.anchor = pos;
322 }
323 d.cell_selection_override = None;
324 }
325 self.snap_position_to_grapheme_boundary();
330 }
331
332 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
338 let old_pos = self.position();
339 let target = self.resolve_move(operation, n);
340 self.set_position(target, mode);
341 self.position() != old_pos
342 }
343
344 pub fn select(&self, selection: SelectionType) {
346 match selection {
347 SelectionType::Document => {
348 let end = {
349 let inner = self.doc.lock();
350 document_inspection_commands::get_document_stats(&inner.ctx)
351 .map(|s| max_cursor_position(&s))
352 .unwrap_or(0)
353 };
354 let mut d = self.data.lock();
355 d.anchor = 0;
356 d.position = end;
357 d.cell_selection_override = None;
358 }
359 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
360 let pos = self.position();
361 let inner = self.doc.lock();
362 let dto = frontend::document_inspection::GetBlockAtPositionDto {
363 position: to_i64(pos),
364 };
365 if let Ok(info) =
366 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
367 {
368 let start = to_usize(info.block_start);
369 let end = start + to_usize(info.block_length);
370 drop(inner);
371 let mut d = self.data.lock();
372 d.anchor = start;
373 d.position = end;
374 d.cell_selection_override = None;
375 }
376 }
377 SelectionType::WordUnderCursor => {
378 let pos = self.position();
379 let (word_start, word_end) = self.find_word_boundaries(pos);
380 let mut d = self.data.lock();
381 d.anchor = word_start;
382 d.position = word_end;
383 d.cell_selection_override = None;
384 }
385 }
386 }
387
388 pub fn insert_text(&self, text: &str) -> Result<()> {
392 let (pos, anchor) = self.read_cursor();
393
394 let dto = frontend::document_editing::InsertTextDto {
396 position: to_i64(pos),
397 anchor: to_i64(anchor),
398 text: text.into(),
399 };
400
401 let queued = {
402 let mut inner = self.doc.lock();
403 let result = match document_editing_commands::insert_text(
404 &inner.ctx,
405 Some(inner.stack_id),
406 &dto,
407 ) {
408 Ok(r) => r,
409 Err(_) if pos != anchor => {
410 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
412
413 let del_dto = frontend::document_editing::DeleteTextDto {
414 position: to_i64(pos),
415 anchor: to_i64(anchor),
416 };
417 let del_result = document_editing_commands::delete_text(
418 &inner.ctx,
419 Some(inner.stack_id),
420 &del_dto,
421 )?;
422 let del_pos = to_usize(del_result.new_position);
423
424 let ins_dto = frontend::document_editing::InsertTextDto {
425 position: to_i64(del_pos),
426 anchor: to_i64(del_pos),
427 text: text.into(),
428 };
429 let ins_result = document_editing_commands::insert_text(
430 &inner.ctx,
431 Some(inner.stack_id),
432 &ins_dto,
433 )?;
434
435 undo_redo_commands::end_composite(&inner.ctx);
436 ins_result
437 }
438 Err(e) => return Err(e.into()),
439 };
440
441 let edit_pos = pos.min(anchor);
442 let removed = pos.max(anchor) - edit_pos;
443 self.finish_edit_ext(
444 &mut inner,
445 edit_pos,
446 removed,
447 to_usize(result.new_position),
448 to_usize(result.blocks_affected),
449 false,
450 )
451 };
452 crate::inner::dispatch_queued_events(queued);
453 Ok(())
454 }
455
456 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
458 let (pos, anchor) = self.read_cursor();
459
460 let make_dto = |p: usize, a: usize| frontend::document_editing::InsertFormattedTextDto {
461 position: to_i64(p),
462 anchor: to_i64(a),
463 text: text.into(),
464 font_family: format.font_family.clone().unwrap_or_default(),
465 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
466 font_bold: format.font_bold.unwrap_or(false),
467 font_italic: format.font_italic.unwrap_or(false),
468 font_underline: format.font_underline.unwrap_or(false),
469 font_strikeout: format.font_strikeout.unwrap_or(false),
470 };
471
472 let queued = {
473 let mut inner = self.doc.lock();
474 let result = match document_editing_commands::insert_formatted_text(
475 &inner.ctx,
476 Some(inner.stack_id),
477 &make_dto(pos, anchor),
478 ) {
479 Ok(r) => r,
480 Err(_) if pos != anchor => {
481 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
483
484 let del_dto = frontend::document_editing::DeleteTextDto {
485 position: to_i64(pos),
486 anchor: to_i64(anchor),
487 };
488 let del_result = document_editing_commands::delete_text(
489 &inner.ctx,
490 Some(inner.stack_id),
491 &del_dto,
492 )?;
493 let del_pos = to_usize(del_result.new_position);
494
495 let ins_result = document_editing_commands::insert_formatted_text(
496 &inner.ctx,
497 Some(inner.stack_id),
498 &make_dto(del_pos, del_pos),
499 )?;
500
501 undo_redo_commands::end_composite(&inner.ctx);
502 ins_result
503 }
504 Err(e) => return Err(e.into()),
505 };
506
507 let edit_pos = pos.min(anchor);
508 let removed = pos.max(anchor) - edit_pos;
509 self.finish_edit_ext(
510 &mut inner,
511 edit_pos,
512 removed,
513 to_usize(result.new_position),
514 1,
515 false,
516 )
517 };
518 crate::inner::dispatch_queued_events(queued);
519 Ok(())
520 }
521
522 pub fn insert_block(&self) -> Result<()> {
524 let (pos, anchor) = self.read_cursor();
525 let queued = {
526 let mut inner = self.doc.lock();
527
528 let (insert_pos, removed) = if pos != anchor {
529 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
531 let del_dto = frontend::document_editing::DeleteTextDto {
532 position: to_i64(pos),
533 anchor: to_i64(anchor),
534 };
535 let del_result = document_editing_commands::delete_text(
536 &inner.ctx,
537 Some(inner.stack_id),
538 &del_dto,
539 )?;
540 (
541 to_usize(del_result.new_position),
542 pos.max(anchor) - pos.min(anchor),
543 )
544 } else {
545 (pos, 0)
546 };
547
548 let dto = frontend::document_editing::InsertBlockDto {
549 position: to_i64(insert_pos),
550 anchor: to_i64(insert_pos),
551 };
552 let result =
553 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
554
555 if pos != anchor {
556 undo_redo_commands::end_composite(&inner.ctx);
557 }
558
559 let edit_pos = pos.min(anchor);
560 self.finish_edit(
561 &mut inner,
562 edit_pos,
563 removed,
564 to_usize(result.new_position),
565 2,
566 )
567 };
568 crate::inner::dispatch_queued_events(queued);
569 Ok(())
570 }
571
572 pub fn insert_html(&self, html: &str) -> Result<()> {
574 let frag = DocumentFragment::from_html(html);
576 self.insert_fragment(&frag)
577 }
578
579 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
581 let frag = DocumentFragment::from_markdown(markdown);
582 self.insert_fragment(&frag)
583 }
584
585 pub fn insert_djot(&self, djot: &str) -> Result<()> {
587 let frag = DocumentFragment::from_djot(djot);
588 self.insert_fragment(&frag)
589 }
590
591 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
593 let (pos, anchor) = self.read_cursor();
594 let queued = {
595 let mut inner = self.doc.lock();
596
597 let (insert_pos, removed) = if pos != anchor {
598 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
599 let del_dto = frontend::document_editing::DeleteTextDto {
600 position: to_i64(pos),
601 anchor: to_i64(anchor),
602 };
603 let del_result = document_editing_commands::delete_text(
604 &inner.ctx,
605 Some(inner.stack_id),
606 &del_dto,
607 )?;
608 (
609 to_usize(del_result.new_position),
610 pos.max(anchor) - pos.min(anchor),
611 )
612 } else {
613 (pos, 0)
614 };
615
616 let dto = frontend::document_editing::InsertFragmentDto {
617 position: to_i64(insert_pos),
618 anchor: to_i64(insert_pos),
619 fragment_data: fragment.raw_data().into(),
620 };
621 let result =
622 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
623
624 if pos != anchor {
625 undo_redo_commands::end_composite(&inner.ctx);
626 }
627
628 let edit_pos = pos.min(anchor);
629 self.finish_edit(
630 &mut inner,
631 edit_pos,
632 removed,
633 to_usize(result.new_position),
634 to_usize(result.blocks_added),
635 )
636 };
637 crate::inner::dispatch_queued_events(queued);
638 Ok(())
639 }
640
641 pub fn selection(&self) -> DocumentFragment {
643 let (pos, anchor) = self.read_cursor();
644
645 let (extract_pos, extract_anchor) = match self.selection_kind() {
648 SelectionKind::Cells(ref range) => match self.cell_range_positions(range) {
649 Some((start, end)) => (start, end),
650 None => return DocumentFragment::new(),
651 },
652 SelectionKind::Mixed {
653 ref cell_range,
654 text_before,
655 text_after,
656 } => {
657 let (cell_start, cell_end) = match self.cell_range_positions(cell_range) {
658 Some(p) => p,
659 None => return DocumentFragment::new(),
660 };
661 let start = if text_before {
662 pos.min(anchor)
663 } else {
664 cell_start
665 };
666 let end = if text_after {
667 pos.max(anchor)
668 } else {
669 cell_end
670 };
671 (start.min(cell_start), end.max(cell_end))
672 }
673 SelectionKind::None => return DocumentFragment::new(),
674 SelectionKind::Text => (pos, anchor),
675 };
676
677 if extract_pos == extract_anchor {
678 return DocumentFragment::new();
679 }
680
681 let inner = self.doc.lock();
682 let dto = frontend::document_inspection::ExtractFragmentDto {
683 position: to_i64(extract_pos),
684 anchor: to_i64(extract_anchor),
685 };
686 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
687 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
688 Err(_) => DocumentFragment::new(),
689 }
690 }
691
692 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
694 let (pos, anchor) = self.read_cursor();
695 let queued = {
696 let mut inner = self.doc.lock();
697
698 let (insert_pos, removed) = if pos != anchor {
699 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
700 let del_dto = frontend::document_editing::DeleteTextDto {
701 position: to_i64(pos),
702 anchor: to_i64(anchor),
703 };
704 let del_result = document_editing_commands::delete_text(
705 &inner.ctx,
706 Some(inner.stack_id),
707 &del_dto,
708 )?;
709 (
710 to_usize(del_result.new_position),
711 pos.max(anchor) - pos.min(anchor),
712 )
713 } else {
714 (pos, 0)
715 };
716
717 let dto = frontend::document_editing::InsertImageDto {
718 position: to_i64(insert_pos),
719 anchor: to_i64(insert_pos),
720 image_name: name.into(),
721 width: width as i64,
722 height: height as i64,
723 };
724 let result =
725 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
726
727 if pos != anchor {
728 undo_redo_commands::end_composite(&inner.ctx);
729 }
730
731 let edit_pos = pos.min(anchor);
732 self.finish_edit_ext(
733 &mut inner,
734 edit_pos,
735 removed,
736 to_usize(result.new_position),
737 1,
738 false,
739 )
740 };
741 crate::inner::dispatch_queued_events(queued);
742 Ok(())
743 }
744
745 pub fn insert_frame(&self) -> Result<()> {
747 let (pos, anchor) = self.read_cursor();
748 let queued = {
749 let mut inner = self.doc.lock();
750 let dto = frontend::document_editing::InsertFrameDto {
751 position: to_i64(pos),
752 anchor: to_i64(anchor),
753 };
754 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
755 inner.modified = true;
758 inner.invalidate_text_cache();
759 inner.rehighlight_affected(pos.min(anchor));
760 inner.queue_event(DocumentEvent::ContentsChanged {
761 position: pos.min(anchor),
762 chars_removed: 0,
763 chars_added: 0,
764 blocks_affected: 1,
765 });
766 inner.check_block_count_changed();
767 inner.check_flow_changed();
768 self.queue_undo_redo_event(&mut inner)
769 };
770 crate::inner::dispatch_queued_events(queued);
771 Ok(())
772 }
773
774 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
780 let (pos, anchor) = self.read_cursor();
781 let (table_id, queued) = {
782 let mut inner = self.doc.lock();
783 let dto = frontend::document_editing::InsertTableDto {
784 position: to_i64(pos),
785 anchor: to_i64(anchor),
786 rows: to_i64(rows),
787 columns: to_i64(columns),
788 };
789 let result =
790 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
791 let new_pos = to_usize(result.new_position);
792 let table_id = to_usize(result.table_id);
793 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
794 {
795 let mut d = self.data.lock();
796 d.position = new_pos;
797 d.anchor = new_pos;
798 }
799 inner.modified = true;
800 inner.invalidate_text_cache();
801 inner.rehighlight_affected(pos.min(anchor));
802 inner.queue_event(DocumentEvent::ContentsChanged {
803 position: pos.min(anchor),
804 chars_removed: 0,
805 chars_added: new_pos - pos.min(anchor),
806 blocks_affected: 1,
807 });
808 inner.check_block_count_changed();
809 inner.check_flow_changed();
810 (table_id, self.queue_undo_redo_event(&mut inner))
811 };
812 crate::inner::dispatch_queued_events(queued);
813 Ok(TextTable {
814 doc: self.doc.clone(),
815 table_id,
816 })
817 }
818
819 pub fn current_table(&self) -> Option<TextTable> {
824 self.current_table_cell().map(|c| c.table)
825 }
826
827 pub fn current_table_cell(&self) -> Option<TableCellRef> {
832 let pos = self.position();
833 let inner = self.doc.lock();
834 let dto = frontend::document_inspection::GetBlockAtPositionDto {
836 position: to_i64(pos),
837 };
838 let block_info =
839 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
840
841 let block_id = if to_i64(pos) < block_info.block_start && pos > 0 {
845 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
846 position: to_i64(pos - 1),
847 };
848 let prev_info =
849 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
850 prev_info.block_id as usize
851 } else {
852 block_info.block_id as usize
853 };
854
855 let block = crate::text_block::TextBlock {
856 doc: self.doc.clone(),
857 block_id,
858 };
859 drop(inner);
861 block.table_cell()
862 }
863
864 pub fn current_frame(&self) -> Option<FrameRef> {
871 let pos = self.position();
872 let inner = self.doc.lock();
873 let dto = frontend::document_inspection::GetBlockAtPositionDto {
874 position: to_i64(pos),
875 };
876 let block_info =
877 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
878 let block_id = block_info.block_id as u64;
879 cursor_frame_ref(&inner, block_id)
880 }
881
882 pub fn is_in_blockquote(&self) -> bool {
885 self.current_blockquote_frame_id().is_some()
886 }
887
888 pub fn current_blockquote_frame_id(&self) -> Option<usize> {
891 let pos = self.position();
892 let inner = self.doc.lock();
893 let dto = frontend::document_inspection::GetBlockAtPositionDto {
894 position: to_i64(pos),
895 };
896 let block_info =
897 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
898 innermost_blockquote_frame_id(&inner, block_info.block_id as u64)
899 }
900
901 pub fn blockquote_depth_at_cursor(&self) -> usize {
904 let pos = self.position();
905 let inner = self.doc.lock();
906 let dto = frontend::document_inspection::GetBlockAtPositionDto {
907 position: to_i64(pos),
908 };
909 let Some(block_info) =
910 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
911 else {
912 return 0;
913 };
914 blockquote_depth_for_block(&inner, block_info.block_id as u64)
915 }
916
917 pub fn is_first_block_in_current_frame(&self) -> bool {
923 matches!(
924 block_position_in_current_frame(self),
925 Some(BlockEdge::First) | Some(BlockEdge::OnlyOne)
926 )
927 }
928
929 pub fn is_last_block_in_current_frame(&self) -> bool {
933 matches!(
934 block_position_in_current_frame(self),
935 Some(BlockEdge::Last) | Some(BlockEdge::OnlyOne)
936 )
937 }
938
939 pub fn current_block_is_empty(&self) -> bool {
942 let pos = self.position();
943 let inner = self.doc.lock();
944 let dto = frontend::document_inspection::GetBlockAtPositionDto {
945 position: to_i64(pos),
946 };
947 let Some(block_info) =
948 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
949 else {
950 return false;
951 };
952 let store = inner.ctx.db_context.get_store();
953 let block_entity = store
954 .blocks
955 .read()
956 .get(&(block_info.block_id as common::types::EntityId))
957 .cloned();
958 match block_entity {
959 Some(b) => {
960 let len = common::database::rope_helpers::block_char_length(&b, store);
961 len == 0
962 }
963 None => false,
964 }
965 }
966
967 pub fn selection_spans_multiple_frames(&self) -> bool {
971 let (pos, anchor) = self.read_cursor();
972 if pos == anchor {
973 return false;
974 }
975 let inner = self.doc.lock();
976 let pos_dto = frontend::document_inspection::GetBlockAtPositionDto {
977 position: to_i64(pos),
978 };
979 let anchor_dto = frontend::document_inspection::GetBlockAtPositionDto {
980 position: to_i64(anchor),
981 };
982 let Some(pos_block) =
983 document_inspection_commands::get_block_at_position(&inner.ctx, &pos_dto).ok()
984 else {
985 return false;
986 };
987 let Some(anchor_block) =
988 document_inspection_commands::get_block_at_position(&inner.ctx, &anchor_dto).ok()
989 else {
990 return false;
991 };
992 let pos_owner = crate::text_block::find_parent_frame(&inner, pos_block.block_id as u64);
993 let anchor_owner =
994 crate::text_block::find_parent_frame(&inner, anchor_block.block_id as u64);
995 pos_owner != anchor_owner
996 }
997
998 pub fn wrap_selection_in_blockquote(&self) -> Result<()> {
1005 if self.selection_spans_multiple_frames() {
1006 return Err(DocumentError::InvalidArgument(
1007 "Cannot wrap selection in blockquote: selection spans multiple frames".into(),
1008 ));
1009 }
1010 let (start_block_id, end_block_id) = self.resolve_selection_block_range()?;
1011 let dto = frontend::document_editing::WrapBlocksInFrameDto {
1012 start_block_id: start_block_id as i64,
1013 end_block_id: end_block_id as i64,
1014 position: Some(frontend::document_editing::FramePosition::InFlow),
1015 top_margin: None,
1016 bottom_margin: None,
1017 left_margin: None,
1018 right_margin: None,
1019 padding: None,
1020 border: None,
1021 is_blockquote: Some(true),
1022 };
1023 let queued = {
1024 let mut inner = self.doc.lock();
1025 let _result = document_editing_commands::wrap_blocks_in_frame(
1026 &inner.ctx,
1027 Some(inner.stack_id),
1028 &dto,
1029 )?;
1030 inner.modified = true;
1031 inner.queue_event(DocumentEvent::FormatChanged {
1039 position: 0,
1040 length: 0,
1041 kind: crate::flow::FormatChangeKind::Block,
1042 });
1043 self.queue_undo_redo_event(&mut inner)
1044 };
1045 crate::inner::dispatch_queued_events(queued);
1046 Ok(())
1047 }
1048
1049 pub fn insert_blockquote(&self) -> Result<()> {
1053 self.wrap_selection_in_blockquote()
1054 }
1055
1056 pub fn toggle_blockquote(&self) -> Result<()> {
1061 if let Some(frame_id) = self.current_blockquote_frame_id() {
1062 self.unwrap_frame_by_id(frame_id)
1063 } else {
1064 self.wrap_selection_in_blockquote()
1065 }
1066 }
1067
1068 pub fn unwrap_current_frame(&self) -> Result<()> {
1072 let frame_ref = self.current_frame().ok_or_else(|| {
1073 DocumentError::InvalidCursorContext("Cursor is not inside any sub-frame".into())
1074 })?;
1075 self.unwrap_frame_by_id(frame_ref.frame_id)
1076 }
1077
1078 pub fn unwrap_current_block_from_blockquote(&self) -> Result<()> {
1082 if self.current_blockquote_frame_id().is_none() {
1083 return Err(DocumentError::InvalidCursorContext(
1084 "Cursor is not inside a blockquote".into(),
1085 ));
1086 }
1087 let block_id = self.current_block_id_for_mutation()?;
1088 let dto = frontend::document_editing::UnwrapBlockFromFrameDto {
1089 block_id: block_id as i64,
1090 };
1091 let queued = {
1092 let mut inner = self.doc.lock();
1093 let _result = document_editing_commands::unwrap_block_from_frame(
1094 &inner.ctx,
1095 Some(inner.stack_id),
1096 &dto,
1097 )?;
1098 inner.modified = true;
1099 inner.queue_event(DocumentEvent::FormatChanged {
1103 position: 0,
1104 length: 0,
1105 kind: crate::flow::FormatChangeKind::Block,
1106 });
1107 self.queue_undo_redo_event(&mut inner)
1108 };
1109 crate::inner::dispatch_queued_events(queued);
1110 Ok(())
1111 }
1112
1113 pub fn increase_blockquote_depth(&self) -> Result<()> {
1117 self.wrap_selection_in_blockquote()
1118 }
1119
1120 pub fn decrease_blockquote_depth(&self) -> Result<()> {
1126 if self.current_blockquote_frame_id().is_none() {
1127 return Err(DocumentError::InvalidCursorContext(
1128 "Cursor is not inside a blockquote to decrease depth".into(),
1129 ));
1130 }
1131 self.unwrap_current_block_from_blockquote()
1132 }
1133
1134 fn unwrap_frame_by_id(&self, frame_id: usize) -> Result<()> {
1135 let dto = frontend::document_editing::UnwrapFrameDto {
1136 frame_id: frame_id as i64,
1137 };
1138 let queued = {
1139 let mut inner = self.doc.lock();
1140 let _result =
1141 document_editing_commands::unwrap_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
1142 inner.modified = true;
1143 inner.queue_event(DocumentEvent::FormatChanged {
1147 position: 0,
1148 length: 0,
1149 kind: crate::flow::FormatChangeKind::Block,
1150 });
1151 self.queue_undo_redo_event(&mut inner)
1152 };
1153 crate::inner::dispatch_queued_events(queued);
1154 Ok(())
1155 }
1156
1157 fn current_block_id_for_mutation(&self) -> Result<usize> {
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 .map_err(|e| anyhow::anyhow!("get_block_at_position: {}", e))?;
1165 Ok(block_info.block_id as usize)
1166 }
1167
1168 fn resolve_selection_block_range(&self) -> Result<(usize, usize)> {
1169 let (pos, anchor) = self.read_cursor();
1170 let lo = pos.min(anchor);
1171 let hi = pos.max(anchor);
1172 let inner = self.doc.lock();
1173 let lo_dto = frontend::document_inspection::GetBlockAtPositionDto {
1174 position: to_i64(lo),
1175 };
1176 let hi_dto = frontend::document_inspection::GetBlockAtPositionDto {
1177 position: to_i64(hi),
1178 };
1179 let lo_block = document_inspection_commands::get_block_at_position(&inner.ctx, &lo_dto)
1180 .map_err(|e| anyhow::anyhow!("get_block_at_position(start): {}", e))?;
1181 let hi_block = document_inspection_commands::get_block_at_position(&inner.ctx, &hi_dto)
1182 .map_err(|e| anyhow::anyhow!("get_block_at_position(end): {}", e))?;
1183 Ok((lo_block.block_id as usize, hi_block.block_id as usize))
1184 }
1185
1186 pub fn remove_table(&self, table_id: usize) -> Result<()> {
1190 let queued = {
1191 let mut inner = self.doc.lock();
1192 let dto = frontend::document_editing::RemoveTableDto {
1193 table_id: to_i64(table_id),
1194 };
1195 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1196 inner.modified = true;
1197 inner.invalidate_text_cache();
1198 inner.rehighlight_all();
1199 inner.check_block_count_changed();
1200 inner.check_flow_changed();
1201 self.queue_undo_redo_event(&mut inner)
1202 };
1203 crate::inner::dispatch_queued_events(queued);
1204 Ok(())
1205 }
1206
1207 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1209 let queued = {
1210 let mut inner = self.doc.lock();
1211 let dto = frontend::document_editing::InsertTableRowDto {
1212 table_id: to_i64(table_id),
1213 row_index: to_i64(row_index),
1214 };
1215 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1216 inner.modified = true;
1217 inner.invalidate_text_cache();
1218 inner.rehighlight_all();
1219 inner.check_block_count_changed();
1220 self.queue_undo_redo_event(&mut inner)
1221 };
1222 crate::inner::dispatch_queued_events(queued);
1223 Ok(())
1224 }
1225
1226 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1228 let queued = {
1229 let mut inner = self.doc.lock();
1230 let dto = frontend::document_editing::InsertTableColumnDto {
1231 table_id: to_i64(table_id),
1232 column_index: to_i64(column_index),
1233 };
1234 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1235 inner.modified = true;
1236 inner.invalidate_text_cache();
1237 inner.rehighlight_all();
1238 inner.check_block_count_changed();
1239 self.queue_undo_redo_event(&mut inner)
1240 };
1241 crate::inner::dispatch_queued_events(queued);
1242 Ok(())
1243 }
1244
1245 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1247 let queued = {
1248 let mut inner = self.doc.lock();
1249 let dto = frontend::document_editing::RemoveTableRowDto {
1250 table_id: to_i64(table_id),
1251 row_index: to_i64(row_index),
1252 };
1253 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1254 inner.modified = true;
1255 inner.invalidate_text_cache();
1256 inner.rehighlight_all();
1257 inner.check_block_count_changed();
1258 self.queue_undo_redo_event(&mut inner)
1259 };
1260 crate::inner::dispatch_queued_events(queued);
1261 Ok(())
1262 }
1263
1264 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1266 let queued = {
1267 let mut inner = self.doc.lock();
1268 let dto = frontend::document_editing::RemoveTableColumnDto {
1269 table_id: to_i64(table_id),
1270 column_index: to_i64(column_index),
1271 };
1272 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1273 inner.modified = true;
1274 inner.invalidate_text_cache();
1275 inner.rehighlight_all();
1276 inner.check_block_count_changed();
1277 self.queue_undo_redo_event(&mut inner)
1278 };
1279 crate::inner::dispatch_queued_events(queued);
1280 Ok(())
1281 }
1282
1283 pub fn merge_table_cells(
1285 &self,
1286 table_id: usize,
1287 start_row: usize,
1288 start_column: usize,
1289 end_row: usize,
1290 end_column: usize,
1291 ) -> Result<()> {
1292 let queued = {
1293 let mut inner = self.doc.lock();
1294 let dto = frontend::document_editing::MergeTableCellsDto {
1295 table_id: to_i64(table_id),
1296 start_row: to_i64(start_row),
1297 start_column: to_i64(start_column),
1298 end_row: to_i64(end_row),
1299 end_column: to_i64(end_column),
1300 };
1301 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
1302 inner.modified = true;
1303 inner.invalidate_text_cache();
1304 inner.rehighlight_all();
1305 inner.check_block_count_changed();
1306 self.queue_undo_redo_event(&mut inner)
1307 };
1308 crate::inner::dispatch_queued_events(queued);
1309 Ok(())
1310 }
1311
1312 pub fn split_table_cell(
1314 &self,
1315 cell_id: usize,
1316 split_rows: usize,
1317 split_columns: usize,
1318 ) -> Result<()> {
1319 let queued = {
1320 let mut inner = self.doc.lock();
1321 let dto = frontend::document_editing::SplitTableCellDto {
1322 cell_id: to_i64(cell_id),
1323 split_rows: to_i64(split_rows),
1324 split_columns: to_i64(split_columns),
1325 };
1326 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
1327 inner.modified = true;
1328 inner.invalidate_text_cache();
1329 inner.rehighlight_all();
1330 inner.check_block_count_changed();
1331 self.queue_undo_redo_event(&mut inner)
1332 };
1333 crate::inner::dispatch_queued_events(queued);
1334 Ok(())
1335 }
1336
1337 pub fn set_table_format(
1341 &self,
1342 table_id: usize,
1343 format: &crate::flow::TableFormat,
1344 ) -> Result<()> {
1345 let queued = {
1346 let mut inner = self.doc.lock();
1347 let dto = format.to_set_dto(table_id);
1348 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1349 inner.modified = true;
1350 inner.queue_event(DocumentEvent::FormatChanged {
1351 position: 0,
1352 length: 0,
1353 kind: crate::flow::FormatChangeKind::Block,
1354 });
1355 self.queue_undo_redo_event(&mut inner)
1356 };
1357 crate::inner::dispatch_queued_events(queued);
1358 Ok(())
1359 }
1360
1361 pub fn set_table_cell_format(
1363 &self,
1364 cell_id: usize,
1365 format: &crate::flow::CellFormat,
1366 ) -> Result<()> {
1367 let queued = {
1368 let mut inner = self.doc.lock();
1369 let dto = format.to_set_dto(cell_id);
1370 document_formatting_commands::set_table_cell_format(
1371 &inner.ctx,
1372 Some(inner.stack_id),
1373 &dto,
1374 )?;
1375 inner.modified = true;
1376 inner.queue_event(DocumentEvent::FormatChanged {
1377 position: 0,
1378 length: 0,
1379 kind: crate::flow::FormatChangeKind::Block,
1380 });
1381 self.queue_undo_redo_event(&mut inner)
1382 };
1383 crate::inner::dispatch_queued_events(queued);
1384 Ok(())
1385 }
1386
1387 pub fn remove_current_table(&self) -> Result<()> {
1392 let table = self.current_table().ok_or_else(|| {
1393 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1394 })?;
1395 self.remove_table(table.id())
1396 }
1397
1398 pub fn insert_row_above(&self) -> Result<()> {
1401 let cell_ref = self.current_table_cell().ok_or_else(|| {
1402 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1403 })?;
1404 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
1405 }
1406
1407 pub fn insert_row_below(&self) -> Result<()> {
1410 let cell_ref = self.current_table_cell().ok_or_else(|| {
1411 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1412 })?;
1413 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
1414 }
1415
1416 pub fn insert_column_before(&self) -> Result<()> {
1419 let cell_ref = self.current_table_cell().ok_or_else(|| {
1420 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1421 })?;
1422 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
1423 }
1424
1425 pub fn insert_column_after(&self) -> Result<()> {
1428 let cell_ref = self.current_table_cell().ok_or_else(|| {
1429 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1430 })?;
1431 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
1432 }
1433
1434 pub fn remove_current_row(&self) -> Result<()> {
1437 let cell_ref = self.current_table_cell().ok_or_else(|| {
1438 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1439 })?;
1440 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
1441 }
1442
1443 pub fn remove_current_column(&self) -> Result<()> {
1446 let cell_ref = self.current_table_cell().ok_or_else(|| {
1447 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1448 })?;
1449 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
1450 }
1451
1452 pub fn merge_selected_cells(&self) -> Result<()> {
1459 let pos_cell = self.current_table_cell().ok_or_else(|| {
1460 DocumentError::InvalidCursorContext("cursor position is not inside a table".into())
1461 })?;
1462
1463 let (_pos, anchor) = self.read_cursor();
1465 let anchor_cell = {
1466 let inner = self.doc.lock();
1468 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1469 position: to_i64(anchor),
1470 };
1471 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1472 .map_err(|_| {
1473 DocumentError::InvalidCursorContext(
1474 "cursor anchor is not inside a table".into(),
1475 )
1476 })?;
1477 let block = crate::text_block::TextBlock {
1478 doc: self.doc.clone(),
1479 block_id: block_info.block_id as usize,
1480 };
1481 drop(inner);
1482 block.table_cell().ok_or_else(|| {
1483 DocumentError::InvalidCursorContext("cursor anchor is not inside a table".into())
1484 })?
1485 };
1486
1487 if pos_cell.table.id() != anchor_cell.table.id() {
1488 return Err(DocumentError::InvalidArgument(
1489 "position and anchor are in different tables".into(),
1490 ));
1491 }
1492
1493 let start_row = pos_cell.row.min(anchor_cell.row);
1494 let start_col = pos_cell.column.min(anchor_cell.column);
1495 let end_row = pos_cell.row.max(anchor_cell.row);
1496 let end_col = pos_cell.column.max(anchor_cell.column);
1497
1498 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
1499 }
1500
1501 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1504 let cell_ref = self.current_table_cell().ok_or_else(|| {
1505 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1506 })?;
1507 let cell = cell_ref
1509 .table
1510 .cell(cell_ref.row, cell_ref.column)
1511 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1512 self.split_table_cell(cell.id(), split_rows, split_columns)
1514 }
1515
1516 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1519 let table = self.current_table().ok_or_else(|| {
1520 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1521 })?;
1522 self.set_table_format(table.id(), format)
1523 }
1524
1525 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1528 let cell_ref = self.current_table_cell().ok_or_else(|| {
1529 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1530 })?;
1531 let cell = cell_ref
1532 .table
1533 .cell(cell_ref.row, cell_ref.column)
1534 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1535 self.set_table_cell_format(cell.id(), format)
1536 }
1537
1538 pub fn selection_kind(&self) -> crate::flow::SelectionKind {
1546 use crate::flow::{CellRange, SelectionKind};
1547
1548 {
1550 let d = self.data.lock();
1551 if let Some(ref range) = d.cell_selection_override {
1552 return SelectionKind::Cells(range.clone());
1553 }
1554 if d.position == d.anchor {
1555 return SelectionKind::None;
1556 }
1557 }
1558
1559 let (pos, anchor) = self.read_cursor();
1560
1561 let pos_cell = self.table_cell_at(pos);
1563 let anchor_cell = self.table_cell_at(anchor);
1564
1565 match (&pos_cell, &anchor_cell) {
1566 (None, None) => {
1567 let (start, end) = (pos.min(anchor), pos.max(anchor));
1571 if let Some(t) = self.find_table_between(start, end) {
1572 let table_id = t.id();
1573 let rows = t.rows();
1574 let cols = t.columns();
1575 let range = CellRange {
1576 table_id,
1577 start_row: 0,
1578 start_col: 0,
1579 end_row: if rows > 0 { rows - 1 } else { 0 },
1580 end_col: if cols > 0 { cols - 1 } else { 0 },
1581 };
1582 let spans = self.collect_cell_spans(table_id);
1583 SelectionKind::Mixed {
1584 cell_range: range.expand_for_spans(&spans),
1585 text_before: true,
1586 text_after: true,
1587 }
1588 } else {
1589 SelectionKind::Text
1590 }
1591 }
1592 (Some(pc), Some(ac)) => {
1593 if pc.table.id() != ac.table.id() {
1594 return SelectionKind::Text;
1596 }
1597 if pc.row == ac.row && pc.column == ac.column {
1598 return SelectionKind::Text;
1600 }
1601 let range = CellRange {
1603 table_id: pc.table.id(),
1604 start_row: pc.row.min(ac.row),
1605 start_col: pc.column.min(ac.column),
1606 end_row: pc.row.max(ac.row),
1607 end_col: pc.column.max(ac.column),
1608 };
1609 let spans = self.collect_cell_spans(pc.table.id());
1610 SelectionKind::Cells(range.expand_for_spans(&spans))
1611 }
1612 (Some(tc), None) | (None, Some(tc)) => {
1613 let table_id = tc.table.id();
1617 let rows = tc.table.rows();
1618 let cols = tc.table.columns();
1619
1620 let inside_pos = if pos_cell.is_some() { pos } else { anchor };
1621 let outside_pos = if pos_cell.is_some() { anchor } else { pos };
1622
1623 let text_before = outside_pos < inside_pos;
1624 let text_after = !text_before;
1625
1626 let range = CellRange {
1627 table_id,
1628 start_row: 0,
1629 start_col: 0,
1630 end_row: if rows > 0 { rows - 1 } else { 0 },
1631 end_col: if cols > 0 { cols - 1 } else { 0 },
1632 };
1633 let spans = self.collect_cell_spans(table_id);
1634 SelectionKind::Mixed {
1635 cell_range: range.expand_for_spans(&spans),
1636 text_before,
1637 text_after,
1638 }
1639 }
1640 }
1641 }
1642
1643 pub fn is_cell_selection(&self) -> bool {
1645 matches!(
1646 self.selection_kind(),
1647 crate::flow::SelectionKind::Cells(_) | crate::flow::SelectionKind::Mixed { .. }
1648 )
1649 }
1650
1651 pub fn selected_cell_range(&self) -> Option<crate::flow::CellRange> {
1653 match self.selection_kind() {
1654 crate::flow::SelectionKind::Cells(r) => Some(r),
1655 crate::flow::SelectionKind::Mixed { cell_range, .. } => Some(cell_range),
1656 _ => None,
1657 }
1658 }
1659
1660 pub fn selected_cells(&self) -> Vec<TableCellRef> {
1662 let range = match self.selected_cell_range() {
1663 Some(r) => r,
1664 None => return Vec::new(),
1665 };
1666 let table = TextTable {
1667 doc: self.doc.clone(),
1668 table_id: range.table_id,
1669 };
1670 let mut cells = Vec::new();
1671 for row in range.start_row..=range.end_row {
1672 for col in range.start_col..=range.end_col {
1673 if table.cell(row, col).is_some() {
1674 cells.push(TableCellRef {
1675 table: table.clone(),
1676 row,
1677 column: col,
1678 });
1679 }
1680 }
1681 }
1682 cells
1683 }
1684
1685 pub fn select_table_cell(&self, table_id: usize, row: usize, col: usize) {
1689 let mut d = self.data.lock();
1690 d.cell_selection_override = Some(crate::flow::CellRange {
1691 table_id,
1692 start_row: row,
1693 start_col: col,
1694 end_row: row,
1695 end_col: col,
1696 });
1697 }
1698
1699 pub fn select_cell_range(
1701 &self,
1702 table_id: usize,
1703 start_row: usize,
1704 start_col: usize,
1705 end_row: usize,
1706 end_col: usize,
1707 ) {
1708 let range = crate::flow::CellRange {
1709 table_id,
1710 start_row,
1711 start_col,
1712 end_row,
1713 end_col,
1714 };
1715 let spans = self.collect_cell_spans(table_id);
1716 let mut d = self.data.lock();
1717 d.cell_selection_override = Some(range.expand_for_spans(&spans));
1718 }
1719
1720 pub fn clear_cell_selection(&self) {
1722 let mut d = self.data.lock();
1723 d.cell_selection_override = None;
1724 }
1725
1726 fn cell_range_positions(&self, range: &CellRange) -> Option<(usize, usize)> {
1729 let inner = self.doc.lock();
1730 let main_frame_id = get_main_frame_id(&inner);
1731 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1732 drop(inner);
1733
1734 let table = flow.into_iter().find_map(|e| match e {
1736 FlowElement::Table(t) if t.id() == range.table_id => Some(t),
1737 _ => None,
1738 })?;
1739
1740 let mut min_pos = usize::MAX;
1741 let mut max_pos = 0usize;
1742
1743 for row in range.start_row..=range.end_row {
1744 for col in range.start_col..=range.end_col {
1745 if let Some(cell) = table.cell(row, col) {
1746 for block in cell.blocks() {
1747 let bp = block.position();
1748 let bl = block.length();
1749 min_pos = min_pos.min(bp);
1750 max_pos = max_pos.max(bp + bl);
1751 }
1752 }
1753 }
1754 }
1755
1756 if min_pos == usize::MAX {
1757 return None;
1758 }
1759
1760 Some((min_pos, max_pos + 1))
1762 }
1763
1764 fn table_cell_at(&self, position: usize) -> Option<TableCellRef> {
1768 let inner = self.doc.lock();
1769 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1770 position: to_i64(position),
1771 };
1772 let block_info =
1773 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1774
1775 let block_id = if to_i64(position) < block_info.block_start && position > 0 {
1776 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1777 position: to_i64(position - 1),
1778 };
1779 let prev_info =
1780 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
1781 prev_info.block_id as usize
1782 } else {
1783 block_info.block_id as usize
1784 };
1785
1786 let block = crate::text_block::TextBlock {
1787 doc: self.doc.clone(),
1788 block_id,
1789 };
1790 drop(inner);
1791 block.table_cell()
1792 }
1793
1794 fn table_boundary_position(&self, table_id: usize, before: bool) -> Option<usize> {
1805 let inner = self.doc.lock();
1806 let main_frame_id = get_main_frame_id(&inner);
1807 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1808 drop(inner);
1809
1810 let idx = flow
1812 .iter()
1813 .position(|e| matches!(e, FlowElement::Table(t) if t.id() == table_id))?;
1814
1815 if before {
1816 for i in (0..idx).rev() {
1818 if let FlowElement::Block(b) = &flow[i] {
1819 return Some(b.position() + b.length());
1820 }
1821 }
1822 } else {
1823 for item in flow.iter().skip(idx + 1) {
1825 if let FlowElement::Block(b) = item {
1826 return Some(b.position());
1827 }
1828 }
1829 }
1830 None
1831 }
1832
1833 fn find_table_between(&self, start: usize, end: usize) -> Option<TextTable> {
1835 let inner = self.doc.lock();
1836 let main_frame_id = get_main_frame_id(&inner);
1837 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1838 drop(inner);
1839
1840 for elem in flow {
1841 if let FlowElement::Table(t) = elem {
1842 if let Some(first_cell) = t.cell(0, 0) {
1845 let blocks = first_cell.blocks();
1846 if let Some(fb) = blocks.first() {
1847 let p = fb.position();
1848 if p > start && p < end {
1849 return Some(t);
1850 }
1851 }
1852 }
1853 }
1854 }
1855 None
1856 }
1857
1858 fn collect_cell_spans(&self, table_id: usize) -> Vec<(usize, usize, usize, usize)> {
1860 let inner = self.doc.lock();
1861 let table_dto =
1862 match frontend::commands::table_commands::get_table(&inner.ctx, &(table_id as u64))
1863 .ok()
1864 .flatten()
1865 {
1866 Some(t) => t,
1867 None => return Vec::new(),
1868 };
1869
1870 let mut spans = Vec::with_capacity(table_dto.cells.len());
1871 for &cell_id in &table_dto.cells {
1872 if let Some(cell) =
1873 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &cell_id)
1874 .ok()
1875 .flatten()
1876 {
1877 spans.push((
1878 cell.row as usize,
1879 cell.column as usize,
1880 cell.row_span.max(1) as usize,
1881 cell.column_span.max(1) as usize,
1882 ));
1883 }
1884 }
1885 spans
1886 }
1887
1888 pub fn delete_char(&self) -> Result<()> {
1890 let (pos, anchor) = self.read_cursor();
1891 let (del_pos, del_anchor) = if pos != anchor {
1892 (pos, anchor)
1893 } else {
1894 let end = {
1896 let inner = self.doc.lock();
1897 document_inspection_commands::get_document_stats(&inner.ctx)
1898 .map(|s| max_cursor_position(&s))
1899 .unwrap_or(0)
1900 };
1901 if pos >= end {
1902 return Ok(());
1903 }
1904 let to = self.next_grapheme_boundary(pos);
1908 if to == pos {
1909 return Ok(());
1910 }
1911 (pos, to)
1912 };
1913 self.do_delete(del_pos, del_anchor)
1914 }
1915
1916 pub fn delete_previous_char(&self) -> Result<()> {
1918 let (pos, anchor) = self.read_cursor();
1919 let (del_pos, del_anchor) = if pos != anchor {
1920 (pos, anchor)
1921 } else if pos > 0 {
1922 let from = self.prev_grapheme_boundary(pos);
1923 if from == pos {
1924 return Ok(());
1925 }
1926 (from, pos)
1927 } else {
1928 return Ok(());
1929 };
1930 self.do_delete(del_pos, del_anchor)
1931 }
1932
1933 pub fn remove_selected_text(&self) -> Result<String> {
1935 let (pos, anchor) = self.read_cursor();
1936 if pos == anchor {
1937 return Ok(String::new());
1938 }
1939 let queued = {
1940 let mut inner = self.doc.lock();
1941 let dto = frontend::document_editing::DeleteTextDto {
1942 position: to_i64(pos),
1943 anchor: to_i64(anchor),
1944 };
1945 let result =
1946 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1947 let edit_pos = pos.min(anchor);
1948 let removed = pos.max(anchor) - edit_pos;
1949 let new_pos = to_usize(result.new_position);
1950 inner.adjust_cursors(edit_pos, removed, 0);
1951 {
1952 let mut d = self.data.lock();
1953 d.position = new_pos;
1954 d.anchor = new_pos;
1955 }
1956 inner.modified = true;
1957 inner.invalidate_text_cache();
1958 inner.rehighlight_affected(edit_pos);
1959 inner.queue_event(DocumentEvent::ContentsChanged {
1960 position: edit_pos,
1961 chars_removed: removed,
1962 chars_added: 0,
1963 blocks_affected: 1,
1964 });
1965 inner.check_block_count_changed();
1966 inner.check_flow_changed();
1967 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1969 };
1970 crate::inner::dispatch_queued_events(queued.1);
1971 Ok(queued.0)
1972 }
1973
1974 pub fn current_list(&self) -> Option<crate::TextList> {
1979 let pos = self.position();
1980 let inner = self.doc.lock();
1981 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1982 position: to_i64(pos),
1983 };
1984 let block_info =
1985 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1986 let block = crate::text_block::TextBlock {
1987 doc: self.doc.clone(),
1988 block_id: block_info.block_id as usize,
1989 };
1990 drop(inner);
1991 block.list()
1992 }
1993
1994 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1996 let (pos, anchor) = self.read_cursor();
1997 let queued = {
1998 let mut inner = self.doc.lock();
1999 let dto = frontend::document_editing::CreateListDto {
2000 position: to_i64(pos),
2001 anchor: to_i64(anchor),
2002 style: style.clone(),
2003 };
2004 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2005 inner.modified = true;
2006 inner.rehighlight_affected(pos.min(anchor));
2007 inner.queue_event(DocumentEvent::ContentsChanged {
2008 position: pos.min(anchor),
2009 chars_removed: 0,
2010 chars_added: 0,
2011 blocks_affected: 1,
2012 });
2013 self.queue_undo_redo_event(&mut inner)
2014 };
2015 crate::inner::dispatch_queued_events(queued);
2016 Ok(())
2017 }
2018
2019 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
2021 let (pos, anchor) = self.read_cursor();
2022 let queued = {
2023 let mut inner = self.doc.lock();
2024 let dto = frontend::document_editing::InsertListDto {
2025 position: to_i64(pos),
2026 anchor: to_i64(anchor),
2027 style: style.clone(),
2028 };
2029 let result =
2030 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2031 let edit_pos = pos.min(anchor);
2032 let removed = pos.max(anchor) - edit_pos;
2033 self.finish_edit_ext(
2034 &mut inner,
2035 edit_pos,
2036 removed,
2037 to_usize(result.new_position),
2038 1,
2039 false,
2040 )
2041 };
2042 crate::inner::dispatch_queued_events(queued);
2043 Ok(())
2044 }
2045
2046 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
2048 let queued = {
2049 let mut inner = self.doc.lock();
2050 let dto = format.to_set_dto(list_id);
2051 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2052 inner.modified = true;
2053 inner.queue_event(DocumentEvent::FormatChanged {
2054 position: 0,
2055 length: 0,
2056 kind: crate::flow::FormatChangeKind::List,
2057 });
2058 self.queue_undo_redo_event(&mut inner)
2059 };
2060 crate::inner::dispatch_queued_events(queued);
2061 Ok(())
2062 }
2063
2064 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
2067 let list = self.current_list().ok_or_else(|| {
2068 DocumentError::InvalidCursorContext("cursor is not inside a list".into())
2069 })?;
2070 self.set_list_format(list.id(), format)
2071 }
2072
2073 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
2075 let queued = {
2076 let mut inner = self.doc.lock();
2077 let dto = frontend::document_editing::AddBlockToListDto {
2078 block_id: to_i64(block_id),
2079 list_id: to_i64(list_id),
2080 };
2081 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2082 inner.modified = true;
2083 inner.queue_event(DocumentEvent::FormatChanged {
2090 position: 0,
2091 length: 0,
2092 kind: crate::flow::FormatChangeKind::List,
2093 });
2094 self.queue_undo_redo_event(&mut inner)
2095 };
2096 crate::inner::dispatch_queued_events(queued);
2097 Ok(())
2098 }
2099
2100 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
2102 let pos = self.position();
2103 let inner = self.doc.lock();
2104 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2105 position: to_i64(pos),
2106 };
2107 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2108 drop(inner);
2109 self.add_block_to_list(block_info.block_id as usize, list_id)
2110 }
2111
2112 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
2114 let queued = {
2115 let mut inner = self.doc.lock();
2116 let dto = frontend::document_editing::RemoveBlockFromListDto {
2117 block_id: to_i64(block_id),
2118 };
2119 document_editing_commands::remove_block_from_list(
2120 &inner.ctx,
2121 Some(inner.stack_id),
2122 &dto,
2123 )?;
2124 inner.modified = true;
2125 inner.queue_event(DocumentEvent::FormatChanged {
2128 position: 0,
2129 length: 0,
2130 kind: crate::flow::FormatChangeKind::List,
2131 });
2132 self.queue_undo_redo_event(&mut inner)
2133 };
2134 crate::inner::dispatch_queued_events(queued);
2135 Ok(())
2136 }
2137
2138 pub fn remove_current_block_from_list(&self) -> Result<()> {
2141 let pos = self.position();
2142 let inner = self.doc.lock();
2143 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2144 position: to_i64(pos),
2145 };
2146 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2147 drop(inner);
2148 self.remove_block_from_list(block_info.block_id as usize)
2149 }
2150
2151 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
2154 let list = crate::text_list::TextList {
2155 doc: self.doc.clone(),
2156 list_id,
2157 };
2158 let block = list.item(index).ok_or_else(|| {
2159 DocumentError::OutOfRange(format!("list item index {index} out of range"))
2160 })?;
2161 self.remove_block_from_list(block.id())
2162 }
2163
2164 pub fn char_format(&self) -> Result<TextFormat> {
2169 let pos = self.position();
2170 let inner = self.doc.lock();
2171
2172 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2174 position: to_i64(pos),
2175 };
2176 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2177 let block_id = block_info.block_id as u64;
2178 let mut block_dto =
2179 frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2180 .ok_or_else(|| DocumentError::NotFound("block not found at position".into()))?;
2181 let store = inner.ctx.db_context.get_store();
2182 crate::inner::refresh_block_position(&mut block_dto, store);
2183
2184 let local_char = pos.saturating_sub(block_dto.document_position as usize);
2187 let entity: common::entities::Block = block_dto.clone().into();
2188 let plain_owned = common::database::rope_helpers::block_content_via_store(&entity, store);
2189 let plain: &str = &plain_owned;
2190 let byte_offset: u32 = plain
2191 .char_indices()
2192 .nth(local_char)
2193 .map(|(b, _)| b as u32)
2194 .unwrap_or(plain.len() as u32);
2195
2196 let images = store
2199 .block_images
2200 .read()
2201 .get(&block_id)
2202 .cloned()
2203 .unwrap_or_default();
2204 if let Some(img) = images.iter().find(|i| i.byte_offset == byte_offset) {
2205 return Ok(TextFormat::from(&img.format));
2206 }
2207
2208 let runs = store
2210 .format_runs
2211 .read()
2212 .get(&block_id)
2213 .cloned()
2214 .unwrap_or_default();
2215 let fmt = runs
2216 .iter()
2217 .find(|r| r.byte_start <= byte_offset && byte_offset < r.byte_end)
2218 .map(|r| TextFormat::from(&r.format))
2219 .unwrap_or_default();
2220 Ok(fmt)
2221 }
2222
2223 pub fn block_format(&self) -> Result<BlockFormat> {
2225 let pos = self.position();
2226 let inner = self.doc.lock();
2227 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2228 position: to_i64(pos),
2229 };
2230 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2231 let block_id = block_info.block_id as u64;
2232 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2233 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
2234 Ok(BlockFormat::from(&block))
2235 }
2236
2237 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
2241 let (pos, anchor) = self.read_cursor();
2242 let queued = {
2243 let mut inner = self.doc.lock();
2244 let dto = format.to_set_dto(pos, anchor);
2245 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2246 let start = pos.min(anchor);
2247 let length = pos.max(anchor) - start;
2248 inner.modified = true;
2249 inner.queue_event(DocumentEvent::FormatChanged {
2250 position: start,
2251 length,
2252 kind: crate::flow::FormatChangeKind::Character,
2253 });
2254 self.queue_undo_redo_event(&mut inner)
2255 };
2256 crate::inner::dispatch_queued_events(queued);
2257 Ok(())
2258 }
2259
2260 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
2262 let (pos, anchor) = self.read_cursor();
2263 let queued = {
2264 let mut inner = self.doc.lock();
2265 let dto = format.to_merge_dto(pos, anchor);
2266 document_formatting_commands::merge_text_format(
2267 &inner.ctx,
2268 Some(inner.stack_id),
2269 &dto,
2270 )?;
2271 let start = pos.min(anchor);
2272 let length = pos.max(anchor) - start;
2273 inner.modified = true;
2274 inner.queue_event(DocumentEvent::FormatChanged {
2275 position: start,
2276 length,
2277 kind: crate::flow::FormatChangeKind::Character,
2278 });
2279 self.queue_undo_redo_event(&mut inner)
2280 };
2281 crate::inner::dispatch_queued_events(queued);
2282 Ok(())
2283 }
2284
2285 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
2287 let (pos, anchor) = self.read_cursor();
2288 let queued = {
2289 let mut inner = self.doc.lock();
2290 let dto = format.to_set_dto(pos, anchor);
2291 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2292 let start = pos.min(anchor);
2293 let length = pos.max(anchor) - start;
2294 inner.modified = true;
2295 inner.queue_event(DocumentEvent::FormatChanged {
2296 position: start,
2297 length,
2298 kind: crate::flow::FormatChangeKind::Block,
2299 });
2300 self.queue_undo_redo_event(&mut inner)
2301 };
2302 crate::inner::dispatch_queued_events(queued);
2303 Ok(())
2304 }
2305
2306 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
2308 let (pos, anchor) = self.read_cursor();
2309 let queued = {
2310 let mut inner = self.doc.lock();
2311 let dto = format.to_set_dto(pos, anchor, frame_id);
2312 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2313 let start = pos.min(anchor);
2314 let length = pos.max(anchor) - start;
2315 inner.modified = true;
2316 inner.queue_event(DocumentEvent::FormatChanged {
2317 position: start,
2318 length,
2319 kind: crate::flow::FormatChangeKind::Block,
2320 });
2321 self.queue_undo_redo_event(&mut inner)
2322 };
2323 crate::inner::dispatch_queued_events(queued);
2324 Ok(())
2325 }
2326
2327 pub fn begin_edit_block(&self) {
2331 let inner = self.doc.lock();
2332 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
2333 }
2334
2335 pub fn end_edit_block(&self) {
2337 let inner = self.doc.lock();
2338 undo_redo_commands::end_composite(&inner.ctx);
2339 }
2340
2341 pub fn join_previous_edit_block(&self) {
2348 self.begin_edit_block();
2349 }
2350
2351 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
2355 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
2356 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
2357 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
2358 inner.take_queued_events()
2359 }
2360
2361 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
2362 let queued = {
2363 let mut inner = self.doc.lock();
2364 let dto = frontend::document_editing::DeleteTextDto {
2365 position: to_i64(pos),
2366 anchor: to_i64(anchor),
2367 };
2368 let result =
2369 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2370 let edit_pos = pos.min(anchor);
2371 let removed = pos.max(anchor) - edit_pos;
2372 let new_pos = to_usize(result.new_position);
2373 inner.adjust_cursors(edit_pos, removed, 0);
2374 {
2375 let mut d = self.data.lock();
2376 d.position = new_pos;
2377 d.anchor = new_pos;
2378 }
2379 inner.modified = true;
2380 inner.invalidate_text_cache();
2381 inner.rehighlight_affected(edit_pos);
2382 inner.queue_event(DocumentEvent::ContentsChanged {
2383 position: edit_pos,
2384 chars_removed: removed,
2385 chars_added: 0,
2386 blocks_affected: 1,
2387 });
2388 inner.check_block_count_changed();
2389 inner.check_flow_changed();
2390 self.queue_undo_redo_event(&mut inner)
2391 };
2392 crate::inner::dispatch_queued_events(queued);
2393 Ok(())
2394 }
2395
2396 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
2398 let pos = self.position();
2399 match op {
2400 MoveOperation::NoMove => pos,
2401 MoveOperation::Start => 0,
2402 MoveOperation::End => {
2403 let inner = self.doc.lock();
2404 document_inspection_commands::get_document_stats(&inner.ctx)
2405 .map(|s| max_cursor_position(&s))
2406 .unwrap_or(pos)
2407 }
2408 MoveOperation::NextCharacter | MoveOperation::Right => {
2409 let mut cur = pos;
2410 for _ in 0..n {
2411 let next = self.next_grapheme_boundary(cur);
2412 if next == cur {
2413 break;
2414 }
2415 cur = next;
2416 }
2417 cur
2418 }
2419 MoveOperation::PreviousCharacter | MoveOperation::Left => {
2420 let mut cur = pos;
2421 for _ in 0..n {
2422 let prev = self.prev_grapheme_boundary(cur);
2423 if prev == cur {
2424 break;
2425 }
2426 cur = prev;
2427 }
2428 cur
2429 }
2430 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
2431 let inner = self.doc.lock();
2432 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2433 position: to_i64(pos),
2434 };
2435 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2436 .map(|info| to_usize(info.block_start))
2437 .unwrap_or(pos)
2438 }
2439 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
2440 let inner = self.doc.lock();
2441 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2442 position: to_i64(pos),
2443 };
2444 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2445 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
2446 .unwrap_or(pos)
2447 }
2448 MoveOperation::NextBlock => {
2449 let inner = self.doc.lock();
2450 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2451 position: to_i64(pos),
2452 };
2453 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2454 .map(|info| {
2455 to_usize(info.block_start) + to_usize(info.block_length) + 1
2457 })
2458 .unwrap_or(pos)
2459 }
2460 MoveOperation::PreviousBlock => {
2461 let inner = self.doc.lock();
2462 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2463 position: to_i64(pos),
2464 };
2465 let block_start =
2466 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2467 .map(|info| to_usize(info.block_start))
2468 .unwrap_or(pos);
2469 if block_start >= 2 {
2470 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2472 position: to_i64(block_start - 2),
2473 };
2474 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
2475 .map(|info| to_usize(info.block_start))
2476 .unwrap_or(0)
2477 } else {
2478 0
2479 }
2480 }
2481 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
2482 let (_, end) = self.find_word_boundaries(pos);
2483 if end == pos {
2485 let inner = self.doc.lock();
2487 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
2488 .map(|s| max_cursor_position(&s))
2489 .unwrap_or(0);
2490 let scan_len = max_pos.saturating_sub(pos).min(64);
2491 if scan_len == 0 {
2492 return pos;
2493 }
2494 let dto = frontend::document_inspection::GetTextAtPositionDto {
2495 position: to_i64(pos),
2496 length: to_i64(scan_len),
2497 };
2498 if let Ok(r) =
2499 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
2500 {
2501 for (i, ch) in r.text.chars().enumerate() {
2502 if ch.is_alphanumeric() || ch == '_' {
2503 let word_pos = pos + i;
2505 drop(inner);
2506 let (_, word_end) = self.find_word_boundaries(word_pos);
2507 return word_end;
2508 }
2509 }
2510 }
2511 pos + scan_len
2512 } else {
2513 end
2514 }
2515 }
2516 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
2517 let (start, _) = self.find_word_boundaries(pos);
2518 if start < pos {
2519 start
2520 } else if pos > 0 {
2521 let mut search = pos - 1;
2524 loop {
2525 let (ws, we) = self.find_word_boundaries(search);
2526 if ws < we {
2527 break ws;
2529 }
2530 if search == 0 {
2532 break 0;
2533 }
2534 search -= 1;
2535 }
2536 } else {
2537 0
2538 }
2539 }
2540 MoveOperation::Up | MoveOperation::Down => {
2541 if matches!(op, MoveOperation::Up) {
2544 self.resolve_move(MoveOperation::PreviousBlock, 1)
2545 } else {
2546 self.resolve_move(MoveOperation::NextBlock, 1)
2547 }
2548 }
2549 }
2550 }
2551
2552 pub(crate) fn snap_position_to_grapheme_boundary(&self) {
2563 let pos = {
2564 let data = self.data.lock();
2565 data.position
2566 };
2567 let snapped = self.forward_grapheme_boundary_at_or_after(pos);
2568 if snapped != pos {
2569 let mut data = self.data.lock();
2570 data.position = snapped;
2571 if data.anchor == pos {
2572 data.anchor = snapped;
2573 }
2574 }
2575 }
2576
2577 fn forward_grapheme_boundary_at_or_after(&self, pos: usize) -> usize {
2587 let inner = self.doc.lock();
2588 let end = document_inspection_commands::get_document_stats(&inner.ctx)
2589 .map(|s| max_cursor_position(&s))
2590 .unwrap_or(pos);
2591 if pos >= end {
2592 return pos;
2593 }
2594 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2595 position: to_i64(pos),
2596 };
2597 let Ok(block_info) =
2598 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto)
2599 else {
2600 return pos;
2601 };
2602 let block_start = to_usize(block_info.block_start);
2603 let block_length = to_usize(block_info.block_length);
2604 let offset_in_block = pos.saturating_sub(block_start);
2605 if offset_in_block == 0 || offset_in_block >= block_length {
2607 return pos;
2608 }
2609 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2610 position: to_i64(block_start),
2611 length: to_i64(block_length),
2612 };
2613 let Ok(r) = document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto)
2614 else {
2615 return pos;
2616 };
2617 let text = r.text;
2618 drop(inner);
2619 let mut acc = 0usize;
2622 for g in text.graphemes(true) {
2623 if acc >= offset_in_block {
2624 return block_start + acc;
2625 }
2626 acc += g.chars().count();
2627 }
2628 block_start + acc
2629 }
2630
2631 fn next_grapheme_boundary(&self, pos: usize) -> usize {
2644 let inner = self.doc.lock();
2645 let end = document_inspection_commands::get_document_stats(&inner.ctx)
2646 .map(|s| max_cursor_position(&s))
2647 .unwrap_or(pos);
2648 if pos >= end {
2649 return pos;
2650 }
2651 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2652 position: to_i64(pos),
2653 };
2654 let block_info =
2655 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2656 Ok(info) => info,
2657 Err(_) => return pos + 1,
2658 };
2659 let block_start = to_usize(block_info.block_start);
2660 let block_length = to_usize(block_info.block_length);
2661 let offset_in_block = pos.saturating_sub(block_start);
2662 if offset_in_block >= block_length {
2663 return (pos + 1).min(end);
2666 }
2667 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2668 position: to_i64(pos),
2669 length: to_i64(block_length - offset_in_block),
2670 };
2671 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
2672 Ok(r) => r.text,
2673 Err(_) => return pos + 1,
2674 };
2675 drop(inner);
2676 match text.graphemes(true).next() {
2677 Some(g) if !g.is_empty() => (pos + g.chars().count()).min(end),
2678 _ => (pos + 1).min(end),
2679 }
2680 }
2681
2682 fn prev_grapheme_boundary(&self, pos: usize) -> usize {
2686 if pos == 0 {
2687 return 0;
2688 }
2689 let inner = self.doc.lock();
2690 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2691 position: to_i64(pos.saturating_sub(1)),
2692 };
2693 let block_info =
2694 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2695 Ok(info) => info,
2696 Err(_) => return pos - 1,
2697 };
2698 let block_start = to_usize(block_info.block_start);
2699 let block_length = to_usize(block_info.block_length);
2700 let block_end = block_start + block_length;
2701 if pos > block_end {
2705 return pos - 1;
2706 }
2707 if block_length == 0 || pos <= block_start {
2708 return pos.saturating_sub(1);
2709 }
2710 let scan_len = pos - block_start;
2711 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2712 position: to_i64(block_start),
2713 length: to_i64(scan_len),
2714 };
2715 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
2716 Ok(r) => r.text,
2717 Err(_) => return pos - 1,
2718 };
2719 drop(inner);
2720 match text.graphemes(true).next_back() {
2721 Some(g) if !g.is_empty() => pos - g.chars().count(),
2722 _ => pos - 1,
2723 }
2724 }
2725
2726 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
2732 let inner = self.doc.lock();
2733 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2735 position: to_i64(pos),
2736 };
2737 let block_info =
2738 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2739 Ok(info) => info,
2740 Err(_) => return (pos, pos),
2741 };
2742
2743 let block_start = to_usize(block_info.block_start);
2744 let block_length = to_usize(block_info.block_length);
2745 if block_length == 0 {
2746 return (pos, pos);
2747 }
2748
2749 let dto = frontend::document_inspection::GetTextAtPositionDto {
2750 position: to_i64(block_start),
2751 length: to_i64(block_length),
2752 };
2753 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
2754 Ok(r) => r.text,
2755 Err(_) => return (pos, pos),
2756 };
2757
2758 let cursor_offset = pos.saturating_sub(block_start);
2760
2761 let mut last_char_start = 0;
2763 let mut last_char_end = 0;
2764
2765 for (word_byte_start, word) in text.unicode_word_indices() {
2766 let word_char_start = text[..word_byte_start].chars().count();
2768 let word_char_len = word.chars().count();
2769 let word_char_end = word_char_start + word_char_len;
2770
2771 last_char_start = word_char_start;
2772 last_char_end = word_char_end;
2773
2774 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
2775 return (block_start + word_char_start, block_start + word_char_end);
2776 }
2777 }
2778
2779 if cursor_offset == last_char_end && last_char_start < last_char_end {
2781 return (block_start + last_char_start, block_start + last_char_end);
2782 }
2783
2784 (pos, pos)
2785 }
2786}
2787
2788#[derive(Clone, Copy, PartialEq, Eq)]
2795enum BlockEdge {
2796 First,
2797 Middle,
2798 Last,
2799 OnlyOne,
2800}
2801
2802fn cursor_frame_ref(inner: &TextDocumentInner, block_id: u64) -> Option<FrameRef> {
2806 let parent = crate::text_block::find_parent_frame(inner, block_id)?;
2807 let store = inner.ctx.db_context.get_store();
2808 let frames = store.frames.read();
2809 let frame = frames.get(&parent)?.clone();
2810 frame.parent_frame?;
2811 let is_blockquote = frame.fmt_is_blockquote.unwrap_or(false);
2812
2813 let mut depth = 0;
2814 let mut current = Some(parent);
2815 while let Some(id) = current {
2816 let Some(f) = frames.get(&id) else {
2817 break;
2818 };
2819 if f.parent_frame.is_none() {
2820 break;
2821 }
2822 depth += 1;
2823 current = f.parent_frame;
2824 }
2825
2826 Some(FrameRef {
2827 frame_id: frame.id as usize,
2828 parent_frame_id: frame.parent_frame.map(|id| id as usize),
2829 is_blockquote,
2830 depth,
2831 })
2832}
2833
2834fn innermost_blockquote_frame_id(inner: &TextDocumentInner, block_id: u64) -> Option<usize> {
2838 let mut current = crate::text_block::find_parent_frame(inner, block_id);
2839 let store = inner.ctx.db_context.get_store();
2840 let frames = store.frames.read();
2841 while let Some(id) = current {
2842 let f = frames.get(&id)?;
2843 if f.fmt_is_blockquote == Some(true) {
2844 return Some(f.id as usize);
2845 }
2846 current = f.parent_frame;
2847 }
2848 None
2849}
2850
2851fn blockquote_depth_for_block(inner: &TextDocumentInner, block_id: u64) -> usize {
2854 let mut current = crate::text_block::find_parent_frame(inner, block_id);
2855 let store = inner.ctx.db_context.get_store();
2856 let frames = store.frames.read();
2857 let mut count = 0;
2858 while let Some(id) = current {
2859 let Some(f) = frames.get(&id) else {
2860 break;
2861 };
2862 if f.fmt_is_blockquote == Some(true) {
2863 count += 1;
2864 }
2865 current = f.parent_frame;
2866 }
2867 count
2868}
2869
2870fn block_position_in_current_frame(cursor: &TextCursor) -> Option<BlockEdge> {
2876 let pos = cursor.position();
2877 let inner = cursor.doc.lock();
2878 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2879 position: to_i64(pos),
2880 };
2881 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2882 let block_id = block_info.block_id as common::types::EntityId;
2883 let parent_id = crate::text_block::find_parent_frame(&inner, block_info.block_id as u64)?;
2884 let store = inner.ctx.db_context.get_store();
2885 let frames = store.frames.read();
2886 let frame = frames.get(&parent_id)?;
2887 let block_positions: Vec<usize> = frame
2888 .child_order
2889 .iter()
2890 .enumerate()
2891 .filter_map(|(i, &e)| {
2892 if e > 0 {
2893 Some((i, e as common::types::EntityId))
2894 } else {
2895 None
2896 }
2897 })
2898 .filter(|(_, id)| *id == block_id)
2899 .map(|(i, _)| i)
2900 .collect();
2901 let block_idx = *block_positions.first()?;
2902 let positive_entries: Vec<usize> = frame
2903 .child_order
2904 .iter()
2905 .enumerate()
2906 .filter_map(|(i, &e)| if e > 0 { Some(i) } else { None })
2907 .collect();
2908 let first_pos = *positive_entries.first()?;
2909 let last_pos = *positive_entries.last()?;
2910 let is_first = block_idx == first_pos;
2911 let is_last = block_idx == last_pos;
2912 let edge = match (is_first, is_last, positive_entries.len()) {
2913 (_, _, 1) => BlockEdge::OnlyOne,
2914 (true, _, _) => BlockEdge::First,
2915 (_, true, _) => BlockEdge::Last,
2916 _ => BlockEdge::Middle,
2917 };
2918 Some(edge)
2919}