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
25pub struct TextCursor {
33 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
34 pub(crate) data: Arc<Mutex<CursorData>>,
35}
36
37impl Clone for TextCursor {
38 fn clone(&self) -> Self {
39 let (position, anchor) = {
40 let d = self.data.lock();
41 (d.position, d.anchor)
42 };
43 let data = {
44 let mut inner = self.doc.lock();
45 let data = Arc::new(Mutex::new(CursorData { position, anchor }));
46 inner.cursors.push(Arc::downgrade(&data));
47 data
48 };
49 TextCursor {
50 doc: self.doc.clone(),
51 data,
52 }
53 }
54}
55
56impl TextCursor {
57 fn read_cursor(&self) -> (usize, usize) {
60 let d = self.data.lock();
61 (d.position, d.anchor)
62 }
63
64 fn finish_edit(
68 &self,
69 inner: &mut TextDocumentInner,
70 edit_pos: usize,
71 removed: usize,
72 new_pos: usize,
73 blocks_affected: usize,
74 ) -> QueuedEvents {
75 let added = new_pos - edit_pos;
76 inner.adjust_cursors(edit_pos, removed, added);
77 {
78 let mut d = self.data.lock();
79 d.position = new_pos;
80 d.anchor = new_pos;
81 }
82 inner.modified = true;
83 inner.invalidate_text_cache();
84 inner.rehighlight_affected(edit_pos);
85 inner.queue_event(DocumentEvent::ContentsChanged {
86 position: edit_pos,
87 chars_removed: removed,
88 chars_added: added,
89 blocks_affected,
90 });
91 inner.check_block_count_changed();
92 inner.check_flow_changed();
93 self.queue_undo_redo_event(inner)
94 }
95
96 pub fn position(&self) -> usize {
100 self.data.lock().position
101 }
102
103 pub fn anchor(&self) -> usize {
105 self.data.lock().anchor
106 }
107
108 pub fn has_selection(&self) -> bool {
110 let d = self.data.lock();
111 d.position != d.anchor
112 }
113
114 pub fn selection_start(&self) -> usize {
116 let d = self.data.lock();
117 d.position.min(d.anchor)
118 }
119
120 pub fn selection_end(&self) -> usize {
122 let d = self.data.lock();
123 d.position.max(d.anchor)
124 }
125
126 pub fn selected_text(&self) -> Result<String> {
128 let (pos, anchor) = self.read_cursor();
129 if pos == anchor {
130 return Ok(String::new());
131 }
132 let start = pos.min(anchor);
133 let len = pos.max(anchor) - start;
134 let inner = self.doc.lock();
135 let dto = frontend::document_inspection::GetTextAtPositionDto {
136 position: to_i64(start),
137 length: to_i64(len),
138 };
139 let result = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
140 Ok(result.text)
141 }
142
143 pub fn clear_selection(&self) {
145 let mut d = self.data.lock();
146 d.anchor = d.position;
147 }
148
149 pub fn at_block_start(&self) -> bool {
153 let pos = self.position();
154 let inner = self.doc.lock();
155 let dto = frontend::document_inspection::GetBlockAtPositionDto {
156 position: to_i64(pos),
157 };
158 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
159 pos == to_usize(info.block_start)
160 } else {
161 false
162 }
163 }
164
165 pub fn at_block_end(&self) -> bool {
167 let pos = self.position();
168 let inner = self.doc.lock();
169 let dto = frontend::document_inspection::GetBlockAtPositionDto {
170 position: to_i64(pos),
171 };
172 if let Ok(info) = document_inspection_commands::get_block_at_position(&inner.ctx, &dto) {
173 pos == to_usize(info.block_start) + to_usize(info.block_length)
174 } else {
175 false
176 }
177 }
178
179 pub fn at_start(&self) -> bool {
181 self.data.lock().position == 0
182 }
183
184 pub fn at_end(&self) -> bool {
186 let pos = self.position();
187 let inner = self.doc.lock();
188 let stats = document_inspection_commands::get_document_stats(&inner.ctx).unwrap_or({
189 frontend::document_inspection::DocumentStatsDto {
190 character_count: 0,
191 word_count: 0,
192 block_count: 0,
193 frame_count: 0,
194 image_count: 0,
195 list_count: 0,
196 table_count: 0,
197 }
198 });
199 pos >= to_usize(stats.character_count)
200 }
201
202 pub fn block_number(&self) -> usize {
204 let pos = self.position();
205 let inner = self.doc.lock();
206 let dto = frontend::document_inspection::GetBlockAtPositionDto {
207 position: to_i64(pos),
208 };
209 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
210 .map(|info| to_usize(info.block_number))
211 .unwrap_or(0)
212 }
213
214 pub fn position_in_block(&self) -> usize {
216 let pos = self.position();
217 let inner = self.doc.lock();
218 let dto = frontend::document_inspection::GetBlockAtPositionDto {
219 position: to_i64(pos),
220 };
221 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
222 .map(|info| pos.saturating_sub(to_usize(info.block_start)))
223 .unwrap_or(0)
224 }
225
226 pub fn set_position(&self, position: usize, mode: MoveMode) {
230 let end = {
232 let inner = self.doc.lock();
233 document_inspection_commands::get_document_stats(&inner.ctx)
234 .map(|s| to_usize(s.character_count))
235 .unwrap_or(0)
236 };
237 let pos = position.min(end);
238 let mut d = self.data.lock();
239 d.position = pos;
240 if mode == MoveMode::MoveAnchor {
241 d.anchor = pos;
242 }
243 }
244
245 pub fn move_position(&self, operation: MoveOperation, mode: MoveMode, n: usize) -> bool {
251 let old_pos = self.position();
252 let target = self.resolve_move(operation, n);
253 self.set_position(target, mode);
254 self.position() != old_pos
255 }
256
257 pub fn select(&self, selection: SelectionType) {
259 match selection {
260 SelectionType::Document => {
261 let end = {
262 let inner = self.doc.lock();
263 document_inspection_commands::get_document_stats(&inner.ctx)
264 .map(|s| to_usize(s.character_count))
265 .unwrap_or(0)
266 };
267 let mut d = self.data.lock();
268 d.anchor = 0;
269 d.position = end;
270 }
271 SelectionType::BlockUnderCursor | SelectionType::LineUnderCursor => {
272 let pos = self.position();
273 let inner = self.doc.lock();
274 let dto = frontend::document_inspection::GetBlockAtPositionDto {
275 position: to_i64(pos),
276 };
277 if let Ok(info) =
278 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
279 {
280 let start = to_usize(info.block_start);
281 let end = start + to_usize(info.block_length);
282 drop(inner);
283 let mut d = self.data.lock();
284 d.anchor = start;
285 d.position = end;
286 }
287 }
288 SelectionType::WordUnderCursor => {
289 let pos = self.position();
290 let (word_start, word_end) = self.find_word_boundaries(pos);
291 let mut d = self.data.lock();
292 d.anchor = word_start;
293 d.position = word_end;
294 }
295 }
296 }
297
298 pub fn insert_text(&self, text: &str) -> Result<()> {
302 let (pos, anchor) = self.read_cursor();
303
304 let dto = frontend::document_editing::InsertTextDto {
306 position: to_i64(pos),
307 anchor: to_i64(anchor),
308 text: text.into(),
309 };
310
311 let queued = {
312 let mut inner = self.doc.lock();
313 let result = match document_editing_commands::insert_text(
314 &inner.ctx,
315 Some(inner.stack_id),
316 &dto,
317 ) {
318 Ok(r) => r,
319 Err(_) if pos != anchor => {
320 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
322
323 let del_dto = frontend::document_editing::DeleteTextDto {
324 position: to_i64(pos),
325 anchor: to_i64(anchor),
326 };
327 let del_result = document_editing_commands::delete_text(
328 &inner.ctx,
329 Some(inner.stack_id),
330 &del_dto,
331 )?;
332 let del_pos = to_usize(del_result.new_position);
333
334 let ins_dto = frontend::document_editing::InsertTextDto {
335 position: to_i64(del_pos),
336 anchor: to_i64(del_pos),
337 text: text.into(),
338 };
339 let ins_result = document_editing_commands::insert_text(
340 &inner.ctx,
341 Some(inner.stack_id),
342 &ins_dto,
343 )?;
344
345 undo_redo_commands::end_composite(&inner.ctx);
346 ins_result
347 }
348 Err(e) => return Err(e),
349 };
350
351 let edit_pos = pos.min(anchor);
352 let removed = pos.max(anchor) - edit_pos;
353 self.finish_edit(
354 &mut inner,
355 edit_pos,
356 removed,
357 to_usize(result.new_position),
358 to_usize(result.blocks_affected),
359 )
360 };
361 crate::inner::dispatch_queued_events(queued);
362 Ok(())
363 }
364
365 pub fn insert_formatted_text(&self, text: &str, format: &TextFormat) -> Result<()> {
367 let (pos, anchor) = self.read_cursor();
368 let queued = {
369 let mut inner = self.doc.lock();
370 let dto = frontend::document_editing::InsertFormattedTextDto {
371 position: to_i64(pos),
372 anchor: to_i64(anchor),
373 text: text.into(),
374 font_family: format.font_family.clone().unwrap_or_default(),
375 font_point_size: format.font_point_size.map(|v| v as i64).unwrap_or(0),
376 font_bold: format.font_bold.unwrap_or(false),
377 font_italic: format.font_italic.unwrap_or(false),
378 font_underline: format.font_underline.unwrap_or(false),
379 font_strikeout: format.font_strikeout.unwrap_or(false),
380 };
381 let result = document_editing_commands::insert_formatted_text(
382 &inner.ctx,
383 Some(inner.stack_id),
384 &dto,
385 )?;
386 let edit_pos = pos.min(anchor);
387 let removed = pos.max(anchor) - edit_pos;
388 self.finish_edit(
389 &mut inner,
390 edit_pos,
391 removed,
392 to_usize(result.new_position),
393 1,
394 )
395 };
396 crate::inner::dispatch_queued_events(queued);
397 Ok(())
398 }
399
400 pub fn insert_block(&self) -> Result<()> {
402 let (pos, anchor) = self.read_cursor();
403 let queued = {
404 let mut inner = self.doc.lock();
405 let dto = frontend::document_editing::InsertBlockDto {
406 position: to_i64(pos),
407 anchor: to_i64(anchor),
408 };
409 let result =
410 document_editing_commands::insert_block(&inner.ctx, Some(inner.stack_id), &dto)?;
411 let edit_pos = pos.min(anchor);
412 let removed = pos.max(anchor) - edit_pos;
413 self.finish_edit(
414 &mut inner,
415 edit_pos,
416 removed,
417 to_usize(result.new_position),
418 2,
419 )
420 };
421 crate::inner::dispatch_queued_events(queued);
422 Ok(())
423 }
424
425 pub fn insert_html(&self, html: &str) -> Result<()> {
427 let (pos, anchor) = self.read_cursor();
428 let queued = {
429 let mut inner = self.doc.lock();
430 let dto = frontend::document_editing::InsertHtmlAtPositionDto {
431 position: to_i64(pos),
432 anchor: to_i64(anchor),
433 html: html.into(),
434 };
435 let result = document_editing_commands::insert_html_at_position(
436 &inner.ctx,
437 Some(inner.stack_id),
438 &dto,
439 )?;
440 let edit_pos = pos.min(anchor);
441 let removed = pos.max(anchor) - edit_pos;
442 self.finish_edit(
443 &mut inner,
444 edit_pos,
445 removed,
446 to_usize(result.new_position),
447 to_usize(result.blocks_added),
448 )
449 };
450 crate::inner::dispatch_queued_events(queued);
451 Ok(())
452 }
453
454 pub fn insert_markdown(&self, markdown: &str) -> Result<()> {
456 let (pos, anchor) = self.read_cursor();
457 let queued = {
458 let mut inner = self.doc.lock();
459 let dto = frontend::document_editing::InsertMarkdownAtPositionDto {
460 position: to_i64(pos),
461 anchor: to_i64(anchor),
462 markdown: markdown.into(),
463 };
464 let result = document_editing_commands::insert_markdown_at_position(
465 &inner.ctx,
466 Some(inner.stack_id),
467 &dto,
468 )?;
469 let edit_pos = pos.min(anchor);
470 let removed = pos.max(anchor) - edit_pos;
471 self.finish_edit(
472 &mut inner,
473 edit_pos,
474 removed,
475 to_usize(result.new_position),
476 to_usize(result.blocks_added),
477 )
478 };
479 crate::inner::dispatch_queued_events(queued);
480 Ok(())
481 }
482
483 pub fn insert_fragment(&self, fragment: &DocumentFragment) -> Result<()> {
485 let (pos, anchor) = self.read_cursor();
486 let queued = {
487 let mut inner = self.doc.lock();
488 let dto = frontend::document_editing::InsertFragmentDto {
489 position: to_i64(pos),
490 anchor: to_i64(anchor),
491 fragment_data: fragment.raw_data().into(),
492 };
493 let result =
494 document_editing_commands::insert_fragment(&inner.ctx, Some(inner.stack_id), &dto)?;
495 let edit_pos = pos.min(anchor);
496 let removed = pos.max(anchor) - edit_pos;
497 self.finish_edit(
498 &mut inner,
499 edit_pos,
500 removed,
501 to_usize(result.new_position),
502 to_usize(result.blocks_added),
503 )
504 };
505 crate::inner::dispatch_queued_events(queued);
506 Ok(())
507 }
508
509 pub fn selection(&self) -> DocumentFragment {
511 let (pos, anchor) = self.read_cursor();
512 if pos == anchor {
513 return DocumentFragment::new();
514 }
515 let inner = self.doc.lock();
516 let dto = frontend::document_inspection::ExtractFragmentDto {
517 position: to_i64(pos),
518 anchor: to_i64(anchor),
519 };
520 match document_inspection_commands::extract_fragment(&inner.ctx, &dto) {
521 Ok(result) => DocumentFragment::from_raw(result.fragment_data, result.plain_text),
522 Err(_) => DocumentFragment::new(),
523 }
524 }
525
526 pub fn insert_image(&self, name: &str, width: u32, height: u32) -> Result<()> {
528 let (pos, anchor) = self.read_cursor();
529 let queued = {
530 let mut inner = self.doc.lock();
531 let dto = frontend::document_editing::InsertImageDto {
532 position: to_i64(pos),
533 anchor: to_i64(anchor),
534 image_name: name.into(),
535 width: width as i64,
536 height: height as i64,
537 };
538 let result =
539 document_editing_commands::insert_image(&inner.ctx, Some(inner.stack_id), &dto)?;
540 let edit_pos = pos.min(anchor);
541 let removed = pos.max(anchor) - edit_pos;
542 self.finish_edit(
543 &mut inner,
544 edit_pos,
545 removed,
546 to_usize(result.new_position),
547 1,
548 )
549 };
550 crate::inner::dispatch_queued_events(queued);
551 Ok(())
552 }
553
554 pub fn insert_frame(&self) -> Result<()> {
556 let (pos, anchor) = self.read_cursor();
557 let queued = {
558 let mut inner = self.doc.lock();
559 let dto = frontend::document_editing::InsertFrameDto {
560 position: to_i64(pos),
561 anchor: to_i64(anchor),
562 };
563 document_editing_commands::insert_frame(&inner.ctx, Some(inner.stack_id), &dto)?;
564 inner.modified = true;
567 inner.invalidate_text_cache();
568 inner.rehighlight_affected(pos.min(anchor));
569 inner.queue_event(DocumentEvent::ContentsChanged {
570 position: pos.min(anchor),
571 chars_removed: 0,
572 chars_added: 0,
573 blocks_affected: 1,
574 });
575 inner.check_block_count_changed();
576 inner.check_flow_changed();
577 self.queue_undo_redo_event(&mut inner)
578 };
579 crate::inner::dispatch_queued_events(queued);
580 Ok(())
581 }
582
583 pub fn insert_table(&self, rows: usize, columns: usize) -> Result<TextTable> {
589 let (pos, anchor) = self.read_cursor();
590 let (table_id, queued) = {
591 let mut inner = self.doc.lock();
592 let dto = frontend::document_editing::InsertTableDto {
593 position: to_i64(pos),
594 anchor: to_i64(anchor),
595 rows: to_i64(rows),
596 columns: to_i64(columns),
597 };
598 let result =
599 document_editing_commands::insert_table(&inner.ctx, Some(inner.stack_id), &dto)?;
600 let new_pos = to_usize(result.new_position);
601 let table_id = to_usize(result.table_id);
602 inner.adjust_cursors(pos.min(anchor), 0, new_pos - pos.min(anchor));
603 {
604 let mut d = self.data.lock();
605 d.position = new_pos;
606 d.anchor = new_pos;
607 }
608 inner.modified = true;
609 inner.invalidate_text_cache();
610 inner.rehighlight_affected(pos.min(anchor));
611 inner.queue_event(DocumentEvent::ContentsChanged {
612 position: pos.min(anchor),
613 chars_removed: 0,
614 chars_added: new_pos - pos.min(anchor),
615 blocks_affected: 1,
616 });
617 inner.check_block_count_changed();
618 inner.check_flow_changed();
619 (table_id, self.queue_undo_redo_event(&mut inner))
620 };
621 crate::inner::dispatch_queued_events(queued);
622 Ok(TextTable {
623 doc: self.doc.clone(),
624 table_id,
625 })
626 }
627
628 pub fn current_table(&self) -> Option<TextTable> {
633 self.current_table_cell().map(|c| c.table)
634 }
635
636 pub fn current_table_cell(&self) -> Option<TableCellRef> {
641 let pos = self.position();
642 let inner = self.doc.lock();
643 let dto = frontend::document_inspection::GetBlockAtPositionDto {
645 position: to_i64(pos),
646 };
647 let block_info =
648 document_inspection_commands::get_block_at_position(&inner.ctx, &dto).ok()?;
649 let block = crate::text_block::TextBlock {
650 doc: self.doc.clone(),
651 block_id: block_info.block_id as usize,
652 };
653 drop(inner);
655 block.table_cell()
656 }
657
658 pub fn remove_table(&self, table_id: usize) -> Result<()> {
662 let queued = {
663 let mut inner = self.doc.lock();
664 let dto = frontend::document_editing::RemoveTableDto {
665 table_id: to_i64(table_id),
666 };
667 document_editing_commands::remove_table(&inner.ctx, Some(inner.stack_id), &dto)?;
668 inner.modified = true;
669 inner.invalidate_text_cache();
670 inner.rehighlight_all();
671 inner.check_block_count_changed();
672 inner.check_flow_changed();
673 self.queue_undo_redo_event(&mut inner)
674 };
675 crate::inner::dispatch_queued_events(queued);
676 Ok(())
677 }
678
679 pub fn insert_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
681 let queued = {
682 let mut inner = self.doc.lock();
683 let dto = frontend::document_editing::InsertTableRowDto {
684 table_id: to_i64(table_id),
685 row_index: to_i64(row_index),
686 };
687 document_editing_commands::insert_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
688 inner.modified = true;
689 inner.invalidate_text_cache();
690 inner.rehighlight_all();
691 inner.check_block_count_changed();
692 self.queue_undo_redo_event(&mut inner)
693 };
694 crate::inner::dispatch_queued_events(queued);
695 Ok(())
696 }
697
698 pub fn insert_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
700 let queued = {
701 let mut inner = self.doc.lock();
702 let dto = frontend::document_editing::InsertTableColumnDto {
703 table_id: to_i64(table_id),
704 column_index: to_i64(column_index),
705 };
706 document_editing_commands::insert_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
707 inner.modified = true;
708 inner.invalidate_text_cache();
709 inner.rehighlight_all();
710 inner.check_block_count_changed();
711 self.queue_undo_redo_event(&mut inner)
712 };
713 crate::inner::dispatch_queued_events(queued);
714 Ok(())
715 }
716
717 pub fn remove_table_row(&self, table_id: usize, row_index: usize) -> Result<()> {
719 let queued = {
720 let mut inner = self.doc.lock();
721 let dto = frontend::document_editing::RemoveTableRowDto {
722 table_id: to_i64(table_id),
723 row_index: to_i64(row_index),
724 };
725 document_editing_commands::remove_table_row(&inner.ctx, Some(inner.stack_id), &dto)?;
726 inner.modified = true;
727 inner.invalidate_text_cache();
728 inner.rehighlight_all();
729 inner.check_block_count_changed();
730 self.queue_undo_redo_event(&mut inner)
731 };
732 crate::inner::dispatch_queued_events(queued);
733 Ok(())
734 }
735
736 pub fn remove_table_column(&self, table_id: usize, column_index: usize) -> Result<()> {
738 let queued = {
739 let mut inner = self.doc.lock();
740 let dto = frontend::document_editing::RemoveTableColumnDto {
741 table_id: to_i64(table_id),
742 column_index: to_i64(column_index),
743 };
744 document_editing_commands::remove_table_column(&inner.ctx, Some(inner.stack_id), &dto)?;
745 inner.modified = true;
746 inner.invalidate_text_cache();
747 inner.rehighlight_all();
748 inner.check_block_count_changed();
749 self.queue_undo_redo_event(&mut inner)
750 };
751 crate::inner::dispatch_queued_events(queued);
752 Ok(())
753 }
754
755 pub fn merge_table_cells(
757 &self,
758 table_id: usize,
759 start_row: usize,
760 start_column: usize,
761 end_row: usize,
762 end_column: usize,
763 ) -> Result<()> {
764 let queued = {
765 let mut inner = self.doc.lock();
766 let dto = frontend::document_editing::MergeTableCellsDto {
767 table_id: to_i64(table_id),
768 start_row: to_i64(start_row),
769 start_column: to_i64(start_column),
770 end_row: to_i64(end_row),
771 end_column: to_i64(end_column),
772 };
773 document_editing_commands::merge_table_cells(&inner.ctx, Some(inner.stack_id), &dto)?;
774 inner.modified = true;
775 inner.invalidate_text_cache();
776 inner.rehighlight_all();
777 inner.check_block_count_changed();
778 self.queue_undo_redo_event(&mut inner)
779 };
780 crate::inner::dispatch_queued_events(queued);
781 Ok(())
782 }
783
784 pub fn split_table_cell(
786 &self,
787 cell_id: usize,
788 split_rows: usize,
789 split_columns: usize,
790 ) -> Result<()> {
791 let queued = {
792 let mut inner = self.doc.lock();
793 let dto = frontend::document_editing::SplitTableCellDto {
794 cell_id: to_i64(cell_id),
795 split_rows: to_i64(split_rows),
796 split_columns: to_i64(split_columns),
797 };
798 document_editing_commands::split_table_cell(&inner.ctx, Some(inner.stack_id), &dto)?;
799 inner.modified = true;
800 inner.invalidate_text_cache();
801 inner.rehighlight_all();
802 inner.check_block_count_changed();
803 self.queue_undo_redo_event(&mut inner)
804 };
805 crate::inner::dispatch_queued_events(queued);
806 Ok(())
807 }
808
809 pub fn set_table_format(
813 &self,
814 table_id: usize,
815 format: &crate::flow::TableFormat,
816 ) -> Result<()> {
817 let queued = {
818 let mut inner = self.doc.lock();
819 let dto = format.to_set_dto(table_id);
820 document_formatting_commands::set_table_format(&inner.ctx, Some(inner.stack_id), &dto)?;
821 inner.modified = true;
822 inner.queue_event(DocumentEvent::FormatChanged {
823 position: 0,
824 length: 0,
825 kind: crate::flow::FormatChangeKind::Block,
826 });
827 self.queue_undo_redo_event(&mut inner)
828 };
829 crate::inner::dispatch_queued_events(queued);
830 Ok(())
831 }
832
833 pub fn set_table_cell_format(
835 &self,
836 cell_id: usize,
837 format: &crate::flow::CellFormat,
838 ) -> Result<()> {
839 let queued = {
840 let mut inner = self.doc.lock();
841 let dto = format.to_set_dto(cell_id);
842 document_formatting_commands::set_table_cell_format(
843 &inner.ctx,
844 Some(inner.stack_id),
845 &dto,
846 )?;
847 inner.modified = true;
848 inner.queue_event(DocumentEvent::FormatChanged {
849 position: 0,
850 length: 0,
851 kind: crate::flow::FormatChangeKind::Block,
852 });
853 self.queue_undo_redo_event(&mut inner)
854 };
855 crate::inner::dispatch_queued_events(queued);
856 Ok(())
857 }
858
859 pub fn remove_current_table(&self) -> Result<()> {
864 let table = self
865 .current_table()
866 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
867 self.remove_table(table.id())
868 }
869
870 pub fn insert_row_above(&self) -> Result<()> {
873 let cell_ref = self
874 .current_table_cell()
875 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
876 self.insert_table_row(cell_ref.table.id(), cell_ref.row)
877 }
878
879 pub fn insert_row_below(&self) -> Result<()> {
882 let cell_ref = self
883 .current_table_cell()
884 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
885 self.insert_table_row(cell_ref.table.id(), cell_ref.row + 1)
886 }
887
888 pub fn insert_column_before(&self) -> Result<()> {
891 let cell_ref = self
892 .current_table_cell()
893 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
894 self.insert_table_column(cell_ref.table.id(), cell_ref.column)
895 }
896
897 pub fn insert_column_after(&self) -> Result<()> {
900 let cell_ref = self
901 .current_table_cell()
902 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
903 self.insert_table_column(cell_ref.table.id(), cell_ref.column + 1)
904 }
905
906 pub fn remove_current_row(&self) -> Result<()> {
909 let cell_ref = self
910 .current_table_cell()
911 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
912 self.remove_table_row(cell_ref.table.id(), cell_ref.row)
913 }
914
915 pub fn remove_current_column(&self) -> Result<()> {
918 let cell_ref = self
919 .current_table_cell()
920 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
921 self.remove_table_column(cell_ref.table.id(), cell_ref.column)
922 }
923
924 pub fn merge_selected_cells(&self) -> Result<()> {
931 let pos_cell = self
932 .current_table_cell()
933 .ok_or_else(|| anyhow::anyhow!("cursor position is not inside a table"))?;
934
935 let (_pos, anchor) = self.read_cursor();
937 let anchor_cell = {
938 let inner = self.doc.lock();
940 let dto = frontend::document_inspection::GetBlockAtPositionDto {
941 position: to_i64(anchor),
942 };
943 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
944 .map_err(|_| anyhow::anyhow!("cursor anchor is not inside a table"))?;
945 let block = crate::text_block::TextBlock {
946 doc: self.doc.clone(),
947 block_id: block_info.block_id as usize,
948 };
949 drop(inner);
950 block
951 .table_cell()
952 .ok_or_else(|| anyhow::anyhow!("cursor anchor is not inside a table"))?
953 };
954
955 if pos_cell.table.id() != anchor_cell.table.id() {
956 return Err(anyhow::anyhow!(
957 "position and anchor are in different tables"
958 ));
959 }
960
961 let start_row = pos_cell.row.min(anchor_cell.row);
962 let start_col = pos_cell.column.min(anchor_cell.column);
963 let end_row = pos_cell.row.max(anchor_cell.row);
964 let end_col = pos_cell.column.max(anchor_cell.column);
965
966 self.merge_table_cells(pos_cell.table.id(), start_row, start_col, end_row, end_col)
967 }
968
969 pub fn split_current_cell(&self, split_rows: usize, split_columns: usize) -> Result<()> {
972 let cell_ref = self
973 .current_table_cell()
974 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
975 let cell = cell_ref
977 .table
978 .cell(cell_ref.row, cell_ref.column)
979 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
980 self.split_table_cell(cell.id(), split_rows, split_columns)
982 }
983
984 pub fn set_current_table_format(&self, format: &crate::flow::TableFormat) -> Result<()> {
987 let table = self
988 .current_table()
989 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
990 self.set_table_format(table.id(), format)
991 }
992
993 pub fn set_current_cell_format(&self, format: &crate::flow::CellFormat) -> Result<()> {
996 let cell_ref = self
997 .current_table_cell()
998 .ok_or_else(|| anyhow::anyhow!("cursor is not inside a table"))?;
999 let cell = cell_ref
1000 .table
1001 .cell(cell_ref.row, cell_ref.column)
1002 .ok_or_else(|| anyhow::anyhow!("cell not found"))?;
1003 self.set_table_cell_format(cell.id(), format)
1004 }
1005
1006 pub fn delete_char(&self) -> Result<()> {
1008 let (pos, anchor) = self.read_cursor();
1009 let (del_pos, del_anchor) = if pos != anchor {
1010 (pos, anchor)
1011 } else {
1012 (pos, pos + 1)
1013 };
1014 self.do_delete(del_pos, del_anchor)
1015 }
1016
1017 pub fn delete_previous_char(&self) -> Result<()> {
1019 let (pos, anchor) = self.read_cursor();
1020 let (del_pos, del_anchor) = if pos != anchor {
1021 (pos, anchor)
1022 } else if pos > 0 {
1023 (pos - 1, pos)
1024 } else {
1025 return Ok(());
1026 };
1027 self.do_delete(del_pos, del_anchor)
1028 }
1029
1030 pub fn remove_selected_text(&self) -> Result<String> {
1032 let (pos, anchor) = self.read_cursor();
1033 if pos == anchor {
1034 return Ok(String::new());
1035 }
1036 let queued = {
1037 let mut inner = self.doc.lock();
1038 let dto = frontend::document_editing::DeleteTextDto {
1039 position: to_i64(pos),
1040 anchor: to_i64(anchor),
1041 };
1042 let result =
1043 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1044 let edit_pos = pos.min(anchor);
1045 let removed = pos.max(anchor) - edit_pos;
1046 let new_pos = to_usize(result.new_position);
1047 inner.adjust_cursors(edit_pos, removed, 0);
1048 {
1049 let mut d = self.data.lock();
1050 d.position = new_pos;
1051 d.anchor = new_pos;
1052 }
1053 inner.modified = true;
1054 inner.invalidate_text_cache();
1055 inner.rehighlight_affected(edit_pos);
1056 inner.queue_event(DocumentEvent::ContentsChanged {
1057 position: edit_pos,
1058 chars_removed: removed,
1059 chars_added: 0,
1060 blocks_affected: 1,
1061 });
1062 inner.check_block_count_changed();
1063 inner.check_flow_changed();
1064 (result.deleted_text, self.queue_undo_redo_event(&mut inner))
1066 };
1067 crate::inner::dispatch_queued_events(queued.1);
1068 Ok(queued.0)
1069 }
1070
1071 pub fn create_list(&self, style: ListStyle) -> Result<()> {
1075 let (pos, anchor) = self.read_cursor();
1076 let queued = {
1077 let mut inner = self.doc.lock();
1078 let dto = frontend::document_editing::CreateListDto {
1079 position: to_i64(pos),
1080 anchor: to_i64(anchor),
1081 style: style.clone(),
1082 };
1083 document_editing_commands::create_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1084 inner.modified = true;
1085 inner.rehighlight_affected(pos.min(anchor));
1086 inner.queue_event(DocumentEvent::ContentsChanged {
1087 position: pos.min(anchor),
1088 chars_removed: 0,
1089 chars_added: 0,
1090 blocks_affected: 1,
1091 });
1092 self.queue_undo_redo_event(&mut inner)
1093 };
1094 crate::inner::dispatch_queued_events(queued);
1095 Ok(())
1096 }
1097
1098 pub fn insert_list(&self, style: ListStyle) -> Result<()> {
1100 let (pos, anchor) = self.read_cursor();
1101 let queued = {
1102 let mut inner = self.doc.lock();
1103 let dto = frontend::document_editing::InsertListDto {
1104 position: to_i64(pos),
1105 anchor: to_i64(anchor),
1106 style: style.clone(),
1107 };
1108 let result =
1109 document_editing_commands::insert_list(&inner.ctx, Some(inner.stack_id), &dto)?;
1110 let edit_pos = pos.min(anchor);
1111 let removed = pos.max(anchor) - edit_pos;
1112 self.finish_edit(
1113 &mut inner,
1114 edit_pos,
1115 removed,
1116 to_usize(result.new_position),
1117 1,
1118 )
1119 };
1120 crate::inner::dispatch_queued_events(queued);
1121 Ok(())
1122 }
1123
1124 pub fn char_format(&self) -> Result<TextFormat> {
1128 let pos = self.position();
1129 let inner = self.doc.lock();
1130 let dto = frontend::document_inspection::GetTextAtPositionDto {
1131 position: to_i64(pos),
1132 length: 1,
1133 };
1134 let text_info = document_inspection_commands::get_text_at_position(&inner.ctx, &dto)?;
1135 let element_id = text_info.element_id as u64;
1136 let element = inline_element_commands::get_inline_element(&inner.ctx, &element_id)?
1137 .ok_or_else(|| anyhow::anyhow!("element not found at position"))?;
1138 Ok(TextFormat::from(&element))
1139 }
1140
1141 pub fn block_format(&self) -> Result<BlockFormat> {
1143 let pos = self.position();
1144 let inner = self.doc.lock();
1145 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1146 position: to_i64(pos),
1147 };
1148 let block_info = document_inspection_commands::get_block_at_position(&inner.ctx, &dto)?;
1149 let block_id = block_info.block_id as u64;
1150 let block = frontend::commands::block_commands::get_block(&inner.ctx, &block_id)?
1151 .ok_or_else(|| anyhow::anyhow!("block not found"))?;
1152 Ok(BlockFormat::from(&block))
1153 }
1154
1155 pub fn set_char_format(&self, format: &TextFormat) -> Result<()> {
1159 let (pos, anchor) = self.read_cursor();
1160 let queued = {
1161 let mut inner = self.doc.lock();
1162 let dto = format.to_set_dto(pos, anchor);
1163 document_formatting_commands::set_text_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1164 let start = pos.min(anchor);
1165 let length = pos.max(anchor) - start;
1166 inner.modified = true;
1167 inner.queue_event(DocumentEvent::FormatChanged {
1168 position: start,
1169 length,
1170 kind: crate::flow::FormatChangeKind::Character,
1171 });
1172 self.queue_undo_redo_event(&mut inner)
1173 };
1174 crate::inner::dispatch_queued_events(queued);
1175 Ok(())
1176 }
1177
1178 pub fn merge_char_format(&self, format: &TextFormat) -> Result<()> {
1180 let (pos, anchor) = self.read_cursor();
1181 let queued = {
1182 let mut inner = self.doc.lock();
1183 let dto = format.to_merge_dto(pos, anchor);
1184 document_formatting_commands::merge_text_format(
1185 &inner.ctx,
1186 Some(inner.stack_id),
1187 &dto,
1188 )?;
1189 let start = pos.min(anchor);
1190 let length = pos.max(anchor) - start;
1191 inner.modified = true;
1192 inner.queue_event(DocumentEvent::FormatChanged {
1193 position: start,
1194 length,
1195 kind: crate::flow::FormatChangeKind::Character,
1196 });
1197 self.queue_undo_redo_event(&mut inner)
1198 };
1199 crate::inner::dispatch_queued_events(queued);
1200 Ok(())
1201 }
1202
1203 pub fn set_block_format(&self, format: &BlockFormat) -> Result<()> {
1205 let (pos, anchor) = self.read_cursor();
1206 let queued = {
1207 let mut inner = self.doc.lock();
1208 let dto = format.to_set_dto(pos, anchor);
1209 document_formatting_commands::set_block_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1210 let start = pos.min(anchor);
1211 let length = pos.max(anchor) - start;
1212 inner.modified = true;
1213 inner.queue_event(DocumentEvent::FormatChanged {
1214 position: start,
1215 length,
1216 kind: crate::flow::FormatChangeKind::Block,
1217 });
1218 self.queue_undo_redo_event(&mut inner)
1219 };
1220 crate::inner::dispatch_queued_events(queued);
1221 Ok(())
1222 }
1223
1224 pub fn set_frame_format(&self, frame_id: usize, format: &FrameFormat) -> Result<()> {
1226 let (pos, anchor) = self.read_cursor();
1227 let queued = {
1228 let mut inner = self.doc.lock();
1229 let dto = format.to_set_dto(pos, anchor, frame_id);
1230 document_formatting_commands::set_frame_format(&inner.ctx, Some(inner.stack_id), &dto)?;
1231 let start = pos.min(anchor);
1232 let length = pos.max(anchor) - start;
1233 inner.modified = true;
1234 inner.queue_event(DocumentEvent::FormatChanged {
1235 position: start,
1236 length,
1237 kind: crate::flow::FormatChangeKind::Block,
1238 });
1239 self.queue_undo_redo_event(&mut inner)
1240 };
1241 crate::inner::dispatch_queued_events(queued);
1242 Ok(())
1243 }
1244
1245 pub fn begin_edit_block(&self) {
1249 let inner = self.doc.lock();
1250 undo_redo_commands::begin_composite(&inner.ctx, Some(inner.stack_id));
1251 }
1252
1253 pub fn end_edit_block(&self) {
1255 let inner = self.doc.lock();
1256 undo_redo_commands::end_composite(&inner.ctx);
1257 }
1258
1259 pub fn join_previous_edit_block(&self) {
1266 self.begin_edit_block();
1267 }
1268
1269 fn queue_undo_redo_event(&self, inner: &mut TextDocumentInner) -> QueuedEvents {
1273 let can_undo = undo_redo_commands::can_undo(&inner.ctx, Some(inner.stack_id));
1274 let can_redo = undo_redo_commands::can_redo(&inner.ctx, Some(inner.stack_id));
1275 inner.queue_event(DocumentEvent::UndoRedoChanged { can_undo, can_redo });
1276 inner.take_queued_events()
1277 }
1278
1279 fn do_delete(&self, pos: usize, anchor: usize) -> Result<()> {
1280 let queued = {
1281 let mut inner = self.doc.lock();
1282 let dto = frontend::document_editing::DeleteTextDto {
1283 position: to_i64(pos),
1284 anchor: to_i64(anchor),
1285 };
1286 let result =
1287 document_editing_commands::delete_text(&inner.ctx, Some(inner.stack_id), &dto)?;
1288 let edit_pos = pos.min(anchor);
1289 let removed = pos.max(anchor) - edit_pos;
1290 let new_pos = to_usize(result.new_position);
1291 inner.adjust_cursors(edit_pos, removed, 0);
1292 {
1293 let mut d = self.data.lock();
1294 d.position = new_pos;
1295 d.anchor = new_pos;
1296 }
1297 inner.modified = true;
1298 inner.invalidate_text_cache();
1299 inner.rehighlight_affected(edit_pos);
1300 inner.queue_event(DocumentEvent::ContentsChanged {
1301 position: edit_pos,
1302 chars_removed: removed,
1303 chars_added: 0,
1304 blocks_affected: 1,
1305 });
1306 inner.check_block_count_changed();
1307 inner.check_flow_changed();
1308 self.queue_undo_redo_event(&mut inner)
1309 };
1310 crate::inner::dispatch_queued_events(queued);
1311 Ok(())
1312 }
1313
1314 fn resolve_move(&self, op: MoveOperation, n: usize) -> usize {
1316 let pos = self.position();
1317 match op {
1318 MoveOperation::NoMove => pos,
1319 MoveOperation::Start => 0,
1320 MoveOperation::End => {
1321 let inner = self.doc.lock();
1322 document_inspection_commands::get_document_stats(&inner.ctx)
1323 .map(|s| to_usize(s.character_count))
1324 .unwrap_or(pos)
1325 }
1326 MoveOperation::NextCharacter | MoveOperation::Right => pos + n,
1327 MoveOperation::PreviousCharacter | MoveOperation::Left => pos.saturating_sub(n),
1328 MoveOperation::StartOfBlock | MoveOperation::StartOfLine => {
1329 let inner = self.doc.lock();
1330 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1331 position: to_i64(pos),
1332 };
1333 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1334 .map(|info| to_usize(info.block_start))
1335 .unwrap_or(pos)
1336 }
1337 MoveOperation::EndOfBlock | MoveOperation::EndOfLine => {
1338 let inner = self.doc.lock();
1339 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1340 position: to_i64(pos),
1341 };
1342 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1343 .map(|info| to_usize(info.block_start) + to_usize(info.block_length))
1344 .unwrap_or(pos)
1345 }
1346 MoveOperation::NextBlock => {
1347 let inner = self.doc.lock();
1348 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1349 position: to_i64(pos),
1350 };
1351 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1352 .map(|info| {
1353 to_usize(info.block_start) + to_usize(info.block_length) + 1
1355 })
1356 .unwrap_or(pos)
1357 }
1358 MoveOperation::PreviousBlock => {
1359 let inner = self.doc.lock();
1360 let dto = frontend::document_inspection::GetBlockAtPositionDto {
1361 position: to_i64(pos),
1362 };
1363 let block_start =
1364 document_inspection_commands::get_block_at_position(&inner.ctx, &dto)
1365 .map(|info| to_usize(info.block_start))
1366 .unwrap_or(pos);
1367 if block_start >= 2 {
1368 let prev_dto = frontend::document_inspection::GetBlockAtPositionDto {
1370 position: to_i64(block_start - 2),
1371 };
1372 document_inspection_commands::get_block_at_position(&inner.ctx, &prev_dto)
1373 .map(|info| to_usize(info.block_start))
1374 .unwrap_or(0)
1375 } else {
1376 0
1377 }
1378 }
1379 MoveOperation::NextWord | MoveOperation::EndOfWord | MoveOperation::WordRight => {
1380 let (_, end) = self.find_word_boundaries(pos);
1381 if end == pos {
1383 let inner = self.doc.lock();
1385 let stats = document_inspection_commands::get_document_stats(&inner.ctx)
1386 .map(|s| to_usize(s.character_count))
1387 .unwrap_or(0);
1388 let scan_len = (stats - pos).min(64);
1389 if scan_len == 0 {
1390 return pos;
1391 }
1392 let dto = frontend::document_inspection::GetTextAtPositionDto {
1393 position: to_i64(pos),
1394 length: to_i64(scan_len),
1395 };
1396 if let Ok(r) =
1397 document_inspection_commands::get_text_at_position(&inner.ctx, &dto)
1398 {
1399 for (i, ch) in r.text.chars().enumerate() {
1400 if ch.is_alphanumeric() || ch == '_' {
1401 let word_pos = pos + i;
1403 drop(inner);
1404 let (_, word_end) = self.find_word_boundaries(word_pos);
1405 return word_end;
1406 }
1407 }
1408 }
1409 pos + scan_len
1410 } else {
1411 end
1412 }
1413 }
1414 MoveOperation::PreviousWord | MoveOperation::StartOfWord | MoveOperation::WordLeft => {
1415 let (start, _) = self.find_word_boundaries(pos);
1416 if start < pos {
1417 start
1418 } else if pos > 0 {
1419 let mut search = pos - 1;
1422 loop {
1423 let (ws, we) = self.find_word_boundaries(search);
1424 if ws < we {
1425 break ws;
1427 }
1428 if search == 0 {
1430 break 0;
1431 }
1432 search -= 1;
1433 }
1434 } else {
1435 0
1436 }
1437 }
1438 MoveOperation::Up | MoveOperation::Down => {
1439 if matches!(op, MoveOperation::Up) {
1442 self.resolve_move(MoveOperation::PreviousBlock, 1)
1443 } else {
1444 self.resolve_move(MoveOperation::NextBlock, 1)
1445 }
1446 }
1447 }
1448 }
1449
1450 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
1456 let inner = self.doc.lock();
1457 let block_dto = frontend::document_inspection::GetBlockAtPositionDto {
1459 position: to_i64(pos),
1460 };
1461 let block_info =
1462 match document_inspection_commands::get_block_at_position(&inner.ctx, &block_dto) {
1463 Ok(info) => info,
1464 Err(_) => return (pos, pos),
1465 };
1466
1467 let block_start = to_usize(block_info.block_start);
1468 let block_length = to_usize(block_info.block_length);
1469 if block_length == 0 {
1470 return (pos, pos);
1471 }
1472
1473 let dto = frontend::document_inspection::GetTextAtPositionDto {
1474 position: to_i64(block_start),
1475 length: to_i64(block_length),
1476 };
1477 let text = match document_inspection_commands::get_text_at_position(&inner.ctx, &dto) {
1478 Ok(r) => r.text,
1479 Err(_) => return (pos, pos),
1480 };
1481
1482 let cursor_offset = pos.saturating_sub(block_start);
1484
1485 let mut last_char_start = 0;
1487 let mut last_char_end = 0;
1488
1489 for (word_byte_start, word) in text.unicode_word_indices() {
1490 let word_char_start = text[..word_byte_start].chars().count();
1492 let word_char_len = word.chars().count();
1493 let word_char_end = word_char_start + word_char_len;
1494
1495 last_char_start = word_char_start;
1496 last_char_end = word_char_end;
1497
1498 if cursor_offset >= word_char_start && cursor_offset < word_char_end {
1499 return (block_start + word_char_start, block_start + word_char_end);
1500 }
1501 }
1502
1503 if cursor_offset == last_char_end && last_char_start < last_char_end {
1505 return (block_start + last_char_start, block_start + last_char_end);
1506 }
1507
1508 (pos, pos)
1509 }
1510}