/** Inspect Code, Locate Duplicates and Search / Replace Structurally dialogs. */ import { useEffect, useMemo, useState } from "react"; import { call, commands, isTauri, type DuplicateOptions, type StructuralFile, type StructuralLanguage, type WorkspaceEdit, } from "@/platform/ipc"; import { executeAction } from "@/platform/actions"; import { activeProfileId, listProfiles, validateInspections, type ProfileId } from "@/platform/inspections"; import { basename, useProject } from "@/platform/project"; import { Button, Checkbox, Dialog, Input, Select, Spinner, T } from "@/ui"; import { ScopeFields, openDialog } from "./dialogs"; import { locateDuplicates, loadDuplicateOptions } from "./duplicates"; import { runInspection } from "./inspect"; import { runCodeCleanup, type CleanupOptions } from "./cleanup"; import { currentFile, defaultScope, type ScopeChoice } from "./scope"; import { applyStructuralReplace, buildReplaceEdit, loadStructuralForm, matchKey, saveStructuralForm, searchStructurally, showStructuralResults, type StructuralForm, } from "./structural"; import styles from "./Analysis.module.css"; import { tx } from "@/platform/i18n"; import { notifications } from "@/workbench/contributions"; function InspectCodeDialog({ onClose }: { onClose: () => void }) { const [scope, setScope] = useState(() => defaultScope()); const [profile, setProfile] = useState(activeProfileId); const hasProject = useProject((s) => !!s.project); const settings = useProject((s) => s.settings); const profiles = useMemo( () => listProfiles(validateInspections(settings?.inspections), hasProject).map((p) => ({ value: p.id, label: p.label, detail: p.level === "project" ? "Project" : "IDE", })), [hasProject, settings], ); const ok = () => { onClose(); void runInspection(scope, profile); }; return (
Inspection profile: setMinText(e.target.value)} aria-label="Minimum duplicate size in tokens" /> tokens setOptions({ ...options, anonymizeIdentifiers: v })} label="Anonymize variables, fields, methods and types" /> setOptions({ ...options, anonymizeLiterals: v })} label="Anonymize literals" />
); } function useStructuralLanguages(): StructuralLanguage[] | null { const [langs, setLangs] = useState(() => (isTauri() ? null : [])); useEffect(() => { if (!isTauri()) return; let alive = true; void call(commands.structuralLanguages()).then( (l) => alive && setLangs(l), () => alive && setLangs([]), ); return () => { alive = false; }; }, []); return langs; } /** Template validation while typing (debounced). */ function useTemplateError(pattern: string, language: string): string | null { const key = `${language}\u0000${pattern}`; const [result, setResult] = useState<{ key: string; error: string | null }>({ key: "", error: null }); const applicable = isTauri() && !!pattern.trim() && !!language; useEffect(() => { if (!applicable) return; let alive = true; const t = setTimeout(() => { void call(commands.structuralValidate({ pattern, language, replacement: null, maxMatches: 0 })).then( () => alive && setResult({ key, error: null }), (e) => alive && setResult({ key, error: e instanceof Error ? e.message : String(e) }), ); }, 250); return () => { alive = false; clearTimeout(t); }; }, [applicable, key, pattern, language]); return applicable && result.key === key ? result.error : null; } function StructuralDialog({ replace, onClose }: { replace: boolean; onClose: () => void }) { const [form, setForm] = useState(loadStructuralForm); const [scope, setScope] = useState(() => defaultScope()); const [busy, setBusy] = useState(false); const languages = useStructuralLanguages(); // The editor's language when it has a grammar, else the first supported one. const language = languages && languages.length && !languages.some((l) => l.id === form.language) ? languages[0]!.id : form.language; const error = useTemplateError(form.pattern, language); const options = useMemo(() => (languages ?? []).map((l) => ({ value: l.id, label: l.name })), [languages]); const ready = !!form.pattern.trim() && !!language && !error && !busy; const run = async () => { if (!ready) return; saveStructuralForm({ ...form, language }); setBusy(true); const query = { pattern: form.pattern, language, replacement: replace ? form.replacement : null, maxMatches: 0 }; const result = await searchStructurally(query, scope); setBusy(false); if (!result) return; onClose(); if (!replace) { await showStructuralResults(form.pattern, result.files, result.summary); } else if (!result.files.length) { await showStructuralResults(form.pattern, result.files, result.summary); } else { await showReplacePreview(result.files); } }; return ( : undefined} buttons={[ { label: replace ? "Find…" : "Find", role: "default", disabled: !ready, onClick: () => void run() }, { label: "Cancel", role: "cancel" }, ]} >
Language