├── .gitignore ├── resources ├── clover.png ├── activity.png ├── codelens.gif ├── snippets.gif ├── assetViewer.gif ├── assetexplorer.png ├── attributehelper.gif ├── unity_message_docs.png ├── unity_method_usage.png ├── dark │ └── clover.svg └── light │ └── clover.svg ├── media ├── clover-icon.woff ├── assetViewer.js ├── assetViewer.css └── assetViewerComponents.js ├── .vscodeignore ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── src ├── extension.ts ├── unityAssetExplorer │ ├── unityAssetConnector.ts │ └── unityAssetExplorerProvider.ts ├── test │ ├── suite │ │ ├── extension.test.ts │ │ └── index.ts │ └── runTest.ts ├── vscodeUtils.ts ├── controller │ ├── commandController.ts │ ├── providerController.ts │ └── unityProjectController.ts ├── initializer.ts ├── parser │ ├── guidConnector.ts │ ├── guidParser.ts │ ├── assetConnector.ts │ └── assetParser.ts ├── unityAssetViewer │ ├── hierarchy.ts │ └── unityAssetViewer.ts ├── attributeHelper │ └── attributeItem.ts ├── unityReference │ ├── metaReferenceProvider.ts │ ├── unityUsageProvider.ts │ └── unityMessageProvider.ts └── provider │ └── mainViewProvider.ts ├── tsconfig.json ├── .eslintrc.json ├── .github └── ISSUE_TEMPLATE │ ├── feature-request-✨.md │ └── bug-report-🐛.md ├── LICENSE ├── README.md ├── CHANGELOG.md ├── package.json ├── unityMessages.json └── unitySnippets.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /resources/clover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/resources/clover.png -------------------------------------------------------------------------------- /media/clover-icon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/media/clover-icon.woff -------------------------------------------------------------------------------- /resources/activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/resources/activity.png -------------------------------------------------------------------------------- /resources/codelens.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/resources/codelens.gif -------------------------------------------------------------------------------- /resources/snippets.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/resources/snippets.gif -------------------------------------------------------------------------------- /resources/assetViewer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/resources/assetViewer.gif -------------------------------------------------------------------------------- /resources/assetexplorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/resources/assetexplorer.png -------------------------------------------------------------------------------- /resources/attributehelper.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/resources/attributehelper.gif -------------------------------------------------------------------------------- /resources/unity_message_docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/resources/unity_message_docs.png -------------------------------------------------------------------------------- /resources/unity_method_usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunward/clover/HEAD/resources/unity_method_usage.png -------------------------------------------------------------------------------- /.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/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { initialize } from './initializer'; 3 | 4 | export function activate(context: vscode.ExtensionContext) { 5 | initialize(context); 6 | 7 | vscode.commands.executeCommand('workbench.view.extension.clover-activitybar'); 8 | } -------------------------------------------------------------------------------- /src/unityAssetExplorer/unityAssetConnector.ts: -------------------------------------------------------------------------------- 1 | var assetPaths: string[] = []; 2 | 3 | export function addAssetPath(path: string) { 4 | assetPaths.push(path); 5 | } 6 | 7 | export function getAssetPaths(): string[] { 8 | return assetPaths; 9 | } 10 | 11 | export function refresh() { 12 | assetPaths = []; 13 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /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 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request-✨.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request ✨ 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report-🐛.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug report \U0001F41B" 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | - OS: [e.g. macOS] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /src/vscodeUtils.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | let clover = vscode.window.createOutputChannel("Clover"); 4 | 5 | export function outputLog(log: string) { 6 | clover.appendLine(`[${new Date().toLocaleTimeString()}] ${log}`); 7 | } 8 | 9 | export function insertText(text: string) { 10 | const editor = vscode.window.activeTextEditor; 11 | editor?.edit(editBuilder => { 12 | editBuilder.insert(editor.selection.active, text); 13 | }); 14 | } 15 | 16 | export function getWorkspacePath(): string { 17 | return vscode.workspace.workspaceFolders?.[0].uri.fsPath || ""; 18 | } 19 | 20 | export function getActiveFilePath(): string { 21 | return vscode.window.activeTextEditor?.document.uri.fsPath || ""; 22 | } -------------------------------------------------------------------------------- /src/test/runTest.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'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/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 | } 39 | -------------------------------------------------------------------------------- /src/controller/commandController.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import path = require('path'); 3 | import { refresh } from './unityProjectController'; 4 | import { showAttributeHelper } from '../attributeHelper/attributeItem'; 5 | 6 | export function updateStatus(name: string, value: T) { 7 | vscode.commands.executeCommand('setContext', name, value); 8 | } 9 | 10 | export function initialize(context: vscode.ExtensionContext, workspacePath: string) { 11 | registerCommand(context, 'clover.refreshUnityProject', () => refresh()); 12 | registerCommand(context, 'clover.noReferenceMessage', () => vscode.window.showInformationMessage("No reference found")); 13 | registerCommand(context, 'clover.showAttributeHelper', () => showAttributeHelper()); 14 | } 15 | 16 | export function registerCommand(context: vscode.ExtensionContext, command: string, callback: (...args: any[]) => any) { 17 | context.subscriptions.push(vscode.commands.registerCommand(command, callback)); 18 | } -------------------------------------------------------------------------------- /src/initializer.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as CommandController from './controller/commandController'; 3 | import * as ProviderController from './controller/providerController'; 4 | import * as UnityProjectController from './controller/unityProjectController'; 5 | import * as UnityAssetViewer from './unityAssetViewer/unityAssetViewer'; 6 | import * as VSCodeUtils from './vscodeUtils'; 7 | 8 | export async function initialize(context: vscode.ExtensionContext) { 9 | var workspacePath = VSCodeUtils.getWorkspacePath(); 10 | if (!UnityProjectController.isUnityProject(workspacePath)) return; 11 | 12 | await UnityProjectController.initialize(workspacePath); 13 | 14 | ProviderController.initialize(context); 15 | UnityAssetViewer.init(context); 16 | 17 | CommandController.updateStatus('clover.workspace.valid', true); 18 | // CommandController.updateStatus('clover.unity.initialized', true); 19 | 20 | CommandController.initialize(context, workspacePath); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /.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 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "${defaultBuildTask}" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dealer 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. 22 | -------------------------------------------------------------------------------- /src/controller/providerController.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { MetaReferenceProvider } from '../unityReference/metaReferenceProvider'; 3 | import { UnityMessageProvider } from '../unityReference/unityMessageProvider'; 4 | import { UnityUsageProvider } from '../unityReference/unityUsageProvider'; 5 | import { UnityAssetExplorer } from '../unityAssetExplorer/unityAssetExplorerProvider'; 6 | import { MainViewProvider } from '../provider/mainViewProvider'; 7 | 8 | export function initialize(context: vscode.ExtensionContext) { 9 | const unityAssetExplorer = new UnityAssetExplorer(context); 10 | 11 | const mainViewProvider = new MainViewProvider(context.extensionUri); 12 | vscode.window.registerWebviewViewProvider('clover.mainView', mainViewProvider); 13 | 14 | const metaReferenceProvider = new MetaReferenceProvider(context); 15 | vscode.languages.registerCodeLensProvider('csharp', metaReferenceProvider); 16 | 17 | const unityMessageProvider = new UnityMessageProvider(context); 18 | vscode.languages.registerCodeLensProvider('csharp', unityMessageProvider); 19 | 20 | const unityUsageProvider = new UnityUsageProvider(context); 21 | vscode.languages.registerCodeLensProvider('csharp', unityUsageProvider); 22 | } 23 | -------------------------------------------------------------------------------- /src/parser/guidConnector.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import path = require('path'); 3 | 4 | var guidByPath: Map = new Map(); 5 | var pathByGuid: Map = new Map(); 6 | var locationByGuid: Map = new Map(); 7 | 8 | function mapGuid(filePath: string, guid: string) { 9 | guidByPath.set(filePath, guid); 10 | pathByGuid.set(guid, filePath); 11 | } 12 | 13 | function mapLocation(guid: string, location: vscode.Location) { 14 | if (locationByGuid.has(guid)) { 15 | var list = locationByGuid.get(guid); 16 | list?.push(location); 17 | } else { 18 | locationByGuid.set(guid, [location]); 19 | } 20 | } 21 | 22 | export function addGuid(filePath: string, guid: string) { 23 | var parsePath = path.parse(filePath); 24 | mapGuid(path.join(parsePath.dir, parsePath.name), guid); 25 | } 26 | 27 | export function getGuidByPath(filePath: string) { 28 | var guid = guidByPath.get(filePath); 29 | return guid ?? ''; 30 | } 31 | 32 | export function getPathByGuidMap() { 33 | return pathByGuid; 34 | } 35 | 36 | export function addLocation(guid: string, path: string, lineNumber: number) { 37 | const uri = vscode.Uri.file(path); 38 | const location = new vscode.Location(uri, new vscode.Position(lineNumber, 0)); 39 | mapLocation(guid, location); 40 | } 41 | 42 | export function getLocationsByGuid(guid: string) { 43 | return locationByGuid.get(guid); 44 | } 45 | 46 | export function refresh() { 47 | guidByPath.clear(); 48 | pathByGuid.clear(); 49 | locationByGuid.clear(); 50 | } -------------------------------------------------------------------------------- /src/unityAssetViewer/hierarchy.ts: -------------------------------------------------------------------------------- 1 | import * as UnityYamlParser from 'unity-yaml-parser'; 2 | 3 | var datas: Map = new Map(); 4 | var transforms: UnityYamlParser.UnityYamlData[] = []; 5 | 6 | export function getHierarchyHtmlTreeBase(fileId: string, name: string, objectId: string) { 7 | return ` 8 |
  • 9 |
    ${name}
    10 |
      11 |
    12 |
  • 13 | `; 14 | } 15 | 16 | export function initialize(path: string) { 17 | datas.clear(); 18 | transforms = []; 19 | datas = UnityYamlParser.parse(path); 20 | datas.forEach((data) => { 21 | if ((data.classId == "4" || data.classId == "224") && !data.stripped) { 22 | transforms.push(data); 23 | } 24 | }); 25 | 26 | return datas; 27 | } 28 | 29 | export function getTransforms() { 30 | return transforms; 31 | } 32 | 33 | export function getTransform(transform: UnityYamlParser.UnityYamlData) { 34 | switch (transform.classId) { 35 | case "4": 36 | return transform.data.Transform; 37 | case "224": 38 | return transform.data.RectTransform; 39 | } 40 | } 41 | 42 | export function getTransformGameObject(transform: UnityYamlParser.UnityYamlData) { 43 | switch (transform.classId) { 44 | case "4": 45 | return datas.get(transform.data.Transform.m_GameObject?.fileID.toString())?.data.GameObject; 46 | case "224": 47 | return datas.get(transform.data.RectTransform.m_GameObject?.fileID.toString())?.data.GameObject; 48 | } 49 | } -------------------------------------------------------------------------------- /src/parser/guidParser.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import * as readline from 'readline'; 3 | import * as GuidConnector from './guidConnector'; 4 | import * as Logger from '../vscodeUtils'; 5 | 6 | export function getGuid(data: string) { 7 | let regex = /guid: (\w+)/; 8 | let match = data.match(regex); 9 | return match ? match[1] : ''; 10 | } 11 | 12 | export async function parseUnityCsGuid(path: string) { 13 | try { 14 | const data = await fs.promises.readFile(path, { encoding: 'utf8' }); 15 | const guid = getGuid(data); 16 | GuidConnector.addGuid(path, guid); 17 | } catch (error) { 18 | Logger.outputLog(`Error reading meta file at ${path}: ${error}`); 19 | } 20 | } 21 | 22 | export async function parseUnityAssets(path: string) { 23 | let stream: fs.ReadStream | null = null; 24 | let rl: readline.Interface | null = null; 25 | 26 | try { 27 | stream = fs.createReadStream(path, { encoding: 'utf8' }); 28 | rl = readline.createInterface({ input: stream, crlfDelay: Infinity }); 29 | 30 | const lines: number[] = []; 31 | let i = 0; 32 | 33 | for await (const line of rl) { 34 | const guid = getGuid(line); 35 | if (guid !== '') { 36 | GuidConnector.addLocation(guid, path, i); 37 | } 38 | i++; 39 | } 40 | 41 | Logger.outputLog(`Parsed GUID Finished: ${path}`); 42 | 43 | return lines; 44 | } catch (error) { 45 | Logger.outputLog(`Error parsing Unity assets at ${path}: ${error}`); 46 | return []; 47 | } finally { 48 | if (rl) { 49 | rl.close(); 50 | } 51 | if (stream) { 52 | stream.destroy(); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /media/assetViewer.js: -------------------------------------------------------------------------------- 1 | var datas; 2 | var pathByGuid; 3 | 4 | function initialize(_datas, _pathByGuid) 5 | { 6 | datas = new Map(); 7 | pathByGuid = new Map(); 8 | Object.entries(_pathByGuid).forEach(([key, value]) => { 9 | pathByGuid.set(key, value); 10 | }); 11 | 12 | Object.entries(_datas).forEach(([key, value]) => { 13 | datas.set(key, value); 14 | }); 15 | 16 | document.querySelectorAll('.hierarchy-object').forEach(element => { 17 | element.addEventListener('click', function() { 18 | updateInspector(this.id); 19 | }); 20 | }); 21 | } 22 | 23 | function updateInspector(id) { 24 | var inspector = document.getElementById('inspector'); 25 | var gameObject = datas.get(id.toString()).data.GameObject; 26 | var components = gameObject.m_Component; 27 | 28 | inspector.innerHTML = ''; 29 | inspector.innerHTML += gameObjectBaseHtml(gameObject); 30 | if (components) { 31 | components.forEach(component => { 32 | inspector.innerHTML += getComponentHtml(datas.get(component.component.fileID)); 33 | }); 34 | } 35 | } 36 | 37 | function updateHierarchy(transforms) { 38 | const hierarchy = document.getElementById('hierarchy'); 39 | transforms.forEach(transform => { 40 | let myId; 41 | let children; 42 | if (transform.classId == "4") 43 | { 44 | myId = transform.fileId; 45 | children = transform.data.Transform.m_Children; 46 | } 47 | else 48 | { 49 | myId = transform.fileId; 50 | children = transform.data.RectTransform.m_Children; 51 | } 52 | 53 | const myElement = document.getElementById(myId + "-children"); 54 | if (children) 55 | { 56 | children.forEach(child => { 57 | const childId = child.fileID; 58 | const childElement = document.getElementById(childId); 59 | if (childElement) { 60 | myElement.appendChild(childElement); 61 | } 62 | }); 63 | } 64 | }); 65 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/visual-studio-marketplace/v/november.clover-unity)](https://marketplace.visualstudio.com/items?itemName=november.clover-unity) 2 | [![](https://img.shields.io/visual-studio-marketplace/d/november.clover-unity)](https://marketplace.visualstudio.com/items?itemName=november.clover-unity) 3 | ![](https://img.shields.io/github/license/dunward/clover) 4 | 5 | 6 | 7 | # Clover for Unity – VSCode Extension 8 | 9 | Clover is a powerful VSCode extension for unity project. With Clover, you can quickly find and manage asset references to ensure your code is efficient and well-organized. 10 | 11 | The Asset References system in Clover makes it easy to see all the places where a particular asset is referenced in your project. You can quickly navigate to those locations to make changes or updates to your code. This helps you to avoid errors, improve efficiency, and save time. 12 | 13 | Also Clover is support much more Unity utilities. 14 | 15 | Request and Ideas section where you can provide feedback and suggestions for future update, Whether you have a feature request or an idea for improving the extension, want to hear from you! Leave a comment on this repository issues tab. 16 | 17 | ## Meta Reference 18 | You can find meta references of the current .cs file using CodeLens. 19 | 20 | 21 | 22 | ## Method Usage 23 | You can track easily where methods are used in. 24 | 25 | 26 | 27 | ## Unity Event Functions Summary 28 | Display unity built-in event functions summary and documentation link to navigate to the Unity official documentation. 29 | 30 | 31 | 32 | ## Unity Asset Viewer (Experimental) 33 | You can check your prefab, scene file in VSCode. 34 | 35 | 36 | 37 | ## Unity Asset Explorer 38 | Unity scene, prefab file explorer 39 | 40 | 41 | 42 | 43 | ## Snippets 44 | Support unity code snippets. 45 | 46 | 47 | 48 | ## Attribute Helper 49 | You can find unity attributes. 50 | 51 | -------------------------------------------------------------------------------- /src/attributeHelper/attributeItem.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as VSCodeUtils from '../vscodeUtils'; 3 | 4 | export async function showAttributeHelper() { 5 | const selected = await vscode.window.showQuickPick(items, { placeHolder: 'Select attribute' }); 6 | 7 | if (selected) { 8 | VSCodeUtils.insertText(selected.attribute); 9 | } 10 | } 11 | 12 | interface AttributeItem extends vscode.QuickPickItem { 13 | attribute: string; 14 | } 15 | 16 | const items: AttributeItem[] = [ 17 | // field 18 | { 19 | label: '$(symbol-field) SerializeField', 20 | attribute: '[SerializeField]' 21 | }, 22 | { 23 | label: '$(symbol-field) Header', 24 | attribute: '[Header("string")]' 25 | }, 26 | { 27 | label: '$(symbol-field) HideInInspector', 28 | attribute: '[HideInInspector]' 29 | }, 30 | { 31 | label: '$(symbol-field) Range', 32 | attribute: '[Range(number, number)]' 33 | }, 34 | { 35 | label: '$(symbol-field) Space', 36 | attribute: '[Space(number)]' 37 | }, 38 | { 39 | label: '$(symbol-field) Mutiline', 40 | attribute: '[Mutiline(number)]', 41 | description: 'use for string field' 42 | }, 43 | { 44 | label: '$(symbol-field) TextArea', 45 | attribute: '[TextArea(number, number)]', 46 | description: 'use for string field' 47 | }, 48 | { 49 | label: '$(symbol-field) Tooltip', 50 | attribute: '[Tooltip("string")]' 51 | }, 52 | // method 53 | { 54 | label: '$(symbol-method) MenuItem', 55 | attribute: '[MenuItem("string")]', 56 | description: 'use for static method' 57 | }, 58 | { 59 | label: '$(symbol-method) ContextMenu', 60 | attribute: '[ContextMenu("string")]' 61 | }, 62 | // class 63 | { 64 | label: '$(symbol-class) Serializable', 65 | attribute: '[System.Serializable]' 66 | }, 67 | { 68 | label: '$(symbol-class) RequireComponent', 69 | attribute: '[RequireComponent(typeof(Type))]' 70 | }, 71 | { 72 | label: '$(symbol-class) CreateAssetMenu', 73 | attribute: '[CreateAssetMenu(fileName ="string", menuName ="string")]' 74 | }, 75 | { 76 | label: '$(symbol-class) ExecuteInEditMode', 77 | attribute: '[ExecuteInEditMode]' 78 | }, 79 | ]; -------------------------------------------------------------------------------- /src/parser/assetConnector.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as Logger from '../vscodeUtils'; 3 | import { MethodLocation, isSupportedAsset, parseUnityAssets } from './assetParser'; 4 | 5 | const methodLocationCache: Map = new Map(); 6 | 7 | function updateMethodLocationCache(parseResults: Map) { 8 | parseResults.forEach((locations, fullPath) => { 9 | const existingLocations = methodLocationCache.get(fullPath) || []; 10 | methodLocationCache.set(fullPath, [...existingLocations, ...locations]); 11 | }); 12 | logCacheContents(); 13 | } 14 | 15 | function logCacheContents() { 16 | if (methodLocationCache.size > 0) { 17 | methodLocationCache.forEach((locations, fullPath) => { 18 | locations.forEach(loc => { 19 | }); 20 | }); 21 | } 22 | } 23 | 24 | function splitFullPath(key: string): { namespace?: string; className: string; methodName: string } { 25 | const parts = key.split('.'); 26 | if (parts.length === 2) return { className: parts[0], methodName: parts[1] }; 27 | const methodName = parts.pop()!; 28 | const className = parts.pop()!; 29 | const namespace = parts.length ? parts.join('.') : undefined; 30 | return { namespace, className, methodName }; 31 | } 32 | 33 | export function addMethodLocation(fullPath: string, location: MethodLocation) { 34 | const existingLocations = methodLocationCache.get(fullPath) || []; 35 | methodLocationCache.set(fullPath, [...existingLocations, location]); 36 | logCacheContents(); 37 | } 38 | 39 | export function refresh(): void { 40 | methodLocationCache.clear(); 41 | } 42 | 43 | export function validateMethod(namespaceName: string, className: string, methodName: string) { 44 | const result = { isValid: false, componentIds: [] as string[], foundIn: [] as string[], locations: [] as MethodLocation[] }; 45 | methodLocationCache.forEach((locations, fullPath) => { 46 | const k = splitFullPath(fullPath); 47 | if (k.className === className && 48 | k.methodName === methodName && 49 | (!namespaceName || k.namespace === namespaceName)) { 50 | result.isValid = true; 51 | for (const loc of locations) { 52 | result.componentIds.push(loc.componentId); 53 | result.foundIn.push(loc.filePath); 54 | result.locations.push(loc); 55 | } 56 | } 57 | }); 58 | return result; 59 | } 60 | 61 | export function getMethodLocations(namespaceName: string, className: string, methodName: string) { 62 | const key2 = `${className}.${methodName}`; 63 | const key3 = namespaceName ? `${namespaceName}.${className}.${methodName}` : undefined; 64 | return (key3 && methodLocationCache.get(key3)) || methodLocationCache.get(key2); 65 | } -------------------------------------------------------------------------------- /src/unityReference/metaReferenceProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as GuidConnector from '../parser/guidConnector'; 3 | import * as Logger from '../vscodeUtils'; 4 | import * as CommandController from '../controller/commandController'; 5 | import path = require('path'); 6 | 7 | class MetaReferenceCodeLens extends vscode.CodeLens { 8 | constructor( 9 | public document: vscode.Uri, 10 | public file: string, 11 | range: vscode.Range 12 | ) { 13 | super(range); 14 | } 15 | } 16 | 17 | export class MetaReferenceProvider implements vscode.CodeLensProvider { 18 | private codeLenses: vscode.CodeLens[] = []; 19 | private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); 20 | public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event; 21 | 22 | constructor(context: vscode.ExtensionContext) { 23 | vscode.workspace.onDidChangeConfiguration((_) => { 24 | this._onDidChangeCodeLenses.fire(); 25 | }); 26 | 27 | CommandController.registerCommand(context, "clover.findMetaReference", () => this.showReferences()); 28 | 29 | Logger.outputLog("Initialize succeed CodeLens Provider"); 30 | } 31 | 32 | public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.CodeLens[] | Thenable { 33 | this.codeLenses = []; 34 | const regex = new RegExp(`class ${path.parse(document.fileName).name}`); 35 | const text = document.getText(); 36 | let matches; 37 | if ((matches = regex.exec(text)) !== null) { 38 | const line = document.lineAt(document.positionAt(matches.index).line); 39 | this.codeLenses.push(new MetaReferenceCodeLens(document.uri, document.uri.fsPath, line.range)); 40 | } 41 | return this.codeLenses; 42 | } 43 | 44 | public resolveCodeLens(codeLens: MetaReferenceCodeLens, token: vscode.CancellationToken) { 45 | const guid = GuidConnector.getGuidByPath(codeLens.file); 46 | var locations = GuidConnector.getLocationsByGuid(guid); 47 | var length = locations?.length || 0; 48 | 49 | codeLens.command = { 50 | title: this.getTitle(length), 51 | command: length == 0 ? "clover.noReferenceMessage" : "editor.action.showReferences", 52 | arguments: [codeLens.document, codeLens.range.start, locations], 53 | }; 54 | return codeLens; 55 | } 56 | 57 | showReferences() { 58 | var activeEditor = vscode.window.activeTextEditor; 59 | if (activeEditor) { 60 | const document = activeEditor.document; 61 | const filePath = document.uri.fsPath; 62 | const guid = GuidConnector.getGuidByPath(filePath); 63 | var locations = GuidConnector.getLocationsByGuid(guid); 64 | var length = locations?.length || 0; 65 | 66 | if (length > 0) { 67 | vscode.commands.executeCommand("editor.action.showReferences", document.uri, new vscode.Position(0, 0), locations); 68 | } else { 69 | vscode.commands.executeCommand("clover.noReferenceMessage"); 70 | } 71 | } 72 | } 73 | 74 | getTitle(length: number): string { 75 | if (length <= 1) { 76 | return `$(unity-symbol) ${length} meta reference`; 77 | } else { 78 | return `$(unity-symbol) ${length} meta references`; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /media/assetViewer.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'clover-icon'; 3 | src: url('clover-icon.woff') format('woff'); 4 | } 5 | 6 | div { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | 11 | div.left { 12 | width: 50%; 13 | float: left; 14 | overflow-y: auto; 15 | } 16 | 17 | div.right { 18 | width: 50%; 19 | float: right; 20 | overflow-y: auto; 21 | } 22 | 23 | .icon { 24 | font-family: 'clover-icon'; 25 | font-size: 16px; 26 | font-weight: normal; 27 | user-select: none; 28 | } 29 | 30 | .hierarchy-object { 31 | font-size: 16px; 32 | width: fit-content; 33 | padding: 1px; 34 | padding-left: 4px; 35 | padding-right: 4px; 36 | user-select: none; 37 | } 38 | 39 | .hierarchy-object:hover { 40 | background-color: var(--vscode-sideBar-background); 41 | border-radius: 5px; 42 | } 43 | 44 | .inspector-object, .inspector-game-object { 45 | font-size: 16px; 46 | width: 90%; 47 | min-height: 30px; 48 | height: auto; 49 | border: 2px solid var(--vscode-editorGroup-border); 50 | border-radius: 10px; 51 | border-style: dotted; 52 | margin-top: 5px; 53 | margin-bottom: 5px; 54 | padding: 5px; 55 | padding-left: 10px; 56 | } 57 | 58 | .inspector-game-object { 59 | display: flex; 60 | align-items: center; 61 | } 62 | 63 | .inspector .icon { 64 | font-size: 12px; 65 | margin-right: 3px; 66 | user-select: none; 67 | } 68 | 69 | .inspector-gameObject-base-left { 70 | flex: 0 0 auto; 71 | width: 32px; 72 | } 73 | 74 | .inspector-gameObject-base-left .icon { 75 | font-size: 32px; 76 | user-select: none; 77 | } 78 | 79 | .inspector-gameObject-base-right { 80 | flex: 1 1 auto; 81 | margin-left: 3px; 82 | } 83 | 84 | .flex-width { 85 | display: flex; 86 | justify-content: space-between; 87 | } 88 | 89 | .flex-width div { 90 | flex: 1; 91 | display: flex; 92 | align-items: center; 93 | } 94 | 95 | .property { 96 | display: flex; 97 | margin-bottom: 2px; 98 | } 99 | 100 | .property .name { 101 | width: 200px; 102 | } 103 | 104 | .property .content { 105 | display: flex; 106 | flex: 1; 107 | gap: 4px; 108 | align-items: center; 109 | } 110 | 111 | .property .content .label { 112 | width: fit-content; 113 | } 114 | 115 | .property .content .value { 116 | flex: 1; 117 | padding-left: 5px; 118 | background-color: var(--vscode-sideBar-background); 119 | border-radius: 5px; 120 | } 121 | 122 | .color-box { 123 | position: relative; 124 | width: 100%; 125 | height: 100%; 126 | border: 1px solid black; 127 | background-color: rgb( 128 | calc(var(--r) * 255), 129 | calc(var(--g) * 255), 130 | calc(var(--b) * 255) 131 | ); 132 | } 133 | 134 | .alpha-box { 135 | position: absolute; 136 | bottom: 0; 137 | left: 0; 138 | width: 100%; 139 | height: 3px; 140 | background-color: rgb( 141 | calc(var(--a) * 255), 142 | calc(var(--a) * 255), 143 | calc(var(--a) * 255) 144 | ); 145 | } 146 | 147 | h2 { 148 | margin-top: 0; 149 | margin-bottom: 0; 150 | } 151 | 152 | h3 { 153 | margin-left: 7px; 154 | margin-top: 0; 155 | margin-bottom: 0; 156 | } 157 | 158 | ul { 159 | list-style-type: none; 160 | margin: 0; 161 | padding: 0; 162 | margin-left: 14px; 163 | } -------------------------------------------------------------------------------- /src/controller/unityProjectController.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import path = require('path'); 3 | import * as GuidParser from '../parser/guidParser'; 4 | import * as AssetParser from '../parser/assetParser'; 5 | 6 | import * as UnityAssetConnector from '../unityAssetExplorer/unityAssetConnector'; 7 | import * as GuidConnector from '../parser/guidConnector'; 8 | import * as AssetConnector from '../parser/assetConnector'; 9 | 10 | import * as Logger from '../vscodeUtils'; 11 | 12 | var assetPath = ''; 13 | var refreshStartCallback: Function[] = []; 14 | var refreshEndCallback: Function[] = []; 15 | 16 | export async function initialize(workspacePath: string) { 17 | assetPath = path.join(workspacePath, 'Assets'); 18 | addRefreshStartCallback(() => GuidConnector.refresh()); 19 | addRefreshStartCallback(() => UnityAssetConnector.refresh()); 20 | addRefreshStartCallback(() => AssetConnector.refresh()); 21 | await refresh(); 22 | } 23 | 24 | export async function refresh() { 25 | Logger.outputLog("Start refreshing Unity project"); 26 | refreshStartCallback.forEach((callback) => callback()); 27 | await refreshUnityProject(assetPath); 28 | refreshEndCallback.forEach((callback) => callback()); 29 | Logger.outputLog("Unity project refreshed"); 30 | } 31 | 32 | export function addRefreshStartCallback(callback: Function) { 33 | refreshStartCallback.push(callback); 34 | } 35 | 36 | export function addRefreshEndCallback(callback: Function) { 37 | refreshEndCallback.push(callback); 38 | } 39 | 40 | export function isUnityProject(projectPath: string): boolean { 41 | var assetPath = path.join(projectPath, 'Assets'); 42 | var projectSettingsPath = path.join(projectPath, 'ProjectSettings'); 43 | 44 | return fs.existsSync(assetPath) && fs.existsSync(projectSettingsPath); 45 | } 46 | 47 | async function collectAllFiles(dirPath: string): Promise { 48 | let files: string[] = []; 49 | const dirents = await fs.promises.readdir(dirPath, { withFileTypes: true }); 50 | for (const dirent of dirents) { 51 | const fullPath = path.join(dirPath, dirent.name); 52 | if (dirent.isDirectory()) { 53 | files = files.concat(await collectAllFiles(fullPath)); 54 | } else { 55 | files.push(fullPath); 56 | } 57 | } 58 | return files; 59 | } 60 | 61 | async function refreshUnityProject(dirPath: string): Promise { 62 | try { 63 | const allFiles = await collectAllFiles(dirPath); 64 | const total = allFiles.length; 65 | let finished = 0; 66 | 67 | Logger.outputLog(`Refreshing Unity project: ${total} files`); 68 | 69 | const tasks = allFiles.map(async (filePath, index) => { 70 | const extname = path.extname(filePath); 71 | try { 72 | if (extname === '.meta' && filePath.endsWith('.cs.meta')) { 73 | await GuidParser.parseUnityCsGuid(filePath); 74 | } else if (extname === '.prefab' || extname === '.asset' || extname === '.unity') { 75 | await GuidParser.parseUnityAssets(filePath); 76 | if (extname !== '.asset') { 77 | await AssetParser.parseUnityAssets(filePath); 78 | UnityAssetConnector.addAssetPath(filePath); 79 | } 80 | } 81 | } finally { 82 | finished++; 83 | Logger.outputLog(`Refreshing Unity project: ${finished}/${total} (${filePath})`); 84 | } 85 | }); 86 | 87 | await Promise.all(tasks); 88 | Logger.outputLog(`Refreshing Unity project finished`); 89 | 90 | } catch (err) { 91 | Logger.outputLog(`Error while refreshing Unity project: ${err}`); 92 | throw err; 93 | } 94 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Change Log 2 | 3 | ## [1.0.2] 4 | ### Fixed 5 | - Fix method usage provider tracking didn't working namespace is empty 6 | - Fix too large file disturb C# intellisense 7 | 8 | ## [1.0.1] 9 | ### Added 10 | - Add vscode publish keywords 11 | 12 | ## [1.0.0] - Official Release! 🎉 13 | ### Added 14 | - Add method usages provider 15 | - Add collider/collision events on Unity Messages 16 | 17 | ### Fixed 18 | - Change main view background color 19 | 20 | ## [0.9.2] 21 | ### Fixed 22 | - Edit executeDocumentSymbolProvider to regex line matching 23 | - Fix unity message hover provider bug 24 | 25 | ## [0.9.1] 26 | ### Fixed 27 | - Fix load fail unity message format json file 28 | 29 | ## [0.9.0] 30 | ### Added 31 | - Add unity message provider 32 | 33 | ## [0.8.1] 34 | ### Changed 35 | - Edit clover main webview design 36 | 37 | ## [0.8.0] 38 | ### Added 39 | - Add loading view on asset viewer 40 | 41 | ## [0.7.1] 42 | ### Changed 43 | - Improve Rect Transform view 44 | 45 | ### Fixed 46 | - Unity multi line text quote parsing issue 47 | 48 | ## [0.7.0] 49 | ### Added 50 | - Add more types on unity asset viewer 51 | - Camera 52 | - AudioListener 53 | - MonoBehaviour (class name not support) 54 | 55 | ## [0.6.0] 56 | ### Added 57 | - Add inspector view in unity asset viewer 58 | 59 | ## [0.5.2] 60 | ### Fixed 61 | - Fix unity asset view when open prefab 62 | 63 | ## [0.5.1] 64 | ### Added 65 | - Add unity meta reference in right click context-menu 66 | - Add unity meta reference in command `> Clover: Find Meta Reference` 67 | 68 | ## [0.5.0] 69 | ### Added 70 | - Add unity asset viewer (prefab, scene only) 71 | - Add unity asset viewer menu icon (prefab, unity extension only) 72 | 73 | ## [0.4.0] 74 | ### Added 75 | - Add unity asset explorer 76 | 77 | ### Deleted 78 | - Remove meta explorer 79 | 80 | ### Fixed 81 | - Reference duplicated when unity project refresh 82 | 83 | ## [0.3.0] 84 | ### Added 85 | - Add attribute helper 86 | - Add 2D collision, trigger snippets 87 | 88 | ### Changed 89 | - Refactoring refresh project logic, code structure 90 | - Activity bar icon clover to unity logo 91 | 92 | ### Deleted 93 | - Clover activity bar always focus tab when extension initailized 94 | 95 | ## [0.2.0] 96 | ### Added 97 | - Add unity event snippets 98 | 99 | ## [0.1.2] 100 | ### Added 101 | - Add unity icon meta references codelens 102 | 103 | ### Fixed 104 | - Clover activated none unity project 105 | 106 | ## [0.1.1] 107 | ### Fixed 108 | - The reference provider view bug on Windows 109 | 110 | ## [0.1.0] 111 | ### Added 112 | - Add meta reference count 113 | - Add vscode reference view 114 | 115 | ### Changed 116 | - Improve unity refresh logic 117 | 118 | ## [0.0.12] - preview 119 | ### Added 120 | - Add clover activity bar 121 | - Add clover main web view 122 | - Add project name, version display on main view 123 | 124 | ### Changed 125 | - The meta explorer tree view moved on `Explorer` to `Clover` activity bar 126 | 127 | ## [0.0.11] - preview 128 | ### Changed 129 | - The reference path changed relative path 130 | 131 | ## [0.0.10] - preview 132 | ### Added 133 | - Now asset references list is display on tree view 134 | 135 | ## [0.0.9] - preview 136 | ### Changed 137 | - Output is not forced show up 138 | 139 | ## [0.0.8] - preview 140 | ### Changed 141 | - Unity loader logic is now working async 142 | 143 | ## [0.0.7] - preview 144 | ### Added 145 | - New feature **Code Lens** 146 | 147 | ### Changed 148 | - The output channel show when get message 149 | 150 | ## [0.0.6] - preview(hotfix) 151 | ### Fixed 152 | - Empty webview is appear in editor view 153 | 154 | ## [0.0.5] - preview 155 | ### Added 156 | - Reference finder from prefabs, scenes, assets 157 | - Valid workspace status check 158 | 159 | ## [0.0.4] - preview 160 | ### Changed 161 | - Logo images 162 | 163 | ## [0.0.3] - preview 164 | ### Added 165 | - Extension editor toolbar 166 | 167 | ## [0.0.2] - preview 168 | ### Fixed 169 | - Separate symbol(`/ or \`) os error 170 | 171 | ## [0.0.1] - preview 172 | ### Added 173 | - Unity csharp reference find from prefab -------------------------------------------------------------------------------- /src/unityAssetExplorer/unityAssetExplorerProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as UnityAssetConnector from './unityAssetConnector'; 3 | import * as unityProjectController from '../controller/unityProjectController'; 4 | import path = require('path'); 5 | import * as VSCodeUtils from '../vscodeUtils'; 6 | 7 | class UnityAssetProvider implements vscode.TreeDataProvider { 8 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 9 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 10 | 11 | private items: UnityAssetTreeItem[] = []; 12 | 13 | refresh(element?: UnityAssetTreeItem): void { 14 | this._onDidChangeTreeData.fire(element); 15 | } 16 | 17 | getTreeItem(element: UnityAssetTreeItem): vscode.TreeItem { 18 | if (element.resourceUri) { 19 | element.command = { command: 'vscode.open', title: 'Open', arguments: [element.resourceUri] }; 20 | } 21 | return element; 22 | } 23 | 24 | addItem(filePath: string): void { 25 | var replativePath = path.relative(VSCodeUtils.getWorkspacePath(), filePath); 26 | const parts = replativePath.split(path.sep); 27 | let currentItems = this.items; 28 | 29 | parts.forEach((part, index) => { 30 | let item = currentItems.find(item => item.label === part && item.depth === index); 31 | 32 | if (!item) { 33 | item = new UnityAssetTreeItem( 34 | part, 35 | index < parts.length - 1 ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None, 36 | index, 37 | index === parts.length - 1 ? vscode.Uri.file(filePath) : undefined 38 | ); 39 | currentItems.push(item); 40 | } 41 | 42 | if (!item.children) { 43 | item.children = []; 44 | } 45 | 46 | currentItems = item.children; 47 | }); 48 | 49 | this.refresh(); 50 | } 51 | 52 | getChildren(element?: UnityAssetTreeItem): Thenable { 53 | if (element) { 54 | return Promise.resolve(element.children ?? []); 55 | } else { 56 | return Promise.resolve(this.items); 57 | } 58 | } 59 | 60 | clearItems(): void { 61 | this.items = []; 62 | this.refresh(); 63 | } 64 | } 65 | 66 | class UnityAssetTreeItem extends vscode.TreeItem { 67 | constructor( 68 | public readonly label: string, 69 | public readonly collapsibleState: vscode.TreeItemCollapsibleState, 70 | public readonly depth: number, 71 | public resourceUri?: vscode.Uri, 72 | public children?: UnityAssetTreeItem[] 73 | ) { 74 | super(label, collapsibleState); 75 | 76 | if (path.parse(label).ext === '.unity') 77 | this.iconPath = new vscode.ThemeIcon("unity-symbol"); 78 | else if (path.parse(label).ext === '.prefab') 79 | this.iconPath = new vscode.ThemeIcon("unity-prefab"); 80 | this.label = label; 81 | this.resourceUri = resourceUri; 82 | if (collapsibleState === vscode.TreeItemCollapsibleState.None) { 83 | this.children = undefined; 84 | } else { 85 | this.children = children ? children : []; 86 | } 87 | } 88 | } 89 | 90 | export class UnityAssetExplorer { 91 | private unityAssetProvider: UnityAssetProvider; 92 | private unityAssetTreeView: vscode.TreeView; 93 | 94 | constructor(context: vscode.ExtensionContext) { 95 | this.unityAssetProvider = new UnityAssetProvider(); 96 | this.unityAssetTreeView = vscode.window.createTreeView('clover.unityAssetExplorer', { treeDataProvider: this.unityAssetProvider }); 97 | this.refresh(); 98 | context.subscriptions.push(this.unityAssetTreeView); 99 | unityProjectController.addRefreshEndCallback(() => this.refresh()); 100 | } 101 | 102 | public refresh(): void { 103 | this.unityAssetProvider.clearItems(); 104 | UnityAssetConnector.getAssetPaths().forEach((filePath) => { 105 | this.unityAssetProvider.addItem(filePath); 106 | }); 107 | } 108 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clover-unity", 3 | "displayName": "Clover", 4 | "description": "Unity Engine Extension - Easy to find prefabs, asset references, and more.", 5 | "version": "1.0.2", 6 | "publisher": "november", 7 | "license": "MIT", 8 | "engines": { 9 | "vscode": "^1.75.0" 10 | }, 11 | "categories": [ 12 | "Snippets", 13 | "Programming Languages", 14 | "Other" 15 | ], 16 | "keywords": [ 17 | "unity", 18 | "unity extension", 19 | "unity script reference", 20 | "unity asset reference", 21 | "game development" 22 | ], 23 | "icon": "resources/clover.png", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/dunward/clover" 27 | }, 28 | "activationEvents": [ 29 | "onStartupFinished" 30 | ], 31 | "preview": false, 32 | "main": "./out/extension.js", 33 | "contributes": { 34 | "icons": { 35 | "unity-symbol": { 36 | "description": "unity symbol icon", 37 | "default": { 38 | "fontPath": "./media/clover-icon.woff", 39 | "fontCharacter": "\\e902" 40 | } 41 | }, 42 | "unity-prefab": { 43 | "description": "unity prefab icon", 44 | "default": { 45 | "fontPath": "./media/clover-icon.woff", 46 | "fontCharacter": "\\e900" 47 | } 48 | }, 49 | "unity-game-object": { 50 | "description": "unity game object icon", 51 | "default": { 52 | "fontPath": "./media/clover-icon.woff", 53 | "fontCharacter": "\\e901" 54 | } 55 | } 56 | }, 57 | "snippets": [ 58 | { 59 | "language": "csharp", 60 | "path": "./unitySnippets.json" 61 | } 62 | ], 63 | "viewsContainers": { 64 | "activitybar": [ 65 | { 66 | "id": "clover-activitybar", 67 | "title": "Clover", 68 | "icon": "$(unity-symbol)" 69 | } 70 | ] 71 | }, 72 | "views": { 73 | "clover-activitybar": [ 74 | { 75 | "type": "webview", 76 | "id": "clover.mainView", 77 | "name": "Main", 78 | "icon": "resources/dark/clover.svg", 79 | "contextualTitle": "Clover", 80 | "when": "clover.workspace.valid" 81 | }, 82 | { 83 | "type": "tree", 84 | "id": "clover.unityAssetExplorer", 85 | "name": "Asset Explorer", 86 | "when": "clover.workspace.valid" 87 | } 88 | ] 89 | }, 90 | "commands": [ 91 | { 92 | "command": "clover.refreshUnityProject", 93 | "category": "Clover", 94 | "title": "Refresh Unity Project", 95 | "enablement": "clover.workspace.valid" 96 | }, 97 | { 98 | "command": "clover.showAttributeHelper", 99 | "category": "Clover", 100 | "title": "Show Attribute Helper", 101 | "enablement": "clover.workspace.valid" 102 | }, 103 | { 104 | "command": "clover.showUnityAssetViewer", 105 | "category": "Clover", 106 | "title": "Show Unity Asset Viewer", 107 | "icon": "$(unity-symbol)" 108 | }, 109 | { 110 | "command": "clover.findMetaReference", 111 | "category": "Clover", 112 | "title": "Find Meta Reference" 113 | } 114 | ], 115 | "menus": { 116 | "editor/title": [ 117 | { 118 | "command": "clover.showUnityAssetViewer", 119 | "group": "navigation@1", 120 | "when": "(resourceExtname == .prefab || resourceExtname == .unity) && clover.workspace.valid" 121 | } 122 | ], 123 | "editor/context": [ 124 | { 125 | "command": "clover.findMetaReference", 126 | "when": "clover.workspace.valid && resourceExtname == .cs", 127 | "group": "navigation" 128 | } 129 | ] 130 | } 131 | }, 132 | "scripts": { 133 | "vscode:prepublish": "npm run compile", 134 | "compile": "tsc -p ./", 135 | "watch": "tsc -watch -p ./", 136 | "pretest": "npm run compile && npm run lint", 137 | "lint": "eslint src --ext ts", 138 | "test": "node ./out/test/runTest.js" 139 | }, 140 | "devDependencies": { 141 | "@types/glob": "^7.2.0", 142 | "@types/mocha": "^9.1.1", 143 | "@types/node": "14.x", 144 | "@types/vscode": "^1.67.0", 145 | "@typescript-eslint/eslint-plugin": "^5.21.0", 146 | "@typescript-eslint/parser": "^5.21.0", 147 | "@vscode/test-electron": "^2.1.3", 148 | "eslint": "^8.14.0", 149 | "glob": "^8.0.1", 150 | "mocha": "^9.2.2", 151 | "typescript": "^4.6.4" 152 | }, 153 | "dependencies": { 154 | "unity-yaml-parser": "^0.1.7" 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/unityReference/unityUsageProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import path = require('path'); 3 | import * as fs from 'fs'; 4 | import { validateMethod } from '../parser/assetConnector'; 5 | 6 | class unityUsageProvider extends vscode.CodeLens { 7 | constructor( 8 | public document: vscode.Uri, 9 | public methodName: string, 10 | public usageInfo: any, 11 | range: vscode.Range 12 | ) { 13 | super(range); 14 | } 15 | } 16 | 17 | export class UnityUsageProvider implements vscode.CodeLensProvider { 18 | private codeLenses: vscode.CodeLens[] = []; 19 | private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); 20 | public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event; 21 | 22 | constructor(context: vscode.ExtensionContext) { 23 | vscode.workspace.onDidChangeConfiguration((_) => { 24 | this._onDidChangeCodeLenses.fire(); 25 | }); 26 | 27 | context.subscriptions.push( 28 | vscode.commands.registerCommand('unity.showUsage', async (methodName: string, usageInfo: any, range: vscode.Range) => { 29 | const editor = vscode.window.activeTextEditor; 30 | if (!editor) return; 31 | 32 | const references: vscode.Location[] = usageInfo.locations.map((location: any) => { 33 | return new vscode.Location( 34 | vscode.Uri.file(location.filePath), 35 | new vscode.Position(location.lineNumber - 1, 0) 36 | ); 37 | }); 38 | 39 | if (references.length > 0) { 40 | await vscode.commands.executeCommand( 41 | 'editor.action.showReferences', 42 | editor.document.uri, 43 | range.start, 44 | references 45 | ); 46 | } else { 47 | vscode.window.showInformationMessage('No references found.'); 48 | } 49 | }) 50 | ); 51 | } 52 | 53 | public async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { 54 | this.codeLenses = []; 55 | const text = document.getText(); 56 | 57 | let namespace = ''; 58 | const namespaceMatch = text.match(/namespace\s+([^\s{]+)/); 59 | if (namespaceMatch) { 60 | namespace = namespaceMatch[1]; 61 | } 62 | 63 | let currentClass = ''; 64 | const lines = text.split('\n'); 65 | 66 | for (let i = 0; i < lines.length; i++) { 67 | const line = lines[i].trim(); 68 | 69 | const classMatch = line.match(/class\s+(\w+)/); 70 | if (classMatch) { 71 | currentClass = classMatch[1]; 72 | } 73 | 74 | const methodMatch = line.match(/(?:public|private|protected)\s+(?:static\s+)?(?:async\s+)?[\w<>[\]]+\s+(\w+)\s*\([^)]*\)/); 75 | if (methodMatch && currentClass) { 76 | const methodName = methodMatch[1]; 77 | const fullName = `${namespace}.${currentClass}.${methodName}`; 78 | 79 | const usageInfo = validateMethod(namespace, currentClass, methodName); 80 | if (usageInfo.foundIn.length > 0) { 81 | const indent = lines[i].match(/^\s*/)?.[0].length ?? 0; 82 | const range = new vscode.Range( 83 | new vscode.Position(i, indent), 84 | new vscode.Position(i, lines[i].length) 85 | ); 86 | 87 | this.codeLenses.push(new unityUsageProvider( 88 | document.uri, 89 | fullName, 90 | usageInfo, 91 | range 92 | )); 93 | } 94 | } 95 | } 96 | 97 | return this.codeLenses; 98 | } 99 | 100 | public resolveCodeLens(codeLens: unityUsageProvider, token: vscode.CancellationToken) { 101 | codeLens.command = { 102 | title: this.getTitle(codeLens.usageInfo.foundIn.length), 103 | command: 'unity.showUsage', 104 | arguments: [ 105 | codeLens.methodName, 106 | codeLens.usageInfo, 107 | codeLens.range 108 | ] 109 | }; 110 | return codeLens; 111 | } 112 | 113 | getTitle(length: number): string { 114 | if (length <= 1) { 115 | return `${length} usage`; 116 | } else { 117 | return `${length} usages`; 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /src/provider/mainViewProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import fs = require('fs'); 3 | import path = require('path'); 4 | 5 | export class MainViewProvider implements vscode.WebviewViewProvider { 6 | private _extensionUri: vscode.Uri; 7 | 8 | constructor(extensionUri: vscode.Uri) { 9 | this._extensionUri = extensionUri; 10 | } 11 | 12 | resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, token: vscode.CancellationToken) { 13 | webviewView.webview.options = { 14 | enableScripts: true, 15 | localResourceRoots: [ 16 | this._extensionUri 17 | ] 18 | }; 19 | 20 | webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); 21 | } 22 | 23 | private _getHtmlForWebview(webview: vscode.Webview) { 24 | const logoPath = vscode.Uri.joinPath(this._extensionUri, 'resources', 'clover.png'); 25 | const logoUri = webview.asWebviewUri(logoPath); 26 | 27 | return ` 28 | 29 | 30 | 31 | 32 | 33 | 78 | 79 | 80 |
    81 | 82 |

    Clover

    83 |
    84 |
    85 |

    Have ideas for VS Code extensions that could enhance Unity development?
    Feel free to contribute to Clover!

    86 | 87 | 88 | 89 |
    90 | 91 | `; 92 | } 93 | } -------------------------------------------------------------------------------- /src/unityReference/unityMessageProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import path = require('path'); 3 | import * as fs from 'fs'; 4 | 5 | interface UnityMethodPattern { 6 | pattern: string; 7 | documentation: string; 8 | } 9 | 10 | interface UnityMessagesConfig { 11 | methodPatterns: UnityMethodPattern[]; 12 | } 13 | 14 | class unityMessageProvider extends vscode.CodeLens { 15 | constructor( 16 | public document: vscode.Uri, 17 | public methodName: string, 18 | public documentation: string, 19 | range: vscode.Range 20 | ) { 21 | super(range); 22 | } 23 | } 24 | 25 | export class UnityMessageProvider implements vscode.CodeLensProvider { 26 | private codeLenses: vscode.CodeLens[] = []; 27 | private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); 28 | public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event; 29 | private currentHoverProvider?: vscode.Disposable; 30 | private methodPatterns: UnityMethodPattern[] = []; 31 | 32 | constructor(context: vscode.ExtensionContext) { 33 | this.loadMethodPatterns(context); 34 | 35 | vscode.workspace.onDidChangeConfiguration((_) => { 36 | this.loadMethodPatterns(context); 37 | this._onDidChangeCodeLenses.fire(); 38 | }); 39 | 40 | context.subscriptions.push( 41 | vscode.commands.registerCommand('unity.showMessage', async (methodName: string, documentation: string, range: vscode.Range) => { 42 | const editor = vscode.window.activeTextEditor; 43 | if (!editor) return; 44 | 45 | if (this.currentHoverProvider) { 46 | this.currentHoverProvider.dispose(); 47 | } 48 | 49 | editor.selection = new vscode.Selection(range.start, range.start); 50 | 51 | const hover = new vscode.Hover([ 52 | new vscode.MarkdownString(documentation) 53 | ], range); 54 | 55 | this.currentHoverProvider = vscode.languages.registerHoverProvider({ scheme: 'file', language: 'csharp' }, { 56 | provideHover: (document, position, token) => { 57 | if (range.contains(position)) { 58 | return hover; 59 | } 60 | return null; 61 | } 62 | }); 63 | 64 | await vscode.commands.executeCommand('editor.action.showHover'); 65 | }) 66 | ); 67 | } 68 | 69 | private loadMethodPatterns(context: vscode.ExtensionContext) { 70 | const configPath = path.join(context.extensionPath, 'unityMessages.json'); 71 | try { 72 | const configContent = fs.readFileSync(configPath, 'utf8'); 73 | const config: UnityMessagesConfig = JSON.parse(configContent); 74 | this.methodPatterns = config.methodPatterns; 75 | } catch (error) { 76 | console.error('Failed to load Unity messages configuration:', error); 77 | this.methodPatterns = []; 78 | } 79 | } 80 | 81 | private isUnityMethod(methodName: string): UnityMethodPattern | undefined { 82 | return this.methodPatterns.find(pattern => 83 | new RegExp(pattern.pattern).test(methodName) 84 | ); 85 | } 86 | 87 | public async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { 88 | this.codeLenses = []; 89 | 90 | const text = document.getText(); 91 | const lines = text.split('\n'); 92 | 93 | for (let i = 0; i < lines.length; i++) { 94 | const line = lines[i].trim(); 95 | const methodPattern = this.isUnityMethod(line); 96 | 97 | if (methodPattern) { 98 | const indent = lines[i].match(/^\s*/)?.[0].length ?? 0; 99 | const range = new vscode.Range( 100 | new vscode.Position(i, indent), 101 | new vscode.Position(i, lines[i].length) 102 | ); 103 | 104 | this.codeLenses.push(new unityMessageProvider( 105 | document.uri, 106 | lines[i].trim(), 107 | methodPattern.documentation, 108 | range 109 | )); 110 | } 111 | } 112 | 113 | return this.codeLenses; 114 | } 115 | 116 | public resolveCodeLens(codeLens: unityMessageProvider, token: vscode.CancellationToken) { 117 | codeLens.command = { 118 | title: `$(unity-symbol) Unity Message`, 119 | command: 'unity.showMessage', 120 | arguments: [codeLens.methodName, codeLens.documentation, codeLens.range] 121 | }; 122 | return codeLens; 123 | } 124 | } -------------------------------------------------------------------------------- /src/parser/assetParser.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import * as Logger from '../vscodeUtils'; 3 | import * as AssetConnector from './assetConnector'; 4 | 5 | interface MethodCall { 6 | targetId: string; 7 | targetAssemblyTypeName: string; 8 | methodName: string; 9 | } 10 | 11 | export interface MethodLocation { 12 | filePath: string; 13 | lineNumber: number; 14 | componentId: string; 15 | } 16 | 17 | const SUPPORTED_EXTENSIONS = ['.unity', '.prefab']; 18 | const COMPONENT_PATTERN = /^--- !u!\d+ &(\d+)\s*\n([^:]+):/gm; 19 | const METHOD_CALLS_SECTION_PATTERN = /m_PersistentCalls:\s*\n\s*m_Calls:\s*((?:\s*-[^-]*)*)/g; 20 | const SINGLE_METHOD_CALL_PATTERN = /^\s*-\s*m_Target:\s*\{[^}]*fileID:\s*(-?\d+)[^}]*\}\s*[\r\n]+\s*m_TargetAssemblyTypeName:\s*([^\r\n,]+)(?:,[^\r\n]+)?\s*[\r\n]+\s*m_MethodName:\s*([^\r\n]+)/gm; 21 | const ASSEMBLY_TYPE_LINE_PATTERN = /\s*m_TargetAssemblyTypeName:/; 22 | 23 | export function isSupportedAsset(filePath: string): boolean { 24 | const ext = filePath.toLowerCase().split('.').pop(); 25 | return SUPPORTED_EXTENSIONS.includes(`.${ext}`); 26 | } 27 | 28 | function getMethodFullPath(assemblyTypeName: string, methodName: string): string { 29 | const [typeName] = assemblyTypeName.split(',').map(s => s.trim()); 30 | return `${typeName}.${methodName}`; 31 | } 32 | 33 | function buildNewlineIndex(s: string): number[] { 34 | const arr: number[] = []; 35 | let i = 0; 36 | while (true) { 37 | const p = s.indexOf('\n', i); 38 | if (p === -1) break; 39 | arr.push(p); 40 | i = p + 1; 41 | } 42 | return arr; 43 | } 44 | 45 | function indexToLine(nl: number[], idx: number): number { 46 | let lo = 0, hi = nl.length; 47 | while (lo < hi) { 48 | const mid = (lo + hi) >>> 1; 49 | if (nl[mid] <= idx) lo = mid + 1; else hi = mid; 50 | } 51 | return lo + 1; 52 | } 53 | 54 | export async function parseData(fileContent: string, filePath: string): Promise> { 55 | const methodLocations = new Map(); 56 | const nl = buildNewlineIndex(fileContent); 57 | 58 | for (const match of fileContent.matchAll(COMPONENT_PATTERN)) { 59 | const id = match[1]; 60 | const componentStart = match.index! + match[0].length; 61 | const componentStartLine = indexToLine(nl, match.index!); 62 | 63 | const nextComponentStart = fileContent.indexOf('--- !u!', componentStart); 64 | const end = nextComponentStart === -1 ? fileContent.length : nextComponentStart; 65 | const componentContent = fileContent.slice(componentStart, end); 66 | 67 | const methodSectionMatch = METHOD_CALLS_SECTION_PATTERN.exec(componentContent); 68 | if (!methodSectionMatch) continue; 69 | 70 | const methodsSection = methodSectionMatch[1]; 71 | const methodSectionStartLine = indexToLine(nl, componentStart + methodSectionMatch.index); 72 | 73 | for (const methodMatch of methodsSection.matchAll(SINGLE_METHOD_CALL_PATTERN)) { 74 | const targetAssemblyTypeName = methodMatch[2]; 75 | const methodName = methodMatch[3]; 76 | 77 | const beforeLen = methodMatch.index!; 78 | let linesBefore = 0; 79 | for (let i = 0; i < beforeLen; ) { 80 | const p = methodsSection.indexOf('\n', i); 81 | if (p === -1 || p >= beforeLen) break; 82 | linesBefore++; 83 | i = p + 1; 84 | } 85 | 86 | const fullMethodMatch = methodMatch[0]; 87 | const asmIdx = fullMethodMatch.search(ASSEMBLY_TYPE_LINE_PATTERN); 88 | let assemblyTypeLineOffset = 0; 89 | if (asmIdx >= 0) { 90 | for (let i = 0; i < asmIdx; ) { 91 | const p = fullMethodMatch.indexOf('\n', i); 92 | if (p === -1 || p >= asmIdx) break; 93 | assemblyTypeLineOffset++; 94 | i = p + 1; 95 | } 96 | } 97 | 98 | const exactLineNumber = methodSectionStartLine + linesBefore + assemblyTypeLineOffset; 99 | const fullPath = getMethodFullPath(targetAssemblyTypeName.trim(), methodName.trim()); 100 | const location: MethodLocation = { filePath, lineNumber: exactLineNumber, componentId: id }; 101 | 102 | const arr = methodLocations.get(fullPath); 103 | if (arr) arr.push(location); 104 | else methodLocations.set(fullPath, [location]); 105 | 106 | AssetConnector.addMethodLocation(fullPath, location); 107 | } 108 | } 109 | 110 | return methodLocations; 111 | } 112 | 113 | export async function parseUnityAssets(filePath: string): Promise | null> { 114 | try { 115 | const data = await fs.promises.readFile(filePath, { encoding: 'utf8' }); 116 | return await parseData(data, filePath); 117 | } catch (error) { 118 | Logger.outputLog(`Error parsing Unity asset at ${filePath}: ${error}`); 119 | return null; 120 | } 121 | } -------------------------------------------------------------------------------- /src/unityAssetViewer/unityAssetViewer.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as Hierarchy from './hierarchy'; 3 | import * as CommandController from '../controller/commandController'; 4 | import * as VSCodeUtils from '../vscodeUtils'; 5 | import * as GuidConnector from '../parser/guidConnector'; 6 | import path = require('path'); 7 | 8 | export function init(context: vscode.ExtensionContext) { 9 | CommandController.registerCommand(context, 'clover.showUnityAssetViewer', () => UnityAssetViewer.show(context, VSCodeUtils.getActiveFilePath())); 10 | 11 | vscode.workspace.onDidOpenTextDocument((document: vscode.TextDocument) => { 12 | const fileName = document.fileName; 13 | if (fileName.endsWith(".prefab") || fileName.endsWith(".unity")) { 14 | vscode.window.showInformationMessage(`This file can be open with unity asset viewer. Do you want to open this file?`, 'YES').then((selection) => { 15 | if (selection === 'YES') { 16 | UnityAssetViewer.show(context, fileName); 17 | } 18 | }); 19 | } 20 | }); 21 | } 22 | 23 | class UnityAssetViewer { 24 | public static readonly viewType = 'unityAssetViewer'; 25 | 26 | public static show(context: vscode.ExtensionContext, path: string) { 27 | const extensionUri = context.extensionUri; 28 | 29 | const panel = vscode.window.createWebviewPanel( 30 | this.viewType, 31 | 'Unity Asset Viewer', 32 | vscode.ViewColumn.Active, 33 | { 34 | enableScripts: true, 35 | localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')], 36 | } 37 | ); 38 | 39 | panel.webview.html = this.getLoadingHtml(); 40 | 41 | const fontUri = panel.webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'clover-icon.woff')) 42 | const cssUri = panel.webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'assetViewer.css')) 43 | const jsUri = panel.webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'assetViewer.js')) 44 | const jsComponentsUri = panel.webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'assetViewerComponents.js')) 45 | 46 | panel.webview.html = this.getHtmlForWebview(path, fontUri, cssUri, jsUri, jsComponentsUri); 47 | } 48 | 49 | private static getLoadingHtml() { 50 | return ` 51 | 52 | 53 | 54 | 55 | 85 | 86 | 87 |
    88 |
    89 |
    90 | 91 | `; 92 | } 93 | 94 | private static getHtmlForWebview(filePath: string, fontUri: vscode.Uri, hierarchyCss: vscode.Uri, assetViewerJs: vscode.Uri, assetViewerComponentsJs: vscode.Uri) { 95 | var datas = Hierarchy.initialize(filePath); 96 | var transforms = Hierarchy.getTransforms(); 97 | var trees = transforms.map((transform) => { 98 | return Hierarchy.getHierarchyHtmlTreeBase(transform.fileId, Hierarchy.getTransformGameObject(transform)?.m_Name ?? "Unknown Object", Hierarchy.getTransform(transform)?.m_GameObject.fileID ?? "-1"); 99 | }); 100 | return ` 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
    113 |
    114 |

    Hierarchy

    115 |

    ${path.basename(filePath)}

    116 |
      117 |
    • 118 | ${trees.join('')} 119 |
    • 120 |
    121 |
    122 |

    Inspector (read-only)

    123 |
    124 |
    125 |
    126 |
    127 | 131 | 132 | 133 | `; 134 | } 135 | } -------------------------------------------------------------------------------- /unityMessages.json: -------------------------------------------------------------------------------- 1 | { 2 | "methodPatterns": [ 3 | { 4 | "pattern": "\\bvoid\\s+Awake\\s*\\(\\s*\\)\\s*$", 5 | "documentation": "## Summary\n\nUnity calls Awake when an enabled script instance is being loaded.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.Awake.html)" 6 | }, 7 | { 8 | "pattern": "\\b(?:void|IEnumerator)\\s+Start\\s*\\(\\s*\\)\\s*$", 9 | "documentation": "## Summary\n\nStart is called on the frame when a script is enabled just before any of the Update methods are called the first time.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.Start.html)" 10 | }, 11 | { 12 | "pattern": "\\bvoid\\s+Update\\s*\\(\\s*\\)\\s*$", 13 | "documentation": "## Summary\n\nUpdate is called every frame, if the MonoBehaviour is enabled.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.Update.html)" 14 | }, 15 | { 16 | "pattern": "\\bvoid\\s+FixedUpdate\\s*\\(\\s*\\)\\s*$", 17 | "documentation": "## Summary\n\nFrame-rate independent MonoBehaviour.FixedUpdate message for physics calculations.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.FixedUpdate.html)" 18 | }, 19 | { 20 | "pattern": "\\bvoid\\s+LateUpdate\\s*\\(\\s*\\)\\s*$", 21 | "documentation": "## Summary\n\nLateUpdate is called every frame, if the Behaviour is enabled.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.LateUpdate.html)" 22 | }, 23 | { 24 | "pattern": "\\bvoid\\s+OnGUI\\s*\\(\\s*\\)\\s*$", 25 | "documentation": "## Summary\n\nOnGUI is called for rendering and handling GUI events.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.OnGUI.html)" 26 | }, 27 | { 28 | "pattern": "\\bvoid\\s+OnEnable\\s*\\(\\s*\\)\\s*$", 29 | "documentation": "## Summary\n\nThis function is called when the object becomes enabled and active.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.OnEnable.html)" 30 | }, 31 | { 32 | "pattern": "\\bvoid\\s+OnDisable\\s*\\(\\s*\\)\\s*$", 33 | "documentation": "## Summary\n\nThis function is called when the behaviour becomes disabled.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.OnDisable.html)" 34 | }, 35 | { 36 | "pattern": "\\bvoid\\s+OnDestroy\\s*\\(\\s*\\)\\s*$", 37 | "documentation": "## Summary\n\nDestroying the attached Behaviour will result in the game or Scene receiving OnDestroy.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.OnEnable.html)" 38 | }, 39 | { 40 | "pattern": "\\bvoid\\s+OnTriggerEnter\\s*\\(\\s*Collider\\s+\\w+\\s*\\)\\s*$", 41 | "documentation": "## Summary\n\nCalled when a Collider with the Collider.isTrigger property overlaps another Collider.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider.OnTriggerEnter.html)" 42 | }, 43 | { 44 | "pattern": "\\bvoid\\s+OnTriggerExit\\s*\\(\\s*Collider\\s+\\w+\\s*\\)\\s*$", 45 | "documentation": "## Summary\n\nOnTriggerExit is called when the Collider other has stopped touching the trigger.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider.OnTriggerExit.html)" 46 | }, 47 | { 48 | "pattern": "\\bvoid\\s+OnTriggerStay\\s*\\(\\s*Collider\\s+\\w+\\s*\\)\\s*$", 49 | "documentation": "## Summary\n\nOnTriggerStay is called almost all the frames for every Collider other that is touching the trigger.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider.OnTriggerStay.html)" 50 | }, 51 | { 52 | "pattern": "\\bvoid\\s+OnCollisionEnter\\s*\\(\\s*Collision\\s+\\w+\\s*\\)\\s*$", 53 | "documentation": "## Summary\n\nOnCollisionEnter is called when this collider/rigidbody has begun touching another rigidbody/collider.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider.OnCollisionEnter.html)" 54 | }, 55 | { 56 | "pattern": "\\bvoid\\s+OnCollisionExit\\s*\\(\\s*Collision\\s+\\w+\\s*\\)\\s*$", 57 | "documentation": "## Summary\n\nOnCollisionExit is called when this collider/rigidbody has stopped touching another rigidbody/collider.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider.OnCollisionExit.html)" 58 | }, 59 | { 60 | "pattern": "\\bvoid\\s+OnCollisionStay\\s*\\(\\s*Collision\\s+\\w+\\s*\\)\\s*$", 61 | "documentation": "## Summary\n\nOnCollisionStay is called once per frame for every Collider or Rigidbody that touches another Collider or Rigidbody.\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider.OnCollisionStay.html)" 62 | }, 63 | { 64 | "pattern": "\\bvoid\\s+OnTriggerEnter2D\\s*\\(\\s*Collider2D\\s+\\w+\\s*\\)\\s*$", 65 | "documentation": "## Summary\n\nSent when another object enters a trigger collider attached to this object (2D physics only).\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider2D.OnTriggerEnter2D.html)" 66 | }, 67 | { 68 | "pattern": "\\bvoid\\s+OnTriggerExit2D\\s*\\(\\s*Collider2D\\s+\\w+\\s*\\)\\s*$", 69 | "documentation": "## Summary\n\nSent when another object leaves a trigger collider attached to this object (2D physics only).\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider2D.OnTriggerExit2D.html)" 70 | }, 71 | { 72 | "pattern": "\\bvoid\\s+OnTriggerStay2D\\s*\\(\\s*Collider2D\\s+\\w+\\s*\\)\\s*$", 73 | "documentation": "## Summary\n\nSent each frame where another object is within a trigger collider attached to this object (2D physics only).\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider2D.OnTriggerStay2D.html)" 74 | }, 75 | { 76 | "pattern": "\\bvoid\\s+OnCollisionEnter2D\\s*\\(\\s*Collision2D\\s+\\w+\\s*\\)\\s*$", 77 | "documentation": "## Summary\n\nSent when an incoming collider makes contact with this object's collider (2D physics only).\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider2D.OnCollisionEnter2D.html)" 78 | }, 79 | { 80 | "pattern": "\\bvoid\\s+OnCollisionExit2D\\s*\\(\\s*Collision2D\\s+\\w+\\s*\\)\\s*$", 81 | "documentation": "## Summary\n\nSent when a collider on another object stops touching this object's collider (2D physics only).\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider2D.OnCollisionExit2D.html)" 82 | }, 83 | { 84 | "pattern": "\\bvoid\\s+OnCollisionStay2D\\s*\\(\\s*Collision2D\\s+\\w+\\s*\\)\\s*$", 85 | "documentation": "## Summary\n\nSent each frame where a collider on another object is touching this object's collider (2D physics only).\n\n[Learn More](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Collider2D.OnCollisionStay2D.html)" 86 | } 87 | ] 88 | } -------------------------------------------------------------------------------- /media/assetViewerComponents.js: -------------------------------------------------------------------------------- 1 | function gameObjectBaseHtml(gameObject) { 2 | return ` 3 |
    4 |
    5 | 6 |
    7 |
    8 |
    9 | ${getCheckBoxIcon(gameObject.m_IsActive)}${gameObject.m_Name} 10 |
    11 |
    12 |
    Tag ${gameObject.m_TagString}
    13 |
    Layer ${gameObject.m_Layer}
    14 |
    15 |
    16 |
    17 | `; 18 | } 19 | 20 | function getComponentHtml(component) { 21 | switch (component.classId) { 22 | case "4": 23 | return getTransformHtml(component.data.Transform); 24 | case "20": 25 | return getCameraHtml(component.data.Camera); 26 | case "81": 27 | return getAudioListenerHtml(component.data.AudioListener); 28 | case "114": 29 | return getMonoBehaviourHtml(component.data.MonoBehaviour); 30 | case "224": 31 | return getRectTransformHtml(component.data.RectTransform); 32 | default: 33 | return getUnknownComponentHtml(component); 34 | } 35 | } 36 | 37 | function getTransformHtml(component) { 38 | return ` 39 |
    40 |
    Transform
    41 |
    42 |
    Position
    43 |
    44 |
    X
    ${component.m_LocalPosition.x}
    45 |
    Y
    ${component.m_LocalPosition.y}
    46 |
    Z
    ${component.m_LocalPosition.z}
    47 |
    48 |
    49 |
    50 |
    Rotation
    51 |
    52 |
    X
    ${component.m_LocalRotation.x}
    53 |
    Y
    ${component.m_LocalRotation.y}
    54 |
    Z
    ${component.m_LocalRotation.z}
    55 |
    56 |
    57 |
    58 |
    Scale
    59 |
    60 |
    X
    ${component.m_LocalScale.x}
    61 |
    Y
    ${component.m_LocalScale.y}
    62 |
    Z
    ${component.m_LocalScale.z}
    63 |
    64 |
    65 |
    66 | `; 67 | } 68 | 69 | function getCameraHtml(component) { 70 | return ` 71 |
    72 |
    ${getCheckBoxIcon(component.m_Enabled)}Camera
    73 |
    74 |
    Clear Flags
    75 |
    76 | ${component.m_ClearFlags} 77 |
    78 |
    79 |
    80 |
    Background
    81 |
    82 |
    83 |   84 |
    85 |
    86 |
    87 |
    88 |
    89 |
    Projection
    90 |
    91 | ${component.orthographic == 0 ? "Perspective" : "Orthographic"} 92 |
    93 |
    94 | ${getProjectionView(component.orthographic, component)} 95 |
    96 |
    Clipping Planes
    97 |
    98 |
    Near
    ${component["near clip plane"]}
    99 |
    Far
    ${component["far clip plane"]}
    100 |
    101 |
    102 |
    103 |
    Viewport Rect
    104 |
    105 |
    X
    ${component.m_NormalizedViewPortRect.x}
    106 |
    Y
    ${component.m_NormalizedViewPortRect.y}
    107 |
    W
    ${component.m_NormalizedViewPortRect.width}
    108 |
    H
    ${component.m_NormalizedViewPortRect.height}
    109 |
    110 |
    111 |
    112 |
    Depth
    113 |
    114 | ${component.m_Depth} 115 |
    116 |
    117 |
    118 |
    Rendering Path
    119 |
    120 | ${getRendringPath(component)} 121 |
    122 |
    123 |
    124 |
    Occlusion Culling
    125 |
    126 | ${getCheckBoxIcon(component.m_UseOcclusionCulling)}  127 |
    128 |
    129 |
    130 |
    Allow Dynamic Resolution
    131 |
    132 | ${getCheckBoxIcon(component.m_AllowDynamicResolution)}  133 |
    134 |
    135 |
    136 | 137 | `; 138 | } 139 | 140 | function getRendringPath(component) { 141 | if (component.m_RenderingPath == -1) { 142 | return "Use Player Settings"; 143 | } else if (component.m_RenderingPath == 0) { 144 | return "Legacy Vertex Lit"; 145 | } else if (component.m_RenderingPath == 1) { 146 | return "Forward"; 147 | } else if (component.m_RenderingPath == 2) { 148 | return "Legacy Deferred (light prepass)"; 149 | } else if (component.m_RenderingPath == 3) { 150 | return "Deferred"; 151 | } 152 | } 153 | 154 | function getProjectionView(orthographic, component) { 155 | if (orthographic == 0) { 156 | return ` 157 |
    158 |
    FOV Axis
    159 |
    160 | ${component.m_FOVAxisMode == 0 ? "Vertical" : "Horizontal"} 161 |
    162 |
    163 |
    164 |
    Field of View
    165 |
    166 | ${component["field of view"]} 167 |
    168 |
    169 | `; 170 | } else { 171 | return ` 172 |
    173 |
    Size
    174 |
    175 | ${component["orthographic size"]} 176 |
    177 |
    178 | `; 179 | } 180 | } 181 | 182 | function getAudioListenerHtml(component) { 183 | return ` 184 |
    185 |
    ${getCheckBoxIcon(component.m_Enabled)}Audio Listener
    186 |
    187 | `; 188 | 189 | } 190 | 191 | function getMonoBehaviourHtml(component) { 192 | var filePath = pathByGuid.get(component.m_Script.guid); 193 | return ` 194 |
    195 |
    ${getCheckBoxIcon(component.m_Enabled)}${filePath === undefined ? "Unknown Script" : filePath.split(/[/\\]/).pop()}
    196 |
    197 | `; 198 | } 199 | 200 | function getRectTransformHtml(component) { 201 | return ` 202 |
    203 |
    Rect Transform
    204 |
    205 |
    Position
    206 |
    207 |
    X
    ${component.m_LocalPosition.x}
    208 |
    Y
    ${component.m_LocalPosition.y}
    209 |
    Z
    ${component.m_LocalPosition.z}
    210 |
    211 |
    212 |
    213 |
    214 |
    215 |
    W Delta
    ${component.m_SizeDelta.x}
    216 |
    H Delta
    ${component.m_SizeDelta.y}
    217 |
    218 |
    219 |
    220 |
    Pivot
    221 |
    222 |
    X
    ${component.m_Pivot.x}
    223 |
    Y
    ${component.m_Pivot.y}
    224 |
    225 |
    226 |
    227 |
    Rotation
    228 |
    229 |
    X
    ${component.m_LocalRotation.x}
    230 |
    Y
    ${component.m_LocalRotation.y}
    231 |
    Z
    ${component.m_LocalRotation.z}
    232 |
    233 |
    234 |
    235 |
    Scale
    236 |
    237 |
    X
    ${component.m_LocalScale.x}
    238 |
    Y
    ${component.m_LocalScale.y}
    239 |
    Z
    ${component.m_LocalScale.z}
    240 |
    241 |
    242 |
    243 | `; 244 | } 245 | 246 | function getUnknownComponentHtml(component) { 247 | return ` 248 |
    249 |
    Unsupported component type : ${component.classId}
    250 | 251 |
    252 | `; 253 | } 254 | 255 | function getCheckBoxIcon(isActive) { 256 | if (isActive == 1) { 257 | return ''; 258 | } else { 259 | return ''; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /unitySnippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "UnityEngine.MonoBehaviour.Awake": { 3 | "prefix": "Awake", 4 | "body": [ 5 | "private void Awake()", 6 | "{", 7 | "\t$1", 8 | "}" 9 | ], 10 | "description": "Awake is always called before any Start functions. This allows you to order initialization of scripts." 11 | }, 12 | "UnityEngine.MonoBehaviour.OnEnable": { 13 | "prefix": "OnEnable", 14 | "body": [ 15 | "private void OnEnable()", 16 | "{", 17 | "\t$1", 18 | "}" 19 | ], 20 | "description": "This function is called when the object becomes enabled and active." 21 | }, 22 | "UnityEngine.MonoBehaviour.Reset": { 23 | "prefix": "Reset", 24 | "body": [ 25 | "private void Reset()", 26 | "{", 27 | "\t$1", 28 | "}" 29 | ], 30 | "description": "Reset is called when the user hits the Reset button in the Inspector's context menu or when adding the component the first time. This function is only called in editor mode." 31 | }, 32 | "UnityEngine.MonoBehaviour.Start": { 33 | "prefix": "Start", 34 | "body": [ 35 | "private void Start()", 36 | "{", 37 | "\t$1", 38 | "}" 39 | ], 40 | "description": "Start is called on the frame when a script is enabled just before any of the Update methods are called the first time." 41 | }, 42 | "UnityEngine.MonoBehaviour.FixedUpdate": { 43 | "prefix": "FixedUpdate", 44 | "body": [ 45 | "private void FixedUpdate()", 46 | "{", 47 | "\t$1", 48 | "}" 49 | ], 50 | "description": "MonoBehaviour.FixedUpdate has the frequency of the physics system; it is called every fixed frame-rate frame." 51 | }, 52 | "UnityEngine.MonoBehaviour.OnTriggerEnter": { 53 | "prefix": "OnTriggerEnter", 54 | "body": [ 55 | "private void OnTriggerEnter(Collider collider)", 56 | "{", 57 | "\t$1", 58 | "}" 59 | ], 60 | "description": "OnTriggerEnter happens on the FixedUpdate function when two GameObjects collide." 61 | }, 62 | "UnityEngine.MonoBehaviour.OnTriggerEnter2D": { 63 | "prefix": "OnTriggerEnter2D", 64 | "body": [ 65 | "private void OnTriggerEnter2D(Collider2D collider)", 66 | "{", 67 | "\t$1", 68 | "}" 69 | ], 70 | "description": "Sent when another object enters a trigger collider attached to this object (2D physics only)." 71 | }, 72 | "UnityEngine.MonoBehaviour.OnTriggerStay": { 73 | "prefix": "OnTriggerStay", 74 | "body": [ 75 | "private void OnTriggerStay(Collider collider)", 76 | "{", 77 | "\t$1", 78 | "}" 79 | ], 80 | "description": "OnTriggerStay is called almost all the frames for every Collider other that is touching the trigger." 81 | }, 82 | "UnityEngine.MonoBehaviour.OnTriggerStay2D": { 83 | "prefix": "OnTriggerStay2D", 84 | "body": [ 85 | "private void OnTriggerStay2D(Collider2D collider)", 86 | "{", 87 | "\t$1", 88 | "}" 89 | ], 90 | "description": "Sent each frame where another object is within a trigger collider attached to this object (2D physics only)." 91 | }, 92 | "UnityEngine.MonoBehaviour.OnTriggerExit": { 93 | "prefix": "OnTriggerExit", 94 | "body": [ 95 | "private void OnTriggerExit(Collider collider)", 96 | "{", 97 | "\t$1", 98 | "}" 99 | ], 100 | "description": "OnTriggerExit is called when the Collider other has stopped touching the trigger." 101 | }, 102 | "UnityEngine.MonoBehaviour.OnTriggerExit2D": { 103 | "prefix": "OnTriggerExit2D", 104 | "body": [ 105 | "private void OnTriggerExit2D(Collider2D collider)", 106 | "{", 107 | "\t$1", 108 | "}" 109 | ], 110 | "description": "Sent when another object leaves a trigger collider attached to this object (2D physics only)." 111 | }, 112 | "UnityEngine.MonoBehaviour.OnCollisionEnter": { 113 | "prefix": "OnCollisionEnter", 114 | "body": [ 115 | "private void OnCollisionEnter(Collision collision)", 116 | "{", 117 | "\t$1", 118 | "}" 119 | ], 120 | "description": "OnCollisionEnter is called when this collider/rigidbody has begun touching another rigidbody/collider." 121 | }, 122 | "UnityEngine.MonoBehaviour.OnCollisionEnter2D": { 123 | "prefix": "OnCollisionEnter2D", 124 | "body": [ 125 | "private void OnCollisionEnter2D(Collision2D collision)", 126 | "{", 127 | "\t$1", 128 | "}" 129 | ], 130 | "description": "Sent when an incoming collider makes contact with this object's collider (2D physics only)." 131 | }, 132 | "UnityEngine.MonoBehaviour.OnCollisionStay": { 133 | "prefix": "OnCollisionStay", 134 | "body": [ 135 | "private void OnCollisionStay(Collision collision)", 136 | "{", 137 | "\t$1", 138 | "}" 139 | ], 140 | "description": "OnCollisionStay is called once per frame for every Collider or Rigidbody that touches another Collider or Rigidbody." 141 | }, 142 | "UnityEngine.MonoBehaviour.OnCollisionStay2D": { 143 | "prefix": "OnCollisionStay2D", 144 | "body": [ 145 | "private void OnCollisionStay2D(Collision2D collision)", 146 | "{", 147 | "\t$1", 148 | "}" 149 | ], 150 | "description": "Sent when a collider on another object stops touching this object's collider (2D physics only)." 151 | }, 152 | "UnityEngine.MonoBehaviour.OnCollisionExit": { 153 | "prefix": "OnCollisionExit", 154 | "body": [ 155 | "private void OnCollisionExit(Collision collision)", 156 | "{", 157 | "\t$1", 158 | "}" 159 | ], 160 | "description": "OnCollisionExit is called when this collider/rigidbody has stopped touching another rigidbody/collider." 161 | }, 162 | "UnityEngine.MonoBehaviour.OnCollisionExit2D": { 163 | "prefix": "OnCollisionExit2D", 164 | "body": [ 165 | "private void OnCollisionExit2D(Collision2D collision)", 166 | "{", 167 | "\t$1", 168 | "}" 169 | ], 170 | "description": "Sent each frame where a collider on another object is touching this object's collider (2D physics only)." 171 | }, 172 | "UnityEngine.MonoBehaviour.OnMouseEnter": { 173 | "prefix": "OnMouseEnter", 174 | "body": [ 175 | "private void OnMouseEnter()", 176 | "{", 177 | "\t$1", 178 | "}" 179 | ], 180 | "description": "Called when the mouse enters the Collider." 181 | }, 182 | "UnityEngine.MonoBehaviour.OnMouseExit": { 183 | "prefix": "OnMouseExit", 184 | "body": [ 185 | "private void OnMouseExit()", 186 | "{", 187 | "\t$1", 188 | "}" 189 | ], 190 | "description": "Called when the mouse is not any longer over the Collider." 191 | }, 192 | "UnityEngine.MonoBehaviour.OnMouseDown": { 193 | "prefix": "OnMouseDown", 194 | "body": [ 195 | "private void OnMouseDown()", 196 | "{", 197 | "\t$1", 198 | "}" 199 | ], 200 | "description": "OnMouseDown is called when the user has pressed the mouse button while over the Collider." 201 | }, 202 | "UnityEngine.MonoBehaviour.OnMouseUp": { 203 | "prefix": "OnMouseUp", 204 | "body": [ 205 | "private void OnMouseUp()", 206 | "{", 207 | "\t$1", 208 | "}" 209 | ], 210 | "description": "OnMouseUp is called when the user has released the mouse button." 211 | }, 212 | "UnityEngine.MonoBehaviour.OnMouseUpAsButton": { 213 | "prefix": "OnMouseUpAsButton", 214 | "body": [ 215 | "private void OnMouseUpAsButton()", 216 | "{", 217 | "\t$1", 218 | "}" 219 | ], 220 | "description": "OnMouseUpAsButton is only called when the mouse is released over the same Collider as it was pressed." 221 | }, 222 | "UnityEngine.MonoBehaviour.OnMouseOver": { 223 | "prefix": "OnMouseOver", 224 | "body": [ 225 | "private void OnMouseOver()", 226 | "{", 227 | "\t$1", 228 | "}" 229 | ], 230 | "description": "Called every frame while the mouse is over the Collider." 231 | }, 232 | "UnityEngine.MonoBehaviour.OnMouseDrag": { 233 | "prefix": "OnMouseDrag", 234 | "body": [ 235 | "private void OnMouseDrag()", 236 | "{", 237 | "\t$1", 238 | "}" 239 | ], 240 | "description": "OnMouseDrag is called when the user has clicked on a Collider and is still holding down the mouse." 241 | }, 242 | "UnityEngine.MonoBehaviour.Update": { 243 | "prefix": "Update", 244 | "body": [ 245 | "private void Update()", 246 | "{", 247 | "\t$1", 248 | "}" 249 | ], 250 | "description": "Update is called every frame, if the MonoBehaviour is enabled." 251 | }, 252 | "UnityEngine.MonoBehaviour.LateUpdate": { 253 | "prefix": "LateUpdate", 254 | "body": [ 255 | "private void LateUpdate()", 256 | "{", 257 | "\t$1", 258 | "}" 259 | ], 260 | "description": "LateUpdate is called every frame, if the Behaviour is enabled." 261 | }, 262 | "UnityEngine.MonoBehaviour.PreCull": { 263 | "prefix": "PreCull", 264 | "body": [ 265 | "private void PreCull()", 266 | "{", 267 | "\t$1", 268 | "}" 269 | ], 270 | "description": "Event function that Unity calls before a Camera culls the scene." 271 | }, 272 | "UnityEngine.MonoBehaviour.OnWillRenderObject": { 273 | "prefix": "OnWillRenderObject", 274 | "body": [ 275 | "private void OnWillRenderObject()", 276 | "{", 277 | "\t$1", 278 | "}" 279 | ], 280 | "description": "OnWillRenderObject is called for each camera if the object is visible and not a UI element." 281 | }, 282 | "UnityEngine.MonoBehaviour.OnBecameVisible": { 283 | "prefix": "OnBecameVisible", 284 | "body": [ 285 | "private void OnBecameVisible()", 286 | "{", 287 | "\t$1", 288 | "}" 289 | ], 290 | "description": "OnBecameVisible is called when the renderer became visible by any camera." 291 | }, 292 | "UnityEngine.MonoBehaviour.OnBecameInvisible": { 293 | "prefix": "OnBecameInvisible", 294 | "body": [ 295 | "private void OnBecameInvisible()", 296 | "{", 297 | "\t$1", 298 | "}" 299 | ], 300 | "description": "OnBecameInvisible is called when the renderer is no longer visible by any camera." 301 | }, 302 | "UnityEngine.MonoBehaviour.OnPreRender": { 303 | "prefix": "OnPreRender", 304 | "body": [ 305 | "private void OnPreRender()", 306 | "{", 307 | "\t$1", 308 | "}" 309 | ], 310 | "description": "Event function that Unity calls before a Camera renders the scene." 311 | }, 312 | "UnityEngine.MonoBehaviour.OnRenderObject": { 313 | "prefix": "OnRenderObject", 314 | "body": [ 315 | "private void OnRenderObject()", 316 | "{", 317 | "\t$1", 318 | "}" 319 | ], 320 | "description": "OnRenderObject is called after camera has rendered the scene." 321 | }, 322 | "UnityEngine.MonoBehaviour.OnPostRender": { 323 | "prefix": "OnPostRender", 324 | "body": [ 325 | "private void OnPostRender()", 326 | "{", 327 | "\t$1", 328 | "}" 329 | ], 330 | "description": "Event function that Unity calls after a Camera renders the scene." 331 | }, 332 | "UnityEngine.MonoBehaviour.OnRenderImage": { 333 | "prefix": "OnRenderImage", 334 | "body": [ 335 | "private void OnRenderImage(RenderTexture src, RenderTexture dest)", 336 | "{", 337 | "\t$1", 338 | "}" 339 | ], 340 | "description": "Event function that Unity calls after a Camera has finished rendering, that allows you to modify the Camera's final image." 341 | }, 342 | "UnityEngine.MonoBehaviour.OnDrawGizmos": { 343 | "prefix": "OnDrawGizmos", 344 | "body": [ 345 | "private void OnDrawGizmos()", 346 | "{", 347 | "\t$1", 348 | "}" 349 | ], 350 | "description": "OnDrawGizmos is called when the gizmos are drawn." 351 | }, 352 | "UnityEngine.MonoBehaviour.OnGUI": { 353 | "prefix": "OnGUI", 354 | "body": [ 355 | "private void OnGUI()", 356 | "{", 357 | "\t$1", 358 | "}" 359 | ], 360 | "description": "OnGUI is called for rendering and handling GUI events." 361 | }, 362 | "UnityEngine.MonoBehaviour.OnApplicationPause": { 363 | "prefix": "OnApplicationPause", 364 | "body": [ 365 | "private void OnApplicationPause(bool pauseStatus)", 366 | "{", 367 | "\t$1", 368 | "}" 369 | ], 370 | "description": "Sent to all GameObjects when the playing application pauses or resumes on losing or regaining focus." 371 | }, 372 | "UnityEngine.MonoBehaviour.OnApplicationQuit": { 373 | "prefix": "OnApplicationQuit", 374 | "body": [ 375 | "private void OnApplicationQuit()", 376 | "{", 377 | "\t$1", 378 | "}" 379 | ], 380 | "description": "Sent to all GameObjects before the application quits." 381 | }, 382 | "UnityEngine.MonoBehaviour.OnDisable": { 383 | "prefix": "OnDisable", 384 | "body": [ 385 | "private void OnDisable()", 386 | "{", 387 | "\t$1", 388 | "}" 389 | ], 390 | "description": "This function is called when the behaviour becomes disabled." 391 | }, 392 | "UnityEngine.MonoBehaviour.OnDestroy": { 393 | "prefix": "OnDestroy", 394 | "body": [ 395 | "private void OnDestroy()", 396 | "{", 397 | "\t$1", 398 | "}" 399 | ], 400 | "description": "Destroying the attached Behaviour will result in the game or Scene receiving OnDestroy." 401 | } 402 | } -------------------------------------------------------------------------------- /resources/dark/clover.svg: -------------------------------------------------------------------------------- 1 | 2 | clover 3 | 4 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /resources/light/clover.svg: -------------------------------------------------------------------------------- 1 | 2 | clover 3 | 4 | 5 | 6 | 8 | 9 | --------------------------------------------------------------------------------