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, InsertionOrigin};
19use crate::flow::{CellRange, FlowElement, FrameRef, SelectionKind, TableCellRef};
20use crate::fragment::DocumentFragment;
21use crate::inner::{CursorData, QueuedEvents, TextDocumentInner};
22use crate::link_extent::LinkExtent;
23use crate::text_block::TextBlock;
24use crate::text_table::TextTable;
25use crate::{BlockFormat, FrameFormat, MoveMode, MoveOperation, SelectionType, TextFormat};
26
27use crate::document::get_main_frame_id;
28
29fn max_cursor_position(stats: &frontend::document_inspection::DocumentStatsDto) -> usize {
35 let chars = to_usize(stats.character_count);
36 let blocks = to_usize(stats.block_count);
37 if blocks > 1 {
38 chars + blocks - 1
39 } else {
40 chars
41 }
42}
43
44pub struct TextCursor {
52 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
53 pub(crate) data: Arc<Mutex<CursorData>>,
54}
55
56impl Clone for TextCursor {
57 fn clone(&self) -> Self {
58 let (position, anchor, content_locale) = {
59 let d = self.data.lock();
60 (d.position, d.anchor, d.content_locale.clone())
61 };
62 let data = {
63 let mut inner = self.doc.lock();
64 let data = Arc::new(Mutex::new(CursorData {
65 position,
66 anchor,
67 cell_selection_override: None,
68 content_locale,
70 }));
71 inner.cursors.push(Arc::downgrade(&data));
72 data
73 };
74 TextCursor {
75 doc: self.doc.clone(),
76 data,
77 }
78 }
79}
80
81impl TextCursor {
82 fn read_cursor(&self) -> (usize, usize) {
85 let d = self.data.lock();
86 (d.position, d.anchor)
87 }
88
89 fn finish_edit(
93 &self,
94 inner: &mut TextDocumentInner,
95 edit_pos: usize,
96 removed: usize,
97 new_pos: usize,
98 blocks_affected: usize,
99 ) -> QueuedEvents {
100 self.finish_edit_ext(inner, edit_pos, removed, new_pos, blocks_affected, true)
101 }
102
103 fn finish_edit_ext(
104 &self,
105 inner: &mut TextDocumentInner,
106 edit_pos: usize,
107 removed: usize,
108 new_pos: usize,
109 blocks_affected: usize,
110 flow_may_change: bool,
111 ) -> QueuedEvents {
112 self.finish_edit_from(
113 inner,
114 edit_pos,
115 removed,
116 new_pos,
117 blocks_affected,
118 flow_may_change,
119 InsertionOrigin::Unspecified,
120 )
121 }
122
123 #[allow(clippy::too_many_arguments)]
132 fn finish_edit_from(
133 &self,
134 inner: &mut TextDocumentInner,
135 edit_pos: usize,
136 removed: usize,
137 new_pos: usize,
138 blocks_affected: usize,
139 flow_may_change: bool,
140 origin: InsertionOrigin,
141 ) -> QueuedEvents {
142 let added = new_pos.saturating_sub(edit_pos);
149 inner.adjust_cursors(edit_pos, removed, added);
150 {
151 let mut d = self.data.lock();
152 d.position = new_pos;
153 d.anchor = new_pos;
154 }
155 inner.modified = true;
156 inner.invalidate_text_cache();
157 inner.rehighlight_affected(edit_pos);
158 inner.queue_event(DocumentEvent::ContentsChanged {
159 position: edit_pos,
160 chars_removed: removed,
161 chars_added: added,
162 blocks_affected,
163 });
164 if added > 0 {
168 inner.queue_event(DocumentEvent::TextInserted {
169 position: edit_pos,
170 chars_inserted: added,
171 origin,
172 });
173 }
174 inner.check_block_count_changed();
175 if flow_may_change {
176 inner.check_flow_changed();
177 }
178 self.queue_undo_redo_event(inner)
179 }
180
181 pub fn position(&self) -> usize {
185 self.data.lock().position
186 }
187
188 pub fn anchor(&self) -> usize {
190 self.data.lock().anchor
191 }
192
193 pub fn has_selection(&self) -> bool {
195 let d = self.data.lock();
196 d.position != d.anchor
197 }
198
199 pub fn selection_start(&self) -> usize {
201 let d = self.data.lock();
202 d.position.min(d.anchor)
203 }
204
205 pub fn selection_end(&self) -> usize {
207 let d = self.data.lock();
208 d.position.max(d.anchor)
209 }
210
211 pub fn selected_text(&self) -> Result<String> {
213 let (pos, anchor) = self.read_cursor();
214 if pos == anchor {
215 return Ok(String::new());
216 }
217 let start = pos.min(anchor);
218 let len = pos.max(anchor) - start;
219 let inner = self.doc.lock();
220 let dto = frontend::document_inspection::GetTextAtPositionDto {
221 position: to_i64(start),
222 length: to_i64(len),
223 };
224 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
225 Ok(result.text)
226 }
227
228 pub fn text_before(&self, max_len: usize) -> Result<String> {
241 if max_len == 0 {
242 return Ok(String::new());
243 }
244 let pos = self.position();
245 let inner = self.doc.lock();
246 let store = inner.ctx.db_context.get_store();
247
248 if pos > 0
253 && common::database::rope_helpers::find_block_at_char_position(store, 0).is_none()
254 {
255 let dto = frontend::document_inspection::GetTextAtPositionDto {
256 position: 0,
257 length: to_i64(pos),
258 };
259 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
260 let full = result.text;
261 let total = full.chars().count();
262 let skip = total.saturating_sub(max_len);
263 return Ok(full.chars().skip(skip).collect());
264 }
265
266 let mut pieces: Vec<String> = Vec::new();
267 let mut remaining = max_len;
268 let mut end_pos = pos;
269
270 while remaining > 0 && end_pos > 0 {
271 let query = (end_pos - 1) as i64;
272 let Some((block_id, char_in_block, block_char_start)) =
278 common::database::rope_helpers::find_block_at_char_position(store, query)
279 else {
280 break;
284 };
285 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
286 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
287 let entity: common::entities::Block = block_dto.into();
288 let block_text =
289 common::database::rope_helpers::block_content_via_store(&entity, store);
290 let block_len = block_text.chars().count() as i64;
291 let block_char_start = block_char_start as usize;
292
293 if char_in_block == block_len {
294 pieces.push("\n".to_string());
296 remaining -= 1;
297 if remaining == 0 {
298 break;
299 }
300 let take = remaining.min(block_len as usize);
301 let local_start = block_len as usize - take;
302 let slice: String = block_text.chars().skip(local_start).take(take).collect();
303 pieces.push(slice);
304 remaining -= take;
305 end_pos = block_char_start + local_start;
306 } else {
307 let available = char_in_block as usize + 1;
309 let take = remaining.min(available);
310 let local_start = available - take;
311 let slice: String = block_text.chars().skip(local_start).take(take).collect();
312 pieces.push(slice);
313 remaining -= take;
314 end_pos = block_char_start + local_start;
315 }
316 }
317
318 pieces.reverse();
319 Ok(pieces.concat())
320 }
321
322 pub fn clear_selection(&self) {
324 let mut d = self.data.lock();
325 d.anchor = d.position;
326 }
327
328 pub fn at_block_start(&self) -> bool {
332 let pos = self.position();
333 let inner = self.doc.lock();
334 let dto = frontend::document_inspection::GetBlockAtPositionDto {
335 position: to_i64(pos),
336 };
337 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
338 pos == to_usize(info.block_start)
339 } else {
340 false
341 }
342 }
343
344 pub fn at_block_end(&self) -> bool {
346 let pos = self.position();
347 let inner = self.doc.lock();
348 let dto = frontend::document_inspection::GetBlockAtPositionDto {
349 position: to_i64(pos),
350 };
351 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
352 pos == to_usize(info.block_start) + to_usize(info.block_length)
353 } else {
354 false
355 }
356 }
357
358 pub fn at_start(&self) -> bool {
360 self.data.lock().position == 0
361 }
362
363 pub fn at_end(&self) -> bool {
365 let pos = self.position();
366 let inner = self.doc.lock();
367 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
368 frontend::document_inspection::DocumentStatsDto {
369 character_count: 0,
370 word_count: 0,
371 block_count: 0,
372 frame_count: 0,
373 image_count: 0,
374 list_count: 0,
375 table_count: 0,
376 }
377 });
378 pos >= max_cursor_position(&stats)
379 }
380
381 pub fn block_number(&self) -> usize {
383 let pos = self.position();
384 let inner = self.doc.lock();
385 let dto = frontend::document_inspection::GetBlockAtPositionDto {
386 position: to_i64(pos),
387 };
388 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
389 .map(|info| to_usize(info.block_number))
390 .unwrap_or(0)
391 }
392
393 pub fn position_in_block(&self) -> usize {
395 let pos = self.position();
396 let inner = self.doc.lock();
397 let dto = frontend::document_inspection::GetBlockAtPositionDto {
398 position: to_i64(pos),
399 };
400 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
401 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
402 .unwrap_or(0)
403 }
404
405 pub fn set_position(&self, position: usize, mode: MoveMode) {
419 let end = {
421 let inner = self.doc.lock();
422 document_inspection_commands::get_document_stats(&inner.ctx)
423 .map(|s| max_cursor_position(&s))
424 .unwrap_or(0)
425 };
426 let mut pos = position.min(end);
427
428 if mode == MoveMode::KeepAnchor {
432 let anchor = self.data.lock().anchor;
433 let pos_cell = self.table_cell_at(pos);
434 let anchor_cell = self.table_cell_at(anchor);
435 match (&pos_cell, &anchor_cell) {
436 (Some(tc), None) => {
437 let before = anchor < pos;
439 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
440 pos = boundary;
441 }
442 }
443 (None, Some(tc)) => {
444 let before = pos < anchor;
447 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
448 pos = boundary;
449 }
450 }
451 _ => {}
452 }
453 }
454
455 {
456 let mut d = self.data.lock();
457 d.position = pos;
458 if mode == MoveMode::MoveAnchor {
459 d.anchor = pos;
460 }
461 d.cell_selection_override = None;
462 }
463 self.snap_position_to_grapheme_boundary();
468 }
469
470 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
476 let old_pos = self.position();
477 let target = self.resolve_move(operation, n);
478 self.set_position(target, mode);
479 self.position() != old_pos
480 }
481
482 pub fn select(&self, selection: SelectionType) {
484 match selection {
485 SelectionType::Document => {
486 let end = {
487 let inner = self.doc.lock();
488 document_inspection_commands::get_document_stats(&inner.ctx)
489 .map(|s| max_cursor_position(&s))
490 .unwrap_or(0)
491 };
492 let mut d = self.data.lock();
493 d.anchor = 0;
494 d.position = end;
495 d.cell_selection_override = None;
496 }
497 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
498 let pos = self.position();
499 let inner = self.doc.lock();
500 let dto = frontend::document_inspection::GetBlockAtPositionDto {
501 position: to_i64(pos),
502 };
503 if let Ok(info) =
504 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
505 {
506 let start = to_usize(info.block_start);
507 let end = start + to_usize(info.block_length);
508 drop(inner);
509 let mut d = self.data.lock();
510 d.anchor = start;
511 d.position = end;
512 d.cell_selection_override = None;
513 }
514 }
515 SelectionType::WordUnderCursor => {
516 let pos = self.position();
517 let (word_start, word_end) = self.find_word_boundaries(pos);
518 let mut d = self.data.lock();
519 d.anchor = word_start;
520 d.position = word_end;
521 d.cell_selection_override = None;
522 }
523 SelectionType::SentenceUnderCursor => {
524 let pos = self.position();
525 if let Some((start, end)) = self.find_sentence_boundaries(pos) {
528 let mut d = self.data.lock();
529 d.anchor = start;
530 d.position = end;
531 d.cell_selection_override = None;
532 }
533 }
534 }
535 }
536
537 pub fn set_content_locale(&self, locale: Option<&str>) {
546 self.data.lock().content_locale = locale.map(str::to_string);
547 }
548
549 pub fn content_locale(&self) -> Option<String> {
551 self.data.lock().content_locale.clone()
552 }
553
554 pub fn insert_text(&self, text: &str) -> Result<()> {
561 self.insert_text_with_origin(text, InsertionOrigin::Unspecified)
562 }
563
564 pub fn insert_text_with_origin(&self, text: &str, origin: InsertionOrigin) -> Result<()> {
569 let (pos, anchor) = self.read_cursor();
570
571 let dto = frontend::document_editing::InsertTextDto {
573 format_policy: Default::default(),
574 position: to_i64(pos),
575 anchor: to_i64(anchor),
576 text: text.into(),
577 };
578
579 let queued = {
580 let mut inner = self.doc.lock();
581 let result = match document_editing_commands::insert_text(
582 &inner.ctx,
583 Some(inner.stack_id),
584 &dto,
585 ) {
586 Ok(r) => r,
587 Err(_) if pos != anchor => {
588 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
590
591 let del_dto = frontend::document_editing::DeleteTextDto {
592 position: to_i64(pos),
593 anchor: to_i64(anchor),
594 };
595 let del_result = document_editing_commands::delete_text(
596 &inner.ctx,
597 Some(inner.stack_id),
598 &del_dto,
599 )?;
600 let del_pos = to_usize(del_result.new_position);
601
602 let ins_dto = frontend::document_editing::InsertTextDto {
603 format_policy: Default::default(),
604 position: to_i64(del_pos),
605 anchor: to_i64(del_pos),
606 text: text.into(),
607 };
608 let ins_result = document_editing_commands::insert_text(
609 &inner.ctx,
610 Some(inner.stack_id),
611 &ins_dto,
612 )?;
613
614 undo_redo_commands::end_composite(&inner.ctx);
615 ins_result
616 }
617 Err(e) => return Err(e.into()),
618 };
619
620 let edit_pos = pos.min(anchor);
621 let removed = pos.max(anchor) - edit_pos;
622 self.finish_edit_from(
623 &mut inner,
624 edit_pos,
625 removed,
626 to_usize(result.new_position),
627 to_usize(result.blocks_affected),
628 false,
629 origin,
630 )
631 };
632 crate::inner::dispatch_queued_events(queued);
633 Ok(())
634 }
635
636 pub fn replace(
650 &self,
651 start: usize,
652 end: usize,
653 text: &str,
654 policy: crate::ReplaceFormatPolicy,
655 ) -> Result<()> {
656 let (pos, anchor) = (start, end);
657
658 let dto = frontend::document_editing::InsertTextDto {
659 format_policy: policy,
660 position: to_i64(pos),
661 anchor: to_i64(anchor),
662 text: text.into(),
663 };
664
665 let queued = {
666 let mut inner = self.doc.lock();
667 let result = match document_editing_commands::insert_text(
668 &inner.ctx,
669 Some(inner.stack_id),
670 &dto,
671 ) {
672 Ok(r) => r,
673 Err(_) if pos != anchor => {
674 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
678
679 let del_dto = frontend::document_editing::DeleteTextDto {
680 position: to_i64(pos),
681 anchor: to_i64(anchor),
682 };
683 let del_result = document_editing_commands::delete_text(
684 &inner.ctx,
685 Some(inner.stack_id),
686 &del_dto,
687 )?;
688 let del_pos = to_usize(del_result.new_position);
689
690 let ins_dto = frontend::document_editing::InsertTextDto {
691 format_policy: Default::default(),
692 position: to_i64(del_pos),
693 anchor: to_i64(del_pos),
694 text: text.into(),
695 };
696 let ins_result = document_editing_commands::insert_text(
697 &inner.ctx,
698 Some(inner.stack_id),
699 &ins_dto,
700 )?;
701
702 undo_redo_commands::end_composite(&inner.ctx);
703 ins_result
704 }
705 Err(e) => return Err(e.into()),
706 };
707
708 let edit_pos = pos.min(anchor);
709 let removed = pos.max(anchor) - edit_pos;
710 self.finish_edit_ext(
711 &mut inner,
712 edit_pos,
713 removed,
714 to_usize(result.new_position),
715 to_usize(result.blocks_affected),
716 false,
717 )
718 };
719 crate::inner::dispatch_queued_events(queued);
720 Ok(())
721 }
722
723 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
726 self.insert_formatted_text_with_origin(text, format, InsertionOrigin::Unspecified)
727 }
728
729 pub fn insert_formatted_text_with_origin(
732 &self,
733 text: &str,
734 format: &TextFormat,
735 origin: InsertionOrigin,
736 ) -> Result<()> {
737 let (pos, anchor) = self.read_cursor();
738
739 let make_dto = |p: usize, a: usize| frontend::document_editing::InsertFormattedTextDto {
740 position: to_i64(p),
741 anchor: to_i64(a),
742 text: text.into(),
743 font_family: format.font_family.clone().unwrap_or_default(),
744 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
745 font_bold: format.font_bold.unwrap_or(false),
746 font_italic: format.font_italic.unwrap_or(false),
747 font_underline: format.font_underline.unwrap_or(false),
748 font_strikeout: format.font_strikeout.unwrap_or(false),
749 };
750
751 let queued = {
752 let mut inner = self.doc.lock();
753 let result = match document_editing_commands::insert_formatted_text(
754 &inner.ctx,
755 Some(inner.stack_id),
756 &make_dto(pos, anchor),
757 ) {
758 Ok(r) => r,
759 Err(_) if pos != anchor => {
760 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
762
763 let del_dto = frontend::document_editing::DeleteTextDto {
764 position: to_i64(pos),
765 anchor: to_i64(anchor),
766 };
767 let del_result = document_editing_commands::delete_text(
768 &inner.ctx,
769 Some(inner.stack_id),
770 &del_dto,
771 )?;
772 let del_pos = to_usize(del_result.new_position);
773
774 let ins_result = document_editing_commands::insert_formatted_text(
775 &inner.ctx,
776 Some(inner.stack_id),
777 &make_dto(del_pos, del_pos),
778 )?;
779
780 undo_redo_commands::end_composite(&inner.ctx);
781 ins_result
782 }
783 Err(e) => return Err(e.into()),
784 };
785
786 let edit_pos = pos.min(anchor);
787 let removed = pos.max(anchor) - edit_pos;
788 self.finish_edit_from(
789 &mut inner,
790 edit_pos,
791 removed,
792 to_usize(result.new_position),
793 1,
794 false,
795 origin,
796 )
797 };
798 crate::inner::dispatch_queued_events(queued);
799 Ok(())
800 }
801
802 pub fn insert_block(&self) -> Result<()> {
804 let (pos, anchor) = self.read_cursor();
805 let queued = {
806 let mut inner = self.doc.lock();
807
808 let (insert_pos, removed) = if pos != anchor {
809 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
811 let del_dto = frontend::document_editing::DeleteTextDto {
812 position: to_i64(pos),
813 anchor: to_i64(anchor),
814 };
815 let del_result = document_editing_commands::delete_text(
816 &inner.ctx,
817 Some(inner.stack_id),
818 &del_dto,
819 )?;
820 (
821 to_usize(del_result.new_position),
822 pos.max(anchor) - pos.min(anchor),
823 )
824 } else {
825 (pos, 0)
826 };
827
828 let dto = frontend::document_editing::InsertBlockDto {
829 position: to_i64(insert_pos),
830 anchor: to_i64(insert_pos),
831 };
832 let result =
833 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
834
835 if pos != anchor {
836 undo_redo_commands::end_composite(&inner.ctx);
837 }
838
839 let edit_pos = pos.min(anchor);
840 self.finish_edit(
841 &mut inner,
842 edit_pos,
843 removed,
844 to_usize(result.new_position),
845 2,
846 )
847 };
848 crate::inner::dispatch_queued_events(queued);
849 Ok(())
850 }
851
852 pub fn insert_html_with_origin(&self, html: &str, origin: InsertionOrigin) -> Result<()> {
856 let frag = DocumentFragment::from_html(html);
857 self.insert_fragment_with_origin(&frag, origin)
858 }
859
860 pub fn insert_html(&self, html: &str) -> Result<()> {
861 let frag = DocumentFragment::from_html(html);
863 self.insert_fragment(&frag)
864 }
865
866 pub fn insert_markdown_with_origin(
870 &self,
871 markdown: &str,
872 origin: InsertionOrigin,
873 ) -> Result<()> {
874 let frag = DocumentFragment::from_markdown(markdown);
875 self.insert_fragment_with_origin(&frag, origin)
876 }
877
878 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
879 let frag = DocumentFragment::from_markdown(markdown);
880 self.insert_fragment(&frag)
881 }
882
883 pub fn insert_djot_with_origin(&self, djot: &str, origin: InsertionOrigin) -> Result<()> {
887 let frag = DocumentFragment::from_djot(djot);
888 self.insert_fragment_with_origin(&frag, origin)
889 }
890
891 pub fn insert_djot(&self, djot: &str) -> Result<()> {
892 let frag = DocumentFragment::from_djot(djot);
893 self.insert_fragment(&frag)
894 }
895
896 pub fn insert_footnote_reference(&self, label: &str) -> Result<()> {
909 self.insert_djot(&format!("[^{label}]"))
910 }
911
912 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
915 self.insert_fragment_with_origin(fragment, InsertionOrigin::Unspecified)
916 }
917
918 pub fn insert_fragment_with_origin(
922 &self,
923 fragment: &DocumentFragment,
924 origin: InsertionOrigin,
925 ) -> Result<()> {
926 let (pos, anchor) = self.read_cursor();
927 let queued = {
928 let mut inner = self.doc.lock();
929
930 let (insert_pos, removed) = if pos != anchor {
931 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
932 let del_dto = frontend::document_editing::DeleteTextDto {
933 position: to_i64(pos),
934 anchor: to_i64(anchor),
935 };
936 let del_result = document_editing_commands::delete_text(
937 &inner.ctx,
938 Some(inner.stack_id),
939 &del_dto,
940 )?;
941 (
942 to_usize(del_result.new_position),
943 pos.max(anchor) - pos.min(anchor),
944 )
945 } else {
946 (pos, 0)
947 };
948
949 let dto = frontend::document_editing::InsertFragmentDto {
950 position: to_i64(insert_pos),
951 anchor: to_i64(insert_pos),
952 fragment_data: fragment.raw_data().into(),
953 };
954 let result =
955 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
956
957 if pos != anchor {
958 undo_redo_commands::end_composite(&inner.ctx);
959 }
960
961 let edit_pos = pos.min(anchor);
962 self.finish_edit_from(
963 &mut inner,
964 edit_pos,
965 removed,
966 to_usize(result.new_position),
967 to_usize(result.blocks_added),
968 true,
969 origin,
970 )
971 };
972 crate::inner::dispatch_queued_events(queued);
973 Ok(())
974 }
975
976 pub fn selection(&self) -> DocumentFragment {
978 let (pos, anchor) = self.read_cursor();
979
980 let (extract_pos, extract_anchor) = match self.selection_kind() {
983 SelectionKind::Cells(ref range) => match self.cell_range_positions(range) {
984 Some((start, end)) => (start, end),
985 None => return DocumentFragment::new(),
986 },
987 SelectionKind::Mixed {
988 ref cell_range,
989 text_before,
990 text_after,
991 } => {
992 let (cell_start, cell_end) = match self.cell_range_positions(cell_range) {
993 Some(p) => p,
994 None => return DocumentFragment::new(),
995 };
996 let start = if text_before {
997 pos.min(anchor)
998 } else {
999 cell_start
1000 };
1001 let end = if text_after {
1002 pos.max(anchor)
1003 } else {
1004 cell_end
1005 };
1006 (start.min(cell_start), end.max(cell_end))
1007 }
1008 SelectionKind::None => return DocumentFragment::new(),
1009 SelectionKind::Text => (pos, anchor),
1010 };
1011
1012 if extract_pos == extract_anchor {
1013 return DocumentFragment::new();
1014 }
1015
1016 let inner = self.doc.lock();
1017 let dto = frontend::document_inspection::ExtractFragmentDto {
1018 position: to_i64(extract_pos),
1019 anchor: to_i64(extract_anchor),
1020 };
1021 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
1022 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
1023 Err(_) => DocumentFragment::new(),
1024 }
1025 }
1026
1027 pub fn insert_image(&self, name: &str, alt: &str, width: u32, height: u32) -> Result<()> {
1033 let (pos, anchor) = self.read_cursor();
1034 let queued = {
1035 let mut inner = self.doc.lock();
1036
1037 let (insert_pos, removed) = if pos != anchor {
1038 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1039 let del_dto = frontend::document_editing::DeleteTextDto {
1040 position: to_i64(pos),
1041 anchor: to_i64(anchor),
1042 };
1043 let del_result = document_editing_commands::delete_text(
1044 &inner.ctx,
1045 Some(inner.stack_id),
1046 &del_dto,
1047 )?;
1048 (
1049 to_usize(del_result.new_position),
1050 pos.max(anchor) - pos.min(anchor),
1051 )
1052 } else {
1053 (pos, 0)
1054 };
1055
1056 let dto = frontend::document_editing::InsertImageDto {
1057 position: to_i64(insert_pos),
1058 anchor: to_i64(insert_pos),
1059 image_name: name.into(),
1060 alt: alt.into(),
1061 width: width as i64,
1062 height: height as i64,
1063 quality: 100,
1064 };
1065 let result =
1066 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
1067
1068 if pos != anchor {
1069 undo_redo_commands::end_composite(&inner.ctx);
1070 }
1071
1072 let edit_pos = pos.min(anchor);
1073 self.finish_edit_ext(
1074 &mut inner,
1075 edit_pos,
1076 removed,
1077 to_usize(result.new_position),
1078 1,
1079 false,
1080 )
1081 };
1082 crate::inner::dispatch_queued_events(queued);
1083 Ok(())
1084 }
1085
1086 pub fn insert_frame(&self) -> Result<()> {
1088 let (pos, anchor) = self.read_cursor();
1089 let queued = {
1090 let mut inner = self.doc.lock();
1091 let dto = frontend::document_editing::InsertFrameDto {
1092 position: to_i64(pos),
1093 anchor: to_i64(anchor),
1094 };
1095 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
1096 inner.modified = true;
1099 inner.invalidate_text_cache();
1100 inner.rehighlight_affected(pos.min(anchor));
1101 inner.queue_event(DocumentEvent::ContentsChanged {
1102 position: pos.min(anchor),
1103 chars_removed: 0,
1104 chars_added: 0,
1105 blocks_affected: 1,
1106 });
1107 inner.check_block_count_changed();
1108 inner.check_flow_changed();
1109 self.queue_undo_redo_event(&mut inner)
1110 };
1111 crate::inner::dispatch_queued_events(queued);
1112 Ok(())
1113 }
1114
1115 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
1121 let (pos, anchor) = self.read_cursor();
1122 let (table_id, queued) = {
1123 let mut inner = self.doc.lock();
1124 let dto = frontend::document_editing::InsertTableDto {
1125 position: to_i64(pos),
1126 anchor: to_i64(anchor),
1127 rows: to_i64(rows),
1128 columns: to_i64(columns),
1129 };
1130 let result =
1131 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1132 let new_pos = to_usize(result.new_position);
1133 let table_id = to_usize(result.table_id);
1134 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
1135 {
1136 let mut d = self.data.lock();
1137 d.position = new_pos;
1138 d.anchor = new_pos;
1139 }
1140 inner.modified = true;
1141 inner.invalidate_text_cache();
1142 inner.rehighlight_affected(pos.min(anchor));
1143 inner.queue_event(DocumentEvent::ContentsChanged {
1144 position: pos.min(anchor),
1145 chars_removed: 0,
1146 chars_added: new_pos - pos.min(anchor),
1147 blocks_affected: 1,
1148 });
1149 inner.check_block_count_changed();
1150 inner.check_flow_changed();
1151 (table_id, self.queue_undo_redo_event(&mut inner))
1152 };
1153 crate::inner::dispatch_queued_events(queued);
1154 Ok(TextTable {
1155 doc: self.doc.clone(),
1156 table_id,
1157 })
1158 }
1159
1160 pub fn current_table(&self) -> Option<TextTable> {
1165 self.current_table_cell().map(|c| c.table)
1166 }
1167
1168 pub fn current_table_cell(&self) -> Option<TableCellRef> {
1173 let pos = self.position();
1174 let inner = self.doc.lock();
1175 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1177 position: to_i64(pos),
1178 };
1179 let block_info =
1180 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1181
1182 let block_id = if to_i64(pos) < block_info.block_start && pos > 0 {
1186 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1187 position: to_i64(pos - 1),
1188 };
1189 let prev_info =
1190 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
1191 prev_info.block_id as usize
1192 } else {
1193 block_info.block_id as usize
1194 };
1195
1196 let block = crate::text_block::TextBlock {
1197 doc: self.doc.clone(),
1198 block_id,
1199 };
1200 drop(inner);
1202 block.table_cell()
1203 }
1204
1205 pub fn current_frame(&self) -> Option<FrameRef> {
1212 let pos = self.position();
1213 let inner = self.doc.lock();
1214 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1215 position: to_i64(pos),
1216 };
1217 let block_info =
1218 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1219 let block_id = block_info.block_id as u64;
1220 cursor_frame_ref(&inner, block_id)
1221 }
1222
1223 pub fn is_in_blockquote(&self) -> bool {
1226 self.current_blockquote_frame_id().is_some()
1227 }
1228
1229 pub fn current_blockquote_frame_id(&self) -> Option<usize> {
1232 let pos = self.position();
1233 let inner = self.doc.lock();
1234 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1235 position: to_i64(pos),
1236 };
1237 let block_info =
1238 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1239 innermost_blockquote_frame_id(&inner, block_info.block_id as u64)
1240 }
1241
1242 pub fn blockquote_depth_at_cursor(&self) -> usize {
1245 let pos = self.position();
1246 let inner = self.doc.lock();
1247 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1248 position: to_i64(pos),
1249 };
1250 let Some(block_info) =
1251 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
1252 else {
1253 return 0;
1254 };
1255 blockquote_depth_for_block(&inner, block_info.block_id as u64)
1256 }
1257
1258 pub fn is_first_block_in_current_frame(&self) -> bool {
1264 matches!(
1265 block_position_in_current_frame(self),
1266 Some(BlockEdge::First) | Some(BlockEdge::OnlyOne)
1267 )
1268 }
1269
1270 pub fn is_last_block_in_current_frame(&self) -> bool {
1274 matches!(
1275 block_position_in_current_frame(self),
1276 Some(BlockEdge::Last) | Some(BlockEdge::OnlyOne)
1277 )
1278 }
1279
1280 pub fn current_block_is_empty(&self) -> bool {
1283 let pos = self.position();
1284 let inner = self.doc.lock();
1285 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1286 position: to_i64(pos),
1287 };
1288 let Some(block_info) =
1289 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
1290 else {
1291 return false;
1292 };
1293 let store = inner.ctx.db_context.get_store();
1294 let block_entity = store
1295 .blocks
1296 .read()
1297 .get(&(block_info.block_id as common::types::EntityId))
1298 .cloned();
1299 match block_entity {
1300 Some(b) => {
1301 let len = common::database::rope_helpers::block_char_length(&b, store);
1302 len == 0
1303 }
1304 None => false,
1305 }
1306 }
1307
1308 pub fn selection_spans_multiple_frames(&self) -> bool {
1312 let (pos, anchor) = self.read_cursor();
1313 if pos == anchor {
1314 return false;
1315 }
1316 let inner = self.doc.lock();
1317 let pos_dto = frontend::document_inspection::GetBlockAtPositionDto {
1318 position: to_i64(pos),
1319 };
1320 let anchor_dto = frontend::document_inspection::GetBlockAtPositionDto {
1321 position: to_i64(anchor),
1322 };
1323 let Some(pos_block) =
1324 document_inspection_commands::get_block_at_position(&inner.ctx, &pos_dto).ok()
1325 else {
1326 return false;
1327 };
1328 let Some(anchor_block) =
1329 document_inspection_commands::get_block_at_position(&inner.ctx, &anchor_dto).ok()
1330 else {
1331 return false;
1332 };
1333 let pos_owner = crate::text_block::find_parent_frame(&inner, pos_block.block_id as u64);
1334 let anchor_owner =
1335 crate::text_block::find_parent_frame(&inner, anchor_block.block_id as u64);
1336 pos_owner != anchor_owner
1337 }
1338
1339 pub fn wrap_selection_in_blockquote(&self) -> Result<()> {
1346 if self.selection_spans_multiple_frames() {
1347 return Err(DocumentError::InvalidArgument(
1348 "Cannot wrap selection in blockquote: selection spans multiple frames".into(),
1349 ));
1350 }
1351 let (start_block_id, end_block_id) = self.resolve_selection_block_range()?;
1352 let dto = frontend::document_editing::WrapBlocksInFrameDto {
1353 start_block_id: start_block_id as i64,
1354 end_block_id: end_block_id as i64,
1355 position: Some(frontend::document_editing::FramePosition::InFlow),
1356 top_margin: None,
1357 bottom_margin: None,
1358 left_margin: None,
1359 right_margin: None,
1360 padding: None,
1361 border: None,
1362 is_blockquote: Some(true),
1363 };
1364 let queued = {
1365 let mut inner = self.doc.lock();
1366 let _result = document_editing_commands::wrap_blocks_in_frame(
1367 &inner.ctx,
1368 Some(inner.stack_id),
1369 &dto,
1370 )?;
1371 inner.modified = true;
1372 inner.queue_event(DocumentEvent::FormatChanged {
1380 position: 0,
1381 length: 0,
1382 kind: crate::flow::FormatChangeKind::Block,
1383 });
1384 self.queue_undo_redo_event(&mut inner)
1385 };
1386 crate::inner::dispatch_queued_events(queued);
1387 Ok(())
1388 }
1389
1390 pub fn insert_blockquote(&self) -> Result<()> {
1394 self.wrap_selection_in_blockquote()
1395 }
1396
1397 pub fn toggle_blockquote(&self) -> Result<()> {
1402 if let Some(frame_id) = self.current_blockquote_frame_id() {
1403 self.unwrap_frame_by_id(frame_id)
1404 } else {
1405 self.wrap_selection_in_blockquote()
1406 }
1407 }
1408
1409 pub fn unwrap_current_frame(&self) -> Result<()> {
1413 let frame_ref = self.current_frame().ok_or_else(|| {
1414 DocumentError::InvalidCursorContext("Cursor is not inside any sub-frame".into())
1415 })?;
1416 self.unwrap_frame_by_id(frame_ref.frame_id)
1417 }
1418
1419 pub fn unwrap_current_block_from_blockquote(&self) -> Result<()> {
1423 if self.current_blockquote_frame_id().is_none() {
1424 return Err(DocumentError::InvalidCursorContext(
1425 "Cursor is not inside a blockquote".into(),
1426 ));
1427 }
1428 let block_id = self.current_block_id_for_mutation()?;
1429 let dto = frontend::document_editing::UnwrapBlockFromFrameDto {
1430 block_id: block_id as i64,
1431 };
1432 let queued = {
1433 let mut inner = self.doc.lock();
1434 let _result = document_editing_commands::unwrap_block_from_frame(
1435 &inner.ctx,
1436 Some(inner.stack_id),
1437 &dto,
1438 )?;
1439 inner.modified = true;
1440 inner.queue_event(DocumentEvent::FormatChanged {
1444 position: 0,
1445 length: 0,
1446 kind: crate::flow::FormatChangeKind::Block,
1447 });
1448 self.queue_undo_redo_event(&mut inner)
1449 };
1450 crate::inner::dispatch_queued_events(queued);
1451 Ok(())
1452 }
1453
1454 pub fn increase_blockquote_depth(&self) -> Result<()> {
1458 self.wrap_selection_in_blockquote()
1459 }
1460
1461 pub fn decrease_blockquote_depth(&self) -> Result<()> {
1467 if self.current_blockquote_frame_id().is_none() {
1468 return Err(DocumentError::InvalidCursorContext(
1469 "Cursor is not inside a blockquote to decrease depth".into(),
1470 ));
1471 }
1472 self.unwrap_current_block_from_blockquote()
1473 }
1474
1475 fn unwrap_frame_by_id(&self, frame_id: usize) -> Result<()> {
1476 let dto = frontend::document_editing::UnwrapFrameDto {
1477 frame_id: frame_id as i64,
1478 };
1479 let queued = {
1480 let mut inner = self.doc.lock();
1481 let _result =
1482 document_editing_commands::unwrap_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
1483 inner.modified = true;
1484 inner.queue_event(DocumentEvent::FormatChanged {
1488 position: 0,
1489 length: 0,
1490 kind: crate::flow::FormatChangeKind::Block,
1491 });
1492 self.queue_undo_redo_event(&mut inner)
1493 };
1494 crate::inner::dispatch_queued_events(queued);
1495 Ok(())
1496 }
1497
1498 fn current_block_id_for_mutation(&self) -> Result<usize> {
1499 let pos = self.position();
1500 let inner = self.doc.lock();
1501 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1502 position: to_i64(pos),
1503 };
1504 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1505 .map_err(|e| anyhow::anyhow!("get_block_at_position: {}", e))?;
1506 Ok(block_info.block_id as usize)
1507 }
1508
1509 fn resolve_selection_block_range(&self) -> Result<(usize, usize)> {
1510 let (pos, anchor) = self.read_cursor();
1511 let lo = pos.min(anchor);
1512 let hi = pos.max(anchor);
1513 let inner = self.doc.lock();
1514 let lo_dto = frontend::document_inspection::GetBlockAtPositionDto {
1515 position: to_i64(lo),
1516 };
1517 let hi_dto = frontend::document_inspection::GetBlockAtPositionDto {
1518 position: to_i64(hi),
1519 };
1520 let lo_block = document_inspection_commands::get_block_at_position(&inner.ctx, &lo_dto)
1521 .map_err(|e| anyhow::anyhow!("get_block_at_position(start): {}", e))?;
1522 let hi_block = document_inspection_commands::get_block_at_position(&inner.ctx, &hi_dto)
1523 .map_err(|e| anyhow::anyhow!("get_block_at_position(end): {}", e))?;
1524 Ok((lo_block.block_id as usize, hi_block.block_id as usize))
1525 }
1526
1527 pub fn remove_table(&self, table_id: usize) -> Result<()> {
1531 let queued = {
1532 let mut inner = self.doc.lock();
1533 let before = crate::document::capture_block_state(&inner);
1540 let dto = frontend::document_editing::RemoveTableDto {
1541 table_id: to_i64(table_id),
1542 };
1543 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1544 inner.modified = true;
1545 inner.invalidate_text_cache();
1546 inner.rehighlight_all();
1547 crate::document::emit_content_change_events(&mut inner, &before);
1548 inner.check_block_count_changed();
1549 inner.check_flow_changed();
1550 self.queue_undo_redo_event(&mut inner)
1551 };
1552 crate::inner::dispatch_queued_events(queued);
1553 Ok(())
1554 }
1555
1556 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1558 let queued = {
1559 let mut inner = self.doc.lock();
1560 let before = crate::document::capture_block_state(&inner);
1561 let dto = frontend::document_editing::InsertTableRowDto {
1562 table_id: to_i64(table_id),
1563 row_index: to_i64(row_index),
1564 };
1565 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1566 inner.modified = true;
1567 inner.invalidate_text_cache();
1568 inner.rehighlight_all();
1569 crate::document::emit_content_change_events(&mut inner, &before);
1570 inner.check_block_count_changed();
1571 self.queue_undo_redo_event(&mut inner)
1572 };
1573 crate::inner::dispatch_queued_events(queued);
1574 Ok(())
1575 }
1576
1577 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1579 let queued = {
1580 let mut inner = self.doc.lock();
1581 let before = crate::document::capture_block_state(&inner);
1582 let dto = frontend::document_editing::InsertTableColumnDto {
1583 table_id: to_i64(table_id),
1584 column_index: to_i64(column_index),
1585 };
1586 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1587 inner.modified = true;
1588 inner.invalidate_text_cache();
1589 inner.rehighlight_all();
1590 crate::document::emit_content_change_events(&mut inner, &before);
1591 inner.check_block_count_changed();
1592 self.queue_undo_redo_event(&mut inner)
1593 };
1594 crate::inner::dispatch_queued_events(queued);
1595 Ok(())
1596 }
1597
1598 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1600 let queued = {
1601 let mut inner = self.doc.lock();
1602 let before = crate::document::capture_block_state(&inner);
1603 let dto = frontend::document_editing::RemoveTableRowDto {
1604 table_id: to_i64(table_id),
1605 row_index: to_i64(row_index),
1606 };
1607 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1608 inner.modified = true;
1609 inner.invalidate_text_cache();
1610 inner.rehighlight_all();
1611 crate::document::emit_content_change_events(&mut inner, &before);
1612 inner.check_block_count_changed();
1613 self.queue_undo_redo_event(&mut inner)
1614 };
1615 crate::inner::dispatch_queued_events(queued);
1616 Ok(())
1617 }
1618
1619 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1621 let queued = {
1622 let mut inner = self.doc.lock();
1623 let before = crate::document::capture_block_state(&inner);
1624 let dto = frontend::document_editing::RemoveTableColumnDto {
1625 table_id: to_i64(table_id),
1626 column_index: to_i64(column_index),
1627 };
1628 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1629 inner.modified = true;
1630 inner.invalidate_text_cache();
1631 inner.rehighlight_all();
1632 crate::document::emit_content_change_events(&mut inner, &before);
1633 inner.check_block_count_changed();
1634 self.queue_undo_redo_event(&mut inner)
1635 };
1636 crate::inner::dispatch_queued_events(queued);
1637 Ok(())
1638 }
1639
1640 pub fn merge_table_cells(
1642 &self,
1643 table_id: usize,
1644 start_row: usize,
1645 start_column: usize,
1646 end_row: usize,
1647 end_column: usize,
1648 ) -> Result<()> {
1649 let queued = {
1650 let mut inner = self.doc.lock();
1651 let before = crate::document::capture_block_state(&inner);
1652 let dto = frontend::document_editing::MergeTableCellsDto {
1653 table_id: to_i64(table_id),
1654 start_row: to_i64(start_row),
1655 start_column: to_i64(start_column),
1656 end_row: to_i64(end_row),
1657 end_column: to_i64(end_column),
1658 };
1659 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
1660 inner.modified = true;
1661 inner.invalidate_text_cache();
1662 inner.rehighlight_all();
1663 crate::document::emit_content_change_events(&mut inner, &before);
1664 inner.check_block_count_changed();
1665 self.queue_undo_redo_event(&mut inner)
1666 };
1667 crate::inner::dispatch_queued_events(queued);
1668 Ok(())
1669 }
1670
1671 pub fn split_table_cell(
1673 &self,
1674 cell_id: usize,
1675 split_rows: usize,
1676 split_columns: usize,
1677 ) -> Result<()> {
1678 let queued = {
1679 let mut inner = self.doc.lock();
1680 let before = crate::document::capture_block_state(&inner);
1681 let dto = frontend::document_editing::SplitTableCellDto {
1682 cell_id: to_i64(cell_id),
1683 split_rows: to_i64(split_rows),
1684 split_columns: to_i64(split_columns),
1685 };
1686 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
1687 inner.modified = true;
1688 inner.invalidate_text_cache();
1689 inner.rehighlight_all();
1690 crate::document::emit_content_change_events(&mut inner, &before);
1691 inner.check_block_count_changed();
1692 self.queue_undo_redo_event(&mut inner)
1693 };
1694 crate::inner::dispatch_queued_events(queued);
1695 Ok(())
1696 }
1697
1698 pub fn set_table_format(
1702 &self,
1703 table_id: usize,
1704 format: &crate::flow::TableFormat,
1705 ) -> Result<()> {
1706 let queued = {
1707 let mut inner = self.doc.lock();
1708 let dto = format.to_set_dto(table_id);
1709 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1710 inner.modified = true;
1711 inner.queue_event(DocumentEvent::FormatChanged {
1712 position: 0,
1713 length: 0,
1714 kind: crate::flow::FormatChangeKind::Block,
1715 });
1716 self.queue_undo_redo_event(&mut inner)
1717 };
1718 crate::inner::dispatch_queued_events(queued);
1719 Ok(())
1720 }
1721
1722 pub fn set_table_cell_format(
1724 &self,
1725 cell_id: usize,
1726 format: &crate::flow::CellFormat,
1727 ) -> Result<()> {
1728 let queued = {
1729 let mut inner = self.doc.lock();
1730 let dto = format.to_set_dto(cell_id);
1731 document_formatting_commands::set_table_cell_format(
1732 &inner.ctx,
1733 Some(inner.stack_id),
1734 &dto,
1735 )?;
1736 inner.modified = true;
1737 inner.queue_event(DocumentEvent::FormatChanged {
1738 position: 0,
1739 length: 0,
1740 kind: crate::flow::FormatChangeKind::Block,
1741 });
1742 self.queue_undo_redo_event(&mut inner)
1743 };
1744 crate::inner::dispatch_queued_events(queued);
1745 Ok(())
1746 }
1747
1748 pub fn remove_current_table(&self) -> Result<()> {
1753 let table = self.current_table().ok_or_else(|| {
1754 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1755 })?;
1756 self.remove_table(table.id())
1757 }
1758
1759 pub fn insert_row_above(&self) -> Result<()> {
1762 let cell_ref = self.current_table_cell().ok_or_else(|| {
1763 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1764 })?;
1765 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
1766 }
1767
1768 pub fn insert_row_below(&self) -> Result<()> {
1771 let cell_ref = self.current_table_cell().ok_or_else(|| {
1772 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1773 })?;
1774 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
1775 }
1776
1777 pub fn insert_column_before(&self) -> Result<()> {
1780 let cell_ref = self.current_table_cell().ok_or_else(|| {
1781 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1782 })?;
1783 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
1784 }
1785
1786 pub fn insert_column_after(&self) -> Result<()> {
1789 let cell_ref = self.current_table_cell().ok_or_else(|| {
1790 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1791 })?;
1792 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
1793 }
1794
1795 pub fn remove_current_row(&self) -> Result<()> {
1798 let cell_ref = self.current_table_cell().ok_or_else(|| {
1799 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1800 })?;
1801 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
1802 }
1803
1804 pub fn remove_current_column(&self) -> Result<()> {
1807 let cell_ref = self.current_table_cell().ok_or_else(|| {
1808 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1809 })?;
1810 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
1811 }
1812
1813 pub fn merge_selected_cells(&self) -> Result<()> {
1820 let pos_cell = self.current_table_cell().ok_or_else(|| {
1821 DocumentError::InvalidCursorContext("cursor position is not inside a table".into())
1822 })?;
1823
1824 let (_pos, anchor) = self.read_cursor();
1826 let anchor_cell = {
1827 let inner = self.doc.lock();
1829 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1830 position: to_i64(anchor),
1831 };
1832 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1833 .map_err(|_| {
1834 DocumentError::InvalidCursorContext(
1835 "cursor anchor is not inside a table".into(),
1836 )
1837 })?;
1838 let block = crate::text_block::TextBlock {
1839 doc: self.doc.clone(),
1840 block_id: block_info.block_id as usize,
1841 };
1842 drop(inner);
1843 block.table_cell().ok_or_else(|| {
1844 DocumentError::InvalidCursorContext("cursor anchor is not inside a table".into())
1845 })?
1846 };
1847
1848 if pos_cell.table.id() != anchor_cell.table.id() {
1849 return Err(DocumentError::InvalidArgument(
1850 "position and anchor are in different tables".into(),
1851 ));
1852 }
1853
1854 let start_row = pos_cell.row.min(anchor_cell.row);
1855 let start_col = pos_cell.column.min(anchor_cell.column);
1856 let end_row = pos_cell.row.max(anchor_cell.row);
1857 let end_col = pos_cell.column.max(anchor_cell.column);
1858
1859 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
1860 }
1861
1862 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1865 let cell_ref = self.current_table_cell().ok_or_else(|| {
1866 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1867 })?;
1868 let cell = cell_ref
1870 .table
1871 .cell(cell_ref.row, cell_ref.column)
1872 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1873 self.split_table_cell(cell.id(), split_rows, split_columns)
1875 }
1876
1877 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1880 let table = self.current_table().ok_or_else(|| {
1881 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1882 })?;
1883 self.set_table_format(table.id(), format)
1884 }
1885
1886 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1889 let cell_ref = self.current_table_cell().ok_or_else(|| {
1890 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1891 })?;
1892 let cell = cell_ref
1893 .table
1894 .cell(cell_ref.row, cell_ref.column)
1895 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1896 self.set_table_cell_format(cell.id(), format)
1897 }
1898
1899 pub fn selection_kind(&self) -> crate::flow::SelectionKind {
1907 use crate::flow::{CellRange, SelectionKind};
1908
1909 {
1911 let d = self.data.lock();
1912 if let Some(ref range) = d.cell_selection_override {
1913 return SelectionKind::Cells(range.clone());
1914 }
1915 if d.position == d.anchor {
1916 return SelectionKind::None;
1917 }
1918 }
1919
1920 let (pos, anchor) = self.read_cursor();
1921
1922 let pos_cell = self.table_cell_at(pos);
1924 let anchor_cell = self.table_cell_at(anchor);
1925
1926 match (&pos_cell, &anchor_cell) {
1927 (None, None) => {
1928 let (start, end) = (pos.min(anchor), pos.max(anchor));
1932 if let Some(t) = self.find_table_between(start, end) {
1933 let table_id = t.id();
1934 let rows = t.rows();
1935 let cols = t.columns();
1936 let range = CellRange {
1937 table_id,
1938 start_row: 0,
1939 start_col: 0,
1940 end_row: if rows > 0 { rows - 1 } else { 0 },
1941 end_col: if cols > 0 { cols - 1 } else { 0 },
1942 };
1943 let spans = self.collect_cell_spans(table_id);
1944 SelectionKind::Mixed {
1945 cell_range: range.expand_for_spans(&spans),
1946 text_before: true,
1947 text_after: true,
1948 }
1949 } else {
1950 SelectionKind::Text
1951 }
1952 }
1953 (Some(pc), Some(ac)) => {
1954 if pc.table.id() != ac.table.id() {
1955 return SelectionKind::Text;
1957 }
1958 if pc.row == ac.row && pc.column == ac.column {
1959 return SelectionKind::Text;
1961 }
1962 let range = CellRange {
1964 table_id: pc.table.id(),
1965 start_row: pc.row.min(ac.row),
1966 start_col: pc.column.min(ac.column),
1967 end_row: pc.row.max(ac.row),
1968 end_col: pc.column.max(ac.column),
1969 };
1970 let spans = self.collect_cell_spans(pc.table.id());
1971 SelectionKind::Cells(range.expand_for_spans(&spans))
1972 }
1973 (Some(tc), None) | (None, Some(tc)) => {
1974 let table_id = tc.table.id();
1978 let rows = tc.table.rows();
1979 let cols = tc.table.columns();
1980
1981 let inside_pos = if pos_cell.is_some() { pos } else { anchor };
1982 let outside_pos = if pos_cell.is_some() { anchor } else { pos };
1983
1984 let text_before = outside_pos < inside_pos;
1985 let text_after = !text_before;
1986
1987 let range = CellRange {
1988 table_id,
1989 start_row: 0,
1990 start_col: 0,
1991 end_row: if rows > 0 { rows - 1 } else { 0 },
1992 end_col: if cols > 0 { cols - 1 } else { 0 },
1993 };
1994 let spans = self.collect_cell_spans(table_id);
1995 SelectionKind::Mixed {
1996 cell_range: range.expand_for_spans(&spans),
1997 text_before,
1998 text_after,
1999 }
2000 }
2001 }
2002 }
2003
2004 pub fn is_cell_selection(&self) -> bool {
2006 matches!(
2007 self.selection_kind(),
2008 crate::flow::SelectionKind::Cells(_) | crate::flow::SelectionKind::Mixed { .. }
2009 )
2010 }
2011
2012 pub fn selected_cell_range(&self) -> Option<crate::flow::CellRange> {
2014 match self.selection_kind() {
2015 crate::flow::SelectionKind::Cells(r) => Some(r),
2016 crate::flow::SelectionKind::Mixed { cell_range, .. } => Some(cell_range),
2017 _ => None,
2018 }
2019 }
2020
2021 pub fn selected_cells(&self) -> Vec<TableCellRef> {
2023 let range = match self.selected_cell_range() {
2024 Some(r) => r,
2025 None => return Vec::new(),
2026 };
2027 let table = TextTable {
2028 doc: self.doc.clone(),
2029 table_id: range.table_id,
2030 };
2031 let mut cells = Vec::new();
2032 for row in range.start_row..=range.end_row {
2033 for col in range.start_col..=range.end_col {
2034 if table.cell(row, col).is_some() {
2035 cells.push(TableCellRef {
2036 table: table.clone(),
2037 row,
2038 column: col,
2039 });
2040 }
2041 }
2042 }
2043 cells
2044 }
2045
2046 pub fn select_table_cell(&self, table_id: usize, row: usize, col: usize) {
2050 let mut d = self.data.lock();
2051 d.cell_selection_override = Some(crate::flow::CellRange {
2052 table_id,
2053 start_row: row,
2054 start_col: col,
2055 end_row: row,
2056 end_col: col,
2057 });
2058 }
2059
2060 pub fn select_cell_range(
2062 &self,
2063 table_id: usize,
2064 start_row: usize,
2065 start_col: usize,
2066 end_row: usize,
2067 end_col: usize,
2068 ) {
2069 let range = crate::flow::CellRange {
2070 table_id,
2071 start_row,
2072 start_col,
2073 end_row,
2074 end_col,
2075 };
2076 let spans = self.collect_cell_spans(table_id);
2077 let mut d = self.data.lock();
2078 d.cell_selection_override = Some(range.expand_for_spans(&spans));
2079 }
2080
2081 pub fn clear_cell_selection(&self) {
2083 let mut d = self.data.lock();
2084 d.cell_selection_override = None;
2085 }
2086
2087 fn cell_range_positions(&self, range: &CellRange) -> Option<(usize, usize)> {
2090 let inner = self.doc.lock();
2091 let main_frame_id = get_main_frame_id(&inner);
2092 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2093 drop(inner);
2094
2095 let table = flow.into_iter().find_map(|e| match e {
2097 FlowElement::Table(t) if t.id() == range.table_id => Some(t),
2098 _ => None,
2099 })?;
2100
2101 let mut min_pos = usize::MAX;
2102 let mut max_pos = 0usize;
2103
2104 for row in range.start_row..=range.end_row {
2105 for col in range.start_col..=range.end_col {
2106 if let Some(cell) = table.cell(row, col) {
2107 for block in cell.blocks() {
2108 let bp = block.position();
2109 let bl = block.length();
2110 min_pos = min_pos.min(bp);
2111 max_pos = max_pos.max(bp + bl);
2112 }
2113 }
2114 }
2115 }
2116
2117 if min_pos == usize::MAX {
2118 return None;
2119 }
2120
2121 Some((min_pos, max_pos + 1))
2123 }
2124
2125 fn table_cell_at(&self, position: usize) -> Option<TableCellRef> {
2129 let inner = self.doc.lock();
2130 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2131 position: to_i64(position),
2132 };
2133 let block_info =
2134 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2135
2136 let block_id = if to_i64(position) < block_info.block_start && position > 0 {
2137 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2138 position: to_i64(position - 1),
2139 };
2140 let prev_info =
2141 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
2142 prev_info.block_id as usize
2143 } else {
2144 block_info.block_id as usize
2145 };
2146
2147 let block = crate::text_block::TextBlock {
2148 doc: self.doc.clone(),
2149 block_id,
2150 };
2151 drop(inner);
2152 block.table_cell()
2153 }
2154
2155 fn table_boundary_position(&self, table_id: usize, before: bool) -> Option<usize> {
2166 let inner = self.doc.lock();
2167 let main_frame_id = get_main_frame_id(&inner);
2168 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2169 drop(inner);
2170
2171 let idx = flow
2173 .iter()
2174 .position(|e| matches!(e, FlowElement::Table(t) if t.id() == table_id))?;
2175
2176 if before {
2177 for i in (0..idx).rev() {
2179 if let FlowElement::Block(b) = &flow[i] {
2180 return Some(b.position() + b.length());
2181 }
2182 }
2183 } else {
2184 for item in flow.iter().skip(idx + 1) {
2186 if let FlowElement::Block(b) = item {
2187 return Some(b.position());
2188 }
2189 }
2190 }
2191 None
2192 }
2193
2194 fn find_table_between(&self, start: usize, end: usize) -> Option<TextTable> {
2196 let inner = self.doc.lock();
2197 let main_frame_id = get_main_frame_id(&inner);
2198 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2199 drop(inner);
2200
2201 for elem in flow {
2202 if let FlowElement::Table(t) = elem {
2203 if let Some(first_cell) = t.cell(0, 0) {
2206 let blocks = first_cell.blocks();
2207 if let Some(fb) = blocks.first() {
2208 let p = fb.position();
2209 if p > start && p < end {
2210 return Some(t);
2211 }
2212 }
2213 }
2214 }
2215 }
2216 None
2217 }
2218
2219 fn collect_cell_spans(&self, table_id: usize) -> Vec<(usize, usize, usize, usize)> {
2221 let inner = self.doc.lock();
2222 let table_dto =
2223 match frontend::commands::table_commands::get_table(&inner.ctx, &(table_id as u64))
2224 .ok()
2225 .flatten()
2226 {
2227 Some(t) => t,
2228 None => return Vec::new(),
2229 };
2230
2231 let mut spans = Vec::with_capacity(table_dto.cells.len());
2232 for &cell_id in &table_dto.cells {
2233 if let Some(cell) =
2234 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &cell_id)
2235 .ok()
2236 .flatten()
2237 {
2238 spans.push((
2239 cell.row as usize,
2240 cell.column as usize,
2241 cell.row_span.max(1) as usize,
2242 cell.column_span.max(1) as usize,
2243 ));
2244 }
2245 }
2246 spans
2247 }
2248
2249 pub fn delete_char(&self) -> Result<()> {
2251 let (pos, anchor) = self.read_cursor();
2252 let (del_pos, del_anchor) = if pos != anchor {
2253 (pos, anchor)
2254 } else {
2255 let end = {
2257 let inner = self.doc.lock();
2258 document_inspection_commands::get_document_stats(&inner.ctx)
2259 .map(|s| max_cursor_position(&s))
2260 .unwrap_or(0)
2261 };
2262 if pos >= end {
2263 return Ok(());
2264 }
2265 let to = self.next_grapheme_boundary(pos);
2269 if to == pos {
2270 return Ok(());
2271 }
2272 (pos, to)
2273 };
2274 self.do_delete(del_pos, del_anchor)
2275 }
2276
2277 pub fn delete_previous_char(&self) -> Result<()> {
2279 let (pos, anchor) = self.read_cursor();
2280 let (del_pos, del_anchor) = if pos != anchor {
2281 (pos, anchor)
2282 } else if pos > 0 {
2283 let from = self.prev_grapheme_boundary(pos);
2284 if from == pos {
2285 return Ok(());
2286 }
2287 (from, pos)
2288 } else {
2289 return Ok(());
2290 };
2291 self.do_delete(del_pos, del_anchor)
2292 }
2293
2294 pub fn remove_selected_text(&self) -> Result<String> {
2296 let (pos, anchor) = self.read_cursor();
2297 if pos == anchor {
2298 return Ok(String::new());
2299 }
2300 let queued = {
2301 let mut inner = self.doc.lock();
2302 let dto = frontend::document_editing::DeleteTextDto {
2303 position: to_i64(pos),
2304 anchor: to_i64(anchor),
2305 };
2306 let result =
2307 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2308 let edit_pos = pos.min(anchor);
2309 let removed = pos.max(anchor) - edit_pos;
2310 let new_pos = to_usize(result.new_position);
2311 inner.adjust_cursors(edit_pos, removed, 0);
2312 {
2313 let mut d = self.data.lock();
2314 d.position = new_pos;
2315 d.anchor = new_pos;
2316 }
2317 inner.modified = true;
2318 inner.invalidate_text_cache();
2319 inner.rehighlight_affected(edit_pos);
2320 inner.queue_event(DocumentEvent::ContentsChanged {
2321 position: edit_pos,
2322 chars_removed: removed,
2323 chars_added: 0,
2324 blocks_affected: 1,
2325 });
2326 inner.check_block_count_changed();
2327 inner.check_flow_changed();
2328 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
2330 };
2331 crate::inner::dispatch_queued_events(queued.1);
2332 Ok(queued.0)
2333 }
2334
2335 pub fn current_list(&self) -> Option<crate::TextList> {
2340 let pos = self.position();
2341 let inner = self.doc.lock();
2342 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2343 position: to_i64(pos),
2344 };
2345 let block_info =
2346 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2347 let block = crate::text_block::TextBlock {
2348 doc: self.doc.clone(),
2349 block_id: block_info.block_id as usize,
2350 };
2351 drop(inner);
2352 block.list()
2353 }
2354
2355 pub fn create_list(&self, style: ListStyle) -> Result<()> {
2357 let (pos, anchor) = self.read_cursor();
2358 let queued = {
2359 let mut inner = self.doc.lock();
2360 let dto = frontend::document_editing::CreateListDto {
2361 position: to_i64(pos),
2362 anchor: to_i64(anchor),
2363 style: style.clone(),
2364 };
2365 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2366 inner.modified = true;
2367 inner.rehighlight_affected(pos.min(anchor));
2368 inner.queue_event(DocumentEvent::ContentsChanged {
2369 position: pos.min(anchor),
2370 chars_removed: 0,
2371 chars_added: 0,
2372 blocks_affected: 1,
2373 });
2374 self.queue_undo_redo_event(&mut inner)
2375 };
2376 crate::inner::dispatch_queued_events(queued);
2377 Ok(())
2378 }
2379
2380 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
2382 let (pos, anchor) = self.read_cursor();
2383 let queued = {
2384 let mut inner = self.doc.lock();
2385 let dto = frontend::document_editing::InsertListDto {
2386 position: to_i64(pos),
2387 anchor: to_i64(anchor),
2388 style: style.clone(),
2389 };
2390 let result =
2391 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2392 let edit_pos = pos.min(anchor);
2393 let removed = pos.max(anchor) - edit_pos;
2394 self.finish_edit_ext(
2395 &mut inner,
2396 edit_pos,
2397 removed,
2398 to_usize(result.new_position),
2399 1,
2400 false,
2401 )
2402 };
2403 crate::inner::dispatch_queued_events(queued);
2404 Ok(())
2405 }
2406
2407 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
2409 let queued = {
2410 let mut inner = self.doc.lock();
2411 let dto = format.to_set_dto(list_id);
2412 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2413 inner.modified = true;
2414 inner.queue_event(DocumentEvent::FormatChanged {
2415 position: 0,
2416 length: 0,
2417 kind: crate::flow::FormatChangeKind::List,
2418 });
2419 self.queue_undo_redo_event(&mut inner)
2420 };
2421 crate::inner::dispatch_queued_events(queued);
2422 Ok(())
2423 }
2424
2425 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
2428 let list = self.current_list().ok_or_else(|| {
2429 DocumentError::InvalidCursorContext("cursor is not inside a list".into())
2430 })?;
2431 self.set_list_format(list.id(), format)
2432 }
2433
2434 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
2436 let queued = {
2437 let mut inner = self.doc.lock();
2438 let dto = frontend::document_editing::AddBlockToListDto {
2439 block_id: to_i64(block_id),
2440 list_id: to_i64(list_id),
2441 };
2442 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2443 inner.modified = true;
2444 inner.queue_event(DocumentEvent::FormatChanged {
2451 position: 0,
2452 length: 0,
2453 kind: crate::flow::FormatChangeKind::List,
2454 });
2455 self.queue_undo_redo_event(&mut inner)
2456 };
2457 crate::inner::dispatch_queued_events(queued);
2458 Ok(())
2459 }
2460
2461 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
2463 let pos = self.position();
2464 let inner = self.doc.lock();
2465 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2466 position: to_i64(pos),
2467 };
2468 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2469 drop(inner);
2470 self.add_block_to_list(block_info.block_id as usize, list_id)
2471 }
2472
2473 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
2475 let queued = {
2476 let mut inner = self.doc.lock();
2477 let dto = frontend::document_editing::RemoveBlockFromListDto {
2478 block_id: to_i64(block_id),
2479 };
2480 document_editing_commands::remove_block_from_list(
2481 &inner.ctx,
2482 Some(inner.stack_id),
2483 &dto,
2484 )?;
2485 inner.modified = true;
2486 inner.queue_event(DocumentEvent::FormatChanged {
2489 position: 0,
2490 length: 0,
2491 kind: crate::flow::FormatChangeKind::List,
2492 });
2493 self.queue_undo_redo_event(&mut inner)
2494 };
2495 crate::inner::dispatch_queued_events(queued);
2496 Ok(())
2497 }
2498
2499 pub fn remove_current_block_from_list(&self) -> Result<()> {
2502 let pos = self.position();
2503 let inner = self.doc.lock();
2504 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2505 position: to_i64(pos),
2506 };
2507 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2508 drop(inner);
2509 self.remove_block_from_list(block_info.block_id as usize)
2510 }
2511
2512 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
2515 let list = crate::text_list::TextList {
2516 doc: self.doc.clone(),
2517 list_id,
2518 };
2519 let block = list.item(index).ok_or_else(|| {
2520 DocumentError::OutOfRange(format!("list item index {index} out of range"))
2521 })?;
2522 self.remove_block_from_list(block.id())
2523 }
2524
2525 pub fn char_format(&self) -> Result<TextFormat> {
2530 let pos = self.position();
2531 let inner = self.doc.lock();
2532
2533 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2535 position: to_i64(pos),
2536 };
2537 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2538 let block_id = block_info.block_id as u64;
2539 let mut block_dto =
2540 frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2541 .ok_or_else(|| DocumentError::NotFound("block not found at position".into()))?;
2542 let store = inner.ctx.db_context.get_store();
2543 crate::inner::refresh_block_position(&mut block_dto, store);
2544
2545 let local_char = pos.saturating_sub(block_dto.document_position as usize);
2548 let entity: common::entities::Block = block_dto.clone().into();
2549 let plain_owned = common::database::rope_helpers::block_content_via_store(&entity, store);
2550 let plain: &str = &plain_owned;
2551 let byte_offset: u32 = plain
2552 .char_indices()
2553 .nth(local_char)
2554 .map(|(b, _)| b as u32)
2555 .unwrap_or(plain.len() as u32);
2556
2557 let images = store
2560 .block_images
2561 .read()
2562 .get(&block_id)
2563 .cloned()
2564 .unwrap_or_default();
2565 if let Some(img) = images.iter().find(|i| i.byte_offset == byte_offset) {
2566 return Ok(TextFormat::from(&img.format));
2567 }
2568
2569 let runs = store
2571 .format_runs
2572 .read()
2573 .get(&block_id)
2574 .cloned()
2575 .unwrap_or_default();
2576 let fmt = runs
2577 .iter()
2578 .find(|r| r.byte_start <= byte_offset && byte_offset < r.byte_end)
2579 .map(|r| TextFormat::from(&r.format))
2580 .unwrap_or_default();
2581 Ok(fmt)
2582 }
2583
2584 pub fn block_format(&self) -> Result<BlockFormat> {
2591 let pos = self.position();
2592 let inner = self.doc.lock();
2593 let block_info = crate::inner::block_at_caret_dto(&inner.ctx, pos)?;
2594 let block_id = block_info.block_id as u64;
2595 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2596 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
2597 Ok(BlockFormat::from(&block))
2598 }
2599
2600 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
2604 let (pos, anchor) = self.read_cursor();
2605 let queued = {
2606 let mut inner = self.doc.lock();
2607 let dto = format.to_set_dto(pos, anchor);
2608 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2609 let start = pos.min(anchor);
2610 let length = pos.max(anchor) - start;
2611 inner.modified = true;
2612 inner.queue_event(DocumentEvent::FormatChanged {
2613 position: start,
2614 length,
2615 kind: crate::flow::FormatChangeKind::Character,
2616 });
2617 self.queue_undo_redo_event(&mut inner)
2618 };
2619 crate::inner::dispatch_queued_events(queued);
2620 Ok(())
2621 }
2622
2623 pub fn link_at_caret(&self) -> Option<LinkExtent> {
2633 let pos = self.position();
2634 let block_id = {
2635 let inner = self.doc.lock();
2636 crate::inner::block_at_caret_dto(&inner.ctx, pos)
2637 .ok()?
2638 .block_id as usize
2639 };
2640 let block = TextBlock {
2641 doc: self.doc.clone(),
2642 block_id,
2643 };
2644 crate::link_extent::link_extent_at(&block, pos)
2645 }
2646
2647 pub fn clear_char_anchor(&self) -> Result<()> {
2659 self.merge_char_format(&TextFormat {
2660 clear_link: true,
2661 ..Default::default()
2662 })
2663 }
2664
2665 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
2667 let (pos, anchor) = self.read_cursor();
2668 let queued = {
2669 let mut inner = self.doc.lock();
2670 let dto = format.to_merge_dto(pos, anchor);
2671 document_formatting_commands::merge_text_format(
2672 &inner.ctx,
2673 Some(inner.stack_id),
2674 &dto,
2675 )?;
2676 let start = pos.min(anchor);
2677 let length = pos.max(anchor) - start;
2678 inner.modified = true;
2679 inner.queue_event(DocumentEvent::FormatChanged {
2680 position: start,
2681 length,
2682 kind: crate::flow::FormatChangeKind::Character,
2683 });
2684 self.queue_undo_redo_event(&mut inner)
2685 };
2686 crate::inner::dispatch_queued_events(queued);
2687 Ok(())
2688 }
2689
2690 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
2692 let (pos, anchor) = self.read_cursor();
2693 let queued = {
2694 let mut inner = self.doc.lock();
2695 let dto = format.to_set_dto(pos, anchor);
2696 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2697 let start = pos.min(anchor);
2698 let length = pos.max(anchor) - start;
2699 inner.modified = true;
2700 inner.queue_event(DocumentEvent::FormatChanged {
2701 position: start,
2702 length,
2703 kind: crate::flow::FormatChangeKind::Block,
2704 });
2705 self.queue_undo_redo_event(&mut inner)
2706 };
2707 crate::inner::dispatch_queued_events(queued);
2708 Ok(())
2709 }
2710
2711 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
2713 let (pos, anchor) = self.read_cursor();
2714 let queued = {
2715 let mut inner = self.doc.lock();
2716 let dto = format.to_set_dto(pos, anchor, frame_id);
2717 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2718 let start = pos.min(anchor);
2719 let length = pos.max(anchor) - start;
2720 inner.modified = true;
2721 inner.queue_event(DocumentEvent::FormatChanged {
2722 position: start,
2723 length,
2724 kind: crate::flow::FormatChangeKind::Block,
2725 });
2726 self.queue_undo_redo_event(&mut inner)
2727 };
2728 crate::inner::dispatch_queued_events(queued);
2729 Ok(())
2730 }
2731
2732 pub fn begin_edit_block(&self) {
2736 let inner = self.doc.lock();
2737 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
2738 }
2739
2740 pub fn end_edit_block(&self) {
2742 let inner = self.doc.lock();
2743 undo_redo_commands::end_composite(&inner.ctx);
2744 }
2745
2746 pub fn join_previous_edit_block(&self) {
2753 self.begin_edit_block();
2754 }
2755
2756 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
2760 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
2761 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
2762 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
2763 inner.take_queued_events()
2764 }
2765
2766 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
2767 let queued = {
2768 let mut inner = self.doc.lock();
2769 let dto = frontend::document_editing::DeleteTextDto {
2770 position: to_i64(pos),
2771 anchor: to_i64(anchor),
2772 };
2773 let result =
2774 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2775 let edit_pos = pos.min(anchor);
2776 let removed = pos.max(anchor) - edit_pos;
2777 let new_pos = to_usize(result.new_position);
2778 inner.adjust_cursors(edit_pos, removed, 0);
2779 {
2780 let mut d = self.data.lock();
2781 d.position = new_pos;
2782 d.anchor = new_pos;
2783 }
2784 inner.modified = true;
2785 inner.invalidate_text_cache();
2786 inner.rehighlight_affected(edit_pos);
2787 inner.queue_event(DocumentEvent::ContentsChanged {
2788 position: edit_pos,
2789 chars_removed: removed,
2790 chars_added: 0,
2791 blocks_affected: 1,
2792 });
2793 inner.check_block_count_changed();
2794 inner.check_flow_changed();
2795 self.queue_undo_redo_event(&mut inner)
2796 };
2797 crate::inner::dispatch_queued_events(queued);
2798 Ok(())
2799 }
2800
2801 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
2803 let pos = self.position();
2804 match op {
2805 MoveOperation::NoMove => pos,
2806 MoveOperation::Start => 0,
2807 MoveOperation::End => {
2808 let inner = self.doc.lock();
2809 document_inspection_commands::get_document_stats(&inner.ctx)
2810 .map(|s| max_cursor_position(&s))
2811 .unwrap_or(pos)
2812 }
2813 MoveOperation::NextCharacter | MoveOperation::Right => {
2814 let mut cur = pos;
2815 for _ in 0..n {
2816 let next = self.next_grapheme_boundary(cur);
2817 if next == cur {
2818 break;
2819 }
2820 cur = next;
2821 }
2822 cur
2823 }
2824 MoveOperation::PreviousCharacter | MoveOperation::Left => {
2825 let mut cur = pos;
2826 for _ in 0..n {
2827 let prev = self.prev_grapheme_boundary(cur);
2828 if prev == cur {
2829 break;
2830 }
2831 cur = prev;
2832 }
2833 cur
2834 }
2835 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
2836 let inner = self.doc.lock();
2837 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2838 position: to_i64(pos),
2839 };
2840 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2841 .map(|info| to_usize(info.block_start))
2842 .unwrap_or(pos)
2843 }
2844 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
2845 let inner = self.doc.lock();
2846 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2847 position: to_i64(pos),
2848 };
2849 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2850 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
2851 .unwrap_or(pos)
2852 }
2853 MoveOperation::NextBlock => {
2854 let inner = self.doc.lock();
2855 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2856 position: to_i64(pos),
2857 };
2858 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2859 .map(|info| {
2860 to_usize(info.block_start) + to_usize(info.block_length) + 1
2862 })
2863 .unwrap_or(pos)
2864 }
2865 MoveOperation::PreviousBlock => {
2866 let inner = self.doc.lock();
2867 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2868 position: to_i64(pos),
2869 };
2870 let block_start =
2871 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2872 .map(|info| to_usize(info.block_start))
2873 .unwrap_or(pos);
2874 if block_start >= 2 {
2875 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2877 position: to_i64(block_start - 2),
2878 };
2879 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
2880 .map(|info| to_usize(info.block_start))
2881 .unwrap_or(0)
2882 } else {
2883 0
2884 }
2885 }
2886 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
2887 let (_, end) = self.find_word_boundaries(pos);
2888 if end == pos {
2890 let inner = self.doc.lock();
2892 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
2893 .map(|s| max_cursor_position(&s))
2894 .unwrap_or(0);
2895 let scan_len = max_pos.saturating_sub(pos).min(64);
2896 if scan_len == 0 {
2897 return pos;
2898 }
2899 let dto = frontend::document_inspection::GetTextAtPositionDto {
2900 position: to_i64(pos),
2901 length: to_i64(scan_len),
2902 };
2903 if let Ok(r) =
2904 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
2905 {
2906 for (i, ch) in r.text.chars().enumerate() {
2907 if ch.is_alphanumeric() || ch == '_' {
2908 let word_pos = pos + i;
2910 drop(inner);
2911 let (_, word_end) = self.find_word_boundaries(word_pos);
2912 return word_end;
2913 }
2914 }
2915 }
2916 pos + scan_len
2917 } else {
2918 end
2919 }
2920 }
2921 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
2922 let (start, _) = self.find_word_boundaries(pos);
2923 if start < pos {
2924 start
2925 } else if pos > 0 {
2926 let mut search = pos - 1;
2929 loop {
2930 let (ws, we) = self.find_word_boundaries(search);
2931 if ws < we {
2932 break ws;
2934 }
2935 if search == 0 {
2937 break 0;
2938 }
2939 search -= 1;
2940 }
2941 } else {
2942 0
2943 }
2944 }
2945 MoveOperation::StartOfSentence | MoveOperation::PreviousSentence => {
2946 let mut cur = pos;
2947 for _ in 0..n.max(1) {
2948 let start = match self.find_sentence_boundaries(cur) {
2949 Some((start, _)) => start,
2950 None => break,
2951 };
2952 if start < cur && op == MoveOperation::StartOfSentence {
2955 cur = start;
2956 } else if cur > 0 {
2957 match self.find_sentence_boundaries(cur - 1) {
2958 Some((prev, _)) if prev < cur => cur = prev,
2959 _ => cur = cur.saturating_sub(1),
2962 }
2963 } else {
2964 break;
2965 }
2966 }
2967 cur
2968 }
2969 MoveOperation::EndOfSentence => {
2970 let mut cur = pos;
2971 for _ in 0..n.max(1) {
2972 let end = match self.find_sentence_boundaries(cur) {
2973 Some((_, end)) => end,
2974 None => break,
2975 };
2976 if end > cur {
2977 cur = end;
2978 } else {
2979 match self.find_sentence_boundaries(cur + 1) {
2980 Some((_, next)) if next > cur => cur = next,
2981 _ => break,
2982 }
2983 }
2984 }
2985 cur
2986 }
2987 MoveOperation::NextSentence => {
2988 let mut cur = pos;
2989 for _ in 0..n.max(1) {
2990 let end = match self.find_sentence_boundaries(cur) {
2993 Some((_, end)) => end,
2994 None => break,
2995 };
2996 match self.find_sentence_boundaries(end + 1) {
2997 Some((start, _)) if start > cur => cur = start,
2998 _ => {
2999 if end > cur {
3000 cur = end;
3001 } else {
3002 break;
3003 }
3004 }
3005 }
3006 }
3007 cur
3008 }
3009 MoveOperation::Up | MoveOperation::Down => {
3010 if matches!(op, MoveOperation::Up) {
3013 self.resolve_move(MoveOperation::PreviousBlock, 1)
3014 } else {
3015 self.resolve_move(MoveOperation::NextBlock, 1)
3016 }
3017 }
3018 }
3019 }
3020
3021 pub(crate) fn snap_position_to_grapheme_boundary(&self) {
3032 let pos = {
3033 let data = self.data.lock();
3034 data.position
3035 };
3036 let snapped = self.forward_grapheme_boundary_at_or_after(pos);
3037 if snapped != pos {
3038 let mut data = self.data.lock();
3039 data.position = snapped;
3040 if data.anchor == pos {
3041 data.anchor = snapped;
3042 }
3043 }
3044 }
3045
3046 fn forward_grapheme_boundary_at_or_after(&self, pos: usize) -> usize {
3056 let inner = self.doc.lock();
3057 let end = document_inspection_commands::get_document_stats(&inner.ctx)
3058 .map(|s| max_cursor_position(&s))
3059 .unwrap_or(pos);
3060 if pos >= end {
3061 return pos;
3062 }
3063 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3064 position: to_i64(pos),
3065 };
3066 let Ok(block_info) =
3067 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto)
3068 else {
3069 return pos;
3070 };
3071 let block_start = to_usize(block_info.block_start);
3072 let block_length = to_usize(block_info.block_length);
3073 let offset_in_block = pos.saturating_sub(block_start);
3074 if offset_in_block == 0 || offset_in_block >= block_length {
3076 return pos;
3077 }
3078 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3079 position: to_i64(block_start),
3080 length: to_i64(block_length),
3081 };
3082 let Ok(r) = document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto)
3083 else {
3084 return pos;
3085 };
3086 let text = r.text;
3087 drop(inner);
3088 let mut acc = 0usize;
3091 for g in text.graphemes(true) {
3092 if acc >= offset_in_block {
3093 return block_start + acc;
3094 }
3095 acc += g.chars().count();
3096 }
3097 block_start + acc
3098 }
3099
3100 fn next_grapheme_boundary(&self, pos: usize) -> usize {
3113 let inner = self.doc.lock();
3114 let end = document_inspection_commands::get_document_stats(&inner.ctx)
3115 .map(|s| max_cursor_position(&s))
3116 .unwrap_or(pos);
3117 if pos >= end {
3118 return pos;
3119 }
3120 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3121 position: to_i64(pos),
3122 };
3123 let block_info =
3124 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3125 Ok(info) => info,
3126 Err(_) => return pos + 1,
3127 };
3128 let block_start = to_usize(block_info.block_start);
3129 let block_length = to_usize(block_info.block_length);
3130 let offset_in_block = pos.saturating_sub(block_start);
3131 if offset_in_block >= block_length {
3132 return (pos + 1).min(end);
3135 }
3136 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3137 position: to_i64(pos),
3138 length: to_i64(block_length - offset_in_block),
3139 };
3140 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
3141 Ok(r) => r.text,
3142 Err(_) => return pos + 1,
3143 };
3144 drop(inner);
3145 match text.graphemes(true).next() {
3146 Some(g) if !g.is_empty() => (pos + g.chars().count()).min(end),
3147 _ => (pos + 1).min(end),
3148 }
3149 }
3150
3151 fn prev_grapheme_boundary(&self, pos: usize) -> usize {
3155 if pos == 0 {
3156 return 0;
3157 }
3158 let inner = self.doc.lock();
3159 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3160 position: to_i64(pos.saturating_sub(1)),
3161 };
3162 let block_info =
3163 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3164 Ok(info) => info,
3165 Err(_) => return pos - 1,
3166 };
3167 let block_start = to_usize(block_info.block_start);
3168 let block_length = to_usize(block_info.block_length);
3169 let block_end = block_start + block_length;
3170 if pos > block_end {
3174 return pos - 1;
3175 }
3176 if block_length == 0 || pos <= block_start {
3177 return pos.saturating_sub(1);
3178 }
3179 let scan_len = pos - block_start;
3180 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3181 position: to_i64(block_start),
3182 length: to_i64(scan_len),
3183 };
3184 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
3185 Ok(r) => r.text,
3186 Err(_) => return pos - 1,
3187 };
3188 drop(inner);
3189 match text.graphemes(true).next_back() {
3190 Some(g) if !g.is_empty() => pos - g.chars().count(),
3191 _ => pos - 1,
3192 }
3193 }
3194
3195 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
3201 let inner = self.doc.lock();
3202 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3204 position: to_i64(pos),
3205 };
3206 let block_info =
3207 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3208 Ok(info) => info,
3209 Err(_) => return (pos, pos),
3210 };
3211
3212 let block_start = to_usize(block_info.block_start);
3213 let block_length = to_usize(block_info.block_length);
3214 if block_length == 0 {
3215 return (pos, pos);
3216 }
3217
3218 let dto = frontend::document_inspection::GetTextAtPositionDto {
3219 position: to_i64(block_start),
3220 length: to_i64(block_length),
3221 };
3222 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
3223 Ok(r) => r.text,
3224 Err(_) => return (pos, pos),
3225 };
3226
3227 let cursor_offset = pos.saturating_sub(block_start);
3229
3230 let mut last_char_start = 0;
3232 let mut last_char_end = 0;
3233
3234 for (word_byte_start, word) in text.unicode_word_indices() {
3235 let word_char_start = text[..word_byte_start].chars().count();
3237 let word_char_len = word.chars().count();
3238 let word_char_end = word_char_start + word_char_len;
3239
3240 last_char_start = word_char_start;
3241 last_char_end = word_char_end;
3242
3243 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
3244 return (block_start + word_char_start, block_start + word_char_end);
3245 }
3246 }
3247
3248 if cursor_offset == last_char_end && last_char_start < last_char_end {
3250 return (block_start + last_char_start, block_start + last_char_end);
3251 }
3252
3253 (pos, pos)
3254 }
3255
3256 fn find_sentence_boundaries(&self, pos: usize) -> Option<(usize, usize)> {
3262 let locale = self.data.lock().content_locale.clone();
3263
3264 let inner = self.doc.lock();
3265 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3266 position: to_i64(pos),
3267 };
3268 let block_info =
3269 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto).ok()?;
3270 let block_start = to_usize(block_info.block_start);
3271 let block_length = to_usize(block_info.block_length);
3272 if block_length == 0 {
3273 return None;
3274 }
3275 let dto = frontend::document_inspection::GetTextAtPositionDto {
3276 position: to_i64(block_start),
3277 length: to_i64(block_length),
3278 };
3279 let text = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
3280 .ok()?
3281 .text;
3282 drop(inner);
3283
3284 let offset = pos.saturating_sub(block_start);
3285 let (start, end) =
3286 frontend::common::parser_tools::sentence_bounds(&text, offset, locale.as_deref())?;
3287 Some((block_start + start, block_start + end))
3288 }
3289}
3290
3291#[derive(Clone, Copy, PartialEq, Eq)]
3298enum BlockEdge {
3299 First,
3300 Middle,
3301 Last,
3302 OnlyOne,
3303}
3304
3305fn cursor_frame_ref(inner: &TextDocumentInner, block_id: u64) -> Option<FrameRef> {
3309 let parent = crate::text_block::find_parent_frame(inner, block_id)?;
3310 let store = inner.ctx.db_context.get_store();
3311 let frames = store.frames.read();
3312 let frame = frames.get(&parent)?.clone();
3313 frame.parent_frame?;
3314 let is_blockquote = frame.fmt_is_blockquote.unwrap_or(false);
3315
3316 let mut depth = 0;
3317 let mut current = Some(parent);
3318 while let Some(id) = current {
3319 let Some(f) = frames.get(&id) else {
3320 break;
3321 };
3322 if f.parent_frame.is_none() {
3323 break;
3324 }
3325 depth += 1;
3326 current = f.parent_frame;
3327 }
3328
3329 Some(FrameRef {
3330 frame_id: frame.id as usize,
3331 parent_frame_id: frame.parent_frame.map(|id| id as usize),
3332 is_blockquote,
3333 depth,
3334 })
3335}
3336
3337fn innermost_blockquote_frame_id(inner: &TextDocumentInner, block_id: u64) -> Option<usize> {
3341 let mut current = crate::text_block::find_parent_frame(inner, block_id);
3342 let store = inner.ctx.db_context.get_store();
3343 let frames = store.frames.read();
3344 while let Some(id) = current {
3345 let f = frames.get(&id)?;
3346 if f.fmt_is_blockquote == Some(true) {
3347 return Some(f.id as usize);
3348 }
3349 current = f.parent_frame;
3350 }
3351 None
3352}
3353
3354fn blockquote_depth_for_block(inner: &TextDocumentInner, block_id: u64) -> usize {
3357 let mut current = crate::text_block::find_parent_frame(inner, block_id);
3358 let store = inner.ctx.db_context.get_store();
3359 let frames = store.frames.read();
3360 let mut count = 0;
3361 while let Some(id) = current {
3362 let Some(f) = frames.get(&id) else {
3363 break;
3364 };
3365 if f.fmt_is_blockquote == Some(true) {
3366 count += 1;
3367 }
3368 current = f.parent_frame;
3369 }
3370 count
3371}
3372
3373fn block_position_in_current_frame(cursor: &TextCursor) -> Option<BlockEdge> {
3379 let pos = cursor.position();
3380 let inner = cursor.doc.lock();
3381 let dto = frontend::document_inspection::GetBlockAtPositionDto {
3382 position: to_i64(pos),
3383 };
3384 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
3385 let block_id = block_info.block_id as common::types::EntityId;
3386 let parent_id = crate::text_block::find_parent_frame(&inner, block_info.block_id as u64)?;
3387 let store = inner.ctx.db_context.get_store();
3388 let frames = store.frames.read();
3389 let frame = frames.get(&parent_id)?;
3390 let block_positions: Vec<usize> = frame
3391 .child_order
3392 .iter()
3393 .enumerate()
3394 .filter_map(|(i, &e)| {
3395 if e > 0 {
3396 Some((i, e as common::types::EntityId))
3397 } else {
3398 None
3399 }
3400 })
3401 .filter(|(_, id)| *id == block_id)
3402 .map(|(i, _)| i)
3403 .collect();
3404 let block_idx = *block_positions.first()?;
3405 let positive_entries: Vec<usize> = frame
3406 .child_order
3407 .iter()
3408 .enumerate()
3409 .filter_map(|(i, &e)| if e > 0 { Some(i) } else { None })
3410 .collect();
3411 let first_pos = *positive_entries.first()?;
3412 let last_pos = *positive_entries.last()?;
3413 let is_first = block_idx == first_pos;
3414 let is_last = block_idx == last_pos;
3415 let edge = match (is_first, is_last, positive_entries.len()) {
3416 (_, _, 1) => BlockEdge::OnlyOne,
3417 (true, _, _) => BlockEdge::First,
3418 (_, true, _) => BlockEdge::Last,
3419 _ => BlockEdge::Middle,
3420 };
3421 Some(edge)
3422}