@brimveyn/aimux 1.7.3 → 1.7.4
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 +12 -3
- package/src/app-runtime/split-drag-controller.ts +31 -1
- package/src/app-runtime/use-mouse-handlers.ts +93 -5
- package/src/app-runtime/use-terminal-resize.ts +4 -4
- package/src/app.tsx +16 -1
- package/src/config.ts +21 -2
- package/src/state/git-pane-sizing.ts +15 -0
- package/src/state/reducers/git-panel-state.ts +12 -7
- package/src/state/reducers/ui-state.ts +6 -0
- package/src/state/store.ts +2 -1
- package/src/state/types.ts +4 -1
- package/src/state/workspace-save.ts +2 -1
- package/src/ui/components/context-menu-overlay.tsx +4 -2
- package/src/ui/components/git-pane-context-menu.ts +26 -0
- package/src/ui/components/sidebar.tsx +114 -30
- package/src/ui/root.tsx +101 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brimveyn/aimux",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4",
|
|
4
4
|
"description": "A terminal multiplexer for AI CLIs. Run Claude, Codex, OpenCode side-by-side with tabbed navigation, split panes, and persistent sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"bump": "bun run scripts/bump.ts"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@brimveyn/aimux-config": "0.4.
|
|
63
|
+
"@brimveyn/aimux-config": "0.4.6",
|
|
64
64
|
"@opentui/core": "^0.1.90",
|
|
65
65
|
"@opentui/react": "^0.1.90",
|
|
66
66
|
"@xterm/headless": "^6.0.0",
|
|
@@ -477,14 +477,17 @@ export function executeSideEffect(effect: SideEffect, ctx: SideEffectContext): v
|
|
|
477
477
|
case 'persist-git-diff-mode-ratio': {
|
|
478
478
|
const config = loadConfig()
|
|
479
479
|
const persistedGitPane = config.gitPane
|
|
480
|
+
const paneRatio = persistedGitPane?.paneRatio ?? persistedGitPane?.ratio ?? 0.5
|
|
481
|
+
const embeddedRatio = persistedGitPane?.embeddedRatio ?? persistedGitPane?.ratio ?? 0.5
|
|
480
482
|
saveConfig({
|
|
481
483
|
...config,
|
|
482
484
|
gitPane: {
|
|
483
485
|
diffModeRatio: effect.ratio,
|
|
486
|
+
embeddedRatio,
|
|
484
487
|
fileListMode: persistedGitPane?.fileListMode,
|
|
485
488
|
mode: persistedGitPane?.mode ?? 'embedded',
|
|
489
|
+
paneRatio,
|
|
486
490
|
position: persistedGitPane?.position ?? 'bottom',
|
|
487
|
-
ratio: persistedGitPane?.ratio ?? 0.5,
|
|
488
491
|
treeCompaction: persistedGitPane?.treeCompaction,
|
|
489
492
|
visible: persistedGitPane?.visible ?? true,
|
|
490
493
|
},
|
|
@@ -494,14 +497,17 @@ export function executeSideEffect(effect: SideEffect, ctx: SideEffectContext): v
|
|
|
494
497
|
case 'persist-git-file-list-mode': {
|
|
495
498
|
const config = loadConfig()
|
|
496
499
|
const persistedGitPane = config.gitPane
|
|
500
|
+
const paneRatio = persistedGitPane?.paneRatio ?? persistedGitPane?.ratio ?? 0.5
|
|
501
|
+
const embeddedRatio = persistedGitPane?.embeddedRatio ?? persistedGitPane?.ratio ?? 0.5
|
|
497
502
|
saveConfig({
|
|
498
503
|
...config,
|
|
499
504
|
gitPane: {
|
|
500
505
|
diffModeRatio: persistedGitPane?.diffModeRatio,
|
|
506
|
+
embeddedRatio,
|
|
501
507
|
fileListMode: effect.mode,
|
|
502
508
|
mode: persistedGitPane?.mode ?? 'embedded',
|
|
509
|
+
paneRatio,
|
|
503
510
|
position: persistedGitPane?.position ?? 'bottom',
|
|
504
|
-
ratio: persistedGitPane?.ratio ?? 0.5,
|
|
505
511
|
treeCompaction: persistedGitPane?.treeCompaction,
|
|
506
512
|
visible: persistedGitPane?.visible ?? true,
|
|
507
513
|
},
|
|
@@ -511,14 +517,17 @@ export function executeSideEffect(effect: SideEffect, ctx: SideEffectContext): v
|
|
|
511
517
|
case 'persist-git-tree-compaction': {
|
|
512
518
|
const config = loadConfig()
|
|
513
519
|
const persistedGitPane = config.gitPane
|
|
520
|
+
const paneRatio = persistedGitPane?.paneRatio ?? persistedGitPane?.ratio ?? 0.5
|
|
521
|
+
const embeddedRatio = persistedGitPane?.embeddedRatio ?? persistedGitPane?.ratio ?? 0.5
|
|
514
522
|
saveConfig({
|
|
515
523
|
...config,
|
|
516
524
|
gitPane: {
|
|
517
525
|
diffModeRatio: persistedGitPane?.diffModeRatio,
|
|
526
|
+
embeddedRatio,
|
|
518
527
|
fileListMode: persistedGitPane?.fileListMode,
|
|
519
528
|
mode: persistedGitPane?.mode ?? 'embedded',
|
|
529
|
+
paneRatio,
|
|
520
530
|
position: persistedGitPane?.position ?? 'bottom',
|
|
521
|
-
ratio: persistedGitPane?.ratio ?? 0.5,
|
|
522
531
|
treeCompaction: effect.enabled,
|
|
523
532
|
visible: persistedGitPane?.visible ?? true,
|
|
524
533
|
},
|
|
@@ -2,6 +2,8 @@ import type { MouseEvent as OtuiMouseEvent } from '@opentui/core'
|
|
|
2
2
|
|
|
3
3
|
import type { SplitDirection } from '../state/layout-tree'
|
|
4
4
|
|
|
5
|
+
type ScreenAxis = 'x' | 'y'
|
|
6
|
+
|
|
5
7
|
export interface SplitDragState {
|
|
6
8
|
tabId: string
|
|
7
9
|
direction: SplitDirection
|
|
@@ -9,7 +11,35 @@ export interface SplitDragState {
|
|
|
9
11
|
totalSize: number
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
export interface AxisDragState {
|
|
15
|
+
axis: ScreenAxis
|
|
16
|
+
screenStart: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AnchoredRatioDragState {
|
|
20
|
+
anchor: 'start' | 'end'
|
|
21
|
+
axis: ScreenAxis
|
|
22
|
+
screenStart: number
|
|
23
|
+
totalSize: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getScreenPosition(event: OtuiMouseEvent, axis: ScreenAxis): number {
|
|
27
|
+
return axis === 'x' ? event.x : event.y
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
export function getSplitRatioFromDrag(event: OtuiMouseEvent, drag: SplitDragState): number {
|
|
13
|
-
const position = drag.direction === 'vertical' ?
|
|
31
|
+
const position = getScreenPosition(event, drag.direction === 'vertical' ? 'x' : 'y')
|
|
14
32
|
return (position - drag.screenStart) / drag.totalSize
|
|
15
33
|
}
|
|
34
|
+
|
|
35
|
+
export function getAxisDeltaFromDrag(event: OtuiMouseEvent, drag: AxisDragState): number {
|
|
36
|
+
return getScreenPosition(event, drag.axis) - drag.screenStart
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getAnchoredRatioFromDrag(
|
|
40
|
+
event: OtuiMouseEvent,
|
|
41
|
+
drag: AnchoredRatioDragState
|
|
42
|
+
): number {
|
|
43
|
+
const offset = getScreenPosition(event, drag.axis) - drag.screenStart
|
|
44
|
+
return drag.anchor === 'start' ? offset / drag.totalSize : 1 - offset / drag.totalSize
|
|
45
|
+
}
|
|
@@ -16,9 +16,22 @@ import {
|
|
|
16
16
|
resolveClickSelection,
|
|
17
17
|
} from './click-selection-resolver'
|
|
18
18
|
import { requestRenderUpTree } from './render-invalidation'
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type AnchoredRatioDragState,
|
|
21
|
+
type AxisDragState,
|
|
22
|
+
getAnchoredRatioFromDrag,
|
|
23
|
+
getAxisDeltaFromDrag,
|
|
24
|
+
getSplitRatioFromDrag,
|
|
25
|
+
type SplitDragState,
|
|
26
|
+
} from './split-drag-controller'
|
|
20
27
|
import { getForwardedMouseSequence, getScrollViewportDelta } from './terminal-mouse-adapter'
|
|
21
28
|
|
|
29
|
+
type ResizeDragState =
|
|
30
|
+
| ({ kind: 'split' } & SplitDragState)
|
|
31
|
+
| ({ initialWidth: number; kind: 'sidebar' } & AxisDragState)
|
|
32
|
+
| ({ initialWidth: number; kind: 'git-pane'; side: 'left' | 'right' } & AxisDragState)
|
|
33
|
+
| ({ kind: 'embedded-git'; position: 'top' | 'bottom' } & AnchoredRatioDragState)
|
|
34
|
+
|
|
22
35
|
interface UseMouseHandlersOptions {
|
|
23
36
|
state: AppState
|
|
24
37
|
dispatch: (action: AppAction) => void
|
|
@@ -68,7 +81,7 @@ export function useMouseHandlers({
|
|
|
68
81
|
renderer,
|
|
69
82
|
state,
|
|
70
83
|
}: UseMouseHandlersOptions) {
|
|
71
|
-
const separatorDragRef = useRef<
|
|
84
|
+
const separatorDragRef = useRef<ResizeDragState | null>(null)
|
|
72
85
|
const multiClickRef = useRef(new MultiClickDetector())
|
|
73
86
|
|
|
74
87
|
const handleTerminalMouseEvent = (event: OtuiMouseEvent, origin: TerminalContentOrigin) => {
|
|
@@ -113,7 +126,45 @@ export function useMouseHandlers({
|
|
|
113
126
|
screenStart: number
|
|
114
127
|
totalSize: number
|
|
115
128
|
}) => {
|
|
116
|
-
separatorDragRef.current = info
|
|
129
|
+
separatorDragRef.current = { kind: 'split', ...info }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const handleSidebarResizeStart = (info: { initialWidth: number; screenStart: number }) => {
|
|
133
|
+
separatorDragRef.current = {
|
|
134
|
+
axis: 'x',
|
|
135
|
+
initialWidth: info.initialWidth,
|
|
136
|
+
kind: 'sidebar',
|
|
137
|
+
screenStart: info.screenStart,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const handleGitPaneResizeStart = (info: {
|
|
142
|
+
initialWidth: number
|
|
143
|
+
screenStart: number
|
|
144
|
+
side: 'left' | 'right'
|
|
145
|
+
}) => {
|
|
146
|
+
separatorDragRef.current = {
|
|
147
|
+
axis: 'x',
|
|
148
|
+
initialWidth: info.initialWidth,
|
|
149
|
+
kind: 'git-pane',
|
|
150
|
+
screenStart: info.screenStart,
|
|
151
|
+
side: info.side,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const handleEmbeddedGitResizeStart = (info: {
|
|
156
|
+
containerStart: number
|
|
157
|
+
position: 'top' | 'bottom'
|
|
158
|
+
totalSize: number
|
|
159
|
+
}) => {
|
|
160
|
+
separatorDragRef.current = {
|
|
161
|
+
anchor: info.position === 'top' ? 'start' : 'end',
|
|
162
|
+
axis: 'y',
|
|
163
|
+
kind: 'embedded-git',
|
|
164
|
+
position: info.position,
|
|
165
|
+
screenStart: info.containerStart,
|
|
166
|
+
totalSize: info.totalSize,
|
|
167
|
+
}
|
|
117
168
|
}
|
|
118
169
|
|
|
119
170
|
const handleSeparatorDrag = (event: OtuiMouseEvent): boolean => {
|
|
@@ -122,8 +173,42 @@ export function useMouseHandlers({
|
|
|
122
173
|
return false
|
|
123
174
|
}
|
|
124
175
|
|
|
125
|
-
|
|
126
|
-
|
|
176
|
+
switch (drag.kind) {
|
|
177
|
+
case 'split': {
|
|
178
|
+
const newRatio = getSplitRatioFromDrag(event, drag)
|
|
179
|
+
dispatch({
|
|
180
|
+
axis: drag.direction,
|
|
181
|
+
ratio: newRatio,
|
|
182
|
+
tabId: drag.tabId,
|
|
183
|
+
type: 'set-split-ratio',
|
|
184
|
+
})
|
|
185
|
+
break
|
|
186
|
+
}
|
|
187
|
+
case 'sidebar': {
|
|
188
|
+
const nextWidth = Math.round(drag.initialWidth + getAxisDeltaFromDrag(event, drag))
|
|
189
|
+
dispatch({ type: 'set-sidebar-width', width: nextWidth })
|
|
190
|
+
break
|
|
191
|
+
}
|
|
192
|
+
case 'git-pane': {
|
|
193
|
+
const delta = getAxisDeltaFromDrag(event, drag)
|
|
194
|
+
const direction = drag.side === 'left' ? 1 : -1
|
|
195
|
+
const nextWidth = drag.initialWidth + delta * direction
|
|
196
|
+
dispatch({
|
|
197
|
+
ratio: nextWidth / 80,
|
|
198
|
+
target: 'pane',
|
|
199
|
+
type: 'set-git-pane-ratio',
|
|
200
|
+
})
|
|
201
|
+
break
|
|
202
|
+
}
|
|
203
|
+
case 'embedded-git': {
|
|
204
|
+
dispatch({
|
|
205
|
+
ratio: getAnchoredRatioFromDrag(event, drag),
|
|
206
|
+
target: 'embedded',
|
|
207
|
+
type: 'set-git-pane-ratio',
|
|
208
|
+
})
|
|
209
|
+
break
|
|
210
|
+
}
|
|
211
|
+
}
|
|
127
212
|
return true
|
|
128
213
|
}
|
|
129
214
|
|
|
@@ -171,10 +256,13 @@ export function useMouseHandlers({
|
|
|
171
256
|
}
|
|
172
257
|
|
|
173
258
|
return {
|
|
259
|
+
handleEmbeddedGitResizeStart,
|
|
260
|
+
handleGitPaneResizeStart,
|
|
174
261
|
handlePaneActivate,
|
|
175
262
|
handleSeparatorDrag,
|
|
176
263
|
handleSeparatorDragEnd,
|
|
177
264
|
handleSeparatorDragStart,
|
|
265
|
+
handleSidebarResizeStart,
|
|
178
266
|
handleSplitResize,
|
|
179
267
|
handleTerminalClick,
|
|
180
268
|
handleTerminalMouseEvent,
|
|
@@ -4,6 +4,7 @@ import type { TerminalContentOrigin } from '../input/raw-input-handler'
|
|
|
4
4
|
import type { SessionBackend } from '../session-backend/types'
|
|
5
5
|
import type { AppAction, AppState, ScrollIntent } from '../state/types'
|
|
6
6
|
|
|
7
|
+
import { getGitPaneWidthFromRatio } from '../state/git-pane-sizing'
|
|
7
8
|
import {
|
|
8
9
|
createTerminalBounds,
|
|
9
10
|
forEachSplitPaneRect,
|
|
@@ -140,8 +141,7 @@ export function useTerminalResize({
|
|
|
140
141
|
STATUS_BAR_HEIGHT +
|
|
141
142
|
TERMINAL_PANE_VERTICAL_CHROME +
|
|
142
143
|
sessionBarRows
|
|
143
|
-
const
|
|
144
|
-
const gitPaneWidth = gitPaneInPaneMode ? Math.max(20, Math.min(80, gitPaneRaw)) : 0
|
|
144
|
+
const gitPaneWidth = gitPaneInPaneMode ? getGitPaneWidthFromRatio(state.gitPane.paneRatio) : 0
|
|
145
145
|
const gitOnLeft = gitPaneInPaneMode && state.gitPane.position === 'left'
|
|
146
146
|
const cols = Math.max(
|
|
147
147
|
MIN_TERMINAL_COLS,
|
|
@@ -167,7 +167,7 @@ export function useTerminalResize({
|
|
|
167
167
|
state.sessionBar.position,
|
|
168
168
|
gitPaneInPaneMode,
|
|
169
169
|
state.gitPane.position,
|
|
170
|
-
state.gitPane.
|
|
170
|
+
state.gitPane.paneRatio,
|
|
171
171
|
])
|
|
172
172
|
|
|
173
173
|
useLayoutEffect(() => {
|
|
@@ -196,7 +196,7 @@ export function useTerminalResize({
|
|
|
196
196
|
state.sessionBar.position,
|
|
197
197
|
gitPaneInPaneMode,
|
|
198
198
|
state.gitPane.position,
|
|
199
|
-
state.gitPane.
|
|
199
|
+
state.gitPane.paneRatio,
|
|
200
200
|
])
|
|
201
201
|
|
|
202
202
|
useEffect(() => {
|
package/src/app.tsx
CHANGED
|
@@ -88,16 +88,25 @@ export function App({
|
|
|
88
88
|
const diffModeRatio = userGitPane?.diffModeRatio ?? json.gitPane?.diffModeRatio ?? 0.35
|
|
89
89
|
const treeCompaction = userGitPane?.treeCompaction ?? json.gitPane?.treeCompaction ?? true
|
|
90
90
|
const prefetchRadius = userGitPane?.prefetchRadius ?? json.gitPane?.prefetchRadius ?? 5
|
|
91
|
+
const persistedPaneRatio = json.gitPane?.paneRatio ?? json.gitPane?.ratio ?? 0.5
|
|
92
|
+
const persistedEmbeddedRatio = json.gitPane?.embeddedRatio ?? json.gitPane?.ratio ?? 0.5
|
|
91
93
|
const gitPaneOverrides = {
|
|
92
94
|
...json.gitPane,
|
|
93
95
|
diffModeRatio,
|
|
96
|
+
embeddedRatio:
|
|
97
|
+
userGitPane?.mode === 'embedded' && userGitPane?.ratio !== undefined
|
|
98
|
+
? userGitPane.ratio
|
|
99
|
+
: persistedEmbeddedRatio,
|
|
94
100
|
fileListMode,
|
|
101
|
+
paneRatio:
|
|
102
|
+
userGitPane?.mode === 'pane' && userGitPane?.ratio !== undefined
|
|
103
|
+
? userGitPane.ratio
|
|
104
|
+
: persistedPaneRatio,
|
|
95
105
|
prefetchRadius,
|
|
96
106
|
treeCompaction,
|
|
97
107
|
...(userGitPane?.visible !== undefined ? { visible: userGitPane.visible } : {}),
|
|
98
108
|
...(userGitPane?.mode !== undefined ? { mode: userGitPane.mode } : {}),
|
|
99
109
|
...(userGitPane?.position !== undefined ? { position: userGitPane.position } : {}),
|
|
100
|
-
...(userGitPane?.ratio !== undefined ? { ratio: userGitPane.ratio } : {}),
|
|
101
110
|
...(userGitPane?.diffModeRatio !== undefined
|
|
102
111
|
? { diffModeRatio: userGitPane.diffModeRatio }
|
|
103
112
|
: {}),
|
|
@@ -210,10 +219,13 @@ export function App({
|
|
|
210
219
|
})
|
|
211
220
|
|
|
212
221
|
const {
|
|
222
|
+
handleEmbeddedGitResizeStart,
|
|
223
|
+
handleGitPaneResizeStart,
|
|
213
224
|
handlePaneActivate,
|
|
214
225
|
handleSeparatorDrag,
|
|
215
226
|
handleSeparatorDragEnd,
|
|
216
227
|
handleSeparatorDragStart,
|
|
228
|
+
handleSidebarResizeStart,
|
|
217
229
|
handleSplitResize,
|
|
218
230
|
handleTerminalClick,
|
|
219
231
|
handleTerminalMouseEvent,
|
|
@@ -349,9 +361,12 @@ export function App({
|
|
|
349
361
|
onTerminalClick={handleTerminalClick}
|
|
350
362
|
onPaneActivate={handlePaneActivate}
|
|
351
363
|
onSplitResize={handleSplitResize}
|
|
364
|
+
onEmbeddedGitResizeStart={handleEmbeddedGitResizeStart}
|
|
365
|
+
onGitPaneResizeStart={handleGitPaneResizeStart}
|
|
352
366
|
onSeparatorDragStart={handleSeparatorDragStart}
|
|
353
367
|
onSeparatorDrag={handleSeparatorDrag}
|
|
354
368
|
onSeparatorDragEnd={handleSeparatorDragEnd}
|
|
369
|
+
onSidebarResizeStart={handleSidebarResizeStart}
|
|
355
370
|
terminalCols={terminalSize.cols}
|
|
356
371
|
terminalRows={terminalSize.rows}
|
|
357
372
|
/>
|
package/src/config.ts
CHANGED
|
@@ -22,7 +22,9 @@ export interface PersistedGitPane {
|
|
|
22
22
|
visible: boolean
|
|
23
23
|
mode: 'embedded' | 'pane'
|
|
24
24
|
position: 'top' | 'bottom' | 'left' | 'right'
|
|
25
|
-
|
|
25
|
+
paneRatio?: number
|
|
26
|
+
embeddedRatio?: number
|
|
27
|
+
ratio?: number
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface AimuxConfig {
|
|
@@ -47,7 +49,20 @@ function isPersistedGitPane(value: unknown): value is PersistedGitPane {
|
|
|
47
49
|
v.position === 'left' ||
|
|
48
50
|
v.position === 'right'
|
|
49
51
|
const ratioOk =
|
|
50
|
-
|
|
52
|
+
v.ratio === undefined ||
|
|
53
|
+
(typeof v.ratio === 'number' && Number.isFinite(v.ratio) && v.ratio > 0 && v.ratio < 1)
|
|
54
|
+
const paneRatioOk =
|
|
55
|
+
v.paneRatio === undefined ||
|
|
56
|
+
(typeof v.paneRatio === 'number' &&
|
|
57
|
+
Number.isFinite(v.paneRatio) &&
|
|
58
|
+
v.paneRatio > 0 &&
|
|
59
|
+
v.paneRatio < 1)
|
|
60
|
+
const embeddedRatioOk =
|
|
61
|
+
v.embeddedRatio === undefined ||
|
|
62
|
+
(typeof v.embeddedRatio === 'number' &&
|
|
63
|
+
Number.isFinite(v.embeddedRatio) &&
|
|
64
|
+
v.embeddedRatio > 0 &&
|
|
65
|
+
v.embeddedRatio < 1)
|
|
51
66
|
const diffModeRatioOk =
|
|
52
67
|
v.diffModeRatio === undefined ||
|
|
53
68
|
(typeof v.diffModeRatio === 'number' &&
|
|
@@ -68,6 +83,8 @@ function isPersistedGitPane(value: unknown): value is PersistedGitPane {
|
|
|
68
83
|
!modeOk ||
|
|
69
84
|
!positionOk ||
|
|
70
85
|
!ratioOk ||
|
|
86
|
+
!paneRatioOk ||
|
|
87
|
+
!embeddedRatioOk ||
|
|
71
88
|
!diffModeRatioOk ||
|
|
72
89
|
!visibleOk ||
|
|
73
90
|
!fileListModeOk ||
|
|
@@ -169,7 +186,9 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
169
186
|
: undefined
|
|
170
187
|
if (legacyVisible !== undefined || legacyRatio !== undefined) {
|
|
171
188
|
validGitPane = {
|
|
189
|
+
embeddedRatio: legacyRatio ?? 0.5,
|
|
172
190
|
mode: 'embedded',
|
|
191
|
+
paneRatio: legacyRatio ?? 0.5,
|
|
173
192
|
position: 'bottom',
|
|
174
193
|
ratio: legacyRatio ?? 0.5,
|
|
175
194
|
visible: legacyVisible ?? true,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const GIT_PANE_MIN_RATIO = 0.2
|
|
2
|
+
export const GIT_PANE_MAX_RATIO = 0.8
|
|
3
|
+
export const GIT_PANE_MIN_WIDTH = 20
|
|
4
|
+
export const GIT_PANE_MAX_WIDTH = 80
|
|
5
|
+
|
|
6
|
+
export function clampGitPaneRatio(value: number): number {
|
|
7
|
+
return Math.max(GIT_PANE_MIN_RATIO, Math.min(GIT_PANE_MAX_RATIO, value))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getGitPaneWidthFromRatio(ratio: number): number {
|
|
11
|
+
return Math.max(
|
|
12
|
+
GIT_PANE_MIN_WIDTH,
|
|
13
|
+
Math.min(GIT_PANE_MAX_WIDTH, Math.round(clampGitPaneRatio(ratio) * GIT_PANE_MAX_WIDTH))
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { AppAction, AppState, GitFileEntry, GitFileSection, GitPanelState } from '../types'
|
|
2
2
|
|
|
3
|
+
import { clampGitPaneRatio } from '../git-pane-sizing'
|
|
3
4
|
import { reconcileSelectedGitEntryKey } from '../git-tree'
|
|
4
5
|
import { clearDiffCacheForPaths } from './diff-cache'
|
|
5
6
|
|
|
6
|
-
export const GIT_PANEL_MIN_RATIO = 0.2
|
|
7
|
-
export const GIT_PANEL_MAX_RATIO = 0.8
|
|
8
|
-
|
|
9
7
|
const SECTION_RANK: Record<GitFileSection, number> = {
|
|
10
8
|
historical: 0,
|
|
11
9
|
staged: 1,
|
|
@@ -23,7 +21,7 @@ export function sortFilesBySection(files: GitFileEntry[]): GitFileEntry[] {
|
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
function clampRatio(value: number): number {
|
|
26
|
-
return
|
|
24
|
+
return clampGitPaneRatio(value)
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
export function emptyGitPanel(): GitPanelState {
|
|
@@ -93,9 +91,16 @@ export function reduceGitPanelState(state: AppState, action: AppAction): AppStat
|
|
|
93
91
|
}
|
|
94
92
|
}
|
|
95
93
|
case 'resize-git-pane': {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
const target = state.gitPane.mode === 'pane' ? 'paneRatio' : 'embeddedRatio'
|
|
95
|
+
const nextRatio = clampRatio(state.gitPane[target] + action.delta)
|
|
96
|
+
if (nextRatio === state.gitPane[target]) return state
|
|
97
|
+
return { ...state, gitPane: { ...state.gitPane, [target]: nextRatio } }
|
|
98
|
+
}
|
|
99
|
+
case 'set-git-pane-ratio': {
|
|
100
|
+
const key = action.target === 'pane' ? 'paneRatio' : 'embeddedRatio'
|
|
101
|
+
const nextRatio = clampRatio(action.ratio)
|
|
102
|
+
if (nextRatio === state.gitPane[key]) return state
|
|
103
|
+
return { ...state, gitPane: { ...state.gitPane, [key]: nextRatio } }
|
|
99
104
|
}
|
|
100
105
|
case 'resize-git-diff-pane': {
|
|
101
106
|
const nextRatio = clampRatio(state.gitPane.diffModeRatio + action.delta)
|
|
@@ -9,6 +9,12 @@ export function reduceUIState(state: AppState, action: AppAction): AppState | nu
|
|
|
9
9
|
state.sidebar.maxWidth,
|
|
10
10
|
Math.max(state.sidebar.minWidth, state.sidebar.width + action.delta)
|
|
11
11
|
)
|
|
12
|
+
if (width === state.sidebar.width) return state
|
|
13
|
+
return { ...state, sidebar: { ...state.sidebar, width } }
|
|
14
|
+
}
|
|
15
|
+
case 'set-sidebar-width': {
|
|
16
|
+
const width = Math.min(state.sidebar.maxWidth, Math.max(state.sidebar.minWidth, action.width))
|
|
17
|
+
if (width === state.sidebar.width) return state
|
|
12
18
|
return { ...state, sidebar: { ...state.sidebar, width } }
|
|
13
19
|
}
|
|
14
20
|
case 'set-focus-mode':
|
package/src/state/store.ts
CHANGED
|
@@ -34,12 +34,13 @@ export interface InitialStateOverrides {
|
|
|
34
34
|
const DEFAULT_GIT_PANE: GitPaneState = {
|
|
35
35
|
diffCount: { enabled: true },
|
|
36
36
|
diffModeRatio: 0.35,
|
|
37
|
+
embeddedRatio: 0.5,
|
|
37
38
|
fileListMode: 'tree',
|
|
38
39
|
mode: 'embedded',
|
|
40
|
+
paneRatio: 0.5,
|
|
39
41
|
path: { enabled: true },
|
|
40
42
|
position: 'bottom',
|
|
41
43
|
prefetchRadius: 5,
|
|
42
|
-
ratio: 0.5,
|
|
43
44
|
treeCompaction: true,
|
|
44
45
|
visible: true,
|
|
45
46
|
}
|
package/src/state/types.ts
CHANGED
|
@@ -166,7 +166,8 @@ export interface GitPaneState {
|
|
|
166
166
|
visible: boolean
|
|
167
167
|
mode: GitPaneMode
|
|
168
168
|
position: GitPanePosition
|
|
169
|
-
|
|
169
|
+
paneRatio: number
|
|
170
|
+
embeddedRatio: number
|
|
170
171
|
diffModeRatio: number
|
|
171
172
|
fileListMode: GitFileListMode
|
|
172
173
|
treeCompaction: boolean
|
|
@@ -500,10 +501,12 @@ export type LayoutAction =
|
|
|
500
501
|
export type UIAction =
|
|
501
502
|
| { type: 'toggle-sidebar' }
|
|
502
503
|
| { type: 'resize-sidebar'; delta: number }
|
|
504
|
+
| { type: 'set-sidebar-width'; width: number }
|
|
503
505
|
| { type: 'set-focus-mode'; focusMode: FocusMode }
|
|
504
506
|
| { type: 'set-terminal-size'; cols: number; rows: number }
|
|
505
507
|
| { type: 'toggle-git-pane' }
|
|
506
508
|
| { type: 'resize-git-pane'; delta: number }
|
|
509
|
+
| { type: 'set-git-pane-ratio'; target: 'pane' | 'embedded'; ratio: number }
|
|
507
510
|
| { type: 'resize-git-diff-pane'; delta: number }
|
|
508
511
|
| { type: 'set-git-pane-mode'; mode: GitPaneMode }
|
|
509
512
|
| { type: 'set-git-pane-position'; position: GitPanePosition }
|
|
@@ -26,10 +26,11 @@ export function saveCurrentWorkspace(state: AppState): void {
|
|
|
26
26
|
customCommands: state.customCommands,
|
|
27
27
|
gitPane: {
|
|
28
28
|
diffModeRatio: state.gitPane.diffModeRatio,
|
|
29
|
+
embeddedRatio: state.gitPane.embeddedRatio,
|
|
29
30
|
fileListMode: state.gitPane.fileListMode,
|
|
30
31
|
mode: state.gitPane.mode,
|
|
32
|
+
paneRatio: state.gitPane.paneRatio,
|
|
31
33
|
position: state.gitPane.position,
|
|
32
|
-
ratio: state.gitPane.ratio,
|
|
33
34
|
visible: state.gitPane.visible,
|
|
34
35
|
},
|
|
35
36
|
sessionBarPosition: state.sessionBar.position,
|
|
@@ -53,8 +53,10 @@ export function ContextMenuOverlay() {
|
|
|
53
53
|
const maxLabel = Math.max(...menu.items.map(([label]) => label.length))
|
|
54
54
|
const width = maxLabel + 4
|
|
55
55
|
const height = menu.items.length + 2
|
|
56
|
-
const left =
|
|
57
|
-
|
|
56
|
+
const left =
|
|
57
|
+
menu.anchorX + width <= terminalCols ? menu.anchorX : Math.max(0, menu.anchorX - width)
|
|
58
|
+
const top =
|
|
59
|
+
menu.anchorY + height <= terminalRows ? menu.anchorY : Math.max(0, menu.anchorY - height)
|
|
58
60
|
|
|
59
61
|
return (
|
|
60
62
|
<box position="absolute" top={0} left={0} width="100%" height="100%">
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { GitPaneMode, GitPanePosition, GitPaneState } from '../../state/types'
|
|
2
|
+
import type { ContextMenuItem } from '../context-menu/controller'
|
|
3
|
+
|
|
4
|
+
const GIT_PANE_MENU_OPTIONS: Array<{
|
|
5
|
+
label: string
|
|
6
|
+
mode: GitPaneMode
|
|
7
|
+
position: GitPanePosition
|
|
8
|
+
}> = [
|
|
9
|
+
{ label: 'Move to top', mode: 'embedded', position: 'top' },
|
|
10
|
+
{ label: 'Move to bottom', mode: 'embedded', position: 'bottom' },
|
|
11
|
+
{ label: 'Move to left', mode: 'pane', position: 'left' },
|
|
12
|
+
{ label: 'Move to right', mode: 'pane', position: 'right' },
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
export function buildGitPaneContextMenu(
|
|
16
|
+
gitPane: Pick<GitPaneState, 'mode' | 'position'>,
|
|
17
|
+
onToggle: () => void,
|
|
18
|
+
onMove: (mode: GitPaneMode, position: GitPanePosition) => void
|
|
19
|
+
): ContextMenuItem[] {
|
|
20
|
+
return [
|
|
21
|
+
['Toggle', onToggle],
|
|
22
|
+
...GIT_PANE_MENU_OPTIONS.filter(
|
|
23
|
+
({ mode, position }) => !(gitPane.mode === mode && gitPane.position === position)
|
|
24
|
+
).map(({ label, mode, position }) => [label, () => onMove(mode, position)] as ContextMenuItem),
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type BoxRenderable,
|
|
3
|
+
type MouseEvent as OtuiMouseEvent,
|
|
4
|
+
type ScrollBoxRenderable,
|
|
5
|
+
} from '@opentui/core'
|
|
2
6
|
import { memo, useMemo, useRef } from 'react'
|
|
3
7
|
|
|
4
8
|
import { useAppStore } from '../../state/app-store'
|
|
5
9
|
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
6
10
|
import { getCurrentTokens, type ThemeTokens, useBg, useTokens } from '../theme'
|
|
7
11
|
import { ContextMenuBox } from './context-menu-box'
|
|
12
|
+
import { buildGitPaneContextMenu } from './git-pane-context-menu'
|
|
8
13
|
import { GitPaneWidget } from './git-pane-widget'
|
|
9
14
|
import { buildTabGroupInfo } from './sidebar-group-metadata'
|
|
10
15
|
import { TabItem } from './tab-item'
|
|
@@ -13,12 +18,21 @@ import { useSidebarBranch } from './use-sidebar-branch'
|
|
|
13
18
|
|
|
14
19
|
interface SidebarProps {
|
|
15
20
|
onTabActivate?: (tabId: string) => void
|
|
21
|
+
onResizeDrag?: (event: OtuiMouseEvent) => boolean
|
|
22
|
+
onResizeDragEnd?: () => void
|
|
23
|
+
onSidebarResizeStart?: (info: { initialWidth: number; screenStart: number }) => void
|
|
24
|
+
onEmbeddedGitResizeStart?: (info: {
|
|
25
|
+
containerStart: number
|
|
26
|
+
position: 'top' | 'bottom'
|
|
27
|
+
totalSize: number
|
|
28
|
+
}) => void
|
|
16
29
|
}
|
|
17
30
|
|
|
18
31
|
const GUTTER_START = '╭'
|
|
19
32
|
const GUTTER_MIDDLE = '├'
|
|
20
33
|
const GUTTER_END = '╰'
|
|
21
34
|
const GUTTER_PAD = '│'
|
|
35
|
+
const RESIZE_HANDLE = '│'
|
|
22
36
|
|
|
23
37
|
function getRowBackground({
|
|
24
38
|
alternate,
|
|
@@ -34,9 +48,8 @@ function getRowBackground({
|
|
|
34
48
|
return undefined
|
|
35
49
|
}
|
|
36
50
|
|
|
37
|
-
const SidebarTop = memo(function SidebarTop() {
|
|
51
|
+
const SidebarTop = memo(function SidebarTop({ contentWidth }: { contentWidth: number }) {
|
|
38
52
|
const tokens = useTokens()
|
|
39
|
-
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
40
53
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
41
54
|
const sessions = useAppStore((s) => s.sessions)
|
|
42
55
|
const currentSession = currentSessionId
|
|
@@ -70,7 +83,7 @@ const SidebarTop = memo(function SidebarTop() {
|
|
|
70
83
|
>
|
|
71
84
|
<text fg={tokens.palette.ink}>+ New assistant</text>
|
|
72
85
|
</box>
|
|
73
|
-
<text fg={tokens.hover}>{'·'.repeat(Math.max(0,
|
|
86
|
+
<text fg={tokens.hover}>{'·'.repeat(Math.max(0, contentWidth - 2))}</text>
|
|
74
87
|
</box>
|
|
75
88
|
)
|
|
76
89
|
})
|
|
@@ -164,13 +177,20 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
164
177
|
)
|
|
165
178
|
})
|
|
166
179
|
|
|
167
|
-
export function Sidebar({
|
|
180
|
+
export function Sidebar({
|
|
181
|
+
onEmbeddedGitResizeStart,
|
|
182
|
+
onResizeDrag,
|
|
183
|
+
onResizeDragEnd,
|
|
184
|
+
onSidebarResizeStart,
|
|
185
|
+
onTabActivate,
|
|
186
|
+
}: SidebarProps) {
|
|
168
187
|
const tokens = useTokens()
|
|
169
188
|
const sidebarBg = useBg('elevated')
|
|
170
189
|
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
171
190
|
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
172
191
|
const gitPane = useAppStore((s) => s.gitPane)
|
|
173
192
|
const focusMode = useAppStore((s) => s.focusMode)
|
|
193
|
+
const bodyRef = useRef<BoxRenderable | null>(null)
|
|
174
194
|
|
|
175
195
|
if (!sidebarVisible) {
|
|
176
196
|
return null
|
|
@@ -179,15 +199,51 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
179
199
|
const gitEmbedded = gitPane.mode === 'embedded' && gitPane.visible
|
|
180
200
|
const gitOnTop = gitEmbedded && gitPane.position === 'top'
|
|
181
201
|
const gitOnBottom = gitEmbedded && gitPane.position === 'bottom'
|
|
202
|
+
const contentWidth = Math.max(1, sidebarWidth - 1)
|
|
203
|
+
const gitPaneMenu = buildGitPaneContextMenu(
|
|
204
|
+
gitPane,
|
|
205
|
+
() => dispatchGlobal({ type: 'toggle-git-pane' }),
|
|
206
|
+
(mode, position) => {
|
|
207
|
+
dispatchGlobal({ mode, type: 'set-git-pane-mode' })
|
|
208
|
+
dispatchGlobal({ position, type: 'set-git-pane-position' })
|
|
209
|
+
}
|
|
210
|
+
)
|
|
182
211
|
|
|
183
212
|
// flex-grow scaled by 100 (integer preferred); tabs gets (1-ratio), git gets ratio.
|
|
184
|
-
const tabsGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.
|
|
185
|
-
const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.
|
|
213
|
+
const tabsGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.embeddedRatio) * 100)) : 1
|
|
214
|
+
const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.embeddedRatio * 100)) : 0
|
|
186
215
|
|
|
187
|
-
const separator = <text fg={tokens.hover}>{'·'.repeat(Math.max(0,
|
|
216
|
+
const separator = <text fg={tokens.hover}>{'·'.repeat(Math.max(0, contentWidth - 2))}</text>
|
|
188
217
|
const gitBody = gitEmbedded ? (
|
|
189
|
-
<
|
|
218
|
+
<ContextMenuBox
|
|
219
|
+
flexDirection="column"
|
|
220
|
+
flexGrow={gitGrow}
|
|
221
|
+
flexShrink={1}
|
|
222
|
+
flexBasis={0}
|
|
223
|
+
overflow="hidden"
|
|
224
|
+
rightClickMenu={gitPaneMenu}
|
|
225
|
+
>
|
|
190
226
|
<GitPaneWidget pollingEnabled={gitPane.visible} />
|
|
227
|
+
</ContextMenuBox>
|
|
228
|
+
) : null
|
|
229
|
+
const embeddedHandle = gitEmbedded ? (
|
|
230
|
+
<box
|
|
231
|
+
minHeight={1}
|
|
232
|
+
flexShrink={0}
|
|
233
|
+
backgroundColor={tokens.border}
|
|
234
|
+
onMouseDown={(event) => {
|
|
235
|
+
const body = bodyRef.current
|
|
236
|
+
if (!body) return
|
|
237
|
+
event.preventDefault()
|
|
238
|
+
event.stopPropagation()
|
|
239
|
+
onEmbeddedGitResizeStart?.({
|
|
240
|
+
containerStart: body.y,
|
|
241
|
+
position: gitPane.position === 'top' ? 'top' : 'bottom',
|
|
242
|
+
totalSize: Math.max(1, body.height),
|
|
243
|
+
})
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
<text fg={tokens.border}>{RESIZE_HANDLE.repeat(Math.max(1, contentWidth))}</text>
|
|
191
247
|
</box>
|
|
192
248
|
) : null
|
|
193
249
|
|
|
@@ -198,6 +254,7 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
198
254
|
flexDirection="column"
|
|
199
255
|
backgroundColor={sidebarBg}
|
|
200
256
|
gap={0}
|
|
257
|
+
overflow="hidden"
|
|
201
258
|
rightClickMenu={[
|
|
202
259
|
['Hide sidebar', () => dispatchGlobal({ type: 'toggle-sidebar' })],
|
|
203
260
|
['Toggle git pane', () => dispatchGlobal({ type: 'toggle-git-pane' })],
|
|
@@ -214,29 +271,56 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
214
271
|
dispatchGlobal({ focusMode: 'navigation', type: 'set-focus-mode' })
|
|
215
272
|
}
|
|
216
273
|
}}
|
|
274
|
+
onMouseDrag={(event) => {
|
|
275
|
+
if (onResizeDrag?.(event)) {
|
|
276
|
+
event.preventDefault()
|
|
277
|
+
event.stopPropagation()
|
|
278
|
+
}
|
|
279
|
+
}}
|
|
280
|
+
onMouseUp={() => {
|
|
281
|
+
onResizeDragEnd?.()
|
|
282
|
+
}}
|
|
217
283
|
>
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
{
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
284
|
+
<box flexDirection="row" width={sidebarWidth} flexGrow={1} overflow="hidden">
|
|
285
|
+
<box width={contentWidth} flexGrow={1} flexDirection="column" overflow="hidden">
|
|
286
|
+
<SidebarTop contentWidth={contentWidth} />
|
|
287
|
+
<box ref={bodyRef} flexDirection="column" flexGrow={1} overflow="hidden">
|
|
288
|
+
{gitOnTop ? (
|
|
289
|
+
<>
|
|
290
|
+
{gitBody}
|
|
291
|
+
{embeddedHandle}
|
|
292
|
+
</>
|
|
293
|
+
) : null}
|
|
294
|
+
<box
|
|
295
|
+
flexDirection="column"
|
|
296
|
+
flexGrow={tabsGrow}
|
|
297
|
+
flexShrink={1}
|
|
298
|
+
flexBasis={0}
|
|
299
|
+
overflow="hidden"
|
|
300
|
+
>
|
|
301
|
+
<TabsBody onTabActivate={onTabActivate} />
|
|
302
|
+
</box>
|
|
303
|
+
{gitOnBottom ? (
|
|
304
|
+
<>
|
|
305
|
+
{embeddedHandle}
|
|
306
|
+
{gitBody}
|
|
307
|
+
</>
|
|
308
|
+
) : null}
|
|
309
|
+
</box>
|
|
310
|
+
{!gitEmbedded ? separator : null}
|
|
311
|
+
</box>
|
|
312
|
+
<box
|
|
313
|
+
height="100%"
|
|
314
|
+
width={1}
|
|
315
|
+
flexShrink={0}
|
|
316
|
+
backgroundColor={tokens.border}
|
|
317
|
+
onMouseDown={(event) => {
|
|
318
|
+
event.preventDefault()
|
|
319
|
+
event.stopPropagation()
|
|
320
|
+
onSidebarResizeStart?.({ initialWidth: sidebarWidth, screenStart: event.x })
|
|
321
|
+
}}
|
|
322
|
+
/>
|
|
233
323
|
</box>
|
|
234
|
-
{gitOnBottom ? (
|
|
235
|
-
<>
|
|
236
|
-
{separator}
|
|
237
|
-
{gitBody}
|
|
238
|
-
</>
|
|
239
|
-
) : null}
|
|
240
324
|
</ContextMenuBox>
|
|
241
325
|
)
|
|
242
326
|
}
|
package/src/ui/root.tsx
CHANGED
|
@@ -5,10 +5,14 @@ import type { ModalState, SessionRecord, SnippetRecord } from '../state/types'
|
|
|
5
5
|
import type { ThemeId } from './themes'
|
|
6
6
|
|
|
7
7
|
import { useAppStore } from '../state/app-store'
|
|
8
|
+
import { dispatchGlobal } from '../state/dispatch-ref'
|
|
9
|
+
import { getGitPaneWidthFromRatio } from '../state/git-pane-sizing'
|
|
8
10
|
import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout-tree'
|
|
11
|
+
import { ContextMenuBox } from './components/context-menu-box'
|
|
9
12
|
import { ContextMenuOverlay } from './components/context-menu-overlay'
|
|
10
13
|
import { CreateSessionModal } from './components/create-session-modal'
|
|
11
14
|
import { GitCommitModal } from './components/git-commit-modal'
|
|
15
|
+
import { buildGitPaneContextMenu } from './components/git-pane-context-menu'
|
|
12
16
|
import { GitPaneWidget } from './components/git-pane-widget'
|
|
13
17
|
import { GitView } from './components/git-view'
|
|
14
18
|
import { HelpModal } from './components/help-modal'
|
|
@@ -25,7 +29,7 @@ import { StatusBar } from './components/status-bar'
|
|
|
25
29
|
import { TerminalPane } from './components/terminal-pane'
|
|
26
30
|
import { ThemePickerModal } from './components/theme-picker-modal'
|
|
27
31
|
import { UpdateAvailableModal } from './components/update-available-modal'
|
|
28
|
-
import { useBg } from './theme'
|
|
32
|
+
import { useBg, useTokens } from './theme'
|
|
29
33
|
|
|
30
34
|
function getCreateSessionFields(modal: ModalState) {
|
|
31
35
|
if (modal.type !== 'create-session') {
|
|
@@ -195,6 +199,17 @@ interface RootViewProps {
|
|
|
195
199
|
onTerminalClick?: (event: MouseEvent, origin: TerminalContentOrigin, tabId?: string) => void
|
|
196
200
|
onPaneActivate?: (tabId: string) => void
|
|
197
201
|
onSplitResize?: (tabId: string, ratio: number, axis: SplitDirection) => void
|
|
202
|
+
onSidebarResizeStart?: (info: { initialWidth: number; screenStart: number }) => void
|
|
203
|
+
onGitPaneResizeStart?: (info: {
|
|
204
|
+
initialWidth: number
|
|
205
|
+
screenStart: number
|
|
206
|
+
side: 'left' | 'right'
|
|
207
|
+
}) => void
|
|
208
|
+
onEmbeddedGitResizeStart?: (info: {
|
|
209
|
+
containerStart: number
|
|
210
|
+
position: 'top' | 'bottom'
|
|
211
|
+
totalSize: number
|
|
212
|
+
}) => void
|
|
198
213
|
onSeparatorDragStart?: (info: {
|
|
199
214
|
tabId: string
|
|
200
215
|
direction: SplitDirection
|
|
@@ -211,10 +226,13 @@ export function RootView({
|
|
|
211
226
|
contentOrigin,
|
|
212
227
|
localScrollbackEnabled,
|
|
213
228
|
mouseForwardingEnabled,
|
|
229
|
+
onEmbeddedGitResizeStart,
|
|
230
|
+
onGitPaneResizeStart,
|
|
214
231
|
onPaneActivate,
|
|
215
232
|
onSeparatorDrag,
|
|
216
233
|
onSeparatorDragEnd,
|
|
217
234
|
onSeparatorDragStart,
|
|
235
|
+
onSidebarResizeStart,
|
|
218
236
|
onSplitResize,
|
|
219
237
|
onTerminalClick,
|
|
220
238
|
onTerminalMouseEvent,
|
|
@@ -238,7 +256,7 @@ export function RootView({
|
|
|
238
256
|
const gitPaneMode = useAppStore((s) => s.gitPane.mode)
|
|
239
257
|
const gitPaneVisible = useAppStore((s) => s.gitPane.visible)
|
|
240
258
|
const gitPanePosition = useAppStore((s) => s.gitPane.position)
|
|
241
|
-
const gitPaneRatio = useAppStore((s) => s.gitPane.
|
|
259
|
+
const gitPaneRatio = useAppStore((s) => s.gitPane.paneRatio)
|
|
242
260
|
|
|
243
261
|
const activeTab = tabs.find((tab) => tab.id === activeTabId)
|
|
244
262
|
const activeTree = activeTabId ? getTreeForTab(layoutTrees, tabGroupMap, activeTabId) : null
|
|
@@ -269,12 +287,36 @@ export function RootView({
|
|
|
269
287
|
}
|
|
270
288
|
|
|
271
289
|
return (
|
|
272
|
-
<box
|
|
290
|
+
<box
|
|
291
|
+
flexDirection="column"
|
|
292
|
+
width="100%"
|
|
293
|
+
height="100%"
|
|
294
|
+
backgroundColor={editorBg}
|
|
295
|
+
onMouseDrag={(event) => {
|
|
296
|
+
if (onSeparatorDrag?.(event)) {
|
|
297
|
+
event.preventDefault()
|
|
298
|
+
event.stopPropagation()
|
|
299
|
+
}
|
|
300
|
+
}}
|
|
301
|
+
onMouseUp={() => {
|
|
302
|
+
onSeparatorDragEnd?.()
|
|
303
|
+
}}
|
|
304
|
+
>
|
|
273
305
|
{sessionBarPosition === 'top' && <SessionBar />}
|
|
274
306
|
<box flexDirection="row" gap={0} padding={0} flexGrow={1}>
|
|
275
|
-
<Sidebar
|
|
307
|
+
<Sidebar
|
|
308
|
+
onTabActivate={onPaneActivate}
|
|
309
|
+
onEmbeddedGitResizeStart={onEmbeddedGitResizeStart}
|
|
310
|
+
onResizeDrag={onSeparatorDrag}
|
|
311
|
+
onResizeDragEnd={onSeparatorDragEnd}
|
|
312
|
+
onSidebarResizeStart={onSidebarResizeStart}
|
|
313
|
+
/>
|
|
276
314
|
{gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left' ? (
|
|
277
|
-
<GitPaneInPaneMode
|
|
315
|
+
<GitPaneInPaneMode
|
|
316
|
+
position="left"
|
|
317
|
+
ratio={gitPaneRatio}
|
|
318
|
+
onGitPaneResizeStart={onGitPaneResizeStart}
|
|
319
|
+
/>
|
|
278
320
|
) : null}
|
|
279
321
|
{activeTree && activeTree.type === 'split' ? (
|
|
280
322
|
<SplitLayout
|
|
@@ -321,7 +363,11 @@ export function RootView({
|
|
|
321
363
|
/>
|
|
322
364
|
)}
|
|
323
365
|
{gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'right' ? (
|
|
324
|
-
<GitPaneInPaneMode
|
|
366
|
+
<GitPaneInPaneMode
|
|
367
|
+
position="right"
|
|
368
|
+
ratio={gitPaneRatio}
|
|
369
|
+
onGitPaneResizeStart={onGitPaneResizeStart}
|
|
370
|
+
/>
|
|
325
371
|
) : null}
|
|
326
372
|
</box>
|
|
327
373
|
{sessionBarPosition === 'bottom' && <SessionBar />}
|
|
@@ -342,14 +388,58 @@ export function RootView({
|
|
|
342
388
|
)
|
|
343
389
|
}
|
|
344
390
|
|
|
345
|
-
function GitPaneInPaneMode({
|
|
391
|
+
function GitPaneInPaneMode({
|
|
392
|
+
onGitPaneResizeStart,
|
|
393
|
+
position,
|
|
394
|
+
ratio,
|
|
395
|
+
}: {
|
|
396
|
+
ratio: number
|
|
397
|
+
position: 'left' | 'right'
|
|
398
|
+
onGitPaneResizeStart?: (info: {
|
|
399
|
+
initialWidth: number
|
|
400
|
+
screenStart: number
|
|
401
|
+
side: 'left' | 'right'
|
|
402
|
+
}) => void
|
|
403
|
+
}) {
|
|
346
404
|
const bg = useBg('elevated')
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const width =
|
|
405
|
+
const tokens = useTokens()
|
|
406
|
+
const gitPane = useAppStore((s) => s.gitPane)
|
|
407
|
+
const width = getGitPaneWidthFromRatio(ratio)
|
|
408
|
+
const contentWidth = Math.max(1, width - 1)
|
|
409
|
+
const gitPaneMenu = buildGitPaneContextMenu(
|
|
410
|
+
gitPane,
|
|
411
|
+
() => dispatchGlobal({ type: 'toggle-git-pane' }),
|
|
412
|
+
(mode, nextPosition) => {
|
|
413
|
+
dispatchGlobal({ mode, type: 'set-git-pane-mode' })
|
|
414
|
+
dispatchGlobal({ position: nextPosition, type: 'set-git-pane-position' })
|
|
415
|
+
}
|
|
416
|
+
)
|
|
417
|
+
const handle = (
|
|
418
|
+
<box
|
|
419
|
+
width={1}
|
|
420
|
+
flexShrink={0}
|
|
421
|
+
backgroundColor={tokens.border}
|
|
422
|
+
onMouseDown={(event) => {
|
|
423
|
+
event.preventDefault()
|
|
424
|
+
event.stopPropagation()
|
|
425
|
+
onGitPaneResizeStart?.({ initialWidth: width, screenStart: event.x, side: position })
|
|
426
|
+
}}
|
|
427
|
+
/>
|
|
428
|
+
)
|
|
350
429
|
return (
|
|
351
430
|
<box flexDirection="column" width={width} flexShrink={0} backgroundColor={bg} overflow="hidden">
|
|
352
|
-
<
|
|
431
|
+
<box flexDirection="row" flexGrow={1} overflow="hidden">
|
|
432
|
+
{position === 'right' ? handle : null}
|
|
433
|
+
<ContextMenuBox
|
|
434
|
+
width={contentWidth}
|
|
435
|
+
flexGrow={1}
|
|
436
|
+
overflow="hidden"
|
|
437
|
+
rightClickMenu={gitPaneMenu}
|
|
438
|
+
>
|
|
439
|
+
<GitPaneWidget pollingEnabled />
|
|
440
|
+
</ContextMenuBox>
|
|
441
|
+
{position === 'left' ? handle : null}
|
|
442
|
+
</box>
|
|
353
443
|
</box>
|
|
354
444
|
)
|
|
355
445
|
}
|