Файли
openidea/src/features/project-tree/NewPopup.tsx
T
l0ng-ai 1e00ea5ee2 feat: Third pass aligning core experience with IntelliJ IDEA
Project & files
- Project view modes (Project Files / Open Files / All Changed Files /
  Scratches); External Libraries for Python, JDK (src.zip), Node, Cargo, Go,
  Maven/Gradle jars; read-only jar/zip browsing
- ⌘N New popup with type search and more file kinds; New Project, Rename
  Project, Reload All from Disk, Export/Import Settings, Power Save Mode
- File type associations, extra content roots and exclude patterns (indexed
  and watched without reopening the project)

Editor & navigation
- Find bar: search scope (comments/strings), Preserve Case, Exclude, multiline,
  history; Find in Files: Module scope, custom pattern scopes, multiline
- Search Everywhere filters, Assign Shortcut, settings results; named bookmark
  lists, file/folder bookmarks; Structure filters and Show Inherited; Recent
  Locations timestamps and breadcrumbs; Code Vision author
- Completion options, editor tab placement/layout, inline completion (LSP 3.18)
- Spell checking (Typo inspection, ide-spell) and Inspections settings with
  custom profiles; Code Style schemes, formatter markers, Do not format

Run, VCS, services, database
- Typed run configurations (Shell, Program, npm, Gradle, Maven, Docker,
  Compound), Modify options, Before launch, env dialog, Current File,
  Emulate terminal (PTY)
- Git log graph/view options, Go to Hash/Branch/Tag, date filter keeps the
  graph; Pull/Merge/Rebase dialogs, Manage Remotes, Worktrees
- Generic Services host (Docker connections/registries, run sessions); TODO
  scope/changes tabs with changelists; Coverage report import
- Data sources: wire-compatible vendors, URL import, SSL certs, Options,
  Schemas and Advanced tabs, color and comment

Workbench
- Run Anything, Customize Main Toolbar, named tool window layouts, menu
  completeness, Code Cleanup, macros, "Don't Show Again", editor banners
- Chinese translations for the new UI

Claude-Session: https://claude.ai/code/session_01SaPPsVPuk6ZTNsoUCNCbMv
2026-10-25 21:24:00 +08:00

103 рядки
3.2 KiB
TypeScript

import { useMemo, useState, type KeyboardEvent } from "react";
import clsx from "clsx";
import { HighlightedText, Icon, Input, List, Popup, T, firstSelectable, listNavigate, type MenuItem } from "@/ui";
import { closeNewPopup, newPopupRows, useNewPopup, type NewPopupRow } from "./newPopupModel";
import styles from "./NewPopup.module.css";
const isSeparator = (r: NewPopupRow) => !!r.separator;
/** Overlay: IDEA's "New" popup — file kinds with a type-to-filter field. */
export function NewPopupHost() {
const request = useNewPopup((s) => s.request);
if (!request) return null;
return <NewPopup key={request.seq} at={request.at} items={request.items} />;
}
function NewPopup({ at, items }: { at: { x: number; y: number }; items: MenuItem[] }) {
const [query, setQuery] = useState("");
const [active, setActive] = useState(-1);
const rows = useMemo(() => newPopupRows(items, query), [items, query]);
const current =
active >= 0 && active < rows.length && !rows[active]!.separator ? active : firstSelectable(rows, isSeparator);
const pick = (row: NewPopupRow | undefined) => {
if (!row?.run) return;
closeNewPopup();
row.run();
};
const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
const next = listNavigate(e.key === "ArrowUp" || e.key === "ArrowDown" ? e.key : "", {
items: rows,
active: current,
isSection: isSeparator,
wrap: true,
});
if (next !== null) {
e.preventDefault();
setActive(next);
} else if (e.key === "Enter") {
e.preventDefault();
pick(rows[current]);
}
};
return (
<Popup
open
anchor={at}
placement="bottom-start"
onClose={closeNewPopup}
role="dialog"
aria-label="New"
className={styles.popup}
>
<div className={styles.search}>
<Icon name="search" size={14} className={styles.searchIcon} />
<Input
variant="bare"
data-autofocus
value={query}
placeholder="Search for file type"
aria-label="Search for file type"
className={styles.input}
onChange={(e) => {
setQuery(e.target.value);
setActive(-1);
}}
onKeyDown={onKeyDown}
/>
</div>
<List
items={rows}
getKey={(r) => r.key}
isSection={isSeparator}
renderSection={() => <div className={styles.separator} />}
sectionHeight={9}
activeIndex={current}
onActiveChange={setActive}
onActivate={(r) => pick(r)}
focusable={false}
hoverSelects
rowHeight={24}
className={styles.list}
aria-label="File types"
empty={
<div className={styles.empty}>
<T>Nothing found</T>
</div>
}
renderItem={(r, state) => (
<div className={clsx(styles.row, state.active && styles.active)}>
<span className={styles.icon}>{r.icon && <Icon name={r.icon} size={16} />}</span>
<span className={styles.label}>
<HighlightedText text={r.label} indices={r.matches ?? undefined} />
</span>
{r.shortcut && <span className={styles.shortcut}>{r.shortcut}</span>}
</div>
)}
/>
</Popup>
);
}