Skip to content

Commit c8c08f3

Browse files
committed
Rewrite parser to remove trailing '\n', fix changing tag in selection
1 parent aabdb44 commit c8c08f3

File tree

3 files changed

+207
-151
lines changed

3 files changed

+207
-151
lines changed

gemini/src/parser.rs

Lines changed: 79 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,101 @@
11
use once_cell::sync::Lazy;
22
use regex::Regex;
33
static R_GEMINI_LINK: Lazy<Regex> =
4-
Lazy::new(|| Regex::new(r"^=>\s*(?P<href>\S*)\s*(?P<label>.*)").unwrap());
5-
6-
#[derive(Debug)]
7-
pub enum PageElement {
8-
Heading(String),
9-
Quote(String),
10-
Preformatted(String),
11-
Text(String),
4+
Lazy::new(|| Regex::new(r"^=>\s+(?P<href>\S+)(\s+(?P<label>.+))?").unwrap());
5+
6+
// See gemini://gemini.circumlunar.space/docs/cheatsheet.gmi
7+
8+
#[derive(Debug, Clone)]
9+
pub enum Tag {
10+
Paragraph, // Is just a text line
11+
Heading(u8),
12+
BlockQuote,
13+
CodeBlock,
14+
UnorderedList,
15+
Item,
1216
Link(String, Option<String>),
13-
ListItem(String),
14-
Empty,
17+
}
18+
19+
#[derive(Debug, Clone)]
20+
pub enum Event<'a> {
21+
Start(Tag),
22+
End,
23+
Text(&'a str),
24+
BlankLine,
1525
}
1626

1727
#[derive(Debug, Clone, Default)]
1828
pub struct Parser {
19-
inside_pre: bool,
29+
tag_stack: Vec<Tag>,
2030
}
2131

2232
impl Parser {
2333
pub fn new() -> Self {
24-
Self { inside_pre: false }
34+
Self { tag_stack: vec![] }
2535
}
26-
pub fn parse_line(&mut self, line: &str) -> PageElement {
36+
37+
/// Returns an `Event` when an event it's ready, else, `None`
38+
// TODO: Make this work on text input of any length, don't impose the "line" chunk requirement
39+
// some work has already been done, the pushed result is already structured to do so.
40+
pub fn parse_line<'a>(&mut self, line: &'a str, res: &mut Vec<Event<'a>>) {
41+
let parent_tag = self.tag_stack.last();
42+
43+
// Close pending multi-line tags
44+
if matches!(parent_tag, Some(Tag::BlockQuote)) && !line.starts_with('>')
45+
|| matches!(parent_tag, Some(Tag::UnorderedList))
46+
{
47+
res.push(Event::End);
48+
self.tag_stack.pop();
49+
}
50+
51+
let parent_tag = self.tag_stack.last();
52+
2753
if line.starts_with("```") {
28-
self.inside_pre = !self.inside_pre;
29-
PageElement::Empty
30-
} else if self.inside_pre {
31-
PageElement::Preformatted(line.to_string())
54+
let inner_res = if let Some(Tag::CodeBlock) = parent_tag {
55+
self.tag_stack.pop();
56+
Event::End
57+
} else {
58+
self.tag_stack.push(Tag::CodeBlock);
59+
Event::Start(Tag::CodeBlock)
60+
};
61+
res.push(inner_res);
62+
} else if let Some(Tag::CodeBlock) = parent_tag {
63+
res.push(Event::Text(line));
64+
} else if line.trim().is_empty() {
65+
res.push(Event::BlankLine);
3266
} else if line.starts_with('#') {
33-
PageElement::Heading(line.to_string())
67+
let line = line.trim_end();
68+
let lvl = line.chars().filter(|c| *c == '#').count();
69+
let heading = Tag::Heading(lvl as u8);
70+
res.push(Event::Start(heading));
71+
72+
let text = line.trim_start_matches('#').trim_start();
73+
res.push(Event::Text(text));
74+
res.push(Event::End);
3475
} else if line.starts_with('>') {
35-
PageElement::Quote(line.to_string())
76+
if !matches!(parent_tag, Some(Tag::BlockQuote)) {
77+
res.push(Event::Start(Tag::BlockQuote));
78+
}
79+
res.push(Event::Text(line.trim_start_matches('>')));
3680
} else if let Some(stripped) = line.strip_prefix("* ") {
37-
PageElement::ListItem(stripped.to_string())
38-
} else if let Some(captures) = R_GEMINI_LINK.captures(line) {
39-
match (captures.name("href"), captures.name("label")) {
40-
(Some(m_href), Some(m_label)) if !m_label.as_str().is_empty() => PageElement::Link(
41-
m_href.as_str().to_string(),
42-
Some(m_label.as_str().to_string()),
43-
),
44-
(Some(m_href), _) => PageElement::Link(m_href.as_str().to_string(), None),
45-
_ => PageElement::Empty,
81+
if !matches!(parent_tag, Some(Tag::UnorderedList)) {
82+
res.push(Event::Start(Tag::UnorderedList));
4683
}
84+
res.push(Event::Start(Tag::Item));
85+
res.push(Event::Text(stripped.trim_end()));
86+
res.push(Event::End);
87+
} else if let Some(captures) = R_GEMINI_LINK.captures(line.trim_end()) {
88+
let href = captures.name("href").unwrap();
89+
let label = captures.name("label").map(|x| x.as_str());
90+
res.push(Event::Start(Tag::Link(
91+
href.as_str().to_string(),
92+
label.map(|x| x.to_string()),
93+
)));
94+
res.push(Event::End);
4795
} else {
48-
PageElement::Text(line.to_string())
96+
res.push(Event::Start(Tag::Paragraph));
97+
res.push(Event::Text(line.trim_end()));
98+
res.push(Event::End);
4999
}
50100
}
51101
}

src/text_extensions/gemini.rs

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -123,74 +123,6 @@ impl Gemini {
123123
.unwrap()
124124
.set_foreground_rgba(Some(color));
125125
}
126-
pub fn insert_heading(&self, text_iter: &mut gtk::TextIter, line: &str) {
127-
let n = line.chars().filter(|c| *c == '#').count();
128-
let line = line.trim_start_matches('#').trim_start();
129-
let tag_name = match n {
130-
1 => "h1",
131-
2 => "h2",
132-
_ => "h3",
133-
};
134-
135-
let start = text_iter.offset();
136-
137-
self.text_buffer.insert(text_iter, line);
138-
self.text_buffer.apply_tag_by_name(
139-
tag_name,
140-
&self.text_buffer.iter_at_offset(start),
141-
&self.text_buffer.end_iter(),
142-
);
143-
}
144-
145-
pub fn insert_quote(&self, text_iter: &mut gtk::TextIter, line: &str) {
146-
let start = text_iter.offset();
147-
self.text_buffer.insert(text_iter, line);
148-
self.text_buffer
149-
.apply_tag_by_name("q", &self.text_buffer.iter_at_offset(start), text_iter);
150-
}
151-
152-
pub fn insert_preformatted(&self, text_iter: &mut gtk::TextIter, line: &str) {
153-
let start = text_iter.offset();
154-
self.text_buffer.insert(text_iter, line);
155-
self.text_buffer.apply_tag_by_name(
156-
"pre",
157-
&self.text_buffer.iter_at_offset(start),
158-
text_iter,
159-
);
160-
161-
self.text_buffer.insert(text_iter, "\n");
162-
}
163-
pub fn insert_paragraph(&self, text_iter: &mut gtk::TextIter, line: &str) {
164-
let start = text_iter.offset();
165-
self.text_buffer.insert(text_iter, line);
166-
self.text_buffer
167-
.apply_tag_by_name("p", &self.text_buffer.iter_at_offset(start), text_iter);
168-
}
169-
pub fn insert_link(
170-
&mut self,
171-
text_iter: &mut gtk::TextIter,
172-
link: &str,
173-
label: Option<&str>,
174-
) -> gtk::TextTag {
175-
let start = text_iter.offset();
176-
177-
let tag = gtk::TextTag::new(None);
178-
self.text_buffer.tag_table().add(&tag);
179-
180-
let label = label.unwrap_or(link);
181-
self.insert_paragraph(text_iter, label);
182-
self.insert_paragraph(text_iter, "\n");
183-
184-
self.text_buffer
185-
.apply_tag(&tag, &self.text_buffer.iter_at_offset(start), text_iter);
186-
self.text_buffer
187-
.apply_tag_by_name("a", &self.text_buffer.iter_at_offset(start), text_iter);
188-
tag
189-
}
190-
pub fn insert_list_item(&mut self, text_iter: &mut gtk::TextIter, text: &str) {
191-
self.insert_paragraph(text_iter, " • ");
192-
self.insert_paragraph(text_iter, text);
193-
}
194126
pub fn clear(&mut self) {
195127
let b = &self.text_buffer;
196128
b.delete(&mut b.start_iter(), &mut b.end_iter());

0 commit comments

Comments
 (0)