├── .gitignore ├── .gitattributes ├── assets └── unity-toolbox.png ├── src ├── tests │ ├── project │ │ ├── Assets │ │ │ └── Script.cs │ │ └── Packages │ │ │ └── manifest.json │ ├── suite │ │ ├── match.test.ts │ │ ├── assets.test.ts │ │ ├── method.test.ts │ │ ├── logger.test.ts │ │ ├── packages.test.ts │ │ ├── index.ts │ │ ├── unityMessages.test.ts │ │ ├── token.test.ts │ │ ├── file.test.ts │ │ ├── script.test.ts │ │ └── class.test.ts │ └── runTests.ts ├── helpers │ ├── match.ts │ ├── method.ts │ ├── logger.ts │ ├── token.ts │ ├── unityMessages.ts │ ├── packages.ts │ ├── script.ts │ ├── usages.ts │ ├── file.ts │ ├── class.ts │ └── assets.ts ├── features │ ├── unitySearch │ │ └── search.ts │ ├── snippets │ │ └── snippets.json │ ├── unityMessages │ │ ├── hover.ts │ │ ├── codeLens.ts │ │ ├── autocomplete.ts │ │ └── unityMessages.json │ ├── usages │ │ ├── scriptableObject.ts │ │ └── component.ts │ ├── fileTemplates │ │ ├── insert.ts │ │ ├── autocomplete.ts │ │ └── fileTemplates.json │ └── styleConfiguration │ │ └── style.ts └── extension.ts ├── .vscodeignore ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── .eslintrc.json ├── tsconfig.json ├── LICENSE ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /assets/unity-toolbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxsroka/unity-toolbox/HEAD/assets/unity-toolbox.png -------------------------------------------------------------------------------- /src/tests/project/Assets/Script.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | public class Script : MonoBehaviour 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /src/tests/project/Packages/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "com.test.vox2mesh": "1.0.0" 4 | } 5 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | src/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/tests/suite/match.test.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "vscode"; 2 | import Match from "../../helpers/match"; 3 | 4 | suite("Match", () => { 5 | test("new", () => { 6 | new Match(new Position(5, 10), ["SomeContent"]); 7 | }); 8 | }); -------------------------------------------------------------------------------- /src/helpers/match.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "vscode"; 2 | 3 | /** 4 | * A type that contains results of a search. 5 | */ 6 | export default class Match { 7 | readonly position: Position; 8 | readonly content: string[]; 9 | 10 | constructor(position: Position, content: string[]) { 11 | this.position = position; 12 | this.content = content; 13 | } 14 | } -------------------------------------------------------------------------------- /src/tests/suite/assets.test.ts: -------------------------------------------------------------------------------- 1 | import Assets from "../../helpers/assets"; 2 | import { extensions } from "vscode"; 3 | import * as assert from "assert"; 4 | 5 | suite("Assets", () => { 6 | test("initialize", async () => { 7 | const context = await extensions.getExtension("pixl.unity-toolbox")?.activate(); 8 | Assets.initialize(context); 9 | 10 | assert.deepStrictEqual(Assets.all[".cs"].size, 1); 11 | }); 12 | }); -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/helpers/method.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "vscode"; 2 | import Script from "./script"; 3 | import Token from "./token"; 4 | 5 | /** 6 | * An abstraction of a C# method. 7 | */ 8 | export default class Method { 9 | readonly script: Script; 10 | readonly type: Token; 11 | readonly name: Token; 12 | 13 | constructor(script: Script, type: Token, name: Token) { 14 | this.script = script; 15 | this.type = type; 16 | this.name = name; 17 | } 18 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /src/tests/suite/method.test.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "vscode"; 2 | import Method from "../../helpers/method"; 3 | import Script from "../../helpers/script"; 4 | import Token from "../../helpers/token"; 5 | 6 | suite("Method", () => { 7 | test("new", () => { 8 | const script = new Script("void Method() { }"); 9 | 10 | new Method(script, 11 | new Token(new Range(new Position(0, 0), new Position(0, 4)), "void"), 12 | new Token(new Range(new Position(0, 5), new Position(0, 11)), "Method") 13 | ); 14 | }); 15 | }) -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "out", 6 | "lib": [ 7 | "ES2020" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true, /* enable all strict type-checking options */ 12 | "resolveJsonModule": true, 13 | /* Additional Checks */ 14 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 15 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 16 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 17 | } 18 | } -------------------------------------------------------------------------------- /src/features/unitySearch/search.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, Uri, commands, window } from "vscode"; 2 | 3 | const COMMAND_ID = "unityToolbox.searchUnity"; 4 | 5 | export function initialize(context: ExtensionContext) { 6 | context.subscriptions.push(commands.registerCommand(COMMAND_ID, searchUnity)); 7 | } 8 | 9 | function searchUnity() { 10 | const editor = window.activeTextEditor; 11 | if (editor === undefined) return; 12 | 13 | const selection = editor.document.getText(editor.selection); 14 | commands.executeCommand("vscode.open", Uri.parse(`https://docs.unity3d.com/ScriptReference/30_search.html?q=${selection}`)) 15 | } -------------------------------------------------------------------------------- /src/tests/suite/logger.test.ts: -------------------------------------------------------------------------------- 1 | import Logger from "../../helpers/logger"; 2 | import * as vscode from "vscode"; 3 | 4 | suite("Logger", () => { 5 | test("initialize", async () => { 6 | const context = await vscode.extensions.getExtension("pixl.unity-toolbox")?.activate(); 7 | Logger.initialize(context); 8 | }); 9 | 10 | test("log", async () => { 11 | const context = await vscode.extensions.getExtension("pixl.unity-toolbox")?.activate(); 12 | Logger.initialize(context); 13 | 14 | Logger.info("info"); 15 | Logger.debug("debug"); 16 | Logger.warn("warn"); 17 | Logger.error("error"); 18 | Logger.trace("trace"); 19 | }); 20 | }); -------------------------------------------------------------------------------- /src/tests/suite/packages.test.ts: -------------------------------------------------------------------------------- 1 | import Packages from "../../helpers/packages"; 2 | import * as vscode from "vscode"; 3 | import * as assert from "assert"; 4 | 5 | suite("Packages", () => { 6 | test("initialize", async () => { 7 | const context = await vscode.extensions.getExtension("pixl.unity-toolbox")?.activate(); 8 | Packages.initialize(context); 9 | }); 10 | 11 | test("have", async () => { 12 | const context = await vscode.extensions.getExtension("pixl.unity-toolbox")?.activate(); 13 | Packages.initialize(context); 14 | 15 | assert.deepStrictEqual(Packages.have("com.test.vox2mesh"), true); 16 | assert.deepStrictEqual(Packages.have("com.test.examplePackage"), false); 17 | }); 18 | }); -------------------------------------------------------------------------------- /src/features/snippets/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "Debug.Log": { 3 | "prefix": [ 4 | "log" 5 | ], 6 | "description": "Debug.Log", 7 | "body": [ 8 | "Debug.Log(${TM_SELECTED_TEXT:$0});" 9 | ] 10 | }, 11 | "Debug.LogWarning": { 12 | "prefix": [ 13 | "logwarning" 14 | ], 15 | "description": "Debug.LogWarning", 16 | "body": [ 17 | "Debug.LogWarning(${TM_SELECTED_TEXT:$0});" 18 | ] 19 | }, 20 | "Debug.LogError": { 21 | "prefix": [ 22 | "logerror" 23 | ], 24 | "description": "Debug.LogError", 25 | "body": [ 26 | "Debug.LogError(${TM_SELECTED_TEXT:$0});" 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /src/helpers/logger.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, LogOutputChannel, window } from "vscode"; 2 | 3 | /** 4 | * Static helper for logging. 5 | */ 6 | export default class Logger { 7 | private static output: LogOutputChannel; 8 | 9 | static initialize(context: ExtensionContext) { 10 | this.output = window.createOutputChannel("Unity Toolbox", { log: true }); 11 | context.subscriptions.push(this.output); 12 | } 13 | 14 | static info(message: string) { this.output.info(message) }; 15 | static debug(message: string) { this.output.debug(message) }; 16 | static warn(message: string) { this.output.warn(message) }; 17 | static error(message: string) { this.output.error(message) }; 18 | static trace(message: string) { this.output.trace(message) }; 19 | } -------------------------------------------------------------------------------- /src/tests/runTests.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests', err); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); -------------------------------------------------------------------------------- /src/helpers/token.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "vscode" 2 | import Match from "./match"; 3 | 4 | /** 5 | * An abstraction of a token (keyword). 6 | */ 7 | export default class Token { 8 | readonly range: Range; 9 | readonly text: string; 10 | 11 | constructor(range: Range, text: string) { 12 | this.range = range; 13 | this.text = text; 14 | } 15 | 16 | static fromMatch(match: Match, index: number): Token { 17 | let start = new Position(match.position.line, match.position.character); 18 | let end = new Position(match.position.line, match.position.character); 19 | 20 | for (let i = 0; i < index; i++) { 21 | start = start.translate(0, match.content[i].length); 22 | } 23 | 24 | for (let i = 0; i <= index; i++) { 25 | end = end.translate(0, match.content[i].length); 26 | } 27 | 28 | const range = new Range(start, end); 29 | const content = match.content[index]; 30 | 31 | return new Token(range, content); 32 | } 33 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "${workspaceFolder}/src/tests/project", 26 | "--extensionDevelopmentPath=${workspaceFolder}", 27 | "--extensionTestsPath=${workspaceFolder}/out/tests/suite/index" 28 | ], 29 | "outFiles": [ 30 | "${workspaceFolder}/out/tests/**/*.js" 31 | ], 32 | "preLaunchTask": "${defaultBuildTask}" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 maxsroka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/tests/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } -------------------------------------------------------------------------------- /src/helpers/unityMessages.ts: -------------------------------------------------------------------------------- 1 | import * as messages from "../features/unityMessages/unityMessages.json"; 2 | import Method from "./method"; 3 | 4 | type UnityMessage = { 5 | name: string, 6 | coroutine: boolean, 7 | body: string[] 8 | } 9 | 10 | /** 11 | * Static helper for reading Unity Messages. 12 | */ 13 | export default class UnityMessages { 14 | static all = new Map(); 15 | 16 | static initialize() { 17 | for (const message of messages) { 18 | UnityMessages.all.set(message.name, message); 19 | } 20 | } 21 | 22 | static get(method: Method): UnityMessage | null { 23 | const message = this.all.get(method.name.text); 24 | if (message === undefined) return null; 25 | 26 | const validType = method.type.text === "void" || (message.coroutine && method.type.text === "IEnumerator"); 27 | if (!validType) return null; 28 | 29 | return message; 30 | } 31 | 32 | static have(method: Method): boolean { 33 | return this.get(method) !== null; 34 | } 35 | 36 | static getDoc(name: string): string { 37 | return `https://docs.unity3d.com/ScriptReference/MonoBehaviour.${name}`; 38 | } 39 | } -------------------------------------------------------------------------------- /src/features/unityMessages/hover.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, ExtensionContext, Hover, HoverProvider, Position, ProviderResult, TextDocument, languages } from "vscode"; 2 | import Script from "../../helpers/script"; 3 | import UnityMessages from "../../helpers/unityMessages"; 4 | 5 | export function initialize(context: ExtensionContext) { 6 | context.subscriptions.push(languages.registerHoverProvider( 7 | { language: "csharp" }, 8 | new UnityMessageHoverProvider() 9 | )); 10 | } 11 | 12 | class UnityMessageHoverProvider implements HoverProvider { 13 | provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult { 14 | const script = new Script(document.getText()); 15 | const classes = script.getClasses(); 16 | const c = classes.find(c => c.isComponent(document.fileName)); 17 | if (c === undefined) return; 18 | 19 | const methods = c.getMethods(); 20 | for (const method of methods) { 21 | const range = document.getWordRangeAtPosition(method.name.range.start); 22 | if (!range?.contains(position)) continue; 23 | 24 | const message = UnityMessages.get(method); 25 | if (message === null) continue; 26 | 27 | return new Hover(`[**${message.name}**](${UnityMessages.getDoc(method.name.text)}) is called by Unity.`, range); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/features/usages/scriptableObject.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, CodeLens, CodeLensProvider, Command, Event, ExtensionContext, ProviderResult, Range, TextDocument, TextLine, languages } from "vscode"; 2 | import Script from "../../helpers/script"; 3 | import Assets from "../../helpers/assets"; 4 | import Usages from "../../helpers/usages"; 5 | 6 | export function initialize(context: ExtensionContext) { 7 | context.subscriptions.push(languages.registerCodeLensProvider( 8 | { language: "csharp", scheme: "file" }, 9 | new ScriptableObjectUsageCodeLensProvider() 10 | )); 11 | } 12 | 13 | export class ScriptableObjectUsageCodeLensProvider implements CodeLensProvider { 14 | provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult { 15 | const items = []; 16 | 17 | const script = new Script(document.getText()); 18 | const classes = script.getClasses(); 19 | const scriptableObject = classes.find(c => c.isScriptableObject(document.fileName)); 20 | if (scriptableObject === undefined) return; 21 | 22 | const file = Assets.readFile(document.uri.fsPath + ".meta"); 23 | if (file === null) return; 24 | 25 | const guid = Assets.getGuid(file); 26 | if (guid === null) return; 27 | 28 | const range = scriptableObject.name.range; 29 | 30 | items.push(Usages.getCodeLens(guid, range, ".asset", "", { singular: "asset", plural: "assets" })); 31 | 32 | return items; 33 | } 34 | } -------------------------------------------------------------------------------- /src/helpers/packages.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, Uri, workspace } from "vscode"; 2 | import Logger from "./logger"; 3 | 4 | type PackagesJSON = { 5 | dependencies: {} 6 | } 7 | 8 | /** 9 | * Static helper for reading installed packages. 10 | */ 11 | export default class Packages { 12 | private static all = new Set(); 13 | 14 | static initialize(context: ExtensionContext) { 15 | const folders = workspace.workspaceFolders; 16 | if (folders === undefined || folders.length === 0) return; 17 | 18 | const path = Uri.joinPath(folders[0].uri, "Packages\\manifest.json"); 19 | this.update(path); 20 | 21 | const watcher = workspace.createFileSystemWatcher(path.fsPath); 22 | watcher.onDidChange(uri => this.update(uri)); 23 | context.subscriptions.push(watcher); 24 | } 25 | 26 | private static update(path: Uri) { 27 | workspace.fs.readFile(path).then(data => { 28 | const content = Buffer.from(data).toString("utf-8"); 29 | 30 | try { 31 | const json = JSON.parse(content) as PackagesJSON; 32 | 33 | this.all.clear(); 34 | for (const dependency in json.dependencies) { 35 | this.all.add(dependency); 36 | } 37 | } catch (error) { 38 | Logger.warn("couldn't update packages: " + error); 39 | } 40 | }); 41 | } 42 | 43 | static have(name: string): boolean { 44 | return this.all.has(name); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/features/usages/component.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, CodeLens, CodeLensProvider, Command, Event, ExtensionContext, ProviderResult, Range, TextDocument, TextLine, languages } from "vscode"; 2 | import Script from "../../helpers/script"; 3 | import Assets from "../../helpers/assets"; 4 | import Usages from "../../helpers/usages"; 5 | 6 | export function initialize(context: ExtensionContext) { 7 | context.subscriptions.push(languages.registerCodeLensProvider( 8 | { language: "csharp", scheme: "file" }, 9 | new ComponentUsageCodeLensProvider() 10 | )); 11 | } 12 | 13 | export class ComponentUsageCodeLensProvider implements CodeLensProvider { 14 | provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult { 15 | const items = []; 16 | 17 | const script = new Script(document.getText()); 18 | const classes = script.getClasses(); 19 | const component = classes.find(c => c.isComponent(document.fileName)); 20 | if (component === undefined) return; 21 | 22 | const file = Assets.readFile(document.uri.fsPath + ".meta"); 23 | if (file === null) return; 24 | 25 | const guid = Assets.getGuid(file); 26 | if (guid === null) return; 27 | 28 | const range = component.name.range; 29 | 30 | items.push(Usages.getCodeLens(guid, range, ".unity", "scene", { singular: "usage", plural: "usages" })); 31 | items.push(Usages.getCodeLens(guid, range, ".prefab", "prefab", { singular: "usage", plural: "usages" })); 32 | 33 | return items; 34 | } 35 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from "vscode"; 2 | import * as fileTemplateInsert from "./features/fileTemplates/insert"; 3 | import * as fileTemplateAutocomplete from "./features/fileTemplates/autocomplete"; 4 | import * as unitySearch from "./features/unitySearch/search"; 5 | import * as unityMessageAutocomplete from "./features/unityMessages/autocomplete"; 6 | import * as unityMessageHover from "./features/unityMessages/hover"; 7 | import * as unityMessageCodeLens from "./features/unityMessages/codeLens"; 8 | import UnityMessages from "./helpers/unityMessages"; 9 | import * as componentUsages from "./features/usages/component"; 10 | import * as scriptableObjectUsages from "./features/usages/scriptableObject"; 11 | import Assets from "./helpers/assets"; 12 | import Packages from "./helpers/packages"; 13 | import Logger from "./helpers/logger"; 14 | 15 | export function activate(context: ExtensionContext) { 16 | console.log('Unity Toolbox is now active!'); 17 | 18 | Logger.initialize(context); 19 | Packages.initialize(context); 20 | 21 | fileTemplateAutocomplete.initialize(context); 22 | fileTemplateInsert.initialize(context); 23 | 24 | unitySearch.initialize(context); 25 | 26 | UnityMessages.initialize(); 27 | unityMessageAutocomplete.initialize(context); 28 | unityMessageHover.initialize(context); 29 | unityMessageCodeLens.initialize(context); 30 | 31 | Assets.initialize(context); 32 | componentUsages.initialize(context); 33 | scriptableObjectUsages.initialize(context); 34 | 35 | // context is returned to be used in tests 36 | return context; 37 | } 38 | 39 | export function deactivate() { } -------------------------------------------------------------------------------- /src/tests/suite/unityMessages.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import UnityMessages from "../../helpers/unityMessages"; 3 | import Method from "../../helpers/method"; 4 | import Script from "../../helpers/script"; 5 | import Token from "../../helpers/token"; 6 | import { Position, Range } from "vscode"; 7 | 8 | suite("UnityMessages", () => { 9 | test("initialize", () => { 10 | UnityMessages.initialize(); 11 | 12 | assert.deepStrictEqual(UnityMessages.all.size > 0, true); 13 | }); 14 | 15 | test("get", () => { 16 | UnityMessages.initialize(); 17 | 18 | const script = new Script("void Start() { }"); 19 | const method = new Method(script, 20 | new Token(new Range(new Position(0, 0), new Position(0, 4)), "void"), 21 | new Token(new Range(new Position(0, 5), new Position(0, 10)), "Start") 22 | ); 23 | 24 | const message = UnityMessages.get(method); 25 | assert.notDeepStrictEqual(message, null); 26 | }); 27 | 28 | test("have", () => { 29 | UnityMessages.initialize(); 30 | 31 | const script = new Script("void Start() { }"); 32 | const method = new Method(script, 33 | new Token(new Range(new Position(0, 0), new Position(0, 4)), "void"), 34 | new Token(new Range(new Position(0, 5), new Position(0, 10)), "Start") 35 | ); 36 | 37 | const message = UnityMessages.have(method); 38 | assert.deepStrictEqual(message, true); 39 | }); 40 | 41 | test("getDoc", () => { 42 | UnityMessages.initialize(); 43 | 44 | const link = UnityMessages.getDoc("Update"); 45 | assert.deepStrictEqual(link, "https://docs.unity3d.com/ScriptReference/MonoBehaviour.Update"); 46 | }); 47 | }); -------------------------------------------------------------------------------- /src/features/unityMessages/codeLens.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, CodeLens, CodeLensProvider, Command, Event, ExtensionContext, Position, ProviderResult, Range, TextDocument, languages } from "vscode"; 2 | import Script from "../../helpers/script"; 3 | import UnityMessages from "../../helpers/unityMessages"; 4 | 5 | export function initialize(context: ExtensionContext) { 6 | context.subscriptions.push(languages.registerCodeLensProvider( 7 | { language: "csharp" }, 8 | new UnityMessageCodeLensProvider() 9 | )); 10 | } 11 | 12 | class UnityMessageCodeLensProvider implements CodeLensProvider { 13 | provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult { 14 | const script = new Script(document.getText()); 15 | const items = []; 16 | 17 | const classes = script.getClasses(); 18 | const c = classes.find(c => c.isComponent(document.fileName)); 19 | if (c === undefined) return; 20 | 21 | const methods = c.getMethods(); 22 | for (const method of methods) { 23 | const range = document.getWordRangeAtPosition(method.name.range.start); 24 | if (range === undefined) return; 25 | 26 | const message = UnityMessages.get(method); 27 | if (message === null) continue; 28 | 29 | const cmd: Command = { 30 | command: "vscode.open", 31 | arguments: [UnityMessages.getDoc(method.name.text)], 32 | title: "$(symbol-method) Unity Message", 33 | tooltip: "This method is called by Unity" 34 | }; 35 | 36 | const item = new CodeLens(range, cmd); 37 | items.push(item); 38 | } 39 | 40 | return items; 41 | } 42 | } -------------------------------------------------------------------------------- /src/features/unityMessages/autocomplete.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, CompletionContext, CompletionItem, CompletionItemLabel, CompletionItemKind, CompletionItemProvider, CompletionList, ExtensionContext, MarkdownString, Position, ProviderResult, SnippetString, TextDocument, languages } from "vscode"; 2 | import Script from "../../helpers/script"; 3 | import * as style from "../styleConfiguration/style" 4 | import UnityMessages from "../../helpers/unityMessages"; 5 | 6 | export function initialize(context: ExtensionContext) { 7 | context.subscriptions.push(languages.registerCompletionItemProvider( 8 | { language: "csharp" }, 9 | new UnityMessageCompletionItemProvider() 10 | )); 11 | } 12 | 13 | class UnityMessageCompletionItemProvider implements CompletionItemProvider { 14 | provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext): ProviderResult> { 15 | const script = new Script(document.getText()); 16 | const classes = script.getClasses(); 17 | 18 | const c = classes.find(c => script.getBracketPair(c.name.range.end)?.contains(position)); 19 | if (c === undefined || !c.isComponent(document.fileName)) return; 20 | const methods = c.getMethods(); 21 | 22 | const items = []; 23 | 24 | for (const [name, message] of UnityMessages.all) { 25 | if (methods.find(method => method.name.text === name && UnityMessages.have(method))) continue; 26 | 27 | const item = new CompletionItem({ 28 | label: message.name, 29 | description: message.body[0] 30 | }); 31 | item.kind = CompletionItemKind.Method; 32 | item.detail = `${message.name} (Unity Message)`; 33 | 34 | const snippet = style.styleMethod(message.body).join("\n"); 35 | item.documentation = new MarkdownString("```csharp\n" + snippet.replace("$0", "") + "\n```"); 36 | item.insertText = new SnippetString(snippet); 37 | 38 | items.push(item); 39 | } 40 | 41 | return items; 42 | } 43 | } -------------------------------------------------------------------------------- /src/tests/suite/token.test.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "vscode"; 2 | import Token from "../../helpers/token"; 3 | import Script from "../../helpers/script"; 4 | import * as assert from "assert"; 5 | 6 | suite("Token", () => { 7 | test("new", () => { 8 | new Token(new Range(new Position(0, 0), new Position(0, 10)), "Test"); 9 | }); 10 | 11 | suite("fromMatch", () => { 12 | test("found", () => { 13 | const script = new Script("class MyClass"); 14 | const match = script.searchSingle(/(class)( +)(.*)/g); 15 | if (match === null) assert.fail(); 16 | 17 | const keyword = Token.fromMatch(match, 0); 18 | const spaces = Token.fromMatch(match, 1); 19 | const name = Token.fromMatch(match, 2); 20 | 21 | assert.deepStrictEqual(keyword, new Token(new Range(new Position(0, 0), new Position(0, 5)), "class")); 22 | assert.deepStrictEqual(spaces, new Token(new Range(new Position(0, 5), new Position(0, 6)), " ")); 23 | assert.deepStrictEqual(name, new Token(new Range(new Position(0, 6), new Position(0, 13)), "MyClass")); 24 | }); 25 | 26 | test("not found", () => { 27 | const script = new Script("struct MyStruct"); 28 | const match = script.searchSingle(/(class)( +)(.*)/g); 29 | 30 | assert.deepStrictEqual(match, null); 31 | }); 32 | 33 | test("optional group missing", () => { 34 | const script = new Script("classMyClass"); 35 | const match = script.searchSingle(/(class)( ?)(.*)/g); 36 | if (match === null) assert.fail(); 37 | 38 | const keyword = Token.fromMatch(match, 0); 39 | const spaces = Token.fromMatch(match, 1); 40 | const name = Token.fromMatch(match, 2); 41 | 42 | assert.deepStrictEqual(keyword, new Token(new Range(new Position(0, 0), new Position(0, 5)), "class")); 43 | assert.deepStrictEqual(spaces, new Token(new Range(new Position(0, 5), new Position(0, 5)), "")); 44 | assert.deepStrictEqual(name, new Token(new Range(new Position(0, 5), new Position(0, 12)), "MyClass")); 45 | }); 46 | }); 47 | }); -------------------------------------------------------------------------------- /src/helpers/script.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "vscode"; 2 | import File from "./file"; 3 | import Class from "./class"; 4 | import Token from "./token"; 5 | 6 | /** 7 | * An abstraction of a C# file. 8 | */ 9 | export default class Script extends File { 10 | constructor(text: string) { 11 | super(text); 12 | } 13 | 14 | getClasses(): Class[] { 15 | return this.searchMultiple(/(class)( +)([\p{L}_@]{1}[\p{L}0-9_]*)([\p{L}0-9_<> ,]* *)?(: *[\p{L}0-9_]*)?/gu).map(match => { 16 | let parent = null; 17 | 18 | if (match.content[4] !== undefined) { 19 | parent = match.content[4].substring(1).trim(); 20 | 21 | if (parent === "") { 22 | parent = null; 23 | } 24 | } 25 | 26 | const result = new Class( 27 | this, 28 | Token.fromMatch(match, 2), 29 | parent 30 | ); 31 | 32 | return result; 33 | }); 34 | } 35 | 36 | getOpeningBracket(start: Position): Position | null { 37 | const match = this.searchSingle(/{/g, new Range(start, this.end)); 38 | if (match === null) return null 39 | 40 | return match.position; 41 | } 42 | 43 | getClosingBracket(start: Position): Position | null { 44 | const matches = this.searchMultiple(/({|})/g, new Range(start, this.end)); 45 | 46 | let count = 0; 47 | for (const match of matches) { 48 | if (match.content[0] === "{") { 49 | count += 1; 50 | } 51 | 52 | if (match.content[0] === "}") { 53 | count -= 1; 54 | 55 | if (count === 0) { 56 | return match.position; 57 | } 58 | } 59 | } 60 | 61 | return null; 62 | } 63 | 64 | getBracketPair(start: Position): Range | null { 65 | const opening = this.getOpeningBracket(start); 66 | if (opening === null) return null; 67 | const closing = this.getClosingBracket(opening); 68 | if (closing === null) return null; 69 | 70 | return new Range(opening, closing); 71 | } 72 | } -------------------------------------------------------------------------------- /src/helpers/usages.ts: -------------------------------------------------------------------------------- 1 | import { Range, CodeLens, Command } from "vscode"; 2 | import Assets from "./assets"; 3 | 4 | export default class Usages { 5 | static getCodeLens(guid: string, range: Range, extension: string, name: string, titleType: TitleType): CodeLens { 6 | const usages = this.getUsages(guid, extension); 7 | const command = this.getCommand(extension, name, usages, titleType); 8 | 9 | return new CodeLens(range, command); 10 | } 11 | 12 | private static getUsages(guid: string, extension: string): string[] { 13 | const usages = []; 14 | 15 | for (const path of Assets.all[extension]) { 16 | const references = Assets.references.get(path); 17 | if (references === undefined || !references.includes(guid)) continue; 18 | 19 | usages.push(path); 20 | } 21 | 22 | return usages; 23 | } 24 | 25 | private static getCommand(extension: string, name: string, usages: string[], titleType: TitleType): Command { 26 | return { 27 | command: "", 28 | title: this.formatTitle(name, usages.length, titleType), 29 | tooltip: this.formatTooltip(extension, usages) 30 | } 31 | } 32 | 33 | private static formatTitle(base: string, count: number, type: TitleType): string { 34 | let title = ""; 35 | 36 | if (count === 0) { 37 | title = `0 ${base} ${type.plural}`; 38 | } else if (count === 1) { 39 | title = `1 ${base} ${type.singular}`; 40 | } else if (count < 10) { 41 | title = `${count} ${base} ${type.plural}`; 42 | } else { 43 | title = `9+ ${base} ${type.plural}`; 44 | } 45 | 46 | return title; 47 | } 48 | 49 | private static formatTooltip(extension: string, usages: string[]): string { 50 | let tooltip = ""; 51 | 52 | for (const usage of usages) { 53 | tooltip += usage 54 | .slice(0, usage.length - extension.length) 55 | .slice(usage.indexOf("Assets\\")) 56 | + "\n"; 57 | } 58 | 59 | return tooltip; 60 | } 61 | } 62 | 63 | type TitleType = { 64 | singular: string, 65 | plural: string 66 | } -------------------------------------------------------------------------------- /src/features/fileTemplates/insert.ts: -------------------------------------------------------------------------------- 1 | import { QuickPickOptions, SnippetString, window, workspace, commands, ExtensionContext, QuickPickItem, QuickPickItemKind } from "vscode"; 2 | import * as templates from "./fileTemplates.json"; 3 | import * as style from "../styleConfiguration/style" 4 | import Packages from "../../helpers/packages"; 5 | 6 | const COMMAND_ID: string = "unityToolbox.insertFileTemplate"; 7 | const QUICK_PICK_OPTIONS: QuickPickOptions = { 8 | placeHolder: "Select a file template" 9 | }; 10 | const UNITY_TOOLBOX_FILE_TEMPLATES_CONFIG = "unityToolbox.fileTemplates"; 11 | const AUTOMATIC_MENU_CONFIG = "automaticMenu"; 12 | 13 | export function initialize(context: ExtensionContext) { 14 | context.subscriptions.push(commands.registerCommand(COMMAND_ID, insertFileTemplate)); 15 | context.subscriptions.push(workspace.onDidOpenTextDocument((document) => { 16 | if (!workspace.getConfiguration(UNITY_TOOLBOX_FILE_TEMPLATES_CONFIG).get(AUTOMATIC_MENU_CONFIG)) return; 17 | 18 | if (document.languageId === "csharp" && document.getText() === "") { 19 | commands.executeCommand(COMMAND_ID); 20 | } 21 | })); 22 | } 23 | 24 | function insertFileTemplate() { 25 | const items: QuickPickItem[] = []; 26 | let category = ""; 27 | 28 | for (const template of templates) { 29 | if (template.package !== undefined && !Packages.have(template.package)) continue; 30 | 31 | if (template.category !== category) { 32 | category = template.category; 33 | 34 | items.push({ 35 | label: template.category, 36 | kind: QuickPickItemKind.Separator 37 | }); 38 | } 39 | 40 | items.push({ 41 | label: template.name 42 | }); 43 | } 44 | 45 | window.showQuickPick(items, QUICK_PICK_OPTIONS).then(pick => { 46 | if (pick === undefined) return; 47 | 48 | const editor = window.activeTextEditor 49 | if (editor === undefined) return; 50 | 51 | const template = templates.find(template => template.name === pick.label) 52 | if (template === undefined) return; 53 | 54 | const snippet = new SnippetString(style.styleClass(template.body).join("\n")); 55 | editor.insertSnippet(snippet); 56 | }); 57 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Unity Toolbox

4 |

Awesome tools for VSCode & Unity

5 |
6 |

About

7 | Unity Toolbox is a free and open source collection of game development tools for Unity. 8 |

9 | All features are listed below. 10 |

11 | Happy coding! ❤️ 12 |

Features

13 |

Context-Aware Snippets for Unity Messages

14 | Context-Aware Snippets for Unity Messages 15 |

CodeLens for Unity Messages

16 | CodeLens for Unity Messages 17 |

Documentation On Hover for Unity Messages

18 | Documentation On Hover for Unity Messages 19 |

Coding Style Customization

20 | Coding Style Customization 21 |

Quick API Search

22 | Quick API Search 23 |

MonoBehaviour Usages

24 | MonoBehaviour Usages 25 |

ScriptableObject Usages

26 | ScriptableObject Usages 27 |

File Templates Menu

28 | File Templates Menu 29 |

Basic File Templates

30 | Basic File Templates 31 |

DOTS File Templates

32 | DOTS File Templates 33 |

Editor File Templates

34 | Editor File Templates 35 |

File Associations

36 | File Associations 37 |

Note

38 | Unity Toolbox IS NOT made by Unity Technologies.
-------------------------------------------------------------------------------- /src/features/styleConfiguration/style.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from "vscode"; 2 | 3 | const UNITY_TOOLBOX_CONFIG = "unityToolbox"; 4 | const BRACKETS_STYLE_CONFIG = "bracketsStyle"; 5 | const PRIVATE_ACCESS_MODIFIER_STYLE_CONFIG = "privateAccessModifier"; 6 | 7 | enum BracketsStyle { 8 | NewLine = "new line", 9 | SameLineWithSpace = "same line, with space", 10 | SameLineWithoutSpace = "same line, without space", 11 | } 12 | 13 | export function styleClass(snippet: string[]): string[] { 14 | return styleBrackets(snippet); 15 | } 16 | 17 | export function styleMethod(snippet: string[]): string[] { 18 | let result = [...snippet]; 19 | stylePrivateAccessModifier(result); 20 | result = styleBrackets(result); 21 | 22 | return result; 23 | } 24 | 25 | /** 26 | * Applies the chosen bracket style to all opening brackets in a snippet. 27 | * The source snippet muse use the default style (opening bracket on new line). 28 | * @param snippet the source snippet. 29 | * @returns the source snippet with style applied. 30 | */ 31 | function styleBrackets(snippet: string[]): string[] { 32 | const style = workspace.getConfiguration(UNITY_TOOLBOX_CONFIG).get(BRACKETS_STYLE_CONFIG); 33 | if (style === undefined) return []; 34 | if (style === BracketsStyle.NewLine) return snippet; 35 | 36 | let result = []; 37 | 38 | let skipNextLine = false; 39 | for (let line = 0; line < snippet.length; line++) { 40 | const definition = line + 1 < snippet.length && snippet[line + 1].trim() === "{"; 41 | 42 | if (definition) { 43 | skipNextLine = true; 44 | 45 | const bracket = style === BracketsStyle.SameLineWithSpace ? " {" : "{"; 46 | result.push(snippet[line] + bracket); 47 | } else if (skipNextLine) { 48 | skipNextLine = false; 49 | continue; 50 | } else { 51 | result.push(snippet[line]); 52 | } 53 | } 54 | 55 | return result; 56 | } 57 | 58 | /** 59 | * Applies the chosen private access modifier style to a snippet. 60 | * The source snippet muse use the default modifier style (no modifier) and the default bracket style (new line). 61 | * This modifies the provided array instead of returning a new one. 62 | * @param snippet the source snippet. 63 | */ 64 | function stylePrivateAccessModifier(snippet: string[]) { 65 | const style = workspace.getConfiguration(UNITY_TOOLBOX_CONFIG).get(PRIVATE_ACCESS_MODIFIER_STYLE_CONFIG); 66 | if (style === undefined || !style) return; 67 | 68 | const bracket = snippet.findIndex(line => line.trim() === "{"); 69 | const definition = bracket === 0 ? 0 : bracket - 1; 70 | snippet[definition] = "private " + snippet[definition]; 71 | } -------------------------------------------------------------------------------- /src/helpers/file.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "vscode"; 2 | import Match from "./match"; 3 | 4 | /** 5 | * An abstraction of a file (.cs, .meta, .shader, ...). 6 | */ 7 | export default class File { 8 | readonly text: string; 9 | readonly lines: string[]; 10 | readonly start: Position = new Position(0, 0); 11 | readonly end: Position; 12 | 13 | constructor(text: string) { 14 | this.text = text; 15 | this.lines = text.split("\n"); 16 | this.end = new Position(this.lines.length - 1, this.lines[this.lines.length - 1].length) 17 | } 18 | 19 | /** 20 | * Performs a regular expression search in a given range (whole file by default) and returns the first match. 21 | * @param single returns only the first match if true. 22 | * @param expression what to search. 23 | * @param range where to search. 24 | * @returns an array of matches. 25 | */ 26 | private search( 27 | single: boolean, 28 | expression: RegExp, 29 | range = new Range(this.start, this.end) 30 | ): Match[] { 31 | if (!expression.global) { 32 | throw new Error("search() requires a global regular expression."); 33 | } 34 | 35 | const results: Match[] = []; 36 | 37 | for (let line = range.start.line; line <= range.end.line; line++) { 38 | while (true) { 39 | const match = expression.exec(this.lines[line]); 40 | if (match === null) break; 41 | 42 | const position = new Position(line, match.index); 43 | if (!range.contains(position)) continue; 44 | 45 | results.push(new Match(position, match.slice(1))); 46 | 47 | if (single) { 48 | return results; 49 | } 50 | } 51 | } 52 | 53 | return results; 54 | } 55 | 56 | /** 57 | * Performs a regular expression search in a given range (whole file by default) and returns the first match. 58 | * @param expression what to search. 59 | * @param range where to search. 60 | * @returns a match if found, otherwise null. 61 | */ 62 | searchSingle( 63 | expression: RegExp, 64 | range = new Range(this.start, this.end) 65 | ): Match | null { 66 | const matches = this.search(true, expression, range); 67 | return matches.length === 0 ? null : matches[0]; 68 | } 69 | 70 | /** 71 | * Performs a regular expression search in a given range (whole file by default) and returns all matches. 72 | * @param expression what to search. 73 | * @param range where to search. 74 | * @returns an array of matches. 75 | */ 76 | searchMultiple( 77 | expression: RegExp, 78 | range = new Range(this.start, this.end) 79 | ): Match[] { 80 | return this.search(false, expression, range); 81 | } 82 | } -------------------------------------------------------------------------------- /src/features/fileTemplates/autocomplete.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, CompletionContext, CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, ExtensionContext, MarkdownString, Position, ProviderResult, SnippetString, TextDocument, TextEditorOptions, languages, window, workspace } from "vscode"; 2 | import * as templates from "./fileTemplates.json"; 3 | import * as style from "../styleConfiguration/style" 4 | import * as path from "path"; 5 | import Packages from "../../helpers/packages"; 6 | import Script from "../../helpers/script"; 7 | 8 | export function initialize(context: ExtensionContext) { 9 | context.subscriptions.push(languages.registerCompletionItemProvider( 10 | { language: "csharp" }, 11 | new FileTemplateCompletionItemProvider() 12 | )); 13 | } 14 | 15 | class FileTemplateCompletionItemProvider implements CompletionItemProvider { 16 | provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext): ProviderResult> { 17 | const items = []; 18 | 19 | const script = new Script(document.getText()); 20 | const classes = script.getClasses(); 21 | const insideClass = classes.some(c => script.getBracketPair(c.name.range.end)?.contains(position)); 22 | if (insideClass) return; 23 | 24 | for (const template of templates) { 25 | if (template.package !== undefined && !Packages.have(template.package)) continue; 26 | 27 | for (const prefix of template.prefix) { 28 | const item = new CompletionItem({ 29 | label: prefix, 30 | description: `${template.category} File Template`, 31 | }); 32 | item.kind = CompletionItemKind.Snippet; 33 | item.detail = `${template.name} (${template.category} File Template)`; 34 | 35 | const snippet = style.styleClass(template.body).join("\n"); 36 | item.documentation = new MarkdownString( 37 | "```csharp\n" + 38 | getWithVariables(snippet, document) 39 | + "\n```" 40 | ); 41 | item.insertText = new SnippetString(snippet); 42 | 43 | items.push(item); 44 | } 45 | } 46 | 47 | return items; 48 | } 49 | } 50 | 51 | function getWithVariables(snippet: string, document: TextDocument): string { 52 | return snippet 53 | .replace(/\$[0-9]/g, "") 54 | .replace(/(\${[0-9]:)?throw new System\.NotImplementedException\(\);(})?/g, "throw new System.NotImplementedException();") 55 | .replace(/(\${[0-9]:)?\$WORKSPACE_NAME(})?/g, `${workspace.name}`) 56 | .replace(/(\${[0-9]:)?\$TM_FILENAME_BASE(})?/g, path.parse(document.fileName).name) 57 | .replace(/\${1\|struct,class\|}/g, "struct"); 58 | } -------------------------------------------------------------------------------- /src/helpers/class.ts: -------------------------------------------------------------------------------- 1 | import Script from "./script"; 2 | import Method from "./method"; 3 | import Token from "./token"; 4 | import Assets from "./assets"; 5 | import * as path from "path"; 6 | 7 | /** 8 | * An abstraction of a C# class. 9 | */ 10 | export default class Class { 11 | readonly script: Script; 12 | readonly name: Token; 13 | readonly parent: string | null; 14 | 15 | constructor(script: Script, name: Token, parent: string | null) { 16 | this.script = script; 17 | this.name = name; 18 | this.parent = parent; 19 | } 20 | 21 | /** 22 | * Determines if the class is a component (derives from MonoBehaviour). 23 | * @param filePath path to the file the class is in. 24 | * @returns whether the class is a component. 25 | */ 26 | isComponent(filePath: string): boolean { 27 | if (this.parent === null) return false; 28 | if (!this.isParentEqualFileName(filePath)) return false; 29 | 30 | return this.doesDeriveFrom(this.parent, "MonoBehaviour"); 31 | } 32 | 33 | /** 34 | * Determines if the class derives from ScriptableObject. 35 | * @param filePath path to the file the class is in. 36 | * @returns whether the class is a ScriptableObject. 37 | */ 38 | isScriptableObject(filePath: string): boolean { 39 | if (this.parent === null) return false; 40 | if (!this.isParentEqualFileName(filePath)) return false; 41 | 42 | return this.doesDeriveFrom(this.parent, "ScriptableObject"); 43 | } 44 | 45 | isParentEqualFileName(filePath: string): boolean { 46 | return path.parse(filePath).name === this.name.text; 47 | } 48 | 49 | /** 50 | * Recursively searches for parents to determine if the class derives from another class. 51 | * Note: 52 | * Only the first parent of each class is used. 53 | * That's because if a class derives from another class, it's always the first parent in C#. 54 | * Deriving from multiple classes is not allowed in C#. 55 | * @param parent name of a class to start searching from. 56 | * @param from name of the class to search for. 57 | * @returns name of the last parent or null. 58 | */ 59 | private doesDeriveFrom(parent: string, from: string): boolean { 60 | if (parent === from) return true; 61 | 62 | const grandparents = Assets.parents.get(parent); 63 | if (grandparents === undefined || grandparents.length === 0) return false; 64 | 65 | const grandparent = grandparents[0]; 66 | return this.doesDeriveFrom(grandparent, from); 67 | } 68 | 69 | getMethods(): Method[] { 70 | const range = this.script.getBracketPair(this.name.range.end); 71 | if (range === null) return []; 72 | return this.script 73 | .searchMultiple(/([\p{L}_@]{1}[\p{L}0-9_]*)( +)([\p{L}_@]{1}[\p{L}0-9_]*)\(.*?\)/gu, range) 74 | .map(match => new Method( 75 | this.script, 76 | Token.fromMatch(match, 0), 77 | Token.fromMatch(match, 2) 78 | )); 79 | } 80 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unity-toolbox", 3 | "displayName": "Unity Toolbox", 4 | "description": "Awesome tools for VSCode & Unity", 5 | "version": "100.0.4", 6 | "publisher": "pixl", 7 | "icon": "./assets/unity-toolbox.png", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/maxsroka/unity-toolbox.git" 11 | }, 12 | "categories": [ 13 | "Snippets", 14 | "Programming Languages" 15 | ], 16 | "keywords": [ 17 | "unity", 18 | "unity3d", 19 | "c#", 20 | "csharp", 21 | "dotnet", 22 | ".net", 23 | "gamedev" 24 | ], 25 | "engines": { 26 | "vscode": "^1.81.0" 27 | }, 28 | "activationEvents": [ 29 | "onLanguage:csharp" 30 | ], 31 | "main": "./out/extension.js", 32 | "contributes": { 33 | "commands": [ 34 | { 35 | "command": "unityToolbox.insertFileTemplate", 36 | "title": "Insert File Template", 37 | "category": "Unity Toolbox" 38 | }, 39 | { 40 | "command": "unityToolbox.searchUnity", 41 | "title": "Search Unity...", 42 | "category": "Unity Toolbox", 43 | "enablement": "editorHasSelection == true" 44 | } 45 | ], 46 | "menus": { 47 | "editor/context": [ 48 | { 49 | "command": "unityToolbox.searchUnity", 50 | "when": "editorHasSelection == true", 51 | "group": "0_UnityToolbox" 52 | } 53 | ] 54 | }, 55 | "configuration": { 56 | "title": "Unity Toolbox", 57 | "properties": { 58 | "unityToolbox.bracketsStyle": { 59 | "enum": [ 60 | "new line", 61 | "same line, with space", 62 | "same line, without space" 63 | ], 64 | "default": "new line", 65 | "description": "Controls the position of the opening curly bracket in snippets." 66 | }, 67 | "unityToolbox.privateAccessModifier": { 68 | "type": "boolean", 69 | "default": false, 70 | "description": "Controls whether the private access modifier is present in snippets." 71 | }, 72 | "unityToolbox.fileTemplates.automaticMenu": { 73 | "type": "boolean", 74 | "default": true, 75 | "description": "Controls whether the file templates menu is displayed automatically after opening an empty C# file." 76 | } 77 | } 78 | }, 79 | "snippets": [ 80 | { 81 | "language": "csharp", 82 | "path": "./src/features/snippets/snippets.json" 83 | } 84 | ], 85 | "languages": [ 86 | { 87 | "id": "json", 88 | "aliases": [ 89 | "Unity Asset (JSON)" 90 | ], 91 | "extensions": [ 92 | ".asmdef", 93 | ".asmref", 94 | ".index" 95 | ] 96 | }, 97 | { 98 | "id": "yaml", 99 | "aliases": [ 100 | "Unity Asset (YAML)" 101 | ], 102 | "extensions": [ 103 | ".meta", 104 | ".asset", 105 | ".unity", 106 | ".scenetemplate", 107 | ".prefab", 108 | ".anim", 109 | ".controller", 110 | ".overrideController", 111 | ".brush", 112 | ".guiskin", 113 | ".physicMaterial", 114 | ".physicsMaterial2D", 115 | ".signal", 116 | ".playable", 117 | ".terrainlayer", 118 | ".fontsettings", 119 | ".mask", 120 | ".lighting", 121 | ".giparams", 122 | ".renderTexture", 123 | ".mixer" 124 | ] 125 | }, 126 | { 127 | "id": "xml", 128 | "aliases": [ 129 | "UXML" 130 | ], 131 | "extensions": [ 132 | ".uxml" 133 | ] 134 | }, 135 | { 136 | "id": "css", 137 | "aliases": [ 138 | "USS" 139 | ], 140 | "extensions": [ 141 | ".uss", 142 | ".tss" 143 | ] 144 | } 145 | ] 146 | }, 147 | "scripts": { 148 | "vscode:prepublish": "npm run compile", 149 | "compile": "tsc -p ./", 150 | "watch": "tsc -watch -p ./", 151 | "pretest": "npm run compile && npm run lint", 152 | "lint": "eslint src --ext ts", 153 | "test": "node ./out/tests/runTests.js" 154 | }, 155 | "devDependencies": { 156 | "@types/vscode": "^1.81.0", 157 | "@types/glob": "^8.1.0", 158 | "@types/mocha": "^10.0.1", 159 | "@types/node": "20.2.5", 160 | "@typescript-eslint/eslint-plugin": "^5.59.8", 161 | "@typescript-eslint/parser": "^5.59.8", 162 | "eslint": "^8.41.0", 163 | "glob": "^8.1.0", 164 | "mocha": "^10.2.0", 165 | "typescript": "^5.1.3", 166 | "@vscode/test-electron": "^2.3.2" 167 | } 168 | } -------------------------------------------------------------------------------- /src/tests/suite/file.test.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "vscode"; 2 | import File from "../../helpers/file"; 3 | import * as assert from "assert"; 4 | import Match from "../../helpers/match"; 5 | 6 | suite("File", () => { 7 | test("new", () => { 8 | new File(""); 9 | new File("content"); 10 | }); 11 | 12 | test("text", () => { 13 | const file = new File("text"); 14 | 15 | assert.deepStrictEqual(file.text, "text"); 16 | }); 17 | 18 | test("lines", () => { 19 | const singleLine = new File("single line"); 20 | const threeLines = new File("single line\nsecond line\nthird line"); 21 | 22 | assert.deepStrictEqual(singleLine.lines.length, 1); 23 | assert.deepStrictEqual(threeLines.lines.length, 3); 24 | }); 25 | 26 | test("start", () => { 27 | const file = new File("class MyClass"); 28 | 29 | assert.deepStrictEqual(file.start, new Position(0, 0)); 30 | }); 31 | 32 | test("end", () => { 33 | const file = new File(`class MyClass 34 | { 35 | 36 | }`); 37 | 38 | assert.deepStrictEqual(file.end, new Position(3, 1)); 39 | }); 40 | 41 | suite("searchSingle", () => { 42 | test("found", () => { 43 | const file = new File(` 44 | using UnityEngine; 45 | using UnityEngine.UI; 46 | `); 47 | const match = file.searchSingle(/UnityEngine/g); 48 | 49 | assert.deepStrictEqual(match, 50 | new Match( 51 | new Position(1, 6), 52 | [] 53 | ) 54 | ); 55 | }); 56 | 57 | test("not found", () => { 58 | const file = new File(` 59 | using UnityEngine; 60 | using UnityEngine.UI; 61 | `); 62 | const match = file.searchSingle(/System/g); 63 | 64 | assert.deepStrictEqual(match, null); 65 | }); 66 | 67 | test("requires global", () => { 68 | const file = new File(""); 69 | 70 | assert.throws(() => file.searchSingle(/class/)); 71 | }); 72 | 73 | test("content group", () => { 74 | const file = new File(` 75 | using UnityEngine; 76 | using UnityEngine.UI; 77 | `); 78 | const match = file.searchSingle(/using +(.*)/g); 79 | 80 | assert.deepStrictEqual(match, 81 | new Match( 82 | new Position(1, 0), 83 | ["UnityEngine;"] 84 | ) 85 | ); 86 | }); 87 | 88 | test("custom range", () => { 89 | const file = new File(` 90 | using UnityEngine; 91 | using UnityEngine.UI; 92 | `); 93 | const match = file.searchSingle(/using +(.*)/g, new Range(new Position(1, 10), file.end)); 94 | 95 | assert.deepStrictEqual(match, 96 | new Match( 97 | new Position(2, 0), 98 | ["UnityEngine.UI;"] 99 | ) 100 | ); 101 | }); 102 | }); 103 | 104 | suite("searchMultiple", () => { 105 | test("found", () => { 106 | const file = new File(` 107 | using UnityEngine; 108 | using UnityEngine.UI; 109 | `); 110 | const matches = file.searchMultiple(/UnityEngine/g); 111 | 112 | assert.deepStrictEqual(matches, [ 113 | new Match(new Position(1, 6), []), 114 | new Match(new Position(2, 6), []) 115 | ]); 116 | }); 117 | 118 | test("not found", () => { 119 | const file = new File(` 120 | using UnityEngine; 121 | using UnityEngine.UI; 122 | `); 123 | const matches = file.searchMultiple(/System/g); 124 | 125 | assert.deepStrictEqual(matches, []); 126 | }); 127 | 128 | test("requires global", () => { 129 | const file = new File(""); 130 | 131 | assert.throws(() => file.searchMultiple(/class/)); 132 | }); 133 | 134 | test("content groups", () => { 135 | const file = new File(` 136 | using UnityEngine; 137 | using UnityEngine.UI; 138 | `); 139 | const match = file.searchMultiple(/using +(.*)/g); 140 | 141 | assert.deepStrictEqual(match, [ 142 | new Match(new Position(1, 0), ["UnityEngine;"]), 143 | new Match(new Position(2, 0), ["UnityEngine.UI;"]) 144 | ]); 145 | }); 146 | 147 | test("custom range", () => { 148 | const file = new File(` 149 | using UnityEngine; 150 | using UnityEngine.UI; 151 | using UnityEngine.Events; 152 | `); 153 | const matches = file.searchMultiple(/using +(.*)/g, new Range(new Position(1, 10), new Position(2, 10))); 154 | 155 | assert.deepStrictEqual(matches, [ 156 | new Match(new Position(2, 0), ["UnityEngine.UI;"]) 157 | ]); 158 | }); 159 | }); 160 | }); -------------------------------------------------------------------------------- /src/helpers/assets.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, window, workspace } from "vscode"; 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import Logger from "./logger"; 5 | import Script from "./script"; 6 | 7 | const ALL_EXTENSIONS = [ 8 | ".prefab", 9 | ".unity", 10 | ".cs", 11 | ".asset" 12 | ] 13 | 14 | const REFERENCES_EXTENSIONS = [ 15 | ".prefab", 16 | ".unity", 17 | ".asset" 18 | ] 19 | 20 | const PARENTS_EXTENSION = ".cs"; 21 | 22 | type AssetSet = { 23 | [index: string]: Set 24 | } 25 | 26 | /** 27 | * Static helper for reading assets. 28 | */ 29 | export default class Assets { 30 | static all: AssetSet = {}; 31 | static references = new Map(); 32 | static parents = new Map(); 33 | 34 | static initialize(context: ExtensionContext) { 35 | // data 36 | const workspaceFolders = workspace.workspaceFolders; 37 | if (workspaceFolders === undefined || workspaceFolders.length === 0) return; 38 | 39 | const workspaceFolder = workspaceFolders[0]; 40 | const assetsFolder = path.join(workspaceFolder.uri.fsPath, "Assets"); 41 | 42 | for (const extension of ALL_EXTENSIONS) { 43 | this.all[extension] = new Set(); 44 | } 45 | 46 | const assets = this.getFilesInDirectory(assetsFolder); 47 | for (const asset of assets) { 48 | this.tryAdd(asset); 49 | } 50 | 51 | // watcher 52 | const watcher = workspace.createFileSystemWatcher("**/*.{unity,prefab,cs,asset}"); 53 | 54 | watcher.onDidCreate(uri => this.tryAdd(uri.fsPath)); 55 | watcher.onDidChange(uri => this.tryAdd(uri.fsPath)); 56 | watcher.onDidDelete(uri => this.tryDelete(uri.fsPath)); 57 | 58 | context.subscriptions.push(watcher); 59 | } 60 | 61 | private static tryAdd(asset: string) { 62 | const extension = path.extname(asset); 63 | if (!ALL_EXTENSIONS.includes(extension)) return null; 64 | 65 | const file = this.readFile(asset); 66 | if (file === null) return; 67 | 68 | this.all[extension].add(asset); 69 | 70 | if (REFERENCES_EXTENSIONS.includes(extension)) { 71 | this.references.set(asset, this.getGuids(file)); 72 | } 73 | 74 | if (extension === PARENTS_EXTENSION) { 75 | this.parents.set(path.parse(asset).name, this.getParents(file)); 76 | } 77 | } 78 | 79 | private static tryDelete(asset: string) { 80 | const extension = path.extname(asset); 81 | if (!ALL_EXTENSIONS.includes(extension)) return null; 82 | 83 | this.all[extension].delete(asset); 84 | 85 | if (REFERENCES_EXTENSIONS.includes(extension)) { 86 | this.references.delete(asset); 87 | } 88 | 89 | if (extension === PARENTS_EXTENSION) { 90 | this.parents.delete(path.parse(asset).name); 91 | } 92 | } 93 | 94 | /** 95 | * Reads a file and returns its content. 96 | * @param path path to the file. 97 | * @returns the content or null. 98 | */ 99 | static readFile(path: string): string | null { 100 | let file; 101 | 102 | try { 103 | file = fs.readFileSync(path, "utf-8"); 104 | } catch (error) { 105 | Logger.warn("couldn't read file: " + error); 106 | return null; 107 | } 108 | 109 | return file; 110 | } 111 | 112 | /** 113 | * Searches a file for all parents of all classes in that file. 114 | * @param file content of the file. 115 | * @returns names of all parents. 116 | */ 117 | private static getParents(file: string): string[] { 118 | const parents = []; 119 | 120 | const script = new Script(file); 121 | const classes = script.getClasses(); 122 | for (const c of classes) { 123 | if (c.parent === null) continue; 124 | 125 | parents.push(c.parent); 126 | } 127 | 128 | return parents; 129 | } 130 | 131 | /** 132 | * Searches a file for a guid. 133 | * @param file content of the file. 134 | * @returns guid if found, null if not. 135 | */ 136 | static getGuid(file: string): string | null { 137 | const match = file.match(/guid: ([a-zA-Z0-9]+)/); 138 | if (match === null) return null; 139 | 140 | return match[1]; 141 | } 142 | 143 | /** 144 | * Searches a file for all guids. 145 | * @param file content of the file. 146 | * @returns array of guids if found any, empty array if not. 147 | */ 148 | static getGuids(file: string): string[] { 149 | const guids: string[] = []; 150 | 151 | const matches = file.matchAll(/guid: ([a-zA-Z0-9]+)/g); 152 | for (const match of matches) { 153 | guids.push(match[1]); 154 | } 155 | 156 | return guids; 157 | } 158 | 159 | /** 160 | * Searches a directory recursively and returns file paths of all found files. 161 | * @param directory path to a directory. 162 | * @returns array of file paths. 163 | */ 164 | private static getFilesInDirectory(directory: string): string[] { 165 | const files: string[] = []; 166 | 167 | try { 168 | fs.readdirSync(directory).forEach(file => { 169 | const child = path.join(directory, file); 170 | 171 | if (fs.lstatSync(child).isDirectory()) { 172 | files.push(...this.getFilesInDirectory(child)); 173 | } else { 174 | files.push(child); 175 | } 176 | }); 177 | } catch (error) { 178 | Logger.warn("couldn't get files in directory: " + error); 179 | return []; 180 | } 181 | 182 | return files; 183 | } 184 | } -------------------------------------------------------------------------------- /src/features/fileTemplates/fileTemplates.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "MonoBehaviour", 4 | "category": "Basic", 5 | "prefix": [ 6 | "monobehaviour" 7 | ], 8 | "body": [ 9 | "using UnityEngine;", 10 | "", 11 | "public class $TM_FILENAME_BASE : MonoBehaviour", 12 | "{", 13 | "\t$0", 14 | "}" 15 | ] 16 | }, 17 | { 18 | "name": "ScriptableObject", 19 | "category": "Basic", 20 | "prefix": [ 21 | "scriptableobject" 22 | ], 23 | "body": [ 24 | "using UnityEngine;", 25 | "", 26 | "[CreateAssetMenu(menuName = \"${2:$WORKSPACE_NAME}/${1:$TM_FILENAME_BASE}\")]", 27 | "public class $TM_FILENAME_BASE : ScriptableObject", 28 | "{", 29 | "\t$0", 30 | "}" 31 | ] 32 | }, 33 | { 34 | "name": "Editor", 35 | "category": "Editor", 36 | "prefix": [ 37 | "editor" 38 | ], 39 | "body": [ 40 | "using UnityEngine;", 41 | "using UnityEditor;", 42 | "using UnityEngine.UIElements;", 43 | "using UnityEditor.UIElements;", 44 | "", 45 | "[CustomEditor(typeof($1))]", 46 | "public class $TM_FILENAME_BASE : Editor", 47 | "{", 48 | "\tpublic override VisualElement CreateInspectorGUI()", 49 | "\t{", 50 | "\t\t${0:throw new System.NotImplementedException();}", 51 | "\t}", 52 | "}" 53 | ] 54 | }, 55 | { 56 | "name": "EditorWindow", 57 | "category": "Editor", 58 | "prefix": [ 59 | "editorwindow" 60 | ], 61 | "body": [ 62 | "using UnityEngine;", 63 | "using UnityEditor;", 64 | "using UnityEngine.UIElements;", 65 | "using UnityEditor.UIElements;", 66 | "", 67 | "public class $TM_FILENAME_BASE : EditorWindow", 68 | "{", 69 | "\t[MenuItem(\"Tools/$TM_FILENAME_BASE\")]", 70 | "\tpublic static void ShowEditorWindow()", 71 | "\t{", 72 | "\t\tGetWindow<$TM_FILENAME_BASE>();", 73 | "\t}", 74 | "", 75 | "\tpublic void CreateGUI()", 76 | "\t{", 77 | "\t\t$0", 78 | "\t}", 79 | "}" 80 | ] 81 | }, 82 | { 83 | "name": "PropertyDrawer", 84 | "category": "Editor", 85 | "prefix": [ 86 | "propertydrawer" 87 | ], 88 | "body": [ 89 | "using UnityEngine;", 90 | "using UnityEditor;", 91 | "using UnityEngine.UIElements;", 92 | "using UnityEditor.UIElements;", 93 | "", 94 | "[CustomPropertyDrawer(typeof($1))]", 95 | "public class $TM_FILENAME_BASE : PropertyDrawer", 96 | "{", 97 | "\tpublic override VisualElement CreatePropertyGUI(SerializedProperty property)", 98 | "\t{", 99 | "\t\t${0:throw new System.NotImplementedException();}", 100 | "\t}", 101 | "}" 102 | ] 103 | }, 104 | { 105 | "name": "IJob", 106 | "category": "Jobs", 107 | "prefix": [ 108 | "ijob" 109 | ], 110 | "body": [ 111 | "using UnityEngine;", 112 | "using Unity.Collections;", 113 | "using Unity.Mathematics;", 114 | "using Unity.Burst;", 115 | "using Unity.Jobs;", 116 | "", 117 | "public struct $TM_FILENAME_BASE : IJob", 118 | "{", 119 | "\tpublic void Execute()", 120 | "\t{", 121 | "\t\t$0", 122 | "\t}", 123 | "}" 124 | ] 125 | }, 126 | { 127 | "name": "IJobFor", 128 | "category": "Jobs", 129 | "prefix": [ 130 | "ijobfor" 131 | ], 132 | "body": [ 133 | "using UnityEngine;", 134 | "using Unity.Collections;", 135 | "using Unity.Mathematics;", 136 | "using Unity.Burst;", 137 | "using Unity.Jobs;", 138 | "", 139 | "public struct $TM_FILENAME_BASE : IJobFor", 140 | "{", 141 | "\tpublic void Execute(int index)", 142 | "\t{", 143 | "\t\t$0", 144 | "\t}", 145 | "}" 146 | ] 147 | }, 148 | { 149 | "name": "IJobParallelFor", 150 | "category": "Jobs", 151 | "prefix": [ 152 | "ijobparallelfor" 153 | ], 154 | "body": [ 155 | "using UnityEngine;", 156 | "using Unity.Collections;", 157 | "using Unity.Mathematics;", 158 | "using Unity.Burst;", 159 | "using Unity.Jobs;", 160 | "", 161 | "public struct $TM_FILENAME_BASE : IJobParallelFor", 162 | "{", 163 | "\tpublic void Execute(int index)", 164 | "\t{", 165 | "\t\t$0", 166 | "\t}", 167 | "}" 168 | ] 169 | }, 170 | { 171 | "name": "IComponentData", 172 | "category": "Entities", 173 | "package": "com.unity.entities", 174 | "prefix": [ 175 | "icomponentdata" 176 | ], 177 | "body": [ 178 | "using Unity.Entities;", 179 | "", 180 | "public ${1|struct,class|} $TM_FILENAME_BASE : IComponentData", 181 | "{", 182 | "\t$0", 183 | "}" 184 | ] 185 | }, 186 | { 187 | "name": "ISystem", 188 | "category": "Entities", 189 | "package": "com.unity.entities", 190 | "prefix": [ 191 | "isystem" 192 | ], 193 | "body": [ 194 | "using UnityEngine;", 195 | "using Unity.Entities;", 196 | "", 197 | "public partial struct $TM_FILENAME_BASE : ISystem", 198 | "{", 199 | "\tpublic void OnUpdate(ref SystemState state)", 200 | "\t{", 201 | "\t\t$0", 202 | "\t}", 203 | "}" 204 | ] 205 | }, 206 | { 207 | "name": "SystemBase", 208 | "category": "Entities", 209 | "package": "com.unity.entities", 210 | "prefix": [ 211 | "systembase" 212 | ], 213 | "body": [ 214 | "using UnityEngine;", 215 | "using Unity.Entities;", 216 | "", 217 | "public partial class $TM_FILENAME_BASE : SystemBase", 218 | "{", 219 | "\tprotected override void OnUpdate()", 220 | "\t{", 221 | "\t\t$0", 222 | "\t}", 223 | "}" 224 | ] 225 | } 226 | ] -------------------------------------------------------------------------------- /src/tests/suite/script.test.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "vscode"; 2 | import Script from "../../helpers/script"; 3 | import * as assert from "assert"; 4 | import Class from "../../helpers/class"; 5 | import Token from "../../helpers/token"; 6 | 7 | suite("Script", () => { 8 | test("new", () => { 9 | new Script(""); 10 | new Script("content"); 11 | }); 12 | 13 | suite("getClasses", () => { 14 | test("found", () => { 15 | const script = new Script(` 16 | using UnityEngine; 17 | 18 | public class MyClass : MonoBehaviour 19 | { 20 | 21 | } 22 | `); 23 | 24 | const classes = script.getClasses(); 25 | 26 | assert.deepStrictEqual(classes, [ 27 | new Class(script, new Token(new Range(new Position(3, 13), new Position(3, 20)), "MyClass"), "MonoBehaviour") 28 | ]); 29 | }); 30 | 31 | test("not found", () => { 32 | const script = new Script(` 33 | using UnityEngine; 34 | 35 | public struct MyStruct 36 | { 37 | 38 | } 39 | `); 40 | 41 | const classes = script.getClasses(); 42 | 43 | assert.deepStrictEqual(classes, []); 44 | }); 45 | 46 | test("multiple same line", () => { 47 | const script = new Script(` 48 | class MyClass1 { } class MyClass2 { } 49 | `); 50 | 51 | const classes = script.getClasses(); 52 | 53 | assert.deepStrictEqual(classes, [ 54 | new Class(script, new Token(new Range(new Position(1, 6), new Position(1, 14)), "MyClass1"), null), 55 | new Class(script, new Token(new Range(new Position(1, 25), new Position(1, 33)), "MyClass2"), null) 56 | ]); 57 | }); 58 | 59 | test("multiple different lines", () => { 60 | const script = new Script(` 61 | class MyClass1 { 62 | 63 | } 64 | 65 | class MyClass2 { 66 | 67 | } 68 | `); 69 | 70 | const classes = script.getClasses(); 71 | 72 | assert.deepStrictEqual(classes, [ 73 | new Class(script, new Token(new Range(new Position(1, 6), new Position(1, 14)), "MyClass1"), null), 74 | new Class(script, new Token(new Range(new Position(5, 6), new Position(5, 14)), "MyClass2"), null) 75 | ]); 76 | }); 77 | 78 | suite("rules", () => { 79 | test("requires name", () => { 80 | const script = new Script("class { }"); 81 | const classes = script.getClasses(); 82 | 83 | assert.deepStrictEqual(classes, []); 84 | }); 85 | 86 | test("allows 1 character name", () => { 87 | const script = new Script("class A{ }"); 88 | const classes = script.getClasses(); 89 | 90 | assert.deepStrictEqual(classes, [ 91 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 7)), "A"), null) 92 | ]); 93 | }); 94 | 95 | test("allows bracket behind name", () => { 96 | const script = new Script("class MyClass{ }"); 97 | const classes = script.getClasses(); 98 | 99 | assert.deepStrictEqual(classes, [ 100 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 13)), "MyClass"), null) 101 | ]); 102 | }); 103 | 104 | test("allows parents", () => { 105 | const script = new Script("class MyClass : MonoBehaviour { }"); 106 | const classes = script.getClasses(); 107 | 108 | assert.deepStrictEqual(classes, [ 109 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 13)), "MyClass"), "MonoBehaviour") 110 | ]); 111 | }); 112 | 113 | test("allows generics", () => { 114 | const script = new Script("class MyClass : MonoBehaviour where T : MonoBehaviour { }"); 115 | const classes = script.getClasses(); 116 | 117 | assert.deepStrictEqual(classes, [ 118 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 13)), "MyClass"), "MonoBehaviour") 119 | ]); 120 | }); 121 | 122 | test("allows additional keywords", () => { 123 | const script = new Script("public abstract class MyClass { }"); 124 | const classes = script.getClasses(); 125 | 126 | assert.deepStrictEqual(classes, [ 127 | new Class(script, new Token(new Range(new Position(0, 22), new Position(0, 29)), "MyClass"), null) 128 | ]); 129 | }); 130 | 131 | test("allows @ prefix", () => { 132 | const script = new Script("class @MyClass { }"); 133 | const classes = script.getClasses(); 134 | 135 | assert.deepStrictEqual(classes, [ 136 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 14)), "@MyClass"), null) 137 | ]); 138 | }); 139 | 140 | test("disallows @ postfix", () => { 141 | const script = new Script("class MyCl@ss { }"); 142 | const classes = script.getClasses(); 143 | 144 | assert.deepStrictEqual(classes, [ 145 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 10)), "MyCl"), null) 146 | ]); 147 | }); 148 | 149 | test("allows _ prefix", () => { 150 | const script = new Script("class _MyClass { }"); 151 | const classes = script.getClasses(); 152 | 153 | assert.deepStrictEqual(classes, [ 154 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 14)), "_MyClass"), null) 155 | ]); 156 | }); 157 | 158 | test("allows _ postfix", () => { 159 | const script = new Script("class My_Class { }"); 160 | const classes = script.getClasses(); 161 | 162 | assert.deepStrictEqual(classes, [ 163 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 14)), "My_Class"), null) 164 | ]); 165 | }); 166 | 167 | test("disallows number prefix", () => { 168 | const script = new Script("class 0MyClass { }"); 169 | const classes = script.getClasses(); 170 | 171 | assert.deepStrictEqual(classes, []); 172 | }); 173 | 174 | test("allows number postfix", () => { 175 | const script = new Script("class MyClass1 { }"); 176 | const classes = script.getClasses(); 177 | 178 | assert.deepStrictEqual(classes, [ 179 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 14)), "MyClass1"), null) 180 | ]); 181 | }); 182 | 183 | test("disallows - prefix", () => { 184 | const script = new Script("class -MyClass { }"); 185 | const classes = script.getClasses(); 186 | 187 | assert.deepStrictEqual(classes, []); 188 | }); 189 | 190 | test("disallows - postfix", () => { 191 | const script = new Script("class My-Class { }"); 192 | const classes = script.getClasses(); 193 | 194 | assert.deepStrictEqual(classes, [ 195 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 8)), "My"), null) 196 | ]); 197 | }); 198 | 199 | test("allows unicode", () => { 200 | const script = new Script("class KątDînerKočkaНомерσαλάτα { }"); 201 | const classes = script.getClasses(); 202 | 203 | assert.deepStrictEqual(classes, [ 204 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 30)), "KątDînerKočkaНомерσαλάτα"), null) 205 | ]); 206 | }); 207 | }); 208 | }); 209 | 210 | suite("getOpeningBracket", () => { 211 | test("found", () => { 212 | const script = new Script(` 213 | using UnityEngine; 214 | 215 | public class MyClass : MonoBehaviour 216 | { 217 | 218 | } 219 | `); 220 | 221 | const bracket = script.getOpeningBracket(script.start); 222 | 223 | assert.deepStrictEqual(bracket, new Position(4, 0)); 224 | }); 225 | 226 | test("not found", () => { 227 | const script = new Script(` 228 | using UnityEngine; 229 | 230 | public class MyClass : MonoBehaviour 231 | `); 232 | 233 | const bracket = script.getOpeningBracket(script.start); 234 | 235 | assert.deepStrictEqual(bracket, null); 236 | }); 237 | 238 | test("line outside of range", () => { 239 | const script = new Script(` 240 | using UnityEngine; 241 | 242 | public class MyClass : MonoBehaviour { 243 | 244 | } 245 | `); 246 | 247 | const bracket = script.getOpeningBracket(new Position(4, 0)); 248 | 249 | assert.deepStrictEqual(bracket, null); 250 | }); 251 | 252 | test("character outside of range", () => { 253 | const script = new Script(` 254 | using UnityEngine; 255 | 256 | public class MyClass : MonoBehaviour { } 257 | `); 258 | 259 | const bracket = script.getOpeningBracket(new Position(3, 38)); 260 | 261 | assert.deepStrictEqual(bracket, null); 262 | }); 263 | }); 264 | 265 | suite("getClosingBracket", () => { 266 | test("found", () => { 267 | const script = new Script(` 268 | using UnityEngine; 269 | 270 | public class MyClass : MonoBehaviour 271 | { 272 | 273 | } 274 | `); 275 | 276 | const bracket = script.getClosingBracket(script.start); 277 | 278 | assert.deepStrictEqual(bracket, new Position(6, 0)); 279 | }); 280 | 281 | test("not found", () => { 282 | const script = new Script(` 283 | using UnityEngine; 284 | 285 | public class MyClass : MonoBehaviour 286 | `); 287 | 288 | const bracket = script.getClosingBracket(script.start); 289 | 290 | assert.deepStrictEqual(bracket, null); 291 | }); 292 | 293 | test("opening bracket line outside of range", () => { 294 | const script = new Script(` 295 | using UnityEngine; 296 | 297 | public class MyClass : MonoBehaviour { 298 | 299 | } 300 | `); 301 | 302 | const bracket = script.getClosingBracket(new Position(4, 0)); 303 | 304 | assert.deepStrictEqual(bracket, null); 305 | }); 306 | }); 307 | 308 | suite("getBracketPair", () => { 309 | test("found", () => { 310 | const script = new Script(` 311 | using UnityEngine; 312 | 313 | public class MyClass : MonoBehaviour 314 | { 315 | 316 | } 317 | `); 318 | 319 | const brackets = script.getBracketPair(script.start); 320 | 321 | assert.deepStrictEqual(brackets, new Range(new Position(4, 0), new Position(6, 0))); 322 | }); 323 | 324 | test("not found", () => { 325 | const script = new Script(` 326 | using UnityEngine; 327 | 328 | public class MyClass : MonoBehaviour 329 | `); 330 | 331 | const brackets = script.getBracketPair(script.start); 332 | 333 | assert.deepStrictEqual(brackets, null); 334 | }); 335 | }); 336 | }); -------------------------------------------------------------------------------- /src/tests/suite/class.test.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "vscode"; 2 | import Class from "../../helpers/class"; 3 | import Script from "../../helpers/script"; 4 | import Token from "../../helpers/token"; 5 | import * as assert from "assert"; 6 | import Method from "../../helpers/method"; 7 | 8 | suite("Class", () => { 9 | test("new", () => { 10 | const script = new Script("class MyClass { }"); 11 | 12 | new Class(script, new Token(new Range(new Position(0, 6), new Position(0, 13)), "MyClass"), null); 13 | }); 14 | 15 | suite("isComponent", () => { 16 | test("true", () => { 17 | const script = new Script("class MyClass : MonoBehaviour { }"); 18 | const classes = script.getClasses(); 19 | const isComponent = classes[0].isComponent("MyClass.cs"); 20 | 21 | assert.deepStrictEqual(isComponent, true); 22 | }); 23 | 24 | test("no parent", () => { 25 | const script = new Script("class MyClass { }"); 26 | const classes = script.getClasses(); 27 | const isComponent = classes[0].isComponent("MyClass.cs"); 28 | 29 | assert.deepStrictEqual(isComponent, false); 30 | }); 31 | 32 | test("wrong parent", () => { 33 | const script = new Script("class MyClass : IInteractable { }"); 34 | const classes = script.getClasses(); 35 | const isComponent = classes[0].isComponent("MyClass.cs"); 36 | 37 | assert.deepStrictEqual(isComponent, false); 38 | }); 39 | 40 | test("wrong file name", () => { 41 | const script = new Script("class MyClass : MonoBehaviour { }"); 42 | const classes = script.getClasses(); 43 | const isComponent = classes[0].isComponent("NotMyClass.cs"); 44 | 45 | assert.deepStrictEqual(isComponent, false); 46 | }); 47 | }); 48 | 49 | suite("isScriptableObject", () => { 50 | test("true", () => { 51 | const script = new Script("class MyClass : ScriptableObject { }"); 52 | const classes = script.getClasses(); 53 | const isScriptableObject = classes[0].isScriptableObject("MyClass.cs"); 54 | 55 | assert.deepStrictEqual(isScriptableObject, true); 56 | }); 57 | 58 | test("no parent", () => { 59 | const script = new Script("class MyClass { }"); 60 | const classes = script.getClasses(); 61 | const isScriptableObject = classes[0].isScriptableObject("MyClass.cs"); 62 | 63 | assert.deepStrictEqual(isScriptableObject, false); 64 | }); 65 | 66 | test("wrong parent", () => { 67 | const script = new Script("class MyClass : IInteractable { }"); 68 | const classes = script.getClasses(); 69 | const isScriptableObject = classes[0].isScriptableObject("MyClass.cs"); 70 | 71 | assert.deepStrictEqual(isScriptableObject, false); 72 | }); 73 | 74 | test("wrong file name", () => { 75 | const script = new Script("class MyClass : ScriptableObject { }"); 76 | const classes = script.getClasses(); 77 | const isScriptableObject = classes[0].isScriptableObject("NotMyClass.cs"); 78 | 79 | assert.deepStrictEqual(isScriptableObject, false); 80 | }); 81 | }); 82 | 83 | suite("getMethods", () => { 84 | test("found", () => { 85 | const script = new Script(` 86 | using UnityEngine; 87 | 88 | public class MyClass : MonoBehaviour 89 | { 90 | void MyMethod() 91 | { 92 | 93 | } 94 | } 95 | `); 96 | 97 | const classes = script.getClasses(); 98 | const methods = classes[0].getMethods(); 99 | 100 | assert.deepStrictEqual(methods, [ 101 | new Method(script, 102 | new Token(new Range(new Position(5, 4), new Position(5, 8)), "void"), 103 | new Token(new Range(new Position(5, 9), new Position(5, 17)), "MyMethod") 104 | ) 105 | ]); 106 | }); 107 | 108 | test("not found", () => { 109 | const script = new Script(` 110 | using UnityEngine; 111 | 112 | public class MyClass : MonoBehaviour 113 | { 114 | 115 | } 116 | `); 117 | 118 | const classes = script.getClasses(); 119 | const methods = classes[0].getMethods(); 120 | 121 | assert.deepStrictEqual(methods, []); 122 | }); 123 | 124 | test("multiple same line", () => { 125 | const script = new Script(` 126 | using UnityEngine; 127 | 128 | public class MyClass : MonoBehaviour 129 | { 130 | void MyMethod1() { } void MyMethod2() { } 131 | } 132 | `); 133 | 134 | const classes = script.getClasses(); 135 | const methods = classes[0].getMethods(); 136 | 137 | assert.deepStrictEqual(methods, [ 138 | new Method(script, 139 | new Token(new Range(new Position(5, 4), new Position(5, 8)), "void"), 140 | new Token(new Range(new Position(5, 9), new Position(5, 18)), "MyMethod1") 141 | ), 142 | new Method(script, 143 | new Token(new Range(new Position(5, 25), new Position(5, 29)), "void"), 144 | new Token(new Range(new Position(5, 30), new Position(5, 39)), "MyMethod2") 145 | ) 146 | ]); 147 | }); 148 | 149 | test("multiple different lines", () => { 150 | const script = new Script(` 151 | using UnityEngine; 152 | 153 | public class MyClass : MonoBehaviour 154 | { 155 | void MyMethod1() { } 156 | void MyMethod2() { } 157 | } 158 | `); 159 | 160 | const classes = script.getClasses(); 161 | const methods = classes[0].getMethods(); 162 | 163 | assert.deepStrictEqual(methods, [ 164 | new Method(script, 165 | new Token(new Range(new Position(5, 4), new Position(5, 8)), "void"), 166 | new Token(new Range(new Position(5, 9), new Position(5, 18)), "MyMethod1") 167 | ), 168 | new Method(script, 169 | new Token(new Range(new Position(6, 4), new Position(6, 8)), "void"), 170 | new Token(new Range(new Position(6, 9), new Position(6, 18)), "MyMethod2") 171 | ) 172 | ]); 173 | }); 174 | 175 | suite("rules", () => { 176 | test("requires name", () => { 177 | const script = new Script("class MyClass { void () { } }"); 178 | const classes = script.getClasses(); 179 | const methods = classes[0].getMethods(); 180 | 181 | assert.deepStrictEqual(methods, []); 182 | }); 183 | 184 | test("requires type", () => { 185 | const script = new Script("class MyClass { MyMethod() { } }"); 186 | const classes = script.getClasses(); 187 | const methods = classes[0].getMethods(); 188 | 189 | assert.deepStrictEqual(methods, []); 190 | }); 191 | 192 | test("allows 1 character name", () => { 193 | const script = new Script("class MyClass { void M() { } }"); 194 | const classes = script.getClasses(); 195 | const methods = classes[0].getMethods(); 196 | 197 | assert.deepStrictEqual(methods, [ 198 | new Method(script, 199 | new Token(new Range(new Position(0, 16), new Position(0, 20)), "void"), 200 | new Token(new Range(new Position(0, 21), new Position(0, 22)), "M") 201 | ), 202 | ]); 203 | }); 204 | 205 | test("allows 1 character type", () => { 206 | const script = new Script("class MyClass { T MyMethod() { } }"); 207 | const classes = script.getClasses(); 208 | const methods = classes[0].getMethods(); 209 | 210 | assert.deepStrictEqual(methods, [ 211 | new Method(script, 212 | new Token(new Range(new Position(0, 16), new Position(0, 17)), "T"), 213 | new Token(new Range(new Position(0, 18), new Position(0, 26)), "MyMethod") 214 | ), 215 | ]); 216 | }); 217 | 218 | test("allows bracket behind name", () => { 219 | const script = new Script("class MyClass { void MyMethod(){ } }"); 220 | const classes = script.getClasses(); 221 | const methods = classes[0].getMethods(); 222 | 223 | assert.deepStrictEqual(methods, [ 224 | new Method(script, 225 | new Token(new Range(new Position(0, 16), new Position(0, 20)), "void"), 226 | new Token(new Range(new Position(0, 21), new Position(0, 29)), "MyMethod") 227 | ), 228 | ]); 229 | }); 230 | 231 | test("allows additional keywords", () => { 232 | const script = new Script("class MyClass { public abstract void MyMethod(){ } }"); 233 | const classes = script.getClasses(); 234 | const methods = classes[0].getMethods(); 235 | 236 | assert.deepStrictEqual(methods, [ 237 | new Method(script, 238 | new Token(new Range(new Position(0, 32), new Position(0, 36)), "void"), 239 | new Token(new Range(new Position(0, 37), new Position(0, 45)), "MyMethod") 240 | ), 241 | ]); 242 | }); 243 | 244 | test("allows _ prefix", () => { 245 | const script = new Script("class MyClass { void _MyMethod() { } }"); 246 | const classes = script.getClasses(); 247 | const methods = classes[0].getMethods(); 248 | 249 | assert.deepStrictEqual(methods, [ 250 | new Method(script, 251 | new Token(new Range(new Position(0, 16), new Position(0, 20)), "void"), 252 | new Token(new Range(new Position(0, 21), new Position(0, 30)), "_MyMethod") 253 | ), 254 | ]); 255 | }); 256 | 257 | test("allows _ postfix", () => { 258 | const script = new Script("class MyClass { void My_Method() { } }"); 259 | const classes = script.getClasses(); 260 | const methods = classes[0].getMethods(); 261 | 262 | assert.deepStrictEqual(methods, [ 263 | new Method(script, 264 | new Token(new Range(new Position(0, 16), new Position(0, 20)), "void"), 265 | new Token(new Range(new Position(0, 21), new Position(0, 30)), "My_Method") 266 | ), 267 | ]); 268 | }); 269 | 270 | test("disallows number prefix", () => { 271 | const script = new Script("class MyClass { void 0MyMethod() { } }"); 272 | const classes = script.getClasses(); 273 | const methods = classes[0].getMethods(); 274 | 275 | assert.deepStrictEqual(methods, []); 276 | }); 277 | 278 | test("allows number postfix", () => { 279 | const script = new Script("class MyClass { void MyMethod1() { } }"); 280 | const classes = script.getClasses(); 281 | const methods = classes[0].getMethods(); 282 | 283 | assert.deepStrictEqual(methods, [ 284 | new Method(script, 285 | new Token(new Range(new Position(0, 16), new Position(0, 20)), "void"), 286 | new Token(new Range(new Position(0, 21), new Position(0, 30)), "MyMethod1") 287 | ), 288 | ]); 289 | }); 290 | 291 | test("disallows - prefix", () => { 292 | const script = new Script("class MyClass { void -MyMethod() { } }"); 293 | const classes = script.getClasses(); 294 | const methods = classes[0].getMethods(); 295 | 296 | assert.deepStrictEqual(methods, []); 297 | }); 298 | 299 | test("disallows - postfix", () => { 300 | const script = new Script("class MyClass { void My-Method() { } }"); 301 | const classes = script.getClasses(); 302 | const methods = classes[0].getMethods(); 303 | 304 | assert.deepStrictEqual(methods, []); 305 | }); 306 | 307 | test("allows unicode", () => { 308 | const script = new Script("class MyClass { void KątDînerKočkaНомерσαλάτα() { } }"); 309 | const classes = script.getClasses(); 310 | const methods = classes[0].getMethods(); 311 | 312 | assert.deepStrictEqual(methods, [ 313 | new Method(script, 314 | new Token(new Range(new Position(0, 16), new Position(0, 20)), "void"), 315 | new Token(new Range(new Position(0, 21), new Position(0, 45)), "KątDînerKočkaНомерσαλάτα") 316 | ), 317 | ]); 318 | }); 319 | }); 320 | }); 321 | }); -------------------------------------------------------------------------------- /src/features/unityMessages/unityMessages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Awake", 4 | "coroutine": false, 5 | "body": [ 6 | "void Awake()", 7 | "{", 8 | "\t$0", 9 | "}" 10 | ] 11 | }, 12 | { 13 | "name": "FixedUpdate", 14 | "coroutine": false, 15 | "body": [ 16 | "void FixedUpdate()", 17 | "{", 18 | "\t$0", 19 | "}" 20 | ] 21 | }, 22 | { 23 | "name": "LateUpdate", 24 | "coroutine": false, 25 | "body": [ 26 | "void LateUpdate()", 27 | "{", 28 | "\t$0", 29 | "}" 30 | ] 31 | }, 32 | { 33 | "name": "OnAnimatorIK", 34 | "coroutine": true, 35 | "body": [ 36 | "void OnAnimatorIK(int layerIndex)", 37 | "{", 38 | "\t$0", 39 | "}" 40 | ] 41 | }, 42 | { 43 | "name": "OnAnimatorMove", 44 | "coroutine": true, 45 | "body": [ 46 | "void OnAnimatorMove()", 47 | "{", 48 | "\t$0", 49 | "}" 50 | ] 51 | }, 52 | { 53 | "name": "OnApplicationFocus", 54 | "coroutine": true, 55 | "body": [ 56 | "void OnApplicationFocus(bool hasFocus)", 57 | "{", 58 | "\t$0", 59 | "}" 60 | ] 61 | }, 62 | { 63 | "name": "OnApplicationPause", 64 | "coroutine": true, 65 | "body": [ 66 | "void OnApplicationPause(bool pauseStatus)", 67 | "{", 68 | "\t$0", 69 | "}" 70 | ] 71 | }, 72 | { 73 | "name": "OnApplicationQuit", 74 | "coroutine": true, 75 | "body": [ 76 | "void OnApplicationQuit()", 77 | "{", 78 | "\t$0", 79 | "}" 80 | ] 81 | }, 82 | { 83 | "name": "OnAudioFilterRead", 84 | "coroutine": true, 85 | "body": [ 86 | "void OnAudioFilterRead(float[] data, int channels)", 87 | "{", 88 | "\t$0", 89 | "}" 90 | ] 91 | }, 92 | { 93 | "name": "OnBecameInvisible", 94 | "coroutine": true, 95 | "body": [ 96 | "void OnBecameInvisible()", 97 | "{", 98 | "\t$0", 99 | "}" 100 | ] 101 | }, 102 | { 103 | "name": "OnBecameVisible", 104 | "coroutine": true, 105 | "body": [ 106 | "void OnBecameVisible()", 107 | "{", 108 | "\t$0", 109 | "}" 110 | ] 111 | }, 112 | { 113 | "name": "OnCollisionEnter", 114 | "coroutine": true, 115 | "body": [ 116 | "void OnCollisionEnter(Collision other)", 117 | "{", 118 | "\t$0", 119 | "}" 120 | ] 121 | }, 122 | { 123 | "name": "OnCollisionEnter2D", 124 | "coroutine": true, 125 | "body": [ 126 | "void OnCollisionEnter2D(Collision2D other)", 127 | "{", 128 | "\t$0", 129 | "}" 130 | ] 131 | }, 132 | { 133 | "name": "OnCollisionExit", 134 | "coroutine": true, 135 | "body": [ 136 | "void OnCollisionExit(Collision other)", 137 | "{", 138 | "\t$0", 139 | "}" 140 | ] 141 | }, 142 | { 143 | "name": "OnCollisionExit2D", 144 | "coroutine": true, 145 | "body": [ 146 | "void OnCollisionExit2D(Collision2D other)", 147 | "{", 148 | "\t$0", 149 | "}" 150 | ] 151 | }, 152 | { 153 | "name": "OnCollisionStay", 154 | "coroutine": true, 155 | "body": [ 156 | "void OnCollisionStay(Collision other)", 157 | "{", 158 | "\t$0", 159 | "}" 160 | ] 161 | }, 162 | { 163 | "name": "OnCollisionStay2D", 164 | "coroutine": true, 165 | "body": [ 166 | "void OnCollisionStay2D(Collision2D other)", 167 | "{", 168 | "\t$0", 169 | "}" 170 | ] 171 | }, 172 | { 173 | "name": "OnControllerColliderHit", 174 | "coroutine": true, 175 | "body": [ 176 | "void OnControllerColliderHit(ControllerColliderHit hit)", 177 | "{", 178 | "\t$0", 179 | "}" 180 | ] 181 | }, 182 | { 183 | "name": "OnDestroy", 184 | "coroutine": false, 185 | "body": [ 186 | "void OnDestroy()", 187 | "{", 188 | "\t$0", 189 | "}" 190 | ] 191 | }, 192 | { 193 | "name": "OnDisable", 194 | "coroutine": false, 195 | "body": [ 196 | "void OnDisable()", 197 | "{", 198 | "\t$0", 199 | "}" 200 | ] 201 | }, 202 | { 203 | "name": "OnDrawGizmos", 204 | "coroutine": false, 205 | "body": [ 206 | "void OnDrawGizmos()", 207 | "{", 208 | "\t$0", 209 | "}" 210 | ] 211 | }, 212 | { 213 | "name": "OnDrawGizmosSelected", 214 | "coroutine": false, 215 | "body": [ 216 | "void OnDrawGizmosSelected()", 217 | "{", 218 | "\t$0", 219 | "}" 220 | ] 221 | }, 222 | { 223 | "name": "OnEnable", 224 | "coroutine": false, 225 | "body": [ 226 | "void OnEnable()", 227 | "{", 228 | "\t$0", 229 | "}" 230 | ] 231 | }, 232 | { 233 | "name": "OnGUI", 234 | "coroutine": false, 235 | "body": [ 236 | "void OnGUI()", 237 | "{", 238 | "\t$0", 239 | "}" 240 | ] 241 | }, 242 | { 243 | "name": "OnJointBreak", 244 | "coroutine": true, 245 | "body": [ 246 | "void OnJointBreak(float breakForce)", 247 | "{", 248 | "\t$0", 249 | "}" 250 | ] 251 | }, 252 | { 253 | "name": "OnJointBreak2D", 254 | "coroutine": true, 255 | "body": [ 256 | "void OnJointBreak2D(Joint2D brokenJoint)", 257 | "{", 258 | "\t$0", 259 | "}" 260 | ] 261 | }, 262 | { 263 | "name": "OnMouseDown", 264 | "coroutine": true, 265 | "body": [ 266 | "void OnMouseDown()", 267 | "{", 268 | "\t$0", 269 | "}" 270 | ] 271 | }, 272 | { 273 | "name": "OnMouseDrag", 274 | "coroutine": true, 275 | "body": [ 276 | "void OnMouseDrag()", 277 | "{", 278 | "\t$0", 279 | "}" 280 | ] 281 | }, 282 | { 283 | "name": "OnMouseEnter", 284 | "coroutine": true, 285 | "body": [ 286 | "void OnMouseEnter()", 287 | "{", 288 | "\t$0", 289 | "}" 290 | ] 291 | }, 292 | { 293 | "name": "OnMouseExit", 294 | "coroutine": true, 295 | "body": [ 296 | "void OnMouseExit()", 297 | "{", 298 | "\t$0", 299 | "}" 300 | ] 301 | }, 302 | { 303 | "name": "OnMouseOver", 304 | "coroutine": true, 305 | "body": [ 306 | "void OnMouseOver()", 307 | "{", 308 | "\t$0", 309 | "}" 310 | ] 311 | }, 312 | { 313 | "name": "OnMouseUp", 314 | "coroutine": true, 315 | "body": [ 316 | "void OnMouseUp()", 317 | "{", 318 | "\t$0", 319 | "}" 320 | ] 321 | }, 322 | { 323 | "name": "OnMouseUpAsButton", 324 | "coroutine": true, 325 | "body": [ 326 | "void OnMouseUpAsButton()", 327 | "{", 328 | "\t$0", 329 | "}" 330 | ] 331 | }, 332 | { 333 | "name": "OnParticleCollision", 334 | "coroutine": true, 335 | "body": [ 336 | "void OnParticleCollision(GameObject other)", 337 | "{", 338 | "\t$0", 339 | "}" 340 | ] 341 | }, 342 | { 343 | "name": "OnParticleSystemStopped", 344 | "coroutine": true, 345 | "body": [ 346 | "void OnParticleSystemStopped()", 347 | "{", 348 | "\t$0", 349 | "}" 350 | ] 351 | }, 352 | { 353 | "name": "OnParticleTrigger", 354 | "coroutine": true, 355 | "body": [ 356 | "void OnParticleTrigger()", 357 | "{", 358 | "\t$0", 359 | "}" 360 | ] 361 | }, 362 | { 363 | "name": "OnParticleUpdateJobScheduled", 364 | "coroutine": true, 365 | "body": [ 366 | "void OnParticleUpdateJobScheduled()", 367 | "{", 368 | "\t$0", 369 | "}" 370 | ] 371 | }, 372 | { 373 | "name": "OnPostRender", 374 | "coroutine": true, 375 | "body": [ 376 | "void OnPostRender()", 377 | "{", 378 | "\t$0", 379 | "}" 380 | ] 381 | }, 382 | { 383 | "name": "OnPreCull", 384 | "coroutine": true, 385 | "body": [ 386 | "void OnPreCull()", 387 | "{", 388 | "\t$0", 389 | "}" 390 | ] 391 | }, 392 | { 393 | "name": "OnPreRender", 394 | "coroutine": true, 395 | "body": [ 396 | "void OnPreRender()", 397 | "{", 398 | "\t$0", 399 | "}" 400 | ] 401 | }, 402 | { 403 | "name": "OnRenderImage", 404 | "coroutine": true, 405 | "body": [ 406 | "void OnRenderImage(RenderTexture src, RenderTexture dest)", 407 | "{", 408 | "\t$0", 409 | "}" 410 | ] 411 | }, 412 | { 413 | "name": "OnRenderObject", 414 | "coroutine": false, 415 | "body": [ 416 | "void OnRenderObject()", 417 | "{", 418 | "\t$0", 419 | "}" 420 | ] 421 | }, 422 | { 423 | "name": "OnTransformChildrenChanged", 424 | "coroutine": true, 425 | "body": [ 426 | "void OnTransformChildrenChanged()", 427 | "{", 428 | "\t$0", 429 | "}" 430 | ] 431 | }, 432 | { 433 | "name": "OnTransformParentChanged", 434 | "coroutine": true, 435 | "body": [ 436 | "void OnTransformParentChanged()", 437 | "{", 438 | "\t$0", 439 | "}" 440 | ] 441 | }, 442 | { 443 | "name": "OnTriggerEnter", 444 | "coroutine": true, 445 | "body": [ 446 | "void OnTriggerEnter(Collider other)", 447 | "{", 448 | "\t$0", 449 | "}" 450 | ] 451 | }, 452 | { 453 | "name": "OnTriggerEnter2D", 454 | "coroutine": true, 455 | "body": [ 456 | "void OnTriggerEnter2D(Collider2D other)", 457 | "{", 458 | "\t$0", 459 | "}" 460 | ] 461 | }, 462 | { 463 | "name": "OnTriggerExit", 464 | "coroutine": true, 465 | "body": [ 466 | "void OnTriggerExit(Collider other)", 467 | "{", 468 | "\t$0", 469 | "}" 470 | ] 471 | }, 472 | { 473 | "name": "OnTriggerExit2D", 474 | "coroutine": true, 475 | "body": [ 476 | "void OnTriggerExit2D(Collider2D other)", 477 | "{", 478 | "\t$0", 479 | "}" 480 | ] 481 | }, 482 | { 483 | "name": "OnTriggerStay", 484 | "coroutine": true, 485 | "body": [ 486 | "void OnTriggerStay(Collider other)", 487 | "{", 488 | "\t$0", 489 | "}" 490 | ] 491 | }, 492 | { 493 | "name": "OnTriggerStay2D", 494 | "coroutine": true, 495 | "body": [ 496 | "void OnTriggerStay2D(Collider2D other)", 497 | "{", 498 | "\t$0", 499 | "}" 500 | ] 501 | }, 502 | { 503 | "name": "OnValidate", 504 | "coroutine": false, 505 | "body": [ 506 | "void OnValidate()", 507 | "{", 508 | "\t$0", 509 | "}" 510 | ] 511 | }, 512 | { 513 | "name": "OnWillRenderObject", 514 | "coroutine": true, 515 | "body": [ 516 | "void OnWillRenderObject()", 517 | "{", 518 | "\t$0", 519 | "}" 520 | ] 521 | }, 522 | { 523 | "name": "Reset", 524 | "coroutine": true, 525 | "body": [ 526 | "void Reset()", 527 | "{", 528 | "\t$0", 529 | "}" 530 | ] 531 | }, 532 | { 533 | "name": "Start", 534 | "coroutine": true, 535 | "body": [ 536 | "void Start()", 537 | "{", 538 | "\t$0", 539 | "}" 540 | ] 541 | }, 542 | { 543 | "name": "Update", 544 | "coroutine": false, 545 | "body": [ 546 | "void Update()", 547 | "{", 548 | "\t$0", 549 | "}" 550 | ] 551 | } 552 | ] --------------------------------------------------------------------------------