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> {
52 let store = inner.ctx.db_context.get_store();
53 if common::database::rope_helpers::rope_positions_match_flow(store) {
54 return Some(store.rope.read().len_chars());
55 }
56 let (chars, blocks) = crate::inner::document_counts(inner)?;
57 Some(if blocks > 1 {
58 chars + blocks - 1
59 } else {
60 chars
61 })
62}
63
64pub struct TextCursor {
72 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
73 pub(crate) data: Arc<Mutex<CursorData>>,
74}
75
76impl Clone for TextCursor {
77 fn clone(&self) -> Self {
78 let (position, anchor, content_locale) = {
79 let d = self.data.lock();
80 (d.position, d.anchor, d.content_locale.clone())
81 };
82 let data = {
83 let mut inner = self.doc.lock();
84 let data = Arc::new(Mutex::new(CursorData {
85 position,
86 anchor,
87 cell_selection_override: None,
88 content_locale,
90 }));
91 inner.cursors.push(Arc::downgrade(&data));
92 data
93 };
94 TextCursor {
95 doc: self.doc.clone(),
96 data,
97 }
98 }
99}
100
101impl TextCursor {
102 fn read_cursor(&self) -> (usize, usize) {
105 let d = self.data.lock();
106 (d.position, d.anchor)
107 }
108
109 fn finish_edit(
113 &self,
114 inner: &mut TextDocumentInner,
115 edit_pos: usize,
116 removed: usize,
117 new_pos: usize,
118 blocks_affected: usize,
119 ) -> QueuedEvents {
120 self.finish_edit_ext(inner, edit_pos, removed, new_pos, blocks_affected, true)
121 }
122
123 fn finish_edit_ext(
124 &self,
125 inner: &mut TextDocumentInner,
126 edit_pos: usize,
127 removed: usize,
128 new_pos: usize,
129 blocks_affected: usize,
130 flow_may_change: bool,
131 ) -> QueuedEvents {
132 self.finish_edit_from(
133 inner,
134 edit_pos,
135 removed,
136 new_pos,
137 blocks_affected,
138 flow_may_change,
139 InsertionOrigin::Unspecified,
140 )
141 }
142
143 #[allow(clippy::too_many_arguments)]
152 fn finish_edit_from(
153 &self,
154 inner: &mut TextDocumentInner,
155 edit_pos: usize,
156 removed: usize,
157 new_pos: usize,
158 blocks_affected: usize,
159 flow_may_change: bool,
160 origin: InsertionOrigin,
161 ) -> QueuedEvents {
162 let added = new_pos.saturating_sub(edit_pos);
169 inner.adjust_cursors(edit_pos, removed, added);
170 {
171 let mut d = self.data.lock();
172 d.position = new_pos;
173 d.anchor = new_pos;
174 }
175 inner.modified = true;
176 inner.invalidate_text_cache();
177 inner.rehighlight_affected(edit_pos);
178 inner.queue_event(DocumentEvent::ContentsChanged {
179 position: edit_pos,
180 chars_removed: removed,
181 chars_added: added,
182 blocks_affected,
183 });
184 if added > 0 {
188 inner.queue_event(DocumentEvent::TextInserted {
189 position: edit_pos,
190 chars_inserted: added,
191 origin,
192 });
193 }
194 inner.check_block_count_changed();
195 if flow_may_change {
196 inner.check_flow_changed();
197 }
198 self.queue_undo_redo_event(inner)
199 }
200
201 pub fn position(&self) -> usize {
205 self.data.lock().position
206 }
207
208 pub fn anchor(&self) -> usize {
210 self.data.lock().anchor
211 }
212
213 pub fn has_selection(&self) -> bool {
215 let d = self.data.lock();
216 d.position != d.anchor
217 }
218
219 pub fn selection_start(&self) -> usize {
221 let d = self.data.lock();
222 d.position.min(d.anchor)
223 }
224
225 pub fn selection_end(&self) -> usize {
227 let d = self.data.lock();
228 d.position.max(d.anchor)
229 }
230
231 pub fn selected_text(&self) -> Result<String> {
233 let (pos, anchor) = self.read_cursor();
234 if pos == anchor {
235 return Ok(String::new());
236 }
237 let start = pos.min(anchor);
238 let len = pos.max(anchor) - start;
239 let inner = self.doc.lock();
240 let dto = frontend::document_inspection::GetTextAtPositionDto {
241 position: to_i64(start),
242 length: to_i64(len),
243 };
244 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
245 Ok(result.text)
246 }
247
248 pub fn text_before(&self, max_len: usize) -> Result<String> {
261 if max_len == 0 {
262 return Ok(String::new());
263 }
264 let pos = self.position();
265 let inner = self.doc.lock();
266 let store = inner.ctx.db_context.get_store();
267
268 if pos > 0
273 && common::database::rope_helpers::find_block_at_char_position(store, 0).is_none()
274 {
275 let dto = frontend::document_inspection::GetTextAtPositionDto {
276 position: 0,
277 length: to_i64(pos),
278 };
279 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
280 let full = result.text;
281 let total = full.chars().count();
282 let skip = total.saturating_sub(max_len);
283 return Ok(full.chars().skip(skip).collect());
284 }
285
286 let mut pieces: Vec<String> = Vec::new();
287 let mut remaining = max_len;
288 let mut end_pos = pos;
289
290 while remaining > 0 && end_pos > 0 {
291 let query = (end_pos - 1) as i64;
292 let Some((block_id, char_in_block, block_char_start)) =
298 common::database::rope_helpers::find_block_at_char_position(store, query)
299 else {
300 break;
304 };
305 let block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
306 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
307 let entity: common::entities::Block = block_dto.into();
308 let block_text =
309 common::database::rope_helpers::block_content_via_store(&entity, store);
310 let block_len = block_text.chars().count() as i64;
311 let block_char_start = block_char_start as usize;
312
313 if char_in_block == block_len {
314 pieces.push("\n".to_string());
316 remaining -= 1;
317 if remaining == 0 {
318 break;
319 }
320 let take = remaining.min(block_len as usize);
321 let local_start = block_len as usize - take;
322 let slice: String = block_text.chars().skip(local_start).take(take).collect();
323 pieces.push(slice);
324 remaining -= take;
325 end_pos = block_char_start + local_start;
326 } else {
327 let available = char_in_block as usize + 1;
329 let take = remaining.min(available);
330 let local_start = available - take;
331 let slice: String = block_text.chars().skip(local_start).take(take).collect();
332 pieces.push(slice);
333 remaining -= take;
334 end_pos = block_char_start + local_start;
335 }
336 }
337
338 pieces.reverse();
339 Ok(pieces.concat())
340 }
341
342 pub fn clear_selection(&self) {
344 let mut d = self.data.lock();
345 d.anchor = d.position;
346 }
347
348 pub fn at_block_start(&self) -> bool {
352 let pos = self.position();
353 let inner = self.doc.lock();
354 let dto = frontend::document_inspection::GetBlockAtPositionDto {
355 position: to_i64(pos),
356 };
357 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
358 pos == to_usize(info.block_start)
359 } else {
360 false
361 }
362 }
363
364 pub fn at_block_end(&self) -> bool {
366 let pos = self.position();
367 let inner = self.doc.lock();
368 let dto = frontend::document_inspection::GetBlockAtPositionDto {
369 position: to_i64(pos),
370 };
371 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
372 pos == to_usize(info.block_start) + to_usize(info.block_length)
373 } else {
374 false
375 }
376 }
377
378 pub fn at_start(&self) -> bool {
380 self.data.lock().position == 0
381 }
382
383 pub fn at_end(&self) -> bool {
385 let pos = self.position();
386 let inner = self.doc.lock();
387 pos >= max_cursor_position_of(&inner).unwrap_or(0)
388 }
389
390 pub fn block_number(&self) -> usize {
392 let pos = self.position();
393 let inner = self.doc.lock();
394 let dto = frontend::document_inspection::GetBlockAtPositionDto {
395 position: to_i64(pos),
396 };
397 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
398 .map(|info| to_usize(info.block_number))
399 .unwrap_or(0)
400 }
401
402 pub fn position_in_block(&self) -> usize {
404 let pos = self.position();
405 let inner = self.doc.lock();
406 let dto = frontend::document_inspection::GetBlockAtPositionDto {
407 position: to_i64(pos),
408 };
409 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
410 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
411 .unwrap_or(0)
412 }
413
414 pub fn set_position(&self, position: usize, mode: MoveMode) {
428 let end = {
430 let inner = self.doc.lock();
431 max_cursor_position_of(&inner).unwrap_or(0)
432 };
433 let mut pos = position.min(end);
434
435 if mode == MoveMode::KeepAnchor {
439 let anchor = self.data.lock().anchor;
440 let pos_cell = self.table_cell_at(pos);
441 let anchor_cell = self.table_cell_at(anchor);
442 match (&pos_cell, &anchor_cell) {
443 (Some(tc), None) => {
444 let before = anchor < pos;
446 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
447 pos = boundary;
448 }
449 }
450 (None, Some(tc)) => {
451 let before = pos < anchor;
454 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
455 pos = boundary;
456 }
457 }
458 _ => {}
459 }
460 }
461
462 {
463 let mut d = self.data.lock();
464 d.position = pos;
465 if mode == MoveMode::MoveAnchor {
466 d.anchor = pos;
467 }
468 d.cell_selection_override = None;
469 }
470 self.snap_position_to_grapheme_boundary();
475 }
476
477 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
483 let old_pos = self.position();
484 let target = self.resolve_move(operation, n);
485 self.set_position(target, mode);
486 self.position() != old_pos
487 }
488
489 pub fn select(&self, selection: SelectionType) {
491 match selection {
492 SelectionType::Document => {
493 let end = {
494 let inner = self.doc.lock();
495 max_cursor_position_of(&inner).unwrap_or(0)
496 };
497 let mut d = self.data.lock();
498 d.anchor = 0;
499 d.position = end;
500 d.cell_selection_override = None;
501 }
502 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
503 let pos = self.position();
504 let inner = self.doc.lock();
505 let dto = frontend::document_inspection::GetBlockAtPositionDto {
506 position: to_i64(pos),
507 };
508 if let Ok(info) =
509 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
510 {
511 let start = to_usize(info.block_start);
512 let end = start + to_usize(info.block_length);
513 drop(inner);
514 let mut d = self.data.lock();
515 d.anchor = start;
516 d.position = end;
517 d.cell_selection_override = None;
518 }
519 }
520 SelectionType::WordUnderCursor => {
521 let pos = self.position();
522 let (word_start, word_end) = self.find_word_boundaries(pos);
523 let mut d = self.data.lock();
524 d.anchor = word_start;
525 d.position = word_end;
526 d.cell_selection_override = None;
527 }
528 SelectionType::SentenceUnderCursor => {
529 let pos = self.position();
530 if let Some((start, end)) = self.find_sentence_boundaries(pos) {
533 let mut d = self.data.lock();
534 d.anchor = start;
535 d.position = end;
536 d.cell_selection_override = None;
537 }
538 }
539 }
540 }
541
542 pub fn set_content_locale(&self, locale: Option<&str>) {
551 self.data.lock().content_locale = locale.map(str::to_string);
552 }
553
554 pub fn content_locale(&self) -> Option<String> {
556 self.data.lock().content_locale.clone()
557 }
558
559 pub fn insert_text(&self, text: &str) -> Result<()> {
566 self.insert_text_with_origin(text, InsertionOrigin::Unspecified)
567 }
568
569 pub fn insert_text_with_origin(&self, text: &str, origin: InsertionOrigin) -> Result<()> {
574 let (pos, anchor) = self.read_cursor();
575
576 let dto = frontend::document_editing::InsertTextDto {
578 format_policy: Default::default(),
579 position: to_i64(pos),
580 anchor: to_i64(anchor),
581 text: text.into(),
582 };
583
584 let queued = {
585 let mut inner = self.doc.lock();
586 let result = match document_editing_commands::insert_text(
587 &inner.ctx,
588 Some(inner.stack_id),
589 &dto,
590 ) {
591 Ok(r) => r,
592 Err(_) if pos != anchor => {
593 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
595
596 let del_dto = frontend::document_editing::DeleteTextDto {
597 position: to_i64(pos),
598 anchor: to_i64(anchor),
599 };
600 let del_result = document_editing_commands::delete_text(
601 &inner.ctx,
602 Some(inner.stack_id),
603 &del_dto,
604 )?;
605 let del_pos = to_usize(del_result.new_position);
606
607 let ins_dto = frontend::document_editing::InsertTextDto {
608 format_policy: Default::default(),
609 position: to_i64(del_pos),
610 anchor: to_i64(del_pos),
611 text: text.into(),
612 };
613 let ins_result = document_editing_commands::insert_text(
614 &inner.ctx,
615 Some(inner.stack_id),
616 &ins_dto,
617 )?;
618
619 undo_redo_commands::end_composite(&inner.ctx);
620 ins_result
621 }
622 Err(e) => return Err(e.into()),
623 };
624
625 let edit_pos = pos.min(anchor);
626 let removed = pos.max(anchor) - edit_pos;
627 self.finish_edit_from(
628 &mut inner,
629 edit_pos,
630 removed,
631 to_usize(result.new_position),
632 to_usize(result.blocks_affected),
633 false,
634 origin,
635 )
636 };
637 crate::inner::dispatch_queued_events(queued);
638 Ok(())
639 }
640
641 pub fn replace(
655 &self,
656 start: usize,
657 end: usize,
658 text: &str,
659 policy: crate::ReplaceFormatPolicy,
660 ) -> Result<()> {
661 let (pos, anchor) = (start, end);
662
663 let dto = frontend::document_editing::InsertTextDto {
664 format_policy: policy,
665 position: to_i64(pos),
666 anchor: to_i64(anchor),
667 text: text.into(),
668 };
669
670 let queued = {
671 let mut inner = self.doc.lock();
672 let result = match document_editing_commands::insert_text(
673 &inner.ctx,
674 Some(inner.stack_id),
675 &dto,
676 ) {
677 Ok(r) => r,
678 Err(_) if pos != anchor => {
679 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
683
684 let del_dto = frontend::document_editing::DeleteTextDto {
685 position: to_i64(pos),
686 anchor: to_i64(anchor),
687 };
688 let del_result = document_editing_commands::delete_text(
689 &inner.ctx,
690 Some(inner.stack_id),
691 &del_dto,
692 )?;
693 let del_pos = to_usize(del_result.new_position);
694
695 let ins_dto = frontend::document_editing::InsertTextDto {
696 format_policy: Default::default(),
697 position: to_i64(del_pos),
698 anchor: to_i64(del_pos),
699 text: text.into(),
700 };
701 let ins_result = document_editing_commands::insert_text(
702 &inner.ctx,
703 Some(inner.stack_id),
704 &ins_dto,
705 )?;
706
707 undo_redo_commands::end_composite(&inner.ctx);
708 ins_result
709 }
710 Err(e) => return Err(e.into()),
711 };
712
713 let edit_pos = pos.min(anchor);
714 let removed = pos.max(anchor) - edit_pos;
715 self.finish_edit_ext(
716 &mut inner,
717 edit_pos,
718 removed,
719 to_usize(result.new_position),
720 to_usize(result.blocks_affected),
721 false,
722 )
723 };
724 crate::inner::dispatch_queued_events(queued);
725 Ok(())
726 }
727
728 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
731 self.insert_formatted_text_with_origin(text, format, InsertionOrigin::Unspecified)
732 }
733
734 pub fn insert_formatted_text_with_origin(
737 &self,
738 text: &str,
739 format: &TextFormat,
740 origin: InsertionOrigin,
741 ) -> Result<()> {
742 let (pos, anchor) = self.read_cursor();
743
744 let make_dto = |p: usize, a: usize| frontend::document_editing::InsertFormattedTextDto {
745 position: to_i64(p),
746 anchor: to_i64(a),
747 text: text.into(),
748 font_family: format.font_family.clone().unwrap_or_default(),
749 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
750 font_bold: format.font_bold.unwrap_or(false),
751 font_italic: format.font_italic.unwrap_or(false),
752 font_underline: format.font_underline.unwrap_or(false),
753 font_strikeout: format.font_strikeout.unwrap_or(false),
754 };
755
756 let queued = {
757 let mut inner = self.doc.lock();
758 let result = match document_editing_commands::insert_formatted_text(
759 &inner.ctx,
760 Some(inner.stack_id),
761 &make_dto(pos, anchor),
762 ) {
763 Ok(r) => r,
764 Err(_) if pos != anchor => {
765 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
767
768 let del_dto = frontend::document_editing::DeleteTextDto {
769 position: to_i64(pos),
770 anchor: to_i64(anchor),
771 };
772 let del_result = document_editing_commands::delete_text(
773 &inner.ctx,
774 Some(inner.stack_id),
775 &del_dto,
776 )?;
777 let del_pos = to_usize(del_result.new_position);
778
779 let ins_result = document_editing_commands::insert_formatted_text(
780 &inner.ctx,
781 Some(inner.stack_id),
782 &make_dto(del_pos, del_pos),
783 )?;
784
785 undo_redo_commands::end_composite(&inner.ctx);
786 ins_result
787 }
788 Err(e) => return Err(e.into()),
789 };
790
791 let edit_pos = pos.min(anchor);
792 let removed = pos.max(anchor) - edit_pos;
793 self.finish_edit_from(
794 &mut inner,
795 edit_pos,
796 removed,
797 to_usize(result.new_position),
798 1,
799 false,
800 origin,
801 )
802 };
803 crate::inner::dispatch_queued_events(queued);
804 Ok(())
805 }
806
807 pub fn insert_block(&self) -> Result<()> {
809 let (pos, anchor) = self.read_cursor();
810 let queued = {
811 let mut inner = self.doc.lock();
812
813 let (insert_pos, removed) = if pos != anchor {
814 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
816 let del_dto = frontend::document_editing::DeleteTextDto {
817 position: to_i64(pos),
818 anchor: to_i64(anchor),
819 };
820 let del_result = document_editing_commands::delete_text(
821 &inner.ctx,
822 Some(inner.stack_id),
823 &del_dto,
824 )?;
825 (
826 to_usize(del_result.new_position),
827 pos.max(anchor) - pos.min(anchor),
828 )
829 } else {
830 (pos, 0)
831 };
832
833 let dto = frontend::document_editing::InsertBlockDto {
834 position: to_i64(insert_pos),
835 anchor: to_i64(insert_pos),
836 };
837 let result =
838 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
839
840 if pos != anchor {
841 undo_redo_commands::end_composite(&inner.ctx);
842 }
843
844 let edit_pos = pos.min(anchor);
845 self.finish_edit(
846 &mut inner,
847 edit_pos,
848 removed,
849 to_usize(result.new_position),
850 2,
851 )
852 };
853 crate::inner::dispatch_queued_events(queued);
854 Ok(())
855 }
856
857 pub fn insert_html_with_origin(&self, html: &str, origin: InsertionOrigin) -> Result<()> {
861 let frag = DocumentFragment::from_html(html);
862 self.insert_fragment_with_origin(&frag, origin)
863 }
864
865 pub fn insert_html(&self, html: &str) -> Result<()> {
866 let frag = DocumentFragment::from_html(html);
868 self.insert_fragment(&frag)
869 }
870
871 pub fn insert_markdown_with_origin(
875 &self,
876 markdown: &str,
877 origin: InsertionOrigin,
878 ) -> Result<()> {
879 let frag = DocumentFragment::from_markdown(markdown);
880 self.insert_fragment_with_origin(&frag, origin)
881 }
882
883 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
884 let frag = DocumentFragment::from_markdown(markdown);
885 self.insert_fragment(&frag)
886 }
887
888 pub fn insert_djot_with_origin(&self, djot: &str, origin: InsertionOrigin) -> Result<()> {
892 let frag = DocumentFragment::from_djot(djot);
893 self.insert_fragment_with_origin(&frag, origin)
894 }
895
896 pub fn insert_djot(&self, djot: &str) -> Result<()> {
897 let frag = DocumentFragment::from_djot(djot);
898 self.insert_fragment(&frag)
899 }
900
901 pub fn insert_footnote_reference(&self, label: &str) -> Result<()> {
914 self.insert_djot(&format!("[^{label}]"))
915 }
916
917 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
920 self.insert_fragment_with_origin(fragment, InsertionOrigin::Unspecified)
921 }
922
923 pub fn insert_fragment_with_origin(
927 &self,
928 fragment: &DocumentFragment,
929 origin: InsertionOrigin,
930 ) -> Result<()> {
931 let (pos, anchor) = self.read_cursor();
932 let queued = {
933 let mut inner = self.doc.lock();
934
935 let (insert_pos, removed) = if pos != anchor {
936 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
937 let del_dto = frontend::document_editing::DeleteTextDto {
938 position: to_i64(pos),
939 anchor: to_i64(anchor),
940 };
941 let del_result = document_editing_commands::delete_text(
942 &inner.ctx,
943 Some(inner.stack_id),
944 &del_dto,
945 )?;
946 (
947 to_usize(del_result.new_position),
948 pos.max(anchor) - pos.min(anchor),
949 )
950 } else {
951 (pos, 0)
952 };
953
954 let dto = frontend::document_editing::InsertFragmentDto {
955 position: to_i64(insert_pos),
956 anchor: to_i64(insert_pos),
957 fragment_data: fragment.raw_data().into(),
958 };
959 let result =
960 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
961
962 if pos != anchor {
963 undo_redo_commands::end_composite(&inner.ctx);
964 }
965
966 let edit_pos = pos.min(anchor);
967 self.finish_edit_from(
968 &mut inner,
969 edit_pos,
970 removed,
971 to_usize(result.new_position),
972 to_usize(result.blocks_added),
973 true,
974 origin,
975 )
976 };
977 crate::inner::dispatch_queued_events(queued);
978 Ok(())
979 }
980
981 pub fn selection(&self) -> DocumentFragment {
983 let (pos, anchor) = self.read_cursor();
984
985 let (extract_pos, extract_anchor) = match self.selection_kind() {
988 SelectionKind::Cells(ref range) => match self.cell_range_positions(range) {
989 Some((start, end)) => (start, end),
990 None => return DocumentFragment::new(),
991 },
992 SelectionKind::Mixed {
993 ref cell_range,
994 text_before,
995 text_after,
996 } => {
997 let (cell_start, cell_end) = match self.cell_range_positions(cell_range) {
998 Some(p) => p,
999 None => return DocumentFragment::new(),
1000 };
1001 let start = if text_before {
1002 pos.min(anchor)
1003 } else {
1004 cell_start
1005 };
1006 let end = if text_after {
1007 pos.max(anchor)
1008 } else {
1009 cell_end
1010 };
1011 (start.min(cell_start), end.max(cell_end))
1012 }
1013 SelectionKind::None => return DocumentFragment::new(),
1014 SelectionKind::Text => (pos, anchor),
1015 };
1016
1017 if extract_pos == extract_anchor {
1018 return DocumentFragment::new();
1019 }
1020
1021 let inner = self.doc.lock();
1022 let dto = frontend::document_inspection::ExtractFragmentDto {
1023 position: to_i64(extract_pos),
1024 anchor: to_i64(extract_anchor),
1025 };
1026 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
1027 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
1028 Err(_) => DocumentFragment::new(),
1029 }
1030 }
1031
1032 pub fn insert_image(&self, name: &str, alt: &str, width: u32, height: u32) -> Result<()> {
1038 let (pos, anchor) = self.read_cursor();
1039 let queued = {
1040 let mut inner = self.doc.lock();
1041
1042 let (insert_pos, removed) = if pos != anchor {
1043 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1044 let del_dto = frontend::document_editing::DeleteTextDto {
1045 position: to_i64(pos),
1046 anchor: to_i64(anchor),
1047 };
1048 let del_result = document_editing_commands::delete_text(
1049 &inner.ctx,
1050 Some(inner.stack_id),
1051 &del_dto,
1052 )?;
1053 (
1054 to_usize(del_result.new_position),
1055 pos.max(anchor) - pos.min(anchor),
1056 )
1057 } else {
1058 (pos, 0)
1059 };
1060
1061 let dto = frontend::document_editing::InsertImageDto {
1062 position: to_i64(insert_pos),
1063 anchor: to_i64(insert_pos),
1064 image_name: name.into(),
1065 alt: alt.into(),
1066 width: width as i64,
1067 height: height as i64,
1068 quality: 100,
1069 };
1070 let result =
1071 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
1072
1073 if pos != anchor {
1074 undo_redo_commands::end_composite(&inner.ctx);
1075 }
1076
1077 let edit_pos = pos.min(anchor);
1078 self.finish_edit_ext(
1079 &mut inner,
1080 edit_pos,
1081 removed,
1082 to_usize(result.new_position),
1083 1,
1084 false,
1085 )
1086 };
1087 crate::inner::dispatch_queued_events(queued);
1088 Ok(())
1089 }
1090
1091 pub fn insert_frame(&self) -> Result<()> {
1093 let (pos, anchor) = self.read_cursor();
1094 let queued = {
1095 let mut inner = self.doc.lock();
1096 let dto = frontend::document_editing::InsertFrameDto {
1097 position: to_i64(pos),
1098 anchor: to_i64(anchor),
1099 };
1100 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
1101 inner.modified = true;
1104 inner.invalidate_text_cache();
1105 inner.rehighlight_affected(pos.min(anchor));
1106 inner.queue_event(DocumentEvent::ContentsChanged {
1107 position: pos.min(anchor),
1108 chars_removed: 0,
1109 chars_added: 0,
1110 blocks_affected: 1,
1111 });
1112 inner.check_block_count_changed();
1113 inner.check_flow_changed();
1114 self.queue_undo_redo_event(&mut inner)
1115 };
1116 crate::inner::dispatch_queued_events(queued);
1117 Ok(())
1118 }
1119
1120 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
1126 let (pos, anchor) = self.read_cursor();
1127 let (table_id, queued) = {
1128 let mut inner = self.doc.lock();
1129 let dto = frontend::document_editing::InsertTableDto {
1130 position: to_i64(pos),
1131 anchor: to_i64(anchor),
1132 rows: to_i64(rows),
1133 columns: to_i64(columns),
1134 };
1135 let result =
1136 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1137 let new_pos = to_usize(result.new_position);
1138 let table_id = to_usize(result.table_id);
1139 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
1140 {
1141 let mut d = self.data.lock();
1142 d.position = new_pos;
1143 d.anchor = new_pos;
1144 }
1145 inner.modified = true;
1146 inner.invalidate_text_cache();
1147 inner.rehighlight_affected(pos.min(anchor));
1148 inner.queue_event(DocumentEvent::ContentsChanged {
1149 position: pos.min(anchor),
1150 chars_removed: 0,
1151 chars_added: new_pos - pos.min(anchor),
1152 blocks_affected: 1,
1153 });
1154 inner.check_block_count_changed();
1155 inner.check_flow_changed();
1156 (table_id, self.queue_undo_redo_event(&mut inner))
1157 };
1158 crate::inner::dispatch_queued_events(queued);
1159 Ok(TextTable {
1160 doc: self.doc.clone(),
1161 table_id,
1162 })
1163 }
1164
1165 pub fn current_table(&self) -> Option<TextTable> {
1170 self.current_table_cell().map(|c| c.table)
1171 }
1172
1173 pub fn current_table_cell(&self) -> Option<TableCellRef> {
1178 let pos = self.position();
1179 let inner = self.doc.lock();
1180 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1182 position: to_i64(pos),
1183 };
1184 let block_info =
1185 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1186
1187 let block_id = if to_i64(pos) < block_info.block_start && pos > 0 {
1191 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1192 position: to_i64(pos - 1),
1193 };
1194 let prev_info =
1195 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
1196 prev_info.block_id as usize
1197 } else {
1198 block_info.block_id as usize
1199 };
1200
1201 let block = crate::text_block::TextBlock {
1202 doc: self.doc.clone(),
1203 block_id,
1204 };
1205 drop(inner);
1207 block.table_cell()
1208 }
1209
1210 pub fn current_frame(&self) -> Option<FrameRef> {
1217 let pos = self.position();
1218 let inner = self.doc.lock();
1219 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1220 position: to_i64(pos),
1221 };
1222 let block_info =
1223 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1224 let block_id = block_info.block_id as u64;
1225 cursor_frame_ref(&inner, block_id)
1226 }
1227
1228 pub fn is_in_blockquote(&self) -> bool {
1231 self.current_blockquote_frame_id().is_some()
1232 }
1233
1234 pub fn current_blockquote_frame_id(&self) -> Option<usize> {
1237 let pos = self.position();
1238 let inner = self.doc.lock();
1239 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1240 position: to_i64(pos),
1241 };
1242 let block_info =
1243 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1244 innermost_blockquote_frame_id(&inner, block_info.block_id as u64)
1245 }
1246
1247 pub fn blockquote_depth_at_cursor(&self) -> usize {
1250 let pos = self.position();
1251 let inner = self.doc.lock();
1252 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1253 position: to_i64(pos),
1254 };
1255 let Some(block_info) =
1256 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
1257 else {
1258 return 0;
1259 };
1260 blockquote_depth_for_block(&inner, block_info.block_id as u64)
1261 }
1262
1263 pub fn is_first_block_in_current_frame(&self) -> bool {
1269 matches!(
1270 block_position_in_current_frame(self),
1271 Some(BlockEdge::First) | Some(BlockEdge::OnlyOne)
1272 )
1273 }
1274
1275 pub fn is_last_block_in_current_frame(&self) -> bool {
1279 matches!(
1280 block_position_in_current_frame(self),
1281 Some(BlockEdge::Last) | Some(BlockEdge::OnlyOne)
1282 )
1283 }
1284
1285 pub fn current_block_is_empty(&self) -> bool {
1288 let pos = self.position();
1289 let inner = self.doc.lock();
1290 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1291 position: to_i64(pos),
1292 };
1293 let Some(block_info) =
1294 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()
1295 else {
1296 return false;
1297 };
1298 let store = inner.ctx.db_context.get_store();
1299 let block_entity = store
1300 .blocks
1301 .read()
1302 .get(&(block_info.block_id as common::types::EntityId))
1303 .cloned();
1304 match block_entity {
1305 Some(b) => {
1306 let len = common::database::rope_helpers::block_char_length(&b, store);
1307 len == 0
1308 }
1309 None => false,
1310 }
1311 }
1312
1313 pub fn selection_spans_multiple_frames(&self) -> bool {
1317 let (pos, anchor) = self.read_cursor();
1318 if pos == anchor {
1319 return false;
1320 }
1321 let inner = self.doc.lock();
1322 let pos_dto = frontend::document_inspection::GetBlockAtPositionDto {
1323 position: to_i64(pos),
1324 };
1325 let anchor_dto = frontend::document_inspection::GetBlockAtPositionDto {
1326 position: to_i64(anchor),
1327 };
1328 let Some(pos_block) =
1329 document_inspection_commands::get_block_at_position(&inner.ctx, &pos_dto).ok()
1330 else {
1331 return false;
1332 };
1333 let Some(anchor_block) =
1334 document_inspection_commands::get_block_at_position(&inner.ctx, &anchor_dto).ok()
1335 else {
1336 return false;
1337 };
1338 let pos_owner = crate::text_block::find_parent_frame(&inner, pos_block.block_id as u64);
1339 let anchor_owner =
1340 crate::text_block::find_parent_frame(&inner, anchor_block.block_id as u64);
1341 pos_owner != anchor_owner
1342 }
1343
1344 pub fn wrap_selection_in_blockquote(&self) -> Result<()> {
1351 if self.selection_spans_multiple_frames() {
1352 return Err(DocumentError::InvalidArgument(
1353 "Cannot wrap selection in blockquote: selection spans multiple frames".into(),
1354 ));
1355 }
1356 let (start_block_id, end_block_id) = self.resolve_selection_block_range()?;
1357 let dto = frontend::document_editing::WrapBlocksInFrameDto {
1358 start_block_id: start_block_id as i64,
1359 end_block_id: end_block_id as i64,
1360 position: Some(frontend::document_editing::FramePosition::InFlow),
1361 top_margin: None,
1362 bottom_margin: None,
1363 left_margin: None,
1364 right_margin: None,
1365 padding: None,
1366 border: None,
1367 is_blockquote: Some(true),
1368 };
1369 let queued = {
1370 let mut inner = self.doc.lock();
1371 let _result = document_editing_commands::wrap_blocks_in_frame(
1372 &inner.ctx,
1373 Some(inner.stack_id),
1374 &dto,
1375 )?;
1376 inner.modified = true;
1377 inner.queue_event(DocumentEvent::FormatChanged {
1385 position: 0,
1386 length: 0,
1387 kind: crate::flow::FormatChangeKind::Block,
1388 });
1389 self.queue_undo_redo_event(&mut inner)
1390 };
1391 crate::inner::dispatch_queued_events(queued);
1392 Ok(())
1393 }
1394
1395 pub fn insert_blockquote(&self) -> Result<()> {
1399 self.wrap_selection_in_blockquote()
1400 }
1401
1402 pub fn toggle_blockquote(&self) -> Result<()> {
1407 if let Some(frame_id) = self.current_blockquote_frame_id() {
1408 self.unwrap_frame_by_id(frame_id)
1409 } else {
1410 self.wrap_selection_in_blockquote()
1411 }
1412 }
1413
1414 pub fn unwrap_current_frame(&self) -> Result<()> {
1418 let frame_ref = self.current_frame().ok_or_else(|| {
1419 DocumentError::InvalidCursorContext("Cursor is not inside any sub-frame".into())
1420 })?;
1421 self.unwrap_frame_by_id(frame_ref.frame_id)
1422 }
1423
1424 pub fn unwrap_current_block_from_blockquote(&self) -> Result<()> {
1428 if self.current_blockquote_frame_id().is_none() {
1429 return Err(DocumentError::InvalidCursorContext(
1430 "Cursor is not inside a blockquote".into(),
1431 ));
1432 }
1433 let block_id = self.current_block_id_for_mutation()?;
1434 let dto = frontend::document_editing::UnwrapBlockFromFrameDto {
1435 block_id: block_id as i64,
1436 };
1437 let queued = {
1438 let mut inner = self.doc.lock();
1439 let _result = document_editing_commands::unwrap_block_from_frame(
1440 &inner.ctx,
1441 Some(inner.stack_id),
1442 &dto,
1443 )?;
1444 inner.modified = true;
1445 inner.queue_event(DocumentEvent::FormatChanged {
1449 position: 0,
1450 length: 0,
1451 kind: crate::flow::FormatChangeKind::Block,
1452 });
1453 self.queue_undo_redo_event(&mut inner)
1454 };
1455 crate::inner::dispatch_queued_events(queued);
1456 Ok(())
1457 }
1458
1459 pub fn increase_blockquote_depth(&self) -> Result<()> {
1463 self.wrap_selection_in_blockquote()
1464 }
1465
1466 pub fn decrease_blockquote_depth(&self) -> Result<()> {
1472 if self.current_blockquote_frame_id().is_none() {
1473 return Err(DocumentError::InvalidCursorContext(
1474 "Cursor is not inside a blockquote to decrease depth".into(),
1475 ));
1476 }
1477 self.unwrap_current_block_from_blockquote()
1478 }
1479
1480 fn unwrap_frame_by_id(&self, frame_id: usize) -> Result<()> {
1481 let dto = frontend::document_editing::UnwrapFrameDto {
1482 frame_id: frame_id as i64,
1483 };
1484 let queued = {
1485 let mut inner = self.doc.lock();
1486 let _result =
1487 document_editing_commands::unwrap_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
1488 inner.modified = true;
1489 inner.queue_event(DocumentEvent::FormatChanged {
1493 position: 0,
1494 length: 0,
1495 kind: crate::flow::FormatChangeKind::Block,
1496 });
1497 self.queue_undo_redo_event(&mut inner)
1498 };
1499 crate::inner::dispatch_queued_events(queued);
1500 Ok(())
1501 }
1502
1503 fn current_block_id_for_mutation(&self) -> Result<usize> {
1504 let pos = self.position();
1505 let inner = self.doc.lock();
1506 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1507 position: to_i64(pos),
1508 };
1509 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1510 .map_err(|e| anyhow::anyhow!("get_block_at_position: {}", e))?;
1511 Ok(block_info.block_id as usize)
1512 }
1513
1514 fn resolve_selection_block_range(&self) -> Result<(usize, usize)> {
1515 let (pos, anchor) = self.read_cursor();
1516 let lo = pos.min(anchor);
1517 let hi = pos.max(anchor);
1518 let inner = self.doc.lock();
1519 let lo_dto = frontend::document_inspection::GetBlockAtPositionDto {
1520 position: to_i64(lo),
1521 };
1522 let hi_dto = frontend::document_inspection::GetBlockAtPositionDto {
1523 position: to_i64(hi),
1524 };
1525 let lo_block = document_inspection_commands::get_block_at_position(&inner.ctx, &lo_dto)
1526 .map_err(|e| anyhow::anyhow!("get_block_at_position(start): {}", e))?;
1527 let hi_block = document_inspection_commands::get_block_at_position(&inner.ctx, &hi_dto)
1528 .map_err(|e| anyhow::anyhow!("get_block_at_position(end): {}", e))?;
1529 Ok((lo_block.block_id as usize, hi_block.block_id as usize))
1530 }
1531
1532 pub fn remove_table(&self, table_id: usize) -> Result<()> {
1536 let queued = {
1537 let mut inner = self.doc.lock();
1538 let before = crate::document::capture_block_state(&inner);
1545 let dto = frontend::document_editing::RemoveTableDto {
1546 table_id: to_i64(table_id),
1547 };
1548 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
1549 inner.modified = true;
1550 inner.invalidate_text_cache();
1551 inner.rehighlight_all();
1552 crate::document::emit_content_change_events(&mut inner, &before);
1553 inner.check_block_count_changed();
1554 inner.check_flow_changed();
1555 self.queue_undo_redo_event(&mut inner)
1556 };
1557 crate::inner::dispatch_queued_events(queued);
1558 Ok(())
1559 }
1560
1561 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1563 let queued = {
1564 let mut inner = self.doc.lock();
1565 let before = crate::document::capture_block_state(&inner);
1566 let dto = frontend::document_editing::InsertTableRowDto {
1567 table_id: to_i64(table_id),
1568 row_index: to_i64(row_index),
1569 };
1570 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1571 inner.modified = true;
1572 inner.invalidate_text_cache();
1573 inner.rehighlight_all();
1574 crate::document::emit_content_change_events(&mut inner, &before);
1575 inner.check_block_count_changed();
1576 self.queue_undo_redo_event(&mut inner)
1577 };
1578 crate::inner::dispatch_queued_events(queued);
1579 Ok(())
1580 }
1581
1582 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1584 let queued = {
1585 let mut inner = self.doc.lock();
1586 let before = crate::document::capture_block_state(&inner);
1587 let dto = frontend::document_editing::InsertTableColumnDto {
1588 table_id: to_i64(table_id),
1589 column_index: to_i64(column_index),
1590 };
1591 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1592 inner.modified = true;
1593 inner.invalidate_text_cache();
1594 inner.rehighlight_all();
1595 crate::document::emit_content_change_events(&mut inner, &before);
1596 inner.check_block_count_changed();
1597 self.queue_undo_redo_event(&mut inner)
1598 };
1599 crate::inner::dispatch_queued_events(queued);
1600 Ok(())
1601 }
1602
1603 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
1605 let queued = {
1606 let mut inner = self.doc.lock();
1607 let before = crate::document::capture_block_state(&inner);
1608 let dto = frontend::document_editing::RemoveTableRowDto {
1609 table_id: to_i64(table_id),
1610 row_index: to_i64(row_index),
1611 };
1612 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
1613 inner.modified = true;
1614 inner.invalidate_text_cache();
1615 inner.rehighlight_all();
1616 crate::document::emit_content_change_events(&mut inner, &before);
1617 inner.check_block_count_changed();
1618 self.queue_undo_redo_event(&mut inner)
1619 };
1620 crate::inner::dispatch_queued_events(queued);
1621 Ok(())
1622 }
1623
1624 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
1626 let queued = {
1627 let mut inner = self.doc.lock();
1628 let before = crate::document::capture_block_state(&inner);
1629 let dto = frontend::document_editing::RemoveTableColumnDto {
1630 table_id: to_i64(table_id),
1631 column_index: to_i64(column_index),
1632 };
1633 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
1634 inner.modified = true;
1635 inner.invalidate_text_cache();
1636 inner.rehighlight_all();
1637 crate::document::emit_content_change_events(&mut inner, &before);
1638 inner.check_block_count_changed();
1639 self.queue_undo_redo_event(&mut inner)
1640 };
1641 crate::inner::dispatch_queued_events(queued);
1642 Ok(())
1643 }
1644
1645 pub fn merge_table_cells(
1647 &self,
1648 table_id: usize,
1649 start_row: usize,
1650 start_column: usize,
1651 end_row: usize,
1652 end_column: usize,
1653 ) -> Result<()> {
1654 let queued = {
1655 let mut inner = self.doc.lock();
1656 let before = crate::document::capture_block_state(&inner);
1657 let dto = frontend::document_editing::MergeTableCellsDto {
1658 table_id: to_i64(table_id),
1659 start_row: to_i64(start_row),
1660 start_column: to_i64(start_column),
1661 end_row: to_i64(end_row),
1662 end_column: to_i64(end_column),
1663 };
1664 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
1665 inner.modified = true;
1666 inner.invalidate_text_cache();
1667 inner.rehighlight_all();
1668 crate::document::emit_content_change_events(&mut inner, &before);
1669 inner.check_block_count_changed();
1670 self.queue_undo_redo_event(&mut inner)
1671 };
1672 crate::inner::dispatch_queued_events(queued);
1673 Ok(())
1674 }
1675
1676 pub fn split_table_cell(
1678 &self,
1679 cell_id: usize,
1680 split_rows: usize,
1681 split_columns: usize,
1682 ) -> Result<()> {
1683 let queued = {
1684 let mut inner = self.doc.lock();
1685 let before = crate::document::capture_block_state(&inner);
1686 let dto = frontend::document_editing::SplitTableCellDto {
1687 cell_id: to_i64(cell_id),
1688 split_rows: to_i64(split_rows),
1689 split_columns: to_i64(split_columns),
1690 };
1691 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
1692 inner.modified = true;
1693 inner.invalidate_text_cache();
1694 inner.rehighlight_all();
1695 crate::document::emit_content_change_events(&mut inner, &before);
1696 inner.check_block_count_changed();
1697 self.queue_undo_redo_event(&mut inner)
1698 };
1699 crate::inner::dispatch_queued_events(queued);
1700 Ok(())
1701 }
1702
1703 pub fn set_table_format(
1707 &self,
1708 table_id: usize,
1709 format: &crate::flow::TableFormat,
1710 ) -> Result<()> {
1711 let queued = {
1712 let mut inner = self.doc.lock();
1713 let dto = format.to_set_dto(table_id);
1714 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1715 inner.modified = true;
1716 inner.queue_event(DocumentEvent::FormatChanged {
1717 position: 0,
1718 length: 0,
1719 kind: crate::flow::FormatChangeKind::Block,
1720 });
1721 self.queue_undo_redo_event(&mut inner)
1722 };
1723 crate::inner::dispatch_queued_events(queued);
1724 Ok(())
1725 }
1726
1727 pub fn set_table_cell_format(
1729 &self,
1730 cell_id: usize,
1731 format: &crate::flow::CellFormat,
1732 ) -> Result<()> {
1733 let queued = {
1734 let mut inner = self.doc.lock();
1735 let dto = format.to_set_dto(cell_id);
1736 document_formatting_commands::set_table_cell_format(
1737 &inner.ctx,
1738 Some(inner.stack_id),
1739 &dto,
1740 )?;
1741 inner.modified = true;
1742 inner.queue_event(DocumentEvent::FormatChanged {
1743 position: 0,
1744 length: 0,
1745 kind: crate::flow::FormatChangeKind::Block,
1746 });
1747 self.queue_undo_redo_event(&mut inner)
1748 };
1749 crate::inner::dispatch_queued_events(queued);
1750 Ok(())
1751 }
1752
1753 pub fn remove_current_table(&self) -> Result<()> {
1758 let table = self.current_table().ok_or_else(|| {
1759 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1760 })?;
1761 self.remove_table(table.id())
1762 }
1763
1764 pub fn insert_row_above(&self) -> Result<()> {
1767 let cell_ref = self.current_table_cell().ok_or_else(|| {
1768 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1769 })?;
1770 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
1771 }
1772
1773 pub fn insert_row_below(&self) -> Result<()> {
1776 let cell_ref = self.current_table_cell().ok_or_else(|| {
1777 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1778 })?;
1779 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
1780 }
1781
1782 pub fn insert_column_before(&self) -> Result<()> {
1785 let cell_ref = self.current_table_cell().ok_or_else(|| {
1786 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1787 })?;
1788 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
1789 }
1790
1791 pub fn insert_column_after(&self) -> Result<()> {
1794 let cell_ref = self.current_table_cell().ok_or_else(|| {
1795 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1796 })?;
1797 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
1798 }
1799
1800 pub fn remove_current_row(&self) -> Result<()> {
1803 let cell_ref = self.current_table_cell().ok_or_else(|| {
1804 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1805 })?;
1806 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
1807 }
1808
1809 pub fn remove_current_column(&self) -> Result<()> {
1812 let cell_ref = self.current_table_cell().ok_or_else(|| {
1813 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1814 })?;
1815 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
1816 }
1817
1818 pub fn merge_selected_cells(&self) -> Result<()> {
1825 let pos_cell = self.current_table_cell().ok_or_else(|| {
1826 DocumentError::InvalidCursorContext("cursor position is not inside a table".into())
1827 })?;
1828
1829 let (_pos, anchor) = self.read_cursor();
1831 let anchor_cell = {
1832 let inner = self.doc.lock();
1834 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1835 position: to_i64(anchor),
1836 };
1837 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1838 .map_err(|_| {
1839 DocumentError::InvalidCursorContext(
1840 "cursor anchor is not inside a table".into(),
1841 )
1842 })?;
1843 let block = crate::text_block::TextBlock {
1844 doc: self.doc.clone(),
1845 block_id: block_info.block_id as usize,
1846 };
1847 drop(inner);
1848 block.table_cell().ok_or_else(|| {
1849 DocumentError::InvalidCursorContext("cursor anchor is not inside a table".into())
1850 })?
1851 };
1852
1853 if pos_cell.table.id() != anchor_cell.table.id() {
1854 return Err(DocumentError::InvalidArgument(
1855 "position and anchor are in different tables".into(),
1856 ));
1857 }
1858
1859 let start_row = pos_cell.row.min(anchor_cell.row);
1860 let start_col = pos_cell.column.min(anchor_cell.column);
1861 let end_row = pos_cell.row.max(anchor_cell.row);
1862 let end_col = pos_cell.column.max(anchor_cell.column);
1863
1864 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
1865 }
1866
1867 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1870 let cell_ref = self.current_table_cell().ok_or_else(|| {
1871 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1872 })?;
1873 let cell = cell_ref
1875 .table
1876 .cell(cell_ref.row, cell_ref.column)
1877 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1878 self.split_table_cell(cell.id(), split_rows, split_columns)
1880 }
1881
1882 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1885 let table = self.current_table().ok_or_else(|| {
1886 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1887 })?;
1888 self.set_table_format(table.id(), format)
1889 }
1890
1891 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1894 let cell_ref = self.current_table_cell().ok_or_else(|| {
1895 DocumentError::InvalidCursorContext("cursor is not inside a table".into())
1896 })?;
1897 let cell = cell_ref
1898 .table
1899 .cell(cell_ref.row, cell_ref.column)
1900 .ok_or_else(|| DocumentError::NotFound("cell not found".into()))?;
1901 self.set_table_cell_format(cell.id(), format)
1902 }
1903
1904 pub fn selection_kind(&self) -> crate::flow::SelectionKind {
1912 use crate::flow::{CellRange, SelectionKind};
1913
1914 {
1916 let d = self.data.lock();
1917 if let Some(ref range) = d.cell_selection_override {
1918 return SelectionKind::Cells(range.clone());
1919 }
1920 if d.position == d.anchor {
1921 return SelectionKind::None;
1922 }
1923 }
1924
1925 let (pos, anchor) = self.read_cursor();
1926
1927 let pos_cell = self.table_cell_at(pos);
1929 let anchor_cell = self.table_cell_at(anchor);
1930
1931 match (&pos_cell, &anchor_cell) {
1932 (None, None) => {
1933 let (start, end) = (pos.min(anchor), pos.max(anchor));
1937 if let Some(t) = self.find_table_between(start, end) {
1938 let table_id = t.id();
1939 let rows = t.rows();
1940 let cols = t.columns();
1941 let range = CellRange {
1942 table_id,
1943 start_row: 0,
1944 start_col: 0,
1945 end_row: if rows > 0 { rows - 1 } else { 0 },
1946 end_col: if cols > 0 { cols - 1 } else { 0 },
1947 };
1948 let spans = self.collect_cell_spans(table_id);
1949 SelectionKind::Mixed {
1950 cell_range: range.expand_for_spans(&spans),
1951 text_before: true,
1952 text_after: true,
1953 }
1954 } else {
1955 SelectionKind::Text
1956 }
1957 }
1958 (Some(pc), Some(ac)) => {
1959 if pc.table.id() != ac.table.id() {
1960 return SelectionKind::Text;
1962 }
1963 if pc.row == ac.row && pc.column == ac.column {
1964 return SelectionKind::Text;
1966 }
1967 let range = CellRange {
1969 table_id: pc.table.id(),
1970 start_row: pc.row.min(ac.row),
1971 start_col: pc.column.min(ac.column),
1972 end_row: pc.row.max(ac.row),
1973 end_col: pc.column.max(ac.column),
1974 };
1975 let spans = self.collect_cell_spans(pc.table.id());
1976 SelectionKind::Cells(range.expand_for_spans(&spans))
1977 }
1978 (Some(tc), None) | (None, Some(tc)) => {
1979 let table_id = tc.table.id();
1983 let rows = tc.table.rows();
1984 let cols = tc.table.columns();
1985
1986 let inside_pos = if pos_cell.is_some() { pos } else { anchor };
1987 let outside_pos = if pos_cell.is_some() { anchor } else { pos };
1988
1989 let text_before = outside_pos < inside_pos;
1990 let text_after = !text_before;
1991
1992 let range = CellRange {
1993 table_id,
1994 start_row: 0,
1995 start_col: 0,
1996 end_row: if rows > 0 { rows - 1 } else { 0 },
1997 end_col: if cols > 0 { cols - 1 } else { 0 },
1998 };
1999 let spans = self.collect_cell_spans(table_id);
2000 SelectionKind::Mixed {
2001 cell_range: range.expand_for_spans(&spans),
2002 text_before,
2003 text_after,
2004 }
2005 }
2006 }
2007 }
2008
2009 pub fn is_cell_selection(&self) -> bool {
2011 matches!(
2012 self.selection_kind(),
2013 crate::flow::SelectionKind::Cells(_) | crate::flow::SelectionKind::Mixed { .. }
2014 )
2015 }
2016
2017 pub fn selected_cell_range(&self) -> Option<crate::flow::CellRange> {
2019 match self.selection_kind() {
2020 crate::flow::SelectionKind::Cells(r) => Some(r),
2021 crate::flow::SelectionKind::Mixed { cell_range, .. } => Some(cell_range),
2022 _ => None,
2023 }
2024 }
2025
2026 pub fn selected_cells(&self) -> Vec<TableCellRef> {
2028 let range = match self.selected_cell_range() {
2029 Some(r) => r,
2030 None => return Vec::new(),
2031 };
2032 let table = TextTable {
2033 doc: self.doc.clone(),
2034 table_id: range.table_id,
2035 };
2036 let mut cells = Vec::new();
2037 for row in range.start_row..=range.end_row {
2038 for col in range.start_col..=range.end_col {
2039 if table.cell(row, col).is_some() {
2040 cells.push(TableCellRef {
2041 table: table.clone(),
2042 row,
2043 column: col,
2044 });
2045 }
2046 }
2047 }
2048 cells
2049 }
2050
2051 pub fn select_table_cell(&self, table_id: usize, row: usize, col: usize) {
2055 let mut d = self.data.lock();
2056 d.cell_selection_override = Some(crate::flow::CellRange {
2057 table_id,
2058 start_row: row,
2059 start_col: col,
2060 end_row: row,
2061 end_col: col,
2062 });
2063 }
2064
2065 pub fn select_cell_range(
2067 &self,
2068 table_id: usize,
2069 start_row: usize,
2070 start_col: usize,
2071 end_row: usize,
2072 end_col: usize,
2073 ) {
2074 let range = crate::flow::CellRange {
2075 table_id,
2076 start_row,
2077 start_col,
2078 end_row,
2079 end_col,
2080 };
2081 let spans = self.collect_cell_spans(table_id);
2082 let mut d = self.data.lock();
2083 d.cell_selection_override = Some(range.expand_for_spans(&spans));
2084 }
2085
2086 pub fn clear_cell_selection(&self) {
2088 let mut d = self.data.lock();
2089 d.cell_selection_override = None;
2090 }
2091
2092 fn cell_range_positions(&self, range: &CellRange) -> Option<(usize, usize)> {
2095 let inner = self.doc.lock();
2096 let main_frame_id = get_main_frame_id(&inner);
2097 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2098 drop(inner);
2099
2100 let table = flow.into_iter().find_map(|e| match e {
2102 FlowElement::Table(t) if t.id() == range.table_id => Some(t),
2103 _ => None,
2104 })?;
2105
2106 let mut min_pos = usize::MAX;
2107 let mut max_pos = 0usize;
2108
2109 for row in range.start_row..=range.end_row {
2110 for col in range.start_col..=range.end_col {
2111 if let Some(cell) = table.cell(row, col) {
2112 for block in cell.blocks() {
2113 let bp = block.position();
2114 let bl = block.length();
2115 min_pos = min_pos.min(bp);
2116 max_pos = max_pos.max(bp + bl);
2117 }
2118 }
2119 }
2120 }
2121
2122 if min_pos == usize::MAX {
2123 return None;
2124 }
2125
2126 Some((min_pos, max_pos + 1))
2128 }
2129
2130 fn table_cell_at(&self, position: usize) -> Option<TableCellRef> {
2134 let inner = self.doc.lock();
2135 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2136 position: to_i64(position),
2137 };
2138 let block_info =
2139 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2140
2141 let block_id = if to_i64(position) < block_info.block_start && position > 0 {
2142 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2143 position: to_i64(position - 1),
2144 };
2145 let prev_info =
2146 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
2147 prev_info.block_id as usize
2148 } else {
2149 block_info.block_id as usize
2150 };
2151
2152 let block = crate::text_block::TextBlock {
2153 doc: self.doc.clone(),
2154 block_id,
2155 };
2156 drop(inner);
2157 block.table_cell()
2158 }
2159
2160 fn table_boundary_position(&self, table_id: usize, before: bool) -> Option<usize> {
2171 let inner = self.doc.lock();
2172 let main_frame_id = get_main_frame_id(&inner);
2173 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2174 drop(inner);
2175
2176 let idx = flow
2178 .iter()
2179 .position(|e| matches!(e, FlowElement::Table(t) if t.id() == table_id))?;
2180
2181 if before {
2182 for i in (0..idx).rev() {
2184 if let FlowElement::Block(b) = &flow[i] {
2185 return Some(b.position() + b.length());
2186 }
2187 }
2188 } else {
2189 for item in flow.iter().skip(idx + 1) {
2191 if let FlowElement::Block(b) = item {
2192 return Some(b.position());
2193 }
2194 }
2195 }
2196 None
2197 }
2198
2199 fn find_table_between(&self, start: usize, end: usize) -> Option<TextTable> {
2201 let inner = self.doc.lock();
2202 let main_frame_id = get_main_frame_id(&inner);
2203 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
2204 drop(inner);
2205
2206 for elem in flow {
2207 if let FlowElement::Table(t) = elem {
2208 if let Some(first_cell) = t.cell(0, 0) {
2211 let blocks = first_cell.blocks();
2212 if let Some(fb) = blocks.first() {
2213 let p = fb.position();
2214 if p > start && p < end {
2215 return Some(t);
2216 }
2217 }
2218 }
2219 }
2220 }
2221 None
2222 }
2223
2224 fn collect_cell_spans(&self, table_id: usize) -> Vec<(usize, usize, usize, usize)> {
2226 let inner = self.doc.lock();
2227 let table_dto =
2228 match frontend::commands::table_commands::get_table(&inner.ctx, &(table_id as u64))
2229 .ok()
2230 .flatten()
2231 {
2232 Some(t) => t,
2233 None => return Vec::new(),
2234 };
2235
2236 let mut spans = Vec::with_capacity(table_dto.cells.len());
2237 for &cell_id in &table_dto.cells {
2238 if let Some(cell) =
2239 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &cell_id)
2240 .ok()
2241 .flatten()
2242 {
2243 spans.push((
2244 cell.row as usize,
2245 cell.column as usize,
2246 cell.row_span.max(1) as usize,
2247 cell.column_span.max(1) as usize,
2248 ));
2249 }
2250 }
2251 spans
2252 }
2253
2254 pub fn delete_char(&self) -> Result<()> {
2256 let (pos, anchor) = self.read_cursor();
2257 let (del_pos, del_anchor) = if pos != anchor {
2258 (pos, anchor)
2259 } else {
2260 let end = {
2262 let inner = self.doc.lock();
2263 max_cursor_position_of(&inner).unwrap_or(0)
2264 };
2265 if pos >= end {
2266 return Ok(());
2267 }
2268 let to = self.next_grapheme_boundary(pos);
2272 if to == pos {
2273 return Ok(());
2274 }
2275 (pos, to)
2276 };
2277 self.do_delete(del_pos, del_anchor)
2278 }
2279
2280 pub fn delete_previous_char(&self) -> Result<()> {
2282 let (pos, anchor) = self.read_cursor();
2283 let (del_pos, del_anchor) = if pos != anchor {
2284 (pos, anchor)
2285 } else if pos > 0 {
2286 let from = self.prev_grapheme_boundary(pos);
2287 if from == pos {
2288 return Ok(());
2289 }
2290 (from, pos)
2291 } else {
2292 return Ok(());
2293 };
2294 self.do_delete(del_pos, del_anchor)
2295 }
2296
2297 pub fn remove_selected_text(&self) -> Result<String> {
2299 let (pos, anchor) = self.read_cursor();
2300 if pos == anchor {
2301 return Ok(String::new());
2302 }
2303 let queued = {
2304 let mut inner = self.doc.lock();
2305 let dto = frontend::document_editing::DeleteTextDto {
2306 position: to_i64(pos),
2307 anchor: to_i64(anchor),
2308 };
2309 let result =
2310 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2311 let edit_pos = pos.min(anchor);
2312 let removed = pos.max(anchor) - edit_pos;
2313 let new_pos = to_usize(result.new_position);
2314 inner.adjust_cursors(edit_pos, removed, 0);
2315 {
2316 let mut d = self.data.lock();
2317 d.position = new_pos;
2318 d.anchor = new_pos;
2319 }
2320 inner.modified = true;
2321 inner.invalidate_text_cache();
2322 inner.rehighlight_affected(edit_pos);
2323 inner.queue_event(DocumentEvent::ContentsChanged {
2324 position: edit_pos,
2325 chars_removed: removed,
2326 chars_added: 0,
2327 blocks_affected: 1,
2328 });
2329 inner.check_block_count_changed();
2330 inner.check_flow_changed();
2331 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
2333 };
2334 crate::inner::dispatch_queued_events(queued.1);
2335 Ok(queued.0)
2336 }
2337
2338 pub fn current_list(&self) -> Option<crate::TextList> {
2343 let pos = self.position();
2344 let inner = self.doc.lock();
2345 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2346 position: to_i64(pos),
2347 };
2348 let block_info =
2349 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
2350 let block = crate::text_block::TextBlock {
2351 doc: self.doc.clone(),
2352 block_id: block_info.block_id as usize,
2353 };
2354 drop(inner);
2355 block.list()
2356 }
2357
2358 pub fn create_list(&self, style: ListStyle) -> Result<()> {
2360 let (pos, anchor) = self.read_cursor();
2361 let queued = {
2362 let mut inner = self.doc.lock();
2363 let dto = frontend::document_editing::CreateListDto {
2364 position: to_i64(pos),
2365 anchor: to_i64(anchor),
2366 style: style.clone(),
2367 };
2368 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2369 inner.modified = true;
2370 inner.rehighlight_affected(pos.min(anchor));
2371 inner.queue_event(DocumentEvent::ContentsChanged {
2372 position: pos.min(anchor),
2373 chars_removed: 0,
2374 chars_added: 0,
2375 blocks_affected: 1,
2376 });
2377 self.queue_undo_redo_event(&mut inner)
2378 };
2379 crate::inner::dispatch_queued_events(queued);
2380 Ok(())
2381 }
2382
2383 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
2385 let (pos, anchor) = self.read_cursor();
2386 let queued = {
2387 let mut inner = self.doc.lock();
2388 let dto = frontend::document_editing::InsertListDto {
2389 position: to_i64(pos),
2390 anchor: to_i64(anchor),
2391 style: style.clone(),
2392 };
2393 let result =
2394 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2395 let edit_pos = pos.min(anchor);
2396 let removed = pos.max(anchor) - edit_pos;
2397 self.finish_edit_ext(
2398 &mut inner,
2399 edit_pos,
2400 removed,
2401 to_usize(result.new_position),
2402 1,
2403 false,
2404 )
2405 };
2406 crate::inner::dispatch_queued_events(queued);
2407 Ok(())
2408 }
2409
2410 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
2412 let queued = {
2413 let mut inner = self.doc.lock();
2414 let dto = format.to_set_dto(list_id);
2415 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2416 inner.modified = true;
2417 inner.queue_event(DocumentEvent::FormatChanged {
2418 position: 0,
2419 length: 0,
2420 kind: crate::flow::FormatChangeKind::List,
2421 });
2422 self.queue_undo_redo_event(&mut inner)
2423 };
2424 crate::inner::dispatch_queued_events(queued);
2425 Ok(())
2426 }
2427
2428 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
2431 let list = self.current_list().ok_or_else(|| {
2432 DocumentError::InvalidCursorContext("cursor is not inside a list".into())
2433 })?;
2434 self.set_list_format(list.id(), format)
2435 }
2436
2437 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
2439 let queued = {
2440 let mut inner = self.doc.lock();
2441 let dto = frontend::document_editing::AddBlockToListDto {
2442 block_id: to_i64(block_id),
2443 list_id: to_i64(list_id),
2444 };
2445 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
2446 inner.modified = true;
2447 inner.queue_event(DocumentEvent::FormatChanged {
2454 position: 0,
2455 length: 0,
2456 kind: crate::flow::FormatChangeKind::List,
2457 });
2458 self.queue_undo_redo_event(&mut inner)
2459 };
2460 crate::inner::dispatch_queued_events(queued);
2461 Ok(())
2462 }
2463
2464 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
2466 let pos = self.position();
2467 let inner = self.doc.lock();
2468 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2469 position: to_i64(pos),
2470 };
2471 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2472 drop(inner);
2473 self.add_block_to_list(block_info.block_id as usize, list_id)
2474 }
2475
2476 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
2478 let queued = {
2479 let mut inner = self.doc.lock();
2480 let dto = frontend::document_editing::RemoveBlockFromListDto {
2481 block_id: to_i64(block_id),
2482 };
2483 document_editing_commands::remove_block_from_list(
2484 &inner.ctx,
2485 Some(inner.stack_id),
2486 &dto,
2487 )?;
2488 inner.modified = true;
2489 inner.queue_event(DocumentEvent::FormatChanged {
2492 position: 0,
2493 length: 0,
2494 kind: crate::flow::FormatChangeKind::List,
2495 });
2496 self.queue_undo_redo_event(&mut inner)
2497 };
2498 crate::inner::dispatch_queued_events(queued);
2499 Ok(())
2500 }
2501
2502 pub fn remove_current_block_from_list(&self) -> Result<()> {
2505 let pos = self.position();
2506 let inner = self.doc.lock();
2507 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2508 position: to_i64(pos),
2509 };
2510 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2511 drop(inner);
2512 self.remove_block_from_list(block_info.block_id as usize)
2513 }
2514
2515 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
2518 let list = crate::text_list::TextList {
2519 doc: self.doc.clone(),
2520 list_id,
2521 };
2522 let block = list.item(index).ok_or_else(|| {
2523 DocumentError::OutOfRange(format!("list item index {index} out of range"))
2524 })?;
2525 self.remove_block_from_list(block.id())
2526 }
2527
2528 pub fn char_format(&self) -> Result<TextFormat> {
2533 let pos = self.position();
2534 let inner = self.doc.lock();
2535
2536 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2538 position: to_i64(pos),
2539 };
2540 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
2541 let block_id = block_info.block_id as u64;
2542 let mut block_dto =
2543 frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2544 .ok_or_else(|| DocumentError::NotFound("block not found at position".into()))?;
2545 let store = inner.ctx.db_context.get_store();
2546 crate::inner::refresh_block_position(&mut block_dto, store);
2547
2548 let local_char = pos.saturating_sub(block_dto.document_position as usize);
2551 let entity: common::entities::Block = block_dto.clone().into();
2552 let plain_owned = common::database::rope_helpers::block_content_via_store(&entity, store);
2553 let plain: &str = &plain_owned;
2554 let byte_offset: u32 = plain
2555 .char_indices()
2556 .nth(local_char)
2557 .map(|(b, _)| b as u32)
2558 .unwrap_or(plain.len() as u32);
2559
2560 let images = store
2563 .block_images
2564 .read()
2565 .get(&block_id)
2566 .cloned()
2567 .unwrap_or_default();
2568 if let Some(img) = images.iter().find(|i| i.byte_offset == byte_offset) {
2569 return Ok(TextFormat::from(&img.format));
2570 }
2571
2572 let runs = store
2574 .format_runs
2575 .read()
2576 .get(&block_id)
2577 .cloned()
2578 .unwrap_or_default();
2579 let fmt = runs
2580 .iter()
2581 .find(|r| r.byte_start <= byte_offset && byte_offset < r.byte_end)
2582 .map(|r| TextFormat::from(&r.format))
2583 .unwrap_or_default();
2584 Ok(fmt)
2585 }
2586
2587 pub fn block_format(&self) -> Result<BlockFormat> {
2594 let pos = self.position();
2595 let inner = self.doc.lock();
2596 let block_info = crate::inner::block_at_caret_dto(&inner.ctx, pos)?;
2597 let block_id = block_info.block_id as u64;
2598 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
2599 .ok_or_else(|| DocumentError::NotFound("block not found".into()))?;
2600 Ok(BlockFormat::from(&block))
2601 }
2602
2603 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
2607 let (pos, anchor) = self.read_cursor();
2608 let queued = {
2609 let mut inner = self.doc.lock();
2610 let dto = format.to_set_dto(pos, anchor);
2611 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2612 let start = pos.min(anchor);
2613 let length = pos.max(anchor) - start;
2614 inner.modified = true;
2615 inner.queue_event(DocumentEvent::FormatChanged {
2616 position: start,
2617 length,
2618 kind: crate::flow::FormatChangeKind::Character,
2619 });
2620 self.queue_undo_redo_event(&mut inner)
2621 };
2622 crate::inner::dispatch_queued_events(queued);
2623 Ok(())
2624 }
2625
2626 pub fn link_at_caret(&self) -> Option<LinkExtent> {
2636 let pos = self.position();
2637 let block_id = {
2638 let inner = self.doc.lock();
2639 crate::inner::block_at_caret_dto(&inner.ctx, pos)
2640 .ok()?
2641 .block_id as usize
2642 };
2643 let block = TextBlock {
2644 doc: self.doc.clone(),
2645 block_id,
2646 };
2647 crate::link_extent::link_extent_at(&block, pos)
2648 }
2649
2650 pub fn clear_char_anchor(&self) -> Result<()> {
2662 self.merge_char_format(&TextFormat {
2663 clear_link: true,
2664 ..Default::default()
2665 })
2666 }
2667
2668 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
2670 let (pos, anchor) = self.read_cursor();
2671 let queued = {
2672 let mut inner = self.doc.lock();
2673 let dto = format.to_merge_dto(pos, anchor);
2674 document_formatting_commands::merge_text_format(
2675 &inner.ctx,
2676 Some(inner.stack_id),
2677 &dto,
2678 )?;
2679 let start = pos.min(anchor);
2680 let length = pos.max(anchor) - start;
2681 inner.modified = true;
2682 inner.queue_event(DocumentEvent::FormatChanged {
2683 position: start,
2684 length,
2685 kind: crate::flow::FormatChangeKind::Character,
2686 });
2687 self.queue_undo_redo_event(&mut inner)
2688 };
2689 crate::inner::dispatch_queued_events(queued);
2690 Ok(())
2691 }
2692
2693 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
2695 let (pos, anchor) = self.read_cursor();
2696 let queued = {
2697 let mut inner = self.doc.lock();
2698 let dto = format.to_set_dto(pos, anchor);
2699 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2700 let start = pos.min(anchor);
2701 let length = pos.max(anchor) - start;
2702 inner.modified = true;
2703 inner.queue_event(DocumentEvent::FormatChanged {
2704 position: start,
2705 length,
2706 kind: crate::flow::FormatChangeKind::Block,
2707 });
2708 self.queue_undo_redo_event(&mut inner)
2709 };
2710 crate::inner::dispatch_queued_events(queued);
2711 Ok(())
2712 }
2713
2714 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
2716 let (pos, anchor) = self.read_cursor();
2717 let queued = {
2718 let mut inner = self.doc.lock();
2719 let dto = format.to_set_dto(pos, anchor, frame_id);
2720 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
2721 let start = pos.min(anchor);
2722 let length = pos.max(anchor) - start;
2723 inner.modified = true;
2724 inner.queue_event(DocumentEvent::FormatChanged {
2725 position: start,
2726 length,
2727 kind: crate::flow::FormatChangeKind::Block,
2728 });
2729 self.queue_undo_redo_event(&mut inner)
2730 };
2731 crate::inner::dispatch_queued_events(queued);
2732 Ok(())
2733 }
2734
2735 pub fn begin_edit_block(&self) {
2739 let inner = self.doc.lock();
2740 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
2741 }
2742
2743 pub fn end_edit_block(&self) {
2745 let inner = self.doc.lock();
2746 undo_redo_commands::end_composite(&inner.ctx);
2747 }
2748
2749 pub fn join_previous_edit_block(&self) {
2756 self.begin_edit_block();
2757 }
2758
2759 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
2763 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
2764 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
2765 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
2766 inner.take_queued_events()
2767 }
2768
2769 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
2770 let queued = {
2771 let mut inner = self.doc.lock();
2772 let dto = frontend::document_editing::DeleteTextDto {
2773 position: to_i64(pos),
2774 anchor: to_i64(anchor),
2775 };
2776 let result =
2777 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2778 let edit_pos = pos.min(anchor);
2779 let removed = pos.max(anchor) - edit_pos;
2780 let new_pos = to_usize(result.new_position);
2781 inner.adjust_cursors(edit_pos, removed, 0);
2782 {
2783 let mut d = self.data.lock();
2784 d.position = new_pos;
2785 d.anchor = new_pos;
2786 }
2787 inner.modified = true;
2788 inner.invalidate_text_cache();
2789 inner.rehighlight_affected(edit_pos);
2790 inner.queue_event(DocumentEvent::ContentsChanged {
2791 position: edit_pos,
2792 chars_removed: removed,
2793 chars_added: 0,
2794 blocks_affected: 1,
2795 });
2796 inner.check_block_count_changed();
2797 inner.check_flow_changed();
2798 self.queue_undo_redo_event(&mut inner)
2799 };
2800 crate::inner::dispatch_queued_events(queued);
2801 Ok(())
2802 }
2803
2804 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
2806 let pos = self.position();
2807 match op {
2808 MoveOperation::NoMove => pos,
2809 MoveOperation::Start => 0,
2810 MoveOperation::End => {
2811 let inner = self.doc.lock();
2812 max_cursor_position_of(&inner).unwrap_or(pos)
2813 }
2814 MoveOperation::NextCharacter | MoveOperation::Right => {
2815 let mut cur = pos;
2816 for _ in 0..n {
2817 let next = self.next_grapheme_boundary(cur);
2818 if next == cur {
2819 break;
2820 }
2821 cur = next;
2822 }
2823 cur
2824 }
2825 MoveOperation::PreviousCharacter | MoveOperation::Left => {
2826 let mut cur = pos;
2827 for _ in 0..n {
2828 let prev = self.prev_grapheme_boundary(cur);
2829 if prev == cur {
2830 break;
2831 }
2832 cur = prev;
2833 }
2834 cur
2835 }
2836 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
2837 let inner = self.doc.lock();
2838 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2839 position: to_i64(pos),
2840 };
2841 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2842 .map(|info| to_usize(info.block_start))
2843 .unwrap_or(pos)
2844 }
2845 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
2846 let inner = self.doc.lock();
2847 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2848 position: to_i64(pos),
2849 };
2850 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2851 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
2852 .unwrap_or(pos)
2853 }
2854 MoveOperation::NextBlock => {
2855 let inner = self.doc.lock();
2856 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2857 position: to_i64(pos),
2858 };
2859 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2860 .map(|info| {
2861 to_usize(info.block_start) + to_usize(info.block_length) + 1
2863 })
2864 .unwrap_or(pos)
2865 }
2866 MoveOperation::PreviousBlock => {
2867 let inner = self.doc.lock();
2868 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2869 position: to_i64(pos),
2870 };
2871 let block_start =
2872 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2873 .map(|info| to_usize(info.block_start))
2874 .unwrap_or(pos);
2875 if block_start >= 2 {
2876 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2878 position: to_i64(block_start - 2),
2879 };
2880 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
2881 .map(|info| to_usize(info.block_start))
2882 .unwrap_or(0)
2883 } else {
2884 0
2885 }
2886 }
2887 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
2888 let (_, end) = self.find_word_boundaries(pos);
2889 if end == pos {
2891 let inner = self.doc.lock();
2893 let max_pos = max_cursor_position_of(&inner).unwrap_or(0);
2894 let scan_len = max_pos.saturating_sub(pos).min(64);
2895 if scan_len == 0 {
2896 return pos;
2897 }
2898 let dto = frontend::document_inspection::GetTextAtPositionDto {
2899 position: to_i64(pos),
2900 length: to_i64(scan_len),
2901 };
2902 if let Ok(r) =
2903 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
2904 {
2905 for (i, ch) in r.text.chars().enumerate() {
2906 if ch.is_alphanumeric() || ch == '_' {
2907 let word_pos = pos + i;
2909 drop(inner);
2910 let (_, word_end) = self.find_word_boundaries(word_pos);
2911 return word_end;
2912 }
2913 }
2914 }
2915 pos + scan_len
2916 } else {
2917 end
2918 }
2919 }
2920 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
2921 let (start, _) = self.find_word_boundaries(pos);
2922 if start < pos {
2923 start
2924 } else if pos > 0 {
2925 let mut search = pos - 1;
2928 loop {
2929 let (ws, we) = self.find_word_boundaries(search);
2930 if ws < we {
2931 break ws;
2933 }
2934 if search == 0 {
2936 break 0;
2937 }
2938 search -= 1;
2939 }
2940 } else {
2941 0
2942 }
2943 }
2944 MoveOperation::StartOfSentence | MoveOperation::PreviousSentence => {
2945 let mut cur = pos;
2946 for _ in 0..n.max(1) {
2947 let start = match self.find_sentence_boundaries(cur) {
2948 Some((start, _)) => start,
2949 None => break,
2950 };
2951 if start < cur && op == MoveOperation::StartOfSentence {
2954 cur = start;
2955 } else if cur > 0 {
2956 match self.find_sentence_boundaries(cur - 1) {
2957 Some((prev, _)) if prev < cur => cur = prev,
2958 _ => cur = cur.saturating_sub(1),
2961 }
2962 } else {
2963 break;
2964 }
2965 }
2966 cur
2967 }
2968 MoveOperation::EndOfSentence => {
2969 let mut cur = pos;
2970 for _ in 0..n.max(1) {
2971 let end = match self.find_sentence_boundaries(cur) {
2972 Some((_, end)) => end,
2973 None => break,
2974 };
2975 if end > cur {
2976 cur = end;
2977 } else {
2978 match self.find_sentence_boundaries(cur + 1) {
2979 Some((_, next)) if next > cur => cur = next,
2980 _ => break,
2981 }
2982 }
2983 }
2984 cur
2985 }
2986 MoveOperation::NextSentence => {
2987 let mut cur = pos;
2988 for _ in 0..n.max(1) {
2989 let end = match self.find_sentence_boundaries(cur) {
2992 Some((_, end)) => end,
2993 None => break,
2994 };
2995 match self.find_sentence_boundaries(end + 1) {
2996 Some((start, _)) if start > cur => cur = start,
2997 _ => {
2998 if end > cur {
2999 cur = end;
3000 } else {
3001 break;
3002 }
3003 }
3004 }
3005 }
3006 cur
3007 }
3008 MoveOperation::Up | MoveOperation::Down => {
3009 if matches!(op, MoveOperation::Up) {
3012 self.resolve_move(MoveOperation::PreviousBlock, 1)
3013 } else {
3014 self.resolve_move(MoveOperation::NextBlock, 1)
3015 }
3016 }
3017 }
3018 }
3019
3020 pub(crate) fn snap_position_to_grapheme_boundary(&self) {
3031 let pos = {
3032 let data = self.data.lock();
3033 data.position
3034 };
3035 let snapped = self.forward_grapheme_boundary_at_or_after(pos);
3036 if snapped != pos {
3037 let mut data = self.data.lock();
3038 data.position = snapped;
3039 if data.anchor == pos {
3040 data.anchor = snapped;
3041 }
3042 }
3043 }
3044
3045 fn forward_grapheme_boundary_at_or_after(&self, pos: usize) -> usize {
3055 let inner = self.doc.lock();
3056 let end = max_cursor_position_of(&inner).unwrap_or(pos);
3057 if pos >= end {
3058 return pos;
3059 }
3060 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3061 position: to_i64(pos),
3062 };
3063 let Ok(block_info) =
3064 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto)
3065 else {
3066 return pos;
3067 };
3068 let block_start = to_usize(block_info.block_start);
3069 let block_length = to_usize(block_info.block_length);
3070 let offset_in_block = pos.saturating_sub(block_start);
3071 if offset_in_block == 0 || offset_in_block >= block_length {
3073 return pos;
3074 }
3075 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3076 position: to_i64(block_start),
3077 length: to_i64(block_length),
3078 };
3079 let Ok(r) = document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto)
3080 else {
3081 return pos;
3082 };
3083 let text = r.text;
3084 drop(inner);
3085 let mut acc = 0usize;
3088 for g in text.graphemes(true) {
3089 if acc >= offset_in_block {
3090 return block_start + acc;
3091 }
3092 acc += g.chars().count();
3093 }
3094 block_start + acc
3095 }
3096
3097 fn next_grapheme_boundary(&self, pos: usize) -> usize {
3110 let inner = self.doc.lock();
3111 let end = max_cursor_position_of(&inner).unwrap_or(pos);
3112 if pos >= end {
3113 return pos;
3114 }
3115 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3116 position: to_i64(pos),
3117 };
3118 let block_info =
3119 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3120 Ok(info) => info,
3121 Err(_) => return pos + 1,
3122 };
3123 let block_start = to_usize(block_info.block_start);
3124 let block_length = to_usize(block_info.block_length);
3125 let offset_in_block = pos.saturating_sub(block_start);
3126 if offset_in_block >= block_length {
3127 return (pos + 1).min(end);
3130 }
3131 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3132 position: to_i64(pos),
3133 length: to_i64(block_length - offset_in_block),
3134 };
3135 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
3136 Ok(r) => r.text,
3137 Err(_) => return pos + 1,
3138 };
3139 drop(inner);
3140 match text.graphemes(true).next() {
3141 Some(g) if !g.is_empty() => (pos + g.chars().count()).min(end),
3142 _ => (pos + 1).min(end),
3143 }
3144 }
3145
3146 fn prev_grapheme_boundary(&self, pos: usize) -> usize {
3150 if pos == 0 {
3151 return 0;
3152 }
3153 let inner = self.doc.lock();
3154 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3155 position: to_i64(pos.saturating_sub(1)),
3156 };
3157 let block_info =
3158 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3159 Ok(info) => info,
3160 Err(_) => return pos - 1,
3161 };
3162 let block_start = to_usize(block_info.block_start);
3163 let block_length = to_usize(block_info.block_length);
3164 let block_end = block_start + block_length;
3165 if pos > block_end {
3169 return pos - 1;
3170 }
3171 if block_length == 0 || pos <= block_start {
3172 return pos.saturating_sub(1);
3173 }
3174 let scan_len = pos - block_start;
3175 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
3176 position: to_i64(block_start),
3177 length: to_i64(scan_len),
3178 };
3179 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
3180 Ok(r) => r.text,
3181 Err(_) => return pos - 1,
3182 };
3183 drop(inner);
3184 match text.graphemes(true).next_back() {
3185 Some(g) if !g.is_empty() => pos - g.chars().count(),
3186 _ => pos - 1,
3187 }
3188 }
3189
3190 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
3196 let inner = self.doc.lock();
3197 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3199 position: to_i64(pos),
3200 };
3201 let block_info =
3202 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
3203 Ok(info) => info,
3204 Err(_) => return (pos, pos),
3205 };
3206
3207 let block_start = to_usize(block_info.block_start);
3208 let block_length = to_usize(block_info.block_length);
3209 if block_length == 0 {
3210 return (pos, pos);
3211 }
3212
3213 let dto = frontend::document_inspection::GetTextAtPositionDto {
3214 position: to_i64(block_start),
3215 length: to_i64(block_length),
3216 };
3217 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
3218 Ok(r) => r.text,
3219 Err(_) => return (pos, pos),
3220 };
3221
3222 let cursor_offset = pos.saturating_sub(block_start);
3224
3225 let mut last_char_start = 0;
3227 let mut last_char_end = 0;
3228
3229 for (word_byte_start, word) in text.unicode_word_indices() {
3230 let word_char_start = text[..word_byte_start].chars().count();
3232 let word_char_len = word.chars().count();
3233 let word_char_end = word_char_start + word_char_len;
3234
3235 last_char_start = word_char_start;
3236 last_char_end = word_char_end;
3237
3238 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
3239 return (block_start + word_char_start, block_start + word_char_end);
3240 }
3241 }
3242
3243 if cursor_offset == last_char_end && last_char_start < last_char_end {
3245 return (block_start + last_char_start, block_start + last_char_end);
3246 }
3247
3248 (pos, pos)
3249 }
3250
3251 fn find_sentence_boundaries(&self, pos: usize) -> Option<(usize, usize)> {
3257 let locale = self.data.lock().content_locale.clone();
3258
3259 let inner = self.doc.lock();
3260 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
3261 position: to_i64(pos),
3262 };
3263 let block_info =
3264 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto).ok()?;
3265 let block_start = to_usize(block_info.block_start);
3266 let block_length = to_usize(block_info.block_length);
3267 if block_length == 0 {
3268 return None;
3269 }
3270 let dto = frontend::document_inspection::GetTextAtPositionDto {
3271 position: to_i64(block_start),
3272 length: to_i64(block_length),
3273 };
3274 let text = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
3275 .ok()?
3276 .text;
3277 drop(inner);
3278
3279 let offset = pos.saturating_sub(block_start);
3280 let (start, end) =
3281 frontend::common::parser_tools::sentence_bounds(&text, offset, locale.as_deref())?;
3282 Some((block_start + start, block_start + end))
3283 }
3284}
3285
3286#[derive(Clone, Copy, PartialEq, Eq)]
3293enum BlockEdge {
3294 First,
3295 Middle,
3296 Last,
3297 OnlyOne,
3298}
3299
3300fn cursor_frame_ref(inner: &TextDocumentInner, block_id: u64) -> Option<FrameRef> {
3304 let parent = crate::text_block::find_parent_frame(inner, block_id)?;
3305 let store = inner.ctx.db_context.get_store();
3306 let frames = store.frames.read();
3307 let frame = frames.get(&parent)?.clone();
3308 frame.parent_frame?;
3309 let is_blockquote = frame.fmt_is_blockquote.unwrap_or(false);
3310
3311 let mut depth = 0;
3312 let mut current = Some(parent);
3313 while let Some(id) = current {
3314 let Some(f) = frames.get(&id) else {
3315 break;
3316 };
3317 if f.parent_frame.is_none() {
3318 break;
3319 }
3320 depth += 1;
3321 current = f.parent_frame;
3322 }
3323
3324 Some(FrameRef {
3325 frame_id: frame.id as usize,
3326 parent_frame_id: frame.parent_frame.map(|id| id as usize),
3327 is_blockquote,
3328 depth,
3329 })
3330}
3331
3332fn innermost_blockquote_frame_id(inner: &TextDocumentInner, block_id: u64) -> Option<usize> {
3336 let mut current = crate::text_block::find_parent_frame(inner, block_id);
3337 let store = inner.ctx.db_context.get_store();
3338 let frames = store.frames.read();
3339 while let Some(id) = current {
3340 let f = frames.get(&id)?;
3341 if f.fmt_is_blockquote == Some(true) {
3342 return Some(f.id as usize);
3343 }
3344 current = f.parent_frame;
3345 }
3346 None
3347}
3348
3349fn blockquote_depth_for_block(inner: &TextDocumentInner, block_id: u64) -> usize {
3352 let mut current = crate::text_block::find_parent_frame(inner, block_id);
3353 let store = inner.ctx.db_context.get_store();
3354 let frames = store.frames.read();
3355 let mut count = 0;
3356 while let Some(id) = current {
3357 let Some(f) = frames.get(&id) else {
3358 break;
3359 };
3360 if f.fmt_is_blockquote == Some(true) {
3361 count += 1;
3362 }
3363 current = f.parent_frame;
3364 }
3365 count
3366}
3367
3368fn block_position_in_current_frame(cursor: &TextCursor) -> Option<BlockEdge> {
3374 let pos = cursor.position();
3375 let inner = cursor.doc.lock();
3376 let dto = frontend::document_inspection::GetBlockAtPositionDto {
3377 position: to_i64(pos),
3378 };
3379 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
3380 let block_id = block_info.block_id as common::types::EntityId;
3381 let parent_id = crate::text_block::find_parent_frame(&inner, block_info.block_id as u64)?;
3382 let store = inner.ctx.db_context.get_store();
3383 let frames = store.frames.read();
3384 let frame = frames.get(&parent_id)?;
3385 let block_positions: Vec<usize> = frame
3386 .child_order
3387 .iter()
3388 .enumerate()
3389 .filter_map(|(i, &e)| {
3390 if e > 0 {
3391 Some((i, e as common::types::EntityId))
3392 } else {
3393 None
3394 }
3395 })
3396 .filter(|(_, id)| *id == block_id)
3397 .map(|(i, _)| i)
3398 .collect();
3399 let block_idx = *block_positions.first()?;
3400 let positive_entries: Vec<usize> = frame
3401 .child_order
3402 .iter()
3403 .enumerate()
3404 .filter_map(|(i, &e)| if e > 0 { Some(i) } else { None })
3405 .collect();
3406 let first_pos = *positive_entries.first()?;
3407 let last_pos = *positive_entries.last()?;
3408 let is_first = block_idx == first_pos;
3409 let is_last = block_idx == last_pos;
3410 let edge = match (is_first, is_last, positive_entries.len()) {
3411 (_, _, 1) => BlockEdge::OnlyOne,
3412 (true, _, _) => BlockEdge::First,
3413 (_, true, _) => BlockEdge::Last,
3414 _ => BlockEdge::Middle,
3415 };
3416 Some(edge)
3417}