mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-15 12:41:37 +03:00
fix(app): stop an extra ⌘W closing the session after the side panel empties (#58497)
This commit is contained in:
committed by
GitHub
Unverified
parent
7d70f22f33
commit
e4b15661b9
@@ -1191,6 +1191,26 @@ test("tab shortcuts cycle side-panel tabs when the side panel is focused with mu
|
||||
await expectPath(page, sessionHref(beta.id))
|
||||
})
|
||||
|
||||
test("⌘W past the side panel's last tab keeps the session tab", async ({ page }) => {
|
||||
const sessions = await emptySidePanel(page)
|
||||
await page.keyboard.press("ControlOrMeta+w")
|
||||
|
||||
// From the composer ⌘W closes the session tab, so only a swallowed press above leaves Beta to land on.
|
||||
await page.getByRole("textbox", { name: "Prompt", exact: true }).click()
|
||||
await page.keyboard.press("ControlOrMeta+w")
|
||||
await expectPath(page, sessionHref(sessions.beta.id))
|
||||
await expect(sessions.alphaTab).toHaveCount(0)
|
||||
})
|
||||
|
||||
test("⌘W closes the session tab again once the window loses focus after the side panel empties", async ({ page }) => {
|
||||
const sessions = await emptySidePanel(page)
|
||||
// ⌘Tab away and back sends no click or key the page sees.
|
||||
await page.evaluate(() => window.dispatchEvent(new Event("blur")))
|
||||
await page.keyboard.press("ControlOrMeta+w")
|
||||
await expectPath(page, sessionHref(sessions.beta.id))
|
||||
await expect(sessions.alphaTab).toHaveCount(0)
|
||||
})
|
||||
|
||||
// Windows has no native menu bar: the titlebar menu owns Paste, which only the desktop edit action can perform.
|
||||
test("the Windows titlebar menu pastes through the desktop edit action", async ({ page }) => {
|
||||
await mockWorkspace(page, { name: "WindowsMenu", sessions: [] })
|
||||
@@ -1279,3 +1299,37 @@ test("loads a tab's transcript while the pointer rests on it, and not when it pa
|
||||
await expect(message(b.id)).toBeVisible()
|
||||
expect(requested.filter((id) => id === b.id)).toHaveLength(1)
|
||||
})
|
||||
|
||||
// Closes Alpha's side tabs with ⌘W until the side panel empties and collapses, as an overshooting hand would.
|
||||
async function emptySidePanel(page: Page) {
|
||||
await page.setViewportSize({ width: 1440, height: 900 })
|
||||
const directory = "C:/OpenCode/SideCloseOvershoot"
|
||||
const alpha = { id: "ses_side_close_alpha", title: "Side close alpha" }
|
||||
const beta = { id: "ses_side_close_beta", title: "Side close beta" }
|
||||
await openSession(page, {
|
||||
name: "SideCloseOvershoot",
|
||||
sessions: [alpha, beta],
|
||||
fileList: (path) => (path ? [] : ["README.md"].map((file) => fileNode(directory, file))),
|
||||
fileContent: (path) => ({ type: "text", content: `contents:${path}` }),
|
||||
seed: {
|
||||
panes: { [alpha.id]: { review: true }, [beta.id]: { review: true } },
|
||||
storage: reviewTabs(directory, alpha.id),
|
||||
},
|
||||
})
|
||||
|
||||
const panel = page.locator("#review-panel")
|
||||
const reviewTab = panel.getByRole("tab", { name: "Review", exact: true })
|
||||
const fileTab = panel.getByRole("tab", { name: "README.md", exact: true })
|
||||
|
||||
await openFileLauncher(panel)
|
||||
await panel.getByRole("treeitem", { name: "README.md", exact: true }).click()
|
||||
await expect(fileTab).toHaveAttribute("aria-selected", "true")
|
||||
await panel.getByText("contents:README.md", { exact: true }).click()
|
||||
await page.keyboard.press("ControlOrMeta+w")
|
||||
await expect(fileTab).toHaveCount(0)
|
||||
await expect(reviewTab).toHaveAttribute("aria-selected", "true")
|
||||
await page.keyboard.press("ControlOrMeta+w")
|
||||
await expect(panel).toHaveAttribute("aria-hidden", "true")
|
||||
|
||||
return { beta, alphaTab: page.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(alpha.id)}"])`) }
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { createEffect, createSignal, on } from "solid-js"
|
||||
import { createEventListener } from "@solid-primitives/event-listener"
|
||||
import { useCommand, type CommandOption } from "@/shell/commands/command"
|
||||
import { adjacentTabKey } from "@/shell/titlebar/tab-order"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
@@ -81,6 +83,27 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
||||
const inSide = actions.region.focus
|
||||
// ⌘T and ⌘W inside the side region open and close its tabs, unless the focused panel binds the key itself there.
|
||||
const sideKey = (event: KeyboardEvent) => inSide(event) && !extensionClaims(event)
|
||||
// ⌘W on the side panel's last tab closes the panel and moves focus out of it, where one press too many would close
|
||||
// the session tab. Until the user moves on, by clicking, focusing, another key, or another session, ⌘W closes nothing.
|
||||
// The close moves focus synchronously, before this is set, so its own focusin does not clear it.
|
||||
const [emptied, setEmptied] = createSignal(false)
|
||||
const resetEmptied = () => setEmptied(false)
|
||||
createEventListener(document, ["pointerdown", "focusin"], resetEmptied, { capture: true })
|
||||
createEventListener(window, "blur", resetEmptied)
|
||||
createEventListener(
|
||||
document,
|
||||
"keydown",
|
||||
(event) => {
|
||||
if (["Meta", "Control", "Shift", "Alt"].includes(event.key)) return
|
||||
if (!command.matches("file.close", event)) resetEmptied()
|
||||
},
|
||||
{ capture: true },
|
||||
)
|
||||
createEffect(
|
||||
on(() => [actions.session.layout.tabKey(), actions.session.layout.view().side.opened()], resetEmptied, {
|
||||
defer: true,
|
||||
}),
|
||||
)
|
||||
const cycleSideTabs = () => actions.session.layout.view().side.opened() && actions.region.keys().length > 1
|
||||
|
||||
const sideFocused = (target: EventTarget | null) => {
|
||||
@@ -404,15 +427,18 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
||||
slash: "open",
|
||||
onSelect: openFile,
|
||||
}),
|
||||
side &&
|
||||
fileCommand({
|
||||
id: "file.close",
|
||||
title: language.t("command.tab.close"),
|
||||
keybind: settings.keybinds.get("tab.close") ?? "mod+w",
|
||||
hidden: !tab,
|
||||
when: sideKey,
|
||||
onSelect: closeTab,
|
||||
}),
|
||||
fileCommand({
|
||||
id: "file.close",
|
||||
title: language.t("command.tab.close"),
|
||||
keybind: settings.keybinds.get("tab.close") ?? "mod+w",
|
||||
hidden: !tab,
|
||||
when: (event) => (side ? sideKey(event) : emptied()),
|
||||
onSelect: (source) => {
|
||||
if (!side) return
|
||||
closeTab()
|
||||
setEmptied(source === "keybind" && !actions.session.layout.view().side.opened())
|
||||
},
|
||||
}),
|
||||
side &&
|
||||
fileCommand({
|
||||
id: "file.tab.new",
|
||||
|
||||
Reference in New Issue
Block a user