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