126 lines
3.8 KiB
TypeScript
126 lines
3.8 KiB
TypeScript
import * as vscode from "vscode";
|
|
import { relative } from "node:path";
|
|
import { findElementAt, parseXml } from "../language/xmlParser";
|
|
import { unreferencedByType } from "../indexer/referenceIndex";
|
|
import type { ModWorkspace } from "../workspace";
|
|
import { t } from "../localize";
|
|
|
|
interface TypePickItem extends vscode.QuickPickItem {
|
|
type: string;
|
|
}
|
|
|
|
interface AssetPickItem extends vscode.QuickPickItem {
|
|
file: string;
|
|
line: number;
|
|
}
|
|
|
|
/**
|
|
* Palette command: pick an asset type, then jump to any project asset of
|
|
* that type that has zero incoming references. Only types that are reference
|
|
* targets by design are offered, so auto-registered data (settings, map
|
|
* metadata, w3x sub-assets...) is not reported as "unused".
|
|
*/
|
|
export async function findUnreferencedAssets(
|
|
ws: ModWorkspace,
|
|
args?: { type?: string },
|
|
): Promise<void> {
|
|
const idx = ws.activeIndex();
|
|
if (!ws.isRa3Workspace() || !idx) {
|
|
void vscode.window.showInformationMessage(
|
|
t("RA3 Mod XML: no index available yet."),
|
|
);
|
|
return;
|
|
}
|
|
const byType = unreferencedByType(idx);
|
|
|
|
let type = args?.type;
|
|
if (!type) {
|
|
if (!byType.size) {
|
|
void vscode.window.showInformationMessage(
|
|
t("RA3 Mod XML: no unreferenced assets found."),
|
|
);
|
|
return;
|
|
}
|
|
const pickedType = await vscode.window.showQuickPick<TypePickItem>(
|
|
[...byType.entries()].map(([typeName, defs]) => ({
|
|
label: typeName,
|
|
description: t("{0} unreferenced", defs.length),
|
|
type: typeName,
|
|
})),
|
|
{
|
|
placeHolder: t("Select an asset type"),
|
|
matchOnDescription: true,
|
|
},
|
|
);
|
|
if (!pickedType) return;
|
|
type = pickedType.type;
|
|
}
|
|
|
|
const defs = byType.get(type) ?? [];
|
|
if (!defs.length) {
|
|
void vscode.window.showInformationMessage(
|
|
t("RA3 Mod XML: no unreferenced {0} assets found.", type),
|
|
);
|
|
return;
|
|
}
|
|
const pickedAsset = await vscode.window.showQuickPick<AssetPickItem>(
|
|
defs.map((d) => ({
|
|
label: d.id,
|
|
description: `${displayPath(idx.projectDir, d.file)}:${d.line}`,
|
|
file: d.file,
|
|
line: d.line,
|
|
})),
|
|
{
|
|
placeHolder: t("{0}: {1} unreferenced", type, defs.length),
|
|
matchOnDescription: true,
|
|
},
|
|
);
|
|
if (!pickedAsset) return;
|
|
|
|
const uri = vscode.Uri.file(pickedAsset.file);
|
|
const document = await vscode.workspace.openTextDocument(uri);
|
|
const line = Math.max(0, pickedAsset.line - 1);
|
|
await vscode.window.showTextDocument(document, {
|
|
selection: new vscode.Range(
|
|
new vscode.Position(line, 0),
|
|
new vscode.Position(line, 1),
|
|
),
|
|
preview: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Editor context-menu entry: pre-selects the asset type under the cursor.
|
|
* Falls back to the type picker when the cursor is not on a top-level asset.
|
|
*/
|
|
export async function findUnreferencedAssetsOfType(
|
|
ws: ModWorkspace,
|
|
): Promise<void> {
|
|
const editor = vscode.window.activeTextEditor;
|
|
if (editor && ws.isRa3Workspace()) {
|
|
const document = editor.document;
|
|
const offset = document.offsetAt(editor.selection.active);
|
|
const doc = parseXml(document.getText());
|
|
const el = findElementAt(doc, offset);
|
|
if (el && el.parent === doc.root) {
|
|
const local = localName(el.name);
|
|
const isStructural = local === "Tags" || local === "Includes" || local === "Defines";
|
|
const hasId = el.attrs.some((a) => a.name === "id" && a.hasValue);
|
|
if (!isStructural && hasId) {
|
|
return findUnreferencedAssets(ws, { type: local });
|
|
}
|
|
}
|
|
}
|
|
return findUnreferencedAssets(ws);
|
|
}
|
|
|
|
function localName(tag: string): string {
|
|
const idx = tag.lastIndexOf(":");
|
|
return idx >= 0 ? tag.slice(idx + 1) : tag;
|
|
}
|
|
|
|
function displayPath(projectDir: string, file: string): string {
|
|
const rel = relative(projectDir, file);
|
|
return rel && !rel.startsWith("..") ? rel : file;
|
|
}
|