@brimveyn/aimux 1.5.0 → 1.6.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 +14 -2
- package/package.json +5 -3
- package/src/app-runtime/side-effects.ts +41 -6
- package/src/app-runtime/use-renderer-bindings.ts +1 -1
- 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/index.tsx +2 -2
- 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 +4 -4
- package/src/state/git-tree.ts +220 -0
- package/src/state/reducers/git-mode-state.ts +232 -35
- package/src/state/reducers/git-panel-state.ts +29 -3
- package/src/state/reducers/modal-state.ts +43 -10
- package/src/state/store.ts +5 -1
- package/src/state/types.ts +41 -5
- package/src/state/workspace-save.ts +2 -0
- package/src/ui/components/create-session-modal.tsx +23 -7
- 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 +84 -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 +170 -0
- package/src/ui/components/diff-renderer/split-view.tsx +207 -0
- package/src/ui/components/diff-renderer/stacked-view.tsx +166 -0
- package/src/ui/components/git-commit-modal.tsx +14 -2
- package/src/ui/components/git-pane-widget.tsx +5 -0
- package/src/ui/components/git-panel.tsx +176 -79
- package/src/ui/components/git-view.tsx +89 -87
- package/src/ui/components/help-modal.tsx +43 -11
- package/src/ui/components/input-field.tsx +2 -2
- package/src/ui/components/list-item.tsx +9 -1
- package/src/ui/components/modal-filter-bar.tsx +1 -1
- package/src/ui/components/modal-keybinds-overlay.tsx +2 -2
- package/src/ui/components/modal-shell.tsx +3 -3
- package/src/ui/components/new-tab-modal.tsx +15 -3
- package/src/ui/components/pending-chord-overlay.tsx +3 -3
- package/src/ui/components/session-bar.tsx +10 -5
- package/src/ui/components/session-picker-modal.tsx +26 -9
- package/src/ui/components/sidebar.tsx +22 -10
- package/src/ui/components/snippet-editor-modal.tsx +16 -2
- package/src/ui/components/snippet-picker-modal.tsx +11 -3
- package/src/ui/components/split-layout.tsx +1 -1
- package/src/ui/components/status-bar.tsx +12 -9
- package/src/ui/components/surface.tsx +5 -5
- package/src/ui/components/tab-item.tsx +22 -16
- package/src/ui/components/terminal-pane.tsx +25 -15
- package/src/ui/components/theme-picker-modal.tsx +111 -16
- package/src/ui/components/update-available-modal.tsx +11 -1
- package/src/ui/filter-themes.ts +10 -0
- package/src/ui/house-themes.ts +313 -0
- package/src/ui/keymap-context.ts +10 -2
- package/src/ui/root.tsx +26 -6
- package/src/ui/shiki.ts +49 -0
- package/src/ui/status-bar-model.ts +19 -1
- package/src/ui/theme.ts +22 -10
- package/src/ui/themes.generated.ts +59794 -0
- package/src/ui/themes.ts +68 -274
- package/src/ui/syntax.ts +0 -102
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
// Vendored from @pierre/diffs v1.1.15 — parser only. See AIMUX-14.
|
|
2
|
+
/* eslint-disable eqeqeq, no-console, typescript/strict-boolean-expressions, no-else-return, unicorn/no-array-for-each */
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ChangeContent,
|
|
6
|
+
ContextContent,
|
|
7
|
+
FileContents,
|
|
8
|
+
FileDiffMetadata,
|
|
9
|
+
HunkContent,
|
|
10
|
+
ParsedPatch,
|
|
11
|
+
} from './types'
|
|
12
|
+
|
|
13
|
+
import { cleanLastNewline } from './clean-last-newline'
|
|
14
|
+
import {
|
|
15
|
+
ALTERNATE_FILE_NAMES_GIT,
|
|
16
|
+
COMMIT_METADATA_SPLIT,
|
|
17
|
+
FILE_CONTEXT_BLOB,
|
|
18
|
+
FILENAME_HEADER_REGEX,
|
|
19
|
+
FILENAME_HEADER_REGEX_GIT,
|
|
20
|
+
GIT_DIFF_FILE_BREAK_REGEX,
|
|
21
|
+
HUNK_HEADER,
|
|
22
|
+
INDEX_LINE_METADATA,
|
|
23
|
+
SPLIT_WITH_NEWLINES,
|
|
24
|
+
UNIFIED_DIFF_FILE_BREAK_REGEX,
|
|
25
|
+
} from './constants'
|
|
26
|
+
import { parseLineType } from './parse-line-type'
|
|
27
|
+
|
|
28
|
+
interface ProcessFileOptions {
|
|
29
|
+
cacheKey?: string
|
|
30
|
+
isGitDiff?: boolean
|
|
31
|
+
oldFile?: FileContents
|
|
32
|
+
newFile?: FileContents
|
|
33
|
+
throwOnError?: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createContentGroup(
|
|
37
|
+
type: 'change' | 'context',
|
|
38
|
+
deletionLineIndex: number,
|
|
39
|
+
additionLineIndex: number
|
|
40
|
+
): HunkContent {
|
|
41
|
+
if (type === 'change') {
|
|
42
|
+
return { additionLineIndex, additions: 0, deletionLineIndex, deletions: 0, type: 'change' }
|
|
43
|
+
}
|
|
44
|
+
return { additionLineIndex, deletionLineIndex, lines: 0, type: 'context' }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function processFile(
|
|
48
|
+
fileDiffString: string,
|
|
49
|
+
{
|
|
50
|
+
cacheKey,
|
|
51
|
+
isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(fileDiffString),
|
|
52
|
+
newFile,
|
|
53
|
+
oldFile,
|
|
54
|
+
throwOnError = false,
|
|
55
|
+
}: ProcessFileOptions = {}
|
|
56
|
+
): FileDiffMetadata | undefined {
|
|
57
|
+
let lastHunkEnd = 0
|
|
58
|
+
const hunks = fileDiffString.split(FILE_CONTEXT_BLOB)
|
|
59
|
+
let currentFile: FileDiffMetadata | undefined
|
|
60
|
+
const isPartial = oldFile == null || newFile == null
|
|
61
|
+
let deletionLineIndex = 0
|
|
62
|
+
let additionLineIndex = 0
|
|
63
|
+
for (const hunk of hunks) {
|
|
64
|
+
const lines = hunk.split(SPLIT_WITH_NEWLINES)
|
|
65
|
+
const firstLine = lines.shift()
|
|
66
|
+
if (firstLine == null) {
|
|
67
|
+
if (throwOnError) throw Error('parsePatchContent: invalid hunk')
|
|
68
|
+
else console.error('parsePatchContent: invalid hunk', hunk)
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
const fileHeaderMatch = firstLine.match(HUNK_HEADER)
|
|
72
|
+
let additionLines = 0
|
|
73
|
+
let deletionLines = 0
|
|
74
|
+
if (fileHeaderMatch == null || currentFile == null) {
|
|
75
|
+
if (currentFile != null) {
|
|
76
|
+
if (throwOnError) throw Error('parsePatchContent: Invalid hunk')
|
|
77
|
+
else console.error('parsePatchContent: Invalid hunk', hunk)
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
currentFile = {
|
|
81
|
+
additionLines:
|
|
82
|
+
!isPartial && oldFile != null && newFile != null
|
|
83
|
+
? newFile.contents.split(SPLIT_WITH_NEWLINES)
|
|
84
|
+
: [],
|
|
85
|
+
cacheKey,
|
|
86
|
+
deletionLines:
|
|
87
|
+
!isPartial && oldFile != null && newFile != null
|
|
88
|
+
? oldFile.contents.split(SPLIT_WITH_NEWLINES)
|
|
89
|
+
: [],
|
|
90
|
+
hunks: [],
|
|
91
|
+
isPartial,
|
|
92
|
+
name: '',
|
|
93
|
+
splitLineCount: 0,
|
|
94
|
+
type: 'change',
|
|
95
|
+
unifiedLineCount: 0,
|
|
96
|
+
}
|
|
97
|
+
if (currentFile.additionLines.length === 1 && newFile?.contents === '') {
|
|
98
|
+
currentFile.additionLines.length = 0
|
|
99
|
+
}
|
|
100
|
+
if (currentFile.deletionLines.length === 1 && oldFile?.contents === '') {
|
|
101
|
+
currentFile.deletionLines.length = 0
|
|
102
|
+
}
|
|
103
|
+
lines.unshift(firstLine)
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
const filenameMatch = line.match(
|
|
106
|
+
isGitDiff ? FILENAME_HEADER_REGEX_GIT : FILENAME_HEADER_REGEX
|
|
107
|
+
)
|
|
108
|
+
if (line.startsWith('diff --git')) {
|
|
109
|
+
const match = line.trim().match(ALTERNATE_FILE_NAMES_GIT)
|
|
110
|
+
if (match) {
|
|
111
|
+
const prevName = match[1] ?? match[2]
|
|
112
|
+
const name = match[3] ?? match[4]
|
|
113
|
+
if (name != null) {
|
|
114
|
+
currentFile.name = name.trim()
|
|
115
|
+
if (prevName != null && prevName !== name) currentFile.prevName = prevName.trim()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else if (filenameMatch != null) {
|
|
119
|
+
const [, type, fileName] = filenameMatch
|
|
120
|
+
if (fileName != null && fileName !== '/dev/null') {
|
|
121
|
+
if (type === '---') {
|
|
122
|
+
currentFile.prevName = fileName.trim()
|
|
123
|
+
currentFile.name = fileName.trim()
|
|
124
|
+
} else if (type === '+++') {
|
|
125
|
+
currentFile.name = fileName.trim()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else if (isGitDiff) {
|
|
129
|
+
if (line.startsWith('new mode ')) currentFile.mode = line.replace('new mode', '').trim()
|
|
130
|
+
if (line.startsWith('old mode ')) {
|
|
131
|
+
currentFile.prevMode = line.replace('old mode', '').trim()
|
|
132
|
+
}
|
|
133
|
+
if (line.startsWith('new file mode')) {
|
|
134
|
+
currentFile.type = 'new'
|
|
135
|
+
currentFile.mode = line.replace('new file mode', '').trim()
|
|
136
|
+
}
|
|
137
|
+
if (line.startsWith('deleted file mode')) {
|
|
138
|
+
currentFile.type = 'deleted'
|
|
139
|
+
currentFile.mode = line.replace('deleted file mode', '').trim()
|
|
140
|
+
}
|
|
141
|
+
if (line.startsWith('similarity index')) {
|
|
142
|
+
if (line.startsWith('similarity index 100%')) currentFile.type = 'rename-pure'
|
|
143
|
+
else currentFile.type = 'rename-changed'
|
|
144
|
+
}
|
|
145
|
+
if (line.startsWith('index ')) {
|
|
146
|
+
const [, prevObjectId, newObjectId, mode] = line.trim().match(INDEX_LINE_METADATA) ?? []
|
|
147
|
+
if (prevObjectId != null) currentFile.prevObjectId = prevObjectId
|
|
148
|
+
if (newObjectId != null) currentFile.newObjectId = newObjectId
|
|
149
|
+
if (mode != null) currentFile.mode = mode
|
|
150
|
+
}
|
|
151
|
+
if (line.startsWith('rename from ')) {
|
|
152
|
+
currentFile.prevName = line.replace('rename from ', '').trim()
|
|
153
|
+
}
|
|
154
|
+
if (line.startsWith('rename to ')) {
|
|
155
|
+
currentFile.name = line.replace('rename to ', '').trim()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
let currentContent: HunkContent | undefined
|
|
162
|
+
let lastLineType: 'addition' | 'deletion' | 'context' | undefined
|
|
163
|
+
while (
|
|
164
|
+
lines.length > 0 &&
|
|
165
|
+
(lines[lines.length - 1] === '\n' ||
|
|
166
|
+
lines[lines.length - 1] === '\r' ||
|
|
167
|
+
lines[lines.length - 1] === '\r\n' ||
|
|
168
|
+
lines[lines.length - 1] === '')
|
|
169
|
+
) {
|
|
170
|
+
lines.pop()
|
|
171
|
+
}
|
|
172
|
+
const additionStart = parseInt(fileHeaderMatch[3] ?? '')
|
|
173
|
+
const deletionStart = parseInt(fileHeaderMatch[1] ?? '')
|
|
174
|
+
deletionLineIndex = isPartial ? deletionLineIndex : deletionStart - 1
|
|
175
|
+
additionLineIndex = isPartial ? additionLineIndex : additionStart - 1
|
|
176
|
+
const hunkData = {
|
|
177
|
+
additionCount: parseInt(fileHeaderMatch[4] ?? '1'),
|
|
178
|
+
additionLineIndex,
|
|
179
|
+
additionLines,
|
|
180
|
+
additionStart,
|
|
181
|
+
collapsedBefore: 0,
|
|
182
|
+
deletionCount: parseInt(fileHeaderMatch[2] ?? '1'),
|
|
183
|
+
deletionLineIndex,
|
|
184
|
+
deletionLines,
|
|
185
|
+
deletionStart,
|
|
186
|
+
hunkContent: [] as HunkContent[],
|
|
187
|
+
hunkContext: fileHeaderMatch[5],
|
|
188
|
+
hunkSpecs: firstLine,
|
|
189
|
+
noEOFCRAdditions: false,
|
|
190
|
+
noEOFCRDeletions: false,
|
|
191
|
+
splitLineCount: 0,
|
|
192
|
+
splitLineStart: 0,
|
|
193
|
+
unifiedLineCount: 0,
|
|
194
|
+
unifiedLineStart: 0,
|
|
195
|
+
}
|
|
196
|
+
if (
|
|
197
|
+
isNaN(hunkData.additionCount) ||
|
|
198
|
+
isNaN(hunkData.deletionCount) ||
|
|
199
|
+
isNaN(hunkData.additionStart) ||
|
|
200
|
+
isNaN(hunkData.deletionStart)
|
|
201
|
+
) {
|
|
202
|
+
if (throwOnError) throw Error('parsePatchContent: invalid hunk metadata')
|
|
203
|
+
else console.error('parsePatchContent: invalid hunk metadata', hunkData)
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
for (const rawLine of lines) {
|
|
207
|
+
const parsedLine = parseLineType(rawLine)
|
|
208
|
+
if (parsedLine == null) {
|
|
209
|
+
console.error('processFile: invalid rawLine:', rawLine)
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
const { line, type } = parsedLine
|
|
213
|
+
if (type === 'addition') {
|
|
214
|
+
if (currentContent == null || currentContent.type !== 'change') {
|
|
215
|
+
currentContent = createContentGroup('change', deletionLineIndex, additionLineIndex)
|
|
216
|
+
hunkData.hunkContent.push(currentContent)
|
|
217
|
+
}
|
|
218
|
+
additionLineIndex++
|
|
219
|
+
if (isPartial) currentFile.additionLines.push(line)
|
|
220
|
+
;(currentContent as ChangeContent).additions++
|
|
221
|
+
additionLines++
|
|
222
|
+
lastLineType = 'addition'
|
|
223
|
+
} else if (type === 'deletion') {
|
|
224
|
+
if (currentContent == null || currentContent.type !== 'change') {
|
|
225
|
+
currentContent = createContentGroup('change', deletionLineIndex, additionLineIndex)
|
|
226
|
+
hunkData.hunkContent.push(currentContent)
|
|
227
|
+
}
|
|
228
|
+
deletionLineIndex++
|
|
229
|
+
if (isPartial) currentFile.deletionLines.push(line)
|
|
230
|
+
;(currentContent as ChangeContent).deletions++
|
|
231
|
+
deletionLines++
|
|
232
|
+
lastLineType = 'deletion'
|
|
233
|
+
} else if (type === 'context') {
|
|
234
|
+
if (currentContent == null || currentContent.type !== 'context') {
|
|
235
|
+
currentContent = createContentGroup('context', deletionLineIndex, additionLineIndex)
|
|
236
|
+
hunkData.hunkContent.push(currentContent)
|
|
237
|
+
}
|
|
238
|
+
additionLineIndex++
|
|
239
|
+
deletionLineIndex++
|
|
240
|
+
if (isPartial) {
|
|
241
|
+
currentFile.deletionLines.push(line)
|
|
242
|
+
currentFile.additionLines.push(line)
|
|
243
|
+
}
|
|
244
|
+
;(currentContent as ContextContent).lines++
|
|
245
|
+
lastLineType = 'context'
|
|
246
|
+
} else if (type === 'metadata' && currentContent != null) {
|
|
247
|
+
if (currentContent.type === 'context') {
|
|
248
|
+
hunkData.noEOFCRAdditions = true
|
|
249
|
+
hunkData.noEOFCRDeletions = true
|
|
250
|
+
} else if (lastLineType === 'deletion') hunkData.noEOFCRDeletions = true
|
|
251
|
+
else if (lastLineType === 'addition') hunkData.noEOFCRAdditions = true
|
|
252
|
+
if (isPartial && (lastLineType === 'addition' || lastLineType === 'context')) {
|
|
253
|
+
const lastIndex = currentFile.additionLines.length - 1
|
|
254
|
+
const last = currentFile.additionLines[lastIndex]
|
|
255
|
+
if (lastIndex >= 0 && last != null) {
|
|
256
|
+
currentFile.additionLines[lastIndex] = cleanLastNewline(last)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (isPartial && (lastLineType === 'deletion' || lastLineType === 'context')) {
|
|
260
|
+
const lastIndex = currentFile.deletionLines.length - 1
|
|
261
|
+
const last = currentFile.deletionLines[lastIndex]
|
|
262
|
+
if (lastIndex >= 0 && last != null) {
|
|
263
|
+
currentFile.deletionLines[lastIndex] = cleanLastNewline(last)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
hunkData.additionLines = additionLines
|
|
269
|
+
hunkData.deletionLines = deletionLines
|
|
270
|
+
hunkData.collapsedBefore = Math.max(hunkData.additionStart - 1 - lastHunkEnd, 0)
|
|
271
|
+
currentFile.hunks.push(hunkData)
|
|
272
|
+
lastHunkEnd = hunkData.additionStart + hunkData.additionCount - 1
|
|
273
|
+
for (const content of hunkData.hunkContent) {
|
|
274
|
+
if (content.type === 'context') {
|
|
275
|
+
hunkData.splitLineCount += content.lines
|
|
276
|
+
hunkData.unifiedLineCount += content.lines
|
|
277
|
+
} else {
|
|
278
|
+
hunkData.splitLineCount += Math.max(content.additions, content.deletions)
|
|
279
|
+
hunkData.unifiedLineCount += content.deletions + content.additions
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
hunkData.splitLineStart = currentFile.splitLineCount + hunkData.collapsedBefore
|
|
283
|
+
hunkData.unifiedLineStart = currentFile.unifiedLineCount + hunkData.collapsedBefore
|
|
284
|
+
currentFile.splitLineCount += hunkData.collapsedBefore + hunkData.splitLineCount
|
|
285
|
+
currentFile.unifiedLineCount += hunkData.collapsedBefore + hunkData.unifiedLineCount
|
|
286
|
+
}
|
|
287
|
+
if (currentFile == null) return
|
|
288
|
+
const lastHunk = currentFile.hunks[currentFile.hunks.length - 1]
|
|
289
|
+
if (
|
|
290
|
+
lastHunk != null &&
|
|
291
|
+
!isPartial &&
|
|
292
|
+
currentFile.additionLines.length > 0 &&
|
|
293
|
+
currentFile.deletionLines.length > 0
|
|
294
|
+
) {
|
|
295
|
+
const lastHunkEnd$1 = lastHunk.additionStart + lastHunk.additionCount - 1
|
|
296
|
+
const totalFileLines = currentFile.additionLines.length
|
|
297
|
+
const collapsedAfter = Math.max(totalFileLines - lastHunkEnd$1, 0)
|
|
298
|
+
currentFile.splitLineCount += collapsedAfter
|
|
299
|
+
currentFile.unifiedLineCount += collapsedAfter
|
|
300
|
+
}
|
|
301
|
+
if (!isGitDiff) {
|
|
302
|
+
if (currentFile.prevName != null && currentFile.name !== currentFile.prevName) {
|
|
303
|
+
if (currentFile.hunks.length > 0) currentFile.type = 'rename-changed'
|
|
304
|
+
else currentFile.type = 'rename-pure'
|
|
305
|
+
} else if (newFile != null && newFile.contents === '') currentFile.type = 'deleted'
|
|
306
|
+
else if (oldFile != null && oldFile.contents === '') currentFile.type = 'new'
|
|
307
|
+
}
|
|
308
|
+
if (currentFile.type !== 'rename-pure' && currentFile.type !== 'rename-changed') {
|
|
309
|
+
currentFile.prevName = undefined
|
|
310
|
+
}
|
|
311
|
+
return currentFile
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function processPatch(
|
|
315
|
+
data: string,
|
|
316
|
+
cacheKeyPrefix?: string,
|
|
317
|
+
throwOnError = false
|
|
318
|
+
): ParsedPatch {
|
|
319
|
+
const isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(data)
|
|
320
|
+
const rawFiles = data.split(isGitDiff ? GIT_DIFF_FILE_BREAK_REGEX : UNIFIED_DIFF_FILE_BREAK_REGEX)
|
|
321
|
+
let patchMetadata: string | undefined
|
|
322
|
+
const files: FileDiffMetadata[] = []
|
|
323
|
+
for (const fileOrPatchMetadata of rawFiles) {
|
|
324
|
+
if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(fileOrPatchMetadata)) {
|
|
325
|
+
if (patchMetadata == null) patchMetadata = fileOrPatchMetadata
|
|
326
|
+
else if (throwOnError) throw Error('parsePatchContent: unknown file blob')
|
|
327
|
+
else console.error('parsePatchContent: unknown file blob:', fileOrPatchMetadata)
|
|
328
|
+
continue
|
|
329
|
+
} else if (!isGitDiff && !UNIFIED_DIFF_FILE_BREAK_REGEX.test(fileOrPatchMetadata)) {
|
|
330
|
+
if (patchMetadata == null) patchMetadata = fileOrPatchMetadata
|
|
331
|
+
else if (throwOnError) throw Error('parsePatchContent: unknown file blob')
|
|
332
|
+
else console.error('parsePatchContent: unknown file blob:', fileOrPatchMetadata)
|
|
333
|
+
continue
|
|
334
|
+
}
|
|
335
|
+
const currentFile = processFile(fileOrPatchMetadata, {
|
|
336
|
+
cacheKey: cacheKeyPrefix != null ? `${cacheKeyPrefix}-${files.length}` : undefined,
|
|
337
|
+
isGitDiff,
|
|
338
|
+
throwOnError,
|
|
339
|
+
})
|
|
340
|
+
if (currentFile != null) files.push(currentFile)
|
|
341
|
+
}
|
|
342
|
+
return { files, patchMetadata }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Parses a patch file string into an array of parsed patches.
|
|
347
|
+
*/
|
|
348
|
+
export function parsePatchFiles(
|
|
349
|
+
data: string,
|
|
350
|
+
cacheKeyPrefix?: string,
|
|
351
|
+
throwOnError = false
|
|
352
|
+
): ParsedPatch[] {
|
|
353
|
+
const patches: ParsedPatch[] = []
|
|
354
|
+
for (const patch of data.split(COMMIT_METADATA_SPLIT)) {
|
|
355
|
+
try {
|
|
356
|
+
patches.push(
|
|
357
|
+
processPatch(
|
|
358
|
+
patch,
|
|
359
|
+
cacheKeyPrefix != null ? `${cacheKeyPrefix}-${patches.length}` : undefined,
|
|
360
|
+
throwOnError
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (throwOnError) throw error
|
|
365
|
+
else console.error(error)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return patches
|
|
369
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Vendored from @pierre/diffs v1.1.15 — parser only. See AIMUX-14.
|
|
2
|
+
// Parser-only subset of the library's types. Renderer/shiki/hast types removed.
|
|
3
|
+
|
|
4
|
+
export type ChangeTypes = 'change' | 'rename-pure' | 'rename-changed' | 'new' | 'deleted'
|
|
5
|
+
|
|
6
|
+
export type HunkLineType = 'context' | 'expanded' | 'addition' | 'deletion' | 'metadata'
|
|
7
|
+
|
|
8
|
+
export interface ContextContent {
|
|
9
|
+
type: 'context'
|
|
10
|
+
lines: number
|
|
11
|
+
additionLineIndex: number
|
|
12
|
+
deletionLineIndex: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ChangeContent {
|
|
16
|
+
type: 'change'
|
|
17
|
+
deletions: number
|
|
18
|
+
deletionLineIndex: number
|
|
19
|
+
additions: number
|
|
20
|
+
additionLineIndex: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type HunkContent = ContextContent | ChangeContent
|
|
24
|
+
|
|
25
|
+
export interface Hunk {
|
|
26
|
+
collapsedBefore: number
|
|
27
|
+
additionStart: number
|
|
28
|
+
additionCount: number
|
|
29
|
+
additionLines: number
|
|
30
|
+
additionLineIndex: number
|
|
31
|
+
deletionStart: number
|
|
32
|
+
deletionCount: number
|
|
33
|
+
deletionLines: number
|
|
34
|
+
deletionLineIndex: number
|
|
35
|
+
hunkContent: HunkContent[]
|
|
36
|
+
hunkContext?: string
|
|
37
|
+
hunkSpecs?: string
|
|
38
|
+
splitLineStart: number
|
|
39
|
+
splitLineCount: number
|
|
40
|
+
unifiedLineStart: number
|
|
41
|
+
unifiedLineCount: number
|
|
42
|
+
noEOFCRDeletions: boolean
|
|
43
|
+
noEOFCRAdditions: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface FileDiffMetadata {
|
|
47
|
+
name: string
|
|
48
|
+
prevName?: string
|
|
49
|
+
lang?: string
|
|
50
|
+
newObjectId?: string
|
|
51
|
+
prevObjectId?: string
|
|
52
|
+
mode?: string
|
|
53
|
+
prevMode?: string
|
|
54
|
+
type: ChangeTypes
|
|
55
|
+
hunks: Hunk[]
|
|
56
|
+
splitLineCount: number
|
|
57
|
+
unifiedLineCount: number
|
|
58
|
+
isPartial: boolean
|
|
59
|
+
deletionLines: string[]
|
|
60
|
+
additionLines: string[]
|
|
61
|
+
cacheKey?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ParsedPatch {
|
|
65
|
+
patchMetadata?: string
|
|
66
|
+
files: FileDiffMetadata[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface FileContents {
|
|
70
|
+
name: string
|
|
71
|
+
contents: string
|
|
72
|
+
lang?: string
|
|
73
|
+
header?: string
|
|
74
|
+
cacheKey?: string
|
|
75
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -58,9 +58,9 @@ if (command === '--help' || command === '-h') {
|
|
|
58
58
|
|
|
59
59
|
const renderer = await createCliRenderer({
|
|
60
60
|
autoFocus: true,
|
|
61
|
+
consoleMode: 'disabled',
|
|
61
62
|
exitOnCtrlC: false,
|
|
62
|
-
|
|
63
|
-
useConsole: false,
|
|
63
|
+
screenMode: 'alternate-screen',
|
|
64
64
|
useMouse: true,
|
|
65
65
|
})
|
|
66
66
|
|
|
@@ -19,6 +19,7 @@ const COMMAND_EDIT_MODE_IDS: Partial<Record<SupportedModalType, ModeId>> = {
|
|
|
19
19
|
'session-picker': 'modal.session-picker.filtering',
|
|
20
20
|
'snippet-editor': 'modal.snippet-editor',
|
|
21
21
|
'snippet-picker': 'modal.snippet-picker.filtering',
|
|
22
|
+
'theme-picker': 'modal.theme-picker.filtering',
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const MODAL_MODE_IDS: Partial<Record<SupportedModalType, ModeId>> = {
|
|
@@ -32,6 +33,13 @@ const MODAL_MODE_IDS: Partial<Record<SupportedModalType, ModeId>> = {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export function deriveModeId(state: AppState): ModeId {
|
|
36
|
+
// Help renders as an overlay on top of git/navigation without flipping
|
|
37
|
+
// focusMode, so it needs modal-first dispatch. Filter sub-mode is tracked
|
|
38
|
+
// by editBuffer presence, not focusMode.
|
|
39
|
+
if (state.modal.type === 'help') {
|
|
40
|
+
return state.modal.editBuffer !== null ? 'modal.help.filtering' : 'modal.help'
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
const directMode = DIRECT_FOCUS_MODE_IDS[state.focusMode]
|
|
36
44
|
if (directMode) {
|
|
37
45
|
return directMode
|
|
@@ -21,7 +21,8 @@ const TRANSITIONS: Record<ModeId, readonly ModeId[]> = {
|
|
|
21
21
|
'modal.snippet-picker': ['navigation', 'modal.snippet-picker.filtering', 'modal.snippet-editor'],
|
|
22
22
|
'modal.snippet-picker.filtering': ['modal.snippet-picker'],
|
|
23
23
|
'modal.split-picker': ['navigation', 'terminal-input'],
|
|
24
|
-
'modal.theme-picker': ['navigation'],
|
|
24
|
+
'modal.theme-picker': ['navigation', 'modal.theme-picker.filtering'],
|
|
25
|
+
'modal.theme-picker.filtering': ['modal.theme-picker'],
|
|
25
26
|
'modal.update-available': ['navigation'],
|
|
26
27
|
'navigation': [
|
|
27
28
|
'terminal-input',
|
package/src/input/modes/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { KeyEvent } from '@opentui/core'
|
|
2
2
|
|
|
3
|
-
import type { AppAction, AppState, TabSession } from '../../state/types'
|
|
3
|
+
import type { AppAction, AppState, GitFileListMode, TabSession } from '../../state/types'
|
|
4
4
|
|
|
5
5
|
export type ModeId =
|
|
6
6
|
| 'navigation'
|
|
@@ -17,6 +17,7 @@ export type ModeId =
|
|
|
17
17
|
| 'modal.snippet-picker.filtering'
|
|
18
18
|
| 'modal.snippet-editor'
|
|
19
19
|
| 'modal.theme-picker'
|
|
20
|
+
| 'modal.theme-picker.filtering'
|
|
20
21
|
| 'modal.help'
|
|
21
22
|
| 'modal.help.filtering'
|
|
22
23
|
| 'modal.split-picker'
|
|
@@ -46,6 +47,8 @@ export type SideEffect =
|
|
|
46
47
|
| { type: 'split-pane'; direction: import('../../state/layout-tree').SplitDirection }
|
|
47
48
|
| { type: 'confirm-split' }
|
|
48
49
|
| { type: 'scroll-git-diff'; delta: number }
|
|
50
|
+
| { type: 'persist-git-diff-mode-ratio'; ratio: number }
|
|
51
|
+
| { type: 'persist-git-file-list-mode'; mode: GitFileListMode }
|
|
49
52
|
| { type: 'git-stage'; path: string }
|
|
50
53
|
| { type: 'git-unstage'; path: string }
|
|
51
54
|
| { type: 'git-restore'; path: string }
|
|
@@ -113,15 +113,15 @@ function buildLine(
|
|
|
113
113
|
let bg = getColorHex(current.getBgColor(), bgMode)
|
|
114
114
|
|
|
115
115
|
if (current.isInverse()) {
|
|
116
|
-
const resolvedFg = fg ?? theme.
|
|
117
|
-
const resolvedBg = bg ?? theme.background
|
|
116
|
+
const resolvedFg = fg ?? theme.colors['editor.foreground']
|
|
117
|
+
const resolvedBg = bg ?? theme.colors['editor.background']
|
|
118
118
|
;[fg, bg] = [resolvedBg, resolvedFg]
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
const isCursorCell = cursorVisible && cursorColumn === column
|
|
122
122
|
if (isCursorCell) {
|
|
123
|
-
const resolvedFg = fg ?? theme.
|
|
124
|
-
const resolvedBg = bg ?? theme.background
|
|
123
|
+
const resolvedFg = fg ?? theme.colors['editor.foreground']
|
|
124
|
+
const resolvedBg = bg ?? theme.colors['editor.background']
|
|
125
125
|
;[fg, bg] = [resolvedBg, resolvedFg]
|
|
126
126
|
}
|
|
127
127
|
|