@brimveyn/aimux 1.7.1 → 1.7.3
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 +3 -4
- package/src/app-runtime/pty-write.ts +15 -3
- package/src/app-runtime/side-effects.ts +10 -2
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app.tsx +5 -8
- package/src/config.ts +2 -10
- package/src/daemon/daemon.ts +7 -2
- package/src/input/modes/bridge.ts +1 -1
- package/src/input/modes/types.ts +6 -1
- package/src/input/raw-input-handler.ts +12 -3
- package/src/pty/terminal-snapshot.ts +4 -4
- package/src/state/reducers/modal-state.ts +3 -2
- package/src/state/reducers/session-state.ts +0 -7
- package/src/state/types.ts +7 -1
- package/src/ui/components/bare-input.tsx +5 -5
- package/src/ui/components/context-menu-box.tsx +21 -0
- package/src/ui/components/context-menu-overlay.tsx +114 -0
- package/src/ui/components/create-session-modal.tsx +12 -41
- package/src/ui/components/diff-renderer/fold-strip.tsx +7 -7
- package/src/ui/components/diff-renderer/highlight.ts +6 -10
- package/src/ui/components/diff-renderer/pierre-diff.tsx +3 -3
- package/src/ui/components/diff-renderer/prepare-diff.ts +2 -3
- package/src/ui/components/diff-renderer/split-view.tsx +22 -29
- package/src/ui/components/diff-renderer/stacked-view.tsx +18 -31
- package/src/ui/components/diff-renderer/use-diff-prefetch.ts +0 -1
- package/src/ui/components/diff-renderer/use-diff-preparation.ts +1 -1
- package/src/ui/components/git-commit-modal.tsx +4 -16
- package/src/ui/components/git-panel.tsx +37 -73
- package/src/ui/components/git-view.tsx +17 -19
- package/src/ui/components/help-modal.tsx +5 -13
- package/src/ui/components/input-field.tsx +5 -7
- package/src/ui/components/list-item.tsx +3 -11
- package/src/ui/components/modal-keybinds-overlay.tsx +4 -4
- package/src/ui/components/modal-shell.tsx +6 -11
- package/src/ui/components/new-tab-modal.tsx +7 -15
- package/src/ui/components/pending-chord-overlay.tsx +5 -5
- package/src/ui/components/picker.tsx +6 -6
- package/src/ui/components/session-bar.tsx +34 -20
- package/src/ui/components/session-picker-modal.tsx +7 -20
- package/src/ui/components/sidebar.tsx +36 -35
- package/src/ui/components/snippet-editor-modal.tsx +4 -18
- package/src/ui/components/snippet-picker-modal.tsx +5 -9
- package/src/ui/components/split-layout.tsx +3 -3
- package/src/ui/components/status-bar.tsx +12 -13
- package/src/ui/components/surface.tsx +8 -11
- package/src/ui/components/tab-item.tsx +54 -29
- package/src/ui/components/terminal-pane.tsx +59 -25
- package/src/ui/components/theme-picker-modal.tsx +7 -21
- package/src/ui/components/update-available-modal.tsx +3 -13
- package/src/ui/context-menu/controller.ts +34 -0
- package/src/ui/root.tsx +5 -2
- package/src/ui/shiki.ts +29 -21
- package/src/ui/theme-store.ts +110 -21
- package/src/ui/theme.ts +6 -3
- package/src/ui/themes.ts +18 -13
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ThemedToken } from 'shiki'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ensureActiveShikiTheme, ensureShikiLang, getShikiHighlighter } from '../../shiki'
|
|
4
4
|
|
|
5
5
|
export interface HighlightSpan {
|
|
6
6
|
bold?: boolean
|
|
@@ -22,24 +22,20 @@ export function tokenToSpan(token: ThemedToken): HighlightSpan {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export async function tokenizeSide(
|
|
26
|
-
lines: string[],
|
|
27
|
-
lang: string,
|
|
28
|
-
theme: string
|
|
29
|
-
): Promise<ThemedToken[][]> {
|
|
25
|
+
export async function tokenizeSide(lines: string[], lang: string): Promise<ThemedToken[][]> {
|
|
30
26
|
if (lines.length === 0) return []
|
|
31
27
|
const highlighter = await getShikiHighlighter()
|
|
32
|
-
const [langOk,
|
|
28
|
+
const [langOk, themeName] = await Promise.all([
|
|
33
29
|
ensureShikiLang(highlighter, lang),
|
|
34
|
-
|
|
30
|
+
ensureActiveShikiTheme(highlighter),
|
|
35
31
|
])
|
|
36
|
-
if (!langOk
|
|
32
|
+
if (!langOk) return []
|
|
37
33
|
try {
|
|
38
34
|
const result = highlighter.codeToTokens(lines.join(''), {
|
|
39
35
|
// eslint-disable-next-line typescript/no-explicit-any
|
|
40
36
|
lang: lang as any,
|
|
41
37
|
// eslint-disable-next-line typescript/no-explicit-any
|
|
42
|
-
theme:
|
|
38
|
+
theme: themeName as any,
|
|
43
39
|
})
|
|
44
40
|
return result.tokens
|
|
45
41
|
} catch {
|
|
@@ -8,7 +8,7 @@ import type { ThemeId } from '../../themes'
|
|
|
8
8
|
import { useAppStore } from '../../../state/app-store'
|
|
9
9
|
import { dispatchGlobal } from '../../../state/dispatch-ref'
|
|
10
10
|
import { type FoldState } from '../../../state/types'
|
|
11
|
-
import {
|
|
11
|
+
import { useTokens } from '../../theme'
|
|
12
12
|
import { buildSplitRows, buildUnifiedRows, firstChangeRowOffset, gutterWidth } from './build-rows'
|
|
13
13
|
import { SplitView, type SplitViewHandle } from './split-view'
|
|
14
14
|
import { StackedView, type StackedViewHandle } from './stacked-view'
|
|
@@ -45,7 +45,7 @@ export const PierreDiff = forwardRef<PierreDiffHandle, Props>(function PierreDif
|
|
|
45
45
|
{ cacheKey, diff, path, themeId, view },
|
|
46
46
|
ref
|
|
47
47
|
) {
|
|
48
|
-
const
|
|
48
|
+
const t = useTokens()
|
|
49
49
|
const preparation = useDiffPreparation(cacheKey, diff, path, themeId)
|
|
50
50
|
const file = preparation.file ?? undefined
|
|
51
51
|
const highlights: DiffHighlights = preparation.highlights
|
|
@@ -117,7 +117,7 @@ export const PierreDiff = forwardRef<PierreDiffHandle, Props>(function PierreDif
|
|
|
117
117
|
if (!file) {
|
|
118
118
|
return (
|
|
119
119
|
<box flexGrow={1} padding={1}>
|
|
120
|
-
<text fg={
|
|
120
|
+
<text fg={t.muted}>
|
|
121
121
|
{preparation.preparing ? 'Preparing diff…' : '(could not parse diff)'}
|
|
122
122
|
</text>
|
|
123
123
|
</box>
|
|
@@ -14,7 +14,6 @@ export interface PreparedDiff {
|
|
|
14
14
|
|
|
15
15
|
interface PrepareOptions {
|
|
16
16
|
signal?: AbortSignal
|
|
17
|
-
themeId: string
|
|
18
17
|
/** Skip Shiki for big files to keep prepare under a few tens of ms. */
|
|
19
18
|
skipHighlightThreshold?: number
|
|
20
19
|
}
|
|
@@ -55,11 +54,11 @@ export async function prepareDiff(
|
|
|
55
54
|
|
|
56
55
|
await yieldToEventLoop()
|
|
57
56
|
throwIfAborted(opts.signal)
|
|
58
|
-
const add = await tokenizeSide(file.additionLines, filetype
|
|
57
|
+
const add = await tokenizeSide(file.additionLines, filetype)
|
|
59
58
|
throwIfAborted(opts.signal)
|
|
60
59
|
await yieldToEventLoop()
|
|
61
60
|
throwIfAborted(opts.signal)
|
|
62
|
-
const del = await tokenizeSide(file.deletionLines, filetype
|
|
61
|
+
const del = await tokenizeSide(file.deletionLines, filetype)
|
|
63
62
|
throwIfAborted(opts.signal)
|
|
64
63
|
|
|
65
64
|
return { file, filetype, hash, highlights: { add, del } }
|
|
@@ -13,14 +13,11 @@ import type { DiffHighlights, FoldDispatch } from './pierre-diff'
|
|
|
13
13
|
|
|
14
14
|
import { getScrollViewportDelta } from '../../../app-runtime/terminal-mouse-adapter'
|
|
15
15
|
import { scrollGitDiff } from '../../git-view-controls'
|
|
16
|
-
import { useBg,
|
|
16
|
+
import { useBg, useTokens, useTransparent } from '../../theme'
|
|
17
17
|
import { buildSplitRows, gutterWidth, type SplitCell, type SplitRowOrHeader } from './build-rows'
|
|
18
18
|
import { FoldStrip } from './fold-strip'
|
|
19
19
|
import { tokenToSpan } from './highlight'
|
|
20
20
|
|
|
21
|
-
// Cap DOM size for extreme diffs. Rows beyond this are hidden behind a banner;
|
|
22
|
-
// users scroll within the visible window. Shiki highlighting is already skipped
|
|
23
|
-
// upstream in prepare-diff for similarly large diffs.
|
|
24
21
|
const LARGE_ROW_CAP = 5000
|
|
25
22
|
|
|
26
23
|
export interface SplitViewHandle {
|
|
@@ -48,7 +45,7 @@ export const SplitView = forwardRef<SplitViewHandle, Props>(function SplitView(
|
|
|
48
45
|
{ contentWidth, file, foldDispatch, folds, highlights },
|
|
49
46
|
ref
|
|
50
47
|
) {
|
|
51
|
-
const separatorBg = useBg('
|
|
48
|
+
const separatorBg = useBg('base')
|
|
52
49
|
const leftRef = useRef<ScrollBoxRenderable | null>(null)
|
|
53
50
|
const rightRef = useRef<ScrollBoxRenderable | null>(null)
|
|
54
51
|
|
|
@@ -121,13 +118,11 @@ export const SplitView = forwardRef<SplitViewHandle, Props>(function SplitView(
|
|
|
121
118
|
})
|
|
122
119
|
|
|
123
120
|
function TruncationNotice({ hidden }: { hidden: number }) {
|
|
124
|
-
const
|
|
125
|
-
const headerBg = useBg('
|
|
121
|
+
const t = useTokens()
|
|
122
|
+
const headerBg = useBg('elevated')
|
|
126
123
|
return (
|
|
127
124
|
<box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
|
|
128
|
-
<text fg={
|
|
129
|
-
…diff truncated — {hidden} more rows hidden
|
|
130
|
-
</text>
|
|
125
|
+
<text fg={t.palette.warning}>…diff truncated — {hidden} more rows hidden</text>
|
|
131
126
|
</box>
|
|
132
127
|
)
|
|
133
128
|
}
|
|
@@ -154,14 +149,12 @@ function SideRow({
|
|
|
154
149
|
}
|
|
155
150
|
|
|
156
151
|
function HunkHeaderRow({ row }: { row: Extract<SplitRowOrHeader, { type: 'hunk-header' }> }) {
|
|
157
|
-
const
|
|
158
|
-
const headerBg = useBg('
|
|
152
|
+
const t = useTokens()
|
|
153
|
+
const headerBg = useBg('elevated')
|
|
159
154
|
return (
|
|
160
155
|
<box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
|
|
161
|
-
<text fg={
|
|
162
|
-
{row.context ?
|
|
163
|
-
<text fg={theme.colors['editor.lineHighlightBackground']}> {row.context}</text>
|
|
164
|
-
) : null}
|
|
156
|
+
<text fg={t.muted}>{row.spec}</text>
|
|
157
|
+
{row.context ? <text fg={t.hover}> {row.context}</text> : null}
|
|
165
158
|
</box>
|
|
166
159
|
)
|
|
167
160
|
}
|
|
@@ -177,29 +170,29 @@ function HalfRow({
|
|
|
177
170
|
height: number
|
|
178
171
|
tokens: ThemedToken[][]
|
|
179
172
|
}) {
|
|
180
|
-
const
|
|
181
|
-
const headerBg = useBg('
|
|
173
|
+
const t = useTokens()
|
|
174
|
+
const headerBg = useBg('elevated')
|
|
182
175
|
const transparent = useTransparent()
|
|
183
176
|
if (cell.type === 'filler') {
|
|
184
177
|
return <box backgroundColor={headerBg} height={height} />
|
|
185
178
|
}
|
|
186
179
|
let bg: string | undefined
|
|
187
180
|
let sign = ' '
|
|
188
|
-
let signColor =
|
|
181
|
+
let signColor = t.muted
|
|
189
182
|
if (cell.type === 'addition') {
|
|
190
|
-
bg =
|
|
183
|
+
bg = t.diffAddBg
|
|
191
184
|
sign = '+'
|
|
192
|
-
signColor =
|
|
185
|
+
signColor = t.palette.success
|
|
193
186
|
} else if (cell.type === 'deletion') {
|
|
194
|
-
bg =
|
|
187
|
+
bg = t.diffDeleteBg
|
|
195
188
|
sign = '-'
|
|
196
|
-
signColor =
|
|
189
|
+
signColor = t.palette.error
|
|
197
190
|
}
|
|
198
191
|
const num = String(cell.lineNumber).padStart(gw, ' ')
|
|
199
192
|
const lineTokens = tokens[cell.lineIdx]
|
|
200
193
|
return (
|
|
201
194
|
<box flexDirection="row" backgroundColor={transparent ? undefined : bg} height={height}>
|
|
202
|
-
<text fg={
|
|
195
|
+
<text fg={t.muted}>{` ${num} `}</text>
|
|
203
196
|
<text fg={signColor}>{`${sign} `}</text>
|
|
204
197
|
<LineContent content={cell.content} tokens={lineTokens} />
|
|
205
198
|
</box>
|
|
@@ -207,20 +200,20 @@ function HalfRow({
|
|
|
207
200
|
}
|
|
208
201
|
|
|
209
202
|
function LineContent({ content, tokens }: { content: string; tokens: ThemedToken[] | undefined }) {
|
|
210
|
-
const
|
|
203
|
+
const t = useTokens()
|
|
211
204
|
if (!tokens || tokens.length === 0) {
|
|
212
|
-
return <text fg={
|
|
205
|
+
return <text fg={t.palette.ink}>{content}</text>
|
|
213
206
|
}
|
|
214
207
|
return (
|
|
215
208
|
<text>
|
|
216
|
-
{tokens.map((
|
|
217
|
-
const s = tokenToSpan(
|
|
209
|
+
{tokens.map((tok, i) => {
|
|
210
|
+
const s = tokenToSpan(tok)
|
|
218
211
|
let attributes = 0
|
|
219
212
|
if (s.bold) attributes |= TextAttributes.BOLD
|
|
220
213
|
if (s.italic) attributes |= TextAttributes.ITALIC
|
|
221
214
|
if (s.underline) attributes |= TextAttributes.UNDERLINE
|
|
222
215
|
return (
|
|
223
|
-
<span key={i} fg={s.fg ??
|
|
216
|
+
<span key={i} fg={s.fg ?? t.palette.ink} attributes={attributes}>
|
|
224
217
|
{s.text}
|
|
225
218
|
</span>
|
|
226
219
|
)
|
|
@@ -13,12 +13,11 @@ import type { DiffHighlights, FoldDispatch } from './pierre-diff'
|
|
|
13
13
|
|
|
14
14
|
import { getScrollViewportDelta } from '../../../app-runtime/terminal-mouse-adapter'
|
|
15
15
|
import { scrollGitDiff } from '../../git-view-controls'
|
|
16
|
-
import { useBg,
|
|
16
|
+
import { useBg, useTokens, useTransparent } from '../../theme'
|
|
17
17
|
import { buildUnifiedRows, gutterWidth, type UnifiedRowOrHeader } from './build-rows'
|
|
18
18
|
import { FoldStrip } from './fold-strip'
|
|
19
19
|
import { tokenToSpan } from './highlight'
|
|
20
20
|
|
|
21
|
-
// Same cap as split-view — keeps the React child count bounded.
|
|
22
21
|
const LARGE_ROW_CAP = 5000
|
|
23
22
|
|
|
24
23
|
export interface StackedViewHandle {
|
|
@@ -89,13 +88,11 @@ export const StackedView = forwardRef<StackedViewHandle, Props>(function Stacked
|
|
|
89
88
|
})
|
|
90
89
|
|
|
91
90
|
function TruncationNotice({ hidden }: { hidden: number }) {
|
|
92
|
-
const
|
|
93
|
-
const headerBg = useBg('
|
|
91
|
+
const t = useTokens()
|
|
92
|
+
const headerBg = useBg('elevated')
|
|
94
93
|
return (
|
|
95
94
|
<box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
|
|
96
|
-
<text fg={
|
|
97
|
-
…diff truncated — {hidden} more rows hidden
|
|
98
|
-
</text>
|
|
95
|
+
<text fg={t.palette.warning}>…diff truncated — {hidden} more rows hidden</text>
|
|
99
96
|
</box>
|
|
100
97
|
)
|
|
101
98
|
}
|
|
@@ -111,16 +108,14 @@ function UnifiedRowRender({
|
|
|
111
108
|
highlights: DiffHighlights
|
|
112
109
|
row: UnifiedRowOrHeader
|
|
113
110
|
}) {
|
|
114
|
-
const
|
|
115
|
-
const headerBg = useBg('
|
|
111
|
+
const t = useTokens()
|
|
112
|
+
const headerBg = useBg('elevated')
|
|
116
113
|
const transparent = useTransparent()
|
|
117
114
|
if (row.type === 'hunk-header') {
|
|
118
115
|
return (
|
|
119
116
|
<box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
|
|
120
|
-
<text fg={
|
|
121
|
-
{row.context ?
|
|
122
|
-
<text fg={theme.colors['editor.lineHighlightBackground']}> {row.context}</text>
|
|
123
|
-
) : null}
|
|
117
|
+
<text fg={t.muted}>{row.spec}</text>
|
|
118
|
+
{row.context ? <text fg={t.hover}> {row.context}</text> : null}
|
|
124
119
|
</box>
|
|
125
120
|
)
|
|
126
121
|
}
|
|
@@ -133,29 +128,21 @@ function UnifiedRowRender({
|
|
|
133
128
|
const tokens = highlights.add[row.lineIdx]
|
|
134
129
|
return (
|
|
135
130
|
<box flexDirection="row" height={row.height}>
|
|
136
|
-
<text
|
|
137
|
-
|
|
138
|
-
>{` ${pad(row.delLineNumber)} ${pad(row.addLineNumber)} `}</text>
|
|
139
|
-
<text fg={theme.colors['editor.foreground']}> </text>
|
|
131
|
+
<text fg={t.muted}>{` ${pad(row.delLineNumber)} ${pad(row.addLineNumber)} `}</text>
|
|
132
|
+
<text fg={t.palette.ink}> </text>
|
|
140
133
|
<LineContent content={row.content} tokens={tokens} />
|
|
141
134
|
</box>
|
|
142
135
|
)
|
|
143
136
|
}
|
|
144
|
-
const bg =
|
|
145
|
-
row.type === 'addition'
|
|
146
|
-
? theme.colors['diffEditor.insertedLineBackground']
|
|
147
|
-
: theme.colors['diffEditor.removedLineBackground']
|
|
137
|
+
const bg = row.type === 'addition' ? t.diffAddBg : t.diffDeleteBg
|
|
148
138
|
const sign = row.type === 'addition' ? '+' : '-'
|
|
149
|
-
const signColor =
|
|
150
|
-
row.type === 'addition'
|
|
151
|
-
? theme.colors['gitDecoration.addedResourceForeground']
|
|
152
|
-
: theme.colors['editorError.foreground']
|
|
139
|
+
const signColor = row.type === 'addition' ? t.palette.success : t.palette.error
|
|
153
140
|
const delNum = row.type === 'deletion' ? row.lineNumber : undefined
|
|
154
141
|
const addNum = row.type === 'addition' ? row.lineNumber : undefined
|
|
155
142
|
const tokens = row.type === 'addition' ? highlights.add[row.lineIdx] : highlights.del[row.lineIdx]
|
|
156
143
|
return (
|
|
157
144
|
<box flexDirection="row" backgroundColor={transparent ? undefined : bg} height={row.height}>
|
|
158
|
-
<text fg={
|
|
145
|
+
<text fg={t.muted}>{` ${pad(delNum)} ${pad(addNum)} `}</text>
|
|
159
146
|
<text fg={signColor}>{`${sign} `}</text>
|
|
160
147
|
<LineContent content={row.content} tokens={tokens} />
|
|
161
148
|
</box>
|
|
@@ -163,20 +150,20 @@ function UnifiedRowRender({
|
|
|
163
150
|
}
|
|
164
151
|
|
|
165
152
|
function LineContent({ content, tokens }: { content: string; tokens: ThemedToken[] | undefined }) {
|
|
166
|
-
const
|
|
153
|
+
const t = useTokens()
|
|
167
154
|
if (!tokens || tokens.length === 0) {
|
|
168
|
-
return <text fg={
|
|
155
|
+
return <text fg={t.palette.ink}>{content}</text>
|
|
169
156
|
}
|
|
170
157
|
return (
|
|
171
158
|
<text>
|
|
172
|
-
{tokens.map((
|
|
173
|
-
const s = tokenToSpan(
|
|
159
|
+
{tokens.map((tok, i) => {
|
|
160
|
+
const s = tokenToSpan(tok)
|
|
174
161
|
let attributes = 0
|
|
175
162
|
if (s.bold) attributes |= TextAttributes.BOLD
|
|
176
163
|
if (s.italic) attributes |= TextAttributes.ITALIC
|
|
177
164
|
if (s.underline) attributes |= TextAttributes.UNDERLINE
|
|
178
165
|
return (
|
|
179
|
-
<span key={i} fg={s.fg ??
|
|
166
|
+
<span key={i} fg={s.fg ?? t.palette.ink} attributes={attributes}>
|
|
180
167
|
{s.text}
|
|
181
168
|
</span>
|
|
182
169
|
)
|
|
@@ -108,7 +108,6 @@ export function useDiffPrefetch(
|
|
|
108
108
|
dispatchGlobal({ diff, hash, key: task.key, type: 'git-mode-set-diff' })
|
|
109
109
|
const prep = await prepareDiff(diff.rawDiff, task.file.path, {
|
|
110
110
|
signal: task.controller.signal,
|
|
111
|
-
themeId,
|
|
112
111
|
})
|
|
113
112
|
if (task.controller.signal.aborted) return
|
|
114
113
|
dispatchGlobal({
|
|
@@ -68,7 +68,7 @@ export function useDiffPreparation(
|
|
|
68
68
|
|
|
69
69
|
const controller = new AbortController()
|
|
70
70
|
setPreparing(true)
|
|
71
|
-
void prepareDiff(diff, path, { signal: controller.signal
|
|
71
|
+
void prepareDiff(diff, path, { signal: controller.signal })
|
|
72
72
|
.then((result) => {
|
|
73
73
|
if (controller.signal.aborted) return
|
|
74
74
|
setLocalFile(result.file)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTokens } from '../theme'
|
|
2
2
|
import { uiTokens } from '../ui-tokens'
|
|
3
3
|
import { InputField } from './input-field'
|
|
4
4
|
import { ModalShell } from './modal-shell'
|
|
@@ -11,20 +11,14 @@ interface GitCommitModalProps {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function GitCommitModal({ activeField, body, cursorPos, title }: GitCommitModalProps) {
|
|
14
|
-
const
|
|
14
|
+
const t = useTokens()
|
|
15
15
|
const titleActive = activeField === 'title'
|
|
16
16
|
const bodyActive = activeField === 'body'
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<ModalShell title="Commit" keybindsModeId="modal.git-commit" width={uiTokens.modalWidth.xl}>
|
|
20
20
|
<box flexDirection="column">
|
|
21
|
-
<text
|
|
22
|
-
fg={
|
|
23
|
-
titleActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
|
|
24
|
-
}
|
|
25
|
-
>
|
|
26
|
-
Title
|
|
27
|
-
</text>
|
|
21
|
+
<text fg={titleActive ? t.palette.ink : t.muted}>Title</text>
|
|
28
22
|
<InputField
|
|
29
23
|
active={titleActive}
|
|
30
24
|
cursorPos={titleActive ? cursorPos : undefined}
|
|
@@ -33,13 +27,7 @@ export function GitCommitModal({ activeField, body, cursorPos, title }: GitCommi
|
|
|
33
27
|
</box>
|
|
34
28
|
|
|
35
29
|
<box flexDirection="column">
|
|
36
|
-
<text
|
|
37
|
-
fg={
|
|
38
|
-
bodyActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
|
|
39
|
-
}
|
|
40
|
-
>
|
|
41
|
-
Body (optional)
|
|
42
|
-
</text>
|
|
30
|
+
<text fg={bodyActive ? t.palette.ink : t.muted}>Body (optional)</text>
|
|
43
31
|
<InputField
|
|
44
32
|
active={bodyActive}
|
|
45
33
|
cursorPos={bodyActive ? cursorPos : undefined}
|
|
@@ -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 { getCurrentTokens, getTransparent, useTokens, useTransparent } from '../theme'
|
|
17
17
|
|
|
18
18
|
interface GitPanelProps {
|
|
19
19
|
collapsedFolders?: Record<string, true>
|
|
@@ -44,19 +44,19 @@ const BASE_SECTION_ORDER: GitFileSection[] = ['staged', 'unstaged', 'untracked']
|
|
|
44
44
|
const HISTORICAL_SECTION_ORDER: GitFileSection[] = ['historical', 'untracked']
|
|
45
45
|
|
|
46
46
|
function statusColor(status: GitFileEntry['status']): string {
|
|
47
|
-
const t =
|
|
47
|
+
const t = getCurrentTokens()
|
|
48
48
|
switch (status) {
|
|
49
49
|
case '?':
|
|
50
50
|
case 'A':
|
|
51
|
-
return t.
|
|
51
|
+
return t.palette.success
|
|
52
52
|
case 'C':
|
|
53
53
|
case 'R':
|
|
54
|
-
return t.
|
|
54
|
+
return t.palette.primary
|
|
55
55
|
case 'D':
|
|
56
56
|
case 'U':
|
|
57
|
-
return t.
|
|
57
|
+
return t.palette.error
|
|
58
58
|
case 'M':
|
|
59
|
-
return t.
|
|
59
|
+
return t.palette.warning
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -94,6 +94,7 @@ function renderFileLabel(
|
|
|
94
94
|
pathConfig: GitPanePathConfig,
|
|
95
95
|
fileListMode: GitFileListMode
|
|
96
96
|
): ReactNode {
|
|
97
|
+
const t = getCurrentTokens()
|
|
97
98
|
const transform = pathConfig.enabled ? pathConfig.pathFn : undefined
|
|
98
99
|
const displayPath = transform ? transform(file.path) : file.path
|
|
99
100
|
const { basename, prefix } = splitPath(displayPath)
|
|
@@ -102,10 +103,8 @@ function renderFileLabel(
|
|
|
102
103
|
if (!file.renamedFrom) {
|
|
103
104
|
return (
|
|
104
105
|
<text wrapMode="none">
|
|
105
|
-
<span fg={
|
|
106
|
-
{showDir ?
|
|
107
|
-
<span fg={getCurrentTheme().colors['descriptionForeground']}> {dir}</span>
|
|
108
|
-
) : null}
|
|
106
|
+
<span fg={t.palette.ink}>{basename}</span>
|
|
107
|
+
{showDir ? <span fg={t.muted}> {dir}</span> : null}
|
|
109
108
|
</text>
|
|
110
109
|
)
|
|
111
110
|
}
|
|
@@ -114,13 +113,11 @@ function renderFileLabel(
|
|
|
114
113
|
const renamedDir = stripTrailingSlash(renamed.prefix)
|
|
115
114
|
return (
|
|
116
115
|
<text wrapMode="none">
|
|
117
|
-
<span fg={
|
|
118
|
-
{showDir && renamedDir ?
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<span fg={
|
|
122
|
-
<span fg={getCurrentTheme().colors['editor.foreground']}>{basename}</span>
|
|
123
|
-
{showDir ? <span fg={getCurrentTheme().colors['descriptionForeground']}> {dir}</span> : null}
|
|
116
|
+
<span fg={t.muted}>{renamed.basename}</span>
|
|
117
|
+
{showDir && renamedDir ? <span fg={t.muted}> {renamedDir}</span> : null}
|
|
118
|
+
<span fg={t.muted}> → </span>
|
|
119
|
+
<span fg={t.palette.ink}>{basename}</span>
|
|
120
|
+
{showDir ? <span fg={t.muted}> {dir}</span> : null}
|
|
124
121
|
</text>
|
|
125
122
|
)
|
|
126
123
|
}
|
|
@@ -134,35 +131,28 @@ function renderDiffCount(
|
|
|
134
131
|
hasNumstat: boolean
|
|
135
132
|
): ReactNode {
|
|
136
133
|
if (!diffCountConfig.enabled) return null
|
|
134
|
+
const t = getCurrentTokens()
|
|
137
135
|
if (!hasNumstat) {
|
|
138
136
|
return (
|
|
139
|
-
<text fg={
|
|
137
|
+
<text fg={t.muted} bg={bg} flexShrink={0}>
|
|
140
138
|
—
|
|
141
139
|
</text>
|
|
142
140
|
)
|
|
143
141
|
}
|
|
144
142
|
return (
|
|
145
143
|
<box flexDirection="row" flexShrink={0}>
|
|
146
|
-
<text
|
|
147
|
-
|
|
148
|
-
bg={bg}
|
|
149
|
-
>{`+${padRight(file.added, addedW)}`}</text>
|
|
150
|
-
<text fg={getCurrentTheme().colors['editor.lineHighlightBackground']} bg={bg}>
|
|
144
|
+
<text fg={t.palette.success} bg={bg}>{`+${padRight(file.added, addedW)}`}</text>
|
|
145
|
+
<text fg={t.hover} bg={bg}>
|
|
151
146
|
{' '}
|
|
152
147
|
</text>
|
|
153
|
-
<text
|
|
154
|
-
fg={getCurrentTheme().colors['editorError.foreground']}
|
|
155
|
-
bg={bg}
|
|
156
|
-
>{`−${padRight(file.removed, removedW)}`}</text>
|
|
148
|
+
<text fg={t.palette.error} bg={bg}>{`−${padRight(file.removed, removedW)}`}</text>
|
|
157
149
|
</box>
|
|
158
150
|
)
|
|
159
151
|
}
|
|
160
152
|
|
|
161
153
|
function renderFolderRow(row: GitTreeFolderRow, isSelected: boolean): ReactNode {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
? getCurrentTheme().colors['list.activeSelectionBackground']
|
|
165
|
-
: undefined
|
|
154
|
+
const t = getCurrentTokens()
|
|
155
|
+
const bg = isSelected && !getTransparent() ? t.selected : undefined
|
|
166
156
|
const onSelect = (): void => {
|
|
167
157
|
dispatchGlobal({ key: row.key, type: 'git-mode-select-entry-by-key' })
|
|
168
158
|
}
|
|
@@ -173,13 +163,13 @@ function renderFolderRow(row: GitTreeFolderRow, isSelected: boolean): ReactNode
|
|
|
173
163
|
<box key={row.key} flexDirection="row" gap={1} backgroundColor={bg} onMouseDown={onSelect}>
|
|
174
164
|
<box flexGrow={1} overflow="hidden" paddingLeft={row.depth * 2}>
|
|
175
165
|
<box flexDirection="row" gap={1} onMouseDown={onToggle}>
|
|
176
|
-
<text fg={
|
|
166
|
+
<text fg={t.muted} bg={bg}>
|
|
177
167
|
{row.isCollapsed ? '▸' : '▾'}
|
|
178
168
|
</text>
|
|
179
|
-
<text fg={
|
|
169
|
+
<text fg={t.palette.primary} bg={bg}>
|
|
180
170
|
{row.isCollapsed ? '\uf07b' : '\uf07c'}
|
|
181
171
|
</text>
|
|
182
|
-
<text fg={
|
|
172
|
+
<text fg={t.palette.primary} bg={bg} wrapMode="none">
|
|
183
173
|
{row.name}
|
|
184
174
|
</text>
|
|
185
175
|
</box>
|
|
@@ -199,10 +189,7 @@ function renderFileRow(
|
|
|
199
189
|
): ReactNode {
|
|
200
190
|
const file = row.file
|
|
201
191
|
const hasNumstat = file.added !== null || file.removed !== null
|
|
202
|
-
const bg =
|
|
203
|
-
isSelected && !getTransparent()
|
|
204
|
-
? getCurrentTheme().colors['list.activeSelectionBackground']
|
|
205
|
-
: undefined
|
|
192
|
+
const bg = isSelected && !getTransparent() ? getCurrentTokens().selected : undefined
|
|
206
193
|
const onSelect = (): void => {
|
|
207
194
|
dispatchGlobal({ key: row.key, type: 'git-mode-select-entry-by-key' })
|
|
208
195
|
}
|
|
@@ -236,6 +223,7 @@ function renderTreeSection(
|
|
|
236
223
|
marginTop: number
|
|
237
224
|
): ReactNode {
|
|
238
225
|
if (files.length === 0) return null
|
|
226
|
+
const t = getCurrentTokens()
|
|
239
227
|
const nextFileListMode = fileListMode === 'tree' ? 'flat' : 'tree'
|
|
240
228
|
const toggleListMode = (): void => {
|
|
241
229
|
dispatchGlobal({ type: 'git-mode-toggle-file-list-mode' })
|
|
@@ -244,32 +232,16 @@ function renderTreeSection(
|
|
|
244
232
|
return (
|
|
245
233
|
<box key={section} flexDirection="column" marginTop={marginTop}>
|
|
246
234
|
<box flexDirection="row" justifyContent="space-between">
|
|
247
|
-
<text fg={
|
|
235
|
+
<text fg={t.accent}>
|
|
248
236
|
<strong>
|
|
249
237
|
{title} ({files.length})
|
|
250
238
|
</strong>
|
|
251
239
|
</text>
|
|
252
240
|
{showListModeToggle ? (
|
|
253
241
|
<box flexDirection="row" gap={1} onMouseDown={toggleListMode}>
|
|
254
|
-
<text
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
? getCurrentTheme().colors['textLink.foreground']
|
|
258
|
-
: getCurrentTheme().colors['descriptionForeground']
|
|
259
|
-
}
|
|
260
|
-
>
|
|
261
|
-
tree
|
|
262
|
-
</text>
|
|
263
|
-
<text fg={getCurrentTheme().colors['descriptionForeground']}>|</text>
|
|
264
|
-
<text
|
|
265
|
-
fg={
|
|
266
|
-
fileListMode === 'flat'
|
|
267
|
-
? getCurrentTheme().colors['textLink.foreground']
|
|
268
|
-
: getCurrentTheme().colors['descriptionForeground']
|
|
269
|
-
}
|
|
270
|
-
>
|
|
271
|
-
flat
|
|
272
|
-
</text>
|
|
242
|
+
<text fg={fileListMode === 'tree' ? t.palette.primary : t.muted}>tree</text>
|
|
243
|
+
<text fg={t.muted}>|</text>
|
|
244
|
+
<text fg={fileListMode === 'flat' ? t.palette.primary : t.muted}>flat</text>
|
|
273
245
|
</box>
|
|
274
246
|
) : null}
|
|
275
247
|
</box>
|
|
@@ -309,26 +281,18 @@ function computeStatusPlaceholder(
|
|
|
309
281
|
gitPanel: GitPanelState,
|
|
310
282
|
hasProjectPath: boolean
|
|
311
283
|
): StatusPlaceholder | null {
|
|
284
|
+
const t = getCurrentTokens()
|
|
312
285
|
if (!hasProjectPath) {
|
|
313
|
-
return {
|
|
314
|
-
label: 'No active session',
|
|
315
|
-
labelColor: getCurrentTheme().colors['descriptionForeground'],
|
|
316
|
-
}
|
|
286
|
+
return { label: 'No active session', labelColor: t.muted }
|
|
317
287
|
}
|
|
318
288
|
if (gitPanel.error === 'not-a-repo') {
|
|
319
|
-
return {
|
|
320
|
-
label: 'Not a git repository',
|
|
321
|
-
labelColor: getCurrentTheme().colors['descriptionForeground'],
|
|
322
|
-
}
|
|
289
|
+
return { label: 'Not a git repository', labelColor: t.muted }
|
|
323
290
|
}
|
|
324
291
|
if (gitPanel.error === 'unknown') {
|
|
325
|
-
return { label: 'Git error', labelColor:
|
|
292
|
+
return { label: 'Git error', labelColor: t.palette.error }
|
|
326
293
|
}
|
|
327
294
|
if (gitPanel.files.length === 0) {
|
|
328
|
-
return {
|
|
329
|
-
label: 'Working tree clean',
|
|
330
|
-
labelColor: getCurrentTheme().colors['descriptionForeground'],
|
|
331
|
-
}
|
|
295
|
+
return { label: 'Working tree clean', labelColor: t.muted }
|
|
332
296
|
}
|
|
333
297
|
return null
|
|
334
298
|
}
|
|
@@ -347,7 +311,7 @@ export const GitPanel = memo(function GitPanel({
|
|
|
347
311
|
projectPath,
|
|
348
312
|
selectedEntryKey,
|
|
349
313
|
}: GitPanelProps) {
|
|
350
|
-
const
|
|
314
|
+
const t = useTokens()
|
|
351
315
|
useTransparent()
|
|
352
316
|
const sectionOrder = headOffset > 0 ? HISTORICAL_SECTION_ORDER : BASE_SECTION_ORDER
|
|
353
317
|
const scrollRef = useRef<ScrollBoxRenderable | null>(null)
|
|
@@ -382,7 +346,7 @@ export const GitPanel = memo(function GitPanel({
|
|
|
382
346
|
return (
|
|
383
347
|
<box flexDirection="column" flexGrow={1} gap={0}>
|
|
384
348
|
{hasRemoteTracking ? (
|
|
385
|
-
<text fg={
|
|
349
|
+
<text fg={t.muted}>
|
|
386
350
|
↑{gitPanel.ahead} ↓{gitPanel.behind}
|
|
387
351
|
</text>
|
|
388
352
|
) : null}
|