@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
|
@@ -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)
|
|
@@ -108,7 +109,7 @@ export function SessionBar() {
|
|
|
108
109
|
flexDirection="row"
|
|
109
110
|
paddingLeft={1}
|
|
110
111
|
paddingRight={1}
|
|
111
|
-
backgroundColor={theme.
|
|
112
|
+
backgroundColor={theme.colors['sideBarSectionHeader.background']}
|
|
112
113
|
>
|
|
113
114
|
{visibleSessions.map((session) => {
|
|
114
115
|
const displayIndex = baselineOrder.indexOf(session.id) + 1
|
|
@@ -165,12 +166,18 @@ 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 : '●'
|
|
171
|
-
const indicatorColor =
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
const indicatorColor =
|
|
174
|
+
active || showSpinner
|
|
175
|
+
? theme.colors['textLink.foreground']
|
|
176
|
+
: theme.colors['gitDecoration.addedResourceForeground']
|
|
177
|
+
const labelColor = active
|
|
178
|
+
? theme.colors['editor.foreground']
|
|
179
|
+
: theme.colors['descriptionForeground']
|
|
180
|
+
const bgColor = dragging || active ? theme.colors['list.activeSelectionBackground'] : undefined
|
|
174
181
|
|
|
175
182
|
return (
|
|
176
183
|
<box
|
|
@@ -200,7 +207,7 @@ function SessionChip({
|
|
|
200
207
|
<text fg={labelColor} selectable={false}>
|
|
201
208
|
[{index}] {session.name}
|
|
202
209
|
</text>
|
|
203
|
-
<text fg={theme.
|
|
210
|
+
<text fg={theme.colors['editor.lineHighlightBackground']} selectable={false}>
|
|
204
211
|
{' '}
|
|
205
212
|
</text>
|
|
206
213
|
</box>
|
|
@@ -2,7 +2,8 @@ import type { SessionRecord } from '../../state/types'
|
|
|
2
2
|
|
|
3
3
|
import { filterSessions } from '../../state/selectors'
|
|
4
4
|
import { abbreviatePath } from '../path-format'
|
|
5
|
-
import {
|
|
5
|
+
import { orderSessionsForDisplay } from '../session-ordering'
|
|
6
|
+
import { useTheme } from '../theme'
|
|
6
7
|
import { uiTokens } from '../ui-tokens'
|
|
7
8
|
import { ListItem } from './list-item'
|
|
8
9
|
import { ModalFilterBar } from './modal-filter-bar'
|
|
@@ -19,13 +20,14 @@ interface SessionPickerModalProps {
|
|
|
19
20
|
function formatSessionLine(
|
|
20
21
|
session: SessionRecord,
|
|
21
22
|
currentSessionId: string | null,
|
|
22
|
-
currentTabCount: number
|
|
23
|
+
currentTabCount: number,
|
|
24
|
+
displayIndex: number
|
|
23
25
|
): string {
|
|
24
26
|
const tabCount =
|
|
25
27
|
session.id === currentSessionId
|
|
26
28
|
? currentTabCount
|
|
27
29
|
: (session.workspaceSnapshot?.tabs.length ?? 0)
|
|
28
|
-
return
|
|
30
|
+
return `[${displayIndex}] ${session.name} (${tabCount} tab${tabCount === 1 ? '' : 's'})`
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
function getEmptyStateMessage(hasFilter: boolean): string {
|
|
@@ -43,7 +45,10 @@ export function SessionPickerModal({
|
|
|
43
45
|
selectedIndex,
|
|
44
46
|
sessions,
|
|
45
47
|
}: SessionPickerModalProps) {
|
|
46
|
-
const
|
|
48
|
+
const theme = useTheme()
|
|
49
|
+
const ordered = orderSessionsForDisplay(sessions)
|
|
50
|
+
const baselineOrder = ordered.map((s) => s.id)
|
|
51
|
+
const filtered = filterSessions(ordered, filter)
|
|
47
52
|
const hasFilter = !!filter
|
|
48
53
|
const showFilteredEmptyState = filtered.length === 0 && sessions.length > 0
|
|
49
54
|
const showInitialEmptyState = filtered.length === 0 && sessions.length === 0
|
|
@@ -56,25 +61,32 @@ export function SessionPickerModal({
|
|
|
56
61
|
footer={<ModalFilterBar filter={filter} />}
|
|
57
62
|
>
|
|
58
63
|
{showFilteredEmptyState ? (
|
|
59
|
-
<text fg={theme.
|
|
64
|
+
<text fg={theme.colors['descriptionForeground']}>{getEmptyStateMessage(hasFilter)}</text>
|
|
60
65
|
) : null}
|
|
61
66
|
{showInitialEmptyState ? (
|
|
62
|
-
<text fg={theme.
|
|
67
|
+
<text fg={theme.colors['descriptionForeground']}>{getEmptyStateMessage(false)}</text>
|
|
63
68
|
) : null}
|
|
64
69
|
{filtered.map((session, index) => {
|
|
65
70
|
const active = index === selectedIndex
|
|
71
|
+
const displayIndex = baselineOrder.indexOf(session.id) + 1
|
|
66
72
|
return (
|
|
67
73
|
<ListItem
|
|
68
74
|
key={session.id}
|
|
69
75
|
active={active}
|
|
70
76
|
title={
|
|
71
|
-
<text
|
|
72
|
-
{
|
|
77
|
+
<text
|
|
78
|
+
fg={
|
|
79
|
+
active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
|
|
80
|
+
}
|
|
81
|
+
>
|
|
82
|
+
{formatSessionLine(session, currentSessionId, currentTabCount, displayIndex)}
|
|
73
83
|
</text>
|
|
74
84
|
}
|
|
75
85
|
subtitle={
|
|
76
86
|
session.projectPath ? (
|
|
77
|
-
<text fg={theme.
|
|
87
|
+
<text fg={theme.colors['descriptionForeground']}>
|
|
88
|
+
{abbreviatePath(session.projectPath)}
|
|
89
|
+
</text>
|
|
78
90
|
) : undefined
|
|
79
91
|
}
|
|
80
92
|
/>
|
|
@@ -83,7 +95,13 @@ export function SessionPickerModal({
|
|
|
83
95
|
<ListItem
|
|
84
96
|
active={selectedIndex === filtered.length}
|
|
85
97
|
title={
|
|
86
|
-
<text
|
|
98
|
+
<text
|
|
99
|
+
fg={
|
|
100
|
+
selectedIndex === filtered.length
|
|
101
|
+
? theme.colors['editor.foreground']
|
|
102
|
+
: theme.colors['descriptionForeground']
|
|
103
|
+
}
|
|
104
|
+
>
|
|
87
105
|
Create new session
|
|
88
106
|
</text>
|
|
89
107
|
}
|
|
@@ -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)
|
|
@@ -31,19 +32,21 @@ const SidebarTop = memo(function SidebarTop() {
|
|
|
31
32
|
|
|
32
33
|
return (
|
|
33
34
|
<box flexDirection="column" flexShrink={0} gap={0}>
|
|
34
|
-
<text fg={theme.
|
|
35
|
+
<text fg={theme.colors['textLink.foreground']}>
|
|
35
36
|
<strong>aimux</strong>
|
|
36
37
|
</text>
|
|
37
|
-
<text fg={theme.
|
|
38
|
+
<text fg={theme.colors['terminal.ansiMagenta']}>
|
|
38
39
|
{currentSession ? currentSession.name : 'No session selected'}
|
|
39
40
|
</text>
|
|
40
41
|
{branch ? (
|
|
41
42
|
<box flexDirection="row">
|
|
42
|
-
<text fg={theme.
|
|
43
|
-
<text fg={theme.
|
|
43
|
+
<text fg={theme.colors['textLink.foreground']}>{'\u{e702}'} </text>
|
|
44
|
+
<text fg={theme.colors['descriptionForeground']}>{branch}</text>
|
|
44
45
|
</box>
|
|
45
46
|
) : null}
|
|
46
|
-
<text fg={theme.
|
|
47
|
+
<text fg={theme.colors['editor.lineHighlightBackground']}>
|
|
48
|
+
{'·'.repeat(Math.max(0, sidebarWidth - 2))}
|
|
49
|
+
</text>
|
|
47
50
|
</box>
|
|
48
51
|
)
|
|
49
52
|
})
|
|
@@ -56,11 +59,17 @@ function renderGroupGutter(
|
|
|
56
59
|
) {
|
|
57
60
|
return (
|
|
58
61
|
<box flexDirection="column" width={1} overflow="hidden">
|
|
59
|
-
<text
|
|
62
|
+
<text
|
|
63
|
+
fg={getCurrentTheme().colors['terminal.ansiMagenta']}
|
|
64
|
+
bg={isActive ? getCurrentTheme().colors['list.activeSelectionBackground'] : undefined}
|
|
65
|
+
>
|
|
60
66
|
{/* oxlint-disable-next-line no-nested-ternary */}
|
|
61
67
|
{isGroupStart ? GUTTER_START : isGroupMiddle ? GUTTER_MIDDLE : GUTTER_PAD}
|
|
62
68
|
</text>
|
|
63
|
-
<text
|
|
69
|
+
<text
|
|
70
|
+
fg={getCurrentTheme().colors['terminal.ansiMagenta']}
|
|
71
|
+
bg={isActive ? getCurrentTheme().colors['list.activeSelectionBackground'] : undefined}
|
|
72
|
+
>
|
|
64
73
|
{isGroupEnd ? GUTTER_END : GUTTER_PAD}
|
|
65
74
|
</text>
|
|
66
75
|
</box>
|
|
@@ -72,6 +81,7 @@ interface TabsBodyProps {
|
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
84
|
+
const theme = useTheme()
|
|
75
85
|
const tabs = useAppStore((s) => s.tabs)
|
|
76
86
|
const activeTabId = useAppStore((s) => s.activeTabId)
|
|
77
87
|
const focusMode = useAppStore((s) => s.focusMode)
|
|
@@ -101,7 +111,7 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
101
111
|
>
|
|
102
112
|
{tabs.length === 0 ? (
|
|
103
113
|
<box paddingTop={1}>
|
|
104
|
-
<text fg={theme.
|
|
114
|
+
<text fg={theme.colors['descriptionForeground']}>No tabs yet. Press Ctrl+n.</text>
|
|
105
115
|
</box>
|
|
106
116
|
) : (
|
|
107
117
|
tabs.map((tab, index) => {
|
|
@@ -141,6 +151,7 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
141
151
|
})
|
|
142
152
|
|
|
143
153
|
export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
154
|
+
const theme = useTheme()
|
|
144
155
|
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
145
156
|
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
146
157
|
const gitPane = useAppStore((s) => s.gitPane)
|
|
@@ -157,7 +168,11 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
157
168
|
const tabsGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.ratio) * 100)) : 1
|
|
158
169
|
const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.ratio * 100)) : 0
|
|
159
170
|
|
|
160
|
-
const separator =
|
|
171
|
+
const separator = (
|
|
172
|
+
<text fg={theme.colors['editor.lineHighlightBackground']}>
|
|
173
|
+
{'·'.repeat(Math.max(0, sidebarWidth - 2))}
|
|
174
|
+
</text>
|
|
175
|
+
)
|
|
161
176
|
const gitBody = gitEmbedded ? (
|
|
162
177
|
<box flexDirection="column" flexGrow={gitGrow} flexShrink={1} flexBasis={0} overflow="hidden">
|
|
163
178
|
<GitPaneWidget pollingEnabled={gitPane.visible} />
|
|
@@ -169,7 +184,7 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
169
184
|
width={sidebarWidth}
|
|
170
185
|
padding={0}
|
|
171
186
|
flexDirection="column"
|
|
172
|
-
backgroundColor={theme.
|
|
187
|
+
backgroundColor={theme.colors['sideBar.background']}
|
|
173
188
|
gap={0}
|
|
174
189
|
>
|
|
175
190
|
<SidebarTop />
|
|
@@ -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
|
|
|
@@ -26,12 +27,26 @@ export function SnippetEditorModal({
|
|
|
26
27
|
width={uiTokens.modalWidth.xl}
|
|
27
28
|
>
|
|
28
29
|
<box flexDirection="column">
|
|
29
|
-
<text
|
|
30
|
+
<text
|
|
31
|
+
fg={
|
|
32
|
+
nameActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
|
|
33
|
+
}
|
|
34
|
+
>
|
|
35
|
+
Name
|
|
36
|
+
</text>
|
|
30
37
|
<InputField active={nameActive} value={snippetName} />
|
|
31
38
|
</box>
|
|
32
39
|
|
|
33
40
|
<box flexDirection="column">
|
|
34
|
-
<text
|
|
41
|
+
<text
|
|
42
|
+
fg={
|
|
43
|
+
contentActive
|
|
44
|
+
? theme.colors['editor.foreground']
|
|
45
|
+
: theme.colors['descriptionForeground']
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
Content
|
|
49
|
+
</text>
|
|
35
50
|
<InputField active={contentActive} value={snippetContent} />
|
|
36
51
|
</box>
|
|
37
52
|
</ModalShell>
|
|
@@ -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 (
|
|
@@ -32,7 +33,7 @@ export function SnippetPickerModal({ filter, selectedIndex, snippets }: SnippetP
|
|
|
32
33
|
footer={<ModalFilterBar filter={filter} />}
|
|
33
34
|
>
|
|
34
35
|
{filtered.length === 0 ? (
|
|
35
|
-
<text fg={theme.
|
|
36
|
+
<text fg={theme.colors['descriptionForeground']}>
|
|
36
37
|
{filter ? 'No matching snippets.' : 'No snippets yet. Press n to create one.'}
|
|
37
38
|
</text>
|
|
38
39
|
) : null}
|
|
@@ -43,11 +44,19 @@ export function SnippetPickerModal({ filter, selectedIndex, snippets }: SnippetP
|
|
|
43
44
|
key={snippet.id}
|
|
44
45
|
active={active}
|
|
45
46
|
title={
|
|
46
|
-
<text
|
|
47
|
+
<text
|
|
48
|
+
fg={
|
|
49
|
+
active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
|
|
50
|
+
}
|
|
51
|
+
>
|
|
47
52
|
<strong>{snippet.name}</strong>
|
|
48
53
|
</text>
|
|
49
54
|
}
|
|
50
|
-
subtitle={
|
|
55
|
+
subtitle={
|
|
56
|
+
<text fg={theme.colors['descriptionForeground']}>
|
|
57
|
+
{truncateContent(snippet.content)}
|
|
58
|
+
</text>
|
|
59
|
+
}
|
|
51
60
|
/>
|
|
52
61
|
)
|
|
53
62
|
})}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type PaneRect,
|
|
12
12
|
type SplitDirection,
|
|
13
13
|
} from '../../state/layout-tree'
|
|
14
|
-
import {
|
|
14
|
+
import { useTheme } from '../theme'
|
|
15
15
|
import { TerminalPane } from './terminal-pane'
|
|
16
16
|
|
|
17
17
|
const PANE_CHROME = PANE_BORDER
|
|
@@ -58,6 +58,7 @@ export function SplitLayout({
|
|
|
58
58
|
onTerminalScrollEvent,
|
|
59
59
|
tabs,
|
|
60
60
|
}: SplitLayoutProps) {
|
|
61
|
+
const theme = useTheme()
|
|
61
62
|
if (node.type === 'leaf') {
|
|
62
63
|
const tab = tabs.find((t) => t.id === node.tabId)
|
|
63
64
|
const isActive = node.tabId === activeTabId
|
|
@@ -134,7 +135,7 @@ export function SplitLayout({
|
|
|
134
135
|
<box
|
|
135
136
|
minWidth={node.direction === 'vertical' ? 1 : undefined}
|
|
136
137
|
minHeight={node.direction === 'horizontal' ? 1 : undefined}
|
|
137
|
-
backgroundColor={theme.border}
|
|
138
|
+
backgroundColor={theme.colors['editorGroup.border']}
|
|
138
139
|
onMouseDown={(e: OtuiMouseEvent) => {
|
|
139
140
|
e.preventDefault()
|
|
140
141
|
if (!onSeparatorDragStart) return
|
|
@@ -4,21 +4,22 @@ import { version as APP_VERSION } from '../../../package.json'
|
|
|
4
4
|
import { useAppStore } from '../../state/app-store'
|
|
5
5
|
import { useKeymap } from '../keymap-context'
|
|
6
6
|
import { getStatusBarModel } from '../status-bar-model'
|
|
7
|
-
import {
|
|
7
|
+
import { getCurrentTheme, useTheme } from '../theme'
|
|
8
8
|
|
|
9
9
|
function getModeColor(focusMode: AppState['focusMode']): string {
|
|
10
|
+
const t = getCurrentTheme()
|
|
10
11
|
switch (focusMode) {
|
|
11
12
|
case 'terminal-input':
|
|
12
|
-
return
|
|
13
|
+
return t.colors['textLink.foreground']
|
|
13
14
|
case 'modal':
|
|
14
|
-
return
|
|
15
|
+
return t.colors['editorWarning.foreground']
|
|
15
16
|
case 'command-edit':
|
|
16
|
-
return
|
|
17
|
+
return t.colors['editorWarning.foreground']
|
|
17
18
|
case 'git':
|
|
18
|
-
return
|
|
19
|
+
return t.colors['gitDecoration.addedResourceForeground']
|
|
19
20
|
case 'navigation':
|
|
20
21
|
default:
|
|
21
|
-
return
|
|
22
|
+
return t.colors['terminal.ansiMagenta']
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -39,6 +40,7 @@ function getModeLabel(focusMode: AppState['focusMode']): string {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export function StatusBar() {
|
|
43
|
+
const theme = useTheme()
|
|
42
44
|
const state = useAppStore((s) => s)
|
|
43
45
|
const activeTab = state.tabs.find((tab) => tab.id === state.activeTabId)
|
|
44
46
|
const config = useKeymap()
|
|
@@ -52,16 +54,19 @@ export function StatusBar() {
|
|
|
52
54
|
paddingTop={0}
|
|
53
55
|
paddingBottom={0}
|
|
54
56
|
flexDirection="column"
|
|
55
|
-
backgroundColor={theme.
|
|
57
|
+
backgroundColor={theme.colors['sideBarSectionHeader.background']}
|
|
56
58
|
>
|
|
57
59
|
<box width="100%" flexDirection="row">
|
|
58
60
|
<text fg={getModeColor(state.focusMode)}>[{getModeLabel(state.focusMode)}]</text>
|
|
59
61
|
<text> </text>
|
|
60
|
-
<text fg={theme.
|
|
62
|
+
<text fg={theme.colors['editor.foreground']}>{model.left}</text>
|
|
61
63
|
</box>
|
|
62
64
|
<box width="100%" flexDirection="row" justifyContent="space-between">
|
|
63
|
-
<text fg={theme.
|
|
64
|
-
<
|
|
65
|
+
<text fg={theme.colors['descriptionForeground']}>{model.right}</text>
|
|
66
|
+
<box flexDirection="row" gap={2}>
|
|
67
|
+
{model.help ? <text fg={theme.colors['descriptionForeground']}>{model.help}</text> : null}
|
|
68
|
+
<text fg={theme.colors['editor.lineHighlightBackground']}>v{APP_VERSION}</text>
|
|
69
|
+
</box>
|
|
65
70
|
</box>
|
|
66
71
|
</box>
|
|
67
72
|
)
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentTheme } from '../theme'
|
|
4
4
|
|
|
5
5
|
type SurfaceTone = 'muted' | 'elevated' | 'selected' | 'input' | 'inputActive'
|
|
6
6
|
|
|
7
7
|
function getSurfaceColor(tone: SurfaceTone): string {
|
|
8
8
|
switch (tone) {
|
|
9
9
|
case 'elevated':
|
|
10
|
-
return
|
|
10
|
+
return getCurrentTheme().colors['sideBar.background']
|
|
11
11
|
case 'selected':
|
|
12
|
-
return
|
|
12
|
+
return getCurrentTheme().colors['list.activeSelectionBackground']
|
|
13
13
|
case 'input':
|
|
14
|
-
return
|
|
14
|
+
return getCurrentTheme().colors['editor.background']
|
|
15
15
|
case 'inputActive':
|
|
16
|
-
return
|
|
16
|
+
return getCurrentTheme().colors['list.activeSelectionBackground']
|
|
17
17
|
case 'muted':
|
|
18
18
|
default:
|
|
19
|
-
return
|
|
19
|
+
return getCurrentTheme().colors['sideBarSectionHeader.background']
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TabSession } from '../../state/types'
|
|
2
2
|
|
|
3
3
|
import { useBusySpinner } from '../hooks/use-busy-spinner'
|
|
4
|
-
import {
|
|
4
|
+
import { getCurrentTheme, useTheme } from '../theme'
|
|
5
5
|
|
|
6
6
|
interface TabItemProps {
|
|
7
7
|
id?: string
|
|
@@ -15,15 +15,15 @@ interface TabItemProps {
|
|
|
15
15
|
function getStatusColor(status: TabSession['status']): string {
|
|
16
16
|
switch (status) {
|
|
17
17
|
case 'running':
|
|
18
|
-
return
|
|
18
|
+
return getCurrentTheme().colors['gitDecoration.addedResourceForeground']
|
|
19
19
|
case 'disconnected':
|
|
20
|
-
return
|
|
20
|
+
return getCurrentTheme().colors['editorWarning.foreground']
|
|
21
21
|
case 'error':
|
|
22
|
-
return
|
|
22
|
+
return getCurrentTheme().colors['editorError.foreground']
|
|
23
23
|
case 'exited':
|
|
24
|
-
return
|
|
24
|
+
return getCurrentTheme().colors['editorWarning.foreground']
|
|
25
25
|
default:
|
|
26
|
-
return
|
|
26
|
+
return getCurrentTheme().colors['descriptionForeground']
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -37,32 +37,38 @@ function getIndicator(active: boolean, focused: boolean, inLayout: boolean): str
|
|
|
37
37
|
|
|
38
38
|
function getIndicatorColor(active: boolean, focused: boolean, inLayout: boolean): string {
|
|
39
39
|
if (active) {
|
|
40
|
-
return focused
|
|
40
|
+
return focused
|
|
41
|
+
? getCurrentTheme().colors['textLink.foreground']
|
|
42
|
+
: getCurrentTheme().colors['terminal.ansiMagenta']
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
return inLayout
|
|
45
|
+
return inLayout
|
|
46
|
+
? getCurrentTheme().colors['descriptionForeground']
|
|
47
|
+
: getCurrentTheme().colors['editor.lineHighlightBackground']
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
function BusyIndicator() {
|
|
51
|
+
const theme = useTheme()
|
|
47
52
|
const frame = useBusySpinner()
|
|
48
|
-
return <text fg={theme.
|
|
53
|
+
return <text fg={theme.colors['textLink.foreground']}>{frame} busy</text>
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
function ActivityIndicator({ isFocusedInput, tab }: { tab: TabSession; isFocusedInput: boolean }) {
|
|
57
|
+
const theme = useTheme()
|
|
52
58
|
if (tab.status === 'error') {
|
|
53
|
-
return <text fg={theme.
|
|
59
|
+
return <text fg={theme.colors['editorError.foreground']}>✗ error</text>
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
if (tab.status === 'disconnected') {
|
|
57
|
-
return <text fg={theme.
|
|
63
|
+
return <text fg={theme.colors['editorWarning.foreground']}>⏸ restore</text>
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
if (tab.status === 'exited') {
|
|
61
|
-
return <text fg={theme.
|
|
67
|
+
return <text fg={theme.colors['editorWarning.foreground']}>⏹ exited</text>
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
if (isFocusedInput) {
|
|
65
|
-
return <text fg={theme.
|
|
71
|
+
return <text fg={theme.colors['focusBorder']}>▸ focused</text>
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
if (tab.activity === 'busy') {
|
|
@@ -70,13 +76,14 @@ function ActivityIndicator({ isFocusedInput, tab }: { tab: TabSession; isFocused
|
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
if (tab.activity === 'idle') {
|
|
73
|
-
return <text fg={theme.
|
|
79
|
+
return <text fg={theme.colors['gitDecoration.addedResourceForeground']}>● idle</text>
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
return <text fg={getStatusColor(tab.status)}>{tab.status}</text>
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
export function TabItem({ active, focused, id, inLayout, isFocusedInput, tab }: TabItemProps) {
|
|
86
|
+
const theme = useTheme()
|
|
80
87
|
const label = tab.command.split(' ')[0]
|
|
81
88
|
const isInLayout = inLayout ?? false
|
|
82
89
|
const indicator = getIndicator(active, focused, isInLayout)
|
|
@@ -89,16 +96,20 @@ export function TabItem({ active, focused, id, inLayout, isFocusedInput, tab }:
|
|
|
89
96
|
paddingRight={1}
|
|
90
97
|
paddingTop={0}
|
|
91
98
|
paddingBottom={0}
|
|
92
|
-
backgroundColor={active ? theme.
|
|
99
|
+
backgroundColor={active ? theme.colors['list.activeSelectionBackground'] : undefined}
|
|
93
100
|
flexDirection="column"
|
|
94
101
|
gap={0}
|
|
95
102
|
>
|
|
96
103
|
<box flexDirection="row">
|
|
97
104
|
<text fg={indicatorColor}>{indicator} </text>
|
|
98
|
-
<text
|
|
105
|
+
<text
|
|
106
|
+
fg={active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']}
|
|
107
|
+
>
|
|
108
|
+
{tab.title}
|
|
109
|
+
</text>
|
|
99
110
|
</box>
|
|
100
111
|
<box flexDirection="row">
|
|
101
|
-
<text fg={theme.
|
|
112
|
+
<text fg={theme.colors['descriptionForeground']}> {label} </text>
|
|
102
113
|
<ActivityIndicator tab={tab} isFocusedInput={isFocusedInput} />
|
|
103
114
|
</box>
|
|
104
115
|
</box>
|