0.1.21
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
type ShowReferencesArgs,
|
||||
} from "./references";
|
||||
import type { ModWorkspace } from "../workspace";
|
||||
import { t } from "../localize";
|
||||
|
||||
/** Never build a DOM for huge files just to show counts (w3x safety). */
|
||||
const MAX_CODELENS_TEXT = 4 * 1024 * 1024;
|
||||
@@ -124,10 +125,10 @@ export class Ra3CodeLensProvider implements vscode.CodeLensProvider {
|
||||
new vscode.CodeLens(range, {
|
||||
title:
|
||||
count === 0
|
||||
? "0 references"
|
||||
? t("0 references")
|
||||
: count === 1
|
||||
? "1 reference"
|
||||
: `${count} references`,
|
||||
? t("1 reference")
|
||||
: t("{0} references", count),
|
||||
command: "ra3modxml.showReferences",
|
||||
arguments: [args],
|
||||
}),
|
||||
|
||||
+90
-32
@@ -17,6 +17,7 @@ import {
|
||||
} from "../indexer/logicalTree";
|
||||
import type { ModWorkspace } from "../workspace";
|
||||
import type { ModIndex, AssetDef } from "../indexer/types";
|
||||
import { t } from "../localize";
|
||||
|
||||
const MAX_VALUE_ITEMS = 400;
|
||||
|
||||
@@ -79,9 +80,9 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
const docText =
|
||||
child.doc ||
|
||||
(info?.kind === "complex" ? info.doc : "") ||
|
||||
(type ? `Type: ${type}` : "");
|
||||
(type ? t("Type: {0}", type) : "");
|
||||
item.documentation = docText ? new vscode.MarkdownString(docText) : undefined;
|
||||
item.detail = type ? `RA3 XML · ${type}` : "RA3 XML";
|
||||
item.detail = type ? t("RA3 XML · {0}", type) : t("RA3 XML");
|
||||
item.insertText = this.elementSnippet(child.name, type, ctx.element == null);
|
||||
items.push(item);
|
||||
}
|
||||
@@ -93,7 +94,11 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
): { name: string; type: string | null; doc: string }[] {
|
||||
if (!parent) {
|
||||
return [
|
||||
{ name: "AssetDeclaration", type: null, doc: "Root element of every RA3 asset file" },
|
||||
{
|
||||
name: "AssetDeclaration",
|
||||
type: null,
|
||||
doc: t("Root element of every RA3 asset file"),
|
||||
},
|
||||
];
|
||||
}
|
||||
const parentType = resolveElementType(parent);
|
||||
@@ -160,12 +165,16 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
item.sortText = attr.required ? "0" + attr.name : "1" + attr.name;
|
||||
const md = new vscode.MarkdownString();
|
||||
if (attr.doc) md.appendMarkdown(attr.doc + "\n\n");
|
||||
if (attr.required) md.appendMarkdown(`**Required** \n`);
|
||||
if (attr.refType) md.appendMarkdown(`References: \`${attr.refType}\` \n`);
|
||||
if (attr.required) md.appendMarkdown(`${t("**Required**")} \n`);
|
||||
if (attr.refType) {
|
||||
md.appendMarkdown(`${t("References: `{0}`", attr.refType)} \n`);
|
||||
}
|
||||
if (attr.enumValues.length)
|
||||
md.appendMarkdown(`Values: ${attr.enumValues.join(", ")} \n`);
|
||||
if (attr.default != null) md.appendMarkdown(`Default: \`${attr.default}\` \n`);
|
||||
md.appendMarkdown(`Type: \`${attr.type ?? "string"}\``);
|
||||
md.appendMarkdown(`${t("Values: {0}", attr.enumValues.join(", "))} \n`);
|
||||
if (attr.default != null) {
|
||||
md.appendMarkdown(`${t("Default: `{0}`", attr.default)} \n`);
|
||||
}
|
||||
md.appendMarkdown(t("Type: `{0}`", attr.type ?? "string"));
|
||||
item.documentation = md;
|
||||
const value = this.attributeValuePlaceholder(attr, el);
|
||||
item.insertText = new vscode.SnippetString(
|
||||
@@ -174,7 +183,7 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
if (value.trigger) {
|
||||
item.command = {
|
||||
command: "editor.action.triggerSuggest",
|
||||
title: "Suggest attribute values",
|
||||
title: t("Suggest attribute values"),
|
||||
};
|
||||
}
|
||||
items.push(item);
|
||||
@@ -187,13 +196,15 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
j.insertText = new vscode.SnippetString(
|
||||
layout.prefix + 'xai:joinAction="$1"',
|
||||
);
|
||||
j.detail = "Instance join action";
|
||||
j.detail = t("Instance join action");
|
||||
j.documentation = new vscode.MarkdownString(
|
||||
"Controls how this element merges with the inherited definition: `Replace` or `Remove`.",
|
||||
t(
|
||||
"Controls how this element merges with the inherited definition: `Replace` or `Remove`.",
|
||||
),
|
||||
);
|
||||
j.command = {
|
||||
command: "editor.action.triggerSuggest",
|
||||
title: "Suggest attribute values",
|
||||
title: t("Suggest attribute values"),
|
||||
};
|
||||
items.push(j);
|
||||
}
|
||||
@@ -203,7 +214,7 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
ns.insertText = new vscode.SnippetString(
|
||||
layout.prefix + 'xmlns:xai="uri:ea.com:eala:asset:instance"',
|
||||
);
|
||||
ns.detail = "xai namespace";
|
||||
ns.detail = t("xai namespace");
|
||||
items.push(ns);
|
||||
}
|
||||
return items;
|
||||
@@ -312,7 +323,7 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
// Include type / source
|
||||
if (isInclude && attrName === "type") {
|
||||
return ["reference", "instance", "all"].map((v) =>
|
||||
make(v, vscode.CompletionItemKind.EnumMember, "Include type"),
|
||||
make(v, vscode.CompletionItemKind.EnumMember, t("Include type")),
|
||||
);
|
||||
}
|
||||
if (isInclude && attrName === "source") {
|
||||
@@ -321,7 +332,7 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
}
|
||||
if (attrName === "xai:joinaction" || attrName === "joinaction") {
|
||||
return ["Replace", "Remove"].map((v) =>
|
||||
make(v, vscode.CompletionItemKind.EnumMember, "xai:joinAction"),
|
||||
make(v, vscode.CompletionItemKind.EnumMember, t("xai:joinAction")),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -348,12 +359,18 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
}
|
||||
return attrInfo.enumValues
|
||||
.filter((v) => v.toLowerCase().startsWith(prefix.toLowerCase()))
|
||||
.map((v) => make(v, vscode.CompletionItemKind.EnumMember, attrInfo.type ?? "enum"));
|
||||
.map((v) =>
|
||||
make(
|
||||
v,
|
||||
vscode.CompletionItemKind.EnumMember,
|
||||
attrInfo.type ?? t("enum"),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (attrInfo?.isBoolean) {
|
||||
return ["true", "false"]
|
||||
.filter((v) => v.startsWith(prefix.toLowerCase()))
|
||||
.map((v) => make(v, vscode.CompletionItemKind.Value, "boolean"));
|
||||
.map((v) => make(v, vscode.CompletionItemKind.Value, t("boolean")));
|
||||
}
|
||||
if (idx && attrInfo?.allowsDefine) {
|
||||
return this.defineItems(idx, prefix, make);
|
||||
@@ -412,7 +429,7 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
make(
|
||||
v,
|
||||
vscode.CompletionItemKind.EnumMember,
|
||||
attrInfo.type ?? "enum",
|
||||
attrInfo.type ?? t("enum"),
|
||||
undefined,
|
||||
range,
|
||||
append ? ` ${v}` : v,
|
||||
@@ -436,10 +453,14 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
a.source.localeCompare(b.source),
|
||||
);
|
||||
const items = candidates.map((c) => {
|
||||
const item = make(c.source, vscode.CompletionItemKind.File, "Include source");
|
||||
const item = make(
|
||||
c.source,
|
||||
vscode.CompletionItemKind.File,
|
||||
t("Include source"),
|
||||
);
|
||||
item.detail = c.path;
|
||||
item.documentation = new vscode.MarkdownString(
|
||||
`\`${c.prefix ?? "relative"}\` · ${c.path}`,
|
||||
t("`{0}` · {1}", c.prefix ?? t("relative"), c.path),
|
||||
);
|
||||
return item;
|
||||
});
|
||||
@@ -520,19 +541,30 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
);
|
||||
const items = top.map(({ def }) => {
|
||||
const originLabel = (d: AssetDef) =>
|
||||
d.origin === "manifest" ? `manifest (${d.manifestSource ?? ""})` : d.origin;
|
||||
d.origin === "manifest"
|
||||
? d.manifestSource
|
||||
? t("manifest ({0})", d.manifestSource)
|
||||
: t("manifest")
|
||||
: originLabelText(d.origin);
|
||||
const origin = originLabel(def);
|
||||
const doc = new vscode.MarkdownString();
|
||||
doc.appendCodeblock(def.id);
|
||||
doc.appendMarkdown(`**Type**: ${def.type} \n`);
|
||||
if (def.manifestSource) doc.appendMarkdown(`**Source**: ${def.manifestSource} \n`);
|
||||
doc.appendMarkdown(`**Origin**: ${origin}`);
|
||||
doc.appendMarkdown(`${t("**Type**: {0}", def.type)} \n`);
|
||||
if (def.manifestSource) {
|
||||
doc.appendMarkdown(`${t("**Source**: {0}", def.manifestSource)} \n`);
|
||||
}
|
||||
doc.appendMarkdown(t("**Origin**: {0}", origin));
|
||||
for (const extra of byId.get(def.id.toLowerCase())?.extras ?? []) {
|
||||
doc.appendMarkdown(
|
||||
`\n\nAlso defined as **${extra.type}** · ${originLabel(extra)}`,
|
||||
`\n\n${t("Also defined as **{0}** · {1}", extra.type, originLabel(extra))}`,
|
||||
);
|
||||
}
|
||||
return make(def.id, vscode.CompletionItemKind.Value, `${def.type} · ${origin}`, doc.value);
|
||||
return make(
|
||||
def.id,
|
||||
vscode.CompletionItemKind.Value,
|
||||
t("{0} · {1}", def.type, origin),
|
||||
doc.value,
|
||||
);
|
||||
});
|
||||
return this.limitItems(items, byId.size);
|
||||
}
|
||||
@@ -557,7 +589,12 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
if (seen.has(dedupe)) continue;
|
||||
seen.add(dedupe);
|
||||
const label = `$${def.name}`;
|
||||
const item = make(label, vscode.CompletionItemKind.Constant, "Define", def.value);
|
||||
const item = make(
|
||||
label,
|
||||
vscode.CompletionItemKind.Constant,
|
||||
t("Define"),
|
||||
def.value,
|
||||
);
|
||||
item.insertText = label;
|
||||
items.push(item);
|
||||
}
|
||||
@@ -580,8 +617,10 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
make(
|
||||
id,
|
||||
vscode.CompletionItemKind.Value,
|
||||
"local module",
|
||||
"Pipeline-local id in the enclosing GameObject (includes xi:include targets).",
|
||||
t("local module"),
|
||||
t(
|
||||
"Pipeline-local id in the enclosing GameObject (includes xi:include targets).",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -674,7 +713,13 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
if (isList) return this.listEnumItems(info, rawPrefix, seg, valueRange, make);
|
||||
return info.enumValues
|
||||
.filter((v) => v.toLowerCase().startsWith(seg.token.toLowerCase()))
|
||||
.map((v) => make(v, vscode.CompletionItemKind.EnumMember, elType ?? "enum"));
|
||||
.map((v) =>
|
||||
make(
|
||||
v,
|
||||
vscode.CompletionItemKind.EnumMember,
|
||||
elType ?? t("enum"),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (idx && info.allowsDefine) {
|
||||
return this.defineItems(idx, seg.token, make);
|
||||
@@ -708,13 +753,13 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
item.insertText = this.elementSnippet(child.name, child.type, !typedOpen);
|
||||
const type = child.type;
|
||||
const info = type ? model.typeInfo(type) : undefined;
|
||||
item.detail = type ? `RA3 XML · ${type}` : "RA3 XML";
|
||||
item.detail = type ? t("RA3 XML · {0}", type) : t("RA3 XML");
|
||||
const doc = child.doc || (info?.kind === "complex" ? info.doc : "");
|
||||
if (doc) item.documentation = new vscode.MarkdownString(doc);
|
||||
if (info?.kind === "simple" && this.simpleContentValueKind(info)) {
|
||||
item.command = {
|
||||
command: "editor.action.triggerSuggest",
|
||||
title: "Suggest content value",
|
||||
title: t("Suggest content value"),
|
||||
};
|
||||
}
|
||||
items.push(item);
|
||||
@@ -736,6 +781,19 @@ interface ScoredDef {
|
||||
score: number;
|
||||
}
|
||||
|
||||
function originLabelText(origin: AssetDef["origin"]): string {
|
||||
switch (origin) {
|
||||
case "project":
|
||||
return t("project");
|
||||
case "sdk":
|
||||
return t("SDK");
|
||||
case "manifest":
|
||||
return t("manifest");
|
||||
default:
|
||||
return origin;
|
||||
}
|
||||
}
|
||||
|
||||
function compareScoredDefs(a: ScoredDef, b: ScoredDef): number {
|
||||
return a.score - b.score || a.def.id.localeCompare(b.def.id);
|
||||
}
|
||||
|
||||
+117
-36
@@ -1,6 +1,10 @@
|
||||
import * as vscode from "vscode";
|
||||
import { dirname } from "node:path";
|
||||
import { LineMap, type XmlElement } from "../language/xmlParser";
|
||||
import {
|
||||
LineMap,
|
||||
type XmlElement,
|
||||
type XmlParseError,
|
||||
} from "../language/xmlParser";
|
||||
import { resolveElementType } from "../language/typeContext";
|
||||
import { resolveSource, buildSearchPaths } from "../indexer/includeResolver";
|
||||
import { validateSdkPath } from "../sdk";
|
||||
@@ -16,6 +20,7 @@ import {
|
||||
} from "../indexer/refs";
|
||||
import type { LogicalElement } from "../indexer/logicalTree";
|
||||
import { scopePathKey } from "../indexer/localScope";
|
||||
import { t } from "../localize";
|
||||
|
||||
export class Ra3Diagnostics {
|
||||
private collection: vscode.DiagnosticCollection;
|
||||
@@ -57,7 +62,7 @@ export class Ra3Diagnostics {
|
||||
new vscode.Position(err.line, err.character),
|
||||
new vscode.Position(err.line, err.character + 1),
|
||||
),
|
||||
err.message,
|
||||
this.parseErrorMessage(err),
|
||||
vscode.DiagnosticSeverity.Error,
|
||||
"xml-syntax",
|
||||
),
|
||||
@@ -79,6 +84,42 @@ export class Ra3Diagnostics {
|
||||
this.collection.set(document.uri, diags);
|
||||
}
|
||||
|
||||
private parseErrorMessage(err: XmlParseError): string {
|
||||
switch (err.code) {
|
||||
case "content-before-root":
|
||||
return t("Content is not allowed before the root element");
|
||||
case "unterminated-comment":
|
||||
return t("Unterminated comment");
|
||||
case "unterminated-cdata":
|
||||
return t("Unterminated CDATA section");
|
||||
case "unterminated-doctype":
|
||||
return t("Unterminated DOCTYPE");
|
||||
case "unterminated-processing-instruction":
|
||||
return t("Unterminated processing instruction");
|
||||
case "unterminated-closing-tag":
|
||||
return t("Unterminated closing tag");
|
||||
case "unterminated-start-tag":
|
||||
return t("Unterminated start tag");
|
||||
case "malformed-markup":
|
||||
return t("Malformed markup");
|
||||
case "unexpected-closing-tag":
|
||||
return t(
|
||||
"Unexpected closing tag </{0}>",
|
||||
err.params?.name ?? "",
|
||||
);
|
||||
case "mismatched-closing-tag":
|
||||
return t(
|
||||
"Mismatched closing tag: expected </{0}>, found </{1}>",
|
||||
err.params?.expected ?? "",
|
||||
err.params?.found ?? "",
|
||||
);
|
||||
case "element-never-closed":
|
||||
return t("Element <{0}> is never closed", err.params?.name ?? "");
|
||||
default:
|
||||
return err.message;
|
||||
}
|
||||
}
|
||||
|
||||
clear(uri: vscode.Uri): void {
|
||||
this.collection.delete(uri);
|
||||
}
|
||||
@@ -120,7 +161,7 @@ export class Ra3Diagnostics {
|
||||
diags.push(
|
||||
this.diag(
|
||||
range,
|
||||
`Top-level asset <${local}> requires an id attribute`,
|
||||
t("Top-level asset <{0}> requires an id attribute", local),
|
||||
vscode.DiagnosticSeverity.Error,
|
||||
"missing-id",
|
||||
),
|
||||
@@ -136,7 +177,12 @@ export class Ra3Diagnostics {
|
||||
diags.push(
|
||||
this.diag(
|
||||
where,
|
||||
`Duplicate id "${idAttr.value}" for <${local}> (also defined on line ${prev.line})`,
|
||||
t(
|
||||
'Duplicate id "{0}" for <{1}> (also defined on line {2})',
|
||||
idAttr.value,
|
||||
local,
|
||||
prev.line,
|
||||
),
|
||||
vscode.DiagnosticSeverity.Error,
|
||||
"duplicate-id",
|
||||
),
|
||||
@@ -166,7 +212,7 @@ export class Ra3Diagnostics {
|
||||
diags.push(
|
||||
this.diag(
|
||||
range,
|
||||
`Unknown element <${local}> (not in the RA3 XSD model)`,
|
||||
t("Unknown element <{0}> (not in the RA3 XSD model)", local),
|
||||
vscode.DiagnosticSeverity.Warning,
|
||||
"unknown-element",
|
||||
),
|
||||
@@ -187,16 +233,16 @@ export class Ra3Diagnostics {
|
||||
continue;
|
||||
}
|
||||
if (settings.diagnoseUnknownElements && !knownNames.has(aName)) {
|
||||
diags.push(
|
||||
this.diag(
|
||||
new vscode.Range(
|
||||
document.positionAt(attr.nameStart),
|
||||
document.positionAt(attr.nameEnd),
|
||||
diags.push(
|
||||
this.diag(
|
||||
new vscode.Range(
|
||||
document.positionAt(attr.nameStart),
|
||||
document.positionAt(attr.nameEnd),
|
||||
),
|
||||
t('Unknown attribute "{0}" for <{1}>', aName, local),
|
||||
vscode.DiagnosticSeverity.Warning,
|
||||
"unknown-attribute",
|
||||
),
|
||||
`Unknown attribute "${aName}" for <${local}>`,
|
||||
vscode.DiagnosticSeverity.Warning,
|
||||
"unknown-attribute",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -258,8 +304,12 @@ export class Ra3Diagnostics {
|
||||
diags.push(
|
||||
this.diag(
|
||||
range,
|
||||
`Duplicate id "${id}" for <${type}> (also defined in ${other.file})` +
|
||||
(provisional ? " (based on a partial index)" : ""),
|
||||
t(
|
||||
'Duplicate id "{0}" for <{1}> (also defined in {2})',
|
||||
id,
|
||||
type,
|
||||
other.file,
|
||||
) + (provisional ? t(" (based on a partial index)") : ""),
|
||||
vscode.DiagnosticSeverity.Error,
|
||||
"duplicate-id",
|
||||
),
|
||||
@@ -296,8 +346,8 @@ export class Ra3Diagnostics {
|
||||
diags.push(
|
||||
this.diag(
|
||||
range,
|
||||
`Undefined define "$${m[1]}"` +
|
||||
(provisional ? " (index incomplete — may be a false positive)" : ""),
|
||||
t('Undefined define "${0}"', m[1]) +
|
||||
(provisional ? t(" (index incomplete — may be a false positive)") : ""),
|
||||
vscode.DiagnosticSeverity.Warning,
|
||||
code,
|
||||
),
|
||||
@@ -318,20 +368,18 @@ export class Ra3Diagnostics {
|
||||
const attrRef = model
|
||||
.attributesOfType(elType)
|
||||
.find((a) => a.name === attrName);
|
||||
const expected = attrRef?.refType
|
||||
? `of type \`${attrRef.refType}\``
|
||||
: attrRef?.isRef
|
||||
? "of the expected declared type"
|
||||
: "matching";
|
||||
const code = provisional ? "unresolved-reference-indexing" : "unresolved-reference";
|
||||
const baseMessage = anyDef
|
||||
? `Reference "${value}" has no definition ${expected} (ids with the same name exist for other types)`
|
||||
: `Unresolved reference "${value}" (not found in the current index)`;
|
||||
const baseMessage = unresolvedReferenceMessage(
|
||||
value,
|
||||
anyDef,
|
||||
attrRef?.refType ?? null,
|
||||
attrRef?.isRef ?? false,
|
||||
);
|
||||
diags.push(
|
||||
this.diag(
|
||||
range,
|
||||
provisional
|
||||
? `${baseMessage} (index incomplete — may be a false positive)`
|
||||
? baseMessage + t(" (index incomplete — may be a false positive)")
|
||||
: baseMessage,
|
||||
severity === "warning"
|
||||
? vscode.DiagnosticSeverity.Warning
|
||||
@@ -379,8 +427,8 @@ export class Ra3Diagnostics {
|
||||
diags.push(
|
||||
this.diag(
|
||||
range,
|
||||
`Undefined define "$${m[1]}"` +
|
||||
(provisional ? " (index incomplete — may be a false positive)" : ""),
|
||||
t('Undefined define "${0}"', m[1]) +
|
||||
(provisional ? t(" (index incomplete — may be a false positive)") : ""),
|
||||
vscode.DiagnosticSeverity.Warning,
|
||||
code,
|
||||
),
|
||||
@@ -398,16 +446,18 @@ export class Ra3Diagnostics {
|
||||
(idx.local?.assetsById.has(value.toLowerCase()) ?? false) ||
|
||||
idx.assetsById.has(value.toLowerCase());
|
||||
const refType = info.refType;
|
||||
const expected = refType ? `of type \`${refType}\`` : "of the expected declared type";
|
||||
const code = provisional ? "unresolved-reference-indexing" : "unresolved-reference";
|
||||
const baseMessage = anyDef
|
||||
? `Reference "${value}" has no definition ${expected} (ids with the same name exist for other types)`
|
||||
: `Unresolved reference "${value}" (not found in the current index)`;
|
||||
const baseMessage = unresolvedReferenceMessage(
|
||||
value,
|
||||
anyDef,
|
||||
refType ?? null,
|
||||
!refType,
|
||||
);
|
||||
diags.push(
|
||||
this.diag(
|
||||
range,
|
||||
provisional
|
||||
? `${baseMessage} (index incomplete — may be a false positive)`
|
||||
? baseMessage + t(" (index incomplete — may be a false positive)")
|
||||
: baseMessage,
|
||||
severity === "warning"
|
||||
? vscode.DiagnosticSeverity.Warning
|
||||
@@ -432,7 +482,10 @@ export class Ra3Diagnostics {
|
||||
document.positionAt(typeAttr.valueStart),
|
||||
document.positionAt(typeAttr.valueEnd),
|
||||
),
|
||||
`Invalid Include type "${typeAttr.value}" (expected reference, instance or all)`,
|
||||
t(
|
||||
'Invalid Include type "{0}" (expected reference, instance or all)',
|
||||
typeAttr.value,
|
||||
),
|
||||
vscode.DiagnosticSeverity.Error,
|
||||
"include-type",
|
||||
),
|
||||
@@ -462,7 +515,7 @@ export class Ra3Diagnostics {
|
||||
document.positionAt(sourceAttr.valueStart),
|
||||
document.positionAt(sourceAttr.valueEnd),
|
||||
),
|
||||
`Include target not found: ${sourceAttr.value}`,
|
||||
t("Include target not found: {0}", sourceAttr.value),
|
||||
vscode.DiagnosticSeverity.Warning,
|
||||
"include-not-found",
|
||||
),
|
||||
@@ -488,6 +541,34 @@ function localName(tag: string): string {
|
||||
return idx >= 0 ? tag.slice(idx + 1) : tag;
|
||||
}
|
||||
|
||||
function unresolvedReferenceMessage(
|
||||
value: string,
|
||||
anyDef: boolean,
|
||||
refType: string | null,
|
||||
isRef: boolean,
|
||||
): string {
|
||||
if (anyDef) {
|
||||
if (refType) {
|
||||
return t(
|
||||
'Reference "{0}" has no definition of type `{1}` (ids with the same name exist for other types)',
|
||||
value,
|
||||
refType,
|
||||
);
|
||||
}
|
||||
if (isRef) {
|
||||
return t(
|
||||
'Reference "{0}" has no definition of the expected declared type (ids with the same name exist for other types)',
|
||||
value,
|
||||
);
|
||||
}
|
||||
return t(
|
||||
'Reference "{0}" has no matching definition (ids with the same name exist for other types)',
|
||||
value,
|
||||
);
|
||||
}
|
||||
return t('Unresolved reference "{0}" (not found in the current index)', value);
|
||||
}
|
||||
|
||||
function tagRange(document: vscode.TextDocument, el: XmlElement): vscode.Range {
|
||||
return new vscode.Range(
|
||||
document.positionAt(el.start),
|
||||
|
||||
+89
-43
@@ -19,6 +19,7 @@ import {
|
||||
import { scopePathKey, type DocumentScope } from "../indexer/localScope";
|
||||
import { dirname } from "node:path";
|
||||
import { buildSearchPaths, resolveSource } from "../indexer/includeResolver";
|
||||
import { t } from "../localize";
|
||||
|
||||
export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
constructor(private ws: ModWorkspace) {}
|
||||
@@ -68,7 +69,9 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
const md = new vscode.MarkdownString();
|
||||
md.appendCodeblock(`<${name}>`, "xml");
|
||||
md.appendMarkdown(
|
||||
"XInclude element (W3C XInclude namespace) — not part of the RA3 XSD model.",
|
||||
t(
|
||||
"XInclude element (W3C XInclude namespace) — not part of the RA3 XSD model.",
|
||||
),
|
||||
);
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
@@ -76,19 +79,25 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
const info = type ? model.typeInfo(type) : undefined;
|
||||
const md = new vscode.MarkdownString();
|
||||
md.appendCodeblock(`<${name}>`, "xml");
|
||||
if (model.isTopLevelElement(name)) md.appendMarkdown(`**Top-level asset element** \n`);
|
||||
if (model.isTopLevelElement(name)) {
|
||||
md.appendMarkdown(`${t("**Top-level asset element**")} \n`);
|
||||
}
|
||||
if (info?.kind === "complex") {
|
||||
if (info.doc) md.appendMarkdown(`${info.doc} \n`);
|
||||
md.appendMarkdown(
|
||||
`Attributes: ${info.attributes.length} · Children: ${info.children.length} \n`,
|
||||
`${t(
|
||||
"Attributes: {0} · Children: {1}",
|
||||
info.attributes.length,
|
||||
info.children.length,
|
||||
)} \n`,
|
||||
);
|
||||
if (info.base) md.appendMarkdown(`Extends: \`${info.base}\``);
|
||||
if (info.base) md.appendMarkdown(t("Extends: `{0}`", info.base));
|
||||
} else if (info?.kind === "simple") {
|
||||
md.appendMarkdown(`Simple type: \`${type}\``);
|
||||
md.appendMarkdown(t("Simple type: `{0}`", type ?? ""));
|
||||
} else if (type) {
|
||||
md.appendMarkdown(`Type: \`${type}\``);
|
||||
md.appendMarkdown(t("Type: `{0}`", type));
|
||||
} else {
|
||||
md.appendMarkdown("Not found in the bundled XSD model.");
|
||||
md.appendMarkdown(t("Not found in the bundled XSD model."));
|
||||
}
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
@@ -104,26 +113,38 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
md.appendCodeblock(`${attrName}=""`, "xml");
|
||||
if (!attr) {
|
||||
if (/^(xmlns|xai:)/.test(attrName)) {
|
||||
md.appendMarkdown(`Namespace/instance attribute.`);
|
||||
md.appendMarkdown(t("Namespace/instance attribute."));
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
if (!model.isXsdElementName(el.name)) {
|
||||
md.appendMarkdown(
|
||||
`XInclude attribute (W3C XInclude namespace) — not part of the RA3 XSD model.`,
|
||||
t(
|
||||
"XInclude attribute (W3C XInclude namespace) — not part of the RA3 XSD model.",
|
||||
),
|
||||
);
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
md.appendMarkdown("Unknown attribute for this element.");
|
||||
md.appendMarkdown(t("Unknown attribute for this element."));
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
if (attr.doc) md.appendMarkdown(`${attr.doc} \n`);
|
||||
if (attr.required) md.appendMarkdown(`**Required** \n`);
|
||||
if (attr.refType) md.appendMarkdown(`References assets of type \`${attr.refType}\` \n`);
|
||||
if (attr.required) md.appendMarkdown(`${t("**Required**")} \n`);
|
||||
if (attr.refType) {
|
||||
md.appendMarkdown(
|
||||
`${t("References assets of type `{0}`", attr.refType)} \n`,
|
||||
);
|
||||
}
|
||||
if (attr.enumValues.length)
|
||||
md.appendMarkdown(`Values: \`${attr.enumValues.join("`, `")}\` \n`);
|
||||
if (attr.default != null) md.appendMarkdown(`Default: \`${attr.default}\` \n`);
|
||||
if (attr.allowsDefine) md.appendMarkdown(`May use \`$DEFINE\` constants \n`);
|
||||
md.appendMarkdown(`Type: \`${attr.type ?? "string"}\``);
|
||||
md.appendMarkdown(
|
||||
`${t("Values: `{0}`", attr.enumValues.join("`, `"))} \n`,
|
||||
);
|
||||
if (attr.default != null) {
|
||||
md.appendMarkdown(`${t("Default: `{0}`", attr.default)} \n`);
|
||||
}
|
||||
if (attr.allowsDefine) {
|
||||
md.appendMarkdown(`${t("May use `$DEFINE` constants")} \n`);
|
||||
}
|
||||
md.appendMarkdown(t("Type: `{0}`", attr.type ?? "string"));
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
|
||||
@@ -164,17 +185,19 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
).path
|
||||
: null;
|
||||
if (resolved) {
|
||||
md.appendMarkdown(`**Include source** \n`);
|
||||
md.appendMarkdown(`${t("**Include source**")} \n`);
|
||||
md.appendCodeblock(resolved);
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
const cand = idx?.sourceCandidates.find((c) => c.source === value);
|
||||
if (cand) {
|
||||
md.appendMarkdown(`**Include source** \n`);
|
||||
md.appendMarkdown(`${t("**Include source**")} \n`);
|
||||
md.appendCodeblock(cand.path);
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
md.appendMarkdown(`Include source: \`${value}\` (not in candidate index)`);
|
||||
md.appendMarkdown(
|
||||
t("Include source: `{0}` (not in candidate index)", value),
|
||||
);
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
|
||||
@@ -191,7 +214,7 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
if (!idx) {
|
||||
if (isReferenceAttributeOfType(elType, attrName)) {
|
||||
md.appendMarkdown(
|
||||
"Index is still building — references cannot be resolved yet.",
|
||||
t("Index is still building — references cannot be resolved yet."),
|
||||
);
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
@@ -204,12 +227,14 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
const attrRef = model
|
||||
.attributesOfType(elType)
|
||||
.find((a) => a.name === attrName);
|
||||
const expected = attrRef?.refType
|
||||
? ` of type \`${attrRef.refType}\``
|
||||
: attrRef?.isRef
|
||||
? " of the expected declared type"
|
||||
: "";
|
||||
return this.noDefinitionHover(expected);
|
||||
return this.noDefinitionHover(
|
||||
attrRef?.refType
|
||||
? "typed"
|
||||
: attrRef?.isRef
|
||||
? "untyped"
|
||||
: "generic",
|
||||
attrRef?.refType ?? undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,16 +260,16 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
if (!isReferenceContentType(elType)) return null;
|
||||
if (!idx) {
|
||||
const md = new vscode.MarkdownString();
|
||||
md.appendMarkdown("Index is still building — references cannot be resolved yet.");
|
||||
md.appendMarkdown(
|
||||
t("Index is still building — references cannot be resolved yet."),
|
||||
);
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
const targets = resolveContentReferenceTargets(idx, elType, value);
|
||||
if (targets.length) return this.definitionsHover(targets, document);
|
||||
const info = elType ? model.typeInfo(elType) : undefined;
|
||||
const refType = info?.kind === "simple" ? info.refType : null;
|
||||
return this.noDefinitionHover(
|
||||
refType ? ` of type \`${refType}\`` : " of the expected declared type",
|
||||
);
|
||||
return this.noDefinitionHover(refType ? "typed" : "untyped", refType ?? undefined);
|
||||
}
|
||||
|
||||
private defineHover(
|
||||
@@ -254,10 +279,10 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
if (!defs?.length) return null;
|
||||
const d = defs[0];
|
||||
const md = new vscode.MarkdownString();
|
||||
md.appendMarkdown(`**Define** \`$${d.name}\` \n`);
|
||||
md.appendMarkdown(`${t("**Define** `{0}`", `$${d.name}`)} \n`);
|
||||
md.appendCodeblock(d.value);
|
||||
const rel = relativePath(document, d.file);
|
||||
md.appendMarkdown(`Defined in \`${rel}:${d.line}\``);
|
||||
md.appendMarkdown(t("Defined in `{0}:{1}`", rel, d.line));
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
|
||||
@@ -266,23 +291,44 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
document: vscode.TextDocument,
|
||||
): vscode.Hover {
|
||||
const md2 = new vscode.MarkdownString();
|
||||
md2.appendMarkdown(`**${targets.length} definition${targets.length > 1 ? "s" : ""}** \n`);
|
||||
md2.appendMarkdown(
|
||||
`${targets.length === 1 ? t("**1 definition**") : t("**{0} definitions**", targets.length)} \n`,
|
||||
);
|
||||
for (const { def: d } of targets.slice(0, 8)) {
|
||||
const loc =
|
||||
d.origin === "manifest"
|
||||
? `manifest \`${d.manifestSource ?? d.file}\``
|
||||
: `\`${relativePath(document, d.file)}:${d.line}\``;
|
||||
md2.appendMarkdown(`- \`${d.type}\` · ${loc} \n`);
|
||||
? t("manifest `{0}`", d.manifestSource ?? d.file)
|
||||
: t("`{0}:{1}`", relativePath(document, d.file), d.line);
|
||||
md2.appendMarkdown(`${t("- `{0}` · {1}", d.type, loc)} \n`);
|
||||
}
|
||||
return new vscode.Hover(md2);
|
||||
}
|
||||
|
||||
private noDefinitionHover(expected: string): vscode.Hover {
|
||||
private noDefinitionHover(
|
||||
kind: "typed" | "untyped" | "generic",
|
||||
refType?: string,
|
||||
): vscode.Hover {
|
||||
const md = new vscode.MarkdownString();
|
||||
md.appendMarkdown(
|
||||
`No matching definition${expected} in the current index` +
|
||||
" (may exist in a compiled manifest or vanilla data).",
|
||||
);
|
||||
if (kind === "typed" && refType) {
|
||||
md.appendMarkdown(
|
||||
t(
|
||||
"No matching definition of type `{0}` in the current index (may exist in a compiled manifest or vanilla data).",
|
||||
refType,
|
||||
),
|
||||
);
|
||||
} else if (kind === "untyped") {
|
||||
md.appendMarkdown(
|
||||
t(
|
||||
"No matching definition of the expected declared type in the current index (may exist in a compiled manifest or vanilla data).",
|
||||
),
|
||||
);
|
||||
} else {
|
||||
md.appendMarkdown(
|
||||
t(
|
||||
"No matching definition in the current index (may exist in a compiled manifest or vanilla data).",
|
||||
),
|
||||
);
|
||||
}
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
|
||||
@@ -301,10 +347,10 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
const lineMap = scope.lineMaps.get(scopePathKey(target.sourceFile));
|
||||
const line = lineMap ? lineMap.positionAt(idAttr.valueStart).line + 1 : 0;
|
||||
const md = new vscode.MarkdownString();
|
||||
md.appendMarkdown(`**Local pipeline id** \`${value}\` \n`);
|
||||
md.appendMarkdown(`${t("**Local pipeline id** `{0}`", value)} \n`);
|
||||
md.appendCodeblock(`<${target.name}>`);
|
||||
const rel = relativePath(document, target.sourceFile);
|
||||
md.appendMarkdown(`Defined in \`${rel}:${line}\``);
|
||||
md.appendMarkdown(t("Defined in `{0}:{1}`", rel, line));
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
import { scopePathKey, type DocumentScope } from "../indexer/localScope";
|
||||
import type { ModWorkspace } from "../workspace";
|
||||
import type { AssetDef, ModIndex } from "../indexer/types";
|
||||
import { t } from "../localize";
|
||||
|
||||
function searchPathsFor(idx: ModIndex): SearchPaths {
|
||||
return buildSearchPaths(idx.sdkDir, idx.projectDir);
|
||||
@@ -404,7 +405,13 @@ export class Ra3DocumentSymbolProvider implements vscode.DocumentSymbolProvider
|
||||
document.positionAt(define.end),
|
||||
);
|
||||
symbols.push(
|
||||
new vscode.DocumentSymbol(`$${name}`, "Define", vscode.SymbolKind.Constant, range, range),
|
||||
new vscode.DocumentSymbol(
|
||||
`$${name}`,
|
||||
t("Define"),
|
||||
vscode.SymbolKind.Constant,
|
||||
range,
|
||||
range,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -26,7 +27,7 @@ export async function findUnreferencedAssets(
|
||||
const idx = ws.activeIndex();
|
||||
if (!ws.isRa3Workspace() || !idx) {
|
||||
void vscode.window.showInformationMessage(
|
||||
"RA3 Mod XML: no index available yet.",
|
||||
t("RA3 Mod XML: no index available yet."),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -36,18 +37,18 @@ export async function findUnreferencedAssets(
|
||||
if (!type) {
|
||||
if (!byType.size) {
|
||||
void vscode.window.showInformationMessage(
|
||||
"RA3 Mod XML: no unreferenced assets found.",
|
||||
t("RA3 Mod XML: no unreferenced assets found."),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const pickedType = await vscode.window.showQuickPick<TypePickItem>(
|
||||
[...byType.entries()].map(([t, defs]) => ({
|
||||
label: t,
|
||||
description: `${defs.length} unreferenced`,
|
||||
type: t,
|
||||
[...byType.entries()].map(([typeName, defs]) => ({
|
||||
label: typeName,
|
||||
description: t("{0} unreferenced", defs.length),
|
||||
type: typeName,
|
||||
})),
|
||||
{
|
||||
placeHolder: "Select an asset type",
|
||||
placeHolder: t("Select an asset type"),
|
||||
matchOnDescription: true,
|
||||
},
|
||||
);
|
||||
@@ -58,7 +59,7 @@ export async function findUnreferencedAssets(
|
||||
const defs = byType.get(type) ?? [];
|
||||
if (!defs.length) {
|
||||
void vscode.window.showInformationMessage(
|
||||
`RA3 Mod XML: no unreferenced ${type} assets found.`,
|
||||
t("RA3 Mod XML: no unreferenced {0} assets found.", type),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -70,7 +71,7 @@ export async function findUnreferencedAssets(
|
||||
line: d.line,
|
||||
})),
|
||||
{
|
||||
placeHolder: `${type}: ${defs.length} unreferenced`,
|
||||
placeHolder: t("{0}: {1} unreferenced", type, defs.length),
|
||||
matchOnDescription: true,
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user