From 332c0d03d12807b650e15b84634808e092e47569 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Dec 2025 22:40:48 -0700 Subject: [PATCH] Terminal regex perf improvements (#44679) Closes #44510 Release Notes: - Improve performance of terminal link matching even more --- Cargo.lock | 2 +- assets/settings/default.json | 34 ++++++++++------------ crates/terminal/Cargo.toml | 2 +- crates/terminal/src/terminal_hyperlinks.rs | 33 ++++----------------- 4 files changed, 23 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0989e7212c4d4d6a92931e01f2de263324bddae..7631b8f4c5d46437452ca1b42dfc1a2609cb0c54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16365,13 +16365,13 @@ dependencies = [ "alacritty_terminal", "anyhow", "collections", - "fancy-regex", "futures 0.3.31", "gpui", "itertools 0.14.0", "libc", "log", "rand 0.9.2", + "regex", "release_channel", "schemars", "serde", diff --git a/assets/settings/default.json b/assets/settings/default.json index f6c0b51be5fb5dd9c4b76aab6d90dab72c7e2e83..2eea3c34c6be3f34b5db9d5849b9070c5cfc7963 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1646,25 +1646,21 @@ // surrounding symbols or quotes [ "(?x)", - "# optionally starts with 0-2 opening prefix symbols", - "[({\\[<]{0,2}", - "# which may be followed by an opening quote", - "(?[\"'`])?", - "# `path` is the shortest sequence of any non-space character", - "(?(?[^ ]+?", - " # which may end with a line and optionally a column,", - " (?:+[0-9]+(:[0-9]+)?|:?\\([0-9]+([,:][0-9]+)?\\))?", - "))", - "# which must be followed by a matching quote", - "(?()\\k)", - "# and optionally a single closing symbol", - "[)}\\]>]?", - "# if line/column matched, may be followed by a description", - "(?():[^ 0-9][^ ]*)?", - "# which may be followed by trailing punctuation", - "[.,:)}\\]>]*", - "# and always includes trailing whitespace or end of line", - "([ ]+|$)", + "(?", + " (", + " # multi-char path: first char (not opening delimiter or space)", + " [^({\\[<\"'`\\ ]", + " # middle chars: non-space, and colon/paren only if not followed by digit/paren", + " ([^\\ :(]|[:(][^0-9()])*", + " # last char: not closing delimiter or colon", + " [^()}\\]>\"'`.,;:\\ ]", + " |", + " # single-char path: not delimiter, punctuation, or space", + " [^(){}\\[\\]<>\"'`.,;:\\ ]", + " )", + " # optional line/column suffix (included in path for PathWithPosition::parse_str)", + " (:+[0-9]+(:[0-9]+)?|:?\\([0-9]+([,:]?[0-9]+)?\\))?", + ")", ], ], // Timeout for hover and Cmd-click path hyperlink discovery in milliseconds. Specifying a diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index dac9db190dbd0864142a1d429b69db17b4ae25e9..1266c5a6e5c2141be4255530d062f22cd87046fd 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -28,6 +28,7 @@ gpui.workspace = true itertools.workspace = true libc.workspace = true log.workspace = true +regex.workspace = true release_channel.workspace = true schemars.workspace = true serde.workspace = true @@ -38,7 +39,6 @@ task.workspace = true theme.workspace = true thiserror.workspace = true util.workspace = true -fancy-regex.workspace = true urlencoding.workspace = true [target.'cfg(windows)'.dependencies] diff --git a/crates/terminal/src/terminal_hyperlinks.rs b/crates/terminal/src/terminal_hyperlinks.rs index 0d108ade3f9a5916aef21092bba98239b76e0131..71a1634076b7081cce4f5cbaa155e7eec5d7f57e 100644 --- a/crates/terminal/src/terminal_hyperlinks.rs +++ b/crates/terminal/src/terminal_hyperlinks.rs @@ -8,8 +8,8 @@ use alacritty_terminal::{ search::{Match, RegexIter, RegexSearch}, }, }; -use fancy_regex::Regex; use log::{info, warn}; +use regex::Regex; use std::{ ops::{Index, Range}, time::{Duration, Instant}, @@ -308,17 +308,6 @@ fn path_match( let mut path_found = false; for captures in regex.captures_iter(&line) { - let captures = match captures { - Ok(captures) => captures, - Err(error) => { - warn!("Error '{error}' searching for path hyperlinks in line: {line}"); - info!( - "Skipping match from path hyperlinks with regex: {}", - regex.as_str() - ); - continue; - } - }; path_found = true; let match_range = captures.get(0).unwrap().range(); let (path_range, line_column) = if let Some(path) = captures.name("path") { @@ -376,7 +365,7 @@ mod tests { term::{Config, cell::Flags, test::TermSize}, vte::ansi::Handler, }; - use fancy_regex::Regex; + use regex::Regex; use settings::{self, Settings, SettingsContent}; use std::{cell::RefCell, ops::RangeInclusive, path::PathBuf, rc::Rc}; use url::Url; @@ -386,7 +375,7 @@ mod tests { let results: Vec<_> = Regex::new(re) .unwrap() .find_iter(hay) - .map(|m| m.unwrap().as_str()) + .map(|m| m.as_str()) .collect(); assert_eq!(results, expected); } @@ -578,8 +567,6 @@ mod tests { test_path!("/test/cool.rs(4,2)👉:", "What is this?"); // path, line, column, and description - test_path!("/test/cool.rs:4:2👉:Error!"); - test_path!("/test/cool.rs:4:2:👉Error!"); test_path!("‹«/test/co👉ol.rs»:«4»:«2»›:Error!"); test_path!("‹«/test/co👉ol.rs»(«4»,«2»)›:Error!"); @@ -590,6 +577,7 @@ mod tests { // Python test_path!("‹«awe👉some.py»›"); + test_path!("‹«👉a»› "); test_path!(" ‹F👉ile \"«/awesome.py»\", line «42»›: Wat?"); test_path!(" ‹File \"«/awe👉some.py»\", line «42»›"); @@ -602,18 +590,14 @@ mod tests { // path, line, column and description test_path!("‹«/👉test/cool.rs»:«4»:«2»›:例Desc例例例"); test_path!("‹«/test/cool.rs»:«4»:«👉2»›:例Desc例例例"); - test_path!("/test/cool.rs:4:2:例Desc例👉例例"); test_path!("‹«/👉test/cool.rs»(«4»,«2»)›:例Desc例例例"); test_path!("‹«/test/cool.rs»(«4»👉,«2»)›:例Desc例例例"); - test_path!("/test/cool.rs(4,2):例Desc例👉例例"); // path, line, column and description w/extra colons test_path!("‹«/👉test/cool.rs»:«4»:«2»›::例Desc例例例"); test_path!("‹«/test/cool.rs»:«4»:«👉2»›::例Desc例例例"); - test_path!("/test/cool.rs:4:2::例Desc例👉例例"); test_path!("‹«/👉test/cool.rs»(«4»,«2»)›::例Desc例例例"); test_path!("‹«/test/cool.rs»(«4»,«2»👉)›::例Desc例例例"); - test_path!("/test/cool.rs(4,2)::例Desc例👉例例"); } #[test] @@ -658,8 +642,6 @@ mod tests { test_path!("‹«/test/co👉ol.rs»(«1»,«618»)›:"); test_path!("‹«/test/co👉ol.rs»::«42»›"); test_path!("‹«/test/co👉ol.rs»::«42»›:"); - test_path!("‹«/test/co👉ol.rs:4:2»(«1»,«618»)›"); - test_path!("‹«/test/co👉ol.rs:4:2»(«1»,«618»)›:"); test_path!("‹«/test/co👉ol.rs»(«1»,«618»)›::"); } @@ -675,7 +657,7 @@ mod tests { test_path!("<‹«/test/co👉ol.rs»:«4»›>"); test_path!("[\"‹«/test/co👉ol.rs»:«4»›\"]"); - test_path!("'‹«(/test/co👉ol.rs:4)»›'"); + test_path!("'(‹«/test/co👉ol.rs»:«4»›)'"); test_path!("\"‹«/test/co👉ol.rs»:«4»:«2»›\""); test_path!("'‹«/test/co👉ol.rs»:«4»:«2»›'"); @@ -724,7 +706,7 @@ mod tests { test_path!("‹«/test/co👉ol.rs»:«4»›:,"); test_path!("/test/cool.rs:4:👉,"); test_path!("[\"‹«/test/co👉ol.rs»:«4»›\"]:,"); - test_path!("'‹«(/test/co👉ol.rs:4),,»›'.."); + test_path!("'(‹«/test/co👉ol.rs»:«4»›),,'..."); test_path!("('‹«/test/co👉ol.rs»:«4»›'::: was here...)"); test_path!("[Here's <‹«/test/co👉ol.rs»:«4»›>]::: "); } @@ -849,9 +831,6 @@ mod tests { test_path!( "‹«test/c👉ontrollers/template_items_controller_test.rb»:«20»›:in 'block (2 levels) in '" ); - test_path!( - "test/controllers/template_items_controller_test.rb:19:i👉n 'block in '" - ); } #[test]