From 6a491751b6b879fb0585d4fd4fe2aa0c4c49f5e1 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 11 Mar 2026 04:33:50 +0200 Subject: [PATCH] fix buffer tag search by the fragment --- .../tab/item/page/content/text/markdown.rs | 48 ++++++++----------- .../item/page/content/text/markdown/tags.rs | 12 ++++- .../page/content/text/markdown/tags/header.rs | 10 ++-- 3 files changed, 34 insertions(+), 36 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 d059c605..a701f0e0 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 @@ -4,11 +4,11 @@ mod tags; use super::{ItemAction, WindowAction}; use crate::{app::browser::window::action::Position, profile::Profile}; use gtk::{ - EventControllerMotion, GestureClick, PopoverMenu, TextBuffer, TextSearchFlags, TextTag, - TextTagTable, TextView, TextWindowType, UriLauncher, Window, WrapMode, + EventControllerMotion, GestureClick, PopoverMenu, TextBuffer, TextTag, TextTagTable, TextView, + TextWindowType, UriLauncher, Window, WrapMode, gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY, BUTTON_SECONDARY, Display, RGBA}, gio::{Cancellable, Menu, SimpleAction, SimpleActionGroup}, - glib::{ControlFlow, GString, Uri, idle_add_local, uri_unescape_string, uuid_string_random}, + glib::{ControlFlow, GString, Uri, idle_add_local, uuid_string_random}, prelude::{PopoverExt, TextBufferExt, TextTagExt, TextViewExt, WidgetExt}, }; use gutter::Gutter; @@ -312,6 +312,7 @@ impl Markdown { // Init events primary_button_controller.connect_released({ + let headers = headers.clone(); let item_action = item_action.clone(); let links = links.clone(); let text_view = text_view.clone(); @@ -327,7 +328,7 @@ impl Markdown { // Tag is link if let Some(uri) = links.get(&tag) { return if let Some(fragment) = uri.fragment() { - scroll_to_anchor(&text_view, fragment); + scroll_to_anchor(&text_view, &headers, fragment); } else { open_link_in_current_tab(&uri.to_string(), &item_action); }; @@ -338,10 +339,10 @@ impl Markdown { }); secondary_button_controller.connect_pressed({ - let links = links.clone(); let headers = headers.clone(); - let text_view = text_view.clone(); let link_context = link_context.clone(); + let links = links.clone(); + let text_view = text_view.clone(); move |_, _, window_x, window_y| { let x = window_x as i32; let y = window_y as i32; @@ -518,7 +519,7 @@ impl Markdown { let text_view = text_view.clone(); move || { if let Some(fragment) = base.fragment() { - scroll_to_anchor(&text_view, fragment); + scroll_to_anchor(&text_view, &headers, fragment); } ControlFlow::Break } @@ -528,29 +529,20 @@ impl Markdown { } } -fn scroll_to_anchor(text_view: &TextView, fragment: GString) -> bool { - fn try_scroll(text_view: &TextView, query: &str) -> bool { - let mut cursor = text_view.buffer().start_iter(); - while let Some((mut match_start, match_end)) = - cursor.forward_search(query, TextSearchFlags::CASE_INSENSITIVE, None) - { - if match_start - .tags() - .iter() - .any(|t| t.name().is_some_and(|n| n.starts_with("h"))) - { - return text_view.scroll_to_iter(&mut match_start, 0.0, true, 0.0, 0.0); - } - cursor = match_end; +fn scroll_to_anchor( + text_view: &TextView, + headers: &HashMap, + fragment: GString, +) { + if let Some((tag, _)) = headers.iter().find(|(_, (_, uri))| { + uri.fragment() + .is_some_and(|f| fragment == tags::format_header_fragment(&f)) + }) { + let mut iter = text_view.buffer().start_iter(); + if iter.starts_tag(Some(tag)) || iter.forward_to_tag_toggle(Some(tag)) { + text_view.scroll_to_iter(&mut iter, 0.0, true, 0.0, 0.0); } - false } - let query = uri_unescape_string(&fragment, None::<&str>).unwrap_or(fragment); - let result = try_scroll(text_view, &query); // exact match - if !result { - return try_scroll(text_view, &query.replace(" ", "-")); // alt syntax - } - result } fn is_internal_link(request: &str) -> bool { 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 96b590c9..e33ed470 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 @@ -10,7 +10,12 @@ mod underline; use bold::Bold; use code::Code; -use gtk::{TextBuffer, TextSearchFlags, TextTag, gdk::RGBA, glib::Uri, prelude::TextBufferExt}; +use gtk::{ + TextBuffer, TextSearchFlags, TextTag, + gdk::RGBA, + glib::{GString, Uri}, + prelude::TextBufferExt, +}; use header::Header; use pre::Pre; use quote::Quote; @@ -99,4 +104,9 @@ impl Tags { } } +/// Shared URL #fragment logic (for the Header tags ref) +pub fn format_header_fragment(value: &str) -> GString { + Uri::escape_string(&value.to_lowercase().replace(" ", "-"), None, true) +} + const ESC: &str = "\\"; diff --git a/src/app/browser/window/tab/item/page/content/text/markdown/tags/header.rs b/src/app/browser/window/tab/item/page/content/text/markdown/tags/header.rs index 8a2e125e..e399d92c 100644 --- a/src/app/browser/window/tab/item/page/content/text/markdown/tags/header.rs +++ b/src/app/browser/window/tab/item/page/content/text/markdown/tags/header.rs @@ -126,8 +126,8 @@ impl Header { } // Create unique phantom tag for each header - // * it is required for context menu relationships - let h = TextTag::builder().build(); + // * for the #fragment references implementation + let h = TextTag::new(Some(&format!("h{}", gtk::glib::uuid_string_random()))); assert!(table.add(&h)); // Render header in text buffer @@ -158,11 +158,7 @@ impl Header { base.port(), &base.path(), base.query().as_deref(), - Some(&Uri::escape_string( - &cap["title"].to_lowercase().replace(" ", "-"), - None, - true - )), + Some(&super::format_header_fragment(&cap["title"])), ) ), )