decomp: add option to auto-format decompiler results (#336)

This commit is contained in:
Tyler Wilding 2024-01-26 14:13:50 -05:00 committed by GitHub
parent 40a94c4e2e
commit bcf8098b8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 135 additions and 8 deletions

View file

@ -240,6 +240,14 @@
"default": null,
"description": "File path to the decompiler executable"
},
"opengoal.formatterPath": {
"type": [
"string",
"null"
],
"default": null,
"description": "File path to the formatter executable for when invoking directly (not via LSP)"
},
"opengoal.typeSearcherPath": {
"type": [
"string",
@ -262,6 +270,11 @@
"type": "string",
"default": "ntsc_v1",
"description": "Config version to use for decompiling Jak 3 related files"
},
"opengoal.formatDecompilationOutput": {
"type": "boolean",
"default": false,
"description": "Whether or not the results of the decompiler should be auto-formatted"
}
}
},
@ -427,11 +440,6 @@
"command": "opengoal.decomp.openManPage",
"group": "z_commands"
}
],
"editor/title": [
{
"when": "resourceScheme == opengoalBatchRename"
}
]
},
"languages": [

View file

@ -13,10 +13,14 @@ export function getConfig() {
opengoalLspPath: configOptions.get<string>("opengoalLspPath"),
opengoalLspLogPath: configOptions.get<string>("opengoalLspLogPath"),
opengoalLspLogVerbose: configOptions.get<boolean>("opengoalLspLogVerbose"),
formatDecompilationOutput: configOptions.get<boolean>(
"formatDecompilationOutput",
),
eeManPagePath: configOptions.get<string>("eeManPagePath"),
vuManPagePath: configOptions.get<string>("vuManPagePath"),
decompilerPath: configOptions.get<string>("decompilerPath"),
formatterPath: configOptions.get<string>("formatterPath"),
typeSearcherPath: configOptions.get<string>("typeSearcherPath"),
jak1DecompConfigVersion: configOptions.get<string>(
"decompilerJak1ConfigVersion",
@ -83,6 +87,15 @@ export async function updateDecompilerPath(path: string) {
);
}
export async function updateFormatterPath(path: string) {
const userConfig = vscode.workspace.getConfiguration();
await userConfig.update(
"opengoal.formatterPath",
path,
vscode.ConfigurationTarget.Global,
);
}
export async function updateTypeSearcherPath(path: string) {
const userConfig = vscode.workspace.getConfiguration();
await userConfig.update(

View file

@ -4,7 +4,11 @@ import * as vscode from "vscode";
import { determineGameFromPath, GameName } from "../utils/file-utils";
import { open_in_pdf } from "./man-page";
import * as util from "util";
import { getConfig, updateDecompilerPath } from "../config/config";
import {
getConfig,
updateDecompilerPath,
updateFormatterPath,
} from "../config/config";
import * as path from "path";
import { getExtensionContext, getProjectRoot } from "../context";
import {
@ -36,6 +40,7 @@ enum DecompStatus {
Idle,
Running,
Errored,
Formatting,
}
function updateStatus(status: DecompStatus, metadata?: any) {
@ -71,6 +76,20 @@ function updateStatus(status: DecompStatus, metadata?: any) {
decompStatusItem.tooltip = "Decompiling...";
decompStatusItem.command = undefined;
break;
case DecompStatus.Formatting:
if (metadata.objectNames.length > 0) {
if (metadata.objectNames.length <= 5) {
subText = metadata.objectNames.join(", ");
} else {
subText = `${metadata.objectNames.slice(0, 5).join(", ")}, and ${
metadata.objectNames.length - 5
} more`;
}
}
decompStatusItem.text = `$(loading~spin) Formatting - ${subText} - [ ${metadata.decompConfig} ]`;
decompStatusItem.tooltip = "Formatting...";
decompStatusItem.command = undefined;
break;
default:
break;
}
@ -85,6 +104,15 @@ function defaultDecompPath() {
}
}
function defaultFormatterPath() {
const platform = process.platform;
if (platform == "win32") {
return "out/build/Release/bin/formatter.exe";
} else {
return "build/tools/formatter";
}
}
function getDecompilerConfig(gameName: GameName): string | undefined {
let decompConfigPath = undefined;
if (gameName == GameName.Jak1) {
@ -159,6 +187,39 @@ async function checkDecompilerPath(): Promise<string | undefined> {
return decompilerPath;
}
async function checkFormatterPath(): Promise<string | undefined> {
let formatterPath = getConfig().formatterPath;
// Look for the decompiler if the path isn't set or the file is now missing
if (formatterPath !== undefined && existsSync(formatterPath)) {
return formatterPath;
}
const potentialPath = vscode.Uri.joinPath(
getProjectRoot(),
defaultFormatterPath(),
);
if (existsSync(potentialPath.fsPath)) {
formatterPath = potentialPath.fsPath;
} else {
// Ask the user to find it cause we have no idea
const path = await vscode.window.showOpenDialog({
canSelectMany: false,
openLabel: "Select Formatter",
title: "Provide the formatter executable's path",
});
if (path === undefined || path.length == 0) {
vscode.window.showErrorMessage(
"OpenGOAL - Aborting formatting, you didn't provide a path to the executable",
);
return undefined;
}
formatterPath = path[0].fsPath;
}
updateFormatterPath(formatterPath);
return formatterPath;
}
async function decompFiles(
gameName: GameName,
fileNames: string[],
@ -216,6 +277,49 @@ async function decompFiles(
`DECOMP ERROR:\nSTDOUT:\n${error.stdout}\nSTDERR:\n${error.stderr}`,
);
}
// Format results
if (getConfig().formatDecompilationOutput) {
const formatterPath = await checkFormatterPath();
if (!formatterPath) {
return;
}
updateStatus(DecompStatus.Formatting, {
objectNames: fileNames,
decompConfig: path.parse(decompConfig).name,
});
for (const name of fileNames) {
const filePath = path.join(
getProjectRoot()?.fsPath,
"decompiler_out",
gameName,
`${name}_disasm.gc`,
);
const formatterArgs = ["--write", "--file", filePath];
try {
const { stdout, stderr } = await execFileAsync(
formatterPath,
formatterArgs,
{
encoding: "utf8",
cwd: getProjectRoot()?.fsPath,
timeout: 20000,
},
);
channel.append(stdout.toString());
channel.append(stderr.toString());
} catch (error: any) {
updateStatus(DecompStatus.Errored);
channel.append(
`DECOMP ERROR:\nSTDOUT:\n${error.stdout}\nSTDERR:\n${error.stderr}`,
);
}
}
updateStatus(DecompStatus.Idle);
}
}
async function getValidObjectNames(gameName: string) {

View file

@ -45,19 +45,21 @@ export class IRCompletionItemProvider implements vscode.CompletionItemProvider {
// ! - mutated (if it's involved in a set)
// ? - optional (can't easily determine this and is frankly rare)
let paramFound = false;
let paramPrinted = false;
for (let i = 1; i < funcBody.length; i++) {
const line = funcBody[i];
if (line.includes(`(set! (-> ${arg.name}`)) {
docstring += ` @param! ${arg.name} something\n`;
paramFound = true;
paramPrinted = true;
break;
} else if (line.includes(arg.name)) {
paramFound = true;
}
}
if (paramFound) {
if (paramFound && !paramPrinted) {
docstring += ` @param ${arg.name} something\n`;
} else {
} else if (!paramPrinted) {
docstring += ` @param_ ${arg.name} something\n`;
}
}