Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 32 additions & 12 deletions crates/zed/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1554,21 +1554,41 @@ impl ToString for IdType {
}

fn parse_url_arg(arg: &str, cx: &App) -> String {
match std::fs::canonicalize(Path::new(&arg)) {
Ok(path) => format!("file://{}", path.display()),
Err(_) => {
if arg.starts_with("file://")
|| arg.starts_with("zed://")
|| arg.starts_with("zed-cli://")
|| arg.starts_with("ssh://")
|| parse_zed_link(arg, cx).is_some()
{
arg.into()
} else {
format!("file://{arg}")
if arg.starts_with("file://")
|| arg.starts_with("zed://")
|| arg.starts_with("zed-cli://")
|| arg.starts_with("ssh://")
|| parse_zed_link(arg, cx).is_some()
{
return arg.into();
}

#[cfg(target_os = "windows")]
{
use util::paths::PathWithPosition;

// On Windows, `path:line[:column]` is never a valid filesystem path (':' isn't allowed
// in filenames), but we still want to canonicalize the base path so opening works even
// when forwarding the request to an already-running Zed instance with a different CWD.
let path_with_position = PathWithPosition::parse_str(arg);
if let Some(row) = path_with_position.row {
if let Ok(canonicalized_path) = std::fs::canonicalize(&path_with_position.path) {
let mut canonicalized = canonicalized_path.display().to_string();
canonicalized.push(':');
canonicalized.push_str(&row.to_string());
if let Some(column) = path_with_position.column {
canonicalized.push(':');
canonicalized.push_str(&column.to_string());
}
return format!("file://{canonicalized}");
}
}
}

match std::fs::canonicalize(Path::new(arg)) {
Ok(path) => format!("file://{}", path.display()),
Err(_) => format!("file://{arg}"),
}
}

fn load_embedded_fonts(cx: &App) {
Expand Down
64 changes: 56 additions & 8 deletions crates/zed/src/zed/open_listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,16 +687,35 @@ pub async fn derive_paths_with_position(
path_strings: impl IntoIterator<Item = impl AsRef<str>>,
) -> Vec<PathWithPosition> {
join_all(path_strings.into_iter().map(|path_str| async move {
let canonicalized = fs.canonicalize(Path::new(path_str.as_ref())).await;
(path_str, canonicalized)
let original = path_str.as_ref();

// On Windows, `path:line[:column]` is never a valid filename (':' isn't allowed), but the
// filesystem may still accept it (e.g. NTFS alternate data streams). Canonicalizing the
// whole string can therefore succeed and cause us to drop the position suffix.
//
// To ensure `zed file:line[:column]` jumps to the right location, canonicalize only the
// base path and keep the parsed row/column.
#[cfg(target_os = "windows")]
{
let parsed = PathWithPosition::parse_str(original);
if parsed.row.is_some() {
return match fs.canonicalize(&parsed.path).await {
Ok(canonicalized) => PathWithPosition {
path: canonicalized,
row: parsed.row,
column: parsed.column,
},
Err(_) => parsed,
};
}
}

match fs.canonicalize(Path::new(original)).await {
Ok(canonicalized) => PathWithPosition::from_path(canonicalized),
Err(_) => PathWithPosition::parse_str(original),
}
}))
.await
.into_iter()
.map(|(original, canonicalized)| match canonicalized {
Ok(canonicalized) => PathWithPosition::from_path(canonicalized),
Err(_) => PathWithPosition::parse_str(original.as_ref()),
})
.collect()
}

#[cfg(test)]
Expand All @@ -718,6 +737,35 @@ mod tests {
use util::path;
use workspace::{AppState, Workspace};

#[cfg(target_os = "windows")]
#[gpui::test]
async fn test_derive_paths_with_position_preserves_row_on_windows(cx: &mut TestAppContext) {
init_test(cx);

let fs = fs::FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({
"README.md": "line 1\nline 2\nline 3\n",
// Create an entry for the full `path:line` string so `canonicalize` would succeed
// even if it were attempted on the full string.
"README.md:2": "not used\n",
}),
)
.await;

let original = format!("{}:2", path!("/dir/README.md"));
let parsed = derive_paths_with_position(fs.as_ref(), [&original]).await;
assert_eq!(
parsed,
vec![PathWithPosition {
path: path!("/dir/README.md").into(),
row: Some(2),
column: None,
}]
);
}

#[gpui::test]
fn test_parse_ssh_url(cx: &mut TestAppContext) {
let _app_state = init_test(cx);
Expand Down