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_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
587 let (pos, anchor) = self.read_cursor();
588 let queued = {
589 let mut inner = self.doc.lock();
590
591 let (insert_pos, removed) = if pos != anchor {
592 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
593 let del_dto = frontend::document_editing::DeleteTextDto {
594 position: to_i64(pos),
595 anchor: to_i64(anchor),
596 };
597 let del_result = document_editing_commands::delete_text(
598 &inner.ctx,
599 Some(inner.stack_id),
600 &del_dto,
601 )?;
602 (
603 to_usize(del_result.new_position),
604 pos.max(anchor) - pos.min(anchor),
605 )
606 } else {
607 (pos, 0)
608 };
609
610 let dto = frontend::document_editing::InsertFragmentDto {
611 position: to_i64(insert_pos),
612 anchor: to_i64(insert_pos),
613 fragment_data: fragment.raw_data().into(),
614 };
615 let result =
616 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
617
618 if pos != anchor {
619 undo_redo_commands::end_composite(&inner.ctx);
620 }
621
622 let edit_pos = pos.min(anchor);
623 self.finish_edit(
624 &mut inner,
625 edit_pos,
626 removed,
627 to_usize(result.new_position),
628 to_usize(result.blocks_added),
629 )
630 };
631 crate::inner::dispatch_queued_events(queued);
632 Ok(())
633 }
634
635 pub fn selection(&self) -> DocumentFragment {
637 let (pos, anchor) = self.read_cursor();
638
639 let (extract_pos, extract_anchor) = match self.selection_kind() {
642 SelectionKind::Cells(ref range) => match self.cell_range_positions(range) {
643 Some((start, end)) => (start, end),
644 None => return DocumentFragment::new(),
645 },
646 SelectionKind::Mixed {
647 ref cell_range,
648 text_before,
649 text_after,
650 } => {
651 let (cell_start, cell_end) = match self.cell_range_positions(cell_range) {
652 Some(p) => p,
653 None => return DocumentFragment::new(),
654 };
655 let start = if text_before {
656 pos.min(anchor)
657 } else {
658 cell_start
659 };
660 let end = if text_after {
661 pos.max(anchor)
662 } else {
663 cell_end
664 };
665 (start.min(cell_start), end.max(cell_end))
666 }
667 SelectionKind::None => return DocumentFragment::new(),
668 SelectionKind::Text => (pos, anchor),
669 };
670
671 if extract_pos == extract_anchor {
672 return DocumentFragment::new();
673 }
674
675 let inner = self.doc.lock();
676 let dto = frontend::document_inspection::ExtractFragmentDto {
677 position: to_i64(extract_pos),
678 anchor: to_i64(extract_anchor),
679 };
680 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
681 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
682 Err(_) => DocumentFragment::new(),
683 }
684 }
685
686 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
688 let (pos, anchor) = self.read_cursor();
689 let queued = {
690 let mut inner = self.doc.lock();
691
692 let (insert_pos, removed) = if pos != anchor {
693 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
694 let del_dto = frontend::document_editing::DeleteTextDto {
695 position: to_i64(pos),
696 anchor: to_i64(anchor),
697 };
698 let del_result = document_editing_commands::delete_text(
699 &inner.ctx,
700 Some(inner.stack_id),
701 &del_dto,
702 )?;
703 (
704 to_usize(del_result.new_position),
705 pos.max(anchor) - pos.min(anchor),
706 )
707 } else {
708 (pos, 0)
709 };
710
711 let dto = frontend::document_editing::InsertImageDto {
712 position: to_i64(insert_pos),
713 anchor: to_i64(insert_pos),
714 image_name: name.into(),
715 width: width as i64,
716 height: height as i64,
717 };
718 let result =
719 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
720
721 if pos != anchor {
722 undo_redo_commands::end_composite(&inner.ctx);
723 }
724
725 let edit_pos = pos.min(anchor);
726 self.finish_edit_ext(
727 &mut inner,
728 edit_pos,
729 removed,
730 to_usize(result.new_position),
731 1,
732 false,
733 )
734 };
735 crate::inner::dispatch_queued_events(queued);
736 Ok(())
737 }
738
739 pub fn insert_frame(&self) -> Result<()> {
741 let (pos, anchor) = self.read_cursor();
742 let queued = {
743 let mut inner = self.doc.lock();
744 let dto = frontend::document_editing::InsertFrameDto {
745 position: to_i64(pos),
746 anchor: to_i64(anchor),
747 };
748 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
749 inner.modified = true;
752 inner.invalidate_text_cache();
753 inner.rehighlight_affected(pos.min(anchor));
754 inner.queue_event(DocumentEvent::ContentsChanged {
755 position: pos.min(anchor),
756 chars_removed: 0,
757 chars_added: 0,
758 blocks_affected: 1,
759 });
760 inner.check_block_count_changed();
761 inner.check_flow_changed();
762 self.queue_undo_redo_event(&mut inner)
763 };
764 crate::inner::dispatch_queued_events(queued);
765 Ok(())
766 }
767
768 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
774 let (pos, anchor) = self.read_cursor();
775 let (table_id, queued) = {
776 let mut inner = self.doc.lock();
777 let dto = frontend::document_editing::InsertTableDto {
778 position: to_i64(pos),
779 anchor: to_i64(anchor),
780 rows: to_i64(rows),
781 columns: to_i64(columns),
782 };
783 let result =
784 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
785 let new_pos = to_usize(result.new_position);
786 let table_id = to_usize(result.table_id);
787 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
788 {
789 let mut d = self.data.lock();
790 d.position = new_pos;
791 d.anchor = new_pos;
792 }
793 inner.modified = true;
794 inner.invalidate_text_cache();
795 inner.rehighlight_affected(pos.min(anchor));
796 inner.queue_event(DocumentEvent::ContentsChanged {
797 position: pos.min(anchor),
798 chars_removed: 0,
799 chars_added: new_pos - pos.min(anchor),
800 blocks_affected: 1,
801 });
802 inner.check_block_count_changed();
803 inner.check_flow_changed();
804 (table_id, self.queue_undo_redo_event(&mut inner))
805 };
806 crate::inner::dispatch_queued_events(queued);
807 Ok(TextTable {
808 doc: self.doc.clone(),
809 table_id,
810 })
811 }
812
813 pub fn current_table(&self) -> Option<TextTable> {
818 self.current_table_cell().map(|c| c.table)
819 }
820
821 pub fn current_table_cell(&self) -> Option<TableCellRef> {
826 let pos = self.position();
827 let inner = self.doc.lock();
828 let dto = frontend::document_inspection::GetBlockAtPositionDto {
830 position: to_i64(pos),
831 };
832 let block_info =
833 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
834
835 let block_id = if to_i64(pos) < block_info.block_start && pos > 0 {
839 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
840 position: to_i64(pos - 1),
841 };
842 let prev_info =
843 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
844 prev_info.block_id as usize
845 } else {
846 block_info.block_id as usize
847 };
848
849 let block = crate::text_block::TextBlock {
850 doc: self.doc.clone(),
851 block_id,
852 };
853 drop(inner);
855 block.table_cell()
856 }
857
858 pub fn current_frame(&self) -> Option<FrameRef> {
865 let pos = self.position();
866 let inner = self.doc.lock();
867 let dto = frontend::document_inspection::GetBlockAtPositionDto {
868 position: to_i64(pos),
869 };
870 let block_info =
871 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
872 let block_id = block_info.block_id as u64;
873 cursor_frame_ref(&inner, block_id)
874 }
875
876 pub fn is_in_blockquote(&self) -> bool {
879 self.current_blockquote_frame_id().is_some()
880 }
881
882 pub fn current_blockquote_frame_id(&self) -> Option<usize> {
885 let pos = self.position();
886 let inner = self.doc.lock();
887 let dto = frontend::document_inspection::GetBlockAtPositionDto {
888 position: to_i64(pos),
889 };
890 let block_info =
891 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
892 innermost_blockquote_frame_id(&inner, block_info.block_id as u64)
893 }
894
895 pub fn blockquote_depth_at_cursor(&self) -> usize {
898 let pos = self.position();
899 let inner = self.doc.lock();
900 let dto = frontend::document_inspection::GetBlockAtPositionDto {
901 position: to_i64(pos),
902 };
903 let Some(block_info) =
904 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
905 else {
906 return 0;
907 };
908 blockquote_depth_for_block(&inner, block_info.block_id as u64)
909 }
910
911 pub fn is_first_block_in_current_frame(&self) -> bool {
917 matches!(
918 block_position_in_current_frame(self),
919 Some(BlockEdge::First) | Some(BlockEdge::OnlyOne)
920 )
921 }
922
923 pub fn is_last_block_in_current_frame(&self) -> bool {
927 matches!(
928 block_position_in_current_frame(self),
929 Some(BlockEdge::Last) | Some(BlockEdge::OnlyOne)
930 )
931 }
932
933 pub fn current_block_is_empty(&self) -> bool {
936 let pos = self.position();
937 let inner = self.doc.lock();
938 let dto = frontend::document_inspection::GetBlockAtPositionDto {
939 position: to_i64(pos),
940 };
941 let Some(block_info) =
942 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
943 else {
944 return false;
945 };
946 let store = inner.ctx.db_context.get_store();
947 let block_entity = store
948 .blocks
949 .read()
950 .get(&(block_info.block_id as common::types::EntityId))
951 .cloned();
952 match block_entity {
953 Some(b) => {
954 let len = common::database::rope_helpers::block_char_length(&b, store);
955 len == 0
956 }
957 None => false,
958 }
959 }
960
961 pub fn selection_spans_multiple_frames(&self) -> bool {
965 let (pos, anchor) = self.read_cursor();
966 if pos == anchor {
967 return false;
968 }
969 let inner = self.doc.lock();
970 let pos_dto = frontend::document_inspection::GetBlockAtPositionDto {
971 position: to_i64(pos),
972 };
973 let anchor_dto = frontend::document_inspection::GetBlockAtPositionDto {
974 position: to_i64(anchor),
975 };
976 let Some(pos_block) =
977 document_inspection_commands::get_block_at_position(&inner.ctx, &pos_dto).ok()
978 else {
979 return false;
980 };
981 let Some(anchor_block) =
982 document_inspection_commands::get_block_at_position(&inner.ctx, &anchor_dto).ok()
983 else {
984 return false;
985 };
986 let pos_owner = crate::text_block::find_parent_frame(&inner, pos_block.block_id as u64);
987 let anchor_owner =
988 crate::text_block::find_parent_frame(&inner, anchor_block.block_id as u64);
989 pos_owner != anchor_owner
990 }
991
992 pub fn wrap_selection_in_blockquote(&self) -> Result<()> {
999 if self.selection_spans_multiple_frames() {
1000 return Err(DocumentError::InvalidArgument(
1001 "Cannot wrap selection in blockquote: selection spans multiple frames".into(),
1002 ));
1003 }
1004 let (start_block_id, end_block_id) = self.resolve_selection_block_range()?;
1005 let dto = frontend::document_editing::WrapBlocksInFrameDto {
1006 start_block_id: start_block_id as i64,
1007 end_block_id: end_block_id as i64,
1008 position: Some(frontend::document_editing::FramePosition::InFlow),
1009 top_margin: None,
1010 bottom_margin: None,
1011 left_margin: None,
1012 right_margin: None,
1013 padding: None,
1014 border: None,
1015 is_blockquote: Some(true),
1016 };
1017 let queued = {
1018 let mut inner = self.doc.lock();
1019 let _result = document_editing_commands::wrap_blocks_in_frame(
1020 &inner.ctx,
1021 Some(inner.stack_id),
1022 &dto,
1023 )?;
1024 inner.modified = true;
1025 inner.queue_event(DocumentEvent::FormatChanged {
1033 position: 0,
1034 length: 0,
1035 kind: crate::flow::FormatChangeKind::Block,
1036 });
1037 self.queue_undo_redo_event(&mut inner)
1038 };
1039 crate::inner::dispatch_queued_events(queued);
1040 Ok(())
1041 }
1042
1043 pub fn insert_blockquote(&self) -> Result<()> {
1047 self.wrap_selection_in_blockquote()
1048 }
1049
1050 pub fn toggle_blockquote(&self) -> Result<()> {
1055 if let Some(frame_id) = self.current_blockquote_frame_id() {
1056 self.unwrap_frame_by_id(frame_id)
1057 } else {
1058 self.wrap_selection_in_blockquote()
1059 }
1060 }
1061
1062 pub fn unwrap_current_frame(&self) -> Result<()> {
1066 let frame_ref = self.current_frame().ok_or_else(|| {
1067 DocumentError::InvalidCursorContext("Cursor is not inside any sub-frame".into())
1068 })?;
1069 self.unwrap_frame_by_id(frame_ref.frame_id)
1070 }
1071
1072 pub fn unwrap_current_block_from_blockquote(&self) -> Result<()> {
1076 if self.current_blockquote_frame_id().is_none() {
1077 return Err(DocumentError::InvalidCursorContext(
1078 "Cursor is not inside a blockquote".into(),
1079 ));
1080 }
1081 let block_id = self.current_block_id_for_mutation()?;
1082 let dto = frontend::document_editing::UnwrapBlockFromFrameDto {
1083 block_id: block_id as i64,
1084 };
1085 let queued = {
1086 let mut inner = self.doc.lock();
1087 let _result = document_editing_commands::unwrap_block_from_frame(
1088 &inner.ctx,
1089 Some(inner.stack_id),
1090 &dto,
1091 )?;
1092 inner.modified = true;
1093 inner.queue_event(DocumentEvent::FormatChanged {
1097 position: 0,
1098 length: 0,
1099 kind: crate::flow::FormatChangeKind::Block,
1100 });
1101 self.queue_undo_redo_event(&mut inner)
1102 };
1103 crate::inner::dispatch_queued_events(queued);
1104 Ok(())
1105 }
1106
1107 pub fn increase_blockquote_depth(&self) -> Result<()> {
1111 self.wrap_selection_in_blockquote()
1112 }
1113
1114 pub fn decrease_blockquote_depth(&self) -> Result<()> {
1120 if self.current_blockquote_frame_id().is_none() {
1121 return Err(DocumentError::InvalidCursorContext(
1122 "Cursor is not inside a blockquote to decrease depth".into(),
1123 ));
1124 }
1125 self.unwrap_current_block_from_blockquote()
1126 }
1127
1128 fn unwrap_frame_by_id(&self, frame_id: usize) -> Result<()> {
1129 let dto = frontend::document_editing::UnwrapFrameDto {
1130 frame_id: frame_id as i64,
1131 };
1132 let queued = {
1133 let mut inner = self.doc.lock();
1134 let _result =
1135 document_editing_commands::unwrap_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
1136 inner.modified = true;
1137 inner.queue_event(DocumentEvent::FormatChanged {
1141 position: 0,
1142 length: 0,
1143 kind: crate::flow::FormatChangeKind::Block,
1144 });
1145 self.queue_undo_redo_event(&mut inner)
1146 };
1147 crate::inner::dispatch_queued_events(queued);
1148 Ok(())
1149 }
1150
1151 fn current_block_id_for_mutation(&self) -> Result<usize> {
1152 let pos = self.position();
1153 let inner = self.doc.lock();
1154 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1155 position: to_i64(pos),
1156 };
1157 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1158 .map_err(|e| anyhow::anyhow!("get_block_at_position: {}", e))?;
1159 Ok(block_info.block_id as usize)
1160 }
1161
1162 fn resolve_selection_block_range(&self) -> Result<(usize, usize)> {
1163 let (pos, anchor) = self.read_cursor();
1164 let lo = pos.min(anchor);
1165 let hi = pos.max(anchor);
1166 let inner = self.doc.lock();
1167 let lo_dto = frontend::document_inspection::GetBlockAtPositionDto {
1168 position: to_i64(lo),
1169 };
1170 let hi_dto = frontend::document_inspection::GetBlockAtPositionDto {
1171 position: to_i64(hi),
1172 };
1173 let lo_block = document_inspection_commands::get_block_at_position(&inner.ctx, &lo_dto)
1174 .map_err(|e| anyhow::anyhow!("get_block_at_position(start): {}", e))?;
1175 let hi_block = document_inspection_commands::get_block_at_position(&inner.ctx, &hi_dto)
1176 .map_err(|e| anyhow::anyhow!("get_block_at_position(end): {}", e))?;
1177 Ok((lo_block.block_id as usize, hi_block.block_id as usize))
1178 }
1179
1180 pub fn remove_table(&self, table_id: usize) -> Result<()> {
1184 let queued = {
1185 let mut inner = self.doc.lock();
1186 let dto = frontend::document_editing::RemoveTableDto {
1187 table_id: to_i64(table_id),
1188 };
1189 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1190 inner.modified = true;
1191 inner.invalidate_text_cache();
1192 inner.rehighlight_all();
1193 inner.check_block_count_changed();
1194 inner.check_flow_changed();
1195 self.queue_undo_redo_event(&mut inner)
1196 };
1197 crate::inner::dispatch_queued_events(queued);
1198 Ok(())
1199 }
1200
1201 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1203 let queued = {
1204 let mut inner = self.doc.lock();
1205 let dto = frontend::document_editing::InsertTableRowDto {
1206 table_id: to_i64(table_id),
1207 row_index: to_i64(row_index),
1208 };
1209 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1210 inner.modified = true;
1211 inner.invalidate_text_cache();
1212 inner.rehighlight_all();
1213 inner.check_block_count_changed();
1214 self.queue_undo_redo_event(&mut inner)
1215 };
1216 crate::inner::dispatch_queued_events(queued);
1217 Ok(())
1218 }
1219
1220 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1222 let queued = {
1223 let mut inner = self.doc.lock();
1224 let dto = frontend::document_editing::InsertTableColumnDto {
1225 table_id: to_i64(table_id),
1226 column_index: to_i64(column_index),
1227 };
1228 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1229 inner.modified = true;
1230 inner.invalidate_text_cache();
1231 inner.rehighlight_all();
1232 inner.check_block_count_changed();
1233 self.queue_undo_redo_event(&mut inner)
1234 };
1235 crate::inner::dispatch_queued_events(queued);
1236 Ok(())
1237 }
1238
1239 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1241 let queued = {
1242 let mut inner = self.doc.lock();
1243 let dto = frontend::document_editing::RemoveTableRowDto {
1244 table_id: to_i64(table_id),
1245 row_index: to_i64(row_index),
1246 };
1247 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1248 inner.modified = true;
1249 inner.invalidate_text_cache();
1250 inner.rehighlight_all();
1251 inner.check_block_count_changed();
1252 self.queue_undo_redo_event(&mut inner)
1253 };
1254 crate::inner::dispatch_queued_events(queued);
1255 Ok(())
1256 }
1257
1258 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1260 let queued = {
1261 let mut inner = self.doc.lock();
1262 let dto = frontend::document_editing::RemoveTableColumnDto {
1263 table_id: to_i64(table_id),
1264 column_index: to_i64(column_index),
1265 };
1266 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1267 inner.modified = true;
1268 inner.invalidate_text_cache();
1269 inner.rehighlight_all();
1270 inner.check_block_count_changed();
1271 self.queue_undo_redo_event(&mut inner)
1272 };
1273 crate::inner::dispatch_queued_events(queued);
1274 Ok(())
1275 }
1276
1277 pub fn merge_table_cells(
1279 &self,
1280 table_id: usize,
1281 start_row: usize,
1282 start_column: usize,
1283 end_row: usize,
1284 end_column: usize,
1285 ) -> Result<()> {
1286 let queued = {
1287 let mut inner = self.doc.lock();
1288 let dto = frontend::document_editing::MergeTableCellsDto {
1289 table_id: to_i64(table_id),
1290 start_row: to_i64(start_row),
1291 start_column: to_i64(start_column),
1292 end_row: to_i64(end_row),
1293 end_column: to_i64(end_column),
1294 };
1295 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
1296 inner.modified = true;
1297 inner.invalidate_text_cache();
1298 inner.rehighlight_all();
1299 inner.check_block_count_changed();
1300 self.queue_undo_redo_event(&mut inner)
1301 };
1302 crate::inner::dispatch_queued_events(queued);
1303 Ok(())
1304 }
1305
1306 pub fn split_table_cell(
1308 &self,
1309 cell_id: usize,
1310 split_rows: usize,
1311 split_columns: usize,
1312 ) -> Result<()> {
1313 let queued = {
1314 let mut inner = self.doc.lock();
1315 let dto = frontend::document_editing::SplitTableCellDto {
1316 cell_id: to_i64(cell_id),
1317 split_rows: to_i64(split_rows),
1318 split_columns: to_i64(split_columns),
1319 };
1320 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
1321 inner.modified = true;
1322 inner.invalidate_text_cache();
1323 inner.rehighlight_all();
1324 inner.check_block_count_changed();
1325 self.queue_undo_redo_event(&mut inner)
1326 };
1327 crate::inner::dispatch_queued_events(queued);
1328 Ok(())
1329 }
1330
1331 pub fn set_table_format(
1335 &self,
1336 table_id: usize,
1337 format: &crate::flow::TableFormat,
1338 ) -> Result<()> {
1339 let queued = {
1340 let mut inner = self.doc.lock();
1341 let dto = format.to_set_dto(table_id);
1342 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1343 inner.modified = true;
1344 inner.queue_event(DocumentEvent::FormatChanged {
1345 position: 0,
1346 length: 0,
1347 kind: crate::flow::FormatChangeKind::Block,
1348 });
1349 self.queue_undo_redo_event(&mut inner)
1350 };
1351 crate::inner::dispatch_queued_events(queued);
1352 Ok(())
1353 }
1354
1355 pub fn set_table_cell_format(
1357 &self,
1358 cell_id: usize,
1359 format: &crate::flow::CellFormat,
1360 ) -> Result<()> {
1361 let queued = {
1362 let mut inner = self.doc.lock();
1363 let dto = format.to_set_dto(cell_id);
1364 document_formatting_commands::set_table_cell_format(
1365 &inner.ctx,
1366 Some(inner.stack_id),
1367 &dto,
1368 )?;
1369 inner.modified = true;
1370 inner.queue_event(DocumentEvent::FormatChanged {
1371 position: 0,
1372 length: 0,
1373 kind: crate::flow::FormatChangeKind::Block,
1374 });
1375 self.queue_undo_redo_event(&mut inner)
1376 };
1377 crate::inner::dispatch_queued_events(queued);
1378 Ok(())
1379 }
1380
1381 pub fn remove_current_table(&self) -> Result<()> {
1386 let table = self.current_table().ok_or_else(|| {
1387 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1388 })?;
1389 self.remove_table(table.id())
1390 }
1391
1392 pub fn insert_row_above(&self) -> Result<()> {
1395 let cell_ref = self.current_table_cell().ok_or_else(|| {
1396 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1397 })?;
1398 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
1399 }
1400
1401 pub fn insert_row_below(&self) -> Result<()> {
1404 let cell_ref = self.current_table_cell().ok_or_else(|| {
1405 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1406 })?;
1407 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
1408 }
1409
1410 pub fn insert_column_before(&self) -> Result<()> {
1413 let cell_ref = self.current_table_cell().ok_or_else(|| {
1414 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1415 })?;
1416 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
1417 }
1418
1419 pub fn insert_column_after(&self) -> Result<()> {
1422 let cell_ref = self.current_table_cell().ok_or_else(|| {
1423 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1424 })?;
1425 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
1426 }
1427
1428 pub fn remove_current_row(&self) -> Result<()> {
1431 let cell_ref = self.current_table_cell().ok_or_else(|| {
1432 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1433 })?;
1434 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
1435 }
1436
1437 pub fn remove_current_column(&self) -> Result<()> {
1440 let cell_ref = self.current_table_cell().ok_or_else(|| {
1441 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1442 })?;
1443 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
1444 }
1445
1446 pub fn merge_selected_cells(&self) -> Result<()> {
1453 let pos_cell = self.current_table_cell().ok_or_else(|| {
1454 DocumentError::InvalidCursorContext("cursor position is not inside a table".into())
1455 })?;
1456
1457 let (_pos, anchor) = self.read_cursor();
1459 let anchor_cell = {
1460 let inner = self.doc.lock();
1462 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1463 position: to_i64(anchor),
1464 };
1465 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1466 .map_err(|_| {
1467 DocumentError::InvalidCursorContext(
1468 "cursor anchor is not inside a table".into(),
1469 )
1470 })?;
1471 let block = crate::text_block::TextBlock {
1472 doc: self.doc.clone(),
1473 block_id: block_info.block_id as usize,
1474 };
1475 drop(inner);
1476 block.table_cell().ok_or_else(|| {
1477 DocumentError::InvalidCursorContext("cursor anchor is not inside a table".into())
1478 })?
1479 };
1480
1481 if pos_cell.table.id() != anchor_cell.table.id() {
1482 return Err(DocumentError::InvalidArgument(
1483 "position and anchor are in different tables".into(),
1484 ));
1485 }
1486
1487 let start_row = pos_cell.row.min(anchor_cell.row);
1488 let start_col = pos_cell.column.min(anchor_cell.column);
1489 let end_row = pos_cell.row.max(anchor_cell.row);
1490 let end_col = pos_cell.column.max(anchor_cell.column);
1491
1492 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
1493 }
1494
1495 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1498 let cell_ref = self.current_table_cell().ok_or_else(|| {
1499 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1500 })?;
1501 let cell = cell_ref
1503 .table
1504 .cell(cell_ref.row, cell_ref.column)
1505 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1506 self.split_table_cell(cell.id(), split_rows, split_columns)
1508 }
1509
1510 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1513 let table = self.current_table().ok_or_else(|| {
1514 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1515 })?;
1516 self.set_table_format(table.id(), format)
1517 }
1518
1519 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1522 let cell_ref = self.current_table_cell().ok_or_else(|| {
1523 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1524 })?;
1525 let cell = cell_ref
1526 .table
1527 .cell(cell_ref.row, cell_ref.column)
1528 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1529 self.set_table_cell_format(cell.id(), format)
1530 }
1531
1532 pub fn selection_kind(&self) -> crate::flow::SelectionKind {
1540 use crate::flow::{CellRange, SelectionKind};
1541
1542 {
1544 let d = self.data.lock();
1545 if let Some(ref range) = d.cell_selection_override {
1546 return SelectionKind::Cells(range.clone());
1547 }
1548 if d.position == d.anchor {
1549 return SelectionKind::None;
1550 }
1551 }
1552
1553 let (pos, anchor) = self.read_cursor();
1554
1555 let pos_cell = self.table_cell_at(pos);
1557 let anchor_cell = self.table_cell_at(anchor);
1558
1559 match (&pos_cell, &anchor_cell) {
1560 (None, None) => {
1561 let (start, end) = (pos.min(anchor), pos.max(anchor));
1565 if let Some(t) = self.find_table_between(start, end) {
1566 let table_id = t.id();
1567 let rows = t.rows();
1568 let cols = t.columns();
1569 let range = CellRange {
1570 table_id,
1571 start_row: 0,
1572 start_col: 0,
1573 end_row: if rows > 0 { rows - 1 } else { 0 },
1574 end_col: if cols > 0 { cols - 1 } else { 0 },
1575 };
1576 let spans = self.collect_cell_spans(table_id);
1577 SelectionKind::Mixed {
1578 cell_range: range.expand_for_spans(&spans),
1579 text_before: true,
1580 text_after: true,
1581 }
1582 } else {
1583 SelectionKind::Text
1584 }
1585 }
1586 (Some(pc), Some(ac)) => {
1587 if pc.table.id() != ac.table.id() {
1588 return SelectionKind::Text;
1590 }
1591 if pc.row == ac.row && pc.column == ac.column {
1592 return SelectionKind::Text;
1594 }
1595 let range = CellRange {
1597 table_id: pc.table.id(),
1598 start_row: pc.row.min(ac.row),
1599 start_col: pc.column.min(ac.column),
1600 end_row: pc.row.max(ac.row),
1601 end_col: pc.column.max(ac.column),
1602 };
1603 let spans = self.collect_cell_spans(pc.table.id());
1604 SelectionKind::Cells(range.expand_for_spans(&spans))
1605 }
1606 (Some(tc), None) | (None, Some(tc)) => {
1607 let table_id = tc.table.id();
1611 let rows = tc.table.rows();
1612 let cols = tc.table.columns();
1613
1614 let inside_pos = if pos_cell.is_some() { pos } else { anchor };
1615 let outside_pos = if pos_cell.is_some() { anchor } else { pos };
1616
1617 let text_before = outside_pos < inside_pos;
1618 let text_after = !text_before;
1619
1620 let range = CellRange {
1621 table_id,
1622 start_row: 0,
1623 start_col: 0,
1624 end_row: if rows > 0 { rows - 1 } else { 0 },
1625 end_col: if cols > 0 { cols - 1 } else { 0 },
1626 };
1627 let spans = self.collect_cell_spans(table_id);
1628 SelectionKind::Mixed {
1629 cell_range: range.expand_for_spans(&spans),
1630 text_before,
1631 text_after,
1632 }
1633 }
1634 }
1635 }
1636
1637 pub fn is_cell_selection(&self) -> bool {
1639 matches!(
1640 self.selection_kind(),
1641 crate::flow::SelectionKind::Cells(_) | crate::flow::SelectionKind::Mixed { .. }
1642 )
1643 }
1644
1645 pub fn selected_cell_range(&self) -> Option<crate::flow::CellRange> {
1647 match self.selection_kind() {
1648 crate::flow::SelectionKind::Cells(r) => Some(r),
1649 crate::flow::SelectionKind::Mixed { cell_range, .. } => Some(cell_range),
1650 _ => None,
1651 }
1652 }
1653
1654 pub fn selected_cells(&self) -> Vec<TableCellRef> {
1656 let range = match self.selected_cell_range() {
1657 Some(r) => r,
1658 None => return Vec::new(),
1659 };
1660 let table = TextTable {
1661 doc: self.doc.clone(),
1662 table_id: range.table_id,
1663 };
1664 let mut cells = Vec::new();
1665 for row in range.start_row..=range.end_row {
1666 for col in range.start_col..=range.end_col {
1667 if table.cell(row, col).is_some() {
1668 cells.push(TableCellRef {
1669 table: table.clone(),
1670 row,
1671 column: col,
1672 });
1673 }
1674 }
1675 }
1676 cells
1677 }
1678
1679 pub fn select_table_cell(&self, table_id: usize, row: usize, col: usize) {
1683 let mut d = self.data.lock();
1684 d.cell_selection_override = Some(crate::flow::CellRange {
1685 table_id,
1686 start_row: row,
1687 start_col: col,
1688 end_row: row,
1689 end_col: col,
1690 });
1691 }
1692
1693 pub fn select_cell_range(
1695 &self,
1696 table_id: usize,
1697 start_row: usize,
1698 start_col: usize,
1699 end_row: usize,
1700 end_col: usize,
1701 ) {
1702 let range = crate::flow::CellRange {
1703 table_id,
1704 start_row,
1705 start_col,
1706 end_row,
1707 end_col,
1708 };
1709 let spans = self.collect_cell_spans(table_id);
1710 let mut d = self.data.lock();
1711 d.cell_selection_override = Some(range.expand_for_spans(&spans));
1712 }
1713
1714 pub fn clear_cell_selection(&self) {
1716 let mut d = self.data.lock();
1717 d.cell_selection_override = None;
1718 }
1719
1720 fn cell_range_positions(&self, range: &CellRange) -> Option<(usize, usize)> {
1723 let inner = self.doc.lock();
1724 let main_frame_id = get_main_frame_id(&inner);
1725 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1726 drop(inner);
1727
1728 let table = flow.into_iter().find_map(|e| match e {
1730 FlowElement::Table(t) if t.id() == range.table_id => Some(t),
1731 _ => None,
1732 })?;
1733
1734 let mut min_pos = usize::MAX;
1735 let mut max_pos = 0usize;
1736
1737 for row in range.start_row..=range.end_row {
1738 for col in range.start_col..=range.end_col {
1739 if let Some(cell) = table.cell(row, col) {
1740 for block in cell.blocks() {
1741 let bp = block.position();
1742 let bl = block.length();
1743 min_pos = min_pos.min(bp);
1744 max_pos = max_pos.max(bp + bl);
1745 }
1746 }
1747 }
1748 }
1749
1750 if min_pos == usize::MAX {
1751 return None;
1752 }
1753
1754 Some((min_pos, max_pos + 1))
1756 }
1757
1758 fn table_cell_at(&self, position: usize) -> Option<TableCellRef> {
1762 let inner = self.doc.lock();
1763 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1764 position: to_i64(position),
1765 };
1766 let block_info =
1767 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1768
1769 let block_id = if to_i64(position) < block_info.block_start && position > 0 {
1770 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1771 position: to_i64(position - 1),
1772 };
1773 let prev_info =
1774 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
1775 prev_info.block_id as usize
1776 } else {
1777 block_info.block_id as usize
1778 };
1779
1780 let block = crate::text_block::TextBlock {
1781 doc: self.doc.clone(),
1782 block_id,
1783 };
1784 drop(inner);
1785 block.table_cell()
1786 }
1787
1788 fn table_boundary_position(&self, table_id: usize, before: bool) -> Option<usize> {
1799 let inner = self.doc.lock();
1800 let main_frame_id = get_main_frame_id(&inner);
1801 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1802 drop(inner);
1803
1804 let idx = flow
1806 .iter()
1807 .position(|e| matches!(e, FlowElement::Table(t) if t.id() == table_id))?;
1808
1809 if before {
1810 for i in (0..idx).rev() {
1812 if let FlowElement::Block(b) = &flow[i] {
1813 return Some(b.position() + b.length());
1814 }
1815 }
1816 } else {
1817 for item in flow.iter().skip(idx + 1) {
1819 if let FlowElement::Block(b) = item {
1820 return Some(b.position());
1821 }
1822 }
1823 }
1824 None
1825 }
1826
1827 fn find_table_between(&self, start: usize, end: usize) -> Option<TextTable> {
1829 let inner = self.doc.lock();
1830 let main_frame_id = get_main_frame_id(&inner);
1831 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1832 drop(inner);
1833
1834 for elem in flow {
1835 if let FlowElement::Table(t) = elem {
1836 if let Some(first_cell) = t.cell(0, 0) {
1839 let blocks = first_cell.blocks();
1840 if let Some(fb) = blocks.first() {
1841 let p = fb.position();
1842 if p > start && p < end {
1843 return Some(t);
1844 }
1845 }
1846 }
1847 }
1848 }
1849 None
1850 }
1851
1852 fn collect_cell_spans(&self, table_id: usize) -> Vec<(usize, usize, usize, usize)> {
1854 let inner = self.doc.lock();
1855 let table_dto =
1856 match frontend::commands::table_commands::get_table(&inner.ctx, &(table_id as u64))
1857 .ok()
1858 .flatten()
1859 {
1860 Some(t) => t,
1861 None => return Vec::new(),
1862 };
1863
1864 let mut spans = Vec::with_capacity(table_dto.cells.len());
1865 for &cell_id in &table_dto.cells {
1866 if let Some(cell) =
1867 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &cell_id)
1868 .ok()
1869 .flatten()
1870 {
1871 spans.push((
1872 cell.row as usize,
1873 cell.column as usize,
1874 cell.row_span.max(1) as usize,
1875 cell.column_span.max(1) as usize,
1876 ));
1877 }
1878 }
1879 spans
1880 }
1881
1882 pub fn delete_char(&self) -> Result<()> {
1884 let (pos, anchor) = self.read_cursor();
1885 let (del_pos, del_anchor) = if pos != anchor {
1886 (pos, anchor)
1887 } else {
1888 let end = {
1890 let inner = self.doc.lock();
1891 document_inspection_commands::get_document_stats(&inner.ctx)
1892 .map(|s| max_cursor_position(&s))
1893 .unwrap_or(0)
1894 };
1895 if pos >= end {
1896 return Ok(());
1897 }
1898 let to = self.next_grapheme_boundary(pos);
1902 if to == pos {
1903 return Ok(());
1904 }
1905 (pos, to)
1906 };
1907 self.do_delete(del_pos, del_anchor)
1908 }
1909
1910 pub fn delete_previous_char(&self) -> Result<()> {
1912 let (pos, anchor) = self.read_cursor();
1913 let (del_pos, del_anchor) = if pos != anchor {
1914 (pos, anchor)
1915 } else if pos > 0 {
1916 let from = self.prev_grapheme_boundary(pos);
1917 if from == pos {
1918 return Ok(());
1919 }
1920 (from, pos)
1921 } else {
1922 return Ok(());
1923 };
1924 self.do_delete(del_pos, del_anchor)
1925 }
1926
1927 pub fn remove_selected_text(&self) -> Result<String> {
1929 let (pos, anchor) = self.read_cursor();
1930 if pos == anchor {
1931 return Ok(String::new());
1932 }
1933 let queued = {
1934 let mut inner = self.doc.lock();
1935 let dto = frontend::document_editing::DeleteTextDto {
1936 position: to_i64(pos),
1937 anchor: to_i64(anchor),
1938 };
1939 let result =
1940 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1941 let edit_pos = pos.min(anchor);
1942 let removed = pos.max(anchor) - edit_pos;
1943 let new_pos = to_usize(result.new_position);
1944 inner.adjust_cursors(edit_pos, removed, 0);
1945 {
1946 let mut d = self.data.lock();
1947 d.position = new_pos;
1948 d.anchor = new_pos;
1949 }
1950 inner.modified = true;
1951 inner.invalidate_text_cache();
1952 inner.rehighlight_affected(edit_pos);
1953 inner.queue_event(DocumentEvent::ContentsChanged {
1954 position: edit_pos,
1955 chars_removed: removed,
1956 chars_added: 0,
1957 blocks_affected: 1,
1958 });
1959 inner.check_block_count_changed();
1960 inner.check_flow_changed();
1961 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1963 };
1964 crate::inner::dispatch_queued_events(queued.1);
1965 Ok(queued.0)
1966 }
1967
1968 pub fn current_list(&self) -> Option<crate::TextList> {
1973 let pos = self.position();
1974 let inner = self.doc.lock();
1975 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1976 position: to_i64(pos),
1977 };
1978 let block_info =
1979 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1980 let block = crate::text_block::TextBlock {
1981 doc: self.doc.clone(),
1982 block_id: block_info.block_id as usize,
1983 };
1984 drop(inner);
1985 block.list()
1986 }
1987
1988 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1990 let (pos, anchor) = self.read_cursor();
1991 let queued = {
1992 let mut inner = self.doc.lock();
1993 let dto = frontend::document_editing::CreateListDto {
1994 position: to_i64(pos),
1995 anchor: to_i64(anchor),
1996 style: style.clone(),
1997 };
1998 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1999 inner.modified = true;
2000 inner.rehighlight_affected(pos.min(anchor));
2001 inner.queue_event(DocumentEvent::ContentsChanged {
2002 position: pos.min(anchor),
2003 chars_removed: 0,
2004 chars_added: 0,
2005 blocks_affected: 1,
2006 });
2007 self.queue_undo_redo_event(&mut inner)
2008 };
2009 crate::inner::dispatch_queued_events(queued);
2010 Ok(())
2011 }
2012
2013 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
2015 let (pos, anchor) = self.read_cursor();
2016 let queued = {
2017 let mut inner = self.doc.lock();
2018 let dto = frontend::document_editing::InsertListDto {
2019 position: to_i64(pos),
2020 anchor: to_i64(anchor),
2021 style: style.clone(),
2022 };
2023 let result =
2024 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2025 let edit_pos = pos.min(anchor);
2026 let removed = pos.max(anchor) - edit_pos;
2027 self.finish_edit_ext(
2028 &mut inner,
2029 edit_pos,
2030 removed,
2031 to_usize(result.new_position),
2032 1,
2033 false,
2034 )
2035 };
2036 crate::inner::dispatch_queued_events(queued);
2037 Ok(())
2038 }
2039
2040 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
2042 let queued = {
2043 let mut inner = self.doc.lock();
2044 let dto = format.to_set_dto(list_id);
2045 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2046 inner.modified = true;
2047 inner.queue_event(DocumentEvent::FormatChanged {
2048 position: 0,
2049 length: 0,
2050 kind: crate::flow::FormatChangeKind::List,
2051 });
2052 self.queue_undo_redo_event(&mut inner)
2053 };
2054 crate::inner::dispatch_queued_events(queued);
2055 Ok(())
2056 }
2057
2058 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
2061 let list = self.current_list().ok_or_else(|| {
2062 DocumentError::InvalidCursorContext("cursor is not inside a list".into())
2063 })?;
2064 self.set_list_format(list.id(), format)
2065 }
2066
2067 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
2069 let queued = {
2070 let mut inner = self.doc.lock();
2071 let dto = frontend::document_editing::AddBlockToListDto {
2072 block_id: to_i64(block_id),
2073 list_id: to_i64(list_id),
2074 };
2075 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2076 inner.modified = true;
2077 inner.queue_event(DocumentEvent::FormatChanged {
2084 position: 0,
2085 length: 0,
2086 kind: crate::flow::FormatChangeKind::List,
2087 });
2088 self.queue_undo_redo_event(&mut inner)
2089 };
2090 crate::inner::dispatch_queued_events(queued);
2091 Ok(())
2092 }
2093
2094 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
2096 let pos = self.position();
2097 let inner = self.doc.lock();
2098 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2099 position: to_i64(pos),
2100 };
2101 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2102 drop(inner);
2103 self.add_block_to_list(block_info.block_id as usize, list_id)
2104 }
2105
2106 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
2108 let queued = {
2109 let mut inner = self.doc.lock();
2110 let dto = frontend::document_editing::RemoveBlockFromListDto {
2111 block_id: to_i64(block_id),
2112 };
2113 document_editing_commands::remove_block_from_list(
2114 &inner.ctx,
2115 Some(inner.stack_id),
2116 &dto,
2117 )?;
2118 inner.modified = true;
2119 inner.queue_event(DocumentEvent::FormatChanged {
2122 position: 0,
2123 length: 0,
2124 kind: crate::flow::FormatChangeKind::List,
2125 });
2126 self.queue_undo_redo_event(&mut inner)
2127 };
2128 crate::inner::dispatch_queued_events(queued);
2129 Ok(())
2130 }
2131
2132 pub fn remove_current_block_from_list(&self) -> Result<()> {
2135 let pos = self.position();
2136 let inner = self.doc.lock();
2137 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2138 position: to_i64(pos),
2139 };
2140 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2141 drop(inner);
2142 self.remove_block_from_list(block_info.block_id as usize)
2143 }
2144
2145 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
2148 let list = crate::text_list::TextList {
2149 doc: self.doc.clone(),
2150 list_id,
2151 };
2152 let block = list.item(index).ok_or_else(|| {
2153 DocumentError::OutOfRange(format!("list item index {index} out of range"))
2154 })?;
2155 self.remove_block_from_list(block.id())
2156 }
2157
2158 pub fn char_format(&self) -> Result<TextFormat> {
2163 let pos = self.position();
2164 let inner = self.doc.lock();
2165
2166 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2168 position: to_i64(pos),
2169 };
2170 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2171 let block_id = block_info.block_id as u64;
2172 let mut block_dto =
2173 frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2174 .ok_or_else(|| DocumentError::NotFound("block not found at position".into()))?;
2175 let store = inner.ctx.db_context.get_store();
2176 crate::inner::refresh_block_position(&mut block_dto, store);
2177
2178 let local_char = pos.saturating_sub(block_dto.document_position as usize);
2181 let entity: common::entities::Block = block_dto.clone().into();
2182 let plain_owned = common::database::rope_helpers::block_content_via_store(&entity, store);
2183 let plain: &str = &plain_owned;
2184 let byte_offset: u32 = plain
2185 .char_indices()
2186 .nth(local_char)
2187 .map(|(b, _)| b as u32)
2188 .unwrap_or(plain.len() as u32);
2189
2190 let images = store
2193 .block_images
2194 .read()
2195 .get(&block_id)
2196 .cloned()
2197 .unwrap_or_default();
2198 if let Some(img) = images.iter().find(|i| i.byte_offset == byte_offset) {
2199 return Ok(TextFormat::from(&img.format));
2200 }
2201
2202 let runs = store
2204 .format_runs
2205 .read()
2206 .get(&block_id)
2207 .cloned()
2208 .unwrap_or_default();
2209 let fmt = runs
2210 .iter()
2211 .find(|r| r.byte_start <= byte_offset && byte_offset < r.byte_end)
2212 .map(|r| TextFormat::from(&r.format))
2213 .unwrap_or_default();
2214 Ok(fmt)
2215 }
2216
2217 pub fn block_format(&self) -> Result<BlockFormat> {
2219 let pos = self.position();
2220 let inner = self.doc.lock();
2221 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2222 position: to_i64(pos),
2223 };
2224 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2225 let block_id = block_info.block_id as u64;
2226 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2227 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
2228 Ok(BlockFormat::from(&block))
2229 }
2230
2231 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
2235 let (pos, anchor) = self.read_cursor();
2236 let queued = {
2237 let mut inner = self.doc.lock();
2238 let dto = format.to_set_dto(pos, anchor);
2239 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2240 let start = pos.min(anchor);
2241 let length = pos.max(anchor) - start;
2242 inner.modified = true;
2243 inner.queue_event(DocumentEvent::FormatChanged {
2244 position: start,
2245 length,
2246 kind: crate::flow::FormatChangeKind::Character,
2247 });
2248 self.queue_undo_redo_event(&mut inner)
2249 };
2250 crate::inner::dispatch_queued_events(queued);
2251 Ok(())
2252 }
2253
2254 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
2256 let (pos, anchor) = self.read_cursor();
2257 let queued = {
2258 let mut inner = self.doc.lock();
2259 let dto = format.to_merge_dto(pos, anchor);
2260 document_formatting_commands::merge_text_format(
2261 &inner.ctx,
2262 Some(inner.stack_id),
2263 &dto,
2264 )?;
2265 let start = pos.min(anchor);
2266 let length = pos.max(anchor) - start;
2267 inner.modified = true;
2268 inner.queue_event(DocumentEvent::FormatChanged {
2269 position: start,
2270 length,
2271 kind: crate::flow::FormatChangeKind::Character,
2272 });
2273 self.queue_undo_redo_event(&mut inner)
2274 };
2275 crate::inner::dispatch_queued_events(queued);
2276 Ok(())
2277 }
2278
2279 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
2281 let (pos, anchor) = self.read_cursor();
2282 let queued = {
2283 let mut inner = self.doc.lock();
2284 let dto = format.to_set_dto(pos, anchor);
2285 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2286 let start = pos.min(anchor);
2287 let length = pos.max(anchor) - start;
2288 inner.modified = true;
2289 inner.queue_event(DocumentEvent::FormatChanged {
2290 position: start,
2291 length,
2292 kind: crate::flow::FormatChangeKind::Block,
2293 });
2294 self.queue_undo_redo_event(&mut inner)
2295 };
2296 crate::inner::dispatch_queued_events(queued);
2297 Ok(())
2298 }
2299
2300 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
2302 let (pos, anchor) = self.read_cursor();
2303 let queued = {
2304 let mut inner = self.doc.lock();
2305 let dto = format.to_set_dto(pos, anchor, frame_id);
2306 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2307 let start = pos.min(anchor);
2308 let length = pos.max(anchor) - start;
2309 inner.modified = true;
2310 inner.queue_event(DocumentEvent::FormatChanged {
2311 position: start,
2312 length,
2313 kind: crate::flow::FormatChangeKind::Block,
2314 });
2315 self.queue_undo_redo_event(&mut inner)
2316 };
2317 crate::inner::dispatch_queued_events(queued);
2318 Ok(())
2319 }
2320
2321 pub fn begin_edit_block(&self) {
2325 let inner = self.doc.lock();
2326 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
2327 }
2328
2329 pub fn end_edit_block(&self) {
2331 let inner = self.doc.lock();
2332 undo_redo_commands::end_composite(&inner.ctx);
2333 }
2334
2335 pub fn join_previous_edit_block(&self) {
2342 self.begin_edit_block();
2343 }
2344
2345 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
2349 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
2350 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
2351 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
2352 inner.take_queued_events()
2353 }
2354
2355 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
2356 let queued = {
2357 let mut inner = self.doc.lock();
2358 let dto = frontend::document_editing::DeleteTextDto {
2359 position: to_i64(pos),
2360 anchor: to_i64(anchor),
2361 };
2362 let result =
2363 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2364 let edit_pos = pos.min(anchor);
2365 let removed = pos.max(anchor) - edit_pos;
2366 let new_pos = to_usize(result.new_position);
2367 inner.adjust_cursors(edit_pos, removed, 0);
2368 {
2369 let mut d = self.data.lock();
2370 d.position = new_pos;
2371 d.anchor = new_pos;
2372 }
2373 inner.modified = true;
2374 inner.invalidate_text_cache();
2375 inner.rehighlight_affected(edit_pos);
2376 inner.queue_event(DocumentEvent::ContentsChanged {
2377 position: edit_pos,
2378 chars_removed: removed,
2379 chars_added: 0,
2380 blocks_affected: 1,
2381 });
2382 inner.check_block_count_changed();
2383 inner.check_flow_changed();
2384 self.queue_undo_redo_event(&mut inner)
2385 };
2386 crate::inner::dispatch_queued_events(queued);
2387 Ok(())
2388 }
2389
2390 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
2392 let pos = self.position();
2393 match op {
2394 MoveOperation::NoMove => pos,
2395 MoveOperation::Start => 0,
2396 MoveOperation::End => {
2397 let inner = self.doc.lock();
2398 document_inspection_commands::get_document_stats(&inner.ctx)
2399 .map(|s| max_cursor_position(&s))
2400 .unwrap_or(pos)
2401 }
2402 MoveOperation::NextCharacter | MoveOperation::Right => {
2403 let mut cur = pos;
2404 for _ in 0..n {
2405 let next = self.next_grapheme_boundary(cur);
2406 if next == cur {
2407 break;
2408 }
2409 cur = next;
2410 }
2411 cur
2412 }
2413 MoveOperation::PreviousCharacter | MoveOperation::Left => {
2414 let mut cur = pos;
2415 for _ in 0..n {
2416 let prev = self.prev_grapheme_boundary(cur);
2417 if prev == cur {
2418 break;
2419 }
2420 cur = prev;
2421 }
2422 cur
2423 }
2424 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
2425 let inner = self.doc.lock();
2426 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2427 position: to_i64(pos),
2428 };
2429 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2430 .map(|info| to_usize(info.block_start))
2431 .unwrap_or(pos)
2432 }
2433 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
2434 let inner = self.doc.lock();
2435 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2436 position: to_i64(pos),
2437 };
2438 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2439 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
2440 .unwrap_or(pos)
2441 }
2442 MoveOperation::NextBlock => {
2443 let inner = self.doc.lock();
2444 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2445 position: to_i64(pos),
2446 };
2447 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2448 .map(|info| {
2449 to_usize(info.block_start) + to_usize(info.block_length) + 1
2451 })
2452 .unwrap_or(pos)
2453 }
2454 MoveOperation::PreviousBlock => {
2455 let inner = self.doc.lock();
2456 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2457 position: to_i64(pos),
2458 };
2459 let block_start =
2460 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2461 .map(|info| to_usize(info.block_start))
2462 .unwrap_or(pos);
2463 if block_start >= 2 {
2464 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2466 position: to_i64(block_start - 2),
2467 };
2468 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
2469 .map(|info| to_usize(info.block_start))
2470 .unwrap_or(0)
2471 } else {
2472 0
2473 }
2474 }
2475 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
2476 let (_, end) = self.find_word_boundaries(pos);
2477 if end == pos {
2479 let inner = self.doc.lock();
2481 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
2482 .map(|s| max_cursor_position(&s))
2483 .unwrap_or(0);
2484 let scan_len = max_pos.saturating_sub(pos).min(64);
2485 if scan_len == 0 {
2486 return pos;
2487 }
2488 let dto = frontend::document_inspection::GetTextAtPositionDto {
2489 position: to_i64(pos),
2490 length: to_i64(scan_len),
2491 };
2492 if let Ok(r) =
2493 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
2494 {
2495 for (i, ch) in r.text.chars().enumerate() {
2496 if ch.is_alphanumeric() || ch == '_' {
2497 let word_pos = pos + i;
2499 drop(inner);
2500 let (_, word_end) = self.find_word_boundaries(word_pos);
2501 return word_end;
2502 }
2503 }
2504 }
2505 pos + scan_len
2506 } else {
2507 end
2508 }
2509 }
2510 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
2511 let (start, _) = self.find_word_boundaries(pos);
2512 if start < pos {
2513 start
2514 } else if pos > 0 {
2515 let mut search = pos - 1;
2518 loop {
2519 let (ws, we) = self.find_word_boundaries(search);
2520 if ws < we {
2521 break ws;
2523 }
2524 if search == 0 {
2526 break 0;
2527 }
2528 search -= 1;
2529 }
2530 } else {
2531 0
2532 }
2533 }
2534 MoveOperation::Up | MoveOperation::Down => {
2535 if matches!(op, MoveOperation::Up) {
2538 self.resolve_move(MoveOperation::PreviousBlock, 1)
2539 } else {
2540 self.resolve_move(MoveOperation::NextBlock, 1)
2541 }
2542 }
2543 }
2544 }
2545
2546 pub(crate) fn snap_position_to_grapheme_boundary(&self) {
2557 let pos = {
2558 let data = self.data.lock();
2559 data.position
2560 };
2561 let snapped = self.forward_grapheme_boundary_at_or_after(pos);
2562 if snapped != pos {
2563 let mut data = self.data.lock();
2564 data.position = snapped;
2565 if data.anchor == pos {
2566 data.anchor = snapped;
2567 }
2568 }
2569 }
2570
2571 fn forward_grapheme_boundary_at_or_after(&self, pos: usize) -> usize {
2581 let inner = self.doc.lock();
2582 let end = document_inspection_commands::get_document_stats(&inner.ctx)
2583 .map(|s| max_cursor_position(&s))
2584 .unwrap_or(pos);
2585 if pos >= end {
2586 return pos;
2587 }
2588 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2589 position: to_i64(pos),
2590 };
2591 let Ok(block_info) =
2592 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto)
2593 else {
2594 return pos;
2595 };
2596 let block_start = to_usize(block_info.block_start);
2597 let block_length = to_usize(block_info.block_length);
2598 let offset_in_block = pos.saturating_sub(block_start);
2599 if offset_in_block == 0 || offset_in_block >= block_length {
2601 return pos;
2602 }
2603 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2604 position: to_i64(block_start),
2605 length: to_i64(block_length),
2606 };
2607 let Ok(r) = document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto)
2608 else {
2609 return pos;
2610 };
2611 let text = r.text;
2612 drop(inner);
2613 let mut acc = 0usize;
2616 for g in text.graphemes(true) {
2617 if acc >= offset_in_block {
2618 return block_start + acc;
2619 }
2620 acc += g.chars().count();
2621 }
2622 block_start + acc
2623 }
2624
2625 fn next_grapheme_boundary(&self, pos: usize) -> usize {
2638 let inner = self.doc.lock();
2639 let end = document_inspection_commands::get_document_stats(&inner.ctx)
2640 .map(|s| max_cursor_position(&s))
2641 .unwrap_or(pos);
2642 if pos >= end {
2643 return pos;
2644 }
2645 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2646 position: to_i64(pos),
2647 };
2648 let block_info =
2649 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2650 Ok(info) => info,
2651 Err(_) => return pos + 1,
2652 };
2653 let block_start = to_usize(block_info.block_start);
2654 let block_length = to_usize(block_info.block_length);
2655 let offset_in_block = pos.saturating_sub(block_start);
2656 if offset_in_block >= block_length {
2657 return (pos + 1).min(end);
2660 }
2661 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2662 position: to_i64(pos),
2663 length: to_i64(block_length - offset_in_block),
2664 };
2665 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
2666 Ok(r) => r.text,
2667 Err(_) => return pos + 1,
2668 };
2669 drop(inner);
2670 match text.graphemes(true).next() {
2671 Some(g) if !g.is_empty() => (pos + g.chars().count()).min(end),
2672 _ => (pos + 1).min(end),
2673 }
2674 }
2675
2676 fn prev_grapheme_boundary(&self, pos: usize) -> usize {
2680 if pos == 0 {
2681 return 0;
2682 }
2683 let inner = self.doc.lock();
2684 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2685 position: to_i64(pos.saturating_sub(1)),
2686 };
2687 let block_info =
2688 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2689 Ok(info) => info,
2690 Err(_) => return pos - 1,
2691 };
2692 let block_start = to_usize(block_info.block_start);
2693 let block_length = to_usize(block_info.block_length);
2694 let block_end = block_start + block_length;
2695 if pos > block_end {
2699 return pos - 1;
2700 }
2701 if block_length == 0 || pos <= block_start {
2702 return pos.saturating_sub(1);
2703 }
2704 let scan_len = pos - block_start;
2705 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2706 position: to_i64(block_start),
2707 length: to_i64(scan_len),
2708 };
2709 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
2710 Ok(r) => r.text,
2711 Err(_) => return pos - 1,
2712 };
2713 drop(inner);
2714 match text.graphemes(true).next_back() {
2715 Some(g) if !g.is_empty() => pos - g.chars().count(),
2716 _ => pos - 1,
2717 }
2718 }
2719
2720 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
2726 let inner = self.doc.lock();
2727 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2729 position: to_i64(pos),
2730 };
2731 let block_info =
2732 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2733 Ok(info) => info,
2734 Err(_) => return (pos, pos),
2735 };
2736
2737 let block_start = to_usize(block_info.block_start);
2738 let block_length = to_usize(block_info.block_length);
2739 if block_length == 0 {
2740 return (pos, pos);
2741 }
2742
2743 let dto = frontend::document_inspection::GetTextAtPositionDto {
2744 position: to_i64(block_start),
2745 length: to_i64(block_length),
2746 };
2747 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
2748 Ok(r) => r.text,
2749 Err(_) => return (pos, pos),
2750 };
2751
2752 let cursor_offset = pos.saturating_sub(block_start);
2754
2755 let mut last_char_start = 0;
2757 let mut last_char_end = 0;
2758
2759 for (word_byte_start, word) in text.unicode_word_indices() {
2760 let word_char_start = text[..word_byte_start].chars().count();
2762 let word_char_len = word.chars().count();
2763 let word_char_end = word_char_start + word_char_len;
2764
2765 last_char_start = word_char_start;
2766 last_char_end = word_char_end;
2767
2768 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
2769 return (block_start + word_char_start, block_start + word_char_end);
2770 }
2771 }
2772
2773 if cursor_offset == last_char_end && last_char_start < last_char_end {
2775 return (block_start + last_char_start, block_start + last_char_end);
2776 }
2777
2778 (pos, pos)
2779 }
2780}
2781
2782#[derive(Clone, Copy, PartialEq, Eq)]
2789enum BlockEdge {
2790 First,
2791 Middle,
2792 Last,
2793 OnlyOne,
2794}
2795
2796fn cursor_frame_ref(inner: &TextDocumentInner, block_id: u64) -> Option<FrameRef> {
2800 let parent = crate::text_block::find_parent_frame(inner, block_id)?;
2801 let store = inner.ctx.db_context.get_store();
2802 let frames = store.frames.read();
2803 let frame = frames.get(&parent)?.clone();
2804 frame.parent_frame?;
2805 let is_blockquote = frame.fmt_is_blockquote.unwrap_or(false);
2806
2807 let mut depth = 0;
2808 let mut current = Some(parent);
2809 while let Some(id) = current {
2810 let Some(f) = frames.get(&id) else {
2811 break;
2812 };
2813 if f.parent_frame.is_none() {
2814 break;
2815 }
2816 depth += 1;
2817 current = f.parent_frame;
2818 }
2819
2820 Some(FrameRef {
2821 frame_id: frame.id as usize,
2822 parent_frame_id: frame.parent_frame.map(|id| id as usize),
2823 is_blockquote,
2824 depth,
2825 })
2826}
2827
2828fn innermost_blockquote_frame_id(inner: &TextDocumentInner, block_id: u64) -> Option<usize> {
2832 let mut current = crate::text_block::find_parent_frame(inner, block_id);
2833 let store = inner.ctx.db_context.get_store();
2834 let frames = store.frames.read();
2835 while let Some(id) = current {
2836 let f = frames.get(&id)?;
2837 if f.fmt_is_blockquote == Some(true) {
2838 return Some(f.id as usize);
2839 }
2840 current = f.parent_frame;
2841 }
2842 None
2843}
2844
2845fn blockquote_depth_for_block(inner: &TextDocumentInner, block_id: u64) -> usize {
2848 let mut current = crate::text_block::find_parent_frame(inner, block_id);
2849 let store = inner.ctx.db_context.get_store();
2850 let frames = store.frames.read();
2851 let mut count = 0;
2852 while let Some(id) = current {
2853 let Some(f) = frames.get(&id) else {
2854 break;
2855 };
2856 if f.fmt_is_blockquote == Some(true) {
2857 count += 1;
2858 }
2859 current = f.parent_frame;
2860 }
2861 count
2862}
2863
2864fn block_position_in_current_frame(cursor: &TextCursor) -> Option<BlockEdge> {
2870 let pos = cursor.position();
2871 let inner = cursor.doc.lock();
2872 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2873 position: to_i64(pos),
2874 };
2875 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2876 let block_id = block_info.block_id as common::types::EntityId;
2877 let parent_id = crate::text_block::find_parent_frame(&inner, block_info.block_id as u64)?;
2878 let store = inner.ctx.db_context.get_store();
2879 let frames = store.frames.read();
2880 let frame = frames.get(&parent_id)?;
2881 let block_positions: Vec<usize> = frame
2882 .child_order
2883 .iter()
2884 .enumerate()
2885 .filter_map(|(i, &e)| {
2886 if e > 0 {
2887 Some((i, e as common::types::EntityId))
2888 } else {
2889 None
2890 }
2891 })
2892 .filter(|(_, id)| *id == block_id)
2893 .map(|(i, _)| i)
2894 .collect();
2895 let block_idx = *block_positions.first()?;
2896 let positive_entries: Vec<usize> = frame
2897 .child_order
2898 .iter()
2899 .enumerate()
2900 .filter_map(|(i, &e)| if e > 0 { Some(i) } else { None })
2901 .collect();
2902 let first_pos = *positive_entries.first()?;
2903 let last_pos = *positive_entries.last()?;
2904 let is_first = block_idx == first_pos;
2905 let is_last = block_idx == last_pos;
2906 let edge = match (is_first, is_last, positive_entries.len()) {
2907 (_, _, 1) => BlockEdge::OnlyOne,
2908 (true, _, _) => BlockEdge::First,
2909 (_, true, _) => BlockEdge::Last,
2910 _ => BlockEdge::Middle,
2911 };
2912 Some(edge)
2913}