feat: add external terminal support with cross-platform detection

Add support for opening worktree directories in external terminals
(iTerm2, Warp, Ghostty, System Terminal, etc.) while retaining the
integrated terminal as the default option.

Changes:
- Add terminal detection for macOS, Windows, and Linux
- Add "Open in Terminal" split-button in worktree dropdown
- Add external terminal selection in Settings > Terminal
- Add default open mode setting (new tab vs split)
- Display branch name in terminal panel header
- Support 20+ terminals across platforms

Part of #558, Closes #550
This commit is contained in:
Stefan de Vogelaere
2026-01-17 22:57:11 +01:00
parent 502361fc7c
commit 111eb24856
22 changed files with 1565 additions and 146 deletions

View File

@@ -1852,6 +1852,56 @@ function createMockWorktreeAPI(): WorktreeAPI {
};
},
getAvailableTerminals: async () => {
console.log('[Mock] Getting available terminals');
return {
success: true,
result: {
terminals: [
{ id: 'iterm2', name: 'iTerm2', command: 'open -a iTerm' },
{ id: 'terminal-macos', name: 'Terminal', command: 'open -a Terminal' },
],
},
};
},
getDefaultTerminal: async () => {
console.log('[Mock] Getting default terminal');
return {
success: true,
result: {
terminalId: 'iterm2',
terminalName: 'iTerm2',
terminalCommand: 'open -a iTerm',
},
};
},
refreshTerminals: async () => {
console.log('[Mock] Refreshing available terminals');
return {
success: true,
result: {
terminals: [
{ id: 'iterm2', name: 'iTerm2', command: 'open -a iTerm' },
{ id: 'terminal-macos', name: 'Terminal', command: 'open -a Terminal' },
],
message: 'Found 2 available terminals',
},
};
},
openInExternalTerminal: async (worktreePath: string, terminalId?: string) => {
console.log('[Mock] Opening in external terminal:', worktreePath, terminalId);
return {
success: true,
result: {
message: `Opened ${worktreePath} in ${terminalId ?? 'default terminal'}`,
terminalName: terminalId ?? 'Terminal',
},
};
},
initGit: async (projectPath: string) => {
console.log('[Mock] Initializing git:', projectPath);
return {