From 22c50161af9348ee1f114e8f6efc3141da4ac97c Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 8 Mar 2026 17:25:27 +0200 Subject: [PATCH] separate Reference impl --- .../tab/item/page/content/text/markdown.rs | 81 +++++++------------ .../page/content/text/markdown/reference.rs | 53 ++++++++++++ 2 files changed, 80 insertions(+), 54 deletions(-) create mode 100644 src/app/browser/window/tab/item/page/content/text/markdown/reference.rs 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 1e051762..57c25fa4 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 @@ -2,6 +2,7 @@ mod ansi; pub mod error; mod gutter; mod icon; +mod reference; mod syntax; mod tag; @@ -13,11 +14,12 @@ use gtk::{ UriLauncher, Window, WrapMode, gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY, BUTTON_SECONDARY, RGBA}, gio::{Cancellable, SimpleAction, SimpleActionGroup}, - glib::{Uri, UriFlags, uuid_string_random}, + glib::{Uri, uuid_string_random}, prelude::{PopoverExt, TextBufferExt, TextBufferExtManual, TextTagExt, TextViewExt, WidgetExt}, }; use gutter::Gutter; use icon::Icon; +use reference::Reference; use regex::Regex; use sourceview::prelude::{ActionExt, ActionMapExt, DisplayExt, ToVariant}; use std::{cell::Cell, collections::HashMap, rc::Rc}; @@ -569,7 +571,7 @@ fn link( buffer.set_text(""); let mut last_pos = 0; - for cap in Regex::new(r"(?P!?)\[(?P[^\]]+)\]\((?P[^\)]+)\)") + for cap in Regex::new(r"(?P!)\[(?P[^\]]+)\]\((?P[^\)]+)\)") .unwrap() .captures_iter(&full_content) { @@ -578,59 +580,30 @@ fn link( if !before.is_empty() { buffer.insert(&mut buffer.end_iter(), before); } - // Relative scheme patch - // https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 - let unresolved_url = match cap["url"].strip_prefix("//") { - Some(p) => { - let s = p.trim_start_matches(":"); - &format!( - "{}://{}", - base.scheme(), - if s.is_empty() { - format!("{}/", base.host().unwrap_or_default()) - } else { - s.into() - } - ) - } - None => &cap["url"], - }; - // Convert address to the valid URI, - // resolve to absolute URL format if the target is relative - match Uri::resolve_relative(Some(&base.to_string()), unresolved_url, UriFlags::NONE) { - Ok(url) => match Uri::parse(&url, UriFlags::NONE) { - Ok(uri) => { - let alt = { - let mut a: Vec<&str> = Vec::with_capacity(2); - if uri.scheme() != base.scheme() { - a.push("⇖"); - } - if cap["text"].is_empty() { - a.push(&cap["url"]); - } else { - a.push(&cap["text"]); - } - a.join(" ") - }; - - let a = TextTag::builder() - .foreground_rgba(link_color) - // .foreground_rgba(&adw::StyleManager::default().accent_color_rgba()) - // @TODO adw 1.6 / ubuntu 24.10+ - .sentence(true) - .wrap_mode(WrapMode::Word) - .build(); - - if !tag.text_tag_table.add(&a) { - panic!() - } - - buffer.insert_with_tags(&mut buffer.end_iter(), &alt, &[&a]); - links.insert(a, uri); - } - Err(_) => todo!(), + match Reference::parse( + &cap["url"], + if cap["text"].is_empty() { + None + } else { + Some(&cap["text"]) }, - Err(_) => continue, + base, + ) { + Some(link) => { + let a = TextTag::builder() + .foreground_rgba(link_color) + // .foreground_rgba(&adw::StyleManager::default().accent_color_rgba()) + // @TODO adw 1.6 / ubuntu 24.10+ + .sentence(true) + .wrap_mode(WrapMode::Word) + .build(); + if !tag.text_tag_table.add(&a) { + panic!() + } + buffer.insert_with_tags(&mut buffer.end_iter(), &link.alt, &[&a]); + links.insert(a, link.uri); + } + None => continue, } last_pos = full_match.end(); } diff --git a/src/app/browser/window/tab/item/page/content/text/markdown/reference.rs b/src/app/browser/window/tab/item/page/content/text/markdown/reference.rs new file mode 100644 index 00000000..3ff177f3 --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/text/markdown/reference.rs @@ -0,0 +1,53 @@ +use gtk::glib::{Uri, UriFlags}; + +pub struct Reference { + pub uri: Uri, + pub alt: String, +} + +impl Reference { + pub fn parse(address: &str, alt: Option<&str>, base: &Uri) -> Option { + // Convert address to the valid URI, + // resolve to absolute URL format if the target is relative + match Uri::resolve_relative( + Some(&base.to_string()), + // Relative scheme patch + // https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 + &match address.strip_prefix("//") { + Some(p) => { + let s = p.trim_start_matches(":"); + format!( + "{}://{}", + base.scheme(), + if s.is_empty() { + format!("{}/", base.host().unwrap_or_default()) + } else { + s.into() + } + ) + } + None => String::new(), + }, + UriFlags::NONE, + ) { + Ok(ref url) => match Uri::parse(url, UriFlags::NONE) { + Ok(uri) => { + let mut a: Vec<&str> = Vec::with_capacity(2); + if uri.scheme() != base.scheme() { + a.push("⇖"); + } + match alt { + Some(text) => a.push(text), + None => a.push(url), + } + Some(Self { + uri, + alt: a.join(" "), + }) + } + Err(_) => todo!(), + }, + Err(_) => None, + } + } +}