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>) {
266 if !self.blocks() {
269 return;
270 }
271 let leaving_code = matches!(mark, Mark::Code) && self.cursor().part == Part::Code;
275 if self.selection.is_collapsed() && !leaving_code {
279 match self.stored.iter().position(|stored| *stored == mark) {
280 Some(ix) => drop(self.stored.remove(ix)),
281 None => self.stored.push(mark),
282 }
283 return cx.notify();
284 }
285 let selection = self.selection;
286 self.edit(EditKind::Structure, cx, |this| {
287 let before = this.doc.blocks.len();
290 let refenced = |doc: &Doc, head: Cursor| {
293 vec![Delta::Spliced(Splice {
294 removed: selection,
295 caret: head,
296 blocks: doc.blocks.len() as isize - before as isize,
297 })]
298 };
299 if matches!(mark, Mark::Code) {
300 if let Some(head) = this.doc.edit().unfence(selection) {
301 this.selection = Selection::at(head.clamp(&this.doc));
302 return refenced(&this.doc, head);
303 }
304 if fenceable(&this.doc, selection) {
305 let head = this.doc.edit().fence(selection);
306 this.selection = Selection::at(head.clamp(&this.doc));
307 return refenced(&this.doc, head);
308 }
309 }
310 this.doc.edit().toggle_mark(selection, mark);
312 vec![]
313 });
314 }
315
316 pub(super) fn apply_shortcut(&mut self) -> Option<Delta> {
322 let at = self.cursor();
323 if at.part != Part::Body {
326 return None;
327 }
328 let block = self.doc.blocks.get(at.block)?;
331 let text = block.text_at(Part::Body)?;
332 let (hit, len) = shortcut(&text.text)?;
335 let turns = match hit {
336 markdown::Shortcut::Quote => block.quote.is_none(),
337 _ => matches!(block.kind, BlockKind::Paragraph(_)),
338 };
339 if !turns || at.offset < len {
340 return None;
341 }
342 self.doc.edit().edit_at(at, |text| text.remove(0..len));
345 self.doc.edit().apply(at.block, hit);
346 self.selection =
347 Selection::at(Cursor::new(at.block, Part::Body, at.offset - len).clamp(&self.doc));
348 self.run.interrupt();
351 Some(Self::taken(at, 0..len))
352 }
353
354 pub(super) fn promote_quote_marker(&mut self) -> Option<Delta> {
360 let at = self.cursor();
361 if at.part != Part::Body {
362 return None;
363 }
364 let block = self.doc.blocks.get(at.block)?;
365 if block.quote.is_none_or(|quote| quote.alert.is_some()) {
366 return None;
367 }
368 let text = &block.text_at(Part::Body)?.text;
369 let first = text.split('\n').next().unwrap_or_default();
370 let kind = Self::quote_marker(first)?;
371 let len = first.len() + usize::from(text.len() > first.len());
373 self.doc.edit().edit_at(at, |text| text.remove(0..len));
374 self.doc.edit().set_alert(at.block, Some(kind));
375 self.selection = Selection::at(
376 Cursor::new(at.block, Part::Body, at.offset.saturating_sub(len)).clamp(&self.doc),
377 );
378 self.run.interrupt();
379 Some(Self::taken(at, 0..len))
380 }
381
382 pub(super) fn taken(at: Cursor, range: Range<usize>) -> Delta {
385 let start = Cursor::new(at.block, at.part, range.start);
386 Delta::Spliced(Splice {
387 removed: Selection::new(start, Cursor::new(at.block, at.part, range.end)),
388 caret: start,
389 blocks: 0,
390 })
391 }
392
393 pub(super) fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Context<Self>) {
394 self.delete_back(cx);
395 }
396
397 pub(super) fn quote_marker(text: &str) -> Option<markdown::QuoteKind> {
398 [
399 markdown::QuoteKind::Note,
400 markdown::QuoteKind::Tip,
401 markdown::QuoteKind::Important,
402 markdown::QuoteKind::Warning,
403 markdown::QuoteKind::Caution,
404 ]
405 .into_iter()
406 .find(|kind| text.eq_ignore_ascii_case(kind.marker()))
407 }
408
409 pub(super) fn delete_back(&mut self, cx: &mut Context<Self>) {
415 let at = self.cursor();
416 let kind = if self.selection.is_collapsed() && at.offset == 0 {
418 EditKind::Structure
419 } else {
420 EditKind::Delete
421 };
422 self.edit(kind, cx, |this| {
423 let before = this.doc.blocks.len();
424 let splice = if !this.selection.is_collapsed() {
425 this.doc.edit().replace(this.selection, Text::default())
426 } else if at.offset > 0 {
427 let from = at.left(&this.doc);
428 this.doc
429 .edit()
430 .replace(Selection::new(from, at), Text::default())
431 } else {
432 match this.doc.edit().merge_back(at) {
436 Some(head) => Splice {
439 removed: Selection::new(head, at),
440 caret: head,
441 blocks: this.doc.blocks.len() as isize - before as isize,
442 },
443 None => return vec![],
444 }
445 };
446 let head = splice.caret;
447 this.selection = Selection::at(head.clamp(&this.doc));
448 this.track_slash("", Vec::new());
451 this.track_mention(None);
452 vec![Delta::Spliced(splice)]
453 });
454 }
455
456 pub(super) fn delete(&mut self, _: &Delete, _: &mut Window, cx: &mut Context<Self>) {
457 self.delete_forward(cx);
458 }
459
460 pub(super) fn delete_forward(&mut self, cx: &mut Context<Self>) {
463 self.edit(EditKind::Delete, cx, |this| {
464 let at = this.cursor();
465 let range = if this.selection.is_collapsed() {
466 Selection::new(at, at.right(&this.doc))
467 } else {
468 this.selection
469 };
470 let splice = this.doc.edit().replace(range, Text::default());
471 this.selection = Selection::at(splice.caret.clamp(&this.doc));
472 vec![Delta::Spliced(splice)]
473 });
474 }
475
476 pub(super) fn menu_took_enter(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
482 if let Some(pasted) = &self.pasted {
483 if let Some(choice) = pasted.choice(None) {
484 self.confirm_paste(choice, cx);
485 }
486 return true;
487 }
488 if self.dropdown_enter(window, cx) {
489 return true;
490 }
491 if self.mention.is_some() {
492 return self.confirm_mention(None, cx);
493 }
494 self.confirm_slash(None, window, cx)
495 }
496
497 pub(super) fn split_block(
500 &mut self,
501 _: &SplitBlock,
502 window: &mut Window,
503 cx: &mut Context<Self>,
504 ) {
505 if self.menu_took_enter(window, cx) {
506 return;
507 }
508 let at = self.cursor();
509 match at.part {
510 Part::Code => return self.insert("\n", cx),
511 Part::Cell { .. } => {
513 self.cell_down(cx);
514 return;
515 }
516 Part::Caption
519 if matches!(
520 self.doc.blocks.get(at.block).map(|block| &block.kind),
521 Some(BlockKind::Image { url, .. }) if url.is_empty()
522 ) =>
523 {
524 return self.prompt_for_url(at.block, window, cx);
525 }
526 Part::Caption | Part::Body => {}
530 }
531 let empty = self.selection.is_collapsed()
533 && at.part == Part::Body
534 && self
535 .doc
536 .blocks
537 .get(at.block)
538 .and_then(|block| block.text_at(Part::Body))
539 .is_some_and(Text::is_empty);
540 self.edit(EditKind::Structure, cx, |this| {
541 if empty && this.doc.edit().peel(at.block) {
542 this.selection = Selection::at(at.clamp(&this.doc));
543 return vec![];
544 }
545 let mut deltas = Vec::new();
546 if !this.selection.is_collapsed() {
547 let splice = this.doc.edit().replace(this.selection, Text::default());
548 this.selection = Selection::at(splice.caret.clamp(&this.doc));
549 deltas.push(Delta::Spliced(splice));
550 }
551 let at = this.cursor();
552 let new = this.doc.edit().split(at.block, at.offset);
553 this.selection = Selection::at(Cursor::new(new, Part::Body, 0).clamp(&this.doc));
554 deltas.push(Delta::Spliced(Splice {
557 removed: Selection::at(at),
558 caret: Cursor::new(new, Part::Body, 0),
559 blocks: 1,
560 }));
561 deltas
562 });
563 }
564
565 pub(super) fn soft_break(
568 &mut self,
569 _: &SoftBreak,
570 window: &mut Window,
571 cx: &mut Context<Self>,
572 ) {
573 if self.menu_took_enter(window, cx) {
574 return;
575 }
576 match self.cursor().part {
577 Part::Body | Part::Code => self.insert("\n", cx),
578 Part::Caption | Part::Cell { .. } => {}
579 }
580 }
581
582 pub(super) fn insert_paragraph(
585 &mut self,
586 _: &InsertParagraph,
587 window: &mut Window,
588 cx: &mut Context<Self>,
589 ) {
590 if self.menu_took_enter(window, cx) {
591 return;
592 }
593 if !self.blocks() {
594 return;
595 }
596 self.edit(EditKind::Structure, cx, |this| {
597 let at = this.cursor().block;
598 let insert_at = this.doc.subtree(at).end;
599 let indent = this.doc.blocks.get(at).map_or(0, |block| block.indent);
600 this.doc.edit().blocks.insert(
601 insert_at,
602 Block::at(BlockKind::Paragraph(Text::default()), indent),
603 );
604 this.doc.edit().repair();
605 this.selection = Selection::at(Cursor::new(insert_at, Part::Body, 0).clamp(&this.doc));
606 vec![Delta::Opened {
607 at: insert_at,
608 count: 1,
609 }]
610 });
611 }
612
613 pub(super) fn indent(&mut self, _: &Indent, _: &mut Window, cx: &mut Context<Self>) {
614 if !self.blocks() {
617 return self.insert(INDENT, cx);
618 }
619 if self.step_cell(true, cx) {
620 return;
621 }
622 self.edit(EditKind::Structure, cx, |this| {
623 let block = this.cursor().block;
624 this.doc.edit().indent(block);
625 vec![]
626 });
627 }
628
629 pub(super) fn outdent(&mut self, _: &Outdent, _: &mut Window, cx: &mut Context<Self>) {
630 if !self.blocks() {
631 return self.unindent(cx);
632 }
633 if self.step_cell(false, cx) {
634 return;
635 }
636 self.edit(EditKind::Structure, cx, |this| {
637 let block = this.cursor().block;
638 this.doc.edit().outdent(block);
639 vec![]
640 });
641 }
642
643 pub(super) fn increase_text_size(
644 &mut self,
645 _: &IncreaseTextSize,
646 _: &mut Window,
647 cx: &mut Context<Self>,
648 ) {
649 self.step_text_size(cx.editor_text_size().step, cx);
650 }
651
652 pub(super) fn decrease_text_size(
653 &mut self,
654 _: &DecreaseTextSize,
655 _: &mut Window,
656 cx: &mut Context<Self>,
657 ) {
658 self.step_text_size(-cx.editor_text_size().step, cx);
659 }
660
661 pub(super) fn reset_text_size(
662 &mut self,
663 _: &ResetTextSize,
664 _: &mut Window,
665 cx: &mut Context<Self>,
666 ) {
667 cx.reset_editor_text_size();
668 }
669
670 pub(super) fn step_text_size(&mut self, by: f32, cx: &mut Context<Self>) {
677 let base = self.text_size.unwrap_or_else(theme::base_text_size);
678 let next = cx
679 .editor_text_size()
680 .clamp(text_size::resolve(self.text_size, cx) + by);
681 text_size::set_adjustment(next - base, cx);
682 }
683
684 pub(super) fn dismiss(&mut self, _: &Dismiss, _: &mut Window, cx: &mut Context<Self>) {
687 if self.table_drag.take().is_some() {
688 self.table_dragged = true;
689 cx.notify();
690 return;
691 }
692 if self.pasted.take().is_none()
693 && self.slash.take().is_none()
694 && self.mention.take().is_none()
695 && !self.dropdown_dismiss(cx)
696 {
697 self.selection = Selection::at(self.selection.head);
698 }
699 cx.notify();
700 }
701}