From 16a076bd7f2233333f24dc22a9a359be1c550a22 Mon Sep 17 00:00:00 2001 From: Ben Kircher Date: Sun, 8 Feb 2026 11:23:48 +0100 Subject: [PATCH 01/17] url: refactor regex into documented branches Break up the big monolithic URL and path regex into named sub-pattern constants and compose the final expression from three commented branches: - URLs with a scheme - absolute or dot-relative paths - bare relative paths This commit only breaks up the regex. It keeps the existing matching behavior unchanged. --- src/config/url.zig | 88 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/src/config/url.zig b/src/config/url.zig index 5e78d4716..8102460f1 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -22,12 +22,6 @@ const oni = @import("oniguruma"); /// /// There are many complicated cases where these heuristics break down, but /// handling them well requires a non-regex approach. -pub const regex = - "(?:" ++ url_schemes ++ - \\)(?: - ++ ipv6_url_pattern ++ - \\|[\w\-.~:/?#@!$&*+,;=%]+(?:[\(\[]\w*[\)\]])?)+(? Date: Sun, 8 Feb 2026 11:34:25 +0100 Subject: [PATCH 02/17] url: update top-level comment --- src/config/url.zig | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/config/url.zig b/src/config/url.zig index 8102460f1..0dd03472d 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -1,15 +1,17 @@ const std = @import("std"); const oni = @import("oniguruma"); -/// Default URL regex. This is used to detect URLs in terminal output. +/// Default URL/path regex. This is used to detect URLs and file paths in +/// terminal output. +/// /// This is here in the config package because one day the matchers will be /// configurable and this will be a default. /// -/// This regex is liberal in what it accepts after the scheme, with exceptions -/// for URLs ending with . or ). Although such URLs are perfectly valid, it is -/// common for text to contain URLs surrounded by parentheses (such as in -/// Markdown links) or at the end of sentences. Therefore, this regex excludes -/// them as follows: +/// For scheme URLs, this regex is liberal in what it accepts after the scheme, +/// with exceptions for URLs ending with . or ). Although such URLs are +/// perfectly valid, it is common for text to contain URLs surrounded by +/// parentheses (such as in Markdown links) or at the end of sentences. +/// Therefore, this regex excludes them as follows: /// /// 1. Do not match regexes ending with . /// 2. Do not match regexes ending with ), except for ones which contain a ( From c6e0de0baeb55ed4ee519fbca2724c41f021ee3e Mon Sep 17 00:00:00 2001 From: Ben Kircher Date: Sun, 8 Feb 2026 13:19:38 +0100 Subject: [PATCH 03/17] url: carefully extend test cases Extend existing test cases with `~`, `$VAR`, and bare .-prefixed paths and embedded `,` comma handling. See following issue comments: - https://github.com/ghostty-org/ghostty/pull/10570#issuecomment-3853842036 - https://github.com/ghostty-org/ghostty/issues/1972#issuecomment-3859329233 - https://github.com/ghostty-org/ghostty/issues/1972#issuecomment-3857881196 --- src/config/url.zig | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/config/url.zig b/src/config/url.zig index 0dd03472d..26cd0bd89 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -155,7 +155,7 @@ test "url regex" { .expect = "https://example.com", }, .{ - .input = "Link trailing colon https://example.com, more text.", + .input = "Link trailing comma https://example.com, more text.", .expect = "https://example.com", }, .{ @@ -369,6 +369,47 @@ test "url regex" { .input = "some-pkg/src/file.txt more text", .expect = "some-pkg/src/file.txt", }, + .{ + .input = "~/foo/bar.txt", + .expect = "~/foo/bar.txt", + }, + .{ + .input = "open ~/Documents/notes.md please", + .expect = "~/Documents/notes.md", + }, + .{ + .input = "~/.config/ghostty/config", + .expect = "~/.config/ghostty/config", + }, + .{ + .input = "directory: ~/src/ghostty-org/ghostty", + .expect = "~/src/ghostty-org/ghostty", + }, + .{ + .input = "$HOME/src/config/url.zig", + .expect = "$HOME/src/config/url.zig", + }, + .{ + .input = "project dir: $PWD/src/ghostty/main.zig", + .expect = "$PWD/src/ghostty/main.zig", + }, + .{ + .input = ".config/ghostty/config", + .expect = ".config/ghostty/config", + }, + .{ + .input = "loaded from .local/share/ghostty/state.db now", + .expect = ".local/share/ghostty/state.db", + }, + .{ + .input = "../some/where", + .expect = "../some/where", + }, + // comma-separated file paths + .{ + .input = " - shared/src/foo/SomeItem.m:12, shared/src/", + .expect = "shared/src/foo/SomeItem.m:12", + }, }; for (cases) |case| { From 72a894b13b64a84eecbc04d48999a7914b95aacf Mon Sep 17 00:00:00 2001 From: Ben Kircher Date: Sun, 8 Feb 2026 13:35:36 +0100 Subject: [PATCH 04/17] url: remove `,` from path_chars Related to #1972 Fixes an issue when paths have embedded comma, e.g.: shared/src/foo/SomeItem.m:12, shared/src/ with path_chars greedily consuming the rest of the string. Now file path matching stops at comma. Scheme URLs are unchanged and still using the comma. --- src/config/url.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/url.zig b/src/config/url.zig index 26cd0bd89..7abedcf10 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -37,7 +37,7 @@ const scheme_url_chars = ; const path_chars = - \\[\w\-.~:\/?#@!$&*+,;=%] + \\[\w\-.~:\/?#@!$&*+;=%] ; const optional_bracketed_word_suffix = From d8eb89f384adbebd18459a670409789f554f45f3 Mon Sep 17 00:00:00 2001 From: Ben Kircher Date: Sun, 8 Feb 2026 13:41:23 +0100 Subject: [PATCH 05/17] url: fix matching `~`, `$VAR`, `.directory/` Related to #1972 This commit adds three new alternatives for `rooted_or_relative_path_prefix`: - `~/` - `$VAR` and - `.local/`, `.config/` etc. for dot-prefixed directory names --- src/config/url.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/url.zig b/src/config/url.zig index 7abedcf10..5f008b5e3 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -75,7 +75,7 @@ const scheme_url_branch = no_trailing_punctuation; const rooted_or_relative_path_prefix = - \\(?:\.\.\/|\.\/|(? Date: Sun, 8 Feb 2026 19:26:31 +0100 Subject: [PATCH 06/17] url: fix mid-string dot partial matches A string like foo.local/share should match fully, not partially. This commit fixes this by moving `dotted_path_lookahead` before `bare_relative_path_prefix` so the dot-check scans the entire match rather than only the text after it. --- src/config/url.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/config/url.zig b/src/config/url.zig index 5f008b5e3..7577642a9 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -101,8 +101,8 @@ const bare_relative_path_prefix = ; const bare_relative_path_branch = - bare_relative_path_prefix ++ dotted_path_lookahead ++ + bare_relative_path_prefix ++ path_chars ++ "+" ++ dotted_path_space_segments ++ trailing_spaces_at_eol; @@ -410,6 +410,11 @@ test "url regex" { .input = " - shared/src/foo/SomeItem.m:12, shared/src/", .expect = "shared/src/foo/SomeItem.m:12", }, + // mid-string dot should not partially match but fully + .{ + .input = "foo.local/share", + .expect = "foo.local/share", + }, }; for (cases) |case| { @@ -425,8 +430,8 @@ test "url regex" { try testing.expectEqualStrings(case.expect, match); } - // Bare relative paths without any dot should not match as file paths const no_match_cases = [_][]const u8{ + // bare relative paths without any dot should not match as file paths "input/output", "foo/bar", }; From af643a1a21f2694570faa86f3070b0e9775c810d Mon Sep 17 00:00:00 2001 From: Ben Kircher Date: Sun, 8 Feb 2026 20:58:47 +0100 Subject: [PATCH 07/17] url: fix $-numeric character matches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strings like $10/$20 Should not match. This commit fixes this by narrowing `\w` after `$` to `[A-Za-z_]` which is— according to Google Gemini— what environment variables can start with. --- src/config/url.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config/url.zig b/src/config/url.zig index 7577642a9..96bcc04b7 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -75,7 +75,7 @@ const scheme_url_branch = no_trailing_punctuation; const rooted_or_relative_path_prefix = - \\(?:\.\.\/|\.\/|~\/|\$\w+\/|\.[\w][\w\-.]*\/|(? Date: Sun, 8 Feb 2026 21:29:01 +0100 Subject: [PATCH 08/17] url: fix partial match of mid string $-variable A string like this foo/$BAR/baz should match fully, not partially. This commit fixes this by expanding `\$[A-Za-z_]\w*\/` to `(?:[\w][\w\-.]*\/)*\$[A-Za-z_]\w*\/` in rooted_or_relative_path_prefix so that we optionally eat everything before a variable `$VAR/`. --- src/config/url.zig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/config/url.zig b/src/config/url.zig index 96bcc04b7..c43427d24 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -75,7 +75,7 @@ const scheme_url_branch = no_trailing_punctuation; const rooted_or_relative_path_prefix = - \\(?:\.\.\/|\.\/|~\/|\$[A-Za-z_]\w*\/|\.[\w][\w\-.]*\/|(? Date: Sun, 8 Feb 2026 21:52:58 +0100 Subject: [PATCH 09/17] url: fix incomplete $-numeric behavior This $10/bar.txt was partially matching but should not match at all. This commit fixes this by simply `[\w]` to `[A-Za-z_]` as the first character of a bare relative path, so digit-starting fragments can't match. Another option would be a lookbehind but I think the check above is much simpler. --- src/config/url.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config/url.zig b/src/config/url.zig index c43427d24..66b67082c 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -97,7 +97,7 @@ const rooted_or_relative_path_branch = // Branch 3: Bare relative paths such as src/config/url.zig. const bare_relative_path_prefix = - \\[\w][\w\-.]*\/ + \\[A-Za-z_][\w\-.]*\/ ; const bare_relative_path_branch = @@ -446,6 +446,7 @@ test "url regex" { // $-numeric character should not match "$10/bar", "$10/$20", + "$10/bar.txt", }; for (no_match_cases) |input| { var result = re.search(input, .{}); From 19a41eb26b8f557c4c546786218a3f652a8fd342 Mon Sep 17 00:00:00 2001 From: Ben Kircher Date: Sun, 8 Feb 2026 22:25:35 +0100 Subject: [PATCH 10/17] url: allow numeric characters at start Amends and fixes the last commit which was too simpel. This commit uses a lookbehind to prevent matching $-numeric patterns. --- src/config/url.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/config/url.zig b/src/config/url.zig index 66b67082c..47541f096 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -97,7 +97,7 @@ const rooted_or_relative_path_branch = // Branch 3: Bare relative paths such as src/config/url.zig. const bare_relative_path_prefix = - \\[A-Za-z_][\w\-.]*\/ + \\(? Date: Mon, 9 Feb 2026 09:21:01 +0100 Subject: [PATCH 11/17] url: fix comma handling This string foo/bar,baz.txt should not match but src/foo.c,baz.txt should. Remove `,` from dotted_path_lookahead and non_dotted_path_lookahead. --- src/config/url.zig | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/config/url.zig b/src/config/url.zig index 47541f096..dd9ae94ad 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -53,11 +53,11 @@ const trailing_spaces_at_eol = ; const dotted_path_lookahead = - \\(?=[\w\-.~:\/?#@!$&*+,;=%]*\.) + \\(?=[\w\-.~:\/?#@!$&*+;=%]*\.) ; const non_dotted_path_lookahead = - \\(?![\w\-.~:\/?#@!$&*+,;=%]*\.) + \\(?![\w\-.~:\/?#@!$&*+;=%]*\.) ; const dotted_path_space_segments = @@ -369,6 +369,11 @@ test "url regex" { .input = "some-pkg/src/file.txt more text", .expect = "some-pkg/src/file.txt", }, + // comma should match substrings + .{ + .input = "src/foo.c,baz.txt", + .expect = "src/foo.c", + }, .{ .input = "~/foo/bar.txt", .expect = "~/foo/bar.txt", @@ -452,6 +457,8 @@ test "url regex" { "$10/bar", "$10/$20", "$10/bar.txt", + // comma should not let dot detection look past it + "foo/bar,baz.txt", }; for (no_match_cases) |input| { var result = re.search(input, .{}); From 270ee5468ff3c6220a6c9ed690f172083aa52856 Mon Sep 17 00:00:00 2001 From: Ben Kircher Date: Mon, 9 Feb 2026 09:54:09 +0100 Subject: [PATCH 12/17] url: fix $VAR mid-word partial matches Add `(? Date: Mon, 9 Feb 2026 10:23:24 +0100 Subject: [PATCH 13/17] url: fix tilde mid-word partial matches Don't match `~` mid-word or before `/`. --- src/config/url.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/config/url.zig b/src/config/url.zig index 9c39d9bc5..cb5e765e3 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -75,7 +75,7 @@ const scheme_url_branch = no_trailing_punctuation; const rooted_or_relative_path_prefix = - \\(?:\.\.\/|\.\/|~\/|(?:[\w][\w\-.]*\/)*(? Date: Mon, 9 Feb 2026 10:50:31 +0100 Subject: [PATCH 14/17] url: fix `,` handling for spaced paths Update path_space_segments patterns to consistently handle comma-delimiters. --- src/config/url.zig | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/config/url.zig b/src/config/url.zig index cb5e765e3..5797d4a56 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -61,11 +61,11 @@ const non_dotted_path_lookahead = ; const dotted_path_space_segments = - \\(?: [\w\-.~:\/?#@!$&*+,;=%]*[\/.])* + \\(?: [\w\-.~:\/?#@!$&*+;=%]*[\/.])* ; const any_path_space_segments = - \\(?: [\w\-.~:\/?#@!$&*+,;=%]+)* + \\(?: [\w\-.~:\/?#@!$&*+;=%]+)* ; // Branch 1: URLs with explicit schemes (http, mailto, ftp, etc.). @@ -434,6 +434,15 @@ test "url regex" { .input = "2024/report.txt", .expect = "2024/report.txt", }, + // comma should stop matching in spaced path segments + .{ + .input = "./foo bar,baz", + .expect = "./foo bar", + }, + .{ + .input = "/tmp/foo bar,baz", + .expect = "/tmp/foo bar", + }, }; for (cases) |case| { From 50ba394ed3d625849b870b1c5fb44d80f6b7d87a Mon Sep 17 00:00:00 2001 From: Ben Kircher Date: Mon, 9 Feb 2026 11:10:51 +0100 Subject: [PATCH 15/17] url: do not match on `//` Double-slash comments are not paths so do not match on them. --- src/config/url.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config/url.zig b/src/config/url.zig index 5797d4a56..5eb6e26f2 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -75,7 +75,7 @@ const scheme_url_branch = no_trailing_punctuation; const rooted_or_relative_path_prefix = - \\(?:\.\.\/|\.\/|(? Date: Wed, 11 Feb 2026 09:47:28 +0100 Subject: [PATCH 16/17] urls: fix over-matching single spaced paths This commit adds a negative lookahead `(?!\w+://)` to both `dotted_path_space_segments` and `any_path_space_segments`. This prevents the space-segment matching from consuming text that starts with a URL scheme (like http://), which was causing /tmp/test.txt http://www.google.com to over-match. Fixes https://github.com/ghostty-org/ghostty/issues/1972#issuecomment-3882254792 --- src/config/url.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/config/url.zig b/src/config/url.zig index 5eb6e26f2..fa902210f 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -61,11 +61,11 @@ const non_dotted_path_lookahead = ; const dotted_path_space_segments = - \\(?: [\w\-.~:\/?#@!$&*+;=%]*[\/.])* + \\(?: (?!\w+:\/\/)[\w\-.~:\/?#@!$&*+;=%]*[\/.])* ; const any_path_space_segments = - \\(?: [\w\-.~:\/?#@!$&*+;=%]+)* + \\(?: (?!\w+:\/\/)[\w\-.~:\/?#@!$&*+;=%]+)* ; // Branch 1: URLs with explicit schemes (http, mailto, ftp, etc.). @@ -226,6 +226,10 @@ test "url regex" { .input = "match git://example.com git links", .expect = "git://example.com", }, + .{ + .input = "/tmp/test.txt http://www.google.com", + .expect = "/tmp/test.txt", + }, .{ .input = "match tel:+18005551234 tel links", .expect = "tel:+18005551234", From 7d87a58a731dde41b70e0fd01bf0c03793722ff3 Mon Sep 17 00:00:00 2001 From: Ben Kircher Date: Sat, 14 Feb 2026 09:40:35 +0100 Subject: [PATCH 17/17] url: fix trailing colon in path matches Paths like `./.config/ghostty:` and `./Downloads:` were incorrectly including the trailing colon. Add a `no_trailing_colon` lookbehind to all path branches and prevent space segments from starting after a colon so that `": text"` is not consumed as part of the path. --- src/config/url.zig | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/config/url.zig b/src/config/url.zig index fa902210f..da0892a91 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -48,6 +48,10 @@ const no_trailing_punctuation = \\(?