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 Shortcut {
18 pub fn apply(self, text: Text) -> BlockKind {
20 match self {
21 Self::Heading(level) => BlockKind::Heading { level, text },
22 Self::Bullet => BlockKind::Bullet(text),
23 Self::Ordered => BlockKind::Ordered { number: 1, text },
24 Self::Task(checked) => BlockKind::Task { checked, text },
25 Self::Quote => BlockKind::Quote { kind: None, text },
26 Self::Code => BlockKind::Code {
29 language: None,
30 code: Text::plain(text.text),
31 height: None,
32 },
33 Self::Rule => BlockKind::Rule,
34 }
35 }
36}
37
38pub fn shortcut(text: &str) -> Option<(Shortcut, usize)> {
45 const PREFIXES: &[(&str, Shortcut)] = &[
46 ("- [ ] ", Shortcut::Task(false)),
47 ("- [x] ", Shortcut::Task(true)),
48 ("###### ", Shortcut::Heading(6)),
49 ("##### ", Shortcut::Heading(5)),
50 ("#### ", Shortcut::Heading(4)),
51 ("### ", Shortcut::Heading(3)),
52 ("## ", Shortcut::Heading(2)),
53 ("# ", Shortcut::Heading(1)),
54 ("- ", Shortcut::Bullet),
55 ("* ", Shortcut::Bullet),
56 ("+ ", Shortcut::Bullet),
57 ("1. ", Shortcut::Ordered),
58 ("> ", Shortcut::Quote),
59 ("```", Shortcut::Code),
60 ("---", Shortcut::Rule),
61 ];
62 PREFIXES
63 .iter()
64 .find(|(prefix, _)| text.starts_with(prefix))
65 .map(|(prefix, shortcut)| (*shortcut, prefix.len()))
66}
67
68pub fn inline_rule(text: &str, caret: usize) -> Option<(Range<usize>, Range<usize>, Mark)> {
75 let head = text.get(..caret)?;
76 for (delimiter, mark) in [
78 ("**", Mark::Bold),
79 ("__", Mark::Bold),
80 ("~~", Mark::Strike),
81 ("`", Mark::Code),
82 ("_", Mark::Italic),
83 ("*", Mark::Italic),
84 ] {
85 let Some(closes) = head.strip_suffix(delimiter) else {
86 continue;
87 };
88 let Some(open) = closes.rfind(delimiter) else {
89 continue;
90 };
91 if matches!(delimiter, "*" | "_") && text[..open].ends_with(delimiter) {
95 continue;
96 }
97 let inner = open + delimiter.len()..closes.len();
98 let Some(body) = text.get(inner.clone()).filter(|body| !body.is_empty()) else {
99 continue;
100 };
101 if body.starts_with(char::is_whitespace) || body.ends_with(char::is_whitespace) {
106 continue;
107 }
108 if delimiter.starts_with('_')
111 && text[..open]
112 .chars()
113 .next_back()
114 .is_some_and(char::is_alphanumeric)
115 {
116 continue;
117 }
118 return Some((open..open + delimiter.len(), inner, mark));
119 }
120 None
121}