diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 1266c5a6e5c2141be4255530d062f22cd87046fd..9b4302d02fc0a101cc609274b0abc42105402174 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -38,6 +38,7 @@ smol.workspace = true task.workspace = true theme.workspace = true thiserror.workspace = true +url.workspace = true util.workspace = true urlencoding.workspace = true @@ -49,5 +50,4 @@ gpui = { workspace = true, features = ["test-support"] } rand.workspace = true serde_json.workspace = true settings = { workspace = true, features = ["test-support"] } -url.workspace = true util_macros.workspace = true diff --git a/crates/terminal/src/terminal_hyperlinks.rs b/crates/terminal/src/terminal_hyperlinks.rs index 71a1634076b7081cce4f5cbaa155e7eec5d7f57e..cff27c4567cca84b2310723bf73bfda8d58c166d 100644 --- a/crates/terminal/src/terminal_hyperlinks.rs +++ b/crates/terminal/src/terminal_hyperlinks.rs @@ -14,6 +14,7 @@ use std::{ ops::{Index, Range}, time::{Duration, Instant}, }; +use url::Url; const URL_REGEX: &str = r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`']+"#; const WIDE_CHAR_SPACERS: Flags = @@ -128,8 +129,19 @@ pub(super) fn find_from_grid_point( if is_url { // Treat "file://" IRIs like file paths to ensure // that line numbers at the end of the path are - // handled correctly - if let Some(path) = maybe_url_or_path.strip_prefix("file://") { + // handled correctly. + // Use Url::to_file_path() to properly handle Windows drive letters + // (e.g., file:///C:/path -> C:\path) + if maybe_url_or_path.starts_with("file://") { + if let Ok(url) = Url::parse(&maybe_url_or_path) { + if let Ok(path) = url.to_file_path() { + return (path.to_string_lossy().into_owned(), false, word_match); + } + } + // Fallback: strip file:// prefix if URL parsing fails + let path = maybe_url_or_path + .strip_prefix("file://") + .unwrap_or(&maybe_url_or_path); (path.to_string(), false, word_match) } else { (maybe_url_or_path, true, word_match) @@ -1042,8 +1054,9 @@ mod tests { } mod file_iri { - // File IRIs have a ton of use cases, most of which we currently do not support. A few of - // those cases are documented here as tests which are expected to fail. + // File IRIs have a ton of use cases. Absolute file URIs are supported on all platforms, + // including Windows drive letters (e.g., file:///C:/path) and percent-encoded characters. + // Some cases like relative file IRIs are not supported. // See https://en.wikipedia.org/wiki/File_URI_scheme /// [**`c₀, c₁, …, cₙ;`**]ₒₚₜ := use specified terminal widths of `c₀, c₁, …, cₙ` **columns** @@ -1063,7 +1076,6 @@ mod tests { mod issues { #[cfg(not(target_os = "windows"))] #[test] - #[should_panic(expected = "Path = «/test/Ῥόδος/», at grid cells (0, 0)..=(15, 1)")] fn issue_file_iri_with_percent_encoded_characters() { // Non-space characters // file:///test/Ῥόδος/ @@ -1092,18 +1104,12 @@ mod tests { // See https://en.wikipedia.org/wiki/File_URI_scheme // https://github.com/zed-industries/zed/issues/39189 #[test] - #[should_panic( - expected = r#"Path = «C:\\test\\cool\\index.rs», at grid cells (0, 0)..=(9, 1)"# - )] fn issue_39189() { test_file_iri!("file:///C:/test/cool/index.rs"); test_file_iri!("file:///C:/test/cool/"); } #[test] - #[should_panic( - expected = r#"Path = «C:\\test\\Ῥόδος\\», at grid cells (0, 0)..=(16, 1)"# - )] fn issue_file_iri_with_percent_encoded_characters() { // Non-space characters // file:///test/Ῥόδος/