tmux-ide 2.6.0 → 2.6.1
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 +13 -5
- package/bin/cli.ts +1 -1
- package/bunfig.toml +4 -0
- package/package.json +11 -3
- package/packages/contracts/package.json +22 -0
- package/packages/contracts/src/__tests__/ide-config.test.ts +46 -0
- package/packages/contracts/src/__tests__/terminals.test.ts +87 -0
- package/packages/contracts/src/actions-contract.ts +310 -0
- package/packages/contracts/src/actions-errors.ts +41 -0
- package/packages/contracts/src/domain.ts +36 -0
- package/packages/contracts/src/ide-config.ts +170 -0
- package/packages/contracts/src/index.ts +24 -0
- package/packages/contracts/src/lib-internal/auth.ts +13 -0
- package/packages/contracts/src/lib-internal/hq.ts +38 -0
- package/packages/contracts/src/terminals.ts +116 -0
- package/packages/contracts/src/tmux.ts +60 -0
- package/packages/contracts/src/workspace.ts +67 -0
- package/packages/daemon/src/agent-explain.ts +270 -0
- package/packages/daemon/src/attach.ts +20 -0
- package/packages/daemon/src/bin.ts +4 -0
- package/packages/daemon/src/canonical.ts +7 -0
- package/packages/daemon/src/cli.ts +499 -0
- package/packages/daemon/src/command-center/actions/contract.ts +2 -0
- package/packages/daemon/src/command-center/actions/dispatcher.ts +137 -0
- package/packages/daemon/src/command-center/actions/errors.ts +105 -0
- package/packages/daemon/src/command-center/actions/handlers/_project-context.ts +30 -0
- package/packages/daemon/src/command-center/actions/handlers/_resolve-project.ts +78 -0
- package/packages/daemon/src/command-center/actions/handlers/app-set-remote-access.ts +118 -0
- package/packages/daemon/src/command-center/actions/handlers/config-actions.ts +113 -0
- package/packages/daemon/src/command-center/actions/handlers/daemon-shutdown.ts +38 -0
- package/packages/daemon/src/command-center/actions/handlers/project-activate.ts +30 -0
- package/packages/daemon/src/command-center/actions/handlers/project-launch.ts +70 -0
- package/packages/daemon/src/command-center/actions/handlers/project-open-terminal.ts +87 -0
- package/packages/daemon/src/command-center/actions/handlers/project-restart.ts +38 -0
- package/packages/daemon/src/command-center/actions/handlers/project-stop.ts +62 -0
- package/packages/daemon/src/command-center/actions/handlers/terminal-respawn.ts +119 -0
- package/packages/daemon/src/command-center/actions/handlers/terminal-stop.ts +35 -0
- package/packages/daemon/src/command-center/actions/registry.ts +149 -0
- package/packages/daemon/src/command-center/discovery.ts +96 -0
- package/packages/daemon/src/command-center/index.ts +31 -0
- package/packages/daemon/src/command-center/schemas.ts +85 -0
- package/packages/daemon/src/command-center/server.ts +1260 -0
- package/packages/daemon/src/command-center/ws-events.ts +316 -0
- package/packages/daemon/src/config.ts +549 -0
- package/packages/daemon/src/detect.ts +248 -0
- package/packages/daemon/src/doctor.ts +242 -0
- package/packages/daemon/src/embed.ts +5 -0
- package/packages/daemon/src/index.ts +12 -0
- package/packages/daemon/src/init.ts +211 -0
- package/packages/daemon/src/inspect.ts +178 -0
- package/packages/daemon/src/js-yaml.d.ts +10 -0
- package/packages/daemon/src/launch.ts +349 -0
- package/packages/daemon/src/lib/active-projects.ts +49 -0
- package/packages/daemon/src/lib/agent-discovery.ts +121 -0
- package/packages/daemon/src/lib/app-config.ts +336 -0
- package/packages/daemon/src/lib/app-settings.ts +53 -0
- package/packages/daemon/src/lib/auth/auth-service.ts +227 -0
- package/packages/daemon/src/lib/auth/middleware.ts +56 -0
- package/packages/daemon/src/lib/auth/types.ts +2 -0
- package/packages/daemon/src/lib/auth-token.ts +5 -0
- package/packages/daemon/src/lib/authorship.ts +280 -0
- package/packages/daemon/src/lib/canonical-daemon.ts +122 -0
- package/packages/daemon/src/lib/cli-action-bridge.ts +216 -0
- package/packages/daemon/src/lib/daemon-embed.ts +782 -0
- package/packages/daemon/src/lib/daemon-watchdog.ts +111 -0
- package/packages/daemon/src/lib/daemon.ts +79 -0
- package/packages/daemon/src/lib/dot-path.ts +17 -0
- package/packages/daemon/src/lib/errors.ts +67 -0
- package/packages/daemon/src/lib/filesystem-browser.ts +292 -0
- package/packages/daemon/src/lib/launch-plan.ts +90 -0
- package/packages/daemon/src/lib/log.ts +134 -0
- package/packages/daemon/src/lib/output.ts +76 -0
- package/packages/daemon/src/lib/project-init-runner.ts +150 -0
- package/packages/daemon/src/lib/project-inspect.ts +80 -0
- package/packages/daemon/src/lib/project-onboard.ts +149 -0
- package/packages/daemon/src/lib/project-probe.ts +92 -0
- package/packages/daemon/src/lib/project-registry.ts +296 -0
- package/packages/daemon/src/lib/session-monitor.ts +122 -0
- package/packages/daemon/src/lib/session-options.ts +100 -0
- package/packages/daemon/src/lib/shell.ts +8 -0
- package/packages/daemon/src/lib/sizes.ts +36 -0
- package/packages/daemon/src/lib/skill-sync.ts +155 -0
- package/packages/daemon/src/lib/slugify.ts +10 -0
- package/packages/daemon/src/lib/terminals-store.ts +125 -0
- package/packages/daemon/src/lib/update-check.ts +298 -0
- package/packages/daemon/src/lib/update.ts +158 -0
- package/packages/daemon/src/lib/workspace-registry.ts +229 -0
- package/packages/daemon/src/lib/worktree.ts +289 -0
- package/packages/daemon/src/lib/yaml-io.ts +27 -0
- package/packages/daemon/src/ls.ts +40 -0
- package/packages/daemon/src/restart.ts +24 -0
- package/packages/daemon/src/restore.ts +514 -0
- package/packages/daemon/src/schemas/domain.ts +2 -0
- package/packages/daemon/src/schemas/filesystem.ts +34 -0
- package/packages/daemon/src/schemas/ide-config.ts +2 -0
- package/packages/daemon/src/schemas/index.ts +59 -0
- package/packages/daemon/src/schemas/inspect.ts +67 -0
- package/packages/daemon/src/schemas/registry.ts +55 -0
- package/packages/daemon/src/schemas/ws-events.ts +135 -0
- package/packages/daemon/src/send.ts +171 -0
- package/packages/daemon/src/server/README.md +15 -0
- package/packages/daemon/src/server/index.ts +74 -0
- package/packages/daemon/src/server/pty-bridge.ts +532 -0
- package/packages/daemon/src/server/standalone.ts +18 -0
- package/packages/daemon/src/server/ws-route.ts +483 -0
- package/packages/daemon/src/status.ts +59 -0
- package/packages/daemon/src/stop.ts +28 -0
- package/packages/daemon/src/terminal/NodePtyAdapter.ts +271 -0
- package/packages/daemon/src/terminal/PtyAdapter.ts +140 -0
- package/packages/daemon/src/terminal/README.md +92 -0
- package/packages/daemon/src/tui/chrome/cheatsheet.ts +260 -0
- package/packages/daemon/src/tui/chrome/chip.ts +30 -0
- package/packages/daemon/src/tui/chrome/events.ts +119 -0
- package/packages/daemon/src/tui/chrome/kitty-keys.ts +55 -0
- package/packages/daemon/src/tui/chrome/menu.ts +289 -0
- package/packages/daemon/src/tui/chrome/notify.ts +195 -0
- package/packages/daemon/src/tui/chrome/panels.ts +111 -0
- package/packages/daemon/src/tui/chrome/sidebar.ts +222 -0
- package/packages/daemon/src/tui/chrome/snapshot.ts +425 -0
- package/packages/daemon/src/tui/chrome/statusline.ts +595 -0
- package/packages/daemon/src/tui/chrome/updater.ts +428 -0
- package/packages/daemon/src/tui/chrome/welcome.ts +124 -0
- package/packages/daemon/src/tui/compiled.ts +113 -0
- package/packages/daemon/src/tui/detect/classify.ts +193 -0
- package/packages/daemon/src/tui/detect/manifest-loader.ts +192 -0
- package/packages/daemon/src/tui/detect/manifest.ts +184 -0
- package/packages/daemon/src/tui/detect/manifests.ts +226 -0
- package/packages/daemon/src/tui/detect/process-tree.ts +197 -0
- package/packages/daemon/src/tui/detect/snapshot.ts +70 -0
- package/packages/daemon/src/tui/integrations/claude.ts +176 -0
- package/packages/daemon/src/tui/integrations/offer.ts +145 -0
- package/packages/daemon/src/tui/main.ts +70 -0
- package/packages/daemon/src/tui/mirror/control-client.ts +143 -0
- package/packages/daemon/src/tui/mirror/control.ts +97 -0
- package/packages/daemon/src/tui/mirror/pane-mirror.ts +81 -0
- package/packages/daemon/src/tui/mirror/viewer.tsx +166 -0
- package/packages/daemon/src/tui/team/CONTROL.md +50 -0
- package/packages/daemon/src/tui/team/entry.ts +11 -0
- package/packages/daemon/src/tui/team/fuzzy.ts +133 -0
- package/packages/daemon/src/tui/team/home.ts +170 -0
- package/packages/daemon/src/tui/team/index.tsx +1521 -0
- package/packages/daemon/src/tui/team/input.ts +34 -0
- package/packages/daemon/src/tui/team/keymap.ts +127 -0
- package/packages/daemon/src/tui/team/mouse.ts +29 -0
- package/packages/daemon/src/tui/team/nav.ts +31 -0
- package/packages/daemon/src/tui/team/preview.ts +34 -0
- package/packages/daemon/src/tui/team/projects.ts +191 -0
- package/packages/daemon/src/tui/team/report.ts +73 -0
- package/packages/daemon/src/tui/team/sessions.ts +344 -0
- package/packages/daemon/src/tui/team/tree.ts +62 -0
- package/packages/daemon/src/types.ts +13 -0
- package/packages/daemon/src/ui/index.ts +32 -0
- package/packages/daemon/src/ui/terminal/index.ts +9 -0
- package/packages/daemon/src/ui/types.ts +91 -0
- package/packages/daemon/src/ui/web/base.css +80 -0
- package/packages/daemon/src/ui/web/components/Box.tsx +59 -0
- package/packages/daemon/src/ui/web/components/Input.tsx +32 -0
- package/packages/daemon/src/ui/web/components/ScrollBox.tsx +60 -0
- package/packages/daemon/src/ui/web/components/Text.tsx +28 -0
- package/packages/daemon/src/ui/web/hooks.ts +106 -0
- package/packages/daemon/src/ui/web/index.ts +27 -0
- package/packages/daemon/src/ui/web/render.ts +77 -0
- package/packages/daemon/src/ui/web/utils/color.ts +27 -0
- package/packages/daemon/src/validate.ts +217 -0
- package/packages/daemon/src/widgets/changes/README.md +3 -0
- package/packages/daemon/src/widgets/changes/index.tsx +691 -0
- package/packages/daemon/src/widgets/config/README.md +3 -0
- package/packages/daemon/src/widgets/config/index.tsx +481 -0
- package/packages/daemon/src/widgets/explorer/README.md +3 -0
- package/packages/daemon/src/widgets/explorer/breadcrumbs.tsx +77 -0
- package/packages/daemon/src/widgets/explorer/footer.tsx +20 -0
- package/packages/daemon/src/widgets/explorer/header.tsx +23 -0
- package/packages/daemon/src/widgets/explorer/index.tsx +456 -0
- package/packages/daemon/src/widgets/explorer/tree-model.ts +103 -0
- package/packages/daemon/src/widgets/explorer/tree.tsx +165 -0
- package/packages/daemon/src/widgets/lib/config-model.ts +116 -0
- package/packages/daemon/src/widgets/lib/files.ts +88 -0
- package/packages/daemon/src/widgets/lib/git.ts +88 -0
- package/packages/daemon/src/widgets/lib/grammar.ts +126 -0
- package/packages/daemon/src/widgets/lib/help-overlay.tsx +101 -0
- package/packages/daemon/src/widgets/lib/pane-comms.ts +209 -0
- package/packages/daemon/src/widgets/lib/theme.ts +194 -0
- package/packages/daemon/src/widgets/lib/watcher.ts +132 -0
- package/packages/daemon/src/widgets/preview/README.md +3 -0
- package/packages/daemon/src/widgets/preview/index.tsx +416 -0
- package/packages/daemon/src/widgets/resolve.ts +121 -0
- package/packages/daemon/src/widgets/setup/README.md +3 -0
- package/packages/daemon/src/widgets/setup/agent-naming.tsx +112 -0
- package/packages/daemon/src/widgets/setup/config-tree.tsx +246 -0
- package/packages/daemon/src/widgets/setup/detect-panel.tsx +72 -0
- package/packages/daemon/src/widgets/setup/field-editor.tsx +265 -0
- package/packages/daemon/src/widgets/setup/footer.tsx +107 -0
- package/packages/daemon/src/widgets/setup/index.tsx +341 -0
- package/packages/daemon/src/widgets/setup/layout-picker.tsx +96 -0
- package/packages/daemon/src/widgets/setup/orchestrator-panel.tsx +200 -0
- package/packages/daemon/src/widgets/setup/review-panel.tsx +140 -0
- package/packages/daemon/src/widgets/setup/setup-model.ts +188 -0
- package/packages/daemon/src/widgets/sidebar/index.tsx +527 -0
- package/packages/tmux-bridge/package.json +22 -0
- package/packages/tmux-bridge/src/errors.ts +28 -0
- package/packages/tmux-bridge/src/index.ts +31 -0
- package/packages/tmux-bridge/src/monitor.ts +77 -0
- package/packages/tmux-bridge/src/panes.ts +136 -0
- package/packages/tmux-bridge/src/runner.test.ts +501 -0
- package/packages/tmux-bridge/src/runner.ts +91 -0
- package/packages/tmux-bridge/src/sessions.ts +126 -0
- package/packages/tmux-bridge/src/targeting.test.ts +107 -0
- package/packages/tmux-bridge/src/targeting.ts +90 -0
- package/scripts/postinstall.js +26 -2
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tmux-ide right-click actions menu — native chrome.
|
|
3
|
+
*
|
|
4
|
+
* Right-clicking the chrome row (or pressing `⌥m` on an adopted session) floats
|
|
5
|
+
* tmux's own `display-menu`: a small popup of tmux-ide actions (switch session,
|
|
6
|
+
* cheat sheet, jump to any live session, new session, kill session). tmux draws
|
|
7
|
+
* the whole thing — there is no OpenTUI/bun boot and no per-tick cost — so this
|
|
8
|
+
* module only produces the argument vector.
|
|
9
|
+
*
|
|
10
|
+
* The menu content is LIVE (the session list changes), so it can't be baked into
|
|
11
|
+
* a static bind. Instead the bind runs our CLI via `run-shell`, and the CLI
|
|
12
|
+
* rebuilds the menu args fresh and hands them to `tmux display-menu -c <client>`
|
|
13
|
+
* (see the `menu` case in bin/cli.ts). This mirrors how the switcher/cheat-sheet
|
|
14
|
+
* popups defer their content to a fresh CLI invocation.
|
|
15
|
+
*
|
|
16
|
+
* `buildMenu` and the bind/unbind command builders are PURE (tested); the CLI
|
|
17
|
+
* `menu` command wires the io (resolve the client, run display-menu).
|
|
18
|
+
*/
|
|
19
|
+
import { DEFAULT_THEME, type AppTheme } from "../../lib/app-config.ts";
|
|
20
|
+
import type { AgentStatus } from "../detect/classify.ts";
|
|
21
|
+
import {
|
|
22
|
+
switcherPopupCommand,
|
|
23
|
+
homePopupCommand,
|
|
24
|
+
updatePopupCommand,
|
|
25
|
+
MENU_KEY,
|
|
26
|
+
MENU_PANE_KEY,
|
|
27
|
+
MENU_STATUS_KEY,
|
|
28
|
+
} from "./statusline.ts";
|
|
29
|
+
import type { UpdateStatus } from "../../lib/update-check.ts";
|
|
30
|
+
import { cheatsheetPopupCommand } from "./cheatsheet.ts";
|
|
31
|
+
import { PANEL_POPUPS, panelPopupCommand } from "./panels.ts";
|
|
32
|
+
|
|
33
|
+
/** Single-char menu mnemonics for the panel rows (parallel to PANEL_POPUPS). */
|
|
34
|
+
const PANEL_MENU_KEYS = ["e", "g", ","];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* PURE — glyph + tmux colour per status for the menu's session rows, from the
|
|
38
|
+
* shared theme tokens. Colours are `theme.status.*` (working amber, blocked red,
|
|
39
|
+
* done blue, idle green). `idle` uses the hollow `inactive` glyph so a quiet
|
|
40
|
+
* session reads as "nothing running" at a glance; `unknown` uses the `·` "no
|
|
41
|
+
* signal" marker; everything else the filled `active` glyph.
|
|
42
|
+
*/
|
|
43
|
+
function menuGlyph(status: AgentStatus, theme: AppTheme): { glyph: string; colour: string } {
|
|
44
|
+
const glyph =
|
|
45
|
+
status === "idle" ? theme.glyphs.inactive : status === "unknown" ? "·" : theme.glyphs.active;
|
|
46
|
+
return { glyph, colour: theme.status[status] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Max session rows the menu lists (keys 1..8). Extra sessions are dropped. */
|
|
50
|
+
const MAX_SESSION_ITEMS = 8;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Quote a session name for embedding in a tmux command string (the target of a
|
|
54
|
+
* menu item's `switch-client -t <name>`). Single-quote it and escape any embedded
|
|
55
|
+
* single quote the shell way (`'\''`) — tmux's lexer builds words like the shell,
|
|
56
|
+
* so this keeps a weird name (spaces, `;`, `#`, `$`) inert instead of leaking as
|
|
57
|
+
* extra command tokens. Verified live on tmux 3.6.
|
|
58
|
+
*/
|
|
59
|
+
export function menuQuoteName(name: string): string {
|
|
60
|
+
return `'${name.replace(/'/g, `'\\''`)}'`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The styled `#[fg=…]glyph#[default] name` label for a session row. */
|
|
64
|
+
function sessionLabel(session: { name: string; status: AgentStatus }, theme: AppTheme): string {
|
|
65
|
+
const g = menuGlyph(session.status, theme);
|
|
66
|
+
return `#[fg=${g.colour}]${g.glyph}#[default] ${session.name}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* PURE — the `display-menu` argument vector (everything AFTER `display-menu`,
|
|
71
|
+
* minus the runtime `-c <client>`/placement flags the CLI prepends): a
|
|
72
|
+
* `tmux-ide` title, then four groups joined by separator lines —
|
|
73
|
+
*
|
|
74
|
+
* 1. `⌂ Home cockpit` (h) → the same home popup M-h / the bar trigger opens;
|
|
75
|
+
* `⧉ Switch session…` (s) → the same switcher popup M-p / the bar trigger
|
|
76
|
+
* opens; `? Cheat sheet` (k) → the same cheat-sheet popup M-k opens. All
|
|
77
|
+
* reuse the exact popup command strings so a menu pick behaves identically.
|
|
78
|
+
* 2. the widget PANELS (Files e / Changes g / Config ,) → the same floating
|
|
79
|
+
* `display-popup` each panel's root-table key opens (see ./panels.ts).
|
|
80
|
+
* 3. up to 8 live sessions, each `«glyph» <name>` keyed 1..8 →
|
|
81
|
+
* `switch-client -t <name>` (the name quoted so odd names stay inert). The
|
|
82
|
+
* menu commands run in the client that opened the menu, so the switch lands
|
|
83
|
+
* on the right client.
|
|
84
|
+
* 4. `+ New session…` (n) → a `command-prompt` that creates + switches to a
|
|
85
|
+
* named session; `✕ Kill this session` (x) → a confirmed `kill-session`.
|
|
86
|
+
*
|
|
87
|
+
* A separator is a single empty-string argument (tmux's display-menu convention).
|
|
88
|
+
* Empty groups (no sessions) collapse so two separators never stack.
|
|
89
|
+
*/
|
|
90
|
+
export function buildMenu(
|
|
91
|
+
sessions: Array<{ name: string; status: AgentStatus }>,
|
|
92
|
+
theme: AppTheme = DEFAULT_THEME,
|
|
93
|
+
update?: UpdateStatus,
|
|
94
|
+
): string[] {
|
|
95
|
+
// When an update is pending, lead with a highlighted row that opens the same
|
|
96
|
+
// update popup the dock's `⬆ v<latest>` chip does. Its own group so it reads
|
|
97
|
+
// as a callout above the navigation actions.
|
|
98
|
+
const updateItems: string[] =
|
|
99
|
+
update?.updateAvailable && update.latest
|
|
100
|
+
? [`#[fg=${theme.accent}]⬆ Update available (v${update.latest})`, "u", updatePopupCommand()]
|
|
101
|
+
: [];
|
|
102
|
+
|
|
103
|
+
const header: string[] = [
|
|
104
|
+
"⌂ Home cockpit",
|
|
105
|
+
"h",
|
|
106
|
+
homePopupCommand(),
|
|
107
|
+
"⧉ Switch session…",
|
|
108
|
+
"s",
|
|
109
|
+
switcherPopupCommand(),
|
|
110
|
+
"? Cheat sheet",
|
|
111
|
+
"k",
|
|
112
|
+
cheatsheetPopupCommand(),
|
|
113
|
+
"▏ Toggle sidebar",
|
|
114
|
+
"b",
|
|
115
|
+
// run-shell format-expands #{session_name}, so the toggle targets whatever
|
|
116
|
+
// session the opening client is viewing (bind args don't expand; run-shell
|
|
117
|
+
// does — the same trick the menu bind itself uses).
|
|
118
|
+
`run-shell "tmux-ide sidebar-toggle --session '#{session_name}'"`,
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
// The widget panels — each row opens the same floating popup its root-table
|
|
122
|
+
// key does (esc/q closes). Menu commands run in the opening client, so the
|
|
123
|
+
// popup lands on its pane's cwd.
|
|
124
|
+
const panelItems: string[] = [];
|
|
125
|
+
PANEL_POPUPS.forEach((panel, i) => {
|
|
126
|
+
panelItems.push(panel.label, PANEL_MENU_KEYS[i] ?? "", panelPopupCommand(panel));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const sessionItems: string[] = [];
|
|
130
|
+
sessions.slice(0, MAX_SESSION_ITEMS).forEach((session, i) => {
|
|
131
|
+
sessionItems.push(
|
|
132
|
+
sessionLabel(session, theme),
|
|
133
|
+
String(i + 1),
|
|
134
|
+
`switch-client -t ${menuQuoteName(session.name)}`,
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const footer: string[] = [
|
|
139
|
+
"+ New session…",
|
|
140
|
+
"n",
|
|
141
|
+
`command-prompt -p "new session name:" "new-session -d -s '%%' ; switch-client -t '%%'"`,
|
|
142
|
+
"⎇ New worktree…",
|
|
143
|
+
"w",
|
|
144
|
+
// Prompt for a branch, then create a git worktree + session for it. The
|
|
145
|
+
// command-prompt template is SINGLE-quoted so the inner run-shell arg can be
|
|
146
|
+
// DOUBLE-quoted — run-shell only format-expands #{session_name} inside double
|
|
147
|
+
// quotes (single quotes suppress it). `%%` is command-prompt's branch
|
|
148
|
+
// substitution; --session carries the current session so the CLI resolves the
|
|
149
|
+
// repo from its cwd (run-shell's own cwd is the tmux server's, not the pane's).
|
|
150
|
+
// Quoting verified live on tmux 3.6 with branch `feat/x-1`.
|
|
151
|
+
`command-prompt -p "worktree branch:" 'run-shell "tmux-ide worktree create %% --session #{session_name}"'`,
|
|
152
|
+
"✕ Kill this session",
|
|
153
|
+
"x",
|
|
154
|
+
`confirm-before -p "kill session #S? (y/n)" kill-session`,
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
// Join non-empty groups with a single separator line between them.
|
|
158
|
+
const items: string[] = [];
|
|
159
|
+
for (const group of [updateItems, header, panelItems, sessionItems, footer]) {
|
|
160
|
+
if (group.length === 0) continue;
|
|
161
|
+
if (items.length > 0) items.push(""); // separator
|
|
162
|
+
items.push(...group);
|
|
163
|
+
}
|
|
164
|
+
return ["-T", "tmux-ide", ...items];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Re-export the key constants (defined in statusline.ts alongside POPUP_KEY so
|
|
168
|
+
// the cheat sheet can list them without importing this module) for callers that
|
|
169
|
+
// reach for them via the menu module.
|
|
170
|
+
export { MENU_KEY, MENU_PANE_KEY, MENU_STATUS_KEY };
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The tmux command run by every menu bind: invoke our CLI to (re)build and show
|
|
174
|
+
* the menu. `run-shell` format-expands `#{client_name}` into `--client` (bind
|
|
175
|
+
* args themselves do NOT expand; run-shell DOES — verified live on tmux 3.6), and
|
|
176
|
+
* `-b` detaches so tmux's key/mouse dispatch isn't blocked while the menu is open.
|
|
177
|
+
* The CLI resolves the client from `--client`, falling back to the
|
|
178
|
+
* most-recently-active attached client, and runs display-menu (all tmux calls
|
|
179
|
+
* time-capped so the bind can never hang).
|
|
180
|
+
*/
|
|
181
|
+
function menuRunShellArgs(menuCmd: string): string[] {
|
|
182
|
+
return ["run-shell", "-b", `${menuCmd} --client '#{client_name}'`];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Like {@link menuRunShellArgs} but for a PANE mouse bind: forwards the click
|
|
187
|
+
* position, only knowable at fire time inside the mouse binding.
|
|
188
|
+
*
|
|
189
|
+
* Coordinate model (measured live on tmux 3.6):
|
|
190
|
+
* - `#{mouse_x}`/`#{mouse_y}` are PANE-RELATIVE for pane mouse events — the
|
|
191
|
+
* screen position is `pane_left + mouse_x` / `pane_top + mouse_y`, computed
|
|
192
|
+
* in the bind string itself with tmux format arithmetic (`#{e|+:…}`).
|
|
193
|
+
* - `display-menu -y` is the menu's BOTTOM edge, so passing the pointer row
|
|
194
|
+
* opens the menu upward from the click (tmux clamps/flips near edges).
|
|
195
|
+
* Keyboard binds (M-m) deliberately stay coordless — centered is right when
|
|
196
|
+
* there's no pointer.
|
|
197
|
+
*/
|
|
198
|
+
function menuPaneMouseRunShellArgs(menuCmd: string): string[] {
|
|
199
|
+
return [
|
|
200
|
+
"run-shell",
|
|
201
|
+
"-b",
|
|
202
|
+
`${menuCmd} --client '#{client_name}' --x '#{e|+:#{pane_left},#{mouse_x}}' --y '#{e|+:#{pane_top},#{mouse_y}}'`,
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Status-line (dock) variant: status mouse events carry no pane and their
|
|
208
|
+
* `#{mouse_x}` is already the screen column. The dock sits at the bottom, so
|
|
209
|
+
* `#{client_height}` as the bottom edge opens the menu directly ABOVE the dock
|
|
210
|
+
* at the clicked column.
|
|
211
|
+
*/
|
|
212
|
+
function menuStatusMouseRunShellArgs(menuCmd: string): string[] {
|
|
213
|
+
return [
|
|
214
|
+
"run-shell",
|
|
215
|
+
"-b",
|
|
216
|
+
`${menuCmd} --client '#{client_name}' --x '#{mouse_x}' --y '#{client_height}'`,
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* PURE — the `display-menu` position flags (`-x <n> -y <n>`) for a click at
|
|
222
|
+
* `(x, y)`, or `[]` when either coord is absent or non-numeric (the keyboard
|
|
223
|
+
* path, or an unexpanded `#{mouse_*}` literal) so tmux falls back to centering.
|
|
224
|
+
* `-x` is the menu's LEFT edge; `-y` is the menu's BOTTOM edge on tmux 3.6, so
|
|
225
|
+
* the pointer coords put the menu just above-right of the click.
|
|
226
|
+
*/
|
|
227
|
+
export function menuPositionArgs(x: string | undefined, y: string | undefined): string[] {
|
|
228
|
+
const nx = parseCoord(x);
|
|
229
|
+
const ny = parseCoord(y);
|
|
230
|
+
if (nx === null || ny === null) return [];
|
|
231
|
+
// One row ABOVE the pointer: -y is the menu's bottom edge, and if the pointer
|
|
232
|
+
// cell were ON the menu, the user's trailing button-release would land on the
|
|
233
|
+
// border and dismiss it instantly (even with -O — measured live). A -1 offset
|
|
234
|
+
// puts the release just BELOW the menu, which -O survives.
|
|
235
|
+
return ["-x", String(nx), "-y", String(Math.max(0, ny - 1))];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Parse a non-negative integer coord, or null for anything else. */
|
|
239
|
+
function parseCoord(value: string | undefined): number | null {
|
|
240
|
+
if (typeof value !== "string" || !/^\d+$/.test(value)) return null;
|
|
241
|
+
const n = Number(value);
|
|
242
|
+
return Number.isInteger(n) ? n : null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* PURE — the tmux argv that binds {@link MENU_KEY} (`M-m`, root table) to open
|
|
247
|
+
* the actions menu. Server-wide bind, mirroring the switcher's M-p — see the note
|
|
248
|
+
* on `unadoptSession`.
|
|
249
|
+
*/
|
|
250
|
+
export function menuBindCommand(menuCmd = "tmux-ide menu", key = MENU_KEY): string[] {
|
|
251
|
+
return ["bind-key", "-n", key, ...menuRunShellArgs(menuCmd)];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* PURE — the tmux argv that binds a RIGHT-click on the chrome row
|
|
256
|
+
* ({@link MENU_STATUS_KEY}) to open the same actions menu. Unlike the left-click
|
|
257
|
+
* router this is range-independent: a right-click anywhere on the bar opens the
|
|
258
|
+
* menu, so the session-list content is what matters, not the click target.
|
|
259
|
+
*/
|
|
260
|
+
export function menuStatusBindCommand(menuCmd = "tmux-ide menu"): string[] {
|
|
261
|
+
return ["bind-key", "-n", MENU_STATUS_KEY, ...menuStatusMouseRunShellArgs(menuCmd)];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* PURE — the tmux argv that binds a RIGHT-click on ANY pane body
|
|
266
|
+
* ({@link MENU_PANE_KEY}) to open the same actions menu, so the menu is reachable
|
|
267
|
+
* everywhere, not only from the dock row. Forwards the click position (see
|
|
268
|
+
* {@link menuPaneMouseRunShellArgs}) so the menu opens at the pointer. Panes whose app
|
|
269
|
+
* captures the mouse (vim, agent TUIs) eat the event — graceful degradation,
|
|
270
|
+
* `M-m` still works there.
|
|
271
|
+
*/
|
|
272
|
+
export function menuPaneBindCommand(menuCmd = "tmux-ide menu"): string[] {
|
|
273
|
+
return ["bind-key", "-n", MENU_PANE_KEY, ...menuPaneMouseRunShellArgs(menuCmd)];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** PURE — the tmux argv that removes the {@link MENU_KEY} binding. */
|
|
277
|
+
export function menuUnbindCommand(key = MENU_KEY): string[] {
|
|
278
|
+
return ["unbind-key", "-n", key];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** PURE — the tmux argv that removes the {@link MENU_STATUS_KEY} binding. */
|
|
282
|
+
export function menuStatusUnbindCommand(): string[] {
|
|
283
|
+
return ["unbind-key", "-n", MENU_STATUS_KEY];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** PURE — the tmux argv that removes the {@link MENU_PANE_KEY} binding. */
|
|
287
|
+
export function menuPaneUnbindCommand(): string[] {
|
|
288
|
+
return ["unbind-key", "-n", MENU_PANE_KEY];
|
|
289
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "who needs me" loop — turn fleet transitions into user-facing pings.
|
|
3
|
+
*
|
|
4
|
+
* The chrome {@link ./updater.ts updater} already sees every session's state
|
|
5
|
+
* transition each tick (via {@link ./events.ts diffFleet}). Watching the status
|
|
6
|
+
* bar for a `blocked`/`done` flip is a chore, so this module fans those two
|
|
7
|
+
* signals out to the human: a tmux toast on each attached client
|
|
8
|
+
* (`display-message -c`), and optionally a macOS notification.
|
|
9
|
+
*
|
|
10
|
+
* Split as usual: {@link decideNotifications} + {@link notificationPrefs} +
|
|
11
|
+
* {@link applyKillSwitch} + {@link parseClients} are PURE (unit-tested without a
|
|
12
|
+
* live tmux / filesystem); {@link sendToasts}, {@link sendSystemNotification},
|
|
13
|
+
* {@link listAttachedClients} and {@link readNotificationPrefs} are the thin io
|
|
14
|
+
* wrappers. Every io path is best-effort — a failed ping must never break the
|
|
15
|
+
* updater loop.
|
|
16
|
+
*/
|
|
17
|
+
import { execFileSync } from "node:child_process";
|
|
18
|
+
import { runTmux } from "@tmux-ide/tmux-bridge";
|
|
19
|
+
import { appConfigPath, loadAppConfig, parseAppConfig } from "../../lib/app-config.ts";
|
|
20
|
+
import type { AgentStatus } from "../detect/classify.ts";
|
|
21
|
+
|
|
22
|
+
/** A fleet transition this tick — the shape {@link ./events.ts diffFleet} emits. */
|
|
23
|
+
export interface NotifyEvent {
|
|
24
|
+
session: string;
|
|
25
|
+
from: AgentStatus | null;
|
|
26
|
+
to: AgentStatus;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** An attached tmux client and the session it's currently viewing. */
|
|
30
|
+
export interface AttachedClient {
|
|
31
|
+
client: string;
|
|
32
|
+
session: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A toast destined for one client's status line. */
|
|
36
|
+
export interface ToastTarget {
|
|
37
|
+
client: string;
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A macOS-notification payload (title is fixed by {@link sendSystemNotification}). */
|
|
42
|
+
export interface SystemNotification {
|
|
43
|
+
message: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The verdict of {@link decideNotifications}. */
|
|
47
|
+
export interface NotifyDecision {
|
|
48
|
+
toasts: ToastTarget[];
|
|
49
|
+
system: SystemNotification[];
|
|
50
|
+
/** The debounce map to thread into the next tick (a copy of the input). */
|
|
51
|
+
nextLastNotified: Map<string, number>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The only two states worth pinging the user over. */
|
|
55
|
+
const NOTIFY_STATES: ReadonlySet<AgentStatus> = new Set<AgentStatus>(["blocked", "done"]);
|
|
56
|
+
/** Don't re-ping the same session+state more than once inside this window. */
|
|
57
|
+
export const NOTIFY_DEBOUNCE_MS = 30_000;
|
|
58
|
+
|
|
59
|
+
/** PURE — the human-readable ping text for a notifiable state. */
|
|
60
|
+
function notifyMessage(session: string, to: AgentStatus): string {
|
|
61
|
+
return to === "blocked" ? `⚠ ${session} needs you (blocked)` : `✓ ${session} finished (done)`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* PURE — decide who to ping from this tick's transitions.
|
|
66
|
+
*
|
|
67
|
+
* Rules:
|
|
68
|
+
* - only `blocked` / `done` transitions qualify (working/idle etc. are noise);
|
|
69
|
+
* - DEBOUNCE: skip a session+state that fired within {@link NOTIFY_DEBOUNCE_MS};
|
|
70
|
+
* - SUPPRESS the toast for any client already viewing that session (they can
|
|
71
|
+
* see the bar flip themselves) — other clients still get toasted;
|
|
72
|
+
* - a `system` entry is produced per qualifying, non-debounced event regardless
|
|
73
|
+
* of clients (so the macOS path fires even with nothing attached — the
|
|
74
|
+
* caller gates it on prefs).
|
|
75
|
+
*
|
|
76
|
+
* Returns the toasts/system to dispatch plus `nextLastNotified` (a copy of the
|
|
77
|
+
* input with fresh timestamps for the events we acted on) to thread onward.
|
|
78
|
+
*/
|
|
79
|
+
export function decideNotifications(
|
|
80
|
+
events: NotifyEvent[],
|
|
81
|
+
clients: AttachedClient[],
|
|
82
|
+
lastNotified: Map<string, number>,
|
|
83
|
+
nowMs: number,
|
|
84
|
+
): NotifyDecision {
|
|
85
|
+
const nextLastNotified = new Map(lastNotified);
|
|
86
|
+
const toasts: ToastTarget[] = [];
|
|
87
|
+
const system: SystemNotification[] = [];
|
|
88
|
+
for (const ev of events) {
|
|
89
|
+
if (!NOTIFY_STATES.has(ev.to)) continue;
|
|
90
|
+
const key = `${ev.session}:${ev.to}`;
|
|
91
|
+
const last = nextLastNotified.get(key);
|
|
92
|
+
if (last !== undefined && nowMs - last < NOTIFY_DEBOUNCE_MS) continue;
|
|
93
|
+
nextLastNotified.set(key, nowMs);
|
|
94
|
+
const message = notifyMessage(ev.session, ev.to);
|
|
95
|
+
for (const c of clients) {
|
|
96
|
+
if (c.session === ev.session) continue; // they're already looking at it
|
|
97
|
+
toasts.push({ client: c.client, message });
|
|
98
|
+
}
|
|
99
|
+
system.push({ message });
|
|
100
|
+
}
|
|
101
|
+
return { toasts, system, nextLastNotified };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** PURE — parse `list-clients -F '#{client_name}\t#{session_name}'` output. */
|
|
105
|
+
export function parseClients(lines: string[]): AttachedClient[] {
|
|
106
|
+
const out: AttachedClient[] = [];
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
const [client = "", session = ""] = line.split("\t");
|
|
109
|
+
if (client && session) out.push({ client, session });
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** io — enumerate attached clients from the live tmux server. Never throws. */
|
|
115
|
+
export function listAttachedClients(): AttachedClient[] {
|
|
116
|
+
try {
|
|
117
|
+
const raw = runTmux(["list-clients", "-F", "#{client_name}\t#{session_name}"])
|
|
118
|
+
.toString()
|
|
119
|
+
.trim();
|
|
120
|
+
return raw ? parseClients(raw.split("\n")) : [];
|
|
121
|
+
} catch {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* io — flash each toast on its client's status line (`-d 3000` = 3s; needs tmux
|
|
128
|
+
* ≥3.2). Best-effort per toast: a gone client / failed call just drops that one.
|
|
129
|
+
*/
|
|
130
|
+
export function sendToasts(toasts: ToastTarget[]): void {
|
|
131
|
+
for (const { client, message } of toasts) {
|
|
132
|
+
try {
|
|
133
|
+
runTmux(["display-message", "-c", client, "-d", "3000", message]);
|
|
134
|
+
} catch {
|
|
135
|
+
// client vanished or display failed — never fatal
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* io — fire a macOS notification via `osascript`. macOS-only (guarded), and
|
|
142
|
+
* quotes/backslashes are escaped for the AppleScript string literal. Best-effort.
|
|
143
|
+
*/
|
|
144
|
+
export function sendSystemNotification(message: string): void {
|
|
145
|
+
if (process.platform !== "darwin") return;
|
|
146
|
+
const escaped = message.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
147
|
+
try {
|
|
148
|
+
execFileSync("osascript", ["-e", `display notification "${escaped}" with title "tmux-ide"`], {
|
|
149
|
+
stdio: "ignore",
|
|
150
|
+
});
|
|
151
|
+
} catch {
|
|
152
|
+
// osascript missing / notification blocked — never fatal
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** User notification preferences (minimal, pre-M14). */
|
|
157
|
+
export interface NotificationPrefs {
|
|
158
|
+
toast: boolean;
|
|
159
|
+
macos: boolean;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Defaults: tmux toasts on, macOS notifications off. */
|
|
163
|
+
export const DEFAULT_NOTIFICATION_PREFS: NotificationPrefs = { toast: true, macos: false };
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* PURE — read `{ notifications: { toast, macos } }` out of a parsed config,
|
|
167
|
+
* falling back to {@link DEFAULT_NOTIFICATION_PREFS} for anything missing or of
|
|
168
|
+
* the wrong type. Delegates to the shared {@link parseAppConfig} so notification
|
|
169
|
+
* parsing can't drift from the rest of the config.
|
|
170
|
+
*/
|
|
171
|
+
export function notificationPrefs(parsedConfig: unknown): NotificationPrefs {
|
|
172
|
+
return parseAppConfig(parsedConfig).notifications;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** PURE — the `TMUX_IDE_NOTIFY=0` kill-switch: disables both channels. */
|
|
176
|
+
export function applyKillSwitch(
|
|
177
|
+
prefs: NotificationPrefs,
|
|
178
|
+
envValue: string | undefined,
|
|
179
|
+
): NotificationPrefs {
|
|
180
|
+
return envValue === "0" ? { toast: false, macos: false } : prefs;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Absolute path to the shared config (honors `TMUX_IDE_CONFIG`). */
|
|
184
|
+
export function notifyConfigPath(): string {
|
|
185
|
+
return appConfigPath();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* io — resolve effective prefs from the shared app config (defaults for a
|
|
190
|
+
* missing/invalid file), then apply the `TMUX_IDE_NOTIFY=0` kill-switch. Reads
|
|
191
|
+
* fresh so the env kill-switch is honored each call.
|
|
192
|
+
*/
|
|
193
|
+
export function readNotificationPrefs(): NotificationPrefs {
|
|
194
|
+
return applyKillSwitch(loadAppConfig().notifications, process.env.TMUX_IDE_NOTIFY);
|
|
195
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tmux-ide widget PANELS — every widget is a floating app dialog.
|
|
3
|
+
*
|
|
4
|
+
* The one-app milestone: instead of living as dedicated tmux panes, each widget
|
|
5
|
+
* opens as a `display-popup -E` over your work, one root-table keystroke away,
|
|
6
|
+
* esc/q to close (the popup exits when the widget process does). This mirrors the
|
|
7
|
+
* chrome popups (switcher `M-p` / cheat sheet `M-k`) but points each key at
|
|
8
|
+
* `tmux-ide popup <widget>`, which execs the bun widget synchronously inside the
|
|
9
|
+
* popup's PTY (see the `popup` case in bin/cli.ts).
|
|
10
|
+
*
|
|
11
|
+
* {@link PANEL_POPUPS} is the single source of truth — the widget name (the
|
|
12
|
+
* `tmux-ide popup <widget>` arg AND the `keys.panels.<widget>` config key), a
|
|
13
|
+
* menu label, and per-widget popup sizing. The status bar's adopt binds one key
|
|
14
|
+
* per entry, the cheat sheet lists them, and the actions menu opens them — all
|
|
15
|
+
* derived from this list so they never drift.
|
|
16
|
+
*
|
|
17
|
+
* Only widgets that EXIST and stand alone are here: `preview` renders whatever
|
|
18
|
+
* the explorer selected (via a shared `@preview_file` tmux option — nothing to
|
|
19
|
+
* show on its own) and `setup` is the onboarding wizard, so neither is a panel.
|
|
20
|
+
* `tasks`/`warroom`/`costs` were removed in the v2.6.0 front-end trim.
|
|
21
|
+
*
|
|
22
|
+
* Every builder here is PURE (tested); the io (running the popup) is the CLI's.
|
|
23
|
+
*/
|
|
24
|
+
import type { AppPanelKeys } from "../../lib/app-config.ts";
|
|
25
|
+
|
|
26
|
+
/** A widget that can float as a panel popup. */
|
|
27
|
+
export interface PanelPopup {
|
|
28
|
+
/** Widget name — the `tmux-ide popup <widget>` arg + `keys.panels` field. */
|
|
29
|
+
widget: "explorer" | "changes" | "config";
|
|
30
|
+
/** Human label for the actions menu row. */
|
|
31
|
+
label: string;
|
|
32
|
+
/**
|
|
33
|
+
* `display-popup` size tokens (tmux `-w`/`-h`). Sized per widget: the explorer
|
|
34
|
+
* is a tall, narrower file column; changes is a wide diff surface; config is a
|
|
35
|
+
* roomy tree editor. All large — a panel floats OVER your work, so it should
|
|
36
|
+
* dominate while open, then vanish on esc.
|
|
37
|
+
*/
|
|
38
|
+
width: string;
|
|
39
|
+
height: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The panel registry — the widgets bound as floating popups, in menu order.
|
|
44
|
+
* Keys are configurable via `~/.tmux-ide/config.json` (`keys.panels.*`); the
|
|
45
|
+
* defaults live in {@link ../../lib/app-config.ts}. Sizing is fixed here (tuned,
|
|
46
|
+
* not configurable — a deliberate scope line).
|
|
47
|
+
*/
|
|
48
|
+
export const PANEL_POPUPS: PanelPopup[] = [
|
|
49
|
+
{ widget: "explorer", label: "⊞ Files", width: "60%", height: "85%" },
|
|
50
|
+
{ widget: "changes", label: "± Changes", width: "85%", height: "90%" },
|
|
51
|
+
{ widget: "config", label: "⚙ Config", width: "80%", height: "85%" },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/** The widget names that `tmux-ide popup <widget>` accepts (the panel set). */
|
|
55
|
+
export const POPUP_WIDGETS: string[] = PANEL_POPUPS.map((p) => p.widget);
|
|
56
|
+
|
|
57
|
+
/** The CLI invocation a panel popup runs (`tmux-ide popup <widget>`). */
|
|
58
|
+
export function panelPopupCli(widget: string): string {
|
|
59
|
+
return `tmux-ide popup ${widget}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The configured root-table key for a panel. `AppPanelKeys` fields are named
|
|
64
|
+
* for the widgets, so this is a typed lookup — the panel registry and the config
|
|
65
|
+
* shape stay in lockstep.
|
|
66
|
+
*/
|
|
67
|
+
export function panelKey(panel: PanelPopup, keys: AppPanelKeys): string {
|
|
68
|
+
return keys[panel.widget];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* PURE — the `display-popup` command STRING that floats a panel (shared by the
|
|
73
|
+
* actions menu's Panels rows, so a menu pick opens an identical popup to the
|
|
74
|
+
* key bind). `-d '#{pane_current_path}'` opens the widget on the pane's cwd, so
|
|
75
|
+
* the explorer/changes/config land on the project you're looking at.
|
|
76
|
+
*/
|
|
77
|
+
export function panelPopupCommand(panel: PanelPopup, cli = panelPopupCli(panel.widget)): string {
|
|
78
|
+
return `display-popup -E -d '#{pane_current_path}' -w ${panel.width} -h ${panel.height} "${cli}"`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* PURE — the tmux argv that binds a panel's root-table key to its
|
|
83
|
+
* `display-popup`. Server-wide root-table bind (tmux has no per-session
|
|
84
|
+
* bind-key), mirroring the switcher's popup key — see the note on
|
|
85
|
+
* `unadoptSession`. Idempotent: re-binding a key overwrites it.
|
|
86
|
+
*/
|
|
87
|
+
export function panelPopupBindCommand(
|
|
88
|
+
panel: PanelPopup,
|
|
89
|
+
key: string,
|
|
90
|
+
cli = panelPopupCli(panel.widget),
|
|
91
|
+
): string[] {
|
|
92
|
+
return [
|
|
93
|
+
"bind-key",
|
|
94
|
+
"-n",
|
|
95
|
+
key,
|
|
96
|
+
"display-popup",
|
|
97
|
+
"-E",
|
|
98
|
+
"-d",
|
|
99
|
+
"#{pane_current_path}",
|
|
100
|
+
"-w",
|
|
101
|
+
panel.width,
|
|
102
|
+
"-h",
|
|
103
|
+
panel.height,
|
|
104
|
+
cli,
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** PURE — the tmux argv that removes a panel's key binding. */
|
|
109
|
+
export function panelPopupUnbindCommand(key: string): string[] {
|
|
110
|
+
return ["unbind-key", "-n", key];
|
|
111
|
+
}
|