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