diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs index 40cef67f..315a0968 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs @@ -1,6 +1,7 @@ mod parser; mod widget; +use parser::code::Code; use parser::header::Header; use parser::link::Link; use parser::list::List; @@ -44,8 +45,95 @@ impl Reader { // Init new text buffer let buffer = TextBuffer::new(None); + // Init multiline code builder features + let mut multiline = None; + // Parse gemtext lines for line in gemtext.lines() { + // Is inline code + if let Some(code) = Code::inline_from(line) { + // Build tag from level parsed + let tag = TextTag::builder() + .family("monospace") + .scale(0.8) + .wrap_mode(WrapMode::None) + .build(); + + // Register tag in buffer + buffer.tag_table().add(&tag); + + // Append value to buffer + buffer.insert_with_tags(&mut buffer.end_iter(), code.value.as_str(), &[&tag]); + buffer.insert(&mut buffer.end_iter(), "\n"); + + // Skip other actions for this line + continue; + } + + // Is multiline code + match multiline { + None => { + // Open tag found + if let Some(code) = Code::multiline_begin_from(line) { + // Begin next lines collection into the code buffer + multiline = Some(code); + + // Skip other actions for this line + continue; + } + } + Some(ref mut this) => { + Code::multiline_continue_from(this, line); + + // Close tag found: + if this.completed { + // Is alt provided + if let Some(alt) = &this.alt { + // Build tag for code alt description + let tag = TextTag::builder() + .pixels_above_lines(4) + .pixels_below_lines(8) + .weight(500) + .wrap_mode(WrapMode::None) + .build(); + + // Register tag in buffer + buffer.tag_table().add(&tag); + + // Insert alt value to the main buffer + buffer.insert_with_tags(&mut buffer.end_iter(), alt.as_str(), &[&tag]); + buffer.insert(&mut buffer.end_iter(), "\n"); + } + + // Build tag container for multiline code result + let tag = TextTag::builder() + .family("monospace") // @TODO does not work + .left_margin(28) + .scale(0.8) + .wrap_mode(WrapMode::None) + .build(); + + // Register tag in buffer + buffer.tag_table().add(&tag); + + // Insert multiline code buffer into main buffer + buffer.insert_with_tags( + &mut buffer.end_iter(), + &this.buffer.join("\n"), + &[&tag], + ); + + buffer.insert(&mut buffer.end_iter(), "\n"); + + // Reset + multiline = None; + } + + // Skip other actions for this line + continue; + } + }; + // Is header if let Some(header) = Header::from(line) { // Build tag from level parsed @@ -54,13 +142,13 @@ impl Reader { .scale(1.6) .sentence(true) .weight(500) - .wrap_mode(gtk::WrapMode::Word) + .wrap_mode(WrapMode::Word) .build(), parser::header::Level::H2 => TextTag::builder() .scale(1.4) .sentence(true) .weight(400) - .wrap_mode(gtk::WrapMode::Word) + .wrap_mode(WrapMode::Word) .build(), parser::header::Level::H3 => TextTag::builder() .scale(1.2) @@ -140,7 +228,7 @@ impl Reader { .left_margin(28) .pixels_above_lines(4) .pixels_below_lines(4) - .wrap_mode(gtk::WrapMode::Word) + .wrap_mode(WrapMode::Word) .build(); // Register tag in buffer @@ -163,7 +251,7 @@ impl Reader { // Build tag from level parsed let tag = TextTag::builder() .style(Style::Italic) - .wrap_mode(gtk::WrapMode::Word) + .wrap_mode(WrapMode::Word) .build(); // Register tag in buffer diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser.rs index 5ac64a79..fd708509 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser.rs @@ -1,3 +1,4 @@ +pub mod code; pub mod header; pub mod link; pub mod list; diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser/code.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser/code.rs new file mode 100644 index 00000000..f9ab3ee0 --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser/code.rs @@ -0,0 +1,25 @@ +pub mod inline; +pub mod multiline; + +use inline::Inline; +use multiline::Multiline; + +pub struct Code { + // nothing yet.. +} + +impl Code { + // Inline + pub fn inline_from(line: &str) -> Option { + Inline::from(line) + } + + // Multiline + pub fn multiline_begin_from(line: &str) -> Option { + Multiline::begin_from(line) + } + + pub fn multiline_continue_from(this: &mut Multiline, line: &str) { + Multiline::continue_from(this, line) + } +} diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser/code/inline.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser/code/inline.rs new file mode 100644 index 00000000..7baddb8c --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser/code/inline.rs @@ -0,0 +1,29 @@ +use gtk::glib::{GString, Regex, RegexCompileFlags, RegexMatchFlags}; + +pub struct Inline { + pub value: GString, +} + +impl Inline { + pub fn from(line: &str) -> Option { + // Parse line + let regex = Regex::split_simple( + r"^`{3}([^`]*)`{3}$", + line, + RegexCompileFlags::DEFAULT, + RegexMatchFlags::DEFAULT, + ); + + // Detect value + let value = regex.get(1)?; + + if value.trim().is_empty() { + return None; + } + + // Result + Some(Self { + value: GString::from(value.as_str()), + }) + } +} diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser/code/multiline.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser/code/multiline.rs new file mode 100644 index 00000000..b8c9c633 --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/parser/code/multiline.rs @@ -0,0 +1,46 @@ +use gtk::glib::GString; + +pub struct Multiline { + pub alt: Option, + pub buffer: Vec, + pub completed: bool, +} + +impl Multiline { + // Search in line for tag open, + // return Self constructed on success or None + pub fn begin_from(line: &str) -> Option { + if line.starts_with("```") { + let alt = line.trim_start_matches("```").trim(); + + return Some(Self { + alt: match alt.is_empty() { + true => None, + false => Some(GString::from(alt)), + }, + buffer: Vec::new(), + completed: false, + }); + } + + None + } + + // Continue preformatted buffer from line, + // set `completed` as True on close tag found + pub fn continue_from(&mut self, line: &str) { + // Make sure buffer not completed yet + if self.completed { + panic!("Could not continue as completed") // @TODO handle + } + + // Line contain close tag + if line.ends_with("```") { + self.completed = true; + } + + // Append data to the buffer, trim close tag on exists + self.buffer + .push(GString::from(line.trim_end_matches("```").trim())); + } +}