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