@brimveyn/aimux 1.10.2 → 1.10.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 +1 -1
- package/src/app-runtime/use-terminal-resize.ts +1 -1
- package/src/ui/components/git/git-panel.tsx +29 -16
- package/src/ui/components/layout/sidebar/sidebar.tsx +32 -26
- package/src/ui/components/layout/sidebar/tab-item.tsx +43 -10
- package/src/ui/components/layout/split-layout.tsx +9 -0
- package/src/ui/components/layout/terminal-pane.tsx +28 -0
- package/src/ui/root.tsx +13 -1
package/package.json
CHANGED
|
@@ -140,7 +140,7 @@ export function useTerminalResize({
|
|
|
140
140
|
|
|
141
141
|
const gitPaneInPaneMode = state.gitPane.mode === 'pane' && state.gitPane.visible
|
|
142
142
|
const terminalSize = useMemo(() => {
|
|
143
|
-
const sidebarWidth = state.sidebar.visible ? state.sidebar.width
|
|
143
|
+
const sidebarWidth = state.sidebar.visible ? state.sidebar.width : 0
|
|
144
144
|
const sessionBarRows = state.sessionBar.visible ? 1 : 0
|
|
145
145
|
const sessionBarTopOffset =
|
|
146
146
|
state.sessionBar.visible && state.sessionBar.position === 'top' ? 1 : 0
|
|
@@ -108,7 +108,7 @@ function renderFileLabel(
|
|
|
108
108
|
const showDir = fileListMode === 'flat' && pathConfig.enabled && dir
|
|
109
109
|
if (!file.renamedFrom) {
|
|
110
110
|
return (
|
|
111
|
-
<text wrapMode="none">
|
|
111
|
+
<text selectable={false} wrapMode="none">
|
|
112
112
|
<span fg={t.text}>{basename}</span>
|
|
113
113
|
{showDir ? <span fg={t.textMuted}> {dir}</span> : null}
|
|
114
114
|
</text>
|
|
@@ -118,7 +118,7 @@ function renderFileLabel(
|
|
|
118
118
|
const renamed = splitPath(renamedDisplay)
|
|
119
119
|
const renamedDir = stripTrailingSlash(renamed.prefix)
|
|
120
120
|
return (
|
|
121
|
-
<text wrapMode="none">
|
|
121
|
+
<text selectable={false} wrapMode="none">
|
|
122
122
|
<span fg={t.textMuted}>{renamed.basename}</span>
|
|
123
123
|
{showDir && renamedDir ? <span fg={t.textMuted}> {renamedDir}</span> : null}
|
|
124
124
|
<span fg={t.textMuted}> → </span>
|
|
@@ -140,18 +140,23 @@ function renderDiffCount(
|
|
|
140
140
|
const t = getCurrentTheme()
|
|
141
141
|
if (!hasNumstat) {
|
|
142
142
|
return (
|
|
143
|
-
<text fg={t.textMuted} bg={bg} flexShrink={0}>
|
|
143
|
+
<text selectable={false} fg={t.textMuted} bg={bg} flexShrink={0}>
|
|
144
144
|
—
|
|
145
145
|
</text>
|
|
146
146
|
)
|
|
147
147
|
}
|
|
148
148
|
return (
|
|
149
149
|
<box flexDirection="row" flexShrink={0}>
|
|
150
|
-
<text
|
|
151
|
-
|
|
150
|
+
<text
|
|
151
|
+
selectable={false}
|
|
152
|
+
fg={getCurrentTheme().diffAdded}
|
|
153
|
+
bg={bg}
|
|
154
|
+
>{`+${padRight(file.added, addedW)}`}</text>
|
|
155
|
+
<text selectable={false} fg={t.textMuted} bg={bg}>
|
|
152
156
|
{' '}
|
|
153
157
|
</text>
|
|
154
158
|
<text
|
|
159
|
+
selectable={false}
|
|
155
160
|
fg={getCurrentTheme().diffRemoved}
|
|
156
161
|
bg={bg}
|
|
157
162
|
>{`−${padRight(file.removed, removedW)}`}</text>
|
|
@@ -172,13 +177,13 @@ function renderFolderRow(row: GitTreeFolderRow, isSelected: boolean): ReactNode
|
|
|
172
177
|
<box key={row.key} flexDirection="row" gap={1} backgroundColor={bg} onMouseDown={onSelect}>
|
|
173
178
|
<box flexGrow={1} overflow="hidden" paddingLeft={row.depth * 2}>
|
|
174
179
|
<box flexDirection="row" gap={1} onMouseDown={onToggle}>
|
|
175
|
-
<text fg={t.textMuted} bg={bg}>
|
|
180
|
+
<text selectable={false} fg={t.textMuted} bg={bg}>
|
|
176
181
|
{row.isCollapsed ? '▸' : '▾'}
|
|
177
182
|
</text>
|
|
178
|
-
<text fg={getCurrentTheme().textMuted} bg={bg}>
|
|
183
|
+
<text selectable={false} fg={getCurrentTheme().textMuted} bg={bg}>
|
|
179
184
|
{row.isCollapsed ? '\uf07b' : '\uf07c'}
|
|
180
185
|
</text>
|
|
181
|
-
<text fg={t.textMuted} bg={bg} wrapMode="none">
|
|
186
|
+
<text selectable={false} fg={t.textMuted} bg={bg} wrapMode="none">
|
|
182
187
|
{row.name}
|
|
183
188
|
</text>
|
|
184
189
|
</box>
|
|
@@ -210,13 +215,13 @@ function renderFileRow(
|
|
|
210
215
|
return (
|
|
211
216
|
<box key={row.key} flexDirection="row" gap={1} backgroundColor={bg} onMouseDown={onSelect}>
|
|
212
217
|
<box width={2} flexShrink={0} justifyContent="center">
|
|
213
|
-
<text fg={statusColor(file.status)} bg={bg}>
|
|
218
|
+
<text selectable={false} fg={statusColor(file.status)} bg={bg}>
|
|
214
219
|
<strong>{displayStatus(file)}</strong>
|
|
215
220
|
</text>
|
|
216
221
|
</box>
|
|
217
222
|
{repoTag ? (
|
|
218
223
|
<box flexShrink={0}>
|
|
219
|
-
<text fg={getCurrentTheme().primary} bg={bg}>
|
|
224
|
+
<text selectable={false} fg={getCurrentTheme().primary} bg={bg}>
|
|
220
225
|
<strong>{repoTag}</strong>
|
|
221
226
|
</text>
|
|
222
227
|
</box>
|
|
@@ -254,16 +259,22 @@ function renderTreeSection(
|
|
|
254
259
|
return (
|
|
255
260
|
<box key={section} flexDirection="column" marginTop={marginTop}>
|
|
256
261
|
<box flexDirection="row" justifyContent="space-between">
|
|
257
|
-
<text fg={t2.text}>
|
|
262
|
+
<text selectable={false} fg={t2.text}>
|
|
258
263
|
<strong>
|
|
259
264
|
{title} ({files.length})
|
|
260
265
|
</strong>
|
|
261
266
|
</text>
|
|
262
267
|
{showListModeToggle ? (
|
|
263
268
|
<box flexDirection="row" gap={1} onMouseDown={toggleListMode}>
|
|
264
|
-
<text fg={fileListMode === 'tree' ? t2.primary : t2.textMuted}>
|
|
265
|
-
|
|
266
|
-
|
|
269
|
+
<text selectable={false} fg={fileListMode === 'tree' ? t2.primary : t2.textMuted}>
|
|
270
|
+
tree
|
|
271
|
+
</text>
|
|
272
|
+
<text selectable={false} fg={t2.textMuted}>
|
|
273
|
+
|
|
|
274
|
+
</text>
|
|
275
|
+
<text selectable={false} fg={fileListMode === 'flat' ? t2.primary : t2.textMuted}>
|
|
276
|
+
flat
|
|
277
|
+
</text>
|
|
267
278
|
</box>
|
|
268
279
|
) : null}
|
|
269
280
|
</box>
|
|
@@ -295,7 +306,9 @@ function renderStatus(gitPanel: GitPanelState, hasProjectPath: boolean): ReactNo
|
|
|
295
306
|
if (!placeholder) return null
|
|
296
307
|
return (
|
|
297
308
|
<box flexGrow={1} flexDirection="column" alignItems="center" paddingTop={1}>
|
|
298
|
-
<text fg={placeholder.labelColor}>
|
|
309
|
+
<text selectable={false} fg={placeholder.labelColor}>
|
|
310
|
+
{placeholder.label}
|
|
311
|
+
</text>
|
|
299
312
|
</box>
|
|
300
313
|
)
|
|
301
314
|
}
|
|
@@ -370,7 +383,7 @@ export const GitPanel = memo(function GitPanel({
|
|
|
370
383
|
return (
|
|
371
384
|
<box flexDirection="column" flexGrow={1} flexShrink={1} flexBasis={0} overflow="hidden" gap={0}>
|
|
372
385
|
{hasRemoteTracking ? (
|
|
373
|
-
<text fg={t.textMuted}>
|
|
386
|
+
<text selectable={false} fg={t.textMuted}>
|
|
374
387
|
↑{gitPanel.ahead} ↓{gitPanel.behind}
|
|
375
388
|
</text>
|
|
376
389
|
) : null}
|
|
@@ -20,7 +20,6 @@ interface SidebarProps {
|
|
|
20
20
|
onTabActivate?: (tabId: string) => void
|
|
21
21
|
onResizeDrag?: (event: OtuiMouseEvent) => boolean
|
|
22
22
|
onResizeDragEnd?: () => void
|
|
23
|
-
onSidebarResizeStart?: (info: { initialWidth: number; screenStart: number }) => void
|
|
24
23
|
onEmbeddedGitResizeStart?: (info: {
|
|
25
24
|
containerStart: number
|
|
26
25
|
position: 'top' | 'bottom'
|
|
@@ -32,7 +31,7 @@ const GUTTER_START = '╭'
|
|
|
32
31
|
const GUTTER_MIDDLE = '├'
|
|
33
32
|
const GUTTER_END = '╰'
|
|
34
33
|
const GUTTER_PAD = '│'
|
|
35
|
-
const RESIZE_HANDLE = '
|
|
34
|
+
const RESIZE_HANDLE = '─'
|
|
36
35
|
|
|
37
36
|
function getRowBackground({
|
|
38
37
|
alternate,
|
|
@@ -60,14 +59,20 @@ const SidebarTop = memo(function SidebarTop({ contentWidth }: { contentWidth: nu
|
|
|
60
59
|
|
|
61
60
|
return (
|
|
62
61
|
<box flexDirection="column" flexShrink={0} gap={0}>
|
|
63
|
-
<text fg={t.text}>
|
|
62
|
+
<text fg={t.text} selectable={false}>
|
|
64
63
|
<strong>aimux</strong>
|
|
65
64
|
</text>
|
|
66
|
-
<text fg={t.text}
|
|
65
|
+
<text fg={t.text} selectable={false}>
|
|
66
|
+
{currentSession ? currentSession.name : 'No workspace selected'}
|
|
67
|
+
</text>
|
|
67
68
|
{branch ? (
|
|
68
69
|
<box flexDirection="row">
|
|
69
|
-
<text fg={t.text}
|
|
70
|
-
|
|
70
|
+
<text fg={t.text} selectable={false}>
|
|
71
|
+
{'\u{e702}'}{' '}
|
|
72
|
+
</text>
|
|
73
|
+
<text fg={t.text} selectable={false}>
|
|
74
|
+
{branch}
|
|
75
|
+
</text>
|
|
71
76
|
</box>
|
|
72
77
|
) : null}
|
|
73
78
|
<box
|
|
@@ -81,9 +86,13 @@ const SidebarTop = memo(function SidebarTop({ contentWidth }: { contentWidth: nu
|
|
|
81
86
|
dispatchGlobal({ type: 'open-new-tab-modal' })
|
|
82
87
|
}}
|
|
83
88
|
>
|
|
84
|
-
<text fg={t.text}
|
|
89
|
+
<text fg={t.text} selectable={false}>
|
|
90
|
+
+ New assistant
|
|
91
|
+
</text>
|
|
85
92
|
</box>
|
|
86
|
-
<text fg={t.textMuted}
|
|
93
|
+
<text fg={t.textMuted} selectable={false}>
|
|
94
|
+
{'·'.repeat(Math.max(0, contentWidth - 2))}
|
|
95
|
+
</text>
|
|
87
96
|
</box>
|
|
88
97
|
)
|
|
89
98
|
})
|
|
@@ -92,11 +101,13 @@ function renderGroupGutter(isGroupStart: boolean, isGroupMiddle: boolean, isGrou
|
|
|
92
101
|
const t = getCurrentTheme()
|
|
93
102
|
return (
|
|
94
103
|
<box flexDirection="column" width={1} overflow="hidden">
|
|
95
|
-
<text fg={t.border}>
|
|
104
|
+
<text fg={t.border} selectable={false}>
|
|
96
105
|
{/* oxlint-disable-next-line no-nested-ternary */}
|
|
97
106
|
{isGroupStart ? GUTTER_START : isGroupMiddle ? GUTTER_MIDDLE : GUTTER_PAD}
|
|
98
107
|
</text>
|
|
99
|
-
<text fg={t.border}
|
|
108
|
+
<text fg={t.border} selectable={false}>
|
|
109
|
+
{isGroupEnd ? GUTTER_END : GUTTER_PAD}
|
|
110
|
+
</text>
|
|
100
111
|
</box>
|
|
101
112
|
)
|
|
102
113
|
}
|
|
@@ -136,7 +147,9 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
136
147
|
>
|
|
137
148
|
{tabs.length === 0 ? (
|
|
138
149
|
<box paddingTop={1}>
|
|
139
|
-
<text fg={t.textMuted}
|
|
150
|
+
<text fg={t.textMuted} selectable={false}>
|
|
151
|
+
No tabs yet. Press Ctrl+n.
|
|
152
|
+
</text>
|
|
140
153
|
</box>
|
|
141
154
|
) : (
|
|
142
155
|
tabs.map((tab, index) => {
|
|
@@ -181,7 +194,6 @@ export function Sidebar({
|
|
|
181
194
|
onEmbeddedGitResizeStart,
|
|
182
195
|
onResizeDrag,
|
|
183
196
|
onResizeDragEnd,
|
|
184
|
-
onSidebarResizeStart,
|
|
185
197
|
onTabActivate,
|
|
186
198
|
}: SidebarProps) {
|
|
187
199
|
const t = useTheme()
|
|
@@ -213,7 +225,11 @@ export function Sidebar({
|
|
|
213
225
|
const tabsGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.embeddedRatio) * 100)) : 1
|
|
214
226
|
const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.embeddedRatio * 100)) : 0
|
|
215
227
|
|
|
216
|
-
const separator =
|
|
228
|
+
const separator = (
|
|
229
|
+
<text fg={t.textMuted} selectable={false}>
|
|
230
|
+
{'·'.repeat(Math.max(0, contentWidth - 2))}
|
|
231
|
+
</text>
|
|
232
|
+
)
|
|
217
233
|
const gitBody = gitEmbedded ? (
|
|
218
234
|
<ContextMenuBox
|
|
219
235
|
flexDirection="column"
|
|
@@ -230,7 +246,6 @@ export function Sidebar({
|
|
|
230
246
|
<box
|
|
231
247
|
minHeight={1}
|
|
232
248
|
flexShrink={0}
|
|
233
|
-
backgroundColor={t.border}
|
|
234
249
|
onMouseDown={(event) => {
|
|
235
250
|
const body = bodyRef.current
|
|
236
251
|
if (!body) return
|
|
@@ -243,7 +258,9 @@ export function Sidebar({
|
|
|
243
258
|
})
|
|
244
259
|
}}
|
|
245
260
|
>
|
|
246
|
-
<text fg={t.border}
|
|
261
|
+
<text fg={t.border} selectable={false}>
|
|
262
|
+
{RESIZE_HANDLE.repeat(Math.max(1, contentWidth))}
|
|
263
|
+
</text>
|
|
247
264
|
</box>
|
|
248
265
|
) : null
|
|
249
266
|
|
|
@@ -309,17 +326,6 @@ export function Sidebar({
|
|
|
309
326
|
</box>
|
|
310
327
|
{!gitEmbedded ? separator : null}
|
|
311
328
|
</box>
|
|
312
|
-
<box
|
|
313
|
-
height="100%"
|
|
314
|
-
width={1}
|
|
315
|
-
flexShrink={0}
|
|
316
|
-
backgroundColor={t.border}
|
|
317
|
-
onMouseDown={(event) => {
|
|
318
|
-
event.preventDefault()
|
|
319
|
-
event.stopPropagation()
|
|
320
|
-
onSidebarResizeStart?.({ initialWidth: sidebarWidth, screenStart: event.x })
|
|
321
|
-
}}
|
|
322
|
-
/>
|
|
323
329
|
</box>
|
|
324
330
|
</ContextMenuBox>
|
|
325
331
|
)
|
|
@@ -49,22 +49,38 @@ function getIndicatorColor(active: boolean, focused: boolean, inLayout: boolean)
|
|
|
49
49
|
function BusyIndicator() {
|
|
50
50
|
const t = useTheme()
|
|
51
51
|
const frame = useBusySpinner()
|
|
52
|
-
return
|
|
52
|
+
return (
|
|
53
|
+
<text fg={t.primary} selectable={false}>
|
|
54
|
+
{frame} working
|
|
55
|
+
</text>
|
|
56
|
+
)
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
function WaitingIndicator() {
|
|
56
60
|
const t = useTheme()
|
|
57
|
-
return
|
|
61
|
+
return (
|
|
62
|
+
<text fg={t.warning} selectable={false}>
|
|
63
|
+
? waiting
|
|
64
|
+
</text>
|
|
65
|
+
)
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
function ActivityIndicator({ tab }: { tab: TabSession }) {
|
|
61
69
|
const t = useTheme()
|
|
62
70
|
if (tab.status === 'error') {
|
|
63
|
-
return
|
|
71
|
+
return (
|
|
72
|
+
<text fg={t.error} selectable={false}>
|
|
73
|
+
✗ error
|
|
74
|
+
</text>
|
|
75
|
+
)
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
if (tab.status === 'disconnected') {
|
|
67
|
-
return
|
|
79
|
+
return (
|
|
80
|
+
<text fg={t.warning} selectable={false}>
|
|
81
|
+
⏸ restore
|
|
82
|
+
</text>
|
|
83
|
+
)
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
if (tab.activity === 'working') {
|
|
@@ -76,10 +92,18 @@ function ActivityIndicator({ tab }: { tab: TabSession }) {
|
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
if (tab.activity === 'idle') {
|
|
79
|
-
return
|
|
95
|
+
return (
|
|
96
|
+
<text fg={t.success} selectable={false}>
|
|
97
|
+
● idle
|
|
98
|
+
</text>
|
|
99
|
+
)
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
return
|
|
102
|
+
return (
|
|
103
|
+
<text fg={getStatusColor(tab.status)} selectable={false}>
|
|
104
|
+
{tab.status}
|
|
105
|
+
</text>
|
|
106
|
+
)
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
|
|
@@ -133,9 +157,13 @@ export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
|
|
|
133
157
|
onMouseOut={() => setHovered(false)}
|
|
134
158
|
>
|
|
135
159
|
<box flexDirection="row" alignItems="center">
|
|
136
|
-
<text fg={indicatorColor}
|
|
160
|
+
<text fg={indicatorColor} selectable={false}>
|
|
161
|
+
{indicator}{' '}
|
|
162
|
+
</text>
|
|
137
163
|
<box flexGrow={1}>
|
|
138
|
-
<text fg={active ? t.text : t.textMuted}
|
|
164
|
+
<text fg={active ? t.text : t.textMuted} selectable={false}>
|
|
165
|
+
{tab.title}
|
|
166
|
+
</text>
|
|
139
167
|
</box>
|
|
140
168
|
{hovered ? (
|
|
141
169
|
<box
|
|
@@ -145,12 +173,17 @@ export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
|
|
|
145
173
|
runSideEffectGlobal({ tabId: tab.id, type: 'close-tab' })
|
|
146
174
|
}}
|
|
147
175
|
>
|
|
148
|
-
<text fg={t.textMuted}
|
|
176
|
+
<text fg={t.textMuted} selectable={false}>
|
|
177
|
+
×
|
|
178
|
+
</text>
|
|
149
179
|
</box>
|
|
150
180
|
) : null}
|
|
151
181
|
</box>
|
|
152
182
|
<box flexDirection="row">
|
|
153
|
-
<text fg={t.textMuted}
|
|
183
|
+
<text fg={t.textMuted} selectable={false}>
|
|
184
|
+
{' '}
|
|
185
|
+
{label}{' '}
|
|
186
|
+
</text>
|
|
154
187
|
<ActivityIndicator tab={tab} />
|
|
155
188
|
</box>
|
|
156
189
|
</ContextMenuBox>
|
|
@@ -38,6 +38,7 @@ interface SplitLayoutProps {
|
|
|
38
38
|
}) => void
|
|
39
39
|
onSeparatorDrag?: (event: OtuiMouseEvent) => boolean
|
|
40
40
|
onSeparatorDragEnd?: () => void
|
|
41
|
+
onLeftEdgeMouseDown?: (event: OtuiMouseEvent) => boolean
|
|
41
42
|
contentOrigin: TerminalContentOrigin
|
|
42
43
|
bounds: PaneRect
|
|
43
44
|
}
|
|
@@ -50,6 +51,7 @@ export function SplitLayout({
|
|
|
50
51
|
localScrollbackEnabled,
|
|
51
52
|
mouseForwardingEnabled,
|
|
52
53
|
node,
|
|
54
|
+
onLeftEdgeMouseDown,
|
|
53
55
|
onPaneActivate,
|
|
54
56
|
onSeparatorDrag,
|
|
55
57
|
onSeparatorDragEnd,
|
|
@@ -101,6 +103,7 @@ export function SplitLayout({
|
|
|
101
103
|
onPaneActivate={onPaneActivate}
|
|
102
104
|
onSeparatorDrag={onSeparatorDrag}
|
|
103
105
|
onSeparatorDragEnd={onSeparatorDragEnd}
|
|
106
|
+
onLeftEdgeMouseDown={onLeftEdgeMouseDown}
|
|
104
107
|
/>
|
|
105
108
|
)
|
|
106
109
|
}
|
|
@@ -116,6 +119,10 @@ export function SplitLayout({
|
|
|
116
119
|
const firstBounds = subtreeBounds(node.first, rects, bounds)
|
|
117
120
|
const secondBounds = subtreeBounds(node.second, rects, bounds)
|
|
118
121
|
|
|
122
|
+
// For vertical splits only the first subtree is leftmost; for horizontal
|
|
123
|
+
// splits both first and second start at the same x.
|
|
124
|
+
const secondLeftEdgeMouseDown = node.direction === 'horizontal' ? onLeftEdgeMouseDown : undefined
|
|
125
|
+
|
|
119
126
|
return (
|
|
120
127
|
<box flexDirection={flexDir} flexGrow={1} gap={0}>
|
|
121
128
|
<box flexGrow={firstGrow} flexDirection="column" overflow="hidden">
|
|
@@ -136,6 +143,7 @@ export function SplitLayout({
|
|
|
136
143
|
onSeparatorDragStart={onSeparatorDragStart}
|
|
137
144
|
onSeparatorDrag={onSeparatorDrag}
|
|
138
145
|
onSeparatorDragEnd={onSeparatorDragEnd}
|
|
146
|
+
onLeftEdgeMouseDown={onLeftEdgeMouseDown}
|
|
139
147
|
contentOrigin={contentOrigin}
|
|
140
148
|
bounds={firstBounds}
|
|
141
149
|
/>
|
|
@@ -178,6 +186,7 @@ export function SplitLayout({
|
|
|
178
186
|
onSeparatorDragStart={onSeparatorDragStart}
|
|
179
187
|
onSeparatorDrag={onSeparatorDrag}
|
|
180
188
|
onSeparatorDragEnd={onSeparatorDragEnd}
|
|
189
|
+
onLeftEdgeMouseDown={secondLeftEdgeMouseDown}
|
|
181
190
|
contentOrigin={contentOrigin}
|
|
182
191
|
bounds={secondBounds}
|
|
183
192
|
/>
|
|
@@ -27,6 +27,7 @@ interface TerminalPaneProps {
|
|
|
27
27
|
onPaneActivate?: (tabId: string) => void
|
|
28
28
|
onSeparatorDrag?: (event: OtuiMouseEvent) => boolean
|
|
29
29
|
onSeparatorDragEnd?: () => void
|
|
30
|
+
onLeftEdgeMouseDown?: (event: OtuiMouseEvent) => boolean
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
function getTitle(
|
|
@@ -110,6 +111,7 @@ export function TerminalPane({
|
|
|
110
111
|
isActive,
|
|
111
112
|
localScrollbackEnabled,
|
|
112
113
|
mouseForwardingEnabled,
|
|
114
|
+
onLeftEdgeMouseDown,
|
|
113
115
|
onPaneActivate,
|
|
114
116
|
onSeparatorDrag,
|
|
115
117
|
onSeparatorDragEnd,
|
|
@@ -155,6 +157,11 @@ export function TerminalPane({
|
|
|
155
157
|
],
|
|
156
158
|
]
|
|
157
159
|
: undefined
|
|
160
|
+
const isOnPaneBorder = (event: OtuiMouseEvent) =>
|
|
161
|
+
event.x === contentOrigin.x - 1 ||
|
|
162
|
+
event.x === contentOrigin.x + contentOrigin.cols ||
|
|
163
|
+
event.y === contentOrigin.y - 1 ||
|
|
164
|
+
event.y === contentOrigin.y + contentOrigin.rows
|
|
158
165
|
const forwardMouseEvent = (event: OtuiMouseEvent) => {
|
|
159
166
|
if (event.type === 'down' && event.button === 2 && rightClickMenu) {
|
|
160
167
|
event.preventDefault()
|
|
@@ -162,6 +169,27 @@ export function TerminalPane({
|
|
|
162
169
|
openContextMenu(event.x, event.y, rightClickMenu)
|
|
163
170
|
return
|
|
164
171
|
}
|
|
172
|
+
if (
|
|
173
|
+
event.type === 'down' &&
|
|
174
|
+
event.button === 0 &&
|
|
175
|
+
onLeftEdgeMouseDown &&
|
|
176
|
+
event.x === contentOrigin.x - 1
|
|
177
|
+
) {
|
|
178
|
+
if (onLeftEdgeMouseDown(event)) {
|
|
179
|
+
event.preventDefault()
|
|
180
|
+
event.stopPropagation()
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Absorb left-button clicks on pane borders — they should not focus the
|
|
185
|
+
// pane or be forwarded to the terminal. This keeps borders available for
|
|
186
|
+
// resize actions (split separators, sidebar edge) without conflicting
|
|
187
|
+
// with focus-on-click behavior in the content area.
|
|
188
|
+
if (event.type === 'down' && event.button === 0 && isOnPaneBorder(event)) {
|
|
189
|
+
event.preventDefault()
|
|
190
|
+
event.stopPropagation()
|
|
191
|
+
return
|
|
192
|
+
}
|
|
165
193
|
if (event.type === 'down') {
|
|
166
194
|
logInputDebug('pane.mouseDown', {
|
|
167
195
|
button: event.button,
|
package/src/ui/root.tsx
CHANGED
|
@@ -271,6 +271,17 @@ export function RootView({
|
|
|
271
271
|
const gitPaneVisible = useAppStore((s) => s.gitPane.visible)
|
|
272
272
|
const gitPanePosition = useAppStore((s) => s.gitPane.position)
|
|
273
273
|
const gitPaneRatio = useAppStore((s) => s.gitPane.paneRatio)
|
|
274
|
+
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
275
|
+
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
276
|
+
|
|
277
|
+
const gitPaneInPaneOnLeft = gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left'
|
|
278
|
+
const handleTerminalLeftEdgeMouseDown =
|
|
279
|
+
sidebarVisible && !gitPaneInPaneOnLeft && onSidebarResizeStart
|
|
280
|
+
? (event: MouseEvent) => {
|
|
281
|
+
onSidebarResizeStart({ initialWidth: sidebarWidth, screenStart: event.x })
|
|
282
|
+
return true
|
|
283
|
+
}
|
|
284
|
+
: undefined
|
|
274
285
|
|
|
275
286
|
const activeTab = tabs.find((tab) => tab.id === activeTabId)
|
|
276
287
|
const activeTree = activeTabId ? getTreeForTab(layoutTrees, tabGroupMap, activeTabId) : null
|
|
@@ -331,7 +342,6 @@ export function RootView({
|
|
|
331
342
|
onEmbeddedGitResizeStart={onEmbeddedGitResizeStart}
|
|
332
343
|
onResizeDrag={onSeparatorDrag}
|
|
333
344
|
onResizeDragEnd={onSeparatorDragEnd}
|
|
334
|
-
onSidebarResizeStart={onSidebarResizeStart}
|
|
335
345
|
/>
|
|
336
346
|
{gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left' ? (
|
|
337
347
|
<GitPaneInPaneMode
|
|
@@ -364,6 +374,7 @@ export function RootView({
|
|
|
364
374
|
onSeparatorDragStart={onSeparatorDragStart}
|
|
365
375
|
onSeparatorDrag={onSeparatorDrag}
|
|
366
376
|
onSeparatorDragEnd={onSeparatorDragEnd}
|
|
377
|
+
onLeftEdgeMouseDown={handleTerminalLeftEdgeMouseDown}
|
|
367
378
|
bounds={{
|
|
368
379
|
cols: terminalCols + splitChrome,
|
|
369
380
|
rows: terminalRows + splitChrome,
|
|
@@ -386,6 +397,7 @@ export function RootView({
|
|
|
386
397
|
onTerminalDrag={onTerminalDrag}
|
|
387
398
|
onTerminalMouseUp={onTerminalMouseUp}
|
|
388
399
|
onPaneActivate={onPaneActivate}
|
|
400
|
+
onLeftEdgeMouseDown={handleTerminalLeftEdgeMouseDown}
|
|
389
401
|
/>
|
|
390
402
|
)}
|
|
391
403
|
{gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'right' ? (
|