Add Windows specific path parsing (#11119)

Since Windows paths are known to be weird and currently not handled at
all (outside of relative paths that just happen to work), I figured I
would add a windows specific implementation for parsing absolute paths.
It should be functionally the same, of course there's always a chance I
missed an edge case though.

This should fix
- #10849

Note that there are still some cases that will probably break the
current implementation, namely local drives that do not have a drive
letter assigned (not sure how to handle those). There's also UNC paths
but I don't know how important those are at the moment (I'll allow
myself to assume not at all)

Release Notes:

- N/A
This commit is contained in:
Tim 2024-05-06 22:27:26 +02:00 committed by GitHub
parent 11bc28080f
commit 9edd81c740
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -189,7 +189,17 @@ impl<P> PathLikeWithPosition<P> {
})
};
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<P> PathLikeWithPosition<P> {
}
}
/// 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<E>(
s: &str,
parse_path_like_str: impl Fn(&str) -> Result<P, E>,
) -> Result<Self, E> {
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::<u32>() {
Ok(row) => match column_str.parse::<u32>() {
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<P2, E>(
self,
mapping: impl FnOnce(P) -> Result<P2, E>,
@ -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!(