From 0315c343fed7db27a90550ba9f9d17491ae21f45 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 22 Aug 2026 16:38:04 +0000 Subject: [PATCH] feat: optimize terminal scrollback search with ASCII fast-path Hoist target character's ASCII lower/upper conversions outside the search loop and use them for a fast short-circuit check against ASCII haystack characters. Co-authored-by: Lucenx9 <185146821+Lucenx9@users.noreply.github.com> --- .../src/gtk_app/terminal_search.rs | 16 ++++++- patch.py | 43 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 patch.py diff --git a/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs b/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs index 90694aa2..1dbc6e6e 100644 --- a/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs +++ b/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs @@ -74,12 +74,26 @@ fn for_each_char_match_start( if needle.is_empty() { return; } + // ⚡ Bolt: Fast path for case-insensitive search. + // Hoist target character's ASCII lower/upper conversions outside the loop. + // If the haystack character is ASCII, we can directly compare it against bounds. let first_needle = needle[0]; + let first_lower = first_needle.to_ascii_lowercase(); + let first_upper = first_needle.to_ascii_uppercase(); + // Only enable the ASCII fast-path if the needle itself is ASCII. + // If the needle is non-ASCII (e.g. Kelvin sign \u{212A}), it might still case-fold + // to an ASCII character, so we must fall back to full Unicode matching. + let can_fast_path = first_needle.is_ascii(); let mut index = 0; while index + needle.len() <= haystack.len() { // Fast-path: short-circuit the full substring check if the first character // doesn't match, avoiding iterator overhead in the common case. - if !chars_eq_ignore_case(haystack[index], first_needle) { + let h = haystack[index]; + if can_fast_path && h.is_ascii() && h != first_lower && h != first_upper { + index += 1; + continue; + } + if !chars_eq_ignore_case(h, first_needle) { index += 1; continue; } diff --git a/patch.py b/patch.py new file mode 100644 index 00000000..67d08971 --- /dev/null +++ b/patch.py @@ -0,0 +1,43 @@ +import sys + +with open("crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs", "r") as f: + text = f.read() + +search = """ + let first_needle = needle[0]; + let first_lower = first_needle.to_ascii_lowercase(); + let first_upper = first_needle.to_ascii_uppercase(); + let mut index = 0; + while index + needle.len() <= haystack.len() { + // Fast-path: short-circuit the full substring check if the first character + // doesn't match, avoiding iterator overhead in the common case. + let h = haystack[index]; + if h.is_ascii() && h != first_lower && h != first_upper { + index += 1; + continue; + } +""" + +replace = r""" + let first_needle = needle[0]; + let first_lower = first_needle.to_ascii_lowercase(); + let first_upper = first_needle.to_ascii_uppercase(); + // Only enable the ASCII fast-path if the needle itself is ASCII. + // If the needle is non-ASCII (e.g. Kelvin sign \u{212A}), it might still case-fold + // to an ASCII character, so we must fall back to full Unicode matching. + let can_fast_path = first_needle.is_ascii(); + let mut index = 0; + while index + needle.len() <= haystack.len() { + // Fast-path: short-circuit the full substring check if the first character + // doesn't match, avoiding iterator overhead in the common case. + let h = haystack[index]; + if can_fast_path && h.is_ascii() && h != first_lower && h != first_upper { + index += 1; + continue; + } +""" + +text = text.replace(search.strip(), replace.strip()) + +with open("crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs", "w") as f: + f.write(text)