diff --git a/e2e/terminal-ui.spec.ts b/e2e/terminal-ui.spec.ts new file mode 100644 index 000000000..3f2d0d04f --- /dev/null +++ b/e2e/terminal-ui.spec.ts @@ -0,0 +1,61 @@ +import { expect, test } from "./fixtures"; + +for (const { width, split } of [{ width: 220 }, { width: 280 }, { width: 520 }, { width: 520, split: true }]) { + test(`terminal search fits a ${width}px ${split ? "split" : "panel"} and its buttons work from the keyboard`, async ({ + workbench: page, + }) => { + await page.evaluate(async (width) => { + const path = "/src/workbench/toolwindows/store.ts"; + const layout = await import(path); + layout.moveToolWindow("terminal", "right"); + layout.setToolWindowSize("right", width); + layout.showToolWindow("terminal", true); + }, width); + const terminal = page.getByRole("region", { name: "Terminal", exact: true }); + await expect(terminal.locator(".xterm-helper-textarea")).toBeFocused(); + if (split) { + await page.evaluate(async () => { + const path = "/src/platform/actions.ts"; + const { executeAction } = await import(path); + await executeAction("terminal.splitRight"); + }); + await expect(terminal.getByRole("tablist", { name: "Terminal sessions" })).toHaveCount(2); + await expect(terminal.locator(".xterm-helper-textarea:focus")).toHaveCount(1); + } + await page.evaluate(async () => { + const path = "/src/features/terminal/runtime.ts"; + const { activeSession } = await import(path); + await new Promise((resolve) => activeSession().term.write("\r\nalpha ALPHA alpha\r\n", resolve)); + }); + await page.keyboard.press("Meta+f"); + const search = terminal.getByRole("search", { name: "Find in terminal" }); + const input = search.getByRole("textbox", { name: "Find in terminal" }); + await input.fill("alpha"); + await expect(search.locator("[aria-live=polite]")).toHaveText(/\d+\/3/); + if (process.env.OPENIDEA_TERMINAL_SHOTS) + await terminal.screenshot({ + path: `${process.env.OPENIDEA_TERMINAL_SHOTS}/terminal-${width}${split ? "-split" : ""}.png`, + }); + + const bounds = (await search.boundingBox())!; + for (const control of await search.locator("input, button, [aria-live]").all()) { + const rect = (await control.boundingBox())!; + expect(rect.x).toBeGreaterThanOrEqual(bounds.x); + expect(rect.x + rect.width).toBeLessThanOrEqual(bounds.x + bounds.width + 1); + } + expect((await input.boundingBox())!.width).toBeGreaterThan(40); + + const matchCase = search.getByRole("button", { name: "Match Case", exact: true }); + await matchCase.focus(); + await page.keyboard.press("Enter"); + await expect(matchCase).toHaveAttribute("aria-pressed", "true"); + await expect(search.locator("[aria-live=polite]")).toHaveText(/\d+\/2/); + await input.fill("missing"); + await expect(search.locator("[aria-live=polite]")).toHaveText("0 results"); + await expect(search.getByRole("button", { name: "Next Occurrence", exact: true })).toBeDisabled(); + await search.getByRole("button", { name: "Close", exact: true }).focus(); + await page.keyboard.press("Enter"); + await expect(search).toBeHidden(); + await expect(terminal.locator(".xterm-helper-textarea:focus")).toHaveCount(1); + }); +} diff --git a/src/features/terminal/FindBar.tsx b/src/features/terminal/FindBar.tsx index fc22a2445..431121fe4 100644 --- a/src/features/terminal/FindBar.tsx +++ b/src/features/terminal/FindBar.tsx @@ -87,6 +87,7 @@ export function TerminalFindBar({ sessionKey }: { sessionKey: string | null }) { e.stopPropagation(); closeTerminalFind(); } else if (e.key === "Enter") { + if ((e.target as Element).closest("button")) return; e.preventDefault(); find(e.shiftKey ? "previous" : "next"); } else if (e.key === "F3" || (hasPrimaryModifier(e) && e.key.toLowerCase() === "g")) { @@ -155,7 +156,7 @@ export function TerminalFindBar({ sessionKey }: { sessionKey: string | null }) { title="Previous Occurrence" shortcut={formatBinding("Shift+Enter")} size="small" - disabled={!query} + disabled={!query || !result?.count} onClick={() => find("previous")} /> find("next")} /> diff --git a/src/features/terminal/Terminal.module.css b/src/features/terminal/Terminal.module.css index acc77a358..369c79c09 100644 --- a/src/features/terminal/Terminal.module.css +++ b/src/features/terminal/Terminal.module.css @@ -27,6 +27,7 @@ /* A tab group: its header (while split) over its tabs' terminals. */ .group { + container: terminal-group / inline-size; flex: 1; display: flex; flex-direction: column; @@ -156,3 +157,17 @@ .findSpacer { flex: 1; } + +@container terminal-group (max-width: 360px) { + .find { + display: grid; + grid-template-columns: minmax(0, 1fr) repeat(3, var(--h-control-small)); + } + .findField { + grid-column: 1 / -1; + min-width: 0; + } + .findSpacer { + display: none; + } +} diff --git a/src/features/terminal/TerminalView.tsx b/src/features/terminal/TerminalView.tsx index efc1964cb..8d91a4011 100644 --- a/src/features/terminal/TerminalView.tsx +++ b/src/features/terminal/TerminalView.tsx @@ -459,7 +459,7 @@ function GroupTabs({ group }: { group: string }) { /> + + + diff --git a/src/ui/expui/general/chevronDownLarge_dark.svg b/src/ui/expui/general/chevronDownLarge_dark.svg new file mode 100644 index 000000000..70a43560d --- /dev/null +++ b/src/ui/expui/general/chevronDownLarge_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/icons.tsx b/src/ui/icons.tsx index 4ec3e57ca..3d1d95336 100644 --- a/src/ui/icons.tsx +++ b/src/ui/icons.tsx @@ -224,6 +224,7 @@ const ICONS = { // General UI "chevron-right": "general/chevronRight", "chevron-down": "general/chevronDown", + "toolbar-expand": "general/chevronDownLarge", "chevron-up": "general/chevronUp", "chevron-left": "general/chevronLeft", close: "general/close",