дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
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
228 рядки
7.7 KiB
TypeScript
228 рядки
7.7 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import clsx from "clsx";
|
|
import { commands, isTauri, type RunConfig } from "@/platform/ipc";
|
|
import { useTx, useTxTemplate } from "@/platform/i18n";
|
|
import { Checkbox, Dialog, IconButton, Input, T } from "@/ui";
|
|
import { parseEnv } from "./configs";
|
|
import styles from "./Run.module.css";
|
|
|
|
export interface EnvRow {
|
|
key: string;
|
|
value: string;
|
|
}
|
|
|
|
export const envRows = (env: RunConfig["env"]): EnvRow[] => Object.entries(env).map(([key, value]) => ({ key, value }));
|
|
|
|
/** Env map from table rows; blank rows are ignored. */
|
|
export function envFromRows(rows: readonly EnvRow[]): { env: RunConfig["env"]; error?: string } {
|
|
const env: RunConfig["env"] = {};
|
|
for (const r of rows) {
|
|
const key = r.key.trim();
|
|
if (!key && !r.value) continue;
|
|
if (!key) return { env, error: "Environment variable names can't be empty" };
|
|
if (key in env) return { env, error: `Duplicate environment variable: ${key}` };
|
|
env[key] = r.value;
|
|
}
|
|
return { env };
|
|
}
|
|
|
|
/** Rows as `NAME=value` lines (what Copy puts on the clipboard). */
|
|
export const rowsText = (rows: readonly EnvRow[]) => rows.map((r) => `${r.key}=${r.value}`).join("\n");
|
|
|
|
/** Pasted variables replace rows with the same name and are appended otherwise. */
|
|
export function pasteRows(rows: readonly EnvRow[], text: string): EnvRow[] {
|
|
const out = rows.filter((r) => r.key || r.value).map((r) => ({ ...r }));
|
|
for (const p of parseEnv(text)) {
|
|
const existing = out.find((r) => r.key === p.key);
|
|
if (existing) existing.value = p.value;
|
|
else out.push(p);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
async function writeClipboard(text: string) {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
} catch {
|
|
/* clipboard unavailable */
|
|
}
|
|
}
|
|
|
|
/** IDEA's Environment Variables dialog: user variables, and the inherited system ones (read-only). */
|
|
export function EnvDialog({
|
|
env,
|
|
includeSystemEnv,
|
|
onOk,
|
|
onClose,
|
|
}: {
|
|
env: RunConfig["env"];
|
|
includeSystemEnv: boolean;
|
|
onOk: (env: RunConfig["env"], includeSystemEnv: boolean) => void;
|
|
onClose: () => void;
|
|
}) {
|
|
const [rows, setRows] = useState<EnvRow[]>(() => envRows(env));
|
|
const [selected, setSelected] = useState<number | null>(null);
|
|
const [include, setInclude] = useState(includeSystemEnv);
|
|
const [system, setSystem] = useState<Array<[string, string]> | null>(() => (isTauri() ? null : []));
|
|
const { error } = envFromRows(rows);
|
|
const tx = useTx();
|
|
const txTemplate = useTxTemplate();
|
|
|
|
useEffect(() => {
|
|
if (!isTauri()) return;
|
|
let live = true;
|
|
commands
|
|
.runSystemEnv()
|
|
.then((vars) => live && setSystem(Object.entries(vars).sort(([a], [b]) => a.localeCompare(b))))
|
|
.catch(() => live && setSystem([]));
|
|
return () => {
|
|
live = false;
|
|
};
|
|
}, []);
|
|
|
|
const set = (i: number, patch: Partial<EnvRow>) =>
|
|
setRows((r) => r.map((x, j) => (j === i ? { ...x, ...patch } : x)));
|
|
const add = () => {
|
|
setRows((r) => [...r, { key: "", value: "" }]);
|
|
setSelected(rows.length);
|
|
};
|
|
const remove = () => {
|
|
if (selected === null) return;
|
|
setRows((r) => r.filter((_, j) => j !== selected));
|
|
setSelected(null);
|
|
};
|
|
const paste = async () => {
|
|
try {
|
|
const text = await navigator.clipboard.readText();
|
|
setRows((r) => pasteRows(r, text));
|
|
} catch {
|
|
/* clipboard unavailable */
|
|
}
|
|
};
|
|
const overridden = new Set(rows.map((r) => r.key));
|
|
|
|
return (
|
|
<Dialog
|
|
open
|
|
title="Environment Variables"
|
|
onClose={onClose}
|
|
width={560}
|
|
footerStart={error && <span className={styles.footerError}>{txTemplate(error)}</span>}
|
|
buttons={[
|
|
{
|
|
label: "OK",
|
|
role: "default",
|
|
disabled: !!error,
|
|
onClick: () => {
|
|
onOk(envFromRows(rows).env, include);
|
|
onClose();
|
|
},
|
|
},
|
|
{ label: "Cancel", role: "cancel" },
|
|
]}
|
|
>
|
|
<div className={styles.envDialog}>
|
|
<span className={styles.fieldLabel}>
|
|
<T>User environment variables:</T>
|
|
</span>
|
|
<div className={styles.envPanel}>
|
|
<div className={styles.editToolbar} role="toolbar" aria-label={tx("User environment variables")}>
|
|
<IconButton icon="plus" size="small" iconSize={14} title="Add" onClick={add} />
|
|
<IconButton
|
|
icon="minus"
|
|
size="small"
|
|
iconSize={14}
|
|
title="Remove"
|
|
disabled={selected === null}
|
|
onClick={remove}
|
|
/>
|
|
<IconButton
|
|
icon="copy"
|
|
size="small"
|
|
iconSize={13}
|
|
title="Copy"
|
|
disabled={!rows.length}
|
|
onClick={() =>
|
|
void writeClipboard(rowsText(selected === null ? rows : rows.slice(selected, selected + 1)))
|
|
}
|
|
/>
|
|
<IconButton icon="paste" size="small" iconSize={13} title="Paste" onClick={() => void paste()} />
|
|
</div>
|
|
<div className={styles.envGrid} role="table" aria-label={tx("User environment variables")}>
|
|
<div className={styles.envHeader} role="row">
|
|
<span role="columnheader">
|
|
<T>Name</T>
|
|
</span>
|
|
<span role="columnheader">
|
|
<T>Value</T>
|
|
</span>
|
|
</div>
|
|
{rows.map((r, i) => (
|
|
<div
|
|
key={i}
|
|
role="row"
|
|
className={clsx(styles.envGridRow, selected === i && styles.editItemSelected)}
|
|
onFocus={() => setSelected(i)}
|
|
>
|
|
<Input
|
|
mono
|
|
variant="bare"
|
|
aria-label={txTemplate(`Variable ${i + 1} name`)}
|
|
value={r.key}
|
|
onChange={(e) => set(i, { key: e.target.value })}
|
|
/>
|
|
<Input
|
|
mono
|
|
variant="bare"
|
|
aria-label={txTemplate(`Variable ${i + 1} value`)}
|
|
value={r.value}
|
|
onChange={(e) => set(i, { value: e.target.value })}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<Checkbox checked={include} onChange={setInclude} label="Include system environment variables" />
|
|
<div className={clsx(styles.envPanel, !include && styles.envDisabled)}>
|
|
<div className={styles.editToolbar} role="toolbar" aria-label={tx("System environment variables")}>
|
|
<IconButton
|
|
icon="copy"
|
|
size="small"
|
|
iconSize={13}
|
|
title="Copy"
|
|
disabled={!system?.length}
|
|
onClick={() => void writeClipboard((system ?? []).map(([k, v]) => `${k}=${v}`).join("\n"))}
|
|
/>
|
|
</div>
|
|
<div className={styles.envGrid} role="table" aria-label={tx("System environment variables")}>
|
|
<div className={styles.envHeader} role="row">
|
|
<span role="columnheader">
|
|
<T>Name</T>
|
|
</span>
|
|
<span role="columnheader">
|
|
<T>Value</T>
|
|
</span>
|
|
</div>
|
|
{system === null && (
|
|
<div className={styles.chooserMuted}>
|
|
<T>Loading…</T>
|
|
</div>
|
|
)}
|
|
{system?.map(([k, v]) => (
|
|
<div
|
|
key={k}
|
|
role="row"
|
|
className={clsx(styles.envGridRow, styles.envSystemRow, overridden.has(k) && styles.envOverridden)}
|
|
title={`${k}=${v}`}
|
|
>
|
|
<span className={styles.envCell}>{k}</span>
|
|
<span className={styles.envCell}>{v}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
);
|
|
}
|