Файли
openidea/src/features/analysis/dialogs.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

118 рядки
3.9 KiB
TypeScript

/** Dialog stack of the analysis feature and the shared "Specify Scope" fields. */
import { Fragment, type ReactNode } from "react";
import { create } from "zustand";
import { executeAction } from "@/platform/actions";
import { isTauri } from "@/platform/ipc";
import { EDIT_SCOPES_ACTION, useCustomScopeList } from "@/platform/search";
import { Button, IconButton, Input, Select, T } from "@/ui";
import { SCOPE_LABELS, currentFile, type ScopeChoice, type ScopeKind } from "./scope";
import styles from "./Analysis.module.css";
interface Entry {
id: number;
render: (close: () => void) => ReactNode;
}
const useDialogs = create<{ stack: Entry[] }>(() => ({ stack: [] }));
let seq = 0;
/** Shows a dialog; `render` receives the function that closes it. */
export function openDialog(render: (close: () => void) => ReactNode): () => void {
const id = ++seq;
const close = () => useDialogs.setState((s) => ({ stack: s.stack.filter((e) => e.id !== id) }));
useDialogs.setState((s) => ({ stack: [...s.stack, { id, render }] }));
return close;
}
export function AnalysisDialogHost() {
const stack = useDialogs((s) => s.stack);
return (
<>
{stack.map((e) => (
<Fragment key={e.id}>
{e.render(() => useDialogs.setState((s) => ({ stack: s.stack.filter((x) => x.id !== e.id) })))}
</Fragment>
))}
</>
);
}
async function browseDirectory(initial: string): Promise<string | null> {
if (!isTauri()) return null;
const { open } = await import("@tauri-apps/plugin-dialog");
const picked = await open({ directory: true, multiple: false, defaultPath: initial || undefined });
return typeof picked === "string" ? picked : null;
}
const KINDS: ScopeKind[] = ["project", "directory", "file", "changed", "custom"];
/** IDEA's "Specify Scope" radio group. */
export function ScopeFields({
value,
onChange,
name,
kinds = KINDS,
}: {
value: ScopeChoice;
onChange: (v: ScopeChoice) => void;
name: string;
/** The choices offered (default: all). */
kinds?: readonly ScopeKind[];
}) {
const file = currentFile();
const scopes = useCustomScopeList();
return (
<fieldset className={styles.scope}>
<legend className={styles.fieldLabel}>
<T>Scope</T>
</legend>
{kinds.map((kind) => (
<label key={kind} className={styles.radioRow}>
<input
type="radio"
name={name}
checked={value.kind === kind}
disabled={kind === "file" && !file}
onChange={() =>
onChange({ ...value, kind, custom: kind === "custom" ? (value.custom ?? scopes[0]?.name) : value.custom })
}
/>
<span>{kind === "file" && file ? `File '${file.split("/").pop()}'` : SCOPE_LABELS[kind]}</span>
</label>
))}
{value.kind === "directory" && (
<div className={styles.dirRow}>
<Input
aria-label="Directory"
value={value.directory}
onChange={(e) => onChange({ ...value, directory: e.target.value })}
className={styles.grow}
/>
<Button
onClick={() => void browseDirectory(value.directory).then((d) => d && onChange({ ...value, directory: d }))}
>
<T>Browse…</T>
</Button>
</div>
)}
{value.kind === "custom" && (
<div className={styles.dirRow}>
<Select<string>
className={styles.grow}
value={value.custom ?? null}
options={scopes.map((s) => ({ value: s.name, label: s.name, detail: s.pattern }))}
placeholder="No custom scopes"
onChange={(custom) => onChange({ ...value, custom })}
aria-label="Custom scope"
/>
<IconButton
icon="more-horizontal"
title="Edit Scopes…"
onClick={() => void executeAction(EDIT_SCOPES_ACTION, value.custom ? { select: value.custom } : undefined)}
/>
</div>
)}
</fieldset>
);
}