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::fragment::DocumentFragment;
20use crate::inner::{CursorData, QueuedEvents, 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 ) -> QueuedEvents {
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.check_block_count_changed();
89 self.queue_undo_redo_event(inner)
90 }
91
92 pub fn position(&self) -> usize {
96 self.data.lock().position
97 }
98
99 pub fn anchor(&self) -> usize {
101 self.data.lock().anchor
102 }
103
104 pub fn has_selection(&self) -> bool {
106 let d = self.data.lock();
107 d.position != d.anchor
108 }
109
110 pub fn selection_start(&self) -> usize {
112 let d = self.data.lock();
113 d.position.min(d.anchor)
114 }
115
116 pub fn selection_end(&self) -> usize {
118 let d = self.data.lock();
119 d.position.max(d.anchor)
120 }
121
122 pub fn selected_text(&self) -> Result<String> {
124 let (pos, anchor) = self.read_cursor();
125 if pos == anchor {
126 return Ok(String::new());
127 }
128 let start = pos.min(anchor);
129 let len = pos.max(anchor) - start;
130 let inner = self.doc.lock();
131 let dto = frontend::document_inspection::GetTextAtPositionDto {
132 position: to_i64(start),
133 length: to_i64(len),
134 };
135 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
136 Ok(result.text)
137 }
138
139 pub fn clear_selection(&self) {
141 let mut d = self.data.lock();
142 d.anchor = d.position;
143 }
144
145 pub fn at_block_start(&self) -> bool {
149 let pos = self.position();
150 let inner = self.doc.lock();
151 let dto = frontend::document_inspection::GetBlockAtPositionDto {
152 position: to_i64(pos),
153 };
154 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
155 pos == to_usize(info.block_start)
156 } else {
157 false
158 }
159 }
160
161 pub fn at_block_end(&self) -> bool {
163 let pos = self.position();
164 let inner = self.doc.lock();
165 let dto = frontend::document_inspection::GetBlockAtPositionDto {
166 position: to_i64(pos),
167 };
168 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
169 pos == to_usize(info.block_start) + to_usize(info.block_length)
170 } else {
171 false
172 }
173 }
174
175 pub fn at_start(&self) -> bool {
177 self.data.lock().position == 0
178 }
179
180 pub fn at_end(&self) -> bool {
182 let pos = self.position();
183 let inner = self.doc.lock();
184 let stats = 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(
384 &mut inner,
385 edit_pos,
386 removed,
387 to_usize(result.new_position),
388 1,
389 )
390 };
391 crate::inner::dispatch_queued_events(queued);
392 Ok(())
393 }
394
395 pub fn insert_block(&self) -> Result<()> {
397 let (pos, anchor) = self.read_cursor();
398 let queued = {
399 let mut inner = self.doc.lock();
400 let dto = frontend::document_editing::InsertBlockDto {
401 position: to_i64(pos),
402 anchor: to_i64(anchor),
403 };
404 let result =
405 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
406 let edit_pos = pos.min(anchor);
407 let removed = pos.max(anchor) - edit_pos;
408 self.finish_edit(
409 &mut inner,
410 edit_pos,
411 removed,
412 to_usize(result.new_position),
413 2,
414 )
415 };
416 crate::inner::dispatch_queued_events(queued);
417 Ok(())
418 }
419
420 pub fn insert_html(&self, html: &str) -> Result<()> {
422 let (pos, anchor) = self.read_cursor();
423 let queued = {
424 let mut inner = self.doc.lock();
425 let dto = frontend::document_editing::InsertHtmlAtPositionDto {
426 position: to_i64(pos),
427 anchor: to_i64(anchor),
428 html: html.into(),
429 };
430 let result = document_editing_commands::insert_html_at_position(
431 &inner.ctx,
432 Some(inner.stack_id),
433 &dto,
434 )?;
435 let edit_pos = pos.min(anchor);
436 let removed = pos.max(anchor) - edit_pos;
437 self.finish_edit(
438 &mut inner,
439 edit_pos,
440 removed,
441 to_usize(result.new_position),
442 to_usize(result.blocks_added),
443 )
444 };
445 crate::inner::dispatch_queued_events(queued);
446 Ok(())
447 }
448
449 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
451 let (pos, anchor) = self.read_cursor();
452 let queued = {
453 let mut inner = self.doc.lock();
454 let dto = frontend::document_editing::InsertMarkdownAtPositionDto {
455 position: to_i64(pos),
456 anchor: to_i64(anchor),
457 markdown: markdown.into(),
458 };
459 let result = document_editing_commands::insert_markdown_at_position(
460 &inner.ctx,
461 Some(inner.stack_id),
462 &dto,
463 )?;
464 let edit_pos = pos.min(anchor);
465 let removed = pos.max(anchor) - edit_pos;
466 self.finish_edit(
467 &mut inner,
468 edit_pos,
469 removed,
470 to_usize(result.new_position),
471 to_usize(result.blocks_added),
472 )
473 };
474 crate::inner::dispatch_queued_events(queued);
475 Ok(())
476 }
477
478 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
480 let (pos, anchor) = self.read_cursor();
481 let queued = {
482 let mut inner = self.doc.lock();
483 let dto = frontend::document_editing::InsertFragmentDto {
484 position: to_i64(pos),
485 anchor: to_i64(anchor),
486 fragment_data: fragment.raw_data().into(),
487 };
488 let result =
489 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
490 let edit_pos = pos.min(anchor);
491 let removed = pos.max(anchor) - edit_pos;
492 self.finish_edit(
493 &mut inner,
494 edit_pos,
495 removed,
496 to_usize(result.new_position),
497 to_usize(result.blocks_added),
498 )
499 };
500 crate::inner::dispatch_queued_events(queued);
501 Ok(())
502 }
503
504 pub fn selection(&self) -> DocumentFragment {
506 let (pos, anchor) = self.read_cursor();
507 if pos == anchor {
508 return DocumentFragment::new();
509 }
510 let inner = self.doc.lock();
511 let dto = frontend::document_inspection::ExtractFragmentDto {
512 position: to_i64(pos),
513 anchor: to_i64(anchor),
514 };
515 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
516 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
517 Err(_) => DocumentFragment::new(),
518 }
519 }
520
521 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
523 let (pos, anchor) = self.read_cursor();
524 let queued = {
525 let mut inner = self.doc.lock();
526 let dto = frontend::document_editing::InsertImageDto {
527 position: to_i64(pos),
528 anchor: to_i64(anchor),
529 image_name: name.into(),
530 width: width as i64,
531 height: height as i64,
532 };
533 let result =
534 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
535 let edit_pos = pos.min(anchor);
536 let removed = pos.max(anchor) - edit_pos;
537 self.finish_edit(
538 &mut inner,
539 edit_pos,
540 removed,
541 to_usize(result.new_position),
542 1,
543 )
544 };
545 crate::inner::dispatch_queued_events(queued);
546 Ok(())
547 }
548
549 pub fn insert_frame(&self) -> Result<()> {
551 let (pos, anchor) = self.read_cursor();
552 let queued = {
553 let mut inner = self.doc.lock();
554 let dto = frontend::document_editing::InsertFrameDto {
555 position: to_i64(pos),
556 anchor: to_i64(anchor),
557 };
558 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
559 inner.modified = true;
562 inner.invalidate_text_cache();
563 inner.queue_event(DocumentEvent::ContentsChanged {
564 position: pos.min(anchor),
565 chars_removed: 0,
566 chars_added: 0,
567 blocks_affected: 1,
568 });
569 inner.check_block_count_changed();
570 self.queue_undo_redo_event(&mut inner)
571 };
572 crate::inner::dispatch_queued_events(queued);
573 Ok(())
574 }
575
576 pub fn delete_char(&self) -> Result<()> {
578 let (pos, anchor) = self.read_cursor();
579 let (del_pos, del_anchor) = if pos != anchor {
580 (pos, anchor)
581 } else {
582 (pos, pos + 1)
583 };
584 self.do_delete(del_pos, del_anchor)
585 }
586
587 pub fn delete_previous_char(&self) -> Result<()> {
589 let (pos, anchor) = self.read_cursor();
590 let (del_pos, del_anchor) = if pos != anchor {
591 (pos, anchor)
592 } else if pos > 0 {
593 (pos - 1, pos)
594 } else {
595 return Ok(());
596 };
597 self.do_delete(del_pos, del_anchor)
598 }
599
600 pub fn remove_selected_text(&self) -> Result<String> {
602 let (pos, anchor) = self.read_cursor();
603 if pos == anchor {
604 return Ok(String::new());
605 }
606 let queued = {
607 let mut inner = self.doc.lock();
608 let dto = frontend::document_editing::DeleteTextDto {
609 position: to_i64(pos),
610 anchor: to_i64(anchor),
611 };
612 let result =
613 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
614 let edit_pos = pos.min(anchor);
615 let removed = pos.max(anchor) - edit_pos;
616 let new_pos = to_usize(result.new_position);
617 inner.adjust_cursors(edit_pos, removed, 0);
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.queue_event(DocumentEvent::ContentsChanged {
626 position: edit_pos,
627 chars_removed: removed,
628 chars_added: 0,
629 blocks_affected: 1,
630 });
631 inner.check_block_count_changed();
632 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
634 };
635 crate::inner::dispatch_queued_events(queued.1);
636 Ok(queued.0)
637 }
638
639 pub fn create_list(&self, style: ListStyle) -> Result<()> {
643 let (pos, anchor) = self.read_cursor();
644 let queued = {
645 let mut inner = self.doc.lock();
646 let dto = frontend::document_editing::CreateListDto {
647 position: to_i64(pos),
648 anchor: to_i64(anchor),
649 style: style.clone(),
650 };
651 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
652 inner.modified = true;
653 inner.queue_event(DocumentEvent::ContentsChanged {
654 position: pos.min(anchor),
655 chars_removed: 0,
656 chars_added: 0,
657 blocks_affected: 1,
658 });
659 self.queue_undo_redo_event(&mut inner)
660 };
661 crate::inner::dispatch_queued_events(queued);
662 Ok(())
663 }
664
665 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
667 let (pos, anchor) = self.read_cursor();
668 let queued = {
669 let mut inner = self.doc.lock();
670 let dto = frontend::document_editing::InsertListDto {
671 position: to_i64(pos),
672 anchor: to_i64(anchor),
673 style: style.clone(),
674 };
675 let result =
676 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
677 let edit_pos = pos.min(anchor);
678 let removed = pos.max(anchor) - edit_pos;
679 self.finish_edit(
680 &mut inner,
681 edit_pos,
682 removed,
683 to_usize(result.new_position),
684 1,
685 )
686 };
687 crate::inner::dispatch_queued_events(queued);
688 Ok(())
689 }
690
691 pub fn char_format(&self) -> Result<TextFormat> {
695 let pos = self.position();
696 let inner = self.doc.lock();
697 let dto = frontend::document_inspection::GetTextAtPositionDto {
698 position: to_i64(pos),
699 length: 1,
700 };
701 let text_info = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
702 let element_id = text_info.element_id as u64;
703 let element = inline_element_commands::get_inline_element(&inner.ctx, &element_id)?
704 .ok_or_else(|| anyhow::anyhow!("element not found at position"))?;
705 Ok(TextFormat::from(&element))
706 }
707
708 pub fn block_format(&self) -> Result<BlockFormat> {
710 let pos = self.position();
711 let inner = self.doc.lock();
712 let dto = frontend::document_inspection::GetBlockAtPositionDto {
713 position: to_i64(pos),
714 };
715 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
716 let block_id = block_info.block_id as u64;
717 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
718 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
719 Ok(BlockFormat::from(&block))
720 }
721
722 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
726 let (pos, anchor) = self.read_cursor();
727 let queued = {
728 let mut inner = self.doc.lock();
729 let dto = format.to_set_dto(pos, anchor);
730 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
731 let start = pos.min(anchor);
732 let length = pos.max(anchor) - start;
733 inner.modified = true;
734 inner.queue_event(DocumentEvent::FormatChanged {
735 position: start,
736 length,
737 });
738 self.queue_undo_redo_event(&mut inner)
739 };
740 crate::inner::dispatch_queued_events(queued);
741 Ok(())
742 }
743
744 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
746 let (pos, anchor) = self.read_cursor();
747 let queued = {
748 let mut inner = self.doc.lock();
749 let dto = format.to_merge_dto(pos, anchor);
750 document_formatting_commands::merge_text_format(
751 &inner.ctx,
752 Some(inner.stack_id),
753 &dto,
754 )?;
755 let start = pos.min(anchor);
756 let length = pos.max(anchor) - start;
757 inner.modified = true;
758 inner.queue_event(DocumentEvent::FormatChanged {
759 position: start,
760 length,
761 });
762 self.queue_undo_redo_event(&mut inner)
763 };
764 crate::inner::dispatch_queued_events(queued);
765 Ok(())
766 }
767
768 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
770 let (pos, anchor) = self.read_cursor();
771 let queued = {
772 let mut inner = self.doc.lock();
773 let dto = format.to_set_dto(pos, anchor);
774 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
775 let start = pos.min(anchor);
776 let length = pos.max(anchor) - start;
777 inner.modified = true;
778 inner.queue_event(DocumentEvent::FormatChanged {
779 position: start,
780 length,
781 });
782 self.queue_undo_redo_event(&mut inner)
783 };
784 crate::inner::dispatch_queued_events(queued);
785 Ok(())
786 }
787
788 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
790 let (pos, anchor) = self.read_cursor();
791 let queued = {
792 let mut inner = self.doc.lock();
793 let dto = format.to_set_dto(pos, anchor, frame_id);
794 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
795 let start = pos.min(anchor);
796 let length = pos.max(anchor) - start;
797 inner.modified = true;
798 inner.queue_event(DocumentEvent::FormatChanged {
799 position: start,
800 length,
801 });
802 self.queue_undo_redo_event(&mut inner)
803 };
804 crate::inner::dispatch_queued_events(queued);
805 Ok(())
806 }
807
808 pub fn begin_edit_block(&self) {
812 let inner = self.doc.lock();
813 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
814 }
815
816 pub fn end_edit_block(&self) {
818 let inner = self.doc.lock();
819 undo_redo_commands::end_composite(&inner.ctx);
820 }
821
822 pub fn join_previous_edit_block(&self) {
829 self.begin_edit_block();
830 }
831
832 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
836 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
837 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
838 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
839 inner.take_queued_events()
840 }
841
842 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
843 let queued = {
844 let mut inner = self.doc.lock();
845 let dto = frontend::document_editing::DeleteTextDto {
846 position: to_i64(pos),
847 anchor: to_i64(anchor),
848 };
849 let result =
850 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
851 let edit_pos = pos.min(anchor);
852 let removed = pos.max(anchor) - edit_pos;
853 let new_pos = to_usize(result.new_position);
854 inner.adjust_cursors(edit_pos, removed, 0);
855 {
856 let mut d = self.data.lock();
857 d.position = new_pos;
858 d.anchor = new_pos;
859 }
860 inner.modified = true;
861 inner.invalidate_text_cache();
862 inner.queue_event(DocumentEvent::ContentsChanged {
863 position: edit_pos,
864 chars_removed: removed,
865 chars_added: 0,
866 blocks_affected: 1,
867 });
868 inner.check_block_count_changed();
869 self.queue_undo_redo_event(&mut inner)
870 };
871 crate::inner::dispatch_queued_events(queued);
872 Ok(())
873 }
874
875 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
877 let pos = self.position();
878 match op {
879 MoveOperation::NoMove => pos,
880 MoveOperation::Start => 0,
881 MoveOperation::End => {
882 let inner = self.doc.lock();
883 document_inspection_commands::get_document_stats(&inner.ctx)
884 .map(|s| to_usize(s.character_count))
885 .unwrap_or(pos)
886 }
887 MoveOperation::NextCharacter | MoveOperation::Right => pos + n,
888 MoveOperation::PreviousCharacter | MoveOperation::Left => pos.saturating_sub(n),
889 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
890 let inner = self.doc.lock();
891 let dto = frontend::document_inspection::GetBlockAtPositionDto {
892 position: to_i64(pos),
893 };
894 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
895 .map(|info| to_usize(info.block_start))
896 .unwrap_or(pos)
897 }
898 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
899 let inner = self.doc.lock();
900 let dto = frontend::document_inspection::GetBlockAtPositionDto {
901 position: to_i64(pos),
902 };
903 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
904 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
905 .unwrap_or(pos)
906 }
907 MoveOperation::NextBlock => {
908 let inner = self.doc.lock();
909 let dto = frontend::document_inspection::GetBlockAtPositionDto {
910 position: to_i64(pos),
911 };
912 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
913 .map(|info| {
914 to_usize(info.block_start) + to_usize(info.block_length) + 1
916 })
917 .unwrap_or(pos)
918 }
919 MoveOperation::PreviousBlock => {
920 let inner = self.doc.lock();
921 let dto = frontend::document_inspection::GetBlockAtPositionDto {
922 position: to_i64(pos),
923 };
924 let block_start =
925 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
926 .map(|info| to_usize(info.block_start))
927 .unwrap_or(pos);
928 if block_start >= 2 {
929 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
931 position: to_i64(block_start - 2),
932 };
933 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
934 .map(|info| to_usize(info.block_start))
935 .unwrap_or(0)
936 } else {
937 0
938 }
939 }
940 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
941 let (_, end) = self.find_word_boundaries(pos);
942 if end == pos {
944 let inner = self.doc.lock();
946 let stats = document_inspection_commands::get_document_stats(&inner.ctx)
947 .map(|s| to_usize(s.character_count))
948 .unwrap_or(0);
949 let scan_len = (stats - pos).min(64);
950 if scan_len == 0 {
951 return pos;
952 }
953 let dto = frontend::document_inspection::GetTextAtPositionDto {
954 position: to_i64(pos),
955 length: to_i64(scan_len),
956 };
957 if let Ok(r) =
958 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
959 {
960 for (i, ch) in r.text.chars().enumerate() {
961 if ch.is_alphanumeric() || ch == '_' {
962 let word_pos = pos + i;
964 drop(inner);
965 let (_, word_end) = self.find_word_boundaries(word_pos);
966 return word_end;
967 }
968 }
969 }
970 pos + scan_len
971 } else {
972 end
973 }
974 }
975 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
976 let (start, _) = self.find_word_boundaries(pos);
977 if start < pos {
978 start
979 } else if pos > 0 {
980 let mut search = pos - 1;
983 loop {
984 let (ws, we) = self.find_word_boundaries(search);
985 if ws < we {
986 break ws;
988 }
989 if search == 0 {
991 break 0;
992 }
993 search -= 1;
994 }
995 } else {
996 0
997 }
998 }
999 MoveOperation::Up | MoveOperation::Down => {
1000 if matches!(op, MoveOperation::Up) {
1003 self.resolve_move(MoveOperation::PreviousBlock, 1)
1004 } else {
1005 self.resolve_move(MoveOperation::NextBlock, 1)
1006 }
1007 }
1008 }
1009 }
1010
1011 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
1017 let inner = self.doc.lock();
1018 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
1020 position: to_i64(pos),
1021 };
1022 let block_info =
1023 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
1024 Ok(info) => info,
1025 Err(_) => return (pos, pos),
1026 };
1027
1028 let block_start = to_usize(block_info.block_start);
1029 let block_length = to_usize(block_info.block_length);
1030 if block_length == 0 {
1031 return (pos, pos);
1032 }
1033
1034 let dto = frontend::document_inspection::GetTextAtPositionDto {
1035 position: to_i64(block_start),
1036 length: to_i64(block_length),
1037 };
1038 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
1039 Ok(r) => r.text,
1040 Err(_) => return (pos, pos),
1041 };
1042
1043 let cursor_offset = pos.saturating_sub(block_start);
1045
1046 let mut last_char_start = 0;
1048 let mut last_char_end = 0;
1049
1050 for (word_byte_start, word) in text.unicode_word_indices() {
1051 let word_char_start = text[..word_byte_start].chars().count();
1053 let word_char_len = word.chars().count();
1054 let word_char_end = word_char_start + word_char_len;
1055
1056 last_char_start = word_char_start;
1057 last_char_end = word_char_end;
1058
1059 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
1060 return (block_start + word_char_start, block_start + word_char_end);
1061 }
1062 }
1063
1064 if cursor_offset == last_char_end && last_char_start < last_char_end {
1066 return (block_start + last_char_start, block_start + last_char_end);
1067 }
1068
1069 (pos, pos)
1070 }
1071}