1use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use anyhow::Result;
8
9use crate::ListStyle;
10use frontend::commands::{
11 document_editing_commands, document_formatting_commands, document_inspection_commands,
12 undo_redo_commands,
13};
14
15use unicode_segmentation::UnicodeSegmentation;
16
17use crate::convert::{to_i64, to_usize};
18use crate::events::DocumentEvent;
19use crate::flow::{CellRange, FlowElement, SelectionKind, TableCellRef};
20use crate::fragment::DocumentFragment;
21use crate::inner::{CursorData, QueuedEvents, TextDocumentInner};
22use crate::text_table::TextTable;
23use crate::{BlockFormat, FrameFormat, MoveMode, MoveOperation, SelectionType, TextFormat};
24
25use crate::document::get_main_frame_id;
26
27fn max_cursor_position(stats: &frontend::document_inspection::DocumentStatsDto) -> usize {
33 let chars = to_usize(stats.character_count);
34 let blocks = to_usize(stats.block_count);
35 if blocks > 1 {
36 chars + blocks - 1
37 } else {
38 chars
39 }
40}
41
42pub struct TextCursor {
50 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
51 pub(crate) data: Arc<Mutex<CursorData>>,
52}
53
54impl Clone for TextCursor {
55 fn clone(&self) -> Self {
56 let (position, anchor) = {
57 let d = self.data.lock();
58 (d.position, d.anchor)
59 };
60 let data = {
61 let mut inner = self.doc.lock();
62 let data = Arc::new(Mutex::new(CursorData {
63 position,
64 anchor,
65 cell_selection_override: None,
66 }));
67 inner.cursors.push(Arc::downgrade(&data));
68 data
69 };
70 TextCursor {
71 doc: self.doc.clone(),
72 data,
73 }
74 }
75}
76
77impl TextCursor {
78 fn read_cursor(&self) -> (usize, usize) {
81 let d = self.data.lock();
82 (d.position, d.anchor)
83 }
84
85 fn finish_edit(
89 &self,
90 inner: &mut TextDocumentInner,
91 edit_pos: usize,
92 removed: usize,
93 new_pos: usize,
94 blocks_affected: usize,
95 ) -> QueuedEvents {
96 self.finish_edit_ext(inner, edit_pos, removed, new_pos, blocks_affected, true)
97 }
98
99 fn finish_edit_ext(
100 &self,
101 inner: &mut TextDocumentInner,
102 edit_pos: usize,
103 removed: usize,
104 new_pos: usize,
105 blocks_affected: usize,
106 flow_may_change: bool,
107 ) -> QueuedEvents {
108 let added = new_pos.saturating_sub(edit_pos);
115 inner.adjust_cursors(edit_pos, removed, added);
116 {
117 let mut d = self.data.lock();
118 d.position = new_pos;
119 d.anchor = new_pos;
120 }
121 inner.modified = true;
122 inner.invalidate_text_cache();
123 inner.rehighlight_affected(edit_pos);
124 inner.queue_event(DocumentEvent::ContentsChanged {
125 position: edit_pos,
126 chars_removed: removed,
127 chars_added: added,
128 blocks_affected,
129 });
130 inner.check_block_count_changed();
131 if flow_may_change {
132 inner.check_flow_changed();
133 }
134 self.queue_undo_redo_event(inner)
135 }
136
137 pub fn position(&self) -> usize {
141 self.data.lock().position
142 }
143
144 pub fn anchor(&self) -> usize {
146 self.data.lock().anchor
147 }
148
149 pub fn has_selection(&self) -> bool {
151 let d = self.data.lock();
152 d.position != d.anchor
153 }
154
155 pub fn selection_start(&self) -> usize {
157 let d = self.data.lock();
158 d.position.min(d.anchor)
159 }
160
161 pub fn selection_end(&self) -> usize {
163 let d = self.data.lock();
164 d.position.max(d.anchor)
165 }
166
167 pub fn selected_text(&self) -> Result<String> {
169 let (pos, anchor) = self.read_cursor();
170 if pos == anchor {
171 return Ok(String::new());
172 }
173 let start = pos.min(anchor);
174 let len = pos.max(anchor) - start;
175 let inner = self.doc.lock();
176 let dto = frontend::document_inspection::GetTextAtPositionDto {
177 position: to_i64(start),
178 length: to_i64(len),
179 };
180 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
181 Ok(result.text)
182 }
183
184 pub fn clear_selection(&self) {
186 let mut d = self.data.lock();
187 d.anchor = d.position;
188 }
189
190 pub fn at_block_start(&self) -> bool {
194 let pos = self.position();
195 let inner = self.doc.lock();
196 let dto = frontend::document_inspection::GetBlockAtPositionDto {
197 position: to_i64(pos),
198 };
199 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
200 pos == to_usize(info.block_start)
201 } else {
202 false
203 }
204 }
205
206 pub fn at_block_end(&self) -> bool {
208 let pos = self.position();
209 let inner = self.doc.lock();
210 let dto = frontend::document_inspection::GetBlockAtPositionDto {
211 position: to_i64(pos),
212 };
213 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
214 pos == to_usize(info.block_start) + to_usize(info.block_length)
215 } else {
216 false
217 }
218 }
219
220 pub fn at_start(&self) -> bool {
222 self.data.lock().position == 0
223 }
224
225 pub fn at_end(&self) -> bool {
227 let pos = self.position();
228 let inner = self.doc.lock();
229 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
230 frontend::document_inspection::DocumentStatsDto {
231 character_count: 0,
232 word_count: 0,
233 block_count: 0,
234 frame_count: 0,
235 image_count: 0,
236 list_count: 0,
237 table_count: 0,
238 }
239 });
240 pos >= max_cursor_position(&stats)
241 }
242
243 pub fn block_number(&self) -> usize {
245 let pos = self.position();
246 let inner = self.doc.lock();
247 let dto = frontend::document_inspection::GetBlockAtPositionDto {
248 position: to_i64(pos),
249 };
250 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
251 .map(|info| to_usize(info.block_number))
252 .unwrap_or(0)
253 }
254
255 pub fn position_in_block(&self) -> usize {
257 let pos = self.position();
258 let inner = self.doc.lock();
259 let dto = frontend::document_inspection::GetBlockAtPositionDto {
260 position: to_i64(pos),
261 };
262 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
263 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
264 .unwrap_or(0)
265 }
266
267 pub fn set_position(&self, position: usize, mode: MoveMode) {
281 let end = {
283 let inner = self.doc.lock();
284 document_inspection_commands::get_document_stats(&inner.ctx)
285 .map(|s| max_cursor_position(&s))
286 .unwrap_or(0)
287 };
288 let mut pos = position.min(end);
289
290 if mode == MoveMode::KeepAnchor {
294 let anchor = self.data.lock().anchor;
295 let pos_cell = self.table_cell_at(pos);
296 let anchor_cell = self.table_cell_at(anchor);
297 match (&pos_cell, &anchor_cell) {
298 (Some(tc), None) => {
299 let before = anchor < pos;
301 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
302 pos = boundary;
303 }
304 }
305 (None, Some(tc)) => {
306 let before = pos < anchor;
309 if let Some(boundary) = self.table_boundary_position(tc.table.id(), !before) {
310 pos = boundary;
311 }
312 }
313 _ => {}
314 }
315 }
316
317 {
318 let mut d = self.data.lock();
319 d.position = pos;
320 if mode == MoveMode::MoveAnchor {
321 d.anchor = pos;
322 }
323 d.cell_selection_override = None;
324 }
325 self.snap_position_to_grapheme_boundary();
330 }
331
332 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
338 let old_pos = self.position();
339 let target = self.resolve_move(operation, n);
340 self.set_position(target, mode);
341 self.position() != old_pos
342 }
343
344 pub fn select(&self, selection: SelectionType) {
346 match selection {
347 SelectionType::Document => {
348 let end = {
349 let inner = self.doc.lock();
350 document_inspection_commands::get_document_stats(&inner.ctx)
351 .map(|s| max_cursor_position(&s))
352 .unwrap_or(0)
353 };
354 let mut d = self.data.lock();
355 d.anchor = 0;
356 d.position = end;
357 d.cell_selection_override = None;
358 }
359 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
360 let pos = self.position();
361 let inner = self.doc.lock();
362 let dto = frontend::document_inspection::GetBlockAtPositionDto {
363 position: to_i64(pos),
364 };
365 if let Ok(info) =
366 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
367 {
368 let start = to_usize(info.block_start);
369 let end = start + to_usize(info.block_length);
370 drop(inner);
371 let mut d = self.data.lock();
372 d.anchor = start;
373 d.position = end;
374 d.cell_selection_override = None;
375 }
376 }
377 SelectionType::WordUnderCursor => {
378 let pos = self.position();
379 let (word_start, word_end) = self.find_word_boundaries(pos);
380 let mut d = self.data.lock();
381 d.anchor = word_start;
382 d.position = word_end;
383 d.cell_selection_override = None;
384 }
385 }
386 }
387
388 pub fn insert_text(&self, text: &str) -> Result<()> {
392 let (pos, anchor) = self.read_cursor();
393
394 let dto = frontend::document_editing::InsertTextDto {
396 position: to_i64(pos),
397 anchor: to_i64(anchor),
398 text: text.into(),
399 };
400
401 let queued = {
402 let mut inner = self.doc.lock();
403 let result = match document_editing_commands::insert_text(
404 &inner.ctx,
405 Some(inner.stack_id),
406 &dto,
407 ) {
408 Ok(r) => r,
409 Err(_) if pos != anchor => {
410 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
412
413 let del_dto = frontend::document_editing::DeleteTextDto {
414 position: to_i64(pos),
415 anchor: to_i64(anchor),
416 };
417 let del_result = document_editing_commands::delete_text(
418 &inner.ctx,
419 Some(inner.stack_id),
420 &del_dto,
421 )?;
422 let del_pos = to_usize(del_result.new_position);
423
424 let ins_dto = frontend::document_editing::InsertTextDto {
425 position: to_i64(del_pos),
426 anchor: to_i64(del_pos),
427 text: text.into(),
428 };
429 let ins_result = document_editing_commands::insert_text(
430 &inner.ctx,
431 Some(inner.stack_id),
432 &ins_dto,
433 )?;
434
435 undo_redo_commands::end_composite(&inner.ctx);
436 ins_result
437 }
438 Err(e) => return Err(e),
439 };
440
441 let edit_pos = pos.min(anchor);
442 let removed = pos.max(anchor) - edit_pos;
443 self.finish_edit_ext(
444 &mut inner,
445 edit_pos,
446 removed,
447 to_usize(result.new_position),
448 to_usize(result.blocks_affected),
449 false,
450 )
451 };
452 crate::inner::dispatch_queued_events(queued);
453 Ok(())
454 }
455
456 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
458 let (pos, anchor) = self.read_cursor();
459
460 let make_dto = |p: usize, a: usize| frontend::document_editing::InsertFormattedTextDto {
461 position: to_i64(p),
462 anchor: to_i64(a),
463 text: text.into(),
464 font_family: format.font_family.clone().unwrap_or_default(),
465 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
466 font_bold: format.font_bold.unwrap_or(false),
467 font_italic: format.font_italic.unwrap_or(false),
468 font_underline: format.font_underline.unwrap_or(false),
469 font_strikeout: format.font_strikeout.unwrap_or(false),
470 };
471
472 let queued = {
473 let mut inner = self.doc.lock();
474 let result = match document_editing_commands::insert_formatted_text(
475 &inner.ctx,
476 Some(inner.stack_id),
477 &make_dto(pos, anchor),
478 ) {
479 Ok(r) => r,
480 Err(_) if pos != anchor => {
481 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
483
484 let del_dto = frontend::document_editing::DeleteTextDto {
485 position: to_i64(pos),
486 anchor: to_i64(anchor),
487 };
488 let del_result = document_editing_commands::delete_text(
489 &inner.ctx,
490 Some(inner.stack_id),
491 &del_dto,
492 )?;
493 let del_pos = to_usize(del_result.new_position);
494
495 let ins_result = document_editing_commands::insert_formatted_text(
496 &inner.ctx,
497 Some(inner.stack_id),
498 &make_dto(del_pos, del_pos),
499 )?;
500
501 undo_redo_commands::end_composite(&inner.ctx);
502 ins_result
503 }
504 Err(e) => return Err(e),
505 };
506
507 let edit_pos = pos.min(anchor);
508 let removed = pos.max(anchor) - edit_pos;
509 self.finish_edit_ext(
510 &mut inner,
511 edit_pos,
512 removed,
513 to_usize(result.new_position),
514 1,
515 false,
516 )
517 };
518 crate::inner::dispatch_queued_events(queued);
519 Ok(())
520 }
521
522 pub fn insert_block(&self) -> Result<()> {
524 let (pos, anchor) = self.read_cursor();
525 let queued = {
526 let mut inner = self.doc.lock();
527
528 let (insert_pos, removed) = if pos != anchor {
529 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
531 let del_dto = frontend::document_editing::DeleteTextDto {
532 position: to_i64(pos),
533 anchor: to_i64(anchor),
534 };
535 let del_result = document_editing_commands::delete_text(
536 &inner.ctx,
537 Some(inner.stack_id),
538 &del_dto,
539 )?;
540 (
541 to_usize(del_result.new_position),
542 pos.max(anchor) - pos.min(anchor),
543 )
544 } else {
545 (pos, 0)
546 };
547
548 let dto = frontend::document_editing::InsertBlockDto {
549 position: to_i64(insert_pos),
550 anchor: to_i64(insert_pos),
551 };
552 let result =
553 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
554
555 if pos != anchor {
556 undo_redo_commands::end_composite(&inner.ctx);
557 }
558
559 let edit_pos = pos.min(anchor);
560 self.finish_edit(
561 &mut inner,
562 edit_pos,
563 removed,
564 to_usize(result.new_position),
565 2,
566 )
567 };
568 crate::inner::dispatch_queued_events(queued);
569 Ok(())
570 }
571
572 pub fn insert_html(&self, html: &str) -> Result<()> {
574 let frag = DocumentFragment::from_html(html);
576 self.insert_fragment(&frag)
577 }
578
579 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
581 let frag = DocumentFragment::from_markdown(markdown);
582 self.insert_fragment(&frag)
583 }
584
585 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
587 let (pos, anchor) = self.read_cursor();
588 let queued = {
589 let mut inner = self.doc.lock();
590
591 let (insert_pos, removed) = if pos != anchor {
592 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
593 let del_dto = frontend::document_editing::DeleteTextDto {
594 position: to_i64(pos),
595 anchor: to_i64(anchor),
596 };
597 let del_result = document_editing_commands::delete_text(
598 &inner.ctx,
599 Some(inner.stack_id),
600 &del_dto,
601 )?;
602 (
603 to_usize(del_result.new_position),
604 pos.max(anchor) - pos.min(anchor),
605 )
606 } else {
607 (pos, 0)
608 };
609
610 let dto = frontend::document_editing::InsertFragmentDto {
611 position: to_i64(insert_pos),
612 anchor: to_i64(insert_pos),
613 fragment_data: fragment.raw_data().into(),
614 };
615 let result =
616 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
617
618 if pos != anchor {
619 undo_redo_commands::end_composite(&inner.ctx);
620 }
621
622 let edit_pos = pos.min(anchor);
623 self.finish_edit(
624 &mut inner,
625 edit_pos,
626 removed,
627 to_usize(result.new_position),
628 to_usize(result.blocks_added),
629 )
630 };
631 crate::inner::dispatch_queued_events(queued);
632 Ok(())
633 }
634
635 pub fn selection(&self) -> DocumentFragment {
637 let (pos, anchor) = self.read_cursor();
638
639 let (extract_pos, extract_anchor) = match self.selection_kind() {
642 SelectionKind::Cells(ref range) => match self.cell_range_positions(range) {
643 Some((start, end)) => (start, end),
644 None => return DocumentFragment::new(),
645 },
646 SelectionKind::Mixed {
647 ref cell_range,
648 text_before,
649 text_after,
650 } => {
651 let (cell_start, cell_end) = match self.cell_range_positions(cell_range) {
652 Some(p) => p,
653 None => return DocumentFragment::new(),
654 };
655 let start = if text_before {
656 pos.min(anchor)
657 } else {
658 cell_start
659 };
660 let end = if text_after {
661 pos.max(anchor)
662 } else {
663 cell_end
664 };
665 (start.min(cell_start), end.max(cell_end))
666 }
667 SelectionKind::None => return DocumentFragment::new(),
668 SelectionKind::Text => (pos, anchor),
669 };
670
671 if extract_pos == extract_anchor {
672 return DocumentFragment::new();
673 }
674
675 let inner = self.doc.lock();
676 let dto = frontend::document_inspection::ExtractFragmentDto {
677 position: to_i64(extract_pos),
678 anchor: to_i64(extract_anchor),
679 };
680 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
681 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
682 Err(_) => DocumentFragment::new(),
683 }
684 }
685
686 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
688 let (pos, anchor) = self.read_cursor();
689 let queued = {
690 let mut inner = self.doc.lock();
691
692 let (insert_pos, removed) = if pos != anchor {
693 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
694 let del_dto = frontend::document_editing::DeleteTextDto {
695 position: to_i64(pos),
696 anchor: to_i64(anchor),
697 };
698 let del_result = document_editing_commands::delete_text(
699 &inner.ctx,
700 Some(inner.stack_id),
701 &del_dto,
702 )?;
703 (
704 to_usize(del_result.new_position),
705 pos.max(anchor) - pos.min(anchor),
706 )
707 } else {
708 (pos, 0)
709 };
710
711 let dto = frontend::document_editing::InsertImageDto {
712 position: to_i64(insert_pos),
713 anchor: to_i64(insert_pos),
714 image_name: name.into(),
715 width: width as i64,
716 height: height as i64,
717 };
718 let result =
719 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
720
721 if pos != anchor {
722 undo_redo_commands::end_composite(&inner.ctx);
723 }
724
725 let edit_pos = pos.min(anchor);
726 self.finish_edit_ext(
727 &mut inner,
728 edit_pos,
729 removed,
730 to_usize(result.new_position),
731 1,
732 false,
733 )
734 };
735 crate::inner::dispatch_queued_events(queued);
736 Ok(())
737 }
738
739 pub fn insert_frame(&self) -> Result<()> {
741 let (pos, anchor) = self.read_cursor();
742 let queued = {
743 let mut inner = self.doc.lock();
744 let dto = frontend::document_editing::InsertFrameDto {
745 position: to_i64(pos),
746 anchor: to_i64(anchor),
747 };
748 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
749 inner.modified = true;
752 inner.invalidate_text_cache();
753 inner.rehighlight_affected(pos.min(anchor));
754 inner.queue_event(DocumentEvent::ContentsChanged {
755 position: pos.min(anchor),
756 chars_removed: 0,
757 chars_added: 0,
758 blocks_affected: 1,
759 });
760 inner.check_block_count_changed();
761 inner.check_flow_changed();
762 self.queue_undo_redo_event(&mut inner)
763 };
764 crate::inner::dispatch_queued_events(queued);
765 Ok(())
766 }
767
768 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
774 let (pos, anchor) = self.read_cursor();
775 let (table_id, queued) = {
776 let mut inner = self.doc.lock();
777 let dto = frontend::document_editing::InsertTableDto {
778 position: to_i64(pos),
779 anchor: to_i64(anchor),
780 rows: to_i64(rows),
781 columns: to_i64(columns),
782 };
783 let result =
784 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
785 let new_pos = to_usize(result.new_position);
786 let table_id = to_usize(result.table_id);
787 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
788 {
789 let mut d = self.data.lock();
790 d.position = new_pos;
791 d.anchor = new_pos;
792 }
793 inner.modified = true;
794 inner.invalidate_text_cache();
795 inner.rehighlight_affected(pos.min(anchor));
796 inner.queue_event(DocumentEvent::ContentsChanged {
797 position: pos.min(anchor),
798 chars_removed: 0,
799 chars_added: new_pos - pos.min(anchor),
800 blocks_affected: 1,
801 });
802 inner.check_block_count_changed();
803 inner.check_flow_changed();
804 (table_id, self.queue_undo_redo_event(&mut inner))
805 };
806 crate::inner::dispatch_queued_events(queued);
807 Ok(TextTable {
808 doc: self.doc.clone(),
809 table_id,
810 })
811 }
812
813 pub fn current_table(&self) -> Option<TextTable> {
818 self.current_table_cell().map(|c| c.table)
819 }
820
821 pub fn current_table_cell(&self) -> Option<TableCellRef> {
826 let pos = self.position();
827 let inner = self.doc.lock();
828 let dto = frontend::document_inspection::GetBlockAtPositionDto {
830 position: to_i64(pos),
831 };
832 let block_info =
833 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
834
835 let block_id = if to_i64(pos) < block_info.block_start && pos > 0 {
839 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
840 position: to_i64(pos - 1),
841 };
842 let prev_info =
843 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
844 prev_info.block_id as usize
845 } else {
846 block_info.block_id as usize
847 };
848
849 let block = crate::text_block::TextBlock {
850 doc: self.doc.clone(),
851 block_id,
852 };
853 drop(inner);
855 block.table_cell()
856 }
857
858 pub fn remove_table(&self, table_id: usize) -> Result<()> {
862 let queued = {
863 let mut inner = self.doc.lock();
864 let dto = frontend::document_editing::RemoveTableDto {
865 table_id: to_i64(table_id),
866 };
867 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
868 inner.modified = true;
869 inner.invalidate_text_cache();
870 inner.rehighlight_all();
871 inner.check_block_count_changed();
872 inner.check_flow_changed();
873 self.queue_undo_redo_event(&mut inner)
874 };
875 crate::inner::dispatch_queued_events(queued);
876 Ok(())
877 }
878
879 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
881 let queued = {
882 let mut inner = self.doc.lock();
883 let dto = frontend::document_editing::InsertTableRowDto {
884 table_id: to_i64(table_id),
885 row_index: to_i64(row_index),
886 };
887 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
888 inner.modified = true;
889 inner.invalidate_text_cache();
890 inner.rehighlight_all();
891 inner.check_block_count_changed();
892 self.queue_undo_redo_event(&mut inner)
893 };
894 crate::inner::dispatch_queued_events(queued);
895 Ok(())
896 }
897
898 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
900 let queued = {
901 let mut inner = self.doc.lock();
902 let dto = frontend::document_editing::InsertTableColumnDto {
903 table_id: to_i64(table_id),
904 column_index: to_i64(column_index),
905 };
906 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
907 inner.modified = true;
908 inner.invalidate_text_cache();
909 inner.rehighlight_all();
910 inner.check_block_count_changed();
911 self.queue_undo_redo_event(&mut inner)
912 };
913 crate::inner::dispatch_queued_events(queued);
914 Ok(())
915 }
916
917 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
919 let queued = {
920 let mut inner = self.doc.lock();
921 let dto = frontend::document_editing::RemoveTableRowDto {
922 table_id: to_i64(table_id),
923 row_index: to_i64(row_index),
924 };
925 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
926 inner.modified = true;
927 inner.invalidate_text_cache();
928 inner.rehighlight_all();
929 inner.check_block_count_changed();
930 self.queue_undo_redo_event(&mut inner)
931 };
932 crate::inner::dispatch_queued_events(queued);
933 Ok(())
934 }
935
936 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
938 let queued = {
939 let mut inner = self.doc.lock();
940 let dto = frontend::document_editing::RemoveTableColumnDto {
941 table_id: to_i64(table_id),
942 column_index: to_i64(column_index),
943 };
944 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
945 inner.modified = true;
946 inner.invalidate_text_cache();
947 inner.rehighlight_all();
948 inner.check_block_count_changed();
949 self.queue_undo_redo_event(&mut inner)
950 };
951 crate::inner::dispatch_queued_events(queued);
952 Ok(())
953 }
954
955 pub fn merge_table_cells(
957 &self,
958 table_id: usize,
959 start_row: usize,
960 start_column: usize,
961 end_row: usize,
962 end_column: usize,
963 ) -> Result<()> {
964 let queued = {
965 let mut inner = self.doc.lock();
966 let dto = frontend::document_editing::MergeTableCellsDto {
967 table_id: to_i64(table_id),
968 start_row: to_i64(start_row),
969 start_column: to_i64(start_column),
970 end_row: to_i64(end_row),
971 end_column: to_i64(end_column),
972 };
973 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
974 inner.modified = true;
975 inner.invalidate_text_cache();
976 inner.rehighlight_all();
977 inner.check_block_count_changed();
978 self.queue_undo_redo_event(&mut inner)
979 };
980 crate::inner::dispatch_queued_events(queued);
981 Ok(())
982 }
983
984 pub fn split_table_cell(
986 &self,
987 cell_id: usize,
988 split_rows: usize,
989 split_columns: usize,
990 ) -> Result<()> {
991 let queued = {
992 let mut inner = self.doc.lock();
993 let dto = frontend::document_editing::SplitTableCellDto {
994 cell_id: to_i64(cell_id),
995 split_rows: to_i64(split_rows),
996 split_columns: to_i64(split_columns),
997 };
998 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
999 inner.modified = true;
1000 inner.invalidate_text_cache();
1001 inner.rehighlight_all();
1002 inner.check_block_count_changed();
1003 self.queue_undo_redo_event(&mut inner)
1004 };
1005 crate::inner::dispatch_queued_events(queued);
1006 Ok(())
1007 }
1008
1009 pub fn set_table_format(
1013 &self,
1014 table_id: usize,
1015 format: &crate::flow::TableFormat,
1016 ) -> Result<()> {
1017 let queued = {
1018 let mut inner = self.doc.lock();
1019 let dto = format.to_set_dto(table_id);
1020 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1021 inner.modified = true;
1022 inner.queue_event(DocumentEvent::FormatChanged {
1023 position: 0,
1024 length: 0,
1025 kind: crate::flow::FormatChangeKind::Block,
1026 });
1027 self.queue_undo_redo_event(&mut inner)
1028 };
1029 crate::inner::dispatch_queued_events(queued);
1030 Ok(())
1031 }
1032
1033 pub fn set_table_cell_format(
1035 &self,
1036 cell_id: usize,
1037 format: &crate::flow::CellFormat,
1038 ) -> Result<()> {
1039 let queued = {
1040 let mut inner = self.doc.lock();
1041 let dto = format.to_set_dto(cell_id);
1042 document_formatting_commands::set_table_cell_format(
1043 &inner.ctx,
1044 Some(inner.stack_id),
1045 &dto,
1046 )?;
1047 inner.modified = true;
1048 inner.queue_event(DocumentEvent::FormatChanged {
1049 position: 0,
1050 length: 0,
1051 kind: crate::flow::FormatChangeKind::Block,
1052 });
1053 self.queue_undo_redo_event(&mut inner)
1054 };
1055 crate::inner::dispatch_queued_events(queued);
1056 Ok(())
1057 }
1058
1059 pub fn remove_current_table(&self) -> Result<()> {
1064 let table = self
1065 .current_table()
1066 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1067 self.remove_table(table.id())
1068 }
1069
1070 pub fn insert_row_above(&self) -> Result<()> {
1073 let cell_ref = self
1074 .current_table_cell()
1075 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1076 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
1077 }
1078
1079 pub fn insert_row_below(&self) -> Result<()> {
1082 let cell_ref = self
1083 .current_table_cell()
1084 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1085 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
1086 }
1087
1088 pub fn insert_column_before(&self) -> Result<()> {
1091 let cell_ref = self
1092 .current_table_cell()
1093 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1094 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
1095 }
1096
1097 pub fn insert_column_after(&self) -> Result<()> {
1100 let cell_ref = self
1101 .current_table_cell()
1102 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1103 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
1104 }
1105
1106 pub fn remove_current_row(&self) -> Result<()> {
1109 let cell_ref = self
1110 .current_table_cell()
1111 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1112 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
1113 }
1114
1115 pub fn remove_current_column(&self) -> Result<()> {
1118 let cell_ref = self
1119 .current_table_cell()
1120 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1121 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
1122 }
1123
1124 pub fn merge_selected_cells(&self) -> Result<()> {
1131 let pos_cell = self
1132 .current_table_cell()
1133 .ok_or_else(|| anyhow::anyhow!("cursor position is not inside a table"))?;
1134
1135 let (_pos, anchor) = self.read_cursor();
1137 let anchor_cell = {
1138 let inner = self.doc.lock();
1140 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1141 position: to_i64(anchor),
1142 };
1143 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1144 .map_err(|_| anyhow::anyhow!("cursor anchor is not inside a table"))?;
1145 let block = crate::text_block::TextBlock {
1146 doc: self.doc.clone(),
1147 block_id: block_info.block_id as usize,
1148 };
1149 drop(inner);
1150 block
1151 .table_cell()
1152 .ok_or_else(|| anyhow::anyhow!("cursor anchor is not inside a table"))?
1153 };
1154
1155 if pos_cell.table.id() != anchor_cell.table.id() {
1156 return Err(anyhow::anyhow!(
1157 "position and anchor are in different tables"
1158 ));
1159 }
1160
1161 let start_row = pos_cell.row.min(anchor_cell.row);
1162 let start_col = pos_cell.column.min(anchor_cell.column);
1163 let end_row = pos_cell.row.max(anchor_cell.row);
1164 let end_col = pos_cell.column.max(anchor_cell.column);
1165
1166 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
1167 }
1168
1169 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1172 let cell_ref = self
1173 .current_table_cell()
1174 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1175 let cell = cell_ref
1177 .table
1178 .cell(cell_ref.row, cell_ref.column)
1179 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1180 self.split_table_cell(cell.id(), split_rows, split_columns)
1182 }
1183
1184 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1187 let table = self
1188 .current_table()
1189 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1190 self.set_table_format(table.id(), format)
1191 }
1192
1193 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1196 let cell_ref = self
1197 .current_table_cell()
1198 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1199 let cell = cell_ref
1200 .table
1201 .cell(cell_ref.row, cell_ref.column)
1202 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1203 self.set_table_cell_format(cell.id(), format)
1204 }
1205
1206 pub fn selection_kind(&self) -> crate::flow::SelectionKind {
1214 use crate::flow::{CellRange, SelectionKind};
1215
1216 {
1218 let d = self.data.lock();
1219 if let Some(ref range) = d.cell_selection_override {
1220 return SelectionKind::Cells(range.clone());
1221 }
1222 if d.position == d.anchor {
1223 return SelectionKind::None;
1224 }
1225 }
1226
1227 let (pos, anchor) = self.read_cursor();
1228
1229 let pos_cell = self.table_cell_at(pos);
1231 let anchor_cell = self.table_cell_at(anchor);
1232
1233 match (&pos_cell, &anchor_cell) {
1234 (None, None) => {
1235 let (start, end) = (pos.min(anchor), pos.max(anchor));
1239 if let Some(t) = self.find_table_between(start, end) {
1240 let table_id = t.id();
1241 let rows = t.rows();
1242 let cols = t.columns();
1243 let range = CellRange {
1244 table_id,
1245 start_row: 0,
1246 start_col: 0,
1247 end_row: if rows > 0 { rows - 1 } else { 0 },
1248 end_col: if cols > 0 { cols - 1 } else { 0 },
1249 };
1250 let spans = self.collect_cell_spans(table_id);
1251 SelectionKind::Mixed {
1252 cell_range: range.expand_for_spans(&spans),
1253 text_before: true,
1254 text_after: true,
1255 }
1256 } else {
1257 SelectionKind::Text
1258 }
1259 }
1260 (Some(pc), Some(ac)) => {
1261 if pc.table.id() != ac.table.id() {
1262 return SelectionKind::Text;
1264 }
1265 if pc.row == ac.row && pc.column == ac.column {
1266 return SelectionKind::Text;
1268 }
1269 let range = CellRange {
1271 table_id: pc.table.id(),
1272 start_row: pc.row.min(ac.row),
1273 start_col: pc.column.min(ac.column),
1274 end_row: pc.row.max(ac.row),
1275 end_col: pc.column.max(ac.column),
1276 };
1277 let spans = self.collect_cell_spans(pc.table.id());
1278 SelectionKind::Cells(range.expand_for_spans(&spans))
1279 }
1280 (Some(tc), None) | (None, Some(tc)) => {
1281 let table_id = tc.table.id();
1285 let rows = tc.table.rows();
1286 let cols = tc.table.columns();
1287
1288 let inside_pos = if pos_cell.is_some() { pos } else { anchor };
1289 let outside_pos = if pos_cell.is_some() { anchor } else { pos };
1290
1291 let text_before = outside_pos < inside_pos;
1292 let text_after = !text_before;
1293
1294 let range = CellRange {
1295 table_id,
1296 start_row: 0,
1297 start_col: 0,
1298 end_row: if rows > 0 { rows - 1 } else { 0 },
1299 end_col: if cols > 0 { cols - 1 } else { 0 },
1300 };
1301 let spans = self.collect_cell_spans(table_id);
1302 SelectionKind::Mixed {
1303 cell_range: range.expand_for_spans(&spans),
1304 text_before,
1305 text_after,
1306 }
1307 }
1308 }
1309 }
1310
1311 pub fn is_cell_selection(&self) -> bool {
1313 matches!(
1314 self.selection_kind(),
1315 crate::flow::SelectionKind::Cells(_) | crate::flow::SelectionKind::Mixed { .. }
1316 )
1317 }
1318
1319 pub fn selected_cell_range(&self) -> Option<crate::flow::CellRange> {
1321 match self.selection_kind() {
1322 crate::flow::SelectionKind::Cells(r) => Some(r),
1323 crate::flow::SelectionKind::Mixed { cell_range, .. } => Some(cell_range),
1324 _ => None,
1325 }
1326 }
1327
1328 pub fn selected_cells(&self) -> Vec<TableCellRef> {
1330 let range = match self.selected_cell_range() {
1331 Some(r) => r,
1332 None => return Vec::new(),
1333 };
1334 let table = TextTable {
1335 doc: self.doc.clone(),
1336 table_id: range.table_id,
1337 };
1338 let mut cells = Vec::new();
1339 for row in range.start_row..=range.end_row {
1340 for col in range.start_col..=range.end_col {
1341 if table.cell(row, col).is_some() {
1342 cells.push(TableCellRef {
1343 table: table.clone(),
1344 row,
1345 column: col,
1346 });
1347 }
1348 }
1349 }
1350 cells
1351 }
1352
1353 pub fn select_table_cell(&self, table_id: usize, row: usize, col: usize) {
1357 let mut d = self.data.lock();
1358 d.cell_selection_override = Some(crate::flow::CellRange {
1359 table_id,
1360 start_row: row,
1361 start_col: col,
1362 end_row: row,
1363 end_col: col,
1364 });
1365 }
1366
1367 pub fn select_cell_range(
1369 &self,
1370 table_id: usize,
1371 start_row: usize,
1372 start_col: usize,
1373 end_row: usize,
1374 end_col: usize,
1375 ) {
1376 let range = crate::flow::CellRange {
1377 table_id,
1378 start_row,
1379 start_col,
1380 end_row,
1381 end_col,
1382 };
1383 let spans = self.collect_cell_spans(table_id);
1384 let mut d = self.data.lock();
1385 d.cell_selection_override = Some(range.expand_for_spans(&spans));
1386 }
1387
1388 pub fn clear_cell_selection(&self) {
1390 let mut d = self.data.lock();
1391 d.cell_selection_override = None;
1392 }
1393
1394 fn cell_range_positions(&self, range: &CellRange) -> Option<(usize, usize)> {
1397 let inner = self.doc.lock();
1398 let main_frame_id = get_main_frame_id(&inner);
1399 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1400 drop(inner);
1401
1402 let table = flow.into_iter().find_map(|e| match e {
1404 FlowElement::Table(t) if t.id() == range.table_id => Some(t),
1405 _ => None,
1406 })?;
1407
1408 let mut min_pos = usize::MAX;
1409 let mut max_pos = 0usize;
1410
1411 for row in range.start_row..=range.end_row {
1412 for col in range.start_col..=range.end_col {
1413 if let Some(cell) = table.cell(row, col) {
1414 for block in cell.blocks() {
1415 let bp = block.position();
1416 let bl = block.length();
1417 min_pos = min_pos.min(bp);
1418 max_pos = max_pos.max(bp + bl);
1419 }
1420 }
1421 }
1422 }
1423
1424 if min_pos == usize::MAX {
1425 return None;
1426 }
1427
1428 Some((min_pos, max_pos + 1))
1430 }
1431
1432 fn table_cell_at(&self, position: usize) -> Option<TableCellRef> {
1436 let inner = self.doc.lock();
1437 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1438 position: to_i64(position),
1439 };
1440 let block_info =
1441 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1442
1443 let block_id = if to_i64(position) < block_info.block_start && position > 0 {
1444 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1445 position: to_i64(position - 1),
1446 };
1447 let prev_info =
1448 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
1449 prev_info.block_id as usize
1450 } else {
1451 block_info.block_id as usize
1452 };
1453
1454 let block = crate::text_block::TextBlock {
1455 doc: self.doc.clone(),
1456 block_id,
1457 };
1458 drop(inner);
1459 block.table_cell()
1460 }
1461
1462 fn table_boundary_position(&self, table_id: usize, before: bool) -> Option<usize> {
1473 let inner = self.doc.lock();
1474 let main_frame_id = get_main_frame_id(&inner);
1475 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1476 drop(inner);
1477
1478 let idx = flow
1480 .iter()
1481 .position(|e| matches!(e, FlowElement::Table(t) if t.id() == table_id))?;
1482
1483 if before {
1484 for i in (0..idx).rev() {
1486 if let FlowElement::Block(b) = &flow[i] {
1487 return Some(b.position() + b.length());
1488 }
1489 }
1490 } else {
1491 for item in flow.iter().skip(idx + 1) {
1493 if let FlowElement::Block(b) = item {
1494 return Some(b.position());
1495 }
1496 }
1497 }
1498 None
1499 }
1500
1501 fn find_table_between(&self, start: usize, end: usize) -> Option<TextTable> {
1503 let inner = self.doc.lock();
1504 let main_frame_id = get_main_frame_id(&inner);
1505 let flow = crate::text_frame::build_flow_elements(&inner, &self.doc, main_frame_id);
1506 drop(inner);
1507
1508 for elem in flow {
1509 if let FlowElement::Table(t) = elem {
1510 if let Some(first_cell) = t.cell(0, 0) {
1513 let blocks = first_cell.blocks();
1514 if let Some(fb) = blocks.first() {
1515 let p = fb.position();
1516 if p > start && p < end {
1517 return Some(t);
1518 }
1519 }
1520 }
1521 }
1522 }
1523 None
1524 }
1525
1526 fn collect_cell_spans(&self, table_id: usize) -> Vec<(usize, usize, usize, usize)> {
1528 let inner = self.doc.lock();
1529 let table_dto =
1530 match frontend::commands::table_commands::get_table(&inner.ctx, &(table_id as u64))
1531 .ok()
1532 .flatten()
1533 {
1534 Some(t) => t,
1535 None => return Vec::new(),
1536 };
1537
1538 let mut spans = Vec::with_capacity(table_dto.cells.len());
1539 for &cell_id in &table_dto.cells {
1540 if let Some(cell) =
1541 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &cell_id)
1542 .ok()
1543 .flatten()
1544 {
1545 spans.push((
1546 cell.row as usize,
1547 cell.column as usize,
1548 cell.row_span.max(1) as usize,
1549 cell.column_span.max(1) as usize,
1550 ));
1551 }
1552 }
1553 spans
1554 }
1555
1556 pub fn delete_char(&self) -> Result<()> {
1558 let (pos, anchor) = self.read_cursor();
1559 let (del_pos, del_anchor) = if pos != anchor {
1560 (pos, anchor)
1561 } else {
1562 let end = {
1564 let inner = self.doc.lock();
1565 document_inspection_commands::get_document_stats(&inner.ctx)
1566 .map(|s| max_cursor_position(&s))
1567 .unwrap_or(0)
1568 };
1569 if pos >= end {
1570 return Ok(());
1571 }
1572 let to = self.next_grapheme_boundary(pos);
1576 if to == pos {
1577 return Ok(());
1578 }
1579 (pos, to)
1580 };
1581 self.do_delete(del_pos, del_anchor)
1582 }
1583
1584 pub fn delete_previous_char(&self) -> Result<()> {
1586 let (pos, anchor) = self.read_cursor();
1587 let (del_pos, del_anchor) = if pos != anchor {
1588 (pos, anchor)
1589 } else if pos > 0 {
1590 let from = self.prev_grapheme_boundary(pos);
1591 if from == pos {
1592 return Ok(());
1593 }
1594 (from, pos)
1595 } else {
1596 return Ok(());
1597 };
1598 self.do_delete(del_pos, del_anchor)
1599 }
1600
1601 pub fn remove_selected_text(&self) -> Result<String> {
1603 let (pos, anchor) = self.read_cursor();
1604 if pos == anchor {
1605 return Ok(String::new());
1606 }
1607 let queued = {
1608 let mut inner = self.doc.lock();
1609 let dto = frontend::document_editing::DeleteTextDto {
1610 position: to_i64(pos),
1611 anchor: to_i64(anchor),
1612 };
1613 let result =
1614 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1615 let edit_pos = pos.min(anchor);
1616 let removed = pos.max(anchor) - edit_pos;
1617 let new_pos = to_usize(result.new_position);
1618 inner.adjust_cursors(edit_pos, removed, 0);
1619 {
1620 let mut d = self.data.lock();
1621 d.position = new_pos;
1622 d.anchor = new_pos;
1623 }
1624 inner.modified = true;
1625 inner.invalidate_text_cache();
1626 inner.rehighlight_affected(edit_pos);
1627 inner.queue_event(DocumentEvent::ContentsChanged {
1628 position: edit_pos,
1629 chars_removed: removed,
1630 chars_added: 0,
1631 blocks_affected: 1,
1632 });
1633 inner.check_block_count_changed();
1634 inner.check_flow_changed();
1635 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1637 };
1638 crate::inner::dispatch_queued_events(queued.1);
1639 Ok(queued.0)
1640 }
1641
1642 pub fn current_list(&self) -> Option<crate::TextList> {
1647 let pos = self.position();
1648 let inner = self.doc.lock();
1649 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1650 position: to_i64(pos),
1651 };
1652 let block_info =
1653 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1654 let block = crate::text_block::TextBlock {
1655 doc: self.doc.clone(),
1656 block_id: block_info.block_id as usize,
1657 };
1658 drop(inner);
1659 block.list()
1660 }
1661
1662 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1664 let (pos, anchor) = self.read_cursor();
1665 let queued = {
1666 let mut inner = self.doc.lock();
1667 let dto = frontend::document_editing::CreateListDto {
1668 position: to_i64(pos),
1669 anchor: to_i64(anchor),
1670 style: style.clone(),
1671 };
1672 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1673 inner.modified = true;
1674 inner.rehighlight_affected(pos.min(anchor));
1675 inner.queue_event(DocumentEvent::ContentsChanged {
1676 position: pos.min(anchor),
1677 chars_removed: 0,
1678 chars_added: 0,
1679 blocks_affected: 1,
1680 });
1681 self.queue_undo_redo_event(&mut inner)
1682 };
1683 crate::inner::dispatch_queued_events(queued);
1684 Ok(())
1685 }
1686
1687 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
1689 let (pos, anchor) = self.read_cursor();
1690 let queued = {
1691 let mut inner = self.doc.lock();
1692 let dto = frontend::document_editing::InsertListDto {
1693 position: to_i64(pos),
1694 anchor: to_i64(anchor),
1695 style: style.clone(),
1696 };
1697 let result =
1698 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1699 let edit_pos = pos.min(anchor);
1700 let removed = pos.max(anchor) - edit_pos;
1701 self.finish_edit_ext(
1702 &mut inner,
1703 edit_pos,
1704 removed,
1705 to_usize(result.new_position),
1706 1,
1707 false,
1708 )
1709 };
1710 crate::inner::dispatch_queued_events(queued);
1711 Ok(())
1712 }
1713
1714 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
1716 let queued = {
1717 let mut inner = self.doc.lock();
1718 let dto = format.to_set_dto(list_id);
1719 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1720 inner.modified = true;
1721 inner.queue_event(DocumentEvent::FormatChanged {
1722 position: 0,
1723 length: 0,
1724 kind: crate::flow::FormatChangeKind::List,
1725 });
1726 self.queue_undo_redo_event(&mut inner)
1727 };
1728 crate::inner::dispatch_queued_events(queued);
1729 Ok(())
1730 }
1731
1732 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
1735 let list = self
1736 .current_list()
1737 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a list"))?;
1738 self.set_list_format(list.id(), format)
1739 }
1740
1741 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
1743 let queued = {
1744 let mut inner = self.doc.lock();
1745 let dto = frontend::document_editing::AddBlockToListDto {
1746 block_id: to_i64(block_id),
1747 list_id: to_i64(list_id),
1748 };
1749 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1750 inner.modified = true;
1751 inner.queue_event(DocumentEvent::FormatChanged {
1758 position: 0,
1759 length: 0,
1760 kind: crate::flow::FormatChangeKind::List,
1761 });
1762 self.queue_undo_redo_event(&mut inner)
1763 };
1764 crate::inner::dispatch_queued_events(queued);
1765 Ok(())
1766 }
1767
1768 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
1770 let pos = self.position();
1771 let inner = self.doc.lock();
1772 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1773 position: to_i64(pos),
1774 };
1775 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1776 drop(inner);
1777 self.add_block_to_list(block_info.block_id as usize, list_id)
1778 }
1779
1780 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
1782 let queued = {
1783 let mut inner = self.doc.lock();
1784 let dto = frontend::document_editing::RemoveBlockFromListDto {
1785 block_id: to_i64(block_id),
1786 };
1787 document_editing_commands::remove_block_from_list(
1788 &inner.ctx,
1789 Some(inner.stack_id),
1790 &dto,
1791 )?;
1792 inner.modified = true;
1793 inner.queue_event(DocumentEvent::FormatChanged {
1796 position: 0,
1797 length: 0,
1798 kind: crate::flow::FormatChangeKind::List,
1799 });
1800 self.queue_undo_redo_event(&mut inner)
1801 };
1802 crate::inner::dispatch_queued_events(queued);
1803 Ok(())
1804 }
1805
1806 pub fn remove_current_block_from_list(&self) -> Result<()> {
1809 let pos = self.position();
1810 let inner = self.doc.lock();
1811 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1812 position: to_i64(pos),
1813 };
1814 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1815 drop(inner);
1816 self.remove_block_from_list(block_info.block_id as usize)
1817 }
1818
1819 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
1822 let list = crate::text_list::TextList {
1823 doc: self.doc.clone(),
1824 list_id,
1825 };
1826 let block = list
1827 .item(index)
1828 .ok_or_else(|| anyhow::anyhow!("list item index {index} out of range"))?;
1829 self.remove_block_from_list(block.id())
1830 }
1831
1832 pub fn char_format(&self) -> Result<TextFormat> {
1837 let pos = self.position();
1838 let inner = self.doc.lock();
1839
1840 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1842 position: to_i64(pos),
1843 };
1844 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1845 let block_id = block_info.block_id as u64;
1846 let mut block_dto = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
1847 .ok_or_else(|| anyhow::anyhow!("block not found at position"))?;
1848 let store = inner.ctx.db_context.get_store();
1849 crate::inner::refresh_block_position(&mut block_dto, store);
1850
1851 let local_char = pos.saturating_sub(block_dto.document_position as usize);
1854 let entity: common::entities::Block = block_dto.clone().into();
1855 let plain_owned = common::database::rope_helpers::block_content_via_store(&entity, store);
1856 let plain: &str = &plain_owned;
1857 let byte_offset: u32 = plain
1858 .char_indices()
1859 .nth(local_char)
1860 .map(|(b, _)| b as u32)
1861 .unwrap_or(plain.len() as u32);
1862
1863 let images = store
1866 .block_images
1867 .read()
1868 .unwrap()
1869 .get(&block_id)
1870 .cloned()
1871 .unwrap_or_default();
1872 if let Some(img) = images.iter().find(|i| i.byte_offset == byte_offset) {
1873 return Ok(TextFormat::from(&img.format));
1874 }
1875
1876 let runs = store
1878 .format_runs
1879 .read()
1880 .unwrap()
1881 .get(&block_id)
1882 .cloned()
1883 .unwrap_or_default();
1884 let fmt = runs
1885 .iter()
1886 .find(|r| r.byte_start <= byte_offset && byte_offset < r.byte_end)
1887 .map(|r| TextFormat::from(&r.format))
1888 .unwrap_or_default();
1889 Ok(fmt)
1890 }
1891
1892 pub fn block_format(&self) -> Result<BlockFormat> {
1894 let pos = self.position();
1895 let inner = self.doc.lock();
1896 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1897 position: to_i64(pos),
1898 };
1899 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1900 let block_id = block_info.block_id as u64;
1901 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
1902 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
1903 Ok(BlockFormat::from(&block))
1904 }
1905
1906 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
1910 let (pos, anchor) = self.read_cursor();
1911 let queued = {
1912 let mut inner = self.doc.lock();
1913 let dto = format.to_set_dto(pos, anchor);
1914 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1915 let start = pos.min(anchor);
1916 let length = pos.max(anchor) - start;
1917 inner.modified = true;
1918 inner.queue_event(DocumentEvent::FormatChanged {
1919 position: start,
1920 length,
1921 kind: crate::flow::FormatChangeKind::Character,
1922 });
1923 self.queue_undo_redo_event(&mut inner)
1924 };
1925 crate::inner::dispatch_queued_events(queued);
1926 Ok(())
1927 }
1928
1929 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
1931 let (pos, anchor) = self.read_cursor();
1932 let queued = {
1933 let mut inner = self.doc.lock();
1934 let dto = format.to_merge_dto(pos, anchor);
1935 document_formatting_commands::merge_text_format(
1936 &inner.ctx,
1937 Some(inner.stack_id),
1938 &dto,
1939 )?;
1940 let start = pos.min(anchor);
1941 let length = pos.max(anchor) - start;
1942 inner.modified = true;
1943 inner.queue_event(DocumentEvent::FormatChanged {
1944 position: start,
1945 length,
1946 kind: crate::flow::FormatChangeKind::Character,
1947 });
1948 self.queue_undo_redo_event(&mut inner)
1949 };
1950 crate::inner::dispatch_queued_events(queued);
1951 Ok(())
1952 }
1953
1954 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
1956 let (pos, anchor) = self.read_cursor();
1957 let queued = {
1958 let mut inner = self.doc.lock();
1959 let dto = format.to_set_dto(pos, anchor);
1960 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1961 let start = pos.min(anchor);
1962 let length = pos.max(anchor) - start;
1963 inner.modified = true;
1964 inner.queue_event(DocumentEvent::FormatChanged {
1965 position: start,
1966 length,
1967 kind: crate::flow::FormatChangeKind::Block,
1968 });
1969 self.queue_undo_redo_event(&mut inner)
1970 };
1971 crate::inner::dispatch_queued_events(queued);
1972 Ok(())
1973 }
1974
1975 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
1977 let (pos, anchor) = self.read_cursor();
1978 let queued = {
1979 let mut inner = self.doc.lock();
1980 let dto = format.to_set_dto(pos, anchor, frame_id);
1981 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1982 let start = pos.min(anchor);
1983 let length = pos.max(anchor) - start;
1984 inner.modified = true;
1985 inner.queue_event(DocumentEvent::FormatChanged {
1986 position: start,
1987 length,
1988 kind: crate::flow::FormatChangeKind::Block,
1989 });
1990 self.queue_undo_redo_event(&mut inner)
1991 };
1992 crate::inner::dispatch_queued_events(queued);
1993 Ok(())
1994 }
1995
1996 pub fn begin_edit_block(&self) {
2000 let inner = self.doc.lock();
2001 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
2002 }
2003
2004 pub fn end_edit_block(&self) {
2006 let inner = self.doc.lock();
2007 undo_redo_commands::end_composite(&inner.ctx);
2008 }
2009
2010 pub fn join_previous_edit_block(&self) {
2017 self.begin_edit_block();
2018 }
2019
2020 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
2024 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
2025 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
2026 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
2027 inner.take_queued_events()
2028 }
2029
2030 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
2031 let queued = {
2032 let mut inner = self.doc.lock();
2033 let dto = frontend::document_editing::DeleteTextDto {
2034 position: to_i64(pos),
2035 anchor: to_i64(anchor),
2036 };
2037 let result =
2038 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
2039 let edit_pos = pos.min(anchor);
2040 let removed = pos.max(anchor) - edit_pos;
2041 let new_pos = to_usize(result.new_position);
2042 inner.adjust_cursors(edit_pos, removed, 0);
2043 {
2044 let mut d = self.data.lock();
2045 d.position = new_pos;
2046 d.anchor = new_pos;
2047 }
2048 inner.modified = true;
2049 inner.invalidate_text_cache();
2050 inner.rehighlight_affected(edit_pos);
2051 inner.queue_event(DocumentEvent::ContentsChanged {
2052 position: edit_pos,
2053 chars_removed: removed,
2054 chars_added: 0,
2055 blocks_affected: 1,
2056 });
2057 inner.check_block_count_changed();
2058 inner.check_flow_changed();
2059 self.queue_undo_redo_event(&mut inner)
2060 };
2061 crate::inner::dispatch_queued_events(queued);
2062 Ok(())
2063 }
2064
2065 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
2067 let pos = self.position();
2068 match op {
2069 MoveOperation::NoMove => pos,
2070 MoveOperation::Start => 0,
2071 MoveOperation::End => {
2072 let inner = self.doc.lock();
2073 document_inspection_commands::get_document_stats(&inner.ctx)
2074 .map(|s| max_cursor_position(&s))
2075 .unwrap_or(pos)
2076 }
2077 MoveOperation::NextCharacter | MoveOperation::Right => {
2078 let mut cur = pos;
2079 for _ in 0..n {
2080 let next = self.next_grapheme_boundary(cur);
2081 if next == cur {
2082 break;
2083 }
2084 cur = next;
2085 }
2086 cur
2087 }
2088 MoveOperation::PreviousCharacter | MoveOperation::Left => {
2089 let mut cur = pos;
2090 for _ in 0..n {
2091 let prev = self.prev_grapheme_boundary(cur);
2092 if prev == cur {
2093 break;
2094 }
2095 cur = prev;
2096 }
2097 cur
2098 }
2099 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
2100 let inner = self.doc.lock();
2101 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2102 position: to_i64(pos),
2103 };
2104 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2105 .map(|info| to_usize(info.block_start))
2106 .unwrap_or(pos)
2107 }
2108 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
2109 let inner = self.doc.lock();
2110 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2111 position: to_i64(pos),
2112 };
2113 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2114 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
2115 .unwrap_or(pos)
2116 }
2117 MoveOperation::NextBlock => {
2118 let inner = self.doc.lock();
2119 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2120 position: to_i64(pos),
2121 };
2122 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2123 .map(|info| {
2124 to_usize(info.block_start) + to_usize(info.block_length) + 1
2126 })
2127 .unwrap_or(pos)
2128 }
2129 MoveOperation::PreviousBlock => {
2130 let inner = self.doc.lock();
2131 let dto = frontend::document_inspection::GetBlockAtPositionDto {
2132 position: to_i64(pos),
2133 };
2134 let block_start =
2135 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
2136 .map(|info| to_usize(info.block_start))
2137 .unwrap_or(pos);
2138 if block_start >= 2 {
2139 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
2141 position: to_i64(block_start - 2),
2142 };
2143 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
2144 .map(|info| to_usize(info.block_start))
2145 .unwrap_or(0)
2146 } else {
2147 0
2148 }
2149 }
2150 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
2151 let (_, end) = self.find_word_boundaries(pos);
2152 if end == pos {
2154 let inner = self.doc.lock();
2156 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
2157 .map(|s| max_cursor_position(&s))
2158 .unwrap_or(0);
2159 let scan_len = max_pos.saturating_sub(pos).min(64);
2160 if scan_len == 0 {
2161 return pos;
2162 }
2163 let dto = frontend::document_inspection::GetTextAtPositionDto {
2164 position: to_i64(pos),
2165 length: to_i64(scan_len),
2166 };
2167 if let Ok(r) =
2168 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
2169 {
2170 for (i, ch) in r.text.chars().enumerate() {
2171 if ch.is_alphanumeric() || ch == '_' {
2172 let word_pos = pos + i;
2174 drop(inner);
2175 let (_, word_end) = self.find_word_boundaries(word_pos);
2176 return word_end;
2177 }
2178 }
2179 }
2180 pos + scan_len
2181 } else {
2182 end
2183 }
2184 }
2185 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
2186 let (start, _) = self.find_word_boundaries(pos);
2187 if start < pos {
2188 start
2189 } else if pos > 0 {
2190 let mut search = pos - 1;
2193 loop {
2194 let (ws, we) = self.find_word_boundaries(search);
2195 if ws < we {
2196 break ws;
2198 }
2199 if search == 0 {
2201 break 0;
2202 }
2203 search -= 1;
2204 }
2205 } else {
2206 0
2207 }
2208 }
2209 MoveOperation::Up | MoveOperation::Down => {
2210 if matches!(op, MoveOperation::Up) {
2213 self.resolve_move(MoveOperation::PreviousBlock, 1)
2214 } else {
2215 self.resolve_move(MoveOperation::NextBlock, 1)
2216 }
2217 }
2218 }
2219 }
2220
2221 pub(crate) fn snap_position_to_grapheme_boundary(&self) {
2232 let pos = {
2233 let data = self.data.lock();
2234 data.position
2235 };
2236 let snapped = self.forward_grapheme_boundary_at_or_after(pos);
2237 if snapped != pos {
2238 let mut data = self.data.lock();
2239 data.position = snapped;
2240 if data.anchor == pos {
2241 data.anchor = snapped;
2242 }
2243 }
2244 }
2245
2246 fn forward_grapheme_boundary_at_or_after(&self, pos: usize) -> usize {
2256 let inner = self.doc.lock();
2257 let end = document_inspection_commands::get_document_stats(&inner.ctx)
2258 .map(|s| max_cursor_position(&s))
2259 .unwrap_or(pos);
2260 if pos >= end {
2261 return pos;
2262 }
2263 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2264 position: to_i64(pos),
2265 };
2266 let Ok(block_info) =
2267 document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto)
2268 else {
2269 return pos;
2270 };
2271 let block_start = to_usize(block_info.block_start);
2272 let block_length = to_usize(block_info.block_length);
2273 let offset_in_block = pos.saturating_sub(block_start);
2274 if offset_in_block == 0 || offset_in_block >= block_length {
2276 return pos;
2277 }
2278 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2279 position: to_i64(block_start),
2280 length: to_i64(block_length),
2281 };
2282 let Ok(r) = document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto)
2283 else {
2284 return pos;
2285 };
2286 let text = r.text;
2287 drop(inner);
2288 let mut acc = 0usize;
2291 for g in text.graphemes(true) {
2292 if acc >= offset_in_block {
2293 return block_start + acc;
2294 }
2295 acc += g.chars().count();
2296 }
2297 block_start + acc
2298 }
2299
2300 fn next_grapheme_boundary(&self, pos: usize) -> usize {
2313 let inner = self.doc.lock();
2314 let end = document_inspection_commands::get_document_stats(&inner.ctx)
2315 .map(|s| max_cursor_position(&s))
2316 .unwrap_or(pos);
2317 if pos >= end {
2318 return pos;
2319 }
2320 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2321 position: to_i64(pos),
2322 };
2323 let block_info =
2324 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2325 Ok(info) => info,
2326 Err(_) => return pos + 1,
2327 };
2328 let block_start = to_usize(block_info.block_start);
2329 let block_length = to_usize(block_info.block_length);
2330 let offset_in_block = pos.saturating_sub(block_start);
2331 if offset_in_block >= block_length {
2332 return (pos + 1).min(end);
2335 }
2336 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2337 position: to_i64(pos),
2338 length: to_i64(block_length - offset_in_block),
2339 };
2340 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
2341 Ok(r) => r.text,
2342 Err(_) => return pos + 1,
2343 };
2344 drop(inner);
2345 match text.graphemes(true).next() {
2346 Some(g) if !g.is_empty() => (pos + g.chars().count()).min(end),
2347 _ => (pos + 1).min(end),
2348 }
2349 }
2350
2351 fn prev_grapheme_boundary(&self, pos: usize) -> usize {
2355 if pos == 0 {
2356 return 0;
2357 }
2358 let inner = self.doc.lock();
2359 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2360 position: to_i64(pos.saturating_sub(1)),
2361 };
2362 let block_info =
2363 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2364 Ok(info) => info,
2365 Err(_) => return pos - 1,
2366 };
2367 let block_start = to_usize(block_info.block_start);
2368 let block_length = to_usize(block_info.block_length);
2369 let block_end = block_start + block_length;
2370 if pos > block_end {
2374 return pos - 1;
2375 }
2376 if block_length == 0 || pos <= block_start {
2377 return pos.saturating_sub(1);
2378 }
2379 let scan_len = pos - block_start;
2380 let text_dto = frontend::document_inspection::GetTextAtPositionDto {
2381 position: to_i64(block_start),
2382 length: to_i64(scan_len),
2383 };
2384 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &text_dto) {
2385 Ok(r) => r.text,
2386 Err(_) => return pos - 1,
2387 };
2388 drop(inner);
2389 match text.graphemes(true).next_back() {
2390 Some(g) if !g.is_empty() => pos - g.chars().count(),
2391 _ => pos - 1,
2392 }
2393 }
2394
2395 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
2401 let inner = self.doc.lock();
2402 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
2404 position: to_i64(pos),
2405 };
2406 let block_info =
2407 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
2408 Ok(info) => info,
2409 Err(_) => return (pos, pos),
2410 };
2411
2412 let block_start = to_usize(block_info.block_start);
2413 let block_length = to_usize(block_info.block_length);
2414 if block_length == 0 {
2415 return (pos, pos);
2416 }
2417
2418 let dto = frontend::document_inspection::GetTextAtPositionDto {
2419 position: to_i64(block_start),
2420 length: to_i64(block_length),
2421 };
2422 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
2423 Ok(r) => r.text,
2424 Err(_) => return (pos, pos),
2425 };
2426
2427 let cursor_offset = pos.saturating_sub(block_start);
2429
2430 let mut last_char_start = 0;
2432 let mut last_char_end = 0;
2433
2434 for (word_byte_start, word) in text.unicode_word_indices() {
2435 let word_char_start = text[..word_byte_start].chars().count();
2437 let word_char_len = word.chars().count();
2438 let word_char_end = word_char_start + word_char_len;
2439
2440 last_char_start = word_char_start;
2441 last_char_end = word_char_end;
2442
2443 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
2444 return (block_start + word_char_start, block_start + word_char_end);
2445 }
2446 }
2447
2448 if cursor_offset == last_char_end && last_char_start < last_char_end {
2450 return (block_start + last_char_start, block_start + last_char_end);
2451 }
2452
2453 (pos, pos)
2454 }
2455}