Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit a8dc71f

Browse files
committed
feat: add support for left strip positioning of terminal splits
part of #8202
1 parent b783857 commit a8dc71f

File tree

23 files changed

+443
-260
lines changed

23 files changed

+443
-260
lines changed

packages/core/src/models/TabLayoutModificationResponse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export type NewSplitRequest = {
3939
inverseColors?: boolean
4040

4141
/** Is this split to be positioned specially, e.g. as a bottom strip? */
42-
position?: 'default' | 'bottom-strip'
42+
position?: 'default' | 'left-strip' | 'bottom-strip'
4343

4444
/** Execute this command line in the new split */
4545
cmdline?: string

packages/test/src/api/selectors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,13 @@ export const SPLIT_N = (N: number, inverseColors = false) =>
157157
`${SPLITS}:nth-child(${N})` + (inverseColors ? INVERTED_COLORS : '')
158158
export const SPLIT_N_HEADER = (N: number) => `${SPLIT_N(N)} .kui--split-header`
159159
export const SPLIT_N_CLOSE = (N: number) => `${SPLIT_N_HEADER(N)} .kui--split-close-button`
160-
export const SPLIT_N_SEND_TO_BOTTOM = (N: number) => `${SPLIT_N_HEADER(N)} .kui--split-bottom-strip-toggle`
160+
export const SPLIT_N_POSITION_TOGGLE = (N: number) => `${SPLIT_N_HEADER(N)} .kui--split-position-toggle`
161161
export const SPLIT_N_CLEAR = (N: number) => `${SPLIT_N_HEADER(N)} .kui--split-clear-button`
162162
export const SPLIT_N_FOCUS = (N: number) => `${SPLITS}:nth-child(${N}) ${current(_PROMPT_BLOCK)} ${_PROMPT}`
163163
export const SPLIT_N_OUTPUT = (N: number) => `${SPLITS}:nth-child(${N}) .repl-output`
164164

165165
export const SPLIT_N_AS_DEFAULT = (N: number) => `${SPLIT_N(N)}[data-position="default"]`
166+
export const SPLIT_N_AS_LEFT_STRIP = (N: number) => `${SPLIT_N(N)}[data-position="left-strip"]`
166167
export const SPLIT_N_AS_BOTTOM_STRIP = (N: number) => `${SPLIT_N(N)}[data-position="bottom-strip"]`
167168

168169
export const CURRENT_PROMPT_BLOCK_FOR_SPLIT = (splitIndex: number) => `${SPLIT_N(splitIndex)} ${current(_PROMPT_BLOCK)}`

plugins/plugin-client-common/notebooks/welcome.json

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"splits": [
99
{
1010
"uuid": "2_c57e811f-2da8-557e-a402-9f0950407675",
11-
"position": "default",
11+
"position": "left-strip",
1212
"inverseColors": true,
1313
"blocks": [
1414
{
@@ -92208,7 +92208,7 @@
9220892208
"command": "prereqs --provider Kubernetes",
9220992209
"startEvent": {
9221092210
"route": "/prereqs",
92211-
"startTime": 1634235327636,
92211+
"startTime": 1634591297035,
9221292212
"command": "prereqs --provider Kubernetes",
9221392213
"redirectDesired": false,
9221492214
"pipeStages": {
@@ -92221,19 +92221,20 @@
9222192221
"preferReExecute": true,
9222292222
"plugin": "plugin-core-support/up"
9222392223
},
92224-
"execType": 0,
92224+
"execType": 3,
9222592225
"execUUID": "8734d091-3ef8-4f6e-976b-2fc77f7c8960",
9222692226
"execOptions": {
92227-
"type": 0,
92228-
"language": "en-US",
92227+
"echo": true,
92228+
"type": 3,
9222992229
"execUUID": "8734d091-3ef8-4f6e-976b-2fc77f7c8960",
9223092230
"history": 249,
9223192231
"env": {}
92232-
}
92232+
},
92233+
"echo": true
9223392234
},
9223492235
"completeEvent": {
92235-
"execType": 0,
92236-
"completeTime": 1634235327707,
92236+
"execType": 3,
92237+
"completeTime": 1634591297248,
9223792238
"command": "prereqs --provider Kubernetes",
9223892239
"argvNoOptions": ["prereqs"],
9223992240
"parsedOptions": {
@@ -92247,14 +92248,15 @@
9224792248
},
9224892249
"execUUID": "8734d091-3ef8-4f6e-976b-2fc77f7c8960",
9224992250
"cancelled": false,
92251+
"echo": true,
9225092252
"evaluatorOptions": {
9225192253
"needsUI": true,
9225292254
"preferReExecute": true,
9225392255
"plugin": "plugin-core-support/up"
9225492256
},
9225592257
"execOptions": {
92256-
"type": 0,
92257-
"language": "en-US",
92258+
"echo": true,
92259+
"type": 3,
9225892260
"execUUID": "8734d091-3ef8-4f6e-976b-2fc77f7c8960",
9225992261
"history": 249,
9226092262
"env": {}
@@ -92301,10 +92303,10 @@
9230192303
},
9230292304
"isExperimental": false,
9230392305
"isReplay": true,
92304-
"execUUID": "8734d091-3ef8-4f6e-976b-2fc77f7c8960",
92306+
"execUUID": "8b2d072d-65a7-46d9-92f7-97e347fee774",
9230592307
"isSectionBreak": false,
9230692308
"originalExecUUID": "8734d091-3ef8-4f6e-976b-2fc77f7c8960",
92307-
"startTime": 1634235327636,
92309+
"startTime": 1634591297035,
9230892310
"outputOnly": false,
9230992311
"state": "valid-response"
9231092312
}

plugins/plugin-client-common/src/components/Client/TabContent.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ type State = Partial<WithTab> & {
5656
sessionInitError?: Error
5757
showSessionInitDone: boolean
5858

59+
/** Does this tab have a left strip layout? */
60+
hasLeftStrip: boolean
61+
5962
/** Does this tab have a bottom strip layout? */
6063
hasBottomStrip: boolean
6164

@@ -99,6 +102,7 @@ export default class TabContent extends React.PureComponent<Props, State> {
99102
tab: React.createRef(),
100103
sessionInit: 'NotYet',
101104
showSessionInitDone: true,
105+
hasLeftStrip: false,
102106
hasBottomStrip: false,
103107
_terminal: React.createRef(),
104108
mutability: initializeState(this.props.snapshot)
@@ -241,6 +245,9 @@ export default class TabContent extends React.PureComponent<Props, State> {
241245
return 'Please wait while we connect to your cluster'
242246
}
243247

248+
/** Enter/exit mode where one split is displayed along the left */
249+
private readonly _toggleLeftStripMode = () => this.setState(curState => ({ hasLeftStrip: !curState.hasLeftStrip }))
250+
244251
/** Enter/exit mode where one split is displayed along the bottom */
245252
private readonly _toggleBottomStripMode = () =>
246253
this.setState(curState => ({ hasBottomStrip: !curState.hasBottomStrip }))
@@ -275,7 +282,9 @@ export default class TabContent extends React.PureComponent<Props, State> {
275282
toggleAttribute={this._toggleAttribute}
276283
onClear={this._onClear}
277284
ref={this.state._terminal}
285+
hasLeftStrip={this.state.hasLeftStrip}
278286
hasBottomStrip={this.state.hasBottomStrip}
287+
willToggleLeftStripMode={this._toggleLeftStripMode}
279288
willToggleBottomStripMode={this._toggleBottomStripMode}
280289
noActiveInput={this.props.noActiveInput || !this.state.mutability.editable}
281290
>
@@ -384,6 +393,7 @@ export default class TabContent extends React.PureComponent<Props, State> {
384393
ref={this.state.tab}
385394
className={this.tabClassName()}
386395
data-tab-id={this.props.uuid}
396+
data-has-left-strip={this.state.hasLeftStrip || undefined}
387397
data-has-bottom-strip={this.state.hasBottomStrip || undefined}
388398
>
389399
{this.body()}

plugins/plugin-client-common/src/components/Views/Terminal/ScrollableTerminal.tsx

Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import ScrollbackState, { ScrollbackOptions, Cleaner } from './ScrollbackState'
4848
import Block from './Block'
4949
import getSize from './getSize'
5050
import SplitHeader from './SplitHeader'
51+
import { SplitPositionProps } from './SplitPosition'
5152
import { NotebookImpl, isNotebookImpl, snapshot, FlightRecorder, tabAlignment } from './Snapshot'
5253
import KuiConfiguration from '../../Client/KuiConfiguration'
5354
import SessionInitStatus from '../../Client/SessionInitStatus'
@@ -103,35 +104,36 @@ export interface TerminalOptions {
103104
noActiveInput?: boolean
104105
}
105106

106-
type Props = TerminalOptions & {
107-
/** tab UUID */
108-
uuid: string
107+
type Props = TerminalOptions &
108+
SplitPositionProps & {
109+
/** tab UUID */
110+
uuid: string
109111

110-
/** tab model */
111-
tab: KuiTab
112+
/** tab model */
113+
tab: KuiTab
112114

113-
snapshot?: Buffer
115+
snapshot?: Buffer
114116

115-
tabTitle?: string
117+
tabTitle?: string
116118

117-
/** handler for terminal clear */
118-
onClear?: () => void
119+
/** handler for terminal clear */
120+
onClear?: () => void
119121

120-
/** KuiConfiguration */
121-
config: KuiConfiguration
122+
/** KuiConfiguration */
123+
config: KuiConfiguration
122124

123-
/** Toggle attribute on Tab DOM */
124-
toggleAttribute(attr: string): void
125+
/** Toggle attribute on Tab DOM */
126+
toggleAttribute(attr: string): void
125127

126-
/** Status of the proxy session (for client-server architectures of Kui) */
127-
sessionInit: SessionInitStatus
128+
/** Status of the proxy session (for client-server architectures of Kui) */
129+
sessionInit: SessionInitStatus
128130

129-
/** Are we to display one of the splits as a bottom strip? */
130-
hasBottomStrip: boolean
131+
/** Toggle whether we have a left strip split */
132+
willToggleLeftStripMode(): void
131133

132-
/** Toggle whether we have a bottom strip split */
133-
willToggleBottomStripMode(): void
134-
}
134+
/** Toggle whether we have a bottom strip split */
135+
willToggleBottomStripMode(): void
136+
}
135137

136138
interface State {
137139
/** This helps enforce sequential block execution semantics */
@@ -255,7 +257,9 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
255257
position: split.position
256258
})
257259

258-
if (split.position === 'bottom-strip') {
260+
if (split.position === 'left-strip') {
261+
this.props.willToggleLeftStripMode()
262+
} else if (split.position === 'bottom-strip') {
259263
this.props.willToggleBottomStripMode()
260264
}
261265

@@ -473,7 +477,7 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
473477
scrollableRef: undefined,
474478

475479
position: opts.position || 'default',
476-
willToggleBottomStripMode: undefined
480+
willToggleSplitPosition: undefined
477481
}
478482

479483
const getBlockIndexFromEvent = (evt: React.SyntheticEvent, doNotComplain = false) => {
@@ -692,17 +696,35 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
692696
}
693697
}
694698

695-
if (this.props.willToggleBottomStripMode) {
696-
state.willToggleBottomStripMode = () => {
697-
const sbidx = this.findSplit(this.state, sbuuid)
698-
if (sbidx >= 0) {
699-
const scrollback = this.state.splits[sbidx]
700-
if (scrollback.position === 'default') {
701-
scrollback.position = 'bottom-strip'
699+
state.willToggleSplitPosition = () => {
700+
const sbidx = this.findSplit(this.state, sbuuid)
701+
if (sbidx >= 0) {
702+
const scrollback = this.state.splits[sbidx]
703+
if (scrollback.position === 'default') {
704+
if (this.props.hasBottomStrip) {
705+
// this split is default, and we have a bottom split; make this a left split
706+
scrollback.position = 'left-strip'
707+
this.props.willToggleLeftStripMode()
702708
} else {
709+
// this split is default, and we don't have a bottom split; make this a bottom split
710+
scrollback.position = 'bottom-strip'
711+
this.props.willToggleBottomStripMode()
712+
}
713+
} else if (scrollback.position === 'bottom-strip') {
714+
if (this.props.hasLeftStrip) {
715+
// this split is bottom, and we have a left split; revert this to default
703716
scrollback.position = 'default'
717+
this.props.willToggleBottomStripMode()
718+
} else {
719+
// this split is bottom, and we don't have a left split; make this a left split
720+
scrollback.position = 'left-strip'
721+
this.props.willToggleLeftStripMode()
722+
this.props.willToggleBottomStripMode()
704723
}
705-
this.props.willToggleBottomStripMode()
724+
} else {
725+
// this split is left; always return to default
726+
this.props.willToggleLeftStripMode()
727+
scrollback.position = 'default'
706728
}
707729
}
708730
}
@@ -1309,10 +1331,20 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
13091331
// a bottom strip... we have a choice: either turn the bottom
13101332
// strip into a default split, or refuse the request to
13111333
// close. For now, we opt for the former
1312-
if (this.props.hasBottomStrip && curState.splits.length === 2 && curState.splits[idx].position === 'default') {
1334+
if (
1335+
(this.props.hasBottomStrip || this.props.hasLeftStrip) &&
1336+
curState.splits.length === 2 &&
1337+
curState.splits[idx].position === 'default'
1338+
) {
13131339
const otherSplit = curState.splits[1 - idx]
13141340
otherSplit.position = 'default'
1315-
setTimeout(() => this.props.willToggleBottomStripMode())
1341+
setTimeout(() => {
1342+
if (this.props.hasBottomStrip) {
1343+
this.props.willToggleBottomStripMode()
1344+
} else {
1345+
this.props.willToggleLeftStripMode()
1346+
}
1347+
})
13161348
}
13171349

13181350
eventBus.emitTabLayoutChange(sbuuid)
@@ -1325,7 +1357,10 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
13251357
this.scrollbackCounter--
13261358
}
13271359

1328-
if (curState.splits[idx].position !== 'default') {
1360+
if (curState.splits[idx].position === 'left-strip') {
1361+
// then we are closing the left strip
1362+
setTimeout(() => this.props.willToggleLeftStripMode())
1363+
} else if (curState.splits[idx].position === 'bottom-strip') {
13291364
// then we are closing the bottom strip
13301365
setTimeout(() => this.props.willToggleBottomStripMode())
13311366
}
@@ -1611,12 +1646,19 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
16111646
{this.state.splits.length > 1 && (
16121647
<SplitHeader
16131648
position={scrollback.position}
1614-
onRemove={!this.props.hasBottomStrip || this.state.splits.length >= 2 ? scrollback.remove : undefined}
1649+
onRemove={scrollback.remove}
16151650
onClear={scrollback.clear}
16161651
onInvert={scrollback.invert}
1617-
willToggleBottomStripMode={
1618-
(!this.props.hasBottomStrip || scrollback.position === 'bottom-strip') &&
1619-
scrollback.willToggleBottomStripMode
1652+
hasLeftStrip={this.props.hasLeftStrip}
1653+
hasBottomStrip={this.props.hasBottomStrip}
1654+
willToggleSplitPosition={
1655+
this.props.hasLeftStrip && this.props.hasBottomStrip && scrollback.position === 'default'
1656+
? undefined // have both strips already and this is a default split? no toggle for you!
1657+
: (this.props.hasLeftStrip || this.props.hasBottomStrip) &&
1658+
this.state.splits.length === 2 &&
1659+
scrollback.position === 'default'
1660+
? undefined // have 2 splits, and one of them is non-default, and this is a default? also no toggler for you
1661+
: scrollback.willToggleSplitPosition
16201662
}
16211663
/>
16221664
)}

plugins/plugin-client-common/src/components/Views/Terminal/ScrollbackState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type ScrollbackState = ScrollbackOptions & {
3232

3333
/** Display as strip along the bottom */
3434
position: SplitPosition
35-
willToggleBottomStripMode(): void
35+
willToggleSplitPosition(): void
3636

3737
/** tab facade */
3838
facade?: KuiTab

0 commit comments

Comments
 (0)