дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +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
177 рядки
6.0 KiB
TypeScript
177 рядки
6.0 KiB
TypeScript
/**
|
|
* The Run Anything popup: a command line with the recent commands and the
|
|
* providers' entries (run configurations, `mvn` / `gradle` goals…). Enter runs
|
|
* the selected entry, or the line itself: a provider that claims it, else the
|
|
* shell in the project directory, its output in the Run tool window.
|
|
*/
|
|
import { useMemo, useState, type KeyboardEvent } from "react";
|
|
import clsx from "clsx";
|
|
import { create } from "zustand";
|
|
import { executeAction } from "@/platform/actions";
|
|
import { usePlatform } from "@/platform/platformInfo";
|
|
import { useProject } from "@/platform/project";
|
|
import { Icon, Input, List, Popup, T, listNavigate } from "@/ui";
|
|
import { runAnythingProviderList } from "@/workbench/contributions";
|
|
import { buildRows, clearHistory, pushHistory, readHistory, shellRunConfig, type Row } from "./model";
|
|
import styles from "./RunAnything.module.css";
|
|
|
|
export const useRunAnything = create<{ open: boolean }>(() => ({ open: false }));
|
|
|
|
export const showRunAnything = () => useRunAnything.setState({ open: true });
|
|
const close = () => useRunAnything.setState({ open: false });
|
|
|
|
/** Runs a command line: a provider that claims it, else the platform shell. */
|
|
export async function runCommandLine(line: string): Promise<void> {
|
|
const text = line.trim();
|
|
const root = useProject.getState().project?.root;
|
|
if (!text || !root) return;
|
|
pushHistory(root, text);
|
|
for (const p of runAnythingProviderList()) {
|
|
try {
|
|
if (p.execute && (await p.execute(text))) return;
|
|
} catch (e) {
|
|
console.error(`run anything provider ${p.id} failed`, e);
|
|
}
|
|
}
|
|
await executeAction("run.runConfig", shellRunConfig(text, root, usePlatform.getState().os === "windows"));
|
|
}
|
|
|
|
export function RunAnythingHost() {
|
|
const open = useRunAnything((s) => s.open);
|
|
const root = useProject((s) => s.project?.root);
|
|
if (!open || !root) return null;
|
|
return <RunAnythingPopup root={root} />;
|
|
}
|
|
|
|
const isSection = (r: Row) => r.kind === "section";
|
|
|
|
function RunAnythingPopup({ root }: { root: string }) {
|
|
const [line, setLine] = useState("");
|
|
const [active, setActive] = useState(-1);
|
|
const [history, setHistory] = useState(() => readHistory(root));
|
|
const rows = useMemo(
|
|
() =>
|
|
buildRows(line.trim(), history, runAnythingProviderList(), (h) => {
|
|
close();
|
|
void runCommandLine(h);
|
|
}),
|
|
[line, history],
|
|
);
|
|
|
|
const run = (row: Row | undefined) => {
|
|
if (!row || row.kind !== "item") return;
|
|
close();
|
|
if (row.group !== "recent") pushHistory(root, row.item.completion ?? row.item.label);
|
|
void row.item.run();
|
|
};
|
|
const runLine = () => {
|
|
if (!line.trim()) return;
|
|
close();
|
|
void runCommandLine(line);
|
|
};
|
|
const complete = (row: Row | undefined) => {
|
|
if (!row || row.kind !== "item") return;
|
|
setLine(row.item.completion ?? row.item.label);
|
|
setActive(-1);
|
|
};
|
|
|
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
if (["ArrowDown", "ArrowUp", "PageDown", "PageUp"].includes(e.key)) {
|
|
const next = listNavigate(e.key, { items: rows, active: Math.max(active, 0), wrap: true, isSection });
|
|
if (next !== null) {
|
|
e.preventDefault();
|
|
setActive(active < 0 && e.key === "ArrowDown" ? rows.findIndex((r) => !isSection(r)) : next);
|
|
}
|
|
return;
|
|
}
|
|
if (e.key === "Tab") {
|
|
e.preventDefault();
|
|
complete(rows[active >= 0 ? active : rows.findIndex((r) => !isSection(r))]);
|
|
} else if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
if (active >= 0) run(rows[active]);
|
|
else runLine();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Popup
|
|
open
|
|
anchor={{ x: window.innerWidth / 2, y: 72 }}
|
|
placement="bottom"
|
|
onClose={close}
|
|
role="dialog"
|
|
aria-label="Run Anything"
|
|
className={styles.popup}
|
|
trapFocus
|
|
>
|
|
<div className={styles.body}>
|
|
<div className={styles.header}>
|
|
<span className={styles.title}>
|
|
<T>Run Anything</T>
|
|
</span>
|
|
{history.length > 0 && (
|
|
<button
|
|
type="button"
|
|
className={styles.link}
|
|
onClick={() => {
|
|
clearHistory(root);
|
|
setHistory([]);
|
|
}}
|
|
>
|
|
<T>Clear History</T>
|
|
</button>
|
|
)}
|
|
</div>
|
|
<div className={styles.inputRow}>
|
|
<Icon name="run-anything" size={16} className={styles.inputIcon} />
|
|
<Input
|
|
data-autofocus
|
|
variant="bare"
|
|
className={styles.input}
|
|
value={line}
|
|
placeholder="Run a command, configuration or goal"
|
|
aria-label="Command line"
|
|
onChange={(e) => {
|
|
setLine(e.target.value);
|
|
setActive(-1);
|
|
}}
|
|
onKeyDown={onKeyDown}
|
|
/>
|
|
</div>
|
|
<List
|
|
items={rows}
|
|
getKey={(r) => r.key}
|
|
isSection={isSection}
|
|
renderSection={(r) => <div className={styles.section}>{r.kind === "section" && <T>{r.label}</T>}</div>}
|
|
activeIndex={active}
|
|
onActiveChange={setActive}
|
|
onActivate={(r) => run(r)}
|
|
focusable={false}
|
|
hoverSelects={false}
|
|
rowHeight={24}
|
|
className={styles.rows}
|
|
aria-label="Run Anything entries"
|
|
empty={
|
|
<div className={styles.empty}>
|
|
{line.trim() ? <T>Press Enter to run the command in the project directory</T> : <T>Type a command</T>}
|
|
</div>
|
|
}
|
|
renderItem={(r, state) =>
|
|
r.kind === "item" ? (
|
|
<div className={clsx(styles.row, state.active && styles.activeRow)}>
|
|
<Icon name={r.item.icon ?? "run"} size={16} className={styles.rowIcon} />
|
|
<span className={styles.label}>{r.item.label}</span>
|
|
{r.item.detail && <span className={styles.detail}>{r.item.detail}</span>}
|
|
</div>
|
|
) : null
|
|
}
|
|
/>
|
|
<div className={styles.footer}>
|
|
<T>Enter to run, Tab to complete. Other commands run in the project directory.</T>
|
|
</div>
|
|
</div>
|
|
</Popup>
|
|
);
|
|
}
|