mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(scratch): add visible language search field
This commit is contained in:
@@ -11,23 +11,17 @@
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
height: var(--h-row);
|
||||
padding: 0 10px;
|
||||
font-size: var(--fs-small);
|
||||
color: var(--fg-subtle);
|
||||
border-bottom: 1px solid var(--border-popup);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.title {
|
||||
flex: 1;
|
||||
}
|
||||
.filter {
|
||||
font-family: var(--font-mono);
|
||||
color: var(--fg-strong);
|
||||
background: var(--accent-soft);
|
||||
border-radius: var(--radius-small);
|
||||
padding: 0 4px;
|
||||
.search {
|
||||
margin: 0 8px 4px;
|
||||
border-bottom: 1px solid var(--border-popup);
|
||||
}
|
||||
.list {
|
||||
max-height: 360px;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useMemo, useState, type KeyboardEvent } from "react";
|
||||
import { useMemo, useRef, useState, type KeyboardEvent } from "react";
|
||||
import clsx from "clsx";
|
||||
import { HighlightedText, Icon, List, Popup, fileIconName, listNavigate, T } from "@/ui";
|
||||
import { HighlightedText, Icon, List, Popup, SearchField, fileIconName, listNavigate, T } from "@/ui";
|
||||
import { chooserRows, type LanguageRow, type ScratchLanguage } from "./model";
|
||||
import { closeChooser, rememberLanguage, useScratch } from "./store";
|
||||
import { createScratch } from "./scratches";
|
||||
import styles from "./ScratchChooser.module.css";
|
||||
|
||||
/** Overlay: IDEA's "New Scratch File" popup — languages, speed search, recent first. */
|
||||
/** Overlay: IDEA's "New Scratch File" popup — searchable languages, recent first. */
|
||||
export function ScratchChooserHost() {
|
||||
const languages = useScratch((s) => s.languages);
|
||||
const seq = useScratch((s) => s.seq);
|
||||
@@ -16,6 +16,7 @@ export function ScratchChooserHost() {
|
||||
|
||||
function ScratchChooser({ languages }: { languages: readonly ScratchLanguage[] }) {
|
||||
const recent = useScratch((s) => s.recent);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [active, setActive] = useState(0);
|
||||
const rows = useMemo(() => chooserRows(languages, recent, filter), [languages, recent, filter]);
|
||||
@@ -29,22 +30,19 @@ function ScratchChooser({ languages }: { languages: readonly ScratchLanguage[] }
|
||||
void createScratch(row.lang);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const next = listNavigate(e.key, { items: rows, active: current, wrap: true });
|
||||
const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.nativeEvent.isComposing || e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
const next = listNavigate(e.key === "Home" || e.key === "End" ? "" : e.key, {
|
||||
items: rows,
|
||||
active: current,
|
||||
wrap: true,
|
||||
});
|
||||
if (next !== null) {
|
||||
e.preventDefault();
|
||||
setActive(next);
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
pick(rows[current]);
|
||||
} else if (e.key === "Backspace") {
|
||||
e.preventDefault();
|
||||
setFilter((f) => f.slice(0, -1));
|
||||
setActive(0);
|
||||
} else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
||||
e.preventDefault();
|
||||
setFilter((f) => f + e.key);
|
||||
setActive(0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -58,13 +56,27 @@ function ScratchChooser({ languages }: { languages: readonly ScratchLanguage[] }
|
||||
aria-label="New Scratch File"
|
||||
className={styles.popup}
|
||||
>
|
||||
<div className={styles.body} tabIndex={0} data-autofocus onKeyDown={onKeyDown}>
|
||||
<div className={styles.body}>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.title}>
|
||||
<T>New Scratch File</T>
|
||||
</span>
|
||||
{filter && <span className={styles.filter}>{filter}</span>}
|
||||
</div>
|
||||
<SearchField
|
||||
ref={inputRef}
|
||||
variant="bare"
|
||||
data-autofocus
|
||||
value={filter}
|
||||
placeholder="Filter languages"
|
||||
aria-label="Filter languages"
|
||||
className={styles.search}
|
||||
onChange={(value) => {
|
||||
setFilter(value);
|
||||
setActive(0);
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<List
|
||||
items={rows}
|
||||
getKey={(r) => r.lang.id}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { getAction } from "@/platform/actions";
|
||||
import { onIdeEvent } from "@/platform/ipc";
|
||||
import { useProjectViewRoots } from "@/workbench/contributions";
|
||||
@@ -103,12 +104,20 @@ describe("creating scratch files", () => {
|
||||
await action.run({ ctx: {} as never, arg: undefined });
|
||||
});
|
||||
render(<ScratchChooserHost />);
|
||||
const dialog = screen.getByRole("dialog", { name: "New Scratch File" });
|
||||
const body = dialog.querySelector<HTMLElement>("[tabindex]")!;
|
||||
for (const ch of "pyt") fireEvent.keyDown(body, { key: ch });
|
||||
expect(dialog.textContent).toContain("pyt");
|
||||
const user = userEvent.setup();
|
||||
const input = screen.getByRole("textbox", { name: "Filter languages" });
|
||||
expect(input).toHaveFocus();
|
||||
await user.paste("java");
|
||||
expect(input).toHaveValue("java");
|
||||
expect(screen.queryByText("Python")).not.toBeInTheDocument();
|
||||
await user.click(screen.getByRole("button", { name: "Clear" }));
|
||||
expect(input).toHaveFocus();
|
||||
expect(input).toHaveValue("");
|
||||
await user.type(input, "pyton");
|
||||
await user.keyboard("{ArrowLeft}{ArrowLeft}h");
|
||||
expect(input).toHaveValue("python");
|
||||
await act(async () => {
|
||||
fireEvent.keyDown(body, { key: "Enter" });
|
||||
fireEvent.keyDown(input, { key: "Enter" });
|
||||
});
|
||||
await vi.waitFor(() => expect(opened).toEqual(["/data/scratches/scratch.py"]));
|
||||
expect(useScratch.getState().languages).toBeNull();
|
||||
|
||||
Reference in New Issue
Block a user