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 (pos, pos + 1)
1045 };
1046 self.do_delete(del_pos, del_anchor)
1047 }
1048
1049 pub fn delete_previous_char(&self) -> Result<()> {
1051 let (pos, anchor) = self.read_cursor();
1052 let (del_pos, del_anchor) = if pos != anchor {
1053 (pos, anchor)
1054 } else if pos > 0 {
1055 (pos - 1, pos)
1056 } else {
1057 return Ok(());
1058 };
1059 self.do_delete(del_pos, del_anchor)
1060 }
1061
1062 pub fn remove_selected_text(&self) -> Result<String> {
1064 let (pos, anchor) = self.read_cursor();
1065 if pos == anchor {
1066 return Ok(String::new());
1067 }
1068 let queued = {
1069 let mut inner = self.doc.lock();
1070 let dto = frontend::document_editing::DeleteTextDto {
1071 position: to_i64(pos),
1072 anchor: to_i64(anchor),
1073 };
1074 let result =
1075 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1076 let edit_pos = pos.min(anchor);
1077 let removed = pos.max(anchor) - edit_pos;
1078 let new_pos = to_usize(result.new_position);
1079 inner.adjust_cursors(edit_pos, removed, 0);
1080 {
1081 let mut d = self.data.lock();
1082 d.position = new_pos;
1083 d.anchor = new_pos;
1084 }
1085 inner.modified = true;
1086 inner.invalidate_text_cache();
1087 inner.rehighlight_affected(edit_pos);
1088 inner.queue_event(DocumentEvent::ContentsChanged {
1089 position: edit_pos,
1090 chars_removed: removed,
1091 chars_added: 0,
1092 blocks_affected: 1,
1093 });
1094 inner.check_block_count_changed();
1095 inner.check_flow_changed();
1096 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1098 };
1099 crate::inner::dispatch_queued_events(queued.1);
1100 Ok(queued.0)
1101 }
1102
1103 pub fn current_list(&self) -> Option<crate::TextList> {
1108 let pos = self.position();
1109 let inner = self.doc.lock();
1110 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1111 position: to_i64(pos),
1112 };
1113 let block_info =
1114 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1115 let block = crate::text_block::TextBlock {
1116 doc: self.doc.clone(),
1117 block_id: block_info.block_id as usize,
1118 };
1119 drop(inner);
1120 block.list()
1121 }
1122
1123 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1125 let (pos, anchor) = self.read_cursor();
1126 let queued = {
1127 let mut inner = self.doc.lock();
1128 let dto = frontend::document_editing::CreateListDto {
1129 position: to_i64(pos),
1130 anchor: to_i64(anchor),
1131 style: style.clone(),
1132 };
1133 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1134 inner.modified = true;
1135 inner.rehighlight_affected(pos.min(anchor));
1136 inner.queue_event(DocumentEvent::ContentsChanged {
1137 position: pos.min(anchor),
1138 chars_removed: 0,
1139 chars_added: 0,
1140 blocks_affected: 1,
1141 });
1142 self.queue_undo_redo_event(&mut inner)
1143 };
1144 crate::inner::dispatch_queued_events(queued);
1145 Ok(())
1146 }
1147
1148 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
1150 let (pos, anchor) = self.read_cursor();
1151 let queued = {
1152 let mut inner = self.doc.lock();
1153 let dto = frontend::document_editing::InsertListDto {
1154 position: to_i64(pos),
1155 anchor: to_i64(anchor),
1156 style: style.clone(),
1157 };
1158 let result =
1159 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1160 let edit_pos = pos.min(anchor);
1161 let removed = pos.max(anchor) - edit_pos;
1162 self.finish_edit_ext(
1163 &mut inner,
1164 edit_pos,
1165 removed,
1166 to_usize(result.new_position),
1167 1,
1168 false,
1169 )
1170 };
1171 crate::inner::dispatch_queued_events(queued);
1172 Ok(())
1173 }
1174
1175 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
1177 let queued = {
1178 let mut inner = self.doc.lock();
1179 let dto = format.to_set_dto(list_id);
1180 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1181 inner.modified = true;
1182 inner.queue_event(DocumentEvent::FormatChanged {
1183 position: 0,
1184 length: 0,
1185 kind: crate::flow::FormatChangeKind::List,
1186 });
1187 self.queue_undo_redo_event(&mut inner)
1188 };
1189 crate::inner::dispatch_queued_events(queued);
1190 Ok(())
1191 }
1192
1193 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
1196 let list = self
1197 .current_list()
1198 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a list"))?;
1199 self.set_list_format(list.id(), format)
1200 }
1201
1202 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
1204 let queued = {
1205 let mut inner = self.doc.lock();
1206 let dto = frontend::document_editing::AddBlockToListDto {
1207 block_id: to_i64(block_id),
1208 list_id: to_i64(list_id),
1209 };
1210 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1211 inner.modified = true;
1212 inner.queue_event(DocumentEvent::ContentsChanged {
1213 position: 0,
1214 chars_removed: 0,
1215 chars_added: 0,
1216 blocks_affected: 1,
1217 });
1218 self.queue_undo_redo_event(&mut inner)
1219 };
1220 crate::inner::dispatch_queued_events(queued);
1221 Ok(())
1222 }
1223
1224 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
1226 let pos = self.position();
1227 let inner = self.doc.lock();
1228 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1229 position: to_i64(pos),
1230 };
1231 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1232 drop(inner);
1233 self.add_block_to_list(block_info.block_id as usize, list_id)
1234 }
1235
1236 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
1238 let queued = {
1239 let mut inner = self.doc.lock();
1240 let dto = frontend::document_editing::RemoveBlockFromListDto {
1241 block_id: to_i64(block_id),
1242 };
1243 document_editing_commands::remove_block_from_list(
1244 &inner.ctx,
1245 Some(inner.stack_id),
1246 &dto,
1247 )?;
1248 inner.modified = true;
1249 inner.queue_event(DocumentEvent::ContentsChanged {
1250 position: 0,
1251 chars_removed: 0,
1252 chars_added: 0,
1253 blocks_affected: 1,
1254 });
1255 self.queue_undo_redo_event(&mut inner)
1256 };
1257 crate::inner::dispatch_queued_events(queued);
1258 Ok(())
1259 }
1260
1261 pub fn remove_current_block_from_list(&self) -> Result<()> {
1264 let pos = self.position();
1265 let inner = self.doc.lock();
1266 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1267 position: to_i64(pos),
1268 };
1269 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1270 drop(inner);
1271 self.remove_block_from_list(block_info.block_id as usize)
1272 }
1273
1274 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
1277 let list = crate::text_list::TextList {
1278 doc: self.doc.clone(),
1279 list_id,
1280 };
1281 let block = list
1282 .item(index)
1283 .ok_or_else(|| anyhow::anyhow!("list item index {index} out of range"))?;
1284 self.remove_block_from_list(block.id())
1285 }
1286
1287 pub fn char_format(&self) -> Result<TextFormat> {
1291 let pos = self.position();
1292 let inner = self.doc.lock();
1293 let dto = frontend::document_inspection::GetTextAtPositionDto {
1294 position: to_i64(pos),
1295 length: 1,
1296 };
1297 let text_info = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
1298 let element_id = text_info.element_id as u64;
1299 let element = inline_element_commands::get_inline_element(&inner.ctx, &element_id)?
1300 .ok_or_else(|| anyhow::anyhow!("element not found at position"))?;
1301 Ok(TextFormat::from(&element))
1302 }
1303
1304 pub fn block_format(&self) -> Result<BlockFormat> {
1306 let pos = self.position();
1307 let inner = self.doc.lock();
1308 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1309 position: to_i64(pos),
1310 };
1311 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1312 let block_id = block_info.block_id as u64;
1313 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
1314 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
1315 Ok(BlockFormat::from(&block))
1316 }
1317
1318 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
1322 let (pos, anchor) = self.read_cursor();
1323 let queued = {
1324 let mut inner = self.doc.lock();
1325 let dto = format.to_set_dto(pos, anchor);
1326 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1327 let start = pos.min(anchor);
1328 let length = pos.max(anchor) - start;
1329 inner.modified = true;
1330 inner.queue_event(DocumentEvent::FormatChanged {
1331 position: start,
1332 length,
1333 kind: crate::flow::FormatChangeKind::Character,
1334 });
1335 self.queue_undo_redo_event(&mut inner)
1336 };
1337 crate::inner::dispatch_queued_events(queued);
1338 Ok(())
1339 }
1340
1341 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
1343 let (pos, anchor) = self.read_cursor();
1344 let queued = {
1345 let mut inner = self.doc.lock();
1346 let dto = format.to_merge_dto(pos, anchor);
1347 document_formatting_commands::merge_text_format(
1348 &inner.ctx,
1349 Some(inner.stack_id),
1350 &dto,
1351 )?;
1352 let start = pos.min(anchor);
1353 let length = pos.max(anchor) - start;
1354 inner.modified = true;
1355 inner.queue_event(DocumentEvent::FormatChanged {
1356 position: start,
1357 length,
1358 kind: crate::flow::FormatChangeKind::Character,
1359 });
1360 self.queue_undo_redo_event(&mut inner)
1361 };
1362 crate::inner::dispatch_queued_events(queued);
1363 Ok(())
1364 }
1365
1366 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
1368 let (pos, anchor) = self.read_cursor();
1369 let queued = {
1370 let mut inner = self.doc.lock();
1371 let dto = format.to_set_dto(pos, anchor);
1372 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1373 let start = pos.min(anchor);
1374 let length = pos.max(anchor) - start;
1375 inner.modified = true;
1376 inner.queue_event(DocumentEvent::FormatChanged {
1377 position: start,
1378 length,
1379 kind: crate::flow::FormatChangeKind::Block,
1380 });
1381 self.queue_undo_redo_event(&mut inner)
1382 };
1383 crate::inner::dispatch_queued_events(queued);
1384 Ok(())
1385 }
1386
1387 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
1389 let (pos, anchor) = self.read_cursor();
1390 let queued = {
1391 let mut inner = self.doc.lock();
1392 let dto = format.to_set_dto(pos, anchor, frame_id);
1393 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1394 let start = pos.min(anchor);
1395 let length = pos.max(anchor) - start;
1396 inner.modified = true;
1397 inner.queue_event(DocumentEvent::FormatChanged {
1398 position: start,
1399 length,
1400 kind: crate::flow::FormatChangeKind::Block,
1401 });
1402 self.queue_undo_redo_event(&mut inner)
1403 };
1404 crate::inner::dispatch_queued_events(queued);
1405 Ok(())
1406 }
1407
1408 pub fn begin_edit_block(&self) {
1412 let inner = self.doc.lock();
1413 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1414 }
1415
1416 pub fn end_edit_block(&self) {
1418 let inner = self.doc.lock();
1419 undo_redo_commands::end_composite(&inner.ctx);
1420 }
1421
1422 pub fn join_previous_edit_block(&self) {
1429 self.begin_edit_block();
1430 }
1431
1432 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
1436 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1437 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1438 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1439 inner.take_queued_events()
1440 }
1441
1442 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
1443 let queued = {
1444 let mut inner = self.doc.lock();
1445 let dto = frontend::document_editing::DeleteTextDto {
1446 position: to_i64(pos),
1447 anchor: to_i64(anchor),
1448 };
1449 let result =
1450 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1451 let edit_pos = pos.min(anchor);
1452 let removed = pos.max(anchor) - edit_pos;
1453 let new_pos = to_usize(result.new_position);
1454 inner.adjust_cursors(edit_pos, removed, 0);
1455 {
1456 let mut d = self.data.lock();
1457 d.position = new_pos;
1458 d.anchor = new_pos;
1459 }
1460 inner.modified = true;
1461 inner.invalidate_text_cache();
1462 inner.rehighlight_affected(edit_pos);
1463 inner.queue_event(DocumentEvent::ContentsChanged {
1464 position: edit_pos,
1465 chars_removed: removed,
1466 chars_added: 0,
1467 blocks_affected: 1,
1468 });
1469 inner.check_block_count_changed();
1470 inner.check_flow_changed();
1471 self.queue_undo_redo_event(&mut inner)
1472 };
1473 crate::inner::dispatch_queued_events(queued);
1474 Ok(())
1475 }
1476
1477 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
1479 let pos = self.position();
1480 match op {
1481 MoveOperation::NoMove => pos,
1482 MoveOperation::Start => 0,
1483 MoveOperation::End => {
1484 let inner = self.doc.lock();
1485 document_inspection_commands::get_document_stats(&inner.ctx)
1486 .map(|s| max_cursor_position(&s))
1487 .unwrap_or(pos)
1488 }
1489 MoveOperation::NextCharacter | MoveOperation::Right => pos + n,
1490 MoveOperation::PreviousCharacter | MoveOperation::Left => pos.saturating_sub(n),
1491 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
1492 let inner = self.doc.lock();
1493 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1494 position: to_i64(pos),
1495 };
1496 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1497 .map(|info| to_usize(info.block_start))
1498 .unwrap_or(pos)
1499 }
1500 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
1501 let inner = self.doc.lock();
1502 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1503 position: to_i64(pos),
1504 };
1505 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1506 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
1507 .unwrap_or(pos)
1508 }
1509 MoveOperation::NextBlock => {
1510 let inner = self.doc.lock();
1511 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1512 position: to_i64(pos),
1513 };
1514 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1515 .map(|info| {
1516 to_usize(info.block_start) + to_usize(info.block_length) + 1
1518 })
1519 .unwrap_or(pos)
1520 }
1521 MoveOperation::PreviousBlock => {
1522 let inner = self.doc.lock();
1523 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1524 position: to_i64(pos),
1525 };
1526 let block_start =
1527 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1528 .map(|info| to_usize(info.block_start))
1529 .unwrap_or(pos);
1530 if block_start >= 2 {
1531 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1533 position: to_i64(block_start - 2),
1534 };
1535 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
1536 .map(|info| to_usize(info.block_start))
1537 .unwrap_or(0)
1538 } else {
1539 0
1540 }
1541 }
1542 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
1543 let (_, end) = self.find_word_boundaries(pos);
1544 if end == pos {
1546 let inner = self.doc.lock();
1548 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
1549 .map(|s| max_cursor_position(&s))
1550 .unwrap_or(0);
1551 let scan_len = max_pos.saturating_sub(pos).min(64);
1552 if scan_len == 0 {
1553 return pos;
1554 }
1555 let dto = frontend::document_inspection::GetTextAtPositionDto {
1556 position: to_i64(pos),
1557 length: to_i64(scan_len),
1558 };
1559 if let Ok(r) =
1560 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
1561 {
1562 for (i, ch) in r.text.chars().enumerate() {
1563 if ch.is_alphanumeric() || ch == '_' {
1564 let word_pos = pos + i;
1566 drop(inner);
1567 let (_, word_end) = self.find_word_boundaries(word_pos);
1568 return word_end;
1569 }
1570 }
1571 }
1572 pos + scan_len
1573 } else {
1574 end
1575 }
1576 }
1577 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
1578 let (start, _) = self.find_word_boundaries(pos);
1579 if start < pos {
1580 start
1581 } else if pos > 0 {
1582 let mut search = pos - 1;
1585 loop {
1586 let (ws, we) = self.find_word_boundaries(search);
1587 if ws < we {
1588 break ws;
1590 }
1591 if search == 0 {
1593 break 0;
1594 }
1595 search -= 1;
1596 }
1597 } else {
1598 0
1599 }
1600 }
1601 MoveOperation::Up | MoveOperation::Down => {
1602 if matches!(op, MoveOperation::Up) {
1605 self.resolve_move(MoveOperation::PreviousBlock, 1)
1606 } else {
1607 self.resolve_move(MoveOperation::NextBlock, 1)
1608 }
1609 }
1610 }
1611 }
1612
1613 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
1619 let inner = self.doc.lock();
1620 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
1622 position: to_i64(pos),
1623 };
1624 let block_info =
1625 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
1626 Ok(info) => info,
1627 Err(_) => return (pos, pos),
1628 };
1629
1630 let block_start = to_usize(block_info.block_start);
1631 let block_length = to_usize(block_info.block_length);
1632 if block_length == 0 {
1633 return (pos, pos);
1634 }
1635
1636 let dto = frontend::document_inspection::GetTextAtPositionDto {
1637 position: to_i64(block_start),
1638 length: to_i64(block_length),
1639 };
1640 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
1641 Ok(r) => r.text,
1642 Err(_) => return (pos, pos),
1643 };
1644
1645 let cursor_offset = pos.saturating_sub(block_start);
1647
1648 let mut last_char_start = 0;
1650 let mut last_char_end = 0;
1651
1652 for (word_byte_start, word) in text.unicode_word_indices() {
1653 let word_char_start = text[..word_byte_start].chars().count();
1655 let word_char_len = word.chars().count();
1656 let word_char_end = word_char_start + word_char_len;
1657
1658 last_char_start = word_char_start;
1659 last_char_end = word_char_end;
1660
1661 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
1662 return (block_start + word_char_start, block_start + word_char_end);
1663 }
1664 }
1665
1666 if cursor_offset == last_char_end && last_char_start < last_char_end {
1668 return (block_start + last_char_start, block_start + last_char_end);
1669 }
1670
1671 (pos, pos)
1672 }
1673}