1use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use anyhow::Result;
8
9use frontend::commands::{
10 document_editing_commands, document_formatting_commands, document_inspection_commands,
11 inline_element_commands, undo_redo_commands,
12};
13use crate::ListStyle;
14
15use unicode_segmentation::UnicodeSegmentation;
16
17use crate::convert::{to_i64, to_usize};
18use crate::events::DocumentEvent;
19use crate::fragment::DocumentFragment;
20use crate::inner::{CursorData, TextDocumentInner};
21use crate::{BlockFormat, FrameFormat, MoveMode, MoveOperation, SelectionType, TextFormat};
22
23pub struct TextCursor {
31 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
32 pub(crate) data: Arc<Mutex<CursorData>>,
33}
34
35impl Clone for TextCursor {
36 fn clone(&self) -> Self {
37 let (position, anchor) = {
38 let d = self.data.lock();
39 (d.position, d.anchor)
40 };
41 let data = {
42 let mut inner = self.doc.lock();
43 let data = Arc::new(Mutex::new(CursorData { position, anchor }));
44 inner.cursors.push(Arc::downgrade(&data));
45 data
46 };
47 TextCursor {
48 doc: self.doc.clone(),
49 data,
50 }
51 }
52}
53
54impl TextCursor {
55 fn read_cursor(&self) -> (usize, usize) {
58 let d = self.data.lock();
59 (d.position, d.anchor)
60 }
61
62 fn finish_edit(
66 &self,
67 inner: &mut TextDocumentInner,
68 edit_pos: usize,
69 removed: usize,
70 new_pos: usize,
71 blocks_affected: usize,
72 ) -> Vec<(DocumentEvent, Vec<Arc<dyn Fn(DocumentEvent) + Send + Sync>>)> {
73 let added = new_pos - edit_pos;
74 inner.adjust_cursors(edit_pos, removed, added);
75 {
76 let mut d = self.data.lock();
77 d.position = new_pos;
78 d.anchor = new_pos;
79 }
80 inner.modified = true;
81 inner.invalidate_text_cache();
82 inner.queue_event(DocumentEvent::ContentsChanged {
83 position: edit_pos,
84 chars_removed: removed,
85 chars_added: added,
86 blocks_affected,
87 });
88 inner.take_queued_events()
89 }
90
91 pub fn position(&self) -> usize {
95 self.data.lock().position
96 }
97
98 pub fn anchor(&self) -> usize {
100 self.data.lock().anchor
101 }
102
103 pub fn has_selection(&self) -> bool {
105 let d = self.data.lock();
106 d.position != d.anchor
107 }
108
109 pub fn selection_start(&self) -> usize {
111 let d = self.data.lock();
112 d.position.min(d.anchor)
113 }
114
115 pub fn selection_end(&self) -> usize {
117 let d = self.data.lock();
118 d.position.max(d.anchor)
119 }
120
121 pub fn selected_text(&self) -> Result<String> {
123 let (pos, anchor) = self.read_cursor();
124 if pos == anchor {
125 return Ok(String::new());
126 }
127 let start = pos.min(anchor);
128 let len = pos.max(anchor) - start;
129 let inner = self.doc.lock();
130 let dto = frontend::document_inspection::GetTextAtPositionDto {
131 position: to_i64(start),
132 length: to_i64(len),
133 };
134 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
135 Ok(result.text)
136 }
137
138 pub fn clear_selection(&self) {
140 let mut d = self.data.lock();
141 d.anchor = d.position;
142 }
143
144 pub fn at_block_start(&self) -> bool {
148 let pos = self.position();
149 let inner = self.doc.lock();
150 let dto = frontend::document_inspection::GetBlockAtPositionDto {
151 position: to_i64(pos),
152 };
153 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
154 pos == to_usize(info.block_start)
155 } else {
156 false
157 }
158 }
159
160 pub fn at_block_end(&self) -> bool {
162 let pos = self.position();
163 let inner = self.doc.lock();
164 let dto = frontend::document_inspection::GetBlockAtPositionDto {
165 position: to_i64(pos),
166 };
167 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
168 pos == to_usize(info.block_start) + to_usize(info.block_length)
169 } else {
170 false
171 }
172 }
173
174 pub fn at_start(&self) -> bool {
176 self.data.lock().position == 0
177 }
178
179 pub fn at_end(&self) -> bool {
181 let pos = self.position();
182 let inner = self.doc.lock();
183 let stats =
184 document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
185 frontend::document_inspection::DocumentStatsDto {
186 character_count: 0,
187 word_count: 0,
188 block_count: 0,
189 frame_count: 0,
190 image_count: 0,
191 list_count: 0,
192 }
193 });
194 pos >= to_usize(stats.character_count)
195 }
196
197 pub fn block_number(&self) -> usize {
199 let pos = self.position();
200 let inner = self.doc.lock();
201 let dto = frontend::document_inspection::GetBlockAtPositionDto {
202 position: to_i64(pos),
203 };
204 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
205 .map(|info| to_usize(info.block_number))
206 .unwrap_or(0)
207 }
208
209 pub fn position_in_block(&self) -> usize {
211 let pos = self.position();
212 let inner = self.doc.lock();
213 let dto = frontend::document_inspection::GetBlockAtPositionDto {
214 position: to_i64(pos),
215 };
216 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
217 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
218 .unwrap_or(0)
219 }
220
221 pub fn set_position(&self, position: usize, mode: MoveMode) {
225 let end = {
227 let inner = self.doc.lock();
228 document_inspection_commands::get_document_stats(&inner.ctx)
229 .map(|s| to_usize(s.character_count))
230 .unwrap_or(0)
231 };
232 let pos = position.min(end);
233 let mut d = self.data.lock();
234 d.position = pos;
235 if mode == MoveMode::MoveAnchor {
236 d.anchor = pos;
237 }
238 }
239
240 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
246 let old_pos = self.position();
247 let target = self.resolve_move(operation, n);
248 self.set_position(target, mode);
249 self.position() != old_pos
250 }
251
252 pub fn select(&self, selection: SelectionType) {
254 match selection {
255 SelectionType::Document => {
256 let end = {
257 let inner = self.doc.lock();
258 document_inspection_commands::get_document_stats(&inner.ctx)
259 .map(|s| to_usize(s.character_count))
260 .unwrap_or(0)
261 };
262 let mut d = self.data.lock();
263 d.anchor = 0;
264 d.position = end;
265 }
266 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
267 let pos = self.position();
268 let inner = self.doc.lock();
269 let dto = frontend::document_inspection::GetBlockAtPositionDto {
270 position: to_i64(pos),
271 };
272 if let Ok(info) =
273 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
274 {
275 let start = to_usize(info.block_start);
276 let end = start + to_usize(info.block_length);
277 drop(inner);
278 let mut d = self.data.lock();
279 d.anchor = start;
280 d.position = end;
281 }
282 }
283 SelectionType::WordUnderCursor => {
284 let pos = self.position();
285 let (word_start, word_end) = self.find_word_boundaries(pos);
286 let mut d = self.data.lock();
287 d.anchor = word_start;
288 d.position = word_end;
289 }
290 }
291 }
292
293 pub fn insert_text(&self, text: &str) -> Result<()> {
297 let (pos, anchor) = self.read_cursor();
298
299 let dto = frontend::document_editing::InsertTextDto {
301 position: to_i64(pos),
302 anchor: to_i64(anchor),
303 text: text.into(),
304 };
305
306 let queued = {
307 let mut inner = self.doc.lock();
308 let result = match document_editing_commands::insert_text(
309 &inner.ctx,
310 Some(inner.stack_id),
311 &dto,
312 ) {
313 Ok(r) => r,
314 Err(_) if pos != anchor => {
315 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
317
318 let del_dto = frontend::document_editing::DeleteTextDto {
319 position: to_i64(pos),
320 anchor: to_i64(anchor),
321 };
322 let del_result = document_editing_commands::delete_text(
323 &inner.ctx,
324 Some(inner.stack_id),
325 &del_dto,
326 )?;
327 let del_pos = to_usize(del_result.new_position);
328
329 let ins_dto = frontend::document_editing::InsertTextDto {
330 position: to_i64(del_pos),
331 anchor: to_i64(del_pos),
332 text: text.into(),
333 };
334 let ins_result = document_editing_commands::insert_text(
335 &inner.ctx,
336 Some(inner.stack_id),
337 &ins_dto,
338 )?;
339
340 undo_redo_commands::end_composite(&inner.ctx);
341 ins_result
342 }
343 Err(e) => return Err(e),
344 };
345
346 let edit_pos = pos.min(anchor);
347 let removed = pos.max(anchor) - edit_pos;
348 self.finish_edit(
349 &mut inner,
350 edit_pos,
351 removed,
352 to_usize(result.new_position),
353 to_usize(result.blocks_affected),
354 )
355 };
356 crate::inner::dispatch_queued_events(queued);
357 Ok(())
358 }
359
360 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
362 let (pos, anchor) = self.read_cursor();
363 let queued = {
364 let mut inner = self.doc.lock();
365 let dto = frontend::document_editing::InsertFormattedTextDto {
366 position: to_i64(pos),
367 anchor: to_i64(anchor),
368 text: text.into(),
369 font_family: format.font_family.clone().unwrap_or_default(),
370 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
371 font_bold: format.font_bold.unwrap_or(false),
372 font_italic: format.font_italic.unwrap_or(false),
373 font_underline: format.font_underline.unwrap_or(false),
374 font_strikeout: format.font_strikeout.unwrap_or(false),
375 };
376 let result = document_editing_commands::insert_formatted_text(
377 &inner.ctx,
378 Some(inner.stack_id),
379 &dto,
380 )?;
381 let edit_pos = pos.min(anchor);
382 let removed = pos.max(anchor) - edit_pos;
383 self.finish_edit(&mut inner, edit_pos, removed, to_usize(result.new_position), 1)
384 };
385 crate::inner::dispatch_queued_events(queued);
386 Ok(())
387 }
388
389 pub fn insert_block(&self) -> Result<()> {
391 let (pos, anchor) = self.read_cursor();
392 let queued = {
393 let mut inner = self.doc.lock();
394 let dto = frontend::document_editing::InsertBlockDto {
395 position: to_i64(pos),
396 anchor: to_i64(anchor),
397 };
398 let result =
399 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
400 let edit_pos = pos.min(anchor);
401 let removed = pos.max(anchor) - edit_pos;
402 self.finish_edit(&mut inner, edit_pos, removed, to_usize(result.new_position), 2)
403 };
404 crate::inner::dispatch_queued_events(queued);
405 Ok(())
406 }
407
408 pub fn insert_html(&self, html: &str) -> Result<()> {
410 let (pos, anchor) = self.read_cursor();
411 let queued = {
412 let mut inner = self.doc.lock();
413 let dto = frontend::document_editing::InsertHtmlAtPositionDto {
414 position: to_i64(pos),
415 anchor: to_i64(anchor),
416 html: html.into(),
417 };
418 let result = document_editing_commands::insert_html_at_position(
419 &inner.ctx,
420 Some(inner.stack_id),
421 &dto,
422 )?;
423 let edit_pos = pos.min(anchor);
424 let removed = pos.max(anchor) - edit_pos;
425 self.finish_edit(
426 &mut inner,
427 edit_pos,
428 removed,
429 to_usize(result.new_position),
430 to_usize(result.blocks_added),
431 )
432 };
433 crate::inner::dispatch_queued_events(queued);
434 Ok(())
435 }
436
437 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
439 let (pos, anchor) = self.read_cursor();
440 let queued = {
441 let mut inner = self.doc.lock();
442 let dto = frontend::document_editing::InsertMarkdownAtPositionDto {
443 position: to_i64(pos),
444 anchor: to_i64(anchor),
445 markdown: markdown.into(),
446 };
447 let result = document_editing_commands::insert_markdown_at_position(
448 &inner.ctx,
449 Some(inner.stack_id),
450 &dto,
451 )?;
452 let edit_pos = pos.min(anchor);
453 let removed = pos.max(anchor) - edit_pos;
454 self.finish_edit(
455 &mut inner,
456 edit_pos,
457 removed,
458 to_usize(result.new_position),
459 to_usize(result.blocks_added),
460 )
461 };
462 crate::inner::dispatch_queued_events(queued);
463 Ok(())
464 }
465
466 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
468 let (pos, anchor) = self.read_cursor();
469 let queued = {
470 let mut inner = self.doc.lock();
471 let dto = frontend::document_editing::InsertFragmentDto {
472 position: to_i64(pos),
473 anchor: to_i64(anchor),
474 fragment_data: fragment.raw_data().into(),
475 };
476 let result =
477 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
478 let edit_pos = pos.min(anchor);
479 let removed = pos.max(anchor) - edit_pos;
480 self.finish_edit(
481 &mut inner,
482 edit_pos,
483 removed,
484 to_usize(result.new_position),
485 to_usize(result.blocks_added),
486 )
487 };
488 crate::inner::dispatch_queued_events(queued);
489 Ok(())
490 }
491
492 pub fn selection(&self) -> DocumentFragment {
494 let (pos, anchor) = self.read_cursor();
495 if pos == anchor {
496 return DocumentFragment::new();
497 }
498 let inner = self.doc.lock();
499 let dto = frontend::document_inspection::ExtractFragmentDto {
500 position: to_i64(pos),
501 anchor: to_i64(anchor),
502 };
503 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
504 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
505 Err(_) => DocumentFragment::new(),
506 }
507 }
508
509 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
511 let (pos, anchor) = self.read_cursor();
512 let queued = {
513 let mut inner = self.doc.lock();
514 let dto = frontend::document_editing::InsertImageDto {
515 position: to_i64(pos),
516 anchor: to_i64(anchor),
517 image_name: name.into(),
518 width: width as i64,
519 height: height as i64,
520 };
521 let result =
522 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
523 let edit_pos = pos.min(anchor);
524 let removed = pos.max(anchor) - edit_pos;
525 self.finish_edit(&mut inner, edit_pos, removed, to_usize(result.new_position), 1)
526 };
527 crate::inner::dispatch_queued_events(queued);
528 Ok(())
529 }
530
531 pub fn insert_frame(&self) -> Result<()> {
533 let (pos, anchor) = self.read_cursor();
534 let queued = {
535 let mut inner = self.doc.lock();
536 let dto = frontend::document_editing::InsertFrameDto {
537 position: to_i64(pos),
538 anchor: to_i64(anchor),
539 };
540 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
541 inner.modified = true;
544 inner.invalidate_text_cache();
545 inner.queue_event(DocumentEvent::ContentsChanged {
546 position: pos.min(anchor),
547 chars_removed: 0,
548 chars_added: 0,
549 blocks_affected: 1,
550 });
551 inner.take_queued_events()
552 };
553 crate::inner::dispatch_queued_events(queued);
554 Ok(())
555 }
556
557 pub fn delete_char(&self) -> Result<()> {
559 let (pos, anchor) = self.read_cursor();
560 let (del_pos, del_anchor) = if pos != anchor {
561 (pos, anchor)
562 } else {
563 (pos, pos + 1)
564 };
565 self.do_delete(del_pos, del_anchor)
566 }
567
568 pub fn delete_previous_char(&self) -> Result<()> {
570 let (pos, anchor) = self.read_cursor();
571 let (del_pos, del_anchor) = if pos != anchor {
572 (pos, anchor)
573 } else if pos > 0 {
574 (pos - 1, pos)
575 } else {
576 return Ok(());
577 };
578 self.do_delete(del_pos, del_anchor)
579 }
580
581 pub fn remove_selected_text(&self) -> Result<String> {
583 let (pos, anchor) = self.read_cursor();
584 if pos == anchor {
585 return Ok(String::new());
586 }
587 let queued = {
588 let mut inner = self.doc.lock();
589 let dto = frontend::document_editing::DeleteTextDto {
590 position: to_i64(pos),
591 anchor: to_i64(anchor),
592 };
593 let result =
594 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
595 let edit_pos = pos.min(anchor);
596 let removed = pos.max(anchor) - edit_pos;
597 let new_pos = to_usize(result.new_position);
598 inner.adjust_cursors(edit_pos, removed, 0);
599 {
600 let mut d = self.data.lock();
601 d.position = new_pos;
602 d.anchor = new_pos;
603 }
604 inner.modified = true;
605 inner.invalidate_text_cache();
606 inner.queue_event(DocumentEvent::ContentsChanged {
607 position: edit_pos,
608 chars_removed: removed,
609 chars_added: 0,
610 blocks_affected: 1,
611 });
612 (result.deleted_text, inner.take_queued_events())
614 };
615 crate::inner::dispatch_queued_events(queued.1);
616 Ok(queued.0)
617 }
618
619 pub fn create_list(&self, style: ListStyle) -> Result<()> {
623 let (pos, anchor) = self.read_cursor();
624 let queued = {
625 let mut inner = self.doc.lock();
626 let dto = frontend::document_editing::CreateListDto {
627 position: to_i64(pos),
628 anchor: to_i64(anchor),
629 style: style.clone(),
630 };
631 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
632 inner.modified = true;
633 inner.queue_event(DocumentEvent::ContentsChanged {
634 position: pos.min(anchor),
635 chars_removed: 0,
636 chars_added: 0,
637 blocks_affected: 1,
638 });
639 inner.take_queued_events()
640 };
641 crate::inner::dispatch_queued_events(queued);
642 Ok(())
643 }
644
645 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
647 let (pos, anchor) = self.read_cursor();
648 let queued = {
649 let mut inner = self.doc.lock();
650 let dto = frontend::document_editing::InsertListDto {
651 position: to_i64(pos),
652 anchor: to_i64(anchor),
653 style: style.clone(),
654 };
655 let result =
656 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
657 let edit_pos = pos.min(anchor);
658 let removed = pos.max(anchor) - edit_pos;
659 self.finish_edit(&mut inner, edit_pos, removed, to_usize(result.new_position), 1)
660 };
661 crate::inner::dispatch_queued_events(queued);
662 Ok(())
663 }
664
665 pub fn char_format(&self) -> Result<TextFormat> {
669 let pos = self.position();
670 let inner = self.doc.lock();
671 let dto = frontend::document_inspection::GetTextAtPositionDto {
672 position: to_i64(pos),
673 length: 1,
674 };
675 let text_info = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
676 let element_id = text_info.element_id as u64;
677 let element = inline_element_commands::get_inline_element(&inner.ctx, &element_id)?
678 .ok_or_else(|| anyhow::anyhow!("element not found at position"))?;
679 Ok(TextFormat::from(&element))
680 }
681
682 pub fn block_format(&self) -> Result<BlockFormat> {
684 let pos = self.position();
685 let inner = self.doc.lock();
686 let dto = frontend::document_inspection::GetBlockAtPositionDto {
687 position: to_i64(pos),
688 };
689 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
690 let block_id = block_info.block_id as u64;
691 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
692 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
693 Ok(BlockFormat::from(&block))
694 }
695
696 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
700 let (pos, anchor) = self.read_cursor();
701 let inner = self.doc.lock();
702 let dto = format.to_set_dto(pos, anchor);
703 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
704 Ok(())
705 }
706
707 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
709 let (pos, anchor) = self.read_cursor();
710 let inner = self.doc.lock();
711 let dto = format.to_merge_dto(pos, anchor);
712 document_formatting_commands::merge_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
713 Ok(())
714 }
715
716 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
718 let (pos, anchor) = self.read_cursor();
719 let inner = self.doc.lock();
720 let dto = format.to_set_dto(pos, anchor);
721 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
722 Ok(())
723 }
724
725 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
727 let (pos, anchor) = self.read_cursor();
728 let inner = self.doc.lock();
729 let dto = format.to_set_dto(pos, anchor, frame_id);
730 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
731 Ok(())
732 }
733
734 pub fn begin_edit_block(&self) {
738 let inner = self.doc.lock();
739 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
740 }
741
742 pub fn end_edit_block(&self) {
744 let inner = self.doc.lock();
745 undo_redo_commands::end_composite(&inner.ctx);
746 }
747
748 pub fn join_previous_edit_block(&self) {
755 self.begin_edit_block();
756 }
757
758 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
761 let queued = {
762 let mut inner = self.doc.lock();
763 let dto = frontend::document_editing::DeleteTextDto {
764 position: to_i64(pos),
765 anchor: to_i64(anchor),
766 };
767 let result =
768 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
769 let edit_pos = pos.min(anchor);
770 let removed = pos.max(anchor) - edit_pos;
771 let new_pos = to_usize(result.new_position);
772 inner.adjust_cursors(edit_pos, removed, 0);
773 {
774 let mut d = self.data.lock();
775 d.position = new_pos;
776 d.anchor = new_pos;
777 }
778 inner.modified = true;
779 inner.invalidate_text_cache();
780 inner.queue_event(DocumentEvent::ContentsChanged {
781 position: edit_pos,
782 chars_removed: removed,
783 chars_added: 0,
784 blocks_affected: 1,
785 });
786 inner.take_queued_events()
787 };
788 crate::inner::dispatch_queued_events(queued);
789 Ok(())
790 }
791
792 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
794 let pos = self.position();
795 match op {
796 MoveOperation::NoMove => pos,
797 MoveOperation::Start => 0,
798 MoveOperation::End => {
799 let inner = self.doc.lock();
800 document_inspection_commands::get_document_stats(&inner.ctx)
801 .map(|s| to_usize(s.character_count))
802 .unwrap_or(pos)
803 }
804 MoveOperation::NextCharacter | MoveOperation::Right => pos + n,
805 MoveOperation::PreviousCharacter | MoveOperation::Left => pos.saturating_sub(n),
806 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
807 let inner = self.doc.lock();
808 let dto = frontend::document_inspection::GetBlockAtPositionDto {
809 position: to_i64(pos),
810 };
811 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
812 .map(|info| to_usize(info.block_start))
813 .unwrap_or(pos)
814 }
815 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
816 let inner = self.doc.lock();
817 let dto = frontend::document_inspection::GetBlockAtPositionDto {
818 position: to_i64(pos),
819 };
820 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
821 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
822 .unwrap_or(pos)
823 }
824 MoveOperation::NextBlock => {
825 let inner = self.doc.lock();
826 let dto = frontend::document_inspection::GetBlockAtPositionDto {
827 position: to_i64(pos),
828 };
829 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
830 .map(|info| {
831 to_usize(info.block_start) + to_usize(info.block_length) + 1
833 })
834 .unwrap_or(pos)
835 }
836 MoveOperation::PreviousBlock => {
837 let inner = self.doc.lock();
838 let dto = frontend::document_inspection::GetBlockAtPositionDto {
839 position: to_i64(pos),
840 };
841 let block_start =
842 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
843 .map(|info| to_usize(info.block_start))
844 .unwrap_or(pos);
845 if block_start >= 2 {
846 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
848 position: to_i64(block_start - 2),
849 };
850 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
851 .map(|info| to_usize(info.block_start))
852 .unwrap_or(0)
853 } else {
854 0
855 }
856 }
857 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
858 let (_, end) = self.find_word_boundaries(pos);
859 if end == pos {
861 let inner = self.doc.lock();
863 let stats = document_inspection_commands::get_document_stats(&inner.ctx)
864 .map(|s| to_usize(s.character_count))
865 .unwrap_or(0);
866 let scan_len = (stats - pos).min(64);
867 if scan_len == 0 {
868 return pos;
869 }
870 let dto = frontend::document_inspection::GetTextAtPositionDto {
871 position: to_i64(pos),
872 length: to_i64(scan_len),
873 };
874 if let Ok(r) =
875 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
876 {
877 for (i, ch) in r.text.chars().enumerate() {
878 if ch.is_alphanumeric() || ch == '_' {
879 let word_pos = pos + i;
881 drop(inner);
882 let (_, word_end) = self.find_word_boundaries(word_pos);
883 return word_end;
884 }
885 }
886 }
887 pos + scan_len
888 } else {
889 end
890 }
891 }
892 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
893 let (start, _) = self.find_word_boundaries(pos);
894 if start < pos {
895 start
896 } else if pos > 0 {
897 let mut search = pos - 1;
900 loop {
901 let (ws, we) = self.find_word_boundaries(search);
902 if ws < we {
903 break ws;
905 }
906 if search == 0 {
908 break 0;
909 }
910 search -= 1;
911 }
912 } else {
913 0
914 }
915 }
916 MoveOperation::Up | MoveOperation::Down => {
917 if matches!(op, MoveOperation::Up) {
920 self.resolve_move(MoveOperation::PreviousBlock, 1)
921 } else {
922 self.resolve_move(MoveOperation::NextBlock, 1)
923 }
924 }
925 }
926 }
927
928 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
934 let inner = self.doc.lock();
935 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
937 position: to_i64(pos),
938 };
939 let block_info =
940 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
941 Ok(info) => info,
942 Err(_) => return (pos, pos),
943 };
944
945 let block_start = to_usize(block_info.block_start);
946 let block_length = to_usize(block_info.block_length);
947 if block_length == 0 {
948 return (pos, pos);
949 }
950
951 let dto = frontend::document_inspection::GetTextAtPositionDto {
952 position: to_i64(block_start),
953 length: to_i64(block_length),
954 };
955 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
956 Ok(r) => r.text,
957 Err(_) => return (pos, pos),
958 };
959
960 let cursor_offset = pos.saturating_sub(block_start);
962
963 let mut last_char_start = 0;
965 let mut last_char_end = 0;
966
967 for (word_byte_start, word) in text.unicode_word_indices() {
968 let word_char_start = text[..word_byte_start].chars().count();
970 let word_char_len = word.chars().count();
971 let word_char_end = word_char_start + word_char_len;
972
973 last_char_start = word_char_start;
974 last_char_end = word_char_end;
975
976 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
977 return (block_start + word_char_start, block_start + word_char_end);
978 }
979 }
980
981 if cursor_offset == last_char_end && last_char_start < last_char_end {
983 return (block_start + last_char_start, block_start + last_char_end);
984 }
985
986 (pos, pos)
987 }
988}