From fb7e00758b7d813c2b115774f1d65dcaa55c333d Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 9 Mar 2026 19:53:11 +0200 Subject: [PATCH] implement `pre` tag --- .../item/page/content/text/markdown/tags.rs | 6 ++ .../page/content/text/markdown/tags/pre.rs | 89 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/app/browser/window/tab/item/page/content/text/markdown/tags/pre.rs diff --git a/src/app/browser/window/tab/item/page/content/text/markdown/tags.rs b/src/app/browser/window/tab/item/page/content/text/markdown/tags.rs index d529f3c3..52b26a9a 100644 --- a/src/app/browser/window/tab/item/page/content/text/markdown/tags.rs +++ b/src/app/browser/window/tab/item/page/content/text/markdown/tags.rs @@ -1,6 +1,7 @@ mod bold; mod code; mod header; +mod pre; mod quote; mod reference; mod strike; @@ -12,6 +13,7 @@ use bold::Bold; use code::Code; use gtk::{TextBuffer, TextTag, gdk::RGBA, glib::Uri}; use header::Header; +use pre::Pre; use quote::Quote; use strike::Strike; use underline::Underline; @@ -20,6 +22,7 @@ pub struct Tags { pub bold: Bold, pub code: Code, pub header: Header, + pub pre: Pre, pub quote: Quote, pub strike: Strike, pub underline: Underline, @@ -38,6 +41,7 @@ impl Tags { bold: Bold::new(), code: Code::new(), header: Header::new(), + pre: Pre::new(), quote: Quote::new(), strike: Strike::new(), underline: Underline::new(), @@ -59,6 +63,7 @@ impl Tags { self.quote.render(buffer); self.bold.render(buffer); + self.pre.render(buffer); self.strike.render(buffer); self.underline.render(buffer); @@ -71,6 +76,7 @@ impl Tags { // Format document title string title.map(|mut s| { s = bold::strip_tags(&s); + s = pre::strip_tags(&s); s = reference::strip_tags(&s); s = strike::strip_tags(&s); s = underline::strip_tags(&s); diff --git a/src/app/browser/window/tab/item/page/content/text/markdown/tags/pre.rs b/src/app/browser/window/tab/item/page/content/text/markdown/tags/pre.rs new file mode 100644 index 00000000..77384fdc --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/text/markdown/tags/pre.rs @@ -0,0 +1,89 @@ +use gtk::{ + TextBuffer, TextTag, + WrapMode::Word, + gdk::RGBA, + prelude::{TextBufferExt, TextBufferExtManual}, +}; +use regex::Regex; + +const REGEX_PRE: &str = r"`(?P[^`]+)`"; + +pub struct Pre(TextTag); + +impl Pre { + pub fn new() -> Self { + Self(if adw::StyleManager::default().is_dark() { + TextTag::builder() + .background_rgba(&RGBA::new(0.0, 0.0, 0.0, 1.)) + .foreground("#ccc") + .family("monospace") // @TODO + .wrap_mode(Word) + .build() + } else { + TextTag::builder() + .background_rgba(&RGBA::new(0.0, 0.0, 0.0, 0.06)) + .family("monospace") // @TODO + .wrap_mode(Word) + .build() + }) + } + + /// Apply preformatted `Tag` to given `TextBuffer` + pub fn render(&self, buffer: &TextBuffer) { + assert!(buffer.tag_table().add(&self.0)); + + let (start, end) = buffer.bounds(); + let full_content = buffer.text(&start, &end, true).to_string(); + + let matches: Vec<_> = Regex::new(REGEX_PRE) + .unwrap() + .captures_iter(&full_content) + .collect(); + + for cap in matches.into_iter().rev() { + let full_match = cap.get(0).unwrap(); + + let start_char_offset = full_content[..full_match.start()].chars().count() as i32; + let end_char_offset = full_content[..full_match.end()].chars().count() as i32; + + let mut start_iter = buffer.iter_at_offset(start_char_offset); + let mut end_iter = buffer.iter_at_offset(end_char_offset); + + buffer.delete(&mut start_iter, &mut end_iter); + buffer.insert_with_tags(&mut start_iter, &cap["text"], &[&self.0]) + } + } +} + +pub fn strip_tags(value: &str) -> String { + let mut result = String::from(value); + for cap in Regex::new(REGEX_PRE).unwrap().captures_iter(value) { + if let Some(m) = cap.get(0) { + result = result.replace(m.as_str(), &cap["text"]); + } + } + result +} + +#[test] +fn test_strip_tags() { + const VALUE: &str = r"Some `pre 1` and `pre 2` with ![img](https://link.com)"; + let mut result = String::from(VALUE); + for cap in Regex::new(REGEX_PRE).unwrap().captures_iter(VALUE) { + if let Some(m) = cap.get(0) { + result = result.replace(m.as_str(), &cap["text"]); + } + } + assert_eq!(result, "Some pre 1 and pre 2 with ![img](https://link.com)") +} + +#[test] +fn test_regex() { + let cap: Vec<_> = Regex::new(REGEX_PRE) + .unwrap() + .captures_iter(r"Some `pre 1` and `pre 2` with ![img](https://link.com)") + .collect(); + + assert_eq!(&cap.get(0).unwrap()["text"], "pre 1"); + assert_eq!(&cap.get(1).unwrap()["text"], "pre 2"); +}