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