├── .yarnrc ├── .gitignore ├── res ├── icons │ ├── plugin.png │ └── ueicon-light.png └── images │ ├── uesplash.jpg │ ├── uetoolslogo.png │ ├── uetools_code00.gif │ ├── uetoolslogo128.png │ ├── uetools_project00.gif │ ├── uetools_project01.gif │ └── uetools_project02.gif ├── src ├── types │ ├── index.ts │ ├── UnrealEngineProject.ts │ └── VSCodeApi.tsx ├── helpers │ ├── EventDispatcher.ts │ └── context.ts ├── components │ ├── styles │ │ └── Layout.tsx │ └── uetools │ │ ├── modules │ │ ├── gameModuleCard.tsx │ │ ├── index.tsx │ │ ├── pluginModuleCard.tsx │ │ ├── controller.ts │ │ └── styles.tsx │ │ └── project │ │ ├── controller.ts │ │ └── index.tsx ├── test │ ├── suite │ │ ├── extension.test.ts │ │ └── index.ts │ └── runTest.ts ├── commands │ ├── showPluginInExplorer.ts │ ├── routines │ │ ├── build.ts │ │ ├── checkProjectAndUnrealInstallation.ts │ │ ├── changeEngineVersionRoutine.ts │ │ └── generateProjectFilesAndCompileCommands.ts │ ├── openProjectEditor.ts │ ├── checkUnrealProject.ts │ ├── changeEngineVersion.ts │ ├── buildProject.ts │ ├── buildModule.ts │ ├── generateProjectFiles.ts │ ├── generateCompileComands.ts │ └── detectUnrealEngineInstallation.ts ├── StatusBarItems │ └── ActiveProject.ts └── extension.ts ├── .vscode ├── extensions.json ├── settings.json ├── tasks.json └── launch.json ├── .vscodeignore ├── CHANGELOG.md ├── .babelrc ├── tsconfig.json ├── .eslintrc.json ├── README.md ├── vsc-extension-quickstart.md ├── package.json └── webpack.config.js /.yarnrc: -------------------------------------------------------------------------------- 1 | --ignore-engines true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /res/icons/plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagodsp/uetools/HEAD/res/icons/plugin.png -------------------------------------------------------------------------------- /res/images/uesplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagodsp/uetools/HEAD/res/images/uesplash.jpg -------------------------------------------------------------------------------- /res/icons/ueicon-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagodsp/uetools/HEAD/res/icons/ueicon-light.png -------------------------------------------------------------------------------- /res/images/uetoolslogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagodsp/uetools/HEAD/res/images/uetoolslogo.png -------------------------------------------------------------------------------- /res/images/uetools_code00.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagodsp/uetools/HEAD/res/images/uetools_code00.gif -------------------------------------------------------------------------------- /res/images/uetoolslogo128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagodsp/uetools/HEAD/res/images/uetoolslogo128.png -------------------------------------------------------------------------------- /res/images/uetools_project00.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagodsp/uetools/HEAD/res/images/uetools_project00.gif -------------------------------------------------------------------------------- /res/images/uetools_project01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagodsp/uetools/HEAD/res/images/uetools_project01.gif -------------------------------------------------------------------------------- /res/images/uetools_project02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagodsp/uetools/HEAD/res/images/uetools_project02.gif -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | // export all the interfaces from the above files 2 | export * from './UnrealEngineProject'; 3 | export * from './VSCodeApi'; -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | webpack.config.js 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/.eslintrc.json 12 | **/*.map 13 | **/*.ts 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "uetools" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | // "@babel/preset-env", 4 | "@babel/preset-react", 5 | ["@babel/preset-typescript", { "allowNamespaces": true }] 6 | ], 7 | "plugins": [ 8 | // "@babel/plugin-proposal-class-properties" 9 | ] 10 | } -------------------------------------------------------------------------------- /src/helpers/EventDispatcher.ts: -------------------------------------------------------------------------------- 1 | export type Handler = (event: E) => void; 2 | 3 | export class EventEmitter{ 4 | private _callbacks: Handler[] = []; 5 | 6 | emit(e: E) { 7 | this._callbacks.forEach(callback => callback(e)); 8 | } 9 | 10 | on(callback: (e: E) => void) { 11 | this._callbacks.push(callback); 12 | } 13 | } -------------------------------------------------------------------------------- /src/components/styles/Layout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | export const Layout = styled.div<{isVertical?: boolean}>` 5 | display: flex; 6 | flex-direction: ${props => props.isVertical ? 'column' : 'row'}; 7 | flex-wrap: nowrap; 8 | justify-content: space-between; 9 | align-items: stretch; 10 | align-content: stretch; 11 | overflow: hidden; 12 | border-radius: 5px; 13 | `; -------------------------------------------------------------------------------- /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 | "lib": [ 6 | "ES2020", 7 | "dom" 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 | "jsx": "react-jsx", 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 | -------------------------------------------------------------------------------- /src/commands/showPluginInExplorer.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { UnrealEnginePlugin } from '../types'; 3 | import { Context } from '../helpers/context'; 4 | import * as path from 'path'; 5 | import { resolveCliArgsFromVSCodeExecutablePath } from '@vscode/test-electron'; 6 | 7 | export const showPluginInExplorer = (args: {plugin : UnrealEnginePlugin}): void => { 8 | const projectFolder = Context.get("projectFolder") as string; 9 | // focus plugin folder in folder view 10 | vscode.commands.executeCommand('workbench.action.quickOpen', vscode.Uri.file(path.join(projectFolder, args.plugin.FriendlyName, `${args.plugin.FriendlyName}.uplugin`))); 11 | }; -------------------------------------------------------------------------------- /src/commands/routines/build.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export const buildAndGenerateCompileCommands = (): Promise => { 4 | return new Promise((resolve, reject) => { 5 | (async () => { 6 | await (vscode.commands.executeCommand('uetools.buildProject') as Promise) 7 | .then(() => vscode.commands.executeCommand('uetools.generateCompileCommands')) 8 | .catch((reason) => { 9 | console.log(reason); 10 | vscode.window.showErrorMessage(reason.message); 11 | reject(reason); 12 | }); 13 | })(); 14 | }); 15 | }; -------------------------------------------------------------------------------- /src/commands/routines/checkProjectAndUnrealInstallation.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export const checkProjectAndUnrealInstallation = (): Promise => { 4 | return new Promise((resolve, reject) => { 5 | (async () => { 6 | await (vscode.commands.executeCommand('uetools.checkUnrealProject') as Promise) 7 | .then(() => vscode.commands.executeCommand('uetools.detectUnrealEngineInstallation')) 8 | .catch((reason) => { 9 | console.log(reason); 10 | vscode.window.showErrorMessage(reason.message); 11 | reject(reason); 12 | }); 13 | })(); 14 | }); 15 | }; -------------------------------------------------------------------------------- /.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 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off", 13 | "svn.ignoreMissingSvnWarning": true 14 | } -------------------------------------------------------------------------------- /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/commands/routines/changeEngineVersionRoutine.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export const changeEngineVersionRoutine = (): Promise => { 4 | return new Promise((resolve, reject) => { 5 | (async () => { 6 | await (vscode.commands.executeCommand('uetools.changeEngineVersion') as Promise) 7 | .then(() => vscode.commands.executeCommand('uetools.checkUnrealProject')) 8 | .then(() => vscode.commands.executeCommand('uetools.generateProjectFilesAndCompileCommands')) 9 | .catch((reason) => { 10 | console.log(reason); 11 | vscode.window.showErrorMessage(reason.message); 12 | reject(reason); 13 | }); 14 | })(); 15 | }); 16 | }; -------------------------------------------------------------------------------- /src/commands/routines/generateProjectFilesAndCompileCommands.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Context } from '../../helpers/context'; 3 | import { UnrealEngineProject } from '../../types'; 4 | 5 | export const generateProjectFilesAndCompileCommands = (): Promise => { 6 | return new Promise((resolve, reject) => { 7 | (async () => { 8 | await (vscode.commands.executeCommand('uetools.generateProjectFiles') as Promise) 9 | .then(() => vscode.commands.executeCommand('uetools.generateCompileCommands')) 10 | .catch((reason) => { 11 | console.log(reason); 12 | vscode.window.showErrorMessage(reason.message); 13 | reject(reason); 14 | }); 15 | })(); 16 | }); 17 | }; -------------------------------------------------------------------------------- /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/StatusBarItems/ActiveProject.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Context } from '../helpers/context'; 3 | import { UnrealEngineProject } from '../types'; 4 | 5 | /** 6 | * Active Project Status Bar Item 7 | */ 8 | export class ActiveProjectStatusBarItem { 9 | private _statusBarItem: vscode.StatusBarItem; 10 | private _project: vscode.WorkspaceFolder | undefined; 11 | 12 | constructor() { 13 | this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); 14 | Context.events.onProjectChanged.on(this.update.bind(this)); 15 | } 16 | 17 | // 18 | public update(project: UnrealEngineProject) { 19 | this._statusBarItem.text = `${project.Modules[0].Name} (UE v${project.EngineAssociation})`; 20 | this._statusBarItem.show(); 21 | } 22 | 23 | public hide(): void { 24 | this._project = undefined; 25 | this._statusBarItem.hide(); 26 | } 27 | } -------------------------------------------------------------------------------- /.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": [ 10 | "$ts-webpack-watch", 11 | "$tslint-webpack-watch" 12 | ], 13 | "isBackground": true, 14 | "presentation": { 15 | "reveal": "never", 16 | "group": "watchers" 17 | }, 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "watch-tests", 26 | "problemMatcher": "$tsc-watch", 27 | "isBackground": true, 28 | "presentation": { 29 | "reveal": "never", 30 | "group": "watchers" 31 | }, 32 | "group": "build" 33 | }, 34 | { 35 | "label": "tasks: watch-tests", 36 | "dependsOn": [ 37 | "npm: watch", 38 | "npm: watch-tests" 39 | ], 40 | "problemMatcher": [] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /src/components/uetools/modules/gameModuleCard.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Layout } from '../../styles/Layout'; 4 | import VSCodeWrapper from '../../../types/VSCodeApi'; 5 | import { UnrealEnginePlugin, UnrealEngineProject, UnrealModule } from '../../../types'; 6 | import * as ModuleCard from './styles'; 7 | 8 | export const GameModuleCard : React.FC<{project: UnrealEngineProject, module: UnrealModule}> = (props) => { 9 | return ( 10 | 11 | 12 | 13 | 14 | {props.module.Name} 15 | 16 | 17 | 18 | ); 19 | }; -------------------------------------------------------------------------------- /src/helpers/context.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "./EventDispatcher"; 2 | import { UnrealEngineProject } from "../types"; 3 | 4 | /** 5 | * Uunreal Tools context singleton class 6 | */ 7 | export class Context { 8 | // members 9 | private static _instance: Context; 10 | 11 | private _data: Map = new Map(); 12 | 13 | private _events = { 14 | onProjectChanged: new EventEmitter(), 15 | }; 16 | 17 | // methods 18 | 19 | private constructor() { } 20 | 21 | public static instance() { 22 | if (!Context._instance) { 23 | Context._instance = new Context(); 24 | } 25 | return this._instance; 26 | } 27 | 28 | public static get(key: string) { 29 | return Context.instance()._data.get(key); 30 | } 31 | 32 | public static set(key: string, value: any) { 33 | Context.instance()._data.set(key, value); 34 | } 35 | 36 | public static get events() { 37 | return Context.instance()._events; 38 | } 39 | } -------------------------------------------------------------------------------- /.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}/dist/**/*.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/**/*.js", 30 | "${workspaceFolder}/dist/**/*.js" 31 | ], 32 | "preLaunchTask": "tasks: watch-tests" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /src/types/UnrealEngineProject.ts: -------------------------------------------------------------------------------- 1 | // UnrealModule 2 | export interface UnrealModule { 3 | Name: string; 4 | Type: string; 5 | LoadingPhase: string; 6 | AdditionalDependencies: string[]; 7 | } 8 | 9 | 10 | export interface UnrealEnginePlugin { 11 | FileVersion: number; 12 | Version: string; 13 | VersionName: string; 14 | FriendlyName: string; 15 | Description: string; 16 | Category: string; 17 | CreatedBy: string; 18 | CreatedByURL: string; 19 | DocsURL: string; 20 | MarketplaceURL: string; 21 | SupportURL: string; 22 | CanContainContent: boolean; 23 | IsBetaVersion: boolean; 24 | IsExperimentalVersion: boolean; 25 | Installed: boolean; 26 | Modules: UnrealModule[]; 27 | } 28 | 29 | // Unreal Engine project struct 30 | export interface UnrealEngineProject { 31 | Description: string; 32 | Categories: string[]; 33 | Modules: UnrealModule[]; 34 | EngineAssociation: string; 35 | Plugins: { 36 | Name: string; 37 | Enabled: boolean; 38 | TargetAllowList: string[]; 39 | }[]; 40 | ProjectPlugins: UnrealEnginePlugin[]; 41 | } 42 | -------------------------------------------------------------------------------- /src/types/VSCodeApi.tsx: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | acquireVsCodeApi: Function; 4 | WORKSPACE_URI: string; 5 | EXTENSION_URI: string; 6 | } 7 | } 8 | 9 | interface VSCodeApi { 10 | getState(): any; 11 | setState(state: any): void; 12 | postMessage(message: any): void; 13 | } 14 | 15 | export default class VSCodeWrapper { 16 | private static readonly _vscodeApi: VSCodeApi = window.acquireVsCodeApi(); 17 | 18 | /** 19 | * Send a message to the VsCode. 20 | * @param message The message to send. 21 | */ 22 | public static postMessage(message: any) { 23 | this._vscodeApi.postMessage(message); 24 | } 25 | 26 | /** 27 | * Add a listener for message from the VsCode to the extension. 28 | * @param callback The callback to be invoked when a message is received. 29 | * @return A function that can be invoked to remove the listener. 30 | */ 31 | public static onMessage(callback: (message: any) => void) { 32 | window.addEventListener('message', callback); 33 | return () => window.removeEventListener('message', callback); 34 | } 35 | 36 | public static get workspaceUri() { 37 | return window.WORKSPACE_URI; 38 | } 39 | 40 | public static get extensionUri() { 41 | return window.EXTENSION_URI; 42 | } 43 | } -------------------------------------------------------------------------------- /src/components/uetools/modules/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { UnrealEngineProject } from '../../../types'; 4 | import VSCodeWrapper from '../../../types/VSCodeApi'; 5 | import styled from 'styled-components'; 6 | import { Layout } from '../../styles/Layout'; 7 | import { PluginModuleCard } from './pluginModuleCard'; 8 | import { GameModuleCard } from './gameModuleCard'; 9 | 10 | 11 | 12 | export const Modules = () => { 13 | const [project, setProject] = React.useState(); 14 | 15 | VSCodeWrapper.onMessage((message) => { 16 | if(message.data.type === 'project') { 17 | setProject(message.data.project); 18 | } 19 | }); 20 | 21 | return ( 22 | <> 23 | {project && project.Modules && project.Modules.map((module) => { 24 | return ( 25 | 26 | ); 27 | })} 28 | {project && project.ProjectPlugins && project.ProjectPlugins.map((plugin, index) => { 29 | return ( 30 | 31 | ); 32 | })} 33 | 34 | ); 35 | 36 | }; 37 | 38 | ReactDOM.render( 39 | , 40 | document.getElementById('root') 41 | ); 42 | 43 | VSCodeWrapper.postMessage({type: 'onReady', data: 'Hello from the extension!'}); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unreal Engine Tools 2 | 3 | Unreal Engine Tools is an extension to Visual Studio Code with some shortcuts and code editor enhancements to improve the experience of developing C++ modules for Unreal Engine projects. 4 | 5 | > Note 0: Finally an initial Windows support! 🥳🎉 6 | 7 | > Note 1: I am still developing, so this extension currently only have `MacOS` and an initial `Windows` support using `Unreal Engine version >= 5.0`. Soon I will give fully support for Windows and Linux environment. Stay tuned! 👨🏻‍💻 8 | 9 | ## Features 10 | 11 | ### Unreal Engine Tools in-editor section: 12 | Section that provides some shortcuts and a overview of the current project in the Workspace. 13 | 14 | ![Unreal Engine Tools editor section](./res/images/uetools_project00.gif) 15 | 16 | ### Build project from editor with Hot Reload: 17 | Build shortcut that offers the engine installation selection and forces hot reload on editor. 18 | 19 | ![Build entire project](./res/images/uetools_project01.gif) 20 | 21 | It is possible to build individual modules too, also with hot reload. 22 | 23 | ![Build module individually](./res/images/uetools_project02.gif) 24 | 25 | ### Improved code completion with Clangd extension (only Unreal Engine 5): 26 | Automatically invokes UnrealBuildTool in GenerateClangDatabase mode to generate the `compile_commands.json` file that is required by [Clangd extention](https://clangd.llvm.org), allowing the editor to have a better code completion experience. It is not perfect, but helps to get the job done. 🙌🏻 27 | 28 | ![Code completion feature](./res/images/uetools_code00.gif) 29 | 30 | ## Requirements 31 | 32 | * `clangd` - For code improved completion experience (only Unreal Engine 5). 33 | * `MacOS` or `Windows` - Currently I developed the extension in a MacOS and Windows only, but as soon I get a Linux machine I will port it to its developing environment. 34 | 35 | ## Extension Settings 36 | This extension contributes the following settings: 37 | 38 | * `uetools.unrealEngineInstallationSearchPath`: Path to search for Unreal Engine installation for. Usually `C:\Program Files\Epic Games\` on Windows, or `/Users/Shared/Epic Games/` on Mac. 39 | 40 | ## Known Issues 41 | 42 | * Currently support only Mac systems; 43 | * Some code completion seems not work due to forward declaration of Unreal Engine classes and structs provided by `CoreMinimal.h`. I guess...; 44 | * Still in development, so give it a try guys... 45 | 46 | If you liked this project, feel free to suggest code, features and improvements. **Enjoy!** 47 | -------------------------------------------------------------------------------- /src/commands/openProjectEditor.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as vscode from 'vscode'; 3 | import { Context } from '../helpers/context'; 4 | import { UnrealEngineProject } from '../types'; 5 | 6 | export const openProjectEditor = (): Promise => { 7 | return new Promise((resolve, reject) => { 8 | (async () => { 9 | // check for project in the context 10 | const project = Context.get("project") as UnrealEngineProject; 11 | if (!project) { 12 | reject(new Error('No project found')); 13 | return; 14 | } 15 | 16 | // check for unreal engine installation 17 | let unrealEngineInstallation = Context.get("unrealEngineInstallation") as string; 18 | if (!unrealEngineInstallation) { 19 | await vscode.commands.executeCommand('uetools.detectUnrealEngineInstallation'); 20 | unrealEngineInstallation = Context.get("unrealEngineInstallation") as string; 21 | if (!unrealEngineInstallation) { 22 | reject(new Error('No unreal engine installation found')); 23 | return; 24 | } 25 | } 26 | const projectFolder = Context.get("projectFolder") as string; 27 | vscode.window.showInformationMessage(`Opening project editor for ${project.Modules[0].Name}`); 28 | 29 | const unrealEditorBin = Context.get("unrealEditorPath") as string; 30 | const projectFile = path.join(projectFolder, `${project.Modules[0].Name}.uproject`); 31 | 32 | // launch unreal editor without debug session in vscode 33 | const editorProcess = vscode.debug.startDebugging( 34 | vscode.workspace.workspaceFolders![0], 35 | { 36 | name: 'Unreal Editor', 37 | type: 'lldb', 38 | request: 'launch', 39 | program: unrealEditorBin, 40 | args: [ 41 | projectFile, 42 | ], 43 | cwd: projectFolder, 44 | } 45 | ); 46 | 47 | resolve(true); 48 | // vscode.tasks.onDidEndTask((e) => { 49 | // if (e.execution.task === execution.task) { 50 | // vscode.window.showInformationMessage(`Project editor opened for ${project.Modules[0].Name}`); 51 | // } 52 | // }); 53 | })(); 54 | }); 55 | }; -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Setup 13 | 14 | * install the recommended extensions (amodio.tsl-problem-matcher and dbaeumer.vscode-eslint) 15 | 16 | 17 | ## Get up and running straight away 18 | 19 | * Press `F5` to open a new window with your extension loaded. 20 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 21 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 22 | * Find output from your extension in the debug console. 23 | 24 | ## Make changes 25 | 26 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 27 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 28 | 29 | 30 | ## Explore the API 31 | 32 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 33 | 34 | ## Run tests 35 | 36 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 37 | * Press `F5` to run the tests in a new window with your extension loaded. 38 | * See the output of the test result in the debug console. 39 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 40 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 41 | * You can create folders inside the `test` folder to structure your tests any way you want. 42 | 43 | ## Go further 44 | 45 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 46 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 47 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 48 | -------------------------------------------------------------------------------- /src/commands/checkUnrealProject.ts: -------------------------------------------------------------------------------- 1 | // Check if the workspace contains a UProject file 2 | import * as fs from 'fs'; 3 | import { Context } from '../helpers/context'; 4 | import * as vscode from 'vscode'; 5 | 6 | // import types 7 | import { UnrealEngineProject, UnrealEnginePlugin } from '../types'; 8 | 9 | const checkUnrealProject = (): Promise => { 10 | return new Promise((resolve, reject) => { 11 | (async () => { 12 | const workspaceFolders = vscode.workspace.workspaceFolders; 13 | 14 | // Check if the workspaces contains a any file with the extension .uproject 15 | if (workspaceFolders) { 16 | for (const folder of workspaceFolders) { 17 | const files = fs.readdirSync(folder.uri.fsPath); 18 | for (const file of files) { 19 | if (file.endsWith('.uproject')) { 20 | // parse the file and cast it as UnrealEngineProject 21 | const project = JSON.parse(fs.readFileSync(`${folder.uri.fsPath}/${file}`, 'utf8')) as UnrealEngineProject; 22 | 23 | // if Plugins folder exists, check each subfolder for a .uplugin file 24 | if (fs.existsSync(`${folder.uri.fsPath}/Plugins`)) { 25 | const pluginDirs = fs.readdirSync(`${folder.uri.fsPath}/Plugins`); 26 | project.ProjectPlugins = []; 27 | for (const pluginDir of pluginDirs) { 28 | if (fs.existsSync(`${folder.uri.fsPath}/Plugins/${pluginDir}/${pluginDir}.uplugin`)) { 29 | const plugin = JSON.parse(fs.readFileSync(`${folder.uri.fsPath}/Plugins/${pluginDir}/${pluginDir}.uplugin`, 'utf8')) as UnrealEnginePlugin; 30 | project.ProjectPlugins.push(plugin); 31 | } 32 | } 33 | } 34 | 35 | //persist project workspace folder 36 | Context.set('projectFolder', folder.uri.fsPath); 37 | 38 | // persist the UnrealEngineProject in the global state 39 | Context.set('project', project); 40 | 41 | // notify the user that the workspace is a valid Unreal Engine project 42 | vscode.window.showInformationMessage(`Unreal Engine project ${project.Modules[0].Name} found associated with Engine Version: ${project.EngineAssociation}.`); 43 | 44 | // save the project information in vscode workspace settings 45 | vscode.workspace.getConfiguration().update('uetools.project', project, vscode.ConfigurationTarget.WorkspaceFolder); 46 | 47 | Context.events.onProjectChanged.emit(project); 48 | resolve(true); 49 | return true; 50 | } 51 | } 52 | } 53 | } 54 | })(); 55 | }); 56 | }; 57 | 58 | export default checkUnrealProject; -------------------------------------------------------------------------------- /src/components/uetools/modules/pluginModuleCard.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Layout } from '../../styles/Layout'; 4 | import VSCodeWrapper from '../../../types/VSCodeApi'; 5 | import { UnrealEnginePlugin, UnrealEngineProject } from '../../../types'; 6 | import * as ModuleCard from './styles'; 7 | import { Project } from '../project'; 8 | 9 | 10 | 11 | interface PluginModuleCardProps { 12 | project: UnrealEngineProject; 13 | plugin: UnrealEnginePlugin; 14 | } 15 | 16 | export const PluginModuleCard: React.FC = (props) => { 17 | const [isMouseHovering, setIsMouseHovering] = React.useState(false); 18 | 19 | const onMouseEnter = () => { 20 | setIsMouseHovering(true); 21 | }; 22 | 23 | const onMouseLeave = () => { 24 | setIsMouseHovering(false); 25 | }; 26 | 27 | const onBuildModule = () => { 28 | VSCodeWrapper.postMessage({ 29 | type: 'runCommand', 30 | command: 'uetools.buildModule', 31 | args: {moduleName: props.plugin.Modules[0].Name}, 32 | }); 33 | }; 34 | 35 | let isPluginEnabled = false; 36 | for(const p of props.project.Plugins) 37 | { 38 | if(p.Name === props.plugin.FriendlyName){ 39 | isPluginEnabled = p.Enabled; 40 | } 41 | } 42 | 43 | 44 | return ( 45 |
46 | 47 | 48 | 49 | 50 | {props.plugin.FriendlyName} 51 | ({props.plugin.VersionName}) 52 | 53 | {isMouseHovering && ( 54 | 55 | Build Module 56 | 57 | )} 58 | {!isMouseHovering && ( 59 |
60 | {props.plugin.Description} 61 | 62 | by {props.plugin.CreatedBy} 63 | {props.plugin.CreatedByURL} 64 | 65 |
66 | )} 67 |
68 | {/* */} 69 |
70 |
71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uetools", 3 | "displayName": "Unreal Engine Tools", 4 | "description": "Unreal Engine toolset to help developing projects using Visual Studio Code.", 5 | "publisher": "tiagodsp", 6 | "icon": "res/images/uetoolslogo128.png", 7 | "repository": { 8 | "url": "https://github.com/tiagodsp/uetools" 9 | }, 10 | "version": "0.0.3", 11 | "engines": { 12 | "vscode": "^1.64.0" 13 | }, 14 | "categories": [ 15 | "Other" 16 | ], 17 | "activationEvents": [ 18 | "onStartupFinished" 19 | ], 20 | "main": "./dist/extension.js", 21 | "contributes": { 22 | "commands": [ 23 | { 24 | "command": "uetools.checkUnrealProject", 25 | "title": "UETools: Check for Unreal Project in this workspace" 26 | }, 27 | { 28 | "command": "uetools.askProjectFilesGeneration", 29 | "title": "UETools: Ask Unreal Project files generation" 30 | } 31 | ], 32 | "configuration": [ 33 | { 34 | "title": "Uneral Engine Tools", 35 | "properties": { 36 | "uetools.unrealEngineInstallationSearchPath": { 37 | "type": "string", 38 | "description": "Path to search for Unreal Engine installations", 39 | "default": "" 40 | } 41 | } 42 | } 43 | ], 44 | "taskDefinitions": [ 45 | { 46 | "type": "uetools" 47 | } 48 | ], 49 | "views": { 50 | "uetools": [ 51 | { 52 | "type": "webview", 53 | "id": "uetools.project", 54 | "name": "Project" 55 | }, 56 | { 57 | "type": "webview", 58 | "id": "uetools.modules", 59 | "name": "Modules" 60 | } 61 | ] 62 | }, 63 | "viewsContainers": { 64 | "activitybar": [ 65 | { 66 | "id": "uetools", 67 | "title": "Unreal Engine Tools", 68 | "icon": "res/icons/ueicon-light.png" 69 | } 70 | ] 71 | } 72 | }, 73 | "scripts": { 74 | "vscode:prepublish": "yarn run package", 75 | "compile": "webpack", 76 | "watch": "webpack --watch", 77 | "package": "webpack --mode production --devtool hidden-source-map", 78 | "compile-tests": "tsc -p . --outDir out", 79 | "watch-tests": "tsc -p . -w --outDir out", 80 | "pretest": "yarn run compile-tests && yarn run compile && yarn run lint", 81 | "lint": "eslint src --ext ts", 82 | "test": "node ./out/test/runTest.js" 83 | }, 84 | "devDependencies": { 85 | "@babel/core": "^7.17.5", 86 | "@babel/preset-react": "^7.16.7", 87 | "@babel/preset-typescript": "^7.16.7", 88 | "@types/glob": "^7.2.0", 89 | "@types/mocha": "^9.1.0", 90 | "@types/node": "14.x", 91 | "@types/react": "^17.0.39", 92 | "@types/react-dom": "^17.0.12", 93 | "@types/styled-components": "^5.1.24", 94 | "@types/vscode": "^1.64.0", 95 | "@typescript-eslint/eslint-plugin": "^5.12.1", 96 | "@typescript-eslint/parser": "^5.12.1", 97 | "@vscode/test-electron": "^2.1.2", 98 | "babel-loader": "^8.2.3", 99 | "eslint": "^8.9.0", 100 | "glob": "^7.2.0", 101 | "mocha": "^9.2.1", 102 | "ts-loader": "^9.2.6", 103 | "typescript": "^4.5.5", 104 | "webpack": "^5.69.1", 105 | "webpack-cli": "^4.9.2" 106 | }, 107 | "dependencies": { 108 | "react": "^17.0.2", 109 | "react-dom": "^17.0.2", 110 | "styled-components": "^5.3.3" 111 | }, 112 | "extensionDependencies": [ 113 | "llvm-vs-code-extensions.vscode-clangd" 114 | ] 115 | } 116 | -------------------------------------------------------------------------------- /src/components/uetools/modules/controller.ts: -------------------------------------------------------------------------------- 1 | // Warning: VsCode side script file only! 2 | import * as vscode from 'vscode'; 3 | import { Context } from '../../../helpers/context'; 4 | import * as path from 'path'; 5 | 6 | /** 7 | * Project View Controller for VSCode 8 | */ 9 | export class ModulesViewController { 10 | private _webviewView: vscode.WebviewView | undefined; 11 | private _extensionContext: vscode.ExtensionContext; 12 | private _bundleName: string = 'uetools-modules'; 13 | private _updateInterval: number = 1000; 14 | 15 | /** 16 | * @param context Extension context 17 | */ 18 | constructor(context: vscode.ExtensionContext) { 19 | this._extensionContext = context; 20 | vscode.window.registerWebviewViewProvider('uetools.modules', { 21 | resolveWebviewView: (webviewView, _webviewView) => { 22 | this._webviewView = webviewView; 23 | this.createOrShowPanel(); 24 | } 25 | }); 26 | } 27 | 28 | /** 29 | * Create or show the webview 30 | */ 31 | private createOrShowPanel() { 32 | if (this._webviewView) { 33 | this._webviewView.webview.options = { 34 | enableScripts: true, 35 | localResourceRoots: [ 36 | vscode.Uri.file(path.join(this._extensionContext.extensionPath, 'dist')), 37 | vscode.Uri.file(path.join(this._extensionContext.extensionPath, 'res')), 38 | vscode.Uri.file(vscode.workspace.workspaceFolders![0].uri.fsPath), 39 | ], 40 | }; 41 | 42 | this._webviewView.webview.onDidReceiveMessage(message => { 43 | switch (message.type) { 44 | case 'onReady': 45 | this._webviewView!.webview.postMessage({ type: 'project', project: Context.get('project') }); 46 | case 'runCommand': 47 | vscode.commands.executeCommand(message.command, message.args); 48 | } 49 | // periodicaly send project data to webview 50 | setInterval(() => { 51 | if (this._webviewView) { 52 | this._webviewView!.webview.postMessage({ type: 'project', project: Context.get('project') }); 53 | } 54 | }, this._updateInterval); 55 | }); 56 | this._webviewView.webview.html = this.getHtml(); 57 | } 58 | } 59 | 60 | /** 61 | * Get html content for webview as string 62 | */ 63 | private getHtml(): string { 64 | return ` 65 | 66 | 67 | 68 | 69 | 70 | 75 | 76 | 77 |
78 | 79 | 80 | 81 | `; 82 | } 83 | } -------------------------------------------------------------------------------- /src/components/uetools/project/controller.ts: -------------------------------------------------------------------------------- 1 | // Warning: VsCode side script file only! 2 | import * as vscode from 'vscode'; 3 | import { Context } from '../../../helpers/context'; 4 | import * as path from 'path'; 5 | 6 | /** 7 | * Project View Controller for VSCode 8 | */ 9 | export class ProjectViewController { 10 | private _webviewView: vscode.WebviewView | undefined; 11 | private _extensionContext: vscode.ExtensionContext; 12 | private _bundleName: string = 'uetools-project'; 13 | private _updateInterval: number = 1000; 14 | 15 | /** 16 | * @param context Extension context 17 | */ 18 | constructor(context: vscode.ExtensionContext) { 19 | this._extensionContext = context; 20 | vscode.window.registerWebviewViewProvider('uetools.project', { 21 | resolveWebviewView: (webviewView, _webviewView) => { 22 | this._webviewView = webviewView; 23 | this.createOrShowPanel(); 24 | } 25 | }); 26 | } 27 | 28 | /** 29 | * Create or show the webview 30 | */ 31 | private createOrShowPanel() { 32 | if (this._webviewView) { 33 | this._webviewView.webview.options = { 34 | enableScripts: true, 35 | localResourceRoots: [ 36 | vscode.Uri.file(path.join(this._extensionContext.extensionPath, 'dist')), 37 | vscode.Uri.file(path.join(this._extensionContext.extensionPath, 'res')), 38 | vscode.Uri.file(vscode.workspace.workspaceFolders![0].uri.fsPath), 39 | ], 40 | }; 41 | 42 | this._webviewView.webview.onDidReceiveMessage(message => { 43 | switch (message.type) { 44 | case 'onReady': 45 | this._webviewView!.webview.postMessage({ type: 'project', project: Context.get('project') }); 46 | break; 47 | case 'runCommand': 48 | vscode.commands.executeCommand(message.command, message.args); 49 | break; 50 | } 51 | // periodicaly send project data to webview 52 | setInterval(() => { 53 | if (this._webviewView) { 54 | this._webviewView!.webview.postMessage({ type: 'project', project: Context.get('project') }); 55 | } 56 | }, this._updateInterval); 57 | }); 58 | this._webviewView.webview.html = this.getHtml(); 59 | } 60 | } 61 | 62 | /** 63 | * Get html content for webview as string 64 | */ 65 | private getHtml(): string { 66 | return ` 67 | 68 | 69 | 70 | 71 | 72 | 77 | 78 | 79 |
80 | 81 | 82 | 83 | `; 84 | } 85 | } -------------------------------------------------------------------------------- /src/components/uetools/modules/styles.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from "styled-components"; 3 | 4 | // Card with module thumbnail and description 5 | export const ModuleCard = styled.div` 6 | position: relative; 7 | display: flex; 8 | flex-direction: row; 9 | flex-wrap: nowrap; 10 | justify-content: space-between; 11 | align-items: stretch; 12 | align-content: stretch; 13 | overflow: hidden; 14 | border-radius: 5px; 15 | margin-bottom: 10px; 16 | background-color: #333333; 17 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); 18 | `; 19 | 20 | export const ModuleThumbnail = styled.div<{img: string, width: number, height: number}>` 21 | width: ${props => props.width}px; 22 | min-width: ${props => props.width}px; 23 | height: ${props => props.height}px; 24 | min-height: ${props => props.height}px; 25 | background-image: url(${props => props.img}); 26 | background-size: cover; 27 | background-position: center; 28 | background-repeat: no-repeat; 29 | border-radius: 5px; 30 | margin-right: 10px; 31 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); 32 | `; 33 | 34 | 35 | export const ModuleDescription = styled.div` 36 | display: flex; 37 | flex-direction: column; 38 | flex-wrap: nowrap; 39 | justify-content: space-between; 40 | flex-grow: 2; 41 | flex-shrink: 1; 42 | padding: 10px; 43 | border-radius: 5px; 44 | `; 45 | 46 | 47 | 48 | export const ModuleDescriptionText = styled.p` 49 | font-size: 1em; 50 | margin: 0; 51 | font-weight: 100; 52 | overflow-y: hidden; 53 | `; 54 | 55 | export const ModuleAuthor = styled.p` 56 | font-size: 0.8em; 57 | margin: 0; 58 | font-weight: 100; 59 | `; 60 | 61 | export const ModduleAuthorUrl = styled.a` 62 | font-size: 0.8em; 63 | margin: 0; 64 | font-weight: 100; 65 | color: #0078d7; 66 | text-decoration: underline; 67 | `; 68 | 69 | export const ModuleEnableCheckbox = styled.input` 70 | position: absolute; 71 | top: 0; 72 | right: 0; 73 | margin: 0; 74 | padding: 0; 75 | width: 20px; 76 | height: 20px; 77 | border: 1px solid #ccc; 78 | border-radius: 5px; 79 | background-color: #fff; 80 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); 81 | `; 82 | 83 | export const ModuleTitleWrapper = styled.div` 84 | display: flex; 85 | flex-direction: row; 86 | flex-wrap: nowrap; 87 | justify-content: left; 88 | align-items: stretch; 89 | align-content: stretch; 90 | `; 91 | 92 | export const ModuleTitle = styled.h1` 93 | font-size: 1.5em; 94 | font-weight: bold; 95 | margin: 0; 96 | `; 97 | 98 | export const ModuleVersion = styled.p` 99 | font-size: 0.8em; 100 | padding-left: 5px; 101 | font-weight: 100; 102 | `; 103 | 104 | // Bootstrap like blue button 105 | export const Button = styled.button` 106 | background-color: #007bff; 107 | border-color: #007bff; 108 | color: white; 109 | border-radius: 5px; 110 | padding: 5px 10px; 111 | margin: 5px; 112 | font-size: 0.8em; 113 | font-weight: bold; 114 | cursor: pointer; 115 | `; 116 | 117 | export const ButtonsWrapper = styled.div<{isHorizontal?: boolean}>` 118 | display: flex; 119 | flex-direction: ${(props) => props.isHorizontal ? 'row' : 'column'}; 120 | flex-wrap: nowrap; 121 | justify-content: center; 122 | align-items: stretch; 123 | align-content: stretch; 124 | width: 100%; 125 | height: 100%; 126 | overflow: hidden; 127 | border-radius: 5px; 128 | margin-bottom: 10px; 129 | background-color: #333333; 130 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); 131 | `; -------------------------------------------------------------------------------- /src/commands/changeEngineVersion.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { UnrealEngineProject } from '../types'; 3 | import * as fs from 'fs'; 4 | import * as path from 'path'; 5 | import { Context } from '../helpers/context'; 6 | 7 | export const changeEngineVersion = (): Promise => { 8 | return new Promise((resolve, reject) => { 9 | (async () => { 10 | // check operating system 11 | const os = process.platform; 12 | 13 | // get unreal engine installation seach path and check if the version associated with project is installed 14 | let unrealEngineInstallationSearchPath = vscode.workspace.getConfiguration().get('uetools.unrealEngineInstallationSearchPath') as string; 15 | if(!unrealEngineInstallationSearchPath) { 16 | // try default installation path by operating system 17 | const os = process.platform; 18 | if (os === 'win32') { 19 | unrealEngineInstallationSearchPath = 'C:\\Program Files\\Epic Games'; 20 | } else if (os === 'darwin') { 21 | unrealEngineInstallationSearchPath = '/Users/Shared/Epic Games'; 22 | } else if(os === 'linux') { 23 | unrealEngineInstallationSearchPath = '/opt/Epic Games'; 24 | } else { 25 | reject(new Error('Unreal Engine installation not found. Please set the path in settings.')); 26 | return false; 27 | } 28 | if(fs.existsSync(unrealEngineInstallationSearchPath)) { 29 | vscode.workspace.getConfiguration().update('uetools.unrealEngineInstallationSearchPath', unrealEngineInstallationSearchPath, vscode.ConfigurationTarget.Global); 30 | } else { 31 | reject(new Error('Unreal Engine installation not found. Please set the path in settings.')); 32 | return false; 33 | } 34 | } 35 | 36 | const folders = fs.readdirSync(unrealEngineInstallationSearchPath).filter(folder => folder.includes('UE_')); 37 | 38 | // ask user to select a unreal engine installation from list with tip 39 | const engineFolder = await vscode.window.showQuickPick(folders, { placeHolder: 'Select Unreal Engine Installation' }); 40 | if (!engineFolder) { 41 | reject(new Error('No Unreal Engine installation selected')); 42 | return; 43 | } 44 | 45 | // Check if the workspaces contains a any file with the extension .uproject 46 | const workspaceFolders = vscode.workspace.workspaceFolders; 47 | 48 | if (workspaceFolders) { 49 | for (const folder of workspaceFolders) { 50 | const files = fs.readdirSync(folder.uri.fsPath); 51 | for (const file of files) { 52 | if (file.endsWith('.uproject')) { 53 | // parse the file and cast it as UnrealEngineProject 54 | const project = JSON.parse(fs.readFileSync(`${folder.uri.fsPath}/${file}`, 'utf8')); 55 | 56 | // change the engine association 57 | project.EngineAssociation = engineFolder.split('UE_')[1]; 58 | 59 | // write the file back 60 | fs.writeFileSync(`${folder.uri.fsPath}/${file}`, JSON.stringify(project)); 61 | 62 | resolve(true); 63 | return true; 64 | } 65 | } 66 | } 67 | } 68 | reject(new Error('No Unreal Engine project found')); 69 | return false; 70 | })(); 71 | }); 72 | }; -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import checkUnrealProject from './commands/checkUnrealProject'; 4 | import { askProjectFilesGeneration, generateProjectFiles } from './commands/generateProjectFiles'; 5 | import { detectUnrealEngineInstallation } from './commands/detectUnrealEngineInstallation'; 6 | import { generateCompileCommands } from './commands/generateCompileComands'; 7 | import { Context } from './helpers/context'; 8 | import { ActiveProjectStatusBarItem } from './StatusBarItems/ActiveProject'; 9 | import * as path from 'path'; 10 | import { ProjectViewController } from './components/uetools/project/controller'; 11 | import { ModulesViewController } from './components/uetools/modules/controller'; 12 | import { openProjectEditor } from './commands/openProjectEditor'; 13 | import { buildProject } from './commands/buildProject'; 14 | import { buildAndGenerateCompileCommands } from './commands/routines/build'; 15 | import { buildModule } from './commands/buildModule'; 16 | import { showPluginInExplorer } from './commands/showPluginInExplorer'; 17 | import { generateProjectFilesAndCompileCommands } from './commands/routines/generateProjectFilesAndCompileCommands'; 18 | import { checkProjectAndUnrealInstallation } from './commands/routines/checkProjectAndUnrealInstallation'; 19 | import { changeEngineVersion } from './commands/changeEngineVersion'; 20 | import { changeEngineVersionRoutine } from './commands/routines/changeEngineVersionRoutine'; 21 | 22 | // command list 23 | interface Command { 24 | command: string; 25 | callback: (...args: any[]) => any; 26 | } 27 | 28 | const commands: Command[] = [ 29 | {command: 'checkUnrealProject', callback: checkUnrealProject}, 30 | {command: 'askProjectFilesGeneration', callback: askProjectFilesGeneration}, 31 | {command: 'detectUnrealEngineInstallation', callback: detectUnrealEngineInstallation}, 32 | {command: 'generateProjectFiles', callback: generateProjectFiles}, 33 | {command: 'generateCompileCommands', callback: generateCompileCommands}, 34 | {command: 'openProjectEditor', callback: openProjectEditor}, 35 | {command: 'buildProject', callback: buildProject}, 36 | {command: 'buildAndGenerateCompileCommands', callback: buildAndGenerateCompileCommands}, 37 | {command: 'buildModule', callback: buildModule}, 38 | {command: 'showPluginInExplorer', callback: showPluginInExplorer}, 39 | {command: 'generateProjectFilesAndCompileCommands', callback: generateProjectFilesAndCompileCommands}, 40 | {command: 'checkProjectAndUnrealInstallation', callback: checkProjectAndUnrealInstallation}, 41 | {command: 'changeEngineVersion', callback: changeEngineVersion}, 42 | {command: 'changeEngineVersionRoutine', callback: changeEngineVersionRoutine}, 43 | ]; 44 | 45 | const tasks: vscode.Task[] = []; 46 | 47 | // this method is called when your extension is activated 48 | export function activate(context: vscode.ExtensionContext) { 49 | 50 | // Register task provider 51 | Context.set('tasks', tasks); 52 | vscode.tasks.registerTaskProvider('uetools', { 53 | provideTasks: () => tasks, 54 | resolveTask: (_task: vscode.Task) => undefined, 55 | }); 56 | 57 | Context.set('tasks', tasks); 58 | 59 | new ActiveProjectStatusBarItem(); 60 | 61 | new ProjectViewController(context); 62 | new ModulesViewController(context); 63 | 64 | // Register commands 65 | commands.forEach(c => { 66 | context.subscriptions.push(vscode.commands.registerCommand(`uetools.${c.command}`, c.callback)); 67 | }); 68 | 69 | // Register status bar items 70 | const projectName = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); 71 | context.subscriptions.push(projectName); 72 | 73 | // check for project 74 | vscode.commands.executeCommand('uetools.checkProjectAndUnrealInstallation'); 75 | } 76 | 77 | // this method is called when your extension is deactivated 78 | export function deactivate() { } 79 | -------------------------------------------------------------------------------- /src/commands/buildProject.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Context } from '../helpers/context'; 3 | import { UnrealEngineProject } from '../types'; 4 | import * as path from 'path'; 5 | 6 | export const buildProject = (): Promise => { 7 | return new Promise((resolve, reject) => { 8 | (async () => { 9 | // check for project in the context 10 | const project = Context.get("project") as UnrealEngineProject; 11 | if (!project) { 12 | reject(new Error('No project found')); 13 | return; 14 | } 15 | 16 | // check for unreal engine installation 17 | const unrealEngineInstallation = Context.get("unrealEngineInstallation") as string; 18 | const unrealBuildToolPath = Context.get("unrealBuildToolPath") as string; 19 | const runtimePath = Context.get("runtimePath") as string; 20 | const projectFolder = Context.get("projectFolder") as string; 21 | 22 | // Create task to build project 23 | const os = process.platform; 24 | const scapeSpace = os === "win32" ? '` ' : '\\ '; 25 | let buildOsType = ""; 26 | let shellCommand; 27 | // TODO - Check for a better way to create shell commands for different OSes and avoid this big if/else statements 28 | if (os === "win32") { 29 | buildOsType = "Win64"; 30 | shellCommand = new vscode.ShellExecution( 31 | `"${unrealBuildToolPath}" -mode=Build -ForceHotReload -project="${path.join(projectFolder, project.Modules[0].Name)}.uproject" ${project.Modules[0].Name}Editor ${buildOsType} Development`, 32 | { cwd: unrealEngineInstallation, executable: runtimePath } 33 | ); 34 | } else if (os === "darwin") { 35 | buildOsType = "Mac"; 36 | shellCommand = new vscode.ShellExecution( 37 | `${runtimePath.split(" ").join("\\ ")} ${unrealBuildToolPath.split(" ").join("\\ ")} -mode=Build -ForceHotReload -project=${path.join(projectFolder, project.Modules[0].Name).split(" ").join("\\ ")}.uproject ${project.Modules[0].Name}Editor ${buildOsType} Development`, 38 | { cwd: unrealEngineInstallation } 39 | ); 40 | } else if (os === "linux") { 41 | buildOsType = "Linux"; 42 | shellCommand = new vscode.ShellExecution( 43 | `${runtimePath.split(" ").join("\\ ")} ${unrealBuildToolPath.split(" ").join("\\ ")} -mode=Build -ForceHotReload -project=${path.join(projectFolder, project.Modules[0].Name).split(" ").join("\\ ")}.uproject ${project.Modules[0].Name}Editor ${buildOsType} Development`, 44 | { cwd: unrealEngineInstallation } 45 | ); 46 | } 47 | 48 | const task = new vscode.Task( 49 | { type: 'shell' }, 50 | vscode.workspace.workspaceFolders![0], 51 | 'Build Project', 52 | 'UETools', 53 | shellCommand, 54 | ); 55 | 56 | const taskList = Context.get("tasks") as vscode.Task[]; 57 | const previousTaskIndex = taskList.findIndex((t) => t.name === task.name); 58 | if (previousTaskIndex > -1) { 59 | taskList.splice(previousTaskIndex, 1); 60 | } 61 | taskList.push(task); 62 | 63 | // Run task 64 | const execution = await vscode.tasks.executeTask(task); 65 | vscode.tasks.onDidEndTask((e) => { 66 | if (e.execution.task === execution.task) { 67 | vscode.window.showInformationMessage(`Project ${project.Modules[0].Name} build completed`); 68 | console.log('End: generateProjectFiles'); 69 | resolve(true); 70 | } 71 | }); 72 | 73 | 74 | })(); 75 | }); 76 | }; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | //@ts-check 8 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 9 | 10 | /** @type WebpackConfig */ 11 | const extensionConfig = { 12 | target: ['node'], // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 13 | mode: 'production', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 14 | 15 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 16 | output: { 17 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'extension.js', 20 | libraryTarget: 'commonjs2' 21 | }, 22 | externals: { 23 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 24 | // modules added here also need to be added in the .vscodeignore file 25 | }, 26 | resolve: { 27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 28 | extensions: ['.ts', '.js', '.tsx', '.jsx'], 29 | }, 30 | module: { 31 | rules: [ 32 | // { 33 | // test: /\.ts$/, 34 | // exclude: /node_modules/, 35 | // use: [ 36 | // { 37 | // loader: 'ts-loader' 38 | // } 39 | // ] 40 | // }, 41 | { 42 | test: /\.(js|jsx|ts|tsx)$/, 43 | exclude: /node_modules/, 44 | use: { 45 | loader: 'babel-loader', 46 | options: { 47 | cacheDirectory: true, 48 | cacheCompression: true, 49 | }, 50 | }, 51 | }, 52 | ] 53 | }, 54 | devtool: 'nosources-source-map', 55 | infrastructureLogging: { 56 | level: "log", // enables logging required for problem matchers 57 | }, 58 | }; 59 | 60 | /** @type WebpackConfig */ 61 | const uetoolsProjectComponentConfig = { 62 | target: ['web'], 63 | mode: 'production', 64 | 65 | entry: './src/components/uetools/project/index.tsx', 66 | output: { 67 | path: path.resolve(__dirname, 'dist'), 68 | filename: 'uetools-project.js', 69 | libraryTarget: 'commonjs2' 70 | }, 71 | resolve: { 72 | extensions: ['.ts', '.js', '.tsx', '.jsx'], 73 | }, 74 | module: { 75 | rules: [ 76 | { 77 | test: /\.(js|jsx|ts|tsx)$/, 78 | exclude: /node_modules/, 79 | use: { 80 | loader: 'babel-loader', 81 | options: { 82 | cacheDirectory: true, 83 | cacheCompression: true, 84 | }, 85 | }, 86 | }, 87 | ] 88 | }, 89 | devtool: 'nosources-source-map', 90 | infrastructureLogging: { 91 | level: "log", // enables logging required for problem matchers 92 | }, 93 | }; 94 | 95 | /** @type WebpackConfig */ 96 | const uetoolsModulesComponentConfig = { 97 | target: ['web'], 98 | mode: 'production', 99 | 100 | entry: './src/components/uetools/modules/index.tsx', 101 | output: { 102 | path: path.resolve(__dirname, 'dist'), 103 | filename: 'uetools-modules.js', 104 | libraryTarget: 'commonjs2' 105 | }, 106 | resolve: { 107 | extensions: ['.ts', '.js', '.tsx', '.jsx'], 108 | }, 109 | module: { 110 | rules: [ 111 | { 112 | test: /\.(js|jsx|ts|tsx)$/, 113 | exclude: /node_modules/, 114 | use: { 115 | loader: 'babel-loader', 116 | options: { 117 | cacheDirectory: true, 118 | cacheCompression: true, 119 | }, 120 | }, 121 | }, 122 | ] 123 | }, 124 | devtool: 'nosources-source-map', 125 | infrastructureLogging: { 126 | level: "log", // enables logging required for problem matchers 127 | }, 128 | }; 129 | 130 | module.exports = [extensionConfig, uetoolsProjectComponentConfig, uetoolsModulesComponentConfig]; -------------------------------------------------------------------------------- /src/commands/buildModule.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Context } from '../helpers/context'; 3 | import { UnrealEngineProject } from '../types'; 4 | import * as path from 'path'; 5 | 6 | export const buildModule = (args: { moduleName: string }): Promise => { 7 | return new Promise((resolve, reject) => { 8 | (async () => { 9 | // check for project in the context 10 | const project = Context.get("project") as UnrealEngineProject; 11 | if (!project) { 12 | reject(new Error('No project found')); 13 | return; 14 | } 15 | 16 | // check module name 17 | if (!args.moduleName) { 18 | reject(new Error('No module name found')); 19 | return; 20 | } 21 | 22 | // check for unreal engine installation 23 | let unrealEngineInstallation = Context.get("unrealEngineInstallation") as string; 24 | if (!unrealEngineInstallation) { 25 | await vscode.commands.executeCommand('uetools.detectUnrealEngineInstallation'); 26 | unrealEngineInstallation = Context.get("unrealEngineInstallation") as string; 27 | if (!unrealEngineInstallation) { 28 | reject(new Error('No unreal engine installation found')); 29 | return; 30 | } 31 | } 32 | 33 | const projectFolder = Context.get("projectFolder") as string; 34 | const unrealBuildToolPath = Context.get("unrealBuildToolPath") as string; 35 | const runtimePath = Context.get("runtimePath") as string; 36 | const randomIntNum = Math.floor(Math.random() * 100000); 37 | 38 | // Create task to build project 39 | const os = process.platform; 40 | const scapeSpace = os === "win32" ? '^ ' : '\\ '; 41 | let buildOsType = ""; 42 | let shellCommand; 43 | // TODO - Check for a better way to create shell commands for different OSes and avoid this big if/else statements 44 | if (os === "win32") { 45 | buildOsType = "Win64"; 46 | shellCommand = new vscode.ShellExecution( 47 | `"${unrealBuildToolPath}" -mode=Build -ModuleWithSuffix=${args.moduleName},${randomIntNum} -ForceHotReload -project="${path.join(projectFolder, project.Modules[0].Name)}.uproject" ${project.Modules[0].Name}Editor ${buildOsType} Development`, 48 | // `dotnet ${path.join(unrealEngineInstallation, "Engine/Binaries/DotNET/UnrealBuildTool/UnrealBuildTool.dll").replace(' ', '\\ ')} -mode=Build -ForceHotReload -project=${path.join(projectFolder, project.Modules[0].Name).replace(' ', '\\ ')}.uproject ${args.moduleName} ${project.Modules[0].Name}Editor Mac Development`, 49 | { cwd: unrealEngineInstallation, executable: runtimePath } 50 | ); 51 | } else if (os === "darwin") { 52 | buildOsType = "Mac"; 53 | shellCommand = new vscode.ShellExecution( 54 | `${runtimePath.split(" ").join("\\ ")} ${unrealBuildToolPath.split(" ").join("\\ ")} -mode=Build -ModuleWithSuffix=${args.moduleName},${randomIntNum} -ForceHotReload -project=${path.join(projectFolder, project.Modules[0].Name).split(" ").join("\\ ")}.uproject ${project.Modules[0].Name}Editor ${buildOsType} Development`, 55 | { cwd: unrealEngineInstallation } 56 | ); 57 | } else if (os === "linux") { 58 | buildOsType = "Linux"; 59 | shellCommand = new vscode.ShellExecution( 60 | `${runtimePath.split(" ").join("\\ ")} ${unrealBuildToolPath.split(" ").join("\\ ")} -mode=Build -ModuleWithSuffix=${args.moduleName},${randomIntNum} -ForceHotReload -project=${path.join(projectFolder, project.Modules[0].Name).split(" ").join("\\ ")}.uproject ${project.Modules[0].Name}Editor ${buildOsType} Development`, 61 | { cwd: unrealEngineInstallation } 62 | ); 63 | } 64 | 65 | const task = new vscode.Task( 66 | { type: 'shell' }, 67 | vscode.workspace.workspaceFolders![0], 68 | `Build ${args.moduleName} Module`, 69 | 'UETools', 70 | shellCommand, 71 | ); 72 | 73 | const taskList = Context.get("tasks") as vscode.Task[]; 74 | const previousTaskIndex = taskList.findIndex((t) => t.name === task.name); 75 | if (previousTaskIndex > -1) { 76 | taskList.splice(previousTaskIndex, 1); 77 | } 78 | taskList.push(task); 79 | 80 | // Run task 81 | const execution = await vscode.tasks.executeTask(task); 82 | vscode.tasks.onDidEndTask((e) => { 83 | if (e.execution.task === execution.task) { 84 | vscode.window.showInformationMessage(`Module ${args.moduleName} build completed`); 85 | resolve(true); 86 | } 87 | }); 88 | 89 | 90 | })(); 91 | }); 92 | }; -------------------------------------------------------------------------------- /src/commands/generateProjectFiles.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { UnrealEngineProject } from '../types'; 3 | import * as path from 'path'; 4 | import { Context } from '../helpers/context'; 5 | 6 | export const generateProjectFiles = (): Promise => { 7 | return new Promise((resolve, reject) => { 8 | (async () => { 9 | console.log('Begin: generateProjectFiles'); 10 | 11 | // check for project in the context 12 | const project = Context.get("project") as UnrealEngineProject; 13 | if (!project) { 14 | reject(new Error('No project found')); 15 | return; 16 | } 17 | 18 | await vscode.commands.executeCommand('uetools.detectUnrealEngineInstallation'); 19 | 20 | // check for unreal engine installation 21 | const unrealEngineInstallation = Context.get("unrealEngineInstallation") as string; 22 | const unrealBuildToolPath = Context.get("unrealBuildToolPath") as string; 23 | const runtimePath = Context.get("runtimePath") as string; 24 | const projectFolder = Context.get("projectFolder") as string; 25 | vscode.window.showInformationMessage(`Generating project files for ${project.Modules[0].Name}`); 26 | 27 | // Create task to generate project files 28 | const os = process.platform; 29 | let buildOsType = ""; 30 | let shellCommand; 31 | // TODO - Check for a better way to create shell commands for different OSes and avoid this big if/else statements 32 | if (os === "win32") { 33 | buildOsType = "Win64"; 34 | shellCommand = new vscode.ShellExecution( 35 | `"${unrealBuildToolPath}" -mode=GenerateProjectFiles -project="${path.join(projectFolder, project.Modules[0].Name)}.uproject" ${project.Modules[0].Name}Editor ${buildOsType} Development`, 36 | { cwd: unrealEngineInstallation, executable: runtimePath } 37 | ); 38 | } 39 | else if (os === "darwin") { 40 | buildOsType = "Mac"; 41 | shellCommand = new vscode.ShellExecution( 42 | `${runtimePath.split(" ").join("\\ ")} ${unrealBuildToolPath.split(" ").join("\\ ")} -mode=GenerateProjectFiles -project=${path.join(projectFolder, project.Modules[0].Name).split(" ").join("\\ ")}.uproject ${project.Modules[0].Name}Editor ${buildOsType} Development`, 43 | { cwd: unrealEngineInstallation } 44 | ); 45 | } 46 | else if (os === "linux") { 47 | buildOsType = "Linux"; 48 | shellCommand = new vscode.ShellExecution( 49 | `${runtimePath.split(" ").join("\\ ")} ${unrealBuildToolPath.split(" ").join("\\ ")} -mode=GenerateProjectFiles -project=${path.join(projectFolder, project.Modules[0].Name).split(" ").join("\\ ")}.uproject ${project.Modules[0].Name}Editor ${buildOsType} Development`, 50 | { cwd: unrealEngineInstallation } 51 | ); 52 | } 53 | 54 | const task = new vscode.Task( 55 | { type: 'shell' }, 56 | vscode.workspace.workspaceFolders![0], 57 | 'Generate Project Files', 58 | 'UETools', 59 | shellCommand, 60 | ); 61 | 62 | const taskList = Context.get("tasks") as vscode.Task[]; 63 | const previousTaskIndex = taskList.findIndex((t) => t.name === task.name); 64 | if (previousTaskIndex > -1) { 65 | taskList.splice(previousTaskIndex, 1); 66 | } 67 | taskList.push(task); 68 | 69 | // Run task 70 | const execution = await vscode.tasks.executeTask(task); 71 | vscode.tasks.onDidEndTask((e) => { 72 | if (e.execution.task === execution.task) { 73 | vscode.window.showInformationMessage(`Project files generated for ${project.Modules[0].Name}`); 74 | console.log('End: generateProjectFiles'); 75 | resolve(true); 76 | } 77 | }); 78 | })(); 79 | }); 80 | }; 81 | 82 | /** 83 | * Check if the workspace contains a UProject file and ask the user if he wants to generate the project files 84 | */ 85 | export const askProjectFilesGeneration = async () => { 86 | // check for project in global state context 87 | const project = Context.get("project") as UnrealEngineProject; 88 | if (!project) { 89 | // ask if user wants to scan for a project 90 | await vscode.window.showInformationMessage('No project found. Would you like to scan for a project?', 'Yes', 'No').then(answer => { 91 | if (answer === 'Yes') { 92 | // scan for a project 93 | vscode.commands.executeCommand('uetools.checkUnrealProject'); 94 | } 95 | }); 96 | return; 97 | }; 98 | 99 | // ask for project generation 100 | const answer = await vscode.window.showInformationMessage('Would you like to generate project files?', 'Yes', 'No'); 101 | if (answer === 'Yes') { 102 | // generate project files 103 | await vscode.commands.executeCommand('uetools.generateProjectFiles') 104 | .then(() => vscode.commands.executeCommand('uetools.generateCompileCommands')); 105 | } 106 | }; -------------------------------------------------------------------------------- /src/components/uetools/project/index.tsx: -------------------------------------------------------------------------------- 1 | // draw a button with a text 2 | import * as React from 'react'; 3 | import * as ReactDOM from 'react-dom'; 4 | import * as path from 'path'; 5 | import VSCodeWrapper from '../../../types/VSCodeApi'; 6 | import { UnrealEngineProject } from '../../../types'; 7 | import styled from 'styled-components'; 8 | import { Layout } from '../../styles/Layout'; 9 | 10 | // Base panel with components on horizontal layout 11 | // to the left the thumbnail with fixed size, to the right the project description 12 | // if to narrow, the thumbnail goes to the top and the project description goes to the bottom 13 | const DescriptionPanel = styled.div` 14 | position: relative; 15 | padding: 10px; 16 | display: flex; 17 | flex-direction: row; 18 | flex-wrap: nowrap; 19 | justify-content: space-between; 20 | align-items: stretch; 21 | align-content: stretch; 22 | overflow: hidden; 23 | border-radius: 5px; 24 | `; 25 | 26 | // Project details Panel with translucent glass effect background 27 | const ProjectDetailsPanel = styled.div` 28 | flex-grow: 2; 29 | padding: 10px; 30 | border-radius: 5px; 31 | background-color: #00000099; 32 | `; 33 | 34 | // Splash background image and fit to the div 35 | const BackgroundImage = styled.div<{img: string}>` 36 | background-image: url(${props => props.img}); 37 | position: absolute; 38 | top: 0; left: 0; bottom: 0; right: 0; 39 | background-size: cover; 40 | background-position: center; 41 | background-repeat: no-repeat; 42 | z-index: -100; 43 | `; 44 | 45 | // project thumbnail with fixed aspect ratio 46 | const ProjectThumbnail = styled.div<{img: string, width: number, height: number}>` 47 | width: ${props => props.width}px; 48 | height: ${props => props.height}px; 49 | background-image: url(${props => props.img}); 50 | background-size: cover; 51 | background-position: center; 52 | background-repeat: no-repeat; 53 | border-radius: 5px; 54 | margin-right: 10px; 55 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); 56 | `; 57 | 58 | const ProjectTitle = styled.h1` 59 | font-size: 1.5em; 60 | font-weight: bold; 61 | margin: 0; 62 | color: white; 63 | `; 64 | 65 | const Text = styled.p` 66 | margin: 0; 67 | `; 68 | 69 | // Bootstrap like blue button 70 | const Button = styled.button` 71 | background-color: #007bff; 72 | border-color: #007bff; 73 | color: white; 74 | border-radius: 5px; 75 | padding: 5px 10px; 76 | margin: 5px; 77 | font-size: 0.8em; 78 | font-weight: bold; 79 | cursor: pointer; 80 | `; 81 | 82 | const ButtonsWrapper = styled.div` 83 | display: flex; 84 | flex-direction: column; 85 | flex-wrap: nowrap; 86 | justify-content: center; 87 | align-items: stretch; 88 | align-content: stretch; 89 | width: 100%; 90 | height: 100%; 91 | overflow: hidden; 92 | border-radius: 5px; 93 | margin-bottom: 10px; 94 | background-color: #333333; 95 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); 96 | `; 97 | 98 | export const Project = () => 99 | { 100 | const [project, setProject] = React.useState(); 101 | 102 | VSCodeWrapper.onMessage((message) => { 103 | setProject(message.data.project); 104 | }); 105 | 106 | const onOpenProject = () => { 107 | VSCodeWrapper.postMessage({ 108 | type: 'runCommand', 109 | command: 'uetools.openProjectEditor', 110 | }); 111 | }; 112 | 113 | const onChangeEngineVersion = () => { 114 | VSCodeWrapper.postMessage({ 115 | type: 'runCommand', 116 | command: 'uetools.changeEngineVersionRoutine', 117 | }); 118 | }; 119 | 120 | const onGenerateProjectFiles = () => { 121 | VSCodeWrapper.postMessage({ 122 | type: 'runCommand', 123 | command: 'uetools.generateProjectFilesAndCompileCommands', 124 | }); 125 | }; 126 | 127 | 128 | const onBuildProject = () => { 129 | VSCodeWrapper.postMessage({ 130 | type: 'runCommand', 131 | command: 'uetools.buildAndGenerateCompileCommands', 132 | }); 133 | }; 134 | 135 | return ( 136 | <> 137 | 138 | 139 | 140 | 141 | {project?.Modules[0].Name} 142 | Unreal Engine {project?.EngineAssociation} 143 | 144 | 145 | 146 | 149 | 152 | 155 | 158 | 159 | 160 | ); 161 | }; 162 | 163 | ReactDOM.render( 164 | , 165 | document.getElementById('root') 166 | ); 167 | 168 | VSCodeWrapper.postMessage({type: 'onReady', data: 'Hello from the extension!'}); -------------------------------------------------------------------------------- /src/commands/generateCompileComands.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { UnrealEngineProject } from '../types'; 3 | import * as path from 'path'; 4 | import * as fs from 'fs'; 5 | import { Context } from '../helpers/context'; 6 | 7 | /** 8 | * Generate compile commands for the current project 9 | */ 10 | export const generateCompileCommands = (): Promise => { 11 | return new Promise((resolve, reject) => { 12 | (async () => { 13 | // check for project in global state context 14 | const project = Context.get("project") as UnrealEngineProject; 15 | if (!project) { 16 | reject(new Error('No project found')); 17 | return; 18 | }; 19 | 20 | // check for unreal engine installation 21 | const unrealEngineInstallation = Context.get("unrealEngineInstallation") as string; 22 | const unrealBuildToolPath = Context.get("unrealBuildToolPath") as string; 23 | const runtimePath = Context.get("runtimePath") as string; 24 | const projectFolder = Context.get("projectFolder") as string; 25 | 26 | vscode.window.showInformationMessage(`Generating compile commands for ${project.Modules[0].Name}`); 27 | 28 | // Delete previous compile_commands.json 29 | const compileCommandsFile = path.join(projectFolder, 'compile_commands.json'); 30 | if (fs.existsSync(compileCommandsFile)) { 31 | fs.unlinkSync(compileCommandsFile); 32 | } 33 | 34 | // Create task to generate compile_commands.json 35 | const os = process.platform; 36 | const scapeSpace = os === "win32" ? '^ ' : '\\ '; 37 | let buildOsType = ""; 38 | let shellCommand; 39 | // TODO - Check for a better way to create shell commands for different OSes and avoid this big if/else statements 40 | if (os === "win32") { 41 | buildOsType = "Win64"; 42 | shellCommand = new vscode.ShellExecution( 43 | `"${unrealBuildToolPath}" -mode=GenerateClangDatabase -project=${path.join(projectFolder, project.Modules[0].Name)}.uproject ${project.Modules[0].Name}Editor ${buildOsType} Development`, 44 | { cwd: unrealEngineInstallation, executable: runtimePath} 45 | ); 46 | } else if (os === "darwin") { 47 | buildOsType = "Mac"; 48 | shellCommand = new vscode.ShellExecution( 49 | `${runtimePath.split(" ").join("\\ ")} ${unrealBuildToolPath.split(" ").join("\\ ")} -mode=GenerateClangDatabase -project=${path.join(projectFolder, project.Modules[0].Name).split(" ").join("\\ ")}.uproject ${project.Modules[0].Name}Editor ${buildOsType} Development`, 50 | { cwd: unrealEngineInstallation } 51 | ); 52 | } else if (os === "linux") { 53 | buildOsType = "Linux"; 54 | shellCommand = new vscode.ShellExecution( 55 | `${runtimePath.split(" ").join("\\ ")} ${unrealBuildToolPath.split(" ").join("\\ ")} -mode=GenerateClangDatabase -project=${path.join(projectFolder, project.Modules[0].Name).split(" ").join("\\ ")}.uproject ${project.Modules[0].Name}Editor ${buildOsType} Development`, 56 | { cwd: unrealEngineInstallation } 57 | ); 58 | } 59 | 60 | const task = new vscode.Task( 61 | { type: 'shell' }, 62 | vscode.workspace.workspaceFolders![0], 63 | 'Generate Compile Commands', 64 | 'UETools', 65 | shellCommand, 66 | ); 67 | 68 | const taskList = Context.get("tasks") as vscode.Task[]; 69 | const previousTaskIndex = taskList.findIndex((t) => t.name === task.name); 70 | if (previousTaskIndex > -1) { 71 | taskList.splice(previousTaskIndex, 1); 72 | }; 73 | taskList.push(task); 74 | 75 | // Run task 76 | const execution = await vscode.tasks.executeTask(task); 77 | 78 | // Wait for task to finish 79 | vscode.tasks.onDidEndTask((e) => { 80 | if (e.execution.task === execution.task) { 81 | // check if compile_commands.json was generated on engine installation folder and move it to project folder 82 | const newCompileCommandsFile = path.join(unrealEngineInstallation, 'compile_commands.json'); 83 | if (!fs.existsSync(newCompileCommandsFile)) { 84 | vscode.window.showErrorMessage(`Could not generate compile_commands.json for ${project.Modules[0].Name}. Intelisense may not work for clangd.`); 85 | console.log('End: generateCompileCommands'); 86 | return; 87 | } 88 | fs.renameSync(newCompileCommandsFile, compileCommandsFile); 89 | 90 | // set compile_commands.json relative dir in clangd arguments in workspace settings 91 | const clangd = vscode.workspace.getConfiguration('clangd', vscode.workspace.workspaceFolders![0]).get('arguments') as string[]; 92 | const clangdIndex = clangd.findIndex((arg) => arg.startsWith('--compile-commands-dir')); 93 | if (clangdIndex !== -1) { 94 | clangd[clangdIndex] = `--compile-commands-dir=${projectFolder}`; 95 | } else { 96 | clangd.push(`--compile-commands-dir=${projectFolder}`); 97 | } 98 | vscode.workspace.getConfiguration('clangd', vscode.workspace.workspaceFolders![0]).update('arguments', clangd, vscode.ConfigurationTarget.Workspace, true); 99 | 100 | resolve(true); 101 | } 102 | }); 103 | })(); 104 | }); 105 | }; -------------------------------------------------------------------------------- /src/commands/detectUnrealEngineInstallation.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { UnrealEngineProject } from '../types'; 3 | import * as fs from 'fs'; 4 | import * as path from 'path'; 5 | import { Context } from '../helpers/context'; 6 | 7 | export const detectUnrealEngineInstallation = (): Promise => { 8 | return new Promise((resolve, reject) => { 9 | (async () => { 10 | 11 | // check for project in the context 12 | const project = Context.get('project') as UnrealEngineProject; 13 | 14 | if (!project) { 15 | reject(new Error('No project found')); 16 | return false; 17 | } 18 | 19 | // check operating system 20 | const os = process.platform; 21 | 22 | // get unreal engine installation seach path and check if the version associated with project is installed 23 | let unrealEngineInstallationSearchPath = vscode.workspace.getConfiguration().get('uetools.unrealEngineInstallationSearchPath') as string; 24 | if(!unrealEngineInstallationSearchPath) { 25 | // try default installation path by operating system 26 | const os = process.platform; 27 | if (os === 'win32') { 28 | unrealEngineInstallationSearchPath = 'C:\\Program Files\\Epic Games'; 29 | } else if (os === 'darwin') { 30 | unrealEngineInstallationSearchPath = '/Users/Shared/Epic Games'; 31 | } else if(os === 'linux') { 32 | unrealEngineInstallationSearchPath = '/opt/Epic Games'; 33 | } else { 34 | reject(new Error('Unreal Engine installation not found. Please set the path in settings.')); 35 | return false; 36 | } 37 | if(fs.existsSync(unrealEngineInstallationSearchPath)) { 38 | vscode.workspace.getConfiguration().update('uetools.unrealEngineInstallationSearchPath', unrealEngineInstallationSearchPath, vscode.ConfigurationTarget.Global); 39 | } else { 40 | reject(new Error('Unreal Engine installation not found. Please set the path in settings.')); 41 | return false; 42 | } 43 | } 44 | 45 | const folders = fs.readdirSync(unrealEngineInstallationSearchPath); 46 | 47 | const engineFolder = folders.find(folder => folder.includes(`UE_${project.EngineAssociation}`)); 48 | if(!engineFolder) { 49 | reject(new Error(`Unreal Engine ${project.EngineAssociation} not found in ${unrealEngineInstallationSearchPath}`)); 50 | return false; 51 | } 52 | 53 | // // ask user to select a unreal engine installation from list with tip 54 | // const engineFolder = await vscode.window.showQuickPick(folders, { placeHolder: 'Select Unreal Engine Installation' }); 55 | // if (!engineFolder) { 56 | // reject(new Error('No Unreal Engine installation selected')); 57 | // return; 58 | // } 59 | 60 | Context.set("unrealEngineInstallation", path.join(unrealEngineInstallationSearchPath, engineFolder)); 61 | 62 | // set UnrealBuildTool, UnrealEditor and Mono path based on Unreal version. 63 | // get engine version as number 64 | const engineVersion = parseInt(project.EngineAssociation.replace('UE_', '')); 65 | if(engineVersion === 4) { 66 | if(os === 'win32') { 67 | Context.set("unrealBuildToolPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine\\Binaries\\DotNET\\UnrealBuildTool.exe')); 68 | Context.set("unrealEditorPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine\\Binaries\\Win64\\UE4Editor.exe')); 69 | Context.set("runtimePath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine\\Binaries\\ThirdParty\\DotNet\\Windows\\dotnet.exe')); 70 | } else if(os === 'darwin') { 71 | Context.set("unrealBuildToolPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/DotNET/UnrealBuildTool.exe')); 72 | Context.set("unrealEditorPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/Mac/UE4Editor.app/Contents/MacOS/UnrealEditor')); 73 | Context.set("runtimePath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/ThirdParty/Mono/Mac/bin/mono')); 74 | } else if(os === 'linux') { 75 | Context.set("unrealBuildToolPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/DotNET/UnrealBuildTool.exe')); 76 | Context.set("unrealEditorPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/Linux/UE4Editor')); 77 | Context.set("runtimePath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/ThirdParty/Mono/Linux/bin/mono')); 78 | } else { 79 | reject(new Error(`Unsupported operating system: ${os}`)); 80 | return false; 81 | } 82 | } else if(engineVersion === 5) { 83 | if(os === 'win32') { 84 | Context.set("unrealBuildToolPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine\\Binaries\\DotNET\\UnrealBuildTool\\UnrealBuildTool.dll')); 85 | Context.set("unrealEditorPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine\\Binaries\\Win64\\UnrealEditor.exe')); 86 | Context.set("runtimePath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine\\Binaries\\ThirdParty\\DotNet\\Windows\\dotnet.exe')); 87 | } else if(os === 'darwin') { 88 | Context.set("unrealBuildToolPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/DotNET/UnrealBuildTool/UnrealBuildTool.dll')); 89 | Context.set("unrealEditorPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/Mac/UnrealEditor.app/Contents/MacOS/UnrealEditor')); 90 | Context.set("runtimePath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/ThirdParty/DotNet/Mac/dotnet')); 91 | } else if(os === 'linux') { 92 | Context.set("unrealBuildToolPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/DotNET/UnrealBuildTool/UnrealBuildTool.dll')); 93 | Context.set("unrealEditorPath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/Linux/UnrealEditor')); 94 | Context.set("runtimePath", path.join(Context.get('unrealEngineInstallation') as string, 'Engine/Binaries/ThirdParty/Mono/Linux/bin/mono')); 95 | } else { 96 | reject(new Error(`Unsupported operating system: ${os}`)); 97 | return false; 98 | } 99 | } else { 100 | reject(new Error(`Unreal Engine ${project.EngineAssociation} not supported`)); 101 | return false; 102 | } 103 | 104 | // Notify user the selected unreal engine installation 105 | vscode.window.showInformationMessage(`Unreal Engine installation ${engineFolder} selected.`); 106 | console.log(`Unreal Engine installation selected.`); 107 | resolve(true); 108 | return true; 109 | })(); 110 | }); 111 | }; --------------------------------------------------------------------------------