дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +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
166 рядки
5.6 KiB
TypeScript
166 рядки
5.6 KiB
TypeScript
/**
|
|
* Coverage reports collected elsewhere: "Import a report collected in CI from
|
|
* disk" (lcov, JaCoCo XML, Go cover profile) and "Manage Coverage Reports"
|
|
* (choose the shown suite, delete suites and remembered imports).
|
|
*/
|
|
import { useMemo, useState } from "react";
|
|
import { create } from "zustand";
|
|
import { call, commands, isTauri } from "@/platform/ipc";
|
|
import { basename } from "@/platform/project";
|
|
import { Dialog, Icon, List, T } from "@/ui";
|
|
import { notifications, toolWindowService } from "@/workbench/contributions";
|
|
import { formatPercent } from "./model";
|
|
import {
|
|
addSuite,
|
|
closeSuite,
|
|
forgetImport,
|
|
importedReports,
|
|
rememberImport,
|
|
reportEntries,
|
|
selectSuite,
|
|
useCoverage,
|
|
type ReportEntry,
|
|
type Suite,
|
|
} from "./store";
|
|
import styles from "./Coverage.module.css";
|
|
|
|
const errorText = (e: unknown) => (e instanceof Error ? e.message : String(e));
|
|
|
|
async function pickReport(): Promise<string | null> {
|
|
const { open } = await import("@tauri-apps/plugin-dialog");
|
|
const file = await open({
|
|
multiple: false,
|
|
directory: false,
|
|
title: "Import Coverage Report",
|
|
filters: [
|
|
{
|
|
name: "Coverage reports (lcov, JaCoCo XML, Go cover profile)",
|
|
extensions: ["info", "lcov", "xml", "out", "txt", "cov", "coverprofile"],
|
|
},
|
|
{ name: "All files", extensions: ["*"] },
|
|
],
|
|
});
|
|
return typeof file === "string" ? file : null;
|
|
}
|
|
|
|
/** Imports `path` (asks for a file without one) and shows it like a run's suite. */
|
|
export async function importReport(path?: string): Promise<Suite | null> {
|
|
if (!isTauri()) {
|
|
notifications().notify({ group: "run", level: "warning", title: "Importing coverage needs the desktop app" });
|
|
return null;
|
|
}
|
|
const file = path ?? (await pickReport());
|
|
if (!file) return null;
|
|
try {
|
|
const data = await call(commands.coverageImport(file));
|
|
const suite = addSuite(basename(file), data, file);
|
|
rememberImport({ path: file, name: suite.name, importedAt: suite.createdAt });
|
|
try {
|
|
toolWindowService().show("coverage");
|
|
} catch {
|
|
/* no workbench (tests) */
|
|
}
|
|
const [covered, total] = data.files.reduce((a, f) => [a[0]! + f.linesCovered, a[1]! + f.linesTotal], [0, 0]);
|
|
notifications().notify({
|
|
group: "run",
|
|
level: "info",
|
|
key: "coverage-import",
|
|
title: `Imported coverage '${suite.name}': ${formatPercent(covered!, total!)} lines`,
|
|
body: data.files.length ? undefined : "The report covers no files of this project.",
|
|
});
|
|
return suite;
|
|
} catch (e) {
|
|
notifications().notify({
|
|
group: "run",
|
|
level: "error",
|
|
key: "coverage-import",
|
|
title: `Cannot import ${basename(file)}`,
|
|
body: errorText(e),
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const useManageDialog = create<{ open: boolean }>(() => ({ open: false }));
|
|
|
|
export const openManageReports = () => useManageDialog.setState({ open: true });
|
|
|
|
export function ManageReportsOverlay() {
|
|
const open = useManageDialog((s) => s.open);
|
|
return open ? <ManageReportsDialog onClose={() => useManageDialog.setState({ open: false })} /> : null;
|
|
}
|
|
|
|
function ManageReportsDialog({ onClose }: { onClose: () => void }) {
|
|
const suites = useCoverage((s) => s.suites);
|
|
const activeId = useCoverage((s) => s.activeId);
|
|
const [imported, setImported] = useState(() => importedReports());
|
|
const entries = useMemo(() => reportEntries(suites, imported), [suites, imported]);
|
|
const [active, setActive] = useState(() =>
|
|
Math.max(
|
|
0,
|
|
entries.findIndex((e) => e.suite?.id === activeId),
|
|
),
|
|
);
|
|
const current: ReportEntry | undefined = entries[Math.min(active, entries.length - 1)];
|
|
|
|
const show = async (entry: ReportEntry | undefined) => {
|
|
if (!entry) return;
|
|
if (entry.suite) selectSuite(entry.suite.id);
|
|
else if (entry.file && !(await importReport(entry.file))) return;
|
|
onClose();
|
|
};
|
|
const remove = (entry: ReportEntry | undefined) => {
|
|
if (!entry) return;
|
|
if (entry.suite) closeSuite(entry.suite.id);
|
|
if (entry.file) {
|
|
forgetImport(entry.file);
|
|
setImported(importedReports());
|
|
}
|
|
};
|
|
const importMore = async () => {
|
|
if (await importReport()) setImported(importedReports());
|
|
};
|
|
|
|
return (
|
|
<Dialog
|
|
open
|
|
title="Coverage Reports"
|
|
onClose={onClose}
|
|
width={560}
|
|
buttons={[
|
|
{ label: "Import…", role: "extra", onClick: () => void importMore() },
|
|
{ label: "Delete", role: "extra", disabled: !current, onClick: () => remove(current) },
|
|
{ label: "Show Selected", role: "default", disabled: !current, onClick: () => void show(current) },
|
|
{ label: "Cancel", role: "cancel" },
|
|
]}
|
|
>
|
|
<div className={styles.reports}>
|
|
<List<ReportEntry>
|
|
aria-label="Coverage reports"
|
|
items={entries}
|
|
getKey={(e) => e.key}
|
|
activeIndex={active}
|
|
onActiveChange={setActive}
|
|
onActivate={(e) => void show(e)}
|
|
renderItem={(e) => (
|
|
<span className={styles.reportRow}>
|
|
<Icon name={e.file ? "folder-open" : "run-coverage"} size={16} className={styles.icon} />
|
|
<span className={styles.label}>{e.name}</span>
|
|
<span className={styles.reportDetail}>{e.detail}</span>
|
|
<span className={styles.reportTime}>
|
|
{e.suite?.id === activeId ? <T>shown</T> : e.suite ? null : <T>not loaded</T>}{" "}
|
|
{new Date(e.time).toLocaleString()}
|
|
</span>
|
|
</span>
|
|
)}
|
|
empty={
|
|
<div className={styles.reportsEmpty}>
|
|
<T>No coverage reports. Run with coverage or import a report.</T>
|
|
</div>
|
|
}
|
|
/>
|
|
</div>
|
|
</Dialog>
|
|
);
|
|
}
|