Skip to content

Commit

Permalink
decomp: add new feature that lets you bulk rename variables in a sele…
Browse files Browse the repository at this point in the history
…ction (#332)
  • Loading branch information
xTVaser authored Jan 24, 2024
1 parent eabd22c commit 3b6ace0
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 3 deletions.
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@
"command": "opengoal.decomp.misc.applyDecompilerSuggestions",
"title": "OpenGOAL - Misc - Apply Decompiler Suggestions to Selection"
},
{
"command": "opengoal.decomp.misc.batchRenameUnnamedVars",
"title": "OpenGOAL - Misc - Batch Rename Unnamed Vars"
},
{
"command": "opengoal.decomp.typeSearcher.open",
"title": "OpenGOAL - Misc - Type Searcher"
Expand Down Expand Up @@ -414,6 +418,11 @@
"command": "opengoal.decomp.openManPage",
"group": "z_commands"
}
],
"editor/title": [
{
"when": "resourceScheme == opengoalBatchRename"
}
]
},
"languages": [
Expand Down
124 changes: 124 additions & 0 deletions src/decomp/misc-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import {
updateFileBeforeDecomp,
} from "../utils/file-utils";
import { getWorkspaceFolderByName } from "../utils/workspace";
import { getFuncNameFromPosition } from "../languages/ir2/ir2-utils";
import {
ArgumentMeta,
getArgumentsInSignature,
getSymbolsArgumentInfo,
} from "../languages/opengoal/opengoal-tools";
import { bulkUpdateVarCasts } from "./utils";

async function addToOffsets() {
const editor = vscode.window.activeTextEditor;
Expand Down Expand Up @@ -455,7 +462,118 @@ async function applyDecompilerSuggestions() {
});
}

let originalDocumentForRename: vscode.TextDocument | undefined = undefined;
let currentRenameWindow: vscode.TextEditor | undefined = undefined;
let currentRenameLines: string[] = [];
let currentRenameFunctionName: string | undefined = undefined;
let currentRenameArgMeta: ArgumentMeta | undefined = undefined;
let currentRenameFileVersion = 0;

async function batchRenameUnnamedVars() {
const editor = vscode.window.activeTextEditor;
if (editor === undefined || editor.selection.isEmpty) {
return;
}
const currentSelection = editor.document.getText(editor.selection);

// We can determine the function in a more consistent way here, that will also allow
// for renaming anon-functions / states / etc
const funcName = await getFuncNameFromPosition(
editor.document,
editor.selection.active,
);
if (funcName === undefined) {
return;
}
currentRenameFunctionName = funcName;
currentRenameArgMeta = {
index: 0,
totalCount: getArgumentsInSignature(currentSelection.split("\n")[0]).length,
isMethod: currentSelection.split("\n")[0].includes("defmethod"),
};

const unnamedVarRegex =
/(?:(?:arg\d+)|(?:f\d+|at|v[0-1]|a[0-3]|t[0-9]|s[0-7]|k[0-1]|gp|sp|sv|fp|ra)-\d+)/g;

const vars = new Set(
[...currentSelection.matchAll(unnamedVarRegex)].map((match) => match[0]),
);

currentRenameLines = [];
currentRenameLines.push(`Renaming Vars in - ${funcName}:`);
for (const variable of vars) {
currentRenameLines.push(`${variable} => `);
}

originalDocumentForRename = editor.document;
currentRenameFileVersion++;
currentRenameWindow = await vscode.window.showTextDocument(
vscode.Uri.from({
scheme: "opengoalBatchRename",
path: "/opengoalBatchRename",
}),
{ preview: false, viewColumn: vscode.ViewColumn.Beside },
);
}

async function processOpengoalBatchRename() {
if (
originalDocumentForRename === undefined ||
currentRenameFunctionName === undefined ||
currentRenameArgMeta === undefined
) {
return;
}

const renameMap: Record<string, string> = {};

for (let i = 0; i < currentRenameLines.length; i++) {
const tokens = currentRenameLines[i].split("=>");
if (tokens.length !== 2) {
continue;
}
const oldName = tokens[0].trim();
const newName = tokens[1].trim();
renameMap[oldName] = newName;
}

await bulkUpdateVarCasts(
originalDocumentForRename,
currentRenameFunctionName,
currentRenameArgMeta,
renameMap,
);
await vscode.commands.executeCommand(
"workbench.action.revertAndCloseActiveEditor",
);
}

export async function activateMiscDecompTools() {
const emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
vscode.workspace.registerFileSystemProvider("opengoalBatchRename", {
createDirectory() {},
delete() {},
onDidChangeFile: emitter.event,
readDirectory() {
return [];
},
readFile() {
return new TextEncoder().encode(currentRenameLines.join("\n"));
},
rename() {},
stat() {
return { ctime: 0, mtime: currentRenameFileVersion, size: 0, type: 0 };
},
watch(uri) {
return new vscode.Disposable(() => {});
},
writeFile(uri, content) {
currentRenameLines = new TextDecoder().decode(content).split("\n");
processOpengoalBatchRename();
currentRenameFileVersion++;
},
});

getExtensionContext().subscriptions.push(
vscode.commands.registerCommand(
"opengoal.decomp.misc.addToOffsets",
Expand Down Expand Up @@ -504,4 +622,10 @@ export async function activateMiscDecompTools() {
applyDecompilerSuggestions,
),
);
getExtensionContext().subscriptions.push(
vscode.commands.registerCommand(
"opengoal.decomp.misc.batchRenameUnnamedVars",
batchRenameUnnamedVars,
),
);
}
63 changes: 62 additions & 1 deletion src/decomp/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export async function updateVarCasts(
}
} else {
if (argMeta.isMethod && i == 0) {
varNameData[funcName].args[i] = "obj";
varNameData[funcName].args[i] = "this";
} else {
varNameData[funcName].args[i] = `arg${i}`;
}
Expand Down Expand Up @@ -215,3 +215,64 @@ export async function updateVarCasts(

writeFileSync(filePath, stringify(varNameData, null, 2));
}

export async function bulkUpdateVarCasts(
document: vscode.TextDocument,
funcName: string,
argMeta: ArgumentMeta,
renameMap: Record<string, string>,
) {
// Update the var-names file
const projectRoot = getWorkspaceFolderByName("jak-project");
if (projectRoot === undefined) {
vscode.window.showErrorMessage(
"OpenGOAL - Unable to locate 'jak-project' workspace folder",
);
return;
}

const varNameData = getCastFileData(projectRoot, document, "var_names.jsonc");
if (varNameData === undefined) {
return;
}

if (!(funcName in varNameData)) {
varNameData[funcName] = {};
}

for (const [oldName, newName] of Object.entries(renameMap)) {
if (oldName.startsWith("arg")) {
// initialize if not already done
if (!("args" in varNameData[funcName])) {
varNameData[funcName].args = [];
for (let i = 0; i < argMeta.totalCount; i++) {
if (argMeta.isMethod && i == 0) {
varNameData[funcName].args[i] = "this";
} else {
varNameData[funcName].args[i] = `arg${i}`;
}
}
}
let argIndex = parseInt(oldName.substring(3));
if (argMeta.isMethod) {
argIndex++;
}
varNameData[funcName].args[argIndex] = newName;
} else {
if (!("vars" in varNameData[funcName])) {
varNameData[funcName].vars = {};
}
// NOTE - omitting check for duplicate names, just know what you're doing
varNameData[funcName].vars[oldName] = newName;
}
}

// Write out cast file change
const configDir = await getDecompilerConfigDirectory(document.uri);
if (configDir === undefined) {
return;
}
const filePath = join(configDir, "var_names.jsonc");

writeFileSync(filePath, stringify(varNameData, null, 2));
}
4 changes: 2 additions & 2 deletions src/languages/opengoal/opengoal-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ export interface ArgumentDefinition {
export function getArgumentsInSignature(
signature: string,
): ArgumentDefinition[] {
const isArgument =
const isSignature =
signature.includes("defun") ||
signature.includes("defmethod") ||
signature.includes("defbehavior");

if (!isArgument) {
if (!isSignature) {
return [];
}

Expand Down

0 comments on commit 3b6ace0

Please sign in to comment.