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 inline_element_commands, undo_redo_commands,
13};
14
15use unicode_segmentation::UnicodeSegmentation;
16
17use crate::convert::{to_i64, to_usize};
18use crate::events::DocumentEvent;
19use crate::flow::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
25fn max_cursor_position(stats: &frontend::document_inspection::DocumentStatsDto) -> usize {
31 let chars = to_usize(stats.character_count);
32 let blocks = to_usize(stats.block_count);
33 if blocks > 1 {
34 chars + blocks - 1
35 } else {
36 chars
37 }
38}
39
40pub struct TextCursor {
48 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
49 pub(crate) data: Arc<Mutex<CursorData>>,
50}
51
52impl Clone for TextCursor {
53 fn clone(&self) -> Self {
54 let (position, anchor) = {
55 let d = self.data.lock();
56 (d.position, d.anchor)
57 };
58 let data = {
59 let mut inner = self.doc.lock();
60 let data = Arc::new(Mutex::new(CursorData { position, anchor }));
61 inner.cursors.push(Arc::downgrade(&data));
62 data
63 };
64 TextCursor {
65 doc: self.doc.clone(),
66 data,
67 }
68 }
69}
70
71impl TextCursor {
72 fn read_cursor(&self) -> (usize, usize) {
75 let d = self.data.lock();
76 (d.position, d.anchor)
77 }
78
79 fn finish_edit(
83 &self,
84 inner: &mut TextDocumentInner,
85 edit_pos: usize,
86 removed: usize,
87 new_pos: usize,
88 blocks_affected: usize,
89 ) -> QueuedEvents {
90 self.finish_edit_ext(inner, edit_pos, removed, new_pos, blocks_affected, true)
91 }
92
93 fn finish_edit_ext(
94 &self,
95 inner: &mut TextDocumentInner,
96 edit_pos: usize,
97 removed: usize,
98 new_pos: usize,
99 blocks_affected: usize,
100 flow_may_change: bool,
101 ) -> QueuedEvents {
102 let added = new_pos - edit_pos;
103 inner.adjust_cursors(edit_pos, removed, added);
104 {
105 let mut d = self.data.lock();
106 d.position = new_pos;
107 d.anchor = new_pos;
108 }
109 inner.modified = true;
110 inner.invalidate_text_cache();
111 inner.rehighlight_affected(edit_pos);
112 inner.queue_event(DocumentEvent::ContentsChanged {
113 position: edit_pos,
114 chars_removed: removed,
115 chars_added: added,
116 blocks_affected,
117 });
118 inner.check_block_count_changed();
119 if flow_may_change {
120 inner.check_flow_changed();
121 }
122 self.queue_undo_redo_event(inner)
123 }
124
125 pub fn position(&self) -> usize {
129 self.data.lock().position
130 }
131
132 pub fn anchor(&self) -> usize {
134 self.data.lock().anchor
135 }
136
137 pub fn has_selection(&self) -> bool {
139 let d = self.data.lock();
140 d.position != d.anchor
141 }
142
143 pub fn selection_start(&self) -> usize {
145 let d = self.data.lock();
146 d.position.min(d.anchor)
147 }
148
149 pub fn selection_end(&self) -> usize {
151 let d = self.data.lock();
152 d.position.max(d.anchor)
153 }
154
155 pub fn selected_text(&self) -> Result<String> {
157 let (pos, anchor) = self.read_cursor();
158 if pos == anchor {
159 return Ok(String::new());
160 }
161 let start = pos.min(anchor);
162 let len = pos.max(anchor) - start;
163 let inner = self.doc.lock();
164 let dto = frontend::document_inspection::GetTextAtPositionDto {
165 position: to_i64(start),
166 length: to_i64(len),
167 };
168 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
169 Ok(result.text)
170 }
171
172 pub fn clear_selection(&self) {
174 let mut d = self.data.lock();
175 d.anchor = d.position;
176 }
177
178 pub fn at_block_start(&self) -> bool {
182 let pos = self.position();
183 let inner = self.doc.lock();
184 let dto = frontend::document_inspection::GetBlockAtPositionDto {
185 position: to_i64(pos),
186 };
187 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
188 pos == to_usize(info.block_start)
189 } else {
190 false
191 }
192 }
193
194 pub fn at_block_end(&self) -> bool {
196 let pos = self.position();
197 let inner = self.doc.lock();
198 let dto = frontend::document_inspection::GetBlockAtPositionDto {
199 position: to_i64(pos),
200 };
201 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
202 pos == to_usize(info.block_start) + to_usize(info.block_length)
203 } else {
204 false
205 }
206 }
207
208 pub fn at_start(&self) -> bool {
210 self.data.lock().position == 0
211 }
212
213 pub fn at_end(&self) -> bool {
215 let pos = self.position();
216 let inner = self.doc.lock();
217 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
218 frontend::document_inspection::DocumentStatsDto {
219 character_count: 0,
220 word_count: 0,
221 block_count: 0,
222 frame_count: 0,
223 image_count: 0,
224 list_count: 0,
225 table_count: 0,
226 }
227 });
228 pos >= max_cursor_position(&stats)
229 }
230
231 pub fn block_number(&self) -> usize {
233 let pos = self.position();
234 let inner = self.doc.lock();
235 let dto = frontend::document_inspection::GetBlockAtPositionDto {
236 position: to_i64(pos),
237 };
238 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
239 .map(|info| to_usize(info.block_number))
240 .unwrap_or(0)
241 }
242
243 pub fn position_in_block(&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| pos.saturating_sub(to_usize(info.block_start)))
252 .unwrap_or(0)
253 }
254
255 pub fn set_position(&self, position: usize, mode: MoveMode) {
259 let end = {
261 let inner = self.doc.lock();
262 document_inspection_commands::get_document_stats(&inner.ctx)
263 .map(|s| max_cursor_position(&s))
264 .unwrap_or(0)
265 };
266 let pos = position.min(end);
267 let mut d = self.data.lock();
268 d.position = pos;
269 if mode == MoveMode::MoveAnchor {
270 d.anchor = pos;
271 }
272 }
273
274 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
280 let old_pos = self.position();
281 let target = self.resolve_move(operation, n);
282 self.set_position(target, mode);
283 self.position() != old_pos
284 }
285
286 pub fn select(&self, selection: SelectionType) {
288 match selection {
289 SelectionType::Document => {
290 let end = {
291 let inner = self.doc.lock();
292 document_inspection_commands::get_document_stats(&inner.ctx)
293 .map(|s| max_cursor_position(&s))
294 .unwrap_or(0)
295 };
296 let mut d = self.data.lock();
297 d.anchor = 0;
298 d.position = end;
299 }
300 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
301 let pos = self.position();
302 let inner = self.doc.lock();
303 let dto = frontend::document_inspection::GetBlockAtPositionDto {
304 position: to_i64(pos),
305 };
306 if let Ok(info) =
307 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
308 {
309 let start = to_usize(info.block_start);
310 let end = start + to_usize(info.block_length);
311 drop(inner);
312 let mut d = self.data.lock();
313 d.anchor = start;
314 d.position = end;
315 }
316 }
317 SelectionType::WordUnderCursor => {
318 let pos = self.position();
319 let (word_start, word_end) = self.find_word_boundaries(pos);
320 let mut d = self.data.lock();
321 d.anchor = word_start;
322 d.position = word_end;
323 }
324 }
325 }
326
327 pub fn insert_text(&self, text: &str) -> Result<()> {
331 let (pos, anchor) = self.read_cursor();
332
333 let dto = frontend::document_editing::InsertTextDto {
335 position: to_i64(pos),
336 anchor: to_i64(anchor),
337 text: text.into(),
338 };
339
340 let queued = {
341 let mut inner = self.doc.lock();
342 let result = match document_editing_commands::insert_text(
343 &inner.ctx,
344 Some(inner.stack_id),
345 &dto,
346 ) {
347 Ok(r) => r,
348 Err(_) if pos != anchor => {
349 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
351
352 let del_dto = frontend::document_editing::DeleteTextDto {
353 position: to_i64(pos),
354 anchor: to_i64(anchor),
355 };
356 let del_result = document_editing_commands::delete_text(
357 &inner.ctx,
358 Some(inner.stack_id),
359 &del_dto,
360 )?;
361 let del_pos = to_usize(del_result.new_position);
362
363 let ins_dto = frontend::document_editing::InsertTextDto {
364 position: to_i64(del_pos),
365 anchor: to_i64(del_pos),
366 text: text.into(),
367 };
368 let ins_result = document_editing_commands::insert_text(
369 &inner.ctx,
370 Some(inner.stack_id),
371 &ins_dto,
372 )?;
373
374 undo_redo_commands::end_composite(&inner.ctx);
375 ins_result
376 }
377 Err(e) => return Err(e),
378 };
379
380 let edit_pos = pos.min(anchor);
381 let removed = pos.max(anchor) - edit_pos;
382 self.finish_edit_ext(
383 &mut inner,
384 edit_pos,
385 removed,
386 to_usize(result.new_position),
387 to_usize(result.blocks_affected),
388 false,
389 )
390 };
391 crate::inner::dispatch_queued_events(queued);
392 Ok(())
393 }
394
395 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
397 let (pos, anchor) = self.read_cursor();
398 let queued = {
399 let mut inner = self.doc.lock();
400 let dto = frontend::document_editing::InsertFormattedTextDto {
401 position: to_i64(pos),
402 anchor: to_i64(anchor),
403 text: text.into(),
404 font_family: format.font_family.clone().unwrap_or_default(),
405 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
406 font_bold: format.font_bold.unwrap_or(false),
407 font_italic: format.font_italic.unwrap_or(false),
408 font_underline: format.font_underline.unwrap_or(false),
409 font_strikeout: format.font_strikeout.unwrap_or(false),
410 };
411 let result = document_editing_commands::insert_formatted_text(
412 &inner.ctx,
413 Some(inner.stack_id),
414 &dto,
415 )?;
416 let edit_pos = pos.min(anchor);
417 let removed = pos.max(anchor) - edit_pos;
418 self.finish_edit_ext(
419 &mut inner,
420 edit_pos,
421 removed,
422 to_usize(result.new_position),
423 1,
424 false,
425 )
426 };
427 crate::inner::dispatch_queued_events(queued);
428 Ok(())
429 }
430
431 pub fn insert_block(&self) -> Result<()> {
433 let (pos, anchor) = self.read_cursor();
434 let queued = {
435 let mut inner = self.doc.lock();
436 let dto = frontend::document_editing::InsertBlockDto {
437 position: to_i64(pos),
438 anchor: to_i64(anchor),
439 };
440 let result =
441 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
442 let edit_pos = pos.min(anchor);
443 let removed = pos.max(anchor) - edit_pos;
444 self.finish_edit(
445 &mut inner,
446 edit_pos,
447 removed,
448 to_usize(result.new_position),
449 2,
450 )
451 };
452 crate::inner::dispatch_queued_events(queued);
453 Ok(())
454 }
455
456 pub fn insert_html(&self, html: &str) -> Result<()> {
458 let (pos, anchor) = self.read_cursor();
459 let queued = {
460 let mut inner = self.doc.lock();
461 let dto = frontend::document_editing::InsertHtmlAtPositionDto {
462 position: to_i64(pos),
463 anchor: to_i64(anchor),
464 html: html.into(),
465 };
466 let result = document_editing_commands::insert_html_at_position(
467 &inner.ctx,
468 Some(inner.stack_id),
469 &dto,
470 )?;
471 let edit_pos = pos.min(anchor);
472 let removed = pos.max(anchor) - edit_pos;
473 self.finish_edit(
474 &mut inner,
475 edit_pos,
476 removed,
477 to_usize(result.new_position),
478 to_usize(result.blocks_added),
479 )
480 };
481 crate::inner::dispatch_queued_events(queued);
482 Ok(())
483 }
484
485 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
487 let (pos, anchor) = self.read_cursor();
488 let queued = {
489 let mut inner = self.doc.lock();
490 let dto = frontend::document_editing::InsertMarkdownAtPositionDto {
491 position: to_i64(pos),
492 anchor: to_i64(anchor),
493 markdown: markdown.into(),
494 };
495 let result = document_editing_commands::insert_markdown_at_position(
496 &inner.ctx,
497 Some(inner.stack_id),
498 &dto,
499 )?;
500 let edit_pos = pos.min(anchor);
501 let removed = pos.max(anchor) - edit_pos;
502 self.finish_edit(
503 &mut inner,
504 edit_pos,
505 removed,
506 to_usize(result.new_position),
507 to_usize(result.blocks_added),
508 )
509 };
510 crate::inner::dispatch_queued_events(queued);
511 Ok(())
512 }
513
514 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
516 let (pos, anchor) = self.read_cursor();
517 let queued = {
518 let mut inner = self.doc.lock();
519 let dto = frontend::document_editing::InsertFragmentDto {
520 position: to_i64(pos),
521 anchor: to_i64(anchor),
522 fragment_data: fragment.raw_data().into(),
523 };
524 let result =
525 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
526 let edit_pos = pos.min(anchor);
527 let removed = pos.max(anchor) - edit_pos;
528 self.finish_edit(
529 &mut inner,
530 edit_pos,
531 removed,
532 to_usize(result.new_position),
533 to_usize(result.blocks_added),
534 )
535 };
536 crate::inner::dispatch_queued_events(queued);
537 Ok(())
538 }
539
540 pub fn selection(&self) -> DocumentFragment {
542 let (pos, anchor) = self.read_cursor();
543 if pos == anchor {
544 return DocumentFragment::new();
545 }
546 let inner = self.doc.lock();
547 let dto = frontend::document_inspection::ExtractFragmentDto {
548 position: to_i64(pos),
549 anchor: to_i64(anchor),
550 };
551 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
552 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
553 Err(_) => DocumentFragment::new(),
554 }
555 }
556
557 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
559 let (pos, anchor) = self.read_cursor();
560 let queued = {
561 let mut inner = self.doc.lock();
562 let dto = frontend::document_editing::InsertImageDto {
563 position: to_i64(pos),
564 anchor: to_i64(anchor),
565 image_name: name.into(),
566 width: width as i64,
567 height: height as i64,
568 };
569 let result =
570 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
571 let edit_pos = pos.min(anchor);
572 let removed = pos.max(anchor) - edit_pos;
573 self.finish_edit_ext(
574 &mut inner,
575 edit_pos,
576 removed,
577 to_usize(result.new_position),
578 1,
579 false,
580 )
581 };
582 crate::inner::dispatch_queued_events(queued);
583 Ok(())
584 }
585
586 pub fn insert_frame(&self) -> Result<()> {
588 let (pos, anchor) = self.read_cursor();
589 let queued = {
590 let mut inner = self.doc.lock();
591 let dto = frontend::document_editing::InsertFrameDto {
592 position: to_i64(pos),
593 anchor: to_i64(anchor),
594 };
595 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
596 inner.modified = true;
599 inner.invalidate_text_cache();
600 inner.rehighlight_affected(pos.min(anchor));
601 inner.queue_event(DocumentEvent::ContentsChanged {
602 position: pos.min(anchor),
603 chars_removed: 0,
604 chars_added: 0,
605 blocks_affected: 1,
606 });
607 inner.check_block_count_changed();
608 inner.check_flow_changed();
609 self.queue_undo_redo_event(&mut inner)
610 };
611 crate::inner::dispatch_queued_events(queued);
612 Ok(())
613 }
614
615 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
621 let (pos, anchor) = self.read_cursor();
622 let (table_id, queued) = {
623 let mut inner = self.doc.lock();
624 let dto = frontend::document_editing::InsertTableDto {
625 position: to_i64(pos),
626 anchor: to_i64(anchor),
627 rows: to_i64(rows),
628 columns: to_i64(columns),
629 };
630 let result =
631 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
632 let new_pos = to_usize(result.new_position);
633 let table_id = to_usize(result.table_id);
634 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
635 {
636 let mut d = self.data.lock();
637 d.position = new_pos;
638 d.anchor = new_pos;
639 }
640 inner.modified = true;
641 inner.invalidate_text_cache();
642 inner.rehighlight_affected(pos.min(anchor));
643 inner.queue_event(DocumentEvent::ContentsChanged {
644 position: pos.min(anchor),
645 chars_removed: 0,
646 chars_added: new_pos - pos.min(anchor),
647 blocks_affected: 1,
648 });
649 inner.check_block_count_changed();
650 inner.check_flow_changed();
651 (table_id, self.queue_undo_redo_event(&mut inner))
652 };
653 crate::inner::dispatch_queued_events(queued);
654 Ok(TextTable {
655 doc: self.doc.clone(),
656 table_id,
657 })
658 }
659
660 pub fn current_table(&self) -> Option<TextTable> {
665 self.current_table_cell().map(|c| c.table)
666 }
667
668 pub fn current_table_cell(&self) -> Option<TableCellRef> {
673 let pos = self.position();
674 let inner = self.doc.lock();
675 let dto = frontend::document_inspection::GetBlockAtPositionDto {
677 position: to_i64(pos),
678 };
679 let block_info =
680 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
681 let block = crate::text_block::TextBlock {
682 doc: self.doc.clone(),
683 block_id: block_info.block_id as usize,
684 };
685 drop(inner);
687 block.table_cell()
688 }
689
690 pub fn remove_table(&self, table_id: usize) -> Result<()> {
694 let queued = {
695 let mut inner = self.doc.lock();
696 let dto = frontend::document_editing::RemoveTableDto {
697 table_id: to_i64(table_id),
698 };
699 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
700 inner.modified = true;
701 inner.invalidate_text_cache();
702 inner.rehighlight_all();
703 inner.check_block_count_changed();
704 inner.check_flow_changed();
705 self.queue_undo_redo_event(&mut inner)
706 };
707 crate::inner::dispatch_queued_events(queued);
708 Ok(())
709 }
710
711 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
713 let queued = {
714 let mut inner = self.doc.lock();
715 let dto = frontend::document_editing::InsertTableRowDto {
716 table_id: to_i64(table_id),
717 row_index: to_i64(row_index),
718 };
719 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
720 inner.modified = true;
721 inner.invalidate_text_cache();
722 inner.rehighlight_all();
723 inner.check_block_count_changed();
724 self.queue_undo_redo_event(&mut inner)
725 };
726 crate::inner::dispatch_queued_events(queued);
727 Ok(())
728 }
729
730 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
732 let queued = {
733 let mut inner = self.doc.lock();
734 let dto = frontend::document_editing::InsertTableColumnDto {
735 table_id: to_i64(table_id),
736 column_index: to_i64(column_index),
737 };
738 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
739 inner.modified = true;
740 inner.invalidate_text_cache();
741 inner.rehighlight_all();
742 inner.check_block_count_changed();
743 self.queue_undo_redo_event(&mut inner)
744 };
745 crate::inner::dispatch_queued_events(queued);
746 Ok(())
747 }
748
749 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
751 let queued = {
752 let mut inner = self.doc.lock();
753 let dto = frontend::document_editing::RemoveTableRowDto {
754 table_id: to_i64(table_id),
755 row_index: to_i64(row_index),
756 };
757 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
758 inner.modified = true;
759 inner.invalidate_text_cache();
760 inner.rehighlight_all();
761 inner.check_block_count_changed();
762 self.queue_undo_redo_event(&mut inner)
763 };
764 crate::inner::dispatch_queued_events(queued);
765 Ok(())
766 }
767
768 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
770 let queued = {
771 let mut inner = self.doc.lock();
772 let dto = frontend::document_editing::RemoveTableColumnDto {
773 table_id: to_i64(table_id),
774 column_index: to_i64(column_index),
775 };
776 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
777 inner.modified = true;
778 inner.invalidate_text_cache();
779 inner.rehighlight_all();
780 inner.check_block_count_changed();
781 self.queue_undo_redo_event(&mut inner)
782 };
783 crate::inner::dispatch_queued_events(queued);
784 Ok(())
785 }
786
787 pub fn merge_table_cells(
789 &self,
790 table_id: usize,
791 start_row: usize,
792 start_column: usize,
793 end_row: usize,
794 end_column: usize,
795 ) -> Result<()> {
796 let queued = {
797 let mut inner = self.doc.lock();
798 let dto = frontend::document_editing::MergeTableCellsDto {
799 table_id: to_i64(table_id),
800 start_row: to_i64(start_row),
801 start_column: to_i64(start_column),
802 end_row: to_i64(end_row),
803 end_column: to_i64(end_column),
804 };
805 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
806 inner.modified = true;
807 inner.invalidate_text_cache();
808 inner.rehighlight_all();
809 inner.check_block_count_changed();
810 self.queue_undo_redo_event(&mut inner)
811 };
812 crate::inner::dispatch_queued_events(queued);
813 Ok(())
814 }
815
816 pub fn split_table_cell(
818 &self,
819 cell_id: usize,
820 split_rows: usize,
821 split_columns: usize,
822 ) -> Result<()> {
823 let queued = {
824 let mut inner = self.doc.lock();
825 let dto = frontend::document_editing::SplitTableCellDto {
826 cell_id: to_i64(cell_id),
827 split_rows: to_i64(split_rows),
828 split_columns: to_i64(split_columns),
829 };
830 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
831 inner.modified = true;
832 inner.invalidate_text_cache();
833 inner.rehighlight_all();
834 inner.check_block_count_changed();
835 self.queue_undo_redo_event(&mut inner)
836 };
837 crate::inner::dispatch_queued_events(queued);
838 Ok(())
839 }
840
841 pub fn set_table_format(
845 &self,
846 table_id: usize,
847 format: &crate::flow::TableFormat,
848 ) -> Result<()> {
849 let queued = {
850 let mut inner = self.doc.lock();
851 let dto = format.to_set_dto(table_id);
852 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
853 inner.modified = true;
854 inner.queue_event(DocumentEvent::FormatChanged {
855 position: 0,
856 length: 0,
857 kind: crate::flow::FormatChangeKind::Block,
858 });
859 self.queue_undo_redo_event(&mut inner)
860 };
861 crate::inner::dispatch_queued_events(queued);
862 Ok(())
863 }
864
865 pub fn set_table_cell_format(
867 &self,
868 cell_id: usize,
869 format: &crate::flow::CellFormat,
870 ) -> Result<()> {
871 let queued = {
872 let mut inner = self.doc.lock();
873 let dto = format.to_set_dto(cell_id);
874 document_formatting_commands::set_table_cell_format(
875 &inner.ctx,
876 Some(inner.stack_id),
877 &dto,
878 )?;
879 inner.modified = true;
880 inner.queue_event(DocumentEvent::FormatChanged {
881 position: 0,
882 length: 0,
883 kind: crate::flow::FormatChangeKind::Block,
884 });
885 self.queue_undo_redo_event(&mut inner)
886 };
887 crate::inner::dispatch_queued_events(queued);
888 Ok(())
889 }
890
891 pub fn remove_current_table(&self) -> Result<()> {
896 let table = self
897 .current_table()
898 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
899 self.remove_table(table.id())
900 }
901
902 pub fn insert_row_above(&self) -> Result<()> {
905 let cell_ref = self
906 .current_table_cell()
907 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
908 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
909 }
910
911 pub fn insert_row_below(&self) -> Result<()> {
914 let cell_ref = self
915 .current_table_cell()
916 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
917 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
918 }
919
920 pub fn insert_column_before(&self) -> Result<()> {
923 let cell_ref = self
924 .current_table_cell()
925 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
926 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
927 }
928
929 pub fn insert_column_after(&self) -> Result<()> {
932 let cell_ref = self
933 .current_table_cell()
934 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
935 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
936 }
937
938 pub fn remove_current_row(&self) -> Result<()> {
941 let cell_ref = self
942 .current_table_cell()
943 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
944 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
945 }
946
947 pub fn remove_current_column(&self) -> Result<()> {
950 let cell_ref = self
951 .current_table_cell()
952 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
953 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
954 }
955
956 pub fn merge_selected_cells(&self) -> Result<()> {
963 let pos_cell = self
964 .current_table_cell()
965 .ok_or_else(|| anyhow::anyhow!("cursor position is not inside a table"))?;
966
967 let (_pos, anchor) = self.read_cursor();
969 let anchor_cell = {
970 let inner = self.doc.lock();
972 let dto = frontend::document_inspection::GetBlockAtPositionDto {
973 position: to_i64(anchor),
974 };
975 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
976 .map_err(|_| anyhow::anyhow!("cursor anchor is not inside a table"))?;
977 let block = crate::text_block::TextBlock {
978 doc: self.doc.clone(),
979 block_id: block_info.block_id as usize,
980 };
981 drop(inner);
982 block
983 .table_cell()
984 .ok_or_else(|| anyhow::anyhow!("cursor anchor is not inside a table"))?
985 };
986
987 if pos_cell.table.id() != anchor_cell.table.id() {
988 return Err(anyhow::anyhow!(
989 "position and anchor are in different tables"
990 ));
991 }
992
993 let start_row = pos_cell.row.min(anchor_cell.row);
994 let start_col = pos_cell.column.min(anchor_cell.column);
995 let end_row = pos_cell.row.max(anchor_cell.row);
996 let end_col = pos_cell.column.max(anchor_cell.column);
997
998 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
999 }
1000
1001 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1004 let cell_ref = self
1005 .current_table_cell()
1006 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1007 let cell = cell_ref
1009 .table
1010 .cell(cell_ref.row, cell_ref.column)
1011 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1012 self.split_table_cell(cell.id(), split_rows, split_columns)
1014 }
1015
1016 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1019 let table = self
1020 .current_table()
1021 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1022 self.set_table_format(table.id(), format)
1023 }
1024
1025 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1028 let cell_ref = self
1029 .current_table_cell()
1030 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1031 let cell = cell_ref
1032 .table
1033 .cell(cell_ref.row, cell_ref.column)
1034 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1035 self.set_table_cell_format(cell.id(), format)
1036 }
1037
1038 pub fn delete_char(&self) -> Result<()> {
1040 let (pos, anchor) = self.read_cursor();
1041 let (del_pos, del_anchor) = if pos != anchor {
1042 (pos, anchor)
1043 } else {
1044 let end = {
1046 let inner = self.doc.lock();
1047 document_inspection_commands::get_document_stats(&inner.ctx)
1048 .map(|s| max_cursor_position(&s))
1049 .unwrap_or(0)
1050 };
1051 if pos >= end {
1052 return Ok(());
1053 }
1054 (pos, pos + 1)
1055 };
1056 self.do_delete(del_pos, del_anchor)
1057 }
1058
1059 pub fn delete_previous_char(&self) -> Result<()> {
1061 let (pos, anchor) = self.read_cursor();
1062 let (del_pos, del_anchor) = if pos != anchor {
1063 (pos, anchor)
1064 } else if pos > 0 {
1065 (pos - 1, pos)
1066 } else {
1067 return Ok(());
1068 };
1069 self.do_delete(del_pos, del_anchor)
1070 }
1071
1072 pub fn remove_selected_text(&self) -> Result<String> {
1074 let (pos, anchor) = self.read_cursor();
1075 if pos == anchor {
1076 return Ok(String::new());
1077 }
1078 let queued = {
1079 let mut inner = self.doc.lock();
1080 let dto = frontend::document_editing::DeleteTextDto {
1081 position: to_i64(pos),
1082 anchor: to_i64(anchor),
1083 };
1084 let result =
1085 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1086 let edit_pos = pos.min(anchor);
1087 let removed = pos.max(anchor) - edit_pos;
1088 let new_pos = to_usize(result.new_position);
1089 inner.adjust_cursors(edit_pos, removed, 0);
1090 {
1091 let mut d = self.data.lock();
1092 d.position = new_pos;
1093 d.anchor = new_pos;
1094 }
1095 inner.modified = true;
1096 inner.invalidate_text_cache();
1097 inner.rehighlight_affected(edit_pos);
1098 inner.queue_event(DocumentEvent::ContentsChanged {
1099 position: edit_pos,
1100 chars_removed: removed,
1101 chars_added: 0,
1102 blocks_affected: 1,
1103 });
1104 inner.check_block_count_changed();
1105 inner.check_flow_changed();
1106 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1108 };
1109 crate::inner::dispatch_queued_events(queued.1);
1110 Ok(queued.0)
1111 }
1112
1113 pub fn current_list(&self) -> Option<crate::TextList> {
1118 let pos = self.position();
1119 let inner = self.doc.lock();
1120 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1121 position: to_i64(pos),
1122 };
1123 let block_info =
1124 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1125 let block = crate::text_block::TextBlock {
1126 doc: self.doc.clone(),
1127 block_id: block_info.block_id as usize,
1128 };
1129 drop(inner);
1130 block.list()
1131 }
1132
1133 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1135 let (pos, anchor) = self.read_cursor();
1136 let queued = {
1137 let mut inner = self.doc.lock();
1138 let dto = frontend::document_editing::CreateListDto {
1139 position: to_i64(pos),
1140 anchor: to_i64(anchor),
1141 style: style.clone(),
1142 };
1143 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1144 inner.modified = true;
1145 inner.rehighlight_affected(pos.min(anchor));
1146 inner.queue_event(DocumentEvent::ContentsChanged {
1147 position: pos.min(anchor),
1148 chars_removed: 0,
1149 chars_added: 0,
1150 blocks_affected: 1,
1151 });
1152 self.queue_undo_redo_event(&mut inner)
1153 };
1154 crate::inner::dispatch_queued_events(queued);
1155 Ok(())
1156 }
1157
1158 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
1160 let (pos, anchor) = self.read_cursor();
1161 let queued = {
1162 let mut inner = self.doc.lock();
1163 let dto = frontend::document_editing::InsertListDto {
1164 position: to_i64(pos),
1165 anchor: to_i64(anchor),
1166 style: style.clone(),
1167 };
1168 let result =
1169 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1170 let edit_pos = pos.min(anchor);
1171 let removed = pos.max(anchor) - edit_pos;
1172 self.finish_edit_ext(
1173 &mut inner,
1174 edit_pos,
1175 removed,
1176 to_usize(result.new_position),
1177 1,
1178 false,
1179 )
1180 };
1181 crate::inner::dispatch_queued_events(queued);
1182 Ok(())
1183 }
1184
1185 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
1187 let queued = {
1188 let mut inner = self.doc.lock();
1189 let dto = format.to_set_dto(list_id);
1190 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1191 inner.modified = true;
1192 inner.queue_event(DocumentEvent::FormatChanged {
1193 position: 0,
1194 length: 0,
1195 kind: crate::flow::FormatChangeKind::List,
1196 });
1197 self.queue_undo_redo_event(&mut inner)
1198 };
1199 crate::inner::dispatch_queued_events(queued);
1200 Ok(())
1201 }
1202
1203 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
1206 let list = self
1207 .current_list()
1208 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a list"))?;
1209 self.set_list_format(list.id(), format)
1210 }
1211
1212 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
1214 let queued = {
1215 let mut inner = self.doc.lock();
1216 let dto = frontend::document_editing::AddBlockToListDto {
1217 block_id: to_i64(block_id),
1218 list_id: to_i64(list_id),
1219 };
1220 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1221 inner.modified = true;
1222 inner.queue_event(DocumentEvent::ContentsChanged {
1223 position: 0,
1224 chars_removed: 0,
1225 chars_added: 0,
1226 blocks_affected: 1,
1227 });
1228 self.queue_undo_redo_event(&mut inner)
1229 };
1230 crate::inner::dispatch_queued_events(queued);
1231 Ok(())
1232 }
1233
1234 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
1236 let pos = self.position();
1237 let inner = self.doc.lock();
1238 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1239 position: to_i64(pos),
1240 };
1241 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1242 drop(inner);
1243 self.add_block_to_list(block_info.block_id as usize, list_id)
1244 }
1245
1246 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
1248 let queued = {
1249 let mut inner = self.doc.lock();
1250 let dto = frontend::document_editing::RemoveBlockFromListDto {
1251 block_id: to_i64(block_id),
1252 };
1253 document_editing_commands::remove_block_from_list(
1254 &inner.ctx,
1255 Some(inner.stack_id),
1256 &dto,
1257 )?;
1258 inner.modified = true;
1259 inner.queue_event(DocumentEvent::ContentsChanged {
1260 position: 0,
1261 chars_removed: 0,
1262 chars_added: 0,
1263 blocks_affected: 1,
1264 });
1265 self.queue_undo_redo_event(&mut inner)
1266 };
1267 crate::inner::dispatch_queued_events(queued);
1268 Ok(())
1269 }
1270
1271 pub fn remove_current_block_from_list(&self) -> Result<()> {
1274 let pos = self.position();
1275 let inner = self.doc.lock();
1276 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1277 position: to_i64(pos),
1278 };
1279 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1280 drop(inner);
1281 self.remove_block_from_list(block_info.block_id as usize)
1282 }
1283
1284 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
1287 let list = crate::text_list::TextList {
1288 doc: self.doc.clone(),
1289 list_id,
1290 };
1291 let block = list
1292 .item(index)
1293 .ok_or_else(|| anyhow::anyhow!("list item index {index} out of range"))?;
1294 self.remove_block_from_list(block.id())
1295 }
1296
1297 pub fn char_format(&self) -> Result<TextFormat> {
1301 let pos = self.position();
1302 let inner = self.doc.lock();
1303 let dto = frontend::document_inspection::GetTextAtPositionDto {
1304 position: to_i64(pos),
1305 length: 1,
1306 };
1307 let text_info = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
1308 let element_id = text_info.element_id as u64;
1309 let element = inline_element_commands::get_inline_element(&inner.ctx, &element_id)?
1310 .ok_or_else(|| anyhow::anyhow!("element not found at position"))?;
1311 Ok(TextFormat::from(&element))
1312 }
1313
1314 pub fn block_format(&self) -> Result<BlockFormat> {
1316 let pos = self.position();
1317 let inner = self.doc.lock();
1318 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1319 position: to_i64(pos),
1320 };
1321 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1322 let block_id = block_info.block_id as u64;
1323 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
1324 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
1325 Ok(BlockFormat::from(&block))
1326 }
1327
1328 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
1332 let (pos, anchor) = self.read_cursor();
1333 let queued = {
1334 let mut inner = self.doc.lock();
1335 let dto = format.to_set_dto(pos, anchor);
1336 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1337 let start = pos.min(anchor);
1338 let length = pos.max(anchor) - start;
1339 inner.modified = true;
1340 inner.queue_event(DocumentEvent::FormatChanged {
1341 position: start,
1342 length,
1343 kind: crate::flow::FormatChangeKind::Character,
1344 });
1345 self.queue_undo_redo_event(&mut inner)
1346 };
1347 crate::inner::dispatch_queued_events(queued);
1348 Ok(())
1349 }
1350
1351 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
1353 let (pos, anchor) = self.read_cursor();
1354 let queued = {
1355 let mut inner = self.doc.lock();
1356 let dto = format.to_merge_dto(pos, anchor);
1357 document_formatting_commands::merge_text_format(
1358 &inner.ctx,
1359 Some(inner.stack_id),
1360 &dto,
1361 )?;
1362 let start = pos.min(anchor);
1363 let length = pos.max(anchor) - start;
1364 inner.modified = true;
1365 inner.queue_event(DocumentEvent::FormatChanged {
1366 position: start,
1367 length,
1368 kind: crate::flow::FormatChangeKind::Character,
1369 });
1370 self.queue_undo_redo_event(&mut inner)
1371 };
1372 crate::inner::dispatch_queued_events(queued);
1373 Ok(())
1374 }
1375
1376 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
1378 let (pos, anchor) = self.read_cursor();
1379 let queued = {
1380 let mut inner = self.doc.lock();
1381 let dto = format.to_set_dto(pos, anchor);
1382 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1383 let start = pos.min(anchor);
1384 let length = pos.max(anchor) - start;
1385 inner.modified = true;
1386 inner.queue_event(DocumentEvent::FormatChanged {
1387 position: start,
1388 length,
1389 kind: crate::flow::FormatChangeKind::Block,
1390 });
1391 self.queue_undo_redo_event(&mut inner)
1392 };
1393 crate::inner::dispatch_queued_events(queued);
1394 Ok(())
1395 }
1396
1397 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
1399 let (pos, anchor) = self.read_cursor();
1400 let queued = {
1401 let mut inner = self.doc.lock();
1402 let dto = format.to_set_dto(pos, anchor, frame_id);
1403 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1404 let start = pos.min(anchor);
1405 let length = pos.max(anchor) - start;
1406 inner.modified = true;
1407 inner.queue_event(DocumentEvent::FormatChanged {
1408 position: start,
1409 length,
1410 kind: crate::flow::FormatChangeKind::Block,
1411 });
1412 self.queue_undo_redo_event(&mut inner)
1413 };
1414 crate::inner::dispatch_queued_events(queued);
1415 Ok(())
1416 }
1417
1418 pub fn begin_edit_block(&self) {
1422 let inner = self.doc.lock();
1423 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1424 }
1425
1426 pub fn end_edit_block(&self) {
1428 let inner = self.doc.lock();
1429 undo_redo_commands::end_composite(&inner.ctx);
1430 }
1431
1432 pub fn join_previous_edit_block(&self) {
1439 self.begin_edit_block();
1440 }
1441
1442 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
1446 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1447 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1448 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1449 inner.take_queued_events()
1450 }
1451
1452 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
1453 let queued = {
1454 let mut inner = self.doc.lock();
1455 let dto = frontend::document_editing::DeleteTextDto {
1456 position: to_i64(pos),
1457 anchor: to_i64(anchor),
1458 };
1459 let result =
1460 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1461 let edit_pos = pos.min(anchor);
1462 let removed = pos.max(anchor) - edit_pos;
1463 let new_pos = to_usize(result.new_position);
1464 inner.adjust_cursors(edit_pos, removed, 0);
1465 {
1466 let mut d = self.data.lock();
1467 d.position = new_pos;
1468 d.anchor = new_pos;
1469 }
1470 inner.modified = true;
1471 inner.invalidate_text_cache();
1472 inner.rehighlight_affected(edit_pos);
1473 inner.queue_event(DocumentEvent::ContentsChanged {
1474 position: edit_pos,
1475 chars_removed: removed,
1476 chars_added: 0,
1477 blocks_affected: 1,
1478 });
1479 inner.check_block_count_changed();
1480 inner.check_flow_changed();
1481 self.queue_undo_redo_event(&mut inner)
1482 };
1483 crate::inner::dispatch_queued_events(queued);
1484 Ok(())
1485 }
1486
1487 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
1489 let pos = self.position();
1490 match op {
1491 MoveOperation::NoMove => pos,
1492 MoveOperation::Start => 0,
1493 MoveOperation::End => {
1494 let inner = self.doc.lock();
1495 document_inspection_commands::get_document_stats(&inner.ctx)
1496 .map(|s| max_cursor_position(&s))
1497 .unwrap_or(pos)
1498 }
1499 MoveOperation::NextCharacter | MoveOperation::Right => pos + n,
1500 MoveOperation::PreviousCharacter | MoveOperation::Left => pos.saturating_sub(n),
1501 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
1502 let inner = self.doc.lock();
1503 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1504 position: to_i64(pos),
1505 };
1506 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1507 .map(|info| to_usize(info.block_start))
1508 .unwrap_or(pos)
1509 }
1510 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
1511 let inner = self.doc.lock();
1512 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1513 position: to_i64(pos),
1514 };
1515 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1516 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
1517 .unwrap_or(pos)
1518 }
1519 MoveOperation::NextBlock => {
1520 let inner = self.doc.lock();
1521 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1522 position: to_i64(pos),
1523 };
1524 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1525 .map(|info| {
1526 to_usize(info.block_start) + to_usize(info.block_length) + 1
1528 })
1529 .unwrap_or(pos)
1530 }
1531 MoveOperation::PreviousBlock => {
1532 let inner = self.doc.lock();
1533 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1534 position: to_i64(pos),
1535 };
1536 let block_start =
1537 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1538 .map(|info| to_usize(info.block_start))
1539 .unwrap_or(pos);
1540 if block_start >= 2 {
1541 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1543 position: to_i64(block_start - 2),
1544 };
1545 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
1546 .map(|info| to_usize(info.block_start))
1547 .unwrap_or(0)
1548 } else {
1549 0
1550 }
1551 }
1552 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
1553 let (_, end) = self.find_word_boundaries(pos);
1554 if end == pos {
1556 let inner = self.doc.lock();
1558 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
1559 .map(|s| max_cursor_position(&s))
1560 .unwrap_or(0);
1561 let scan_len = max_pos.saturating_sub(pos).min(64);
1562 if scan_len == 0 {
1563 return pos;
1564 }
1565 let dto = frontend::document_inspection::GetTextAtPositionDto {
1566 position: to_i64(pos),
1567 length: to_i64(scan_len),
1568 };
1569 if let Ok(r) =
1570 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
1571 {
1572 for (i, ch) in r.text.chars().enumerate() {
1573 if ch.is_alphanumeric() || ch == '_' {
1574 let word_pos = pos + i;
1576 drop(inner);
1577 let (_, word_end) = self.find_word_boundaries(word_pos);
1578 return word_end;
1579 }
1580 }
1581 }
1582 pos + scan_len
1583 } else {
1584 end
1585 }
1586 }
1587 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
1588 let (start, _) = self.find_word_boundaries(pos);
1589 if start < pos {
1590 start
1591 } else if pos > 0 {
1592 let mut search = pos - 1;
1595 loop {
1596 let (ws, we) = self.find_word_boundaries(search);
1597 if ws < we {
1598 break ws;
1600 }
1601 if search == 0 {
1603 break 0;
1604 }
1605 search -= 1;
1606 }
1607 } else {
1608 0
1609 }
1610 }
1611 MoveOperation::Up | MoveOperation::Down => {
1612 if matches!(op, MoveOperation::Up) {
1615 self.resolve_move(MoveOperation::PreviousBlock, 1)
1616 } else {
1617 self.resolve_move(MoveOperation::NextBlock, 1)
1618 }
1619 }
1620 }
1621 }
1622
1623 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
1629 let inner = self.doc.lock();
1630 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
1632 position: to_i64(pos),
1633 };
1634 let block_info =
1635 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
1636 Ok(info) => info,
1637 Err(_) => return (pos, pos),
1638 };
1639
1640 let block_start = to_usize(block_info.block_start);
1641 let block_length = to_usize(block_info.block_length);
1642 if block_length == 0 {
1643 return (pos, pos);
1644 }
1645
1646 let dto = frontend::document_inspection::GetTextAtPositionDto {
1647 position: to_i64(block_start),
1648 length: to_i64(block_length),
1649 };
1650 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
1651 Ok(r) => r.text,
1652 Err(_) => return (pos, pos),
1653 };
1654
1655 let cursor_offset = pos.saturating_sub(block_start);
1657
1658 let mut last_char_start = 0;
1660 let mut last_char_end = 0;
1661
1662 for (word_byte_start, word) in text.unicode_word_indices() {
1663 let word_char_start = text[..word_byte_start].chars().count();
1665 let word_char_len = word.chars().count();
1666 let word_char_end = word_char_start + word_char_len;
1667
1668 last_char_start = word_char_start;
1669 last_char_end = word_char_end;
1670
1671 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
1672 return (block_start + word_char_start, block_start + word_char_end);
1673 }
1674 }
1675
1676 if cursor_offset == last_char_end && last_char_start < last_char_end {
1678 return (block_start + last_char_start, block_start + last_char_end);
1679 }
1680
1681 (pos, pos)
1682 }
1683}