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_of(inner: &TextDocumentInner) -> Option<usize> {
46 let (chars, blocks) = crate::inner::document_counts(inner)?;
47 Some(if blocks > 1 {
48 chars + blocks - 1
49 } else {
50 chars
51 })
52}
53
54pub struct TextCursor {
62 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
63 pub(crate) data: Arc<Mutex<CursorData>>,
64}
65
66impl Clone for TextCursor {
67 fn clone(&self) -> Self {
68 let (position, anchor, content_locale) = {
69 let d = self.data.lock();
70 (d.position, d.anchor, d.content_locale.clone())
71 };
72 let data = {
73 let mut inner = self.doc.lock();
74 let data = Arc::new(Mutex::new(CursorData {
75 position,
76 anchor,
77 cell_selection_override: None,
78 content_locale,
80 }));
81 inner.cursors.push(Arc::downgrade(&data));
82 data
83 };
84 TextCursor {
85 doc: self.doc.clone(),
86 data,
87 }
88 }
89}
90
91impl TextCursor {
92 fn read_cursor(&self) -> (usize, usize) {
95 let d = self.data.lock();
96 (d.position, d.anchor)
97 }
98
99 fn finish_edit(
103 &self,
104 inner: &mut TextDocumentInner,
105 edit_pos: usize,
106 removed: usize,
107 new_pos: usize,
108 blocks_affected: usize,
109 ) -> QueuedEvents {
110 self.finish_edit_ext(inner, edit_pos, removed, new_pos, blocks_affected, true)
111 }
112
113 fn finish_edit_ext(
114 &self,
115 inner: &mut TextDocumentInner,
116 edit_pos: usize,
117 removed: usize,
118 new_pos: usize,
119 blocks_affected: usize,
120 flow_may_change: bool,
121 ) -> QueuedEvents {
122 self.finish_edit_from(
123 inner,
124 edit_pos,
125 removed,
126 new_pos,
127 blocks_affected,
128 flow_may_change,
129 InsertionOrigin::Unspecified,
130 )
131 }
132
133 #[allow(clippy::too_many_arguments)]
142 fn finish_edit_from(
143 &self,
144 inner: &mut TextDocumentInner,
145 edit_pos: usize,
146 removed: usize,
147 new_pos: usize,
148 blocks_affected: usize,
149 flow_may_change: bool,
150 origin: InsertionOrigin,
151 ) -> QueuedEvents {
152 let added = new_pos.saturating_sub(edit_pos);
159 inner.adjust_cursors(edit_pos, removed, added);
160 {
161 let mut d = self.data.lock();
162 d.position = new_pos;
163 d.anchor = new_pos;
164 }
165 inner.modified = true;
166 inner.invalidate_text_cache();
167 inner.rehighlight_affected(edit_pos);
168 inner.queue_event(DocumentEvent::ContentsChanged {
169 position: edit_pos,
170 chars_removed: removed,
171 chars_added: added,
172 blocks_affected,
173 });
174 if added > 0 {
178 inner.queue_event(DocumentEvent::TextInserted {
179 position: edit_pos,
180 chars_inserted: added,
181 origin,
182 });
183 }
184 inner.check_block_count_changed();
185 if flow_may_change {
186 inner.check_flow_changed();
187 }
188 self.queue_undo_redo_event(inner)
189 }
190
191 pub fn position(&self) -> usize {
195 self.data.lock().position
196 }
197
198 pub fn anchor(&self) -> usize {
200 self.data.lock().anchor
201 }
202
203 pub fn has_selection(&self) -> bool {
205 let d = self.data.lock();
206 d.position != d.anchor
207 }
208
209 pub fn selection_start(&self) -> usize {
211 let d = self.data.lock();
212 d.position.min(d.anchor)
213 }
214
215 pub fn selection_end(&self) -> usize {
217 let d = self.data.lock();
218 d.position.max(d.anchor)
219 }
220
221 pub fn selected_text(&self) -> Result<String> {
223 let (pos, anchor) = self.read_cursor();
224 if pos == anchor {
225 return Ok(String::new());
226 }
227 let start = pos.min(anchor);
228 let len = pos.max(anchor) - start;
229 let inner = self.doc.lock();
230 let dto = frontend::document_inspection::GetTextAtPositionDto {
231 position: to_i64(start),
232 length: to_i64(len),
233 };
234 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
235 Ok(result.text)
236 }
237
238 pub fn text_before(&self, max_len: usize) -> Result<String> {
251 if max_len == 0 {
252 return Ok(String::new());
253 }
254 let pos = self.position();
255 let inner = self.doc.lock();
256 let store = inner.ctx.db_context.get_store();
257
258 if pos > 0
263 && common::database::rope_helpers::find_block_at_char_position(store, 0).is_none()
264 {
265 let dto = frontend::document_inspection::GetTextAtPositionDto {
266 position: 0,
267 length: to_i64(pos),
268 };
269 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
270 let full = result.text;
271 let total = full.chars().count();
272 let skip = total.saturating_sub(max_len);
273 return Ok(full.chars().skip(skip).collect());
274 }
275
276 let mut pieces: Vec<String> = Vec::new();
277 let mut remaining = max_len;
278 let mut end_pos = pos;
279
280 while remaining > 0 && end_pos > 0 {
281 let query = (end_pos - 1) as i64;
282 let Some((block_id, char_in_block, block_char_start)) =
288 common::database::rope_helpers::find_block_at_char_position(store, query)
289 else {
290 break;
294 };
295 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
296 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
297 let entity: common::entities::Block = block_dto.into();
298 let block_text =
299 common::database::rope_helpers::block_content_via_store(&entity, store);
300 let block_len = block_text.chars().count() as i64;
301 let block_char_start = block_char_start as usize;
302
303 if char_in_block == block_len {
304 pieces.push("\n".to_string());
306 remaining -= 1;
307 if remaining == 0 {
308 break;
309 }
310 let take = remaining.min(block_len as usize);
311 let local_start = block_len as usize - take;
312 let slice: String = block_text.chars().skip(local_start).take(take).collect();
313 pieces.push(slice);
314 remaining -= take;
315 end_pos = block_char_start + local_start;
316 } else {
317 let available = char_in_block as usize + 1;
319 let take = remaining.min(available);
320 let local_start = available - take;
321 let slice: String = block_text.chars().skip(local_start).take(take).collect();
322 pieces.push(slice);
323 remaining -= take;
324 end_pos = block_char_start + local_start;
325 }
326 }
327
328 pieces.reverse();
329 Ok(pieces.concat())
330 }
331
332 pub fn clear_selection(&self) {
334 let mut d = self.data.lock();
335 d.anchor = d.position;
336 }
337
338 pub fn at_block_start(&self) -> bool {
342 let pos = self.position();
343 let inner = self.doc.lock();
344 let dto = frontend::document_inspection::GetBlockAtPositionDto {
345 position: to_i64(pos),
346 };
347 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
348 pos == to_usize(info.block_start)
349 } else {
350 false
351 }
352 }
353
354 pub fn at_block_end(&self) -> bool {
356 let pos = self.position();
357 let inner = self.doc.lock();
358 let dto = frontend::document_inspection::GetBlockAtPositionDto {
359 position: to_i64(pos),
360 };
361 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
362 pos == to_usize(info.block_start) + to_usize(info.block_length)
363 } else {
364 false
365 }
366 }
367
368 pub fn at_start(&self) -> bool {
370 self.data.lock().position == 0
371 }
372
373 pub fn at_end(&self) -> bool {
375 let pos = self.position();
376 let inner = self.doc.lock();
377 pos >= max_cursor_position_of(&inner).unwrap_or(0)
378 }
379
380 pub fn block_number(&self) -> usize {
382 let pos = self.position();
383 let inner = self.doc.lock();
384 let dto = frontend::document_inspection::GetBlockAtPositionDto {
385 position: to_i64(pos),
386 };
387 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
388 .map(|info| to_usize(info.block_number))
389 .unwrap_or(0)
390 }
391
392 pub fn position_in_block(&self) -> usize {
394 let pos = self.position();
395 let inner = self.doc.lock();
396 let dto = frontend::document_inspection::GetBlockAtPositionDto {
397 position: to_i64(pos),
398 };
399 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
400 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
401 .unwrap_or(0)
402 }
403
404 pub fn set_position(&self, position: usize, mode: MoveMode) {
418 let end = {
420 let inner = self.doc.lock();
421 max_cursor_position_of(&inner).unwrap_or(0)
422 };
423 let mut pos = position.min(end);
424
425 if mode == MoveMode::KeepAnchor {
429 let anchor = self.data.lock().anchor;
430 let pos_cell = self.table_cell_at(pos);
431 let anchor_cell = self.table_cell_at(anchor);
432 match (&pos_cell, &anchor_cell) {
433 (Some(tc), None) => {
434 let before = anchor < pos;
436 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
437 pos = boundary;
438 }
439 }
440 (None, Some(tc)) => {
441 let before = pos < anchor;
444 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
445 pos = boundary;
446 }
447 }
448 _ => {}
449 }
450 }
451
452 {
453 let mut d = self.data.lock();
454 d.position = pos;
455 if mode == MoveMode::MoveAnchor {
456 d.anchor = pos;
457 }
458 d.cell_selection_override = None;
459 }
460 self.snap_position_to_grapheme_boundary();
465 }
466
467 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
473 let old_pos = self.position();
474 let target = self.resolve_move(operation, n);
475 self.set_position(target, mode);
476 self.position() != old_pos
477 }
478
479 pub fn select(&self, selection: SelectionType) {
481 match selection {
482 SelectionType::Document => {
483 let end = {
484 let inner = self.doc.lock();
485 max_cursor_position_of(&inner).unwrap_or(0)
486 };
487 let mut d = self.data.lock();
488 d.anchor = 0;
489 d.position = end;
490 d.cell_selection_override = None;
491 }
492 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
493 let pos = self.position();
494 let inner = self.doc.lock();
495 let dto = frontend::document_inspection::GetBlockAtPositionDto {
496 position: to_i64(pos),
497 };
498 if let Ok(info) =
499 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
500 {
501 let start = to_usize(info.block_start);
502 let end = start + to_usize(info.block_length);
503 drop(inner);
504 let mut d = self.data.lock();
505 d.anchor = start;
506 d.position = end;
507 d.cell_selection_override = None;
508 }
509 }
510 SelectionType::WordUnderCursor => {
511 let pos = self.position();
512 let (word_start, word_end) = self.find_word_boundaries(pos);
513 let mut d = self.data.lock();
514 d.anchor = word_start;
515 d.position = word_end;
516 d.cell_selection_override = None;
517 }
518 SelectionType::SentenceUnderCursor => {
519 let pos = self.position();
520 if let Some((start, end)) = self.find_sentence_boundaries(pos) {
523 let mut d = self.data.lock();
524 d.anchor = start;
525 d.position = end;
526 d.cell_selection_override = None;
527 }
528 }
529 }
530 }
531
532 pub fn set_content_locale(&self, locale: Option<&str>) {
541 self.data.lock().content_locale = locale.map(str::to_string);
542 }
543
544 pub fn content_locale(&self) -> Option<String> {
546 self.data.lock().content_locale.clone()
547 }
548
549 pub fn insert_text(&self, text: &str) -> Result<()> {
556 self.insert_text_with_origin(text, InsertionOrigin::Unspecified)
557 }
558
559 pub fn insert_text_with_origin(&self, text: &str, origin: InsertionOrigin) -> Result<()> {
564 let (pos, anchor) = self.read_cursor();
565
566 let dto = frontend::document_editing::InsertTextDto {
568 format_policy: Default::default(),
569 position: to_i64(pos),
570 anchor: to_i64(anchor),
571 text: text.into(),
572 };
573
574 let queued = {
575 let mut inner = self.doc.lock();
576 let result = match document_editing_commands::insert_text(
577 &inner.ctx,
578 Some(inner.stack_id),
579 &dto,
580 ) {
581 Ok(r) => r,
582 Err(_) if pos != anchor => {
583 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
585
586 let del_dto = frontend::document_editing::DeleteTextDto {
587 position: to_i64(pos),
588 anchor: to_i64(anchor),
589 };
590 let del_result = document_editing_commands::delete_text(
591 &inner.ctx,
592 Some(inner.stack_id),
593 &del_dto,
594 )?;
595 let del_pos = to_usize(del_result.new_position);
596
597 let ins_dto = frontend::document_editing::InsertTextDto {
598 format_policy: Default::default(),
599 position: to_i64(del_pos),
600 anchor: to_i64(del_pos),
601 text: text.into(),
602 };
603 let ins_result = document_editing_commands::insert_text(
604 &inner.ctx,
605 Some(inner.stack_id),
606 &ins_dto,
607 )?;
608
609 undo_redo_commands::end_composite(&inner.ctx);
610 ins_result
611 }
612 Err(e) => return Err(e.into()),
613 };
614
615 let edit_pos = pos.min(anchor);
616 let removed = pos.max(anchor) - edit_pos;
617 self.finish_edit_from(
618 &mut inner,
619 edit_pos,
620 removed,
621 to_usize(result.new_position),
622 to_usize(result.blocks_affected),
623 false,
624 origin,
625 )
626 };
627 crate::inner::dispatch_queued_events(queued);
628 Ok(())
629 }
630
631 pub fn replace(
645 &self,
646 start: usize,
647 end: usize,
648 text: &str,
649 policy: crate::ReplaceFormatPolicy,
650 ) -> Result<()> {
651 let (pos, anchor) = (start, end);
652
653 let dto = frontend::document_editing::InsertTextDto {
654 format_policy: policy,
655 position: to_i64(pos),
656 anchor: to_i64(anchor),
657 text: text.into(),
658 };
659
660 let queued = {
661 let mut inner = self.doc.lock();
662 let result = match document_editing_commands::insert_text(
663 &inner.ctx,
664 Some(inner.stack_id),
665 &dto,
666 ) {
667 Ok(r) => r,
668 Err(_) if pos != anchor => {
669 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
673
674 let del_dto = frontend::document_editing::DeleteTextDto {
675 position: to_i64(pos),
676 anchor: to_i64(anchor),
677 };
678 let del_result = document_editing_commands::delete_text(
679 &inner.ctx,
680 Some(inner.stack_id),
681 &del_dto,
682 )?;
683 let del_pos = to_usize(del_result.new_position);
684
685 let ins_dto = frontend::document_editing::InsertTextDto {
686 format_policy: Default::default(),
687 position: to_i64(del_pos),
688 anchor: to_i64(del_pos),
689 text: text.into(),
690 };
691 let ins_result = document_editing_commands::insert_text(
692 &inner.ctx,
693 Some(inner.stack_id),
694 &ins_dto,
695 )?;
696
697 undo_redo_commands::end_composite(&inner.ctx);
698 ins_result
699 }
700 Err(e) => return Err(e.into()),
701 };
702
703 let edit_pos = pos.min(anchor);
704 let removed = pos.max(anchor) - edit_pos;
705 self.finish_edit_ext(
706 &mut inner,
707 edit_pos,
708 removed,
709 to_usize(result.new_position),
710 to_usize(result.blocks_affected),
711 false,
712 )
713 };
714 crate::inner::dispatch_queued_events(queued);
715 Ok(())
716 }
717
718 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
721 self.insert_formatted_text_with_origin(text, format, InsertionOrigin::Unspecified)
722 }
723
724 pub fn insert_formatted_text_with_origin(
727 &self,
728 text: &str,
729 format: &TextFormat,
730 origin: InsertionOrigin,
731 ) -> Result<()> {
732 let (pos, anchor) = self.read_cursor();
733
734 let make_dto = |p: usize, a: usize| frontend::document_editing::InsertFormattedTextDto {
735 position: to_i64(p),
736 anchor: to_i64(a),
737 text: text.into(),
738 font_family: format.font_family.clone().unwrap_or_default(),
739 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
740 font_bold: format.font_bold.unwrap_or(false),
741 font_italic: format.font_italic.unwrap_or(false),
742 font_underline: format.font_underline.unwrap_or(false),
743 font_strikeout: format.font_strikeout.unwrap_or(false),
744 };
745
746 let queued = {
747 let mut inner = self.doc.lock();
748 let result = match document_editing_commands::insert_formatted_text(
749 &inner.ctx,
750 Some(inner.stack_id),
751 &make_dto(pos, anchor),
752 ) {
753 Ok(r) => r,
754 Err(_) if pos != anchor => {
755 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
757
758 let del_dto = frontend::document_editing::DeleteTextDto {
759 position: to_i64(pos),
760 anchor: to_i64(anchor),
761 };
762 let del_result = document_editing_commands::delete_text(
763 &inner.ctx,
764 Some(inner.stack_id),
765 &del_dto,
766 )?;
767 let del_pos = to_usize(del_result.new_position);
768
769 let ins_result = document_editing_commands::insert_formatted_text(
770 &inner.ctx,
771 Some(inner.stack_id),
772 &make_dto(del_pos, del_pos),
773 )?;
774
775 undo_redo_commands::end_composite(&inner.ctx);
776 ins_result
777 }
778 Err(e) => return Err(e.into()),
779 };
780
781 let edit_pos = pos.min(anchor);
782 let removed = pos.max(anchor) - edit_pos;
783 self.finish_edit_from(
784 &mut inner,
785 edit_pos,
786 removed,
787 to_usize(result.new_position),
788 1,
789 false,
790 origin,
791 )
792 };
793 crate::inner::dispatch_queued_events(queued);
794 Ok(())
795 }
796
797 pub fn insert_block(&self) -> Result<()> {
799 let (pos, anchor) = self.read_cursor();
800 let queued = {
801 let mut inner = self.doc.lock();
802
803 let (insert_pos, removed) = if pos != anchor {
804 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
806 let del_dto = frontend::document_editing::DeleteTextDto {
807 position: to_i64(pos),
808 anchor: to_i64(anchor),
809 };
810 let del_result = document_editing_commands::delete_text(
811 &inner.ctx,
812 Some(inner.stack_id),
813 &del_dto,
814 )?;
815 (
816 to_usize(del_result.new_position),
817 pos.max(anchor) - pos.min(anchor),
818 )
819 } else {
820 (pos, 0)
821 };
822
823 let dto = frontend::document_editing::InsertBlockDto {
824 position: to_i64(insert_pos),
825 anchor: to_i64(insert_pos),
826 };
827 let result =
828 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
829
830 if pos != anchor {
831 undo_redo_commands::end_composite(&inner.ctx);
832 }
833
834 let edit_pos = pos.min(anchor);
835 self.finish_edit(
836 &mut inner,
837 edit_pos,
838 removed,
839 to_usize(result.new_position),
840 2,
841 )
842 };
843 crate::inner::dispatch_queued_events(queued);
844 Ok(())
845 }
846
847 pub fn insert_html_with_origin(&self, html: &str, origin: InsertionOrigin) -> Result<()> {
851 let frag = DocumentFragment::from_html(html);
852 self.insert_fragment_with_origin(&frag, origin)
853 }
854
855 pub fn insert_html(&self, html: &str) -> Result<()> {
856 let frag = DocumentFragment::from_html(html);
858 self.insert_fragment(&frag)
859 }
860
861 pub fn insert_markdown_with_origin(
865 &self,
866 markdown: &str,
867 origin: InsertionOrigin,
868 ) -> Result<()> {
869 let frag = DocumentFragment::from_markdown(markdown);
870 self.insert_fragment_with_origin(&frag, origin)
871 }
872
873 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
874 let frag = DocumentFragment::from_markdown(markdown);
875 self.insert_fragment(&frag)
876 }
877
878 pub fn insert_djot_with_origin(&self, djot: &str, origin: InsertionOrigin) -> Result<()> {
882 let frag = DocumentFragment::from_djot(djot);
883 self.insert_fragment_with_origin(&frag, origin)
884 }
885
886 pub fn insert_djot(&self, djot: &str) -> Result<()> {
887 let frag = DocumentFragment::from_djot(djot);
888 self.insert_fragment(&frag)
889 }
890
891 pub fn insert_footnote_reference(&self, label: &str) -> Result<()> {
904 self.insert_djot(&format!("[^{label}]"))
905 }
906
907 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
910 self.insert_fragment_with_origin(fragment, InsertionOrigin::Unspecified)
911 }
912
913 pub fn insert_fragment_with_origin(
917 &self,
918 fragment: &DocumentFragment,
919 origin: InsertionOrigin,
920 ) -> Result<()> {
921 let (pos, anchor) = self.read_cursor();
922 let queued = {
923 let mut inner = self.doc.lock();
924
925 let (insert_pos, removed) = if pos != anchor {
926 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
927 let del_dto = frontend::document_editing::DeleteTextDto {
928 position: to_i64(pos),
929 anchor: to_i64(anchor),
930 };
931 let del_result = document_editing_commands::delete_text(
932 &inner.ctx,
933 Some(inner.stack_id),
934 &del_dto,
935 )?;
936 (
937 to_usize(del_result.new_position),
938 pos.max(anchor) - pos.min(anchor),
939 )
940 } else {
941 (pos, 0)
942 };
943
944 let dto = frontend::document_editing::InsertFragmentDto {
945 position: to_i64(insert_pos),
946 anchor: to_i64(insert_pos),
947 fragment_data: fragment.raw_data().into(),
948 };
949 let result =
950 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
951
952 if pos != anchor {
953 undo_redo_commands::end_composite(&inner.ctx);
954 }
955
956 let edit_pos = pos.min(anchor);
957 self.finish_edit_from(
958 &mut inner,
959 edit_pos,
960 removed,
961 to_usize(result.new_position),
962 to_usize(result.blocks_added),
963 true,
964 origin,
965 )
966 };
967 crate::inner::dispatch_queued_events(queued);
968 Ok(())
969 }
970
971 pub fn selection(&self) -> DocumentFragment {
973 let (pos, anchor) = self.read_cursor();
974
975 let (extract_pos, extract_anchor) = match self.selection_kind() {
978 SelectionKind::Cells(ref range) => match self.cell_range_positions(range) {
979 Some((start, end)) => (start, end),
980 None => return DocumentFragment::new(),
981 },
982 SelectionKind::Mixed {
983 ref cell_range,
984 text_before,
985 text_after,
986 } => {
987 let (cell_start, cell_end) = match self.cell_range_positions(cell_range) {
988 Some(p) => p,
989 None => return DocumentFragment::new(),
990 };
991 let start = if text_before {
992 pos.min(anchor)
993 } else {
994 cell_start
995 };
996 let end = if text_after {
997 pos.max(anchor)
998 } else {
999 cell_end
1000 };
1001 (start.min(cell_start), end.max(cell_end))
1002 }
1003 SelectionKind::None => return DocumentFragment::new(),
1004 SelectionKind::Text => (pos, anchor),
1005 };
1006
1007 if extract_pos == extract_anchor {
1008 return DocumentFragment::new();
1009 }
1010
1011 let inner = self.doc.lock();
1012 let dto = frontend::document_inspection::ExtractFragmentDto {
1013 position: to_i64(extract_pos),
1014 anchor: to_i64(extract_anchor),
1015 };
1016 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
1017 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
1018 Err(_) => DocumentFragment::new(),
1019 }
1020 }
1021
1022 pub fn insert_image(&self, name: &str, alt: &str, width: u32, height: u32) -> Result<()> {
1028 let (pos, anchor) = self.read_cursor();
1029 let queued = {
1030 let mut inner = self.doc.lock();
1031
1032 let (insert_pos, removed) = if pos != anchor {
1033 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1034 let del_dto = frontend::document_editing::DeleteTextDto {
1035 position: to_i64(pos),
1036 anchor: to_i64(anchor),
1037 };
1038 let del_result = document_editing_commands::delete_text(
1039 &inner.ctx,
1040 Some(inner.stack_id),
1041 &del_dto,
1042 )?;
1043 (
1044 to_usize(del_result.new_position),
1045 pos.max(anchor) - pos.min(anchor),
1046 )
1047 } else {
1048 (pos, 0)
1049 };
1050
1051 let dto = frontend::document_editing::InsertImageDto {
1052 position: to_i64(insert_pos),
1053 anchor: to_i64(insert_pos),
1054 image_name: name.into(),
1055 alt: alt.into(),
1056 width: width as i64,
1057 height: height as i64,
1058 quality: 100,
1059 };
1060 let result =
1061 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
1062
1063 if pos != anchor {
1064 undo_redo_commands::end_composite(&inner.ctx);
1065 }
1066
1067 let edit_pos = pos.min(anchor);
1068 self.finish_edit_ext(
1069 &mut inner,
1070 edit_pos,
1071 removed,
1072 to_usize(result.new_position),
1073 1,
1074 false,
1075 )
1076 };
1077 crate::inner::dispatch_queued_events(queued);
1078 Ok(())
1079 }
1080
1081 pub fn insert_frame(&self) -> Result<()> {
1083 let (pos, anchor) = self.read_cursor();
1084 let queued = {
1085 let mut inner = self.doc.lock();
1086 let dto = frontend::document_editing::InsertFrameDto {
1087 position: to_i64(pos),
1088 anchor: to_i64(anchor),
1089 };
1090 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
1091 inner.modified = true;
1094 inner.invalidate_text_cache();
1095 inner.rehighlight_affected(pos.min(anchor));
1096 inner.queue_event(DocumentEvent::ContentsChanged {
1097 position: pos.min(anchor),
1098 chars_removed: 0,
1099 chars_added: 0,
1100 blocks_affected: 1,
1101 });
1102 inner.check_block_count_changed();
1103 inner.check_flow_changed();
1104 self.queue_undo_redo_event(&mut inner)
1105 };
1106 crate::inner::dispatch_queued_events(queued);
1107 Ok(())
1108 }
1109
1110 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
1116 let (pos, anchor) = self.read_cursor();
1117 let (table_id, queued) = {
1118 let mut inner = self.doc.lock();
1119 let dto = frontend::document_editing::InsertTableDto {
1120 position: to_i64(pos),
1121 anchor: to_i64(anchor),
1122 rows: to_i64(rows),
1123 columns: to_i64(columns),
1124 };
1125 let result =
1126 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1127 let new_pos = to_usize(result.new_position);
1128 let table_id = to_usize(result.table_id);
1129 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
1130 {
1131 let mut d = self.data.lock();
1132 d.position = new_pos;
1133 d.anchor = new_pos;
1134 }
1135 inner.modified = true;
1136 inner.invalidate_text_cache();
1137 inner.rehighlight_affected(pos.min(anchor));
1138 inner.queue_event(DocumentEvent::ContentsChanged {
1139 position: pos.min(anchor),
1140 chars_removed: 0,
1141 chars_added: new_pos - pos.min(anchor),
1142 blocks_affected: 1,
1143 });
1144 inner.check_block_count_changed();
1145 inner.check_flow_changed();
1146 (table_id, self.queue_undo_redo_event(&mut inner))
1147 };
1148 crate::inner::dispatch_queued_events(queued);
1149 Ok(TextTable {
1150 doc: self.doc.clone(),
1151 table_id,
1152 })
1153 }
1154
1155 pub fn current_table(&self) -> Option<TextTable> {
1160 self.current_table_cell().map(|c| c.table)
1161 }
1162
1163 pub fn current_table_cell(&self) -> Option<TableCellRef> {
1168 let pos = self.position();
1169 let inner = self.doc.lock();
1170 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1172 position: to_i64(pos),
1173 };
1174 let block_info =
1175 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1176
1177 let block_id = if to_i64(pos) < block_info.block_start && pos > 0 {
1181 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1182 position: to_i64(pos - 1),
1183 };
1184 let prev_info =
1185 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
1186 prev_info.block_id as usize
1187 } else {
1188 block_info.block_id as usize
1189 };
1190
1191 let block = crate::text_block::TextBlock {
1192 doc: self.doc.clone(),
1193 block_id,
1194 };
1195 drop(inner);
1197 block.table_cell()
1198 }
1199
1200 pub fn current_frame(&self) -> Option<FrameRef> {
1207 let pos = self.position();
1208 let inner = self.doc.lock();
1209 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1210 position: to_i64(pos),
1211 };
1212 let block_info =
1213 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1214 let block_id = block_info.block_id as u64;
1215 cursor_frame_ref(&inner, block_id)
1216 }
1217
1218 pub fn is_in_blockquote(&self) -> bool {
1221 self.current_blockquote_frame_id().is_some()
1222 }
1223
1224 pub fn current_blockquote_frame_id(&self) -> Option<usize> {
1227 let pos = self.position();
1228 let inner = self.doc.lock();
1229 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1230 position: to_i64(pos),
1231 };
1232 let block_info =
1233 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1234 innermost_blockquote_frame_id(&inner, block_info.block_id as u64)
1235 }
1236
1237 pub fn blockquote_depth_at_cursor(&self) -> usize {
1240 let pos = self.position();
1241 let inner = self.doc.lock();
1242 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1243 position: to_i64(pos),
1244 };
1245 let Some(block_info) =
1246 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
1247 else {
1248 return 0;
1249 };
1250 blockquote_depth_for_block(&inner, block_info.block_id as u64)
1251 }
1252
1253 pub fn is_first_block_in_current_frame(&self) -> bool {
1259 matches!(
1260 block_position_in_current_frame(self),
1261 Some(BlockEdge::First) | Some(BlockEdge::OnlyOne)
1262 )
1263 }
1264
1265 pub fn is_last_block_in_current_frame(&self) -> bool {
1269 matches!(
1270 block_position_in_current_frame(self),
1271 Some(BlockEdge::Last) | Some(BlockEdge::OnlyOne)
1272 )
1273 }
1274
1275 pub fn current_block_is_empty(&self) -> bool {
1278 let pos = self.position();
1279 let inner = self.doc.lock();
1280 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1281 position: to_i64(pos),
1282 };
1283 let Some(block_info) =
1284 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
1285 else {
1286 return false;
1287 };
1288 let store = inner.ctx.db_context.get_store();
1289 let block_entity = store
1290 .blocks
1291 .read()
1292 .get(&(block_info.block_id as common::types::EntityId))
1293 .cloned();
1294 match block_entity {
1295 Some(b) => {
1296 let len = common::database::rope_helpers::block_char_length(&b, store);
1297 len == 0
1298 }
1299 None => false,
1300 }
1301 }
1302
1303 pub fn selection_spans_multiple_frames(&self) -> bool {
1307 let (pos, anchor) = self.read_cursor();
1308 if pos == anchor {
1309 return false;
1310 }
1311 let inner = self.doc.lock();
1312 let pos_dto = frontend::document_inspection::GetBlockAtPositionDto {
1313 position: to_i64(pos),
1314 };
1315 let anchor_dto = frontend::document_inspection::GetBlockAtPositionDto {
1316 position: to_i64(anchor),
1317 };
1318 let Some(pos_block) =
1319 document_inspection_commands::get_block_at_position(&inner.ctx, &pos_dto).ok()
1320 else {
1321 return false;
1322 };
1323 let Some(anchor_block) =
1324 document_inspection_commands::get_block_at_position(&inner.ctx, &anchor_dto).ok()
1325 else {
1326 return false;
1327 };
1328 let pos_owner = crate::text_block::find_parent_frame(&inner, pos_block.block_id as u64);
1329 let anchor_owner =
1330 crate::text_block::find_parent_frame(&inner, anchor_block.block_id as u64);
1331 pos_owner != anchor_owner
1332 }
1333
1334 pub fn wrap_selection_in_blockquote(&self) -> Result<()> {
1341 if self.selection_spans_multiple_frames() {
1342 return Err(DocumentError::InvalidArgument(
1343 "Cannot wrap selection in blockquote: selection spans multiple frames".into(),
1344 ));
1345 }
1346 let (start_block_id, end_block_id) = self.resolve_selection_block_range()?;
1347 let dto = frontend::document_editing::WrapBlocksInFrameDto {
1348 start_block_id: start_block_id as i64,
1349 end_block_id: end_block_id as i64,
1350 position: Some(frontend::document_editing::FramePosition::InFlow),
1351 top_margin: None,
1352 bottom_margin: None,
1353 left_margin: None,
1354 right_margin: None,
1355 padding: None,
1356 border: None,
1357 is_blockquote: Some(true),
1358 };
1359 let queued = {
1360 let mut inner = self.doc.lock();
1361 let _result = document_editing_commands::wrap_blocks_in_frame(
1362 &inner.ctx,
1363 Some(inner.stack_id),
1364 &dto,
1365 )?;
1366 inner.modified = true;
1367 inner.queue_event(DocumentEvent::FormatChanged {
1375 position: 0,
1376 length: 0,
1377 kind: crate::flow::FormatChangeKind::Block,
1378 });
1379 self.queue_undo_redo_event(&mut inner)
1380 };
1381 crate::inner::dispatch_queued_events(queued);
1382 Ok(())
1383 }
1384
1385 pub fn insert_blockquote(&self) -> Result<()> {
1389 self.wrap_selection_in_blockquote()
1390 }
1391
1392 pub fn toggle_blockquote(&self) -> Result<()> {
1397 if let Some(frame_id) = self.current_blockquote_frame_id() {
1398 self.unwrap_frame_by_id(frame_id)
1399 } else {
1400 self.wrap_selection_in_blockquote()
1401 }
1402 }
1403
1404 pub fn unwrap_current_frame(&self) -> Result<()> {
1408 let frame_ref = self.current_frame().ok_or_else(|| {
1409 DocumentError::InvalidCursorContext("Cursor is not inside any sub-frame".into())
1410 })?;
1411 self.unwrap_frame_by_id(frame_ref.frame_id)
1412 }
1413
1414 pub fn unwrap_current_block_from_blockquote(&self) -> Result<()> {
1418 if self.current_blockquote_frame_id().is_none() {
1419 return Err(DocumentError::InvalidCursorContext(
1420 "Cursor is not inside a blockquote".into(),
1421 ));
1422 }
1423 let block_id = self.current_block_id_for_mutation()?;
1424 let dto = frontend::document_editing::UnwrapBlockFromFrameDto {
1425 block_id: block_id as i64,
1426 };
1427 let queued = {
1428 let mut inner = self.doc.lock();
1429 let _result = document_editing_commands::unwrap_block_from_frame(
1430 &inner.ctx,
1431 Some(inner.stack_id),
1432 &dto,
1433 )?;
1434 inner.modified = true;
1435 inner.queue_event(DocumentEvent::FormatChanged {
1439 position: 0,
1440 length: 0,
1441 kind: crate::flow::FormatChangeKind::Block,
1442 });
1443 self.queue_undo_redo_event(&mut inner)
1444 };
1445 crate::inner::dispatch_queued_events(queued);
1446 Ok(())
1447 }
1448
1449 pub fn increase_blockquote_depth(&self) -> Result<()> {
1453 self.wrap_selection_in_blockquote()
1454 }
1455
1456 pub fn decrease_blockquote_depth(&self) -> Result<()> {
1462 if self.current_blockquote_frame_id().is_none() {
1463 return Err(DocumentError::InvalidCursorContext(
1464 "Cursor is not inside a blockquote to decrease depth".into(),
1465 ));
1466 }
1467 self.unwrap_current_block_from_blockquote()
1468 }
1469
1470 fn unwrap_frame_by_id(&self, frame_id: usize) -> Result<()> {
1471 let dto = frontend::document_editing::UnwrapFrameDto {
1472 frame_id: frame_id as i64,
1473 };
1474 let queued = {
1475 let mut inner = self.doc.lock();
1476 let _result =
1477 document_editing_commands::unwrap_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
1478 inner.modified = true;
1479 inner.queue_event(DocumentEvent::FormatChanged {
1483 position: 0,
1484 length: 0,
1485 kind: crate::flow::FormatChangeKind::Block,
1486 });
1487 self.queue_undo_redo_event(&mut inner)
1488 };
1489 crate::inner::dispatch_queued_events(queued);
1490 Ok(())
1491 }
1492
1493 fn current_block_id_for_mutation(&self) -> Result<usize> {
1494 let pos = self.position();
1495 let inner = self.doc.lock();
1496 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1497 position: to_i64(pos),
1498 };
1499 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1500 .map_err(|e| anyhow::anyhow!("get_block_at_position: {}", e))?;
1501 Ok(block_info.block_id as usize)
1502 }
1503
1504 fn resolve_selection_block_range(&self) -> Result<(usize, usize)> {
1505 let (pos, anchor) = self.read_cursor();
1506 let lo = pos.min(anchor);
1507 let hi = pos.max(anchor);
1508 let inner = self.doc.lock();
1509 let lo_dto = frontend::document_inspection::GetBlockAtPositionDto {
1510 position: to_i64(lo),
1511 };
1512 let hi_dto = frontend::document_inspection::GetBlockAtPositionDto {
1513 position: to_i64(hi),
1514 };
1515 let lo_block = document_inspection_commands::get_block_at_position(&inner.ctx, &lo_dto)
1516 .map_err(|e| anyhow::anyhow!("get_block_at_position(start): {}", e))?;
1517 let hi_block = document_inspection_commands::get_block_at_position(&inner.ctx, &hi_dto)
1518 .map_err(|e| anyhow::anyhow!("get_block_at_position(end): {}", e))?;
1519 Ok((lo_block.block_id as usize, hi_block.block_id as usize))
1520 }
1521
1522 pub fn remove_table(&self, table_id: usize) -> Result<()> {
1526 let queued = {
1527 let mut inner = self.doc.lock();
1528 let before = crate::document::capture_block_state(&inner);
1535 let dto = frontend::document_editing::RemoveTableDto {
1536 table_id: to_i64(table_id),
1537 };
1538 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1539 inner.modified = true;
1540 inner.invalidate_text_cache();
1541 inner.rehighlight_all();
1542 crate::document::emit_content_change_events(&mut inner, &before);
1543 inner.check_block_count_changed();
1544 inner.check_flow_changed();
1545 self.queue_undo_redo_event(&mut inner)
1546 };
1547 crate::inner::dispatch_queued_events(queued);
1548 Ok(())
1549 }
1550
1551 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1553 let queued = {
1554 let mut inner = self.doc.lock();
1555 let before = crate::document::capture_block_state(&inner);
1556 let dto = frontend::document_editing::InsertTableRowDto {
1557 table_id: to_i64(table_id),
1558 row_index: to_i64(row_index),
1559 };
1560 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1561 inner.modified = true;
1562 inner.invalidate_text_cache();
1563 inner.rehighlight_all();
1564 crate::document::emit_content_change_events(&mut inner, &before);
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 insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1574 let queued = {
1575 let mut inner = self.doc.lock();
1576 let before = crate::document::capture_block_state(&inner);
1577 let dto = frontend::document_editing::InsertTableColumnDto {
1578 table_id: to_i64(table_id),
1579 column_index: to_i64(column_index),
1580 };
1581 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1582 inner.modified = true;
1583 inner.invalidate_text_cache();
1584 inner.rehighlight_all();
1585 crate::document::emit_content_change_events(&mut inner, &before);
1586 inner.check_block_count_changed();
1587 self.queue_undo_redo_event(&mut inner)
1588 };
1589 crate::inner::dispatch_queued_events(queued);
1590 Ok(())
1591 }
1592
1593 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1595 let queued = {
1596 let mut inner = self.doc.lock();
1597 let before = crate::document::capture_block_state(&inner);
1598 let dto = frontend::document_editing::RemoveTableRowDto {
1599 table_id: to_i64(table_id),
1600 row_index: to_i64(row_index),
1601 };
1602 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1603 inner.modified = true;
1604 inner.invalidate_text_cache();
1605 inner.rehighlight_all();
1606 crate::document::emit_content_change_events(&mut inner, &before);
1607 inner.check_block_count_changed();
1608 self.queue_undo_redo_event(&mut inner)
1609 };
1610 crate::inner::dispatch_queued_events(queued);
1611 Ok(())
1612 }
1613
1614 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1616 let queued = {
1617 let mut inner = self.doc.lock();
1618 let before = crate::document::capture_block_state(&inner);
1619 let dto = frontend::document_editing::RemoveTableColumnDto {
1620 table_id: to_i64(table_id),
1621 column_index: to_i64(column_index),
1622 };
1623 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1624 inner.modified = true;
1625 inner.invalidate_text_cache();
1626 inner.rehighlight_all();
1627 crate::document::emit_content_change_events(&mut inner, &before);
1628 inner.check_block_count_changed();
1629 self.queue_undo_redo_event(&mut inner)
1630 };
1631 crate::inner::dispatch_queued_events(queued);
1632 Ok(())
1633 }
1634
1635 pub fn merge_table_cells(
1637 &self,
1638 table_id: usize,
1639 start_row: usize,
1640 start_column: usize,
1641 end_row: usize,
1642 end_column: usize,
1643 ) -> Result<()> {
1644 let queued = {
1645 let mut inner = self.doc.lock();
1646 let before = crate::document::capture_block_state(&inner);
1647 let dto = frontend::document_editing::MergeTableCellsDto {
1648 table_id: to_i64(table_id),
1649 start_row: to_i64(start_row),
1650 start_column: to_i64(start_column),
1651 end_row: to_i64(end_row),
1652 end_column: to_i64(end_column),
1653 };
1654 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
1655 inner.modified = true;
1656 inner.invalidate_text_cache();
1657 inner.rehighlight_all();
1658 crate::document::emit_content_change_events(&mut inner, &before);
1659 inner.check_block_count_changed();
1660 self.queue_undo_redo_event(&mut inner)
1661 };
1662 crate::inner::dispatch_queued_events(queued);
1663 Ok(())
1664 }
1665
1666 pub fn split_table_cell(
1668 &self,
1669 cell_id: usize,
1670 split_rows: usize,
1671 split_columns: usize,
1672 ) -> Result<()> {
1673 let queued = {
1674 let mut inner = self.doc.lock();
1675 let before = crate::document::capture_block_state(&inner);
1676 let dto = frontend::document_editing::SplitTableCellDto {
1677 cell_id: to_i64(cell_id),
1678 split_rows: to_i64(split_rows),
1679 split_columns: to_i64(split_columns),
1680 };
1681 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
1682 inner.modified = true;
1683 inner.invalidate_text_cache();
1684 inner.rehighlight_all();
1685 crate::document::emit_content_change_events(&mut inner, &before);
1686 inner.check_block_count_changed();
1687 self.queue_undo_redo_event(&mut inner)
1688 };
1689 crate::inner::dispatch_queued_events(queued);
1690 Ok(())
1691 }
1692
1693 pub fn set_table_format(
1697 &self,
1698 table_id: usize,
1699 format: &crate::flow::TableFormat,
1700 ) -> Result<()> {
1701 let queued = {
1702 let mut inner = self.doc.lock();
1703 let dto = format.to_set_dto(table_id);
1704 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1705 inner.modified = true;
1706 inner.queue_event(DocumentEvent::FormatChanged {
1707 position: 0,
1708 length: 0,
1709 kind: crate::flow::FormatChangeKind::Block,
1710 });
1711 self.queue_undo_redo_event(&mut inner)
1712 };
1713 crate::inner::dispatch_queued_events(queued);
1714 Ok(())
1715 }
1716
1717 pub fn set_table_cell_format(
1719 &self,
1720 cell_id: usize,
1721 format: &crate::flow::CellFormat,
1722 ) -> Result<()> {
1723 let queued = {
1724 let mut inner = self.doc.lock();
1725 let dto = format.to_set_dto(cell_id);
1726 document_formatting_commands::set_table_cell_format(
1727 &inner.ctx,
1728 Some(inner.stack_id),
1729 &dto,
1730 )?;
1731 inner.modified = true;
1732 inner.queue_event(DocumentEvent::FormatChanged {
1733 position: 0,
1734 length: 0,
1735 kind: crate::flow::FormatChangeKind::Block,
1736 });
1737 self.queue_undo_redo_event(&mut inner)
1738 };
1739 crate::inner::dispatch_queued_events(queued);
1740 Ok(())
1741 }
1742
1743 pub fn remove_current_table(&self) -> Result<()> {
1748 let table = self.current_table().ok_or_else(|| {
1749 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1750 })?;
1751 self.remove_table(table.id())
1752 }
1753
1754 pub fn insert_row_above(&self) -> Result<()> {
1757 let cell_ref = self.current_table_cell().ok_or_else(|| {
1758 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1759 })?;
1760 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
1761 }
1762
1763 pub fn insert_row_below(&self) -> Result<()> {
1766 let cell_ref = self.current_table_cell().ok_or_else(|| {
1767 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1768 })?;
1769 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
1770 }
1771
1772 pub fn insert_column_before(&self) -> Result<()> {
1775 let cell_ref = self.current_table_cell().ok_or_else(|| {
1776 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1777 })?;
1778 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
1779 }
1780
1781 pub fn insert_column_after(&self) -> Result<()> {
1784 let cell_ref = self.current_table_cell().ok_or_else(|| {
1785 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1786 })?;
1787 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
1788 }
1789
1790 pub fn remove_current_row(&self) -> Result<()> {
1793 let cell_ref = self.current_table_cell().ok_or_else(|| {
1794 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1795 })?;
1796 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
1797 }
1798
1799 pub fn remove_current_column(&self) -> Result<()> {
1802 let cell_ref = self.current_table_cell().ok_or_else(|| {
1803 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1804 })?;
1805 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
1806 }
1807
1808 pub fn merge_selected_cells(&self) -> Result<()> {
1815 let pos_cell = self.current_table_cell().ok_or_else(|| {
1816 DocumentError::InvalidCursorContext("cursor position is not inside a table".into())
1817 })?;
1818
1819 let (_pos, anchor) = self.read_cursor();
1821 let anchor_cell = {
1822 let inner = self.doc.lock();
1824 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1825 position: to_i64(anchor),
1826 };
1827 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1828 .map_err(|_| {
1829 DocumentError::InvalidCursorContext(
1830 "cursor anchor is not inside a table".into(),
1831 )
1832 })?;
1833 let block = crate::text_block::TextBlock {
1834 doc: self.doc.clone(),
1835 block_id: block_info.block_id as usize,
1836 };
1837 drop(inner);
1838 block.table_cell().ok_or_else(|| {
1839 DocumentError::InvalidCursorContext("cursor anchor is not inside a table".into())
1840 })?
1841 };
1842
1843 if pos_cell.table.id() != anchor_cell.table.id() {
1844 return Err(DocumentError::InvalidArgument(
1845 "position and anchor are in different tables".into(),
1846 ));
1847 }
1848
1849 let start_row = pos_cell.row.min(anchor_cell.row);
1850 let start_col = pos_cell.column.min(anchor_cell.column);
1851 let end_row = pos_cell.row.max(anchor_cell.row);
1852 let end_col = pos_cell.column.max(anchor_cell.column);
1853
1854 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
1855 }
1856
1857 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1860 let cell_ref = self.current_table_cell().ok_or_else(|| {
1861 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1862 })?;
1863 let cell = cell_ref
1865 .table
1866 .cell(cell_ref.row, cell_ref.column)
1867 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1868 self.split_table_cell(cell.id(), split_rows, split_columns)
1870 }
1871
1872 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1875 let table = self.current_table().ok_or_else(|| {
1876 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1877 })?;
1878 self.set_table_format(table.id(), format)
1879 }
1880
1881 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1884 let cell_ref = self.current_table_cell().ok_or_else(|| {
1885 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1886 })?;
1887 let cell = cell_ref
1888 .table
1889 .cell(cell_ref.row, cell_ref.column)
1890 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1891 self.set_table_cell_format(cell.id(), format)
1892 }
1893
1894 pub fn selection_kind(&self) -> crate::flow::SelectionKind {
1902 use crate::flow::{CellRange, SelectionKind};
1903
1904 {
1906 let d = self.data.lock();
1907 if let Some(ref range) = d.cell_selection_override {
1908 return SelectionKind::Cells(range.clone());
1909 }
1910 if d.position == d.anchor {
1911 return SelectionKind::None;
1912 }
1913 }
1914
1915 let (pos, anchor) = self.read_cursor();
1916
1917 let pos_cell = self.table_cell_at(pos);
1919 let anchor_cell = self.table_cell_at(anchor);
1920
1921 match (&pos_cell, &anchor_cell) {
1922 (None, None) => {
1923 let (start, end) = (pos.min(anchor), pos.max(anchor));
1927 if let Some(t) = self.find_table_between(start, end) {
1928 let table_id = t.id();
1929 let rows = t.rows();
1930 let cols = t.columns();
1931 let range = CellRange {
1932 table_id,
1933 start_row: 0,
1934 start_col: 0,
1935 end_row: if rows > 0 { rows - 1 } else { 0 },
1936 end_col: if cols > 0 { cols - 1 } else { 0 },
1937 };
1938 let spans = self.collect_cell_spans(table_id);
1939 SelectionKind::Mixed {
1940 cell_range: range.expand_for_spans(&spans),
1941 text_before: true,
1942 text_after: true,
1943 }
1944 } else {
1945 SelectionKind::Text
1946 }
1947 }
1948 (Some(pc), Some(ac)) => {
1949 if pc.table.id() != ac.table.id() {
1950 return SelectionKind::Text;
1952 }
1953 if pc.row == ac.row && pc.column == ac.column {
1954 return SelectionKind::Text;
1956 }
1957 let range = CellRange {
1959 table_id: pc.table.id(),
1960 start_row: pc.row.min(ac.row),
1961 start_col: pc.column.min(ac.column),
1962 end_row: pc.row.max(ac.row),
1963 end_col: pc.column.max(ac.column),
1964 };
1965 let spans = self.collect_cell_spans(pc.table.id());
1966 SelectionKind::Cells(range.expand_for_spans(&spans))
1967 }
1968 (Some(tc), None) | (None, Some(tc)) => {
1969 let table_id = tc.table.id();
1973 let rows = tc.table.rows();
1974 let cols = tc.table.columns();
1975
1976 let inside_pos = if pos_cell.is_some() { pos } else { anchor };
1977 let outside_pos = if pos_cell.is_some() { anchor } else { pos };
1978
1979 let text_before = outside_pos < inside_pos;
1980 let text_after = !text_before;
1981
1982 let range = CellRange {
1983 table_id,
1984 start_row: 0,
1985 start_col: 0,
1986 end_row: if rows > 0 { rows - 1 } else { 0 },
1987 end_col: if cols > 0 { cols - 1 } else { 0 },
1988 };
1989 let spans = self.collect_cell_spans(table_id);
1990 SelectionKind::Mixed {
1991 cell_range: range.expand_for_spans(&spans),
1992 text_before,
1993 text_after,
1994 }
1995 }
1996 }
1997 }
1998
1999 pub fn is_cell_selection(&self) -> bool {
2001 matches!(
2002 self.selection_kind(),
2003 crate::flow::SelectionKind::Cells(_) | crate::flow::SelectionKind::Mixed { .. }
2004 )
2005 }
2006
2007 pub fn selected_cell_range(&self) -> Option<crate::flow::CellRange> {
2009 match self.selection_kind() {
2010 crate::flow::SelectionKind::Cells(r) => Some(r),
2011 crate::flow::SelectionKind::Mixed { cell_range, .. } => Some(cell_range),
2012 _ => None,
2013 }
2014 }
2015
2016 pub fn selected_cells(&self) -> Vec<TableCellRef> {
2018 let range = match self.selected_cell_range() {
2019 Some(r) => r,
2020 None => return Vec::new(),
2021 };
2022 let table = TextTable {
2023 doc: self.doc.clone(),
2024 table_id: range.table_id,
2025 };
2026 let mut cells = Vec::new();
2027 for row in range.start_row..=range.end_row {
2028 for col in range.start_col..=range.end_col {
2029 if table.cell(row, col).is_some() {
2030 cells.push(TableCellRef {
2031 table: table.clone(),
2032 row,
2033 column: col,
2034 });
2035 }
2036 }
2037 }
2038 cells
2039 }
2040
2041 pub fn select_table_cell(&self, table_id: usize, row: usize, col: usize) {
2045 let mut d = self.data.lock();
2046 d.cell_selection_override = Some(crate::flow::CellRange {
2047 table_id,
2048 start_row: row,
2049 start_col: col,
2050 end_row: row,
2051 end_col: col,
2052 });
2053 }
2054
2055 pub fn select_cell_range(
2057 &self,
2058 table_id: usize,
2059 start_row: usize,
2060 start_col: usize,
2061 end_row: usize,
2062 end_col: usize,
2063 ) {
2064 let range = crate::flow::CellRange {
2065 table_id,
2066 start_row,
2067 start_col,
2068 end_row,
2069 end_col,
2070 };
2071 let spans = self.collect_cell_spans(table_id);
2072 let mut d = self.data.lock();
2073 d.cell_selection_override = Some(range.expand_for_spans(&spans));
2074 }
2075
2076 pub fn clear_cell_selection(&self) {
2078 let mut d = self.data.lock();
2079 d.cell_selection_override = None;
2080 }
2081
2082 fn cell_range_positions(&self, range: &CellRange) -> Option<(usize, usize)> {
2085 let inner = self.doc.lock();
2086 let main_frame_id = get_main_frame_id(&inner);
2087 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2088 drop(inner);
2089
2090 let table = flow.into_iter().find_map(|e| match e {
2092 FlowElement::Table(t) if t.id() == range.table_id => Some(t),
2093 _ => None,
2094 })?;
2095
2096 let mut min_pos = usize::MAX;
2097 let mut max_pos = 0usize;
2098
2099 for row in range.start_row..=range.end_row {
2100 for col in range.start_col..=range.end_col {
2101 if let Some(cell) = table.cell(row, col) {
2102 for block in cell.blocks() {
2103 let bp = block.position();
2104 let bl = block.length();
2105 min_pos = min_pos.min(bp);
2106 max_pos = max_pos.max(bp + bl);
2107 }
2108 }
2109 }
2110 }
2111
2112 if min_pos == usize::MAX {
2113 return None;
2114 }
2115
2116 Some((min_pos, max_pos + 1))
2118 }
2119
2120 fn table_cell_at(&self, position: usize) -> Option<TableCellRef> {
2124 let inner = self.doc.lock();
2125 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2126 position: to_i64(position),
2127 };
2128 let block_info =
2129 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2130
2131 let block_id = if to_i64(position) < block_info.block_start && position > 0 {
2132 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2133 position: to_i64(position - 1),
2134 };
2135 let prev_info =
2136 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
2137 prev_info.block_id as usize
2138 } else {
2139 block_info.block_id as usize
2140 };
2141
2142 let block = crate::text_block::TextBlock {
2143 doc: self.doc.clone(),
2144 block_id,
2145 };
2146 drop(inner);
2147 block.table_cell()
2148 }
2149
2150 fn table_boundary_position(&self, table_id: usize, before: bool) -> Option<usize> {
2161 let inner = self.doc.lock();
2162 let main_frame_id = get_main_frame_id(&inner);
2163 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2164 drop(inner);
2165
2166 let idx = flow
2168 .iter()
2169 .position(|e| matches!(e, FlowElement::Table(t) if t.id() == table_id))?;
2170
2171 if before {
2172 for i in (0..idx).rev() {
2174 if let FlowElement::Block(b) = &flow[i] {
2175 return Some(b.position() + b.length());
2176 }
2177 }
2178 } else {
2179 for item in flow.iter().skip(idx + 1) {
2181 if let FlowElement::Block(b) = item {
2182 return Some(b.position());
2183 }
2184 }
2185 }
2186 None
2187 }
2188
2189 fn find_table_between(&self, start: usize, end: usize) -> Option<TextTable> {
2191 let inner = self.doc.lock();
2192 let main_frame_id = get_main_frame_id(&inner);
2193 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2194 drop(inner);
2195
2196 for elem in flow {
2197 if let FlowElement::Table(t) = elem {
2198 if let Some(first_cell) = t.cell(0, 0) {
2201 let blocks = first_cell.blocks();
2202 if let Some(fb) = blocks.first() {
2203 let p = fb.position();
2204 if p > start && p < end {
2205 return Some(t);
2206 }
2207 }
2208 }
2209 }
2210 }
2211 None
2212 }
2213
2214 fn collect_cell_spans(&self, table_id: usize) -> Vec<(usize, usize, usize, usize)> {
2216 let inner = self.doc.lock();
2217 let table_dto =
2218 match frontend::commands::table_commands::get_table(&inner.ctx, &(table_id as u64))
2219 .ok()
2220 .flatten()
2221 {
2222 Some(t) => t,
2223 None => return Vec::new(),
2224 };
2225
2226 let mut spans = Vec::with_capacity(table_dto.cells.len());
2227 for &cell_id in &table_dto.cells {
2228 if let Some(cell) =
2229 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &cell_id)
2230 .ok()
2231 .flatten()
2232 {
2233 spans.push((
2234 cell.row as usize,
2235 cell.column as usize,
2236 cell.row_span.max(1) as usize,
2237 cell.column_span.max(1) as usize,
2238 ));
2239 }
2240 }
2241 spans
2242 }
2243
2244 pub fn delete_char(&self) -> Result<()> {
2246 let (pos, anchor) = self.read_cursor();
2247 let (del_pos, del_anchor) = if pos != anchor {
2248 (pos, anchor)
2249 } else {
2250 let end = {
2252 let inner = self.doc.lock();
2253 max_cursor_position_of(&inner).unwrap_or(0)
2254 };
2255 if pos >= end {
2256 return Ok(());
2257 }
2258 let to = self.next_grapheme_boundary(pos);
2262 if to == pos {
2263 return Ok(());
2264 }
2265 (pos, to)
2266 };
2267 self.do_delete(del_pos, del_anchor)
2268 }
2269
2270 pub fn delete_previous_char(&self) -> Result<()> {
2272 let (pos, anchor) = self.read_cursor();
2273 let (del_pos, del_anchor) = if pos != anchor {
2274 (pos, anchor)
2275 } else if pos > 0 {
2276 let from = self.prev_grapheme_boundary(pos);
2277 if from == pos {
2278 return Ok(());
2279 }
2280 (from, pos)
2281 } else {
2282 return Ok(());
2283 };
2284 self.do_delete(del_pos, del_anchor)
2285 }
2286
2287 pub fn remove_selected_text(&self) -> Result<String> {
2289 let (pos, anchor) = self.read_cursor();
2290 if pos == anchor {
2291 return Ok(String::new());
2292 }
2293 let queued = {
2294 let mut inner = self.doc.lock();
2295 let dto = frontend::document_editing::DeleteTextDto {
2296 position: to_i64(pos),
2297 anchor: to_i64(anchor),
2298 };
2299 let result =
2300 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2301 let edit_pos = pos.min(anchor);
2302 let removed = pos.max(anchor) - edit_pos;
2303 let new_pos = to_usize(result.new_position);
2304 inner.adjust_cursors(edit_pos, removed, 0);
2305 {
2306 let mut d = self.data.lock();
2307 d.position = new_pos;
2308 d.anchor = new_pos;
2309 }
2310 inner.modified = true;
2311 inner.invalidate_text_cache();
2312 inner.rehighlight_affected(edit_pos);
2313 inner.queue_event(DocumentEvent::ContentsChanged {
2314 position: edit_pos,
2315 chars_removed: removed,
2316 chars_added: 0,
2317 blocks_affected: 1,
2318 });
2319 inner.check_block_count_changed();
2320 inner.check_flow_changed();
2321 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
2323 };
2324 crate::inner::dispatch_queued_events(queued.1);
2325 Ok(queued.0)
2326 }
2327
2328 pub fn current_list(&self) -> Option<crate::TextList> {
2333 let pos = self.position();
2334 let inner = self.doc.lock();
2335 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2336 position: to_i64(pos),
2337 };
2338 let block_info =
2339 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2340 let block = crate::text_block::TextBlock {
2341 doc: self.doc.clone(),
2342 block_id: block_info.block_id as usize,
2343 };
2344 drop(inner);
2345 block.list()
2346 }
2347
2348 pub fn create_list(&self, style: ListStyle) -> Result<()> {
2350 let (pos, anchor) = self.read_cursor();
2351 let queued = {
2352 let mut inner = self.doc.lock();
2353 let dto = frontend::document_editing::CreateListDto {
2354 position: to_i64(pos),
2355 anchor: to_i64(anchor),
2356 style: style.clone(),
2357 };
2358 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2359 inner.modified = true;
2360 inner.rehighlight_affected(pos.min(anchor));
2361 inner.queue_event(DocumentEvent::ContentsChanged {
2362 position: pos.min(anchor),
2363 chars_removed: 0,
2364 chars_added: 0,
2365 blocks_affected: 1,
2366 });
2367 self.queue_undo_redo_event(&mut inner)
2368 };
2369 crate::inner::dispatch_queued_events(queued);
2370 Ok(())
2371 }
2372
2373 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
2375 let (pos, anchor) = self.read_cursor();
2376 let queued = {
2377 let mut inner = self.doc.lock();
2378 let dto = frontend::document_editing::InsertListDto {
2379 position: to_i64(pos),
2380 anchor: to_i64(anchor),
2381 style: style.clone(),
2382 };
2383 let result =
2384 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2385 let edit_pos = pos.min(anchor);
2386 let removed = pos.max(anchor) - edit_pos;
2387 self.finish_edit_ext(
2388 &mut inner,
2389 edit_pos,
2390 removed,
2391 to_usize(result.new_position),
2392 1,
2393 false,
2394 )
2395 };
2396 crate::inner::dispatch_queued_events(queued);
2397 Ok(())
2398 }
2399
2400 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
2402 let queued = {
2403 let mut inner = self.doc.lock();
2404 let dto = format.to_set_dto(list_id);
2405 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2406 inner.modified = true;
2407 inner.queue_event(DocumentEvent::FormatChanged {
2408 position: 0,
2409 length: 0,
2410 kind: crate::flow::FormatChangeKind::List,
2411 });
2412 self.queue_undo_redo_event(&mut inner)
2413 };
2414 crate::inner::dispatch_queued_events(queued);
2415 Ok(())
2416 }
2417
2418 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
2421 let list = self.current_list().ok_or_else(|| {
2422 DocumentError::InvalidCursorContext("cursor is not inside a list".into())
2423 })?;
2424 self.set_list_format(list.id(), format)
2425 }
2426
2427 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
2429 let queued = {
2430 let mut inner = self.doc.lock();
2431 let dto = frontend::document_editing::AddBlockToListDto {
2432 block_id: to_i64(block_id),
2433 list_id: to_i64(list_id),
2434 };
2435 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2436 inner.modified = true;
2437 inner.queue_event(DocumentEvent::FormatChanged {
2444 position: 0,
2445 length: 0,
2446 kind: crate::flow::FormatChangeKind::List,
2447 });
2448 self.queue_undo_redo_event(&mut inner)
2449 };
2450 crate::inner::dispatch_queued_events(queued);
2451 Ok(())
2452 }
2453
2454 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
2456 let pos = self.position();
2457 let inner = self.doc.lock();
2458 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2459 position: to_i64(pos),
2460 };
2461 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2462 drop(inner);
2463 self.add_block_to_list(block_info.block_id as usize, list_id)
2464 }
2465
2466 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
2468 let queued = {
2469 let mut inner = self.doc.lock();
2470 let dto = frontend::document_editing::RemoveBlockFromListDto {
2471 block_id: to_i64(block_id),
2472 };
2473 document_editing_commands::remove_block_from_list(
2474 &inner.ctx,
2475 Some(inner.stack_id),
2476 &dto,
2477 )?;
2478 inner.modified = true;
2479 inner.queue_event(DocumentEvent::FormatChanged {
2482 position: 0,
2483 length: 0,
2484 kind: crate::flow::FormatChangeKind::List,
2485 });
2486 self.queue_undo_redo_event(&mut inner)
2487 };
2488 crate::inner::dispatch_queued_events(queued);
2489 Ok(())
2490 }
2491
2492 pub fn remove_current_block_from_list(&self) -> Result<()> {
2495 let pos = self.position();
2496 let inner = self.doc.lock();
2497 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2498 position: to_i64(pos),
2499 };
2500 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2501 drop(inner);
2502 self.remove_block_from_list(block_info.block_id as usize)
2503 }
2504
2505 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
2508 let list = crate::text_list::TextList {
2509 doc: self.doc.clone(),
2510 list_id,
2511 };
2512 let block = list.item(index).ok_or_else(|| {
2513 DocumentError::OutOfRange(format!("list item index {index} out of range"))
2514 })?;
2515 self.remove_block_from_list(block.id())
2516 }
2517
2518 pub fn char_format(&self) -> Result<TextFormat> {
2523 let pos = self.position();
2524 let inner = self.doc.lock();
2525
2526 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2528 position: to_i64(pos),
2529 };
2530 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2531 let block_id = block_info.block_id as u64;
2532 let mut block_dto =
2533 frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2534 .ok_or_else(|| DocumentError::NotFound("block not found at position".into()))?;
2535 let store = inner.ctx.db_context.get_store();
2536 crate::inner::refresh_block_position(&mut block_dto, store);
2537
2538 let local_char = pos.saturating_sub(block_dto.document_position as usize);
2541 let entity: common::entities::Block = block_dto.clone().into();
2542 let plain_owned = common::database::rope_helpers::block_content_via_store(&entity, store);
2543 let plain: &str = &plain_owned;
2544 let byte_offset: u32 = plain
2545 .char_indices()
2546 .nth(local_char)
2547 .map(|(b, _)| b as u32)
2548 .unwrap_or(plain.len() as u32);
2549
2550 let images = store
2553 .block_images
2554 .read()
2555 .get(&block_id)
2556 .cloned()
2557 .unwrap_or_default();
2558 if let Some(img) = images.iter().find(|i| i.byte_offset == byte_offset) {
2559 return Ok(TextFormat::from(&img.format));
2560 }
2561
2562 let runs = store
2564 .format_runs
2565 .read()
2566 .get(&block_id)
2567 .cloned()
2568 .unwrap_or_default();
2569 let fmt = runs
2570 .iter()
2571 .find(|r| r.byte_start <= byte_offset && byte_offset < r.byte_end)
2572 .map(|r| TextFormat::from(&r.format))
2573 .unwrap_or_default();
2574 Ok(fmt)
2575 }
2576
2577 pub fn block_format(&self) -> Result<BlockFormat> {
2584 let pos = self.position();
2585 let inner = self.doc.lock();
2586 let block_info = crate::inner::block_at_caret_dto(&inner.ctx, pos)?;
2587 let block_id = block_info.block_id as u64;
2588 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2589 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
2590 Ok(BlockFormat::from(&block))
2591 }
2592
2593 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
2597 let (pos, anchor) = self.read_cursor();
2598 let queued = {
2599 let mut inner = self.doc.lock();
2600 let dto = format.to_set_dto(pos, anchor);
2601 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2602 let start = pos.min(anchor);
2603 let length = pos.max(anchor) - start;
2604 inner.modified = true;
2605 inner.queue_event(DocumentEvent::FormatChanged {
2606 position: start,
2607 length,
2608 kind: crate::flow::FormatChangeKind::Character,
2609 });
2610 self.queue_undo_redo_event(&mut inner)
2611 };
2612 crate::inner::dispatch_queued_events(queued);
2613 Ok(())
2614 }
2615
2616 pub fn link_at_caret(&self) -> Option<LinkExtent> {
2626 let pos = self.position();
2627 let block_id = {
2628 let inner = self.doc.lock();
2629 crate::inner::block_at_caret_dto(&inner.ctx, pos)
2630 .ok()?
2631 .block_id as usize
2632 };
2633 let block = TextBlock {
2634 doc: self.doc.clone(),
2635 block_id,
2636 };
2637 crate::link_extent::link_extent_at(&block, pos)
2638 }
2639
2640 pub fn clear_char_anchor(&self) -> Result<()> {
2652 self.merge_char_format(&TextFormat {
2653 clear_link: true,
2654 ..Default::default()
2655 })
2656 }
2657
2658 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
2660 let (pos, anchor) = self.read_cursor();
2661 let queued = {
2662 let mut inner = self.doc.lock();
2663 let dto = format.to_merge_dto(pos, anchor);
2664 document_formatting_commands::merge_text_format(
2665 &inner.ctx,
2666 Some(inner.stack_id),
2667 &dto,
2668 )?;
2669 let start = pos.min(anchor);
2670 let length = pos.max(anchor) - start;
2671 inner.modified = true;
2672 inner.queue_event(DocumentEvent::FormatChanged {
2673 position: start,
2674 length,
2675 kind: crate::flow::FormatChangeKind::Character,
2676 });
2677 self.queue_undo_redo_event(&mut inner)
2678 };
2679 crate::inner::dispatch_queued_events(queued);
2680 Ok(())
2681 }
2682
2683 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
2685 let (pos, anchor) = self.read_cursor();
2686 let queued = {
2687 let mut inner = self.doc.lock();
2688 let dto = format.to_set_dto(pos, anchor);
2689 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2690 let start = pos.min(anchor);
2691 let length = pos.max(anchor) - start;
2692 inner.modified = true;
2693 inner.queue_event(DocumentEvent::FormatChanged {
2694 position: start,
2695 length,
2696 kind: crate::flow::FormatChangeKind::Block,
2697 });
2698 self.queue_undo_redo_event(&mut inner)
2699 };
2700 crate::inner::dispatch_queued_events(queued);
2701 Ok(())
2702 }
2703
2704 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
2706 let (pos, anchor) = self.read_cursor();
2707 let queued = {
2708 let mut inner = self.doc.lock();
2709 let dto = format.to_set_dto(pos, anchor, frame_id);
2710 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2711 let start = pos.min(anchor);
2712 let length = pos.max(anchor) - start;
2713 inner.modified = true;
2714 inner.queue_event(DocumentEvent::FormatChanged {
2715 position: start,
2716 length,
2717 kind: crate::flow::FormatChangeKind::Block,
2718 });
2719 self.queue_undo_redo_event(&mut inner)
2720 };
2721 crate::inner::dispatch_queued_events(queued);
2722 Ok(())
2723 }
2724
2725 pub fn begin_edit_block(&self) {
2729 let inner = self.doc.lock();
2730 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
2731 }
2732
2733 pub fn end_edit_block(&self) {
2735 let inner = self.doc.lock();
2736 undo_redo_commands::end_composite(&inner.ctx);
2737 }
2738
2739 pub fn join_previous_edit_block(&self) {
2746 self.begin_edit_block();
2747 }
2748
2749 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
2753 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
2754 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
2755 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
2756 inner.take_queued_events()
2757 }
2758
2759 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
2760 let queued = {
2761 let mut inner = self.doc.lock();
2762 let dto = frontend::document_editing::DeleteTextDto {
2763 position: to_i64(pos),
2764 anchor: to_i64(anchor),
2765 };
2766 let result =
2767 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2768 let edit_pos = pos.min(anchor);
2769 let removed = pos.max(anchor) - edit_pos;
2770 let new_pos = to_usize(result.new_position);
2771 inner.adjust_cursors(edit_pos, removed, 0);
2772 {
2773 let mut d = self.data.lock();
2774 d.position = new_pos;
2775 d.anchor = new_pos;
2776 }
2777 inner.modified = true;
2778 inner.invalidate_text_cache();
2779 inner.rehighlight_affected(edit_pos);
2780 inner.queue_event(DocumentEvent::ContentsChanged {
2781 position: edit_pos,
2782 chars_removed: removed,
2783 chars_added: 0,
2784 blocks_affected: 1,
2785 });
2786 inner.check_block_count_changed();
2787 inner.check_flow_changed();
2788 self.queue_undo_redo_event(&mut inner)
2789 };
2790 crate::inner::dispatch_queued_events(queued);
2791 Ok(())
2792 }
2793
2794 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
2796 let pos = self.position();
2797 match op {
2798 MoveOperation::NoMove => pos,
2799 MoveOperation::Start => 0,
2800 MoveOperation::End => {
2801 let inner = self.doc.lock();
2802 max_cursor_position_of(&inner).unwrap_or(pos)
2803 }
2804 MoveOperation::NextCharacter | MoveOperation::Right => {
2805 let mut cur = pos;
2806 for _ in 0..n {
2807 let next = self.next_grapheme_boundary(cur);
2808 if next == cur {
2809 break;
2810 }
2811 cur = next;
2812 }
2813 cur
2814 }
2815 MoveOperation::PreviousCharacter | MoveOperation::Left => {
2816 let mut cur = pos;
2817 for _ in 0..n {
2818 let prev = self.prev_grapheme_boundary(cur);
2819 if prev == cur {
2820 break;
2821 }
2822 cur = prev;
2823 }
2824 cur
2825 }
2826 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
2827 let inner = self.doc.lock();
2828 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2829 position: to_i64(pos),
2830 };
2831 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2832 .map(|info| to_usize(info.block_start))
2833 .unwrap_or(pos)
2834 }
2835 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
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) + to_usize(info.block_length))
2842 .unwrap_or(pos)
2843 }
2844 MoveOperation::NextBlock => {
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| {
2851 to_usize(info.block_start) + to_usize(info.block_length) + 1
2853 })
2854 .unwrap_or(pos)
2855 }
2856 MoveOperation::PreviousBlock => {
2857 let inner = self.doc.lock();
2858 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2859 position: to_i64(pos),
2860 };
2861 let block_start =
2862 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2863 .map(|info| to_usize(info.block_start))
2864 .unwrap_or(pos);
2865 if block_start >= 2 {
2866 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2868 position: to_i64(block_start - 2),
2869 };
2870 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
2871 .map(|info| to_usize(info.block_start))
2872 .unwrap_or(0)
2873 } else {
2874 0
2875 }
2876 }
2877 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
2878 let (_, end) = self.find_word_boundaries(pos);
2879 if end == pos {
2881 let inner = self.doc.lock();
2883 let max_pos = max_cursor_position_of(&inner).unwrap_or(0);
2884 let scan_len = max_pos.saturating_sub(pos).min(64);
2885 if scan_len == 0 {
2886 return pos;
2887 }
2888 let dto = frontend::document_inspection::GetTextAtPositionDto {
2889 position: to_i64(pos),
2890 length: to_i64(scan_len),
2891 };
2892 if let Ok(r) =
2893 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
2894 {
2895 for (i, ch) in r.text.chars().enumerate() {
2896 if ch.is_alphanumeric() || ch == '_' {
2897 let word_pos = pos + i;
2899 drop(inner);
2900 let (_, word_end) = self.find_word_boundaries(word_pos);
2901 return word_end;
2902 }
2903 }
2904 }
2905 pos + scan_len
2906 } else {
2907 end
2908 }
2909 }
2910 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
2911 let (start, _) = self.find_word_boundaries(pos);
2912 if start < pos {
2913 start
2914 } else if pos > 0 {
2915 let mut search = pos - 1;
2918 loop {
2919 let (ws, we) = self.find_word_boundaries(search);
2920 if ws < we {
2921 break ws;
2923 }
2924 if search == 0 {
2926 break 0;
2927 }
2928 search -= 1;
2929 }
2930 } else {
2931 0
2932 }
2933 }
2934 MoveOperation::StartOfSentence | MoveOperation::PreviousSentence => {
2935 let mut cur = pos;
2936 for _ in 0..n.max(1) {
2937 let start = match self.find_sentence_boundaries(cur) {
2938 Some((start, _)) => start,
2939 None => break,
2940 };
2941 if start < cur && op == MoveOperation::StartOfSentence {
2944 cur = start;
2945 } else if cur > 0 {
2946 match self.find_sentence_boundaries(cur - 1) {
2947 Some((prev, _)) if prev < cur => cur = prev,
2948 _ => cur = cur.saturating_sub(1),
2951 }
2952 } else {
2953 break;
2954 }
2955 }
2956 cur
2957 }
2958 MoveOperation::EndOfSentence => {
2959 let mut cur = pos;
2960 for _ in 0..n.max(1) {
2961 let end = match self.find_sentence_boundaries(cur) {
2962 Some((_, end)) => end,
2963 None => break,
2964 };
2965 if end > cur {
2966 cur = end;
2967 } else {
2968 match self.find_sentence_boundaries(cur + 1) {
2969 Some((_, next)) if next > cur => cur = next,
2970 _ => break,
2971 }
2972 }
2973 }
2974 cur
2975 }
2976 MoveOperation::NextSentence => {
2977 let mut cur = pos;
2978 for _ in 0..n.max(1) {
2979 let end = match self.find_sentence_boundaries(cur) {
2982 Some((_, end)) => end,
2983 None => break,
2984 };
2985 match self.find_sentence_boundaries(end + 1) {
2986 Some((start, _)) if start > cur => cur = start,
2987 _ => {
2988 if end > cur {
2989 cur = end;
2990 } else {
2991 break;
2992 }
2993 }
2994 }
2995 }
2996 cur
2997 }
2998 MoveOperation::Up | MoveOperation::Down => {
2999 if matches!(op, MoveOperation::Up) {
3002 self.resolve_move(MoveOperation::PreviousBlock, 1)
3003 } else {
3004 self.resolve_move(MoveOperation::NextBlock, 1)
3005 }
3006 }
3007 }
3008 }
3009
3010 pub(crate) fn snap_position_to_grapheme_boundary(&self) {
3021 let pos = {
3022 let data = self.data.lock();
3023 data.position
3024 };
3025 let snapped = self.forward_grapheme_boundary_at_or_after(pos);
3026 if snapped != pos {
3027 let mut data = self.data.lock();
3028 data.position = snapped;
3029 if data.anchor == pos {
3030 data.anchor = snapped;
3031 }
3032 }
3033 }
3034
3035 fn forward_grapheme_boundary_at_or_after(&self, pos: usize) -> usize {
3045 let inner = self.doc.lock();
3046 let end = max_cursor_position_of(&inner).unwrap_or(pos);
3047 if pos >= end {
3048 return pos;
3049 }
3050 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3051 position: to_i64(pos),
3052 };
3053 let Ok(block_info) =
3054 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto)
3055 else {
3056 return pos;
3057 };
3058 let block_start = to_usize(block_info.block_start);
3059 let block_length = to_usize(block_info.block_length);
3060 let offset_in_block = pos.saturating_sub(block_start);
3061 if offset_in_block == 0 || offset_in_block >= block_length {
3063 return pos;
3064 }
3065 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3066 position: to_i64(block_start),
3067 length: to_i64(block_length),
3068 };
3069 let Ok(r) = document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto)
3070 else {
3071 return pos;
3072 };
3073 let text = r.text;
3074 drop(inner);
3075 let mut acc = 0usize;
3078 for g in text.graphemes(true) {
3079 if acc >= offset_in_block {
3080 return block_start + acc;
3081 }
3082 acc += g.chars().count();
3083 }
3084 block_start + acc
3085 }
3086
3087 fn next_grapheme_boundary(&self, pos: usize) -> usize {
3100 let inner = self.doc.lock();
3101 let end = max_cursor_position_of(&inner).unwrap_or(pos);
3102 if pos >= end {
3103 return pos;
3104 }
3105 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3106 position: to_i64(pos),
3107 };
3108 let block_info =
3109 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3110 Ok(info) => info,
3111 Err(_) => return pos + 1,
3112 };
3113 let block_start = to_usize(block_info.block_start);
3114 let block_length = to_usize(block_info.block_length);
3115 let offset_in_block = pos.saturating_sub(block_start);
3116 if offset_in_block >= block_length {
3117 return (pos + 1).min(end);
3120 }
3121 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3122 position: to_i64(pos),
3123 length: to_i64(block_length - offset_in_block),
3124 };
3125 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
3126 Ok(r) => r.text,
3127 Err(_) => return pos + 1,
3128 };
3129 drop(inner);
3130 match text.graphemes(true).next() {
3131 Some(g) if !g.is_empty() => (pos + g.chars().count()).min(end),
3132 _ => (pos + 1).min(end),
3133 }
3134 }
3135
3136 fn prev_grapheme_boundary(&self, pos: usize) -> usize {
3140 if pos == 0 {
3141 return 0;
3142 }
3143 let inner = self.doc.lock();
3144 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3145 position: to_i64(pos.saturating_sub(1)),
3146 };
3147 let block_info =
3148 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3149 Ok(info) => info,
3150 Err(_) => return pos - 1,
3151 };
3152 let block_start = to_usize(block_info.block_start);
3153 let block_length = to_usize(block_info.block_length);
3154 let block_end = block_start + block_length;
3155 if pos > block_end {
3159 return pos - 1;
3160 }
3161 if block_length == 0 || pos <= block_start {
3162 return pos.saturating_sub(1);
3163 }
3164 let scan_len = pos - block_start;
3165 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3166 position: to_i64(block_start),
3167 length: to_i64(scan_len),
3168 };
3169 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
3170 Ok(r) => r.text,
3171 Err(_) => return pos - 1,
3172 };
3173 drop(inner);
3174 match text.graphemes(true).next_back() {
3175 Some(g) if !g.is_empty() => pos - g.chars().count(),
3176 _ => pos - 1,
3177 }
3178 }
3179
3180 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
3186 let inner = self.doc.lock();
3187 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3189 position: to_i64(pos),
3190 };
3191 let block_info =
3192 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3193 Ok(info) => info,
3194 Err(_) => return (pos, pos),
3195 };
3196
3197 let block_start = to_usize(block_info.block_start);
3198 let block_length = to_usize(block_info.block_length);
3199 if block_length == 0 {
3200 return (pos, pos);
3201 }
3202
3203 let dto = frontend::document_inspection::GetTextAtPositionDto {
3204 position: to_i64(block_start),
3205 length: to_i64(block_length),
3206 };
3207 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
3208 Ok(r) => r.text,
3209 Err(_) => return (pos, pos),
3210 };
3211
3212 let cursor_offset = pos.saturating_sub(block_start);
3214
3215 let mut last_char_start = 0;
3217 let mut last_char_end = 0;
3218
3219 for (word_byte_start, word) in text.unicode_word_indices() {
3220 let word_char_start = text[..word_byte_start].chars().count();
3222 let word_char_len = word.chars().count();
3223 let word_char_end = word_char_start + word_char_len;
3224
3225 last_char_start = word_char_start;
3226 last_char_end = word_char_end;
3227
3228 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
3229 return (block_start + word_char_start, block_start + word_char_end);
3230 }
3231 }
3232
3233 if cursor_offset == last_char_end && last_char_start < last_char_end {
3235 return (block_start + last_char_start, block_start + last_char_end);
3236 }
3237
3238 (pos, pos)
3239 }
3240
3241 fn find_sentence_boundaries(&self, pos: usize) -> Option<(usize, usize)> {
3247 let locale = self.data.lock().content_locale.clone();
3248
3249 let inner = self.doc.lock();
3250 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3251 position: to_i64(pos),
3252 };
3253 let block_info =
3254 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto).ok()?;
3255 let block_start = to_usize(block_info.block_start);
3256 let block_length = to_usize(block_info.block_length);
3257 if block_length == 0 {
3258 return None;
3259 }
3260 let dto = frontend::document_inspection::GetTextAtPositionDto {
3261 position: to_i64(block_start),
3262 length: to_i64(block_length),
3263 };
3264 let text = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
3265 .ok()?
3266 .text;
3267 drop(inner);
3268
3269 let offset = pos.saturating_sub(block_start);
3270 let (start, end) =
3271 frontend::common::parser_tools::sentence_bounds(&text, offset, locale.as_deref())?;
3272 Some((block_start + start, block_start + end))
3273 }
3274}
3275
3276#[derive(Clone, Copy, PartialEq, Eq)]
3283enum BlockEdge {
3284 First,
3285 Middle,
3286 Last,
3287 OnlyOne,
3288}
3289
3290fn cursor_frame_ref(inner: &TextDocumentInner, block_id: u64) -> Option<FrameRef> {
3294 let parent = crate::text_block::find_parent_frame(inner, block_id)?;
3295 let store = inner.ctx.db_context.get_store();
3296 let frames = store.frames.read();
3297 let frame = frames.get(&parent)?.clone();
3298 frame.parent_frame?;
3299 let is_blockquote = frame.fmt_is_blockquote.unwrap_or(false);
3300
3301 let mut depth = 0;
3302 let mut current = Some(parent);
3303 while let Some(id) = current {
3304 let Some(f) = frames.get(&id) else {
3305 break;
3306 };
3307 if f.parent_frame.is_none() {
3308 break;
3309 }
3310 depth += 1;
3311 current = f.parent_frame;
3312 }
3313
3314 Some(FrameRef {
3315 frame_id: frame.id as usize,
3316 parent_frame_id: frame.parent_frame.map(|id| id as usize),
3317 is_blockquote,
3318 depth,
3319 })
3320}
3321
3322fn innermost_blockquote_frame_id(inner: &TextDocumentInner, block_id: u64) -> Option<usize> {
3326 let mut current = crate::text_block::find_parent_frame(inner, block_id);
3327 let store = inner.ctx.db_context.get_store();
3328 let frames = store.frames.read();
3329 while let Some(id) = current {
3330 let f = frames.get(&id)?;
3331 if f.fmt_is_blockquote == Some(true) {
3332 return Some(f.id as usize);
3333 }
3334 current = f.parent_frame;
3335 }
3336 None
3337}
3338
3339fn blockquote_depth_for_block(inner: &TextDocumentInner, block_id: u64) -> usize {
3342 let mut current = crate::text_block::find_parent_frame(inner, block_id);
3343 let store = inner.ctx.db_context.get_store();
3344 let frames = store.frames.read();
3345 let mut count = 0;
3346 while let Some(id) = current {
3347 let Some(f) = frames.get(&id) else {
3348 break;
3349 };
3350 if f.fmt_is_blockquote == Some(true) {
3351 count += 1;
3352 }
3353 current = f.parent_frame;
3354 }
3355 count
3356}
3357
3358fn block_position_in_current_frame(cursor: &TextCursor) -> Option<BlockEdge> {
3364 let pos = cursor.position();
3365 let inner = cursor.doc.lock();
3366 let dto = frontend::document_inspection::GetBlockAtPositionDto {
3367 position: to_i64(pos),
3368 };
3369 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
3370 let block_id = block_info.block_id as common::types::EntityId;
3371 let parent_id = crate::text_block::find_parent_frame(&inner, block_info.block_id as u64)?;
3372 let store = inner.ctx.db_context.get_store();
3373 let frames = store.frames.read();
3374 let frame = frames.get(&parent_id)?;
3375 let block_positions: Vec<usize> = frame
3376 .child_order
3377 .iter()
3378 .enumerate()
3379 .filter_map(|(i, &e)| {
3380 if e > 0 {
3381 Some((i, e as common::types::EntityId))
3382 } else {
3383 None
3384 }
3385 })
3386 .filter(|(_, id)| *id == block_id)
3387 .map(|(i, _)| i)
3388 .collect();
3389 let block_idx = *block_positions.first()?;
3390 let positive_entries: Vec<usize> = frame
3391 .child_order
3392 .iter()
3393 .enumerate()
3394 .filter_map(|(i, &e)| if e > 0 { Some(i) } else { None })
3395 .collect();
3396 let first_pos = *positive_entries.first()?;
3397 let last_pos = *positive_entries.last()?;
3398 let is_first = block_idx == first_pos;
3399 let is_last = block_idx == last_pos;
3400 let edge = match (is_first, is_last, positive_entries.len()) {
3401 (_, _, 1) => BlockEdge::OnlyOne,
3402 (true, _, _) => BlockEdge::First,
3403 (_, true, _) => BlockEdge::Last,
3404 _ => BlockEdge::Middle,
3405 };
3406 Some(edge)
3407}