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

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>
);
}