Skip to main content

editor/
editor.rs

1//! The editing surface.
2//!
3//! The document is the single source of truth. There is no `TextField` per
4//! block: a selection is a pair of `Cursor`s into one `Doc`, which is what
5//! makes Enter split, Backspace merge and Tab indent into list operations
6//! rather than negotiations between separate widgets each owning a string.
7//!
8//! Everything about *what* an edit does lives in `markdown` — `edit` and
9//! `select` — and is tested there without a window. This crate owns only what
10//! needs one: a focus handle, key bindings, the platform input handler, and
11//! turning a click into a position.
12
13use gpui::{
14    App, Context, CursorStyle, ElementInputHandler, EventEmitter, FocusHandle, Focusable,
15    KeyContext, MouseButton, Render, Styled as _, Task, Window, canvas, div, prelude::*,
16};
17use markdown::{
18    Annotation, Block, BlockKind, BlockLayouts, Cursor, Doc, Form, Mark, Part, Selection, Splice,
19    Text, edit, edit::shortcut,
20};
21use motion::Painter;
22use std::{ops::Range, time::Duration};
23use theme::Theme;
24
25use crate::{
26    comment::{Anchor, CommentId, Delta},
27    history::{EditKind, History},
28    layout::Layout,
29    link::{self, Choice},
30    slash::Slash,
31    text_size::{self, TextSize},
32};
33
34pub(crate) mod image;
35mod input;
36pub mod keys;
37pub(crate) mod menu;
38
39pub use keys::init;
40use keys::{
41    Backspace, Copy, Cut, DecreaseTextSize, Delete, DeleteToHome, DeleteWordLeft, DeleteWordRight,
42    Dismiss, Down, DuplicateBlock, End, Home, IncreaseTextSize, Indent, KillLine, Left,
43    MoveBlockDown, MoveBlockUp, Outdent, Paste, Redo, RemoveBlock, ResetTextSize, Right, SelectAll,
44    SelectDown, SelectEnd, SelectHome, SelectLeft, SelectRight, SelectUp, SelectWordLeft,
45    SelectWordRight, SplitBlock, ToggleBold, ToggleCode, ToggleItalic, ToggleStrike, Undo, Up,
46    WordLeft, WordRight,
47};
48
49pub const CONTEXT: &str = "BezelEditor";
50
51/// [`CONTEXT`], which every binding in [`keys`] is scoped to, plus the mark
52/// that keeps `tab` for [`Editor::indent`].
53fn key_context() -> KeyContext {
54    let mut context = KeyContext::default();
55    context.add(CONTEXT);
56    context.add(ui::focus::CLAIMS_TAB);
57    context
58}
59
60/// What the editor tells its host about.
61///
62/// An app holding comment threads has to hear that the document moved, or its
63/// side of the pairing goes stale against anchors that did not. Split the way
64/// [`ui::input::FieldEvent`] is, so a listener takes only the half it needs.
65#[derive(Clone, Copy, Debug, PartialEq)]
66pub enum EditorEvent {
67    /// The document is different, and the anchors have been mapped through it.
68    Changed,
69    /// A click landed on a comment's range.
70    CommentActivated(CommentId),
71    /// The editor switched between the document and its source, which a host
72    /// lighting its own toggle has no other way to hear about — the switch can
73    /// come from an undo as well as from the button.
74    ModeChanged(Mode),
75}
76
77/// Which form the document is being edited in.
78///
79/// The trigger is the app's: a button, a menu row, a chord of its own. What is
80/// here is the switch it calls, because the caret, the undo history and the
81/// focus have to survive it, and only the editor owns all three.
82#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
83pub enum Mode {
84    /// The document as it reads.
85    #[default]
86    Blocks,
87    /// The markdown a save would write, in one editable text.
88    Source,
89}
90
91/// Which of the editor's own affordances are on.
92///
93/// All of them unless an app says otherwise: a document with nothing
94/// discoverable on it is the wrong default for a library. Turning one off is
95/// for an app that puts its own in the same place — a bar with its own block
96/// menu does not want the gutter handle's as well — rather than for trimming.
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub struct Chrome {
99    /// The gutter handle, the menu it opens, and dragging a block by it.
100    pub handle: bool,
101    /// The `/` menu at an empty block.
102    pub slash: bool,
103    /// A fence's language label, and the picker it opens.
104    pub language: bool,
105    /// The menu a pasted URL drops — leave it, or make a card, a chip or the
106    /// picture it points at.
107    pub paste: bool,
108}
109
110impl Default for Chrome {
111    fn default() -> Self {
112        Self {
113            handle: true,
114            slash: true,
115            language: true,
116            paste: true,
117        }
118    }
119}
120
121/// What a toolbar reads to light itself, in one call.
122///
123/// Every field is a question a bar asks on every frame, and each was a separate
124/// reach into the document before: which marks are lit, what the block is
125/// called, whether cmd-E would fence, and whether any of it applies at all.
126/// Taken together so a bar cannot answer half of them from one frame and half
127/// from the next.
128#[derive(Clone, Debug, PartialEq, Eq)]
129pub struct Formatting {
130    /// Which form the document is in. In [`Mode::Source`] the markup is already
131    /// spelled out, so [`Self::marks`] is empty and a bar has nothing to light.
132    pub mode: Mode,
133    /// The marks the selection carries throughout — and at a collapsed caret,
134    /// the ones the next character typed would carry, cmd-B before typing
135    /// included.
136    pub marks: Vec<Mark>,
137    /// What the caret's block is called in [`turns`], or `None` for a block the
138    /// menu does not offer.
139    pub block: Option<gpui::SharedString>,
140    /// Whether [`Mark::Code`] here makes a fence out of the selection rather
141    /// than an inline span — the one chord whose meaning changes with what is
142    /// selected, and the one a bar cannot work out for itself.
143    pub fenceable: bool,
144}
145
146/// Every block a block can be turned into, and what each is called — the
147/// vocabulary the slash menu and the block menu both offer, for an app building
148/// a menu of its own. Pair with [`Editor::set_block`], which is what both of
149/// bezel's own menus call.
150pub fn turns() -> Vec<(gpui::SharedString, BlockKind)> {
151    crate::slash::items()
152}
153
154/// Shown on the focused block while it is empty — the only discoverable place
155/// to say that `/` does anything.
156const PLACEHOLDER: &str = "Type / for commands";
157
158/// What tab inserts in the source, where there are no blocks to indent. Two
159/// spaces, which is what markdown's own nesting is written in.
160const INDENT: &str = "  ";
161
162/// Half the caret's blink period — `ui::TextField`'s, which is the 500ms on,
163/// 500ms off macOS itself uses.
164const BLINK: Duration = Duration::from_millis(500);
165
166/// The handle's box. Wide enough to be hit without crowding the margin; how
167/// far left of the text it sits is [`Layout::text_inset`](crate::Layout).
168const HANDLE_SIZE: f32 = 18.0;
169
170/// How far a drag on an image's edge handle can shrink it — matches
171/// `markdown::render`'s `TABLE_MIN_COLUMN_WIDTH`, the same floor for the
172/// other block content a drag can resize.
173const MIN_IMAGE_WIDTH: f32 = 96.0;
174
175/// Whether a selection is prose covering more than one line — two blocks, or
176/// one line break inside a single block.
177///
178/// What a chord means about a selection is the editor's to decide; a [`Doc`]
179/// has no opinion about it. A selection reaching into a table or a fence is not
180/// prose and keeps the inline behaviour, because half a table has no lines to
181/// make a fence out of.
182fn fenceable(doc: &Doc, selection: Selection) -> bool {
183    let spans = doc.spans(selection);
184    let covered = |at: &Cursor, range: &Range<usize>| {
185        doc.blocks[at.block]
186            .text_at(at.part)
187            .and_then(|text| text.text.get(range.clone()))
188    };
189    spans.iter().all(|(at, _)| at.part == Part::Body)
190        && (spans.len() > 1
191            || spans
192                .iter()
193                .any(|(at, range)| covered(at, range).is_some_and(|text| text.contains('\n'))))
194}
195
196/// Give an empty document a block to hold a caret, and say whether one was
197/// needed.
198///
199/// A [`Doc`] with no blocks is a legitimate document — it is what `parse("")`
200/// returns — but it is not something that can be edited: nothing paints, so
201/// there is no caret and no placeholder, and neither a click nor a hit test
202/// has a target to find. An empty file would sit inert until something typed
203/// a block into existence, which is the one thing you cannot do with no caret.
204fn ensure_block(doc: &mut Doc) -> bool {
205    if !doc.blocks.is_empty() {
206        return false;
207    }
208    doc.blocks
209        .push(Block::new(BlockKind::Paragraph(Text::default())));
210    true
211}
212
213/// The document a source view is edited as: one fence holding the markdown.
214///
215/// A fence rather than a paragraph because a fence is the block whose caret
216/// already behaves like a plain text editor's — Enter is a newline, nothing
217/// typed into it is markup, and its lines are laid out one per source line.
218fn source_doc(source: &str) -> Doc {
219    Doc {
220        blocks: vec![Block::new(BlockKind::Code {
221            language: Some(markdown::source::LANGUAGES[0].to_string()),
222            code: Text::plain(source),
223        })],
224    }
225}
226
227/// One of the two floating menus a block drops — the block it belongs to and
228/// where it hangs. A `Popup` rather than an `Option` for the exit phase, and
229/// for the press note: the card's `on_mouse_down_out` fires on the *press*, so
230/// without one a trigger's click on the *release* reopens what it just shut.
231pub(crate) type MenuPopup = ui::popover::Popup<(usize, gpui::Point<gpui::Pixels>)>;
232
233pub struct Editor {
234    doc: Doc,
235    /// The dialect this document is read and written in — the app's own marks,
236    /// taken once at construction. One editor, one spelling: a document that
237    /// changed dialect between a read and a write would rewrite itself.
238    marks: markdown::Marks,
239    /// Which of the editor's own affordances paint. The app's, so one document
240    /// can carry the lot and another none of it.
241    chrome: Chrome,
242    /// Which form the document is in. In [`Mode::Source`] `doc` is one fenced
243    /// block holding the markdown, so every operation below that is about
244    /// *blocks* asks [`Editor::blocks`] first.
245    mode: Mode,
246    /// Collapsed for an ordinary caret, so there is one position here rather
247    /// than a caret and a range that can disagree.
248    selection: Selection,
249    focus_handle: FocusHandle,
250    /// The IME composition range within the caret's text, underlined while it
251    /// is being composed.
252    marked: Option<Range<usize>>,
253    /// Where each text landed last frame, so a click can be turned into a
254    /// caret. Only paint knows this, so the renderer fills it.
255    layouts: BlockLayouts,
256    history: History,
257    /// Which half of the blink the caret is in. Flipped by [`Self::start_blink`].
258    caret_on: bool,
259    /// The blink, alive only while the document holds focus.
260    blink: Option<Task<()>>,
261    /// Comment ranges, mapped through every edit and snapshotted with the
262    /// document. Here rather than in the app because an undo restores a whole
263    /// document and leaves no delta an app could map its own copy through.
264    anchors: Vec<Anchor>,
265    /// Marks the next typed character will carry — cmd-B at a collapsed caret,
266    /// which otherwise has no range to apply to and so would do nothing.
267    /// Cleared by any motion, because they belong to a spot and not to a mood.
268    stored: Vec<Mark>,
269    /// The open slash menu, if `/` started one.
270    slash: Option<Slash>,
271    /// The open paste menu, if a URL landed in a block of its own.
272    pasted: Option<link::Paste>,
273    /// The open prompt, if an image is waiting to be told where to look.
274    url_prompt: Option<image::Prompt>,
275    /// The block a file being dragged over the document would land after.
276    dropping: Option<usize>,
277    /// The block the pointer is over, which is the only one showing a handle.
278    hovered: Option<usize>,
279    /// A block being dragged by its handle, and where it would land.
280    lifted: Option<(usize, usize)>,
281    /// An image being dragged wider or narrower by its edge handle, and the
282    /// width it holds now — `None` being the natural one, exactly as the
283    /// document spells it. The document only learns the final width on
284    /// release, the same reason `lifted` waits for the drop; carrying the
285    /// document's own value here is what makes a press that never moved
286    /// read back as no change at all.
287    resizing: Option<(usize, Option<u32>)>,
288    /// The block menu the handle opened, and where to anchor it.
289    block_menu: MenuPopup,
290    /// The language menu a fence's header opened, and the block it belongs to.
291    language_menu: MenuPopup,
292    /// Set by a floating layer's press — the gutter handle, the URL prompt —
293    /// so the editor's own press does not undo what that press just did.
294    press_claimed: bool,
295    /// Where the editor's own box starts, so a position recorded in window
296    /// coordinates can be placed inside it, and how far it reaches — which is
297    /// how wide a resized image is allowed to be. Its own box rather than the
298    /// picture's: a picture already narrowed would otherwise be its own
299    /// ceiling, and no drag could ever widen it again.
300    origin: gpui::Point<gpui::Pixels>,
301    width: gpui::Pixels,
302    /// Whether the pointer is dragging out a selection.
303    dragging: bool,
304    /// Whether the pointer is over painted text, which is the only place the
305    /// editor claims an I-beam.
306    over_text: bool,
307    /// The host's scroll box, when it gave one, and whether the caret still
308    /// owes it a reveal.
309    scroll: Option<gpui::ScrollHandle>,
310    reveal: bool,
311    /// Where the gutter handle was placed this frame, so the frame after can
312    /// tell whether the block moved out from under it.
313    handle_at: Option<gpui::Point<gpui::Pixels>>,
314    /// The point vertical motion is trying to keep. Held across a run of
315    /// up/down so walking through a short line and out the other side returns
316    /// to the column you started in, and dropped by anything horizontal —
317    /// which is every other way the caret moves.
318    ///
319    /// The *row* is held as well as the column because an offset at a soft
320    /// wrap belongs to two rows and answers with the first, so a caret that
321    /// derived its own row would step down into the same one forever.
322    goal: Option<gpui::Point<gpui::Pixels>>,
323    /// The size the app set this document in, in points, or `None` to follow
324    /// the app's own text size. Absolute rather than a factor over the ladder,
325    /// so moving the interface size leaves a document set to 16pt at 16pt.
326    ///
327    /// What the chords move is the shared adjustment on top of this; the base
328    /// itself is the app's alone.
329    text_size: Option<f32>,
330}
331
332impl Editor {
333    pub fn new(source: &str, cx: &mut Context<Self>) -> Self {
334        let marks = markdown::Marks::of(cx);
335        let mut doc = markdown::parse_with(source, &marks);
336        ensure_block(&mut doc);
337        Self {
338            marks,
339            // Clamped, not defaulted: a document opening on a fence or a table
340            // has no body at block zero, and a caret claiming one resolves
341            // against nothing until something moves it.
342            selection: Selection::at(Cursor::default().clamp(&doc)),
343            doc,
344            chrome: Chrome::default(),
345            mode: Mode::default(),
346            focus_handle: cx.focus_handle(),
347            marked: None,
348            layouts: BlockLayouts::default(),
349            history: History::default(),
350            caret_on: true,
351            blink: None,
352            anchors: Vec::new(),
353            stored: Vec::new(),
354            slash: None,
355            pasted: None,
356            url_prompt: None,
357            dropping: None,
358            hovered: None,
359            lifted: None,
360            resizing: None,
361            block_menu: MenuPopup::default(),
362            language_menu: MenuPopup::default(),
363            press_claimed: false,
364            origin: gpui::Point::default(),
365            width: gpui::Pixels::ZERO,
366            dragging: false,
367            over_text: false,
368            scroll: None,
369            reveal: false,
370            goal: None,
371            handle_at: None,
372            text_size: None,
373        }
374    }
375
376    /// How many undo steps to keep. App-wide configuration would be a gpui
377    /// global alongside [`init`], not a `Theme` field — the theme is rebuilt on
378    /// every light/dark switch, which would quietly reset anything behavioural
379    /// parked in it.
380    pub fn with_undo_limit(mut self, limit: usize) -> Self {
381        self.history = History::with_limit(limit);
382        self
383    }
384
385    /// Read and write this document with marks of its own, rather than the ones
386    /// [`markdown::set_marks`] installed. For an app whose editors do not all
387    /// speak the same dialect.
388    pub fn with_marks(mut self, marks: markdown::Marks) -> Self {
389        let source = self.source();
390        self.marks = marks;
391        self.doc = markdown::parse_with(&source, &self.marks);
392        ensure_block(&mut self.doc);
393        self.selection = self.selection.clamp(&self.doc);
394        self
395    }
396
397    /// Which of the editor's own affordances to paint. See [`Chrome`].
398    pub fn with_chrome(mut self, chrome: Chrome) -> Self {
399        self.chrome = chrome;
400        self
401    }
402
403    /// What is painting now, for an app whose own bar mirrors it.
404    pub fn chrome(&self) -> Chrome {
405        self.chrome
406    }
407
408    /// Open in [`Mode::Source`] rather than on the document — an app whose
409    /// editor is a markdown file first. Nothing is recorded: this is where the
410    /// document starts, not a switch to step back over.
411    pub fn with_mode(mut self, mode: Mode) -> Self {
412        if mode != self.mode {
413            self.switch(mode);
414        }
415        self
416    }
417
418    /// Open the document at a size of its own, in points — the app's settings
419    /// field for prose. Unset, it is set at the app's own text size.
420    ///
421    /// The base, not the current size: `cmd-+` moves a shared adjustment over
422    /// this, and `cmd-0` clears that adjustment to come back here.
423    pub fn with_text_size(mut self, points: f32) -> Self {
424        self.text_size = Some(points);
425        self
426    }
427
428    /// Update the configured base size without changing the temporary zoom.
429    pub fn set_text_size(&mut self, points: f32, cx: &mut Context<Self>) {
430        if self.text_size != Some(points) {
431            self.text_size = Some(points);
432            cx.notify();
433        }
434    }
435
436    /// The base the app set, if any. Add
437    /// [`text_size_adjustment`](crate::text_size_adjustment) for what is on
438    /// screen.
439    pub fn text_size(&self) -> Option<f32> {
440        self.text_size
441    }
442
443    /// The box the document scrolls in, so typing off the bottom follows the
444    /// caret down.
445    ///
446    /// The host's rather than the editor's: a document goes in whatever pane
447    /// the app gives it, and the gutter handle, the drop indicator and the
448    /// menus are all placed absolutely against this editor's own origin — put
449    /// the scroll box here and every one of them would be offset twice.
450    pub fn with_scroll(mut self, handle: gpui::ScrollHandle) -> Self {
451        self.scroll = Some(handle);
452        self
453    }
454
455    /// Bring the caret back into view.
456    ///
457    /// Read *after* paint, because a block that has only just appeared — the
458    /// one Enter made — has no position recorded until it has painted once,
459    /// which is exactly the case worth scrolling for.
460    /// Ask for another frame when the block the handle sits on has moved.
461    ///
462    /// The handle is built from the records of the frame *before* this one —
463    /// the document fills them as it paints, which is after the editor has
464    /// finished building its tree — so a block that has just been indented, or
465    /// grown a line, leaves the handle behind. Reading the records back here,
466    /// once the document has painted, is what turns that into one late frame
467    /// instead of a handle stranded until the caret blink happens to draw
468    /// again.
469    fn settle_handle(&mut self, window: &Window, cx: &mut Context<Self>) {
470        let focused = self.focus_handle.is_focused(window);
471        let now = self
472            .handle_block(focused)
473            .and_then(|ix| self.handle_origin(ix, cx));
474        if now != self.handle_at {
475            // Not `notify`: this runs *during* the draw, and the dirty flag it
476            // sets is cleared when that draw finishes. Asking for the next
477            // frame is what survives it.
478            window.request_animation_frame();
479        }
480    }
481
482    fn reveal_caret(&mut self, cx: &mut Context<Self>) {
483        if !self.reveal {
484            return;
485        }
486        let Some(scroll) = self.scroll.clone() else {
487            self.reveal = false;
488            return;
489        };
490        // Left set when the caret has not painted: a block with no text at all
491        // never answers, and the next move is what gets it back.
492        let Some((at, line)) = self.layouts.position(self.selection.head) else {
493            return;
494        };
495        self.reveal = false;
496
497        let view = scroll.bounds();
498        let offset = scroll.offset();
499        let mut y = offset.y;
500        if at.y < view.top() {
501            y += view.top() - at.y;
502        } else if at.y + line > view.bottom() {
503            y -= at.y + line - view.bottom();
504        }
505        // `set_offset` clamps nothing, and past the ends the document would
506        // scroll away from the caret it was asked to show.
507        let y = y.clamp(-scroll.max_offset().y, gpui::px(0.0));
508        if y != offset.y {
509            scroll.set_offset(gpui::point(offset.x, y));
510            cx.notify();
511        }
512    }
513
514    pub fn doc(&self) -> &Doc {
515        &self.doc
516    }
517
518    pub fn selection(&self) -> Selection {
519        self.selection
520    }
521
522    /// Put the selection somewhere — what a thread in a sidebar does when it is
523    /// clicked, and what a caret restored with a document needs.
524    ///
525    /// Clamped, because the caller's range came from somewhere the document may
526    /// have moved on from.
527    pub fn select(&mut self, selection: Selection, cx: &mut Context<Self>) {
528        self.selection = selection.clamp(&self.doc);
529        self.history.interrupt();
530        self.reveal = true;
531        self.caret_moved();
532        cx.notify();
533    }
534
535    /// Where the selection sits on screen, in window coordinates, so a host can
536    /// float a toolbar at it.
537    ///
538    /// The head's row only — a selection spanning ten blocks wants its bubble
539    /// where the pointer left off, not centred over the whole span. `None` when
540    /// nothing is selected or the caret has not painted yet.
541    /// Where everything landed last frame — blocks, pictures, a fence's
542    /// language label, and the row rects of any range.
543    ///
544    /// Handed out whole rather than a method per question: an app placing
545    /// chrome of its own asks the geometry, and which question it needs is not
546    /// this crate's to guess. See [`markdown::BlockLayouts`].
547    pub fn layouts(&self) -> &BlockLayouts {
548        &self.layouts
549    }
550
551    pub fn selection_bounds(&self) -> Option<gpui::Bounds<gpui::Pixels>> {
552        // The head alone. A bar centred over the whole selection wants
553        // `layouts().rects(selection)`, which is every painted row of it.
554        if self.selection.is_collapsed() {
555            return None;
556        }
557        let (point, line_height) = self.layouts.position(self.selection.head)?;
558        Some(gpui::Bounds::new(
559            point,
560            gpui::size(gpui::px(0.0), line_height),
561        ))
562    }
563
564    /// The comment ranges, mapped up to date with the document.
565    ///
566    /// A range that reads [`Anchor::detached`] lost the words it pointed at.
567    /// It is kept rather than dropped, because whether that means "outdated" or
568    /// "resolved" is the app's question.
569    pub fn anchors(&self) -> &[Anchor] {
570        &self.anchors
571    }
572
573    /// Hand over the whole list — the app keeps the threads, this keeps their
574    /// ranges. One entry point rather than add/remove/update, since the app is
575    /// already holding the list that decides all three.
576    pub fn set_anchors(&mut self, anchors: Vec<Anchor>, cx: &mut Context<Self>) {
577        self.anchors = anchors;
578        cx.notify();
579    }
580
581    /// The comment under a point in window coordinates — the space
582    /// [`Self::anchor_bounds`] answers in and the press handler resolves in.
583    ///
584    /// The last match wins, so the newer of two overlapping ranges is the one a
585    /// click opens.
586    pub fn comment_at(&self, at: gpui::Point<gpui::Pixels>) -> Option<CommentId> {
587        let at = self.layouts.hit(at)?;
588        self.anchors
589            .iter()
590            .rfind(|anchor| {
591                let (start, end) = anchor.range.ordered();
592                !anchor.detached() && start <= at && at <= end
593            })
594            .map(|anchor| anchor.id)
595    }
596
597    /// Where to float a thread, mirroring [`Self::selection_bounds`].
598    pub fn anchor_bounds(&self, id: CommentId) -> Option<gpui::Bounds<gpui::Pixels>> {
599        let anchor = self.anchors.iter().find(|anchor| anchor.id == id)?;
600        let (point, line_height) = self.layouts.position(anchor.range.ordered().0)?;
601        Some(gpui::Bounds::new(
602            point,
603            gpui::size(gpui::px(0.0), line_height),
604        ))
605    }
606
607    /// The ranges the renderer washes, clamped because a block can change kind
608    /// under an anchor and take its part with it.
609    fn annotations(&self) -> Vec<(Selection, Annotation)> {
610        self.anchors
611            .iter()
612            .filter(|anchor| !anchor.detached())
613            .map(|anchor| (anchor.range.clamp(&self.doc), anchor.state))
614            .collect()
615    }
616
617    /// The caret moved: drop the blink so the next render starts a fresh one.
618    /// Without the reset it would blink through your own typing, which reads as
619    /// a dropped keystroke.
620    fn caret_moved(&mut self) {
621        self.blink = None;
622    }
623
624    /// Blink the caret for as long as the document holds focus.
625    fn start_blink(&mut self, cx: &mut Context<Self>) {
626        self.caret_on = true;
627        self.blink = Some(cx.spawn(async move |editor, cx| {
628            loop {
629                cx.background_executor().timer(BLINK).await;
630                let flipped = editor.update(cx, |editor, cx| {
631                    editor.caret_on = !editor.caret_on;
632                    cx.notify();
633                });
634                if flipped.is_err() {
635                    break;
636                }
637            }
638        }));
639    }
640
641    /// Where typing would land — the moving end of the selection.
642    fn cursor(&self) -> Cursor {
643        self.selection.head
644    }
645
646    /// Put the caret somewhere, collapsed.
647    fn place(&mut self, cursor: Cursor) {
648        self.selection = Selection::at(cursor.clamp(&self.doc));
649    }
650
651    /// Move the head, extending the selection or collapsing it — the one path
652    /// every motion key takes, so shift is a flag rather than a second handler.
653    fn moved(
654        &mut self,
655        extend: bool,
656        to: impl FnOnce(Cursor, &Doc) -> Cursor,
657        cx: &mut Context<Self>,
658    ) {
659        let head = to(self.selection.head, &self.doc).clamp(&self.doc);
660        self.head_to(head, extend);
661        // Every horizontal motion drops the goal; the two vertical ones put it
662        // back after calling this.
663        self.goal = None;
664        cx.notify();
665    }
666
667    /// Delete from the caret to wherever `to` lands — every kill chord, sharing
668    /// the cursor functions the motion chords use so the two cannot disagree.
669    ///
670    /// Nothing left to take within the block — the target crossed out of it, or
671    /// landed on the caret — is the block edge, and `forward` is which edge:
672    /// [`Self::delete_forward`] joins the next block, [`Self::delete_back`]
673    /// outdents or strips block syntax before it merges anything. The direction
674    /// has to be the chord's own, because a target that lands on the caret is
675    /// the same cursor whichever way it was reaching.
676    fn delete_to(
677        &mut self,
678        forward: bool,
679        to: impl FnOnce(Cursor, &Doc) -> Cursor,
680        cx: &mut Context<Self>,
681    ) {
682        if !self.selection.is_collapsed() {
683            return self.delete_back(cx);
684        }
685        let at = self.cursor();
686        let target = to(at, &self.doc).clamp(&self.doc);
687        if target.block != at.block || target.part != at.part || target.offset == at.offset {
688            return if forward {
689                self.delete_forward(cx)
690            } else {
691                self.delete_back(cx)
692            };
693        }
694        let painter = Painter::of(cx);
695        self.edit(EditKind::Delete, cx, |this| {
696            let splice = this
697                .doc
698                .replace(Selection::new(target, at), Text::default());
699            this.selection = Selection::at(splice.caret.clamp(&this.doc));
700            this.track_slash("", painter);
701            vec![Delta::Spliced(splice)]
702        });
703    }
704
705    fn head_to(&mut self, head: Cursor, extend: bool) {
706        self.selection = if extend {
707            self.selection.extend_to(head)
708        } else {
709            Selection::at(head)
710        };
711        // A motion ends the undo group: typing a word, moving away and typing
712        // again must not undo as one step across two places. It also spends any
713        // stored mark and any open paste menu, both of which belonged to the
714        // spot the caret just left.
715        self.history.interrupt();
716        self.stored.clear();
717        self.pasted = None;
718        self.reveal = true;
719        self.caret_moved();
720    }
721
722    /// Every mutation goes through here, so none of them can forget to record
723    /// a step and none of them has to know how steps coalesce.
724    fn edit(
725        &mut self,
726        kind: EditKind,
727        cx: &mut Context<Self>,
728        edit: impl FnOnce(&mut Self) -> Vec<Delta>,
729    ) {
730        // Any edit answers the paste menu by ignoring it — whatever it offered
731        // was about a block that no longer holds only the link.
732        self.pasted = None;
733        self.history
734            .record(kind, self.mode, &self.doc, self.selection, &self.anchors);
735        // A list rather than one: Enter clears a selection *and* splits, and an
736        // anchor mapped through only half of that lands in the wrong place.
737        // Source mode maps nothing: its deltas are about one fence, and an
738        // anchor dragged through those would point at the markup. They are
739        // clamped back onto the document on the way out instead.
740        for delta in edit(self) {
741            if !self.blocks() {
742                continue;
743            }
744            for anchor in &mut self.anchors {
745                anchor.map(&delta);
746            }
747        }
748        // Deleting the last block is the other way to an empty document, and
749        // the caret belongs at the start of whatever replaces it.
750        if ensure_block(&mut self.doc) {
751            self.selection = Selection::at(Cursor::default());
752        }
753        if !self.blocks() {
754            self.ensure_source();
755        }
756        self.history.landed(kind, self.selection);
757        // Typing moves the caret as surely as an arrow key does, and a split
758        // moves it onto a block that does not exist until this frame paints.
759        self.reveal = true;
760        self.caret_moved();
761        cx.emit(EditorEvent::Changed);
762        cx.notify();
763    }
764
765    /// Up and down, by one painted row.
766    ///
767    /// Geometry rather than arithmetic on line numbers, so a wrapped paragraph,
768    /// a code block's lines and a table's rows are all the same case and none
769    /// needs counting — but geometry walked in document order rather than
770    /// hit-tested, which is [`markdown::BlockLayouts::step_row`]'s whole point.
771    /// Falls back to the block-wise motion off either end of the document, and
772    /// on the first frame, when nothing has painted to walk.
773    fn vertical(&mut self, down: bool, extend: bool, cx: &mut Context<Self>) {
774        // Up and down walk a menu while it is open, not the document.
775        let delta = if down { 1 } else { -1 };
776        if let Some(pasted) = &mut self.pasted {
777            pasted.step(delta);
778            return cx.notify();
779        }
780        if let Some(slash) = &mut self.slash {
781            slash.step(delta);
782            return cx.notify();
783        }
784        let head = self.selection.head;
785        let Some((at, _)) = self.layouts.position(head) else {
786            return self.moved(
787                extend,
788                |at, doc| if down { at.down(doc) } else { at.up(doc) },
789                cx,
790            );
791        };
792        let from = self.goal.unwrap_or(at);
793        match self.layouts.step_row(head, from, down) {
794            Some((to, row)) => {
795                self.head_to(to.clamp(&self.doc), extend);
796                self.goal = Some(gpui::point(from.x, row));
797            }
798            // Off the top is the start of the document and off the bottom is
799            // its end, which is what every native field does.
800            None => {
801                // Except where the end is a block a caret cannot carry on from,
802                // and going down means the paragraph after it — the one a click
803                // below the document asks for by the same rule.
804                if down
805                    && !extend
806                    && self.cursor().block + 1 == self.doc.blocks.len()
807                    && self.append_tail(cx)
808                {
809                    return;
810                }
811                let to = if down {
812                    head.down(&self.doc)
813                } else {
814                    head.up(&self.doc)
815                };
816                self.head_to(to.clamp(&self.doc), extend);
817                self.goal = Some(from);
818            }
819        }
820        cx.notify();
821    }
822
823    /// The document as markdown — normalized, because that is the form that
824    /// survives being read back. In [`Mode::Source`] it is the text being
825    /// edited, exactly as it stands.
826    pub fn source(&self) -> String {
827        match self.mode {
828            Mode::Blocks => {
829                let mut doc = self.doc.clone();
830                doc.normalize_with(&self.marks);
831                markdown::serialize_with(&doc, &self.marks)
832            }
833            Mode::Source => self.source_text().to_string(),
834        }
835    }
836
837    /// Which form the document is being edited in.
838    pub fn mode(&self) -> Mode {
839        self.mode
840    }
841
842    /// What a toolbar needs to light itself. See [`Formatting`].
843    pub fn formatting(&self) -> Formatting {
844        let at = self.cursor();
845        let marks = if self.blocks() {
846            let mut marks = self.doc.marks(self.selection);
847            // A stored mark is one cmd-B has already taken and nothing has
848            // spent yet, so the button that took it stays lit.
849            for mark in &self.stored {
850                if !marks.contains(mark) {
851                    marks.push(mark.clone());
852                }
853            }
854            marks
855        } else {
856            Vec::new()
857        };
858        Formatting {
859            mode: self.mode,
860            marks,
861            block: self
862                .doc
863                .blocks
864                .get(at.block)
865                .and_then(|block| crate::slash::label(&block.kind)),
866            fenceable: self.blocks() && fenceable(&self.doc, self.selection),
867        }
868    }
869
870    /// Switch between the document and its markdown, carrying the caret across.
871    ///
872    /// One undo step, and one the history knows the mode of: stepping back
873    /// over a switch puts the document back in the form it was edited in.
874    ///
875    /// A comment anchor does not follow an edit made to the source — there are
876    /// no blocks there to anchor to — and is clamped back onto the document on
877    /// the way out.
878    pub fn set_mode(&mut self, mode: Mode, cx: &mut Context<Self>) {
879        if mode == self.mode {
880            return;
881        }
882        self.history.record(
883            EditKind::Structure,
884            self.mode,
885            &self.doc,
886            self.selection,
887            &self.anchors,
888        );
889        self.dismiss_menus();
890        self.switch(mode);
891        self.history.landed(EditKind::Structure, self.selection);
892        self.reveal = true;
893        self.caret_moved();
894        cx.emit(EditorEvent::ModeChanged(mode));
895        cx.emit(EditorEvent::Changed);
896        cx.notify();
897    }
898
899    /// Turn the document into the other form, caret and all. The half of
900    /// [`Self::set_mode`] that [`Self::with_mode`] needs without a window.
901    fn switch(&mut self, mode: Mode) {
902        match mode {
903            Mode::Source => {
904                let (source, offset) =
905                    markdown::serialize_at(&self.doc, self.cursor(), &self.marks);
906                self.doc = source_doc(&source);
907                self.selection = Selection::at(Cursor::new(0, Part::Code, offset));
908            }
909            Mode::Blocks => {
910                let (doc, at) =
911                    markdown::parse_at(self.source_text(), self.cursor().offset, &self.marks);
912                self.doc = doc;
913                ensure_block(&mut self.doc);
914                self.selection = Selection::at(at.clamp(&self.doc));
915                for anchor in &mut self.anchors {
916                    anchor.range = anchor.range.clamp(&self.doc);
917                }
918            }
919        }
920        self.mode = mode;
921    }
922
923    /// [`Mode::Source`] if the document is in blocks, and back again — what a
924    /// toggle in the app's own chrome calls.
925    pub fn toggle_source(&mut self, cx: &mut Context<Self>) {
926        self.set_mode(
927            match self.mode {
928                Mode::Blocks => Mode::Source,
929                Mode::Source => Mode::Blocks,
930            },
931            cx,
932        );
933    }
934
935    /// Whether the document is the thing being edited, rather than its source.
936    ///
937    /// Every operation that acts on *blocks* asks this: in source mode there is
938    /// one block, it is a fence holding a string, and turning it into a heading
939    /// or dragging it somewhere would edit the markup rather than the document
940    /// the markup spells.
941    fn blocks(&self) -> bool {
942        self.mode == Mode::Blocks
943    }
944
945    /// The text of the fence the source is held in — what is being edited in
946    /// [`Mode::Source`]. Only meaningful there; in [`Mode::Blocks`] the
947    /// document is the truth and this is whatever block zero happens to be.
948    fn source_text(&self) -> &str {
949        self.doc
950            .blocks
951            .first()
952            .and_then(|block| block.text_at(Part::Code))
953            .map_or("", |text| text.text.as_str())
954    }
955
956    /// Put the source back in the one fence it is edited as.
957    ///
958    /// Backspace at the start of an empty fence is a merge, and a merge with
959    /// nothing above it leaves a paragraph — a block the source view does not
960    /// paint and the caret would be stranded in. The text survives either way,
961    /// so this is a change of container and never of content.
962    fn ensure_source(&mut self) {
963        if self.doc.blocks.len() == 1 && matches!(self.doc.blocks[0].kind, BlockKind::Code { .. }) {
964            return;
965        }
966        let source = self
967            .doc
968            .blocks
969            .iter()
970            .filter_map(|block| block.text_at(*block.parts().first()?))
971            .map(|text| text.text.clone())
972            .collect::<Vec<_>>()
973            .join("\n");
974        let offset = self.selection.head.offset.min(source.len());
975        self.doc = source_doc(&source);
976        self.selection = Selection::at(Cursor::new(0, Part::Code, offset));
977    }
978
979    /// Shut everything floating. A switch of mode is a new document as far as
980    /// a menu anchored to a block is concerned.
981    fn dismiss_menus(&mut self) {
982        self.slash = None;
983        self.pasted = None;
984        self.url_prompt = None;
985        self.hovered = None;
986        self.lifted = None;
987        self.dropping = None;
988    }
989
990    /// Replace whatever is selected with `text`, applying a markdown prefix if
991    /// one completes.
992    ///
993    /// Typing, backspace, delete and IME all land here, so none of them has to
994    /// ask whether a selection was empty.
995    fn insert(&mut self, text: &str, cx: &mut Context<Self>) {
996        let painter = Painter::of(cx);
997        self.edit(EditKind::Insert, cx, |this| {
998            let mut typed = Text::plain(text);
999            // A stored mark applies to what is typed next and to nothing else,
1000            // so it is spent here.
1001            for mark in this.stored.drain(..) {
1002                typed.marks.push(markdown::MarkSpan {
1003                    range: 0..typed.text.len(),
1004                    mark,
1005                });
1006            }
1007            let splice = this.doc.replace(this.selection, typed);
1008            this.selection = Selection::at(splice.caret.clamp(&this.doc));
1009            this.apply_shortcut();
1010            this.apply_inline_rule();
1011            this.track_slash(text, painter);
1012            vec![Delta::Spliced(splice)]
1013        });
1014    }
1015
1016    /// Open the menu on a typed `/`, and keep its query in step afterwards.
1017    ///
1018    /// The query is the text between the `/` and the caret, so there is no
1019    /// second field and no focus to hand over — typing filters because typing
1020    /// is what it already was.
1021    fn track_slash(&mut self, typed: &str, painter: Painter) {
1022        if !self.chrome.slash {
1023            return;
1024        }
1025        let at = self.cursor();
1026        let text = self
1027            .doc
1028            .blocks
1029            .get(at.block)
1030            .and_then(|block| block.text_at(at.part))
1031            .map(|text| text.text.clone())
1032            .unwrap_or_default();
1033
1034        if self.slash.is_none() {
1035            // Only a `/` that starts a word — a URL's slashes are not commands.
1036            let opened = at.offset.checked_sub(1).filter(|_| typed == "/");
1037            let starts_word = opened.is_none_or(|slash| {
1038                text[..slash]
1039                    .chars()
1040                    .next_back()
1041                    .is_none_or(char::is_whitespace)
1042            });
1043            // Only in a body: a fence holds its slash literally, and a caption
1044            // belongs to a block that is already what it is.
1045            if let Some(slash) = opened.filter(|_| starts_word && at.part == Part::Body) {
1046                self.slash = Some(Slash::open(
1047                    Cursor {
1048                        offset: slash,
1049                        ..at
1050                    },
1051                    painter,
1052                ));
1053            }
1054            return;
1055        }
1056
1057        // Anything that leaves the run — a space, a click away, backspacing
1058        // onto the slash — closes it.
1059        let Some(query) = self.slash.as_ref().and_then(|slash| slash.query(at, &text)) else {
1060            self.slash = None;
1061            return;
1062        };
1063        if let Some(slash) = &mut self.slash {
1064            slash.refilter(&query);
1065        }
1066    }
1067
1068    /// Take the highlighted block, replacing the `/query` that summoned it.
1069    /// Take `kind`, or the highlighted row when the caller names none — Enter
1070    /// and a click are the same operation with a different source.
1071    pub(super) fn confirm_slash(
1072        &mut self,
1073        kind: Option<BlockKind>,
1074        cx: &mut Context<Self>,
1075    ) -> bool {
1076        let Some(slash) = &self.slash else {
1077            return false;
1078        };
1079        let (at, kind) = (slash.at, kind.or_else(|| slash.choice()));
1080        self.slash = None;
1081        let Some(kind) = kind else {
1082            return false;
1083        };
1084        let caret = self.cursor();
1085        self.edit(EditKind::Structure, cx, |this| {
1086            this.doc
1087                .edit_at(at, |text| text.remove(at.offset..caret.offset));
1088            this.doc.set_kind(at.block, kind);
1089            this.selection =
1090                Selection::at(Cursor::new(at.block, Part::Body, at.offset).clamp(&this.doc));
1091            vec![Delta::Spliced(Splice {
1092                removed: Selection::new(at, caret),
1093                caret: at,
1094                blocks: 0,
1095            })]
1096        });
1097        true
1098    }
1099
1100    /// Collapse `**bold**` into a mark when its closing delimiter is typed.
1101    ///
1102    /// Runs after the insertion, on the text as it now stands, so a paste and a
1103    /// keystroke reach it the same way.
1104    fn apply_inline_rule(&mut self) {
1105        let at = self.cursor();
1106        // Code is literal to its closing fence, and a caption holds no mark a
1107        // `![...]` could spell.
1108        if matches!(at.part, Part::Code | Part::Caption) {
1109            return;
1110        }
1111        let Some(text) = self
1112            .doc
1113            .blocks
1114            .get(at.block)
1115            .and_then(|block| block.text_at(at.part))
1116        else {
1117            return;
1118        };
1119        let Some((open, inner, mark)) = edit::inline_rule(&text.text, at.offset) else {
1120            return;
1121        };
1122        let width = open.len();
1123        self.doc.edit_at(at, |text| {
1124            // The closing delimiter first — taking the opening one would move
1125            // every offset after it.
1126            text.remove(inner.end..at.offset);
1127            text.remove(open);
1128            text.toggle(inner.start - width..inner.end - width, mark);
1129        });
1130        self.selection =
1131            Selection::at(Cursor::new(at.block, at.part, at.offset - 2 * width).clamp(&self.doc));
1132    }
1133
1134    /// Add `mark` over the selection, or take it away if the whole selection
1135    /// already carries it. Public because a toolbar reaches the same operation
1136    /// the key does.
1137    pub fn toggle_mark(&mut self, mark: Mark, cx: &mut Context<Self>) {
1138        // Nothing in the source is a mark: the markup is already spelled out,
1139        // and cmd-B over `**bold**` would fence what it reads.
1140        if !self.blocks() {
1141            return;
1142        }
1143        // A caret inside a fence is enough to leave one, so this is the mark
1144        // that does not wait for a range: nothing typed into code is markup,
1145        // which leaves a stored mark there nothing to mean.
1146        let leaving_code = matches!(mark, Mark::Code) && self.cursor().part == Part::Code;
1147        // With nothing selected there is no range to mark, so the mark waits
1148        // for the next character — ProseMirror's stored marks, and the only way
1149        // cmd-B before typing can mean anything.
1150        if self.selection.is_collapsed() && !leaving_code {
1151            match self.stored.iter().position(|stored| *stored == mark) {
1152                Some(ix) => drop(self.stored.remove(ix)),
1153                None => self.stored.push(mark),
1154            }
1155            return cx.notify();
1156        }
1157        let selection = self.selection;
1158        self.edit(EditKind::Structure, cx, |this| {
1159            // Code over more than one line is a fence, which is the only shape
1160            // markdown has for it, and the same key is the way back out.
1161            let before = this.doc.blocks.len();
1162            // Fencing and unfencing rebuild the blocks the selection covered,
1163            // so what sat inside it has no position left to keep.
1164            let refenced = |doc: &Doc, head: Cursor| {
1165                vec![Delta::Spliced(Splice {
1166                    removed: selection,
1167                    caret: head,
1168                    blocks: doc.blocks.len() as isize - before as isize,
1169                })]
1170            };
1171            if matches!(mark, Mark::Code) {
1172                if let Some(head) = this.doc.unfence(selection) {
1173                    this.selection = Selection::at(head.clamp(&this.doc));
1174                    return refenced(&this.doc, head);
1175                }
1176                if fenceable(&this.doc, selection) {
1177                    let head = this.doc.fence(selection);
1178                    this.selection = Selection::at(head.clamp(&this.doc));
1179                    return refenced(&this.doc, head);
1180                }
1181            }
1182            // A mark is paint over text that does not move.
1183            this.doc.toggle_mark(selection, mark);
1184            vec![]
1185        });
1186    }
1187
1188    /// Turn a typed prefix into the block it spells — `## ` into a heading.
1189    ///
1190    /// Runs after every insertion rather than only on space, because the
1191    /// vocabulary includes prefixes that end in one (`- [ ] `) and prefixes
1192    /// that do not (```` ``` ````).
1193    fn apply_shortcut(&mut self) {
1194        let at = self.cursor();
1195        // A prefix is block syntax; inside a code fence or a table cell it is
1196        // the literal text the author typed.
1197        if at.part != Part::Body {
1198            return;
1199        }
1200        let Some(block) = self.doc.blocks.get(at.block) else {
1201            return;
1202        };
1203        let Some(text) = block.text_at(Part::Body) else {
1204            return;
1205        };
1206        // Only from the very start of a block, and only up to the caret: a
1207        // `- ` typed in the middle of a sentence is a hyphen.
1208        let Some((hit, len)) = shortcut(&text.text) else {
1209            return;
1210        };
1211        if at.offset < len {
1212            return;
1213        }
1214        // Strip the prefix, then turn the block — the same two steps the slash
1215        // menu takes, so a `## ` and a menu pick land in one place.
1216        self.doc.edit_at(at, |text| text.remove(0..len));
1217        self.doc.set_kind(at.block, hit.apply(Text::default()));
1218        self.selection =
1219            Selection::at(Cursor::new(at.block, Part::Body, at.offset - len).clamp(&self.doc));
1220        // The transformation is its own step: undo after typing `## Title`
1221        // should give back the heading, not the paragraph before the hashes.
1222        self.history.interrupt();
1223    }
1224
1225    fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Context<Self>) {
1226        self.delete_back(cx);
1227    }
1228
1229    /// Delete backwards: the selection if there is one, otherwise the character
1230    /// before the caret, otherwise whatever the start of a block means.
1231    ///
1232    /// The kill chords land here too when they have nothing left to take within
1233    /// the block, so reaching out of one is decided in a single place.
1234    fn delete_back(&mut self, cx: &mut Context<Self>) {
1235        let at = self.cursor();
1236        // Reaching out of a block is structural; taking a character is not.
1237        let kind = if self.selection.is_collapsed() && at.offset == 0 {
1238            EditKind::Structure
1239        } else {
1240            EditKind::Delete
1241        };
1242        let painter = Painter::of(cx);
1243        self.edit(kind, cx, |this| {
1244            let before = this.doc.blocks.len();
1245            let splice = if !this.selection.is_collapsed() {
1246                this.doc.replace(this.selection, Text::default())
1247            } else if at.offset > 0 {
1248                this.doc
1249                    .replace(Selection::new(at.left(&this.doc), at), Text::default())
1250            } else {
1251                // `merge_back` outdents, unmarkers, unfences or merges —
1252                // whichever the block's state calls for — and says where the
1253                // caret landed.
1254                match this.doc.merge_back(at) {
1255                    // The seam between where the caret was and where it landed
1256                    // is exactly what the merge closed up.
1257                    Some(head) => Splice {
1258                        removed: Selection::new(head, at),
1259                        caret: head,
1260                        blocks: this.doc.blocks.len() as isize - before as isize,
1261                    },
1262                    None => return vec![],
1263                }
1264            };
1265            let head = splice.caret;
1266            this.selection = Selection::at(head.clamp(&this.doc));
1267            // Deleting narrows the query too, and backspacing onto the slash
1268            // itself is what closes the menu.
1269            this.track_slash("", painter);
1270            vec![Delta::Spliced(splice)]
1271        });
1272    }
1273
1274    fn delete(&mut self, _: &Delete, _: &mut Window, cx: &mut Context<Self>) {
1275        self.delete_forward(cx);
1276    }
1277
1278    /// Delete forwards, joining the next block when the caret is at the end of
1279    /// this one — which is what a kill to the end of a line does there too.
1280    fn delete_forward(&mut self, cx: &mut Context<Self>) {
1281        self.edit(EditKind::Delete, cx, |this| {
1282            let at = this.cursor();
1283            let range = if this.selection.is_collapsed() {
1284                Selection::new(at, at.right(&this.doc))
1285            } else {
1286                this.selection
1287            };
1288            let splice = this.doc.replace(range, Text::default());
1289            this.selection = Selection::at(splice.caret.clamp(&this.doc));
1290            vec![Delta::Spliced(splice)]
1291        });
1292    }
1293
1294    /// Enter. In a body it splits the block; in a code fence it is a newline,
1295    /// which is the whole reason a fence is worth typing into.
1296    fn split_block(&mut self, _: &SplitBlock, window: &mut Window, cx: &mut Context<Self>) {
1297        // A menu owns Enter while it is open, or picking a block would also
1298        // split the one it is turning.
1299        if let Some(choice) = self.pasted.as_ref().map(link::Paste::choice) {
1300            return self.confirm_paste(choice, cx);
1301        }
1302        if self.confirm_slash(None, cx) {
1303            return;
1304        }
1305        let at = self.cursor();
1306        match at.part {
1307            Part::Code => return self.insert("\n", cx),
1308            // A cell is one line by definition; Enter has nowhere to put a
1309            // break, so it does nothing rather than something surprising.
1310            Part::Cell { .. } => return,
1311            // An image with nothing to show yet is missing one thing, so Enter
1312            // asks for it rather than carrying on past a blank.
1313            Part::Caption
1314                if matches!(
1315                    self.doc.blocks.get(at.block).map(|block| &block.kind),
1316                    Some(BlockKind::Image { url, .. }) if url.is_empty()
1317                ) =>
1318            {
1319                return self.prompt_for_url(at.block, window, cx);
1320            }
1321            // A caption is one line too, but the block it belongs to is the
1322            // end of something — so Enter carries on underneath the picture,
1323            // which is [`Doc::split`]'s answer for a block with no body.
1324            Part::Caption | Part::Body => {}
1325        }
1326        self.edit(EditKind::Structure, cx, |this| {
1327            let mut deltas = Vec::new();
1328            if !this.selection.is_collapsed() {
1329                let splice = this.doc.replace(this.selection, Text::default());
1330                this.selection = Selection::at(splice.caret.clamp(&this.doc));
1331                deltas.push(Delta::Spliced(splice));
1332            }
1333            let at = this.cursor();
1334            let new = this.doc.split(at.block, at.offset);
1335            this.selection = Selection::at(Cursor::new(new, Part::Body, 0).clamp(&this.doc));
1336            // What followed the caret moved into a block of its own, which
1337            // everything below it now sits under.
1338            deltas.push(Delta::Spliced(Splice {
1339                removed: Selection::at(at),
1340                caret: Cursor::new(new, Part::Body, 0),
1341                blocks: 1,
1342            }));
1343            deltas
1344        });
1345    }
1346
1347    fn indent(&mut self, _: &Indent, _: &mut Window, cx: &mut Context<Self>) {
1348        // In the source there is one block to indent and indenting it would be
1349        // invisible, so tab is what it is in any text editor: two spaces.
1350        if !self.blocks() {
1351            return self.insert(INDENT, cx);
1352        }
1353        self.edit(EditKind::Structure, cx, |this| {
1354            this.doc.indent(this.cursor().block);
1355            vec![]
1356        });
1357    }
1358
1359    fn outdent(&mut self, _: &Outdent, _: &mut Window, cx: &mut Context<Self>) {
1360        if !self.blocks() {
1361            return self.unindent(cx);
1362        }
1363        self.edit(EditKind::Structure, cx, |this| {
1364            this.doc.outdent(this.cursor().block);
1365            vec![]
1366        });
1367    }
1368
1369    fn increase_text_size(&mut self, _: &IncreaseTextSize, _: &mut Window, cx: &mut Context<Self>) {
1370        self.step_text_size(TextSize::of(cx).step, cx);
1371    }
1372
1373    fn decrease_text_size(&mut self, _: &DecreaseTextSize, _: &mut Window, cx: &mut Context<Self>) {
1374        self.step_text_size(-TextSize::of(cx).step, cx);
1375    }
1376
1377    fn reset_text_size(&mut self, _: &ResetTextSize, _: &mut Window, cx: &mut Context<Self>) {
1378        text_size::reset_text_size(cx);
1379    }
1380
1381    /// Sizing is not an edit: it changes nothing about the document, so it
1382    /// leaves no undo step and no anchor moves.
1383    ///
1384    /// The step is taken against *this* document's size and stored back as the
1385    /// shared adjustment, so a press at the end of the range banks up nothing
1386    /// to work back through on the way down.
1387    fn step_text_size(&mut self, by: f32, cx: &mut Context<Self>) {
1388        let base = self.text_size.unwrap_or_else(theme::base_text_size);
1389        let next = TextSize::of(cx).clamp(text_size::resolve(self.text_size, cx) + by);
1390        text_size::set_adjustment(next - base, cx);
1391    }
1392
1393    /// Escape closes an open menu, and otherwise collapses a selection — the
1394    /// things there are to back out of, innermost first.
1395    fn dismiss(&mut self, _: &Dismiss, _: &mut Window, cx: &mut Context<Self>) {
1396        if self.pasted.take().is_none() && self.slash.take().is_none() {
1397            self.selection = Selection::at(self.selection.head);
1398        }
1399        cx.notify();
1400    }
1401
1402    fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
1403        self.selection = Selection::all(&self.doc);
1404        self.history.interrupt();
1405        cx.notify();
1406    }
1407
1408    /// The selection as markdown — what a copy puts on the clipboard, and what
1409    /// a paste elsewhere reads back.
1410    fn selected_source(&self) -> Option<String> {
1411        (!self.selection.is_collapsed()).then(|| {
1412            let mut slice = self.doc.slice(self.selection);
1413            slice.normalize_with(&self.marks);
1414            markdown::serialize_with(&slice, &self.marks)
1415        })
1416    }
1417
1418    fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
1419        if let Some(source) = self.selected_source() {
1420            cx.write_to_clipboard(gpui::ClipboardItem::new_string(source));
1421        }
1422    }
1423
1424    fn cut(&mut self, _: &Cut, _: &mut Window, cx: &mut Context<Self>) {
1425        let Some(source) = self.selected_source() else {
1426            return;
1427        };
1428        cx.write_to_clipboard(gpui::ClipboardItem::new_string(source));
1429        self.edit(EditKind::Structure, cx, |this| {
1430            let splice = this.doc.replace(this.selection, Text::default());
1431            this.selection = Selection::at(splice.caret.clamp(&this.doc));
1432            vec![Delta::Spliced(splice)]
1433        });
1434    }
1435
1436    /// Markdown in, at the caret. A lone paragraph goes in as inline text with
1437    /// its marks; anything else arrives as blocks.
1438    fn paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
1439        let Some(item) = cx.read_from_clipboard() else {
1440            return;
1441        };
1442        // A picture before its text, because a clipboard carrying both is
1443        // carrying a name for the picture — which is not the picture. A
1444        // screenshot has a file name beside its bytes, and a file copied in a
1445        // file manager has its path beside the path itself.
1446        for entry in item.entries() {
1447            let placed = match entry {
1448                gpui::ClipboardEntry::Image(image) => self.paste_image(image, cx),
1449                gpui::ClipboardEntry::ExternalPaths(paths) => self.paste_paths(paths, cx),
1450                gpui::ClipboardEntry::String(_) => false,
1451            };
1452            if placed {
1453                return;
1454            }
1455        }
1456        let Some(source) = item.text() else {
1457            return;
1458        };
1459        let url = source.trim();
1460        if markdown::is_url(url) {
1461            return self.paste_url(url.to_string(), cx);
1462        }
1463        self.edit(EditKind::Structure, cx, |this| {
1464            let removed = this.selection;
1465            let before = this.doc.blocks.len();
1466            let head = this
1467                .doc
1468                .splice(removed, markdown::parse_with(&source, &this.marks));
1469            this.selection = Selection::at(head.clamp(&this.doc));
1470            vec![Delta::Spliced(Splice {
1471                removed,
1472                caret: head,
1473                blocks: this.doc.blocks.len() as isize - before as isize,
1474            })]
1475        });
1476    }
1477
1478    /// A URL is never spliced in as a block. It links whatever is selected, or
1479    /// lands as a link where the caret is — and only when the block it landed
1480    /// in held nothing else does it also offer to become a card, which is the
1481    /// one place a card would not eat a sentence.
1482    fn paste_url(&mut self, url: String, cx: &mut Context<Self>) {
1483        // The one paste people expect to *not* overwrite what they chose.
1484        if !self.selection.is_collapsed() {
1485            return self.toggle_mark(Mark::Link(url), cx);
1486        }
1487        // A card needs a block with nothing else in it; a chip needs a body or
1488        // a cell to sit in. A fence holds its URL literally and offers neither.
1489        let at = self.cursor();
1490        let alone = at.part == Part::Body && self.caret_text().is_some_and(Text::is_empty);
1491        self.edit(EditKind::Structure, cx, |this| {
1492            let splice = this.doc.replace(this.selection, Text::link(&url));
1493            this.selection = Selection::at(splice.caret.clamp(&this.doc));
1494            vec![Delta::Spliced(splice)]
1495        });
1496        // A fence holds its URL literally and a caption cannot spell a mark, so
1497        // neither has a richer form to offer.
1498        if self.chrome.paste && !matches!(at.part, Part::Code | Part::Caption) {
1499            self.pasted = Some(link::Paste::open(at, url, alone));
1500            cx.notify();
1501        }
1502    }
1503
1504    /// Answer the paste menu: leave the link, or turn its block into a card or
1505    /// the picture it points at.
1506    pub(super) fn confirm_paste(&mut self, choice: Choice, cx: &mut Context<Self>) {
1507        let Some(pasted) = self.pasted.take() else {
1508            return;
1509        };
1510        let ix = pasted.at.block;
1511        let card = |url, form| BlockKind::Bookmark { url, form };
1512        match choice {
1513            Choice::Dismiss => cx.notify(),
1514            // A chip with a line to itself is a block, which is what gives it
1515            // room for a favicon; inside a sentence it is a mark over the text
1516            // that is already there, and only the spelling changes.
1517            Choice::Chip if pasted.alone => self.turn_into(ix, card(pasted.url, Form::Chip), cx),
1518            Choice::Chip => self.edit(EditKind::Structure, cx, |this| {
1519                let end = Cursor {
1520                    offset: pasted.at.offset + pasted.url.len(),
1521                    ..pasted.at
1522                };
1523                let text = Text {
1524                    text: pasted.url.clone(),
1525                    marks: vec![markdown::MarkSpan {
1526                        range: 0..pasted.url.len(),
1527                        mark: Mark::Mention {
1528                            url: pasted.url,
1529                            form: markdown::Form::Chip,
1530                        },
1531                    }],
1532                };
1533                let splice = this.doc.replace(Selection::new(pasted.at, end), text);
1534                this.selection = Selection::at(splice.caret.clamp(&this.doc));
1535                vec![Delta::Spliced(splice)]
1536            }),
1537            Choice::Bookmark => self.turn_into(ix, card(pasted.url, Form::Auto), cx),
1538            Choice::Embed => self.turn_into(ix, card(pasted.url, Form::Embed), cx),
1539            Choice::Image => self.turn_into(
1540                ix,
1541                BlockKind::Image {
1542                    url: pasted.url,
1543                    alt: Text::default(),
1544                    width: None,
1545                },
1546                cx,
1547            ),
1548        }
1549    }
1550
1551    /// Give a block over to the link it holds — a card, or the picture it
1552    /// points at.
1553    ///
1554    /// One step, not two: turning the block and giving the caret somewhere to
1555    /// go are one gesture, and undo has to agree.
1556    fn turn_into(&mut self, ix: usize, kind: BlockKind, cx: &mut Context<Self>) {
1557        self.edit(EditKind::Structure, cx, |this| {
1558            // The URL is the block's whole text and the new block shows it
1559            // already, so [`Doc::set_kind`] is given nothing to carry across —
1560            // otherwise a picture prints its own URL as its caption.
1561            let held = this.doc.blocks[ix]
1562                .text_at(Part::Body)
1563                .map_or(0, |text| text.text.len());
1564            this.doc.edit_at(Cursor::new(ix, Part::Body, 0), |text| {
1565                *text = Text::default()
1566            });
1567            this.doc.set_kind(ix, kind);
1568            // A caret goes where the block admits one, and otherwise carries on
1569            // in the block after it — a fresh one when it ends the document.
1570            let part = this.doc.blocks[ix].parts().first().copied();
1571            let at = match part {
1572                Some(part) => Cursor::new(ix, part, 0),
1573                None => {
1574                    if this.doc.blocks.len() <= ix + 1 {
1575                        this.doc
1576                            .blocks
1577                            .push(markdown::Block::new(BlockKind::Paragraph(Text::default())));
1578                    }
1579                    Cursor::new(ix + 1, Part::Body, 0)
1580                }
1581            };
1582            this.selection = Selection::at(at.clamp(&this.doc));
1583            vec![Delta::Spliced(Splice {
1584                removed: Selection::new(
1585                    Cursor::new(ix, Part::Body, 0),
1586                    Cursor::new(ix, Part::Body, held),
1587                ),
1588                caret: Cursor::new(ix, Part::Body, 0),
1589                blocks: 0,
1590            })]
1591        });
1592    }
1593
1594    fn undo(&mut self, _: &Undo, _: &mut Window, cx: &mut Context<Self>) {
1595        if let Some(step) = self
1596            .history
1597            .undo(self.mode, &self.doc, self.selection, &self.anchors)
1598        {
1599            self.restore(step, cx);
1600        }
1601    }
1602
1603    fn redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
1604        if let Some(step) = self
1605            .history
1606            .redo(self.mode, &self.doc, self.selection, &self.anchors)
1607        {
1608            self.restore(step, cx);
1609        }
1610    }
1611
1612    /// Put a whole moment back — document, caret and anchors together.
1613    ///
1614    /// The anchors come from the snapshot rather than from mapping, because a
1615    /// step back is not an edit: there is no delta between here and a document
1616    /// two hundred keystrokes ago.
1617    fn restore(&mut self, step: crate::history::Step, cx: &mut Context<Self>) {
1618        self.doc = step.doc;
1619        self.selection = step.selection.clamp(&self.doc);
1620        self.anchors = step.anchors;
1621        if step.mode != self.mode {
1622            self.mode = step.mode;
1623            self.dismiss_menus();
1624            cx.emit(EditorEvent::ModeChanged(step.mode));
1625        }
1626        cx.emit(EditorEvent::Changed);
1627        cx.notify();
1628    }
1629
1630    /// Move the caret's block, children and all, and follow it.
1631    ///
1632    /// Public because a gutter handle and a menu row reach the same operation
1633    /// as the key does — one vocabulary, not three paths into [`Doc`].
1634    pub fn move_block(&mut self, ix: usize, delta: isize, cx: &mut Context<Self>) {
1635        if !self.blocks() {
1636            return;
1637        }
1638        self.edit(EditKind::Structure, cx, |this| {
1639            let caret = this.cursor();
1640            let at = this.doc.subtree(ix);
1641            let Some(to) = this.doc.move_block(ix, delta) else {
1642                return vec![];
1643            };
1644            // The caret rides along, keeping its depth within the subtree
1645            // that moved and its offset within its own text.
1646            let block = to + caret.block.saturating_sub(ix);
1647            this.selection = Selection::at(Cursor { block, ..caret }.clamp(&this.doc));
1648            vec![Delta::Moved { at, to: Some(to) }]
1649        });
1650    }
1651
1652    pub fn duplicate_block(&mut self, ix: usize, cx: &mut Context<Self>) {
1653        if !self.blocks() {
1654            return;
1655        }
1656        self.edit(EditKind::Structure, cx, |this| {
1657            let span = this.doc.subtree(ix);
1658            let Some(copy) = this.doc.duplicate(ix) else {
1659                return vec![];
1660            };
1661            this.selection = Selection::at(Cursor::new(copy, Part::Body, 0).clamp(&this.doc));
1662            vec![Delta::Opened {
1663                at: copy,
1664                count: span.len(),
1665            }]
1666        });
1667    }
1668
1669    pub fn remove_block(&mut self, ix: usize, cx: &mut Context<Self>) {
1670        if !self.blocks() {
1671            return;
1672        }
1673        self.edit(EditKind::Structure, cx, |this| {
1674            let at = this.doc.subtree(ix);
1675            this.doc.remove_block(ix);
1676            this.selection =
1677                Selection::at(Cursor::new(ix.saturating_sub(1), Part::Body, 0).clamp(&this.doc));
1678            vec![Delta::Moved { at, to: None }]
1679        });
1680    }
1681
1682    /// Tag a fenced block with the language it holds, or `None` for plain.
1683    pub fn set_language(&mut self, ix: usize, language: Option<String>, cx: &mut Context<Self>) {
1684        if !self.blocks() {
1685            return;
1686        }
1687        self.edit(EditKind::Structure, cx, |this| {
1688            this.doc.set_language(ix, language);
1689            vec![]
1690        });
1691    }
1692
1693    /// Turn the caret's block into `kind` — what the slash menu and the block
1694    /// menu both do.
1695    pub fn set_block(&mut self, ix: usize, kind: BlockKind, cx: &mut Context<Self>) {
1696        if !self.blocks() {
1697            return;
1698        }
1699        self.edit(EditKind::Structure, cx, |this| {
1700            this.doc.set_kind(ix, kind);
1701            this.selection = this.selection.clamp(&this.doc);
1702            vec![]
1703        });
1704    }
1705    /// The paragraph a document ending in a fence, a table, a rule or an image
1706    /// has no other way to grow: a fence swallows Enter, a cell and a caption
1707    /// have nowhere to put one, and a rule holds no caret at all. `false` when
1708    /// the last block ends in a body, which can carry on by itself.
1709    fn append_tail(&mut self, cx: &mut Context<Self>) -> bool {
1710        let Some(last) = self.doc.blocks.len().checked_sub(1) else {
1711            return false;
1712        };
1713        if !self.blocks() {
1714            return false;
1715        }
1716        if self.doc.blocks[last].parts().last() == Some(&Part::Body) {
1717            return false;
1718        }
1719        self.edit(EditKind::Structure, cx, |this| {
1720            this.doc
1721                .blocks
1722                .push(markdown::Block::new(BlockKind::Paragraph(Text::default())));
1723            let ix = this.doc.blocks.len() - 1;
1724            this.selection = Selection::at(Cursor::new(ix, Part::Body, 0).clamp(&this.doc));
1725            vec![]
1726        });
1727        true
1728    }
1729
1730    /// A click past the end of the document. Without this the document has no
1731    /// end: the click snaps back into the block above it, and what gets typed
1732    /// lands inside the code the reader was trying to escape.
1733    fn tail_click(&mut self, at: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) -> bool {
1734        let Some(last) = self.doc.blocks.len().checked_sub(1) else {
1735            return false;
1736        };
1737        let Some(bounds) = self.layouts.block_bounds(last) else {
1738            return false;
1739        };
1740        at.y > bounds.origin.y + bounds.size.height && self.append_tail(cx)
1741    }
1742
1743    /// Shift-tab in the source: take back up to one [`INDENT`] of the spaces
1744    /// before the caret, and nothing else — a line that is not indented has
1745    /// nothing to give.
1746    fn unindent(&mut self, cx: &mut Context<Self>) {
1747        let at = self.cursor();
1748        let Some(text) = self.caret_text() else {
1749            return;
1750        };
1751        let before = &text.text[..at.offset];
1752        let width = before.len() - before.trim_end_matches(' ').len();
1753        let width = width.min(INDENT.len());
1754        if width == 0 {
1755            return;
1756        }
1757        self.edit(EditKind::Delete, cx, |this| {
1758            let from = Cursor::new(at.block, at.part, at.offset - width);
1759            let splice = this.doc.replace(Selection::new(from, at), Text::default());
1760            this.selection = Selection::at(splice.caret.clamp(&this.doc));
1761            vec![Delta::Spliced(splice)]
1762        });
1763    }
1764
1765    /// The caret's text, for the input handler's offset arithmetic.
1766    fn caret_text(&self) -> Option<&Text> {
1767        let at = self.cursor();
1768        self.doc.blocks.get(at.block)?.text_at(at.part)
1769    }
1770}
1771
1772impl EventEmitter<EditorEvent> for Editor {}
1773
1774impl Focusable for Editor {
1775    fn focus_handle(&self, _: &App) -> FocusHandle {
1776        self.focus_handle.clone()
1777    }
1778}
1779
1780impl Render for Editor {
1781    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1782        let theme = Theme::of(cx).clone();
1783        let layout = Layout::of(cx);
1784        let focused = self.focus_handle.is_focused(window);
1785        // The only place the blink starts: `caret_moved` drops the task, so the
1786        // next render brings it back in phase, lit beat first.
1787        if focused && ui::input::caret_blink(cx) {
1788            if self.blink.is_none() {
1789                self.start_blink(cx);
1790            }
1791        } else {
1792            self.blink = None;
1793            self.caret_on = true;
1794        }
1795        let selection = focused.then_some(self.selection);
1796
1797        // Typed text and IME reach an entity only through an input handler
1798        // registered during *paint*, against the bounds it should be anchored
1799        // to. There is no custom element here to do that from, so a zero-cost
1800        // canvas over the document supplies the paint phase. Without this the
1801        // key bindings still fire and nothing types.
1802        let handle = self.focus_handle.clone();
1803        let entity = cx.entity();
1804        let input = canvas(
1805            |_, _, _| (),
1806            move |bounds, _, window, cx| {
1807                // The gutter handle is placed from positions recorded in window
1808                // coordinates, so the box they have to be measured against is
1809                // taken here — the one place that knows it.
1810                entity.update(cx, |this, _| {
1811                    this.origin = bounds.origin;
1812                    this.width = bounds.size.width;
1813                });
1814                window.handle_input(
1815                    &handle,
1816                    ElementInputHandler::new(bounds, entity.clone()),
1817                    cx,
1818                );
1819            },
1820        )
1821        .absolute()
1822        .size_full();
1823
1824        // A tab stop, so the editor is reachable the same way every other
1825        // control in the library is.
1826        let handle = self.focus_handle.clone().tab_stop(true);
1827
1828        div()
1829            // Stateful only so the pointer leaving can be heard: `on_hover` is
1830            // what tells the gutter handle to stop pointing at a block the
1831            // pointer left behind.
1832            .id("bezel-editor")
1833            // The mark is what keeps `tab`: without it traversal answers the
1834            // key first and the caret never sees it.
1835            .key_context(key_context())
1836            .track_focus(&handle)
1837            // Tracking focus does not take it. Without this, clicking into the
1838            // document blurs the editor instead of putting a caret in it, and
1839            // the caret vanishes on the first click.
1840            .on_mouse_down(
1841                MouseButton::Left,
1842                cx.listener(|this, event: &gpui::MouseDownEvent, window, cx| {
1843                    // The handle's own listener runs first and claims the
1844                    // press; without the flag this would close the menu it
1845                    // just opened. `ui::popover::Popup` solves it the same way.
1846                    if std::mem::take(&mut this.press_claimed) {
1847                        return;
1848                    }
1849                    ui::popover::close_popup(this, cx, |this| &mut this.block_menu);
1850                    ui::popover::close_popup(this, cx, |this| &mut this.language_menu);
1851                    this.pasted = None;
1852                    this.focus_handle.clone().focus(window, cx);
1853                    if this.tail_click(event.position, cx) {
1854                        return;
1855                    }
1856                    let Some(hit) = this.layouts.hit(event.position) else {
1857                        return cx.notify();
1858                    };
1859                    this.selection = match event.click_count {
1860                        // Shift extends from wherever the anchor already is,
1861                        // which is what makes click-then-shift-click a range.
1862                        _ if event.modifiers.shift => this.selection.extend_to(hit),
1863                        1 => Selection::at(hit),
1864                        2 => Selection::new(hit.word_left(&this.doc), hit.word_right(&this.doc)),
1865                        _ => Selection::new(hit.home(), hit.end(&this.doc)),
1866                    }
1867                    .clamp(&this.doc);
1868                    this.dragging = event.click_count == 1 && !event.modifiers.shift;
1869                    this.history.interrupt();
1870                    this.caret_moved();
1871                    // Only the editor sees the press, so only the editor can
1872                    // say which thread it landed on.
1873                    if let Some(id) = this.comment_at(event.position) {
1874                        cx.emit(EditorEvent::CommentActivated(id));
1875                    }
1876                    cx.notify();
1877                }),
1878            )
1879            // The drag has to be tracked from the container rather than from a
1880            // payload: a text selection has nothing to carry, and gpui's drag
1881            // payload is for things being dropped somewhere.
1882            .on_hover(cx.listener(|this, hovered: &bool, _, cx| {
1883                if !*hovered && this.hovered.take().is_some() {
1884                    cx.notify();
1885                }
1886            }))
1887            .on_mouse_move(cx.listener(|this, event: &gpui::MouseMoveEvent, _, cx| {
1888                // Ahead of every drag branch below, because the pointer's shape
1889                // is about where it *is* rather than about what it is doing.
1890                let over_text = this.layouts.over_text(event.position);
1891                if over_text != this.over_text {
1892                    this.over_text = over_text;
1893                    cx.notify();
1894                }
1895                // A lifted block follows the pointer; otherwise the pointer
1896                // only decides which block wears the handle.
1897                if let Some((from, _)) = this.lifted.filter(|_| event.dragging()) {
1898                    if let Some(to) = this.layouts.block_at(event.position) {
1899                        this.lifted = Some((from, to));
1900                        cx.notify();
1901                    }
1902                    return;
1903                }
1904                // An image being resized follows the pointer the same way — the
1905                // document holds nothing until the handle is released.
1906                if let Some((ix, _)) = this.resizing.filter(|_| event.dragging()) {
1907                    if let Some(width) = this.dragged_width(ix, event.position.x) {
1908                        this.resizing = Some((ix, Some(width)));
1909                        cx.notify();
1910                    }
1911                    return;
1912                }
1913                if this.dragging && event.dragging() {
1914                    if let Some(hit) = this.layouts.hit(event.position) {
1915                        this.selection = this.selection.extend_to(hit).clamp(&this.doc);
1916                        cx.notify();
1917                    }
1918                    return;
1919                }
1920                let hovered = this.layouts.block_at(event.position);
1921                if hovered != this.hovered {
1922                    this.hovered = hovered;
1923                    cx.notify();
1924                }
1925            }))
1926            // Both, because a release can land anywhere on screen and only the
1927            // first fires over the editor. A resize left running would leave
1928            // its stand-in picture painted over the document for good.
1929            .on_mouse_up_out(
1930                MouseButton::Left,
1931                cx.listener(|this, _: &gpui::MouseUpEvent, window, cx| {
1932                    this.dragging = false;
1933                    this.drop_resize(window, cx);
1934                }),
1935            )
1936            .on_mouse_up(
1937                MouseButton::Left,
1938                cx.listener(|this, event: &gpui::MouseUpEvent, window, cx| {
1939                    this.dragging = false;
1940                    if this.drop_resize(window, cx) {
1941                        return;
1942                    }
1943                    let Some((from, to)) = this.lifted.take() else {
1944                        return;
1945                    };
1946                    if from == to {
1947                        // A press that never moved is a click, and a click on
1948                        // the handle is what opens the menu — unless that same
1949                        // press is what dismissed it, which the note taken on
1950                        // the way down is the only way to tell.
1951                        if !this.block_menu.take_press_was_open() {
1952                            this.block_menu.open((from, event.position));
1953                        }
1954                        return cx.notify();
1955                    }
1956                    this.edit(EditKind::Structure, cx, |this| {
1957                        // `move_block` steps one sibling at a time, so a drop
1958                        // several blocks away is that many steps. Bounded by
1959                        // the block count, which no drag can exceed.
1960                        let delta = if to > from { 1 } else { -1 };
1961                        let mut at = from;
1962                        // Each step is its own move, so each is its own delta —
1963                        // folding them into one would have to compose the
1964                        // hops, and they are already in order.
1965                        let mut deltas = Vec::new();
1966                        for _ in 0..this.doc.blocks.len() {
1967                            let span = this.doc.subtree(at);
1968                            let Some(next) = this.doc.move_block(at, delta) else {
1969                                break;
1970                            };
1971                            deltas.push(Delta::Moved {
1972                                at: span,
1973                                to: Some(next),
1974                            });
1975                            at = next;
1976                            if (delta > 0 && at >= to) || (delta < 0 && at <= to) {
1977                                break;
1978                            }
1979                        }
1980                        this.selection =
1981                            Selection::at(Cursor::new(at, Part::Body, 0).clamp(&this.doc));
1982                        deltas
1983                    });
1984                }),
1985            )
1986            .on_action(cx.listener(Self::backspace))
1987            .on_action(cx.listener(Self::delete))
1988            .on_action(
1989                cx.listener(|this, _: &KillLine, _, cx| this.delete_to(true, Cursor::end, cx)),
1990            )
1991            .on_action(cx.listener(|this, _: &DeleteWordLeft, _, cx| {
1992                this.delete_to(false, Cursor::word_left, cx)
1993            }))
1994            .on_action(cx.listener(|this, _: &DeleteWordRight, _, cx| {
1995                this.delete_to(true, Cursor::word_right, cx)
1996            }))
1997            .on_action(cx.listener(|this, _: &DeleteToHome, _, cx| {
1998                this.delete_to(false, |at, _| at.home(), cx)
1999            }))
2000            .on_action(cx.listener(Self::split_block))
2001            .on_action(cx.listener(Self::indent))
2002            .on_action(cx.listener(Self::increase_text_size))
2003            .on_action(cx.listener(Self::decrease_text_size))
2004            .on_action(cx.listener(Self::reset_text_size))
2005            .on_action(cx.listener(Self::outdent))
2006            .on_action(cx.listener(Self::dismiss))
2007            .on_action(cx.listener(Self::select_all))
2008            .on_action(cx.listener(Self::copy))
2009            .on_action(cx.listener(Self::cut))
2010            .on_action(cx.listener(Self::paste))
2011            .on_action(cx.listener(Self::undo))
2012            .on_action(cx.listener(Self::redo))
2013            .on_action(cx.listener(Self::confirm_url))
2014            .on_action(cx.listener(Self::cancel_url))
2015            // A file crossing the document lights the same indicator a lifted
2016            // block does, so a drop from outside lands where it looks like it
2017            // will.
2018            .on_drag_move(cx.listener(
2019                |this, event: &gpui::DragMoveEvent<gpui::ExternalPaths>, _, cx| {
2020                    let over = this.layouts.block_at(event.event.position);
2021                    if over != this.dropping {
2022                        this.dropping = over;
2023                        cx.notify();
2024                    }
2025                },
2026            ))
2027            .on_drop(
2028                cx.listener(|this, paths: &gpui::ExternalPaths, window, cx| {
2029                    this.focus_handle.clone().focus(window, cx);
2030                    this.drop_paths(paths, cx);
2031                }),
2032            )
2033            .on_action(cx.listener(|this, _: &ToggleBold, _, cx| this.toggle_mark(Mark::Bold, cx)))
2034            .on_action(
2035                cx.listener(|this, _: &ToggleItalic, _, cx| this.toggle_mark(Mark::Italic, cx)),
2036            )
2037            .on_action(
2038                cx.listener(|this, _: &ToggleStrike, _, cx| this.toggle_mark(Mark::Strike, cx)),
2039            )
2040            .on_action(cx.listener(|this, _: &ToggleCode, _, cx| this.toggle_mark(Mark::Code, cx)))
2041            .on_action(cx.listener(|this, _: &MoveBlockUp, _, cx| {
2042                this.move_block(this.cursor().block, -1, cx)
2043            }))
2044            .on_action(cx.listener(|this, _: &MoveBlockDown, _, cx| {
2045                this.move_block(this.cursor().block, 1, cx)
2046            }))
2047            .on_action(cx.listener(|this, _: &DuplicateBlock, _, cx| {
2048                this.duplicate_block(this.cursor().block, cx)
2049            }))
2050            .on_action(cx.listener(|this, _: &RemoveBlock, _, cx| {
2051                this.remove_block(this.cursor().block, cx)
2052            }))
2053            // Motion is one method with a `Cursor` function and an "extend"
2054            // flag, so a shift variant cannot drift from the key it shadows.
2055            .on_action(cx.listener(|this, _: &Left, _, cx| this.moved(false, Cursor::left, cx)))
2056            .on_action(cx.listener(|this, _: &Right, _, cx| this.moved(false, Cursor::right, cx)))
2057            .on_action(cx.listener(|this, _: &Up, _, cx| this.vertical(false, false, cx)))
2058            .on_action(cx.listener(|this, _: &Down, _, cx| this.vertical(true, false, cx)))
2059            .on_action(
2060                cx.listener(|this, _: &Home, _, cx| this.moved(false, |at, _| at.home(), cx)),
2061            )
2062            .on_action(cx.listener(|this, _: &End, _, cx| this.moved(false, Cursor::end, cx)))
2063            .on_action(
2064                cx.listener(|this, _: &WordLeft, _, cx| this.moved(false, Cursor::word_left, cx)),
2065            )
2066            .on_action(
2067                cx.listener(|this, _: &WordRight, _, cx| this.moved(false, Cursor::word_right, cx)),
2068            )
2069            .on_action(
2070                cx.listener(|this, _: &SelectLeft, _, cx| this.moved(true, Cursor::left, cx)),
2071            )
2072            .on_action(
2073                cx.listener(|this, _: &SelectRight, _, cx| this.moved(true, Cursor::right, cx)),
2074            )
2075            .on_action(cx.listener(|this, _: &SelectUp, _, cx| this.vertical(false, true, cx)))
2076            .on_action(cx.listener(|this, _: &SelectDown, _, cx| this.vertical(true, true, cx)))
2077            .on_action(
2078                cx.listener(|this, _: &SelectHome, _, cx| this.moved(true, |at, _| at.home(), cx)),
2079            )
2080            .on_action(cx.listener(|this, _: &SelectEnd, _, cx| this.moved(true, Cursor::end, cx)))
2081            .on_action(cx.listener(|this, _: &SelectWordLeft, _, cx| {
2082                this.moved(true, Cursor::word_left, cx)
2083            }))
2084            .on_action(cx.listener(|this, _: &SelectWordRight, _, cx| {
2085                this.moved(true, Cursor::word_right, cx)
2086            }))
2087            .w_full()
2088            // Text under the pointer, so the pointer says so — and only there,
2089            // or while a drag is still sweeping one out. The editor's box
2090            // reaches over its gutter, the margin beside a short line, a rule,
2091            // an image and a card, none of which a caret can be put into.
2092            // Where it has nothing to say it stays quiet rather than
2093            // overriding the page with an arrow of its own.
2094            .when(self.over_text || self.dragging, |el| {
2095                el.cursor(CursorStyle::IBeam)
2096            })
2097            // No focus ring. A ring says *widget*, and a document is not one —
2098            // the caret already paints only while focused, so a box around the
2099            // whole page is a second, louder signal for the same fact.
2100            .relative()
2101            .child(input)
2102            // The document is inset by the gutter so the handle has somewhere
2103            // to sit *inside* the editor. Outside it the handle is clipped by
2104            // any scrolling ancestor, and a drag through it never reaches
2105            // `on_mouse_move`, which fires only while this element is the one
2106            // under the pointer.
2107            .child(
2108                div()
2109                    .w_full()
2110                    .pl(gpui::px(layout.text_inset))
2111                    .child(match self.mode {
2112                        // The source is one text, so it paints as one text —
2113                        // the same caret, the same clicks, no block chrome to
2114                        // suppress a piece at a time.
2115                        Mode::Source => markdown::render_source(
2116                            self.source_text(),
2117                            markdown::Editing {
2118                                selection,
2119                                caret_on: self.caret_on,
2120                                layouts: Some(&self.layouts),
2121                                typography: Some(markdown::Typography::of(cx).scaled(
2122                                    text_size::resolve(self.text_size, cx)
2123                                        / theme::base_text_size(),
2124                                )),
2125                                ..Default::default()
2126                            },
2127                            cx,
2128                        ),
2129                        Mode::Blocks => markdown::render_with(
2130                            &self.doc,
2131                            markdown::Editing {
2132                                selection,
2133                                caret_on: self.caret_on,
2134                                layouts: Some(&self.layouts),
2135                                annotations: &self.annotations(),
2136                                placeholder: focused.then(|| PLACEHOLDER.into()),
2137                                // A caret goes into the caption here, so it is always
2138                                // painted — an editor that could hide it would be
2139                                // hiding a place you can already be typing.
2140                                caption: markdown::Caption::Shown,
2141                                // The size is absolute, so the factor the ladder
2142                                // is already scaled by comes back out of it —
2143                                // otherwise the app's size and this one multiply.
2144                                typography: Some(markdown::Typography::of(cx).scaled(
2145                                    text_size::resolve(self.text_size, cx)
2146                                        / theme::base_text_size(),
2147                                )),
2148                            },
2149                            window,
2150                            cx,
2151                        ),
2152                    }),
2153            )
2154            // Last, so the layouts it reads are this frame's rather than the
2155            // one before — children paint in order.
2156            .child(
2157                canvas(|_, _, _| (), {
2158                    let entity = cx.entity();
2159                    move |_, _, window, cx| {
2160                        entity.update(cx, |this, cx| {
2161                            this.reveal_caret(cx);
2162                            this.settle_handle(window, cx);
2163                        });
2164                    }
2165                })
2166                .absolute()
2167                .size(gpui::px(0.0)),
2168            )
2169            .children(self.slash_menu(&theme, cx))
2170            .children(self.paste_menu(&theme, cx))
2171            .children(self.url_prompt(&theme, cx))
2172            .children(self.image_target(cx))
2173            .children(self.resize_preview())
2174            .children(self.handle(focused, &theme, cx))
2175            .children(self.resize_handle(&theme, cx))
2176            .children(self.drop_indicator(&theme))
2177            .children(self.language_chip(&theme, cx))
2178            .children(self.block_menu(&theme, cx))
2179            .children(self.language_menu(&theme, cx))
2180    }
2181}