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 {
61 position,
62 anchor,
63 cell_selection_override: None,
64 }));
65 inner.cursors.push(Arc::downgrade(&data));
66 data
67 };
68 TextCursor {
69 doc: self.doc.clone(),
70 data,
71 }
72 }
73}
74
75impl TextCursor {
76 fn read_cursor(&self) -> (usize, usize) {
79 let d = self.data.lock();
80 (d.position, d.anchor)
81 }
82
83 fn finish_edit(
87 &self,
88 inner: &mut TextDocumentInner,
89 edit_pos: usize,
90 removed: usize,
91 new_pos: usize,
92 blocks_affected: usize,
93 ) -> QueuedEvents {
94 self.finish_edit_ext(inner, edit_pos, removed, new_pos, blocks_affected, true)
95 }
96
97 fn finish_edit_ext(
98 &self,
99 inner: &mut TextDocumentInner,
100 edit_pos: usize,
101 removed: usize,
102 new_pos: usize,
103 blocks_affected: usize,
104 flow_may_change: bool,
105 ) -> QueuedEvents {
106 let added = new_pos - edit_pos;
107 inner.adjust_cursors(edit_pos, removed, added);
108 {
109 let mut d = self.data.lock();
110 d.position = new_pos;
111 d.anchor = new_pos;
112 }
113 inner.modified = true;
114 inner.invalidate_text_cache();
115 inner.rehighlight_affected(edit_pos);
116 inner.queue_event(DocumentEvent::ContentsChanged {
117 position: edit_pos,
118 chars_removed: removed,
119 chars_added: added,
120 blocks_affected,
121 });
122 inner.check_block_count_changed();
123 if flow_may_change {
124 inner.check_flow_changed();
125 }
126 self.queue_undo_redo_event(inner)
127 }
128
129 pub fn position(&self) -> usize {
133 self.data.lock().position
134 }
135
136 pub fn anchor(&self) -> usize {
138 self.data.lock().anchor
139 }
140
141 pub fn has_selection(&self) -> bool {
143 let d = self.data.lock();
144 d.position != d.anchor
145 }
146
147 pub fn selection_start(&self) -> usize {
149 let d = self.data.lock();
150 d.position.min(d.anchor)
151 }
152
153 pub fn selection_end(&self) -> usize {
155 let d = self.data.lock();
156 d.position.max(d.anchor)
157 }
158
159 pub fn selected_text(&self) -> Result<String> {
161 let (pos, anchor) = self.read_cursor();
162 if pos == anchor {
163 return Ok(String::new());
164 }
165 let start = pos.min(anchor);
166 let len = pos.max(anchor) - start;
167 let inner = self.doc.lock();
168 let dto = frontend::document_inspection::GetTextAtPositionDto {
169 position: to_i64(start),
170 length: to_i64(len),
171 };
172 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
173 Ok(result.text)
174 }
175
176 pub fn clear_selection(&self) {
178 let mut d = self.data.lock();
179 d.anchor = d.position;
180 }
181
182 pub fn at_block_start(&self) -> bool {
186 let pos = self.position();
187 let inner = self.doc.lock();
188 let dto = frontend::document_inspection::GetBlockAtPositionDto {
189 position: to_i64(pos),
190 };
191 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
192 pos == to_usize(info.block_start)
193 } else {
194 false
195 }
196 }
197
198 pub fn at_block_end(&self) -> bool {
200 let pos = self.position();
201 let inner = self.doc.lock();
202 let dto = frontend::document_inspection::GetBlockAtPositionDto {
203 position: to_i64(pos),
204 };
205 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
206 pos == to_usize(info.block_start) + to_usize(info.block_length)
207 } else {
208 false
209 }
210 }
211
212 pub fn at_start(&self) -> bool {
214 self.data.lock().position == 0
215 }
216
217 pub fn at_end(&self) -> bool {
219 let pos = self.position();
220 let inner = self.doc.lock();
221 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
222 frontend::document_inspection::DocumentStatsDto {
223 character_count: 0,
224 word_count: 0,
225 block_count: 0,
226 frame_count: 0,
227 image_count: 0,
228 list_count: 0,
229 table_count: 0,
230 }
231 });
232 pos >= max_cursor_position(&stats)
233 }
234
235 pub fn block_number(&self) -> usize {
237 let pos = self.position();
238 let inner = self.doc.lock();
239 let dto = frontend::document_inspection::GetBlockAtPositionDto {
240 position: to_i64(pos),
241 };
242 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
243 .map(|info| to_usize(info.block_number))
244 .unwrap_or(0)
245 }
246
247 pub fn position_in_block(&self) -> usize {
249 let pos = self.position();
250 let inner = self.doc.lock();
251 let dto = frontend::document_inspection::GetBlockAtPositionDto {
252 position: to_i64(pos),
253 };
254 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
255 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
256 .unwrap_or(0)
257 }
258
259 pub fn set_position(&self, position: usize, mode: MoveMode) {
263 let end = {
265 let inner = self.doc.lock();
266 document_inspection_commands::get_document_stats(&inner.ctx)
267 .map(|s| max_cursor_position(&s))
268 .unwrap_or(0)
269 };
270 let pos = position.min(end);
271 let mut d = self.data.lock();
272 d.position = pos;
273 if mode == MoveMode::MoveAnchor {
274 d.anchor = pos;
275 }
276 d.cell_selection_override = None;
277 }
278
279 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
285 let old_pos = self.position();
286 let target = self.resolve_move(operation, n);
287 self.set_position(target, mode);
288 self.position() != old_pos
289 }
290
291 pub fn select(&self, selection: SelectionType) {
293 match selection {
294 SelectionType::Document => {
295 let end = {
296 let inner = self.doc.lock();
297 document_inspection_commands::get_document_stats(&inner.ctx)
298 .map(|s| max_cursor_position(&s))
299 .unwrap_or(0)
300 };
301 let mut d = self.data.lock();
302 d.anchor = 0;
303 d.position = end;
304 d.cell_selection_override = None;
305 }
306 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
307 let pos = self.position();
308 let inner = self.doc.lock();
309 let dto = frontend::document_inspection::GetBlockAtPositionDto {
310 position: to_i64(pos),
311 };
312 if let Ok(info) =
313 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
314 {
315 let start = to_usize(info.block_start);
316 let end = start + to_usize(info.block_length);
317 drop(inner);
318 let mut d = self.data.lock();
319 d.anchor = start;
320 d.position = end;
321 d.cell_selection_override = None;
322 }
323 }
324 SelectionType::WordUnderCursor => {
325 let pos = self.position();
326 let (word_start, word_end) = self.find_word_boundaries(pos);
327 let mut d = self.data.lock();
328 d.anchor = word_start;
329 d.position = word_end;
330 d.cell_selection_override = None;
331 }
332 }
333 }
334
335 pub fn insert_text(&self, text: &str) -> Result<()> {
339 let (pos, anchor) = self.read_cursor();
340
341 let dto = frontend::document_editing::InsertTextDto {
343 position: to_i64(pos),
344 anchor: to_i64(anchor),
345 text: text.into(),
346 };
347
348 let queued = {
349 let mut inner = self.doc.lock();
350 let result = match document_editing_commands::insert_text(
351 &inner.ctx,
352 Some(inner.stack_id),
353 &dto,
354 ) {
355 Ok(r) => r,
356 Err(_) if pos != anchor => {
357 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
359
360 let del_dto = frontend::document_editing::DeleteTextDto {
361 position: to_i64(pos),
362 anchor: to_i64(anchor),
363 };
364 let del_result = document_editing_commands::delete_text(
365 &inner.ctx,
366 Some(inner.stack_id),
367 &del_dto,
368 )?;
369 let del_pos = to_usize(del_result.new_position);
370
371 let ins_dto = frontend::document_editing::InsertTextDto {
372 position: to_i64(del_pos),
373 anchor: to_i64(del_pos),
374 text: text.into(),
375 };
376 let ins_result = document_editing_commands::insert_text(
377 &inner.ctx,
378 Some(inner.stack_id),
379 &ins_dto,
380 )?;
381
382 undo_redo_commands::end_composite(&inner.ctx);
383 ins_result
384 }
385 Err(e) => return Err(e),
386 };
387
388 let edit_pos = pos.min(anchor);
389 let removed = pos.max(anchor) - edit_pos;
390 self.finish_edit_ext(
391 &mut inner,
392 edit_pos,
393 removed,
394 to_usize(result.new_position),
395 to_usize(result.blocks_affected),
396 false,
397 )
398 };
399 crate::inner::dispatch_queued_events(queued);
400 Ok(())
401 }
402
403 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
405 let (pos, anchor) = self.read_cursor();
406 let queued = {
407 let mut inner = self.doc.lock();
408 let dto = frontend::document_editing::InsertFormattedTextDto {
409 position: to_i64(pos),
410 anchor: to_i64(anchor),
411 text: text.into(),
412 font_family: format.font_family.clone().unwrap_or_default(),
413 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
414 font_bold: format.font_bold.unwrap_or(false),
415 font_italic: format.font_italic.unwrap_or(false),
416 font_underline: format.font_underline.unwrap_or(false),
417 font_strikeout: format.font_strikeout.unwrap_or(false),
418 };
419 let result = document_editing_commands::insert_formatted_text(
420 &inner.ctx,
421 Some(inner.stack_id),
422 &dto,
423 )?;
424 let edit_pos = pos.min(anchor);
425 let removed = pos.max(anchor) - edit_pos;
426 self.finish_edit_ext(
427 &mut inner,
428 edit_pos,
429 removed,
430 to_usize(result.new_position),
431 1,
432 false,
433 )
434 };
435 crate::inner::dispatch_queued_events(queued);
436 Ok(())
437 }
438
439 pub fn insert_block(&self) -> Result<()> {
441 let (pos, anchor) = self.read_cursor();
442 let queued = {
443 let mut inner = self.doc.lock();
444 let dto = frontend::document_editing::InsertBlockDto {
445 position: to_i64(pos),
446 anchor: to_i64(anchor),
447 };
448 let result =
449 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
450 let edit_pos = pos.min(anchor);
451 let removed = pos.max(anchor) - edit_pos;
452 self.finish_edit(
453 &mut inner,
454 edit_pos,
455 removed,
456 to_usize(result.new_position),
457 2,
458 )
459 };
460 crate::inner::dispatch_queued_events(queued);
461 Ok(())
462 }
463
464 pub fn insert_html(&self, html: &str) -> Result<()> {
466 let (pos, anchor) = self.read_cursor();
467 let queued = {
468 let mut inner = self.doc.lock();
469 let dto = frontend::document_editing::InsertHtmlAtPositionDto {
470 position: to_i64(pos),
471 anchor: to_i64(anchor),
472 html: html.into(),
473 };
474 let result = document_editing_commands::insert_html_at_position(
475 &inner.ctx,
476 Some(inner.stack_id),
477 &dto,
478 )?;
479 let edit_pos = pos.min(anchor);
480 let removed = pos.max(anchor) - edit_pos;
481 self.finish_edit(
482 &mut inner,
483 edit_pos,
484 removed,
485 to_usize(result.new_position),
486 to_usize(result.blocks_added),
487 )
488 };
489 crate::inner::dispatch_queued_events(queued);
490 Ok(())
491 }
492
493 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
495 let (pos, anchor) = self.read_cursor();
496 let queued = {
497 let mut inner = self.doc.lock();
498 let dto = frontend::document_editing::InsertMarkdownAtPositionDto {
499 position: to_i64(pos),
500 anchor: to_i64(anchor),
501 markdown: markdown.into(),
502 };
503 let result = document_editing_commands::insert_markdown_at_position(
504 &inner.ctx,
505 Some(inner.stack_id),
506 &dto,
507 )?;
508 let edit_pos = pos.min(anchor);
509 let removed = pos.max(anchor) - edit_pos;
510 self.finish_edit(
511 &mut inner,
512 edit_pos,
513 removed,
514 to_usize(result.new_position),
515 to_usize(result.blocks_added),
516 )
517 };
518 crate::inner::dispatch_queued_events(queued);
519 Ok(())
520 }
521
522 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
524 let (pos, anchor) = self.read_cursor();
525 let queued = {
526 let mut inner = self.doc.lock();
527 let dto = frontend::document_editing::InsertFragmentDto {
528 position: to_i64(pos),
529 anchor: to_i64(anchor),
530 fragment_data: fragment.raw_data().into(),
531 };
532 let result =
533 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
534 let edit_pos = pos.min(anchor);
535 let removed = pos.max(anchor) - edit_pos;
536 self.finish_edit(
537 &mut inner,
538 edit_pos,
539 removed,
540 to_usize(result.new_position),
541 to_usize(result.blocks_added),
542 )
543 };
544 crate::inner::dispatch_queued_events(queued);
545 Ok(())
546 }
547
548 pub fn selection(&self) -> DocumentFragment {
550 let (pos, anchor) = self.read_cursor();
551 if pos == anchor {
552 return DocumentFragment::new();
553 }
554 let inner = self.doc.lock();
555 let dto = frontend::document_inspection::ExtractFragmentDto {
556 position: to_i64(pos),
557 anchor: to_i64(anchor),
558 };
559 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
560 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
561 Err(_) => DocumentFragment::new(),
562 }
563 }
564
565 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
567 let (pos, anchor) = self.read_cursor();
568 let queued = {
569 let mut inner = self.doc.lock();
570 let dto = frontend::document_editing::InsertImageDto {
571 position: to_i64(pos),
572 anchor: to_i64(anchor),
573 image_name: name.into(),
574 width: width as i64,
575 height: height as i64,
576 };
577 let result =
578 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
579 let edit_pos = pos.min(anchor);
580 let removed = pos.max(anchor) - edit_pos;
581 self.finish_edit_ext(
582 &mut inner,
583 edit_pos,
584 removed,
585 to_usize(result.new_position),
586 1,
587 false,
588 )
589 };
590 crate::inner::dispatch_queued_events(queued);
591 Ok(())
592 }
593
594 pub fn insert_frame(&self) -> Result<()> {
596 let (pos, anchor) = self.read_cursor();
597 let queued = {
598 let mut inner = self.doc.lock();
599 let dto = frontend::document_editing::InsertFrameDto {
600 position: to_i64(pos),
601 anchor: to_i64(anchor),
602 };
603 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
604 inner.modified = true;
607 inner.invalidate_text_cache();
608 inner.rehighlight_affected(pos.min(anchor));
609 inner.queue_event(DocumentEvent::ContentsChanged {
610 position: pos.min(anchor),
611 chars_removed: 0,
612 chars_added: 0,
613 blocks_affected: 1,
614 });
615 inner.check_block_count_changed();
616 inner.check_flow_changed();
617 self.queue_undo_redo_event(&mut inner)
618 };
619 crate::inner::dispatch_queued_events(queued);
620 Ok(())
621 }
622
623 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
629 let (pos, anchor) = self.read_cursor();
630 let (table_id, queued) = {
631 let mut inner = self.doc.lock();
632 let dto = frontend::document_editing::InsertTableDto {
633 position: to_i64(pos),
634 anchor: to_i64(anchor),
635 rows: to_i64(rows),
636 columns: to_i64(columns),
637 };
638 let result =
639 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
640 let new_pos = to_usize(result.new_position);
641 let table_id = to_usize(result.table_id);
642 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
643 {
644 let mut d = self.data.lock();
645 d.position = new_pos;
646 d.anchor = new_pos;
647 }
648 inner.modified = true;
649 inner.invalidate_text_cache();
650 inner.rehighlight_affected(pos.min(anchor));
651 inner.queue_event(DocumentEvent::ContentsChanged {
652 position: pos.min(anchor),
653 chars_removed: 0,
654 chars_added: new_pos - pos.min(anchor),
655 blocks_affected: 1,
656 });
657 inner.check_block_count_changed();
658 inner.check_flow_changed();
659 (table_id, self.queue_undo_redo_event(&mut inner))
660 };
661 crate::inner::dispatch_queued_events(queued);
662 Ok(TextTable {
663 doc: self.doc.clone(),
664 table_id,
665 })
666 }
667
668 pub fn current_table(&self) -> Option<TextTable> {
673 self.current_table_cell().map(|c| c.table)
674 }
675
676 pub fn current_table_cell(&self) -> Option<TableCellRef> {
681 let pos = self.position();
682 let inner = self.doc.lock();
683 let dto = frontend::document_inspection::GetBlockAtPositionDto {
685 position: to_i64(pos),
686 };
687 let block_info =
688 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
689
690 let block_id = if to_i64(pos) < block_info.block_start && pos > 0 {
694 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
695 position: to_i64(pos - 1),
696 };
697 let prev_info =
698 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
699 prev_info.block_id as usize
700 } else {
701 block_info.block_id as usize
702 };
703
704 let block = crate::text_block::TextBlock {
705 doc: self.doc.clone(),
706 block_id,
707 };
708 drop(inner);
710 block.table_cell()
711 }
712
713 pub fn remove_table(&self, table_id: usize) -> Result<()> {
717 let queued = {
718 let mut inner = self.doc.lock();
719 let dto = frontend::document_editing::RemoveTableDto {
720 table_id: to_i64(table_id),
721 };
722 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
723 inner.modified = true;
724 inner.invalidate_text_cache();
725 inner.rehighlight_all();
726 inner.check_block_count_changed();
727 inner.check_flow_changed();
728 self.queue_undo_redo_event(&mut inner)
729 };
730 crate::inner::dispatch_queued_events(queued);
731 Ok(())
732 }
733
734 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
736 let queued = {
737 let mut inner = self.doc.lock();
738 let dto = frontend::document_editing::InsertTableRowDto {
739 table_id: to_i64(table_id),
740 row_index: to_i64(row_index),
741 };
742 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
743 inner.modified = true;
744 inner.invalidate_text_cache();
745 inner.rehighlight_all();
746 inner.check_block_count_changed();
747 self.queue_undo_redo_event(&mut inner)
748 };
749 crate::inner::dispatch_queued_events(queued);
750 Ok(())
751 }
752
753 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
755 let queued = {
756 let mut inner = self.doc.lock();
757 let dto = frontend::document_editing::InsertTableColumnDto {
758 table_id: to_i64(table_id),
759 column_index: to_i64(column_index),
760 };
761 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
762 inner.modified = true;
763 inner.invalidate_text_cache();
764 inner.rehighlight_all();
765 inner.check_block_count_changed();
766 self.queue_undo_redo_event(&mut inner)
767 };
768 crate::inner::dispatch_queued_events(queued);
769 Ok(())
770 }
771
772 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
774 let queued = {
775 let mut inner = self.doc.lock();
776 let dto = frontend::document_editing::RemoveTableRowDto {
777 table_id: to_i64(table_id),
778 row_index: to_i64(row_index),
779 };
780 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
781 inner.modified = true;
782 inner.invalidate_text_cache();
783 inner.rehighlight_all();
784 inner.check_block_count_changed();
785 self.queue_undo_redo_event(&mut inner)
786 };
787 crate::inner::dispatch_queued_events(queued);
788 Ok(())
789 }
790
791 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
793 let queued = {
794 let mut inner = self.doc.lock();
795 let dto = frontend::document_editing::RemoveTableColumnDto {
796 table_id: to_i64(table_id),
797 column_index: to_i64(column_index),
798 };
799 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
800 inner.modified = true;
801 inner.invalidate_text_cache();
802 inner.rehighlight_all();
803 inner.check_block_count_changed();
804 self.queue_undo_redo_event(&mut inner)
805 };
806 crate::inner::dispatch_queued_events(queued);
807 Ok(())
808 }
809
810 pub fn merge_table_cells(
812 &self,
813 table_id: usize,
814 start_row: usize,
815 start_column: usize,
816 end_row: usize,
817 end_column: usize,
818 ) -> Result<()> {
819 let queued = {
820 let mut inner = self.doc.lock();
821 let dto = frontend::document_editing::MergeTableCellsDto {
822 table_id: to_i64(table_id),
823 start_row: to_i64(start_row),
824 start_column: to_i64(start_column),
825 end_row: to_i64(end_row),
826 end_column: to_i64(end_column),
827 };
828 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
829 inner.modified = true;
830 inner.invalidate_text_cache();
831 inner.rehighlight_all();
832 inner.check_block_count_changed();
833 self.queue_undo_redo_event(&mut inner)
834 };
835 crate::inner::dispatch_queued_events(queued);
836 Ok(())
837 }
838
839 pub fn split_table_cell(
841 &self,
842 cell_id: usize,
843 split_rows: usize,
844 split_columns: usize,
845 ) -> Result<()> {
846 let queued = {
847 let mut inner = self.doc.lock();
848 let dto = frontend::document_editing::SplitTableCellDto {
849 cell_id: to_i64(cell_id),
850 split_rows: to_i64(split_rows),
851 split_columns: to_i64(split_columns),
852 };
853 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
854 inner.modified = true;
855 inner.invalidate_text_cache();
856 inner.rehighlight_all();
857 inner.check_block_count_changed();
858 self.queue_undo_redo_event(&mut inner)
859 };
860 crate::inner::dispatch_queued_events(queued);
861 Ok(())
862 }
863
864 pub fn set_table_format(
868 &self,
869 table_id: usize,
870 format: &crate::flow::TableFormat,
871 ) -> Result<()> {
872 let queued = {
873 let mut inner = self.doc.lock();
874 let dto = format.to_set_dto(table_id);
875 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
876 inner.modified = true;
877 inner.queue_event(DocumentEvent::FormatChanged {
878 position: 0,
879 length: 0,
880 kind: crate::flow::FormatChangeKind::Block,
881 });
882 self.queue_undo_redo_event(&mut inner)
883 };
884 crate::inner::dispatch_queued_events(queued);
885 Ok(())
886 }
887
888 pub fn set_table_cell_format(
890 &self,
891 cell_id: usize,
892 format: &crate::flow::CellFormat,
893 ) -> Result<()> {
894 let queued = {
895 let mut inner = self.doc.lock();
896 let dto = format.to_set_dto(cell_id);
897 document_formatting_commands::set_table_cell_format(
898 &inner.ctx,
899 Some(inner.stack_id),
900 &dto,
901 )?;
902 inner.modified = true;
903 inner.queue_event(DocumentEvent::FormatChanged {
904 position: 0,
905 length: 0,
906 kind: crate::flow::FormatChangeKind::Block,
907 });
908 self.queue_undo_redo_event(&mut inner)
909 };
910 crate::inner::dispatch_queued_events(queued);
911 Ok(())
912 }
913
914 pub fn remove_current_table(&self) -> Result<()> {
919 let table = self
920 .current_table()
921 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
922 self.remove_table(table.id())
923 }
924
925 pub fn insert_row_above(&self) -> Result<()> {
928 let cell_ref = self
929 .current_table_cell()
930 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
931 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
932 }
933
934 pub fn insert_row_below(&self) -> Result<()> {
937 let cell_ref = self
938 .current_table_cell()
939 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
940 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
941 }
942
943 pub fn insert_column_before(&self) -> Result<()> {
946 let cell_ref = self
947 .current_table_cell()
948 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
949 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
950 }
951
952 pub fn insert_column_after(&self) -> Result<()> {
955 let cell_ref = self
956 .current_table_cell()
957 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
958 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
959 }
960
961 pub fn remove_current_row(&self) -> Result<()> {
964 let cell_ref = self
965 .current_table_cell()
966 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
967 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
968 }
969
970 pub fn remove_current_column(&self) -> Result<()> {
973 let cell_ref = self
974 .current_table_cell()
975 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
976 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
977 }
978
979 pub fn merge_selected_cells(&self) -> Result<()> {
986 let pos_cell = self
987 .current_table_cell()
988 .ok_or_else(|| anyhow::anyhow!("cursor position is not inside a table"))?;
989
990 let (_pos, anchor) = self.read_cursor();
992 let anchor_cell = {
993 let inner = self.doc.lock();
995 let dto = frontend::document_inspection::GetBlockAtPositionDto {
996 position: to_i64(anchor),
997 };
998 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
999 .map_err(|_| anyhow::anyhow!("cursor anchor is not inside a table"))?;
1000 let block = crate::text_block::TextBlock {
1001 doc: self.doc.clone(),
1002 block_id: block_info.block_id as usize,
1003 };
1004 drop(inner);
1005 block
1006 .table_cell()
1007 .ok_or_else(|| anyhow::anyhow!("cursor anchor is not inside a table"))?
1008 };
1009
1010 if pos_cell.table.id() != anchor_cell.table.id() {
1011 return Err(anyhow::anyhow!(
1012 "position and anchor are in different tables"
1013 ));
1014 }
1015
1016 let start_row = pos_cell.row.min(anchor_cell.row);
1017 let start_col = pos_cell.column.min(anchor_cell.column);
1018 let end_row = pos_cell.row.max(anchor_cell.row);
1019 let end_col = pos_cell.column.max(anchor_cell.column);
1020
1021 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
1022 }
1023
1024 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
1027 let cell_ref = self
1028 .current_table_cell()
1029 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1030 let cell = cell_ref
1032 .table
1033 .cell(cell_ref.row, cell_ref.column)
1034 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1035 self.split_table_cell(cell.id(), split_rows, split_columns)
1037 }
1038
1039 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
1042 let table = self
1043 .current_table()
1044 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1045 self.set_table_format(table.id(), format)
1046 }
1047
1048 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
1051 let cell_ref = self
1052 .current_table_cell()
1053 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
1054 let cell = cell_ref
1055 .table
1056 .cell(cell_ref.row, cell_ref.column)
1057 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1058 self.set_table_cell_format(cell.id(), format)
1059 }
1060
1061 pub fn selection_kind(&self) -> crate::flow::SelectionKind {
1069 use crate::flow::{CellRange, SelectionKind};
1070
1071 {
1073 let d = self.data.lock();
1074 if let Some(ref range) = d.cell_selection_override {
1075 return SelectionKind::Cells(range.clone());
1076 }
1077 if d.position == d.anchor {
1078 return SelectionKind::None;
1079 }
1080 }
1081
1082 let (pos, anchor) = self.read_cursor();
1083
1084 let pos_cell = self.table_cell_at(pos);
1086 let anchor_cell = self.table_cell_at(anchor);
1087
1088 match (&pos_cell, &anchor_cell) {
1089 (None, None) => SelectionKind::Text,
1090 (Some(pc), Some(ac)) => {
1091 if pc.table.id() != ac.table.id() {
1092 return SelectionKind::Text;
1094 }
1095 if pc.row == ac.row && pc.column == ac.column {
1096 return SelectionKind::Text;
1098 }
1099 let range = CellRange {
1101 table_id: pc.table.id(),
1102 start_row: pc.row.min(ac.row),
1103 start_col: pc.column.min(ac.column),
1104 end_row: pc.row.max(ac.row),
1105 end_col: pc.column.max(ac.column),
1106 };
1107 let spans = self.collect_cell_spans(pc.table.id());
1108 SelectionKind::Cells(range.expand_for_spans(&spans))
1109 }
1110 (Some(tc), None) | (None, Some(tc)) => {
1111 let table_id = tc.table.id();
1113 let rows = tc.table.rows();
1114 let cols = tc.table.columns();
1115 let text_before;
1116 let text_after;
1117 let range;
1118
1119 let inside_pos = if pos_cell.is_some() { pos } else { anchor };
1121 let outside_pos = if pos_cell.is_some() { anchor } else { pos };
1122
1123 if outside_pos < inside_pos {
1124 text_before = true;
1126 text_after = false;
1127 range = CellRange {
1128 table_id,
1129 start_row: 0,
1130 start_col: 0,
1131 end_row: tc.row,
1132 end_col: if cols > 0 { cols - 1 } else { 0 },
1133 };
1134 } else {
1135 text_before = false;
1137 text_after = true;
1138 range = CellRange {
1139 table_id,
1140 start_row: tc.row,
1141 start_col: 0,
1142 end_row: if rows > 0 { rows - 1 } else { 0 },
1143 end_col: if cols > 0 { cols - 1 } else { 0 },
1144 };
1145 }
1146 let spans = self.collect_cell_spans(table_id);
1147 SelectionKind::Mixed {
1148 cell_range: range.expand_for_spans(&spans),
1149 text_before,
1150 text_after,
1151 }
1152 }
1153 }
1154 }
1155
1156 pub fn is_cell_selection(&self) -> bool {
1158 matches!(
1159 self.selection_kind(),
1160 crate::flow::SelectionKind::Cells(_) | crate::flow::SelectionKind::Mixed { .. }
1161 )
1162 }
1163
1164 pub fn selected_cell_range(&self) -> Option<crate::flow::CellRange> {
1166 match self.selection_kind() {
1167 crate::flow::SelectionKind::Cells(r) => Some(r),
1168 crate::flow::SelectionKind::Mixed { cell_range, .. } => Some(cell_range),
1169 _ => None,
1170 }
1171 }
1172
1173 pub fn selected_cells(&self) -> Vec<TableCellRef> {
1175 let range = match self.selected_cell_range() {
1176 Some(r) => r,
1177 None => return Vec::new(),
1178 };
1179 let table = TextTable {
1180 doc: self.doc.clone(),
1181 table_id: range.table_id,
1182 };
1183 let mut cells = Vec::new();
1184 for row in range.start_row..=range.end_row {
1185 for col in range.start_col..=range.end_col {
1186 if table.cell(row, col).is_some() {
1187 cells.push(TableCellRef {
1188 table: table.clone(),
1189 row,
1190 column: col,
1191 });
1192 }
1193 }
1194 }
1195 cells
1196 }
1197
1198 pub fn select_table_cell(&self, table_id: usize, row: usize, col: usize) {
1202 let mut d = self.data.lock();
1203 d.cell_selection_override = Some(crate::flow::CellRange {
1204 table_id,
1205 start_row: row,
1206 start_col: col,
1207 end_row: row,
1208 end_col: col,
1209 });
1210 }
1211
1212 pub fn select_cell_range(
1214 &self,
1215 table_id: usize,
1216 start_row: usize,
1217 start_col: usize,
1218 end_row: usize,
1219 end_col: usize,
1220 ) {
1221 let range = crate::flow::CellRange {
1222 table_id,
1223 start_row,
1224 start_col,
1225 end_row,
1226 end_col,
1227 };
1228 let spans = self.collect_cell_spans(table_id);
1229 let mut d = self.data.lock();
1230 d.cell_selection_override = Some(range.expand_for_spans(&spans));
1231 }
1232
1233 pub fn clear_cell_selection(&self) {
1235 let mut d = self.data.lock();
1236 d.cell_selection_override = None;
1237 }
1238
1239 fn table_cell_at(&self, position: usize) -> Option<TableCellRef> {
1243 let inner = self.doc.lock();
1244 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1245 position: to_i64(position),
1246 };
1247 let block_info =
1248 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1249
1250 let block_id = if to_i64(position) < block_info.block_start && position > 0 {
1251 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1252 position: to_i64(position - 1),
1253 };
1254 let prev_info =
1255 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto).ok()?;
1256 prev_info.block_id as usize
1257 } else {
1258 block_info.block_id as usize
1259 };
1260
1261 let block = crate::text_block::TextBlock {
1262 doc: self.doc.clone(),
1263 block_id,
1264 };
1265 drop(inner);
1266 block.table_cell()
1267 }
1268
1269 fn collect_cell_spans(&self, table_id: usize) -> Vec<(usize, usize, usize, usize)> {
1271 let inner = self.doc.lock();
1272 let table_dto =
1273 match frontend::commands::table_commands::get_table(&inner.ctx, &(table_id as u64))
1274 .ok()
1275 .flatten()
1276 {
1277 Some(t) => t,
1278 None => return Vec::new(),
1279 };
1280
1281 let mut spans = Vec::with_capacity(table_dto.cells.len());
1282 for &cell_id in &table_dto.cells {
1283 if let Some(cell) =
1284 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &cell_id)
1285 .ok()
1286 .flatten()
1287 {
1288 spans.push((
1289 cell.row as usize,
1290 cell.column as usize,
1291 cell.row_span.max(1) as usize,
1292 cell.column_span.max(1) as usize,
1293 ));
1294 }
1295 }
1296 spans
1297 }
1298
1299 pub fn delete_char(&self) -> Result<()> {
1301 let (pos, anchor) = self.read_cursor();
1302 let (del_pos, del_anchor) = if pos != anchor {
1303 (pos, anchor)
1304 } else {
1305 let end = {
1307 let inner = self.doc.lock();
1308 document_inspection_commands::get_document_stats(&inner.ctx)
1309 .map(|s| max_cursor_position(&s))
1310 .unwrap_or(0)
1311 };
1312 if pos >= end {
1313 return Ok(());
1314 }
1315 (pos, pos + 1)
1316 };
1317 self.do_delete(del_pos, del_anchor)
1318 }
1319
1320 pub fn delete_previous_char(&self) -> Result<()> {
1322 let (pos, anchor) = self.read_cursor();
1323 let (del_pos, del_anchor) = if pos != anchor {
1324 (pos, anchor)
1325 } else if pos > 0 {
1326 (pos - 1, pos)
1327 } else {
1328 return Ok(());
1329 };
1330 self.do_delete(del_pos, del_anchor)
1331 }
1332
1333 pub fn remove_selected_text(&self) -> Result<String> {
1335 let (pos, anchor) = self.read_cursor();
1336 if pos == anchor {
1337 return Ok(String::new());
1338 }
1339 let queued = {
1340 let mut inner = self.doc.lock();
1341 let dto = frontend::document_editing::DeleteTextDto {
1342 position: to_i64(pos),
1343 anchor: to_i64(anchor),
1344 };
1345 let result =
1346 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1347 let edit_pos = pos.min(anchor);
1348 let removed = pos.max(anchor) - edit_pos;
1349 let new_pos = to_usize(result.new_position);
1350 inner.adjust_cursors(edit_pos, removed, 0);
1351 {
1352 let mut d = self.data.lock();
1353 d.position = new_pos;
1354 d.anchor = new_pos;
1355 }
1356 inner.modified = true;
1357 inner.invalidate_text_cache();
1358 inner.rehighlight_affected(edit_pos);
1359 inner.queue_event(DocumentEvent::ContentsChanged {
1360 position: edit_pos,
1361 chars_removed: removed,
1362 chars_added: 0,
1363 blocks_affected: 1,
1364 });
1365 inner.check_block_count_changed();
1366 inner.check_flow_changed();
1367 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1369 };
1370 crate::inner::dispatch_queued_events(queued.1);
1371 Ok(queued.0)
1372 }
1373
1374 pub fn current_list(&self) -> Option<crate::TextList> {
1379 let pos = self.position();
1380 let inner = self.doc.lock();
1381 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1382 position: to_i64(pos),
1383 };
1384 let block_info =
1385 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
1386 let block = crate::text_block::TextBlock {
1387 doc: self.doc.clone(),
1388 block_id: block_info.block_id as usize,
1389 };
1390 drop(inner);
1391 block.list()
1392 }
1393
1394 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1396 let (pos, anchor) = self.read_cursor();
1397 let queued = {
1398 let mut inner = self.doc.lock();
1399 let dto = frontend::document_editing::CreateListDto {
1400 position: to_i64(pos),
1401 anchor: to_i64(anchor),
1402 style: style.clone(),
1403 };
1404 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1405 inner.modified = true;
1406 inner.rehighlight_affected(pos.min(anchor));
1407 inner.queue_event(DocumentEvent::ContentsChanged {
1408 position: pos.min(anchor),
1409 chars_removed: 0,
1410 chars_added: 0,
1411 blocks_affected: 1,
1412 });
1413 self.queue_undo_redo_event(&mut inner)
1414 };
1415 crate::inner::dispatch_queued_events(queued);
1416 Ok(())
1417 }
1418
1419 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
1421 let (pos, anchor) = self.read_cursor();
1422 let queued = {
1423 let mut inner = self.doc.lock();
1424 let dto = frontend::document_editing::InsertListDto {
1425 position: to_i64(pos),
1426 anchor: to_i64(anchor),
1427 style: style.clone(),
1428 };
1429 let result =
1430 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1431 let edit_pos = pos.min(anchor);
1432 let removed = pos.max(anchor) - edit_pos;
1433 self.finish_edit_ext(
1434 &mut inner,
1435 edit_pos,
1436 removed,
1437 to_usize(result.new_position),
1438 1,
1439 false,
1440 )
1441 };
1442 crate::inner::dispatch_queued_events(queued);
1443 Ok(())
1444 }
1445
1446 pub fn set_list_format(&self, list_id: usize, format: &crate::ListFormat) -> Result<()> {
1448 let queued = {
1449 let mut inner = self.doc.lock();
1450 let dto = format.to_set_dto(list_id);
1451 document_formatting_commands::set_list_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1452 inner.modified = true;
1453 inner.queue_event(DocumentEvent::FormatChanged {
1454 position: 0,
1455 length: 0,
1456 kind: crate::flow::FormatChangeKind::List,
1457 });
1458 self.queue_undo_redo_event(&mut inner)
1459 };
1460 crate::inner::dispatch_queued_events(queued);
1461 Ok(())
1462 }
1463
1464 pub fn set_current_list_format(&self, format: &crate::ListFormat) -> Result<()> {
1467 let list = self
1468 .current_list()
1469 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a list"))?;
1470 self.set_list_format(list.id(), format)
1471 }
1472
1473 pub fn add_block_to_list(&self, block_id: usize, list_id: usize) -> Result<()> {
1475 let queued = {
1476 let mut inner = self.doc.lock();
1477 let dto = frontend::document_editing::AddBlockToListDto {
1478 block_id: to_i64(block_id),
1479 list_id: to_i64(list_id),
1480 };
1481 document_editing_commands::add_block_to_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1482 inner.modified = true;
1483 inner.queue_event(DocumentEvent::ContentsChanged {
1484 position: 0,
1485 chars_removed: 0,
1486 chars_added: 0,
1487 blocks_affected: 1,
1488 });
1489 self.queue_undo_redo_event(&mut inner)
1490 };
1491 crate::inner::dispatch_queued_events(queued);
1492 Ok(())
1493 }
1494
1495 pub fn add_current_block_to_list(&self, list_id: usize) -> Result<()> {
1497 let pos = self.position();
1498 let inner = self.doc.lock();
1499 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1500 position: to_i64(pos),
1501 };
1502 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1503 drop(inner);
1504 self.add_block_to_list(block_info.block_id as usize, list_id)
1505 }
1506
1507 pub fn remove_block_from_list(&self, block_id: usize) -> Result<()> {
1509 let queued = {
1510 let mut inner = self.doc.lock();
1511 let dto = frontend::document_editing::RemoveBlockFromListDto {
1512 block_id: to_i64(block_id),
1513 };
1514 document_editing_commands::remove_block_from_list(
1515 &inner.ctx,
1516 Some(inner.stack_id),
1517 &dto,
1518 )?;
1519 inner.modified = true;
1520 inner.queue_event(DocumentEvent::ContentsChanged {
1521 position: 0,
1522 chars_removed: 0,
1523 chars_added: 0,
1524 blocks_affected: 1,
1525 });
1526 self.queue_undo_redo_event(&mut inner)
1527 };
1528 crate::inner::dispatch_queued_events(queued);
1529 Ok(())
1530 }
1531
1532 pub fn remove_current_block_from_list(&self) -> Result<()> {
1535 let pos = self.position();
1536 let inner = self.doc.lock();
1537 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1538 position: to_i64(pos),
1539 };
1540 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1541 drop(inner);
1542 self.remove_block_from_list(block_info.block_id as usize)
1543 }
1544
1545 pub fn remove_list_item(&self, list_id: usize, index: usize) -> Result<()> {
1548 let list = crate::text_list::TextList {
1549 doc: self.doc.clone(),
1550 list_id,
1551 };
1552 let block = list
1553 .item(index)
1554 .ok_or_else(|| anyhow::anyhow!("list item index {index} out of range"))?;
1555 self.remove_block_from_list(block.id())
1556 }
1557
1558 pub fn char_format(&self) -> Result<TextFormat> {
1562 let pos = self.position();
1563 let inner = self.doc.lock();
1564 let dto = frontend::document_inspection::GetTextAtPositionDto {
1565 position: to_i64(pos),
1566 length: 1,
1567 };
1568 let text_info = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
1569 let element_id = text_info.element_id as u64;
1570 let element = inline_element_commands::get_inline_element(&inner.ctx, &element_id)?
1571 .ok_or_else(|| anyhow::anyhow!("element not found at position"))?;
1572 Ok(TextFormat::from(&element))
1573 }
1574
1575 pub fn block_format(&self) -> Result<BlockFormat> {
1577 let pos = self.position();
1578 let inner = self.doc.lock();
1579 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1580 position: to_i64(pos),
1581 };
1582 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1583 let block_id = block_info.block_id as u64;
1584 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
1585 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
1586 Ok(BlockFormat::from(&block))
1587 }
1588
1589 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
1593 let (pos, anchor) = self.read_cursor();
1594 let queued = {
1595 let mut inner = self.doc.lock();
1596 let dto = format.to_set_dto(pos, anchor);
1597 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1598 let start = pos.min(anchor);
1599 let length = pos.max(anchor) - start;
1600 inner.modified = true;
1601 inner.queue_event(DocumentEvent::FormatChanged {
1602 position: start,
1603 length,
1604 kind: crate::flow::FormatChangeKind::Character,
1605 });
1606 self.queue_undo_redo_event(&mut inner)
1607 };
1608 crate::inner::dispatch_queued_events(queued);
1609 Ok(())
1610 }
1611
1612 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
1614 let (pos, anchor) = self.read_cursor();
1615 let queued = {
1616 let mut inner = self.doc.lock();
1617 let dto = format.to_merge_dto(pos, anchor);
1618 document_formatting_commands::merge_text_format(
1619 &inner.ctx,
1620 Some(inner.stack_id),
1621 &dto,
1622 )?;
1623 let start = pos.min(anchor);
1624 let length = pos.max(anchor) - start;
1625 inner.modified = true;
1626 inner.queue_event(DocumentEvent::FormatChanged {
1627 position: start,
1628 length,
1629 kind: crate::flow::FormatChangeKind::Character,
1630 });
1631 self.queue_undo_redo_event(&mut inner)
1632 };
1633 crate::inner::dispatch_queued_events(queued);
1634 Ok(())
1635 }
1636
1637 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
1639 let (pos, anchor) = self.read_cursor();
1640 let queued = {
1641 let mut inner = self.doc.lock();
1642 let dto = format.to_set_dto(pos, anchor);
1643 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1644 let start = pos.min(anchor);
1645 let length = pos.max(anchor) - start;
1646 inner.modified = true;
1647 inner.queue_event(DocumentEvent::FormatChanged {
1648 position: start,
1649 length,
1650 kind: crate::flow::FormatChangeKind::Block,
1651 });
1652 self.queue_undo_redo_event(&mut inner)
1653 };
1654 crate::inner::dispatch_queued_events(queued);
1655 Ok(())
1656 }
1657
1658 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
1660 let (pos, anchor) = self.read_cursor();
1661 let queued = {
1662 let mut inner = self.doc.lock();
1663 let dto = format.to_set_dto(pos, anchor, frame_id);
1664 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1665 let start = pos.min(anchor);
1666 let length = pos.max(anchor) - start;
1667 inner.modified = true;
1668 inner.queue_event(DocumentEvent::FormatChanged {
1669 position: start,
1670 length,
1671 kind: crate::flow::FormatChangeKind::Block,
1672 });
1673 self.queue_undo_redo_event(&mut inner)
1674 };
1675 crate::inner::dispatch_queued_events(queued);
1676 Ok(())
1677 }
1678
1679 pub fn begin_edit_block(&self) {
1683 let inner = self.doc.lock();
1684 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1685 }
1686
1687 pub fn end_edit_block(&self) {
1689 let inner = self.doc.lock();
1690 undo_redo_commands::end_composite(&inner.ctx);
1691 }
1692
1693 pub fn join_previous_edit_block(&self) {
1700 self.begin_edit_block();
1701 }
1702
1703 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
1707 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1708 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1709 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1710 inner.take_queued_events()
1711 }
1712
1713 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
1714 let queued = {
1715 let mut inner = self.doc.lock();
1716 let dto = frontend::document_editing::DeleteTextDto {
1717 position: to_i64(pos),
1718 anchor: to_i64(anchor),
1719 };
1720 let result =
1721 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1722 let edit_pos = pos.min(anchor);
1723 let removed = pos.max(anchor) - edit_pos;
1724 let new_pos = to_usize(result.new_position);
1725 inner.adjust_cursors(edit_pos, removed, 0);
1726 {
1727 let mut d = self.data.lock();
1728 d.position = new_pos;
1729 d.anchor = new_pos;
1730 }
1731 inner.modified = true;
1732 inner.invalidate_text_cache();
1733 inner.rehighlight_affected(edit_pos);
1734 inner.queue_event(DocumentEvent::ContentsChanged {
1735 position: edit_pos,
1736 chars_removed: removed,
1737 chars_added: 0,
1738 blocks_affected: 1,
1739 });
1740 inner.check_block_count_changed();
1741 inner.check_flow_changed();
1742 self.queue_undo_redo_event(&mut inner)
1743 };
1744 crate::inner::dispatch_queued_events(queued);
1745 Ok(())
1746 }
1747
1748 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
1750 let pos = self.position();
1751 match op {
1752 MoveOperation::NoMove => pos,
1753 MoveOperation::Start => 0,
1754 MoveOperation::End => {
1755 let inner = self.doc.lock();
1756 document_inspection_commands::get_document_stats(&inner.ctx)
1757 .map(|s| max_cursor_position(&s))
1758 .unwrap_or(pos)
1759 }
1760 MoveOperation::NextCharacter | MoveOperation::Right => pos + n,
1761 MoveOperation::PreviousCharacter | MoveOperation::Left => pos.saturating_sub(n),
1762 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
1763 let inner = self.doc.lock();
1764 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1765 position: to_i64(pos),
1766 };
1767 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1768 .map(|info| to_usize(info.block_start))
1769 .unwrap_or(pos)
1770 }
1771 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
1772 let inner = self.doc.lock();
1773 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1774 position: to_i64(pos),
1775 };
1776 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1777 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
1778 .unwrap_or(pos)
1779 }
1780 MoveOperation::NextBlock => {
1781 let inner = self.doc.lock();
1782 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1783 position: to_i64(pos),
1784 };
1785 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1786 .map(|info| {
1787 to_usize(info.block_start) + to_usize(info.block_length) + 1
1789 })
1790 .unwrap_or(pos)
1791 }
1792 MoveOperation::PreviousBlock => {
1793 let inner = self.doc.lock();
1794 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1795 position: to_i64(pos),
1796 };
1797 let block_start =
1798 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1799 .map(|info| to_usize(info.block_start))
1800 .unwrap_or(pos);
1801 if block_start >= 2 {
1802 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1804 position: to_i64(block_start - 2),
1805 };
1806 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
1807 .map(|info| to_usize(info.block_start))
1808 .unwrap_or(0)
1809 } else {
1810 0
1811 }
1812 }
1813 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
1814 let (_, end) = self.find_word_boundaries(pos);
1815 if end == pos {
1817 let inner = self.doc.lock();
1819 let max_pos = document_inspection_commands::get_document_stats(&inner.ctx)
1820 .map(|s| max_cursor_position(&s))
1821 .unwrap_or(0);
1822 let scan_len = max_pos.saturating_sub(pos).min(64);
1823 if scan_len == 0 {
1824 return pos;
1825 }
1826 let dto = frontend::document_inspection::GetTextAtPositionDto {
1827 position: to_i64(pos),
1828 length: to_i64(scan_len),
1829 };
1830 if let Ok(r) =
1831 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
1832 {
1833 for (i, ch) in r.text.chars().enumerate() {
1834 if ch.is_alphanumeric() || ch == '_' {
1835 let word_pos = pos + i;
1837 drop(inner);
1838 let (_, word_end) = self.find_word_boundaries(word_pos);
1839 return word_end;
1840 }
1841 }
1842 }
1843 pos + scan_len
1844 } else {
1845 end
1846 }
1847 }
1848 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
1849 let (start, _) = self.find_word_boundaries(pos);
1850 if start < pos {
1851 start
1852 } else if pos > 0 {
1853 let mut search = pos - 1;
1856 loop {
1857 let (ws, we) = self.find_word_boundaries(search);
1858 if ws < we {
1859 break ws;
1861 }
1862 if search == 0 {
1864 break 0;
1865 }
1866 search -= 1;
1867 }
1868 } else {
1869 0
1870 }
1871 }
1872 MoveOperation::Up | MoveOperation::Down => {
1873 if matches!(op, MoveOperation::Up) {
1876 self.resolve_move(MoveOperation::PreviousBlock, 1)
1877 } else {
1878 self.resolve_move(MoveOperation::NextBlock, 1)
1879 }
1880 }
1881 }
1882 }
1883
1884 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
1890 let inner = self.doc.lock();
1891 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
1893 position: to_i64(pos),
1894 };
1895 let block_info =
1896 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
1897 Ok(info) => info,
1898 Err(_) => return (pos, pos),
1899 };
1900
1901 let block_start = to_usize(block_info.block_start);
1902 let block_length = to_usize(block_info.block_length);
1903 if block_length == 0 {
1904 return (pos, pos);
1905 }
1906
1907 let dto = frontend::document_inspection::GetTextAtPositionDto {
1908 position: to_i64(block_start),
1909 length: to_i64(block_length),
1910 };
1911 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
1912 Ok(r) => r.text,
1913 Err(_) => return (pos, pos),
1914 };
1915
1916 let cursor_offset = pos.saturating_sub(block_start);
1918
1919 let mut last_char_start = 0;
1921 let mut last_char_end = 0;
1922
1923 for (word_byte_start, word) in text.unicode_word_indices() {
1924 let word_char_start = text[..word_byte_start].chars().count();
1926 let word_char_len = word.chars().count();
1927 let word_char_end = word_char_start + word_char_len;
1928
1929 last_char_start = word_char_start;
1930 last_char_end = word_char_end;
1931
1932 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
1933 return (block_start + word_char_start, block_start + word_char_end);
1934 }
1935 }
1936
1937 if cursor_offset == last_char_end && last_char_start < last_char_end {
1939 return (block_start + last_char_start, block_start + last_char_end);
1940 }
1941
1942 (pos, pos)
1943 }
1944}