Skip to main content

editor/
editor.rs

1//! The editing surface.
2//!
3//! The document is the single source of truth. There is no `TextField` per
4//! block: a selection is a pair of `Cursor`s into one `Doc`, which is what
5//! makes Enter split, Backspace merge and Tab indent into list operations
6//! rather than negotiations between separate widgets each owning a string.
7//!
8//! Everything about *what* an edit does lives in `markdown` — `edit` and
9//! `select` — and is tested there without a window. This crate owns only what
10//! needs one: a focus handle, key bindings, the platform input handler, and
11//! turning a click into a position.
12
13use gpui::{
14    App, Context, CursorStyle, ElementInputHandler, EventEmitter, FocusHandle, Focusable,
15    KeyContext, MouseButton, Render, Styled as _, Task, Window, canvas, div, prelude::*,
16};
17use markdown::{
18    Affinity, Annotation, AppExt as _, Block, BlockKind, BlockLayouts, Cursor, Doc, Form, Mark,
19    Part, Selection, Splice, Text, edit, edit::shortcut,
20};
21use std::{ops::Range, time::Duration};
22use theme::Theme;
23
24use crate::{
25    anchor::{Anchor, AnchorId, Delta},
26    history::{EditKind, History},
27    link::{self, Choice},
28    slash::{Slash, SlashAction, SlashAt},
29    text_size,
30};
31
32mod anchors;
33mod blocks;
34mod caret;
35mod clipboard;
36pub(crate) mod image;
37mod input;
38pub mod keys;
39pub(crate) mod menu;
40mod mode;
41mod pointer;
42mod render;
43mod sizing;
44mod table;
45mod typing;
46
47pub use keys::init;
48use keys::{
49    Backspace, Copy, Cut, DecreaseTextSize, Delete, DeleteToHome, DeleteWordLeft, DeleteWordRight,
50    Dismiss, DocumentEnd, DocumentStart, Down, DuplicateBlock, End, Home, IncreaseTextSize, Indent,
51    InsertParagraph, KillLine, Left, MoveBlockDown, MoveBlockUp, Outdent, Paste, Redo, RemoveBlock,
52    ResetTextSize, Right, SelectAll, SelectDocumentEnd, SelectDocumentStart, SelectDown, SelectEnd,
53    SelectHome, SelectLeft, SelectRight, SelectUp, SelectWordLeft, SelectWordRight, SoftBreak,
54    SplitBlock, ToggleBold, ToggleCode, ToggleHighlight, ToggleItalic, ToggleStrike, Undo, Up,
55    WordLeft, WordRight,
56};
57
58pub const CONTEXT: &str = "BezelEditor";
59
60/// The custom mark [`ToggleHighlight`] toggles. It does nothing until the app
61/// registers a mark under this name with [`markdown::AppExt::set_marks`].
62pub const HIGHLIGHT_MARK: &str = "highlight";
63
64/// [`CONTEXT`], which every binding in [`keys`] is scoped to, plus the mark
65/// that keeps `tab` for [`Editor::indent`].
66fn key_context() -> KeyContext {
67    let mut context = KeyContext::default();
68    context.add(CONTEXT);
69    context.add(ui::focus::CLAIMS_TAB);
70    context
71}
72
73/// What the editor tells its host about.
74///
75/// An app holding comment threads has to hear that the document moved, or its
76/// side of the pairing goes stale against anchors that did not. Split the way
77/// [`ui::input::FieldEvent`] is, so a listener takes only the half it needs.
78#[derive(Clone, Copy, Debug, PartialEq)]
79pub enum EditorEvent {
80    /// The document is different, and the anchors have been mapped through it.
81    Changed,
82    /// A click landed on an anchor's range.
83    AnchorActivated(AnchorId),
84    /// The editor switched between the document and its source, which a host
85    /// lighting its own toggle has no other way to hear about — the switch can
86    /// come from an undo as well as from the button.
87    ModeChanged(Mode),
88}
89
90/// Which form the document is being edited in.
91///
92/// The trigger is the app's: a button, a menu row, a chord of its own. What is
93/// here is the switch it calls, because the caret, the undo history and the
94/// focus have to survive it, and only the editor owns all three.
95#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
96pub enum Mode {
97    /// The document as it reads.
98    #[default]
99    Blocks,
100    /// The markdown a save would write, in one editable text.
101    Source,
102}
103
104/// Which of the editor's own affordances are on.
105///
106/// All of them unless an app says otherwise: a document with nothing
107/// discoverable on it is the wrong default for a library. Turning one off is
108/// for an app that puts its own in the same place — a bar with its own block
109/// menu does not want the gutter handle's as well — rather than for trimming.
110#[derive(Clone, Copy, Debug, PartialEq, Eq)]
111pub struct Chrome {
112    /// The gutter handle, the menu it opens, and dragging a block by it.
113    pub handle: bool,
114    /// The `/` menu at an empty block.
115    pub slash: bool,
116    /// A fence's language label, and the picker it opens.
117    pub language: bool,
118    /// The menu a pasted URL drops — leave it, or make a card, a chip or the
119    /// picture it points at.
120    pub paste: bool,
121    /// The mention menus over the app's [`crate::MentionSource`]s. Off by
122    /// default.
123    pub mention: bool,
124}
125
126impl Default for Chrome {
127    fn default() -> Self {
128        Self {
129            handle: true,
130            slash: true,
131            language: true,
132            paste: true,
133            mention: false,
134        }
135    }
136}
137
138/// What a toolbar reads to light itself, in one call.
139///
140/// Every field is a question a bar asks on every frame, and each was a separate
141/// reach into the document before: which marks are lit, what the block is
142/// called, whether cmd-E would fence, and whether any of it applies at all.
143/// Taken together so a bar cannot answer half of them from one frame and half
144/// from the next.
145#[derive(Clone, Debug, PartialEq, Eq)]
146pub struct Formatting {
147    /// Which form the document is in. In [`Mode::Source`] the markup is already
148    /// spelled out, so [`Self::marks`] is empty and a bar has nothing to light.
149    pub mode: Mode,
150    /// The marks the selection carries throughout — and at a collapsed caret,
151    /// the ones the next character typed would carry, cmd-B before typing
152    /// included.
153    pub marks: Vec<Mark>,
154    /// What the caret's block is called in [`turns`], or `None` for a block the
155    /// menu does not offer.
156    pub block: Option<gpui::SharedString>,
157    /// The quote the caret's block sits in — see [`quotes`].
158    pub quote: Option<markdown::Quoted>,
159    /// Whether [`Mark::Code`] here makes a fence out of the selection rather
160    /// than an inline span — the one chord whose meaning changes with what is
161    /// selected, and the one a bar cannot work out for itself.
162    pub fenceable: bool,
163}
164
165/// Every block a block can be turned into, and what each is called — the
166/// vocabulary the slash menu and the block menu both offer, for an app building
167/// a menu of its own. Pair with [`Editor::set_block`], which is what both of
168/// bezel's own menus call.
169pub fn turns() -> Vec<(gpui::SharedString, BlockKind)> {
170    crate::slash::items()
171}
172
173/// Every quote a block can be put in, and what each is called. Pair with
174/// [`Editor::toggle_quote`].
175pub fn quotes() -> Vec<(gpui::SharedString, Option<markdown::QuoteKind>)> {
176    crate::slash::quotes()
177}
178
179/// Shown on the focused block while it is empty — the only discoverable place
180/// to say that `/` does anything.
181const PLACEHOLDER: &str = "Type / for commands";
182
183/// What tab inserts in the source, where there are no blocks to indent. Two
184/// spaces, which is what markdown's own nesting is written in.
185const INDENT: &str = "  ";
186
187/// Half the caret's blink period — `ui::TextField`'s, which is the 500ms on,
188/// 500ms off macOS itself uses.
189const BLINK: Duration = Duration::from_millis(500);
190
191/// The handle's box. Wide enough to be hit without crowding the margin; how
192/// far left of the text it sits is [`Layout::text_inset`](crate::Layout).
193const HANDLE_SIZE: f32 = 18.0;
194
195/// How far a drag on an image's edge handle can shrink it — matches
196/// `markdown::render`'s `TABLE_MIN_COLUMN_WIDTH`, the same floor for the
197/// other block content a drag can resize.
198const MIN_IMAGE_WIDTH: f32 = 96.0;
199
200/// Whether a selection is prose covering more than one line — two blocks, or
201/// one line break inside a single block.
202///
203/// What a chord means about a selection is the editor's to decide; a [`Doc`]
204/// has no opinion about it. A selection reaching into a table or a fence is not
205/// prose and keeps the inline behaviour, because half a table has no lines to
206/// make a fence out of.
207fn fenceable(doc: &Doc, selection: Selection) -> bool {
208    let spans = doc.spans(selection);
209    let covered = |at: &Cursor, range: &Range<usize>| {
210        doc.blocks[at.block]
211            .text_at(at.part)
212            .and_then(|text| text.text.get(range.clone()))
213    };
214    spans.iter().all(|(at, _)| at.part == Part::Body)
215        && (spans.len() > 1
216            || spans
217                .iter()
218                .any(|(at, range)| covered(at, range).is_some_and(|text| text.contains('\n'))))
219}
220
221/// Give an empty document a block to hold a caret, and say whether one was
222/// needed.
223///
224/// A [`Doc`] with no blocks is a legitimate document — it is what `parse("")`
225/// returns — but it is not something that can be edited: nothing paints, so
226/// there is no caret and no placeholder, and neither a click nor a hit test
227/// has a target to find. An empty file would sit inert until something typed
228/// a block into existence, which is the one thing you cannot do with no caret.
229fn ensure_block(doc: &mut Doc) -> bool {
230    if !doc.blocks.is_empty() {
231        return false;
232    }
233    doc.blocks
234        .push(Block::new(BlockKind::Paragraph(Text::default())));
235    true
236}
237
238fn line_home(at: Cursor, doc: &Doc) -> Cursor {
239    let Some(text) = doc
240        .blocks
241        .get(at.block)
242        .and_then(|block| block.text_at(at.part))
243    else {
244        return at.home();
245    };
246    Cursor {
247        offset: ui::input::line_start(&text.text, at.offset.min(text.text.len())),
248        ..at
249    }
250}
251
252fn line_end(at: Cursor, doc: &Doc) -> Cursor {
253    let Some(text) = doc
254        .blocks
255        .get(at.block)
256        .and_then(|block| block.text_at(at.part))
257    else {
258        return at.end(doc);
259    };
260    Cursor {
261        offset: ui::input::line_end(&text.text, at.offset.min(text.text.len())),
262        ..at
263    }
264}
265
266/// The document a source view is edited as: one fence holding the markdown.
267///
268/// A fence rather than a paragraph because a fence is the block whose caret
269/// already behaves like a plain text editor's — Enter is a newline, nothing
270/// typed into it is markup, and its lines are laid out one per source line.
271fn source_doc(source: &str) -> Doc {
272    Doc {
273        blocks: vec![Block::new(BlockKind::Code {
274            language: Some(markdown::source::LANGUAGES[0].to_string()),
275            code: Text::plain(source),
276            height: None,
277        })],
278    }
279}
280
281/// A floating menu a block drops. A `Popup` rather than an `Option` for the
282/// exit phase, and for the press note: the card's `on_mouse_down_out` fires on
283/// the *press*, so without one a trigger's click on the *release* reopens what
284/// it just shut.
285pub(crate) type MenuPopup<T> = ui::popover::Popup<Dropped<T>>;
286
287/// What a dropped menu belongs to, where it hangs, and its live row.
288pub(crate) struct Dropped<T> {
289    pub of: T,
290    pub at: gpui::Point<gpui::Pixels>,
291    pub cursor: ui::menu::Cursor,
292}
293
294impl<T> Dropped<T> {
295    pub fn new(of: T, at: gpui::Point<gpui::Pixels>) -> Self {
296        Self {
297            of,
298            at,
299            cursor: ui::menu::Cursor::default(),
300        }
301    }
302}
303
304/// The row, column, or cell whose table menu is open.
305#[derive(Clone, Copy, PartialEq, Eq)]
306pub(crate) enum TableTarget {
307    Row(usize),
308    Column(usize),
309    Cell { row: usize, column: usize },
310}
311
312/// A table's row or column, as [`markdown::Part::Cell`] numbers them.
313#[derive(Clone, Copy, Debug, PartialEq, Eq)]
314pub(crate) enum Line {
315    Row(usize),
316    Column(usize),
317}
318
319/// Where a reveal leaves the caret's row in the scroll box.
320#[derive(Clone, Copy, Debug, PartialEq, Eq)]
321pub(crate) enum Reveal {
322    /// The least scroll that brings the row inside the box.
323    Nearest,
324    /// The row at the box's top edge, or as near it as the document's end
325    /// allows.
326    Top,
327}
328
329pub struct Editor {
330    doc: Doc,
331    /// The dialect this document is read and written in — the app's own marks,
332    /// taken once at construction. One editor, one spelling: a document that
333    /// changed dialect between a read and a write would rewrite itself.
334    marks: markdown::Marks,
335    /// Which of the editor's own affordances paint. The app's, so one document
336    /// can carry the lot and another none of it.
337    chrome: Chrome,
338    /// Which form the document is in. In [`Mode::Source`] `doc` is one fenced
339    /// block holding the markdown, so every operation below that is about
340    /// *blocks* asks [`Editor::blocks`] first.
341    mode: Mode,
342    /// Collapsed for an ordinary caret, so there is one position here rather
343    /// than a caret and a range that can disagree.
344    selection: Selection,
345    focus_handle: FocusHandle,
346    /// The IME composition range within the caret's text, underlined while it
347    /// is being composed.
348    marked: Option<Range<usize>>,
349    /// Where each text landed last frame, so a click can be turned into a
350    /// caret. Only paint knows this, so the renderer fills it.
351    layouts: BlockLayouts,
352    history: History,
353    /// Which half of the blink the caret is in. Flipped by [`Self::start_blink`].
354    caret_on: bool,
355    /// The blink, alive only while the document holds focus.
356    blink: Option<Task<()>>,
357    /// Comment ranges, mapped through every edit and snapshotted with the
358    /// document. Here rather than in the app because an undo restores a whole
359    /// document and leaves no delta an app could map its own copy through.
360    anchors: Vec<Anchor>,
361    /// Marks the next typed character will carry — cmd-B at a collapsed caret,
362    /// which otherwise has no range to apply to and so would do nothing.
363    /// Cleared by any motion, because they belong to a spot and not to a mood.
364    stored: Vec<Mark>,
365    /// The open slash menu, if `/` started one.
366    slash: Option<Slash>,
367    mention: Option<crate::mention::MentionMenu>,
368    /// The open paste menu, if a URL landed in a block of its own.
369    pasted: Option<link::Paste>,
370    /// The open prompt, if an image is waiting to be told where to look.
371    url_prompt: Option<image::Prompt>,
372    /// The block a file being dragged over the document would land after.
373    dropping: Option<usize>,
374    /// The block the pointer is over, which is the only one showing a handle.
375    hovered: Option<usize>,
376    /// The table cell the pointer is over, whose row and column show handles.
377    hovered_cell: Option<(usize, Part)>,
378    /// A table row's or column's menu: the table, the line and where it hangs.
379    table_drag: Option<table::TableDrag>,
380    table_dragged: bool,
381    table_menu: MenuPopup<(usize, TableTarget)>,
382    /// The right-click menu on text, with its rows as they stood when it
383    /// opened.
384    text_menu: MenuPopup<Vec<ui::menu::Item>>,
385    /// The right-click menu on a picture.
386    image_menu: MenuPopup<image::ImageTarget>,
387    /// A block being dragged by its handle, and where it would land.
388    lifted: Option<(usize, usize)>,
389    /// An image being dragged wider or narrower by its edge handle, and the
390    /// width it holds now — `None` being the natural one, exactly as the
391    /// document spells it. The document only learns the final width on
392    /// release, the same reason `lifted` waits for the drop; carrying the
393    /// document's own value here is what makes a press that never moved
394    /// read back as no change at all.
395    resizing: Option<(usize, Option<u32>)>,
396    /// A painted block being dragged taller or shorter by its bottom handle.
397    sizing: Option<sizing::Sizing>,
398    /// The block menu the handle opened, over the block and its rows.
399    block_menu: MenuPopup<(usize, crate::slash::Rows)>,
400    /// The language menu a fence's header opened, over its block.
401    language_menu: MenuPopup<usize>,
402    /// Set by a floating layer's press — the gutter handle, the URL prompt —
403    /// so the editor's own press does not undo what that press just did.
404    press_claimed: bool,
405    /// Where the editor's own box starts, so a position recorded in window
406    /// coordinates can be placed inside it, and how far it reaches — which is
407    /// how wide a resized image is allowed to be. Its own box rather than the
408    /// picture's: a picture already narrowed would otherwise be its own
409    /// ceiling, and no drag could ever widen it again.
410    origin: gpui::Point<gpui::Pixels>,
411    width: gpui::Pixels,
412    /// The press being dragged out: what it selects by, and what it selected.
413    dragging: Option<(ui::input::Granularity, Range<Cursor>)>,
414    /// Whether the pointer is over painted text, which is the only place the
415    /// editor claims an I-beam.
416    over_text: bool,
417    /// The host's scroll box, when it gave one, and the reveal the caret still
418    /// owes it.
419    scroll: Option<gpui::ScrollHandle>,
420    reveal: Option<Reveal>,
421    /// Where a held drag last was, in window coordinates.
422    drag_at: Option<gpui::Point<gpui::Pixels>>,
423    /// Scrolls [`Self::scroll`] while a drag is held past its top or bottom.
424    edge_scroll: Option<Task<()>>,
425    /// Where the gutter handle was placed this frame, so the frame after can
426    /// tell whether the block moved out from under it.
427    handle_at: Option<gpui::Point<gpui::Pixels>>,
428    /// The column held across consecutive vertical moves.
429    goal: Option<gpui::Pixels>,
430    /// The size the app set this document in, in points, or `None` to follow
431    /// the app's own text size. Absolute rather than a factor over the ladder,
432    /// so moving the interface size leaves a document set to 16pt at 16pt.
433    ///
434    /// What the chords move is the shared adjustment on top of this; the base
435    /// itself is the app's alone.
436    text_size: Option<f32>,
437    /// The directory relative image paths resolve against.
438    base: Option<std::path::PathBuf>,
439    image_overlay: Option<markdown::ImageOverlay>,
440    image_overlay_corner: markdown::ImageOverlayCorner,
441}
442
443impl Editor {
444    pub fn new(source: &str, cx: &mut Context<Self>) -> Self {
445        let marks = cx.marks();
446        let mut doc = markdown::parse_with(source, &marks);
447        ensure_block(&mut doc);
448        Self {
449            marks,
450            // Clamped, not defaulted: a document opening on a fence or a table
451            // has no body at block zero, and a caret claiming one resolves
452            // against nothing until something moves it.
453            selection: Selection::at(Cursor::default().clamp(&doc)),
454            doc,
455            chrome: Chrome::default(),
456            mode: Mode::default(),
457            focus_handle: cx.focus_handle(),
458            marked: None,
459            layouts: BlockLayouts::default(),
460            history: History::default(),
461            caret_on: true,
462            blink: None,
463            anchors: Vec::new(),
464            stored: Vec::new(),
465            slash: None,
466            mention: None,
467            pasted: None,
468            url_prompt: None,
469            dropping: None,
470            hovered: None,
471            hovered_cell: None,
472            table_menu: Default::default(),
473            text_menu: Default::default(),
474            image_menu: Default::default(),
475            table_drag: None,
476            table_dragged: false,
477            lifted: None,
478            resizing: None,
479            sizing: None,
480            block_menu: MenuPopup::default(),
481            language_menu: MenuPopup::default(),
482            press_claimed: false,
483            origin: gpui::Point::default(),
484            width: gpui::Pixels::ZERO,
485            dragging: None,
486            over_text: false,
487            scroll: None,
488            reveal: None,
489            drag_at: None,
490            edge_scroll: None,
491            goal: None,
492            handle_at: None,
493            text_size: None,
494            base: None,
495            image_overlay: None,
496            image_overlay_corner: markdown::ImageOverlayCorner::default(),
497        }
498    }
499
500    /// How many undo steps to keep. App-wide configuration would be a gpui
501    /// global alongside [`init`], not a `Theme` field — the theme is rebuilt on
502    /// every light/dark switch, which would quietly reset anything behavioural
503    /// parked in it.
504    pub fn with_undo_limit(mut self, limit: usize) -> Self {
505        self.history = History::with_limit(limit);
506        self
507    }
508
509    /// Read and write this document with marks of its own, rather than the ones
510    /// [`markdown::AppExt::set_marks`] installed. For an app whose editors do not all
511    /// speak the same dialect.
512    pub fn with_marks(mut self, marks: markdown::Marks) -> Self {
513        let source = self.source();
514        self.marks = marks;
515        self.doc = markdown::parse_with(&source, &self.marks);
516        ensure_block(&mut self.doc);
517        self.selection = self.selection.clamp(&self.doc);
518        self
519    }
520
521    /// Which of the editor's own affordances to paint. See [`Chrome`].
522    pub fn with_chrome(mut self, chrome: Chrome) -> Self {
523        self.chrome = chrome;
524        self
525    }
526
527    /// Adds an app-provided hover control to pictures in rich mode.
528    pub fn with_image_overlay(mut self, overlay: markdown::ImageOverlay) -> Self {
529        self.image_overlay = Some(overlay);
530        self
531    }
532
533    /// Replaces or removes the picture hover control.
534    pub fn set_image_overlay(
535        &mut self,
536        overlay: Option<markdown::ImageOverlay>,
537        cx: &mut Context<Self>,
538    ) {
539        self.image_overlay = overlay;
540        cx.notify();
541    }
542
543    /// Positions picture hover controls; bottom-right by default.
544    pub fn with_image_overlay_corner(mut self, corner: markdown::ImageOverlayCorner) -> Self {
545        self.image_overlay_corner = corner;
546        self
547    }
548
549    /// Repositions the picture hover control.
550    pub fn set_image_overlay_corner(
551        &mut self,
552        corner: markdown::ImageOverlayCorner,
553        cx: &mut Context<Self>,
554    ) {
555        self.image_overlay_corner = corner;
556        cx.notify();
557    }
558
559    /// What is painting now, for an app whose own bar mirrors it.
560    pub fn chrome(&self) -> Chrome {
561        self.chrome
562    }
563
564    /// Open in [`Mode::Source`] rather than on the document — an app whose
565    /// editor is a markdown file first. Nothing is recorded: this is where the
566    /// document starts, not a switch to step back over.
567    pub fn with_mode(mut self, mode: Mode) -> Self {
568        if mode != self.mode {
569            self.switch(mode);
570        }
571        self
572    }
573
574    /// Open the document at a size of its own, in points — the app's settings
575    /// field for prose. Unset, it is set at the app's own text size.
576    ///
577    /// The base, not the current size: `cmd-+` moves a shared adjustment over
578    /// this, and `cmd-0` clears that adjustment to come back here.
579    pub fn with_text_size(mut self, points: f32) -> Self {
580        self.text_size = Some(points);
581        self
582    }
583
584    /// Update the configured base size without changing the temporary zoom.
585    pub fn set_text_size(&mut self, points: f32, cx: &mut Context<Self>) {
586        if self.text_size != Some(points) {
587            self.text_size = Some(points);
588            cx.notify();
589        }
590    }
591
592    /// The base the app set, if any. Add
593    /// [`crate::AppExt::editor_text_size_adjustment`](crate::AppExt::editor_text_size_adjustment) for what is on
594    /// screen.
595    pub fn text_size(&self) -> Option<f32> {
596        self.text_size
597    }
598
599    /// Resolve relative image paths against `dir` — the document's own folder,
600    /// for a document that keeps its pictures beside it. The stored URL stays
601    /// as written.
602    pub fn with_base(mut self, dir: impl Into<std::path::PathBuf>) -> Self {
603        self.base = Some(dir.into());
604        self
605    }
606
607    /// Change or clear the directory relative image paths resolve against.
608    pub fn set_base(&mut self, dir: Option<std::path::PathBuf>, cx: &mut Context<Self>) {
609        if self.base != dir {
610            self.base = dir;
611            cx.notify();
612        }
613    }
614
615    /// The directory relative image paths resolve against, if one is set.
616    pub fn base(&self) -> Option<&std::path::Path> {
617        self.base.as_deref()
618    }
619
620    /// The box the document scrolls in, so typing off the bottom follows the
621    /// caret down and a drag held past its top or bottom scrolls it.
622    ///
623    /// The host's rather than the editor's: a document goes in whatever pane
624    /// the app gives it, and the gutter handle, the drop indicator and the
625    /// menus are all placed absolutely against this editor's own origin — put
626    /// the scroll box here and every one of them would be offset twice.
627    pub fn with_scroll(mut self, handle: gpui::ScrollHandle) -> Self {
628        self.scroll = Some(handle);
629        self
630    }
631
632    pub fn doc(&self) -> &Doc {
633        &self.doc
634    }
635
636    pub fn selection(&self) -> Selection {
637        self.selection
638    }
639
640    /// Put the selection somewhere — what a thread in a sidebar does when it is
641    /// clicked, and what a caret restored with a document needs.
642    ///
643    /// Clamped, because the caller's range came from somewhere the document may
644    /// have moved on from.
645    pub fn select(&mut self, selection: Selection, cx: &mut Context<Self>) {
646        self.select_revealing(selection, Reveal::Nearest, cx);
647    }
648
649    /// [`Self::select`], scrolling the caret's row to the top of the scroll
650    /// box — what a jump to a heading wants, from either side of it.
651    pub fn select_to_top(&mut self, selection: Selection, cx: &mut Context<Self>) {
652        self.select_revealing(selection, Reveal::Top, cx);
653    }
654
655    fn select_revealing(&mut self, selection: Selection, reveal: Reveal, cx: &mut Context<Self>) {
656        self.selection = selection.clamp(&self.doc);
657        self.history.interrupt();
658        self.reveal = Some(reveal);
659        self.caret_moved();
660        cx.notify();
661    }
662
663    /// Where the selection sits on screen, in window coordinates, so a host can
664    /// float a toolbar at it.
665    ///
666    /// The head's row only — a selection spanning ten blocks wants its bubble
667    /// where the pointer left off, not centred over the whole span. `None` when
668    /// nothing is selected or the caret has not painted yet.
669    /// Where everything landed last frame — blocks, pictures, a fence's
670    /// language label, and the row rects of any range.
671    ///
672    /// Handed out whole rather than a method per question: an app placing
673    /// chrome of its own asks the geometry, and which question it needs is not
674    /// this crate's to guess. See [`markdown::BlockLayouts`].
675    pub fn layouts(&self) -> &BlockLayouts {
676        &self.layouts
677    }
678
679    pub fn selection_bounds(&self) -> Option<gpui::Bounds<gpui::Pixels>> {
680        // The head alone. A bar centred over the whole selection wants
681        // `layouts().rects(selection)`, which is every painted row of it.
682        if self.selection.is_collapsed() {
683            return None;
684        }
685        let (point, line_height) = self
686            .layouts
687            .position(self.selection.head, self.selection.affinity)?;
688        Some(gpui::Bounds::new(
689            point,
690            gpui::size(gpui::px(0.0), line_height),
691        ))
692    }
693}
694
695impl EventEmitter<EditorEvent> for Editor {}
696
697impl Focusable for Editor {
698    fn focus_handle(&self, _: &App) -> FocusHandle {
699        self.focus_handle.clone()
700    }
701}