Skip to content

Commit be2582f

Browse files
feat(plugin): decompose tab controls (#48129)
Co-authored-by: vimtor <36263538+vimtor@users.noreply.github.com> Co-authored-by: Victor Navarro <vn4varro@gmail.com>
1 parent c0cb1c7 commit be2582f

5 files changed

Lines changed: 43 additions & 7 deletions

File tree

packages/plugin/src/tui/context.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,10 +487,12 @@ export interface UI {
487487
readonly attention: boolean
488488
readonly unread?: "activity" | "error"
489489
}[]
490-
/** Opens (or focuses) a tab for a session, adding it when not already open. Returns false when tabs are disabled. */
490+
/** Opens a tab for a session without focusing it. Returns false when tabs are disabled. */
491491
open(sessionID: string): boolean
492-
/** Focuses an already-open tab and returns false when it is not open. */
492+
/** Opens a tab when needed, then focuses it. Returns false when tabs are disabled. */
493493
focus(sessionID: string): boolean
494+
/** Moves an open tab to an index and returns false when it is not open. */
495+
move(sessionID: string, index: number): boolean
494496
/** Closes an open tab, or the active tab when omitted, and returns false when no tab matched. */
495497
close(sessionID?: string): boolean
496498
}

packages/tui/src/context/session-tabs.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,15 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
395395
if (!enabled()) return
396396
route.navigate({ type: "session", sessionID: root(sessionID) })
397397
},
398+
open(sessionID: string) {
399+
if (!enabled()) return
400+
const session = root(sessionID)
401+
if (state().tabs.some((tab) => tab.sessionID === session)) return
402+
cancelledTabs.delete(session)
403+
update((draft) => {
404+
draft.tabs = openSessionTab(draft.tabs, { sessionID: session, title: title(session) })
405+
})
406+
},
398407
promote(sessionID: string) {
399408
if (!enabled()) return
400409
const session = root(sessionID)

packages/tui/src/plugin/api.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,21 @@ export function createPluginContext(input: {
206206
}),
207207
open(sessionID) {
208208
if (!host.sessionTabs.enabled()) return false
209-
host.sessionTabs.select(sessionID)
209+
host.sessionTabs.open(sessionID)
210210
return true
211211
},
212212
focus(sessionID) {
213213
if (!host.sessionTabs.enabled()) return false
214-
if (!host.sessionTabs.tabs().some((tab) => tab.sessionID === sessionID)) return false
215214
host.sessionTabs.select(sessionID)
216215
return true
217216
},
217+
move(sessionID, index) {
218+
if (!host.sessionTabs.enabled()) return false
219+
const target = host.data.session.root(sessionID)
220+
if (!host.sessionTabs.tabs().some((tab) => tab.sessionID === target)) return false
221+
host.sessionTabs.move(target, index)
222+
return true
223+
},
218224
close(sessionID) {
219225
if (!host.sessionTabs.enabled()) return false
220226
const target = sessionID ?? host.sessionTabs.current()

packages/tui/test/context/session-tabs.test.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,23 @@ test("loads VCS metadata for each persisted tab location", async () => {
265265
}
266266
})
267267

268+
test("opens a background tab without changing the current session", async () => {
269+
const setup = await renderSessionTabs("first")
270+
271+
try {
272+
await wait(() => setup.tabs.current() === "first" && setup.tabs.tabs().some((tab) => tab.sessionID === "first"))
273+
setup.tabs.open("background")
274+
await wait(() => setup.tabs.tabs().some((tab) => tab.sessionID === "background"))
275+
276+
expect(setup.tabs.current()).toBe("first")
277+
expect(setup.tabs.isPreview("background")).toBe(false)
278+
setup.tabs.move("background", 0)
279+
await wait(() => setup.tabs.tabs()[0]?.sessionID === "background")
280+
} finally {
281+
await setup.destroy()
282+
}
283+
})
284+
268285
test("loads location metadata when an open session moves", async () => {
269286
const destination = `${directory}/moved-worktree`
270287
const setup = await renderSessionTabs("first")

services/www/src/docs/content/build/plugins/cli.mdx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,13 +375,15 @@ context.ui.router.navigate({ type: "home" })
375375
return unregister
376376
```
377377

378-
Tabs can be listed, opened, focused, and closed when session tabs are enabled.
378+
Tabs can be listed, opened, focused, moved, and closed when session tabs are enabled. `open` leaves focus unchanged;
379+
`focus` opens the tab when needed.
379380

380381
```ts
381382
if (context.ui.tabs.enabled()) {
382-
context.ui.tabs.open(sessionID)
383-
const tabs = context.ui.tabs.list()
383+
context.ui.tabs.open(backgroundSessionID)
384384
context.ui.tabs.focus(sessionID)
385+
const tabs = context.ui.tabs.list()
386+
context.ui.tabs.move(backgroundSessionID, tabs.length - 1)
385387
context.ui.tabs.close(sessionID)
386388
context.ui.tabs.close()
387389
}

0 commit comments

Comments
 (0)