diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6d0fceb..2954d42e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: - name: Install Zig uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2 with: - version: 0.15.2 + version: 0.14.0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable @@ -117,7 +117,7 @@ jobs: - name: Install Zig uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2 with: - version: 0.15.2 + version: 0.14.0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable @@ -176,7 +176,7 @@ jobs: - name: Install Zig uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2 with: - version: 0.15.2 + version: 0.14.0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..0992d4e5 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - Fast paths for terminal search +**Learning:** Checking the first character of the search query inside the loop and converting the characters to lower-case is very slow. Comparing it against statically available ASCII boundaries drastically improves speed. +**Action:** Always extract static loop invariant conditions, and attempt to utilize ascii checks (`.is_ascii()` and `.eq_ignore_ascii_case()`) before falling back to full string `.to_lowercase()` processing. E.g. in `crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs`. 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..3331cb36 100644 --- a/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs +++ b/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs @@ -75,14 +75,26 @@ fn for_each_char_match_start( return; } let first_needle = needle[0]; + let is_ascii_fast_path = first_needle.is_ascii(); + 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. - if !chars_eq_ignore_case(haystack[index], first_needle) { + let h = haystack[index]; + if is_ascii_fast_path { + if h != first_lower + && h != first_upper + && (h.is_ascii() || !chars_eq_ignore_case(h, first_needle)) + { + index += 1; + continue; + } + } else if !chars_eq_ignore_case(h, first_needle) { index += 1; continue; } + let matched = haystack[index + 1..index + needle.len()] .iter() .zip(&needle[1..]) diff --git a/crates/forktty-ui-gtk/src/socket_cli/hooks/event.rs b/crates/forktty-ui-gtk/src/socket_cli/hooks/event.rs index a736c2d5..cf877581 100644 --- a/crates/forktty-ui-gtk/src/socket_cli/hooks/event.rs +++ b/crates/forktty-ui-gtk/src/socket_cli/hooks/event.rs @@ -287,9 +287,9 @@ pub(in crate::socket_cli) fn hook_debug(context: &CliContext, message: &str) { } pub(in crate::socket_cli) fn is_truthy_env(key: &str) -> bool { - trimmed_env(key) - .map(|value| matches!(value.to_lowercase().as_str(), "1" | "true" | "yes")) - .unwrap_or(false) + trimmed_env(key).is_some_and(|v| { + v == "1" || v.eq_ignore_ascii_case("true") || v.eq_ignore_ascii_case("yes") + }) } /// Hook event ordering must survive wall-clock steps (NTP, manual `date`): diff --git a/patch_zig.sh b/patch_zig.sh new file mode 100755 index 00000000..0008208b --- /dev/null +++ b/patch_zig.sh @@ -0,0 +1,3 @@ +#!/bin/bash +sed -i 's/\.name = \.ghostty,/.name = "ghostty",/' target/debug/build/libghostty-vt-sys-*/out/ghostty-src/build.zig.zon +sed -i 's/minimum_zig_version = "0.15.2"/minimum_zig_version = "0.13.0"/' target/debug/build/libghostty-vt-sys-*/out/ghostty-src/build.zig.zon