feat(external-tools): add cross-platform external tool discovery

Adds a new external tools subsystem to detect and launch
diff and editor tools across Windows, macOS, and Linux.
It exposes data models for tools, commands, and results to the UI
and serializes them for consumption by the app.

- Implement cross-platform discovery of editors and diff tools
- Expose serialized results to the UI for user selection
- Centralize per-OS known tool lists and overrides
This commit is contained in:
Christoph Brandau
2026-08-13 14:08:02 +02:00
parent 3eb554fee7
commit 15d1f2bfd6
15 changed files with 3721 additions and 125 deletions
+30
View File
@@ -2620,6 +2620,7 @@
background: rgba(65,209,255,0.08);
color: var(--color-ink);
}
.commit-note-button:not(:disabled) { color: color-mix(in srgb, var(--color-accent) 72%, var(--color-ink-dim)); }
/* --- Git graph --- */
.graph-list {
@@ -3188,6 +3189,9 @@
max-height: calc(100vh - 32px);
overflow: auto;
}
.app-settings-dialog {
width: min(880px, calc(100vw - 32px));
}
.clone-repository-dialog {
display: block;
width: min(620px, calc(100vw - 32px));
@@ -3503,6 +3507,12 @@
.dialog-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--color-border-subtle); background: var(--app-dialog-chrome); }
.dialog-header > div:first-child { min-width: 0; }
.dialog-header-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex: 0 0 auto; min-width: 0; }
.tool-surface-choice { display: inline-flex; align-items: center; gap: 3px; min-width: 0; padding: 3px; border: 1px solid var(--color-border-subtle); border-radius: 7px; background: var(--color-surface-dim); }
.tool-surface-choice > span { padding: 0 6px 0 4px; color: var(--color-ink-faint); font-size: 9px; font-weight: 800; letter-spacing: .035em; text-transform: uppercase; white-space: nowrap; }
.tool-surface-choice > button { display: inline-flex; align-items: center; justify-content: center; gap: 5px; min-width: 0; min-height: 26px; max-width: 190px; padding: 0 8px; border: 1px solid transparent; border-radius: 5px; color: var(--color-ink-dim); background: transparent; font-size: 10px; font-weight: 750; }
.tool-surface-choice > button > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tool-surface-choice > button:hover:not(:disabled) { color: var(--color-ink); background: var(--color-surface-hover); }
.tool-surface-choice > button.active { border-color: color-mix(in srgb, var(--color-accent) 32%, var(--color-border)); color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 9%, var(--color-surface-raised)); box-shadow: inset 2px 0 0 var(--color-accent); }
.compare-restore { max-width: 170px; min-width: 0; }
.compare-restore span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -3511,6 +3521,11 @@
.dialog-close { min-height: 32px; min-width: 32px; padding: 0; justify-content: center; }
@media (max-width: 760px) {
.tool-surface-choice > span { display: none; }
.tool-surface-choice > button { max-width: 120px; padding-inline: 7px; }
}
.dialog-body { display: grid; grid-template-columns: 280px minmax(0, 1fr); min-height: 0; }
.compare-dialog .dialog-body { grid-template-columns: minmax(260px, 320px) minmax(0, 1fr); }
@@ -6589,6 +6604,21 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
padding: 0;
border-radius: 5px;
}
.explorer-head-actions .explorer-tool-action:not(:disabled) {
color: var(--color-accent);
border-color: color-mix(in srgb, var(--color-accent) 24%, var(--color-border));
background: color-mix(in srgb, var(--color-accent) 7%, var(--color-surface-raised));
}
.explorer-head-actions .explorer-tool-action:hover:not(:disabled) {
border-color: color-mix(in srgb, var(--color-accent) 42%, var(--color-border));
background: color-mix(in srgb, var(--color-accent) 13%, var(--color-surface-raised));
}
.explorer-action-divider {
inline-size: 1px;
block-size: 14px;
margin-inline: 1px;
background: var(--color-border);
}
.ai-review-suggestion { display: grid; gap: 3px; margin-top: 9px; padding: 8px 9px; border-radius: 5px; background: var(--color-surface-dim); }
.ai-review-suggestion strong { color: var(--color-ink-dim); font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; }
.ai-review-suggestion span { color: var(--color-ink-muted); font-size: 11.5px; line-height: 1.45; }