summaryrefslogtreecommitdiff
path: root/src/elements/line.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/elements/line.rs')
-rw-r--r--src/elements/line.rs117
1 files changed, 117 insertions, 0 deletions
diff --git a/src/elements/line.rs b/src/elements/line.rs
new file mode 100644
index 0000000..d5c078e
--- /dev/null
+++ b/src/elements/line.rs
@@ -0,0 +1,117 @@
+use crate::*;
+
+macro_rules! opt {
+ ($v:expr) => {|s| Some($v(s)) };
+}
+
+pub struct Line {
+ pub elements: Vec<LineElement>,
+}
+
+impl Line {
+ pub fn from_str(raw_string: &str) -> Self {
+ fn unlabeled_extern_link(target: String) -> Option<LineElement> {
+ target.contains("/").then( ||
+ LineElement::ExternalLink(ExternalLink { target, label:String::new() })
+ )
+ }
+ fn labelled_extern_link(s: String) -> Option<LineElement> {
+ let (label, target) = match s.split_once("](") {
+ Some((l, t)) => (l.to_string(), t.to_string()),
+ None => return None };
+ if label.contains("]") || target.contains("]") { return None }
+ Some(LineElement::ExternalLink(ExternalLink { label, target })) }
+ const DELIMITERS: [(fn(String)->Option<LineElement>, &str, &str, &str); 7] = [
+ ( opt!(LineElement::Bold), "**", "**", "*" ),
+ ( opt!(LineElement::Italic), "_", "_", "_" ),
+ ( opt!(LineElement::Monospace), "`", "`", "`" ),
+ ( opt!(LineElement::Math), "$", "$", "$" ),
+ ( opt!(LineElement::InternalLink), "[[", "]]", "[]" ),
+ ( labelled_extern_link, "[", ")", "[]()" ),
+ ( unlabeled_extern_link, "[", "]", "[]" ),
+ ];
+ let chars: Vec<char> = raw_string.chars().collect();
+ let mut elements = Vec::new();
+ let mut cached_chars = String::new();
+ let mut i = 0;
+
+ let starts_with = |i, p:&str| std::iter::zip(&chars[i..], p.chars()).all(|(a, b)| *a == b);
+
+ 'outer: while let Some(c) = chars.get(i) {
+ // Only check for opening delimiters that directly follow a whitespace character.
+ let follows_whitespace = match chars.get(i.wrapping_sub(1)) {
+ Some(w) => is_whitespace(w),
+ None => true,
+ };
+ if follows_whitespace {
+ // Try to parse an opening delimiter.
+ for (variant, start_delim, end_delim, delim_chars) in DELIMITERS {
+ // Try to match an opening delimiter with a terminating delimiter.
+ if starts_with(i, start_delim) {
+ let s_end = i + start_delim.chars().count();
+ let mut e_start = s_end;
+ let mut e_end = e_start + end_delim.chars().count();
+ while e_end <= chars.len() {
+ e_start += 1; e_end += 1;
+ let end_is_whitespace =
+ if let Some(end_char) = chars.get(e_end) {
+ is_whitespace(end_char)
+ } else {
+ e_end == chars.len()
+ };
+ // If the terminating delimiter is found, store the normal
+ // text and the styled text, and continue to the next character.
+ if end_is_whitespace && starts_with(e_start, end_delim) {
+ // Check that there is content within the styled string.
+ let styled_string: String = chars[s_end..e_start].iter().collect();
+ let non_content_chars: Vec<_> = delim_chars.chars().collect();
+ if !is_contentful(&styled_string, &non_content_chars) { continue }
+ if styled_string.len() != styled_string.trim().len() { continue }
+ let line_element = match variant(styled_string) {
+ Some(e) => e,
+ None => continue,
+ };
+ // Commit the normal and styled strings.
+ if !cached_chars.is_empty() {
+ let normal_string = std::mem::take(&mut cached_chars);
+ elements.push(LineElement::Normal(normal_string)); }
+ elements.push(line_element);
+ i = e_end;
+ continue 'outer;
+ }
+ }
+ }
+ }
+ }
+ cached_chars.push(*c); i += 1;
+ }
+ if !cached_chars.is_empty() {
+ let normal_string = std::mem::take(&mut cached_chars);
+ elements.push(LineElement::Normal(normal_string)); }
+ Self { elements }
+ }
+
+ /// Return only the character content, with none of the styling information.
+ pub fn as_plain_text(&self) -> String {
+ let mut string = String::new();
+ for line_element in &self.elements {
+ string.push_str(line_element.as_plain_text()) }
+ return string;
+ }
+}
+
+impl std::fmt::Display for Line {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+ for line_element in &self.elements {
+ write!(f, "{line_element}")?; }
+ Ok(())
+ }
+}
+
+impl std::fmt::Debug for Line {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+ for line_element in &self.elements {
+ write!(f, "{line_element:?}\n")?; }
+ Ok(())
+ }
+}