Skip to content

Commit 3b41153

Browse files
Apply PR #11196: feat(app): command + w to close the current tab and fix issue of going to first tab when closing
2 parents 8a90a58 + ef66e32 commit 3b41153

4 files changed

Lines changed: 43 additions & 13 deletions

File tree

packages/app/src/components/session/session-sortable-tab.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import type { JSX } from "solid-js"
33
import { createSortable } from "@thisbeyond/solid-dnd"
44
import { FileIcon } from "@opencode-ai/ui/file-icon"
55
import { IconButton } from "@opencode-ai/ui/icon-button"
6-
import { Tooltip } from "@opencode-ai/ui/tooltip"
6+
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
77
import { Tabs } from "@opencode-ai/ui/tabs"
88
import { getFilename } from "@opencode-ai/util/path"
99
import { useFile } from "@/context/file"
1010
import { useLanguage } from "@/context/language"
11+
import { useCommand } from "@/context/command"
1112

1213
export function FileVisual(props: { path: string; active?: boolean }): JSX.Element {
1314
return (
@@ -27,6 +28,7 @@ export function FileVisual(props: { path: string; active?: boolean }): JSX.Eleme
2728
export function SortableTab(props: { tab: string; onTabClose: (tab: string) => void }): JSX.Element {
2829
const file = useFile()
2930
const language = useLanguage()
31+
const command = useCommand()
3032
const sortable = createSortable(props.tab)
3133
const path = createMemo(() => file.pathFromTab(props.tab))
3234
return (
@@ -36,15 +38,15 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
3638
<Tabs.Trigger
3739
value={props.tab}
3840
closeButton={
39-
<Tooltip value={language.t("common.closeTab")} placement="bottom">
41+
<TooltipKeybind title={language.t("common.closeTab")} keybind={command.keybind("tab.close")} placement="bottom">
4042
<IconButton
4143
icon="close-small"
4244
variant="ghost"
4345
class="h-5 w-5"
4446
onClick={() => props.onTabClose(props.tab)}
4547
aria-label={language.t("common.closeTab")}
4648
/>
47-
</Tooltip>
49+
</TooltipKeybind>
4850
}
4951
hideCloseButton
5052
onMiddleClick={() => props.onTabClose(props.tab)}

packages/app/src/context/layout.tsx

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export function getAvatarColors(key?: string) {
2828
type SessionTabs = {
2929
active?: string
3030
all: string[]
31+
history: string[]
3132
}
3233

3334
type SessionView = {
@@ -621,16 +622,20 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
621622
),
622623
)
623624

624-
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
625+
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [], history: [] })
625626
return {
626627
tabs,
627628
active: createMemo(() => (tabs().active === "review" ? undefined : tabs().active)),
628629
all: createMemo(() => tabs().all.filter((tab) => tab !== "review")),
629630
setActive(tab: string | undefined) {
630631
const session = key()
631632
if (tab === "review") return
632-
if (!store.sessionTabs[session]) {
633-
setStore("sessionTabs", session, { all: [], active: tab })
633+
const current = store.sessionTabs[session]
634+
if (current?.active && current.active !== tab) {
635+
setStore("sessionTabs", session, "history", [...(current.history ?? []), current.active])
636+
}
637+
if (!current) {
638+
setStore("sessionTabs", session, { all: [], active: tab, history: [] })
634639
} else {
635640
setStore("sessionTabs", session, "active", tab)
636641
}
@@ -639,41 +644,50 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
639644
const session = key()
640645
const next = all.filter((tab) => tab !== "review")
641646
if (!store.sessionTabs[session]) {
642-
setStore("sessionTabs", session, { all: next, active: undefined })
647+
setStore("sessionTabs", session, { all: next, active: undefined, history: [] })
643648
} else {
644649
setStore("sessionTabs", session, "all", next)
645650
}
646651
},
647652
async open(tab: string) {
648653
if (tab === "review") return
649654
const session = key()
650-
const current = store.sessionTabs[session] ?? { all: [] }
655+
const current = store.sessionTabs[session] ?? { all: [], history: [] }
656+
657+
const pushHistory = () => {
658+
if (current.active && current.active !== tab) {
659+
setStore("sessionTabs", session, "history", [...(current.history ?? []), current.active])
660+
}
661+
}
651662

652663
if (tab === "context") {
653664
const all = [tab, ...current.all.filter((x) => x !== tab)]
654665
if (!store.sessionTabs[session]) {
655-
setStore("sessionTabs", session, { all, active: tab })
666+
setStore("sessionTabs", session, { all, active: tab, history: [] })
656667
return
657668
}
669+
pushHistory()
658670
setStore("sessionTabs", session, "all", all)
659671
setStore("sessionTabs", session, "active", tab)
660672
return
661673
}
662674

663675
if (!current.all.includes(tab)) {
664676
if (!store.sessionTabs[session]) {
665-
setStore("sessionTabs", session, { all: [tab], active: tab })
677+
setStore("sessionTabs", session, { all: [tab], active: tab, history: [] })
666678
return
667679
}
680+
pushHistory()
668681
setStore("sessionTabs", session, "all", [...current.all, tab])
669682
setStore("sessionTabs", session, "active", tab)
670683
return
671684
}
672685

673686
if (!store.sessionTabs[session]) {
674-
setStore("sessionTabs", session, { all: current.all, active: tab })
687+
setStore("sessionTabs", session, { all: current.all, active: tab, history: [] })
675688
return
676689
}
690+
pushHistory()
677691
setStore("sessionTabs", session, "active", tab)
678692
},
679693
close(tab: string) {
@@ -682,12 +696,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
682696
if (!current) return
683697

684698
const all = current.all.filter((x) => x !== tab)
699+
const history = (current.history ?? []).filter((x) => x !== tab)
700+
685701
batch(() => {
686702
setStore("sessionTabs", session, "all", all)
703+
setStore("sessionTabs", session, "history", history)
687704
if (current.active !== tab) return
688705

689-
const index = current.all.findIndex((f) => f === tab)
690-
const next = all[index - 1] ?? all[0]
706+
const next = history.pop() ?? all[0]
691707
setStore("sessionTabs", session, "active", next)
692708
})
693709
},

packages/app/src/i18n/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const dict = {
4343
"command.session.new": "New session",
4444
"command.file.open": "Open file",
4545
"command.file.open.description": "Search files and commands",
46+
"command.tab.close": "Close tab",
4647
"command.context.addSelection": "Add selection to context",
4748
"command.context.addSelection.description": "Add selected lines from the current file",
4849
"command.terminal.toggle": "Toggle terminal",

packages/app/src/pages/session.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,17 @@ export default function Page() {
689689
slash: "open",
690690
onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={() => showAllFiles()} />),
691691
},
692+
{
693+
id: "tab.close",
694+
title: language.t("command.tab.close"),
695+
category: language.t("command.category.file"),
696+
keybind: "mod+w",
697+
disabled: !tabs().active(),
698+
onSelect: () => {
699+
const active = tabs().active()
700+
if (active) tabs().close(active)
701+
},
702+
},
692703
{
693704
id: "context.addSelection",
694705
title: language.t("command.context.addSelection"),

0 commit comments

Comments
 (0)