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.
1587 fn selected_source(&self) -> Option<String> {
1588 (!self.selection.is_collapsed()).then(|| {
1589 let mut slice = self.doc.slice(self.selection);
1590 slice.normalize_with(&self.marks);
1591 markdown::serialize_with(&slice, &self.marks)
1592 })
1593 }
1594
1595 fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
1596 if let Some(source) = self.selected_source() {
1597 cx.write_to_clipboard(gpui::ClipboardItem::new_string(source));
1598 }
1599 }
1600
1601 fn cut(&mut self, _: &Cut, _: &mut Window, cx: &mut Context<Self>) {
1602 let Some(source) = self.selected_source() else {
1603 return;
1604 };
1605 cx.write_to_clipboard(gpui::ClipboardItem::new_string(source));
1606 self.edit(EditKind::Structure, cx, |this| {
1607 let splice = this.doc.replace(this.selection, Text::default());
1608 this.selection = Selection::at(splice.caret.clamp(&this.doc));
1609 vec![Delta::Spliced(splice)]
1610 });
1611 }
1612
1613 /// Markdown in, at the caret. A lone paragraph goes in as inline text with
1614 /// its marks; anything else arrives as blocks.
1615 fn paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
1616 let Some(item) = cx.read_from_clipboard() else {
1617 return;
1618 };
1619 // Source mode included: the whole document is one fence there.
1620 if self.in_fence() {
1621 if let Some(text) = item.text() {
1622 self.paste_literal(&text, cx);
1623 }
1624 return;
1625 }
1626 // A picture before its text, because a clipboard carrying both is
1627 // carrying a name for the picture — which is not the picture. A
1628 // screenshot has a file name beside its bytes, and a file copied in a
1629 // file manager has its path beside the path itself.
1630 for entry in item.entries() {
1631 let placed = match entry {
1632 gpui::ClipboardEntry::Image(image) => self.paste_image(image, cx),
1633 gpui::ClipboardEntry::ExternalPaths(paths) => self.paste_paths(paths, cx),
1634 gpui::ClipboardEntry::String(_) => false,
1635 };
1636 if placed {
1637 return;
1638 }
1639 }
1640 let Some(source) = item.text() else {
1641 return;
1642 };
1643 let url = source.trim();
1644 if markdown::is_url(url) {
1645 return self.paste_url(url.to_string(), cx);
1646 }
1647 self.edit(EditKind::Structure, cx, |this| {
1648 let removed = this.selection;
1649 let before = this.doc.blocks.len();
1650 let head = this
1651 .doc
1652 .splice(removed, markdown::parse_with(&source, &this.marks));
1653 this.selection = Selection::at(head.clamp(&this.doc));
1654 vec![Delta::Spliced(Splice {
1655 removed,
1656 caret: head,
1657 blocks: this.doc.blocks.len() as isize - before as isize,
1658 })]
1659 });
1660 }
1661
1662 /// Whether the selection starts and ends in one fence's code.
1663 fn in_fence(&self) -> bool {
1664 let (start, end) = self.selection.ordered();
1665 start.part == Part::Code && end.part == Part::Code && start.block == end.block
1666 }
1667
1668 /// Put `text` in place of the selection as it stands, caret after it.
1669 fn paste_literal(&mut self, text: &str, cx: &mut Context<Self>) {
1670 self.edit(EditKind::Structure, cx, |this| {
1671 let splice = this.doc.replace(this.selection, Text::plain(text));
1672 this.selection = Selection::at(splice.caret.clamp(&this.doc));
1673 vec![Delta::Spliced(splice)]
1674 });
1675 }
1676
1677 /// A URL is never spliced in as a block. It links whatever is selected, or
1678 /// lands as a link where the caret is — and only when the block it landed
1679 /// in held nothing else does it also offer to become a card, which is the
1680 /// one place a card would not eat a sentence.
1681 fn paste_url(&mut self, url: String, cx: &mut Context<Self>) {
1682 if self.in_fence() {
1683 return self.paste_literal(&url, cx);
1684 }
1685 // The one paste people expect to *not* overwrite what they chose.
1686 if !self.selection.is_collapsed() {
1687 return self.toggle_mark(Mark::Link(url), cx);
1688 }
1689 // A card needs a block with nothing else in it; a chip needs a body or
1690 // a cell to sit in. A fence holds its URL literally and offers neither.
1691 let at = self.cursor();
1692 let alone = at.part == Part::Body && self.caret_text().is_some_and(Text::is_empty);
1693 self.edit(EditKind::Structure, cx, |this| {
1694 let splice = this.doc.replace(this.selection, Text::link(&url));
1695 this.selection = Selection::at(splice.caret.clamp(&this.doc));
1696 vec![Delta::Spliced(splice)]
1697 });
1698 // A fence holds its URL literally and a caption cannot spell a mark, so
1699 // neither has a richer form to offer.
1700 if self.chrome.paste && !matches!(at.part, Part::Code | Part::Caption) {
1701 self.pasted = Some(link::Paste::open(at, url, alone));
1702 cx.notify();
1703 }
1704 }
1705
1706 /// Answer the paste menu: leave the link, or turn its block into a card or
1707 /// the picture it points at.
1708 pub(super) fn confirm_paste(&mut self, choice: Choice, cx: &mut Context<Self>) {
1709 let Some(pasted) = self.pasted.take() else {
1710 return;
1711 };
1712 let ix = pasted.at.block;
1713 let card = |url, form| BlockKind::Bookmark { url, form };
1714 match choice {
1715 Choice::Dismiss => cx.notify(),
1716 // A chip with a line to itself is a block, which is what gives it
1717 // room for a favicon; inside a sentence it is a mark over the text
1718 // that is already there, and only the spelling changes.
1719 Choice::Chip if pasted.alone => self.turn_into(ix, card(pasted.url, Form::Chip), cx),
1720 Choice::Chip => self.edit(EditKind::Structure, cx, |this| {
1721 let end = Cursor {
1722 offset: pasted.at.offset + pasted.url.len(),
1723 ..pasted.at
1724 };
1725 let text = Text {
1726 text: pasted.url.clone(),
1727 marks: vec![markdown::MarkSpan {
1728 range: 0..pasted.url.len(),
1729 mark: Mark::Mention {
1730 url: pasted.url,
1731 form: markdown::Form::Chip,
1732 },
1733 }],
1734 };
1735 let splice = this.doc.replace(Selection::new(pasted.at, end), text);
1736 this.selection = Selection::at(splice.caret.clamp(&this.doc));
1737 vec![Delta::Spliced(splice)]
1738 }),
1739 Choice::Bookmark => self.turn_into(ix, card(pasted.url, Form::Auto), cx),
1740 Choice::Embed => self.turn_into(ix, card(pasted.url, Form::Embed), cx),
1741 Choice::Image => self.turn_into(
1742 ix,
1743 BlockKind::Image {
1744 url: pasted.url,
1745 alt: Text::default(),
1746 width: None,
1747 },
1748 cx,
1749 ),
1750 }
1751 }
1752
1753 /// Give a block over to the link it holds — a card, or the picture it
1754 /// points at.
1755 ///
1756 /// One step, not two: turning the block and giving the caret somewhere to
1757 /// go are one gesture, and undo has to agree.
1758 fn turn_into(&mut self, ix: usize, kind: BlockKind, cx: &mut Context<Self>) {
1759 self.edit(EditKind::Structure, cx, |this| {
1760 // The URL is the block's whole text and the new block shows it
1761 // already, so [`Doc::set_kind`] is given nothing to carry across —
1762 // otherwise a picture prints its own URL as its caption.
1763 let held = this.doc.blocks[ix]
1764 .text_at(Part::Body)
1765 .map_or(0, |text| text.text.len());
1766 this.doc.edit_at(Cursor::new(ix, Part::Body, 0), |text| {
1767 *text = Text::default()
1768 });
1769 this.doc.set_kind(ix, kind);
1770 // A caret goes where the block admits one, and otherwise carries on
1771 // in the block after it — a fresh one when it ends the document.
1772 let part = this.doc.blocks[ix].parts().first().copied();
1773 let at = match part {
1774 Some(part) => Cursor::new(ix, part, 0),
1775 None => {
1776 if this.doc.blocks.len() <= ix + 1 {
1777 this.doc
1778 .blocks
1779 .push(markdown::Block::new(BlockKind::Paragraph(Text::default())));
1780 }
1781 Cursor::new(ix + 1, Part::Body, 0)
1782 }
1783 };
1784 this.selection = Selection::at(at.clamp(&this.doc));
1785 vec![Delta::Spliced(Splice {
1786 removed: Selection::new(
1787 Cursor::new(ix, Part::Body, 0),
1788 Cursor::new(ix, Part::Body, held),
1789 ),
1790 caret: Cursor::new(ix, Part::Body, 0),
1791 blocks: 0,
1792 })]
1793 });
1794 }
1795
1796 fn undo(&mut self, _: &Undo, _: &mut Window, cx: &mut Context<Self>) {
1797 if let Some(step) = self
1798 .history
1799 .undo(self.mode, &self.doc, self.selection, &self.anchors)
1800 {
1801 self.restore(step, cx);
1802 }
1803 }
1804
1805 fn redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
1806 if let Some(step) = self
1807 .history
1808 .redo(self.mode, &self.doc, self.selection, &self.anchors)
1809 {
1810 self.restore(step, cx);
1811 }
1812 }
1813
1814 /// Put a whole moment back — document, caret and anchors together.
1815 ///
1816 /// The anchors come from the snapshot rather than from mapping, because a
1817 /// step back is not an edit: there is no delta between here and a document
1818 /// two hundred keystrokes ago.
1819 fn restore(&mut self, step: crate::history::Step, cx: &mut Context<Self>) {
1820 self.doc = step.doc;
1821 self.selection = step.selection.clamp(&self.doc);
1822 self.caret_moved();
1823 self.anchors = step.anchors;
1824 if step.mode != self.mode {
1825 self.mode = step.mode;
1826 self.dismiss_menus();
1827 cx.emit(EditorEvent::ModeChanged(step.mode));
1828 }
1829 cx.emit(EditorEvent::Changed);
1830 cx.notify();
1831 }
1832
1833 /// Move the caret's block, children and all, and follow it.
1834 ///
1835 /// Public because a gutter handle and a menu row reach the same operation
1836 /// as the key does — one vocabulary, not three paths into [`Doc`].
1837 pub fn move_block(&mut self, ix: usize, delta: isize, cx: &mut Context<Self>) {
1838 if !self.blocks() {
1839 return;
1840 }
1841 self.edit(EditKind::Structure, cx, |this| {
1842 let caret = this.cursor();
1843 let at = this.doc.subtree(ix);
1844 let Some(to) = this.doc.move_block(ix, delta) else {
1845 return vec![];
1846 };
1847 // The caret rides along, keeping its depth within the subtree
1848 // that moved and its offset within its own text.
1849 let block = to + caret.block.saturating_sub(ix);
1850 this.selection = Selection::at(Cursor { block, ..caret }.clamp(&this.doc));
1851 vec![Delta::Moved { at, to: Some(to) }]
1852 });
1853 }
1854
1855 pub fn duplicate_block(&mut self, ix: usize, cx: &mut Context<Self>) {
1856 if !self.blocks() {
1857 return;
1858 }
1859 self.edit(EditKind::Structure, cx, |this| {
1860 let span = this.doc.subtree(ix);
1861 let Some(copy) = this.doc.duplicate(ix) else {
1862 return vec![];
1863 };
1864 this.selection = Selection::at(Cursor::new(copy, Part::Body, 0).clamp(&this.doc));
1865 vec![Delta::Opened {
1866 at: copy,
1867 count: span.len(),
1868 }]
1869 });
1870 }
1871
1872 pub fn remove_block(&mut self, ix: usize, cx: &mut Context<Self>) {
1873 if !self.blocks() {
1874 return;
1875 }
1876 self.edit(EditKind::Structure, cx, |this| {
1877 let at = this.doc.subtree(ix);
1878 this.doc.remove_block(ix);
1879 this.selection =
1880 Selection::at(Cursor::new(ix.saturating_sub(1), Part::Body, 0).clamp(&this.doc));
1881 vec![Delta::Moved { at, to: None }]
1882 });
1883 }
1884
1885 /// Tag a fenced block with the language it holds, or `None` for plain.
1886 pub fn set_language(&mut self, ix: usize, language: Option<String>, cx: &mut Context<Self>) {
1887 if !self.blocks() {
1888 return;
1889 }
1890 self.edit(EditKind::Structure, cx, |this| {
1891 this.doc.set_language(ix, language);
1892 vec![]
1893 });
1894 }
1895
1896 /// Turn the caret's block into `kind` — what the slash menu and the block
1897 /// menu both do.
1898 pub fn set_block(&mut self, ix: usize, kind: BlockKind, cx: &mut Context<Self>) {
1899 if !self.blocks() {
1900 return;
1901 }
1902 self.edit(EditKind::Structure, cx, |this| {
1903 this.doc.set_kind(ix, kind);
1904 this.selection = this.selection.clamp(&this.doc);
1905 vec![]
1906 });
1907 }
1908
1909 /// Check or uncheck the task block at `ix`. Does nothing to a block that
1910 /// is not one.
1911 ///
1912 /// An edit like any other: one undo step, and the caret stays where it
1913 /// was, so toggling a box across the document does not move whatever is
1914 /// being typed.
1915 pub fn toggle_task(&mut self, ix: usize, cx: &mut Context<Self>) {
1916 if !self.blocks() {
1917 return;
1918 }
1919 let checked = match self.doc.blocks.get(ix).map(|block| &block.kind) {
1920 Some(BlockKind::Task { checked, .. }) => !checked,
1921 _ => return,
1922 };
1923 self.edit(EditKind::Structure, cx, |this| {
1924 if let Some(BlockKind::Task { checked: at, .. }) =
1925 this.doc.blocks.get_mut(ix).map(|block| &mut block.kind)
1926 {
1927 *at = checked;
1928 }
1929 vec![]
1930 });
1931 // Every other edit is made at the caret and owes it a reveal. This one
1932 // is made where a press landed, and the caret can be pages away: the
1933 // reveal `edit` asked for would scroll the box being checked off the
1934 // screen.
1935 self.reveal = false;
1936 }
1937
1938 /// The task block whose checkbox `at` landed in, in window coordinates.
1939 ///
1940 /// The box that painted rather than the marker column: a press in the
1941 /// gutter beside it is a press on the row, and still places a caret.
1942 fn checkbox_at(&self, at: gpui::Point<gpui::Pixels>) -> Option<usize> {
1943 let ix = self.layouts.block_at(at)?;
1944 self.layouts
1945 .checkbox_bounds(ix)
1946 .is_some_and(|bounds| bounds.contains(&at))
1947 .then_some(ix)
1948 }
1949
1950 /// The paragraph a document ending in a fence, a table, a rule or an image
1951 /// has no other way to grow: a fence swallows Enter, a cell and a caption
1952 /// have nowhere to put one, and a rule holds no caret at all. `false` when
1953 /// the last block ends in a body, which can carry on by itself.
1954 fn append_tail(&mut self, cx: &mut Context<Self>) -> bool {
1955 let Some(last) = self.doc.blocks.len().checked_sub(1) else {
1956 return false;
1957 };
1958 if !self.blocks() {
1959 return false;
1960 }
1961 if self.doc.blocks[last].parts().last() == Some(&Part::Body) {
1962 return false;
1963 }
1964 self.edit(EditKind::Structure, cx, |this| {
1965 this.doc
1966 .blocks
1967 .push(markdown::Block::new(BlockKind::Paragraph(Text::default())));
1968 let ix = this.doc.blocks.len() - 1;
1969 this.selection = Selection::at(Cursor::new(ix, Part::Body, 0).clamp(&this.doc));
1970 vec![]
1971 });
1972 true
1973 }
1974
1975 /// Press at `position` as if on the document: menus close, the editor
1976 /// takes focus, and the caret goes to the nearest place a caret can be —
1977 /// below the last block, above the first, or beside a line.
1978 ///
1979 /// For a host whose own frame around the editor should behave as the page. A
1980 /// press the editor's box already took is marked with
1981 /// [`Window::prevent_default`], and this ignores one so marked.
1982 pub fn press(
1983 &mut self,
1984 position: gpui::Point<gpui::Pixels>,
1985 click_count: usize,
1986 modifiers: gpui::Modifiers,
1987 window: &mut Window,
1988 cx: &mut Context<Self>,
1989 ) {
1990 if window.default_prevented() {
1991 return;
1992 }
1993 window.prevent_default();
1994 self.pressed(position, click_count, modifiers, window, cx);
1995 }
1996
1997 /// Whether a press is being dragged: a selection, a lifted block or an
1998 /// image resize.
1999 fn in_drag(&self) -> bool {
2000 self.dragging || self.lifted.is_some() || self.resizing.is_some()
2001 }
2002
2003 /// Follow a dragged pointer, wherever in the window it is.
2004 fn drag_to(&mut self, position: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
2005 // A lifted block follows the pointer.
2006 if let Some((from, _)) = self.lifted {
2007 if let Some(to) = self.layouts.block_at(position) {
2008 self.lifted = Some((from, to));
2009 cx.notify();
2010 }
2011 return;
2012 }
2013 // An image being resized follows the pointer the same way — the
2014 // document holds nothing until the handle is released.
2015 if let Some((ix, _)) = self.resizing {
2016 if let Some(width) = self.dragged_width(ix, position.x) {
2017 self.resizing = Some((ix, Some(width)));
2018 cx.notify();
2019 }
2020 return;
2021 }
2022 if self.dragging
2023 && let Some(hit) = self.layouts.hit(position)
2024 {
2025 self.selection = self.selection.extend_to(hit).clamp(&self.doc);
2026 cx.notify();
2027 }
2028 }
2029
2030 fn pressed(
2031 &mut self,
2032 position: gpui::Point<gpui::Pixels>,
2033 click_count: usize,
2034 modifiers: gpui::Modifiers,
2035 window: &mut Window,
2036 cx: &mut Context<Self>,
2037 ) {
2038 ui::popover::close_popup(self, cx, |this| &mut this.block_menu);
2039 ui::popover::close_popup(self, cx, |this| &mut this.language_menu);
2040 self.pasted = None;
2041 self.focus_handle.clone().focus(window, cx);
2042 // Ahead of the hit test, and returning without one: the
2043 // box is a control, and a caret dropped into the row
2044 // behind it would move the caret on every check.
2045 if let Some(ix) = self.checkbox_at(position) {
2046 self.toggle_task(ix, cx);
2047 return;
2048 }
2049 if self.tail_click(position, cx) {
2050 return;
2051 }
2052 let Some(hit) = self.layouts.hit(position) else {
2053 return cx.notify();
2054 };
2055 self.selection = match click_count {
2056 // Shift extends from wherever the anchor already is,
2057 // which is what makes click-then-shift-click a range.
2058 _ if modifiers.shift => self.selection.extend_to(hit),
2059 1 => Selection::at(hit),
2060 2 => Selection::new(hit.word_left(&self.doc), hit.word_right(&self.doc)),
2061 _ => Selection::new(hit.home(), hit.end(&self.doc)),
2062 }
2063 .clamp(&self.doc);
2064 self.dragging = click_count == 1 && !modifiers.shift;
2065 self.history.interrupt();
2066 self.caret_moved();
2067 // Only the editor sees the press, so only the editor can
2068 // say which anchor it landed on.
2069 if let Some(id) = self.anchor_at(position) {
2070 cx.emit(EditorEvent::AnchorActivated(id));
2071 }
2072 cx.notify();
2073 }
2074
2075 /// A click past the end of the document. Without this the document has no
2076 /// end: the click snaps back into the block above it, and what gets typed
2077 /// lands inside the code the reader was trying to escape.
2078 fn tail_click(&mut self, at: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) -> bool {
2079 let Some(last) = self.doc.blocks.len().checked_sub(1) else {
2080 return false;
2081 };
2082 let Some(bounds) = self.layouts.block_bounds(last) else {
2083 return false;
2084 };
2085 at.y > bounds.origin.y + bounds.size.height && self.append_tail(cx)
2086 }
2087
2088 /// Shift-tab in the source: take back up to one [`INDENT`] of the spaces
2089 /// before the caret, and nothing else — a line that is not indented has
2090 /// nothing to give.
2091 fn unindent(&mut self, cx: &mut Context<Self>) {
2092 let at = self.cursor();
2093 let Some(text) = self.caret_text() else {
2094 return;
2095 };
2096 let before = &text.text[..at.offset];
2097 let width = before.len() - before.trim_end_matches(' ').len();
2098 let width = width.min(INDENT.len());
2099 if width == 0 {
2100 return;
2101 }
2102 self.edit(EditKind::Delete, cx, |this| {
2103 let from = Cursor::new(at.block, at.part, at.offset - width);
2104 let splice = this.doc.replace(Selection::new(from, at), Text::default());
2105 this.selection = Selection::at(splice.caret.clamp(&this.doc));
2106 vec![Delta::Spliced(splice)]
2107 });
2108 }
2109
2110 /// The caret's text, for the input handler's offset arithmetic.
2111 fn caret_text(&self) -> Option<&Text> {
2112 let at = self.cursor();
2113 self.doc.blocks.get(at.block)?.text_at(at.part)
2114 }
2115}
2116
2117impl EventEmitter<EditorEvent> for Editor {}
2118
2119impl Focusable for Editor {
2120 fn focus_handle(&self, _: &App) -> FocusHandle {
2121 self.focus_handle.clone()
2122 }
2123}
2124
2125impl Render for Editor {
2126 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2127 let theme = Theme::of(cx).clone();
2128 let layout = Layout::of(cx);
2129 let focused = self.focus_handle.is_focused(window);
2130 // The only place the blink starts: `caret_moved` drops the task, so the
2131 // next render brings it back in phase, lit beat first.
2132 if focused && ui::input::caret_blink(cx) {
2133 if self.blink.is_none() {
2134 self.start_blink(cx);
2135 }
2136 } else {
2137 self.blink = None;
2138 self.caret_on = true;
2139 }
2140 let selection = focused.then_some(self.selection);
2141 // gpui ends an outside file drag — left the window or released
2142 // elsewhere — without a drop here, so the indicator goes with it.
2143 if !cx.has_active_drag() {
2144 self.dropping = None;
2145 }
2146
2147 // Typed text and IME reach an entity only through an input handler
2148 // registered during *paint*, against the bounds it should be anchored
2149 // to. There is no custom element here to do that from, so a zero-cost
2150 // canvas over the document supplies the paint phase. Without this the
2151 // key bindings still fire and nothing types.
2152 let handle = self.focus_handle.clone();
2153 let entity = cx.entity();
2154 let in_drag = self.in_drag();
2155 let input = canvas(
2156 |_, _, _| (),
2157 move |bounds, _, window, cx| {
2158 // `on_mouse_move` hears the pointer only over this box, and a
2159 // drag goes on past it. Registering it is paint's alone.
2160 if in_drag {
2161 let entity = entity.clone();
2162 window.on_mouse_event(move |event: &gpui::MouseMoveEvent, phase, _, cx| {
2163 if phase == gpui::DispatchPhase::Bubble && event.dragging() {
2164 entity.update(cx, |this, cx| this.drag_to(event.position, cx));
2165 }
2166 });
2167 }
2168 // The gutter handle is placed from positions recorded in window
2169 // coordinates, so the box they have to be measured against is
2170 // taken here — the one place that knows it.
2171 entity.update(cx, |this, _| {
2172 this.origin = bounds.origin;
2173 this.width = bounds.size.width;
2174 });
2175 window.handle_input(
2176 &handle,
2177 ElementInputHandler::new(bounds, entity.clone()),
2178 cx,
2179 );
2180 },
2181 )
2182 .absolute()
2183 .size_full();
2184
2185 // A tab stop, so the editor is reachable the same way every other
2186 // control in the library is.
2187 let handle = self.focus_handle.clone().tab_stop(true);
2188
2189 div()
2190 // Stateful only so the pointer leaving can be heard: `on_hover` is
2191 // what tells the gutter handle to stop pointing at a block the
2192 // pointer left behind.
2193 .id("bezel-editor")
2194 // The mark is what keeps `tab`: without it traversal answers the
2195 // key first and the caret never sees it.
2196 .key_context(key_context())
2197 .track_focus(&handle)
2198 // Tracking focus does not take it. Without this, clicking into the
2199 // document blurs the editor instead of putting a caret in it, and
2200 // the caret vanishes on the first click.
2201 .on_mouse_down(
2202 MouseButton::Left,
2203 cx.listener(|this, event: &gpui::MouseDownEvent, window, cx| {
2204 // The handle's own listener runs first and claims the
2205 // press; without the flag this would close the menu it
2206 // just opened. `ui::popover::Popup` solves it the same way.
2207 if std::mem::take(&mut this.press_claimed) {
2208 this.focus_handle.clone().focus(window, cx);
2209 } else {
2210 this.pressed(
2211 event.position,
2212 event.click_count,
2213 event.modifiers,
2214 window,
2215 cx,
2216 );
2217 }
2218 // What `press` reads to skip a press this box already
2219 // took. It also stops gpui's own focus transfer, which runs
2220 // after this listener, so both arms focus by hand.
2221 window.prevent_default();
2222 }),
2223 )
2224 // The drag has to be tracked from the container rather than from a
2225 // payload: a text selection has nothing to carry, and gpui's drag
2226 // payload is for things being dropped somewhere.
2227 .on_hover(cx.listener(|this, hovered: &bool, _, cx| {
2228 if !*hovered && this.hovered.take().is_some() {
2229 cx.notify();
2230 }
2231 }))
2232 .on_mouse_move(cx.listener(|this, event: &gpui::MouseMoveEvent, _, cx| {
2233 // Ahead of every drag branch below, because the pointer's shape
2234 // is about where it *is* rather than about what it is doing.
2235 let over_text = this.layouts.over_text(event.position);
2236 if over_text != this.over_text {
2237 this.over_text = over_text;
2238 cx.notify();
2239 }
2240 // A drag in flight is followed by the window-wide listener
2241 // `render` registers; otherwise the pointer only decides which
2242 // block wears the handle.
2243 if this.in_drag() {
2244 return;
2245 }
2246 let hovered = this.layouts.block_at(event.position);
2247 if hovered != this.hovered {
2248 this.hovered = hovered;
2249 cx.notify();
2250 }
2251 }))
2252 // Both, because a release can land anywhere on screen and only the
2253 // first fires over the editor. A resize left running would leave
2254 // its stand-in picture painted over the document for good.
2255 .on_mouse_up_out(
2256 MouseButton::Left,
2257 cx.listener(|this, _: &gpui::MouseUpEvent, window, cx| {
2258 this.dragging = false;
2259 this.drop_resize(window, cx);
2260 }),
2261 )
2262 .on_mouse_up(
2263 MouseButton::Left,
2264 cx.listener(|this, event: &gpui::MouseUpEvent, window, cx| {
2265 this.dragging = false;
2266 if this.drop_resize(window, cx) {
2267 return;
2268 }
2269 let Some((from, to)) = this.lifted.take() else {
2270 return;
2271 };
2272 if from == to {
2273 // A press that never moved is a click, and a click on
2274 // the handle is what opens the menu — unless that same
2275 // press is what dismissed it, which the note taken on
2276 // the way down is the only way to tell.
2277 if !this.block_menu.take_press_was_open() {
2278 this.block_menu.open((from, event.position));
2279 }
2280 return cx.notify();
2281 }
2282 this.edit(EditKind::Structure, cx, |this| {
2283 // `move_block` steps one sibling at a time, so a drop
2284 // several blocks away is that many steps. Bounded by
2285 // the block count, which no drag can exceed.
2286 let delta = if to > from { 1 } else { -1 };
2287 let mut at = from;
2288 // Each step is its own move, so each is its own delta —
2289 // folding them into one would have to compose the
2290 // hops, and they are already in order.
2291 let mut deltas = Vec::new();
2292 for _ in 0..this.doc.blocks.len() {
2293 let span = this.doc.subtree(at);
2294 let Some(next) = this.doc.move_block(at, delta) else {
2295 break;
2296 };
2297 deltas.push(Delta::Moved {
2298 at: span,
2299 to: Some(next),
2300 });
2301 at = next;
2302 if (delta > 0 && at >= to) || (delta < 0 && at <= to) {
2303 break;
2304 }
2305 }
2306 this.selection =
2307 Selection::at(Cursor::new(at, Part::Body, 0).clamp(&this.doc));
2308 deltas
2309 });
2310 }),
2311 )
2312 .on_action(cx.listener(Self::backspace))
2313 .on_action(cx.listener(Self::delete))
2314 .on_action(
2315 cx.listener(|this, _: &KillLine, _, cx| this.delete_to(true, Cursor::end, cx)),
2316 )
2317 .on_action(cx.listener(|this, _: &DeleteWordLeft, _, cx| {
2318 this.delete_to(false, Cursor::word_left, cx)
2319 }))
2320 .on_action(cx.listener(|this, _: &DeleteWordRight, _, cx| {
2321 this.delete_to(true, Cursor::word_right, cx)
2322 }))
2323 .on_action(cx.listener(|this, _: &DeleteToHome, _, cx| {
2324 this.delete_to(false, |at, _| at.home(), cx)
2325 }))
2326 .on_action(cx.listener(Self::split_block))
2327 .on_action(cx.listener(Self::soft_break))
2328 .on_action(cx.listener(Self::insert_paragraph))
2329 .on_action(cx.listener(Self::indent))
2330 .on_action(cx.listener(Self::increase_text_size))
2331 .on_action(cx.listener(Self::decrease_text_size))
2332 .on_action(cx.listener(Self::reset_text_size))
2333 .on_action(cx.listener(Self::outdent))
2334 .on_action(cx.listener(Self::dismiss))
2335 .on_action(cx.listener(Self::select_all))
2336 .on_action(cx.listener(Self::copy))
2337 .on_action(cx.listener(Self::cut))
2338 .on_action(cx.listener(Self::paste))
2339 .on_action(cx.listener(Self::undo))
2340 .on_action(cx.listener(Self::redo))
2341 .on_action(cx.listener(Self::confirm_url))
2342 .on_action(cx.listener(Self::cancel_url))
2343 // A file crossing the document lights the same indicator a lifted
2344 // block does, so a drop from outside lands where it looks like it
2345 // will.
2346 .on_drag_move(cx.listener(
2347 |this, event: &gpui::DragMoveEvent<gpui::ExternalPaths>, _, cx| {
2348 let at = event.event.position;
2349 let over = event
2350 .bounds
2351 .contains(&at)
2352 .then(|| this.layouts.block_at(at))
2353 .flatten();
2354 if over != this.dropping {
2355 this.dropping = over;
2356 cx.notify();
2357 }
2358 },
2359 ))
2360 .on_drop(
2361 cx.listener(|this, paths: &gpui::ExternalPaths, window, cx| {
2362 this.focus_handle.clone().focus(window, cx);
2363 this.drop_paths(paths, cx);
2364 }),
2365 )
2366 .on_action(cx.listener(|this, _: &ToggleBold, _, cx| this.toggle_mark(Mark::Bold, cx)))
2367 .on_action(
2368 cx.listener(|this, _: &ToggleItalic, _, cx| this.toggle_mark(Mark::Italic, cx)),
2369 )
2370 .on_action(
2371 cx.listener(|this, _: &ToggleStrike, _, cx| this.toggle_mark(Mark::Strike, cx)),
2372 )
2373 .on_action(cx.listener(|this, _: &ToggleCode, _, cx| this.toggle_mark(Mark::Code, cx)))
2374 .on_action(cx.listener(|this, _: &ToggleHighlight, _, cx| {
2375 if this.marks.delimiter(HIGHLIGHT_MARK).is_some() {
2376 this.toggle_mark(Mark::Custom(HIGHLIGHT_MARK.into()), cx);
2377 }
2378 }))
2379 .on_action(cx.listener(|this, _: &MoveBlockUp, _, cx| {
2380 this.move_block(this.cursor().block, -1, cx)
2381 }))
2382 .on_action(cx.listener(|this, _: &MoveBlockDown, _, cx| {
2383 this.move_block(this.cursor().block, 1, cx)
2384 }))
2385 .on_action(cx.listener(|this, _: &DuplicateBlock, _, cx| {
2386 this.duplicate_block(this.cursor().block, cx)
2387 }))
2388 .on_action(cx.listener(|this, _: &RemoveBlock, _, cx| {
2389 this.remove_block(this.cursor().block, cx)
2390 }))
2391 // Motion is one method with a `Cursor` function and an "extend"
2392 // flag, so a shift variant cannot drift from the key it shadows.
2393 .on_action(cx.listener(|this, _: &Left, _, cx| this.moved(false, Cursor::left, cx)))
2394 .on_action(cx.listener(|this, _: &Right, _, cx| this.moved(false, Cursor::right, cx)))
2395 .on_action(cx.listener(|this, _: &Up, _, cx| this.vertical(false, false, cx)))
2396 .on_action(cx.listener(|this, _: &Down, _, cx| this.vertical(true, false, cx)))
2397 .on_action(cx.listener(|this, _: &Home, _, cx| this.moved(false, line_home, cx)))
2398 .on_action(cx.listener(|this, _: &End, _, cx| this.moved(false, line_end, cx)))
2399 .on_action(cx.listener(|this, _: &DocumentStart, _, cx| {
2400 this.moved(false, |_, doc| Selection::all(doc).anchor, cx)
2401 }))
2402 .on_action(cx.listener(|this, _: &DocumentEnd, _, cx| {
2403 this.moved(false, |_, doc| Selection::all(doc).head, cx)
2404 }))
2405 .on_action(
2406 cx.listener(|this, _: &WordLeft, _, cx| this.moved(false, Cursor::word_left, cx)),
2407 )
2408 .on_action(
2409 cx.listener(|this, _: &WordRight, _, cx| this.moved(false, Cursor::word_right, cx)),
2410 )
2411 .on_action(
2412 cx.listener(|this, _: &SelectLeft, _, cx| this.moved(true, Cursor::left, cx)),
2413 )
2414 .on_action(
2415 cx.listener(|this, _: &SelectRight, _, cx| this.moved(true, Cursor::right, cx)),
2416 )
2417 .on_action(cx.listener(|this, _: &SelectUp, _, cx| this.vertical(false, true, cx)))
2418 .on_action(cx.listener(|this, _: &SelectDown, _, cx| this.vertical(true, true, cx)))
2419 .on_action(cx.listener(|this, _: &SelectHome, _, cx| this.moved(true, line_home, cx)))
2420 .on_action(cx.listener(|this, _: &SelectEnd, _, cx| this.moved(true, line_end, cx)))
2421 .on_action(cx.listener(|this, _: &SelectDocumentStart, _, cx| {
2422 this.moved(true, |_, doc| Selection::all(doc).anchor, cx)
2423 }))
2424 .on_action(cx.listener(|this, _: &SelectDocumentEnd, _, cx| {
2425 this.moved(true, |_, doc| Selection::all(doc).head, cx)
2426 }))
2427 .on_action(cx.listener(|this, _: &SelectWordLeft, _, cx| {
2428 this.moved(true, Cursor::word_left, cx)
2429 }))
2430 .on_action(cx.listener(|this, _: &SelectWordRight, _, cx| {
2431 this.moved(true, Cursor::word_right, cx)
2432 }))
2433 .w_full()
2434 // Text under the pointer, so the pointer says so — and only there,
2435 // or while a drag is still sweeping one out. The editor's box
2436 // reaches over its gutter, the margin beside a short line, a rule,
2437 // an image and a card, none of which a caret can be put into.
2438 // Where it has nothing to say it stays quiet rather than
2439 // overriding the page with an arrow of its own.
2440 .when(self.over_text || self.dragging, |el| {
2441 el.cursor(CursorStyle::IBeam)
2442 })
2443 // No focus ring. A ring says *widget*, and a document is not one —
2444 // the caret already paints only while focused, so a box around the
2445 // whole page is a second, louder signal for the same fact.
2446 .relative()
2447 .child(input)
2448 // The document is inset by the gutter so the handle has somewhere
2449 // to sit *inside* the editor. Outside it the handle is clipped by
2450 // any scrolling ancestor, and a pointer over it never reaches
2451 // `on_mouse_move`, which fires only while this element is the one
2452 // under the pointer.
2453 .child(
2454 div()
2455 .w_full()
2456 .pl(gpui::px(layout.text_inset))
2457 .child(match self.mode {
2458 // The source is one text, so it paints as one text —
2459 // the same caret, the same clicks, no block chrome to
2460 // suppress a piece at a time.
2461 Mode::Source => markdown::render_source(
2462 self.source_text(),
2463 markdown::Editing {
2464 selection,
2465 caret_on: self.caret_on,
2466 layouts: Some(&self.layouts),
2467 typography: Some(markdown::Typography::of(cx).scaled(
2468 text_size::resolve(self.text_size, cx)
2469 / theme::base_text_size(),
2470 )),
2471 ..Default::default()
2472 },
2473 cx,
2474 ),
2475 Mode::Blocks => markdown::render_with(
2476 &self.doc,
2477 markdown::Editing {
2478 selection,
2479 caret_on: self.caret_on,
2480 layouts: Some(&self.layouts),
2481 annotations: &self.annotations(),
2482 placeholder: focused.then(|| PLACEHOLDER.into()),
2483 // A caret goes into the caption here, so it is always
2484 // painted — an editor that could hide it would be
2485 // hiding a place you can already be typing.
2486 caption: markdown::Caption::Shown,
2487 // The size is absolute, so the factor the ladder
2488 // is already scaled by comes back out of it —
2489 // otherwise the app's size and this one multiply.
2490 typography: Some(markdown::Typography::of(cx).scaled(
2491 text_size::resolve(self.text_size, cx)
2492 / theme::base_text_size(),
2493 )),
2494 // The editor's own press hit-tests
2495 // `checkbox_bounds`, which is what keeps a
2496 // toggle in the undo history.
2497 toggle: Some(markdown::Toggle::HitTested),
2498 base: self.base.as_deref(),
2499 ..Default::default()
2500 },
2501 window,
2502 cx,
2503 ),
2504 }),
2505 )
2506 // Last, so the layouts it reads are this frame's rather than the
2507 // one before — children paint in order.
2508 .child(
2509 canvas(|_, _, _| (), {
2510 let entity = cx.entity();
2511 move |_, _, window, cx| {
2512 entity.update(cx, |this, cx| {
2513 this.reveal_caret(cx);
2514 this.settle_handle(window, cx);
2515 });
2516 }
2517 })
2518 .absolute()
2519 .size(gpui::px(0.0)),
2520 )
2521 .children(self.slash_menu(&theme, cx))
2522 .children(self.paste_menu(&theme, cx))
2523 .children(self.url_prompt(&theme, cx))
2524 .children(self.image_target(cx))
2525 .children(self.resize_preview())
2526 .children(self.handle(focused, &theme, cx))
2527 .children(self.resize_handle(&theme, cx))
2528 .children(self.drop_indicator(&theme))
2529 .children(self.language_chip(&theme, cx))
2530 .children(self.block_menu(&theme, cx))
2531 .children(self.language_menu(&theme, cx))
2532 }
2533}