1use super::*;
4
5impl Editor {
6 pub(super) fn insert(&mut self, text: &str, cx: &mut Context<Self>) {
12 let painter = Painter::of(cx);
13 self.edit(EditKind::Insert, cx, |this| {
14 let mut typed = Text::plain(text);
15 for mark in this.stored.drain(..) {
18 typed.marks.push(markdown::MarkSpan {
19 range: 0..typed.text.len(),
20 mark,
21 });
22 }
23 let splice = this.doc.replace(this.selection, typed);
24 this.selection = Selection::at(splice.caret.clamp(&this.doc));
25 let shortcut = this.apply_shortcut();
26 let promoted = this.promote_quote_marker();
27 let inline = this.apply_inline_rule();
28 this.track_slash(text, painter);
29 std::iter::once(Delta::Spliced(splice))
30 .chain(shortcut)
31 .chain(promoted)
32 .chain(inline)
33 .collect()
34 });
35 }
36
37 pub(super) fn track_slash(&mut self, typed: &str, painter: Painter) {
43 if !self.chrome.slash {
44 return;
45 }
46 let at = self.cursor();
47 let text = self
48 .doc
49 .blocks
50 .get(at.block)
51 .and_then(|block| block.text_at(at.part))
52 .map(|text| text.text.clone())
53 .unwrap_or_default();
54
55 if self.slash.is_none() {
56 let opened = at.offset.checked_sub(1).filter(|_| typed == "/");
58 let starts_word = opened.is_none_or(|slash| {
59 text[..slash]
60 .chars()
61 .next_back()
62 .is_none_or(char::is_whitespace)
63 });
64 if let Some(slash) = opened.filter(|_| starts_word && at.part == Part::Body) {
67 self.slash = Some(Slash::open(
68 Cursor {
69 offset: slash,
70 ..at
71 },
72 painter,
73 ));
74 }
75 return;
76 }
77
78 let Some(query) = self.slash.as_ref().and_then(|slash| slash.query(at, &text)) else {
81 self.slash = None;
82 return;
83 };
84 if let Some(slash) = &mut self.slash {
85 slash.refilter(&query);
86 }
87 }
88
89 pub(super) fn confirm_slash(
93 &mut self,
94 kind: Option<BlockKind>,
95 cx: &mut Context<Self>,
96 ) -> bool {
97 let Some(slash) = &self.slash else {
98 return false;
99 };
100 let (at, kind) = (slash.at, kind.or_else(|| slash.choice()));
101 self.slash = None;
102 let Some(kind) = kind else {
103 return false;
104 };
105 let caret = self.cursor();
106 self.edit(EditKind::Structure, cx, |this| {
107 this.doc
108 .edit_at(at, |text| text.remove(at.offset..caret.offset));
109 this.doc.set_kind(at.block, kind);
110 this.selection =
111 Selection::at(Cursor::new(at.block, Part::Body, at.offset).clamp(&this.doc));
112 vec![Delta::Spliced(Splice {
113 removed: Selection::new(at, caret),
114 caret: at,
115 blocks: 0,
116 })]
117 });
118 true
119 }
120
121 pub(super) fn apply_inline_rule(&mut self) -> Vec<Delta> {
126 let at = self.cursor();
127 if matches!(at.part, Part::Code | Part::Caption) {
130 return Vec::new();
131 }
132 let Some(text) = self
133 .doc
134 .blocks
135 .get(at.block)
136 .and_then(|block| block.text_at(at.part))
137 else {
138 return Vec::new();
139 };
140 let Some((open, inner, mark)) = edit::inline_rule(&text.text, at.offset) else {
141 return Vec::new();
142 };
143 let width = open.len();
144 let (opening, closing) = (open.clone(), inner.end..at.offset);
145 self.doc.edit_at(at, |text| {
146 text.remove(inner.end..at.offset);
149 text.remove(open);
150 text.toggle(inner.start - width..inner.end - width, mark);
151 });
152 self.selection =
153 Selection::at(Cursor::new(at.block, at.part, at.offset - 2 * width).clamp(&self.doc));
154 vec![Self::taken(at, closing), Self::taken(at, opening)]
157 }
158
159 pub fn toggle_mark(&mut self, mark: Mark, cx: &mut Context<Self>) {
163 if !self.blocks() {
166 return;
167 }
168 let leaving_code = matches!(mark, Mark::Code) && self.cursor().part == Part::Code;
172 if self.selection.is_collapsed() && !leaving_code {
176 match self.stored.iter().position(|stored| *stored == mark) {
177 Some(ix) => drop(self.stored.remove(ix)),
178 None => self.stored.push(mark),
179 }
180 return cx.notify();
181 }
182 let selection = self.selection;
183 self.edit(EditKind::Structure, cx, |this| {
184 let before = this.doc.blocks.len();
187 let refenced = |doc: &Doc, head: Cursor| {
190 vec![Delta::Spliced(Splice {
191 removed: selection,
192 caret: head,
193 blocks: doc.blocks.len() as isize - before as isize,
194 })]
195 };
196 if matches!(mark, Mark::Code) {
197 if let Some(head) = this.doc.unfence(selection) {
198 this.selection = Selection::at(head.clamp(&this.doc));
199 return refenced(&this.doc, head);
200 }
201 if fenceable(&this.doc, selection) {
202 let head = this.doc.fence(selection);
203 this.selection = Selection::at(head.clamp(&this.doc));
204 return refenced(&this.doc, head);
205 }
206 }
207 this.doc.toggle_mark(selection, mark);
209 vec![]
210 });
211 }
212
213 pub(super) fn apply_shortcut(&mut self) -> Option<Delta> {
219 let at = self.cursor();
220 if at.part != Part::Body {
223 return None;
224 }
225 let text = self.doc.blocks.get(at.block)?.text_at(Part::Body)?;
226 let (hit, len) = shortcut(&text.text)?;
229 if at.offset < len {
230 return None;
231 }
232 self.doc.edit_at(at, |text| text.remove(0..len));
235 self.doc.set_kind(at.block, hit.apply(Text::default()));
236 self.selection =
237 Selection::at(Cursor::new(at.block, Part::Body, at.offset - len).clamp(&self.doc));
238 self.history.interrupt();
241 Some(Self::taken(at, 0..len))
242 }
243
244 pub(super) fn promote_quote_marker(&mut self) -> Option<Delta> {
250 let at = self.cursor();
251 if at.part != Part::Body {
252 return None;
253 }
254 let block = self.doc.blocks.get(at.block)?;
255 if !matches!(block.kind, BlockKind::Quote { kind: None, .. }) {
256 return None;
257 }
258 let text = &block.text_at(Part::Body)?.text;
259 let first = text.split('\n').next().unwrap_or_default();
260 let kind = Self::quote_marker(first)?;
261 let len = first.len() + usize::from(text.len() > first.len());
263 self.doc.edit_at(at, |text| text.remove(0..len));
264 self.doc.set_kind(
265 at.block,
266 BlockKind::Quote {
267 kind: Some(kind),
268 text: Text::default(),
269 },
270 );
271 self.selection = Selection::at(
272 Cursor::new(at.block, Part::Body, at.offset.saturating_sub(len)).clamp(&self.doc),
273 );
274 self.history.interrupt();
275 Some(Self::taken(at, 0..len))
276 }
277
278 pub(super) fn taken(at: Cursor, range: Range<usize>) -> Delta {
281 let start = Cursor::new(at.block, at.part, range.start);
282 Delta::Spliced(Splice {
283 removed: Selection {
284 anchor: start,
285 head: Cursor::new(at.block, at.part, range.end),
286 },
287 caret: start,
288 blocks: 0,
289 })
290 }
291
292 pub(super) fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Context<Self>) {
293 self.delete_back(cx);
294 }
295
296 pub(super) fn quote_marker(text: &str) -> Option<markdown::QuoteKind> {
297 [
298 markdown::QuoteKind::Note,
299 markdown::QuoteKind::Tip,
300 markdown::QuoteKind::Important,
301 markdown::QuoteKind::Warning,
302 markdown::QuoteKind::Caution,
303 ]
304 .into_iter()
305 .find(|kind| text.eq_ignore_ascii_case(kind.marker()))
306 }
307
308 pub(super) fn delete_back(&mut self, cx: &mut Context<Self>) {
314 let at = self.cursor();
315 let kind = if self.selection.is_collapsed() && at.offset == 0 {
317 EditKind::Structure
318 } else {
319 EditKind::Delete
320 };
321 let painter = Painter::of(cx);
322 self.edit(kind, cx, |this| {
323 let before = this.doc.blocks.len();
324 let splice = if !this.selection.is_collapsed() {
325 this.doc.replace(this.selection, Text::default())
326 } else if at.offset > 0 {
327 this.doc
328 .replace(Selection::new(at.left(&this.doc), at), Text::default())
329 } else {
330 match this.doc.merge_back(at) {
334 Some(head) => Splice {
337 removed: Selection::new(head, at),
338 caret: head,
339 blocks: this.doc.blocks.len() as isize - before as isize,
340 },
341 None => return vec![],
342 }
343 };
344 let head = splice.caret;
345 this.selection = Selection::at(head.clamp(&this.doc));
346 this.track_slash("", painter);
349 vec![Delta::Spliced(splice)]
350 });
351 }
352
353 pub(super) fn delete(&mut self, _: &Delete, _: &mut Window, cx: &mut Context<Self>) {
354 self.delete_forward(cx);
355 }
356
357 pub(super) fn delete_forward(&mut self, cx: &mut Context<Self>) {
360 self.edit(EditKind::Delete, cx, |this| {
361 let at = this.cursor();
362 let range = if this.selection.is_collapsed() {
363 Selection::new(at, at.right(&this.doc))
364 } else {
365 this.selection
366 };
367 let splice = this.doc.replace(range, Text::default());
368 this.selection = Selection::at(splice.caret.clamp(&this.doc));
369 vec![Delta::Spliced(splice)]
370 });
371 }
372
373 pub(super) fn menu_took_enter(&mut self, cx: &mut Context<Self>) -> bool {
379 if let Some(choice) = self.pasted.as_ref().map(link::Paste::choice) {
380 self.confirm_paste(choice, cx);
381 return true;
382 }
383 self.confirm_slash(None, cx)
384 }
385
386 pub(super) fn split_block(
389 &mut self,
390 _: &SplitBlock,
391 window: &mut Window,
392 cx: &mut Context<Self>,
393 ) {
394 if self.menu_took_enter(cx) {
395 return;
396 }
397 let at = self.cursor();
398 match at.part {
399 Part::Code => return self.insert("\n", cx),
400 Part::Cell { .. } => return,
403 Part::Caption
406 if matches!(
407 self.doc.blocks.get(at.block).map(|block| &block.kind),
408 Some(BlockKind::Image { url, .. }) if url.is_empty()
409 ) =>
410 {
411 return self.prompt_for_url(at.block, window, cx);
412 }
413 Part::Caption | Part::Body => {}
417 }
418 self.edit(EditKind::Structure, cx, |this| {
419 let mut deltas = Vec::new();
420 if !this.selection.is_collapsed() {
421 let splice = this.doc.replace(this.selection, Text::default());
422 this.selection = Selection::at(splice.caret.clamp(&this.doc));
423 deltas.push(Delta::Spliced(splice));
424 }
425 let at = this.cursor();
426 let new = this.doc.split(at.block, at.offset);
427 this.selection = Selection::at(Cursor::new(new, Part::Body, 0).clamp(&this.doc));
428 deltas.push(Delta::Spliced(Splice {
431 removed: Selection::at(at),
432 caret: Cursor::new(new, Part::Body, 0),
433 blocks: 1,
434 }));
435 deltas
436 });
437 }
438
439 pub(super) fn soft_break(&mut self, _: &SoftBreak, _: &mut Window, cx: &mut Context<Self>) {
442 if self.menu_took_enter(cx) {
443 return;
444 }
445 match self.cursor().part {
446 Part::Body | Part::Code => self.insert("\n", cx),
447 Part::Caption | Part::Cell { .. } => {}
448 }
449 }
450
451 pub(super) fn insert_paragraph(
454 &mut self,
455 _: &InsertParagraph,
456 _: &mut Window,
457 cx: &mut Context<Self>,
458 ) {
459 if self.menu_took_enter(cx) {
460 return;
461 }
462 if !self.blocks() {
463 return;
464 }
465 self.edit(EditKind::Structure, cx, |this| {
466 let at = this.cursor().block;
467 let insert_at = this.doc.subtree(at).end;
468 let indent = this.doc.blocks.get(at).map_or(0, |block| block.indent);
469 this.doc.blocks.insert(
470 insert_at,
471 Block::at(BlockKind::Paragraph(Text::default()), indent),
472 );
473 this.doc.repair();
474 this.selection = Selection::at(Cursor::new(insert_at, Part::Body, 0).clamp(&this.doc));
475 vec![Delta::Opened {
476 at: insert_at,
477 count: 1,
478 }]
479 });
480 }
481
482 pub(super) fn indent(&mut self, _: &Indent, _: &mut Window, cx: &mut Context<Self>) {
483 if !self.blocks() {
486 return self.insert(INDENT, cx);
487 }
488 self.edit(EditKind::Structure, cx, |this| {
489 this.doc.indent(this.cursor().block);
490 vec![]
491 });
492 }
493
494 pub(super) fn outdent(&mut self, _: &Outdent, _: &mut Window, cx: &mut Context<Self>) {
495 if !self.blocks() {
496 return self.unindent(cx);
497 }
498 self.edit(EditKind::Structure, cx, |this| {
499 this.doc.outdent(this.cursor().block);
500 vec![]
501 });
502 }
503
504 pub(super) fn increase_text_size(
505 &mut self,
506 _: &IncreaseTextSize,
507 _: &mut Window,
508 cx: &mut Context<Self>,
509 ) {
510 self.step_text_size(TextSize::of(cx).step, cx);
511 }
512
513 pub(super) fn decrease_text_size(
514 &mut self,
515 _: &DecreaseTextSize,
516 _: &mut Window,
517 cx: &mut Context<Self>,
518 ) {
519 self.step_text_size(-TextSize::of(cx).step, cx);
520 }
521
522 pub(super) fn reset_text_size(
523 &mut self,
524 _: &ResetTextSize,
525 _: &mut Window,
526 cx: &mut Context<Self>,
527 ) {
528 text_size::reset_text_size(cx);
529 }
530
531 pub(super) fn step_text_size(&mut self, by: f32, cx: &mut Context<Self>) {
538 let base = self.text_size.unwrap_or_else(theme::base_text_size);
539 let next = TextSize::of(cx).clamp(text_size::resolve(self.text_size, cx) + by);
540 text_size::set_adjustment(next - base, cx);
541 }
542
543 pub(super) fn dismiss(&mut self, _: &Dismiss, _: &mut Window, cx: &mut Context<Self>) {
546 if self.pasted.take().is_none() && self.slash.take().is_none() {
547 self.selection = Selection::at(self.selection.head);
548 }
549 cx.notify();
550 }
551}