1use super::*;
4use crate::AppExt as _;
5
6impl Editor {
7 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 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 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 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 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 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 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 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 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 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 pub(super) fn apply_inline_rule(&mut self) -> Vec<Delta> {
229 let at = self.cursor();
230 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 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 vec![Self::taken(at, closing), Self::taken(at, opening)]
260 }
261
262 pub fn toggle_mark(&mut self, mark: Mark, cx: &mut Context<Self>) {
271 if !self.blocks() {
274 return;
275 }
276 let leaving_code = matches!(mark, Mark::Code) && self.cursor().part == Part::Code;
280 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 let before = this.doc.blocks.len();
295 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 this.doc.edit().toggle_mark(selection, mark);
317 vec![]
318 });
319 }
320
321 pub(super) fn apply_shortcut(&mut self) -> Option<Delta> {
327 let at = self.cursor();
328 if at.part != Part::Body {
331 return None;
332 }
333 let block = self.doc.blocks.get(at.block)?;
336 let text = block.text_at(Part::Body)?;
337 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 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 self.run.interrupt();
356 Some(Self::taken(at, 0..len))
357 }
358
359 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 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 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 pub(super) fn delete_back(&mut self, cx: &mut Context<Self>) {
420 let at = self.cursor();
421 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 match this.doc.edit().merge_back(at) {
441 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 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 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 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 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 Part::Cell { .. } => {
518 self.cell_down(cx);
519 return;
520 }
521 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 Part::Caption | Part::Body => {}
535 }
536 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 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 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 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 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 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 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}