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 let added = new_pos - edit_pos;
91 inner.adjust_cursors(edit_pos, removed, added);
92 {
93 let mut d = self.data.lock();
94 d.position = new_pos;
95 d.anchor = new_pos;
96 }
97 inner.modified = true;
98 inner.invalidate_text_cache();
99 inner.rehighlight_affected(edit_pos);
100 inner.queue_event(DocumentEvent::ContentsChanged {
101 position: edit_pos,
102 chars_removed: removed,
103 chars_added: added,
104 blocks_affected,
105 });
106 inner.check_block_count_changed();
107 inner.check_flow_changed();
108 self.queue_undo_redo_event(inner)
109 }
110
111 pub fn position(&self) -> usize {
115 self.data.lock().position
116 }
117
118 pub fn anchor(&self) -> usize {
120 self.data.lock().anchor
121 }
122
123 pub fn has_selection(&self) -> bool {
125 let d = self.data.lock();
126 d.position != d.anchor
127 }
128
129 pub fn selection_start(&self) -> usize {
131 let d = self.data.lock();
132 d.position.min(d.anchor)
133 }
134
135 pub fn selection_end(&self) -> usize {
137 let d = self.data.lock();
138 d.position.max(d.anchor)
139 }
140
141 pub fn selected_text(&self) -> Result<String> {
143 let (pos, anchor) = self.read_cursor();
144 if pos == anchor {
145 return Ok(String::new());
146 }
147 let start = pos.min(anchor);
148 let len = pos.max(anchor) - start;
149 let inner = self.doc.lock();
150 let dto = frontend::document_inspection::GetTextAtPositionDto {
151 position: to_i64(start),
152 length: to_i64(len),
153 };
154 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
155 Ok(result.text)
156 }
157
158 pub fn clear_selection(&self) {
160 let mut d = self.data.lock();
161 d.anchor = d.position;
162 }
163
164 pub fn at_block_start(&self) -> bool {
168 let pos = self.position();
169 let inner = self.doc.lock();
170 let dto = frontend::document_inspection::GetBlockAtPositionDto {
171 position: to_i64(pos),
172 };
173 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
174 pos == to_usize(info.block_start)
175 } else {
176 false
177 }
178 }
179
180 pub fn at_block_end(&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) + to_usize(info.block_length)
189 } else {
190 false
191 }
192 }
193
194 pub fn at_start(&self) -> bool {
196 self.data.lock().position == 0
197 }
198
199 pub fn at_end(&self) -> bool {
201 let pos = self.position();
202 let inner = self.doc.lock();
203 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
204 frontend::document_inspection::DocumentStatsDto {
205 character_count: 0,
206 word_count: 0,
207 block_count: 0,
208 frame_count: 0,
209 image_count: 0,
210 list_count: 0,
211 table_count: 0,
212 }
213 });
214 pos >= max_cursor_position(&stats)
215 }
216
217 pub fn block_number(&self) -> usize {
219 let pos = self.position();
220 let inner = self.doc.lock();
221 let dto = frontend::document_inspection::GetBlockAtPositionDto {
222 position: to_i64(pos),
223 };
224 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
225 .map(|info| to_usize(info.block_number))
226 .unwrap_or(0)
227 }
228
229 pub fn position_in_block(&self) -> usize {
231 let pos = self.position();
232 let inner = self.doc.lock();
233 let dto = frontend::document_inspection::GetBlockAtPositionDto {
234 position: to_i64(pos),
235 };
236 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
237 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
238 .unwrap_or(0)
239 }
240
241 pub fn set_position(&self, position: usize, mode: MoveMode) {
245 let end = {
247 let inner = self.doc.lock();
248 document_inspection_commands::get_document_stats(&inner.ctx)
249 .map(|s| max_cursor_position(&s))
250 .unwrap_or(0)
251 };
252 let pos = position.min(end);
253 let mut d = self.data.lock();
254 d.position = pos;
255 if mode == MoveMode::MoveAnchor {
256 d.anchor = pos;
257 }
258 }
259
260 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
266 let old_pos = self.position();
267 let target = self.resolve_move(operation, n);
268 self.set_position(target, mode);
269 self.position() != old_pos
270 }
271
272 pub fn select(&self, selection: SelectionType) {
274 match selection {
275 SelectionType::Document => {
276 let end = {
277 let inner = self.doc.lock();
278 document_inspection_commands::get_document_stats(&inner.ctx)
279 .map(|s| max_cursor_position(&s))
280 .unwrap_or(0)
281 };
282 let mut d = self.data.lock();
283 d.anchor = 0;
284 d.position = end;
285 }
286 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
287 let pos = self.position();
288 let inner = self.doc.lock();
289 let dto = frontend::document_inspection::GetBlockAtPositionDto {
290 position: to_i64(pos),
291 };
292 if let Ok(info) =
293 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
294 {
295 let start = to_usize(info.block_start);
296 let end = start + to_usize(info.block_length);
297 drop(inner);
298 let mut d = self.data.lock();
299 d.anchor = start;
300 d.position = end;
301 }
302 }
303 SelectionType::WordUnderCursor => {
304 let pos = self.position();
305 let (word_start, word_end) = self.find_word_boundaries(pos);
306 let mut d = self.data.lock();
307 d.anchor = word_start;
308 d.position = word_end;
309 }
310 }
311 }
312
313 pub fn insert_text(&self, text: &str) -> Result<()> {
317 let (pos, anchor) = self.read_cursor();
318
319 let dto = frontend::document_editing::InsertTextDto {
321 position: to_i64(pos),
322 anchor: to_i64(anchor),
323 text: text.into(),
324 };
325
326 let queued = {
327 let mut inner = self.doc.lock();
328 let result = match document_editing_commands::insert_text(
329 &inner.ctx,
330 Some(inner.stack_id),
331 &dto,
332 ) {
333 Ok(r) => r,
334 Err(_) if pos != anchor => {
335 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
337
338 let del_dto = frontend::document_editing::DeleteTextDto {
339 position: to_i64(pos),
340 anchor: to_i64(anchor),
341 };
342 let del_result = document_editing_commands::delete_text(
343 &inner.ctx,
344 Some(inner.stack_id),
345 &del_dto,
346 )?;
347 let del_pos = to_usize(del_result.new_position);
348
349 let ins_dto = frontend::document_editing::InsertTextDto {
350 position: to_i64(del_pos),
351 anchor: to_i64(del_pos),
352 text: text.into(),
353 };
354 let ins_result = document_editing_commands::insert_text(
355 &inner.ctx,
356 Some(inner.stack_id),
357 &ins_dto,
358 )?;
359
360 undo_redo_commands::end_composite(&inner.ctx);
361 ins_result
362 }
363 Err(e) => return Err(e),
364 };
365
366 let edit_pos = pos.min(anchor);
367 let removed = pos.max(anchor) - edit_pos;
368 self.finish_edit(
369 &mut inner,
370 edit_pos,
371 removed,
372 to_usize(result.new_position),
373 to_usize(result.blocks_affected),
374 )
375 };
376 crate::inner::dispatch_queued_events(queued);
377 Ok(())
378 }
379
380 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
382 let (pos, anchor) = self.read_cursor();
383 let queued = {
384 let mut inner = self.doc.lock();
385 let dto = frontend::document_editing::InsertFormattedTextDto {
386 position: to_i64(pos),
387 anchor: to_i64(anchor),
388 text: text.into(),
389 font_family: format.font_family.clone().unwrap_or_default(),
390 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
391 font_bold: format.font_bold.unwrap_or(false),
392 font_italic: format.font_italic.unwrap_or(false),
393 font_underline: format.font_underline.unwrap_or(false),
394 font_strikeout: format.font_strikeout.unwrap_or(false),
395 };
396 let result = document_editing_commands::insert_formatted_text(
397 &inner.ctx,
398 Some(inner.stack_id),
399 &dto,
400 )?;
401 let edit_pos = pos.min(anchor);
402 let removed = pos.max(anchor) - edit_pos;
403 self.finish_edit(
404 &mut inner,
405 edit_pos,
406 removed,
407 to_usize(result.new_position),
408 1,
409 )
410 };
411 crate::inner::dispatch_queued_events(queued);
412 Ok(())
413 }
414
415 pub fn insert_block(&self) -> Result<()> {
417 let (pos, anchor) = self.read_cursor();
418 let queued = {
419 let mut inner = self.doc.lock();
420 let dto = frontend::document_editing::InsertBlockDto {
421 position: to_i64(pos),
422 anchor: to_i64(anchor),
423 };
424 let result =
425 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
426 let edit_pos = pos.min(anchor);
427 let removed = pos.max(anchor) - edit_pos;
428 self.finish_edit(
429 &mut inner,
430 edit_pos,
431 removed,
432 to_usize(result.new_position),
433 2,
434 )
435 };
436 crate::inner::dispatch_queued_events(queued);
437 Ok(())
438 }
439
440 pub fn insert_html(&self, html: &str) -> Result<()> {
442 let (pos, anchor) = self.read_cursor();
443 let queued = {
444 let mut inner = self.doc.lock();
445 let dto = frontend::document_editing::InsertHtmlAtPositionDto {
446 position: to_i64(pos),
447 anchor: to_i64(anchor),
448 html: html.into(),
449 };
450 let result = document_editing_commands::insert_html_at_position(
451 &inner.ctx,
452 Some(inner.stack_id),
453 &dto,
454 )?;
455 let edit_pos = pos.min(anchor);
456 let removed = pos.max(anchor) - edit_pos;
457 self.finish_edit(
458 &mut inner,
459 edit_pos,
460 removed,
461 to_usize(result.new_position),
462 to_usize(result.blocks_added),
463 )
464 };
465 crate::inner::dispatch_queued_events(queued);
466 Ok(())
467 }
468
469 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
471 let (pos, anchor) = self.read_cursor();
472 let queued = {
473 let mut inner = self.doc.lock();
474 let dto = frontend::document_editing::InsertMarkdownAtPositionDto {
475 position: to_i64(pos),
476 anchor: to_i64(anchor),
477 markdown: markdown.into(),
478 };
479 let result = document_editing_commands::insert_markdown_at_position(
480 &inner.ctx,
481 Some(inner.stack_id),
482 &dto,
483 )?;
484 let edit_pos = pos.min(anchor);
485 let removed = pos.max(anchor) - edit_pos;
486 self.finish_edit(
487 &mut inner,
488 edit_pos,
489 removed,
490 to_usize(result.new_position),
491 to_usize(result.blocks_added),
492 )
493 };
494 crate::inner::dispatch_queued_events(queued);
495 Ok(())
496 }
497
498 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
500 let (pos, anchor) = self.read_cursor();
501 let queued = {
502 let mut inner = self.doc.lock();
503 let dto = frontend::document_editing::InsertFragmentDto {
504 position: to_i64(pos),
505 anchor: to_i64(anchor),
506 fragment_data: fragment.raw_data().into(),
507 };
508 let result =
509 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
510 let edit_pos = pos.min(anchor);
511 let removed = pos.max(anchor) - edit_pos;
512 self.finish_edit(
513 &mut inner,
514 edit_pos,
515 removed,
516 to_usize(result.new_position),
517 to_usize(result.blocks_added),
518 )
519 };
520 crate::inner::dispatch_queued_events(queued);
521 Ok(())
522 }
523
524 pub fn selection(&self) -> DocumentFragment {
526 let (pos, anchor) = self.read_cursor();
527 if pos == anchor {
528 return DocumentFragment::new();
529 }
530 let inner = self.doc.lock();
531 let dto = frontend::document_inspection::ExtractFragmentDto {
532 position: to_i64(pos),
533 anchor: to_i64(anchor),
534 };
535 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
536 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
537 Err(_) => DocumentFragment::new(),
538 }
539 }
540
541 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
543 let (pos, anchor) = self.read_cursor();
544 let queued = {
545 let mut inner = self.doc.lock();
546 let dto = frontend::document_editing::InsertImageDto {
547 position: to_i64(pos),
548 anchor: to_i64(anchor),
549 image_name: name.into(),
550 width: width as i64,
551 height: height as i64,
552 };
553 let result =
554 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
555 let edit_pos = pos.min(anchor);
556 let removed = pos.max(anchor) - edit_pos;
557 self.finish_edit(
558 &mut inner,
559 edit_pos,
560 removed,
561 to_usize(result.new_position),
562 1,
563 )
564 };
565 crate::inner::dispatch_queued_events(queued);
566 Ok(())
567 }
568
569 pub fn insert_frame(&self) -> Result<()> {
571 let (pos, anchor) = self.read_cursor();
572 let queued = {
573 let mut inner = self.doc.lock();
574 let dto = frontend::document_editing::InsertFrameDto {
575 position: to_i64(pos),
576 anchor: to_i64(anchor),
577 };
578 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
579 inner.modified = true;
582 inner.invalidate_text_cache();
583 inner.rehighlight_affected(pos.min(anchor));
584 inner.queue_event(DocumentEvent::ContentsChanged {
585 position: pos.min(anchor),
586 chars_removed: 0,
587 chars_added: 0,
588 blocks_affected: 1,
589 });
590 inner.check_block_count_changed();
591 inner.check_flow_changed();
592 self.queue_undo_redo_event(&mut inner)
593 };
594 crate::inner::dispatch_queued_events(queued);
595 Ok(())
596 }
597
598 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
604 let (pos, anchor) = self.read_cursor();
605 let (table_id, queued) = {
606 let mut inner = self.doc.lock();
607 let dto = frontend::document_editing::InsertTableDto {
608 position: to_i64(pos),
609 anchor: to_i64(anchor),
610 rows: to_i64(rows),
611 columns: to_i64(columns),
612 };
613 let result =
614 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
615 let new_pos = to_usize(result.new_position);
616 let table_id = to_usize(result.table_id);
617 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
618 {
619 let mut d = self.data.lock();
620 d.position = new_pos;
621 d.anchor = new_pos;
622 }
623 inner.modified = true;
624 inner.invalidate_text_cache();
625 inner.rehighlight_affected(pos.min(anchor));
626 inner.queue_event(DocumentEvent::ContentsChanged {
627 position: pos.min(anchor),
628 chars_removed: 0,
629 chars_added: new_pos - pos.min(anchor),
630 blocks_affected: 1,
631 });
632 inner.check_block_count_changed();
633 inner.check_flow_changed();
634 (table_id, self.queue_undo_redo_event(&mut inner))
635 };
636 crate::inner::dispatch_queued_events(queued);
637 Ok(TextTable {
638 doc: self.doc.clone(),
639 table_id,
640 })
641 }
642
643 pub fn current_table(&self) -> Option<TextTable> {
648 self.current_table_cell().map(|c| c.table)
649 }
650
651 pub fn current_table_cell(&self) -> Option<TableCellRef> {
656 let pos = self.position();
657 let inner = self.doc.lock();
658 let dto = frontend::document_inspection::GetBlockAtPositionDto {
660 position: to_i64(pos),
661 };
662 let block_info =
663 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
664 let block = crate::text_block::TextBlock {
665 doc: self.doc.clone(),
666 block_id: block_info.block_id as usize,
667 };
668 drop(inner);
670 block.table_cell()
671 }
672
673 pub fn remove_table(&self, table_id: usize) -> Result<()> {
677 let queued = {
678 let mut inner = self.doc.lock();
679 let dto = frontend::document_editing::RemoveTableDto {
680 table_id: to_i64(table_id),
681 };
682 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
683 inner.modified = true;
684 inner.invalidate_text_cache();
685 inner.rehighlight_all();
686 inner.check_block_count_changed();
687 inner.check_flow_changed();
688 self.queue_undo_redo_event(&mut inner)
689 };
690 crate::inner::dispatch_queued_events(queued);
691 Ok(())
692 }
693
694 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
696 let queued = {
697 let mut inner = self.doc.lock();
698 let dto = frontend::document_editing::InsertTableRowDto {
699 table_id: to_i64(table_id),
700 row_index: to_i64(row_index),
701 };
702 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
703 inner.modified = true;
704 inner.invalidate_text_cache();
705 inner.rehighlight_all();
706 inner.check_block_count_changed();
707 self.queue_undo_redo_event(&mut inner)
708 };
709 crate::inner::dispatch_queued_events(queued);
710 Ok(())
711 }
712
713 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
715 let queued = {
716 let mut inner = self.doc.lock();
717 let dto = frontend::document_editing::InsertTableColumnDto {
718 table_id: to_i64(table_id),
719 column_index: to_i64(column_index),
720 };
721 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
722 inner.modified = true;
723 inner.invalidate_text_cache();
724 inner.rehighlight_all();
725 inner.check_block_count_changed();
726 self.queue_undo_redo_event(&mut inner)
727 };
728 crate::inner::dispatch_queued_events(queued);
729 Ok(())
730 }
731
732 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
734 let queued = {
735 let mut inner = self.doc.lock();
736 let dto = frontend::document_editing::RemoveTableRowDto {
737 table_id: to_i64(table_id),
738 row_index: to_i64(row_index),
739 };
740 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
741 inner.modified = true;
742 inner.invalidate_text_cache();
743 inner.rehighlight_all();
744 inner.check_block_count_changed();
745 self.queue_undo_redo_event(&mut inner)
746 };
747 crate::inner::dispatch_queued_events(queued);
748 Ok(())
749 }
750
751 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
753 let queued = {
754 let mut inner = self.doc.lock();
755 let dto = frontend::document_editing::RemoveTableColumnDto {
756 table_id: to_i64(table_id),
757 column_index: to_i64(column_index),
758 };
759 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
760 inner.modified = true;
761 inner.invalidate_text_cache();
762 inner.rehighlight_all();
763 inner.check_block_count_changed();
764 self.queue_undo_redo_event(&mut inner)
765 };
766 crate::inner::dispatch_queued_events(queued);
767 Ok(())
768 }
769
770 pub fn merge_table_cells(
772 &self,
773 table_id: usize,
774 start_row: usize,
775 start_column: usize,
776 end_row: usize,
777 end_column: usize,
778 ) -> Result<()> {
779 let queued = {
780 let mut inner = self.doc.lock();
781 let dto = frontend::document_editing::MergeTableCellsDto {
782 table_id: to_i64(table_id),
783 start_row: to_i64(start_row),
784 start_column: to_i64(start_column),
785 end_row: to_i64(end_row),
786 end_column: to_i64(end_column),
787 };
788 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
789 inner.modified = true;
790 inner.invalidate_text_cache();
791 inner.rehighlight_all();
792 inner.check_block_count_changed();
793 self.queue_undo_redo_event(&mut inner)
794 };
795 crate::inner::dispatch_queued_events(queued);
796 Ok(())
797 }
798
799 pub fn split_table_cell(
801 &self,
802 cell_id: usize,
803 split_rows: usize,
804 split_columns: usize,
805 ) -> Result<()> {
806 let queued = {
807 let mut inner = self.doc.lock();
808 let dto = frontend::document_editing::SplitTableCellDto {
809 cell_id: to_i64(cell_id),
810 split_rows: to_i64(split_rows),
811 split_columns: to_i64(split_columns),
812 };
813 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
814 inner.modified = true;
815 inner.invalidate_text_cache();
816 inner.rehighlight_all();
817 inner.check_block_count_changed();
818 self.queue_undo_redo_event(&mut inner)
819 };
820 crate::inner::dispatch_queued_events(queued);
821 Ok(())
822 }
823
824 pub fn set_table_format(
828 &self,
829 table_id: usize,
830 format: &crate::flow::TableFormat,
831 ) -> Result<()> {
832 let queued = {
833 let mut inner = self.doc.lock();
834 let dto = format.to_set_dto(table_id);
835 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
836 inner.modified = true;
837 inner.queue_event(DocumentEvent::FormatChanged {
838 position: 0,
839 length: 0,
840 kind: crate::flow::FormatChangeKind::Block,
841 });
842 self.queue_undo_redo_event(&mut inner)
843 };
844 crate::inner::dispatch_queued_events(queued);
845 Ok(())
846 }
847
848 pub fn set_table_cell_format(
850 &self,
851 cell_id: usize,
852 format: &crate::flow::CellFormat,
853 ) -> Result<()> {
854 let queued = {
855 let mut inner = self.doc.lock();
856 let dto = format.to_set_dto(cell_id);
857 document_formatting_commands::set_table_cell_format(
858 &inner.ctx,
859 Some(inner.stack_id),
860 &dto,
861 )?;
862 inner.modified = true;
863 inner.queue_event(DocumentEvent::FormatChanged {
864 position: 0,
865 length: 0,
866 kind: crate::flow::FormatChangeKind::Block,
867 });
868 self.queue_undo_redo_event(&mut inner)
869 };
870 crate::inner::dispatch_queued_events(queued);
871 Ok(())
872 }
873
874 pub fn remove_current_table(&self) -> Result<()> {
879 let table = self
880 .current_table()
881 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
882 self.remove_table(table.id())
883 }
884
885 pub fn insert_row_above(&self) -> Result<()> {
888 let cell_ref = self
889 .current_table_cell()
890 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
891 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
892 }
893
894 pub fn insert_row_below(&self) -> Result<()> {
897 let cell_ref = self
898 .current_table_cell()
899 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
900 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
901 }
902
903 pub fn insert_column_before(&self) -> Result<()> {
906 let cell_ref = self
907 .current_table_cell()
908 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
909 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
910 }
911
912 pub fn insert_column_after(&self) -> Result<()> {
915 let cell_ref = self
916 .current_table_cell()
917 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
918 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
919 }
920
921 pub fn remove_current_row(&self) -> Result<()> {
924 let cell_ref = self
925 .current_table_cell()
926 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
927 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
928 }
929
930 pub fn remove_current_column(&self) -> Result<()> {
933 let cell_ref = self
934 .current_table_cell()
935 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
936 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
937 }
938
939 pub fn merge_selected_cells(&self) -> Result<()> {
946 let pos_cell = self
947 .current_table_cell()
948 .ok_or_else(|| anyhow::anyhow!("cursor position is not inside a table"))?;
949
950 let (_pos, anchor) = self.read_cursor();
952 let anchor_cell = {
953 let inner = self.doc.lock();
955 let dto = frontend::document_inspection::GetBlockAtPositionDto {
956 position: to_i64(anchor),
957 };
958 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
959 .map_err(|_| anyhow::anyhow!("cursor anchor is not inside a table"))?;
960 let block = crate::text_block::TextBlock {
961 doc: self.doc.clone(),
962 block_id: block_info.block_id as usize,
963 };
964 drop(inner);
965 block
966 .table_cell()
967 .ok_or_else(|| anyhow::anyhow!("cursor anchor is not inside a table"))?
968 };
969
970 if pos_cell.table.id() != anchor_cell.table.id() {
971 return Err(anyhow::anyhow!(
972 "position and anchor are in different tables"
973 ));
974 }
975
976 let start_row = pos_cell.row.min(anchor_cell.row);
977 let start_col = pos_cell.column.min(anchor_cell.column);
978 let end_row = pos_cell.row.max(anchor_cell.row);
979 let end_col = pos_cell.column.max(anchor_cell.column);
980
981 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
982 }
983
984 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
987 let cell_ref = self
988 .current_table_cell()
989 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
990 let cell = cell_ref
992 .table
993 .cell(cell_ref.row, cell_ref.column)
994 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
995 self.split_table_cell(cell.id(), split_rows, split_columns)
997 }
998
999 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1002 let table = self
1003 .current_table()
1004 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1005 self.set_table_format(table.id(), format)
1006 }
1007
1008 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1011 let cell_ref = self
1012 .current_table_cell()
1013 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1014 let cell = cell_ref
1015 .table
1016 .cell(cell_ref.row, cell_ref.column)
1017 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1018 self.set_table_cell_format(cell.id(), format)
1019 }
1020
1021 pub fn delete_char(&self) -> Result<()> {
1023 let (pos, anchor) = self.read_cursor();
1024 let (del_pos, del_anchor) = if pos != anchor {
1025 (pos, anchor)
1026 } else {
1027 (pos, pos + 1)
1028 };
1029 self.do_delete(del_pos, del_anchor)
1030 }
1031
1032 pub fn delete_previous_char(&self) -> Result<()> {
1034 let (pos, anchor) = self.read_cursor();
1035 let (del_pos, del_anchor) = if pos != anchor {
1036 (pos, anchor)
1037 } else if pos > 0 {
1038 (pos - 1, pos)
1039 } else {
1040 return Ok(());
1041 };
1042 self.do_delete(del_pos, del_anchor)
1043 }
1044
1045 pub fn remove_selected_text(&self) -> Result<String> {
1047 let (pos, anchor) = self.read_cursor();
1048 if pos == anchor {
1049 return Ok(String::new());
1050 }
1051 let queued = {
1052 let mut inner = self.doc.lock();
1053 let dto = frontend::document_editing::DeleteTextDto {
1054 position: to_i64(pos),
1055 anchor: to_i64(anchor),
1056 };
1057 let result =
1058 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1059 let edit_pos = pos.min(anchor);
1060 let removed = pos.max(anchor) - edit_pos;
1061 let new_pos = to_usize(result.new_position);
1062 inner.adjust_cursors(edit_pos, removed, 0);
1063 {
1064 let mut d = self.data.lock();
1065 d.position = new_pos;
1066 d.anchor = new_pos;
1067 }
1068 inner.modified = true;
1069 inner.invalidate_text_cache();
1070 inner.rehighlight_affected(edit_pos);
1071 inner.queue_event(DocumentEvent::ContentsChanged {
1072 position: edit_pos,
1073 chars_removed: removed,
1074 chars_added: 0,
1075 blocks_affected: 1,
1076 });
1077 inner.check_block_count_changed();
1078 inner.check_flow_changed();
1079 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1081 };
1082 crate::inner::dispatch_queued_events(queued.1);
1083 Ok(queued.0)
1084 }
1085
1086 pub fn current_list(&self) -> Option<crate::TextList> {
1091 let pos = self.position();
1092 let inner = self.doc.lock();
1093 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1094 position: to_i64(pos),
1095 };
1096 let block_info =
1097 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1098 let block = crate::text_block::TextBlock {
1099 doc: self.doc.clone(),
1100 block_id: block_info.block_id as usize,
1101 };
1102 drop(inner);
1103 block.list()
1104 }
1105
1106 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1108 let (pos, anchor) = self.read_cursor();
1109 let queued = {
1110 let mut inner = self.doc.lock();
1111 let dto = frontend::document_editing::CreateListDto {
1112 position: to_i64(pos),
1113 anchor: to_i64(anchor),
1114 style: style.clone(),
1115 };
1116 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1117 inner.modified = true;
1118 inner.rehighlight_affected(pos.min(anchor));
1119 inner.queue_event(DocumentEvent::ContentsChanged {
1120 position: pos.min(anchor),
1121 chars_removed: 0,
1122 chars_added: 0,
1123 blocks_affected: 1,
1124 });
1125 self.queue_undo_redo_event(&mut inner)
1126 };
1127 crate::inner::dispatch_queued_events(queued);
1128 Ok(())
1129 }
1130
1131 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
1133 let (pos, anchor) = self.read_cursor();
1134 let queued = {
1135 let mut inner = self.doc.lock();
1136 let dto = frontend::document_editing::InsertListDto {
1137 position: to_i64(pos),
1138 anchor: to_i64(anchor),
1139 style: style.clone(),
1140 };
1141 let result =
1142 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1143 let edit_pos = pos.min(anchor);
1144 let removed = pos.max(anchor) - edit_pos;
1145 self.finish_edit(
1146 &mut inner,
1147 edit_pos,
1148 removed,
1149 to_usize(result.new_position),
1150 1,
1151 )
1152 };
1153 crate::inner::dispatch_queued_events(queued);
1154 Ok(())
1155 }
1156
1157 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
1159 let queued = {
1160 let mut inner = self.doc.lock();
1161 let dto = format.to_set_dto(list_id);
1162 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1163 inner.modified = true;
1164 inner.queue_event(DocumentEvent::FormatChanged {
1165 position: 0,
1166 length: 0,
1167 kind: crate::flow::FormatChangeKind::List,
1168 });
1169 self.queue_undo_redo_event(&mut inner)
1170 };
1171 crate::inner::dispatch_queued_events(queued);
1172 Ok(())
1173 }
1174
1175 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
1178 let list = self
1179 .current_list()
1180 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a list"))?;
1181 self.set_list_format(list.id(), format)
1182 }
1183
1184 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
1186 let queued = {
1187 let mut inner = self.doc.lock();
1188 let dto = frontend::document_editing::AddBlockToListDto {
1189 block_id: to_i64(block_id),
1190 list_id: to_i64(list_id),
1191 };
1192 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1193 inner.modified = true;
1194 inner.queue_event(DocumentEvent::ContentsChanged {
1195 position: 0,
1196 chars_removed: 0,
1197 chars_added: 0,
1198 blocks_affected: 1,
1199 });
1200 self.queue_undo_redo_event(&mut inner)
1201 };
1202 crate::inner::dispatch_queued_events(queued);
1203 Ok(())
1204 }
1205
1206 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
1208 let pos = self.position();
1209 let inner = self.doc.lock();
1210 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1211 position: to_i64(pos),
1212 };
1213 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1214 drop(inner);
1215 self.add_block_to_list(block_info.block_id as usize, list_id)
1216 }
1217
1218 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
1220 let queued = {
1221 let mut inner = self.doc.lock();
1222 let dto = frontend::document_editing::RemoveBlockFromListDto {
1223 block_id: to_i64(block_id),
1224 };
1225 document_editing_commands::remove_block_from_list(
1226 &inner.ctx,
1227 Some(inner.stack_id),
1228 &dto,
1229 )?;
1230 inner.modified = true;
1231 inner.queue_event(DocumentEvent::ContentsChanged {
1232 position: 0,
1233 chars_removed: 0,
1234 chars_added: 0,
1235 blocks_affected: 1,
1236 });
1237 self.queue_undo_redo_event(&mut inner)
1238 };
1239 crate::inner::dispatch_queued_events(queued);
1240 Ok(())
1241 }
1242
1243 pub fn remove_current_block_from_list(&self) -> Result<()> {
1246 let pos = self.position();
1247 let inner = self.doc.lock();
1248 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1249 position: to_i64(pos),
1250 };
1251 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1252 drop(inner);
1253 self.remove_block_from_list(block_info.block_id as usize)
1254 }
1255
1256 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
1259 let list = crate::text_list::TextList {
1260 doc: self.doc.clone(),
1261 list_id,
1262 };
1263 let block = list
1264 .item(index)
1265 .ok_or_else(|| anyhow::anyhow!("list item index {index} out of range"))?;
1266 self.remove_block_from_list(block.id())
1267 }
1268
1269 pub fn char_format(&self) -> Result<TextFormat> {
1273 let pos = self.position();
1274 let inner = self.doc.lock();
1275 let dto = frontend::document_inspection::GetTextAtPositionDto {
1276 position: to_i64(pos),
1277 length: 1,
1278 };
1279 let text_info = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
1280 let element_id = text_info.element_id as u64;
1281 let element = inline_element_commands::get_inline_element(&inner.ctx, &element_id)?
1282 .ok_or_else(|| anyhow::anyhow!("element not found at position"))?;
1283 Ok(TextFormat::from(&element))
1284 }
1285
1286 pub fn block_format(&self) -> Result<BlockFormat> {
1288 let pos = self.position();
1289 let inner = self.doc.lock();
1290 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1291 position: to_i64(pos),
1292 };
1293 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1294 let block_id = block_info.block_id as u64;
1295 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
1296 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
1297 Ok(BlockFormat::from(&block))
1298 }
1299
1300 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
1304 let (pos, anchor) = self.read_cursor();
1305 let queued = {
1306 let mut inner = self.doc.lock();
1307 let dto = format.to_set_dto(pos, anchor);
1308 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1309 let start = pos.min(anchor);
1310 let length = pos.max(anchor) - start;
1311 inner.modified = true;
1312 inner.queue_event(DocumentEvent::FormatChanged {
1313 position: start,
1314 length,
1315 kind: crate::flow::FormatChangeKind::Character,
1316 });
1317 self.queue_undo_redo_event(&mut inner)
1318 };
1319 crate::inner::dispatch_queued_events(queued);
1320 Ok(())
1321 }
1322
1323 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
1325 let (pos, anchor) = self.read_cursor();
1326 let queued = {
1327 let mut inner = self.doc.lock();
1328 let dto = format.to_merge_dto(pos, anchor);
1329 document_formatting_commands::merge_text_format(
1330 &inner.ctx,
1331 Some(inner.stack_id),
1332 &dto,
1333 )?;
1334 let start = pos.min(anchor);
1335 let length = pos.max(anchor) - start;
1336 inner.modified = true;
1337 inner.queue_event(DocumentEvent::FormatChanged {
1338 position: start,
1339 length,
1340 kind: crate::flow::FormatChangeKind::Character,
1341 });
1342 self.queue_undo_redo_event(&mut inner)
1343 };
1344 crate::inner::dispatch_queued_events(queued);
1345 Ok(())
1346 }
1347
1348 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
1350 let (pos, anchor) = self.read_cursor();
1351 let queued = {
1352 let mut inner = self.doc.lock();
1353 let dto = format.to_set_dto(pos, anchor);
1354 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1355 let start = pos.min(anchor);
1356 let length = pos.max(anchor) - start;
1357 inner.modified = true;
1358 inner.queue_event(DocumentEvent::FormatChanged {
1359 position: start,
1360 length,
1361 kind: crate::flow::FormatChangeKind::Block,
1362 });
1363 self.queue_undo_redo_event(&mut inner)
1364 };
1365 crate::inner::dispatch_queued_events(queued);
1366 Ok(())
1367 }
1368
1369 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
1371 let (pos, anchor) = self.read_cursor();
1372 let queued = {
1373 let mut inner = self.doc.lock();
1374 let dto = format.to_set_dto(pos, anchor, frame_id);
1375 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1376 let start = pos.min(anchor);
1377 let length = pos.max(anchor) - start;
1378 inner.modified = true;
1379 inner.queue_event(DocumentEvent::FormatChanged {
1380 position: start,
1381 length,
1382 kind: crate::flow::FormatChangeKind::Block,
1383 });
1384 self.queue_undo_redo_event(&mut inner)
1385 };
1386 crate::inner::dispatch_queued_events(queued);
1387 Ok(())
1388 }
1389
1390 pub fn begin_edit_block(&self) {
1394 let inner = self.doc.lock();
1395 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1396 }
1397
1398 pub fn end_edit_block(&self) {
1400 let inner = self.doc.lock();
1401 undo_redo_commands::end_composite(&inner.ctx);
1402 }
1403
1404 pub fn join_previous_edit_block(&self) {
1411 self.begin_edit_block();
1412 }
1413
1414 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
1418 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1419 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1420 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1421 inner.take_queued_events()
1422 }
1423
1424 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
1425 let queued = {
1426 let mut inner = self.doc.lock();
1427 let dto = frontend::document_editing::DeleteTextDto {
1428 position: to_i64(pos),
1429 anchor: to_i64(anchor),
1430 };
1431 let result =
1432 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1433 let edit_pos = pos.min(anchor);
1434 let removed = pos.max(anchor) - edit_pos;
1435 let new_pos = to_usize(result.new_position);
1436 inner.adjust_cursors(edit_pos, removed, 0);
1437 {
1438 let mut d = self.data.lock();
1439 d.position = new_pos;
1440 d.anchor = new_pos;
1441 }
1442 inner.modified = true;
1443 inner.invalidate_text_cache();
1444 inner.rehighlight_affected(edit_pos);
1445 inner.queue_event(DocumentEvent::ContentsChanged {
1446 position: edit_pos,
1447 chars_removed: removed,
1448 chars_added: 0,
1449 blocks_affected: 1,
1450 });
1451 inner.check_block_count_changed();
1452 inner.check_flow_changed();
1453 self.queue_undo_redo_event(&mut inner)
1454 };
1455 crate::inner::dispatch_queued_events(queued);
1456 Ok(())
1457 }
1458
1459 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
1461 let pos = self.position();
1462 match op {
1463 MoveOperation::NoMove => pos,
1464 MoveOperation::Start => 0,
1465 MoveOperation::End => {
1466 let inner = self.doc.lock();
1467 document_inspection_commands::get_document_stats(&inner.ctx)
1468 .map(|s| max_cursor_position(&s))
1469 .unwrap_or(pos)
1470 }
1471 MoveOperation::NextCharacter | MoveOperation::Right => pos + n,
1472 MoveOperation::PreviousCharacter | MoveOperation::Left => pos.saturating_sub(n),
1473 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
1474 let inner = self.doc.lock();
1475 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1476 position: to_i64(pos),
1477 };
1478 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1479 .map(|info| to_usize(info.block_start))
1480 .unwrap_or(pos)
1481 }
1482 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
1483 let inner = self.doc.lock();
1484 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1485 position: to_i64(pos),
1486 };
1487 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1488 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
1489 .unwrap_or(pos)
1490 }
1491 MoveOperation::NextBlock => {
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| {
1498 to_usize(info.block_start) + to_usize(info.block_length) + 1
1500 })
1501 .unwrap_or(pos)
1502 }
1503 MoveOperation::PreviousBlock => {
1504 let inner = self.doc.lock();
1505 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1506 position: to_i64(pos),
1507 };
1508 let block_start =
1509 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1510 .map(|info| to_usize(info.block_start))
1511 .unwrap_or(pos);
1512 if block_start >= 2 {
1513 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1515 position: to_i64(block_start - 2),
1516 };
1517 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
1518 .map(|info| to_usize(info.block_start))
1519 .unwrap_or(0)
1520 } else {
1521 0
1522 }
1523 }
1524 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
1525 let (_, end) = self.find_word_boundaries(pos);
1526 if end == pos {
1528 let inner = self.doc.lock();
1530 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
1531 .map(|s| max_cursor_position(&s))
1532 .unwrap_or(0);
1533 let scan_len = max_pos.saturating_sub(pos).min(64);
1534 if scan_len == 0 {
1535 return pos;
1536 }
1537 let dto = frontend::document_inspection::GetTextAtPositionDto {
1538 position: to_i64(pos),
1539 length: to_i64(scan_len),
1540 };
1541 if let Ok(r) =
1542 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
1543 {
1544 for (i, ch) in r.text.chars().enumerate() {
1545 if ch.is_alphanumeric() || ch == '_' {
1546 let word_pos = pos + i;
1548 drop(inner);
1549 let (_, word_end) = self.find_word_boundaries(word_pos);
1550 return word_end;
1551 }
1552 }
1553 }
1554 pos + scan_len
1555 } else {
1556 end
1557 }
1558 }
1559 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
1560 let (start, _) = self.find_word_boundaries(pos);
1561 if start < pos {
1562 start
1563 } else if pos > 0 {
1564 let mut search = pos - 1;
1567 loop {
1568 let (ws, we) = self.find_word_boundaries(search);
1569 if ws < we {
1570 break ws;
1572 }
1573 if search == 0 {
1575 break 0;
1576 }
1577 search -= 1;
1578 }
1579 } else {
1580 0
1581 }
1582 }
1583 MoveOperation::Up | MoveOperation::Down => {
1584 if matches!(op, MoveOperation::Up) {
1587 self.resolve_move(MoveOperation::PreviousBlock, 1)
1588 } else {
1589 self.resolve_move(MoveOperation::NextBlock, 1)
1590 }
1591 }
1592 }
1593 }
1594
1595 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
1601 let inner = self.doc.lock();
1602 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
1604 position: to_i64(pos),
1605 };
1606 let block_info =
1607 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
1608 Ok(info) => info,
1609 Err(_) => return (pos, pos),
1610 };
1611
1612 let block_start = to_usize(block_info.block_start);
1613 let block_length = to_usize(block_info.block_length);
1614 if block_length == 0 {
1615 return (pos, pos);
1616 }
1617
1618 let dto = frontend::document_inspection::GetTextAtPositionDto {
1619 position: to_i64(block_start),
1620 length: to_i64(block_length),
1621 };
1622 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
1623 Ok(r) => r.text,
1624 Err(_) => return (pos, pos),
1625 };
1626
1627 let cursor_offset = pos.saturating_sub(block_start);
1629
1630 let mut last_char_start = 0;
1632 let mut last_char_end = 0;
1633
1634 for (word_byte_start, word) in text.unicode_word_indices() {
1635 let word_char_start = text[..word_byte_start].chars().count();
1637 let word_char_len = word.chars().count();
1638 let word_char_end = word_char_start + word_char_len;
1639
1640 last_char_start = word_char_start;
1641 last_char_end = word_char_end;
1642
1643 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
1644 return (block_start + word_char_start, block_start + word_char_end);
1645 }
1646 }
1647
1648 if cursor_offset == last_char_end && last_char_start < last_char_end {
1650 return (block_start + last_char_start, block_start + last_char_end);
1651 }
1652
1653 (pos, pos)
1654 }
1655}