@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,51 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { useTerminalDimensions } from '@opentui/react'
|
|
3
2
|
import { memo, useEffect, useRef } from 'react'
|
|
4
3
|
|
|
5
|
-
import type { DiffData } from '../../state/types'
|
|
4
|
+
import type { DiffData, GitDiffView } from '../../state/types'
|
|
5
|
+
import type { ThemeId } from '../themes'
|
|
6
6
|
|
|
7
7
|
import { fetchDiff } from '../../git/git-diff'
|
|
8
8
|
import { useGitPanelPolling } from '../../git/git-poller'
|
|
9
9
|
import { useAppStore } from '../../state/app-store'
|
|
10
10
|
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
11
|
+
import { getSelectedGitFile, gitFileKey } from '../../state/git-tree'
|
|
11
12
|
import { setGitDiffScroller } from '../git-view-controls'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
interface CodePaneLike {
|
|
17
|
-
scrollY: number
|
|
18
|
-
maxScrollY: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface DiffRenderableInternals {
|
|
22
|
-
leftCodeRenderable?: CodePaneLike
|
|
23
|
-
rightCodeRenderable?: CodePaneLike
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function filetypeFromPath(path: string): string | undefined {
|
|
27
|
-
const dot = path.lastIndexOf('.')
|
|
28
|
-
if (dot < 0) return undefined
|
|
29
|
-
const ext = path.slice(dot + 1).toLowerCase()
|
|
30
|
-
const map: Record<string, string> = {
|
|
31
|
-
cjs: 'javascript',
|
|
32
|
-
js: 'javascript',
|
|
33
|
-
jsx: 'javascript',
|
|
34
|
-
markdown: 'markdown',
|
|
35
|
-
md: 'markdown',
|
|
36
|
-
mdx: 'markdown',
|
|
37
|
-
mjs: 'javascript',
|
|
38
|
-
ts: 'typescript',
|
|
39
|
-
tsx: 'typescript',
|
|
40
|
-
zig: 'zig',
|
|
41
|
-
}
|
|
42
|
-
return map[ext]
|
|
43
|
-
}
|
|
13
|
+
import { useTheme } from '../theme'
|
|
14
|
+
import { PierreDiff, type PierreDiffHandle } from './diff-renderer'
|
|
15
|
+
import { GitPanel } from './git-panel'
|
|
44
16
|
|
|
45
17
|
interface DiffStageProps {
|
|
46
18
|
diff: DiffData | undefined
|
|
19
|
+
diffKey: string | null
|
|
47
20
|
loading: boolean
|
|
48
|
-
diffRef: React.RefObject<
|
|
21
|
+
diffRef: React.RefObject<PierreDiffHandle | null>
|
|
22
|
+
themeId: ThemeId
|
|
23
|
+
view: GitDiffView
|
|
49
24
|
}
|
|
50
25
|
|
|
51
26
|
function placeholderText(diff: DiffData): string | null {
|
|
@@ -62,25 +37,33 @@ function placeholderText(diff: DiffData): string | null {
|
|
|
62
37
|
return null
|
|
63
38
|
}
|
|
64
39
|
|
|
65
|
-
const DiffStage = memo(function DiffStage({
|
|
40
|
+
const DiffStage = memo(function DiffStage({
|
|
41
|
+
diff,
|
|
42
|
+
diffKey,
|
|
43
|
+
diffRef,
|
|
44
|
+
loading,
|
|
45
|
+
themeId,
|
|
46
|
+
view,
|
|
47
|
+
}: DiffStageProps) {
|
|
48
|
+
const theme = useTheme()
|
|
66
49
|
if (loading && !diff) {
|
|
67
50
|
return (
|
|
68
51
|
<box flexGrow={1} padding={1}>
|
|
69
|
-
<text fg={theme.
|
|
52
|
+
<text fg={theme.colors['descriptionForeground']}>Loading diff…</text>
|
|
70
53
|
</box>
|
|
71
54
|
)
|
|
72
55
|
}
|
|
73
56
|
if (!diff) {
|
|
74
57
|
return (
|
|
75
58
|
<box flexGrow={1} padding={1}>
|
|
76
|
-
<text fg={theme.
|
|
59
|
+
<text fg={theme.colors['descriptionForeground']}>Select a file.</text>
|
|
77
60
|
</box>
|
|
78
61
|
)
|
|
79
62
|
}
|
|
80
63
|
if (diff.errorMessage) {
|
|
81
64
|
return (
|
|
82
65
|
<box flexGrow={1} padding={1}>
|
|
83
|
-
<text fg={theme.
|
|
66
|
+
<text fg={theme.colors['editorError.foreground']}>{diff.errorMessage}</text>
|
|
84
67
|
</box>
|
|
85
68
|
)
|
|
86
69
|
}
|
|
@@ -89,74 +72,77 @@ const DiffStage = memo(function DiffStage({ diff, diffRef, loading }: DiffStageP
|
|
|
89
72
|
if (placeholder) {
|
|
90
73
|
return (
|
|
91
74
|
<box flexGrow={1} padding={1}>
|
|
92
|
-
<text fg={theme.
|
|
75
|
+
<text fg={theme.colors['descriptionForeground']}>{placeholder}</text>
|
|
93
76
|
</box>
|
|
94
77
|
)
|
|
95
78
|
}
|
|
96
79
|
|
|
97
|
-
const filetype = filetypeFromPath(diff.path)
|
|
98
|
-
|
|
99
80
|
return (
|
|
100
81
|
<box flexDirection="column" flexGrow={1} overflow="hidden">
|
|
101
82
|
{diff.oldPath ? (
|
|
102
83
|
<box paddingLeft={1} paddingRight={1}>
|
|
103
|
-
<text fg={theme.
|
|
84
|
+
<text fg={theme.colors['descriptionForeground']}>
|
|
104
85
|
renamed: {diff.oldPath} → {diff.path}
|
|
105
86
|
</text>
|
|
106
87
|
</box>
|
|
107
88
|
) : null}
|
|
108
|
-
<
|
|
89
|
+
<PierreDiff
|
|
90
|
+
cacheKey={diffKey ?? diff.path}
|
|
109
91
|
ref={diffRef}
|
|
110
92
|
diff={diff.rawDiff}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
wrapMode="none"
|
|
115
|
-
filetype={filetype}
|
|
116
|
-
treeSitterClient={filetype ? getSyntaxClient() : undefined}
|
|
117
|
-
syntaxStyle={filetype ? getSyntaxStyle() : undefined}
|
|
118
|
-
addedBg={theme.diffAddBg}
|
|
119
|
-
removedBg={theme.diffRemoveBg}
|
|
120
|
-
addedSignColor={theme.success}
|
|
121
|
-
removedSignColor={theme.danger}
|
|
122
|
-
flexGrow={1}
|
|
93
|
+
path={diff.path}
|
|
94
|
+
themeId={themeId}
|
|
95
|
+
view={view}
|
|
123
96
|
/>
|
|
124
97
|
</box>
|
|
125
98
|
)
|
|
126
99
|
})
|
|
127
100
|
|
|
128
|
-
|
|
129
|
-
|
|
101
|
+
interface GitViewProps {
|
|
102
|
+
themeId: ThemeId
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const GitView = memo(function GitView({ themeId }: GitViewProps) {
|
|
106
|
+
const theme = useTheme()
|
|
107
|
+
const dimensions = useTerminalDimensions()
|
|
108
|
+
const gitPane = useAppStore((s) => s.gitPane)
|
|
130
109
|
const gitPanel = useAppStore((s) => s.gitPanel)
|
|
131
110
|
const gitMode = useAppStore((s) => s.gitMode)
|
|
132
111
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
133
112
|
const sessions = useAppStore((s) => s.sessions)
|
|
134
113
|
const focusMode = useAppStore((s) => s.focusMode)
|
|
135
|
-
const diffRef = useRef<
|
|
114
|
+
const diffRef = useRef<PierreDiffHandle | null>(null)
|
|
136
115
|
|
|
137
116
|
const currentSession = currentSessionId
|
|
138
117
|
? sessions.find((s) => s.id === currentSessionId)
|
|
139
118
|
: undefined
|
|
140
119
|
const projectPath = currentSession?.projectPath
|
|
141
120
|
|
|
142
|
-
useGitPanelPolling({ enabled: focusMode === 'git', projectPath })
|
|
121
|
+
useGitPanelPolling({ enabled: focusMode === 'git', headOffset: gitMode.headOffset, projectPath })
|
|
122
|
+
|
|
123
|
+
const fileBarWidth = Math.max(20, Math.floor(dimensions.width * gitPane.diffModeRatio))
|
|
143
124
|
|
|
144
|
-
const selectedFile = gitPanel.files
|
|
145
|
-
|
|
146
|
-
|
|
125
|
+
const selectedFile = getSelectedGitFile(gitPanel.files, {
|
|
126
|
+
collapsedFolders: gitMode.collapsedFolders,
|
|
127
|
+
fileListMode: gitPane.fileListMode,
|
|
128
|
+
selectedEntryKey: gitMode.selectedEntryKey,
|
|
129
|
+
})
|
|
130
|
+
const selectedDiffKey = selectedFile ? gitFileKey(selectedFile) : null
|
|
131
|
+
const diff = selectedDiffKey ? gitMode.diffs[selectedDiffKey] : undefined
|
|
132
|
+
const loading = selectedDiffKey ? !!gitMode.loading[selectedDiffKey] : false
|
|
147
133
|
|
|
148
134
|
useEffect(() => {
|
|
149
135
|
setGitDiffScroller((delta: number) => {
|
|
150
|
-
const node = diffRef.current
|
|
136
|
+
const node = diffRef.current
|
|
151
137
|
if (!node) return
|
|
152
|
-
const left = node.
|
|
153
|
-
const right = node.
|
|
154
|
-
if (!left
|
|
155
|
-
const cap = Math.max(left.
|
|
156
|
-
const base = left.
|
|
138
|
+
const left = node.leftScroll
|
|
139
|
+
const right = node.rightScroll
|
|
140
|
+
if (!left) return
|
|
141
|
+
const cap = Math.max(left.scrollHeight - left.viewport.height, 0)
|
|
142
|
+
const base = left.scrollTop
|
|
157
143
|
const nextScroll = Math.max(0, Math.min(cap, base + delta))
|
|
158
|
-
left.
|
|
159
|
-
right.
|
|
144
|
+
left.scrollTop = nextScroll
|
|
145
|
+
if (right && right !== left) right.scrollTop = nextScroll
|
|
160
146
|
})
|
|
161
147
|
return () => setGitDiffScroller(null)
|
|
162
148
|
}, [])
|
|
@@ -164,13 +150,15 @@ export const GitView = memo(function GitView() {
|
|
|
164
150
|
useEffect(() => {
|
|
165
151
|
if (focusMode !== 'git') return
|
|
166
152
|
if (!selectedFile || !projectPath) return
|
|
167
|
-
|
|
153
|
+
if (!selectedDiffKey) return
|
|
168
154
|
if (diff || loading) return
|
|
169
|
-
dispatchGlobal({ loading: true,
|
|
170
|
-
void fetchDiff(projectPath, selectedFile)
|
|
171
|
-
.then((d) => dispatchGlobal({ diff: d,
|
|
172
|
-
.catch(() =>
|
|
173
|
-
|
|
155
|
+
dispatchGlobal({ key: selectedDiffKey, loading: true, type: 'git-mode-set-loading' })
|
|
156
|
+
void fetchDiff(projectPath, selectedFile, gitMode.headOffset)
|
|
157
|
+
.then((d) => dispatchGlobal({ diff: d, key: selectedDiffKey, type: 'git-mode-set-diff' }))
|
|
158
|
+
.catch(() =>
|
|
159
|
+
dispatchGlobal({ key: selectedDiffKey, loading: false, type: 'git-mode-set-loading' })
|
|
160
|
+
)
|
|
161
|
+
}, [focusMode, projectPath, selectedFile, selectedDiffKey, diff, loading, gitMode.headOffset])
|
|
174
162
|
|
|
175
163
|
const pendingPath = gitMode.pendingDeletePath
|
|
176
164
|
const pendingIsUntracked =
|
|
@@ -178,7 +166,7 @@ export const GitView = memo(function GitView() {
|
|
|
178
166
|
selectedFile?.path === pendingPath &&
|
|
179
167
|
selectedFile.section === 'untracked'
|
|
180
168
|
let pendingHint: string | null = null
|
|
181
|
-
if (pendingPath !== null) {
|
|
169
|
+
if (pendingPath !== null && selectedFile) {
|
|
182
170
|
pendingHint = pendingIsUntracked
|
|
183
171
|
? 'press d again to delete file'
|
|
184
172
|
: 'press d again to discard changes'
|
|
@@ -187,13 +175,13 @@ export const GitView = memo(function GitView() {
|
|
|
187
175
|
let footerNode: React.ReactNode = null
|
|
188
176
|
if (pendingHint) {
|
|
189
177
|
footerNode = (
|
|
190
|
-
<text fg={theme.
|
|
178
|
+
<text fg={theme.colors['editorWarning.foreground']}>
|
|
191
179
|
<strong>{pendingHint}</strong>
|
|
192
180
|
</text>
|
|
193
181
|
)
|
|
194
182
|
} else if (actionMessage) {
|
|
195
183
|
footerNode = actionMessage.split('\n').map((line, idx) => (
|
|
196
|
-
<text key={idx} fg={theme.
|
|
184
|
+
<text key={idx} fg={theme.colors['textLink.foreground']}>
|
|
197
185
|
{line}
|
|
198
186
|
</text>
|
|
199
187
|
))
|
|
@@ -203,32 +191,57 @@ export const GitView = memo(function GitView() {
|
|
|
203
191
|
<box flexDirection="column" flexGrow={1}>
|
|
204
192
|
<box flexDirection="row" flexGrow={1}>
|
|
205
193
|
<box
|
|
206
|
-
width={
|
|
194
|
+
width={fileBarWidth}
|
|
207
195
|
flexDirection="column"
|
|
208
|
-
backgroundColor={theme.
|
|
196
|
+
backgroundColor={theme.colors['sideBar.background']}
|
|
209
197
|
padding={0}
|
|
210
198
|
gap={0}
|
|
211
199
|
>
|
|
212
|
-
<text fg={theme.
|
|
200
|
+
<text fg={theme.colors['textLink.foreground']}>
|
|
213
201
|
<strong>aimux · git</strong>
|
|
214
202
|
</text>
|
|
215
203
|
{gitPanel.branch ? (
|
|
216
204
|
<box flexDirection="row">
|
|
217
|
-
<text fg={theme.
|
|
218
|
-
<text fg={theme.
|
|
205
|
+
<text fg={theme.colors['textLink.foreground']}>{'\u{e702}'} </text>
|
|
206
|
+
<text fg={theme.colors['descriptionForeground']}>{gitPanel.branch}</text>
|
|
207
|
+
</box>
|
|
208
|
+
) : null}
|
|
209
|
+
{gitMode.headOffset > 0 ? (
|
|
210
|
+
<box flexDirection="row" gap={1}>
|
|
211
|
+
<text fg={theme.colors['editorWarning.foreground']}>
|
|
212
|
+
<strong>HEAD~{gitMode.headOffset}</strong>
|
|
213
|
+
</text>
|
|
214
|
+
<text fg={theme.colors['descriptionForeground']}>[ newer · ] older</text>
|
|
219
215
|
</box>
|
|
220
216
|
) : null}
|
|
221
|
-
<text fg={theme.
|
|
217
|
+
<text fg={theme.colors['editor.lineHighlightBackground']}>
|
|
218
|
+
{'·'.repeat(Math.max(0, fileBarWidth - 2))}
|
|
219
|
+
</text>
|
|
222
220
|
<GitPanel
|
|
221
|
+
collapsedFolders={gitMode.collapsedFolders}
|
|
222
|
+
fileListMode={gitPane.fileListMode}
|
|
223
223
|
gitPanel={gitPanel}
|
|
224
|
+
headOffset={gitMode.headOffset}
|
|
224
225
|
projectPath={projectPath}
|
|
225
|
-
|
|
226
|
+
selectedEntryKey={gitMode.selectedEntryKey}
|
|
226
227
|
/>
|
|
227
228
|
</box>
|
|
228
|
-
<DiffStage
|
|
229
|
+
<DiffStage
|
|
230
|
+
diff={diff}
|
|
231
|
+
diffKey={selectedDiffKey}
|
|
232
|
+
diffRef={diffRef}
|
|
233
|
+
loading={loading}
|
|
234
|
+
themeId={themeId}
|
|
235
|
+
view={gitMode.diffView}
|
|
236
|
+
/>
|
|
229
237
|
</box>
|
|
230
238
|
{footerNode ? (
|
|
231
|
-
<box
|
|
239
|
+
<box
|
|
240
|
+
paddingLeft={1}
|
|
241
|
+
paddingRight={1}
|
|
242
|
+
backgroundColor={theme.colors['sideBar.background']}
|
|
243
|
+
flexDirection="column"
|
|
244
|
+
>
|
|
232
245
|
{footerNode}
|
|
233
246
|
</box>
|
|
234
247
|
) : null}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import type { ModeId } from '@brimveyn/aimux-config'
|
|
2
|
+
|
|
1
3
|
import { useTerminalDimensions } from '@opentui/react'
|
|
2
4
|
import { useLayoutEffect, useMemo, useRef } from 'react'
|
|
3
5
|
|
|
4
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
collectHelpEntries,
|
|
8
|
+
HELP_MODE_LABELS,
|
|
9
|
+
type HelpEntry,
|
|
10
|
+
} from '../../input/keymap/help-entries'
|
|
5
11
|
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
6
12
|
import { useKeymap } from '../keymap-context'
|
|
7
|
-
import {
|
|
13
|
+
import { useTheme } from '../theme'
|
|
8
14
|
import { uiTokens } from '../ui-tokens'
|
|
9
15
|
import { ModalFilterBar } from './modal-filter-bar'
|
|
10
16
|
import { ModalShell } from './modal-shell'
|
|
@@ -12,6 +18,7 @@ import { ModalShell } from './modal-shell'
|
|
|
12
18
|
interface HelpModalProps {
|
|
13
19
|
filter: string | null
|
|
14
20
|
selectedIndex: number
|
|
21
|
+
scope: ModeId | null
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
function matchesFilter(entry: HelpEntry, needle: string): boolean {
|
|
@@ -78,14 +85,24 @@ function computeWindowStart(
|
|
|
78
85
|
return start
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
export function HelpModal({ filter, selectedIndex }: HelpModalProps) {
|
|
88
|
+
export function HelpModal({ filter, scope, selectedIndex }: HelpModalProps) {
|
|
89
|
+
const theme = useTheme()
|
|
82
90
|
const config = useKeymap()
|
|
83
91
|
const dimensions = useTerminalDimensions()
|
|
84
92
|
const allEntries = useMemo(() => collectHelpEntries(config), [config])
|
|
93
|
+
const scoped = useMemo(
|
|
94
|
+
() => (scope ? allEntries.filter((e) => e.mode === scope) : allEntries),
|
|
95
|
+
[allEntries, scope]
|
|
96
|
+
)
|
|
85
97
|
const filtered = useMemo(
|
|
86
|
-
() =>
|
|
87
|
-
[
|
|
98
|
+
() => scoped.filter((e) => matchesFilter(e, filter ?? '')),
|
|
99
|
+
[scoped, filter]
|
|
88
100
|
)
|
|
101
|
+
const title = useMemo(() => {
|
|
102
|
+
if (!scope) return 'Keybindings'
|
|
103
|
+
const label = HELP_MODE_LABELS.find((m) => m.modeId === scope)?.label ?? scope
|
|
104
|
+
return `${label} — keybindings`
|
|
105
|
+
}, [scope])
|
|
89
106
|
const rows = useMemo(() => buildRows(filtered), [filtered])
|
|
90
107
|
|
|
91
108
|
useLayoutEffect(() => {
|
|
@@ -113,12 +130,12 @@ export function HelpModal({ filter, selectedIndex }: HelpModalProps) {
|
|
|
113
130
|
|
|
114
131
|
return (
|
|
115
132
|
<ModalShell
|
|
116
|
-
title=
|
|
133
|
+
title={title}
|
|
117
134
|
keybindsModeId="modal.help"
|
|
118
135
|
width={uiTokens.modalWidth.lg}
|
|
119
136
|
footer={
|
|
120
137
|
<box flexDirection="column" gap={0}>
|
|
121
|
-
<text fg={theme.
|
|
138
|
+
<text fg={theme.colors['editor.lineHighlightBackground']}>
|
|
122
139
|
{filtered.length === 0
|
|
123
140
|
? ''
|
|
124
141
|
: ` ${effectiveIndex + 1} / ${filtered.length}${filter ? '' : ' — type / to filter'}`}
|
|
@@ -128,7 +145,7 @@ export function HelpModal({ filter, selectedIndex }: HelpModalProps) {
|
|
|
128
145
|
}
|
|
129
146
|
>
|
|
130
147
|
{filtered.length === 0 ? (
|
|
131
|
-
<text fg={theme.
|
|
148
|
+
<text fg={theme.colors['descriptionForeground']}>
|
|
132
149
|
{filter ? 'No matching bindings.' : 'No bindings registered.'}
|
|
133
150
|
</text>
|
|
134
151
|
) : (
|
|
@@ -138,14 +155,14 @@ export function HelpModal({ filter, selectedIndex }: HelpModalProps) {
|
|
|
138
155
|
if (row.kind === 'header') {
|
|
139
156
|
return (
|
|
140
157
|
<box key={`h-${rowIndex}`} paddingLeft={1} paddingTop={i === 0 ? 0 : 1}>
|
|
141
|
-
<text fg={theme.
|
|
158
|
+
<text fg={theme.colors['editorWarning.foreground']} wrapMode="none">
|
|
142
159
|
<strong>{row.label}</strong>
|
|
143
160
|
</text>
|
|
144
161
|
</box>
|
|
145
162
|
)
|
|
146
163
|
}
|
|
147
164
|
const active = row.entryIndex === effectiveIndex
|
|
148
|
-
const bg = active ? theme.
|
|
165
|
+
const bg = active ? theme.colors['list.activeSelectionBackground'] : undefined
|
|
149
166
|
return (
|
|
150
167
|
<box
|
|
151
168
|
key={`e-${rowIndex}`}
|
|
@@ -155,12 +172,28 @@ export function HelpModal({ filter, selectedIndex }: HelpModalProps) {
|
|
|
155
172
|
backgroundColor={bg}
|
|
156
173
|
>
|
|
157
174
|
<box width={KEYS_COLUMN_WIDTH} flexShrink={0}>
|
|
158
|
-
<text
|
|
175
|
+
<text
|
|
176
|
+
fg={
|
|
177
|
+
active
|
|
178
|
+
? theme.colors['textLink.foreground']
|
|
179
|
+
: theme.colors['terminal.ansiMagenta']
|
|
180
|
+
}
|
|
181
|
+
bg={bg}
|
|
182
|
+
wrapMode="none"
|
|
183
|
+
>
|
|
159
184
|
{row.entry.keysDisplay}
|
|
160
185
|
</text>
|
|
161
186
|
</box>
|
|
162
187
|
<box flexGrow={1} overflow="hidden">
|
|
163
|
-
<text
|
|
188
|
+
<text
|
|
189
|
+
fg={
|
|
190
|
+
active
|
|
191
|
+
? theme.colors['editor.foreground']
|
|
192
|
+
: theme.colors['descriptionForeground']
|
|
193
|
+
}
|
|
194
|
+
bg={bg}
|
|
195
|
+
wrapMode="none"
|
|
196
|
+
>
|
|
164
197
|
{row.entry.description ?? ''}
|
|
165
198
|
</text>
|
|
166
199
|
</box>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTheme } from '../theme'
|
|
2
2
|
import { Surface } from './surface'
|
|
3
3
|
|
|
4
4
|
interface InputFieldProps {
|
|
@@ -8,7 +8,8 @@ interface InputFieldProps {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function InputField({ active, cursorPos, value }: InputFieldProps) {
|
|
11
|
-
const
|
|
11
|
+
const theme = useTheme()
|
|
12
|
+
const fg = active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
|
|
12
13
|
if (!active) {
|
|
13
14
|
return (
|
|
14
15
|
<Surface tone="input" padding={1}>
|
|
@@ -29,7 +30,7 @@ export function InputField({ active, cursorPos, value }: InputFieldProps) {
|
|
|
29
30
|
<Surface tone="inputActive" padding={1}>
|
|
30
31
|
<text fg={fg}>
|
|
31
32
|
{before}
|
|
32
|
-
<span bg={theme.
|
|
33
|
+
<span bg={theme.colors['editor.foreground']} fg={theme.colors['editor.background']}>
|
|
33
34
|
{cursorDisplay}
|
|
34
35
|
</span>
|
|
35
36
|
{trailing}
|
|
@@ -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
|
|
@@ -33,7 +34,15 @@ export function ListItem({
|
|
|
33
34
|
<box flexDirection="row">
|
|
34
35
|
{isRow ? null : (
|
|
35
36
|
<>
|
|
36
|
-
<text
|
|
37
|
+
<text
|
|
38
|
+
fg={
|
|
39
|
+
active
|
|
40
|
+
? theme.colors['textLink.foreground']
|
|
41
|
+
: theme.colors['editor.lineHighlightBackground']
|
|
42
|
+
}
|
|
43
|
+
>
|
|
44
|
+
{active ? '›' : '·'}
|
|
45
|
+
</text>
|
|
37
46
|
<text> </text>
|
|
38
47
|
</>
|
|
39
48
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTheme } from '../theme'
|
|
2
2
|
import { Surface } from './surface'
|
|
3
3
|
|
|
4
4
|
interface ModalFilterBarProps {
|
|
@@ -6,13 +6,14 @@ 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
|
}
|
|
12
13
|
|
|
13
14
|
return (
|
|
14
15
|
<Surface tone="input" paddingLeft={1} paddingRight={1} paddingTop={1} paddingBottom={1}>
|
|
15
|
-
<text fg={theme.
|
|
16
|
+
<text fg={theme.colors['editor.foreground']}>/{filter}_</text>
|
|
16
17
|
</Surface>
|
|
17
18
|
)
|
|
18
19
|
}
|
|
@@ -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,
|
|
@@ -28,9 +29,9 @@ export function ModalKeybindsOverlay({ limit, modeId }: ModalKeybindsOverlayProp
|
|
|
28
29
|
{entries.map((binding) => (
|
|
29
30
|
<box key={`${binding.keys}-${binding.description}`} flexDirection="row">
|
|
30
31
|
<box width={KEYS_COLUMN_WIDTH}>
|
|
31
|
-
<text fg={theme.
|
|
32
|
+
<text fg={theme.colors['terminal.ansiMagenta']}>{binding.keysDisplay}</text>
|
|
32
33
|
</box>
|
|
33
|
-
<text fg={theme.
|
|
34
|
+
<text fg={theme.colors['descriptionForeground']}>{binding.description ?? ''}</text>
|
|
34
35
|
</box>
|
|
35
36
|
))}
|
|
36
37
|
</Surface>
|
|
@@ -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"
|
|
@@ -40,14 +41,14 @@ export function ModalShell({
|
|
|
40
41
|
left={0}
|
|
41
42
|
width="100%"
|
|
42
43
|
height="100%"
|
|
43
|
-
backgroundColor={theme.
|
|
44
|
+
backgroundColor={theme.colors['editorWidget.background']}
|
|
44
45
|
opacity={0.7}
|
|
45
46
|
/>
|
|
46
47
|
<Surface tone="elevated" padding={1} gap={1} width={width}>
|
|
47
48
|
<box width="100%" flexDirection="column" gap={listGap}>
|
|
48
49
|
<box flexDirection="column">
|
|
49
|
-
<text fg={theme.
|
|
50
|
-
{subtitle ? <text fg={theme.
|
|
50
|
+
<text fg={theme.colors['terminal.ansiMagenta']}>{title}</text>
|
|
51
|
+
{subtitle ? <text fg={theme.colors['descriptionForeground']}>{subtitle}</text> : null}
|
|
51
52
|
</box>
|
|
52
53
|
{children}
|
|
53
54
|
{footer ? <box>{footer}</box> : null}
|
|
@@ -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
|
|
@@ -34,11 +35,23 @@ export function NewTabModal({ customCommands, editBuffer, selectedIndex }: NewTa
|
|
|
34
35
|
<ListItem
|
|
35
36
|
key={option.id}
|
|
36
37
|
active={active}
|
|
37
|
-
title={
|
|
38
|
+
title={
|
|
39
|
+
<text
|
|
40
|
+
fg={
|
|
41
|
+
active
|
|
42
|
+
? theme.colors['editor.foreground']
|
|
43
|
+
: theme.colors['descriptionForeground']
|
|
44
|
+
}
|
|
45
|
+
>
|
|
46
|
+
{option.label}
|
|
47
|
+
</text>
|
|
48
|
+
}
|
|
38
49
|
subtitle={
|
|
39
50
|
<box flexDirection="column">
|
|
40
|
-
<text fg={theme.
|
|
41
|
-
{customCmd ?
|
|
51
|
+
<text fg={theme.colors['descriptionForeground']}>{option.description}</text>
|
|
52
|
+
{customCmd ? (
|
|
53
|
+
<text fg={theme.colors['textLink.foreground']}>{customCmd}</text>
|
|
54
|
+
) : null}
|
|
42
55
|
</box>
|
|
43
56
|
}
|
|
44
57
|
/>
|
|
@@ -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()
|
|
@@ -19,9 +20,9 @@ export function PendingChordOverlay() {
|
|
|
19
20
|
<box position="absolute" bottom={modalOpen ? 10 : 2} right={1}>
|
|
20
21
|
<Surface tone="elevated" paddingLeft={1} paddingRight={1}>
|
|
21
22
|
<box flexDirection="row">
|
|
22
|
-
<text fg={theme.
|
|
23
|
-
<text fg={theme.
|
|
24
|
-
<text fg={theme.
|
|
23
|
+
<text fg={theme.colors['descriptionForeground']}>pending: </text>
|
|
24
|
+
<text fg={theme.colors['textLink.foreground']}>{display}</text>
|
|
25
|
+
<text fg={theme.colors['descriptionForeground']}> …</text>
|
|
25
26
|
</box>
|
|
26
27
|
</Surface>
|
|
27
28
|
</box>
|