@brimveyn/aimux 1.6.0 → 1.6.2
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/package.json +2 -2
- package/src/app-runtime/side-effects.ts +19 -0
- package/src/app.tsx +4 -0
- package/src/config.ts +19 -1
- package/src/git/diff-hash.ts +10 -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/types.ts +1 -0
- package/src/pty/terminal-snapshot.ts +5 -5
- package/src/state/git-tree.ts +43 -17
- package/src/state/reducers/diff-cache.ts +64 -0
- package/src/state/reducers/git-mode-state.ts +135 -34
- package/src/state/reducers/git-panel-state.ts +39 -3
- package/src/state/store.ts +2 -0
- package/src/state/types.ts +63 -2
- package/src/ui/components/create-session-modal.tsx +5 -4
- package/src/ui/components/diff-renderer/fold-strip.tsx +3 -1
- package/src/ui/components/diff-renderer/pierre-diff.tsx +11 -32
- package/src/ui/components/diff-renderer/prepare-diff.ts +66 -0
- package/src/ui/components/diff-renderer/split-view.tsx +35 -6
- package/src/ui/components/diff-renderer/stacked-view.tsx +32 -5
- package/src/ui/components/diff-renderer/use-diff-prefetch.ts +191 -0
- package/src/ui/components/diff-renderer/use-diff-preparation.ts +106 -0
- package/src/ui/components/git-commit-modal.tsx +2 -1
- package/src/ui/components/git-pane-widget.tsx +3 -1
- package/src/ui/components/git-panel.tsx +92 -50
- package/src/ui/components/git-view.tsx +34 -5
- package/src/ui/components/help-modal.tsx +2 -1
- package/src/ui/components/input-field.tsx +2 -1
- package/src/ui/components/list-item.tsx +2 -1
- package/src/ui/components/modal-filter-bar.tsx +2 -1
- package/src/ui/components/modal-keybinds-overlay.tsx +2 -1
- package/src/ui/components/modal-shell.tsx +2 -1
- package/src/ui/components/new-tab-modal.tsx +2 -1
- package/src/ui/components/pending-chord-overlay.tsx +2 -1
- package/src/ui/components/session-bar.tsx +3 -1
- package/src/ui/components/session-picker-modal.tsx +2 -1
- package/src/ui/components/sidebar.tsx +8 -5
- package/src/ui/components/snippet-editor-modal.tsx +2 -1
- package/src/ui/components/snippet-picker-modal.tsx +2 -1
- package/src/ui/components/split-layout.tsx +2 -1
- package/src/ui/components/status-bar.tsx +8 -6
- package/src/ui/components/surface.tsx +6 -6
- package/src/ui/components/tab-item.tsx +14 -9
- package/src/ui/components/terminal-pane.tsx +7 -5
- package/src/ui/components/theme-picker-modal.tsx +2 -1
- package/src/ui/components/update-available-modal.tsx +2 -1
- package/src/ui/filter-themes.ts +7 -2
- package/src/ui/root.tsx +3 -1
- package/src/ui/status-bar-model.ts +13 -3
- package/src/ui/theme-store.ts +36 -0
- package/src/ui/theme.ts +4 -29
- package/src/ui/themes.ts +15 -63
- package/src/ui/house-themes.ts +0 -313
- package/src/ui/themes.generated.ts +0 -59794
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
15
15
|
import { buildGitTreeRows, type GitTreeFileRow, type GitTreeFolderRow } from '../../state/git-tree'
|
|
16
|
-
import {
|
|
16
|
+
import { getCurrentTheme, useTheme } from '../theme'
|
|
17
17
|
|
|
18
18
|
interface GitPanelProps {
|
|
19
19
|
collapsedFolders?: Record<string, true>
|
|
@@ -23,22 +23,41 @@ interface GitPanelProps {
|
|
|
23
23
|
selectedEntryKey?: string | null
|
|
24
24
|
pathConfig?: GitPanePathConfig
|
|
25
25
|
diffCountConfig?: GitPaneDiffCountConfig
|
|
26
|
+
headOffset?: number
|
|
27
|
+
compact?: boolean
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
function sectionTitle(section: GitFileSection, headOffset: number): string {
|
|
31
|
+
switch (section) {
|
|
32
|
+
case 'historical':
|
|
33
|
+
return `HEAD~${headOffset}`
|
|
34
|
+
case 'staged':
|
|
35
|
+
return 'Staged Changes'
|
|
36
|
+
case 'unstaged':
|
|
37
|
+
return 'Changes'
|
|
38
|
+
case 'untracked':
|
|
39
|
+
return 'Untracked'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const BASE_SECTION_ORDER: GitFileSection[] = ['staged', 'unstaged', 'untracked']
|
|
44
|
+
const HISTORICAL_SECTION_ORDER: GitFileSection[] = ['historical', 'untracked']
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
function statusColor(status: GitFileEntry['status']): string {
|
|
47
|
+
const t = getCurrentTheme()
|
|
48
|
+
switch (status) {
|
|
49
|
+
case '?':
|
|
50
|
+
case 'A':
|
|
51
|
+
return t.colors['gitDecoration.addedResourceForeground']
|
|
52
|
+
case 'C':
|
|
53
|
+
case 'R':
|
|
54
|
+
return t.colors['textLink.foreground']
|
|
55
|
+
case 'D':
|
|
56
|
+
case 'U':
|
|
57
|
+
return t.colors['editorError.foreground']
|
|
58
|
+
case 'M':
|
|
59
|
+
return t.colors['editorWarning.foreground']
|
|
60
|
+
}
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
function displayStatus(file: GitFileEntry): string {
|
|
@@ -83,8 +102,10 @@ function renderFileLabel(
|
|
|
83
102
|
if (!file.renamedFrom) {
|
|
84
103
|
return (
|
|
85
104
|
<text wrapMode="none">
|
|
86
|
-
<span fg={
|
|
87
|
-
{showDir ?
|
|
105
|
+
<span fg={getCurrentTheme().colors['editor.foreground']}>{basename}</span>
|
|
106
|
+
{showDir ? (
|
|
107
|
+
<span fg={getCurrentTheme().colors['descriptionForeground']}> {dir}</span>
|
|
108
|
+
) : null}
|
|
88
109
|
</text>
|
|
89
110
|
)
|
|
90
111
|
}
|
|
@@ -93,13 +114,13 @@ function renderFileLabel(
|
|
|
93
114
|
const renamedDir = stripTrailingSlash(renamed.prefix)
|
|
94
115
|
return (
|
|
95
116
|
<text wrapMode="none">
|
|
96
|
-
<span fg={
|
|
117
|
+
<span fg={getCurrentTheme().colors['descriptionForeground']}>{renamed.basename}</span>
|
|
97
118
|
{showDir && renamedDir ? (
|
|
98
|
-
<span fg={
|
|
119
|
+
<span fg={getCurrentTheme().colors['descriptionForeground']}> {renamedDir}</span>
|
|
99
120
|
) : null}
|
|
100
|
-
<span fg={
|
|
101
|
-
<span fg={
|
|
102
|
-
{showDir ? <span fg={
|
|
121
|
+
<span fg={getCurrentTheme().colors['descriptionForeground']}> → </span>
|
|
122
|
+
<span fg={getCurrentTheme().colors['editor.foreground']}>{basename}</span>
|
|
123
|
+
{showDir ? <span fg={getCurrentTheme().colors['descriptionForeground']}> {dir}</span> : null}
|
|
103
124
|
</text>
|
|
104
125
|
)
|
|
105
126
|
}
|
|
@@ -115,7 +136,7 @@ function renderDiffCount(
|
|
|
115
136
|
if (!diffCountConfig.enabled) return null
|
|
116
137
|
if (!hasNumstat) {
|
|
117
138
|
return (
|
|
118
|
-
<text fg={
|
|
139
|
+
<text fg={getCurrentTheme().colors['descriptionForeground']} bg={bg} flexShrink={0}>
|
|
119
140
|
—
|
|
120
141
|
</text>
|
|
121
142
|
)
|
|
@@ -123,14 +144,14 @@ function renderDiffCount(
|
|
|
123
144
|
return (
|
|
124
145
|
<box flexDirection="row" flexShrink={0}>
|
|
125
146
|
<text
|
|
126
|
-
fg={
|
|
147
|
+
fg={getCurrentTheme().colors['gitDecoration.addedResourceForeground']}
|
|
127
148
|
bg={bg}
|
|
128
149
|
>{`+${padRight(file.added, addedW)}`}</text>
|
|
129
|
-
<text fg={
|
|
150
|
+
<text fg={getCurrentTheme().colors['editor.lineHighlightBackground']} bg={bg}>
|
|
130
151
|
{' '}
|
|
131
152
|
</text>
|
|
132
153
|
<text
|
|
133
|
-
fg={
|
|
154
|
+
fg={getCurrentTheme().colors['editorError.foreground']}
|
|
134
155
|
bg={bg}
|
|
135
156
|
>{`−${padRight(file.removed, removedW)}`}</text>
|
|
136
157
|
</box>
|
|
@@ -138,7 +159,7 @@ function renderDiffCount(
|
|
|
138
159
|
}
|
|
139
160
|
|
|
140
161
|
function renderFolderRow(row: GitTreeFolderRow, isSelected: boolean): ReactNode {
|
|
141
|
-
const bg = isSelected ?
|
|
162
|
+
const bg = isSelected ? getCurrentTheme().colors['list.activeSelectionBackground'] : undefined
|
|
142
163
|
const onSelect = (): void => {
|
|
143
164
|
dispatchGlobal({ key: row.key, type: 'git-mode-select-entry-by-key' })
|
|
144
165
|
}
|
|
@@ -149,10 +170,13 @@ function renderFolderRow(row: GitTreeFolderRow, isSelected: boolean): ReactNode
|
|
|
149
170
|
<box key={row.key} flexDirection="row" gap={1} backgroundColor={bg} onMouseDown={onSelect}>
|
|
150
171
|
<box flexGrow={1} overflow="hidden" paddingLeft={row.depth * 2}>
|
|
151
172
|
<box flexDirection="row" gap={1} onMouseDown={onToggle}>
|
|
152
|
-
<text fg={
|
|
173
|
+
<text fg={getCurrentTheme().colors['descriptionForeground']} bg={bg}>
|
|
153
174
|
{row.isCollapsed ? '▸' : '▾'}
|
|
154
175
|
</text>
|
|
155
|
-
<text fg={
|
|
176
|
+
<text fg={getCurrentTheme().colors['textLink.foreground']} bg={bg}>
|
|
177
|
+
{row.isCollapsed ? '\uf07b' : '\uf07c'}
|
|
178
|
+
</text>
|
|
179
|
+
<text fg={getCurrentTheme().colors['textLink.foreground']} bg={bg} wrapMode="none">
|
|
156
180
|
{row.name}
|
|
157
181
|
</text>
|
|
158
182
|
</box>
|
|
@@ -172,14 +196,14 @@ function renderFileRow(
|
|
|
172
196
|
): ReactNode {
|
|
173
197
|
const file = row.file
|
|
174
198
|
const hasNumstat = file.added !== null || file.removed !== null
|
|
175
|
-
const bg = isSelected ?
|
|
199
|
+
const bg = isSelected ? getCurrentTheme().colors['list.activeSelectionBackground'] : undefined
|
|
176
200
|
const onSelect = (): void => {
|
|
177
201
|
dispatchGlobal({ key: row.key, type: 'git-mode-select-entry-by-key' })
|
|
178
202
|
}
|
|
179
203
|
return (
|
|
180
204
|
<box key={row.key} flexDirection="row" gap={1} backgroundColor={bg} onMouseDown={onSelect}>
|
|
181
205
|
<box width={2} flexShrink={0} justifyContent="center">
|
|
182
|
-
<text fg={
|
|
206
|
+
<text fg={statusColor(file.status)} bg={bg}>
|
|
183
207
|
<strong>{displayStatus(file)}</strong>
|
|
184
208
|
</text>
|
|
185
209
|
</box>
|
|
@@ -202,7 +226,8 @@ function renderTreeSection(
|
|
|
202
226
|
selectedEntryKey: string | null | undefined,
|
|
203
227
|
showListModeToggle: boolean,
|
|
204
228
|
pathConfig: GitPanePathConfig,
|
|
205
|
-
diffCountConfig: GitPaneDiffCountConfig
|
|
229
|
+
diffCountConfig: GitPaneDiffCountConfig,
|
|
230
|
+
marginTop: number
|
|
206
231
|
): ReactNode {
|
|
207
232
|
if (files.length === 0) return null
|
|
208
233
|
const nextFileListMode = fileListMode === 'tree' ? 'flat' : 'tree'
|
|
@@ -211,9 +236,9 @@ function renderTreeSection(
|
|
|
211
236
|
runSideEffectGlobal({ mode: nextFileListMode, type: 'persist-git-file-list-mode' })
|
|
212
237
|
}
|
|
213
238
|
return (
|
|
214
|
-
<box key={section} flexDirection="column">
|
|
239
|
+
<box key={section} flexDirection="column" marginTop={marginTop}>
|
|
215
240
|
<box flexDirection="row" justifyContent="space-between">
|
|
216
|
-
<text fg={
|
|
241
|
+
<text fg={getCurrentTheme().colors['terminal.ansiMagenta']}>
|
|
217
242
|
<strong>
|
|
218
243
|
{title} ({files.length})
|
|
219
244
|
</strong>
|
|
@@ -223,18 +248,18 @@ function renderTreeSection(
|
|
|
223
248
|
<text
|
|
224
249
|
fg={
|
|
225
250
|
fileListMode === 'tree'
|
|
226
|
-
?
|
|
227
|
-
:
|
|
251
|
+
? getCurrentTheme().colors['textLink.foreground']
|
|
252
|
+
: getCurrentTheme().colors['descriptionForeground']
|
|
228
253
|
}
|
|
229
254
|
>
|
|
230
255
|
tree
|
|
231
256
|
</text>
|
|
232
|
-
<text fg={
|
|
257
|
+
<text fg={getCurrentTheme().colors['descriptionForeground']}>|</text>
|
|
233
258
|
<text
|
|
234
259
|
fg={
|
|
235
260
|
fileListMode === 'flat'
|
|
236
|
-
?
|
|
237
|
-
:
|
|
261
|
+
? getCurrentTheme().colors['textLink.foreground']
|
|
262
|
+
: getCurrentTheme().colors['descriptionForeground']
|
|
238
263
|
}
|
|
239
264
|
>
|
|
240
265
|
flat
|
|
@@ -279,16 +304,25 @@ function computeStatusPlaceholder(
|
|
|
279
304
|
hasProjectPath: boolean
|
|
280
305
|
): StatusPlaceholder | null {
|
|
281
306
|
if (!hasProjectPath) {
|
|
282
|
-
return {
|
|
307
|
+
return {
|
|
308
|
+
label: 'No active session',
|
|
309
|
+
labelColor: getCurrentTheme().colors['descriptionForeground'],
|
|
310
|
+
}
|
|
283
311
|
}
|
|
284
312
|
if (gitPanel.error === 'not-a-repo') {
|
|
285
|
-
return {
|
|
313
|
+
return {
|
|
314
|
+
label: 'Not a git repository',
|
|
315
|
+
labelColor: getCurrentTheme().colors['descriptionForeground'],
|
|
316
|
+
}
|
|
286
317
|
}
|
|
287
318
|
if (gitPanel.error === 'unknown') {
|
|
288
|
-
return { label: 'Git error', labelColor:
|
|
319
|
+
return { label: 'Git error', labelColor: getCurrentTheme().colors['editorError.foreground'] }
|
|
289
320
|
}
|
|
290
321
|
if (gitPanel.files.length === 0) {
|
|
291
|
-
return {
|
|
322
|
+
return {
|
|
323
|
+
label: 'Working tree clean',
|
|
324
|
+
labelColor: getCurrentTheme().colors['descriptionForeground'],
|
|
325
|
+
}
|
|
292
326
|
}
|
|
293
327
|
return null
|
|
294
328
|
}
|
|
@@ -298,17 +332,21 @@ const DEFAULT_DIFF_COUNT_CONFIG: GitPaneDiffCountConfig = { enabled: true }
|
|
|
298
332
|
|
|
299
333
|
export const GitPanel = memo(function GitPanel({
|
|
300
334
|
collapsedFolders = {},
|
|
335
|
+
compact = false,
|
|
301
336
|
diffCountConfig = DEFAULT_DIFF_COUNT_CONFIG,
|
|
302
337
|
fileListMode = 'tree',
|
|
303
338
|
gitPanel,
|
|
339
|
+
headOffset = 0,
|
|
304
340
|
pathConfig = DEFAULT_PATH_CONFIG,
|
|
305
341
|
projectPath,
|
|
306
342
|
selectedEntryKey,
|
|
307
343
|
}: GitPanelProps) {
|
|
344
|
+
const theme = useTheme()
|
|
345
|
+
const sectionOrder = headOffset > 0 ? HISTORICAL_SECTION_ORDER : BASE_SECTION_ORDER
|
|
308
346
|
const scrollRef = useRef<ScrollBoxRenderable | null>(null)
|
|
309
347
|
const tree = useMemo(
|
|
310
|
-
() => buildGitTreeRows(gitPanel.files, collapsedFolders, fileListMode),
|
|
311
|
-
[collapsedFolders, fileListMode, gitPanel.files]
|
|
348
|
+
() => buildGitTreeRows(gitPanel.files, collapsedFolders, fileListMode, compact),
|
|
349
|
+
[collapsedFolders, compact, fileListMode, gitPanel.files]
|
|
312
350
|
)
|
|
313
351
|
const { added: addedW, removed: removedW } = useMemo(
|
|
314
352
|
() => maxDigitWidth(gitPanel.files),
|
|
@@ -350,20 +388,24 @@ export const GitPanel = memo(function GitPanel({
|
|
|
350
388
|
viewportCulling
|
|
351
389
|
contentOptions={{ flexDirection: 'column', gap: 0 }}
|
|
352
390
|
>
|
|
353
|
-
{
|
|
354
|
-
const section = tree.sections.find((entry) => entry.section ===
|
|
391
|
+
{sectionOrder.map((key, idx) => {
|
|
392
|
+
const section = tree.sections.find((entry) => entry.section === key)
|
|
393
|
+
const priorHasFiles = sectionOrder
|
|
394
|
+
.slice(0, idx)
|
|
395
|
+
.some((k) => (tree.sections.find((e) => e.section === k)?.files.length ?? 0) > 0)
|
|
355
396
|
return renderTreeSection(
|
|
356
|
-
|
|
357
|
-
|
|
397
|
+
key,
|
|
398
|
+
sectionTitle(key, headOffset),
|
|
358
399
|
section?.files ?? [],
|
|
359
400
|
section?.rows ?? [],
|
|
360
401
|
addedW,
|
|
361
402
|
removedW,
|
|
362
403
|
fileListMode,
|
|
363
404
|
selectedEntryKey,
|
|
364
|
-
|
|
405
|
+
key === toggleSection,
|
|
365
406
|
pathConfig,
|
|
366
|
-
diffCountConfig
|
|
407
|
+
diffCountConfig,
|
|
408
|
+
priorHasFiles ? 1 : 0
|
|
367
409
|
)
|
|
368
410
|
})}
|
|
369
411
|
</scrollbox>
|
|
@@ -4,14 +4,16 @@ import { memo, useEffect, useRef } from 'react'
|
|
|
4
4
|
import type { DiffData, GitDiffView } from '../../state/types'
|
|
5
5
|
import type { ThemeId } from '../themes'
|
|
6
6
|
|
|
7
|
+
import { diffHash } from '../../git/diff-hash'
|
|
7
8
|
import { fetchDiff } from '../../git/git-diff'
|
|
8
9
|
import { useGitPanelPolling } from '../../git/git-poller'
|
|
9
10
|
import { useAppStore } from '../../state/app-store'
|
|
10
11
|
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
11
12
|
import { getSelectedGitFile, gitFileKey } from '../../state/git-tree'
|
|
12
13
|
import { setGitDiffScroller } from '../git-view-controls'
|
|
13
|
-
import {
|
|
14
|
+
import { useTheme } from '../theme'
|
|
14
15
|
import { PierreDiff, type PierreDiffHandle } from './diff-renderer'
|
|
16
|
+
import { useDiffPrefetch } from './diff-renderer/use-diff-prefetch'
|
|
15
17
|
import { GitPanel } from './git-panel'
|
|
16
18
|
|
|
17
19
|
interface DiffStageProps {
|
|
@@ -45,6 +47,7 @@ const DiffStage = memo(function DiffStage({
|
|
|
45
47
|
themeId,
|
|
46
48
|
view,
|
|
47
49
|
}: DiffStageProps) {
|
|
50
|
+
const theme = useTheme()
|
|
48
51
|
if (loading && !diff) {
|
|
49
52
|
return (
|
|
50
53
|
<box flexGrow={1} padding={1}>
|
|
@@ -102,6 +105,7 @@ interface GitViewProps {
|
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
export const GitView = memo(function GitView({ themeId }: GitViewProps) {
|
|
108
|
+
const theme = useTheme()
|
|
105
109
|
const dimensions = useTerminalDimensions()
|
|
106
110
|
const gitPane = useAppStore((s) => s.gitPane)
|
|
107
111
|
const gitPanel = useAppStore((s) => s.gitPanel)
|
|
@@ -116,12 +120,13 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
|
|
|
116
120
|
: undefined
|
|
117
121
|
const projectPath = currentSession?.projectPath
|
|
118
122
|
|
|
119
|
-
useGitPanelPolling({ enabled: focusMode === 'git', projectPath })
|
|
123
|
+
useGitPanelPolling({ enabled: focusMode === 'git', headOffset: gitMode.headOffset, projectPath })
|
|
120
124
|
|
|
121
125
|
const fileBarWidth = Math.max(20, Math.floor(dimensions.width * gitPane.diffModeRatio))
|
|
122
126
|
|
|
123
127
|
const selectedFile = getSelectedGitFile(gitPanel.files, {
|
|
124
128
|
collapsedFolders: gitMode.collapsedFolders,
|
|
129
|
+
compact: gitPane.treeCompaction,
|
|
125
130
|
fileListMode: gitPane.fileListMode,
|
|
126
131
|
selectedEntryKey: gitMode.selectedEntryKey,
|
|
127
132
|
})
|
|
@@ -145,18 +150,32 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
|
|
|
145
150
|
return () => setGitDiffScroller(null)
|
|
146
151
|
}, [])
|
|
147
152
|
|
|
153
|
+
useDiffPrefetch(gitMode.selectedEntryKey, gitPane.prefetchRadius, {
|
|
154
|
+
enabled: focusMode === 'git',
|
|
155
|
+
headOffset: gitMode.headOffset,
|
|
156
|
+
projectPath,
|
|
157
|
+
themeId,
|
|
158
|
+
})
|
|
159
|
+
|
|
148
160
|
useEffect(() => {
|
|
149
161
|
if (focusMode !== 'git') return
|
|
150
162
|
if (!selectedFile || !projectPath) return
|
|
151
163
|
if (!selectedDiffKey) return
|
|
152
164
|
if (diff || loading) return
|
|
153
165
|
dispatchGlobal({ key: selectedDiffKey, loading: true, type: 'git-mode-set-loading' })
|
|
154
|
-
void fetchDiff(projectPath, selectedFile)
|
|
155
|
-
.then((d) =>
|
|
166
|
+
void fetchDiff(projectPath, selectedFile, gitMode.headOffset)
|
|
167
|
+
.then((d) =>
|
|
168
|
+
dispatchGlobal({
|
|
169
|
+
diff: d,
|
|
170
|
+
hash: diffHash(d.rawDiff),
|
|
171
|
+
key: selectedDiffKey,
|
|
172
|
+
type: 'git-mode-set-diff',
|
|
173
|
+
})
|
|
174
|
+
)
|
|
156
175
|
.catch(() =>
|
|
157
176
|
dispatchGlobal({ key: selectedDiffKey, loading: false, type: 'git-mode-set-loading' })
|
|
158
177
|
)
|
|
159
|
-
}, [focusMode, projectPath, selectedFile, selectedDiffKey, diff, loading])
|
|
178
|
+
}, [focusMode, projectPath, selectedFile, selectedDiffKey, diff, loading, gitMode.headOffset])
|
|
160
179
|
|
|
161
180
|
const pendingPath = gitMode.pendingDeletePath
|
|
162
181
|
const pendingIsUntracked =
|
|
@@ -204,13 +223,23 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
|
|
|
204
223
|
<text fg={theme.colors['descriptionForeground']}>{gitPanel.branch}</text>
|
|
205
224
|
</box>
|
|
206
225
|
) : null}
|
|
226
|
+
{gitMode.headOffset > 0 ? (
|
|
227
|
+
<box flexDirection="row" gap={1}>
|
|
228
|
+
<text fg={theme.colors['editorWarning.foreground']}>
|
|
229
|
+
<strong>HEAD~{gitMode.headOffset}</strong>
|
|
230
|
+
</text>
|
|
231
|
+
<text fg={theme.colors['descriptionForeground']}>[ newer · ] older</text>
|
|
232
|
+
</box>
|
|
233
|
+
) : null}
|
|
207
234
|
<text fg={theme.colors['editor.lineHighlightBackground']}>
|
|
208
235
|
{'·'.repeat(Math.max(0, fileBarWidth - 2))}
|
|
209
236
|
</text>
|
|
210
237
|
<GitPanel
|
|
211
238
|
collapsedFolders={gitMode.collapsedFolders}
|
|
239
|
+
compact={gitPane.treeCompaction}
|
|
212
240
|
fileListMode={gitPane.fileListMode}
|
|
213
241
|
gitPanel={gitPanel}
|
|
242
|
+
headOffset={gitMode.headOffset}
|
|
214
243
|
projectPath={projectPath}
|
|
215
244
|
selectedEntryKey={gitMode.selectedEntryKey}
|
|
216
245
|
/>
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '../../input/keymap/help-entries'
|
|
11
11
|
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
12
12
|
import { useKeymap } from '../keymap-context'
|
|
13
|
-
import {
|
|
13
|
+
import { useTheme } from '../theme'
|
|
14
14
|
import { uiTokens } from '../ui-tokens'
|
|
15
15
|
import { ModalFilterBar } from './modal-filter-bar'
|
|
16
16
|
import { ModalShell } from './modal-shell'
|
|
@@ -86,6 +86,7 @@ function computeWindowStart(
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export function HelpModal({ filter, scope, selectedIndex }: HelpModalProps) {
|
|
89
|
+
const theme = useTheme()
|
|
89
90
|
const config = useKeymap()
|
|
90
91
|
const dimensions = useTerminalDimensions()
|
|
91
92
|
const allEntries = useMemo(() => collectHelpEntries(config), [config])
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTheme } from '../theme'
|
|
2
2
|
import { Surface } from './surface'
|
|
3
3
|
|
|
4
4
|
interface InputFieldProps {
|
|
@@ -8,6 +8,7 @@ interface InputFieldProps {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function InputField({ active, cursorPos, value }: InputFieldProps) {
|
|
11
|
+
const theme = useTheme()
|
|
11
12
|
const fg = active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
|
|
12
13
|
if (!active) {
|
|
13
14
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme } from '../theme'
|
|
4
4
|
import { Surface } from './surface'
|
|
5
5
|
|
|
6
6
|
export type ListItemDirection = 'row' | 'column'
|
|
@@ -22,6 +22,7 @@ export function ListItem({
|
|
|
22
22
|
title,
|
|
23
23
|
trailing,
|
|
24
24
|
}: ListItemProps) {
|
|
25
|
+
const theme = useTheme()
|
|
25
26
|
const isRow = direction === 'row'
|
|
26
27
|
return (
|
|
27
28
|
<Surface
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTheme } from '../theme'
|
|
2
2
|
import { Surface } from './surface'
|
|
3
3
|
|
|
4
4
|
interface ModalFilterBarProps {
|
|
@@ -6,6 +6,7 @@ interface ModalFilterBarProps {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function ModalFilterBar({ filter }: ModalFilterBarProps) {
|
|
9
|
+
const theme = useTheme()
|
|
9
10
|
if (filter === null) {
|
|
10
11
|
return null
|
|
11
12
|
}
|
|
@@ -2,7 +2,7 @@ import type { ModeId } from '@brimveyn/aimux-config'
|
|
|
2
2
|
|
|
3
3
|
import { describeBindings } from '../../input/keymap/describe-bindings'
|
|
4
4
|
import { useKeymap } from '../keymap-context'
|
|
5
|
-
import {
|
|
5
|
+
import { useTheme } from '../theme'
|
|
6
6
|
import { Surface } from './surface'
|
|
7
7
|
|
|
8
8
|
const KEYS_COLUMN_WIDTH = 12
|
|
@@ -13,6 +13,7 @@ interface ModalKeybindsOverlayProps {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function ModalKeybindsOverlay({ limit, modeId }: ModalKeybindsOverlayProps) {
|
|
16
|
+
const theme = useTheme()
|
|
16
17
|
const config = useKeymap()
|
|
17
18
|
const bindings = describeBindings(config, modeId, {
|
|
18
19
|
dedupeByDescription: true,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ModeId } from '@brimveyn/aimux-config'
|
|
2
2
|
import type { ReactNode } from 'react'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { useTheme } from '../theme'
|
|
5
5
|
import { ModalKeybindsOverlay } from './modal-keybinds-overlay'
|
|
6
6
|
import { Surface } from './surface'
|
|
7
7
|
|
|
@@ -24,6 +24,7 @@ export function ModalShell({
|
|
|
24
24
|
title,
|
|
25
25
|
width,
|
|
26
26
|
}: ModalShellProps) {
|
|
27
|
+
const theme = useTheme()
|
|
27
28
|
return (
|
|
28
29
|
<box
|
|
29
30
|
position="absolute"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getAllAssistantOptions } from '../../pty/command-registry'
|
|
2
|
-
import {
|
|
2
|
+
import { useTheme } from '../theme'
|
|
3
3
|
import { uiTokens } from '../ui-tokens'
|
|
4
4
|
import { InputField } from './input-field'
|
|
5
5
|
import { ListItem } from './list-item'
|
|
@@ -12,6 +12,7 @@ interface NewTabModalProps {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function NewTabModal({ customCommands, editBuffer, selectedIndex }: NewTabModalProps) {
|
|
15
|
+
const theme = useTheme()
|
|
15
16
|
const options = getAllAssistantOptions(customCommands)
|
|
16
17
|
const selectedOption = options[selectedIndex]
|
|
17
18
|
const isEditing = editBuffer !== null
|
|
@@ -2,10 +2,11 @@ import { parseKeyNotation } from '../../input/keymap/key-chord'
|
|
|
2
2
|
import { formatChord } from '../../input/keymap/key-format'
|
|
3
3
|
import { useAppStore } from '../../state/app-store'
|
|
4
4
|
import { useKeymap } from '../keymap-context'
|
|
5
|
-
import {
|
|
5
|
+
import { useTheme } from '../theme'
|
|
6
6
|
import { Surface } from './surface'
|
|
7
7
|
|
|
8
8
|
export function PendingChordOverlay() {
|
|
9
|
+
const theme = useTheme()
|
|
9
10
|
const pendingChords = useAppStore((s) => s.pendingChords)
|
|
10
11
|
const modalOpen = useAppStore((s) => s.modal.type !== null)
|
|
11
12
|
const config = useKeymap()
|
|
@@ -8,9 +8,10 @@ import { useAppStore } from '../../state/app-store'
|
|
|
8
8
|
import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
9
9
|
import { useBusySpinner } from '../hooks/use-busy-spinner'
|
|
10
10
|
import { moveIdToIdPosition, orderSessionsForDisplay } from '../session-ordering'
|
|
11
|
-
import {
|
|
11
|
+
import { useTheme } from '../theme'
|
|
12
12
|
|
|
13
13
|
export function SessionBar() {
|
|
14
|
+
const theme = useTheme()
|
|
14
15
|
const sessions = useAppStore((s) => s.sessions)
|
|
15
16
|
const currentId = useAppStore((s) => s.currentSessionId)
|
|
16
17
|
const bar = useAppStore((s) => s.sessionBar)
|
|
@@ -165,6 +166,7 @@ function SessionChip({
|
|
|
165
166
|
onRef,
|
|
166
167
|
session,
|
|
167
168
|
}: SessionChipProps) {
|
|
169
|
+
const theme = useTheme()
|
|
168
170
|
const showSpinner = busy && !active
|
|
169
171
|
const spinner = useBusySpinner(showSpinner)
|
|
170
172
|
const indicator = showSpinner ? spinner : '●'
|
|
@@ -3,7 +3,7 @@ import type { SessionRecord } from '../../state/types'
|
|
|
3
3
|
import { filterSessions } from '../../state/selectors'
|
|
4
4
|
import { abbreviatePath } from '../path-format'
|
|
5
5
|
import { orderSessionsForDisplay } from '../session-ordering'
|
|
6
|
-
import {
|
|
6
|
+
import { useTheme } from '../theme'
|
|
7
7
|
import { uiTokens } from '../ui-tokens'
|
|
8
8
|
import { ListItem } from './list-item'
|
|
9
9
|
import { ModalFilterBar } from './modal-filter-bar'
|
|
@@ -45,6 +45,7 @@ export function SessionPickerModal({
|
|
|
45
45
|
selectedIndex,
|
|
46
46
|
sessions,
|
|
47
47
|
}: SessionPickerModalProps) {
|
|
48
|
+
const theme = useTheme()
|
|
48
49
|
const ordered = orderSessionsForDisplay(sessions)
|
|
49
50
|
const baselineOrder = ordered.map((s) => s.id)
|
|
50
51
|
const filtered = filterSessions(ordered, filter)
|
|
@@ -3,7 +3,7 @@ import type { ScrollBoxRenderable } from '@opentui/core'
|
|
|
3
3
|
import { memo, useMemo, useRef } from 'react'
|
|
4
4
|
|
|
5
5
|
import { useAppStore } from '../../state/app-store'
|
|
6
|
-
import {
|
|
6
|
+
import { getCurrentTheme, useTheme } from '../theme'
|
|
7
7
|
import { GitPaneWidget } from './git-pane-widget'
|
|
8
8
|
import { buildTabGroupInfo } from './sidebar-group-metadata'
|
|
9
9
|
import { TabItem } from './tab-item'
|
|
@@ -20,6 +20,7 @@ const GUTTER_END = '╰'
|
|
|
20
20
|
const GUTTER_PAD = '│'
|
|
21
21
|
|
|
22
22
|
const SidebarTop = memo(function SidebarTop() {
|
|
23
|
+
const theme = useTheme()
|
|
23
24
|
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
24
25
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
25
26
|
const sessions = useAppStore((s) => s.sessions)
|
|
@@ -59,15 +60,15 @@ function renderGroupGutter(
|
|
|
59
60
|
return (
|
|
60
61
|
<box flexDirection="column" width={1} overflow="hidden">
|
|
61
62
|
<text
|
|
62
|
-
fg={
|
|
63
|
-
bg={isActive ?
|
|
63
|
+
fg={getCurrentTheme().colors['terminal.ansiMagenta']}
|
|
64
|
+
bg={isActive ? getCurrentTheme().colors['list.activeSelectionBackground'] : undefined}
|
|
64
65
|
>
|
|
65
66
|
{/* oxlint-disable-next-line no-nested-ternary */}
|
|
66
67
|
{isGroupStart ? GUTTER_START : isGroupMiddle ? GUTTER_MIDDLE : GUTTER_PAD}
|
|
67
68
|
</text>
|
|
68
69
|
<text
|
|
69
|
-
fg={
|
|
70
|
-
bg={isActive ?
|
|
70
|
+
fg={getCurrentTheme().colors['terminal.ansiMagenta']}
|
|
71
|
+
bg={isActive ? getCurrentTheme().colors['list.activeSelectionBackground'] : undefined}
|
|
71
72
|
>
|
|
72
73
|
{isGroupEnd ? GUTTER_END : GUTTER_PAD}
|
|
73
74
|
</text>
|
|
@@ -80,6 +81,7 @@ interface TabsBodyProps {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
84
|
+
const theme = useTheme()
|
|
83
85
|
const tabs = useAppStore((s) => s.tabs)
|
|
84
86
|
const activeTabId = useAppStore((s) => s.activeTabId)
|
|
85
87
|
const focusMode = useAppStore((s) => s.focusMode)
|
|
@@ -149,6 +151,7 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
149
151
|
})
|
|
150
152
|
|
|
151
153
|
export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
154
|
+
const theme = useTheme()
|
|
152
155
|
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
153
156
|
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
154
157
|
const gitPane = useAppStore((s) => s.gitPane)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTheme } from '../theme'
|
|
2
2
|
import { uiTokens } from '../ui-tokens'
|
|
3
3
|
import { InputField } from './input-field'
|
|
4
4
|
import { ModalShell } from './modal-shell'
|
|
@@ -16,6 +16,7 @@ export function SnippetEditorModal({
|
|
|
16
16
|
snippetContent,
|
|
17
17
|
snippetName,
|
|
18
18
|
}: SnippetEditorModalProps) {
|
|
19
|
+
const theme = useTheme()
|
|
19
20
|
const nameActive = activeField === 'name'
|
|
20
21
|
const contentActive = activeField === 'content'
|
|
21
22
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SnippetRecord } from '../../state/types'
|
|
2
2
|
|
|
3
3
|
import { filterSnippets } from '../../state/selectors'
|
|
4
|
-
import {
|
|
4
|
+
import { useTheme } from '../theme'
|
|
5
5
|
import { uiTokens } from '../ui-tokens'
|
|
6
6
|
import { ListItem } from './list-item'
|
|
7
7
|
import { ModalFilterBar } from './modal-filter-bar'
|
|
@@ -22,6 +22,7 @@ function truncateContent(content: string): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function SnippetPickerModal({ filter, selectedIndex, snippets }: SnippetPickerModalProps) {
|
|
25
|
+
const theme = useTheme()
|
|
25
26
|
const filtered = filterSnippets(snippets, filter)
|
|
26
27
|
|
|
27
28
|
return (
|