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, content_locale) = {
57 let d = self.data.lock();
58 (d.position, d.anchor, d.content_locale.clone())
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 content_locale,
68 }));
69 inner.cursors.push(Arc::downgrade(&data));
70 data
71 };
72 TextCursor {
73 doc: self.doc.clone(),
74 data,
75 }
76 }
77}
78
79impl TextCursor {
80 fn read_cursor(&self) -> (usize, usize) {
83 let d = self.data.lock();
84 (d.position, d.anchor)
85 }
86
87 fn finish_edit(
91 &self,
92 inner: &mut TextDocumentInner,
93 edit_pos: usize,
94 removed: usize,
95 new_pos: usize,
96 blocks_affected: usize,
97 ) -> QueuedEvents {
98 self.finish_edit_ext(inner, edit_pos, removed, new_pos, blocks_affected, true)
99 }
100
101 fn finish_edit_ext(
102 &self,
103 inner: &mut TextDocumentInner,
104 edit_pos: usize,
105 removed: usize,
106 new_pos: usize,
107 blocks_affected: usize,
108 flow_may_change: bool,
109 ) -> QueuedEvents {
110 let added = new_pos.saturating_sub(edit_pos);
117 inner.adjust_cursors(edit_pos, removed, added);
118 {
119 let mut d = self.data.lock();
120 d.position = new_pos;
121 d.anchor = new_pos;
122 }
123 inner.modified = true;
124 inner.invalidate_text_cache();
125 inner.rehighlight_affected(edit_pos);
126 inner.queue_event(DocumentEvent::ContentsChanged {
127 position: edit_pos,
128 chars_removed: removed,
129 chars_added: added,
130 blocks_affected,
131 });
132 inner.check_block_count_changed();
133 if flow_may_change {
134 inner.check_flow_changed();
135 }
136 self.queue_undo_redo_event(inner)
137 }
138
139 pub fn position(&self) -> usize {
143 self.data.lock().position
144 }
145
146 pub fn anchor(&self) -> usize {
148 self.data.lock().anchor
149 }
150
151 pub fn has_selection(&self) -> bool {
153 let d = self.data.lock();
154 d.position != d.anchor
155 }
156
157 pub fn selection_start(&self) -> usize {
159 let d = self.data.lock();
160 d.position.min(d.anchor)
161 }
162
163 pub fn selection_end(&self) -> usize {
165 let d = self.data.lock();
166 d.position.max(d.anchor)
167 }
168
169 pub fn selected_text(&self) -> Result<String> {
171 let (pos, anchor) = self.read_cursor();
172 if pos == anchor {
173 return Ok(String::new());
174 }
175 let start = pos.min(anchor);
176 let len = pos.max(anchor) - start;
177 let inner = self.doc.lock();
178 let dto = frontend::document_inspection::GetTextAtPositionDto {
179 position: to_i64(start),
180 length: to_i64(len),
181 };
182 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
183 Ok(result.text)
184 }
185
186 pub fn text_before(&self, max_len: usize) -> Result<String> {
199 if max_len == 0 {
200 return Ok(String::new());
201 }
202 let pos = self.position();
203 let inner = self.doc.lock();
204 let store = inner.ctx.db_context.get_store();
205
206 if pos > 0
211 && common::database::rope_helpers::find_block_at_char_position(store, 0).is_none()
212 {
213 let dto = frontend::document_inspection::GetTextAtPositionDto {
214 position: 0,
215 length: to_i64(pos),
216 };
217 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
218 let full = result.text;
219 let total = full.chars().count();
220 let skip = total.saturating_sub(max_len);
221 return Ok(full.chars().skip(skip).collect());
222 }
223
224 let mut pieces: Vec<String> = Vec::new();
225 let mut remaining = max_len;
226 let mut end_pos = pos;
227
228 while remaining > 0 && end_pos > 0 {
229 let query = (end_pos - 1) as i64;
230 let Some((block_id, char_in_block, block_char_start)) =
236 common::database::rope_helpers::find_block_at_char_position(store, query)
237 else {
238 break;
242 };
243 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
244 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
245 let entity: common::entities::Block = block_dto.into();
246 let block_text =
247 common::database::rope_helpers::block_content_via_store(&entity, store);
248 let block_len = block_text.chars().count() as i64;
249 let block_char_start = block_char_start as usize;
250
251 if char_in_block == block_len {
252 pieces.push("\n".to_string());
254 remaining -= 1;
255 if remaining == 0 {
256 break;
257 }
258 let take = remaining.min(block_len as usize);
259 let local_start = block_len as usize - take;
260 let slice: String = block_text.chars().skip(local_start).take(take).collect();
261 pieces.push(slice);
262 remaining -= take;
263 end_pos = block_char_start + local_start;
264 } else {
265 let available = char_in_block as usize + 1;
267 let take = remaining.min(available);
268 let local_start = available - take;
269 let slice: String = block_text.chars().skip(local_start).take(take).collect();
270 pieces.push(slice);
271 remaining -= take;
272 end_pos = block_char_start + local_start;
273 }
274 }
275
276 pieces.reverse();
277 Ok(pieces.concat())
278 }
279
280 pub fn clear_selection(&self) {
282 let mut d = self.data.lock();
283 d.anchor = d.position;
284 }
285
286 pub fn at_block_start(&self) -> bool {
290 let pos = self.position();
291 let inner = self.doc.lock();
292 let dto = frontend::document_inspection::GetBlockAtPositionDto {
293 position: to_i64(pos),
294 };
295 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
296 pos == to_usize(info.block_start)
297 } else {
298 false
299 }
300 }
301
302 pub fn at_block_end(&self) -> bool {
304 let pos = self.position();
305 let inner = self.doc.lock();
306 let dto = frontend::document_inspection::GetBlockAtPositionDto {
307 position: to_i64(pos),
308 };
309 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
310 pos == to_usize(info.block_start) + to_usize(info.block_length)
311 } else {
312 false
313 }
314 }
315
316 pub fn at_start(&self) -> bool {
318 self.data.lock().position == 0
319 }
320
321 pub fn at_end(&self) -> bool {
323 let pos = self.position();
324 let inner = self.doc.lock();
325 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
326 frontend::document_inspection::DocumentStatsDto {
327 character_count: 0,
328 word_count: 0,
329 block_count: 0,
330 frame_count: 0,
331 image_count: 0,
332 list_count: 0,
333 table_count: 0,
334 }
335 });
336 pos >= max_cursor_position(&stats)
337 }
338
339 pub fn block_number(&self) -> usize {
341 let pos = self.position();
342 let inner = self.doc.lock();
343 let dto = frontend::document_inspection::GetBlockAtPositionDto {
344 position: to_i64(pos),
345 };
346 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
347 .map(|info| to_usize(info.block_number))
348 .unwrap_or(0)
349 }
350
351 pub fn position_in_block(&self) -> usize {
353 let pos = self.position();
354 let inner = self.doc.lock();
355 let dto = frontend::document_inspection::GetBlockAtPositionDto {
356 position: to_i64(pos),
357 };
358 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
359 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
360 .unwrap_or(0)
361 }
362
363 pub fn set_position(&self, position: usize, mode: MoveMode) {
377 let end = {
379 let inner = self.doc.lock();
380 document_inspection_commands::get_document_stats(&inner.ctx)
381 .map(|s| max_cursor_position(&s))
382 .unwrap_or(0)
383 };
384 let mut pos = position.min(end);
385
386 if mode == MoveMode::KeepAnchor {
390 let anchor = self.data.lock().anchor;
391 let pos_cell = self.table_cell_at(pos);
392 let anchor_cell = self.table_cell_at(anchor);
393 match (&pos_cell, &anchor_cell) {
394 (Some(tc), None) => {
395 let before = anchor < pos;
397 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
398 pos = boundary;
399 }
400 }
401 (None, Some(tc)) => {
402 let before = pos < anchor;
405 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
406 pos = boundary;
407 }
408 }
409 _ => {}
410 }
411 }
412
413 {
414 let mut d = self.data.lock();
415 d.position = pos;
416 if mode == MoveMode::MoveAnchor {
417 d.anchor = pos;
418 }
419 d.cell_selection_override = None;
420 }
421 self.snap_position_to_grapheme_boundary();
426 }
427
428 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
434 let old_pos = self.position();
435 let target = self.resolve_move(operation, n);
436 self.set_position(target, mode);
437 self.position() != old_pos
438 }
439
440 pub fn select(&self, selection: SelectionType) {
442 match selection {
443 SelectionType::Document => {
444 let end = {
445 let inner = self.doc.lock();
446 document_inspection_commands::get_document_stats(&inner.ctx)
447 .map(|s| max_cursor_position(&s))
448 .unwrap_or(0)
449 };
450 let mut d = self.data.lock();
451 d.anchor = 0;
452 d.position = end;
453 d.cell_selection_override = None;
454 }
455 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
456 let pos = self.position();
457 let inner = self.doc.lock();
458 let dto = frontend::document_inspection::GetBlockAtPositionDto {
459 position: to_i64(pos),
460 };
461 if let Ok(info) =
462 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
463 {
464 let start = to_usize(info.block_start);
465 let end = start + to_usize(info.block_length);
466 drop(inner);
467 let mut d = self.data.lock();
468 d.anchor = start;
469 d.position = end;
470 d.cell_selection_override = None;
471 }
472 }
473 SelectionType::WordUnderCursor => {
474 let pos = self.position();
475 let (word_start, word_end) = self.find_word_boundaries(pos);
476 let mut d = self.data.lock();
477 d.anchor = word_start;
478 d.position = word_end;
479 d.cell_selection_override = None;
480 }
481 SelectionType::SentenceUnderCursor => {
482 let pos = self.position();
483 if let Some((start, end)) = self.find_sentence_boundaries(pos) {
486 let mut d = self.data.lock();
487 d.anchor = start;
488 d.position = end;
489 d.cell_selection_override = None;
490 }
491 }
492 }
493 }
494
495 pub fn set_content_locale(&self, locale: Option<&str>) {
504 self.data.lock().content_locale = locale.map(str::to_string);
505 }
506
507 pub fn content_locale(&self) -> Option<String> {
509 self.data.lock().content_locale.clone()
510 }
511
512 pub fn insert_text(&self, text: &str) -> Result<()> {
516 let (pos, anchor) = self.read_cursor();
517
518 let dto = frontend::document_editing::InsertTextDto {
520 format_policy: Default::default(),
521 position: to_i64(pos),
522 anchor: to_i64(anchor),
523 text: text.into(),
524 };
525
526 let queued = {
527 let mut inner = self.doc.lock();
528 let result = match document_editing_commands::insert_text(
529 &inner.ctx,
530 Some(inner.stack_id),
531 &dto,
532 ) {
533 Ok(r) => r,
534 Err(_) if pos != anchor => {
535 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
537
538 let del_dto = frontend::document_editing::DeleteTextDto {
539 position: to_i64(pos),
540 anchor: to_i64(anchor),
541 };
542 let del_result = document_editing_commands::delete_text(
543 &inner.ctx,
544 Some(inner.stack_id),
545 &del_dto,
546 )?;
547 let del_pos = to_usize(del_result.new_position);
548
549 let ins_dto = frontend::document_editing::InsertTextDto {
550 format_policy: Default::default(),
551 position: to_i64(del_pos),
552 anchor: to_i64(del_pos),
553 text: text.into(),
554 };
555 let ins_result = document_editing_commands::insert_text(
556 &inner.ctx,
557 Some(inner.stack_id),
558 &ins_dto,
559 )?;
560
561 undo_redo_commands::end_composite(&inner.ctx);
562 ins_result
563 }
564 Err(e) => return Err(e.into()),
565 };
566
567 let edit_pos = pos.min(anchor);
568 let removed = pos.max(anchor) - edit_pos;
569 self.finish_edit_ext(
570 &mut inner,
571 edit_pos,
572 removed,
573 to_usize(result.new_position),
574 to_usize(result.blocks_affected),
575 false,
576 )
577 };
578 crate::inner::dispatch_queued_events(queued);
579 Ok(())
580 }
581
582 pub fn replace(
596 &self,
597 start: usize,
598 end: usize,
599 text: &str,
600 policy: crate::ReplaceFormatPolicy,
601 ) -> Result<()> {
602 let (pos, anchor) = (start, end);
603
604 let dto = frontend::document_editing::InsertTextDto {
605 format_policy: policy,
606 position: to_i64(pos),
607 anchor: to_i64(anchor),
608 text: text.into(),
609 };
610
611 let queued = {
612 let mut inner = self.doc.lock();
613 let result = match document_editing_commands::insert_text(
614 &inner.ctx,
615 Some(inner.stack_id),
616 &dto,
617 ) {
618 Ok(r) => r,
619 Err(_) if pos != anchor => {
620 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
624
625 let del_dto = frontend::document_editing::DeleteTextDto {
626 position: to_i64(pos),
627 anchor: to_i64(anchor),
628 };
629 let del_result = document_editing_commands::delete_text(
630 &inner.ctx,
631 Some(inner.stack_id),
632 &del_dto,
633 )?;
634 let del_pos = to_usize(del_result.new_position);
635
636 let ins_dto = frontend::document_editing::InsertTextDto {
637 format_policy: Default::default(),
638 position: to_i64(del_pos),
639 anchor: to_i64(del_pos),
640 text: text.into(),
641 };
642 let ins_result = document_editing_commands::insert_text(
643 &inner.ctx,
644 Some(inner.stack_id),
645 &ins_dto,
646 )?;
647
648 undo_redo_commands::end_composite(&inner.ctx);
649 ins_result
650 }
651 Err(e) => return Err(e.into()),
652 };
653
654 let edit_pos = pos.min(anchor);
655 let removed = pos.max(anchor) - edit_pos;
656 self.finish_edit_ext(
657 &mut inner,
658 edit_pos,
659 removed,
660 to_usize(result.new_position),
661 to_usize(result.blocks_affected),
662 false,
663 )
664 };
665 crate::inner::dispatch_queued_events(queued);
666 Ok(())
667 }
668
669 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
671 let (pos, anchor) = self.read_cursor();
672
673 let make_dto = |p: usize, a: usize| frontend::document_editing::InsertFormattedTextDto {
674 position: to_i64(p),
675 anchor: to_i64(a),
676 text: text.into(),
677 font_family: format.font_family.clone().unwrap_or_default(),
678 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
679 font_bold: format.font_bold.unwrap_or(false),
680 font_italic: format.font_italic.unwrap_or(false),
681 font_underline: format.font_underline.unwrap_or(false),
682 font_strikeout: format.font_strikeout.unwrap_or(false),
683 };
684
685 let queued = {
686 let mut inner = self.doc.lock();
687 let result = match document_editing_commands::insert_formatted_text(
688 &inner.ctx,
689 Some(inner.stack_id),
690 &make_dto(pos, anchor),
691 ) {
692 Ok(r) => r,
693 Err(_) if pos != anchor => {
694 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
696
697 let del_dto = frontend::document_editing::DeleteTextDto {
698 position: to_i64(pos),
699 anchor: to_i64(anchor),
700 };
701 let del_result = document_editing_commands::delete_text(
702 &inner.ctx,
703 Some(inner.stack_id),
704 &del_dto,
705 )?;
706 let del_pos = to_usize(del_result.new_position);
707
708 let ins_result = document_editing_commands::insert_formatted_text(
709 &inner.ctx,
710 Some(inner.stack_id),
711 &make_dto(del_pos, del_pos),
712 )?;
713
714 undo_redo_commands::end_composite(&inner.ctx);
715 ins_result
716 }
717 Err(e) => return Err(e.into()),
718 };
719
720 let edit_pos = pos.min(anchor);
721 let removed = pos.max(anchor) - edit_pos;
722 self.finish_edit_ext(
723 &mut inner,
724 edit_pos,
725 removed,
726 to_usize(result.new_position),
727 1,
728 false,
729 )
730 };
731 crate::inner::dispatch_queued_events(queued);
732 Ok(())
733 }
734
735 pub fn insert_block(&self) -> Result<()> {
737 let (pos, anchor) = self.read_cursor();
738 let queued = {
739 let mut inner = self.doc.lock();
740
741 let (insert_pos, removed) = if pos != anchor {
742 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
744 let del_dto = frontend::document_editing::DeleteTextDto {
745 position: to_i64(pos),
746 anchor: to_i64(anchor),
747 };
748 let del_result = document_editing_commands::delete_text(
749 &inner.ctx,
750 Some(inner.stack_id),
751 &del_dto,
752 )?;
753 (
754 to_usize(del_result.new_position),
755 pos.max(anchor) - pos.min(anchor),
756 )
757 } else {
758 (pos, 0)
759 };
760
761 let dto = frontend::document_editing::InsertBlockDto {
762 position: to_i64(insert_pos),
763 anchor: to_i64(insert_pos),
764 };
765 let result =
766 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
767
768 if pos != anchor {
769 undo_redo_commands::end_composite(&inner.ctx);
770 }
771
772 let edit_pos = pos.min(anchor);
773 self.finish_edit(
774 &mut inner,
775 edit_pos,
776 removed,
777 to_usize(result.new_position),
778 2,
779 )
780 };
781 crate::inner::dispatch_queued_events(queued);
782 Ok(())
783 }
784
785 pub fn insert_html(&self, html: &str) -> Result<()> {
787 let frag = DocumentFragment::from_html(html);
789 self.insert_fragment(&frag)
790 }
791
792 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
794 let frag = DocumentFragment::from_markdown(markdown);
795 self.insert_fragment(&frag)
796 }
797
798 pub fn insert_djot(&self, djot: &str) -> Result<()> {
800 let frag = DocumentFragment::from_djot(djot);
801 self.insert_fragment(&frag)
802 }
803
804 pub fn insert_footnote_reference(&self, label: &str) -> Result<()> {
817 self.insert_djot(&format!("[^{label}]"))
818 }
819
820 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
822 let (pos, anchor) = self.read_cursor();
823 let queued = {
824 let mut inner = self.doc.lock();
825
826 let (insert_pos, removed) = if pos != anchor {
827 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
828 let del_dto = frontend::document_editing::DeleteTextDto {
829 position: to_i64(pos),
830 anchor: to_i64(anchor),
831 };
832 let del_result = document_editing_commands::delete_text(
833 &inner.ctx,
834 Some(inner.stack_id),
835 &del_dto,
836 )?;
837 (
838 to_usize(del_result.new_position),
839 pos.max(anchor) - pos.min(anchor),
840 )
841 } else {
842 (pos, 0)
843 };
844
845 let dto = frontend::document_editing::InsertFragmentDto {
846 position: to_i64(insert_pos),
847 anchor: to_i64(insert_pos),
848 fragment_data: fragment.raw_data().into(),
849 };
850 let result =
851 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
852
853 if pos != anchor {
854 undo_redo_commands::end_composite(&inner.ctx);
855 }
856
857 let edit_pos = pos.min(anchor);
858 self.finish_edit(
859 &mut inner,
860 edit_pos,
861 removed,
862 to_usize(result.new_position),
863 to_usize(result.blocks_added),
864 )
865 };
866 crate::inner::dispatch_queued_events(queued);
867 Ok(())
868 }
869
870 pub fn selection(&self) -> DocumentFragment {
872 let (pos, anchor) = self.read_cursor();
873
874 let (extract_pos, extract_anchor) = match self.selection_kind() {
877 SelectionKind::Cells(ref range) => match self.cell_range_positions(range) {
878 Some((start, end)) => (start, end),
879 None => return DocumentFragment::new(),
880 },
881 SelectionKind::Mixed {
882 ref cell_range,
883 text_before,
884 text_after,
885 } => {
886 let (cell_start, cell_end) = match self.cell_range_positions(cell_range) {
887 Some(p) => p,
888 None => return DocumentFragment::new(),
889 };
890 let start = if text_before {
891 pos.min(anchor)
892 } else {
893 cell_start
894 };
895 let end = if text_after {
896 pos.max(anchor)
897 } else {
898 cell_end
899 };
900 (start.min(cell_start), end.max(cell_end))
901 }
902 SelectionKind::None => return DocumentFragment::new(),
903 SelectionKind::Text => (pos, anchor),
904 };
905
906 if extract_pos == extract_anchor {
907 return DocumentFragment::new();
908 }
909
910 let inner = self.doc.lock();
911 let dto = frontend::document_inspection::ExtractFragmentDto {
912 position: to_i64(extract_pos),
913 anchor: to_i64(extract_anchor),
914 };
915 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
916 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
917 Err(_) => DocumentFragment::new(),
918 }
919 }
920
921 pub fn insert_image(&self, name: &str, alt: &str, width: u32, height: u32) -> Result<()> {
927 let (pos, anchor) = self.read_cursor();
928 let queued = {
929 let mut inner = self.doc.lock();
930
931 let (insert_pos, removed) = if pos != anchor {
932 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
933 let del_dto = frontend::document_editing::DeleteTextDto {
934 position: to_i64(pos),
935 anchor: to_i64(anchor),
936 };
937 let del_result = document_editing_commands::delete_text(
938 &inner.ctx,
939 Some(inner.stack_id),
940 &del_dto,
941 )?;
942 (
943 to_usize(del_result.new_position),
944 pos.max(anchor) - pos.min(anchor),
945 )
946 } else {
947 (pos, 0)
948 };
949
950 let dto = frontend::document_editing::InsertImageDto {
951 position: to_i64(insert_pos),
952 anchor: to_i64(insert_pos),
953 image_name: name.into(),
954 alt: alt.into(),
955 width: width as i64,
956 height: height as i64,
957 quality: 100,
958 };
959 let result =
960 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
961
962 if pos != anchor {
963 undo_redo_commands::end_composite(&inner.ctx);
964 }
965
966 let edit_pos = pos.min(anchor);
967 self.finish_edit_ext(
968 &mut inner,
969 edit_pos,
970 removed,
971 to_usize(result.new_position),
972 1,
973 false,
974 )
975 };
976 crate::inner::dispatch_queued_events(queued);
977 Ok(())
978 }
979
980 pub fn insert_frame(&self) -> Result<()> {
982 let (pos, anchor) = self.read_cursor();
983 let queued = {
984 let mut inner = self.doc.lock();
985 let dto = frontend::document_editing::InsertFrameDto {
986 position: to_i64(pos),
987 anchor: to_i64(anchor),
988 };
989 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
990 inner.modified = true;
993 inner.invalidate_text_cache();
994 inner.rehighlight_affected(pos.min(anchor));
995 inner.queue_event(DocumentEvent::ContentsChanged {
996 position: pos.min(anchor),
997 chars_removed: 0,
998 chars_added: 0,
999 blocks_affected: 1,
1000 });
1001 inner.check_block_count_changed();
1002 inner.check_flow_changed();
1003 self.queue_undo_redo_event(&mut inner)
1004 };
1005 crate::inner::dispatch_queued_events(queued);
1006 Ok(())
1007 }
1008
1009 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
1015 let (pos, anchor) = self.read_cursor();
1016 let (table_id, queued) = {
1017 let mut inner = self.doc.lock();
1018 let dto = frontend::document_editing::InsertTableDto {
1019 position: to_i64(pos),
1020 anchor: to_i64(anchor),
1021 rows: to_i64(rows),
1022 columns: to_i64(columns),
1023 };
1024 let result =
1025 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1026 let new_pos = to_usize(result.new_position);
1027 let table_id = to_usize(result.table_id);
1028 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
1029 {
1030 let mut d = self.data.lock();
1031 d.position = new_pos;
1032 d.anchor = new_pos;
1033 }
1034 inner.modified = true;
1035 inner.invalidate_text_cache();
1036 inner.rehighlight_affected(pos.min(anchor));
1037 inner.queue_event(DocumentEvent::ContentsChanged {
1038 position: pos.min(anchor),
1039 chars_removed: 0,
1040 chars_added: new_pos - pos.min(anchor),
1041 blocks_affected: 1,
1042 });
1043 inner.check_block_count_changed();
1044 inner.check_flow_changed();
1045 (table_id, self.queue_undo_redo_event(&mut inner))
1046 };
1047 crate::inner::dispatch_queued_events(queued);
1048 Ok(TextTable {
1049 doc: self.doc.clone(),
1050 table_id,
1051 })
1052 }
1053
1054 pub fn current_table(&self) -> Option<TextTable> {
1059 self.current_table_cell().map(|c| c.table)
1060 }
1061
1062 pub fn current_table_cell(&self) -> Option<TableCellRef> {
1067 let pos = self.position();
1068 let inner = self.doc.lock();
1069 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1071 position: to_i64(pos),
1072 };
1073 let block_info =
1074 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1075
1076 let block_id = if to_i64(pos) < block_info.block_start && pos > 0 {
1080 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1081 position: to_i64(pos - 1),
1082 };
1083 let prev_info =
1084 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
1085 prev_info.block_id as usize
1086 } else {
1087 block_info.block_id as usize
1088 };
1089
1090 let block = crate::text_block::TextBlock {
1091 doc: self.doc.clone(),
1092 block_id,
1093 };
1094 drop(inner);
1096 block.table_cell()
1097 }
1098
1099 pub fn current_frame(&self) -> Option<FrameRef> {
1106 let pos = self.position();
1107 let inner = self.doc.lock();
1108 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1109 position: to_i64(pos),
1110 };
1111 let block_info =
1112 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1113 let block_id = block_info.block_id as u64;
1114 cursor_frame_ref(&inner, block_id)
1115 }
1116
1117 pub fn is_in_blockquote(&self) -> bool {
1120 self.current_blockquote_frame_id().is_some()
1121 }
1122
1123 pub fn current_blockquote_frame_id(&self) -> Option<usize> {
1126 let pos = self.position();
1127 let inner = self.doc.lock();
1128 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1129 position: to_i64(pos),
1130 };
1131 let block_info =
1132 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1133 innermost_blockquote_frame_id(&inner, block_info.block_id as u64)
1134 }
1135
1136 pub fn blockquote_depth_at_cursor(&self) -> usize {
1139 let pos = self.position();
1140 let inner = self.doc.lock();
1141 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1142 position: to_i64(pos),
1143 };
1144 let Some(block_info) =
1145 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
1146 else {
1147 return 0;
1148 };
1149 blockquote_depth_for_block(&inner, block_info.block_id as u64)
1150 }
1151
1152 pub fn is_first_block_in_current_frame(&self) -> bool {
1158 matches!(
1159 block_position_in_current_frame(self),
1160 Some(BlockEdge::First) | Some(BlockEdge::OnlyOne)
1161 )
1162 }
1163
1164 pub fn is_last_block_in_current_frame(&self) -> bool {
1168 matches!(
1169 block_position_in_current_frame(self),
1170 Some(BlockEdge::Last) | Some(BlockEdge::OnlyOne)
1171 )
1172 }
1173
1174 pub fn current_block_is_empty(&self) -> bool {
1177 let pos = self.position();
1178 let inner = self.doc.lock();
1179 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1180 position: to_i64(pos),
1181 };
1182 let Some(block_info) =
1183 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
1184 else {
1185 return false;
1186 };
1187 let store = inner.ctx.db_context.get_store();
1188 let block_entity = store
1189 .blocks
1190 .read()
1191 .get(&(block_info.block_id as common::types::EntityId))
1192 .cloned();
1193 match block_entity {
1194 Some(b) => {
1195 let len = common::database::rope_helpers::block_char_length(&b, store);
1196 len == 0
1197 }
1198 None => false,
1199 }
1200 }
1201
1202 pub fn selection_spans_multiple_frames(&self) -> bool {
1206 let (pos, anchor) = self.read_cursor();
1207 if pos == anchor {
1208 return false;
1209 }
1210 let inner = self.doc.lock();
1211 let pos_dto = frontend::document_inspection::GetBlockAtPositionDto {
1212 position: to_i64(pos),
1213 };
1214 let anchor_dto = frontend::document_inspection::GetBlockAtPositionDto {
1215 position: to_i64(anchor),
1216 };
1217 let Some(pos_block) =
1218 document_inspection_commands::get_block_at_position(&inner.ctx, &pos_dto).ok()
1219 else {
1220 return false;
1221 };
1222 let Some(anchor_block) =
1223 document_inspection_commands::get_block_at_position(&inner.ctx, &anchor_dto).ok()
1224 else {
1225 return false;
1226 };
1227 let pos_owner = crate::text_block::find_parent_frame(&inner, pos_block.block_id as u64);
1228 let anchor_owner =
1229 crate::text_block::find_parent_frame(&inner, anchor_block.block_id as u64);
1230 pos_owner != anchor_owner
1231 }
1232
1233 pub fn wrap_selection_in_blockquote(&self) -> Result<()> {
1240 if self.selection_spans_multiple_frames() {
1241 return Err(DocumentError::InvalidArgument(
1242 "Cannot wrap selection in blockquote: selection spans multiple frames".into(),
1243 ));
1244 }
1245 let (start_block_id, end_block_id) = self.resolve_selection_block_range()?;
1246 let dto = frontend::document_editing::WrapBlocksInFrameDto {
1247 start_block_id: start_block_id as i64,
1248 end_block_id: end_block_id as i64,
1249 position: Some(frontend::document_editing::FramePosition::InFlow),
1250 top_margin: None,
1251 bottom_margin: None,
1252 left_margin: None,
1253 right_margin: None,
1254 padding: None,
1255 border: None,
1256 is_blockquote: Some(true),
1257 };
1258 let queued = {
1259 let mut inner = self.doc.lock();
1260 let _result = document_editing_commands::wrap_blocks_in_frame(
1261 &inner.ctx,
1262 Some(inner.stack_id),
1263 &dto,
1264 )?;
1265 inner.modified = true;
1266 inner.queue_event(DocumentEvent::FormatChanged {
1274 position: 0,
1275 length: 0,
1276 kind: crate::flow::FormatChangeKind::Block,
1277 });
1278 self.queue_undo_redo_event(&mut inner)
1279 };
1280 crate::inner::dispatch_queued_events(queued);
1281 Ok(())
1282 }
1283
1284 pub fn insert_blockquote(&self) -> Result<()> {
1288 self.wrap_selection_in_blockquote()
1289 }
1290
1291 pub fn toggle_blockquote(&self) -> Result<()> {
1296 if let Some(frame_id) = self.current_blockquote_frame_id() {
1297 self.unwrap_frame_by_id(frame_id)
1298 } else {
1299 self.wrap_selection_in_blockquote()
1300 }
1301 }
1302
1303 pub fn unwrap_current_frame(&self) -> Result<()> {
1307 let frame_ref = self.current_frame().ok_or_else(|| {
1308 DocumentError::InvalidCursorContext("Cursor is not inside any sub-frame".into())
1309 })?;
1310 self.unwrap_frame_by_id(frame_ref.frame_id)
1311 }
1312
1313 pub fn unwrap_current_block_from_blockquote(&self) -> Result<()> {
1317 if self.current_blockquote_frame_id().is_none() {
1318 return Err(DocumentError::InvalidCursorContext(
1319 "Cursor is not inside a blockquote".into(),
1320 ));
1321 }
1322 let block_id = self.current_block_id_for_mutation()?;
1323 let dto = frontend::document_editing::UnwrapBlockFromFrameDto {
1324 block_id: block_id as i64,
1325 };
1326 let queued = {
1327 let mut inner = self.doc.lock();
1328 let _result = document_editing_commands::unwrap_block_from_frame(
1329 &inner.ctx,
1330 Some(inner.stack_id),
1331 &dto,
1332 )?;
1333 inner.modified = true;
1334 inner.queue_event(DocumentEvent::FormatChanged {
1338 position: 0,
1339 length: 0,
1340 kind: crate::flow::FormatChangeKind::Block,
1341 });
1342 self.queue_undo_redo_event(&mut inner)
1343 };
1344 crate::inner::dispatch_queued_events(queued);
1345 Ok(())
1346 }
1347
1348 pub fn increase_blockquote_depth(&self) -> Result<()> {
1352 self.wrap_selection_in_blockquote()
1353 }
1354
1355 pub fn decrease_blockquote_depth(&self) -> Result<()> {
1361 if self.current_blockquote_frame_id().is_none() {
1362 return Err(DocumentError::InvalidCursorContext(
1363 "Cursor is not inside a blockquote to decrease depth".into(),
1364 ));
1365 }
1366 self.unwrap_current_block_from_blockquote()
1367 }
1368
1369 fn unwrap_frame_by_id(&self, frame_id: usize) -> Result<()> {
1370 let dto = frontend::document_editing::UnwrapFrameDto {
1371 frame_id: frame_id as i64,
1372 };
1373 let queued = {
1374 let mut inner = self.doc.lock();
1375 let _result =
1376 document_editing_commands::unwrap_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
1377 inner.modified = true;
1378 inner.queue_event(DocumentEvent::FormatChanged {
1382 position: 0,
1383 length: 0,
1384 kind: crate::flow::FormatChangeKind::Block,
1385 });
1386 self.queue_undo_redo_event(&mut inner)
1387 };
1388 crate::inner::dispatch_queued_events(queued);
1389 Ok(())
1390 }
1391
1392 fn current_block_id_for_mutation(&self) -> Result<usize> {
1393 let pos = self.position();
1394 let inner = self.doc.lock();
1395 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1396 position: to_i64(pos),
1397 };
1398 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1399 .map_err(|e| anyhow::anyhow!("get_block_at_position: {}", e))?;
1400 Ok(block_info.block_id as usize)
1401 }
1402
1403 fn resolve_selection_block_range(&self) -> Result<(usize, usize)> {
1404 let (pos, anchor) = self.read_cursor();
1405 let lo = pos.min(anchor);
1406 let hi = pos.max(anchor);
1407 let inner = self.doc.lock();
1408 let lo_dto = frontend::document_inspection::GetBlockAtPositionDto {
1409 position: to_i64(lo),
1410 };
1411 let hi_dto = frontend::document_inspection::GetBlockAtPositionDto {
1412 position: to_i64(hi),
1413 };
1414 let lo_block = document_inspection_commands::get_block_at_position(&inner.ctx, &lo_dto)
1415 .map_err(|e| anyhow::anyhow!("get_block_at_position(start): {}", e))?;
1416 let hi_block = document_inspection_commands::get_block_at_position(&inner.ctx, &hi_dto)
1417 .map_err(|e| anyhow::anyhow!("get_block_at_position(end): {}", e))?;
1418 Ok((lo_block.block_id as usize, hi_block.block_id as usize))
1419 }
1420
1421 pub fn remove_table(&self, table_id: usize) -> Result<()> {
1425 let queued = {
1426 let mut inner = self.doc.lock();
1427 let dto = frontend::document_editing::RemoveTableDto {
1428 table_id: to_i64(table_id),
1429 };
1430 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1431 inner.modified = true;
1432 inner.invalidate_text_cache();
1433 inner.rehighlight_all();
1434 inner.check_block_count_changed();
1435 inner.check_flow_changed();
1436 self.queue_undo_redo_event(&mut inner)
1437 };
1438 crate::inner::dispatch_queued_events(queued);
1439 Ok(())
1440 }
1441
1442 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1444 let queued = {
1445 let mut inner = self.doc.lock();
1446 let dto = frontend::document_editing::InsertTableRowDto {
1447 table_id: to_i64(table_id),
1448 row_index: to_i64(row_index),
1449 };
1450 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1451 inner.modified = true;
1452 inner.invalidate_text_cache();
1453 inner.rehighlight_all();
1454 inner.check_block_count_changed();
1455 self.queue_undo_redo_event(&mut inner)
1456 };
1457 crate::inner::dispatch_queued_events(queued);
1458 Ok(())
1459 }
1460
1461 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1463 let queued = {
1464 let mut inner = self.doc.lock();
1465 let dto = frontend::document_editing::InsertTableColumnDto {
1466 table_id: to_i64(table_id),
1467 column_index: to_i64(column_index),
1468 };
1469 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1470 inner.modified = true;
1471 inner.invalidate_text_cache();
1472 inner.rehighlight_all();
1473 inner.check_block_count_changed();
1474 self.queue_undo_redo_event(&mut inner)
1475 };
1476 crate::inner::dispatch_queued_events(queued);
1477 Ok(())
1478 }
1479
1480 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1482 let queued = {
1483 let mut inner = self.doc.lock();
1484 let dto = frontend::document_editing::RemoveTableRowDto {
1485 table_id: to_i64(table_id),
1486 row_index: to_i64(row_index),
1487 };
1488 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1489 inner.modified = true;
1490 inner.invalidate_text_cache();
1491 inner.rehighlight_all();
1492 inner.check_block_count_changed();
1493 self.queue_undo_redo_event(&mut inner)
1494 };
1495 crate::inner::dispatch_queued_events(queued);
1496 Ok(())
1497 }
1498
1499 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1501 let queued = {
1502 let mut inner = self.doc.lock();
1503 let dto = frontend::document_editing::RemoveTableColumnDto {
1504 table_id: to_i64(table_id),
1505 column_index: to_i64(column_index),
1506 };
1507 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1508 inner.modified = true;
1509 inner.invalidate_text_cache();
1510 inner.rehighlight_all();
1511 inner.check_block_count_changed();
1512 self.queue_undo_redo_event(&mut inner)
1513 };
1514 crate::inner::dispatch_queued_events(queued);
1515 Ok(())
1516 }
1517
1518 pub fn merge_table_cells(
1520 &self,
1521 table_id: usize,
1522 start_row: usize,
1523 start_column: usize,
1524 end_row: usize,
1525 end_column: usize,
1526 ) -> Result<()> {
1527 let queued = {
1528 let mut inner = self.doc.lock();
1529 let dto = frontend::document_editing::MergeTableCellsDto {
1530 table_id: to_i64(table_id),
1531 start_row: to_i64(start_row),
1532 start_column: to_i64(start_column),
1533 end_row: to_i64(end_row),
1534 end_column: to_i64(end_column),
1535 };
1536 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
1537 inner.modified = true;
1538 inner.invalidate_text_cache();
1539 inner.rehighlight_all();
1540 inner.check_block_count_changed();
1541 self.queue_undo_redo_event(&mut inner)
1542 };
1543 crate::inner::dispatch_queued_events(queued);
1544 Ok(())
1545 }
1546
1547 pub fn split_table_cell(
1549 &self,
1550 cell_id: usize,
1551 split_rows: usize,
1552 split_columns: usize,
1553 ) -> Result<()> {
1554 let queued = {
1555 let mut inner = self.doc.lock();
1556 let dto = frontend::document_editing::SplitTableCellDto {
1557 cell_id: to_i64(cell_id),
1558 split_rows: to_i64(split_rows),
1559 split_columns: to_i64(split_columns),
1560 };
1561 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
1562 inner.modified = true;
1563 inner.invalidate_text_cache();
1564 inner.rehighlight_all();
1565 inner.check_block_count_changed();
1566 self.queue_undo_redo_event(&mut inner)
1567 };
1568 crate::inner::dispatch_queued_events(queued);
1569 Ok(())
1570 }
1571
1572 pub fn set_table_format(
1576 &self,
1577 table_id: usize,
1578 format: &crate::flow::TableFormat,
1579 ) -> Result<()> {
1580 let queued = {
1581 let mut inner = self.doc.lock();
1582 let dto = format.to_set_dto(table_id);
1583 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1584 inner.modified = true;
1585 inner.queue_event(DocumentEvent::FormatChanged {
1586 position: 0,
1587 length: 0,
1588 kind: crate::flow::FormatChangeKind::Block,
1589 });
1590 self.queue_undo_redo_event(&mut inner)
1591 };
1592 crate::inner::dispatch_queued_events(queued);
1593 Ok(())
1594 }
1595
1596 pub fn set_table_cell_format(
1598 &self,
1599 cell_id: usize,
1600 format: &crate::flow::CellFormat,
1601 ) -> Result<()> {
1602 let queued = {
1603 let mut inner = self.doc.lock();
1604 let dto = format.to_set_dto(cell_id);
1605 document_formatting_commands::set_table_cell_format(
1606 &inner.ctx,
1607 Some(inner.stack_id),
1608 &dto,
1609 )?;
1610 inner.modified = true;
1611 inner.queue_event(DocumentEvent::FormatChanged {
1612 position: 0,
1613 length: 0,
1614 kind: crate::flow::FormatChangeKind::Block,
1615 });
1616 self.queue_undo_redo_event(&mut inner)
1617 };
1618 crate::inner::dispatch_queued_events(queued);
1619 Ok(())
1620 }
1621
1622 pub fn remove_current_table(&self) -> Result<()> {
1627 let table = self.current_table().ok_or_else(|| {
1628 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1629 })?;
1630 self.remove_table(table.id())
1631 }
1632
1633 pub fn insert_row_above(&self) -> Result<()> {
1636 let cell_ref = self.current_table_cell().ok_or_else(|| {
1637 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1638 })?;
1639 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
1640 }
1641
1642 pub fn insert_row_below(&self) -> Result<()> {
1645 let cell_ref = self.current_table_cell().ok_or_else(|| {
1646 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1647 })?;
1648 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
1649 }
1650
1651 pub fn insert_column_before(&self) -> Result<()> {
1654 let cell_ref = self.current_table_cell().ok_or_else(|| {
1655 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1656 })?;
1657 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
1658 }
1659
1660 pub fn insert_column_after(&self) -> Result<()> {
1663 let cell_ref = self.current_table_cell().ok_or_else(|| {
1664 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1665 })?;
1666 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
1667 }
1668
1669 pub fn remove_current_row(&self) -> Result<()> {
1672 let cell_ref = self.current_table_cell().ok_or_else(|| {
1673 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1674 })?;
1675 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
1676 }
1677
1678 pub fn remove_current_column(&self) -> Result<()> {
1681 let cell_ref = self.current_table_cell().ok_or_else(|| {
1682 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1683 })?;
1684 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
1685 }
1686
1687 pub fn merge_selected_cells(&self) -> Result<()> {
1694 let pos_cell = self.current_table_cell().ok_or_else(|| {
1695 DocumentError::InvalidCursorContext("cursor position is not inside a table".into())
1696 })?;
1697
1698 let (_pos, anchor) = self.read_cursor();
1700 let anchor_cell = {
1701 let inner = self.doc.lock();
1703 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1704 position: to_i64(anchor),
1705 };
1706 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1707 .map_err(|_| {
1708 DocumentError::InvalidCursorContext(
1709 "cursor anchor is not inside a table".into(),
1710 )
1711 })?;
1712 let block = crate::text_block::TextBlock {
1713 doc: self.doc.clone(),
1714 block_id: block_info.block_id as usize,
1715 };
1716 drop(inner);
1717 block.table_cell().ok_or_else(|| {
1718 DocumentError::InvalidCursorContext("cursor anchor is not inside a table".into())
1719 })?
1720 };
1721
1722 if pos_cell.table.id() != anchor_cell.table.id() {
1723 return Err(DocumentError::InvalidArgument(
1724 "position and anchor are in different tables".into(),
1725 ));
1726 }
1727
1728 let start_row = pos_cell.row.min(anchor_cell.row);
1729 let start_col = pos_cell.column.min(anchor_cell.column);
1730 let end_row = pos_cell.row.max(anchor_cell.row);
1731 let end_col = pos_cell.column.max(anchor_cell.column);
1732
1733 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
1734 }
1735
1736 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1739 let cell_ref = self.current_table_cell().ok_or_else(|| {
1740 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1741 })?;
1742 let cell = cell_ref
1744 .table
1745 .cell(cell_ref.row, cell_ref.column)
1746 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1747 self.split_table_cell(cell.id(), split_rows, split_columns)
1749 }
1750
1751 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1754 let table = self.current_table().ok_or_else(|| {
1755 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1756 })?;
1757 self.set_table_format(table.id(), format)
1758 }
1759
1760 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1763 let cell_ref = self.current_table_cell().ok_or_else(|| {
1764 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1765 })?;
1766 let cell = cell_ref
1767 .table
1768 .cell(cell_ref.row, cell_ref.column)
1769 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1770 self.set_table_cell_format(cell.id(), format)
1771 }
1772
1773 pub fn selection_kind(&self) -> crate::flow::SelectionKind {
1781 use crate::flow::{CellRange, SelectionKind};
1782
1783 {
1785 let d = self.data.lock();
1786 if let Some(ref range) = d.cell_selection_override {
1787 return SelectionKind::Cells(range.clone());
1788 }
1789 if d.position == d.anchor {
1790 return SelectionKind::None;
1791 }
1792 }
1793
1794 let (pos, anchor) = self.read_cursor();
1795
1796 let pos_cell = self.table_cell_at(pos);
1798 let anchor_cell = self.table_cell_at(anchor);
1799
1800 match (&pos_cell, &anchor_cell) {
1801 (None, None) => {
1802 let (start, end) = (pos.min(anchor), pos.max(anchor));
1806 if let Some(t) = self.find_table_between(start, end) {
1807 let table_id = t.id();
1808 let rows = t.rows();
1809 let cols = t.columns();
1810 let range = CellRange {
1811 table_id,
1812 start_row: 0,
1813 start_col: 0,
1814 end_row: if rows > 0 { rows - 1 } else { 0 },
1815 end_col: if cols > 0 { cols - 1 } else { 0 },
1816 };
1817 let spans = self.collect_cell_spans(table_id);
1818 SelectionKind::Mixed {
1819 cell_range: range.expand_for_spans(&spans),
1820 text_before: true,
1821 text_after: true,
1822 }
1823 } else {
1824 SelectionKind::Text
1825 }
1826 }
1827 (Some(pc), Some(ac)) => {
1828 if pc.table.id() != ac.table.id() {
1829 return SelectionKind::Text;
1831 }
1832 if pc.row == ac.row && pc.column == ac.column {
1833 return SelectionKind::Text;
1835 }
1836 let range = CellRange {
1838 table_id: pc.table.id(),
1839 start_row: pc.row.min(ac.row),
1840 start_col: pc.column.min(ac.column),
1841 end_row: pc.row.max(ac.row),
1842 end_col: pc.column.max(ac.column),
1843 };
1844 let spans = self.collect_cell_spans(pc.table.id());
1845 SelectionKind::Cells(range.expand_for_spans(&spans))
1846 }
1847 (Some(tc), None) | (None, Some(tc)) => {
1848 let table_id = tc.table.id();
1852 let rows = tc.table.rows();
1853 let cols = tc.table.columns();
1854
1855 let inside_pos = if pos_cell.is_some() { pos } else { anchor };
1856 let outside_pos = if pos_cell.is_some() { anchor } else { pos };
1857
1858 let text_before = outside_pos < inside_pos;
1859 let text_after = !text_before;
1860
1861 let range = CellRange {
1862 table_id,
1863 start_row: 0,
1864 start_col: 0,
1865 end_row: if rows > 0 { rows - 1 } else { 0 },
1866 end_col: if cols > 0 { cols - 1 } else { 0 },
1867 };
1868 let spans = self.collect_cell_spans(table_id);
1869 SelectionKind::Mixed {
1870 cell_range: range.expand_for_spans(&spans),
1871 text_before,
1872 text_after,
1873 }
1874 }
1875 }
1876 }
1877
1878 pub fn is_cell_selection(&self) -> bool {
1880 matches!(
1881 self.selection_kind(),
1882 crate::flow::SelectionKind::Cells(_) | crate::flow::SelectionKind::Mixed { .. }
1883 )
1884 }
1885
1886 pub fn selected_cell_range(&self) -> Option<crate::flow::CellRange> {
1888 match self.selection_kind() {
1889 crate::flow::SelectionKind::Cells(r) => Some(r),
1890 crate::flow::SelectionKind::Mixed { cell_range, .. } => Some(cell_range),
1891 _ => None,
1892 }
1893 }
1894
1895 pub fn selected_cells(&self) -> Vec<TableCellRef> {
1897 let range = match self.selected_cell_range() {
1898 Some(r) => r,
1899 None => return Vec::new(),
1900 };
1901 let table = TextTable {
1902 doc: self.doc.clone(),
1903 table_id: range.table_id,
1904 };
1905 let mut cells = Vec::new();
1906 for row in range.start_row..=range.end_row {
1907 for col in range.start_col..=range.end_col {
1908 if table.cell(row, col).is_some() {
1909 cells.push(TableCellRef {
1910 table: table.clone(),
1911 row,
1912 column: col,
1913 });
1914 }
1915 }
1916 }
1917 cells
1918 }
1919
1920 pub fn select_table_cell(&self, table_id: usize, row: usize, col: usize) {
1924 let mut d = self.data.lock();
1925 d.cell_selection_override = Some(crate::flow::CellRange {
1926 table_id,
1927 start_row: row,
1928 start_col: col,
1929 end_row: row,
1930 end_col: col,
1931 });
1932 }
1933
1934 pub fn select_cell_range(
1936 &self,
1937 table_id: usize,
1938 start_row: usize,
1939 start_col: usize,
1940 end_row: usize,
1941 end_col: usize,
1942 ) {
1943 let range = crate::flow::CellRange {
1944 table_id,
1945 start_row,
1946 start_col,
1947 end_row,
1948 end_col,
1949 };
1950 let spans = self.collect_cell_spans(table_id);
1951 let mut d = self.data.lock();
1952 d.cell_selection_override = Some(range.expand_for_spans(&spans));
1953 }
1954
1955 pub fn clear_cell_selection(&self) {
1957 let mut d = self.data.lock();
1958 d.cell_selection_override = None;
1959 }
1960
1961 fn cell_range_positions(&self, range: &CellRange) -> Option<(usize, usize)> {
1964 let inner = self.doc.lock();
1965 let main_frame_id = get_main_frame_id(&inner);
1966 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1967 drop(inner);
1968
1969 let table = flow.into_iter().find_map(|e| match e {
1971 FlowElement::Table(t) if t.id() == range.table_id => Some(t),
1972 _ => None,
1973 })?;
1974
1975 let mut min_pos = usize::MAX;
1976 let mut max_pos = 0usize;
1977
1978 for row in range.start_row..=range.end_row {
1979 for col in range.start_col..=range.end_col {
1980 if let Some(cell) = table.cell(row, col) {
1981 for block in cell.blocks() {
1982 let bp = block.position();
1983 let bl = block.length();
1984 min_pos = min_pos.min(bp);
1985 max_pos = max_pos.max(bp + bl);
1986 }
1987 }
1988 }
1989 }
1990
1991 if min_pos == usize::MAX {
1992 return None;
1993 }
1994
1995 Some((min_pos, max_pos + 1))
1997 }
1998
1999 fn table_cell_at(&self, position: usize) -> Option<TableCellRef> {
2003 let inner = self.doc.lock();
2004 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2005 position: to_i64(position),
2006 };
2007 let block_info =
2008 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2009
2010 let block_id = if to_i64(position) < block_info.block_start && position > 0 {
2011 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2012 position: to_i64(position - 1),
2013 };
2014 let prev_info =
2015 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
2016 prev_info.block_id as usize
2017 } else {
2018 block_info.block_id as usize
2019 };
2020
2021 let block = crate::text_block::TextBlock {
2022 doc: self.doc.clone(),
2023 block_id,
2024 };
2025 drop(inner);
2026 block.table_cell()
2027 }
2028
2029 fn table_boundary_position(&self, table_id: usize, before: bool) -> Option<usize> {
2040 let inner = self.doc.lock();
2041 let main_frame_id = get_main_frame_id(&inner);
2042 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2043 drop(inner);
2044
2045 let idx = flow
2047 .iter()
2048 .position(|e| matches!(e, FlowElement::Table(t) if t.id() == table_id))?;
2049
2050 if before {
2051 for i in (0..idx).rev() {
2053 if let FlowElement::Block(b) = &flow[i] {
2054 return Some(b.position() + b.length());
2055 }
2056 }
2057 } else {
2058 for item in flow.iter().skip(idx + 1) {
2060 if let FlowElement::Block(b) = item {
2061 return Some(b.position());
2062 }
2063 }
2064 }
2065 None
2066 }
2067
2068 fn find_table_between(&self, start: usize, end: usize) -> Option<TextTable> {
2070 let inner = self.doc.lock();
2071 let main_frame_id = get_main_frame_id(&inner);
2072 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2073 drop(inner);
2074
2075 for elem in flow {
2076 if let FlowElement::Table(t) = elem {
2077 if let Some(first_cell) = t.cell(0, 0) {
2080 let blocks = first_cell.blocks();
2081 if let Some(fb) = blocks.first() {
2082 let p = fb.position();
2083 if p > start && p < end {
2084 return Some(t);
2085 }
2086 }
2087 }
2088 }
2089 }
2090 None
2091 }
2092
2093 fn collect_cell_spans(&self, table_id: usize) -> Vec<(usize, usize, usize, usize)> {
2095 let inner = self.doc.lock();
2096 let table_dto =
2097 match frontend::commands::table_commands::get_table(&inner.ctx, &(table_id as u64))
2098 .ok()
2099 .flatten()
2100 {
2101 Some(t) => t,
2102 None => return Vec::new(),
2103 };
2104
2105 let mut spans = Vec::with_capacity(table_dto.cells.len());
2106 for &cell_id in &table_dto.cells {
2107 if let Some(cell) =
2108 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &cell_id)
2109 .ok()
2110 .flatten()
2111 {
2112 spans.push((
2113 cell.row as usize,
2114 cell.column as usize,
2115 cell.row_span.max(1) as usize,
2116 cell.column_span.max(1) as usize,
2117 ));
2118 }
2119 }
2120 spans
2121 }
2122
2123 pub fn delete_char(&self) -> Result<()> {
2125 let (pos, anchor) = self.read_cursor();
2126 let (del_pos, del_anchor) = if pos != anchor {
2127 (pos, anchor)
2128 } else {
2129 let end = {
2131 let inner = self.doc.lock();
2132 document_inspection_commands::get_document_stats(&inner.ctx)
2133 .map(|s| max_cursor_position(&s))
2134 .unwrap_or(0)
2135 };
2136 if pos >= end {
2137 return Ok(());
2138 }
2139 let to = self.next_grapheme_boundary(pos);
2143 if to == pos {
2144 return Ok(());
2145 }
2146 (pos, to)
2147 };
2148 self.do_delete(del_pos, del_anchor)
2149 }
2150
2151 pub fn delete_previous_char(&self) -> Result<()> {
2153 let (pos, anchor) = self.read_cursor();
2154 let (del_pos, del_anchor) = if pos != anchor {
2155 (pos, anchor)
2156 } else if pos > 0 {
2157 let from = self.prev_grapheme_boundary(pos);
2158 if from == pos {
2159 return Ok(());
2160 }
2161 (from, pos)
2162 } else {
2163 return Ok(());
2164 };
2165 self.do_delete(del_pos, del_anchor)
2166 }
2167
2168 pub fn remove_selected_text(&self) -> Result<String> {
2170 let (pos, anchor) = self.read_cursor();
2171 if pos == anchor {
2172 return Ok(String::new());
2173 }
2174 let queued = {
2175 let mut inner = self.doc.lock();
2176 let dto = frontend::document_editing::DeleteTextDto {
2177 position: to_i64(pos),
2178 anchor: to_i64(anchor),
2179 };
2180 let result =
2181 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2182 let edit_pos = pos.min(anchor);
2183 let removed = pos.max(anchor) - edit_pos;
2184 let new_pos = to_usize(result.new_position);
2185 inner.adjust_cursors(edit_pos, removed, 0);
2186 {
2187 let mut d = self.data.lock();
2188 d.position = new_pos;
2189 d.anchor = new_pos;
2190 }
2191 inner.modified = true;
2192 inner.invalidate_text_cache();
2193 inner.rehighlight_affected(edit_pos);
2194 inner.queue_event(DocumentEvent::ContentsChanged {
2195 position: edit_pos,
2196 chars_removed: removed,
2197 chars_added: 0,
2198 blocks_affected: 1,
2199 });
2200 inner.check_block_count_changed();
2201 inner.check_flow_changed();
2202 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
2204 };
2205 crate::inner::dispatch_queued_events(queued.1);
2206 Ok(queued.0)
2207 }
2208
2209 pub fn current_list(&self) -> Option<crate::TextList> {
2214 let pos = self.position();
2215 let inner = self.doc.lock();
2216 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2217 position: to_i64(pos),
2218 };
2219 let block_info =
2220 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2221 let block = crate::text_block::TextBlock {
2222 doc: self.doc.clone(),
2223 block_id: block_info.block_id as usize,
2224 };
2225 drop(inner);
2226 block.list()
2227 }
2228
2229 pub fn create_list(&self, style: ListStyle) -> Result<()> {
2231 let (pos, anchor) = self.read_cursor();
2232 let queued = {
2233 let mut inner = self.doc.lock();
2234 let dto = frontend::document_editing::CreateListDto {
2235 position: to_i64(pos),
2236 anchor: to_i64(anchor),
2237 style: style.clone(),
2238 };
2239 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2240 inner.modified = true;
2241 inner.rehighlight_affected(pos.min(anchor));
2242 inner.queue_event(DocumentEvent::ContentsChanged {
2243 position: pos.min(anchor),
2244 chars_removed: 0,
2245 chars_added: 0,
2246 blocks_affected: 1,
2247 });
2248 self.queue_undo_redo_event(&mut inner)
2249 };
2250 crate::inner::dispatch_queued_events(queued);
2251 Ok(())
2252 }
2253
2254 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
2256 let (pos, anchor) = self.read_cursor();
2257 let queued = {
2258 let mut inner = self.doc.lock();
2259 let dto = frontend::document_editing::InsertListDto {
2260 position: to_i64(pos),
2261 anchor: to_i64(anchor),
2262 style: style.clone(),
2263 };
2264 let result =
2265 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2266 let edit_pos = pos.min(anchor);
2267 let removed = pos.max(anchor) - edit_pos;
2268 self.finish_edit_ext(
2269 &mut inner,
2270 edit_pos,
2271 removed,
2272 to_usize(result.new_position),
2273 1,
2274 false,
2275 )
2276 };
2277 crate::inner::dispatch_queued_events(queued);
2278 Ok(())
2279 }
2280
2281 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
2283 let queued = {
2284 let mut inner = self.doc.lock();
2285 let dto = format.to_set_dto(list_id);
2286 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2287 inner.modified = true;
2288 inner.queue_event(DocumentEvent::FormatChanged {
2289 position: 0,
2290 length: 0,
2291 kind: crate::flow::FormatChangeKind::List,
2292 });
2293 self.queue_undo_redo_event(&mut inner)
2294 };
2295 crate::inner::dispatch_queued_events(queued);
2296 Ok(())
2297 }
2298
2299 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
2302 let list = self.current_list().ok_or_else(|| {
2303 DocumentError::InvalidCursorContext("cursor is not inside a list".into())
2304 })?;
2305 self.set_list_format(list.id(), format)
2306 }
2307
2308 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
2310 let queued = {
2311 let mut inner = self.doc.lock();
2312 let dto = frontend::document_editing::AddBlockToListDto {
2313 block_id: to_i64(block_id),
2314 list_id: to_i64(list_id),
2315 };
2316 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2317 inner.modified = true;
2318 inner.queue_event(DocumentEvent::FormatChanged {
2325 position: 0,
2326 length: 0,
2327 kind: crate::flow::FormatChangeKind::List,
2328 });
2329 self.queue_undo_redo_event(&mut inner)
2330 };
2331 crate::inner::dispatch_queued_events(queued);
2332 Ok(())
2333 }
2334
2335 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
2337 let pos = self.position();
2338 let inner = self.doc.lock();
2339 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2340 position: to_i64(pos),
2341 };
2342 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2343 drop(inner);
2344 self.add_block_to_list(block_info.block_id as usize, list_id)
2345 }
2346
2347 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
2349 let queued = {
2350 let mut inner = self.doc.lock();
2351 let dto = frontend::document_editing::RemoveBlockFromListDto {
2352 block_id: to_i64(block_id),
2353 };
2354 document_editing_commands::remove_block_from_list(
2355 &inner.ctx,
2356 Some(inner.stack_id),
2357 &dto,
2358 )?;
2359 inner.modified = true;
2360 inner.queue_event(DocumentEvent::FormatChanged {
2363 position: 0,
2364 length: 0,
2365 kind: crate::flow::FormatChangeKind::List,
2366 });
2367 self.queue_undo_redo_event(&mut inner)
2368 };
2369 crate::inner::dispatch_queued_events(queued);
2370 Ok(())
2371 }
2372
2373 pub fn remove_current_block_from_list(&self) -> Result<()> {
2376 let pos = self.position();
2377 let inner = self.doc.lock();
2378 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2379 position: to_i64(pos),
2380 };
2381 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2382 drop(inner);
2383 self.remove_block_from_list(block_info.block_id as usize)
2384 }
2385
2386 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
2389 let list = crate::text_list::TextList {
2390 doc: self.doc.clone(),
2391 list_id,
2392 };
2393 let block = list.item(index).ok_or_else(|| {
2394 DocumentError::OutOfRange(format!("list item index {index} out of range"))
2395 })?;
2396 self.remove_block_from_list(block.id())
2397 }
2398
2399 pub fn char_format(&self) -> Result<TextFormat> {
2404 let pos = self.position();
2405 let inner = self.doc.lock();
2406
2407 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2409 position: to_i64(pos),
2410 };
2411 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2412 let block_id = block_info.block_id as u64;
2413 let mut block_dto =
2414 frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2415 .ok_or_else(|| DocumentError::NotFound("block not found at position".into()))?;
2416 let store = inner.ctx.db_context.get_store();
2417 crate::inner::refresh_block_position(&mut block_dto, store);
2418
2419 let local_char = pos.saturating_sub(block_dto.document_position as usize);
2422 let entity: common::entities::Block = block_dto.clone().into();
2423 let plain_owned = common::database::rope_helpers::block_content_via_store(&entity, store);
2424 let plain: &str = &plain_owned;
2425 let byte_offset: u32 = plain
2426 .char_indices()
2427 .nth(local_char)
2428 .map(|(b, _)| b as u32)
2429 .unwrap_or(plain.len() as u32);
2430
2431 let images = store
2434 .block_images
2435 .read()
2436 .get(&block_id)
2437 .cloned()
2438 .unwrap_or_default();
2439 if let Some(img) = images.iter().find(|i| i.byte_offset == byte_offset) {
2440 return Ok(TextFormat::from(&img.format));
2441 }
2442
2443 let runs = store
2445 .format_runs
2446 .read()
2447 .get(&block_id)
2448 .cloned()
2449 .unwrap_or_default();
2450 let fmt = runs
2451 .iter()
2452 .find(|r| r.byte_start <= byte_offset && byte_offset < r.byte_end)
2453 .map(|r| TextFormat::from(&r.format))
2454 .unwrap_or_default();
2455 Ok(fmt)
2456 }
2457
2458 pub fn block_format(&self) -> Result<BlockFormat> {
2460 let pos = self.position();
2461 let inner = self.doc.lock();
2462 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2463 position: to_i64(pos),
2464 };
2465 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2466 let block_id = block_info.block_id as u64;
2467 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2468 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
2469 Ok(BlockFormat::from(&block))
2470 }
2471
2472 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
2476 let (pos, anchor) = self.read_cursor();
2477 let queued = {
2478 let mut inner = self.doc.lock();
2479 let dto = format.to_set_dto(pos, anchor);
2480 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2481 let start = pos.min(anchor);
2482 let length = pos.max(anchor) - start;
2483 inner.modified = true;
2484 inner.queue_event(DocumentEvent::FormatChanged {
2485 position: start,
2486 length,
2487 kind: crate::flow::FormatChangeKind::Character,
2488 });
2489 self.queue_undo_redo_event(&mut inner)
2490 };
2491 crate::inner::dispatch_queued_events(queued);
2492 Ok(())
2493 }
2494
2495 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
2497 let (pos, anchor) = self.read_cursor();
2498 let queued = {
2499 let mut inner = self.doc.lock();
2500 let dto = format.to_merge_dto(pos, anchor);
2501 document_formatting_commands::merge_text_format(
2502 &inner.ctx,
2503 Some(inner.stack_id),
2504 &dto,
2505 )?;
2506 let start = pos.min(anchor);
2507 let length = pos.max(anchor) - start;
2508 inner.modified = true;
2509 inner.queue_event(DocumentEvent::FormatChanged {
2510 position: start,
2511 length,
2512 kind: crate::flow::FormatChangeKind::Character,
2513 });
2514 self.queue_undo_redo_event(&mut inner)
2515 };
2516 crate::inner::dispatch_queued_events(queued);
2517 Ok(())
2518 }
2519
2520 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
2522 let (pos, anchor) = self.read_cursor();
2523 let queued = {
2524 let mut inner = self.doc.lock();
2525 let dto = format.to_set_dto(pos, anchor);
2526 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2527 let start = pos.min(anchor);
2528 let length = pos.max(anchor) - start;
2529 inner.modified = true;
2530 inner.queue_event(DocumentEvent::FormatChanged {
2531 position: start,
2532 length,
2533 kind: crate::flow::FormatChangeKind::Block,
2534 });
2535 self.queue_undo_redo_event(&mut inner)
2536 };
2537 crate::inner::dispatch_queued_events(queued);
2538 Ok(())
2539 }
2540
2541 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
2543 let (pos, anchor) = self.read_cursor();
2544 let queued = {
2545 let mut inner = self.doc.lock();
2546 let dto = format.to_set_dto(pos, anchor, frame_id);
2547 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2548 let start = pos.min(anchor);
2549 let length = pos.max(anchor) - start;
2550 inner.modified = true;
2551 inner.queue_event(DocumentEvent::FormatChanged {
2552 position: start,
2553 length,
2554 kind: crate::flow::FormatChangeKind::Block,
2555 });
2556 self.queue_undo_redo_event(&mut inner)
2557 };
2558 crate::inner::dispatch_queued_events(queued);
2559 Ok(())
2560 }
2561
2562 pub fn begin_edit_block(&self) {
2566 let inner = self.doc.lock();
2567 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
2568 }
2569
2570 pub fn end_edit_block(&self) {
2572 let inner = self.doc.lock();
2573 undo_redo_commands::end_composite(&inner.ctx);
2574 }
2575
2576 pub fn join_previous_edit_block(&self) {
2583 self.begin_edit_block();
2584 }
2585
2586 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
2590 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
2591 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
2592 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
2593 inner.take_queued_events()
2594 }
2595
2596 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
2597 let queued = {
2598 let mut inner = self.doc.lock();
2599 let dto = frontend::document_editing::DeleteTextDto {
2600 position: to_i64(pos),
2601 anchor: to_i64(anchor),
2602 };
2603 let result =
2604 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2605 let edit_pos = pos.min(anchor);
2606 let removed = pos.max(anchor) - edit_pos;
2607 let new_pos = to_usize(result.new_position);
2608 inner.adjust_cursors(edit_pos, removed, 0);
2609 {
2610 let mut d = self.data.lock();
2611 d.position = new_pos;
2612 d.anchor = new_pos;
2613 }
2614 inner.modified = true;
2615 inner.invalidate_text_cache();
2616 inner.rehighlight_affected(edit_pos);
2617 inner.queue_event(DocumentEvent::ContentsChanged {
2618 position: edit_pos,
2619 chars_removed: removed,
2620 chars_added: 0,
2621 blocks_affected: 1,
2622 });
2623 inner.check_block_count_changed();
2624 inner.check_flow_changed();
2625 self.queue_undo_redo_event(&mut inner)
2626 };
2627 crate::inner::dispatch_queued_events(queued);
2628 Ok(())
2629 }
2630
2631 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
2633 let pos = self.position();
2634 match op {
2635 MoveOperation::NoMove => pos,
2636 MoveOperation::Start => 0,
2637 MoveOperation::End => {
2638 let inner = self.doc.lock();
2639 document_inspection_commands::get_document_stats(&inner.ctx)
2640 .map(|s| max_cursor_position(&s))
2641 .unwrap_or(pos)
2642 }
2643 MoveOperation::NextCharacter | MoveOperation::Right => {
2644 let mut cur = pos;
2645 for _ in 0..n {
2646 let next = self.next_grapheme_boundary(cur);
2647 if next == cur {
2648 break;
2649 }
2650 cur = next;
2651 }
2652 cur
2653 }
2654 MoveOperation::PreviousCharacter | MoveOperation::Left => {
2655 let mut cur = pos;
2656 for _ in 0..n {
2657 let prev = self.prev_grapheme_boundary(cur);
2658 if prev == cur {
2659 break;
2660 }
2661 cur = prev;
2662 }
2663 cur
2664 }
2665 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
2666 let inner = self.doc.lock();
2667 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2668 position: to_i64(pos),
2669 };
2670 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2671 .map(|info| to_usize(info.block_start))
2672 .unwrap_or(pos)
2673 }
2674 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
2675 let inner = self.doc.lock();
2676 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2677 position: to_i64(pos),
2678 };
2679 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2680 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
2681 .unwrap_or(pos)
2682 }
2683 MoveOperation::NextBlock => {
2684 let inner = self.doc.lock();
2685 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2686 position: to_i64(pos),
2687 };
2688 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2689 .map(|info| {
2690 to_usize(info.block_start) + to_usize(info.block_length) + 1
2692 })
2693 .unwrap_or(pos)
2694 }
2695 MoveOperation::PreviousBlock => {
2696 let inner = self.doc.lock();
2697 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2698 position: to_i64(pos),
2699 };
2700 let block_start =
2701 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2702 .map(|info| to_usize(info.block_start))
2703 .unwrap_or(pos);
2704 if block_start >= 2 {
2705 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2707 position: to_i64(block_start - 2),
2708 };
2709 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
2710 .map(|info| to_usize(info.block_start))
2711 .unwrap_or(0)
2712 } else {
2713 0
2714 }
2715 }
2716 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
2717 let (_, end) = self.find_word_boundaries(pos);
2718 if end == pos {
2720 let inner = self.doc.lock();
2722 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
2723 .map(|s| max_cursor_position(&s))
2724 .unwrap_or(0);
2725 let scan_len = max_pos.saturating_sub(pos).min(64);
2726 if scan_len == 0 {
2727 return pos;
2728 }
2729 let dto = frontend::document_inspection::GetTextAtPositionDto {
2730 position: to_i64(pos),
2731 length: to_i64(scan_len),
2732 };
2733 if let Ok(r) =
2734 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
2735 {
2736 for (i, ch) in r.text.chars().enumerate() {
2737 if ch.is_alphanumeric() || ch == '_' {
2738 let word_pos = pos + i;
2740 drop(inner);
2741 let (_, word_end) = self.find_word_boundaries(word_pos);
2742 return word_end;
2743 }
2744 }
2745 }
2746 pos + scan_len
2747 } else {
2748 end
2749 }
2750 }
2751 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
2752 let (start, _) = self.find_word_boundaries(pos);
2753 if start < pos {
2754 start
2755 } else if pos > 0 {
2756 let mut search = pos - 1;
2759 loop {
2760 let (ws, we) = self.find_word_boundaries(search);
2761 if ws < we {
2762 break ws;
2764 }
2765 if search == 0 {
2767 break 0;
2768 }
2769 search -= 1;
2770 }
2771 } else {
2772 0
2773 }
2774 }
2775 MoveOperation::StartOfSentence | MoveOperation::PreviousSentence => {
2776 let mut cur = pos;
2777 for _ in 0..n.max(1) {
2778 let start = match self.find_sentence_boundaries(cur) {
2779 Some((start, _)) => start,
2780 None => break,
2781 };
2782 if start < cur && op == MoveOperation::StartOfSentence {
2785 cur = start;
2786 } else if cur > 0 {
2787 match self.find_sentence_boundaries(cur - 1) {
2788 Some((prev, _)) if prev < cur => cur = prev,
2789 _ => cur = cur.saturating_sub(1),
2792 }
2793 } else {
2794 break;
2795 }
2796 }
2797 cur
2798 }
2799 MoveOperation::EndOfSentence => {
2800 let mut cur = pos;
2801 for _ in 0..n.max(1) {
2802 let end = match self.find_sentence_boundaries(cur) {
2803 Some((_, end)) => end,
2804 None => break,
2805 };
2806 if end > cur {
2807 cur = end;
2808 } else {
2809 match self.find_sentence_boundaries(cur + 1) {
2810 Some((_, next)) if next > cur => cur = next,
2811 _ => break,
2812 }
2813 }
2814 }
2815 cur
2816 }
2817 MoveOperation::NextSentence => {
2818 let mut cur = pos;
2819 for _ in 0..n.max(1) {
2820 let end = match self.find_sentence_boundaries(cur) {
2823 Some((_, end)) => end,
2824 None => break,
2825 };
2826 match self.find_sentence_boundaries(end + 1) {
2827 Some((start, _)) if start > cur => cur = start,
2828 _ => {
2829 if end > cur {
2830 cur = end;
2831 } else {
2832 break;
2833 }
2834 }
2835 }
2836 }
2837 cur
2838 }
2839 MoveOperation::Up | MoveOperation::Down => {
2840 if matches!(op, MoveOperation::Up) {
2843 self.resolve_move(MoveOperation::PreviousBlock, 1)
2844 } else {
2845 self.resolve_move(MoveOperation::NextBlock, 1)
2846 }
2847 }
2848 }
2849 }
2850
2851 pub(crate) fn snap_position_to_grapheme_boundary(&self) {
2862 let pos = {
2863 let data = self.data.lock();
2864 data.position
2865 };
2866 let snapped = self.forward_grapheme_boundary_at_or_after(pos);
2867 if snapped != pos {
2868 let mut data = self.data.lock();
2869 data.position = snapped;
2870 if data.anchor == pos {
2871 data.anchor = snapped;
2872 }
2873 }
2874 }
2875
2876 fn forward_grapheme_boundary_at_or_after(&self, pos: usize) -> usize {
2886 let inner = self.doc.lock();
2887 let end = document_inspection_commands::get_document_stats(&inner.ctx)
2888 .map(|s| max_cursor_position(&s))
2889 .unwrap_or(pos);
2890 if pos >= end {
2891 return pos;
2892 }
2893 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2894 position: to_i64(pos),
2895 };
2896 let Ok(block_info) =
2897 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto)
2898 else {
2899 return pos;
2900 };
2901 let block_start = to_usize(block_info.block_start);
2902 let block_length = to_usize(block_info.block_length);
2903 let offset_in_block = pos.saturating_sub(block_start);
2904 if offset_in_block == 0 || offset_in_block >= block_length {
2906 return pos;
2907 }
2908 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2909 position: to_i64(block_start),
2910 length: to_i64(block_length),
2911 };
2912 let Ok(r) = document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto)
2913 else {
2914 return pos;
2915 };
2916 let text = r.text;
2917 drop(inner);
2918 let mut acc = 0usize;
2921 for g in text.graphemes(true) {
2922 if acc >= offset_in_block {
2923 return block_start + acc;
2924 }
2925 acc += g.chars().count();
2926 }
2927 block_start + acc
2928 }
2929
2930 fn next_grapheme_boundary(&self, pos: usize) -> usize {
2943 let inner = self.doc.lock();
2944 let end = document_inspection_commands::get_document_stats(&inner.ctx)
2945 .map(|s| max_cursor_position(&s))
2946 .unwrap_or(pos);
2947 if pos >= end {
2948 return pos;
2949 }
2950 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2951 position: to_i64(pos),
2952 };
2953 let block_info =
2954 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2955 Ok(info) => info,
2956 Err(_) => return pos + 1,
2957 };
2958 let block_start = to_usize(block_info.block_start);
2959 let block_length = to_usize(block_info.block_length);
2960 let offset_in_block = pos.saturating_sub(block_start);
2961 if offset_in_block >= block_length {
2962 return (pos + 1).min(end);
2965 }
2966 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2967 position: to_i64(pos),
2968 length: to_i64(block_length - offset_in_block),
2969 };
2970 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
2971 Ok(r) => r.text,
2972 Err(_) => return pos + 1,
2973 };
2974 drop(inner);
2975 match text.graphemes(true).next() {
2976 Some(g) if !g.is_empty() => (pos + g.chars().count()).min(end),
2977 _ => (pos + 1).min(end),
2978 }
2979 }
2980
2981 fn prev_grapheme_boundary(&self, pos: usize) -> usize {
2985 if pos == 0 {
2986 return 0;
2987 }
2988 let inner = self.doc.lock();
2989 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2990 position: to_i64(pos.saturating_sub(1)),
2991 };
2992 let block_info =
2993 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2994 Ok(info) => info,
2995 Err(_) => return pos - 1,
2996 };
2997 let block_start = to_usize(block_info.block_start);
2998 let block_length = to_usize(block_info.block_length);
2999 let block_end = block_start + block_length;
3000 if pos > block_end {
3004 return pos - 1;
3005 }
3006 if block_length == 0 || pos <= block_start {
3007 return pos.saturating_sub(1);
3008 }
3009 let scan_len = pos - block_start;
3010 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3011 position: to_i64(block_start),
3012 length: to_i64(scan_len),
3013 };
3014 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
3015 Ok(r) => r.text,
3016 Err(_) => return pos - 1,
3017 };
3018 drop(inner);
3019 match text.graphemes(true).next_back() {
3020 Some(g) if !g.is_empty() => pos - g.chars().count(),
3021 _ => pos - 1,
3022 }
3023 }
3024
3025 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
3031 let inner = self.doc.lock();
3032 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3034 position: to_i64(pos),
3035 };
3036 let block_info =
3037 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3038 Ok(info) => info,
3039 Err(_) => return (pos, pos),
3040 };
3041
3042 let block_start = to_usize(block_info.block_start);
3043 let block_length = to_usize(block_info.block_length);
3044 if block_length == 0 {
3045 return (pos, pos);
3046 }
3047
3048 let dto = frontend::document_inspection::GetTextAtPositionDto {
3049 position: to_i64(block_start),
3050 length: to_i64(block_length),
3051 };
3052 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
3053 Ok(r) => r.text,
3054 Err(_) => return (pos, pos),
3055 };
3056
3057 let cursor_offset = pos.saturating_sub(block_start);
3059
3060 let mut last_char_start = 0;
3062 let mut last_char_end = 0;
3063
3064 for (word_byte_start, word) in text.unicode_word_indices() {
3065 let word_char_start = text[..word_byte_start].chars().count();
3067 let word_char_len = word.chars().count();
3068 let word_char_end = word_char_start + word_char_len;
3069
3070 last_char_start = word_char_start;
3071 last_char_end = word_char_end;
3072
3073 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
3074 return (block_start + word_char_start, block_start + word_char_end);
3075 }
3076 }
3077
3078 if cursor_offset == last_char_end && last_char_start < last_char_end {
3080 return (block_start + last_char_start, block_start + last_char_end);
3081 }
3082
3083 (pos, pos)
3084 }
3085
3086 fn find_sentence_boundaries(&self, pos: usize) -> Option<(usize, usize)> {
3092 let locale = self.data.lock().content_locale.clone();
3093
3094 let inner = self.doc.lock();
3095 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3096 position: to_i64(pos),
3097 };
3098 let block_info =
3099 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto).ok()?;
3100 let block_start = to_usize(block_info.block_start);
3101 let block_length = to_usize(block_info.block_length);
3102 if block_length == 0 {
3103 return None;
3104 }
3105 let dto = frontend::document_inspection::GetTextAtPositionDto {
3106 position: to_i64(block_start),
3107 length: to_i64(block_length),
3108 };
3109 let text = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
3110 .ok()?
3111 .text;
3112 drop(inner);
3113
3114 let offset = pos.saturating_sub(block_start);
3115 let (start, end) =
3116 frontend::common::parser_tools::sentence_bounds(&text, offset, locale.as_deref())?;
3117 Some((block_start + start, block_start + end))
3118 }
3119}
3120
3121#[derive(Clone, Copy, PartialEq, Eq)]
3128enum BlockEdge {
3129 First,
3130 Middle,
3131 Last,
3132 OnlyOne,
3133}
3134
3135fn cursor_frame_ref(inner: &TextDocumentInner, block_id: u64) -> Option<FrameRef> {
3139 let parent = crate::text_block::find_parent_frame(inner, block_id)?;
3140 let store = inner.ctx.db_context.get_store();
3141 let frames = store.frames.read();
3142 let frame = frames.get(&parent)?.clone();
3143 frame.parent_frame?;
3144 let is_blockquote = frame.fmt_is_blockquote.unwrap_or(false);
3145
3146 let mut depth = 0;
3147 let mut current = Some(parent);
3148 while let Some(id) = current {
3149 let Some(f) = frames.get(&id) else {
3150 break;
3151 };
3152 if f.parent_frame.is_none() {
3153 break;
3154 }
3155 depth += 1;
3156 current = f.parent_frame;
3157 }
3158
3159 Some(FrameRef {
3160 frame_id: frame.id as usize,
3161 parent_frame_id: frame.parent_frame.map(|id| id as usize),
3162 is_blockquote,
3163 depth,
3164 })
3165}
3166
3167fn innermost_blockquote_frame_id(inner: &TextDocumentInner, block_id: u64) -> Option<usize> {
3171 let mut current = crate::text_block::find_parent_frame(inner, block_id);
3172 let store = inner.ctx.db_context.get_store();
3173 let frames = store.frames.read();
3174 while let Some(id) = current {
3175 let f = frames.get(&id)?;
3176 if f.fmt_is_blockquote == Some(true) {
3177 return Some(f.id as usize);
3178 }
3179 current = f.parent_frame;
3180 }
3181 None
3182}
3183
3184fn blockquote_depth_for_block(inner: &TextDocumentInner, block_id: u64) -> usize {
3187 let mut current = crate::text_block::find_parent_frame(inner, block_id);
3188 let store = inner.ctx.db_context.get_store();
3189 let frames = store.frames.read();
3190 let mut count = 0;
3191 while let Some(id) = current {
3192 let Some(f) = frames.get(&id) else {
3193 break;
3194 };
3195 if f.fmt_is_blockquote == Some(true) {
3196 count += 1;
3197 }
3198 current = f.parent_frame;
3199 }
3200 count
3201}
3202
3203fn block_position_in_current_frame(cursor: &TextCursor) -> Option<BlockEdge> {
3209 let pos = cursor.position();
3210 let inner = cursor.doc.lock();
3211 let dto = frontend::document_inspection::GetBlockAtPositionDto {
3212 position: to_i64(pos),
3213 };
3214 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
3215 let block_id = block_info.block_id as common::types::EntityId;
3216 let parent_id = crate::text_block::find_parent_frame(&inner, block_info.block_id as u64)?;
3217 let store = inner.ctx.db_context.get_store();
3218 let frames = store.frames.read();
3219 let frame = frames.get(&parent_id)?;
3220 let block_positions: Vec<usize> = frame
3221 .child_order
3222 .iter()
3223 .enumerate()
3224 .filter_map(|(i, &e)| {
3225 if e > 0 {
3226 Some((i, e as common::types::EntityId))
3227 } else {
3228 None
3229 }
3230 })
3231 .filter(|(_, id)| *id == block_id)
3232 .map(|(i, _)| i)
3233 .collect();
3234 let block_idx = *block_positions.first()?;
3235 let positive_entries: Vec<usize> = frame
3236 .child_order
3237 .iter()
3238 .enumerate()
3239 .filter_map(|(i, &e)| if e > 0 { Some(i) } else { None })
3240 .collect();
3241 let first_pos = *positive_entries.first()?;
3242 let last_pos = *positive_entries.last()?;
3243 let is_first = block_idx == first_pos;
3244 let is_last = block_idx == last_pos;
3245 let edge = match (is_first, is_last, positive_entries.len()) {
3246 (_, _, 1) => BlockEdge::OnlyOne,
3247 (true, _, _) => BlockEdge::First,
3248 (_, true, _) => BlockEdge::Last,
3249 _ => BlockEdge::Middle,
3250 };
3251 Some(edge)
3252}