From c7329644944e6a10e71ea7581bf0a5923ed9546b Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 9 Mar 2026 02:40:42 +0200 Subject: [PATCH] add header tags renderer --- .../tab/item/page/content/text/markdown.rs | 30 ++-------- .../item/page/content/text/markdown/tag.rs | 56 ++++++++++++++++++- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/app/browser/window/tab/item/page/content/text/markdown.rs b/src/app/browser/window/tab/item/page/content/text/markdown.rs index 3ffef743..5dd62197 100644 --- a/src/app/browser/window/tab/item/page/content/text/markdown.rs +++ b/src/app/browser/window/tab/item/page/content/text/markdown.rs @@ -108,8 +108,10 @@ impl Markdown { t == 0 || t.is_multiple_of(2) }; - // Parse in-line markdown tags - // * keep order! + // Render markdown tags + // * keep in order! + + tag::header(&buffer, &tag); reference::image_link(&buffer, &tag, base, &link_color.0, &mut links); reference::image(&buffer, &tag, base, &link_color.0, &mut links); @@ -201,30 +203,6 @@ impl Markdown { } } - // Is 1-6 level header - for level in 1..=6 { - if let Some(t) = header( - &buffer, - match level { - 1 => &tag.h1, - 2 => &tag.h2, - 3 => &tag.h3, - 4 => &tag.h4, - 5 => &tag.h5, - 6 => &tag.h6, - _ => unreachable!(), - }, - line, - &H.repeat(level), - ) { - // Update document title by tag, if not set before - if title.is_none() { - title = Some(t); - } - continue 'l; - } - } - // Is list if let Some(value) = ggemtext::line::list::Gemtext::as_value(line) { diff --git a/src/app/browser/window/tab/item/page/content/text/markdown/tag.rs b/src/app/browser/window/tab/item/page/content/text/markdown/tag.rs index 1ff62227..3a814541 100644 --- a/src/app/browser/window/tab/item/page/content/text/markdown/tag.rs +++ b/src/app/browser/window/tab/item/page/content/text/markdown/tag.rs @@ -4,11 +4,15 @@ mod plain; mod quote; mod title; -use gtk::{TextTag, TextTagTable}; +use gtk::{ + TextBuffer, TextTag, TextTagTable, + prelude::{TextBufferExt, TextBufferExtManual}, +}; use header::Header; use list::List; use plain::Plain; use quote::Quote; +use regex::Regex; use title::Title; pub struct Tag { @@ -77,3 +81,53 @@ impl Tag { } } } + +// Headers `#`, `##`, etc. + +const REGEX_HEADER: &str = r"(?m)^(?P#{1,6})\s+(?P.*)$"; + +/// Apply header `Tag` to given `TextBuffer` +pub fn header(buffer: &TextBuffer, tag: &Tag) { + let (start, end) = buffer.bounds(); + let full_content = buffer.text(&start, &end, true).to_string(); + + let matches: Vec<_> = Regex::new(REGEX_HEADER) + .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); + + match cap["level"].chars().count() { + 1 => buffer.insert_with_tags(&mut start_iter, &cap["title"], &[&tag.h1]), + 2 => buffer.insert_with_tags(&mut start_iter, &cap["title"], &[&tag.h2]), + 3 => buffer.insert_with_tags(&mut start_iter, &cap["title"], &[&tag.h3]), + 4 => buffer.insert_with_tags(&mut start_iter, &cap["title"], &[&tag.h4]), + 5 => buffer.insert_with_tags(&mut start_iter, &cap["title"], &[&tag.h5]), + 6 => buffer.insert_with_tags(&mut start_iter, &cap["title"], &[&tag.h6]), + _ => buffer.insert_with_tags(&mut start_iter, &cap["title"], &[]), + } + } +} + +#[test] +fn test_regex_header() { + let cap: Vec<_> = Regex::new(REGEX_HEADER) + .unwrap() + .captures_iter(r"## Title ![alt](https://link.com)") + .collect(); + + let first = cap.get(0).unwrap(); + assert_eq!(&first[0], "## Title ![alt](https://link.com)"); + assert_eq!(&first["level"], "##"); + assert_eq!(&first["title"], "Title ![alt](https://link.com)"); +}