Skip to content

Commit

Permalink
suggest methods to hook inside of modify class (#5)
Browse files Browse the repository at this point in the history
somewhat wip
  • Loading branch information
matcool committed Jan 12, 2025
1 parent d5179b7 commit 54f39d1
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 5 deletions.
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,16 @@
"type": "integer",
"default": 250,
"description": "How many items to add in the Sprite Browser when you click 'Load More'"
},
"geode.modifyClassSuggestions.enable": {
"type": "boolean",
"default": true,
"description": "Enable suggestions for Modify classes"
},
"geode.modifyClassSuggestions.stripCocosNamespace": {
"type": "boolean",
"default": true,
"description": "Whether to automatically remove `cocos2d::` from included code"
}
}
},
Expand Down
15 changes: 10 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ import {
ExtensionContext,
commands,
languages,
workspace,
CompletionItem,
CompletionItemKind,
} from "vscode";
import { getOutputChannel, loadData, saveData, setupConfig } from "./config";
import * as geode from "./geode/geode";
import { browser } from "./browser/browser";
import { execSync } from "child_process";
import { getActiveProject, getOpenedProjects } from "./project/project";
import { env } from "vscode";
import { Uri } from "vscode";
import { getOpenedProjects } from "./project/project";
import { CCColor3bProvider, CCColor4bProvider } from "./project/color";
import { SpriteHoverPreview } from "./project/hover";
import { registerLinters } from "./project/lint";
import { ModifyClassMethodCompletion } from "./project/suggest";

export async function activate(context: ExtensionContext) {
const channel = window.createOutputChannel("Geode");
Expand Down Expand Up @@ -85,6 +84,12 @@ export async function activate(context: ExtensionContext) {
new SpriteHoverPreview(),
),
);
context.subscriptions.push(
languages.registerCompletionItemProvider(
{ language: "cpp" },
new ModifyClassMethodCompletion(),
),
);

registerLinters(context);

Expand Down
60 changes: 60 additions & 0 deletions src/project/CodegenData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { existsSync, readFileSync } from "node:fs";
import { None, Option } from "../utils/monads";
import { getActiveProject } from "./project";

export interface CodegenData {
classes: CodegenClass[];
}

export interface CodegenClass {
name: string;
functions: CodegenFunction[];
}

type CodegenBindingType = number | "link" | "inline" | null;

export interface CodegenFunction {
name: string;
args: CodegenArg[];
static: boolean;
const: boolean;
virtual: boolean;
bindings: {
win: CodegenBindingType;
imac: CodegenBindingType;
m1: CodegenBindingType;
ios: CodegenBindingType;
android32: CodegenBindingType;
android64: CodegenBindingType;
};
return: string;
docs?: string;
kind: string;
}

export interface CodegenArg {
type: string;
name: string;
}

let CACHED_CODEGEN_DATA: Option<CodegenData> = None;

export function getActiveCodegenData(): Option<CodegenData> {
if (CACHED_CODEGEN_DATA) {
return CACHED_CODEGEN_DATA;
}

const project = getActiveProject();
if (!project) {
return None;
}

const codegenDataPath = `${project.path}/build/bindings/bindings/Geode/CodegenData.json`;
if (!existsSync(codegenDataPath)) {
return None;
}

return (CACHED_CODEGEN_DATA = JSON.parse(
readFileSync(codegenDataPath).toString(),
));
}
125 changes: 125 additions & 0 deletions src/project/suggest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {
CancellationToken,
CompletionContext,
CompletionItem,
CompletionItemKind,
CompletionItemProvider,
CompletionItemTag,
Position,
SnippetString,
TextDocument,
} from "vscode";
import { getActiveCodegenData } from "./CodegenData";
import { getExtConfig } from "../config";

const MODIFY_CLASS_REGEX =
/^(?:class|struct)(?: \$modify\((?:\w+,)?\s?(\w+)\)|.+Modify\<(\w+)\>)/;

export class ModifyClassMethodCompletion implements CompletionItemProvider {
provideCompletionItems(
document: TextDocument,
position: Position,
_token: CancellationToken,
_context: CompletionContext,
) {
if (!getExtConfig().get<boolean>("modifyClassSuggestions.enable")) {
return;
}

// try to find what modify class we're on
// not a great system but works for now
let currentClass = null;
let lineN = position.line;
while (lineN >= 0) {
let line = document.lineAt(lineN).text;
if (line.startsWith("};")) {
break;
}
let match = line.match(MODIFY_CLASS_REGEX);
if (match) {
currentClass = match[1] ?? match[2];
break;
}
--lineN;
}
if (!currentClass) {
return;
}

const codegenData = getActiveCodegenData();
if (!codegenData) {
return;
}

let classInfo = null;
for (let c of codegenData.classes) {
if (c.name === currentClass) {
classInfo = c;
break;
}
}
if (!classInfo) {
return;
}

const stripCocos = getExtConfig().get<boolean>(
"modifyClassSuggestions.stripCocosNamespace",
);

let suggestions = [];
for (let func of classInfo.functions) {
const shortFuncDecl = `${func.name}(${func.args.map((a) => `${a.type} ${a.name}`).join(", ")})`;
const fullFuncDecl = `${func.static ? "static " : ""}${func.return} ${shortFuncDecl}`;
const origCall = `${currentClass}::${func.name}(${func.args.map((a) => a.name).join(", ")})`;

let origStatement;
if (func.return === "void") {
origStatement = `\${1}\n\t${origCall};`;
} else if (func.return === "bool") {
origStatement = `if (!${origCall}) return false;\n\t\${1}\n\treturn true;`;
} else {
origStatement = `${func.return} ret = ${origCall};\n\t\${1}\n\treturn ret;`;
}

const item = new CompletionItem(
shortFuncDecl,
CompletionItemKind.Method,
);

item.insertText = `${fullFuncDecl} {\n\t${origStatement}\n}`;
if (stripCocos) {
item.insertText = item.insertText.replace(/cocos2d::/g, "");
}
item.insertText = new SnippetString(item.insertText);

let rank = 0;
item.detail = fullFuncDecl;
item.documentation = "";

if (func.docs) {
item.documentation = func.docs.trim();
}
if (func.return === "TodoReturn") {
item.tags = [CompletionItemTag.Deprecated];
item.documentation = "Missing return type, do not use";
item.insertText = `// TODO: fix TodoReturn\n// ${fullFuncDecl}`;
rank = 1;
item.sortText = "1" + shortFuncDecl;
}
if (Object.values(func.bindings).includes("inline")) {
item.tags = [CompletionItemTag.Deprecated];
if (Object.values(func.bindings).every((b) => b === "inline")) {
item.documentation += "\nInline method, cannot hook";
item.insertText = `// ${shortFuncDecl} is inlined on all platforms, cannot hook`;
rank = 1;
} else {
item.documentation += "\nInline method on some platforms";
}
}

item.sortText = rank.toString() + shortFuncDecl;
suggestions.push(item);
}
return suggestions;
}
}

0 comments on commit 54f39d1

Please sign in to comment.