diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 198b3ebb89..e43c60a5b3 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -189,7 +189,17 @@ impl

PathLikeWithPosition

{ }) }; - match s.trim().split_once(FILE_ROW_COLUMN_DELIMITER) { + let trimmed = s.trim(); + + #[cfg(target_os = "windows")] + { + let is_absolute = trimmed.starts_with(r"\\?\"); + if is_absolute { + return Self::parse_absolute_path(trimmed, parse_path_like_str); + } + } + + match trimmed.split_once(FILE_ROW_COLUMN_DELIMITER) { Some((path_like_str, maybe_row_and_col_str)) => { let path_like_str = path_like_str.trim(); let maybe_row_and_col_str = maybe_row_and_col_str.trim(); @@ -243,6 +253,58 @@ impl

PathLikeWithPosition

{ } } + /// This helper function is used for parsing absolute paths on Windows. It exists because absolute paths on Windows are quite different from other platforms. See [this page](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#dos-device-paths) for more information. + #[cfg(target_os = "windows")] + fn parse_absolute_path( + s: &str, + parse_path_like_str: impl Fn(&str) -> Result, + ) -> Result { + let fallback = |fallback_str| { + Ok(Self { + path_like: parse_path_like_str(fallback_str)?, + row: None, + column: None, + }) + }; + + let mut iterator = s.split(FILE_ROW_COLUMN_DELIMITER); + + let drive_prefix = iterator.next().unwrap_or_default(); + let file_path = iterator.next().unwrap_or_default(); + + // TODO: How to handle drives without a letter? UNC paths? + let complete_path = drive_prefix.replace("\\\\?\\", "") + ":" + &file_path; + + if let Some(row_str) = iterator.next() { + if let Some(column_str) = iterator.next() { + match row_str.parse::() { + Ok(row) => match column_str.parse::() { + Ok(col) => { + return Ok(Self { + path_like: parse_path_like_str(&complete_path)?, + row: Some(row), + column: Some(col), + }); + } + + Err(_) => { + return Ok(Self { + path_like: parse_path_like_str(&complete_path)?, + row: Some(row), + column: None, + }); + } + }, + + Err(_) => { + return fallback(&complete_path); + } + } + } + } + return fallback(&complete_path); + } + pub fn map_path_like( self, mapping: impl FnOnce(P) -> Result, @@ -392,6 +454,7 @@ mod tests { // Trim off trailing `:`s for otherwise valid input. #[test] fn path_with_position_parsing_special() { + #[cfg(not(target_os = "windows"))] let input_and_expected = [ ( "test_file.rs:", @@ -419,6 +482,50 @@ mod tests { ), ]; + #[cfg(target_os = "windows")] + let input_and_expected = [ + ( + "test_file.rs:", + PathLikeWithPosition { + path_like: "test_file.rs".to_string(), + row: None, + column: None, + }, + ), + ( + "test_file.rs:1:", + PathLikeWithPosition { + path_like: "test_file.rs".to_string(), + row: Some(1), + column: None, + }, + ), + ( + "\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:", + PathLikeWithPosition { + path_like: "C:\\Users\\someone\\test_file.rs".to_string(), + row: Some(1902), + column: Some(13), + }, + ), + ( + "\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:15:", + PathLikeWithPosition { + path_like: "C:\\Users\\someone\\test_file.rs".to_string(), + row: Some(1902), + column: Some(13), + }, + ), + ( + "\\\\?\\C:\\Users\\someone\\test_file.rs:1902:::15:", + PathLikeWithPosition { + path_like: "C:\\Users\\someone\\test_file.rs".to_string(), + row: Some(1902), + column: None, + }, + ), + ]; + for (input, expected) in input_and_expected { let actual = parse_str(input); assert_eq!(