1use super::input_rule::RuleKind;
4use std::sync::Arc;
5use std::{cell::RefCell, ops::Range, rc::Rc};
6
7use gpui::{
8 App, AppContext, Bounds, ClipboardItem, Context, Entity, EntityInputHandler, EventEmitter,
9 FocusHandle, Focusable, KeyBinding, Pixels, Point, SharedString, Subscription, UTF16Selection,
10 Window, actions,
11};
12
13use super::{
14 command::{CommandError, EditorCommand},
15 editor::BlockEditorModel,
16 frame::FrameLayout,
17 id::BlockId,
18 kind::BlockKind,
19 marks::MarkKind,
20 position::BlockPosition,
21 projection::{ProjectionCache, Snapshot},
22 selection::BlockSelection,
23 style::BlockEditorStyle,
24 transaction::EditOrigin,
25};
26use crate::support::BlinkCursor;
27use gpui_kit::base::text::MarkdownExtensions;
28
29pub(crate) const CONTEXT: &str = "BlockEditor";
30
31actions!(
32 block_editor,
33 [
34 Backspace,
35 Delete,
36 Enter,
37 StructuralEnter,
38 MoveLeft,
39 MoveRight,
40 MoveUp,
41 MoveDown,
42 MoveHome,
43 MoveEnd,
44 SelectLeft,
45 SelectRight,
46 SelectUp,
47 SelectDown,
48 SelectHome,
49 SelectEnd,
50 MoveDocStart,
51 MoveDocEnd,
52 SelectDocStart,
53 SelectDocEnd,
54 SelectAll,
55 MoveWordLeft,
56 MoveWordRight,
57 SelectWordLeft,
58 SelectWordRight,
59 Undo,
60 Redo,
61 ToggleBold,
62 ToggleItalic,
63 EditLink,
64 SoftBreak,
65 ExitBlock,
66 ToggleCode,
67 NormalText,
68 TableInsertColumnLeft,
69 TableInsertColumnRight,
70 TableInsertRowAbove,
71 TableInsertRowBelow,
72 TableDeleteLine,
73 CloseTableMenu,
74 Indent,
75 Outdent,
76 Copy,
77 Cut,
78 Paste,
79 PastePlain,
80 DeleteWordLeft,
81 DeleteWordRight,
82 DeleteToLineStart,
83 DeleteToLineEnd,
84 MoveParagraphStart,
85 MoveParagraphEnd,
86 SelectParagraphStart,
87 SelectParagraphEnd,
88 ]
89);
90
91pub(crate) fn init(cx: &mut App) {
92 let ctx = Some(CONTEXT);
93 #[cfg(target_os = "macos")]
94 let m = "cmd";
95 #[cfg(not(target_os = "macos"))]
96 let m = "ctrl";
97 #[cfg(target_os = "macos")]
99 let w = "alt";
100 #[cfg(not(target_os = "macos"))]
101 let w = "ctrl";
102 let doc = m;
105 cx.bind_keys(vec![
106 KeyBinding::new("backspace", Backspace, ctx),
107 KeyBinding::new("delete", Delete, ctx),
108 KeyBinding::new("enter", Enter, ctx),
109 KeyBinding::new("left", MoveLeft, ctx),
110 KeyBinding::new("right", MoveRight, ctx),
111 KeyBinding::new("up", MoveUp, ctx),
112 KeyBinding::new("down", MoveDown, ctx),
113 KeyBinding::new("home", MoveHome, ctx),
114 KeyBinding::new("end", MoveEnd, ctx),
115 KeyBinding::new(&format!("{w}-left"), MoveWordLeft, ctx),
116 KeyBinding::new(&format!("{w}-right"), MoveWordRight, ctx),
117 KeyBinding::new(&format!("{w}-shift-left"), SelectWordLeft, ctx),
118 KeyBinding::new(&format!("{w}-shift-right"), SelectWordRight, ctx),
119 KeyBinding::new("shift-left", SelectLeft, ctx),
120 KeyBinding::new("shift-right", SelectRight, ctx),
121 KeyBinding::new("shift-up", SelectUp, ctx),
122 KeyBinding::new("shift-down", SelectDown, ctx),
123 KeyBinding::new("shift-home", SelectHome, ctx),
124 KeyBinding::new("shift-end", SelectEnd, ctx),
125 KeyBinding::new(&format!("{doc}-home"), MoveDocStart, ctx),
126 KeyBinding::new(&format!("{doc}-end"), MoveDocEnd, ctx),
127 KeyBinding::new(&format!("{doc}-shift-home"), SelectDocStart, ctx),
128 KeyBinding::new(&format!("{doc}-shift-end"), SelectDocEnd, ctx),
129 #[cfg(target_os = "macos")]
130 KeyBinding::new("cmd-up", MoveDocStart, ctx),
131 #[cfg(target_os = "macos")]
132 KeyBinding::new("cmd-down", MoveDocEnd, ctx),
133 #[cfg(target_os = "macos")]
134 KeyBinding::new("cmd-shift-up", SelectDocStart, ctx),
135 #[cfg(target_os = "macos")]
136 KeyBinding::new("cmd-shift-down", SelectDocEnd, ctx),
137 KeyBinding::new(&format!("{w}-backspace"), DeleteWordLeft, ctx),
139 KeyBinding::new(&format!("{w}-delete"), DeleteWordRight, ctx),
140 #[cfg(target_os = "macos")]
142 KeyBinding::new("cmd-backspace", DeleteToLineStart, ctx),
143 #[cfg(target_os = "macos")]
144 KeyBinding::new("cmd-delete", DeleteToLineEnd, ctx),
145 #[cfg(target_os = "macos")]
146 KeyBinding::new("ctrl-k", DeleteToLineEnd, ctx),
147 #[cfg(target_os = "macos")]
148 KeyBinding::new("ctrl-a", MoveHome, ctx),
149 #[cfg(target_os = "macos")]
150 KeyBinding::new("ctrl-e", MoveEnd, ctx),
151 #[cfg(target_os = "macos")]
152 KeyBinding::new("ctrl-shift-a", SelectHome, ctx),
153 #[cfg(target_os = "macos")]
154 KeyBinding::new("ctrl-shift-e", SelectEnd, ctx),
155 #[cfg(target_os = "macos")]
156 KeyBinding::new("ctrl-d", Delete, ctx),
157 #[cfg(target_os = "macos")]
158 KeyBinding::new("ctrl-h", Backspace, ctx),
159 #[cfg(target_os = "macos")]
160 KeyBinding::new("cmd-left", MoveHome, ctx),
161 #[cfg(target_os = "macos")]
162 KeyBinding::new("cmd-right", MoveEnd, ctx),
163 #[cfg(target_os = "macos")]
164 KeyBinding::new("cmd-shift-left", SelectHome, ctx),
165 #[cfg(target_os = "macos")]
166 KeyBinding::new("cmd-shift-right", SelectEnd, ctx),
167 #[cfg(target_os = "macos")]
168 KeyBinding::new("alt-up", MoveParagraphStart, ctx),
169 #[cfg(target_os = "macos")]
170 KeyBinding::new("alt-down", MoveParagraphEnd, ctx),
171 #[cfg(target_os = "macos")]
172 KeyBinding::new("alt-shift-up", SelectParagraphStart, ctx),
173 #[cfg(target_os = "macos")]
174 KeyBinding::new("alt-shift-down", SelectParagraphEnd, ctx),
175 #[cfg(not(target_os = "macos"))]
178 KeyBinding::new("ctrl-up", MoveParagraphStart, ctx),
179 #[cfg(not(target_os = "macos"))]
180 KeyBinding::new("ctrl-down", MoveParagraphEnd, ctx),
181 #[cfg(not(target_os = "macos"))]
182 KeyBinding::new("ctrl-shift-up", SelectParagraphStart, ctx),
183 #[cfg(not(target_os = "macos"))]
184 KeyBinding::new("ctrl-shift-down", SelectParagraphEnd, ctx),
185 KeyBinding::new("tab", Indent, ctx),
186 KeyBinding::new("shift-tab", Outdent, ctx),
187 KeyBinding::new(&format!("{m}-a"), SelectAll, ctx),
188 KeyBinding::new(&format!("{m}-z"), Undo, ctx),
189 KeyBinding::new(&format!("{m}-shift-z"), Redo, ctx),
190 #[cfg(not(target_os = "macos"))]
191 KeyBinding::new("ctrl-y", Redo, ctx),
192 KeyBinding::new(&format!("{m}-b"), ToggleBold, ctx),
193 KeyBinding::new(&format!("{m}-i"), ToggleItalic, ctx),
194 KeyBinding::new(&format!("{m}-e"), ToggleCode, ctx),
195 KeyBinding::new(&format!("{m}-k"), EditLink, ctx),
196 KeyBinding::new(&format!("{m}-alt-0"), NormalText, ctx),
198 KeyBinding::new(&format!("{m}-alt-left"), TableInsertColumnLeft, ctx),
201 KeyBinding::new(&format!("{m}-alt-right"), TableInsertColumnRight, ctx),
202 KeyBinding::new(&format!("{m}-alt-up"), TableInsertRowAbove, ctx),
203 KeyBinding::new(&format!("{m}-alt-down"), TableInsertRowBelow, ctx),
204 KeyBinding::new(&format!("{m}-alt-backspace"), TableDeleteLine, ctx),
205 KeyBinding::new("escape", CloseTableMenu, Some("BlockEditor && table_menu")),
206 KeyBinding::new("shift-enter", SoftBreak, ctx),
207 KeyBinding::new(&format!("{m}-enter"), ExitBlock, ctx),
208 KeyBinding::new(&format!("{m}-c"), Copy, ctx),
209 KeyBinding::new(&format!("{m}-x"), Cut, ctx),
210 KeyBinding::new(&format!("{m}-v"), Paste, ctx),
211 KeyBinding::new(&format!("{m}-shift-v"), PastePlain, ctx),
212 KeyBinding::new(
215 "shift-enter",
216 StructuralEnter,
217 Some("BlockEditor && submit == enter"),
218 ),
219 KeyBinding::new(
220 "ctrl-enter",
221 SoftBreak,
222 Some("BlockEditor && submit == enter"),
223 ),
224 ]);
225}
226
227pub(crate) const COPY_TAG: &str = "gpui-kit/block-editor/inline-markdown/v1";
231
232pub(crate) const BLOCKS_COPY_TAG: &str = "gpui-kit/block-editor/blocks-markdown/v1";
236
237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
239pub(crate) enum CopyKind {
240 External,
242 Inline,
244 Blocks,
246}
247
248impl CopyKind {
249 fn of(item: &ClipboardItem) -> Self {
250 match item.metadata() {
251 Some(m) if m == COPY_TAG => Self::Inline,
252 Some(m) if m == BLOCKS_COPY_TAG => Self::Blocks,
253 _ => Self::External,
254 }
255 }
256
257 fn tag(self) -> Option<&'static str> {
258 match self {
259 Self::External => None,
260 Self::Inline => Some(COPY_TAG),
261 Self::Blocks => Some(BLOCKS_COPY_TAG),
262 }
263 }
264}
265
266#[non_exhaustive]
268#[derive(Clone, Debug)]
269pub enum BlockEditorEvent {
270 Changed {
272 revision: u64,
273 origin: EditOrigin,
274 },
275 SelectionChanged,
276 EditLinkRequested {
280 url: Option<SharedString>,
281 },
282 LinkClicked {
284 url: SharedString,
285 },
286 LinkHovered {
288 url: Option<SharedString>,
289 },
290 Focus,
291 Blur,
292 SubmitRequested,
295 PasteRequested {
299 request: PasteRequest,
300 item: ClipboardItem,
301 },
302}
303
304#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
306pub enum SubmitPolicy {
307 #[default]
308 Newline,
309 Enter,
312 ModifiedEnter,
313}
314
315#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
317pub enum ClipboardPolicy {
318 #[default]
319 Editor,
320 Host,
321}
322
323type InputRuleFilter = Arc<dyn Fn(super::InputRule, &str) -> bool + Send + Sync>;
324
325#[derive(Clone, Debug)]
329pub struct PasteRequest {
330 range: super::CapturedRange,
331 plain: bool,
332 copy: CopyKind,
334 sequence: u64,
335}
336
337#[derive(Clone, Copy, Debug)]
341pub(crate) struct BlockDrag {
342 pub(crate) block: BlockId,
343 pub(crate) start: Point<Pixels>,
344 pub(crate) active: bool,
345 pub(crate) drop: Option<usize>,
347}
348
349#[derive(Clone, Debug)]
351struct Marked {
352 block: BlockId,
353 range: Range<usize>,
354}
355
356pub struct BlockEditorState {
357 pub(crate) model: BlockEditorModel,
358 pub(crate) focus_handle: FocusHandle,
359 pub(crate) blink_cursor: Entity<BlinkCursor>,
360 pub(crate) style: BlockEditorStyle,
361 pub(crate) projection: ProjectionCache,
362 pub(crate) frame: Rc<RefCell<FrameLayout>>,
364 pub(crate) cull: Rc<RefCell<super::cull::CullState>>,
365 pub(crate) a11y_value: RefCell<Option<(u64, SharedString)>>,
367 pub(crate) scroll_handle: Option<gpui::ScrollHandle>,
369 pub(crate) reveal: std::cell::Cell<bool>,
371 marked: Option<Marked>,
372 composition_open: bool,
374 goal_x: Option<Pixels>,
376 pub(crate) dragging: bool,
377 pub(crate) block_drag: Option<BlockDrag>,
379 readonly: bool,
380 image_policy: super::ImagePolicy,
381 submit_policy: SubmitPolicy,
382 clipboard_policy: ClipboardPolicy,
383 paste_sequence: u64,
384 pending_paste: Option<u64>,
385 input_rules: Vec<super::input_rule::InputRule>,
386 input_rule_filter: Option<InputRuleFilter>,
387 hovered_link: Option<SharedString>,
388 pub(crate) pointer_hand: bool,
390 pub(crate) table_menu: Option<super::table_ui::TableMenu>,
392 pub(crate) table_hover: Vec<(BlockId, crate::TableLine)>,
394 pub(crate) grip_hover: bool,
396 _subscriptions: Vec<Subscription>,
397}
398
399impl EventEmitter<BlockEditorEvent> for BlockEditorState {}
400
401impl Focusable for BlockEditorState {
402 fn focus_handle(&self, _: &App) -> FocusHandle {
403 self.focus_handle.clone()
404 }
405}
406
407impl BlockEditorState {
408 pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
410 let focus_handle = cx.focus_handle();
411 let blink_cursor = cx.new(|_| BlinkCursor::new());
412 let subscriptions = vec![
414 cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
415 cx.on_focus(&focus_handle, window, |this, _, cx| {
416 this.blink_cursor.update(cx, |b, cx| b.start(cx));
417 cx.emit(BlockEditorEvent::Focus);
418 }),
419 cx.on_blur(&focus_handle, window, |this, _, cx| {
420 this.blink_cursor.update(cx, |b, cx| b.stop(cx));
421 this.model.break_coalescing();
422 this.end_composition();
423 cx.emit(BlockEditorEvent::Blur);
424 }),
425 cx.observe_window_activation(window, |this, window, cx| {
426 if window.is_window_active() && this.focus_handle.is_focused(window) {
427 this.blink_cursor.update(cx, |b, cx| b.start(cx));
428 }
429 }),
430 ];
431 let model = BlockEditorModel::from_markdown("", MarkdownExtensions::default())
432 .unwrap_or_else(|_| unreachable!("empty source always parses"));
433 Self {
434 model,
435 focus_handle,
436 blink_cursor,
437 style: BlockEditorStyle::default(),
438 projection: ProjectionCache::default(),
439 frame: Rc::default(),
440 cull: Rc::default(),
441 a11y_value: RefCell::default(),
442 scroll_handle: Some(gpui::ScrollHandle::new()),
443 reveal: Default::default(),
444 marked: None,
445 composition_open: false,
446 goal_x: None,
447 dragging: false,
448 block_drag: None,
449 readonly: false,
450 image_policy: super::ImagePolicy::default(),
451 submit_policy: SubmitPolicy::default(),
452 clipboard_policy: ClipboardPolicy::default(),
453 paste_sequence: 0,
454 pending_paste: None,
455 input_rules: super::input_rule::InputRule::markdown_defaults(),
456 input_rule_filter: None,
457 hovered_link: None,
458 pointer_hand: false,
459 table_menu: None,
460 table_hover: Vec::new(),
461 grip_hover: false,
462 _subscriptions: subscriptions,
463 }
464 }
465
466 pub fn with_submit_policy(mut self, policy: SubmitPolicy) -> Self {
467 self.submit_policy = policy;
468 self
469 }
470 pub fn set_submit_policy(&mut self, policy: SubmitPolicy) {
473 self.submit_policy = policy;
474 }
475 pub(crate) fn key_context(&self) -> &'static str {
476 match (
477 self.submit_policy == SubmitPolicy::Enter,
478 self.table_menu.is_some(),
479 ) {
480 (true, false) => "BlockEditor submit=enter",
481 (true, true) => "BlockEditor submit=enter table_menu",
482 (false, false) => CONTEXT,
483 (false, true) => "BlockEditor table_menu",
484 }
485 }
486 pub fn with_clipboard_policy(mut self, policy: ClipboardPolicy) -> Self {
487 self.clipboard_policy = policy;
488 self
489 }
490 pub fn set_clipboard_policy(&mut self, policy: ClipboardPolicy) {
491 self.clipboard_policy = policy;
492 }
493 pub fn scroll_handle(&self) -> Option<gpui::ScrollHandle> {
495 self.scroll_handle.clone()
496 }
497
498 pub fn with_input_rules(
502 mut self,
503 rules: impl IntoIterator<Item = super::input_rule::InputRule>,
504 ) -> Self {
505 self.input_rules = rules.into_iter().collect();
506 self
507 }
508
509 pub fn with_input_rule_filter(
514 mut self,
515 filter: impl Fn(super::InputRule, &str) -> bool + Send + Sync + 'static,
516 ) -> Self {
517 self.input_rule_filter = Some(Arc::new(filter));
518 self
519 }
520
521 pub fn set_input_rules(
523 &mut self,
524 rules: impl IntoIterator<Item = super::input_rule::InputRule>,
525 ) {
526 self.input_rules = rules.into_iter().collect();
527 }
528
529 pub fn set_task_checked(&mut self, block: BlockId, checked: bool, cx: &mut Context<Self>) {
532 if self.readonly {
533 return;
534 }
535 let leaf = if self.model.doc.inline(block).is_some() {
536 block
537 } else {
538 match self.model.doc.children(block).first() {
539 Some(child) => *child,
540 None => return,
541 }
542 };
543 self.model.break_coalescing();
544 self.edit(
545 EditOrigin::Command("set_task_checked"),
546 |m| m.set_item_checked(leaf, checked).map(|_| ()),
547 cx,
548 );
549 self.model.break_coalescing();
550 }
551
552 fn rule_on(&self, kind: super::input_rule::RuleKind) -> bool {
553 self.input_rules.iter().any(|rule| rule.0 == kind)
554 && self.input_rule_filter.as_ref().is_none_or(|filter| {
555 filter(
556 super::InputRule(kind),
557 self.model.caret_prefix().unwrap_or_default(),
558 )
559 })
560 }
561
562 pub fn with_source(mut self, source: &str) -> Self {
564 let _ = self.model.set_source(source);
565 self.projection.invalidate();
566 self
567 }
568
569 pub fn set_source(&mut self, source: &str, cx: &mut Context<Self>) -> Result<(), SharedString> {
571 self.model.set_source(source)?;
572 self.marked = None;
573 self.composition_open = false;
574 self.pending_paste = None;
575 self.projection.invalidate();
576 cx.notify();
577 Ok(())
578 }
579
580 pub fn source(&self) -> String {
582 self.model.source()
583 }
584
585 pub fn source_snapshot(&self) -> super::SourceSnapshot {
587 self.model.source_snapshot()
588 }
589
590 pub fn capture_caret(&self) -> Result<super::CapturedRange, super::CapturedRangeError> {
591 self.model.capture_caret()
592 }
593 pub fn capture_selection(&self) -> Result<super::CapturedRange, super::CapturedRangeError> {
594 self.model.capture_selection()
595 }
596 pub fn capture_suffix(
597 &self,
598 len: usize,
599 ) -> Result<super::CapturedRange, super::CapturedRangeError> {
600 self.model.capture_suffix(len)
601 }
602 pub fn capture_leaf_range(
603 &self,
604 range: std::ops::Range<usize>,
605 ) -> Result<super::CapturedRange, super::CapturedRangeError> {
606 self.model.capture_leaf_range(range)
607 }
608 pub fn with_image_policy(mut self, policy: super::ImagePolicy) -> Self {
610 self.image_policy = policy;
611 self
612 }
613 pub fn set_image_policy(&mut self, policy: super::ImagePolicy, cx: &mut Context<Self>) {
614 self.image_policy = policy;
615 self.projection.invalidate();
616 cx.notify();
617 }
618 pub fn image_policy(&self) -> super::ImagePolicy {
619 self.image_policy
620 }
621
622 pub fn caret_prefix_snapshot(&self) -> Option<SharedString> {
623 self.model.caret_prefix_snapshot()
624 }
625 pub fn caret_prefix(&self) -> Option<&str> {
626 self.model.caret_prefix()
627 }
628 pub fn is_capture_current(&self, range: &super::CapturedRange) -> bool {
629 self.model.is_capture_current(range)
630 }
631 pub fn replace_captured(
632 &mut self,
633 range: &mut super::CapturedRange,
634 text: &str,
635 cx: &mut Context<Self>,
636 ) -> Result<(), super::CapturedRangeError> {
637 if self.readonly || self.marked.is_some() {
638 return Err(super::CapturedRangeError::Stale);
639 }
640 self.model.replace_captured(range, text)?;
641 self.after_edit(EditOrigin::Api, cx);
642 Ok(())
643 }
644 pub fn select_end(&mut self, cx: &mut Context<Self>) -> Result<(), super::SelectionError> {
645 self.model.select_end()?;
646 self.selection_changed(cx);
647 Ok(())
648 }
649 pub fn reveal_caret(&mut self, cx: &mut Context<Self>) {
651 self.reveal.set(true);
652 cx.notify();
653 }
654 pub fn invalidate_captures(&mut self) {
656 self.model.invalidate_captures();
657 self.pending_paste = None;
658 }
659 pub fn is_composing(&self) -> bool {
660 self.marked.is_some()
661 }
662
663 pub fn model(&self) -> &BlockEditorModel {
665 &self.model
666 }
667
668 pub fn revision(&self) -> u64 {
670 self.model.revision()
671 }
672
673 pub fn selection(&self) -> &BlockSelection {
675 self.model.selection()
676 }
677
678 pub fn block_kind_at_caret(&self) -> Option<BlockKind> {
680 self.model.block_kind_at_caret()
681 }
682
683 pub fn set_editor_style(&mut self, style: BlockEditorStyle, cx: &mut Context<Self>) {
685 self.style = style;
686 cx.notify();
687 }
688
689 pub fn editor_style(&self) -> &BlockEditorStyle {
691 &self.style
692 }
693
694 pub fn set_scroll_handle(&mut self, handle: gpui::ScrollHandle) {
698 self.scroll_handle = Some(handle);
699 }
700
701 pub fn is_readonly(&self) -> bool {
703 self.readonly
704 }
705
706 pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
708 self.readonly = readonly;
709 cx.notify();
710 }
711
712 pub fn focus(&self, window: &mut Window, cx: &mut App) {
714 window.focus(&self.focus_handle, cx);
715 }
716
717 pub fn set_selection(
719 &mut self,
720 selection: BlockSelection,
721 cx: &mut Context<Self>,
722 ) -> Result<(), super::SelectionError> {
723 self.model.set_selection(selection)?;
724 self.selection_changed(cx);
725 Ok(())
726 }
727
728 pub fn run(
730 &mut self,
731 command: EditorCommand,
732 cx: &mut Context<Self>,
733 ) -> Result<bool, CommandError> {
734 if self.readonly {
735 return Ok(false);
736 }
737 let name = command_name(&command);
738 self.end_composition();
739 let revision = self.model.revision();
740 let changed = self.model.run(command)?;
741 if self.model.revision() != revision {
742 self.after_edit(EditOrigin::Command(name), cx);
743 } else {
744 cx.notify();
746 }
747 Ok(changed)
748 }
749
750 pub fn undo(&mut self, cx: &mut Context<Self>) {
752 if self.readonly {
753 return;
754 }
755 if self.model.undo() {
756 self.after_edit(EditOrigin::Undo, cx);
757 }
758 }
759
760 pub fn redo(&mut self, cx: &mut Context<Self>) {
762 if self.readonly {
763 return;
764 }
765 if self.model.redo() {
766 self.after_edit(EditOrigin::Redo, cx);
767 }
768 }
769
770 pub(crate) fn accessible_value(&self) -> SharedString {
774 let revision = self.model.revision();
775 if let Some((cached, value)) = &*self.a11y_value.borrow()
776 && *cached == revision
777 {
778 return value.clone();
779 }
780 let doc = &self.model.doc;
781 let value: SharedString = doc
782 .leaves()
783 .iter()
784 .filter_map(|id| doc.leaf_text(*id))
785 .map(|text| text.replace(super::inline::ATOM_CHAR, ""))
786 .collect::<Vec<_>>()
787 .join("\n")
788 .into();
789 *self.a11y_value.borrow_mut() = Some((revision, value.clone()));
790 value
791 }
792
793 pub(crate) fn snapshot(&mut self) -> Rc<Snapshot> {
794 self.projection
795 .set_placeholders(self.image_policy == super::ImagePolicy::Placeholder);
796 self.projection.snapshot(&self.model.doc)
797 }
798
799 pub(crate) fn pinned_blocks(&self) -> Vec<BlockId> {
802 let doc = &self.model.doc;
803 let mut ids = match self.model.selection() {
804 BlockSelection::Text { anchor, head } => vec![anchor.block, head.block],
805 BlockSelection::Blocks { anchor, head } | BlockSelection::Cells { anchor, head } => {
806 vec![*anchor, *head]
807 }
808 };
809 if let Some((block, _)) = self.marked_range() {
810 ids.push(block);
811 }
812 ids.into_iter().flat_map(|id| doc.path(id)).collect()
815 }
816
817 pub(crate) fn selection_ranges(&self) -> Vec<(BlockId, Range<usize>)> {
819 if let Some(cells) = self.model.selected_cells() {
820 return cells
821 .into_iter()
822 .flatten()
823 .map(|c| (c, 0..self.model.doc.leaf_len(c).unwrap_or(0)))
824 .collect();
825 }
826 let Ok(range) = self.model.doc.resolve_selection(self.model.selection()) else {
827 return Vec::new();
828 };
829 if range.is_collapsed() {
830 return Vec::new();
831 }
832 self.model
833 .text_ranges(range)
834 .into_iter()
835 .map(|(id, lo, hi)| (id, lo..hi))
836 .collect()
837 }
838
839 pub(crate) fn marked_range(&self) -> Option<(BlockId, Range<usize>)> {
840 self.marked.as_ref().map(|m| (m.block, m.range.clone()))
841 }
842
843 pub(crate) fn show_caret(&self, window: &Window, cx: &App) -> bool {
844 self.focus_handle.is_focused(window) && self.blink_cursor.read(cx).visible()
845 }
846
847 fn after_edit(&mut self, origin: EditOrigin, cx: &mut Context<Self>) {
850 self.goal_x = None;
851 self.reveal.set(true);
852 self.pause_blink(cx);
853 cx.emit(BlockEditorEvent::Changed {
854 revision: self.model.revision(),
855 origin,
856 });
857 cx.emit(BlockEditorEvent::SelectionChanged);
858 cx.notify();
859 }
860
861 fn selection_changed(&mut self, cx: &mut Context<Self>) {
862 self.reveal.set(true);
863 self.pause_blink(cx);
864 cx.emit(BlockEditorEvent::SelectionChanged);
865 cx.notify();
866 }
867
868 fn pause_blink(&mut self, cx: &mut Context<Self>) {
869 self.blink_cursor.update(cx, |b, cx| b.pause(cx));
870 }
871
872 fn edit(
873 &mut self,
874 origin: EditOrigin,
875 f: impl FnOnce(&mut BlockEditorModel) -> Result<(), CommandError>,
876 cx: &mut Context<Self>,
877 ) {
878 if self.readonly {
879 return;
880 }
881 let revision = self.model.revision();
882 let _ = f(&mut self.model);
885 if self.model.revision() != revision {
886 self.after_edit(origin, cx);
887 } else {
888 cx.notify();
889 }
890 }
891
892 fn end_composition(&mut self) {
893 self.marked = None;
894 if std::mem::take(&mut self.composition_open) {
895 self.model.history.commit_transaction();
896 }
897 }
898
899 #[cfg(test)]
901 pub(crate) fn composition_open(&self) -> bool {
902 self.composition_open
903 }
904
905 fn collapse_blocks(&mut self, forward: bool, cx: &mut Context<Self>) -> bool {
907 if !matches!(
908 self.model.selection(),
909 BlockSelection::Blocks { .. } | BlockSelection::Cells { .. }
910 ) {
911 return false;
912 }
913 if let Ok(range) = self.model.doc.resolve_selection(self.model.selection()) {
914 let target = if forward { range.end } else { range.start };
915 self.move_to(target, false, cx);
916 }
917 true
918 }
919
920 fn head_anchor(&self) -> Option<(BlockPosition, BlockPosition)> {
921 match self.model.selection() {
922 BlockSelection::Text { anchor, head } => Some((*anchor, *head)),
923 BlockSelection::Blocks { .. } => None,
924 BlockSelection::Cells { anchor, head } => {
925 Some((BlockPosition::new(*anchor, 0), BlockPosition::new(*head, 0)))
926 }
927 }
928 }
929
930 fn move_to(&mut self, head: BlockPosition, extend: bool, cx: &mut Context<Self>) {
931 let anchor = match (extend, self.head_anchor()) {
932 (true, Some((anchor, _))) => anchor,
933 _ => head,
934 };
935 self.end_composition();
936 let doc = &self.model.doc;
939 let selection = match (
940 super::table::table_of(doc, anchor.block),
941 super::table::table_of(doc, head.block),
942 ) {
943 (Some(a), Some(b)) if a == b && anchor.block != head.block => BlockSelection::Cells {
944 anchor: anchor.block,
945 head: head.block,
946 },
947 _ => BlockSelection::Text { anchor, head },
948 };
949 let _ = self.model.set_selection(selection);
950 self.selection_changed(cx);
951 }
952
953 pub(crate) fn step_horizontal(&self, pos: BlockPosition, forward: bool) -> BlockPosition {
955 let doc = &self.model.doc;
956 let len = doc.leaf_len(pos.block).unwrap_or(0);
957 if let Some(code) = doc.plain(pos.block) {
958 let next = if forward {
959 code[pos.offset..]
960 .chars()
961 .next()
962 .map(|c| pos.offset + c.len_utf8())
963 } else {
964 code[..pos.offset]
965 .chars()
966 .next_back()
967 .map(|c| pos.offset - c.len_utf8())
968 };
969 if let Some(offset) = next {
970 return BlockPosition::new(pos.block, offset);
971 }
972 } else if let Some(inline) = doc.inline(pos.block) {
973 let next = if forward {
974 inline.next_grapheme(pos.offset)
975 } else {
976 inline.prev_grapheme(pos.offset)
977 };
978 if let Some(offset) = next.filter(|o| *o != pos.offset) {
979 return BlockPosition::new(pos.block, offset);
980 }
981 } else if forward && pos.offset == 0 {
982 return BlockPosition::new(pos.block, 1);
983 } else if !forward && pos.offset == 1 {
984 return BlockPosition::new(pos.block, 0);
985 }
986 let leaves = doc.leaves();
987 let Some(ix) = leaves.iter().position(|id| *id == pos.block) else {
988 return pos;
989 };
990 let neighbour = if forward {
991 leaves.get(ix + 1)
992 } else {
993 ix.checked_sub(1).and_then(|i| leaves.get(i))
994 };
995 match neighbour {
996 Some(id) if forward => BlockPosition::new(*id, 0),
997 Some(id) => BlockPosition::new(*id, doc.leaf_len(*id).unwrap_or(0)),
998 None => BlockPosition::new(pos.block, if forward { len } else { 0 }),
999 }
1000 }
1001
1002 fn step_word(&self, pos: BlockPosition, forward: bool) -> BlockPosition {
1005 let Some(text) = self.model.doc.leaf_text(pos.block) else {
1006 return self.step_horizontal(pos, forward);
1007 };
1008 let is_word = |c: char| c.is_alphanumeric() || c == '_';
1009 let mut offset = pos.offset;
1010 if forward {
1011 let rest = &text[offset..];
1012 let mut it = rest.char_indices().peekable();
1013 while let Some((_, c)) = it.peek().copied()
1014 && !is_word(c)
1015 {
1016 it.next();
1017 }
1018 while let Some((_, c)) = it.peek().copied()
1019 && is_word(c)
1020 {
1021 it.next();
1022 }
1023 offset += it.peek().map(|(i, _)| *i).unwrap_or(rest.len());
1024 } else {
1025 let before = &text[..offset];
1026 let mut it = before.char_indices().rev().peekable();
1027 while let Some((_, c)) = it.peek().copied()
1028 && !is_word(c)
1029 {
1030 it.next();
1031 }
1032 let mut start = it.peek().map(|(i, c)| i + c.len_utf8()).unwrap_or(0);
1033 while let Some((i, c)) = it.peek().copied()
1034 && is_word(c)
1035 {
1036 start = i;
1037 it.next();
1038 }
1039 offset = if start < offset { start } else { 0 };
1040 }
1041 if offset == pos.offset {
1042 return self.step_horizontal(pos, forward);
1043 }
1044 self.model.doc.clamp(BlockPosition::new(pos.block, offset))
1045 }
1046
1047 fn word(&mut self, forward: bool, extend: bool, cx: &mut Context<Self>) {
1048 if self.collapse_blocks(forward, cx) {
1049 return;
1050 }
1051 let Some((_, head)) = self.head_anchor() else {
1052 return;
1053 };
1054 self.goal_x = None;
1055 let next = self.step_word(head, forward);
1056 self.move_to(next, extend, cx);
1057 }
1058
1059 pub(crate) fn on_move_word_left(
1060 &mut self,
1061 _: &MoveWordLeft,
1062 _: &mut Window,
1063 cx: &mut Context<Self>,
1064 ) {
1065 self.word(false, false, cx);
1066 }
1067 pub(crate) fn on_move_word_right(
1068 &mut self,
1069 _: &MoveWordRight,
1070 _: &mut Window,
1071 cx: &mut Context<Self>,
1072 ) {
1073 self.word(true, false, cx);
1074 }
1075 pub(crate) fn on_select_word_left(
1076 &mut self,
1077 _: &SelectWordLeft,
1078 _: &mut Window,
1079 cx: &mut Context<Self>,
1080 ) {
1081 self.word(false, true, cx);
1082 }
1083 pub(crate) fn on_select_word_right(
1084 &mut self,
1085 _: &SelectWordRight,
1086 _: &mut Window,
1087 cx: &mut Context<Self>,
1088 ) {
1089 self.word(true, true, cx);
1090 }
1091
1092 fn horizontal(&mut self, forward: bool, extend: bool, cx: &mut Context<Self>) {
1093 if self.collapse_blocks(forward, cx) {
1094 return;
1095 }
1096 let Some((anchor, head)) = self.head_anchor() else {
1097 return;
1098 };
1099 self.goal_x = None;
1100 if !extend && anchor != head {
1102 let ord = self.model.doc.cmp_positions(anchor, head).ok();
1103 let pick_head = matches!(
1104 (forward, ord),
1105 (true, Some(std::cmp::Ordering::Less)) | (false, Some(std::cmp::Ordering::Greater))
1106 );
1107 let target = if pick_head { head } else { anchor };
1108 return self.move_to(target, false, cx);
1109 }
1110 let next = self.step_horizontal(head, forward);
1111 self.move_to(next, extend, cx);
1112 }
1113
1114 fn vertical(&mut self, down: bool, extend: bool, cx: &mut Context<Self>) {
1115 if self.collapse_blocks(down, cx) {
1116 return;
1117 }
1118 let Some((_, head)) = self.head_anchor() else {
1119 return;
1120 };
1121 let leaves = self.snapshot().leaves.clone();
1122 let Some(ix) = leaves.iter().position(|l| l.block == head.block) else {
1123 return;
1124 };
1125 let adjacent = if down {
1127 leaves.get(ix + 1)
1128 } else {
1129 ix.checked_sub(1).and_then(|i| leaves.get(i))
1130 };
1131 let frame = self.frame.clone();
1132 let frame = frame.borrow();
1133 let laid_out = frame.caret_bounds(head, self.style.caret_width());
1134 let x = laid_out.map(|caret| *self.goal_x.get_or_insert(caret.origin.x));
1135 let visual = x.and_then(|x| frame.vertical(head, x, down));
1136 let target = match (visual, adjacent) {
1137 (Some(t), _) if t.block == head.block => t,
1139 _ if down && !extend && self.model.in_last_table_row() => {
1141 drop(frame);
1142 self.edit(
1143 EditOrigin::Command("table_exit"),
1144 |m| m.exit_table().map(|_| ()),
1145 cx,
1146 );
1147 return;
1148 }
1149 (Some(t), Some(next)) if t.block == next.block => t,
1150 (Some(t), _)
1153 if super::table::table_of(&self.model.doc, head.block).is_some_and(|table| {
1154 super::table::table_of(&self.model.doc, t.block) == Some(table)
1155 }) =>
1156 {
1157 t
1158 }
1159 (_, Some(next)) => {
1161 let offset = if down { 0 } else { next.len };
1162 BlockPosition::new(next.block, offset)
1163 }
1164 (_, None) if down && self.model.doc.plain(head.block).is_some() && !extend => {
1165 drop(frame);
1166 self.edit(
1167 EditOrigin::Command("exit_code_block"),
1168 |m| m.exit_code_block().map(|_| ()),
1169 cx,
1170 );
1171 return;
1172 }
1173 (_, None) => {
1174 let doc = &self.model.doc;
1175 if down {
1176 doc.last_caret()
1177 } else {
1178 doc.first_caret()
1179 }
1180 }
1181 };
1182 drop(frame);
1183 let goal = self.goal_x;
1184 self.move_to(self.model.doc.clamp(target), extend, cx);
1185 self.goal_x = goal;
1186 }
1187
1188 fn line_edge(&mut self, end: bool, extend: bool, cx: &mut Context<Self>) {
1191 if self.collapse_blocks(end, cx) {
1192 return;
1193 }
1194 let Some((_, head)) = self.head_anchor() else {
1195 return;
1196 };
1197 let target = self.visual_line_edge(head, end);
1198 self.goal_x = None;
1199 self.move_to(target, extend, cx);
1200 }
1201
1202 fn visual_line_edge(&self, head: BlockPosition, end: bool) -> BlockPosition {
1205 let visual = self.frame.borrow().line_edge(head, end);
1206 let visual = visual.map(|pos| {
1209 if !end || pos.block != head.block {
1210 return pos;
1211 }
1212 let frame = self.frame.borrow();
1213 let (Some(leaf), Some(inline)) =
1214 (frame.leaf(pos.block), self.model.doc.inline(pos.block))
1215 else {
1216 return pos;
1217 };
1218 let line_y = leaf.caret_point(head.offset).y;
1219 if pos.offset < inline.len() && leaf.caret_point(pos.offset).y > line_y {
1220 let prev = inline.prev_grapheme(pos.offset).unwrap_or(pos.offset);
1221 BlockPosition::new(pos.block, prev)
1222 } else {
1223 pos
1224 }
1225 });
1226 let target = visual.unwrap_or_else(|| {
1227 let offset = if end {
1228 self.model.doc.leaf_len(head.block).unwrap_or(0)
1229 } else {
1230 0
1231 };
1232 BlockPosition::new(head.block, offset)
1233 });
1234 self.model.doc.clamp(target)
1235 }
1236
1237 fn delete_line_target(&self, head: BlockPosition, end: bool) -> BlockPosition {
1240 if let Some(code) = self.model.doc.plain(head.block) {
1241 let offset = if end {
1242 code[head.offset..]
1243 .find('\n')
1244 .map_or(code.len(), |i| head.offset + i)
1245 } else {
1246 code[..head.offset].rfind('\n').map_or(0, |i| i + 1)
1247 };
1248 return BlockPosition::new(head.block, offset);
1249 }
1250 let target = self.visual_line_edge(head, end);
1251 if target.block == head.block {
1252 target
1253 } else {
1254 head
1255 }
1256 }
1257
1258 fn delete_word_target(&self, head: BlockPosition, forward: bool) -> BlockPosition {
1262 use super::inline::ATOM_CHAR;
1263 let Some(text) = self.model.doc.leaf_text(head.block) else {
1264 return head;
1265 };
1266 let adjacent = if forward {
1267 text[head.offset..].chars().next()
1268 } else {
1269 text[..head.offset].chars().next_back()
1270 };
1271 let atom = ATOM_CHAR.len_utf8();
1272 match adjacent {
1273 None => return head,
1274 Some(ATOM_CHAR) if forward => {
1275 return BlockPosition::new(head.block, head.offset + atom);
1276 }
1277 Some(ATOM_CHAR) => return BlockPosition::new(head.block, head.offset - atom),
1278 Some(_) => {}
1279 }
1280 let step = self.step_word(head, forward);
1281 if step.block != head.block {
1282 return head;
1283 }
1284 let offset = if forward {
1285 text[head.offset..step.offset]
1286 .find(ATOM_CHAR)
1287 .map_or(step.offset, |i| head.offset + i)
1288 } else {
1289 text[step.offset..head.offset]
1290 .rfind(ATOM_CHAR)
1291 .map_or(step.offset, |i| step.offset + i + atom)
1292 };
1293 self.model.doc.clamp(BlockPosition::new(head.block, offset))
1294 }
1295
1296 fn delete_by(&mut self, line: bool, forward: bool, cx: &mut Context<Self>) {
1301 self.end_composition();
1302 if self.readonly {
1303 return;
1304 }
1305 let origin = if forward {
1306 EditOrigin::DeleteForward
1307 } else {
1308 EditOrigin::Backspace
1309 };
1310 let head = match self.model.selection() {
1311 BlockSelection::Text { anchor, head } if anchor == head => *head,
1312 _ => return self.edit(origin, |m| m.delete(forward), cx),
1313 };
1314 let target = if line {
1315 self.delete_line_target(head, forward)
1316 } else {
1317 self.delete_word_target(head, forward)
1318 };
1319 if line {
1320 self.model.break_coalescing();
1321 }
1322 if target == head {
1323 if super::table::table_of(&self.model.doc, head.block).is_none() {
1324 self.edit(origin, |m| m.delete(forward), cx);
1325 }
1326 } else {
1327 let origin = if forward {
1328 EditOrigin::WordDelete
1329 } else {
1330 EditOrigin::WordBackspace
1331 };
1332 self.edit(origin, |m| m.delete_span(target, head, forward), cx);
1333 }
1334 if line {
1335 self.model.break_coalescing();
1336 }
1337 }
1338
1339 fn paragraph_edge(&mut self, forward: bool, extend: bool, cx: &mut Context<Self>) {
1342 if self.collapse_blocks(forward, cx) {
1343 return;
1344 }
1345 let Some((_, head)) = self.head_anchor() else {
1346 return;
1347 };
1348 let doc = &self.model.doc;
1349 let len = doc.leaf_len(head.block).unwrap_or(0);
1350 let mut pos = head;
1351 if (forward && pos.offset >= len) || (!forward && pos.offset == 0) {
1352 pos = self.step_horizontal(pos, forward);
1353 }
1354 let offset = if forward {
1355 doc.leaf_len(pos.block).unwrap_or(0)
1356 } else {
1357 0
1358 };
1359 let target = doc.clamp(BlockPosition::new(pos.block, offset));
1360 self.goal_x = None;
1361 self.move_to(target, extend, cx);
1362 }
1363
1364 pub(crate) fn on_delete_word_left(
1365 &mut self,
1366 _: &DeleteWordLeft,
1367 _: &mut Window,
1368 cx: &mut Context<Self>,
1369 ) {
1370 self.delete_by(false, false, cx);
1371 }
1372 pub(crate) fn on_delete_word_right(
1373 &mut self,
1374 _: &DeleteWordRight,
1375 _: &mut Window,
1376 cx: &mut Context<Self>,
1377 ) {
1378 self.delete_by(false, true, cx);
1379 }
1380 pub(crate) fn on_delete_to_line_start(
1381 &mut self,
1382 _: &DeleteToLineStart,
1383 _: &mut Window,
1384 cx: &mut Context<Self>,
1385 ) {
1386 self.delete_by(true, false, cx);
1387 }
1388 pub(crate) fn on_delete_to_line_end(
1389 &mut self,
1390 _: &DeleteToLineEnd,
1391 _: &mut Window,
1392 cx: &mut Context<Self>,
1393 ) {
1394 self.delete_by(true, true, cx);
1395 }
1396 pub(crate) fn on_move_paragraph_start(
1397 &mut self,
1398 _: &MoveParagraphStart,
1399 _: &mut Window,
1400 cx: &mut Context<Self>,
1401 ) {
1402 self.paragraph_edge(false, false, cx);
1403 }
1404 pub(crate) fn on_move_paragraph_end(
1405 &mut self,
1406 _: &MoveParagraphEnd,
1407 _: &mut Window,
1408 cx: &mut Context<Self>,
1409 ) {
1410 self.paragraph_edge(true, false, cx);
1411 }
1412 pub(crate) fn on_select_paragraph_start(
1413 &mut self,
1414 _: &SelectParagraphStart,
1415 _: &mut Window,
1416 cx: &mut Context<Self>,
1417 ) {
1418 self.paragraph_edge(false, true, cx);
1419 }
1420 pub(crate) fn on_select_paragraph_end(
1421 &mut self,
1422 _: &SelectParagraphEnd,
1423 _: &mut Window,
1424 cx: &mut Context<Self>,
1425 ) {
1426 self.paragraph_edge(true, true, cx);
1427 }
1428
1429 pub fn link_at(&self, at: Point<Pixels>) -> Option<(BlockId, Range<usize>, SharedString)> {
1432 let pos = {
1433 let frame = self.frame.borrow();
1434 let pos = frame.hit_test(at)?;
1435 if !frame.leaf(pos.block)?.bounds().contains(&at) {
1436 return None;
1437 }
1438 pos
1439 };
1440 let inline = self.model.doc.inline(pos.block)?;
1441 let url = inline.mark_after(pos.offset)?.link.as_ref()?.url.clone();
1442 let range = link_extent(inline, pos.offset, &url)?;
1443 Some((pos.block, range, url))
1444 }
1445
1446 pub fn hovered_link(&self) -> Option<&SharedString> {
1448 self.hovered_link.as_ref()
1449 }
1450
1451 pub(crate) fn hover_at(&mut self, at: Point<Pixels>, secondary: bool, cx: &mut Context<Self>) {
1452 self.hover_table(at, cx);
1453 let url = self.link_at(at).map(|(_, _, url)| url);
1454 let hand = self.task_checkbox_at(at).is_some() || (secondary && url.is_some());
1455 if hand != self.pointer_hand {
1456 self.pointer_hand = hand;
1457 cx.notify();
1458 }
1459 if url != self.hovered_link {
1460 self.hovered_link = url.clone();
1461 cx.emit(BlockEditorEvent::LinkHovered { url });
1462 cx.notify();
1463 }
1464 }
1465
1466 pub(crate) fn follow_link_at(&mut self, at: Point<Pixels>, cx: &mut Context<Self>) -> bool {
1469 match self.link_at(at) {
1470 Some((_, _, url)) => {
1471 cx.emit(BlockEditorEvent::LinkClicked { url });
1472 true
1473 }
1474 None => false,
1475 }
1476 }
1477
1478 pub fn link_at_caret(&self) -> Option<SharedString> {
1480 self.link_range_at_caret().map(|(_, _, url)| url)
1481 }
1482
1483 fn link_range_at_caret(&self) -> Option<(BlockId, Range<usize>, SharedString)> {
1484 let head = match &self.model.selection {
1485 BlockSelection::Text { anchor, head } => {
1486 if anchor.block == head.block && anchor.offset < head.offset {
1487 *anchor
1488 } else {
1489 *head
1490 }
1491 }
1492 _ => return None,
1493 };
1494 let inline = self.model.doc.inline(head.block)?;
1495 let mark = inline
1496 .mark_after(head.offset)
1497 .filter(|m| m.link.is_some())
1498 .or_else(|| inline.mark_before(head.offset))?;
1499 let url = mark.link.as_ref()?.url.clone();
1500 let probe = if inline
1501 .mark_after(head.offset)
1502 .is_some_and(|m| m.link.is_some())
1503 {
1504 head.offset
1505 } else {
1506 head.offset.checked_sub(1)?
1507 };
1508 let range = link_extent(inline, probe, &url)?;
1509 Some((head.block, range, url))
1510 }
1511
1512 pub fn request_link_edit(&mut self, cx: &mut Context<Self>) {
1514 if self.readonly {
1515 return;
1516 }
1517 let url = self.link_at_caret();
1518 cx.emit(BlockEditorEvent::EditLinkRequested { url });
1519 }
1520
1521 pub(crate) fn on_edit_link(&mut self, _: &EditLink, _: &mut Window, cx: &mut Context<Self>) {
1522 self.request_link_edit(cx);
1523 }
1524
1525 pub fn set_link(&mut self, url: Option<&str>, cx: &mut Context<Self>) {
1528 if self.readonly {
1529 return;
1530 }
1531 let collapsed = matches!(
1532 &self.model.selection,
1533 BlockSelection::Text { anchor, head } if anchor == head
1534 );
1535 if collapsed && let Some((block, range, _)) = self.link_range_at_caret() {
1536 let _ = self.model.set_selection(BlockSelection::Text {
1537 anchor: BlockPosition::new(block, range.start),
1538 head: BlockPosition::new(block, range.end),
1539 });
1540 } else if collapsed {
1541 return;
1542 }
1543 let change = match url.map(str::trim).filter(|u| !u.is_empty()) {
1544 Some(url) => super::marks::MarkChange::new().with_link(url.to_string(), None),
1545 None => super::marks::MarkChange::new().clear(MarkKind::Link),
1546 };
1547 self.model.break_coalescing();
1548 let _ = self.run(EditorCommand::ApplyMark(change), cx);
1549 self.model.break_coalescing();
1550 }
1551
1552 pub(crate) fn click_checkbox(&mut self, at: Point<Pixels>, cx: &mut Context<Self>) -> bool {
1557 match self.task_checkbox_at(at) {
1558 Some((leaf, checked)) if !self.readonly => {
1559 self.end_composition();
1560 self.dragging = false;
1561 self.set_task_checked(leaf, !checked, cx);
1562 true
1563 }
1564 _ => false,
1565 }
1566 }
1567
1568 pub(crate) fn task_checkbox_at(&self, at: Point<Pixels>) -> Option<(BlockId, bool)> {
1569 use super::kind::BlockKindData;
1570 use gpui::px;
1571 let frame = self.frame.borrow();
1572 frame.leaves.iter().find_map(|leaf| {
1573 let b = leaf.bounds();
1574 let line = px(28.).min(b.size.height);
1575 if !(at.y >= b.top()
1576 && at.y < b.top() + line
1577 && at.x < b.left()
1578 && at.x >= b.left() - px(28.))
1579 {
1580 return None;
1581 }
1582 let block = leaf.info.block;
1583 let (item, index) = self.model.doc.index_in_parent(block)?;
1584 match self.model.doc.kind(item) {
1585 Some(BlockKindData::ListItem {
1586 checked: Some(c), ..
1587 }) if index == 0 => Some((block, *c)),
1588 _ => None,
1589 }
1590 })
1591 }
1592
1593 pub(crate) fn mouse_at(&mut self, at: Point<Pixels>, extend: bool, cx: &mut Context<Self>) {
1595 if !extend && let Some(block) = self.block_handle_at(at) {
1598 self.goal_x = None;
1599 self.end_composition();
1600 let _ = self.model.set_selection(BlockSelection::Blocks {
1601 anchor: block,
1602 head: block,
1603 });
1604 self.dragging = false;
1605 self.selection_changed(cx);
1606 return;
1607 }
1608 let hit = self.frame.borrow().hit_test(at);
1609 if !extend && let Some(pos) = hit.and_then(|pos| self.atom_under(pos, at)) {
1611 self.goal_x = None;
1612 self.end_composition();
1613 let _ = self.model.set_selection(BlockSelection::Text {
1614 anchor: pos,
1615 head: BlockPosition::new(
1616 pos.block,
1617 pos.offset + super::inline::ATOM_CHAR.len_utf8(),
1618 ),
1619 });
1620 self.selection_changed(cx);
1621 return;
1622 }
1623 if let Some(pos) = hit {
1624 self.goal_x = None;
1625 let pos = self.model.doc.clamp(pos);
1626 self.move_to(pos, extend, cx);
1627 }
1628 }
1629
1630 pub(crate) fn select_unit(&mut self, block: bool, cx: &mut Context<Self>) {
1632 let Some(caret) = self.model.caret() else {
1633 return;
1634 };
1635 let Some(text) = self.model.doc.leaf_text(caret.block) else {
1636 return;
1637 };
1638 let range = if block {
1639 0..text.len()
1640 } else {
1641 match crate::support::word_range_at(text, caret.offset) {
1642 Some(r) => r,
1643 None => return,
1644 }
1645 };
1646 let _ = self.model.set_selection(BlockSelection::Text {
1647 anchor: BlockPosition::new(caret.block, range.start),
1648 head: BlockPosition::new(caret.block, range.end),
1649 });
1650 self.dragging = false;
1651 self.selection_changed(cx);
1652 }
1653
1654 pub(crate) fn top_level_bounds(&self) -> Vec<(usize, BlockId, Bounds<Pixels>)> {
1657 let doc = &self.model.doc;
1658 let frame = self.frame.borrow();
1659 let children = doc.children(doc.root);
1660 let mut out: Vec<(usize, BlockId, Bounds<Pixels>)> = Vec::new();
1661 for leaf in &frame.leaves {
1662 let path = doc.path(leaf.info.block);
1663 let Some(top) = path.get(1).copied() else {
1664 continue;
1665 };
1666 let Some(ix) = children.iter().position(|c| *c == top) else {
1667 continue;
1668 };
1669 let b = leaf.bounds();
1670 match out.iter_mut().find(|(_, id, _)| *id == top) {
1671 Some(entry) => entry.2 = entry.2.union(&b),
1672 None => out.push((ix, top, b)),
1673 }
1674 }
1675 out.sort_by_key(|(ix, _, _)| *ix);
1676 out
1677 }
1678
1679 pub(crate) fn drag_handle_at(&self, at: Point<Pixels>) -> Option<BlockId> {
1682 if self.readonly {
1683 return None;
1684 }
1685 let block = self.block_handle_at(at)?;
1686 let doc = &self.model.doc;
1687 let top = doc.path(block).get(1).copied()?;
1689 (top == block).then_some(block)
1690 }
1691
1692 fn drop_gap_at(&self, y: Pixels) -> Option<usize> {
1694 let blocks = self.top_level_bounds();
1695 let len = self.model.doc.children(self.model.doc.root).len();
1696 if blocks.is_empty() {
1697 return None;
1698 }
1699 Some(
1700 blocks
1701 .iter()
1702 .find(|(_, _, b)| y < b.center().y)
1703 .map_or_else(
1704 || blocks.last().map_or(len, |(ix, _, _)| ix + 1),
1705 |(ix, _, _)| *ix,
1706 ),
1707 )
1708 }
1709
1710 pub(crate) fn arm_block_drag(&mut self, at: Point<Pixels>) {
1713 self.block_drag = self.drag_handle_at(at).map(|block| BlockDrag {
1714 block,
1715 start: at,
1716 active: false,
1717 drop: None,
1718 });
1719 }
1720
1721 pub(crate) fn block_drag_to(&mut self, at: Point<Pixels>, cx: &mut Context<Self>) -> bool {
1724 use gpui::px;
1725 let Some(mut drag) = self.block_drag else {
1726 return false;
1727 };
1728 if !drag.active {
1729 let d = at - drag.start;
1730 if d.x.abs().max(d.y.abs()) < px(4.) {
1731 return false;
1732 }
1733 drag.active = true;
1734 self.dragging = false;
1735 self.end_composition();
1736 let _ = self.model.set_selection(BlockSelection::Blocks {
1737 anchor: drag.block,
1738 head: drag.block,
1739 });
1740 self.selection_changed(cx);
1741 }
1742 drag.drop = self.drop_gap_at(at.y);
1743 self.block_drag = Some(drag);
1744 cx.notify();
1745 true
1746 }
1747
1748 pub(crate) fn finish_block_drag(&mut self, cx: &mut Context<Self>) {
1750 let Some(drag) = self.block_drag.take() else {
1751 return;
1752 };
1753 if drag.active {
1754 if let Some(to) = drag.drop {
1755 self.model.break_coalescing();
1756 self.edit(
1757 EditOrigin::Command("move_block_to"),
1758 |m| m.move_block_to(drag.block, to).map(|_| ()),
1759 cx,
1760 );
1761 self.model.break_coalescing();
1762 }
1763 cx.notify();
1764 }
1765 }
1766
1767 pub(crate) fn drop_indicator(&self) -> Option<Bounds<Pixels>> {
1770 use gpui::{point, px, size};
1771 let drag = self.block_drag.filter(|d| d.active)?;
1772 let to = drag.drop?;
1773 let blocks = self.top_level_bounds();
1774 let left = blocks
1775 .iter()
1776 .map(|(_, _, b)| b.left())
1777 .reduce(Pixels::min)?;
1778 let right = blocks
1779 .iter()
1780 .map(|(_, _, b)| b.right())
1781 .reduce(Pixels::max)?;
1782 let prev = blocks.iter().rev().find(|(ix, _, _)| *ix < to);
1783 let next = blocks.iter().find(|(ix, _, _)| *ix >= to);
1784 let y = match (prev, next) {
1785 (Some((_, _, a)), Some((_, _, b))) => (a.bottom() + b.top()) / 2.,
1786 (None, Some((_, _, b))) => b.top() - px(3.),
1787 (Some((_, _, a)), None) => a.bottom() + px(3.),
1788 (None, None) => return None,
1789 };
1790 Some(gpui::Bounds::new(
1791 point(left - px(8.), y - px(1.)),
1792 size(right - left + px(8.), px(2.)),
1793 ))
1794 }
1795
1796 pub(crate) fn block_handle_at(&self, at: Point<Pixels>) -> Option<BlockId> {
1799 let frame = self.frame.borrow();
1800 use gpui::px;
1801 let in_edge = |b: &Bounds<Pixels>| {
1802 at.y >= b.top() && at.y < b.bottom() && at.x < b.left() && at.x >= b.left() - px(24.)
1803 };
1804 if let Some((table, _)) = frame.tables.iter().find(|(_, b)| in_edge(b)) {
1805 return Some(*table);
1806 }
1807 frame
1808 .leaves
1809 .iter()
1810 .find(|leaf| self.model.doc.plain(leaf.info.block).is_some() && in_edge(&leaf.bounds()))
1811 .map(|leaf| leaf.info.block)
1812 }
1813
1814 fn atom_under(&self, pos: BlockPosition, at: Point<Pixels>) -> Option<BlockPosition> {
1817 let frame = self.frame.borrow();
1818 let range = frame.leaf(pos.block)?.atom_at(at)?;
1819 Some(BlockPosition::new(pos.block, range.start))
1820 }
1821
1822 fn toggle_mark(&mut self, kind: MarkKind, cx: &mut Context<Self>) {
1823 let _ = self.run(EditorCommand::ToggleMark(kind), cx);
1824 }
1825
1826 fn selected_text(&self) -> (String, CopyKind) {
1830 if let Some(table) = self.model.cells_markdown() {
1831 return (table, CopyKind::Blocks);
1832 }
1833 if let Some((parent, range)) = self.model.selected_blocks() {
1834 let ids = &self.model.doc.children(parent)[range];
1835 return (
1836 super::serialize::join_blocks(&self.model.doc, ids),
1837 CopyKind::Blocks,
1838 );
1839 }
1840 let Ok(range) = self.model.doc.resolve_selection(self.model.selection()) else {
1841 return (String::new(), CopyKind::External);
1842 };
1843 if range.start.block != range.end.block
1845 && let Some(markdown) = super::slice::selection_markdown(&self.model.doc, range)
1846 {
1847 return (markdown, CopyKind::Blocks);
1848 }
1849 let mut inline_only = true;
1850 let text = self
1851 .model
1852 .text_ranges(range)
1853 .into_iter()
1854 .filter_map(|(id, lo, hi)| {
1855 if let Some(inline) = self.model.doc.inline(id) {
1860 return Some(super::serialize::inline_markdown(&inline.slice(lo..hi)));
1861 }
1862 inline_only = false;
1863 let text = self.model.doc.leaf_text(id)?;
1864 text.get(lo..hi).map(str::to_string)
1865 })
1866 .collect::<Vec<_>>()
1867 .join("\n");
1868 (
1869 text,
1870 if inline_only {
1871 CopyKind::Inline
1872 } else {
1873 CopyKind::External
1874 },
1875 )
1876 }
1877
1878 pub(crate) fn on_backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Context<Self>) {
1881 self.end_composition();
1882 self.edit(EditOrigin::Backspace, |m| m.delete(false), cx);
1883 }
1884 pub(crate) fn on_delete(&mut self, _: &Delete, _: &mut Window, cx: &mut Context<Self>) {
1885 self.end_composition();
1886 self.edit(EditOrigin::DeleteForward, |m| m.delete(true), cx);
1887 }
1888 pub(crate) fn on_enter(&mut self, _: &Enter, _: &mut Window, cx: &mut Context<Self>) {
1889 if self.readonly || self.is_composing() {
1890 return;
1891 }
1892 if self.submit_policy == SubmitPolicy::Enter {
1893 cx.emit(BlockEditorEvent::SubmitRequested);
1894 return;
1895 }
1896 self.newline(cx);
1897 }
1898 pub(crate) fn on_structural_enter(
1899 &mut self,
1900 _: &StructuralEnter,
1901 _: &mut Window,
1902 cx: &mut Context<Self>,
1903 ) {
1904 if self.readonly || self.is_composing() {
1905 return;
1906 }
1907 self.newline(cx);
1908 }
1909 fn newline(&mut self, cx: &mut Context<Self>) {
1910 self.end_composition();
1911 if !self.readonly
1913 && self.rule_on(RuleKind::CodeFence)
1914 && let Some(caret) = self.model.caret()
1915 && self.model.doc.kind(caret.block) == Some(&super::kind::BlockKindData::Paragraph)
1916 && caret.offset == self.model.doc.leaf_len(caret.block).unwrap_or(0)
1917 && let Some(lang) = self
1918 .model
1919 .doc
1920 .inline(caret.block)
1921 .and_then(|i| code_fence(i.text()))
1922 {
1923 self.fence_to_code_block(caret.block, lang, cx);
1924 return;
1925 }
1926 if !self.readonly && self.model.current_cell().is_some() {
1928 self.model.break_coalescing();
1929 self.edit(
1930 EditOrigin::Command("table_enter"),
1931 |m| m.table_enter().map(|_| ()),
1932 cx,
1933 );
1934 self.model.break_coalescing();
1935 return;
1936 }
1937 if !self.readonly
1939 && self.rule_on(RuleKind::Table)
1940 && let Some(caret) = self.model.caret()
1941 && caret.offset == self.model.doc.leaf_len(caret.block).unwrap_or(0)
1942 && self
1943 .model
1944 .doc
1945 .inline(caret.block)
1946 .is_some_and(|i| super::table::table_row_cells(i.text()).is_some())
1947 {
1948 let block = caret.block;
1949 self.model.break_coalescing();
1950 self.edit(
1951 EditOrigin::InputRule("table"),
1952 |m| m.paragraph_to_table(block).map(|_| ()),
1953 cx,
1954 );
1955 self.model.break_coalescing();
1956 return;
1957 }
1958 if !self.readonly
1960 && self.rule_on(RuleKind::HorizontalRule)
1961 && let Some(caret) = self.model.caret()
1962 && self.model.doc.kind(caret.block) == Some(&super::kind::BlockKindData::Paragraph)
1963 && self.model.doc.inline(caret.block).map(|i| i.text()) == Some("---")
1964 {
1965 let block = caret.block;
1966 self.model.break_coalescing();
1967 self.model.history.begin_transaction();
1968 let _ = self.model.set_selection(BlockSelection::Text {
1969 anchor: BlockPosition::new(block, 0),
1970 head: BlockPosition::new(block, 3),
1971 });
1972 let _ = self.model.delete(false);
1973 let _ = self.model.paragraph_to_rule();
1974 self.model.history.commit_transaction();
1975 self.model.break_coalescing();
1976 self.after_edit(EditOrigin::InputRule("horizontal_rule"), cx);
1977 return;
1978 }
1979 self.edit(EditOrigin::Command("split_block"), |m| m.split_block(), cx);
1980 }
1981 pub(crate) fn on_soft_break(&mut self, _: &SoftBreak, _: &mut Window, cx: &mut Context<Self>) {
1984 if self.readonly || self.is_composing() {
1985 return;
1986 }
1987 self.end_composition();
1988 if self.model.current_cell().is_some() {
1989 self.model.break_coalescing();
1990 self.edit(
1991 EditOrigin::Command("cell_soft_break"),
1992 |m| m.cell_soft_break().map(|_| ()),
1993 cx,
1994 );
1995 self.model.break_coalescing();
1996 } else {
1997 self.newline(cx);
1998 }
1999 }
2000
2001 pub(crate) fn on_exit_block(&mut self, _: &ExitBlock, _: &mut Window, cx: &mut Context<Self>) {
2003 if self.readonly || self.is_composing() {
2004 return;
2005 }
2006 if matches!(
2007 self.submit_policy,
2008 SubmitPolicy::Enter | SubmitPolicy::ModifiedEnter
2009 ) {
2010 cx.emit(BlockEditorEvent::SubmitRequested);
2011 return;
2012 }
2013 self.end_composition();
2014 if self.readonly || self.model.current_cell().is_none() {
2015 cx.propagate();
2016 return;
2017 }
2018 self.model.break_coalescing();
2019 self.edit(
2020 EditOrigin::Command("table_exit"),
2021 |m| m.exit_table().map(|_| ()),
2022 cx,
2023 );
2024 self.model.break_coalescing();
2025 }
2026
2027 pub(crate) fn on_move_left(&mut self, _: &MoveLeft, _: &mut Window, cx: &mut Context<Self>) {
2028 self.horizontal(false, false, cx);
2029 }
2030 pub(crate) fn on_move_right(&mut self, _: &MoveRight, _: &mut Window, cx: &mut Context<Self>) {
2031 self.horizontal(true, false, cx);
2032 }
2033 pub(crate) fn on_move_up(&mut self, _: &MoveUp, _: &mut Window, cx: &mut Context<Self>) {
2034 self.vertical(false, false, cx);
2035 }
2036 pub(crate) fn on_move_down(&mut self, _: &MoveDown, _: &mut Window, cx: &mut Context<Self>) {
2037 self.vertical(true, false, cx);
2038 }
2039 pub(crate) fn on_move_home(&mut self, _: &MoveHome, _: &mut Window, cx: &mut Context<Self>) {
2040 self.line_edge(false, false, cx);
2041 }
2042 pub(crate) fn on_move_end(&mut self, _: &MoveEnd, _: &mut Window, cx: &mut Context<Self>) {
2043 self.line_edge(true, false, cx);
2044 }
2045 pub(crate) fn on_select_left(
2046 &mut self,
2047 _: &SelectLeft,
2048 _: &mut Window,
2049 cx: &mut Context<Self>,
2050 ) {
2051 self.horizontal(false, true, cx);
2052 }
2053 pub(crate) fn on_select_right(
2054 &mut self,
2055 _: &SelectRight,
2056 _: &mut Window,
2057 cx: &mut Context<Self>,
2058 ) {
2059 self.horizontal(true, true, cx);
2060 }
2061 pub(crate) fn on_select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
2062 self.vertical(false, true, cx);
2063 }
2064 pub(crate) fn on_select_down(
2065 &mut self,
2066 _: &SelectDown,
2067 _: &mut Window,
2068 cx: &mut Context<Self>,
2069 ) {
2070 self.vertical(true, true, cx);
2071 }
2072 pub(crate) fn on_select_home(
2073 &mut self,
2074 _: &SelectHome,
2075 _: &mut Window,
2076 cx: &mut Context<Self>,
2077 ) {
2078 self.line_edge(false, true, cx);
2079 }
2080 pub(crate) fn on_select_end(&mut self, _: &SelectEnd, _: &mut Window, cx: &mut Context<Self>) {
2081 self.line_edge(true, true, cx);
2082 }
2083 pub(crate) fn on_move_doc_start(
2084 &mut self,
2085 _: &MoveDocStart,
2086 _: &mut Window,
2087 cx: &mut Context<Self>,
2088 ) {
2089 self.goal_x = None;
2090 self.move_to(self.model.doc.first_caret(), false, cx);
2091 }
2092 pub(crate) fn on_move_doc_end(
2093 &mut self,
2094 _: &MoveDocEnd,
2095 _: &mut Window,
2096 cx: &mut Context<Self>,
2097 ) {
2098 self.goal_x = None;
2099 self.move_to(self.model.doc.last_caret(), false, cx);
2100 }
2101 pub(crate) fn on_select_doc_start(
2102 &mut self,
2103 _: &SelectDocStart,
2104 _: &mut Window,
2105 cx: &mut Context<Self>,
2106 ) {
2107 self.goal_x = None;
2108 self.move_to(self.model.doc.first_caret(), true, cx);
2109 }
2110 pub(crate) fn on_select_doc_end(
2111 &mut self,
2112 _: &SelectDocEnd,
2113 _: &mut Window,
2114 cx: &mut Context<Self>,
2115 ) {
2116 self.goal_x = None;
2117 self.move_to(self.model.doc.last_caret(), true, cx);
2118 }
2119 pub(crate) fn on_select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
2120 self.end_composition();
2121 let _ = self.model.run(EditorCommand::SelectAll);
2122 self.selection_changed(cx);
2123 }
2124 pub(crate) fn on_undo(&mut self, _: &Undo, _: &mut Window, cx: &mut Context<Self>) {
2125 self.end_composition();
2126 self.undo(cx);
2127 }
2128 pub(crate) fn on_redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
2129 self.end_composition();
2130 self.redo(cx);
2131 }
2132 pub(crate) fn on_normal_text(
2133 &mut self,
2134 _: &NormalText,
2135 _: &mut Window,
2136 cx: &mut Context<Self>,
2137 ) {
2138 if self.readonly {
2139 return;
2140 }
2141 self.end_composition();
2142 self.model.break_coalescing();
2143 let _ = self.run(EditorCommand::SetBlockKind(BlockKind::Paragraph), cx);
2144 self.model.break_coalescing();
2145 }
2146 pub(crate) fn on_close_table_menu(
2147 &mut self,
2148 _: &CloseTableMenu,
2149 _: &mut Window,
2150 cx: &mut Context<Self>,
2151 ) {
2152 self.close_table_menu(cx);
2153 }
2154 fn table_key(&mut self, edit: Option<crate::TableEdit>, cx: &mut Context<Self>) {
2155 if self.readonly || self.model.current_cell().is_none() {
2156 cx.propagate();
2157 return;
2158 }
2159 self.end_composition();
2160 self.close_table_menu(cx);
2161 if let Some(edit) = edit {
2162 self.model.break_coalescing();
2163 let _ = self.run(EditorCommand::EditTable(edit), cx);
2164 self.model.break_coalescing();
2165 }
2166 }
2167 pub(crate) fn on_table_insert_column_left(
2168 &mut self,
2169 _: &TableInsertColumnLeft,
2170 _: &mut Window,
2171 cx: &mut Context<Self>,
2172 ) {
2173 self.table_key(Some(crate::TableEdit::AddColumnLeft), cx);
2174 }
2175 pub(crate) fn on_table_insert_column_right(
2176 &mut self,
2177 _: &TableInsertColumnRight,
2178 _: &mut Window,
2179 cx: &mut Context<Self>,
2180 ) {
2181 self.table_key(Some(crate::TableEdit::AddColumnRight), cx);
2182 }
2183 pub(crate) fn on_table_insert_row_above(
2184 &mut self,
2185 _: &TableInsertRowAbove,
2186 _: &mut Window,
2187 cx: &mut Context<Self>,
2188 ) {
2189 self.table_key(Some(crate::TableEdit::AddRowAbove), cx);
2190 }
2191 pub(crate) fn on_table_insert_row_below(
2192 &mut self,
2193 _: &TableInsertRowBelow,
2194 _: &mut Window,
2195 cx: &mut Context<Self>,
2196 ) {
2197 self.table_key(Some(crate::TableEdit::AddRowBelow), cx);
2198 }
2199 pub(crate) fn on_table_delete_line(
2203 &mut self,
2204 _: &TableDeleteLine,
2205 _: &mut Window,
2206 cx: &mut Context<Self>,
2207 ) {
2208 let edit = self.table_delete_edit();
2209 self.table_key(edit, cx);
2210 }
2211 pub fn table_delete_edit(&self) -> Option<crate::TableEdit> {
2213 if self.model.whole_columns_selected() {
2214 return Some(crate::TableEdit::RemoveColumn);
2215 }
2216 let (_, rows, _) = self.model.table_span()?;
2217 (*rows.start() > 0).then_some(crate::TableEdit::RemoveRow)
2218 }
2219 pub(crate) fn on_toggle_bold(
2220 &mut self,
2221 _: &ToggleBold,
2222 _: &mut Window,
2223 cx: &mut Context<Self>,
2224 ) {
2225 self.toggle_mark(MarkKind::Bold, cx);
2226 }
2227 pub(crate) fn on_toggle_italic(
2228 &mut self,
2229 _: &ToggleItalic,
2230 _: &mut Window,
2231 cx: &mut Context<Self>,
2232 ) {
2233 self.toggle_mark(MarkKind::Italic, cx);
2234 }
2235 pub(crate) fn on_toggle_code(
2236 &mut self,
2237 _: &ToggleCode,
2238 _: &mut Window,
2239 cx: &mut Context<Self>,
2240 ) {
2241 self.toggle_mark(MarkKind::Code, cx);
2242 }
2243 pub(crate) fn on_indent(&mut self, _: &Indent, _: &mut Window, cx: &mut Context<Self>) {
2244 let _ = self.run(EditorCommand::Indent, cx);
2245 }
2246 pub(crate) fn on_outdent(&mut self, _: &Outdent, _: &mut Window, cx: &mut Context<Self>) {
2247 let _ = self.run(EditorCommand::Outdent, cx);
2248 }
2249 pub(crate) fn on_copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
2250 let (text, kind) = self.selected_text();
2251 if text.is_empty() {
2252 return;
2253 }
2254 cx.write_to_clipboard(match kind.tag() {
2255 Some(tag) => ClipboardItem::new_string_with_metadata(text, tag.to_string()),
2256 None => ClipboardItem::new_string(text),
2257 });
2258 }
2259 pub(crate) fn on_cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
2260 self.end_composition();
2261 self.on_copy(&Copy, window, cx);
2262 self.edit(
2263 EditOrigin::Cut,
2264 |m| m.run(EditorCommand::DeleteSelection).map(|_| ()),
2265 cx,
2266 );
2267 }
2268 pub(crate) fn on_paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
2269 if self.readonly {
2270 return;
2271 }
2272 if let Some(item) = cx.read_from_clipboard() {
2273 self.paste(item, false, cx);
2274 }
2275 }
2276 pub(crate) fn on_native_paste(
2277 &mut self,
2278 _: &gpui_kit::base::input::Paste,
2279 window: &mut Window,
2280 cx: &mut Context<Self>,
2281 ) {
2282 self.on_paste(&Paste, window, cx);
2283 }
2284 pub(crate) fn on_paste_plain(
2285 &mut self,
2286 _: &PastePlain,
2287 _: &mut Window,
2288 cx: &mut Context<Self>,
2289 ) {
2290 if self.readonly {
2291 return;
2292 }
2293 if let Some(item) = cx.read_from_clipboard() {
2294 self.paste(item, true, cx);
2295 }
2296 }
2297
2298 pub fn paste(&mut self, item: ClipboardItem, plain: bool, cx: &mut Context<Self>) {
2303 if self.readonly {
2304 return;
2305 }
2306 self.end_composition();
2307 if self.clipboard_policy == ClipboardPolicy::Host {
2308 self.paste_sequence += 1;
2309 self.pending_paste = Some(self.paste_sequence);
2310 let request = PasteRequest {
2311 range: self.model.capture(self.model.selection.clone()),
2312 plain,
2313 copy: CopyKind::of(&item),
2314 sequence: self.paste_sequence,
2315 };
2316 cx.emit(BlockEditorEvent::PasteRequested { request, item });
2317 } else if let Some(text) = item.text() {
2318 self.paste_text(&text, plain, CopyKind::of(&item), cx);
2319 }
2320 }
2321
2322 pub fn is_paste_current(&self, request: &PasteRequest) -> bool {
2326 !self.readonly
2327 && self.pending_paste == Some(request.sequence)
2328 && self.model.is_capture_current(&request.range)
2329 }
2330 pub fn apply_paste(
2331 &mut self,
2332 request: &PasteRequest,
2333 text: Option<&str>,
2334 cx: &mut Context<Self>,
2335 ) -> bool {
2336 if !self.is_paste_current(request) {
2337 return false;
2338 }
2339 self.pending_paste = None;
2340 if let Some(text) = text {
2341 self.paste_text(text, request.plain, request.copy, cx);
2342 }
2343 true
2344 }
2345 fn paste_text(&mut self, text: &str, plain: bool, copy: CopyKind, cx: &mut Context<Self>) {
2346 self.edit(
2347 EditOrigin::Paste,
2348 |model| {
2349 if !plain && copy == CopyKind::Blocks && model.paste_blocks_markdown(text)? {
2350 return Ok(());
2351 }
2352 if !plain && copy != CopyKind::External && model.paste_inline_markdown(text)? {
2353 return Ok(());
2354 }
2355 if !plain && model.paste_blocks(text)? {
2356 return Ok(());
2357 }
2358 model.insert_text(text, EditOrigin::Paste)
2359 },
2360 cx,
2361 );
2362 }
2363
2364 fn apply_input_rule(&mut self, cx: &mut Context<Self>) {
2369 use super::kind::BlockKindData;
2370 let Some(caret) = self.model.caret() else {
2371 return;
2372 };
2373 let doc = &self.model.doc;
2374 if doc.kind(caret.block) != Some(&BlockKindData::Paragraph) {
2375 return;
2376 }
2377 if self.rule_on(RuleKind::CodeFence)
2379 && let Some(lang) = doc
2380 .inline(caret.block)
2381 .and_then(|i| code_fence(i.text().strip_suffix(' ')?))
2382 .filter(|_| caret.offset == doc.leaf_len(caret.block).unwrap_or(0))
2383 {
2384 self.fence_to_code_block(caret.block, lang, cx);
2385 return;
2386 }
2387 let in_list = doc
2388 .parent(caret.block)
2389 .and_then(|parent| doc.kind(parent))
2390 .is_some_and(|kind| matches!(kind, BlockKindData::ListItem { .. }));
2391 let Some(text) = doc.inline(caret.block).map(|i| i.text().to_string()) else {
2392 return;
2393 };
2394 let prefix = &text[..caret.offset.min(text.len())];
2395 let Some(marker) = prefix.strip_suffix(' ') else {
2396 return;
2397 };
2398 let task = match marker {
2399 "[ ]" => Some(false),
2400 "[x]" | "[X]" => Some(true),
2401 _ => None,
2402 };
2403 let (commands, rule) = if self.rule_on(RuleKind::Heading)
2404 && !marker.is_empty()
2405 && marker.len() <= 6
2406 && marker.bytes().all(|b| b == b'#')
2407 {
2408 let level = marker.len() as u8;
2409 (
2410 vec![EditorCommand::SetBlockKind(BlockKind::Heading { level })],
2411 "heading",
2412 )
2413 } else if self.rule_on(RuleKind::BulletList)
2414 && !in_list
2415 && matches!(marker, "-" | "*" | "+")
2416 {
2417 (
2418 vec![EditorCommand::ToggleList { ordered: false }],
2419 "bullet_list",
2420 )
2421 } else if self.rule_on(RuleKind::OrderedList)
2422 && !in_list
2423 && marker.len() >= 2
2424 && marker.ends_with('.')
2425 && marker[..marker.len() - 1]
2426 .bytes()
2427 .all(|b| b.is_ascii_digit())
2428 {
2429 (
2430 vec![EditorCommand::ToggleList { ordered: true }],
2431 "ordered_list",
2432 )
2433 } else if self.rule_on(RuleKind::TaskItem)
2434 && let Some(checked) = task
2435 {
2436 let mut commands = Vec::new();
2437 if !in_list {
2438 commands.push(EditorCommand::ToggleList { ordered: false });
2439 }
2440 commands.push(EditorCommand::SetTaskChecked(checked));
2441 (commands, "task_item")
2442 } else if self.rule_on(RuleKind::Blockquote) && marker == ">" {
2443 (Vec::new(), "blockquote")
2444 } else {
2445 return;
2446 };
2447 let block = caret.block;
2448 let len = prefix.len();
2449 self.model.break_coalescing();
2450 self.model.history.begin_transaction();
2451 let _ = self.model.set_selection(BlockSelection::Text {
2452 anchor: BlockPosition::new(block, 0),
2453 head: BlockPosition::new(block, len),
2454 });
2455 let _ = self.model.delete(false);
2456 if commands.is_empty() {
2457 let _ = self.model.wrap_in_blockquote();
2458 }
2459 for command in commands {
2460 let _ = self.model.run(command);
2461 }
2462 self.model.history.commit_transaction();
2463 self.model.break_coalescing();
2464 self.after_edit(EditOrigin::InputRule(rule), cx);
2465 }
2466
2467 fn apply_link_rule(&mut self, cx: &mut Context<Self>) {
2470 let Some(caret) = self.model.caret() else {
2471 return;
2472 };
2473 let Some(inline) = self.model.doc.inline(caret.block) else {
2474 return;
2475 };
2476 let Some((start, text, url)) = link_rule(inline.text(), caret.offset) else {
2477 return;
2478 };
2479 let block = caret.block;
2480 let pos = |offset| BlockPosition::new(block, offset);
2481 let text_end = start + 1 + text.len();
2482 self.model.break_coalescing();
2483 self.model.history.begin_transaction();
2484 let _ = self.model.set_selection(BlockSelection::Text {
2486 anchor: pos(text_end),
2487 head: pos(caret.offset),
2488 });
2489 let _ = self.model.delete(false);
2490 let _ = self.model.set_selection(BlockSelection::Text {
2491 anchor: pos(start),
2492 head: pos(start + 1),
2493 });
2494 let _ = self.model.delete(false);
2495 let end = start + text.len();
2496 let _ = self.model.set_selection(BlockSelection::Text {
2497 anchor: pos(start),
2498 head: pos(end),
2499 });
2500 let _ = self.model.run(EditorCommand::ApplyMark(
2501 super::marks::MarkChange::new().with_link(url, None),
2502 ));
2503 let _ = self.model.set_selection(BlockSelection::caret(pos(end)));
2504 let after = super::marks::MarkChange::new()
2505 .clear(super::marks::MarkKind::Link)
2506 .apply(&self.model.active_marks().0);
2507 self.model.history.commit_transaction();
2508 self.model.break_coalescing();
2509 self.model.stored_marks = Some(after);
2510 self.after_edit(EditOrigin::InputRule("link"), cx);
2511 }
2512
2513 fn apply_mark_rule(&mut self, cx: &mut Context<Self>) {
2518 let Some(caret) = self.model.caret() else {
2519 return;
2520 };
2521 let Some(inline) = self.model.doc.inline(caret.block) else {
2522 return;
2523 };
2524 let Some((open, close, kind)) = mark_rule(inline, caret.offset) else {
2525 return;
2526 };
2527 let block = caret.block;
2528 let d = close.len();
2529 let pos = |offset| BlockPosition::new(block, offset);
2530 self.model.break_coalescing();
2531 self.model.history.begin_transaction();
2532 let _ = self.model.set_selection(BlockSelection::Text {
2533 anchor: pos(close.start),
2534 head: pos(close.end),
2535 });
2536 let _ = self.model.delete(false);
2537 let _ = self.model.set_selection(BlockSelection::Text {
2538 anchor: pos(open.start),
2539 head: pos(open.end),
2540 });
2541 let _ = self.model.delete(false);
2542 let end = close.start - d;
2543 let _ = self.model.set_selection(BlockSelection::Text {
2544 anchor: pos(open.start),
2545 head: pos(end),
2546 });
2547 let _ = self.model.run(EditorCommand::ApplyMark(
2548 super::marks::MarkChange::new().set(kind),
2549 ));
2550 let _ = self.model.set_selection(BlockSelection::caret(pos(end)));
2551 let after = super::marks::MarkChange::new()
2553 .clear(kind)
2554 .apply(&self.model.active_marks().0);
2555 self.model.history.commit_transaction();
2556 self.model.break_coalescing();
2557 self.model.stored_marks = Some(after);
2558 self.after_edit(EditOrigin::InputRule("inline_mark"), cx);
2559 }
2560
2561 fn fence_to_code_block(&mut self, block: BlockId, lang: String, cx: &mut Context<Self>) {
2564 let len = self.model.doc.leaf_len(block).unwrap_or(0);
2565 self.model.break_coalescing();
2566 self.model.history.begin_transaction();
2567 let _ = self.model.set_selection(BlockSelection::Text {
2568 anchor: BlockPosition::new(block, 0),
2569 head: BlockPosition::new(block, len),
2570 });
2571 let _ = self.model.delete(false);
2572 let (lang, meta) = lang
2573 .split_once(char::is_whitespace)
2574 .map_or((lang.as_str(), None), |(lang, meta)| {
2575 (lang, Some(meta.trim_start().to_owned()))
2576 });
2577 let _ = self.model.set_code_block_info(Some(lang.to_owned()), meta);
2578 self.model.history.commit_transaction();
2579 self.model.break_coalescing();
2580 self.after_edit(EditOrigin::InputRule("code_block"), cx);
2581 }
2582
2583 fn ime_block(&self) -> Option<BlockId> {
2587 if let Some(m) = &self.marked {
2588 return Some(m.block);
2589 }
2590 let (_, head) = self.head_anchor()?;
2591 Some(head.block)
2592 }
2593
2594 fn ime_text(&self) -> &str {
2595 self.ime_block()
2596 .and_then(|id| self.model.doc.leaf_text(id))
2597 .unwrap_or("")
2598 }
2599
2600 fn select_utf16(&mut self, block: BlockId, range16: Range<usize>) {
2601 let text = self.ime_text();
2602 let range = utf16_to_byte(text, range16.start)..utf16_to_byte(text, range16.end);
2603 let _ = self.model.set_selection(BlockSelection::Text {
2604 anchor: BlockPosition::new(block, range.start),
2605 head: BlockPosition::new(block, range.end),
2606 });
2607 }
2608
2609 fn select_marked_or(&mut self, range16: Option<Range<usize>>) {
2610 let Some(block) = self.ime_block() else {
2611 return;
2612 };
2613 if let Some(range16) = range16 {
2614 self.select_utf16(block, range16);
2615 } else if let Some(m) = self.marked.clone() {
2616 let _ = self.model.set_selection(BlockSelection::Text {
2617 anchor: BlockPosition::new(m.block, m.range.start),
2618 head: BlockPosition::new(m.block, m.range.end),
2619 });
2620 }
2621 }
2622}
2623
2624fn command_name(command: &EditorCommand) -> &'static str {
2625 match command {
2626 EditorCommand::ToggleMark(_) => "toggle_mark",
2627 EditorCommand::ApplyMark(_) => "apply_mark",
2628 EditorCommand::SetBlockKind(_) => "set_block_kind",
2629 EditorCommand::ToggleList { .. } => "toggle_list",
2630 EditorCommand::ToggleTask => "toggle_task",
2631 EditorCommand::SetTaskChecked(_) => "set_task_checked",
2632 EditorCommand::Indent => "indent",
2633 EditorCommand::Outdent => "outdent",
2634 EditorCommand::MoveBlocks { .. } => "move_blocks",
2635 EditorCommand::InsertBlock(_) => "insert_block",
2636 EditorCommand::SelectAll => "select_all",
2637 EditorCommand::DeleteSelection => "delete_selection",
2638 EditorCommand::SetCodeBlock { .. } => "code_block",
2639 EditorCommand::InsertTable { .. } => "insert_table",
2640 EditorCommand::EditTable(_) => "edit_table",
2641 }
2642}
2643
2644pub(crate) fn utf16_to_byte(text: &str, utf16: usize) -> usize {
2645 let mut count = 0;
2646 for (ix, ch) in text.char_indices() {
2647 if count >= utf16 {
2648 return ix;
2649 }
2650 count += ch.len_utf16();
2651 }
2652 text.len()
2653}
2654
2655pub(crate) fn byte_to_utf16(text: &str, byte: usize) -> usize {
2656 text.get(..byte.min(text.len()))
2657 .map(|s| s.encode_utf16().count())
2658 .unwrap_or(0)
2659}
2660
2661impl EntityInputHandler for BlockEditorState {
2662 fn text_for_range(
2663 &mut self,
2664 range16: Range<usize>,
2665 adjusted: &mut Option<Range<usize>>,
2666 _: &mut Window,
2667 _: &mut Context<Self>,
2668 ) -> Option<String> {
2669 let text = self.ime_text();
2670 let range = utf16_to_byte(text, range16.start)..utf16_to_byte(text, range16.end);
2671 adjusted.replace(byte_to_utf16(text, range.start)..byte_to_utf16(text, range.end));
2672 text.get(range).map(str::to_string)
2673 }
2674
2675 fn selected_text_range(
2676 &mut self,
2677 _: bool,
2678 _: &mut Window,
2679 _: &mut Context<Self>,
2680 ) -> Option<UTF16Selection> {
2681 let (anchor, head) = self.head_anchor()?;
2682 let text = self.ime_text();
2683 let in_leaf = |p: BlockPosition| {
2685 if p.block == head.block {
2686 p.offset
2687 } else if self.model.doc.cmp_positions(p, head).ok() == Some(std::cmp::Ordering::Less) {
2688 0
2689 } else {
2690 text.len()
2691 }
2692 };
2693 let (a, h) = (in_leaf(anchor), head.offset);
2694 Some(UTF16Selection {
2695 range: byte_to_utf16(text, a.min(h))..byte_to_utf16(text, a.max(h)),
2696 reversed: h < a,
2697 })
2698 }
2699
2700 fn marked_text_range(&self, _: &mut Window, _: &mut Context<Self>) -> Option<Range<usize>> {
2701 let m = self.marked.as_ref()?;
2702 let text = self.model.doc.leaf_text(m.block)?;
2703 Some(byte_to_utf16(text, m.range.start)..byte_to_utf16(text, m.range.end))
2704 }
2705
2706 fn unmark_text(&mut self, _: &mut Window, _: &mut Context<Self>) {
2707 self.end_composition();
2708 }
2709
2710 fn paste(&mut self, item: ClipboardItem, _: &mut Window, cx: &mut Context<Self>) {
2711 BlockEditorState::paste(self, item, false, cx);
2712 }
2713
2714 fn replace_text_in_range(
2715 &mut self,
2716 range16: Option<Range<usize>>,
2717 text: &str,
2718 _: &mut Window,
2719 cx: &mut Context<Self>,
2720 ) {
2721 if self.readonly {
2722 return;
2723 }
2724 let composing = self.marked.is_some();
2725 self.select_marked_or(range16);
2726 let origin = if composing {
2727 EditOrigin::Ime
2728 } else {
2729 EditOrigin::Typing
2730 };
2731 self.edit(origin, |m| m.insert_text(text, origin), cx);
2732 self.end_composition();
2733 if text == " " {
2734 self.apply_input_rule(cx);
2735 } else if text.ends_with(['`', '*', '~']) && self.rule_on(RuleKind::InlineMarks) {
2736 self.apply_mark_rule(cx);
2737 } else if text.ends_with(')') && self.rule_on(RuleKind::Link) {
2738 self.apply_link_rule(cx);
2739 }
2740 }
2741
2742 fn replace_and_mark_text_in_range(
2743 &mut self,
2744 range16: Option<Range<usize>>,
2745 new_text: &str,
2746 new_selected16: Option<Range<usize>>,
2747 _: &mut Window,
2748 cx: &mut Context<Self>,
2749 ) {
2750 if self.readonly {
2751 return;
2752 }
2753 if new_text.is_empty() {
2754 if let Some(marked) = self.marked.clone() {
2756 self.select_marked_or(None);
2757 if !marked.range.is_empty() {
2758 self.edit(EditOrigin::Ime, |m| m.delete(false), cx);
2759 }
2760 }
2761 self.end_composition();
2762 return;
2763 }
2764 self.select_marked_or(range16);
2765 let start = match self.model.doc.resolve_selection(self.model.selection()) {
2766 Ok(range) => range.start,
2767 Err(_) => return,
2768 };
2769 if !self.composition_open {
2770 self.model.history.begin_transaction();
2774 self.composition_open = true;
2775 }
2776 let revision = self.model.revision();
2777 self.edit(
2778 EditOrigin::Ime,
2779 |m| m.insert_text(new_text, EditOrigin::Ime),
2780 cx,
2781 );
2782 let inserted = self.model.revision() != revision;
2783 let Some((_, head)) = self.head_anchor().filter(|_| inserted) else {
2784 self.end_composition();
2786 return;
2787 };
2788 let block = head.block;
2789 let begin = if block == start.block {
2790 start.offset
2791 } else {
2792 head.offset.saturating_sub(new_text.len())
2793 };
2794 let range = begin..begin + new_text.len();
2795 self.marked = Some(Marked {
2796 block,
2797 range: range.clone(),
2798 });
2799 if let Some(sel16) = new_selected16 {
2800 let sel = utf16_to_byte(new_text, sel16.start)..utf16_to_byte(new_text, sel16.end);
2801 let _ = self.model.set_selection(BlockSelection::Text {
2802 anchor: BlockPosition::new(block, range.start + sel.start),
2803 head: BlockPosition::new(block, range.start + sel.end),
2804 });
2805 }
2806 }
2807
2808 fn bounds_for_range(
2809 &mut self,
2810 range16: Range<usize>,
2811 _: Bounds<Pixels>,
2812 _: &mut Window,
2813 _: &mut Context<Self>,
2814 ) -> Option<Bounds<Pixels>> {
2815 let block = self.ime_block()?;
2816 let text = self.ime_text();
2817 let start = utf16_to_byte(text, range16.start);
2818 let end = utf16_to_byte(text, range16.end);
2819 let frame = self.frame.borrow();
2820 let width = self.style.caret_width();
2821 let a = frame.caret_bounds(BlockPosition::new(block, start), width)?;
2822 let b = frame.caret_bounds(BlockPosition::new(block, end), width)?;
2823 Some(Bounds::from_corners(
2824 a.origin,
2825 gpui::point(
2826 b.origin.x.max(a.origin.x + width),
2827 a.origin.y + a.size.height,
2828 ),
2829 ))
2830 }
2831
2832 fn character_index_for_point(
2833 &mut self,
2834 at: Point<Pixels>,
2835 _: &mut Window,
2836 _: &mut Context<Self>,
2837 ) -> Option<usize> {
2838 let pos = self.frame.borrow().hit_test(at)?;
2839 if Some(pos.block) != self.ime_block() {
2840 return None;
2841 }
2842 Some(byte_to_utf16(self.ime_text(), pos.offset))
2843 }
2844}
2845
2846fn link_extent(
2848 inline: &super::inline::InlineText,
2849 offset: usize,
2850 url: &SharedString,
2851) -> Option<Range<usize>> {
2852 let pieces: Vec<_> = inline
2853 .pieces()
2854 .map(|(range, marks, _)| (range, marks.link.as_ref().map(|l| l.url.clone())))
2855 .collect();
2856 let ix = pieces
2857 .iter()
2858 .position(|(r, _)| r.start <= offset && offset < r.end)?;
2859 let same = |i: usize| pieces[i].1.as_ref() == Some(url);
2860 let (mut lo, mut hi) = (ix, ix);
2861 while lo > 0 && same(lo - 1) {
2862 lo -= 1;
2863 }
2864 while hi + 1 < pieces.len() && same(hi + 1) {
2865 hi += 1;
2866 }
2867 Some(pieces[lo].0.start..pieces[hi].0.end)
2868}
2869
2870fn link_rule(text: &str, caret: usize) -> Option<(usize, String, String)> {
2872 let before = text.get(..caret)?.strip_suffix(')')?;
2873 let open_paren = before.rfind("](")?;
2874 let url = &before[open_paren + 2..];
2875 if url.is_empty() || url.contains(char::is_whitespace) {
2876 return None;
2877 }
2878 let head = &before[..open_paren];
2879 let open = head.rfind('[')?;
2880 let label = &head[open + 1..];
2881 if label.is_empty() || label.contains(['[', ']']) {
2882 return None;
2883 }
2884 Some((open, label.to_string(), url.to_string()))
2885}
2886
2887fn code_fence(text: &str) -> Option<String> {
2889 let lang = text.strip_prefix("```")?;
2890 lang.chars()
2891 .all(|c| c.is_alphanumeric() || matches!(c, '+' | '-' | '_' | '#' | '.'))
2892 .then(|| lang.to_string())
2893}
2894
2895fn mark_rule(
2899 inline: &super::inline::InlineText,
2900 caret: usize,
2901) -> Option<(Range<usize>, Range<usize>, MarkKind)> {
2902 let text = inline.text();
2903 let prefix = text.get(..caret)?;
2904 let (delim, kind) = if prefix.ends_with('`') {
2905 ("`", MarkKind::Code)
2906 } else if prefix.ends_with("~~") {
2907 ("~~", MarkKind::Strikethrough)
2908 } else if prefix.ends_with("**") {
2909 ("**", MarkKind::Bold)
2910 } else if prefix.ends_with('*') {
2911 ("*", MarkKind::Italic)
2912 } else {
2913 return None;
2914 };
2915 let close = caret - delim.len()..caret;
2916 let before = &prefix[..close.start];
2917 let start = before.rfind(delim)?;
2918 if delim == "*" && (before[..start].ends_with('*') || before.ends_with('*')) {
2920 return None;
2921 }
2922 let open = start..start + delim.len();
2923 let content = &text[open.end..close.start];
2924 if content.is_empty()
2925 || content.contains(super::inline::ATOM_CHAR)
2926 || (kind != MarkKind::Code
2927 && (content.starts_with(char::is_whitespace) || content.ends_with(char::is_whitespace)))
2928 {
2929 return None;
2930 }
2931 let in_code = inline
2932 .pieces()
2933 .any(|(r, m, _)| m.code && r.start < close.end && r.end > open.start);
2934 (!in_code).then_some((open, close, kind))
2935}