diff --git a/manual/src/hyperlinks.md b/manual/src/hyperlinks.md index ac2f9ffd2..4568db04b 100644 --- a/manual/src/hyperlinks.md +++ b/manual/src/hyperlinks.md @@ -11,17 +11,18 @@ Commit hashes link to GitHub/GitLab/Bitbucket (use `hyperlinks-commit-link-forma The links on line numbers (in grep output, as well as diffs) are particularly interesting: with a little bit of effort, they can be made to open your editor or IDE at the correct line. Use `hyperlinks-file-link-format` to construct the correct URL for your system. -For VSCode and JetBrains IDEs this is easy, since they support their own special URL protocols. Here are examples: +For VSCode, Zed, and JetBrains IDEs this is easy, since they support their own special URL protocols. Here are examples: ```gitconfig [delta] hyperlinks = true - hyperlinks-file-link-format = "vscode://file/{path}:{line}" + hyperlinks-file-link-format = "vscode://file/{path}{:line}" + # hyperlinks-file-link-format = "zed://file/{path}{:line}" # hyperlinks-file-link-format = "idea://open?file={path}&line={line}" # hyperlinks-file-link-format = "pycharm://open?file={path}&line={line}" ``` -Zed also supports its own URL protocol, and probably others. +Available placeholders: `{path}` (absolute file path), `{line}` (line number), `{:line}` (line number with leading colon), and `{host}` (hostname). The `{:line}` placeholder is useful to avoid trailing colons when no line number is present. If your editor does not have its own URL protocol, then there are still many possibilities, although they may be more work. diff --git a/manual/src/tips-and-tricks/using-delta-with-vscode.md b/manual/src/tips-and-tricks/using-delta-with-vscode.md index 827881848..241ea547a 100644 --- a/manual/src/tips-and-tricks/using-delta-with-vscode.md +++ b/manual/src/tips-and-tricks/using-delta-with-vscode.md @@ -7,9 +7,9 @@ To format file links for opening in VSCode from other terminal emulators, use th ```gitconfig [delta] hyperlinks = true - hyperlinks-file-link-format = "vscode://file/{path}:{line}" + hyperlinks-file-link-format = "vscode://file/{path}{:line}" ``` -(To use VSCode Insiders, change that to `vscode-insiders://file/{path}:{line}`). +(To use VSCode Insiders, change that to `vscode-insiders://file/{path}{:line}`). See [hyperlinks](../hyperlinks.md). diff --git a/src/cli.rs b/src/cli.rs index 544b25ceb..b8522bbcf 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -434,11 +434,11 @@ pub struct Opt { )] /// Format string for file hyperlinks (requires --hyperlinks). /// - /// Placeholders "{path}" and "{line}" will be replaced by the absolute file path and the line - /// number; "{host}" with the hostname delta is currently running on. The default is to create - /// a hyperlink containing a standard file URI with only the filename, which your terminal or - /// OS should handle. You can specify any scheme, such as "file-line://{path}:{line}" and - /// register an application to handle it. See + /// Placeholders "{path}", "{line}", and "{:line}" (with colon prefix) will be replaced by the + /// absolute file path and line number; "{host}" with the hostname delta is currently running + /// on. The default is to create a hyperlink containing a standard file URI with only the + /// filename, which your terminal or OS should handle. You can specify any scheme, such as + /// "vscode://file/{path}{:line}" and register an application to handle it. See /// for details. pub hyperlinks_file_link_format: String, diff --git a/src/features/hyperlinks.rs b/src/features/hyperlinks.rs index a76d39d97..ef85f6c55 100644 --- a/src/features/hyperlinks.rs +++ b/src/features/hyperlinks.rs @@ -101,6 +101,13 @@ where if let Some(host) = &config.hostname { url = url.replace("{host}", host) } + + if let Some(n) = line_number { + url = url.replace("{:line}", &format!(":{n}")) + } else { + url = url.replace("{:line}", "") + } + if let Some(n) = line_number { url = url.replace("{line}", &format!("{n}")) } else { diff --git a/src/handlers/hunk_header.rs b/src/handlers/hunk_header.rs index fbbb68719..fcedad573 100644 --- a/src/handlers/hunk_header.rs +++ b/src/handlers/hunk_header.rs @@ -628,6 +628,82 @@ pub mod tests { assert_eq!(result, ""); } + #[test] + fn test_paint_file_path_with_line_number_hyperlinks_with_colon_line_placeholder() { + use std::{iter::FromIterator, path::PathBuf}; + + use crate::utils; + + // This test confirms that the {:line} placeholder expands to include the colon and line + // number (e.g. ":3") when a line number is present. This is useful for URL schemes like + // zed:// that treat a path ending with a colon as filename that includes a colon + let config = integration_test_utils::make_config_from_args(&[ + "--features", + "hyperlinks", + "--hyperlinks-file-link-format", + "zed://file/{path}{:line}", + ]); + let relative_path = PathBuf::from_iter(["some-dir", "some-file"]); + + let result = paint_file_path_with_line_number( + Some(3), + &relative_path.to_string_lossy(), + &config.hunk_header_style, + &config.hunk_header_line_number_style, + &config.hunk_header_style_include_file_path, + &config.hunk_header_style_include_line_number, + ":", + &config, + ); + + assert_eq!( + result, + format!( + "\u{1b}]8;;zed://file/{}:3\u{1b}\\\u{1b}[34m3\u{1b}[0m\u{1b}]8;;\u{1b}\\", + utils::path::fake_delta_cwd_for_tests() + .join(relative_path) + .to_string_lossy() + ) + ); + } + + #[test] + fn test_paint_file_path_with_line_number_hyperlinks_colon_line_placeholder_without_line() { + use std::{iter::FromIterator, path::PathBuf}; + + use crate::utils; + + // This test confirms that the {:line} placeholder expands to an empty string when no line + // number is present, avoiding a trailing colon in the URL. + let config = integration_test_utils::make_config_from_args(&[ + "--features", + "hyperlinks", + "--hyperlinks-file-link-format", + "zed://file/{path}{:line}", + ]); + let relative_path = PathBuf::from_iter(["some-dir", "some-file"]); + + let result = paint_file_path_with_line_number( + None, + &relative_path.to_string_lossy(), + &config.hunk_header_style, + &config.hunk_header_line_number_style, + &config.hunk_header_style_include_file_path, + &config.hunk_header_style_include_line_number, + ":", + &config, + ); + + let path = utils::path::fake_delta_cwd_for_tests() + .join(relative_path) + .to_string_lossy() + .to_string(); + assert!( + !result.contains(&format!("{}:", path)), + "URL should not have trailing colon" + ); + } + #[test] fn test_paint_file_path_with_line_number_empty_navigate() { let config = integration_test_utils::make_config_from_args(&[