markdown/edit/
shortcut.rs1use super::*;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Shortcut {
8 Heading(u8),
9 Bullet,
10 Ordered,
11 Task(bool),
12 Quote,
13 Code,
14 Rule,
15}
16
17impl Doc {
18 pub fn apply(&mut self, ix: usize, shortcut: Shortcut) {
21 let text = Text::default;
22 let kind = match shortcut {
23 Shortcut::Heading(level) => BlockKind::Heading {
24 level,
25 text: text(),
26 },
27 Shortcut::Bullet => BlockKind::Bullet(text()),
28 Shortcut::Ordered => BlockKind::Ordered {
29 number: 1,
30 text: text(),
31 },
32 Shortcut::Task(checked) => BlockKind::Task {
33 checked,
34 text: text(),
35 },
36 Shortcut::Quote => return self.set_quote(ix, Some(Quoted::default())),
37 Shortcut::Code => BlockKind::Code {
38 language: None,
39 code: text(),
40 height: None,
41 },
42 Shortcut::Rule => BlockKind::Rule,
43 };
44 self.set_kind(ix, kind);
45 }
46}
47
48pub fn shortcut(text: &str) -> Option<(Shortcut, usize)> {
55 const PREFIXES: &[(&str, Shortcut)] = &[
56 ("- [ ] ", Shortcut::Task(false)),
57 ("- [x] ", Shortcut::Task(true)),
58 ("###### ", Shortcut::Heading(6)),
59 ("##### ", Shortcut::Heading(5)),
60 ("#### ", Shortcut::Heading(4)),
61 ("### ", Shortcut::Heading(3)),
62 ("## ", Shortcut::Heading(2)),
63 ("# ", Shortcut::Heading(1)),
64 ("- ", Shortcut::Bullet),
65 ("* ", Shortcut::Bullet),
66 ("+ ", Shortcut::Bullet),
67 ("1. ", Shortcut::Ordered),
68 ("> ", Shortcut::Quote),
69 ("```", Shortcut::Code),
70 ("---", Shortcut::Rule),
71 ];
72 PREFIXES
73 .iter()
74 .find(|(prefix, _)| text.starts_with(prefix))
75 .map(|(prefix, shortcut)| (*shortcut, prefix.len()))
76}
77
78pub fn inline_rule(text: &str, caret: usize) -> Option<(Range<usize>, Range<usize>, Mark)> {
85 let head = text.get(..caret)?;
86 for (delimiter, mark) in [
88 ("**", Mark::Bold),
89 ("__", Mark::Bold),
90 ("~~", Mark::Strike),
91 ("`", Mark::Code),
92 ("_", Mark::Italic),
93 ("*", Mark::Italic),
94 ] {
95 let Some(closes) = head.strip_suffix(delimiter) else {
96 continue;
97 };
98 let Some(open) = closes.rfind(delimiter) else {
99 continue;
100 };
101 if matches!(delimiter, "*" | "_") && text[..open].ends_with(delimiter) {
105 continue;
106 }
107 let inner = open + delimiter.len()..closes.len();
108 let Some(body) = text.get(inner.clone()).filter(|body| !body.is_empty()) else {
109 continue;
110 };
111 if body.starts_with(char::is_whitespace) || body.ends_with(char::is_whitespace) {
116 continue;
117 }
118 if delimiter.starts_with('_')
121 && text[..open]
122 .chars()
123 .next_back()
124 .is_some_and(char::is_alphanumeric)
125 {
126 continue;
127 }
128 return Some((open..open + delimiter.len(), inner, mark));
129 }
130 None
131}