@brimveyn/aimux 1.5.4 → 1.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/README.md +14 -2
- package/package.json +5 -3
- package/src/app-runtime/side-effects.ts +41 -6
- package/src/app.tsx +17 -5
- package/src/config.ts +29 -4
- package/src/diff-parser/clean-last-newline.ts +5 -0
- package/src/diff-parser/constants.ts +13 -0
- package/src/diff-parser/index.ts +12 -0
- package/src/diff-parser/parse-line-type.ts +29 -0
- package/src/diff-parser/parse-patch-files.ts +369 -0
- package/src/diff-parser/types.ts +75 -0
- package/src/git/git-diff.ts +24 -12
- package/src/git/git-poller.ts +11 -3
- package/src/git/git-status.ts +98 -17
- package/src/input/modes/bridge.ts +8 -0
- package/src/input/modes/transitions.ts +2 -1
- package/src/input/modes/types.ts +4 -1
- package/src/pty/terminal-snapshot.ts +5 -5
- package/src/state/git-tree.ts +220 -0
- package/src/state/reducers/git-mode-state.ts +276 -36
- package/src/state/reducers/git-panel-state.ts +35 -4
- package/src/state/reducers/modal-state.ts +43 -10
- package/src/state/store.ts +5 -1
- package/src/state/types.ts +46 -6
- package/src/state/workspace-save.ts +2 -0
- package/src/ui/components/create-session-modal.tsx +25 -8
- package/src/ui/components/diff-renderer/build-rows.ts +349 -0
- package/src/ui/components/diff-renderer/filetype.ts +29 -0
- package/src/ui/components/diff-renderer/fold-strip.tsx +86 -0
- package/src/ui/components/diff-renderer/highlight.ts +48 -0
- package/src/ui/components/diff-renderer/index.ts +1 -0
- package/src/ui/components/diff-renderer/pierre-diff.tsx +171 -0
- package/src/ui/components/diff-renderer/split-view.tsx +211 -0
- package/src/ui/components/diff-renderer/stacked-view.tsx +168 -0
- package/src/ui/components/git-commit-modal.tsx +16 -3
- package/src/ui/components/git-pane-widget.tsx +6 -1
- package/src/ui/components/git-panel.tsx +215 -86
- package/src/ui/components/git-view.tsx +103 -90
- package/src/ui/components/help-modal.tsx +45 -12
- package/src/ui/components/input-field.tsx +4 -3
- package/src/ui/components/list-item.tsx +11 -2
- package/src/ui/components/modal-filter-bar.tsx +3 -2
- package/src/ui/components/modal-keybinds-overlay.tsx +4 -3
- package/src/ui/components/modal-shell.tsx +5 -4
- package/src/ui/components/new-tab-modal.tsx +17 -4
- package/src/ui/components/pending-chord-overlay.tsx +5 -4
- package/src/ui/components/session-bar.tsx +13 -6
- package/src/ui/components/session-picker-modal.tsx +28 -10
- package/src/ui/components/sidebar.tsx +26 -11
- package/src/ui/components/snippet-editor-modal.tsx +18 -3
- package/src/ui/components/snippet-picker-modal.tsx +13 -4
- package/src/ui/components/split-layout.tsx +3 -2
- package/src/ui/components/status-bar.tsx +15 -10
- package/src/ui/components/surface.tsx +6 -6
- package/src/ui/components/tab-item.tsx +28 -17
- package/src/ui/components/terminal-pane.tsx +28 -16
- package/src/ui/components/theme-picker-modal.tsx +113 -17
- package/src/ui/components/update-available-modal.tsx +13 -2
- package/src/ui/filter-themes.ts +15 -0
- package/src/ui/keymap-context.ts +10 -2
- package/src/ui/root.tsx +29 -7
- package/src/ui/shiki.ts +49 -0
- package/src/ui/status-bar-model.ts +32 -4
- package/src/ui/theme-store.ts +36 -0
- package/src/ui/theme.ts +4 -17
- package/src/ui/themes.ts +23 -277
- package/src/ui/syntax.ts +0 -102
|
@@ -1,14 +1,71 @@
|
|
|
1
|
-
import type { AppAction, AppState, GitFileEntry, GitModeState } from '../types'
|
|
1
|
+
import type { AppAction, AppState, FoldState, GitFileEntry, GitModeState } from '../types'
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
getSelectedGitRow,
|
|
5
|
+
gitFileKey,
|
|
6
|
+
moveGitFileSelection,
|
|
7
|
+
moveGitSelection,
|
|
8
|
+
reconcileSelectedGitEntryKey,
|
|
9
|
+
} from '../git-tree'
|
|
3
10
|
import { sortFilesBySection } from './git-panel-state'
|
|
4
11
|
|
|
12
|
+
function applyFold(state: AppState, key: string, foldId: string, next: FoldState): AppState {
|
|
13
|
+
const perPath = state.gitMode.folds[key] ?? {}
|
|
14
|
+
const prev = perPath[foldId] ?? { bottom: 0, top: 0 }
|
|
15
|
+
if (prev.top === next.top && prev.bottom === next.bottom) return state
|
|
16
|
+
const nextPerPath = { ...perPath }
|
|
17
|
+
if (next.top === 0 && next.bottom === 0) {
|
|
18
|
+
delete nextPerPath[foldId]
|
|
19
|
+
} else {
|
|
20
|
+
nextPerPath[foldId] = next
|
|
21
|
+
}
|
|
22
|
+
const nextFolds = { ...state.gitMode.folds }
|
|
23
|
+
if (Object.keys(nextPerPath).length === 0) {
|
|
24
|
+
delete nextFolds[key]
|
|
25
|
+
} else {
|
|
26
|
+
nextFolds[key] = nextPerPath
|
|
27
|
+
}
|
|
28
|
+
return { ...state, gitMode: { ...state.gitMode, folds: nextFolds } }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function clearDiffCacheForPath(state: AppState, path: string): AppState {
|
|
32
|
+
const nextDiffs = { ...state.gitMode.diffs }
|
|
33
|
+
const nextFolds = { ...state.gitMode.folds }
|
|
34
|
+
const nextLoading = { ...state.gitMode.loading }
|
|
35
|
+
let changed = false
|
|
36
|
+
for (const key of Object.keys(nextDiffs)) {
|
|
37
|
+
if (nextDiffs[key]?.path !== path) continue
|
|
38
|
+
delete nextDiffs[key]
|
|
39
|
+
changed = true
|
|
40
|
+
}
|
|
41
|
+
for (const key of Object.keys(nextFolds)) {
|
|
42
|
+
if (!key.endsWith(`:${path}`)) continue
|
|
43
|
+
delete nextFolds[key]
|
|
44
|
+
changed = true
|
|
45
|
+
}
|
|
46
|
+
for (const key of Object.keys(nextLoading)) {
|
|
47
|
+
if (!key.endsWith(`:${path}`)) continue
|
|
48
|
+
delete nextLoading[key]
|
|
49
|
+
changed = true
|
|
50
|
+
}
|
|
51
|
+
if (!changed) return state
|
|
52
|
+
return {
|
|
53
|
+
...state,
|
|
54
|
+
gitMode: { ...state.gitMode, diffs: nextDiffs, folds: nextFolds, loading: nextLoading },
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
5
58
|
export function emptyGitMode(): GitModeState {
|
|
6
59
|
return {
|
|
7
60
|
actionMessage: null,
|
|
61
|
+
collapsedFolders: {},
|
|
8
62
|
diffs: {},
|
|
63
|
+
diffView: 'split',
|
|
64
|
+
folds: {},
|
|
65
|
+
headOffset: 0,
|
|
9
66
|
loading: {},
|
|
10
67
|
pendingDeletePath: null,
|
|
11
|
-
|
|
68
|
+
selectedEntryKey: null,
|
|
12
69
|
}
|
|
13
70
|
}
|
|
14
71
|
|
|
@@ -23,36 +80,176 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
23
80
|
...state.gitMode,
|
|
24
81
|
actionMessage: null,
|
|
25
82
|
pendingDeletePath: null,
|
|
26
|
-
|
|
83
|
+
selectedEntryKey: reconcileSelectedGitEntryKey(
|
|
84
|
+
state.gitPanel.files,
|
|
85
|
+
state.gitMode.collapsedFolders,
|
|
86
|
+
state.gitPane.fileListMode,
|
|
87
|
+
null
|
|
88
|
+
),
|
|
27
89
|
},
|
|
28
90
|
}
|
|
29
91
|
}
|
|
30
92
|
case 'exit-git-mode': {
|
|
31
93
|
if (state.focusMode !== 'git') return state
|
|
32
|
-
return {
|
|
94
|
+
return {
|
|
95
|
+
...state,
|
|
96
|
+
focusMode: 'navigation',
|
|
97
|
+
gitMode: {
|
|
98
|
+
...state.gitMode,
|
|
99
|
+
actionMessage: null,
|
|
100
|
+
diffs: {},
|
|
101
|
+
folds: {},
|
|
102
|
+
headOffset: 0,
|
|
103
|
+
loading: {},
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
case 'git-mode-move-selection': {
|
|
108
|
+
const next = moveGitSelection(
|
|
109
|
+
state.gitPanel.files,
|
|
110
|
+
state.gitMode.collapsedFolders,
|
|
111
|
+
state.gitPane.fileListMode,
|
|
112
|
+
state.gitMode.selectedEntryKey,
|
|
113
|
+
action.delta
|
|
114
|
+
)
|
|
115
|
+
if (!next || next === state.gitMode.selectedEntryKey) return state
|
|
116
|
+
return {
|
|
117
|
+
...state,
|
|
118
|
+
gitMode: {
|
|
119
|
+
...state.gitMode,
|
|
120
|
+
pendingDeletePath: null,
|
|
121
|
+
selectedEntryKey: next,
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
case 'git-mode-move-file-selection': {
|
|
126
|
+
const next = moveGitFileSelection(
|
|
127
|
+
state.gitPanel.files,
|
|
128
|
+
state.gitMode.collapsedFolders,
|
|
129
|
+
state.gitPane.fileListMode,
|
|
130
|
+
state.gitMode.selectedEntryKey,
|
|
131
|
+
action.delta
|
|
132
|
+
)
|
|
133
|
+
if (!next || next === state.gitMode.selectedEntryKey) return state
|
|
134
|
+
return {
|
|
135
|
+
...state,
|
|
136
|
+
gitMode: {
|
|
137
|
+
...state.gitMode,
|
|
138
|
+
pendingDeletePath: null,
|
|
139
|
+
selectedEntryKey: next,
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
case 'git-mode-select-entry-by-key': {
|
|
144
|
+
const next = reconcileSelectedGitEntryKey(
|
|
145
|
+
state.gitPanel.files,
|
|
146
|
+
state.gitMode.collapsedFolders,
|
|
147
|
+
state.gitPane.fileListMode,
|
|
148
|
+
state.gitMode.selectedEntryKey,
|
|
149
|
+
[action.key]
|
|
150
|
+
)
|
|
151
|
+
if (!next || next !== action.key || next === state.gitMode.selectedEntryKey) return state
|
|
152
|
+
return {
|
|
153
|
+
...state,
|
|
154
|
+
gitMode: {
|
|
155
|
+
...state.gitMode,
|
|
156
|
+
pendingDeletePath: null,
|
|
157
|
+
selectedEntryKey: next,
|
|
158
|
+
},
|
|
159
|
+
}
|
|
33
160
|
}
|
|
34
|
-
case 'git-mode-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
161
|
+
case 'git-mode-toggle-folder': {
|
|
162
|
+
const row = getSelectedGitRow(state.gitPanel.files, {
|
|
163
|
+
collapsedFolders: state.gitMode.collapsedFolders,
|
|
164
|
+
fileListMode: state.gitPane.fileListMode,
|
|
165
|
+
selectedEntryKey: action.key,
|
|
166
|
+
})
|
|
167
|
+
if (!row || row.kind !== 'folder') return state
|
|
168
|
+
const nextCollapsed = { ...state.gitMode.collapsedFolders }
|
|
169
|
+
if (action.key in nextCollapsed) {
|
|
170
|
+
delete nextCollapsed[action.key]
|
|
171
|
+
} else {
|
|
172
|
+
nextCollapsed[action.key] = true
|
|
173
|
+
}
|
|
174
|
+
return { ...state, gitMode: { ...state.gitMode, collapsedFolders: nextCollapsed } }
|
|
175
|
+
}
|
|
176
|
+
case 'git-mode-toggle-selected-folder': {
|
|
177
|
+
const row = getSelectedGitRow(state.gitPanel.files, {
|
|
178
|
+
collapsedFolders: state.gitMode.collapsedFolders,
|
|
179
|
+
fileListMode: state.gitPane.fileListMode,
|
|
180
|
+
selectedEntryKey: state.gitMode.selectedEntryKey,
|
|
181
|
+
})
|
|
182
|
+
if (!row || row.kind !== 'folder') return state
|
|
183
|
+
const nextCollapsed = { ...state.gitMode.collapsedFolders }
|
|
184
|
+
if (row.key in nextCollapsed) {
|
|
185
|
+
delete nextCollapsed[row.key]
|
|
186
|
+
} else {
|
|
187
|
+
nextCollapsed[row.key] = true
|
|
188
|
+
}
|
|
189
|
+
return { ...state, gitMode: { ...state.gitMode, collapsedFolders: nextCollapsed } }
|
|
190
|
+
}
|
|
191
|
+
case 'git-mode-collapse-selection': {
|
|
192
|
+
const row = getSelectedGitRow(state.gitPanel.files, {
|
|
193
|
+
collapsedFolders: state.gitMode.collapsedFolders,
|
|
194
|
+
fileListMode: state.gitPane.fileListMode,
|
|
195
|
+
selectedEntryKey: state.gitMode.selectedEntryKey,
|
|
196
|
+
})
|
|
197
|
+
if (!row) return state
|
|
198
|
+
if (row.kind === 'folder' && !row.isCollapsed) {
|
|
199
|
+
return {
|
|
200
|
+
...state,
|
|
201
|
+
gitMode: {
|
|
202
|
+
...state.gitMode,
|
|
203
|
+
collapsedFolders: { ...state.gitMode.collapsedFolders, [row.key]: true },
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (!row.parentKey || row.parentKey === state.gitMode.selectedEntryKey) return state
|
|
39
208
|
return {
|
|
40
209
|
...state,
|
|
41
210
|
gitMode: {
|
|
42
211
|
...state.gitMode,
|
|
43
212
|
pendingDeletePath: null,
|
|
44
|
-
|
|
213
|
+
selectedEntryKey: row.parentKey,
|
|
45
214
|
},
|
|
46
215
|
}
|
|
47
216
|
}
|
|
217
|
+
case 'git-mode-expand-selection': {
|
|
218
|
+
const row = getSelectedGitRow(state.gitPanel.files, {
|
|
219
|
+
collapsedFolders: state.gitMode.collapsedFolders,
|
|
220
|
+
fileListMode: state.gitPane.fileListMode,
|
|
221
|
+
selectedEntryKey: state.gitMode.selectedEntryKey,
|
|
222
|
+
})
|
|
223
|
+
if (!row || row.kind !== 'folder' || !row.isCollapsed) return state
|
|
224
|
+
const nextCollapsed = { ...state.gitMode.collapsedFolders }
|
|
225
|
+
delete nextCollapsed[row.key]
|
|
226
|
+
return { ...state, gitMode: { ...state.gitMode, collapsedFolders: nextCollapsed } }
|
|
227
|
+
}
|
|
228
|
+
case 'git-mode-toggle-file-list-mode': {
|
|
229
|
+
const fileListMode = state.gitPane.fileListMode === 'tree' ? 'flat' : 'tree'
|
|
230
|
+
return {
|
|
231
|
+
...state,
|
|
232
|
+
gitMode: {
|
|
233
|
+
...state.gitMode,
|
|
234
|
+
pendingDeletePath: null,
|
|
235
|
+
selectedEntryKey: reconcileSelectedGitEntryKey(
|
|
236
|
+
state.gitPanel.files,
|
|
237
|
+
state.gitMode.collapsedFolders,
|
|
238
|
+
fileListMode,
|
|
239
|
+
state.gitMode.selectedEntryKey
|
|
240
|
+
),
|
|
241
|
+
},
|
|
242
|
+
gitPane: { ...state.gitPane, fileListMode },
|
|
243
|
+
}
|
|
244
|
+
}
|
|
48
245
|
case 'git-mode-set-diff': {
|
|
49
246
|
const nextLoading = { ...state.gitMode.loading }
|
|
50
|
-
delete nextLoading[action.
|
|
247
|
+
delete nextLoading[action.key]
|
|
51
248
|
return {
|
|
52
249
|
...state,
|
|
53
250
|
gitMode: {
|
|
54
251
|
...state.gitMode,
|
|
55
|
-
diffs: { ...state.gitMode.diffs, [action.
|
|
252
|
+
diffs: { ...state.gitMode.diffs, [action.key]: action.diff },
|
|
56
253
|
loading: nextLoading,
|
|
57
254
|
},
|
|
58
255
|
}
|
|
@@ -60,9 +257,9 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
60
257
|
case 'git-mode-set-loading': {
|
|
61
258
|
const nextLoading = { ...state.gitMode.loading }
|
|
62
259
|
if (action.loading) {
|
|
63
|
-
nextLoading[action.
|
|
260
|
+
nextLoading[action.key] = true
|
|
64
261
|
} else {
|
|
65
|
-
delete nextLoading[action.
|
|
262
|
+
delete nextLoading[action.key]
|
|
66
263
|
}
|
|
67
264
|
return { ...state, gitMode: { ...state.gitMode, loading: nextLoading } }
|
|
68
265
|
}
|
|
@@ -71,17 +268,64 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
71
268
|
return { ...state, gitMode: { ...state.gitMode, pendingDeletePath: action.path } }
|
|
72
269
|
}
|
|
73
270
|
case 'git-mode-clear-diff-cache': {
|
|
74
|
-
|
|
75
|
-
const nextDiffs = { ...state.gitMode.diffs }
|
|
76
|
-
delete nextDiffs[action.path]
|
|
77
|
-
return { ...state, gitMode: { ...state.gitMode, diffs: nextDiffs } }
|
|
271
|
+
return clearDiffCacheForPath(state, action.path)
|
|
78
272
|
}
|
|
79
273
|
case 'git-mode-set-message': {
|
|
80
274
|
if (state.gitMode.actionMessage === action.message) return state
|
|
81
275
|
return { ...state, gitMode: { ...state.gitMode, actionMessage: action.message } }
|
|
82
276
|
}
|
|
277
|
+
case 'git-mode-toggle-diff-view': {
|
|
278
|
+
const next = state.gitMode.diffView === 'split' ? 'stacked' : 'split'
|
|
279
|
+
return { ...state, gitMode: { ...state.gitMode, diffView: next } }
|
|
280
|
+
}
|
|
281
|
+
case 'git-mode-shift-head-offset': {
|
|
282
|
+
const next = Math.max(0, state.gitMode.headOffset + action.delta)
|
|
283
|
+
if (next === state.gitMode.headOffset) return state
|
|
284
|
+
return {
|
|
285
|
+
...state,
|
|
286
|
+
gitMode: {
|
|
287
|
+
...state.gitMode,
|
|
288
|
+
actionMessage: null,
|
|
289
|
+
diffs: {},
|
|
290
|
+
folds: {},
|
|
291
|
+
headOffset: next,
|
|
292
|
+
loading: {},
|
|
293
|
+
pendingDeletePath: null,
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
case 'git-mode-set-head-offset': {
|
|
298
|
+
const next = Math.max(0, action.offset)
|
|
299
|
+
if (next === state.gitMode.headOffset) return state
|
|
300
|
+
return {
|
|
301
|
+
...state,
|
|
302
|
+
gitMode: {
|
|
303
|
+
...state.gitMode,
|
|
304
|
+
diffs: {},
|
|
305
|
+
folds: {},
|
|
306
|
+
headOffset: next,
|
|
307
|
+
loading: {},
|
|
308
|
+
pendingDeletePath: null,
|
|
309
|
+
},
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
case 'git-mode-fold-adjust': {
|
|
313
|
+
const perPath = state.gitMode.folds[action.key] ?? {}
|
|
314
|
+
const prev = perPath[action.foldId] ?? { bottom: 0, top: 0 }
|
|
315
|
+
const nextSide = Math.max(0, prev[action.side] + action.delta)
|
|
316
|
+
const nextFold =
|
|
317
|
+
action.side === 'top'
|
|
318
|
+
? { bottom: prev.bottom, top: nextSide }
|
|
319
|
+
: { bottom: nextSide, top: prev.top }
|
|
320
|
+
return applyFold(state, action.key, action.foldId, nextFold)
|
|
321
|
+
}
|
|
322
|
+
case 'git-mode-fold-set': {
|
|
323
|
+
const top = Math.max(0, action.top)
|
|
324
|
+
const bottom = Math.max(0, action.bottom)
|
|
325
|
+
return applyFold(state, action.key, action.foldId, { bottom, top })
|
|
326
|
+
}
|
|
83
327
|
case 'git-mode-optimistic-move': {
|
|
84
|
-
const
|
|
328
|
+
const currentKey = state.gitMode.selectedEntryKey
|
|
85
329
|
const files = state.gitPanel.files
|
|
86
330
|
const idx = files.findIndex((f) => f.path === action.path && f.section === action.fromSection)
|
|
87
331
|
if (idx < 0) return state
|
|
@@ -103,29 +347,25 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
103
347
|
}
|
|
104
348
|
}
|
|
105
349
|
nextFiles = sortFilesBySection(nextFiles)
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
} else {
|
|
119
|
-
nextIdx = Math.min(currentIdx, newLen - 1)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
350
|
+
const movedCurrentKey = gitFileKey({ path: action.path, section: action.fromSection })
|
|
351
|
+
const preferredSelection =
|
|
352
|
+
currentKey === movedCurrentKey && toSection !== null
|
|
353
|
+
? [gitFileKey({ path: action.path, section: toSection })]
|
|
354
|
+
: []
|
|
355
|
+
const nextSelectedEntryKey = reconcileSelectedGitEntryKey(
|
|
356
|
+
nextFiles,
|
|
357
|
+
state.gitMode.collapsedFolders,
|
|
358
|
+
state.gitPane.fileListMode,
|
|
359
|
+
currentKey,
|
|
360
|
+
preferredSelection
|
|
361
|
+
)
|
|
122
362
|
|
|
123
363
|
return {
|
|
124
364
|
...state,
|
|
125
365
|
gitMode: {
|
|
126
366
|
...state.gitMode,
|
|
127
367
|
pendingDeletePath: null,
|
|
128
|
-
|
|
368
|
+
selectedEntryKey: nextSelectedEntryKey,
|
|
129
369
|
},
|
|
130
370
|
gitPanel: { ...state.gitPanel, files: nextFiles },
|
|
131
371
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { AppAction, AppState, GitFileEntry, GitFileSection, GitPanelState } from '../types'
|
|
2
2
|
|
|
3
|
+
import { reconcileSelectedGitEntryKey } from '../git-tree'
|
|
4
|
+
|
|
3
5
|
export const GIT_PANEL_MIN_RATIO = 0.2
|
|
4
6
|
export const GIT_PANEL_MAX_RATIO = 0.8
|
|
5
7
|
|
|
6
|
-
const SECTION_RANK: Record<GitFileSection, number> = {
|
|
8
|
+
const SECTION_RANK: Record<GitFileSection, number> = {
|
|
9
|
+
historical: 0,
|
|
10
|
+
staged: 1,
|
|
11
|
+
unstaged: 2,
|
|
12
|
+
untracked: 3,
|
|
13
|
+
}
|
|
7
14
|
|
|
8
15
|
export function sortFilesBySection(files: GitFileEntry[]): GitFileEntry[] {
|
|
9
16
|
return [...files].sort((a, b) => {
|
|
@@ -63,6 +70,11 @@ export function reduceGitPanelState(state: AppState, action: AppAction): AppStat
|
|
|
63
70
|
if (nextRatio === state.gitPane.ratio) return state
|
|
64
71
|
return { ...state, gitPane: { ...state.gitPane, ratio: nextRatio } }
|
|
65
72
|
}
|
|
73
|
+
case 'resize-git-diff-pane': {
|
|
74
|
+
const nextRatio = clampRatio(state.gitPane.diffModeRatio + action.delta)
|
|
75
|
+
if (nextRatio === state.gitPane.diffModeRatio) return state
|
|
76
|
+
return { ...state, gitPane: { ...state.gitPane, diffModeRatio: nextRatio } }
|
|
77
|
+
}
|
|
66
78
|
case 'set-git-pane-mode': {
|
|
67
79
|
if (state.gitPane.mode === action.mode) return state
|
|
68
80
|
const isEmbedded = action.mode === 'embedded'
|
|
@@ -93,17 +105,25 @@ export function reduceGitPanelState(state: AppState, action: AppAction): AppStat
|
|
|
93
105
|
const prev = state.gitPanel
|
|
94
106
|
const next = action.payload
|
|
95
107
|
const sortedNext = sortFilesBySection(next.files)
|
|
108
|
+
const nextSelectedEntryKey = reconcileSelectedGitEntryKey(
|
|
109
|
+
sortedNext,
|
|
110
|
+
state.gitMode.collapsedFolders,
|
|
111
|
+
state.gitPane.fileListMode,
|
|
112
|
+
state.gitMode.selectedEntryKey
|
|
113
|
+
)
|
|
96
114
|
if (
|
|
97
115
|
prev.branch === next.branch &&
|
|
98
116
|
prev.ahead === next.ahead &&
|
|
99
117
|
prev.behind === next.behind &&
|
|
100
118
|
prev.error === null &&
|
|
101
|
-
sameFiles(prev.files, sortedNext)
|
|
119
|
+
sameFiles(prev.files, sortedNext) &&
|
|
120
|
+
nextSelectedEntryKey === state.gitMode.selectedEntryKey
|
|
102
121
|
) {
|
|
103
122
|
return state
|
|
104
123
|
}
|
|
105
124
|
return {
|
|
106
125
|
...state,
|
|
126
|
+
gitMode: { ...state.gitMode, selectedEntryKey: nextSelectedEntryKey },
|
|
107
127
|
gitPanel: {
|
|
108
128
|
...prev,
|
|
109
129
|
ahead: next.ahead,
|
|
@@ -116,9 +136,16 @@ export function reduceGitPanelState(state: AppState, action: AppAction): AppStat
|
|
|
116
136
|
}
|
|
117
137
|
case 'git-refresh-error': {
|
|
118
138
|
const prev = state.gitPanel
|
|
119
|
-
if (
|
|
139
|
+
if (
|
|
140
|
+
prev.error === action.kind &&
|
|
141
|
+
prev.files.length === 0 &&
|
|
142
|
+
state.gitMode.selectedEntryKey === null
|
|
143
|
+
) {
|
|
144
|
+
return state
|
|
145
|
+
}
|
|
120
146
|
return {
|
|
121
147
|
...state,
|
|
148
|
+
gitMode: { ...state.gitMode, pendingDeletePath: null, selectedEntryKey: null },
|
|
122
149
|
gitPanel: { ...prev, error: action.kind, files: [] },
|
|
123
150
|
}
|
|
124
151
|
}
|
|
@@ -133,7 +160,11 @@ export function reduceGitPanelState(state: AppState, action: AppAction): AppStat
|
|
|
133
160
|
) {
|
|
134
161
|
return state
|
|
135
162
|
}
|
|
136
|
-
return {
|
|
163
|
+
return {
|
|
164
|
+
...state,
|
|
165
|
+
gitMode: { ...state.gitMode, pendingDeletePath: null, selectedEntryKey: null },
|
|
166
|
+
gitPanel: emptyGitPanel(),
|
|
167
|
+
}
|
|
137
168
|
}
|
|
138
169
|
default:
|
|
139
170
|
return null
|
|
@@ -5,11 +5,9 @@ import type { AppAction, AppState } from '../types'
|
|
|
5
5
|
import { collectHelpEntries } from '../../input/keymap/help-entries'
|
|
6
6
|
import { getActiveKeymap } from '../../input/keymap/keymap-ref'
|
|
7
7
|
import { getAllAssistantOptions } from '../../pty/command-registry'
|
|
8
|
-
import {
|
|
8
|
+
import { filterThemeIds } from '../../ui/filter-themes'
|
|
9
9
|
import { filterSessions, filterSnippets } from '../selectors'
|
|
10
10
|
|
|
11
|
-
const THEME_COUNT = THEME_IDS.length
|
|
12
|
-
|
|
13
11
|
function emptyModal() {
|
|
14
12
|
return {
|
|
15
13
|
cursorPos: 0,
|
|
@@ -42,14 +40,16 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
42
40
|
}
|
|
43
41
|
case 'open-help-modal': {
|
|
44
42
|
const keymap = getActiveKeymap()
|
|
45
|
-
const
|
|
43
|
+
const scope = action.scope ?? null
|
|
44
|
+
const allEntries = keymap ? collectHelpEntries(keymap) : []
|
|
45
|
+
const scopedEntries = scope ? allEntries.filter((e) => e.mode === scope) : allEntries
|
|
46
46
|
return {
|
|
47
47
|
...state,
|
|
48
|
-
focusMode: 'modal',
|
|
49
48
|
modal: {
|
|
50
49
|
cursorPos: 0,
|
|
51
50
|
editBuffer: null,
|
|
52
|
-
entryCount,
|
|
51
|
+
entryCount: scopedEntries.length,
|
|
52
|
+
scope,
|
|
53
53
|
selectedIndex: 0,
|
|
54
54
|
sessionTargetId: null,
|
|
55
55
|
type: 'help',
|
|
@@ -162,6 +162,7 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
162
162
|
modal: {
|
|
163
163
|
cursorPos: 0,
|
|
164
164
|
editBuffer: null,
|
|
165
|
+
entryCount: 0,
|
|
165
166
|
selectedIndex: 0,
|
|
166
167
|
sessionTargetId: null,
|
|
167
168
|
type: 'theme-picker',
|
|
@@ -225,6 +226,27 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
225
226
|
return state
|
|
226
227
|
}
|
|
227
228
|
const buf = state.modal.editBuffer ?? ''
|
|
229
|
+
return {
|
|
230
|
+
...state,
|
|
231
|
+
modal: { ...state.modal, cursorPos: buf.length, editBuffer: buf },
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
case 'set-theme-entry-count': {
|
|
235
|
+
if (state.modal.type !== 'theme-picker') return state
|
|
236
|
+
const clamped = Math.min(state.modal.selectedIndex, Math.max(0, action.count - 1))
|
|
237
|
+
if (state.modal.entryCount === action.count && clamped === state.modal.selectedIndex) {
|
|
238
|
+
return state
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
...state,
|
|
242
|
+
modal: { ...state.modal, entryCount: action.count, selectedIndex: clamped },
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
case 'begin-theme-filter': {
|
|
246
|
+
if (state.modal.type !== 'theme-picker') {
|
|
247
|
+
return state
|
|
248
|
+
}
|
|
249
|
+
const buf = state.modal.editBuffer ?? ''
|
|
228
250
|
return {
|
|
229
251
|
...state,
|
|
230
252
|
focusMode: 'command-edit',
|
|
@@ -232,8 +254,12 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
232
254
|
}
|
|
233
255
|
}
|
|
234
256
|
case 'close-modal': {
|
|
235
|
-
const
|
|
236
|
-
|
|
257
|
+
const closingType = state.modal.type
|
|
258
|
+
// Help is a pure overlay — it never flipped focusMode, so leave it alone.
|
|
259
|
+
if (closingType === 'help') {
|
|
260
|
+
return { ...state, modal: emptyModal() }
|
|
261
|
+
}
|
|
262
|
+
const nextFocus: AppState['focusMode'] = closingType === 'git-commit' ? 'git' : 'navigation'
|
|
237
263
|
return { ...state, focusMode: nextFocus, modal: emptyModal() }
|
|
238
264
|
}
|
|
239
265
|
case 'move-modal-selection': {
|
|
@@ -268,7 +294,7 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
268
294
|
const filtered = filterSnippets(state.snippets, state.modal.editBuffer)
|
|
269
295
|
optionCount = filtered.length
|
|
270
296
|
} else if (state.modal.type === 'theme-picker') {
|
|
271
|
-
optionCount =
|
|
297
|
+
optionCount = filterThemeIds(state.modal.editBuffer).length
|
|
272
298
|
} else if (state.modal.type === 'update-available') {
|
|
273
299
|
optionCount = 2
|
|
274
300
|
} else {
|
|
@@ -334,6 +360,7 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
334
360
|
const resetIndex =
|
|
335
361
|
state.modal.type === 'session-picker' ||
|
|
336
362
|
state.modal.type === 'snippet-picker' ||
|
|
363
|
+
state.modal.type === 'theme-picker' ||
|
|
337
364
|
state.modal.type === 'help'
|
|
338
365
|
? 0
|
|
339
366
|
: state.modal.selectedIndex
|
|
@@ -401,10 +428,16 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
401
428
|
if (state.modal.type === 'create-session' || state.modal.type === 'snippet-editor') {
|
|
402
429
|
return { ...state, focusMode: 'navigation', modal: emptyModal() }
|
|
403
430
|
}
|
|
431
|
+
if (state.modal.type === 'help') {
|
|
432
|
+
return {
|
|
433
|
+
...state,
|
|
434
|
+
modal: { ...state.modal, cursorPos: 0, editBuffer: null, selectedIndex: 0 },
|
|
435
|
+
}
|
|
436
|
+
}
|
|
404
437
|
if (
|
|
405
438
|
state.modal.type === 'session-picker' ||
|
|
406
439
|
state.modal.type === 'snippet-picker' ||
|
|
407
|
-
state.modal.type === '
|
|
440
|
+
state.modal.type === 'theme-picker'
|
|
408
441
|
) {
|
|
409
442
|
return {
|
|
410
443
|
...state,
|
package/src/state/store.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AppAction,
|
|
3
3
|
AppState,
|
|
4
|
+
GitModeState,
|
|
4
5
|
GitPaneMode,
|
|
5
6
|
GitPanePosition,
|
|
6
7
|
GitPaneState,
|
|
@@ -24,6 +25,7 @@ const DEFAULT_TERMINAL_COLS = 80
|
|
|
24
25
|
const DEFAULT_TERMINAL_ROWS = 24
|
|
25
26
|
|
|
26
27
|
export interface InitialStateOverrides {
|
|
28
|
+
gitMode?: Partial<GitModeState>
|
|
27
29
|
gitPane?: Partial<GitPaneState>
|
|
28
30
|
sessionBarVisible?: boolean
|
|
29
31
|
sessionBarPosition?: SessionBarPosition
|
|
@@ -31,6 +33,8 @@ export interface InitialStateOverrides {
|
|
|
31
33
|
|
|
32
34
|
const DEFAULT_GIT_PANE: GitPaneState = {
|
|
33
35
|
diffCount: { enabled: true },
|
|
36
|
+
diffModeRatio: 0.35,
|
|
37
|
+
fileListMode: 'tree',
|
|
34
38
|
mode: 'embedded',
|
|
35
39
|
path: { enabled: true },
|
|
36
40
|
position: 'bottom',
|
|
@@ -62,7 +66,7 @@ export function createInitialState(
|
|
|
62
66
|
currentSessionId: null,
|
|
63
67
|
customCommands,
|
|
64
68
|
focusMode: showSessionPicker ? 'modal' : 'navigation',
|
|
65
|
-
gitMode: emptyGitMode(),
|
|
69
|
+
gitMode: { ...emptyGitMode(), ...overrides.gitMode },
|
|
66
70
|
gitPane: {
|
|
67
71
|
...DEFAULT_GIT_PANE,
|
|
68
72
|
...overrides.gitPane,
|