mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-14 16:07:52 +00:00
fix(terminal): keep session and search controls visible (#2379)
In a narrow Terminal tool window the search field's options and match count pushed the navigation and close buttons out of view (24px overflow), and session tab controls were misaligned. Search now lays out on a control-width grid (match count and navigation on their own row when narrow) and session controls align with the tool window header. Verified on macOS with tabs, new sessions, splits, search, light/dark and narrow panes.
This commit is contained in:
@@ -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<void>((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);
|
||||
});
|
||||
}
|
||||
@@ -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")}
|
||||
/>
|
||||
<IconButton
|
||||
@@ -163,7 +164,7 @@ export function TerminalFindBar({ sessionKey }: { sessionKey: string | null }) {
|
||||
title="Next Occurrence"
|
||||
shortcut="↵"
|
||||
size="small"
|
||||
disabled={!query}
|
||||
disabled={!query || !result?.count}
|
||||
onClick={() => find("next")}
|
||||
/>
|
||||
<span className={styles.findSpacer} />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ function GroupTabs({ group }: { group: string }) {
|
||||
/>
|
||||
<IconButton
|
||||
ref={menuRef}
|
||||
icon="chevron-down"
|
||||
icon="toolbar-expand"
|
||||
size="small"
|
||||
title="More Terminal Options"
|
||||
aria-haspopup="menu"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5 5.75L8 10.25L3.5 5.75" stroke="#6C707E" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 310 B |
@@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5 5.75L8 10.25L3.5 5.75" stroke="#CED0D6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 310 B |
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user