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