Skip to main content

editor/editor/
typing.rs

1//! Typing, deletion, block splits, indent and text size.
2
3use super::*;
4use crate::AppExt as _;
5
6impl Editor {
7    /// Replace whatever is selected with `text`, applying a markdown prefix if
8    /// one completes.
9    ///
10    /// Typing, backspace, delete and IME all land here, so none of them has to
11    /// ask whether a selection was empty.
12    pub(super) fn insert(&mut self, text: &str, cx: &mut Context<Self>) {
13        let items = crate::slash::installed(cx);
14        let trigger = crate::mention::Installed::source(cx, text);
15        self.edit(EditKind::Insert, cx, |this| {
16            let mut typed = Text::plain(text);
17            // A stored mark applies to what is typed next and to nothing else,
18            // so it is spent here.
19            for mark in this.stored.drain(..) {
20                typed.marks.push(markdown::MarkSpan {
21                    range: 0..typed.text.len(),
22                    mark,
23                });
24            }
25            let splice = this.doc.edit().replace(this.selection, typed);
26            this.selection = Selection::at(splice.caret.clamp(&this.doc));
27            let shortcut = this.apply_shortcut();
28            let promoted = this.promote_quote_marker();
29            let inline = this.apply_inline_rule();
30            this.track_slash(text, items);
31            this.track_mention(trigger);
32            std::iter::once(Delta::Spliced(splice))
33                .chain(shortcut)
34                .chain(promoted)
35                .chain(inline)
36                .collect()
37        });
38    }
39
40    /// Open the menu on a typed `/`, and keep its query in step afterwards.
41    ///
42    /// The query is the text between the `/` and the caret, so there is no
43    /// second field and no focus to hand over — typing filters because typing
44    /// is what it already was.
45    pub(super) fn track_slash(&mut self, typed: &str, items: Vec<crate::SlashItem>) {
46        if !self.chrome.slash {
47            return;
48        }
49        let at = self.cursor();
50        let text = self
51            .doc
52            .blocks
53            .get(at.block)
54            .and_then(|block| block.text_at(at.part))
55            .map(|text| text.text.clone())
56            .unwrap_or_default();
57
58        if self.slash.is_none() {
59            // Only a `/` that starts a word — a URL's slashes are not commands.
60            let opened = at.offset.checked_sub(1).filter(|_| typed == "/");
61            let starts_word = opened.is_none_or(|slash| {
62                text[..slash]
63                    .chars()
64                    .next_back()
65                    .is_none_or(char::is_whitespace)
66            });
67            // Only in a body: a fence holds its slash literally, and a caption
68            // belongs to a block that is already what it is.
69            if let Some(slash) = opened.filter(|_| starts_word && at.part == Part::Body) {
70                self.slash = Some(Slash::open(
71                    Cursor {
72                        offset: slash,
73                        ..at
74                    },
75                    items,
76                ));
77            }
78            return;
79        }
80
81        // Anything that leaves the run — a space, a click away, backspacing
82        // onto the slash — closes it.
83        let Some(query) = self.slash.as_ref().and_then(|slash| slash.query(at, &text)) else {
84            self.slash = None;
85            return;
86        };
87        if let Some(slash) = &mut self.slash {
88            slash.refilter(&query);
89        }
90    }
91
92    /// Open the mention menu on `typed`, a trigger just typed, when it starts
93    /// a word, and keep its query in step afterwards — [`Self::track_slash`]'s
94    /// rules.
95    pub(super) fn track_mention(&mut self, typed: Option<(char, crate::MentionSource)>) {
96        if !self.chrome.mention {
97            return;
98        }
99        let at = self.cursor();
100        let text = self
101            .doc
102            .blocks
103            .get(at.block)
104            .and_then(|block| block.text_at(at.part))
105            .map(|text| text.text.clone())
106            .unwrap_or_default();
107        let Some(menu) = &mut self.mention else {
108            let opened = typed.and_then(|typed| {
109                let sign = at.offset.checked_sub(typed.0.len_utf8())?;
110                let starts_word = text[..sign]
111                    .chars()
112                    .next_back()
113                    .is_none_or(char::is_whitespace);
114                (starts_word && at.part == Part::Body).then_some((sign, typed))
115            });
116            if let Some((sign, source)) = opened {
117                self.mention = Some(crate::mention::MentionMenu::open(
118                    Cursor { offset: sign, ..at },
119                    source,
120                ));
121            }
122            return;
123        };
124        let start = menu.at.offset + menu.trigger.len_utf8();
125        let query = (at.block == menu.at.block && at.part == menu.at.part && at.offset >= start)
126            .then(|| text.get(start..at.offset))
127            .flatten()
128            .filter(|query| !query.contains(char::is_whitespace));
129        match query {
130            Some(query) => menu.set_query(query.to_owned()),
131            None => self.mention = None,
132        }
133    }
134
135    /// Replace the trigger and query with a chip linking the row at `ix`, or the live
136    /// one, and a space after it.
137    pub(super) fn confirm_mention(&mut self, ix: Option<usize>, cx: &mut Context<Self>) -> bool {
138        let Some(menu) = self.mention.take() else {
139            return false;
140        };
141        let Some(row) = menu.row(ix).cloned() else {
142            cx.notify();
143            return true;
144        };
145        let (at, caret) = (menu.at, self.cursor());
146        let chip = Text {
147            text: format!("{} ", row.url),
148            marks: vec![markdown::MarkSpan {
149                range: 0..row.url.len(),
150                mark: markdown::Mark::Mention {
151                    url: row.url.clone(),
152                    form: markdown::Form::Chip,
153                },
154            }],
155        };
156        self.edit(EditKind::Insert, cx, |this| {
157            let splice = this.doc.edit().replace(Selection::new(at, caret), chip);
158            this.selection = Selection::at(splice.caret.clamp(&this.doc));
159            vec![Delta::Spliced(splice)]
160        });
161        true
162    }
163
164    /// Take the highlighted block, replacing the `/query` that summoned it.
165    /// Take `kind`, or the highlighted row when the caller names none — Enter
166    /// and a click are the same operation with a different source.
167    pub(super) fn confirm_slash(
168        &mut self,
169        path: Option<Vec<usize>>,
170        window: &mut Window,
171        cx: &mut Context<Self>,
172    ) -> bool {
173        let Some(slash) = &mut self.slash else {
174            return false;
175        };
176        if path.is_none() && slash.on_group() {
177            slash.descend();
178            cx.notify();
179            return true;
180        }
181        let Some(path) = path.or_else(|| slash.cursor.path()) else {
182            return false;
183        };
184        let (at, action) = (slash.at, slash.action_at(&path));
185        self.slash = None;
186        let Some(action) = action else {
187            return false;
188        };
189        let caret = self.cursor();
190        self.edit(EditKind::Structure, cx, |this| {
191            this.doc
192                .edit()
193                .edit_at(at, |text| text.remove(at.offset..caret.offset));
194            match &action {
195                SlashAction::Block(kind) => this.doc.edit().set_kind(at.block, kind.clone()),
196                SlashAction::Quote(alert) => this.doc.edit().toggle_quote(at.block, *alert),
197                SlashAction::Run(_) => {}
198            }
199            this.selection =
200                Selection::at(Cursor::new(at.block, Part::Body, at.offset).clamp(&this.doc));
201            vec![Delta::Spliced(Splice {
202                removed: Selection::new(at, caret),
203                caret: at,
204                blocks: 0,
205            })]
206        });
207        if let SlashAction::Run(run) = action {
208            // After this update ends: the app edits this editor.
209            let editor = cx.entity().downgrade();
210            window.defer(cx, move |window, cx| {
211                run(
212                    SlashAt {
213                        editor,
214                        block: at.block,
215                    },
216                    window,
217                    cx,
218                )
219            });
220        }
221        true
222    }
223
224    /// Collapse `**bold**` into a mark when its closing delimiter is typed.
225    ///
226    /// Runs after the insertion, on the text as it now stands, so a paste and a
227    /// keystroke reach it the same way.
228    pub(super) fn apply_inline_rule(&mut self) -> Vec<Delta> {
229        let at = self.cursor();
230        // Code is literal to its closing fence, and a caption holds no mark a
231        // `![...]` could spell.
232        if matches!(at.part, Part::Code | Part::Caption) {
233            return Vec::new();
234        }
235        let Some(text) = self
236            .doc
237            .blocks
238            .get(at.block)
239            .and_then(|block| block.text_at(at.part))
240        else {
241            return Vec::new();
242        };
243        let Some((open, inner, mark)) = edit::inline_rule(&text.text, at.offset) else {
244            return Vec::new();
245        };
246        let width = open.len();
247        let (opening, closing) = (open.clone(), inner.end..at.offset);
248        self.doc.edit().edit_at(at, |text| {
249            // The closing delimiter first — taking the opening one would move
250            // every offset after it.
251            text.remove(inner.end..at.offset);
252            text.remove(open);
253            text.toggle(inner.start - width..inner.end - width, mark);
254        });
255        self.selection =
256            Selection::at(Cursor::new(at.block, at.part, at.offset - 2 * width).clamp(&self.doc));
257        // In the order the two removals went. The opening delimiter is ahead of
258        // the closing one, so taking that one first left its offsets standing.
259        vec![Self::taken(at, closing), Self::taken(at, opening)]
260    }
261
262    /// Add `mark` over the selection, or take it away if the whole selection
263    /// already carries it. Public because a toolbar reaches the same operation
264    /// the key does.
265    ///
266    /// At a collapsed caret the mark is stored for the next character typed.
267    /// [`Mark::Code`] over a [`Formatting::fenceable`] selection makes a fence,
268    /// and with the caret in a fence takes the code back out. Does nothing in
269    /// [`Mode::Source`].
270    pub fn toggle_mark(&mut self, mark: Mark, cx: &mut Context<Self>) {
271        // Nothing in the source is a mark: the markup is already spelled out,
272        // and cmd-B over `**bold**` would fence what it reads.
273        if !self.blocks() {
274            return;
275        }
276        // A caret inside a fence is enough to leave one, so this is the mark
277        // that does not wait for a range: nothing typed into code is markup,
278        // which leaves a stored mark there nothing to mean.
279        let leaving_code = matches!(mark, Mark::Code) && self.cursor().part == Part::Code;
280        // With nothing selected there is no range to mark, so the mark waits
281        // for the next character — ProseMirror's stored marks, and the only way
282        // cmd-B before typing can mean anything.
283        if self.selection.is_collapsed() && !leaving_code {
284            match self.stored.iter().position(|stored| *stored == mark) {
285                Some(ix) => drop(self.stored.remove(ix)),
286                None => self.stored.push(mark),
287            }
288            return cx.notify();
289        }
290        let selection = self.selection;
291        self.edit(EditKind::Structure, cx, |this| {
292            // Code over more than one line is a fence, which is the only shape
293            // markdown has for it, and the same key is the way back out.
294            let before = this.doc.blocks.len();
295            // Fencing and unfencing rebuild the blocks the selection covered,
296            // so what sat inside it has no position left to keep.
297            let refenced = |doc: &Doc, head: Cursor| {
298                vec![Delta::Spliced(Splice {
299                    removed: selection,
300                    caret: head,
301                    blocks: doc.blocks.len() as isize - before as isize,
302                })]
303            };
304            if matches!(mark, Mark::Code) {
305                if let Some(head) = this.doc.edit().unfence(selection) {
306                    this.selection = Selection::at(head.clamp(&this.doc));
307                    return refenced(&this.doc, head);
308                }
309                if fenceable(&this.doc, selection) {
310                    let head = this.doc.edit().fence(selection);
311                    this.selection = Selection::at(head.clamp(&this.doc));
312                    return refenced(&this.doc, head);
313                }
314            }
315            // A mark is paint over text that does not move.
316            this.doc.edit().toggle_mark(selection, mark);
317            vec![]
318        });
319    }
320
321    /// Turn a typed prefix into the block it spells — `## ` into a heading.
322    ///
323    /// Runs after every insertion rather than only on space, because the
324    /// vocabulary includes prefixes that end in one (`- [ ] `) and prefixes
325    /// that do not (```` ``` ````).
326    pub(super) fn apply_shortcut(&mut self) -> Option<Delta> {
327        let at = self.cursor();
328        // A prefix is block syntax; inside a code fence or a table cell it is
329        // the literal text the author typed.
330        if at.part != Part::Body {
331            return None;
332        }
333        // Only a paragraph turns: in a heading or list item the prefix is
334        // text. `> ` quotes any block not already in a quote.
335        let block = self.doc.blocks.get(at.block)?;
336        let text = block.text_at(Part::Body)?;
337        // Only from the very start of a block, and only up to the caret: a
338        // `- ` typed in the middle of a sentence is a hyphen.
339        let (hit, len) = shortcut(&text.text)?;
340        let turns = match hit {
341            markdown::Shortcut::Quote => block.quote.is_none(),
342            _ => matches!(block.kind, BlockKind::Paragraph(_)),
343        };
344        if !turns || at.offset < len {
345            return None;
346        }
347        // Strip the prefix, then turn the block — the same two steps the slash
348        // menu takes, so a `## ` and a menu pick land in one place.
349        self.doc.edit().edit_at(at, |text| text.remove(0..len));
350        self.doc.edit().apply(at.block, hit);
351        self.selection =
352            Selection::at(Cursor::new(at.block, Part::Body, at.offset - len).clamp(&self.doc));
353        // The transformation is its own step: undo after typing `## Title`
354        // should give back the heading, not the paragraph before the hashes.
355        self.run.interrupt();
356        Some(Self::taken(at, 0..len))
357    }
358
359    /// Promote a quote whose block opens with a GFM alert marker into the
360    /// alert the parser would have produced from the same markdown.
361    ///
362    /// The marker line goes the way a block shortcut's prefix does, so what was
363    /// written under it stays as the alert's body.
364    pub(super) fn promote_quote_marker(&mut self) -> Option<Delta> {
365        let at = self.cursor();
366        if at.part != Part::Body {
367            return None;
368        }
369        let block = self.doc.blocks.get(at.block)?;
370        if block.quote.is_none_or(|quote| quote.alert.is_some()) {
371            return None;
372        }
373        let text = &block.text_at(Part::Body)?.text;
374        let first = text.split('\n').next().unwrap_or_default();
375        let kind = Self::quote_marker(first)?;
376        // The marker line, and the break after it where a body follows.
377        let len = first.len() + usize::from(text.len() > first.len());
378        self.doc.edit().edit_at(at, |text| text.remove(0..len));
379        self.doc.edit().set_alert(at.block, Some(kind));
380        self.selection = Selection::at(
381            Cursor::new(at.block, Part::Body, at.offset.saturating_sub(len)).clamp(&self.doc),
382        );
383        self.run.interrupt();
384        Some(Self::taken(at, 0..len))
385    }
386
387    /// The delta for `range` taken out of the text `at` is in — what an anchor
388    /// sitting in that text has to move through.
389    pub(super) fn taken(at: Cursor, range: Range<usize>) -> Delta {
390        let start = Cursor::new(at.block, at.part, range.start);
391        Delta::Spliced(Splice {
392            removed: Selection::new(start, Cursor::new(at.block, at.part, range.end)),
393            caret: start,
394            blocks: 0,
395        })
396    }
397
398    pub(super) fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Context<Self>) {
399        self.delete_back(cx);
400    }
401
402    pub(super) fn quote_marker(text: &str) -> Option<markdown::QuoteKind> {
403        [
404            markdown::QuoteKind::Note,
405            markdown::QuoteKind::Tip,
406            markdown::QuoteKind::Important,
407            markdown::QuoteKind::Warning,
408            markdown::QuoteKind::Caution,
409        ]
410        .into_iter()
411        .find(|kind| text.eq_ignore_ascii_case(kind.marker()))
412    }
413
414    /// Delete backwards: the selection if there is one, otherwise the character
415    /// before the caret, otherwise whatever the start of a block means.
416    ///
417    /// The kill chords land here too when they have nothing left to take within
418    /// the block, so reaching out of one is decided in a single place.
419    pub(super) fn delete_back(&mut self, cx: &mut Context<Self>) {
420        let at = self.cursor();
421        // Reaching out of a block is structural; taking a character is not.
422        let kind = if self.selection.is_collapsed() && at.offset == 0 {
423            EditKind::Structure
424        } else {
425            EditKind::Delete
426        };
427        self.edit(kind, cx, |this| {
428            let before = this.doc.blocks.len();
429            let splice = if !this.selection.is_collapsed() {
430                this.doc.edit().replace(this.selection, Text::default())
431            } else if at.offset > 0 {
432                let from = at.left(&this.doc);
433                this.doc
434                    .edit()
435                    .replace(Selection::new(from, at), Text::default())
436            } else {
437                // `merge_back` outdents, unmarkers, unfences or merges —
438                // whichever the block's state calls for — and says where the
439                // caret landed.
440                match this.doc.edit().merge_back(at) {
441                    // The seam between where the caret was and where it landed
442                    // is exactly what the merge closed up.
443                    Some(head) => Splice {
444                        removed: Selection::new(head, at),
445                        caret: head,
446                        blocks: this.doc.blocks.len() as isize - before as isize,
447                    },
448                    None => return vec![],
449                }
450            };
451            let head = splice.caret;
452            this.selection = Selection::at(head.clamp(&this.doc));
453            // Deleting narrows the query too, and backspacing onto the slash
454            // itself is what closes the menu.
455            this.track_slash("", Vec::new());
456            this.track_mention(None);
457            vec![Delta::Spliced(splice)]
458        });
459    }
460
461    pub(super) fn delete(&mut self, _: &Delete, _: &mut Window, cx: &mut Context<Self>) {
462        self.delete_forward(cx);
463    }
464
465    /// Delete forwards, joining the next block when the caret is at the end of
466    /// this one — which is what a kill to the end of a line does there too.
467    pub(super) fn delete_forward(&mut self, cx: &mut Context<Self>) {
468        self.edit(EditKind::Delete, cx, |this| {
469            let at = this.cursor();
470            let range = if this.selection.is_collapsed() {
471                Selection::new(at, at.right(&this.doc))
472            } else {
473                this.selection
474            };
475            let splice = this.doc.edit().replace(range, Text::default());
476            this.selection = Selection::at(splice.caret.clamp(&this.doc));
477            vec![Delta::Spliced(splice)]
478        });
479    }
480
481    /// Whether an open menu answered Enter itself.
482    ///
483    /// Every Enter chord asks first, or picking a block would also edit the one
484    /// it is turning — and a chord the menu never sees leaves it open over a
485    /// query the caret has walked away from.
486    pub(super) fn menu_took_enter(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
487        if let Some(pasted) = &self.pasted {
488            if let Some(choice) = pasted.choice(None) {
489                self.confirm_paste(choice, cx);
490            }
491            return true;
492        }
493        if self.dropdown_enter(window, cx) {
494            return true;
495        }
496        if self.mention.is_some() {
497            return self.confirm_mention(None, cx);
498        }
499        self.confirm_slash(None, window, cx)
500    }
501
502    /// Enter. In a body it splits the block; in a code fence it is a newline,
503    /// which is the whole reason a fence is worth typing into.
504    pub(super) fn split_block(
505        &mut self,
506        _: &SplitBlock,
507        window: &mut Window,
508        cx: &mut Context<Self>,
509    ) {
510        if self.menu_took_enter(window, cx) {
511            return;
512        }
513        let at = self.cursor();
514        match at.part {
515            Part::Code => return self.insert("\n", cx),
516            // A cell is one line by definition: Enter goes down a row.
517            Part::Cell { .. } => {
518                self.cell_down(cx);
519                return;
520            }
521            // An image with nothing to show yet is missing one thing, so Enter
522            // asks for it rather than carrying on past a blank.
523            Part::Caption
524                if matches!(
525                    self.doc.blocks.get(at.block).map(|block| &block.kind),
526                    Some(BlockKind::Image { url, .. }) if url.is_empty()
527                ) =>
528            {
529                return self.prompt_for_url(at.block, window, cx);
530            }
531            // A caption is one line too, but the block it belongs to is the
532            // end of something — so Enter carries on underneath the picture,
533            // which is [`Doc::split`]'s answer for a block with no body.
534            Part::Caption | Part::Body => {}
535        }
536        // Enter in an empty block steps out of whatever holds it.
537        let empty = self.selection.is_collapsed()
538            && at.part == Part::Body
539            && self
540                .doc
541                .blocks
542                .get(at.block)
543                .and_then(|block| block.text_at(Part::Body))
544                .is_some_and(Text::is_empty);
545        self.edit(EditKind::Structure, cx, |this| {
546            if empty && this.doc.edit().peel(at.block) {
547                this.selection = Selection::at(at.clamp(&this.doc));
548                return vec![];
549            }
550            let mut deltas = Vec::new();
551            if !this.selection.is_collapsed() {
552                let splice = this.doc.edit().replace(this.selection, Text::default());
553                this.selection = Selection::at(splice.caret.clamp(&this.doc));
554                deltas.push(Delta::Spliced(splice));
555            }
556            let at = this.cursor();
557            let new = this.doc.edit().split(at.block, at.offset);
558            this.selection = Selection::at(Cursor::new(new, Part::Body, 0).clamp(&this.doc));
559            // What followed the caret moved into a block of its own, which
560            // everything below it now sits under.
561            deltas.push(Delta::Spliced(Splice {
562                removed: Selection::at(at),
563                caret: Cursor::new(new, Part::Body, 0),
564                blocks: 1,
565            }));
566            deltas
567        });
568    }
569
570    /// Shift+Enter. In prose it keeps the caret in the block and inserts a
571    /// literal newline; the places markdown itself keeps to one line still do.
572    pub(super) fn soft_break(
573        &mut self,
574        _: &SoftBreak,
575        window: &mut Window,
576        cx: &mut Context<Self>,
577    ) {
578        if self.menu_took_enter(window, cx) {
579            return;
580        }
581        match self.cursor().part {
582            Part::Body | Part::Code => self.insert("\n", cx),
583            Part::Caption | Part::Cell { .. } => {}
584        }
585    }
586
587    /// Ctrl+Enter. Open a plain paragraph after the current block's whole
588    /// subtree and leave the current block's text untouched.
589    pub(super) fn insert_paragraph(
590        &mut self,
591        _: &InsertParagraph,
592        window: &mut Window,
593        cx: &mut Context<Self>,
594    ) {
595        if self.menu_took_enter(window, cx) {
596            return;
597        }
598        if !self.blocks() {
599            return;
600        }
601        self.edit(EditKind::Structure, cx, |this| {
602            let at = this.cursor().block;
603            let insert_at = this.doc.subtree(at).end;
604            let indent = this.doc.blocks.get(at).map_or(0, |block| block.indent);
605            this.doc.edit().blocks.insert(
606                insert_at,
607                Block::at(BlockKind::Paragraph(Text::default()), indent),
608            );
609            this.doc.edit().repair();
610            this.selection = Selection::at(Cursor::new(insert_at, Part::Body, 0).clamp(&this.doc));
611            vec![Delta::Opened {
612                at: insert_at,
613                count: 1,
614            }]
615        });
616    }
617
618    pub(super) fn indent(&mut self, _: &Indent, _: &mut Window, cx: &mut Context<Self>) {
619        // In the source there is one block to indent and indenting it would be
620        // invisible, so tab is what it is in any text editor: two spaces.
621        if !self.blocks() {
622            return self.insert(INDENT, cx);
623        }
624        if self.step_cell(true, cx) {
625            return;
626        }
627        self.edit(EditKind::Structure, cx, |this| {
628            let block = this.cursor().block;
629            this.doc.edit().indent(block);
630            vec![]
631        });
632    }
633
634    pub(super) fn outdent(&mut self, _: &Outdent, _: &mut Window, cx: &mut Context<Self>) {
635        if !self.blocks() {
636            return self.unindent(cx);
637        }
638        if self.step_cell(false, cx) {
639            return;
640        }
641        self.edit(EditKind::Structure, cx, |this| {
642            let block = this.cursor().block;
643            this.doc.edit().outdent(block);
644            vec![]
645        });
646    }
647
648    pub(super) fn increase_text_size(
649        &mut self,
650        _: &IncreaseTextSize,
651        _: &mut Window,
652        cx: &mut Context<Self>,
653    ) {
654        self.step_text_size(cx.editor_text_size().step, cx);
655    }
656
657    pub(super) fn decrease_text_size(
658        &mut self,
659        _: &DecreaseTextSize,
660        _: &mut Window,
661        cx: &mut Context<Self>,
662    ) {
663        self.step_text_size(-cx.editor_text_size().step, cx);
664    }
665
666    pub(super) fn reset_text_size(
667        &mut self,
668        _: &ResetTextSize,
669        _: &mut Window,
670        cx: &mut Context<Self>,
671    ) {
672        cx.reset_editor_text_size();
673    }
674
675    /// Sizing is not an edit: it changes nothing about the document, so it
676    /// leaves no undo step and no anchor moves.
677    ///
678    /// The step is taken against *this* document's size and stored back as the
679    /// shared adjustment, so a press at the end of the range banks up nothing
680    /// to work back through on the way down.
681    pub(super) fn step_text_size(&mut self, by: f32, cx: &mut Context<Self>) {
682        let base = self.text_size.unwrap_or_else(theme::base_text_size);
683        let next = cx
684            .editor_text_size()
685            .clamp(text_size::resolve(self.text_size, cx) + by);
686        text_size::set_adjustment(next - base, cx);
687    }
688
689    /// Escape closes an open menu, and otherwise collapses a selection — the
690    /// things there are to back out of, innermost first.
691    pub(super) fn dismiss(&mut self, _: &Dismiss, _: &mut Window, cx: &mut Context<Self>) {
692        if self.table_drag.take().is_some() {
693            self.table_dragged = true;
694            cx.notify();
695            return;
696        }
697        if self.pasted.take().is_none()
698            && self.slash.take().is_none()
699            && self.mention.take().is_none()
700            && !self.dropdown_dismiss(cx)
701        {
702            self.selection = Selection::at(self.selection.head);
703        }
704        cx.notify();
705    }
706}