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