@brimveyn/aimux 1.4.0 → 1.5.0
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/README.md +142 -176
- package/package.json +7 -3
- package/src/app-runtime/use-terminal-resize.ts +112 -32
- package/src/app.tsx +20 -3
- package/src/config.ts +53 -17
- package/src/daemon/session-manager.ts +11 -4
- package/src/daemon/session-registry.ts +15 -4
- package/src/index.tsx +6 -1
- package/src/input/keymap/build-handlers.ts +1 -0
- package/src/input/keymap/help-entries.ts +44 -0
- package/src/input/keymap/keymap-ref.ts +11 -0
- package/src/input/keymap/sequence-resolver.ts +35 -2
- package/src/input/keymap/trie.ts +1 -0
- package/src/input/modes/bridge.ts +1 -1
- package/src/input/modes/handlers/shared.ts +0 -16
- package/src/input/modes/transitions.ts +4 -4
- package/src/input/modes/types.ts +1 -1
- package/src/platform/daemon-control.ts +0 -8
- package/src/pty/pty-manager.ts +127 -11
- package/src/restart-terminal-manager.ts +44 -0
- package/src/session-backend/local-session-backend.ts +15 -4
- package/src/session-backend/remote-session-backend.ts +13 -2
- package/src/session-backend/types.ts +13 -2
- package/src/state/reducers/git-panel-state.ts +35 -8
- package/src/state/reducers/modal-state.ts +48 -3
- package/src/state/selectors.ts +1 -5
- package/src/state/session-persistence.ts +1 -20
- package/src/state/store.ts +38 -5
- package/src/state/types.ts +27 -13
- package/src/state/validation.ts +0 -2
- package/src/state/workspace-save.ts +6 -2
- package/src/ui/components/create-session-modal.tsx +5 -3
- package/src/ui/components/git-commit-modal.tsx +1 -3
- package/src/ui/components/git-pane-widget.tsx +46 -0
- package/src/ui/components/git-panel.tsx +72 -25
- package/src/ui/components/help-modal.tsx +156 -42
- package/src/ui/components/modal-keybinds-overlay.tsx +39 -0
- package/src/ui/components/modal-shell.tsx +15 -3
- package/src/ui/components/new-tab-modal.tsx +9 -10
- package/src/ui/components/pending-chord-overlay.tsx +2 -1
- package/src/ui/components/session-name-modal.tsx +1 -3
- package/src/ui/components/session-picker-modal.tsx +1 -3
- package/src/ui/components/sidebar.tsx +24 -50
- package/src/ui/components/snippet-editor-modal.tsx +1 -3
- package/src/ui/components/snippet-picker-modal.tsx +1 -3
- package/src/ui/components/status-bar.tsx +0 -4
- package/src/ui/components/terminal-pane.tsx +17 -12
- package/src/ui/components/theme-picker-modal.tsx +6 -3
- package/src/ui/components/update-available-modal.tsx +7 -4
- package/src/ui/keymap-context.ts +1 -6
- package/src/ui/root.tsx +29 -1
- package/src/ui/status-bar-model.ts +0 -5
- package/src/ui/directory-search.ts +0 -1
package/src/config.ts
CHANGED
|
@@ -9,18 +9,43 @@ import { THEME_IDS, type ThemeId } from './ui/themes'
|
|
|
9
9
|
|
|
10
10
|
export const CONFIG_PATH = `${getProfileConfigDir()}/aimux.json`
|
|
11
11
|
|
|
12
|
+
export interface PersistedGitPane {
|
|
13
|
+
visible: boolean
|
|
14
|
+
mode: 'embedded' | 'pane'
|
|
15
|
+
position: 'top' | 'bottom' | 'left' | 'right'
|
|
16
|
+
ratio: number
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
export interface AimuxConfig {
|
|
13
20
|
version: 2
|
|
14
21
|
customCommands: Record<string, string>
|
|
15
22
|
themeId?: ThemeId
|
|
16
|
-
|
|
17
|
-
gitPanelRatio?: number
|
|
23
|
+
gitPane?: PersistedGitPane
|
|
18
24
|
sessionBarVisible?: boolean
|
|
19
25
|
sessionBarPosition?: SessionBarPosition
|
|
20
26
|
workspaceSnapshot?: WorkspaceSnapshotV1
|
|
21
27
|
skippedUpdateVersion?: string
|
|
22
28
|
}
|
|
23
29
|
|
|
30
|
+
function isPersistedGitPane(value: unknown): value is PersistedGitPane {
|
|
31
|
+
if (typeof value !== 'object' || value === null) return false
|
|
32
|
+
const v = value as Record<string, unknown>
|
|
33
|
+
const modeOk = v.mode === 'embedded' || v.mode === 'pane'
|
|
34
|
+
const positionOk =
|
|
35
|
+
v.position === 'top' ||
|
|
36
|
+
v.position === 'bottom' ||
|
|
37
|
+
v.position === 'left' ||
|
|
38
|
+
v.position === 'right'
|
|
39
|
+
const ratioOk =
|
|
40
|
+
typeof v.ratio === 'number' && Number.isFinite(v.ratio) && v.ratio > 0 && v.ratio < 1
|
|
41
|
+
const visibleOk = typeof v.visible === 'boolean'
|
|
42
|
+
if (!modeOk || !positionOk || !ratioOk || !visibleOk) return false
|
|
43
|
+
// cross-field coherence: embedded => top|bottom; pane => left|right
|
|
44
|
+
if (v.mode === 'embedded' && v.position !== 'top' && v.position !== 'bottom') return false
|
|
45
|
+
if (v.mode === 'pane' && v.position !== 'left' && v.position !== 'right') return false
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
24
49
|
const DEFAULT_CONFIG: AimuxConfig = {
|
|
25
50
|
customCommands: {},
|
|
26
51
|
version: 2,
|
|
@@ -58,6 +83,7 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
58
83
|
version?: number
|
|
59
84
|
customCommands?: unknown
|
|
60
85
|
themeId?: unknown
|
|
86
|
+
gitPane?: unknown
|
|
61
87
|
gitPanelVisible?: unknown
|
|
62
88
|
gitPanelRatio?: unknown
|
|
63
89
|
sessionBarVisible?: unknown
|
|
@@ -80,21 +106,32 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
80
106
|
issues.push('ignored invalid themeId')
|
|
81
107
|
}
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
issues.push('ignored invalid gitPanelVisible')
|
|
109
|
+
let validGitPane = isPersistedGitPane(parsed.gitPane) ? parsed.gitPane : undefined
|
|
110
|
+
if (parsed.gitPane !== undefined && validGitPane === undefined) {
|
|
111
|
+
issues.push('ignored invalid gitPane')
|
|
87
112
|
}
|
|
88
113
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
? parsed.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
// Legacy migration: previous schema stored gitPanelVisible/gitPanelRatio at
|
|
115
|
+
// top level. If the new `gitPane` field is absent, synthesize it from legacy
|
|
116
|
+
// keys so users don't lose their toggle/ratio on upgrade.
|
|
117
|
+
if (validGitPane === undefined) {
|
|
118
|
+
const legacyVisible =
|
|
119
|
+
typeof parsed.gitPanelVisible === 'boolean' ? parsed.gitPanelVisible : undefined
|
|
120
|
+
const legacyRatio =
|
|
121
|
+
typeof parsed.gitPanelRatio === 'number' &&
|
|
122
|
+
Number.isFinite(parsed.gitPanelRatio) &&
|
|
123
|
+
parsed.gitPanelRatio > 0 &&
|
|
124
|
+
parsed.gitPanelRatio < 1
|
|
125
|
+
? parsed.gitPanelRatio
|
|
126
|
+
: undefined
|
|
127
|
+
if (legacyVisible !== undefined || legacyRatio !== undefined) {
|
|
128
|
+
validGitPane = {
|
|
129
|
+
mode: 'embedded',
|
|
130
|
+
position: 'bottom',
|
|
131
|
+
ratio: legacyRatio ?? 0.5,
|
|
132
|
+
visible: legacyVisible ?? true,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
98
135
|
}
|
|
99
136
|
|
|
100
137
|
const validSessionBarVisible =
|
|
@@ -133,8 +170,7 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
133
170
|
return {
|
|
134
171
|
config: {
|
|
135
172
|
customCommands: isCustomCommandsRecord(parsed.customCommands) ? parsed.customCommands : {},
|
|
136
|
-
|
|
137
|
-
gitPanelVisible: validGitPanelVisible,
|
|
173
|
+
gitPane: validGitPane,
|
|
138
174
|
sessionBarPosition: validSessionBarPosition,
|
|
139
175
|
sessionBarVisible: validSessionBarVisible,
|
|
140
176
|
skippedUpdateVersion: validSkippedUpdateVersion,
|
|
@@ -67,8 +67,14 @@ export class SessionManager extends EventEmitter<SessionManagerEvents> {
|
|
|
67
67
|
this.getOrCreateRegistry(sessionId).write(tabId, data)
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
resize(
|
|
71
|
-
|
|
70
|
+
resize(
|
|
71
|
+
sessionId: string,
|
|
72
|
+
cols: number,
|
|
73
|
+
rows: number,
|
|
74
|
+
intents?: Map<string, ScrollIntent>,
|
|
75
|
+
options?: { sync?: boolean }
|
|
76
|
+
): void {
|
|
77
|
+
this.getOrCreateRegistry(sessionId).resizeAll(cols, rows, intents, options)
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
resizeTab(
|
|
@@ -76,9 +82,10 @@ export class SessionManager extends EventEmitter<SessionManagerEvents> {
|
|
|
76
82
|
tabId: string,
|
|
77
83
|
cols: number,
|
|
78
84
|
rows: number,
|
|
79
|
-
intent?: ScrollIntent
|
|
85
|
+
intent?: ScrollIntent,
|
|
86
|
+
options?: { sync?: boolean }
|
|
80
87
|
): void {
|
|
81
|
-
this.getOrCreateRegistry(sessionId).resizeTab(tabId, cols, rows, intent)
|
|
88
|
+
this.getOrCreateRegistry(sessionId).resizeTab(tabId, cols, rows, intent, options)
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
scroll(sessionId: string, tabId: string, deltaLines: number): void {
|
|
@@ -165,12 +165,23 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
165
165
|
this.ptyManager.write(tabId, data)
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
resizeAll(
|
|
169
|
-
|
|
168
|
+
resizeAll(
|
|
169
|
+
cols: number,
|
|
170
|
+
rows: number,
|
|
171
|
+
intents?: Map<string, ScrollIntent>,
|
|
172
|
+
options?: { sync?: boolean }
|
|
173
|
+
): void {
|
|
174
|
+
this.ptyManager.resizeAll(cols, rows, intents, options)
|
|
170
175
|
}
|
|
171
176
|
|
|
172
|
-
resizeTab(
|
|
173
|
-
|
|
177
|
+
resizeTab(
|
|
178
|
+
tabId: string,
|
|
179
|
+
cols: number,
|
|
180
|
+
rows: number,
|
|
181
|
+
intent?: ScrollIntent,
|
|
182
|
+
options?: { sync?: boolean }
|
|
183
|
+
): void {
|
|
184
|
+
this.ptyManager.resizeSession(tabId, cols, rows, intent, options)
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
scrollViewport(tabId: string, deltaLines: number): void {
|
package/src/index.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import { getRuntimeProfile } from './daemon/runtime-paths'
|
|
|
9
9
|
import { logDebug } from './debug/input-log'
|
|
10
10
|
import { runDoctor } from './doctor'
|
|
11
11
|
import { runRestartDaemon } from './restart-daemon'
|
|
12
|
+
import { runRestartTerminalManager } from './restart-terminal-manager'
|
|
12
13
|
import { createSessionBackend } from './session-backend/bootstrap'
|
|
13
14
|
import { runTerminalManager } from './terminal-manager/terminal-manager'
|
|
14
15
|
import { runUpdate } from './update'
|
|
@@ -30,6 +31,10 @@ if (command === 'restart-daemon') {
|
|
|
30
31
|
process.exit(await runRestartDaemon())
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
if (command === 'restart-terminal-manager') {
|
|
35
|
+
process.exit(await runRestartTerminalManager())
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
if (command === 'update') {
|
|
34
39
|
process.exit(await runUpdate())
|
|
35
40
|
}
|
|
@@ -46,7 +51,7 @@ if (command === 'terminal-manager') {
|
|
|
46
51
|
|
|
47
52
|
if (command === '--help' || command === '-h') {
|
|
48
53
|
process.stdout.write(
|
|
49
|
-
'aimux -- terminal multiplexer for AI CLIs\n\nUsage:\n aimux
|
|
54
|
+
'aimux -- terminal multiplexer for AI CLIs\n\nUsage:\n aimux Start aimux\n aimux update Update to latest version\n aimux doctor Diagnose setup issues\n aimux restart-daemon Restart IPC daemon\n aimux restart-terminal-manager Restart terminal-manager (kills live sessions)\n\n'
|
|
50
55
|
)
|
|
51
56
|
process.exit(0)
|
|
52
57
|
}
|
|
@@ -18,6 +18,7 @@ export function buildKeymapHandlers(config: ResolvedKeymapConfig): KeymapModeHan
|
|
|
18
18
|
const sequence = parseKeyNotation(binding.keys, leaderChord)
|
|
19
19
|
trie.insert(sequence, {
|
|
20
20
|
group: binding.group,
|
|
21
|
+
repeatable: binding.repeatable,
|
|
21
22
|
result: binding.result,
|
|
22
23
|
})
|
|
23
24
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ModeId, ResolvedKeymapConfig } from '@brimveyn/aimux-config'
|
|
2
|
+
|
|
3
|
+
import { describeBindings, type DescribedBinding } from './describe-bindings'
|
|
4
|
+
|
|
5
|
+
export interface HelpEntry extends DescribedBinding {
|
|
6
|
+
mode: ModeId
|
|
7
|
+
modeLabel: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const HELP_MODE_LABELS: { modeId: ModeId; label: string }[] = [
|
|
11
|
+
{ label: 'Navigation', modeId: 'navigation' },
|
|
12
|
+
{ label: 'Terminal input', modeId: 'terminal-input' },
|
|
13
|
+
{ label: 'Git mode', modeId: 'git-mode' },
|
|
14
|
+
{ label: 'Git commit', modeId: 'modal.git-commit' },
|
|
15
|
+
{ label: 'New tab', modeId: 'modal.new-tab' },
|
|
16
|
+
{ label: 'New tab — command', modeId: 'modal.new-tab.command-edit' },
|
|
17
|
+
{ label: 'Session picker', modeId: 'modal.session-picker' },
|
|
18
|
+
{ label: 'Session picker — filter', modeId: 'modal.session-picker.filtering' },
|
|
19
|
+
{ label: 'Session name', modeId: 'modal.session-name' },
|
|
20
|
+
{ label: 'Create session', modeId: 'modal.create-session' },
|
|
21
|
+
{ label: 'Rename tab', modeId: 'modal.rename-tab' },
|
|
22
|
+
{ label: 'Snippet picker', modeId: 'modal.snippet-picker' },
|
|
23
|
+
{ label: 'Snippet picker — filter', modeId: 'modal.snippet-picker.filtering' },
|
|
24
|
+
{ label: 'Snippet editor', modeId: 'modal.snippet-editor' },
|
|
25
|
+
{ label: 'Theme picker', modeId: 'modal.theme-picker' },
|
|
26
|
+
{ label: 'Split picker', modeId: 'modal.split-picker' },
|
|
27
|
+
{ label: 'Help', modeId: 'modal.help' },
|
|
28
|
+
{ label: 'Help — filter', modeId: 'modal.help.filtering' },
|
|
29
|
+
{ label: 'Update available', modeId: 'modal.update-available' },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
export function collectHelpEntries(config: ResolvedKeymapConfig): HelpEntry[] {
|
|
33
|
+
const entries: HelpEntry[] = []
|
|
34
|
+
for (const { label, modeId } of HELP_MODE_LABELS) {
|
|
35
|
+
const bindings = describeBindings(config, modeId, {
|
|
36
|
+
dedupeByDescription: true,
|
|
37
|
+
withDescriptionOnly: true,
|
|
38
|
+
})
|
|
39
|
+
for (const binding of bindings) {
|
|
40
|
+
entries.push({ ...binding, mode: modeId, modeLabel: label })
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return entries
|
|
44
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ResolvedKeymapConfig } from '@brimveyn/aimux-config'
|
|
2
|
+
|
|
3
|
+
let activeKeymap: ResolvedKeymapConfig | null = null
|
|
4
|
+
|
|
5
|
+
export function setActiveKeymap(config: ResolvedKeymapConfig | null): void {
|
|
6
|
+
activeKeymap = config
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getActiveKeymap(): ResolvedKeymapConfig | null {
|
|
10
|
+
return activeKeymap
|
|
11
|
+
}
|
|
@@ -17,6 +17,8 @@ export class SequenceResolver {
|
|
|
17
17
|
private timeoutHandle: ReturnType<typeof setTimeout> | null = null
|
|
18
18
|
private onTimeout: ((binding: TrieBinding) => void) | null = null
|
|
19
19
|
private onPendingChange: ((chords: KeyChord[] | null) => void) | null = null
|
|
20
|
+
private repeatTerminal: KeyChord | null = null
|
|
21
|
+
private repeatBinding: TrieBinding | null = null
|
|
20
22
|
|
|
21
23
|
constructor(
|
|
22
24
|
private readonly trie: KeyTrie,
|
|
@@ -37,16 +39,29 @@ export class SequenceResolver {
|
|
|
37
39
|
feed(chord: KeyChord): ResolveResult {
|
|
38
40
|
this.clearTimeout()
|
|
39
41
|
|
|
42
|
+
// Repeat: when not mid-sequence and the chord matches the last repeatable
|
|
43
|
+
// sequence's terminal key, re-fire it.
|
|
44
|
+
if (
|
|
45
|
+
this.currentNode === null &&
|
|
46
|
+
this.repeatTerminal !== null &&
|
|
47
|
+
this.repeatBinding !== null &&
|
|
48
|
+
chord === this.repeatTerminal
|
|
49
|
+
) {
|
|
50
|
+
return { binding: this.repeatBinding, type: 'resolved' }
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
const fromNode = this.currentNode ?? this.trie.root
|
|
41
54
|
const match = this.trie.lookup(chord, fromNode)
|
|
42
55
|
|
|
43
56
|
switch (match.type) {
|
|
44
57
|
case 'exact': {
|
|
45
58
|
this.resetState()
|
|
59
|
+
this.updateRepeatOnResolve(chord, match.binding)
|
|
46
60
|
return { binding: match.binding, type: 'resolved' }
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
case 'prefix': {
|
|
64
|
+
this.clearRepeat()
|
|
50
65
|
this.currentNode = match.node
|
|
51
66
|
this.pendingBinding = null
|
|
52
67
|
this.pendingChords.push(chord)
|
|
@@ -55,11 +70,12 @@ export class SequenceResolver {
|
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
case 'exact+prefix': {
|
|
73
|
+
this.clearRepeat()
|
|
58
74
|
this.currentNode = match.node
|
|
59
75
|
this.pendingBinding = match.binding
|
|
60
76
|
this.pendingChords.push(chord)
|
|
61
77
|
this.emitPendingChange()
|
|
62
|
-
this.startTimeout(match.binding)
|
|
78
|
+
this.startTimeout(chord, match.binding)
|
|
63
79
|
return { type: 'pending' }
|
|
64
80
|
}
|
|
65
81
|
|
|
@@ -69,6 +85,7 @@ export class SequenceResolver {
|
|
|
69
85
|
this.resetState()
|
|
70
86
|
return this.feed(chord)
|
|
71
87
|
}
|
|
88
|
+
this.clearRepeat()
|
|
72
89
|
return { type: 'passthrough' }
|
|
73
90
|
}
|
|
74
91
|
}
|
|
@@ -77,6 +94,7 @@ export class SequenceResolver {
|
|
|
77
94
|
reset(): void {
|
|
78
95
|
this.clearTimeout()
|
|
79
96
|
this.resetState()
|
|
97
|
+
this.clearRepeat()
|
|
80
98
|
}
|
|
81
99
|
|
|
82
100
|
setTimeoutCallback(cb: (binding: TrieBinding) => void): void {
|
|
@@ -107,11 +125,26 @@ export class SequenceResolver {
|
|
|
107
125
|
}
|
|
108
126
|
}
|
|
109
127
|
|
|
110
|
-
private startTimeout(binding: TrieBinding): void {
|
|
128
|
+
private startTimeout(chord: KeyChord, binding: TrieBinding): void {
|
|
111
129
|
this.timeoutHandle = setTimeout(() => {
|
|
112
130
|
this.timeoutHandle = null
|
|
113
131
|
this.resetState()
|
|
132
|
+
this.updateRepeatOnResolve(chord, binding)
|
|
114
133
|
this.onTimeout?.(binding)
|
|
115
134
|
}, this.config.timeoutMs)
|
|
116
135
|
}
|
|
136
|
+
|
|
137
|
+
private updateRepeatOnResolve(chord: KeyChord, binding: TrieBinding): void {
|
|
138
|
+
if (binding.repeatable) {
|
|
139
|
+
this.repeatTerminal = chord
|
|
140
|
+
this.repeatBinding = binding
|
|
141
|
+
} else {
|
|
142
|
+
this.clearRepeat()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private clearRepeat(): void {
|
|
147
|
+
this.repeatTerminal = null
|
|
148
|
+
this.repeatBinding = null
|
|
149
|
+
}
|
|
117
150
|
}
|
package/src/input/keymap/trie.ts
CHANGED
|
@@ -5,7 +5,6 @@ type SupportedModalType = Exclude<ModalType, null>
|
|
|
5
5
|
|
|
6
6
|
const DIRECT_FOCUS_MODE_IDS: Partial<Record<FocusMode, ModeId>> = {
|
|
7
7
|
'git': 'git-mode',
|
|
8
|
-
'layout': 'layout',
|
|
9
8
|
'navigation': 'navigation',
|
|
10
9
|
'terminal-input': 'terminal-input',
|
|
11
10
|
}
|
|
@@ -13,6 +12,7 @@ const DIRECT_FOCUS_MODE_IDS: Partial<Record<FocusMode, ModeId>> = {
|
|
|
13
12
|
const COMMAND_EDIT_MODE_IDS: Partial<Record<SupportedModalType, ModeId>> = {
|
|
14
13
|
'create-session': 'modal.create-session',
|
|
15
14
|
'git-commit': 'modal.git-commit',
|
|
15
|
+
'help': 'modal.help.filtering',
|
|
16
16
|
'new-tab': 'modal.new-tab.command-edit',
|
|
17
17
|
'rename-tab': 'modal.rename-tab',
|
|
18
18
|
'session-name': 'modal.session-name',
|
|
@@ -68,19 +68,3 @@ export function handleCtrlNavigation(key: KeyInput): KeyResult | null {
|
|
|
68
68
|
|
|
69
69
|
return null
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
export function handleCursorNavigation(key: KeyInput): KeyResult | null {
|
|
73
|
-
if (key.name === 'left' && !key.ctrl && !key.meta) {
|
|
74
|
-
return result([{ delta: -1, type: 'move-modal-cursor' }])
|
|
75
|
-
}
|
|
76
|
-
if (key.name === 'right' && !key.ctrl && !key.meta) {
|
|
77
|
-
return result([{ delta: 1, type: 'move-modal-cursor' }])
|
|
78
|
-
}
|
|
79
|
-
if (key.name === 'home' || (key.ctrl && key.name === 'a')) {
|
|
80
|
-
return result([{ to: 'home', type: 'move-modal-cursor' }])
|
|
81
|
-
}
|
|
82
|
-
if (key.name === 'end' || (key.ctrl && key.name === 'e')) {
|
|
83
|
-
return result([{ to: 'end', type: 'move-modal-cursor' }])
|
|
84
|
-
}
|
|
85
|
-
return null
|
|
86
|
-
}
|
|
@@ -2,10 +2,10 @@ import type { ModeId } from './types'
|
|
|
2
2
|
|
|
3
3
|
const TRANSITIONS: Record<ModeId, readonly ModeId[]> = {
|
|
4
4
|
'git-mode': ['navigation', 'modal.git-commit'],
|
|
5
|
-
'layout': ['terminal-input', 'navigation', 'modal.split-picker'],
|
|
6
5
|
'modal.create-session': ['navigation', 'modal.session-picker'],
|
|
7
6
|
'modal.git-commit': ['git-mode'],
|
|
8
|
-
'modal.help': ['navigation'],
|
|
7
|
+
'modal.help': ['navigation', 'modal.help.filtering'],
|
|
8
|
+
'modal.help.filtering': ['modal.help'],
|
|
9
9
|
'modal.new-tab': ['navigation', 'modal.new-tab.command-edit'],
|
|
10
10
|
'modal.new-tab.command-edit': ['modal.new-tab'],
|
|
11
11
|
'modal.rename-tab': ['navigation'],
|
|
@@ -20,7 +20,7 @@ const TRANSITIONS: Record<ModeId, readonly ModeId[]> = {
|
|
|
20
20
|
'modal.snippet-editor': ['navigation', 'modal.snippet-picker'],
|
|
21
21
|
'modal.snippet-picker': ['navigation', 'modal.snippet-picker.filtering', 'modal.snippet-editor'],
|
|
22
22
|
'modal.snippet-picker.filtering': ['modal.snippet-picker'],
|
|
23
|
-
'modal.split-picker': ['navigation'],
|
|
23
|
+
'modal.split-picker': ['navigation', 'terminal-input'],
|
|
24
24
|
'modal.theme-picker': ['navigation'],
|
|
25
25
|
'modal.update-available': ['navigation'],
|
|
26
26
|
'navigation': [
|
|
@@ -34,7 +34,7 @@ const TRANSITIONS: Record<ModeId, readonly ModeId[]> = {
|
|
|
34
34
|
'modal.update-available',
|
|
35
35
|
'git-mode',
|
|
36
36
|
],
|
|
37
|
-
'terminal-input': ['navigation', '
|
|
37
|
+
'terminal-input': ['navigation', 'modal.split-picker'],
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function isValidTransition(from: ModeId, to: ModeId): boolean {
|
package/src/input/modes/types.ts
CHANGED
|
@@ -5,7 +5,6 @@ import type { AppAction, AppState, TabSession } from '../../state/types'
|
|
|
5
5
|
export type ModeId =
|
|
6
6
|
| 'navigation'
|
|
7
7
|
| 'terminal-input'
|
|
8
|
-
| 'layout'
|
|
9
8
|
| 'git-mode'
|
|
10
9
|
| 'modal.new-tab'
|
|
11
10
|
| 'modal.new-tab.command-edit'
|
|
@@ -19,6 +18,7 @@ export type ModeId =
|
|
|
19
18
|
| 'modal.snippet-editor'
|
|
20
19
|
| 'modal.theme-picker'
|
|
21
20
|
| 'modal.help'
|
|
21
|
+
| 'modal.help.filtering'
|
|
22
22
|
| 'modal.split-picker'
|
|
23
23
|
| 'modal.git-commit'
|
|
24
24
|
| 'modal.update-available'
|
|
@@ -21,10 +21,6 @@ export async function findSocketProcessPid(socketPath: string): Promise<number |
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export async function findDaemonPid(socketPath: string): Promise<number | null> {
|
|
25
|
-
return findSocketProcessPid(socketPath)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
24
|
export async function findIpcDaemonPid(): Promise<number | null> {
|
|
29
25
|
return findSocketProcessPid(getIpcDaemonSocketPath())
|
|
30
26
|
}
|
|
@@ -53,10 +49,6 @@ export async function killProcess(pid: number): Promise<void> {
|
|
|
53
49
|
}
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
export async function killDaemon(pid: number): Promise<void> {
|
|
57
|
-
await killProcess(pid)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
52
|
async function waitForSocket(socketPath: string): Promise<boolean> {
|
|
61
53
|
const deadline = Date.now() + 2_000
|
|
62
54
|
while (Date.now() < deadline) {
|