feat(external-tools): force tools to open in new windows where needed
Adds a helper that forces selected tools to launch in a new window instead of reusing the current one. This is applied to code editors, diff/merge, and terminal launches, aligning behavior across platforms. Presets and defaults are updated to pass new-window or equivalent flags, and tests verify the new behavior for common tools. - Update code editors to always use a new window when opened - Normalize launch flags for Windows terminals and diff tools - Add tests covering new-window behavior for common tools
This commit is contained in:
@@ -405,8 +405,10 @@ fn known_tools() -> Vec<ToolSpec> {
|
|||||||
tool(
|
tool(
|
||||||
"git-bash",
|
"git-bash",
|
||||||
"Git Bash",
|
"Git Bash",
|
||||||
&["bash.exe"],
|
&["git-bash.exe", "bash.exe"],
|
||||||
&[
|
&[
|
||||||
|
r"%PROGRAMFILES%\Git\git-bash.exe",
|
||||||
|
r"%LOCALAPPDATA%\Programs\Git\git-bash.exe",
|
||||||
r"%PROGRAMFILES%\Git\bin\bash.exe",
|
r"%PROGRAMFILES%\Git\bin\bash.exe",
|
||||||
r"%LOCALAPPDATA%\Programs\Git\bin\bash.exe",
|
r"%LOCALAPPDATA%\Programs\Git\bin\bash.exe",
|
||||||
],
|
],
|
||||||
@@ -1350,6 +1352,101 @@ fn expand_args(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn command_in_new_window(mut command: ExternalToolCommand) -> ExternalToolCommand {
|
||||||
|
let executable = Path::new(command.program.trim())
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.unwrap_or(command.program.trim())
|
||||||
|
.to_ascii_lowercase();
|
||||||
|
|
||||||
|
let remove_flags = |args: &mut Vec<String>, flags: &[&str]| {
|
||||||
|
args.retain(|arg| !flags.iter().any(|flag| arg.eq_ignore_ascii_case(flag)));
|
||||||
|
};
|
||||||
|
let prepend_flag = |args: &mut Vec<String>, flag: &str| {
|
||||||
|
if !args.iter().any(|arg| arg.eq_ignore_ascii_case(flag)) {
|
||||||
|
args.insert(0, flag.to_string());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
executable.as_str(),
|
||||||
|
"code"
|
||||||
|
| "code.exe"
|
||||||
|
| "code-insiders"
|
||||||
|
| "code-insiders.exe"
|
||||||
|
| "code - insiders.exe"
|
||||||
|
| "cursor"
|
||||||
|
| "cursor.exe"
|
||||||
|
| "windsurf"
|
||||||
|
| "windsurf.exe"
|
||||||
|
| "vscodium"
|
||||||
|
| "vscodium.exe"
|
||||||
|
| "codium"
|
||||||
|
| "codium.exe"
|
||||||
|
) {
|
||||||
|
remove_flags(&mut command.args, &["--reuse-window", "-r"]);
|
||||||
|
prepend_flag(&mut command.args, "--new-window");
|
||||||
|
} else if matches!(executable.as_str(), "zed" | "zed.exe") {
|
||||||
|
remove_flags(&mut command.args, &["--reuse", "-r"]);
|
||||||
|
prepend_flag(&mut command.args, "--new");
|
||||||
|
} else if matches!(
|
||||||
|
executable.as_str(),
|
||||||
|
"subl" | "subl.exe" | "sublime_text" | "sublime_text.exe"
|
||||||
|
) {
|
||||||
|
prepend_flag(&mut command.args, "--new-window");
|
||||||
|
} else if executable == "notepad++.exe" {
|
||||||
|
prepend_flag(&mut command.args, "-multiInst");
|
||||||
|
} else if executable == "kate" || executable == "kate.exe" {
|
||||||
|
prepend_flag(&mut command.args, "--new");
|
||||||
|
} else if executable == "geany" || executable == "geany.exe" {
|
||||||
|
prepend_flag(&mut command.args, "--new-instance");
|
||||||
|
} else if executable == "wt" || executable == "wt.exe" {
|
||||||
|
let mut index = 0;
|
||||||
|
while index < command.args.len() {
|
||||||
|
let is_window = command.args[index].eq_ignore_ascii_case("-w")
|
||||||
|
|| command.args[index].eq_ignore_ascii_case("--window")
|
||||||
|
|| command.args[index]
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.starts_with("--window=");
|
||||||
|
if is_window {
|
||||||
|
let has_separate_value = !command.args[index].contains('=');
|
||||||
|
command.args.remove(index);
|
||||||
|
if has_separate_value && index < command.args.len() {
|
||||||
|
command.args.remove(index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command
|
||||||
|
.args
|
||||||
|
.splice(0..0, ["-w".to_string(), "new".to_string()]);
|
||||||
|
} else if executable == "explorer" || executable == "explorer.exe" {
|
||||||
|
prepend_flag(&mut command.args, "/n,");
|
||||||
|
} else if matches!(
|
||||||
|
executable.as_str(),
|
||||||
|
"totalcmd" | "totalcmd.exe" | "totalcmd64" | "totalcmd64.exe"
|
||||||
|
) {
|
||||||
|
command
|
||||||
|
.args
|
||||||
|
.retain(|arg| !arg.to_ascii_lowercase().starts_with("/o"));
|
||||||
|
prepend_flag(&mut command.args, "/N");
|
||||||
|
} else if executable == "open" {
|
||||||
|
prepend_flag(&mut command.args, "-n");
|
||||||
|
} else if executable.starts_with("bcomp") {
|
||||||
|
remove_flags(&mut command.args, &["/solo", "-solo"]);
|
||||||
|
#[cfg(windows)]
|
||||||
|
prepend_flag(&mut command.args, "/solo");
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
prepend_flag(&mut command.args, "-solo");
|
||||||
|
} else if executable == "winmergeu" || executable == "winmergeu.exe" {
|
||||||
|
remove_flags(&mut command.args, &["/s", "/sw", "-s", "-sw"]);
|
||||||
|
prepend_flag(&mut command.args, "/s-");
|
||||||
|
}
|
||||||
|
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
fn tool_values(repo: &Path, file: Option<&Path>) -> BTreeMap<&'static str, String> {
|
fn tool_values(repo: &Path, file: Option<&Path>) -> BTreeMap<&'static str, String> {
|
||||||
let mut values = BTreeMap::new();
|
let mut values = BTreeMap::new();
|
||||||
values.insert("repo", repo.to_string_lossy().into_owned());
|
values.insert("repo", repo.to_string_lossy().into_owned());
|
||||||
@@ -1371,11 +1468,10 @@ fn spawn_tool(
|
|||||||
repo: &Path,
|
repo: &Path,
|
||||||
values: BTreeMap<&str, String>,
|
values: BTreeMap<&str, String>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
|
let command = command_in_new_window(command);
|
||||||
let args = expand_args(&command, &values)?;
|
let args = expand_args(&command, &values)?;
|
||||||
let mut process = Command::new(command.program.trim());
|
let mut process = Command::new(command.program.trim());
|
||||||
process.args(args).current_dir(repo);
|
process.args(args).current_dir(repo);
|
||||||
#[cfg(windows)]
|
|
||||||
process.creation_flags(CREATE_NO_WINDOW);
|
|
||||||
process
|
process
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|error| format!("Could not launch external tool: {error}"))?;
|
.map_err(|error| format!("Could not launch external tool: {error}"))?;
|
||||||
@@ -1388,6 +1484,7 @@ fn run_tool(
|
|||||||
values: BTreeMap<&str, String>,
|
values: BTreeMap<&str, String>,
|
||||||
kind: ExternalToolRunKind,
|
kind: ExternalToolRunKind,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
|
let command = command_in_new_window(command);
|
||||||
let args = expand_args(&command, &values)?;
|
let args = expand_args(&command, &values)?;
|
||||||
let program = command.program.trim().to_owned();
|
let program = command.program.trim().to_owned();
|
||||||
let mut process = Command::new(command.program.trim());
|
let mut process = Command::new(command.program.trim());
|
||||||
@@ -1580,6 +1677,62 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forces_code_family_tools_into_a_new_window() {
|
||||||
|
let command = command_in_new_window(ExternalToolCommand {
|
||||||
|
program: "C:/Program Files/Microsoft VS Code/Code.exe".into(),
|
||||||
|
args: vec![
|
||||||
|
"--reuse-window".into(),
|
||||||
|
"--diff".into(),
|
||||||
|
"{left}".into(),
|
||||||
|
"{right}".into(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
command.args,
|
||||||
|
vec!["--new-window", "--diff", "{left}", "{right}"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forces_windows_terminal_to_use_a_fresh_window() {
|
||||||
|
let command = command_in_new_window(ExternalToolCommand {
|
||||||
|
program: "wt.exe".into(),
|
||||||
|
args: vec![
|
||||||
|
"--window".into(),
|
||||||
|
"last".into(),
|
||||||
|
"-d".into(),
|
||||||
|
"{repo}".into(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(command.args, vec!["-w", "new", "-d", "{repo}"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forces_single_instance_compare_and_file_tools_into_new_windows() {
|
||||||
|
let beyond = command_in_new_window(ExternalToolCommand {
|
||||||
|
program: "BCompare.exe".into(),
|
||||||
|
args: vec!["/readonly".into(), "{left}".into(), "{right}".into()],
|
||||||
|
});
|
||||||
|
let winmerge = command_in_new_window(ExternalToolCommand {
|
||||||
|
program: "WinMergeU.exe".into(),
|
||||||
|
args: vec!["/s".into(), "{left}".into(), "{right}".into()],
|
||||||
|
});
|
||||||
|
let total_commander = command_in_new_window(ExternalToolCommand {
|
||||||
|
program: "TOTALCMD64.EXE".into(),
|
||||||
|
args: vec!["/O".into(), "/T".into(), "{repo}".into()],
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!(beyond.args[0], "/solo");
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
assert_eq!(beyond.args[0], "-solo");
|
||||||
|
assert_eq!(winmerge.args, vec!["/s-", "{left}", "{right}"]);
|
||||||
|
assert_eq!(total_commander.args, vec!["/N", "/T", "{repo}"]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_unknown_placeholder() {
|
fn rejects_unknown_placeholder() {
|
||||||
let command = ExternalToolCommand {
|
let command = ExternalToolCommand {
|
||||||
|
|||||||
+39
-32
@@ -53,15 +53,15 @@ const jetBrains: ToolIdentity[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const editor = (tool: ToolIdentity, args = ["{file}"]): ExternalToolPreset => ({ ...tool, args });
|
const editor = (tool: ToolIdentity, args = ["{file}"]): ExternalToolPreset => ({ ...tool, args });
|
||||||
const codeDiff = (tool: ToolIdentity): ExternalToolPreset => ({ ...tool, args: ["--wait", "--diff", "{left}", "{right}"] });
|
const codeDiff = (tool: ToolIdentity): ExternalToolPreset => ({ ...tool, args: ["--new-window", "--wait", "--diff", "{left}", "{right}"] });
|
||||||
const codeMerge = (tool: ToolIdentity): ExternalToolPreset => ({ ...tool, args: ["--wait", "--merge", "{ours}", "{theirs}", "{base}", "{result}"] });
|
const codeMerge = (tool: ToolIdentity): ExternalToolPreset => ({ ...tool, args: ["--new-window", "--wait", "--merge", "{ours}", "{theirs}", "{base}", "{result}"] });
|
||||||
const jetBrainsDiff = (tool: ToolIdentity): ExternalToolPreset => ({ ...tool, args: ["diff", "{left}", "{right}"] });
|
const jetBrainsDiff = (tool: ToolIdentity): ExternalToolPreset => ({ ...tool, args: ["diff", "{left}", "{right}"] });
|
||||||
const jetBrainsMerge = (tool: ToolIdentity): ExternalToolPreset => ({ ...tool, args: ["merge", "{ours}", "{theirs}", "{base}", "{result}"] });
|
const jetBrainsMerge = (tool: ToolIdentity): ExternalToolPreset => ({ ...tool, args: ["merge", "{ours}", "{theirs}", "{base}", "{result}"] });
|
||||||
|
|
||||||
const commonEditors: ExternalToolPreset[] = [
|
const commonEditors: ExternalToolPreset[] = [
|
||||||
...codeFamily.map((item) => editor(item, ["--reuse-window", "{file}"])),
|
...codeFamily.map((item) => editor(item, ["--new-window", "{file}"])),
|
||||||
editor({ id: "zed", label: "Zed", program: externalToolsPlatform === "windows" ? "zed.exe" : "zed" }),
|
editor({ id: "zed", label: "Zed", program: externalToolsPlatform === "windows" ? "zed.exe" : "zed" }, ["--new", "{file}"]),
|
||||||
editor({ id: "sublime-text", label: "Sublime Text", program: externalToolsPlatform === "windows" ? "subl.exe" : "subl" }),
|
editor({ id: "sublime-text", label: "Sublime Text", program: externalToolsPlatform === "windows" ? "subl.exe" : "subl" }, ["--new-window", "{file}"]),
|
||||||
...jetBrains.map((item) => editor(item)),
|
...jetBrains.map((item) => editor(item)),
|
||||||
editor({ id: "neovim", label: "Neovim", program: externalToolsPlatform === "windows" ? "nvim.exe" : "nvim" }),
|
editor({ id: "neovim", label: "Neovim", program: externalToolsPlatform === "windows" ? "nvim.exe" : "nvim" }),
|
||||||
editor({ id: "vim", label: "Vim", program: externalToolsPlatform === "windows" ? "gvim.exe" : "gvim" }),
|
editor({ id: "vim", label: "Vim", program: externalToolsPlatform === "windows" ? "gvim.exe" : "gvim" }),
|
||||||
@@ -70,7 +70,7 @@ const commonEditors: ExternalToolPreset[] = [
|
|||||||
|
|
||||||
const platformEditors: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
const platformEditors: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
||||||
? [
|
? [
|
||||||
editor({ id: "notepad-plus-plus", label: "Notepad++", program: "notepad++.exe" }),
|
editor({ id: "notepad-plus-plus", label: "Notepad++", program: "notepad++.exe" }, ["-multiInst", "{file}"]),
|
||||||
{ ...editor({ id: "notepad", label: "Windows Notepad", program: "notepad.exe" }), builtIn: true },
|
{ ...editor({ id: "notepad", label: "Windows Notepad", program: "notepad.exe" }), builtIn: true },
|
||||||
]
|
]
|
||||||
: externalToolsPlatform === "macos"
|
: externalToolsPlatform === "macos"
|
||||||
@@ -82,16 +82,16 @@ const platformEditors: ExternalToolPreset[] = externalToolsPlatform === "windows
|
|||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
editor({ id: "lapce", label: "Lapce", program: "lapce" }),
|
editor({ id: "lapce", label: "Lapce", program: "lapce" }),
|
||||||
editor({ id: "kate", label: "Kate", program: "kate" }),
|
editor({ id: "kate", label: "Kate", program: "kate" }, ["--new", "{file}"]),
|
||||||
editor({ id: "gedit", label: "GNOME Text Editor", program: "gnome-text-editor" }),
|
editor({ id: "gedit", label: "GNOME Text Editor", program: "gnome-text-editor" }),
|
||||||
editor({ id: "geany", label: "Geany", program: "geany" }),
|
editor({ id: "geany", label: "Geany", program: "geany" }, ["--new-instance", "{file}"]),
|
||||||
editor({ id: "helix", label: "Helix", program: "hx" }),
|
editor({ id: "helix", label: "Helix", program: "hx" }),
|
||||||
];
|
];
|
||||||
|
|
||||||
const dedicatedDiff: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
const dedicatedDiff: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
||||||
? [
|
? [
|
||||||
{ id: "beyond-compare", label: "Beyond Compare", program: "BCompare.exe", args: ["/readonly", "{left}", "{right}"] },
|
{ id: "beyond-compare", label: "Beyond Compare", program: "BCompare.exe", args: ["/solo", "/readonly", "{left}", "{right}"] },
|
||||||
{ id: "winmerge", label: "WinMerge", program: "WinMergeU.exe", args: ["/u", "/e", "/wl", "/wr", "{left}", "{right}"] },
|
{ id: "winmerge", label: "WinMerge", program: "WinMergeU.exe", args: ["/s-", "/u", "/e", "/wl", "/wr", "{left}", "{right}"] },
|
||||||
{ id: "meld", label: "Meld", program: "meld.exe", args: ["--wait", "{left}", "{right}"] },
|
{ id: "meld", label: "Meld", program: "meld.exe", args: ["--wait", "{left}", "{right}"] },
|
||||||
{ id: "kdiff3", label: "KDiff3", program: "kdiff3.exe", args: ["{left}", "{right}"] },
|
{ id: "kdiff3", label: "KDiff3", program: "kdiff3.exe", args: ["{left}", "{right}"] },
|
||||||
{ id: "p4merge", label: "P4Merge", program: "p4merge.exe", args: ["{left}", "{right}"] },
|
{ id: "p4merge", label: "P4Merge", program: "p4merge.exe", args: ["{left}", "{right}"] },
|
||||||
@@ -100,14 +100,14 @@ const dedicatedDiff: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
|||||||
]
|
]
|
||||||
: externalToolsPlatform === "macos"
|
: externalToolsPlatform === "macos"
|
||||||
? [
|
? [
|
||||||
{ id: "beyond-compare", label: "Beyond Compare", program: "bcompare", args: ["{left}", "{right}"] },
|
{ id: "beyond-compare", label: "Beyond Compare", program: "bcompare", args: ["-solo", "{left}", "{right}"] },
|
||||||
{ id: "kaleidoscope", label: "Kaleidoscope", program: "ksdiff", args: ["--wait", "{left}", "{right}"] },
|
{ id: "kaleidoscope", label: "Kaleidoscope", program: "ksdiff", args: ["--wait", "{left}", "{right}"] },
|
||||||
{ id: "araxis-merge", label: "Araxis Merge", program: "compare", args: ["-wait", "{left}", "{right}"] },
|
{ id: "araxis-merge", label: "Araxis Merge", program: "compare", args: ["-wait", "{left}", "{right}"] },
|
||||||
{ id: "p4merge", label: "P4Merge", program: "p4merge", args: ["{left}", "{right}"] },
|
{ id: "p4merge", label: "P4Merge", program: "p4merge", args: ["{left}", "{right}"] },
|
||||||
{ id: "opendiff", label: "FileMerge", program: "opendiff", args: ["{left}", "{right}"] },
|
{ id: "opendiff", label: "FileMerge", program: "opendiff", args: ["{left}", "{right}"] },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ id: "beyond-compare", label: "Beyond Compare", program: "bcompare", args: ["{left}", "{right}"] },
|
{ id: "beyond-compare", label: "Beyond Compare", program: "bcompare", args: ["-solo", "{left}", "{right}"] },
|
||||||
{ id: "meld", label: "Meld", program: "meld", args: ["--wait", "{left}", "{right}"] },
|
{ id: "meld", label: "Meld", program: "meld", args: ["--wait", "{left}", "{right}"] },
|
||||||
{ id: "kdiff3", label: "KDiff3", program: "kdiff3", args: ["{left}", "{right}"] },
|
{ id: "kdiff3", label: "KDiff3", program: "kdiff3", args: ["{left}", "{right}"] },
|
||||||
{ id: "p4merge", label: "P4Merge", program: "p4merge", args: ["{left}", "{right}"] },
|
{ id: "p4merge", label: "P4Merge", program: "p4merge", args: ["{left}", "{right}"] },
|
||||||
@@ -116,8 +116,8 @@ const dedicatedDiff: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
|||||||
|
|
||||||
const dedicatedMerge: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
const dedicatedMerge: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
||||||
? [
|
? [
|
||||||
{ id: "beyond-compare", label: "Beyond Compare", program: "BCompare.exe", args: ["{ours}", "{theirs}", "{base}", "/mergeoutput={result}"] },
|
{ id: "beyond-compare", label: "Beyond Compare", program: "BCompare.exe", args: ["/solo", "{ours}", "{theirs}", "{base}", "/mergeoutput={result}"] },
|
||||||
{ id: "winmerge", label: "WinMerge", program: "WinMergeU.exe", args: ["/u", "/e", "{ours}", "{theirs}", "{base}", "/o", "{result}"] },
|
{ id: "winmerge", label: "WinMerge", program: "WinMergeU.exe", args: ["/s-", "/u", "/e", "{ours}", "{theirs}", "{base}", "/o", "{result}"] },
|
||||||
{ id: "meld", label: "Meld", program: "meld.exe", args: ["--wait", "--auto-merge", "{ours}", "{base}", "{theirs}", "--output={result}"] },
|
{ id: "meld", label: "Meld", program: "meld.exe", args: ["--wait", "--auto-merge", "{ours}", "{base}", "{theirs}", "--output={result}"] },
|
||||||
{ id: "kdiff3", label: "KDiff3", program: "kdiff3.exe", args: ["{base}", "{ours}", "{theirs}", "-o", "{result}"] },
|
{ id: "kdiff3", label: "KDiff3", program: "kdiff3.exe", args: ["{base}", "{ours}", "{theirs}", "-o", "{result}"] },
|
||||||
{ id: "p4merge", label: "P4Merge", program: "p4merge.exe", args: ["{base}", "{theirs}", "{ours}", "{result}"] },
|
{ id: "p4merge", label: "P4Merge", program: "p4merge.exe", args: ["{base}", "{theirs}", "{ours}", "{result}"] },
|
||||||
@@ -126,14 +126,14 @@ const dedicatedMerge: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
|||||||
]
|
]
|
||||||
: externalToolsPlatform === "macos"
|
: externalToolsPlatform === "macos"
|
||||||
? [
|
? [
|
||||||
{ id: "beyond-compare", label: "Beyond Compare", program: "bcompare", args: ["{ours}", "{theirs}", "{base}", "/mergeoutput={result}"] },
|
{ id: "beyond-compare", label: "Beyond Compare", program: "bcompare", args: ["-solo", "{ours}", "{theirs}", "{base}", "/mergeoutput={result}"] },
|
||||||
{ id: "kaleidoscope", label: "Kaleidoscope", program: "ksdiff", args: ["--merge", "--output", "{result}", "{base}", "{ours}", "{theirs}"] },
|
{ id: "kaleidoscope", label: "Kaleidoscope", program: "ksdiff", args: ["--merge", "--output", "{result}", "{base}", "{ours}", "{theirs}"] },
|
||||||
{ id: "araxis-merge", label: "Araxis Merge", program: "compare", args: ["-wait", "{ours}", "{base}", "{theirs}", "{result}"] },
|
{ id: "araxis-merge", label: "Araxis Merge", program: "compare", args: ["-wait", "{ours}", "{base}", "{theirs}", "{result}"] },
|
||||||
{ id: "p4merge", label: "P4Merge", program: "p4merge", args: ["{base}", "{theirs}", "{ours}", "{result}"] },
|
{ id: "p4merge", label: "P4Merge", program: "p4merge", args: ["{base}", "{theirs}", "{ours}", "{result}"] },
|
||||||
{ id: "opendiff", label: "FileMerge", program: "opendiff", args: ["{ours}", "{theirs}", "-ancestor", "{base}", "-merge", "{result}"] },
|
{ id: "opendiff", label: "FileMerge", program: "opendiff", args: ["{ours}", "{theirs}", "-ancestor", "{base}", "-merge", "{result}"] },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ id: "beyond-compare", label: "Beyond Compare", program: "bcompare", args: ["{ours}", "{theirs}", "{base}", "/mergeoutput={result}"] },
|
{ id: "beyond-compare", label: "Beyond Compare", program: "bcompare", args: ["-solo", "{ours}", "{theirs}", "{base}", "/mergeoutput={result}"] },
|
||||||
{ id: "meld", label: "Meld", program: "meld", args: ["--wait", "--auto-merge", "{ours}", "{base}", "{theirs}", "--output={result}"] },
|
{ id: "meld", label: "Meld", program: "meld", args: ["--wait", "--auto-merge", "{ours}", "{base}", "{theirs}", "--output={result}"] },
|
||||||
{ id: "kdiff3", label: "KDiff3", program: "kdiff3", args: ["{base}", "{ours}", "{theirs}", "-o", "{result}"] },
|
{ id: "kdiff3", label: "KDiff3", program: "kdiff3", args: ["{base}", "{ours}", "{theirs}", "-o", "{result}"] },
|
||||||
{ id: "p4merge", label: "P4Merge", program: "p4merge", args: ["{base}", "{theirs}", "{ours}", "{result}"] },
|
{ id: "p4merge", label: "P4Merge", program: "p4merge", args: ["{base}", "{theirs}", "{ours}", "{result}"] },
|
||||||
@@ -141,10 +141,10 @@ const dedicatedMerge: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
|||||||
|
|
||||||
const terminalPresets: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
const terminalPresets: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
||||||
? [
|
? [
|
||||||
{ id: "windows-terminal", label: "Windows Terminal", program: "wt.exe", args: ["-d", "{repo}"] },
|
{ id: "windows-terminal", label: "Windows Terminal", program: "wt.exe", args: ["-w", "new", "-d", "{repo}"] },
|
||||||
{ id: "powershell", label: "PowerShell 7", program: "pwsh.exe", args: ["-NoExit", "-WorkingDirectory", "{repo}"] },
|
{ id: "powershell", label: "PowerShell 7", program: "pwsh.exe", args: ["-NoExit", "-WorkingDirectory", "{repo}"] },
|
||||||
{ id: "windows-powershell", label: "Windows PowerShell", program: "powershell.exe", args: ["-NoExit"], builtIn: true },
|
{ id: "windows-powershell", label: "Windows PowerShell", program: "powershell.exe", args: ["-NoExit"], builtIn: true },
|
||||||
{ id: "git-bash", label: "Git Bash", program: "bash.exe", args: ["--login", "-i"] },
|
{ id: "git-bash", label: "Git Bash", program: "git-bash.exe", args: ["--login", "-i"] },
|
||||||
{ id: "cmd", label: "Command Prompt", program: "cmd.exe", args: ["/K"], builtIn: true },
|
{ id: "cmd", label: "Command Prompt", program: "cmd.exe", args: ["/K"], builtIn: true },
|
||||||
{ id: "wezterm", label: "WezTerm", program: "wezterm-gui.exe", args: ["start", "--cwd", "{repo}"] },
|
{ id: "wezterm", label: "WezTerm", program: "wezterm-gui.exe", args: ["start", "--cwd", "{repo}"] },
|
||||||
{ id: "alacritty", label: "Alacritty", program: "alacritty.exe", args: ["--working-directory", "{repo}"] },
|
{ id: "alacritty", label: "Alacritty", program: "alacritty.exe", args: ["--working-directory", "{repo}"] },
|
||||||
@@ -152,9 +152,9 @@ const terminalPresets: ExternalToolPreset[] = externalToolsPlatform === "windows
|
|||||||
]
|
]
|
||||||
: externalToolsPlatform === "macos"
|
: externalToolsPlatform === "macos"
|
||||||
? [
|
? [
|
||||||
{ id: "terminal", label: "Terminal", program: "open", args: ["-a", "Terminal", "{repo}"], builtIn: true },
|
{ id: "terminal", label: "Terminal", program: "open", args: ["-n", "-a", "Terminal", "{repo}"], builtIn: true },
|
||||||
{ id: "iterm2", label: "iTerm2", program: "open", args: ["-a", "iTerm", "{repo}"] },
|
{ id: "iterm2", label: "iTerm2", program: "open", args: ["-n", "-a", "iTerm", "{repo}"] },
|
||||||
{ id: "warp", label: "Warp", program: "open", args: ["-a", "Warp", "{repo}"] },
|
{ id: "warp", label: "Warp", program: "open", args: ["-n", "-a", "Warp", "{repo}"] },
|
||||||
{ id: "wezterm", label: "WezTerm", program: "wezterm", args: ["start", "--cwd", "{repo}"] },
|
{ id: "wezterm", label: "WezTerm", program: "wezterm", args: ["start", "--cwd", "{repo}"] },
|
||||||
{ id: "alacritty", label: "Alacritty", program: "alacritty", args: ["--working-directory", "{repo}"] },
|
{ id: "alacritty", label: "Alacritty", program: "alacritty", args: ["--working-directory", "{repo}"] },
|
||||||
]
|
]
|
||||||
@@ -171,8 +171,8 @@ const terminalPresets: ExternalToolPreset[] = externalToolsPlatform === "windows
|
|||||||
|
|
||||||
const fileManagerPresets: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
const fileManagerPresets: ExternalToolPreset[] = externalToolsPlatform === "windows"
|
||||||
? [
|
? [
|
||||||
{ id: "explorer", label: "Windows Explorer", program: "explorer.exe", args: ["{repo}"], builtIn: true },
|
{ id: "explorer", label: "Windows Explorer", program: "explorer.exe", args: ["/n,", "{repo}"], builtIn: true },
|
||||||
{ id: "total-commander", label: "Total Commander", program: "TOTALCMD64.EXE", args: ["/O", "/T", "{repo}"] },
|
{ id: "total-commander", label: "Total Commander", program: "TOTALCMD64.EXE", args: ["/N", "/T", "{repo}"] },
|
||||||
{ id: "directory-opus", label: "Directory Opus", program: "dopus.exe", args: ["{repo}"] },
|
{ id: "directory-opus", label: "Directory Opus", program: "dopus.exe", args: ["{repo}"] },
|
||||||
{ id: "double-commander", label: "Double Commander", program: "doublecmd.exe", args: ["{repo}"] },
|
{ id: "double-commander", label: "Double Commander", program: "doublecmd.exe", args: ["{repo}"] },
|
||||||
{ id: "freecommander", label: "FreeCommander XE", program: "FreeCommander.exe", args: ["/L={repo}"] },
|
{ id: "freecommander", label: "FreeCommander XE", program: "FreeCommander.exe", args: ["/L={repo}"] },
|
||||||
@@ -180,7 +180,7 @@ const fileManagerPresets: ExternalToolPreset[] = externalToolsPlatform === "wind
|
|||||||
]
|
]
|
||||||
: externalToolsPlatform === "macos"
|
: externalToolsPlatform === "macos"
|
||||||
? [
|
? [
|
||||||
{ id: "finder", label: "Finder", program: "open", args: ["{repo}"], builtIn: true },
|
{ id: "finder", label: "Finder", program: "open", args: ["-n", "{repo}"], builtIn: true },
|
||||||
{ id: "forklift", label: "ForkLift", program: "forklift", args: ["{repo}"] },
|
{ id: "forklift", label: "ForkLift", program: "forklift", args: ["{repo}"] },
|
||||||
{ id: "path-finder", label: "Path Finder", program: "Path Finder", args: ["{repo}"] },
|
{ id: "path-finder", label: "Path Finder", program: "Path Finder", args: ["{repo}"] },
|
||||||
]
|
]
|
||||||
@@ -196,7 +196,7 @@ const fileManagerPresets: ExternalToolPreset[] = externalToolsPlatform === "wind
|
|||||||
|
|
||||||
export const externalToolPresets: Record<ExternalToolKind, ExternalToolPreset[]> = {
|
export const externalToolPresets: Record<ExternalToolKind, ExternalToolPreset[]> = {
|
||||||
editor: [...commonEditors, ...platformEditors],
|
editor: [...commonEditors, ...platformEditors],
|
||||||
diff: [...dedicatedDiff, ...codeFamily.map(codeDiff), { id: "zed", label: "Zed", program: externalToolsPlatform === "windows" ? "zed.exe" : "zed", args: ["--diff", "{left}", "{right}"] }, ...jetBrains.map(jetBrainsDiff)],
|
diff: [...dedicatedDiff, ...codeFamily.map(codeDiff), { id: "zed", label: "Zed", program: externalToolsPlatform === "windows" ? "zed.exe" : "zed", args: ["--new", "--diff", "{left}", "{right}"] }, ...jetBrains.map(jetBrainsDiff)],
|
||||||
merge: [...dedicatedMerge, ...codeFamily.map(codeMerge), ...jetBrains.map(jetBrainsMerge)],
|
merge: [...dedicatedMerge, ...codeFamily.map(codeMerge), ...jetBrains.map(jetBrainsMerge)],
|
||||||
terminal: terminalPresets,
|
terminal: terminalPresets,
|
||||||
fileManager: fileManagerPresets,
|
fileManager: fileManagerPresets,
|
||||||
@@ -233,14 +233,21 @@ export function defaultExternalToolsSettings(detectedTools: DetectedExternalTool
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function normaliseSetting(value: unknown, fallback: ExternalToolSetting): ExternalToolSetting {
|
function normaliseSetting(kind: ExternalToolKind, value: unknown, fallback: ExternalToolSetting): ExternalToolSetting {
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) return fallback;
|
if (!value || typeof value !== "object" || Array.isArray(value)) return fallback;
|
||||||
const candidate = value as Partial<ExternalToolSetting>;
|
const candidate = value as Partial<ExternalToolSetting>;
|
||||||
const program = typeof candidate.program === "string" && candidate.program.trim() ? candidate.program : fallback.program;
|
const program = typeof candidate.program === "string" && candidate.program.trim() ? candidate.program : fallback.program;
|
||||||
const args = Array.isArray(candidate.args)
|
const storedArgs = Array.isArray(candidate.args)
|
||||||
? candidate.args.filter((item): item is string => typeof item === "string").slice(0, 64)
|
? candidate.args.filter((item): item is string => typeof item === "string").slice(0, 64)
|
||||||
: fallback.args;
|
: fallback.args;
|
||||||
const preset = typeof candidate.preset === "string" && candidate.preset ? candidate.preset : fallback.preset;
|
const preset = typeof candidate.preset === "string" && candidate.preset ? candidate.preset : fallback.preset;
|
||||||
|
// Preset arguments are application-owned and can be upgraded safely. Editing either
|
||||||
|
// the executable or arguments in Settings changes the preset to "custom", which keeps
|
||||||
|
// genuine user commands untouched.
|
||||||
|
const currentPreset = preset === "custom"
|
||||||
|
? undefined
|
||||||
|
: externalToolPresets[kind].find((item) => item.id === preset);
|
||||||
|
const args = currentPreset ? [...currentPreset.args] : storedArgs;
|
||||||
return { preset, program, args };
|
return { preset, program, args };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,11 +256,11 @@ export function normaliseExternalToolsSettings(value: unknown): ExternalToolsSet
|
|||||||
if (!value || typeof value !== "object" || Array.isArray(value)) return defaults;
|
if (!value || typeof value !== "object" || Array.isArray(value)) return defaults;
|
||||||
const candidate = value as Partial<ExternalToolsSettings>;
|
const candidate = value as Partial<ExternalToolsSettings>;
|
||||||
return {
|
return {
|
||||||
editor: normaliseSetting(candidate.editor, defaults.editor),
|
editor: normaliseSetting("editor", candidate.editor, defaults.editor),
|
||||||
diff: normaliseSetting(candidate.diff, defaults.diff),
|
diff: normaliseSetting("diff", candidate.diff, defaults.diff),
|
||||||
merge: normaliseSetting(candidate.merge, defaults.merge),
|
merge: normaliseSetting("merge", candidate.merge, defaults.merge),
|
||||||
terminal: normaliseSetting(candidate.terminal, defaults.terminal),
|
terminal: normaliseSetting("terminal", candidate.terminal, defaults.terminal),
|
||||||
fileManager: normaliseSetting(candidate.fileManager, defaults.fileManager),
|
fileManager: normaliseSetting("fileManager", candidate.fileManager, defaults.fileManager),
|
||||||
diffOpenMode: normaliseOpenMode(candidate.diffOpenMode, defaults.diffOpenMode),
|
diffOpenMode: normaliseOpenMode(candidate.diffOpenMode, defaults.diffOpenMode),
|
||||||
mergeOpenMode: normaliseOpenMode(candidate.mergeOpenMode, defaults.mergeOpenMode),
|
mergeOpenMode: normaliseOpenMode(candidate.mergeOpenMode, defaults.mergeOpenMode),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user