tmux-ide 2.9.0-beta.28 → 2.9.0-beta.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1 -1
- package/package.json +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-appearance-owner.js +13 -4
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +5 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-experience.js +1 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-command-owner.js +18 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-input.js +4 -1
- package/packages/daemon/dist/tui/mirror/ui/dialog.jsx +1 -1
- package/packages/daemon/dist/tui/mirror/ui/overlay-frame.jsx +24 -8
- package/packages/daemon/dist/tui/mirror/ui/overlay-list-row.jsx +34 -5
- package/packages/daemon/dist/tui/mirror/ui/overlay-model.js +7 -0
- package/packages/daemon/dist/tui/mirror/ui/overlay-surface.js +13 -0
- package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +17 -7
- package/packages/daemon/dist/tui/mirror/workspace/command-palette-surface-view.jsx +22 -16
- package/packages/daemon/dist/tui/mirror/workspace/command-palette-surface.js +9 -14
- package/packages/daemon/dist/tui/mirror/workspace/palette-list-layout.js +47 -0
- package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +12 -12
- package/packages/daemon/src/tui/mirror/runtime/application-appearance-owner.ts +12 -4
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-switcher.tsx +1 -3
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +6 -1
- package/packages/daemon/src/tui/mirror/runtime/application-home-experience.ts +1 -0
- package/packages/daemon/src/tui/mirror/runtime/application-palette-command-owner.ts +19 -0
- package/packages/daemon/src/tui/mirror/runtime/application-palette-input.ts +4 -1
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +2 -0
- package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +174 -140
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +6 -0
- package/packages/daemon/src/tui/mirror/runtime/palette-production-overlay.tsx +5 -8
- package/packages/daemon/src/tui/mirror/ui/dialog.tsx +2 -0
- package/packages/daemon/src/tui/mirror/ui/overlay-frame.tsx +49 -14
- package/packages/daemon/src/tui/mirror/ui/overlay-list-row.tsx +46 -9
- package/packages/daemon/src/tui/mirror/ui/overlay-model.ts +8 -0
- package/packages/daemon/src/tui/mirror/ui/overlay-search-field.tsx +34 -0
- package/packages/daemon/src/tui/mirror/ui/overlay-surface.ts +19 -0
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +168 -168
- package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +27 -8
- package/packages/daemon/src/tui/mirror/workspace/command-palette-surface-view.tsx +24 -16
- package/packages/daemon/src/tui/mirror/workspace/command-palette-surface.ts +9 -14
- package/packages/daemon/src/tui/mirror/workspace/palette-list-layout.ts +55 -0
package/bin/cli.js
CHANGED
|
@@ -11279,7 +11279,7 @@ var require_package = __commonJS({
|
|
|
11279
11279
|
"package.json"(exports, module) {
|
|
11280
11280
|
module.exports = {
|
|
11281
11281
|
name: "tmux-ide",
|
|
11282
|
-
version: "2.9.0-beta.
|
|
11282
|
+
version: "2.9.0-beta.29",
|
|
11283
11283
|
description: "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
11284
11284
|
type: "module",
|
|
11285
11285
|
bin: {
|
package/package.json
CHANGED
|
@@ -77,7 +77,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
77
77
|
{ id: "system", name: "System · follow terminal" },
|
|
78
78
|
{ id: "dark", name: "Dark" },
|
|
79
79
|
{ id: "light", name: "Light" },
|
|
80
|
-
...VISUAL_THEME_PRESETS,
|
|
80
|
+
...[...VISUAL_THEME_PRESETS].sort((a, b) => a.name.localeCompare(b.name)),
|
|
81
81
|
];
|
|
82
82
|
const pickerOptions = () => options.filter((p) => `${p.id} ${p.name}`.toLowerCase().includes(pickerQuery().toLowerCase()));
|
|
83
83
|
const apply = (id) => {
|
|
@@ -102,6 +102,14 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
102
102
|
if (options.some((p) => p.id === id))
|
|
103
103
|
apply(id);
|
|
104
104
|
};
|
|
105
|
+
const filterPicker = (query) => {
|
|
106
|
+
setPickerQuery(query);
|
|
107
|
+
const first = pickerOptions()[0];
|
|
108
|
+
if (!query)
|
|
109
|
+
preview(originalSelection);
|
|
110
|
+
else if (first)
|
|
111
|
+
preview(first.id);
|
|
112
|
+
};
|
|
105
113
|
const cancelPicker = () => {
|
|
106
114
|
if (!pickerOpen())
|
|
107
115
|
return;
|
|
@@ -111,7 +119,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
111
119
|
setPickerError(null);
|
|
112
120
|
};
|
|
113
121
|
const savePicker = () => {
|
|
114
|
-
if (!pickerOpen())
|
|
122
|
+
if (!pickerOpen() || !pickerOptions().some((option) => option.id === pickerSelection()))
|
|
115
123
|
return;
|
|
116
124
|
try {
|
|
117
125
|
updateAppConfig({
|
|
@@ -150,6 +158,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
150
158
|
pickerError,
|
|
151
159
|
pickerQuery,
|
|
152
160
|
pickerSelection,
|
|
161
|
+
pickerOriginalSelection: () => originalSelection,
|
|
153
162
|
pickerOptions,
|
|
154
163
|
openPicker() {
|
|
155
164
|
if (pickerOpen())
|
|
@@ -188,12 +197,12 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
188
197
|
}
|
|
189
198
|
}
|
|
190
199
|
else if (name === "backspace") {
|
|
191
|
-
|
|
200
|
+
filterPicker(pickerQuery().slice(0, -1));
|
|
192
201
|
}
|
|
193
202
|
else if (!event.ctrl && !event.meta) {
|
|
194
203
|
const text = event.sequence ?? (name.length === 1 ? name : "");
|
|
195
204
|
if (text.length === 1 && text >= " ")
|
|
196
|
-
|
|
205
|
+
filterPicker((pickerQuery() + text).slice(0, 80));
|
|
197
206
|
}
|
|
198
207
|
return true;
|
|
199
208
|
},
|
|
@@ -203,7 +203,10 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
203
203
|
if (typeof command === "object" ||
|
|
204
204
|
command === "home" ||
|
|
205
205
|
command === "terminals" ||
|
|
206
|
-
command === "appearance"
|
|
206
|
+
command === "appearance" ||
|
|
207
|
+
command === "shortcuts" ||
|
|
208
|
+
command === "whats-new" ||
|
|
209
|
+
command === "switch-session")
|
|
207
210
|
return null;
|
|
208
211
|
if (options.sessionOwner()?.snapshot()?.status !== "live")
|
|
209
212
|
return "Open a live session first";
|
|
@@ -215,6 +218,7 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
215
218
|
setSurface: options.setSurface,
|
|
216
219
|
setNote: options.setNote,
|
|
217
220
|
openAppearance: options.openAppearance,
|
|
221
|
+
openSessions: options.openSessions,
|
|
218
222
|
zoomPane: options.zoomPane,
|
|
219
223
|
newWindow: options.interaction.newWindow,
|
|
220
224
|
splitPane: options.interaction.splitPane,
|
|
@@ -23,6 +23,7 @@ export function createApplicationHomeExperience(options) {
|
|
|
23
23
|
fleetHome: fleetHome.presentation,
|
|
24
24
|
fleetCommands: machines.paletteCommands,
|
|
25
25
|
openFleet: machines.openPalette,
|
|
26
|
+
openSessions: () => machines.showSwitcher(false),
|
|
26
27
|
});
|
|
27
28
|
const tour = createApplicationGuidedTourIntegration({
|
|
28
29
|
...options,
|
|
@@ -2,6 +2,7 @@ import { createSignal } from "solid-js";
|
|
|
2
2
|
import { applicationPaletteCommands } from "./application-palette-input.js";
|
|
3
3
|
import { createApplicationPaletteSearchOwner } from "./application-palette-search-owner.js";
|
|
4
4
|
export function createApplicationPaletteCommandOwner(options) {
|
|
5
|
+
const [referencePage, setReferencePage] = createSignal();
|
|
5
6
|
const [closeArmed, setCloseArmed] = createSignal(false);
|
|
6
7
|
const [busy, setBusy] = createSignal(false);
|
|
7
8
|
let armedTarget;
|
|
@@ -14,6 +15,7 @@ export function createApplicationPaletteCommandOwner(options) {
|
|
|
14
15
|
onChange: () => setCloseArmed(false),
|
|
15
16
|
});
|
|
16
17
|
const setOpen = (open, source) => {
|
|
18
|
+
setReferencePage(undefined);
|
|
17
19
|
openRevision += 1;
|
|
18
20
|
setCloseArmed(false);
|
|
19
21
|
if (open)
|
|
@@ -25,6 +27,20 @@ export function createApplicationPaletteCommandOwner(options) {
|
|
|
25
27
|
const activate = (command, source, confirmed = false) => {
|
|
26
28
|
if (busy())
|
|
27
29
|
return;
|
|
30
|
+
if (command === "shortcuts" || command === "whats-new") {
|
|
31
|
+
setCloseArmed(false);
|
|
32
|
+
setReferencePage(command === "shortcuts" ? "shortcuts" : "changes");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (command === "switch-session") {
|
|
36
|
+
if (!options.openSessions) {
|
|
37
|
+
options.setNote("Session switching is unavailable.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setOpen(false, source);
|
|
41
|
+
options.openSessions();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
28
44
|
if (command === "appearance") {
|
|
29
45
|
setOpen(false, source);
|
|
30
46
|
options.openAppearance?.();
|
|
@@ -124,6 +140,8 @@ export function createApplicationPaletteCommandOwner(options) {
|
|
|
124
140
|
};
|
|
125
141
|
return {
|
|
126
142
|
...search,
|
|
143
|
+
referencePage,
|
|
144
|
+
setReferencePage,
|
|
127
145
|
busy,
|
|
128
146
|
disabledReason: options.disabledReason ?? (() => null),
|
|
129
147
|
closeArmed: () => closeArmed() && armedTarget === options.targetKey?.(),
|
|
@@ -8,8 +8,11 @@ const BASE_COMMANDS = [
|
|
|
8
8
|
"split-right",
|
|
9
9
|
"split-down",
|
|
10
10
|
"close-pane",
|
|
11
|
-
"appearance",
|
|
12
11
|
"zoom-pane",
|
|
12
|
+
"appearance",
|
|
13
|
+
"shortcuts",
|
|
14
|
+
"whats-new",
|
|
15
|
+
"switch-session",
|
|
13
16
|
];
|
|
14
17
|
/** Agent commands are derived from the same semantic rows as the sidebar. */
|
|
15
18
|
export function applicationPaletteCommands(semantic, liveSessions = []) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OverlayFrame } from "./overlay-frame.jsx";
|
|
2
2
|
export function Dialog(props) {
|
|
3
|
-
return (<OverlayFrame theme={props.theme} viewportWidth={props.viewportWidth} viewportHeight={props.viewportHeight} width={props.width} height={props.height} {...(props.title ? { title: props.title } : {})} {...(props.footer ? { footer: props.footer } : {})} {...(props.placement ? { placement: props.placement } : {})} {...(props.active !== undefined ? { active: props.active } : {})} {...(props.zIndex !== undefined ? { zIndex: props.zIndex } : {})} {...(props.dismissOnOutsidePress !== undefined
|
|
3
|
+
return (<OverlayFrame surface={props.surface} theme={props.theme} viewportWidth={props.viewportWidth} viewportHeight={props.viewportHeight} width={props.width} height={props.height} {...(props.title ? { title: props.title } : {})} {...(props.footer ? { footer: props.footer } : {})} {...(props.placement ? { placement: props.placement } : {})} {...(props.active !== undefined ? { active: props.active } : {})} {...(props.zIndex !== undefined ? { zIndex: props.zIndex } : {})} {...(props.dismissOnOutsidePress !== undefined
|
|
4
4
|
? { dismissOnOutsidePress: props.dismissOnOutsidePress }
|
|
5
5
|
: {})} onDismiss={props.onDismiss}>
|
|
6
6
|
{props.children}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clipTerminal } from "../terminal-text.js";
|
|
2
|
-
import { overlayFrameSize } from "./overlay-model.js";
|
|
2
|
+
import { overlayFrameSize, overlaySurfacePadding } from "./overlay-model.js";
|
|
3
3
|
/** Owns modal geometry and pointer capture without owning commands or renderer lifecycle. */
|
|
4
4
|
export function OverlayFrame(props) {
|
|
5
5
|
const viewportWidth = () => Math.max(1, Math.floor(props.viewportWidth));
|
|
@@ -13,8 +13,9 @@ export function OverlayFrame(props) {
|
|
|
13
13
|
const width = () => size().width;
|
|
14
14
|
const height = () => size().height;
|
|
15
15
|
// Empty border sides below keep OpenTUI border styling from enabling a frame.
|
|
16
|
-
const bordered = () => width() >= 4 && height() >= 3;
|
|
17
|
-
const
|
|
16
|
+
const bordered = () => !props.surface && width() >= 4 && height() >= 3;
|
|
17
|
+
const padding = () => overlaySurfacePadding(width(), height());
|
|
18
|
+
const innerWidth = () => Math.max(1, width() - (props.surface ? padding().horizontal * 2 : bordered() ? 2 : 0));
|
|
18
19
|
const left = () => {
|
|
19
20
|
if (props.placement === "anchor")
|
|
20
21
|
return Math.max(0, Math.min(props.anchor?.x ?? 0, viewportWidth() - width()));
|
|
@@ -29,15 +30,30 @@ export function OverlayFrame(props) {
|
|
|
29
30
|
return Math.min(1, viewportHeight() - height());
|
|
30
31
|
return Math.max(0, Math.floor((viewportHeight() - height()) / 2));
|
|
31
32
|
};
|
|
32
|
-
const frame = () => (<box id="ui-overlay-frame" position="absolute" left={left()} top={top()} width={width()} height={height()} zIndex={props.zIndex ?? 100} border={bordered() ? true : []} borderStyle="rounded" borderColor={props.theme.roles.borders.focused} backgroundColor={props.theme.roles.surfaces.panelRaised} flexDirection="column" paddingLeft={bordered() ? 1 : 0} overflow="hidden" onMouseDown={(event) => {
|
|
33
|
+
const frame = () => (<box id="ui-overlay-frame" position="absolute" left={left()} top={top()} width={width()} height={height()} zIndex={props.zIndex ?? 100} border={bordered() ? true : []} borderStyle="rounded" borderColor={props.theme.roles.borders.focused} backgroundColor={props.surface ? props.theme.roles.surfaces.command : props.theme.roles.surfaces.panelRaised} flexDirection="column" paddingLeft={props.surface ? padding().horizontal : bordered() ? 1 : 0} paddingRight={props.surface ? padding().horizontal : 0} paddingTop={props.surface ? padding().vertical : 0} paddingBottom={props.surface ? padding().vertical : 0} overflow="hidden" onMouseDown={(event) => {
|
|
33
34
|
event.preventDefault();
|
|
34
35
|
event.stopPropagation();
|
|
35
36
|
}}>
|
|
36
|
-
{props.title && height() >= 4 ? (<
|
|
37
|
-
<
|
|
38
|
-
|
|
37
|
+
{props.title && height() >= 4 ? (<box width={innerWidth()} height={1} flexShrink={0} flexDirection="row">
|
|
38
|
+
<text width={Math.max(1, innerWidth() - (props.surface && innerWidth() >= 20 ? 4 : 0))} fg={props.theme.roles.text.primary} overflow="hidden">
|
|
39
|
+
<strong>
|
|
40
|
+
{clipTerminal(props.title, Math.max(1, innerWidth() - (props.surface && innerWidth() >= 20 ? 4 : 0)))}
|
|
41
|
+
</strong>
|
|
42
|
+
</text>
|
|
43
|
+
{props.surface && innerWidth() >= 20 ? (<text width={4} fg={props.theme.roles.text.muted} onMouseDown={(event) => {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
event.stopPropagation();
|
|
46
|
+
if (event.button === 0 && props.active !== false)
|
|
47
|
+
props.onDismiss?.();
|
|
48
|
+
}}>
|
|
49
|
+
{" "}
|
|
50
|
+
esc
|
|
51
|
+
</text>) : null}
|
|
52
|
+
</box>) : null}
|
|
39
53
|
{props.children}
|
|
40
|
-
{props.footer && height() >= 5 ? (<text width={innerWidth()} fg={props.theme.roles.text.muted} bg={props.
|
|
54
|
+
{props.footer && height() >= 5 ? (<text width={innerWidth()} height={1} flexShrink={0} fg={props.theme.roles.text.muted} bg={props.surface
|
|
55
|
+
? props.theme.roles.surfaces.command
|
|
56
|
+
: props.theme.roles.surfaces.panelRaised} overflow="hidden">
|
|
41
57
|
{clipTerminal(props.footer, innerWidth())}
|
|
42
58
|
</text>) : null}
|
|
43
59
|
</box>);
|
|
@@ -2,22 +2,46 @@
|
|
|
2
2
|
import { For, createMemo } from "solid-js";
|
|
3
3
|
import { fuzzyTermsMatch as commandSearchMatch } from "../../team/fuzzy.js";
|
|
4
4
|
import { clipTerminal, terminalDisplayWidth } from "../terminal-text.js";
|
|
5
|
+
import { overlayRowPalette } from "./overlay-surface.js";
|
|
5
6
|
import { componentPalette } from "./state.js";
|
|
6
7
|
export function OverlayListRow(props) {
|
|
7
|
-
const palette = () =>
|
|
8
|
+
const palette = () => props.surface
|
|
9
|
+
? overlayRowPalette(props.theme, {
|
|
10
|
+
selected: props.selected,
|
|
11
|
+
disabled: props.disabled,
|
|
12
|
+
attention: props.danger,
|
|
13
|
+
})
|
|
14
|
+
: componentPalette(props.theme, { selected: props.selected, disabled: props.disabled, attention: props.danger }, props.danger ? "blocked" : "neutral");
|
|
8
15
|
const content = () => {
|
|
9
|
-
const
|
|
10
|
-
|
|
16
|
+
const prefix = props.current !== undefined
|
|
17
|
+
? props.current
|
|
18
|
+
? "● "
|
|
19
|
+
: " "
|
|
20
|
+
: props.selected
|
|
21
|
+
? props.surface
|
|
22
|
+
? " "
|
|
23
|
+
: "› "
|
|
24
|
+
: props.reserveMarker
|
|
25
|
+
? " "
|
|
26
|
+
: "";
|
|
27
|
+
const shortcut = props.shortcut &&
|
|
28
|
+
props.width >=
|
|
29
|
+
terminalDisplayWidth(prefix) +
|
|
30
|
+
terminalDisplayWidth(props.label) +
|
|
31
|
+
terminalDisplayWidth(props.shortcut) +
|
|
32
|
+
2
|
|
33
|
+
? ` ${props.shortcut}`
|
|
34
|
+
: "";
|
|
11
35
|
const available = Math.max(1, props.width - terminalDisplayWidth(prefix) - terminalDisplayWidth(shortcut));
|
|
12
36
|
const label = clipTerminal(props.label, available);
|
|
13
37
|
const gap = Math.max(0, props.width -
|
|
14
38
|
terminalDisplayWidth(prefix) -
|
|
15
39
|
terminalDisplayWidth(label) -
|
|
16
40
|
terminalDisplayWidth(shortcut));
|
|
17
|
-
return clipTerminal(`${prefix}${label}${" ".repeat(gap)}
|
|
41
|
+
return { body: clipTerminal(`${prefix}${label}${" ".repeat(gap)}`, props.width), shortcut };
|
|
18
42
|
};
|
|
19
43
|
const segments = createMemo(() => {
|
|
20
|
-
const value = content();
|
|
44
|
+
const value = content().body;
|
|
21
45
|
const matches = new Set(props.query ? (commandSearchMatch(value, props.query)?.indices ?? []) : []);
|
|
22
46
|
const runs = [];
|
|
23
47
|
for (const [index, character] of Array.from(value).entries()) {
|
|
@@ -48,5 +72,10 @@ export function OverlayListRow(props) {
|
|
|
48
72
|
<u>{segment.text}</u>
|
|
49
73
|
</strong>) : (segment.text)}
|
|
50
74
|
</For>
|
|
75
|
+
<span style={{
|
|
76
|
+
fg: props.selected && !props.disabled ? palette().foreground : props.theme.roles.text.muted,
|
|
77
|
+
}}>
|
|
78
|
+
{content().shortcut}
|
|
79
|
+
</span>
|
|
51
80
|
</text>);
|
|
52
81
|
}
|
|
@@ -43,3 +43,10 @@ export function createOverlayFocusCoordinator(options) {
|
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
/** Cell padding collapses before labels become unusable in tiny terminals. */
|
|
47
|
+
export function overlaySurfacePadding(width, height) {
|
|
48
|
+
return {
|
|
49
|
+
horizontal: height < 6 ? 0 : width >= 48 ? 3 : width >= 12 ? 2 : 0,
|
|
50
|
+
vertical: height >= 8 ? 1 : 0,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { componentPalette } from "./state.js";
|
|
2
|
+
/** Shared surface treatment for command and appearance lists. */
|
|
3
|
+
export function overlayRowPalette(theme, state = {}) {
|
|
4
|
+
const palette = componentPalette(theme, state);
|
|
5
|
+
return {
|
|
6
|
+
...palette,
|
|
7
|
+
// Current is a marker, not a second selection background.
|
|
8
|
+
background: state.selected || state.disabled || state.attention
|
|
9
|
+
? palette.background
|
|
10
|
+
: theme.roles.surfaces.command,
|
|
11
|
+
accent: state.selected && !state.disabled ? palette.foreground : palette.accent,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { fuzzyTermsMatch as commandSearchMatch } from "../../team/fuzzy.js";
|
|
2
2
|
export { fuzzyTermsMatch as commandSearchMatch } from "../../team/fuzzy.js";
|
|
3
3
|
import { PANE_ACTION_MENU_ITEMS } from "./pane-action-menu-model.js";
|
|
4
|
+
export const PALETTE_REFERENCE_COMMANDS = {
|
|
5
|
+
shortcuts: { label: "Keyboard shortcuts", shortcut: "Ctrl+K", key: "k", page: "shortcuts" },
|
|
6
|
+
"whats-new": { label: "What's new", shortcut: "Ctrl+B", key: "b", page: "changes" },
|
|
7
|
+
};
|
|
4
8
|
/** Presentation only: execution remains in the existing application owners. */
|
|
5
9
|
export function applicationCommandDescription(command) {
|
|
10
|
+
if (command === "switch-session")
|
|
11
|
+
return {
|
|
12
|
+
id: command,
|
|
13
|
+
label: "Switch session",
|
|
14
|
+
shortcut: "F6",
|
|
15
|
+
detail: "Sessions across machines",
|
|
16
|
+
};
|
|
17
|
+
if (command === "shortcuts" || command === "whats-new")
|
|
18
|
+
return { id: command, ...PALETTE_REFERENCE_COMMANDS[command], detail: "Help and reference" };
|
|
6
19
|
if (typeof command === "object" && command.kind === "open-machine")
|
|
7
20
|
return {
|
|
8
21
|
id: JSON.stringify([
|
|
@@ -50,11 +63,8 @@ export function applicationCommandDescription(command) {
|
|
|
50
63
|
: command === "zoom-pane"
|
|
51
64
|
? "Zoom / unzoom pane"
|
|
52
65
|
: undefined) ??
|
|
53
|
-
(command === "home"
|
|
54
|
-
|
|
55
|
-
: command === "terminals"
|
|
56
|
-
? "F2 Terminals"
|
|
57
|
-
: "New terminal window"),
|
|
66
|
+
(command === "home" ? "Home" : command === "terminals" ? "Terminals" : "New terminal window"),
|
|
67
|
+
shortcut: command === "home" ? "F1" : command === "terminals" ? "F2" : undefined,
|
|
58
68
|
detail: command === "home"
|
|
59
69
|
? "sessions and agent state"
|
|
60
70
|
: command === "terminals"
|
|
@@ -65,8 +75,8 @@ export function applicationCommandDescription(command) {
|
|
|
65
75
|
export function filterApplicationCommands(commands, query) {
|
|
66
76
|
return commands
|
|
67
77
|
.map((command, index) => {
|
|
68
|
-
const { label, detail } = applicationCommandDescription(command);
|
|
69
|
-
const score = commandSearchMatch(`${label} ${detail}`, query)?.score;
|
|
78
|
+
const { label, detail, shortcut } = applicationCommandDescription(command);
|
|
79
|
+
const score = commandSearchMatch(`${label} ${detail} ${shortcut ?? ""}`, query)?.score;
|
|
70
80
|
const fleet = typeof command === "object" ? command.fleet : undefined;
|
|
71
81
|
return {
|
|
72
82
|
command,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* @jsxImportSource @opentui/solid */
|
|
2
2
|
import { createMemo, For, Show } from "solid-js";
|
|
3
3
|
import { terminalDisplayWidth } from "../panel-host.js";
|
|
4
|
-
import {
|
|
4
|
+
import { overlayRowPalette } from "../ui/overlay-surface.js";
|
|
5
5
|
import { workspaceIcon } from "./icons.js";
|
|
6
6
|
import { clipWorkspaceText } from "./text.js";
|
|
7
7
|
function sameIds(left, right) {
|
|
@@ -14,7 +14,7 @@ function localY(projection, y) {
|
|
|
14
14
|
return y - projection.overlay.y - (projection.bordered ? 1 : 0);
|
|
15
15
|
}
|
|
16
16
|
function commandRowPalette(theme, row) {
|
|
17
|
-
return
|
|
17
|
+
return overlayRowPalette(theme, {
|
|
18
18
|
disabled: row.disabled,
|
|
19
19
|
selected: row.selected,
|
|
20
20
|
focused: row.current,
|
|
@@ -27,7 +27,9 @@ function CommandRow(props) {
|
|
|
27
27
|
<text fg={palette().accent}>{props.row.markerSpan.text}</text>
|
|
28
28
|
</box>
|
|
29
29
|
<box position="absolute" left={props.row.iconSpan.x - props.row.rect.x} top={0} width={props.row.iconSpan.width} height={1}>
|
|
30
|
-
<text fg={props.row.current && !props.row.disabled
|
|
30
|
+
<text fg={props.row.current && !props.row.disabled && !props.row.selected
|
|
31
|
+
? props.theme.colors.focus
|
|
32
|
+
: palette().accent}>
|
|
31
33
|
{props.row.iconSpan.text}
|
|
32
34
|
</text>
|
|
33
35
|
</box>
|
|
@@ -36,15 +38,17 @@ function CommandRow(props) {
|
|
|
36
38
|
</box>
|
|
37
39
|
<Show when={props.row.trailingSpan !== null}>
|
|
38
40
|
<box position="absolute" left={props.row.trailingSpan.x - props.row.rect.x} top={0} width={props.row.trailingSpan.width} height={1}>
|
|
39
|
-
<text fg={props.row.
|
|
41
|
+
<text fg={props.row.selected && !props.row.disabled
|
|
42
|
+
? palette().foreground
|
|
43
|
+
: props.theme.colors.mutedForeground}>
|
|
40
44
|
{props.row.trailingSpan.text}
|
|
41
45
|
</text>
|
|
42
46
|
</box>
|
|
43
47
|
</Show>
|
|
44
48
|
<Show when={props.row.detailSpan !== null}>
|
|
45
49
|
<box position="absolute" left={props.row.detailSpan.x - props.row.rect.x} top={1} width={props.row.detailSpan.width} height={1}>
|
|
46
|
-
<text fg={props.row.disabled
|
|
47
|
-
?
|
|
50
|
+
<text fg={props.row.selected && !props.row.disabled
|
|
51
|
+
? palette().foreground
|
|
48
52
|
: props.theme.colors.mutedForeground}>
|
|
49
53
|
{props.row.detailSpan.text}
|
|
50
54
|
</text>
|
|
@@ -53,7 +57,7 @@ function CommandRow(props) {
|
|
|
53
57
|
</box>);
|
|
54
58
|
}
|
|
55
59
|
function StateRow(props) {
|
|
56
|
-
const palette = () =>
|
|
60
|
+
const palette = () => overlayRowPalette(props.theme, {
|
|
57
61
|
loading: props.row.state === "loading",
|
|
58
62
|
empty: props.row.state === "empty" || props.row.state === "no-match",
|
|
59
63
|
attention: props.row.state === "error",
|
|
@@ -73,7 +77,7 @@ function StateRow(props) {
|
|
|
73
77
|
</box>);
|
|
74
78
|
}
|
|
75
79
|
function Row(props) {
|
|
76
|
-
return (<Show when={props.row.kind === "command"} fallback={<Show when={props.row.kind === "state"} fallback={<box position="absolute" left={localX(props.projection, props.row.rect.x)} top={localY(props.projection, props.row.rect.y)} width={props.row.rect.width} height={1} backgroundColor={props.theme.
|
|
80
|
+
return (<Show when={props.row.kind === "command"} fallback={<Show when={props.row.kind === "state"} fallback={<box position="absolute" left={localX(props.projection, props.row.rect.x)} top={localY(props.projection, props.row.rect.y)} width={props.row.rect.width} height={1} backgroundColor={props.theme.roles.surfaces.command} overflow="hidden">
|
|
77
81
|
<box position="absolute" left={props.row.labelSpan.x - props.row.rect.x} top={0} width={props.row.labelSpan.width} height={1}>
|
|
78
82
|
<text fg={props.theme.colors.mutedForeground}>{props.row.labelSpan.text}</text>
|
|
79
83
|
</box>
|
|
@@ -106,9 +110,9 @@ function footerRight(projection) {
|
|
|
106
110
|
export function CommandPaletteSurface(props) {
|
|
107
111
|
const entries = createMemo(() => new Map(props.projection.rows.map((row) => [row.id, row])));
|
|
108
112
|
const rowIds = createMemo(() => props.projection.rowIds, undefined, { equals: sameIds });
|
|
109
|
-
const count = () =>
|
|
113
|
+
const count = () => "esc";
|
|
110
114
|
const countWidth = () => terminalDisplayWidth(count());
|
|
111
|
-
const headerTitle = () => clipWorkspaceText(
|
|
115
|
+
const headerTitle = () => clipWorkspaceText(props.projection.title, Math.max(0, props.projection.header.width - countWidth() - 2));
|
|
112
116
|
const leftFooter = () => clipWorkspaceText(footerLeft(props.projection), props.projection.footer.width);
|
|
113
117
|
const rightFooter = () => {
|
|
114
118
|
const leftWidth = terminalDisplayWidth(leftFooter());
|
|
@@ -116,17 +120,19 @@ export function CommandPaletteSurface(props) {
|
|
|
116
120
|
};
|
|
117
121
|
const footerRightWidth = () => terminalDisplayWidth(rightFooter());
|
|
118
122
|
return (<box width={props.projection.width} height={props.projection.height} position="absolute" left={0} top={0} overflow="hidden">
|
|
119
|
-
<box position="absolute" left={props.projection.overlay.x} top={props.projection.overlay.y} width={props.projection.overlay.width} height={props.projection.overlay.height} border={props.projection.bordered ? true : []} borderStyle="rounded" borderColor={props.theme.colors.focusBorder} backgroundColor={props.theme.
|
|
123
|
+
<box position="absolute" left={props.projection.overlay.x} top={props.projection.overlay.y} width={props.projection.overlay.width} height={props.projection.overlay.height} border={props.projection.bordered ? true : []} borderStyle="rounded" borderColor={props.theme.colors.focusBorder} backgroundColor={props.theme.roles.surfaces.command} overflow="hidden">
|
|
120
124
|
<Show when={props.projection.header.height > 0}>
|
|
121
|
-
<box position="absolute" left={localX(props.projection, props.projection.header.x)} top={localY(props.projection, props.projection.header.y)} width={props.projection.header.width} height={1} backgroundColor={props.theme.
|
|
122
|
-
<text fg={props.theme.colors.foreground}>
|
|
125
|
+
<box position="absolute" left={localX(props.projection, props.projection.header.x)} top={localY(props.projection, props.projection.header.y)} width={props.projection.header.width} height={1} backgroundColor={props.theme.roles.surfaces.command} overflow="hidden">
|
|
126
|
+
<text fg={props.theme.colors.foreground}>
|
|
127
|
+
<strong>{headerTitle()}</strong>
|
|
128
|
+
</text>
|
|
123
129
|
<box position="absolute" right={0} top={0} width={countWidth()} height={1}>
|
|
124
130
|
<text fg={props.theme.colors.mutedForeground}>{count()}</text>
|
|
125
131
|
</box>
|
|
126
132
|
</box>
|
|
127
133
|
</Show>
|
|
128
134
|
<Show when={props.projection.query.height > 0}>
|
|
129
|
-
<box position="absolute" left={localX(props.projection, props.projection.query.x)} top={localY(props.projection, props.projection.query.y)} width={props.projection.query.width} height={1} backgroundColor={props.theme.
|
|
135
|
+
<box position="absolute" left={localX(props.projection, props.projection.query.x)} top={localY(props.projection, props.projection.query.y)} width={props.projection.query.width} height={1} backgroundColor={props.theme.roles.surfaces.panel} overflow="hidden" flexDirection="row">
|
|
130
136
|
<text fg={props.theme.colors.accent}>{` ${workspaceIcon("search")} `}</text>
|
|
131
137
|
<text fg={props.projection.queryText
|
|
132
138
|
? props.theme.colors.foreground
|
|
@@ -137,14 +143,14 @@ export function CommandPaletteSurface(props) {
|
|
|
137
143
|
</Show>
|
|
138
144
|
<Show when={props.projection.divider.height > 0}>
|
|
139
145
|
<box position="absolute" left={localX(props.projection, props.projection.divider.x)} top={localY(props.projection, props.projection.divider.y)} width={props.projection.divider.width} height={1} overflow="hidden">
|
|
140
|
-
|
|
146
|
+
{/* Space separates search from results without a rule. */}
|
|
141
147
|
</box>
|
|
142
148
|
</Show>
|
|
143
149
|
<For each={rowIds()}>
|
|
144
150
|
{(rowId) => (<Row theme={props.theme} projection={props.projection} row={entries().get(rowId)}/>)}
|
|
145
151
|
</For>
|
|
146
152
|
<Show when={props.projection.footer.height > 0}>
|
|
147
|
-
<box position="absolute" left={localX(props.projection, props.projection.footer.x)} top={localY(props.projection, props.projection.footer.y)} width={props.projection.footer.width} height={1} backgroundColor={props.theme.
|
|
153
|
+
<box position="absolute" left={localX(props.projection, props.projection.footer.x)} top={localY(props.projection, props.projection.footer.y)} width={props.projection.footer.width} height={1} backgroundColor={props.theme.roles.surfaces.command} overflow="hidden">
|
|
148
154
|
<text fg={props.theme.colors.mutedForeground}>{leftFooter()}</text>
|
|
149
155
|
<Show when={footerRightWidth() > 0}>
|
|
150
156
|
<box position="absolute" right={0} top={0} width={footerRightWidth()} height={1}>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { overlaySurfacePadding } from "../ui/overlay-model.js";
|
|
1
2
|
import { terminalDisplayWidth } from "../panel-host.js";
|
|
2
3
|
import { workspaceIcon } from "./icons.js";
|
|
3
4
|
import { clipWorkspaceText } from "./text.js";
|
|
@@ -29,13 +30,13 @@ function paletteGeometry(width, height, variant) {
|
|
|
29
30
|
width: overlayWidth,
|
|
30
31
|
height: overlayHeight,
|
|
31
32
|
};
|
|
32
|
-
const bordered =
|
|
33
|
-
const
|
|
33
|
+
const bordered = false;
|
|
34
|
+
const padding = overlaySurfacePadding(overlay.width, overlay.height);
|
|
34
35
|
const inner = {
|
|
35
|
-
x: overlay.x +
|
|
36
|
-
y: overlay.y +
|
|
37
|
-
width: Math.max(0, overlay.width -
|
|
38
|
-
height: Math.max(0, overlay.height -
|
|
36
|
+
x: overlay.x + padding.horizontal,
|
|
37
|
+
y: overlay.y + padding.vertical,
|
|
38
|
+
width: Math.max(0, overlay.width - padding.horizontal * 2),
|
|
39
|
+
height: Math.max(0, overlay.height - padding.vertical * 2),
|
|
39
40
|
};
|
|
40
41
|
const headerHeight = inner.height >= 3 ? 1 : 0;
|
|
41
42
|
const queryHeight = inner.height >= 2 ? 1 : 0;
|
|
@@ -182,13 +183,7 @@ function projectCommandRow(candidate, rect, selectedCommandId) {
|
|
|
182
183
|
const disabledReason = command.disabledReason?.trim() ?? "";
|
|
183
184
|
const disabled = disabledReason.length > 0;
|
|
184
185
|
const left = rect.x;
|
|
185
|
-
const markerText = disabled
|
|
186
|
-
? "×"
|
|
187
|
-
: selected
|
|
188
|
-
? workspaceIcon("command")
|
|
189
|
-
: command.current
|
|
190
|
-
? "✓"
|
|
191
|
-
: " ";
|
|
186
|
+
const markerText = disabled ? "×" : selected ? " " : command.current ? "✓" : " ";
|
|
192
187
|
const markerSpan = span(markerText, left, rect.y, Math.min(1, rect.width));
|
|
193
188
|
const iconX = left + Math.min(2, rect.width);
|
|
194
189
|
const iconSpan = span(workspaceIcon(command.icon), iconX, rect.y, Math.max(0, rect.width - 2));
|
|
@@ -231,7 +226,7 @@ function projectCommandRow(candidate, rect, selectedCommandId) {
|
|
|
231
226
|
}
|
|
232
227
|
function projectStateRow(candidate, rect, selectedCommandId, retryCommandId) {
|
|
233
228
|
const selected = candidate.actionable && selectedCommandId === retryCommandId;
|
|
234
|
-
const prefix = `${selected ?
|
|
229
|
+
const prefix = `${selected ? " " : " "} ${candidate.icon} `;
|
|
235
230
|
const labelSpan = span(`${prefix}${candidate.title}`, rect.x, rect.y, rect.width);
|
|
236
231
|
const detailSpan = rect.height > 1
|
|
237
232
|
? span(candidate.detail, rect.x + Math.min(4, rect.width), rect.y + 1, Math.max(0, rect.width - 4))
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function paletteSection(command) {
|
|
2
|
+
if (command === "switch-session")
|
|
3
|
+
return "Sessions";
|
|
4
|
+
if (typeof command === "object")
|
|
5
|
+
return command.kind === "jump-agent"
|
|
6
|
+
? "Agents"
|
|
7
|
+
: command.kind === "open-machine"
|
|
8
|
+
? "Machines"
|
|
9
|
+
: "Sessions";
|
|
10
|
+
if (command === "home" || command === "terminals")
|
|
11
|
+
return "Navigation";
|
|
12
|
+
if (command === "appearance")
|
|
13
|
+
return "Appearance";
|
|
14
|
+
if (command === "shortcuts" || command === "whats-new")
|
|
15
|
+
return "Help";
|
|
16
|
+
return "Panes";
|
|
17
|
+
}
|
|
18
|
+
/** Headings consume cells, never command indices or keyboard stops. */
|
|
19
|
+
export function paletteListLayout(commands, selected, capacity, grouped) {
|
|
20
|
+
const limit = Math.max(1, capacity);
|
|
21
|
+
const sections = commands.map(paletteSection);
|
|
22
|
+
const useGroups = grouped && limit >= 6;
|
|
23
|
+
const focus = Math.max(0, Math.min(selected, commands.length - 1));
|
|
24
|
+
let start = focus;
|
|
25
|
+
let used = useGroups ? 2 : 1;
|
|
26
|
+
while (start > 0) {
|
|
27
|
+
const cost = 1 + (useGroups && sections[start - 1] !== sections[start] ? 2 : 0);
|
|
28
|
+
if (used + cost > limit)
|
|
29
|
+
break;
|
|
30
|
+
used += cost;
|
|
31
|
+
start--;
|
|
32
|
+
}
|
|
33
|
+
const rows = [];
|
|
34
|
+
used = 0;
|
|
35
|
+
for (let index = start; index < commands.length; index++) {
|
|
36
|
+
const heading = useGroups && (index === start || sections[index] !== sections[index - 1])
|
|
37
|
+
? sections[index]
|
|
38
|
+
: null;
|
|
39
|
+
const gap = Boolean(heading && index !== start);
|
|
40
|
+
const cost = 1 + (heading ? 1 : 0) + (gap ? 1 : 0);
|
|
41
|
+
if (used + cost > limit)
|
|
42
|
+
break;
|
|
43
|
+
used += cost;
|
|
44
|
+
rows.push({ command: commands[index], index, heading, gap });
|
|
45
|
+
}
|
|
46
|
+
return rows;
|
|
47
|
+
}
|
|
@@ -50,23 +50,23 @@ exports[`production ApplicationShellView retains shell, window strip, pane chrom
|
|
|
50
50
|
|
|
51
51
|
exports[`production ApplicationShellView reacts palette geometry through narrow live resizes 1`] = `
|
|
52
52
|
" ⌂ ●❯ live
|
|
53
|
-
t
|
|
54
|
-
m
|
|
55
|
-
2
|
|
56
|
-
C
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
t Command palette esc
|
|
54
|
+
m Search commands…
|
|
55
|
+
2 Home F1
|
|
56
|
+
C Terminals F2 …
|
|
57
|
+
New terminal wind…
|
|
58
|
+
|
|
59
|
+
|
|
60
60
|
main Live F5"
|
|
61
61
|
`;
|
|
62
62
|
|
|
63
63
|
exports[`production ApplicationShellView reacts palette geometry through narrow live resizes 2`] = `
|
|
64
64
|
" ⌂ ●❯ live
|
|
65
|
-
|
|
66
|
-
m
|
|
67
|
-
2
|
|
68
|
-
C
|
|
69
|
-
|
|
65
|
+
tCommand palette
|
|
66
|
+
m …rch commands…
|
|
67
|
+
2 Home F1w
|
|
68
|
+
C …
|
|
69
|
+
…
|
|
70
70
|
○ Commands F5"
|
|
71
71
|
`;
|
|
72
72
|
|