├── resources ├── UVCH.png ├── UnrealIcon.png ├── readme │ ├── ToolBar.gif │ ├── SwitchFile.gif │ ├── youtubelogo.png │ └── UnrealDocExplorer.gif └── UVCHExtensionIcon.png ├── .gitignore ├── .babelrc ├── .vscodeignore ├── src ├── test │ ├── suite │ │ ├── extension.test.ts │ │ └── index.ts │ └── runTest.ts ├── Commands │ ├── GeneralCommands.ts │ ├── SwitchFilesCommands.ts │ ├── BrowserCommands.ts │ └── ToolbarCommands.ts ├── Views │ ├── components │ │ ├── Toolbar.css │ │ ├── ToolTip.tsx │ │ ├── Tooltip.css │ │ ├── RestApiEntry.css │ │ ├── SearchBar.css │ │ ├── RestApiEntry.tsx │ │ ├── Toolbar.tsx │ │ └── SearchBar.tsx │ ├── UVCHBrowser.css │ ├── UnrealProjectView.tsx │ └── UVCHBrowser.tsx ├── utils │ ├── log_uvch.ts │ └── UETypes.ts ├── SubSystem │ ├── featureSubSystem │ │ ├── BrowserSubsystem.ts │ │ ├── ToolbarSubsystem.ts │ │ ├── FeatureSubSystem.ts │ │ └── SwitchFileSubsystem.ts │ ├── Subsystem.ts │ ├── SettingsSubsystem.ts │ ├── DataSubsystem.ts │ └── WebViewSubsystem.ts └── UnrealVsCodeHelper.ts ├── tsconfig.json ├── LICENSE ├── README.md ├── webpack.config.js ├── .eslintrc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md └── package.json /resources/UVCH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcabel/UnrealVsCodeHelper/HEAD/resources/UVCH.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | .vscode 7 | 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /resources/UnrealIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcabel/UnrealVsCodeHelper/HEAD/resources/UnrealIcon.png -------------------------------------------------------------------------------- /resources/readme/ToolBar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcabel/UnrealVsCodeHelper/HEAD/resources/readme/ToolBar.gif -------------------------------------------------------------------------------- /resources/UVCHExtensionIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcabel/UnrealVsCodeHelper/HEAD/resources/UVCHExtensionIcon.png -------------------------------------------------------------------------------- /resources/readme/SwitchFile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcabel/UnrealVsCodeHelper/HEAD/resources/readme/SwitchFile.gif -------------------------------------------------------------------------------- /resources/readme/youtubelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcabel/UnrealVsCodeHelper/HEAD/resources/readme/youtubelogo.png -------------------------------------------------------------------------------- /resources/readme/UnrealDocExplorer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcabel/UnrealVsCodeHelper/HEAD/resources/readme/UnrealDocExplorer.gif -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | ["@babel/preset-typescript", { "allowNamespaces": true }] 5 | ], 6 | "plugins": [] 7 | } -------------------------------------------------------------------------------- /.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 | 15 | -------------------------------------------------------------------------------- /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 | "jsx": "react-jsx", 4 | "module": "commonjs", 5 | "target": "ES2020", 6 | "lib": [ 7 | "ES2020", 8 | "ES2021.String", 9 | "dom" 10 | ], 11 | "sourceMap": true, 12 | "rootDir": "src", 13 | "strict": true /* enable all strict type-checking options */ 14 | /* Additional Checks */ 15 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 16 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 17 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 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/Commands/GeneralCommands.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* GeneralCommands.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/08/15 20:42:15 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/08/15 20:42:15 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as vscode from 'vscode'; 14 | 15 | export function GoToSettings_Implementation() 16 | { 17 | vscode.commands.executeCommand("workbench.action.openSettings", "@ext:HugoCabel.uvch"); 18 | return true; 19 | } -------------------------------------------------------------------------------- /src/Commands/SwitchFilesCommands.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* FilesCommands.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/25 22:11:11 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/25 22:11:11 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import SwitchFileSubsystem from "../SubSystem/featureSubSystem/SwitchFileSubsystem"; 14 | 15 | export async function SwitchHeaderCppFile_Implementation(): Promise 16 | { 17 | return (await SwitchFileSubsystem.SwitchFile()); 18 | } -------------------------------------------------------------------------------- /src/Views/components/Toolbar.css: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* Toolbar.css :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/25 10:26:43 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/25 10:26:43 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | 14 | .IconSvg { 15 | box-sizing: border-box; 16 | width: 25px; 17 | height: 25px; 18 | cursor: pointer; 19 | padding: 2px; 20 | border-radius: 5px; 21 | } 22 | 23 | .IconSvg:hover { 24 | border: 1px solid var(--vscode-focusBorder); 25 | } -------------------------------------------------------------------------------- /src/utils/log_uvch.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* log_uvch.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/22 08:37:52 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/22 08:37:52 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as vscode from 'vscode'; 14 | 15 | // eslint-disable-next-line @typescript-eslint/naming-convention 16 | const log_uvch = vscode.window.createOutputChannel("UVCH"); 17 | 18 | export default { 19 | natif: log_uvch, 20 | log: log_uvch.appendLine 21 | }; -------------------------------------------------------------------------------- /src/Views/UVCHBrowser.css: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* UVCHBrowser.css :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/29 12:25:38 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/29 12:25:38 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | 14 | @keyframes loadingRestEntry { 15 | 0% { 16 | background-position: -800px 0 17 | } 18 | 100% { 19 | background-position: 800px 0 20 | } 21 | } 22 | 23 | .animated-loading { 24 | animation-duration: 2s; 25 | animation-fill-mode: forwards; 26 | animation-iteration-count: infinite; 27 | animation-name: loadingRestEntry; 28 | animation-timing-function: linear; 29 | background: linear-gradient(to right, var(--vscode-input-background) 8%, var(--vscode-editor-background) 18%, var(--vscode-input-background) 33%); 30 | background-size: 800px 104px; 31 | height: 100px; 32 | position: relative; 33 | } -------------------------------------------------------------------------------- /src/Views/components/ToolTip.tsx: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* ToolTip.tsx :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/24 16:17:38 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/24 16:17:38 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as React from "react"; 14 | import "./Tooltip.css"; 15 | 16 | export interface ITooltip { 17 | msg: string, 18 | direction?: "top" | "down", 19 | children?: React.ReactNode 20 | } 21 | 22 | export default function Tooltip(props: ITooltip) { 23 | // eslint-disable-next-line @typescript-eslint/naming-convention 24 | const [_Active, set_Active] = React.useState(false); 25 | 26 | let timeout: NodeJS.Timeout; 27 | 28 | function ShowTip() 29 | { 30 | timeout = setTimeout(() => { 31 | set_Active(true); 32 | }, 400); 33 | } 34 | 35 | function HideTip() 36 | { 37 | clearInterval(timeout); 38 | set_Active(false); 39 | } 40 | 41 | return ( 42 |
47 | {props.children} 48 | {_Active && ( 49 |
50 | {props.msg} 51 |
52 | )} 53 |
54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /src/SubSystem/featureSubSystem/BrowserSubsystem.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* BrowserSubsystem.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/06/13 09:21:28 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/06/13 09:21:28 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import { 14 | QuickSearch_Implementation, 15 | QuickSearchFromSelection_Implementation, 16 | BrowserSearch_Implementation, 17 | BrowserSearchFromSelection_Implementation 18 | } from '../../Commands/BrowserCommands'; 19 | 20 | import AFeatureSubSystem from "./FeatureSubSystem"; 21 | 22 | export default class BrowserSubsystem extends AFeatureSubSystem 23 | { 24 | protected Assign() 25 | { 26 | this._FeatureName = "Browser"; 27 | this._EnableConfigPath = "Global.UseBrowser"; 28 | this._Commands = [ 29 | { cmd: "QuickSearch", func: QuickSearch_Implementation }, 30 | { cmd: "QuickSearchFromSelection", func: QuickSearchFromSelection_Implementation }, 31 | { cmd: "BrowserSearch", func: BrowserSearch_Implementation }, 32 | { cmd: "BrowserSearchFromSelection", func: BrowserSearchFromSelection_Implementation }, 33 | ]; 34 | this._Views = [ 35 | { 36 | viewId: "UVCH", 37 | panelIds: [ 38 | "UVCHBrowser" 39 | ] 40 | } 41 | ]; 42 | } 43 | } -------------------------------------------------------------------------------- /src/SubSystem/featureSubSystem/ToolbarSubsystem.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* ToolbarSubsystem.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/06/10 13:02:16 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/06/10 13:02:16 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import { 14 | GetProjectInfos_Implementation, 15 | PlayGame_Implementation, 16 | PlayEditor_Implementation, 17 | BuildEditor_Implementation, 18 | GetUnrealEnginePath_Implementation 19 | } from "../../Commands/ToolbarCommands"; 20 | 21 | import AFeatureSubSystem from "./FeatureSubSystem"; 22 | 23 | export default class ToolbarSubsystem extends AFeatureSubSystem 24 | { 25 | protected Assign() 26 | { 27 | this._FeatureName = "Toolbar"; 28 | this._EnableConfigPath = "Global.UseToolbar"; 29 | this._Commands = [ 30 | { cmd: "GetProjectInfos", func: GetProjectInfos_Implementation }, 31 | { cmd: "PlayGame", func: PlayGame_Implementation }, 32 | { cmd: "PlayEditor", func: PlayEditor_Implementation }, 33 | { cmd: "BuildEditor", func: BuildEditor_Implementation }, 34 | { cmd: "GetUnrealEnginePath", func: GetUnrealEnginePath_Implementation }, 35 | ]; 36 | this._Views = [ 37 | { 38 | viewId: "UVCH", 39 | panelIds: [ 40 | "UnrealProjectView" 41 | ] 42 | } 43 | ]; 44 | } 45 | } -------------------------------------------------------------------------------- /src/SubSystem/Subsystem.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* Subsystem.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/06/06 16:52:17 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/06/06 16:52:17 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | export default class ASubsystem 14 | { 15 | // The unique instance of the subsystem 16 | private static _Instance: ASubsystem | undefined = undefined; 17 | /** 18 | * Get or create the instance of the subsystem casted has the given type 19 | * @note I didn't find a way to dynamicly cast it has the current child class so I add a template 20 | * @returns the unique instance of the subsystem 21 | */ 22 | public static GetInstance(): T | undefined { 23 | if (!this._Instance) { 24 | this._Instance = new this(); 25 | } 26 | return (this._Instance as T); // This is why it may be undefined 27 | } 28 | 29 | /** 30 | * Initialise the subsystem 31 | * @returns the unique instance of the subsystem 32 | */ 33 | public static Init(): T | undefined { 34 | // By asking for the instance, we are initalizing the class 35 | return (this.GetInstance()); 36 | } 37 | constructor() { 38 | // Called Assign before Init to allow the child class to assign its own properties 39 | this.Assign(); 40 | this.Init(); 41 | } 42 | 43 | // has to be overridden by the subclass 44 | 45 | protected Assign() {} 46 | protected Init() {} 47 | }; -------------------------------------------------------------------------------- /src/Views/components/Tooltip.css: -------------------------------------------------------------------------------- 1 | /* Custom properties */ 2 | :root { 3 | --tooltip-text-color: white; 4 | --tooltip-background-color: black; 5 | --tooltip-margin: 30px; 6 | --tooltip-arrow-size: 6px; 7 | } 8 | /* Wrapping */ 9 | .Tooltip-Wrapper { 10 | display: inline-block; 11 | position: relative; 12 | } 13 | /* Absolute positioning */ 14 | .Tooltip-Tip { 15 | position: absolute; 16 | border-radius: 4px; 17 | left: 50%; 18 | transform: translateX(-50%); 19 | padding: 6px; 20 | color: var(--tooltip-text-color); 21 | background: var(--tooltip-background-color); 22 | font-size: 14px; 23 | font-family: sans-serif; 24 | line-height: 1; 25 | z-index: 100; 26 | white-space: nowrap; 27 | } 28 | /* CSS border triangles */ 29 | .Tooltip-Tip::before { 30 | content: " "; 31 | left: 50%; 32 | border: solid transparent; 33 | height: 0; 34 | width: 0; 35 | position: absolute; 36 | pointer-events: none; 37 | border-width: var(--tooltip-arrow-size); 38 | margin-left: calc(var(--tooltip-arrow-size) * -1); 39 | } 40 | /* Absolute positioning */ 41 | .Tooltip-Tip.top { 42 | top: calc(var(--tooltip-margin) * -1); 43 | } 44 | /* CSS border triangles */ 45 | .Tooltip-Tip.top::before { 46 | top: 100%; 47 | border-top-color: var(--tooltip-background-color); 48 | } 49 | /* Absolute positioning */ 50 | .Tooltip-Tip.right { 51 | left: calc(100% + var(--tooltip-margin)); 52 | top: 50%; 53 | transform: translateX(0) translateY(-50%); 54 | } 55 | /* CSS border triangles */ 56 | .Tooltip-Tip.right::before { 57 | left: calc(var(--tooltip-arrow-size) * -1); 58 | top: 50%; 59 | transform: translateX(0) translateY(-50%); 60 | border-right-color: var(--tooltip-background-color); 61 | } 62 | /* Absolute positioning */ 63 | .Tooltip-Tip.bottom { 64 | bottom: calc(var(--tooltip-margin) * -1); 65 | } 66 | /* CSS border triangles */ 67 | .Tooltip-Tip.bottom::before { 68 | bottom: 100%; 69 | border-bottom-color: var(--tooltip-background-color); 70 | } 71 | /* Absolute positioning */ 72 | .Tooltip-Tip.left { 73 | left: auto; 74 | right: calc(100% + var(--tooltip-margin)); 75 | top: 50%; 76 | transform: translateX(0) translateY(-50%); 77 | } 78 | /* CSS border triangles */ 79 | .Tooltip-Tip.left::before { 80 | left: auto; 81 | right: calc(var(--tooltip-arrow-size) * -2); 82 | top: 50%; 83 | transform: translateX(0) translateY(-50%); 84 | border-left-color: var(--tooltip-background-color); 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnrealVsCodeHelper README 2 | 3 | ![](https://img.shields.io/github/stars/hcabel/UnrealVsCodeHelper?label=stars&style=social) 4 | ![](https://img.shields.io/github/repo-size/hcabel/UnrealVsCodeHelper) 5 | ![](https://img.shields.io/github/issues/hcabel/UnrealVsCodeHelper) 6 | ![](https://img.shields.io/github/license/hcabel/UnrealVsCodeHelper) 7 | 8 | 15 | 16 | #### This extension is here to help developing with UnrealEngine.
My ambitions is to give as much tools as VisualAssistX on VisualStudio (We're no even close yet :D) 17 | 18 | > - **`Ideas`** coding@hugocabel.com 19 | > - **`Issues`** https://github.com/hcabel/UnrealVsCodeHelper/issues 20 | > - **`Help me`** https://github.com/hcabel/UnrealVsCodeHelper 21 | 22 | # Features![Target](https://img.shields.io/badge/-Unreal%20Engine-313131?style=for-the-badge&logo=unreal-engine&logoColor=white) ![TargetLanguage](https://img.shields.io/badge/C%2B%2B-00599C?style=for-the-badge&logo=c%2B%2B&logoColor=white) 23 | 24 | ### **`UVCH Browser`** 25 | ![UnrealDocExplorer](./resources/readme/UnrealDocExplorer.gif)
26 | This feature allow you to either go directly to a documentation page from selection.(using ctrl+f1 while selecting text)
Or you can search it with the search bar(or ctrl+alt+f1) to see all the results INSIDE VsCode. 27 | 28 | ### **`ToolBar:`** ![Plateform](https://img.shields.io/badge/Windows_Only-0078D6?style=for-the-badge&logo=windows&logoColor=white) 29 | ![ToolBar](./resources/readme/ToolBar.gif)
30 | No need to mess with the launch/task.json with the ToolBar you can **easily launch your project.** 31 | ### **`SwitchFiles:`** 32 | ![SwitchFile](./resources/readme/SwitchFile.gif)
33 | Allow you to find the header/cpp file corresponding to the file you'r curretly editing 34 | 35 | # Extension Settings 36 | 37 | ### 1- Global 38 | - Disable any of the feature 39 | 40 | ### 2- Toolbar: 41 | - Add any launch parameters to Build/PlayEditor/PlayGame 42 | 43 | ------------------------------------------------------------------------------------------------------------------------ 44 | 45 | ## ***`Enjoy!`*** 46 | -------------------------------------------------------------------------------- /src/SubSystem/SettingsSubsystem.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* SettingsSubsystem.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/06/06 09:49:03 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/06/06 09:49:03 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as vscode from 'vscode'; 14 | import ASubsystem from './Subsystem'; 15 | 16 | const configPrefix = "UVCH"; 17 | export type ConfigSubSection = "Global" | "Toolbar" | "Switch" | "Browser"; 18 | export type ConfigPath = `${ConfigSubSection}.${string}`; 19 | 20 | export default class UVCHSettingsSubsystem extends ASubsystem 21 | { 22 | /** 23 | * Get a property in the UVCH setting 24 | * @param section the seting path to get 25 | * @returns the value of the setting cast as the given type 26 | */ 27 | public static Get(section: ConfigPath): T | undefined { 28 | return (UVCHSettingsSubsystem.GetInstance()!.Get(section)); 29 | } 30 | public Get(section: ConfigPath): T | undefined { 31 | const config = vscode.workspace.getConfiguration(section ? configPrefix : undefined); 32 | if (!config) { 33 | throw Error(`No config found for '${section}'`); 34 | } 35 | return (config.get(section)); 36 | } 37 | 38 | public static Set(section: ConfigPath, value: T): Thenable { 39 | return (UVCHSettingsSubsystem.GetInstance()!.Set(section, value)); 40 | } 41 | public Set(section: ConfigPath, value: T): Thenable { 42 | const config = vscode.workspace.getConfiguration(section ? configPrefix : undefined); 43 | if (!config) { 44 | throw Error(`No config found for '${section}'`); 45 | } 46 | return (config.update(section, value, 1)); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/utils/UETypes.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* UETypes.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/23 11:50:43 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/23 11:50:43 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | export const ModuleLoadingPhaseValue = [ 14 | "EarliestPossible", 15 | "PostConfigInit", 16 | "PostSplashScreen", 17 | "PreEarlyLoadingScreen", 18 | "PreLoadingScreen", 19 | "PreDefault", 20 | "Default", 21 | "PostDefault", 22 | "PostEngineInit", 23 | "None", 24 | "Max" 25 | ]; 26 | 27 | export const ModuleHostTypeValue = [ 28 | "Runtime", 29 | "RuntimeNoCommandlet", 30 | "RuntimeAndProgram", 31 | "CookedOnly", 32 | "UncookedOnly", 33 | "Developer", 34 | "DeveloperTool", 35 | "Editor", 36 | "EditorNoCommandlet", 37 | "EditorAndProgram", 38 | "Program", 39 | "ServerOnly", 40 | "ClientOnly", 41 | "ClientOnlyNoCommandlet", 42 | "Max" 43 | ]; 44 | 45 | export interface IUEModule { 46 | Name: string, 47 | type: number, // ModuleHostTypeValue 48 | LoadingPhase: number, // ModuleLoadingPhaseValue 49 | // @TODO: Add and used the other 50 | } 51 | 52 | export interface IUEPlugin { 53 | Name: string, 54 | bEnabled: boolean, 55 | bOptional?: boolean, 56 | Description?: string 57 | MarketplaceURL?: string 58 | } 59 | 60 | export const ProjectFileVersionValues = [ 61 | "Invalid", 62 | "Initial", 63 | "NameHash", 64 | "ProjectPluginUnification" 65 | ]; 66 | 67 | export interface IUEProject { 68 | FileVersion: number, // ProjectFileVersionValues 69 | EngineAssociation: string, 70 | Category: string, 71 | Description?: string, 72 | Modules?: IUEModule[], 73 | Plugins?: IUEPlugin[], 74 | /* NOT USED */ 75 | // TargetPlatforms?: string, 76 | // EpicSampleNameHash?: number, 77 | // bIsEnterpriseProject: boolean, 78 | // bDisableEnginePluginsByDefault: boolean, 79 | }; -------------------------------------------------------------------------------- /src/Views/components/RestApiEntry.css: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* RestApiEntry.css :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/28 16:16:39 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/28 16:16:39 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | .RestApiEntry { 14 | display: flex; 15 | flex-direction: column; 16 | border: 1px solid transparent; 17 | border-bottom: 0px; 18 | height: 100%; 19 | border-radius: 5px; 20 | background-color: var(--vscode-editor-background); 21 | cursor: pointer; 22 | } 23 | 24 | .RestApiEntry:hover { 25 | border: 1px solid var(--vscode-focusBorder); 26 | } 27 | 28 | .RestApiEntryTitle { 29 | font-size: 16px; 30 | color: #5b96f8; /* 8ab4f8 */ 31 | white-space: nowrap; 32 | overflow: hidden; 33 | margin: 0px 5px 0px 5px; 34 | text-overflow: ellipsis; 35 | } 36 | 37 | .RestApiEntryContent { 38 | height: 100%; 39 | display: flex; 40 | flex-direction: row; 41 | flex: 1; 42 | overflow: hidden; 43 | margin: 0px 5px; 44 | } 45 | 46 | .RestApiEntryThumbnail { 47 | width: 15%; 48 | margin: auto; 49 | } 50 | 51 | @media screen and (max-width: 380px) { 52 | .RestApiEntryThumbnail { 53 | display: none; 54 | } 55 | } 56 | 57 | .RestApiEntrySnippet { 58 | overflow-y: scroll; 59 | overflow-x: hidden; 60 | margin: 2px 0px 0px 5px; 61 | text-overflow: ellipsis; 62 | flex: 1; 63 | } 64 | 65 | .RestApiEntryUrl { 66 | display: inline-block; 67 | font-size: 12px; 68 | background-color: var(--vscode-input-background); 69 | color: rgb(129 137 175); 70 | white-space: nowrap; 71 | overflow: hidden; 72 | padding: 0px 6px; 73 | border-radius: 0px 0px 5px 5px; 74 | text-overflow: ellipsis; 75 | } 76 | 77 | .ImportantLink { 78 | border: 1px solid var(--vscode-editorError-foreground); 79 | } 80 | 81 | .ImportantLink:hover { 82 | border: 1px solid var(--vscode-editorError-foreground); 83 | } -------------------------------------------------------------------------------- /src/Views/components/SearchBar.css: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* SeachBar.css :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/28 14:26:20 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/28 14:26:20 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | .SearchBar { 14 | position: relative; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | 19 | .SearchBarContent { 20 | width: calc(100% - 12px); 21 | height: 100%; 22 | display: flex; 23 | flex-direction: row; 24 | justify-content: center; 25 | padding-top: 6px; 26 | padding-bottom: 6px; 27 | margin: 0 12px 0 2px; 28 | } 29 | 30 | ::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ 31 | color: rgb(178.5, 178.5, 178.5); 32 | opacity: 0.5; /* Firefox */ 33 | } 34 | 35 | :-ms-input-placeholder { /* Internet Explorer 10-11 */ 36 | color: rgb(178.5, 178.5, 178.5); 37 | } 38 | 39 | ::-ms-input-placeholder { /* Microsoft Edge */ 40 | color: rgb(178.5, 178.5, 178.5); 41 | } 42 | 43 | .SearchBarInputWrapper { 44 | height: 100%; 45 | width: 100%; 46 | background-color: var(--vscode-input-background); 47 | display: flex; 48 | flex-direction: row; 49 | align-items: center; 50 | padding: 3px 3px 3px 4px; 51 | } 52 | 53 | .SearchBarInputWrapper.focused { 54 | outline-color: var(--vscode-focusBorder); 55 | outline-width: 1px; 56 | outline-style: solid; 57 | outline-offset: -1px; 58 | opacity: 1 !important; 59 | } 60 | 61 | .SearchBarInput { 62 | width: 100%; 63 | border: none; 64 | color: rgb(204, 204, 204); 65 | background-color: inherit; 66 | font-size: 13px; 67 | font-family: Segoe WPC,Segoe UI,sans-serif; 68 | flex: 1; 69 | } 70 | 71 | .SearchBarInput:focus { 72 | outline: none; 73 | } 74 | 75 | .SearchBarIcon, .SearchBarClearIcon { 76 | cursor: pointer; 77 | background-color: inherit; 78 | width: 16px; 79 | box-sizing: border-box; 80 | } 81 | 82 | .SearchBarIcon { 83 | width: 18px; 84 | } 85 | 86 | .SearchBarClearIcon { 87 | align-items: center; 88 | } 89 | -------------------------------------------------------------------------------- /src/UnrealVsCodeHelper.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* extension.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/21 18:34:27 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/21 18:34:27 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import log_uvch from './utils/log_uvch'; 14 | import * as vscode from 'vscode'; 15 | import SwitchFileSubsystem from './SubSystem/featureSubSystem/SwitchFileSubsystem'; 16 | import UVCHDataSubsystem from './SubSystem/DataSubsystem'; 17 | import UVCHSettingsSubsystem from './SubSystem/SettingsSubsystem'; 18 | import ToolbarSubsystem from './SubSystem/featureSubSystem/ToolbarSubsystem'; 19 | import BrowserSubsystem from './SubSystem/featureSubSystem/BrowserSubsystem'; 20 | import { GoToSettings_Implementation } from './Commands/GeneralCommands'; 21 | 22 | // Function triggered when the 'activationEvents' in the package.json is called 23 | // eslint-disable-next-line @typescript-eslint/naming-convention 24 | export function activate(context: vscode.ExtensionContext) 25 | { 26 | log_uvch.log("[UVHC] activate extension"); 27 | 28 | // Register global commands 29 | context.subscriptions.push(vscode.commands.registerCommand(`UVCH.GoToSettings`, () => { 30 | log_uvch.log(`[UVCH.GoToSettings] Fired`); 31 | return (GoToSettings_Implementation()); 32 | })); 33 | 34 | UVCHSettingsSubsystem.Init(); 35 | UVCHDataSubsystem.Init(); 36 | UVCHDataSubsystem.Set("Context", context); // We store the context to access it wherever we are 37 | 38 | // Feature Subsystems, those are the Subsystems who's handling a specific feature, and they can be disbled/enabled 39 | // You should not use a feature outise of the Subsystems, but if you do make sure there not disabled 40 | SwitchFileSubsystem.Init(); 41 | ToolbarSubsystem.Init(); 42 | BrowserSubsystem.Init(); 43 | } 44 | 45 | // eslint-disable-next-line @typescript-eslint/naming-convention 46 | export function deactivate() { 47 | log_uvch.log("[UVHC] Deactivate extension"); 48 | } 49 | -------------------------------------------------------------------------------- /src/Views/components/RestApiEntry.tsx: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* RestApiEntry.tsx :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/28 16:14:23 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/28 16:14:23 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as React from "react"; 14 | import { IGoogleRequestEntry } from "../../Commands/BrowserCommands"; 15 | 16 | import "./RestApiEntry.css"; 17 | 18 | const usefulURL: string[] = [ 19 | // UFUNCTION 20 | "https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/GameplayArchitecture/Functions", 21 | // UPROPERTY 22 | "https://docs.unrealengine.com/5.0/en-US/unreal-engine-uproperty-specifiers", 23 | // METATAGS 24 | "https://docs.unrealengine.com/5.0/en-US/metadata-specifiers-in-unreal-engine" 25 | ]; 26 | 27 | export function RestApiEntry(props: { vscode: any, item: IGoogleRequestEntry }) 28 | { 29 | // eslint-disable-next-line @typescript-eslint/naming-convention 30 | const [_Thumbnail, /* set_Thumbnail */] = React.useState(props.item.favicons.high_res || ""); 31 | 32 | function OnClick() 33 | { 34 | props.vscode.postMessage({ 35 | action: "ExecuteCommand", 36 | content: { 37 | cmd: "simpleBrowser.api.open", 38 | args: [ 39 | props.item.url, 40 | { 41 | preserveFocus: true, 42 | viewColumn: -2 // This is the value to open the tab "beside" the current one 43 | } 44 | ] 45 | } 46 | }); 47 | } 48 | 49 | return ( 50 |
51 | {/* Title */} 52 |
53 | {props.item.title.replace('...', '')} 54 |
55 | {/* CONTENT */} 56 |
57 | {_Thumbnail && 58 | 59 | } 60 | {/* Snippet */} 61 |
62 | {props.item.description} 63 |
64 |
65 | {/* URL */} 66 |
67 | {props.item.url} 68 |
69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /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/UnrealVsCodeHelper.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', '.tsx', '.js', '.jsx'] 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.(css)$/i, 34 | use: ["style-loader", "css-loader"], 35 | }, 36 | { 37 | test: /\.(ts|tsx|js|jsx)$/, 38 | exclude: /node_modules/, 39 | use: { 40 | loader: 'babel-loader', 41 | options: { 42 | cacheDirectory: true, 43 | cacheCompression: true, 44 | }, 45 | }, 46 | } 47 | ] 48 | }, 49 | devtool: 'nosources-source-map', 50 | infrastructureLogging: { 51 | level: "log", // enables logging required for problem matchers 52 | }, 53 | }; 54 | 55 | function GenerateAllViewConfigutation(views) { 56 | return (views.map((entry) => { 57 | return ({ 58 | target: ['web'], 59 | mode: 'production', 60 | 61 | entry: `./src/Views/${entry}.tsx`, 62 | output: { 63 | path: path.resolve(__dirname, 'dist'), 64 | filename: `UVCH-${entry}.js`, 65 | libraryTarget: 'commonjs2' 66 | }, 67 | resolve: { 68 | extensions: ['.ts', '.tsx', '.js', '.jsx'], 69 | }, 70 | module: { 71 | rules: [ 72 | { 73 | test: /\.(css)$/i, 74 | use: ["style-loader", "css-loader"], 75 | }, 76 | { 77 | test: /\.(ts|tsx|js|jsx)$/, 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 | } 96 | 97 | module.exports = [ extensionConfig, ...GenerateAllViewConfigutation( 98 | [ 99 | "UnrealProjectView", 100 | "UVCHBrowser" 101 | ]) 102 | ]; -------------------------------------------------------------------------------- /.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": [ 13 | "error", 14 | { 15 | "selector": "property", 16 | "modifiers": ["private"], 17 | "format": ["PascalCase"], 18 | "leadingUnderscore": "require" 19 | }, 20 | { 21 | "selector": "variable", 22 | "format": ["camelCase"], 23 | "leadingUnderscore": "forbid", 24 | "trailingUnderscore": "forbid" 25 | }, 26 | { 27 | "selector": "variable", 28 | "modifiers": ["exported"], 29 | "format": ["PascalCase"], 30 | "leadingUnderscore": "forbid", 31 | "trailingUnderscore": "forbid" 32 | }, 33 | { 34 | // Function should be 'PascalCase' but they can have '_Implementation' has a suffix 35 | "selector": "function", 36 | "format": null, 37 | "leadingUnderscore": "forbid", 38 | "trailingUnderscore": "forbid", 39 | "custom": { 40 | "regex": "^([A-Z][A-Za-z]*)+(_Implementation)?$", 41 | "match": true 42 | } 43 | }, 44 | { 45 | "selector": "interface", 46 | "format": ["PascalCase"], 47 | "prefix": ["I"], 48 | "leadingUnderscore": "forbid", 49 | "trailingUnderscore": "forbid" 50 | } 51 | ], 52 | "@typescript-eslint/semi": "error", 53 | 54 | "indent": ["error", "tab"], 55 | "no-multi-str": 2, 56 | "prefer-const": "error", 57 | "no-var": "error", 58 | "keyword-spacing": "error", 59 | "semi-spacing": "error", 60 | "space-before-function-paren": [2, "never"], 61 | "prefer-template": "warn", 62 | "no-dupe-class-members": "error", 63 | "no-const-assign": "error", 64 | "no-new-require": "error", 65 | "no-use-before-define": "off", 66 | "no-unused-vars": "warn", 67 | "@typescript-eslint/no-use-before-define": ["error"], 68 | "yoda": "warn", 69 | "radix": [2, "as-needed"], 70 | "no-shadow": "error", 71 | "no-void": "error", 72 | "no-self-assign": "error", 73 | "no-self-compare": "error", 74 | "no-new-func": "error", 75 | "no-new": "off", 76 | "curly": "error", 77 | "dot-notation": "error", 78 | "eqeqeq": "error", 79 | "no-div-regex": "error", 80 | "no-else-return": "error", 81 | "no-empty-pattern": "error", 82 | "no-eq-null": "error", 83 | "no-floating-decimal": "error", 84 | "no-redeclare": "error", 85 | "no-return-assign": "error", 86 | "no-lone-blocks": "error", 87 | "no-multi-spaces": "error", 88 | "no-native-reassign": "error", 89 | "no-throw-literal": "error", 90 | "no-unused-expressions": "error", 91 | "no-useless-concat": "error", 92 | "no-unmodified-loop-condition": "error", 93 | "no-multiple-empty-lines": ["error", {"max": 1}], 94 | "no-implied-eval": "error", 95 | "semi": "error", 96 | "global-require": "error" 97 | }, 98 | "ignorePatterns": [ 99 | "out", 100 | "dist", 101 | "**/*.d.ts" 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /src/Views/components/Toolbar.tsx: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* Toolbar.tsx :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/24 16:12:35 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/24 16:12:35 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as React from "react"; 14 | import Tooltip from "./ToolTip"; 15 | 16 | import "./ToolBar.css"; 17 | 18 | export default function ToolBar(props: { vscode: any }) 19 | { 20 | function OnPlayGameButtonClick() 21 | { 22 | props.vscode.postMessage({ 23 | action: "ExecuteCommand", 24 | content: { 25 | cmd: "UVCH.PlayGame" 26 | } 27 | }); 28 | } 29 | 30 | function OnPlayEditorButtonClick() 31 | { 32 | props.vscode.postMessage({ 33 | action: "ExecuteCommand", 34 | content: { 35 | cmd: "UVCH.PlayEditor" 36 | } 37 | }); 38 | } 39 | 40 | function OnBuildButtonClick() 41 | { 42 | props.vscode.postMessage({ 43 | action: "ExecuteCommand", 44 | content: { 45 | cmd: "UVCH.BuildEditor" 46 | } 47 | }); 48 | } 49 | 50 | return ( 51 |
56 |
66 | 67 | 71 | 72 | 73 | 74 | 75 | 79 | 80 | 81 | 82 | 83 | 84 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
105 |
106 | ); 107 | } -------------------------------------------------------------------------------- /src/Views/UnrealProjectView.tsx: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* UnrealProjectView.tsx :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/21 20:10:15 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/21 20:10:15 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as React from 'react'; 14 | import * as ReactDOM from 'react-dom'; 15 | import { IProjectInfos } from '../Commands/ToolbarCommands'; 16 | import ToolBar from './components/Toolbar'; 17 | 18 | declare const window: Window & { 19 | acquireVsCodeApi: any 20 | }; 21 | 22 | function UnrealProjectView(props: { vscode: any }) 23 | { 24 | // eslint-disable-next-line @typescript-eslint/naming-convention 25 | const [_ProjectInfos, set_ProjectInfos] = React.useState(); 26 | // eslint-disable-next-line @typescript-eslint/naming-convention 27 | const [_ErrorMessage, set_ErrorMessage] = React.useState("Loading..."); 28 | 29 | function UpdateProjectInfos(projectInfos: any) 30 | { 31 | projectInfos = projectInfos || undefined; 32 | set_ProjectInfos(projectInfos); 33 | 34 | if (!projectInfos) { 35 | set_ErrorMessage("Unable to find your project"); 36 | } 37 | } 38 | 39 | // Message Received from VsCode 40 | function OnMessage(message: any) { 41 | if (message.data.type === "Update-Project-Infos") { 42 | UpdateProjectInfos(message.data.data); 43 | } 44 | } 45 | 46 | // Constructor 47 | React.useEffect(() => { 48 | window.addEventListener('message', OnMessage); 49 | 50 | // Listen to our data changes 51 | props.vscode.postMessage({ 52 | action: "ListenToDataSubsystem", 53 | content: [ 54 | { dataKey: "ProjectInfos", callbackMessageType: "Update-Project-Infos" } 55 | ] 56 | }); 57 | 58 | // Refresh project infos (or find them if never open before) 59 | props.vscode.postMessage({ 60 | action: "ExecuteCommand", 61 | content: { 62 | cmd: "UVCH.GetProjectInfos" 63 | } 64 | }); 65 | 66 | return () => window.removeEventListener('message', OnMessage); 67 | }, [false]); 68 | 69 | return ( 70 |
71 | {_ProjectInfos === undefined ? 72 | // ERROR MSG 73 |

74 | {_ErrorMessage} 75 |

76 | : 77 | // INTERFACE 78 |
79 |
84 |

85 | {_ProjectInfos.Name} 86 |

87 |
88 | {_ProjectInfos.UnrealVersion} 89 |
90 |
91 | 92 |
101 | {/* @TODO: Do this programmatically */} 102 | Last update: 02/02/2023 103 |
104 |
105 | } 106 |
107 | ); 108 | } 109 | 110 | ReactDOM.render( 111 | , 112 | document.getElementById('UnrealProjectView-root') 113 | ); -------------------------------------------------------------------------------- /src/Views/UVCHBrowser.tsx: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* UVCHBrowser.tsx :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/21 20:10:15 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/21 20:10:15 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as React from 'react'; 14 | import * as ReactDOM from 'react-dom'; 15 | import { IGoogleQuery, IGoogleRequest, IGoogleRequestEntry } from '../Commands/BrowserCommands'; 16 | import { RestApiEntry } from './components/RestApiEntry'; 17 | import SearchBar from './components/SearchBar'; 18 | 19 | import "./UVCHBrowser.css"; 20 | 21 | declare const window: Window & { 22 | acquireVsCodeApi: any 23 | }; 24 | 25 | function UVCHBrowser(props: { vscode: any }) 26 | { 27 | // eslint-disable-next-line @typescript-eslint/naming-convention 28 | const [_RestApiItems, set_RestApiItems] = React.useState([]); 29 | // eslint-disable-next-line @typescript-eslint/naming-convention 30 | const [_Query, set_Query] = React.useState(); 31 | // eslint-disable-next-line @typescript-eslint/naming-convention 32 | const [_Loading, set_Loading] = React.useState(false); 33 | 34 | function UpdateUrls(request: IGoogleRequest | undefined) 35 | { 36 | set_Loading(false); 37 | if (request) { 38 | set_RestApiItems(request.result || []); 39 | set_Query(request.query); 40 | } 41 | } 42 | 43 | function OnMessage(message: any) { 44 | if (message.data.type === "OnNewQuery") { 45 | UpdateUrls(message.data.data); 46 | } 47 | } 48 | 49 | function OnBlur(value: string) 50 | { 51 | if (value) { 52 | set_Loading(true); 53 | props.vscode.postMessage({ 54 | action: "ExecuteCommand", 55 | content: { 56 | cmd: "UVCH.BrowserSearch", 57 | args: [value] 58 | } 59 | }); 60 | } 61 | } 62 | 63 | // constructor 64 | React.useEffect(() => { 65 | window.addEventListener('message', OnMessage); 66 | 67 | props.vscode.postMessage({ 68 | action: "ListenToDataSubsystem", 69 | content: [ 70 | { dataKey: "LastGoogleRequest", callbackMessageType: "OnNewQuery" } 71 | ] 72 | }); 73 | 74 | return () => window.removeEventListener('message', OnMessage); 75 | }, [ false ]); 76 | 77 | return ( 78 |
79 |
80 | 86 |
87 |
    88 | {_Loading ? 89 | // LOADING ANIMATION 90 | Array.from(Array(10).keys()).map(() => { 91 | return ( 92 |
  • 93 | ); 94 | }) 95 | : 96 | (_RestApiItems.length > 0 ? 97 | // ALL RESULTS 98 | _RestApiItems.map((item: IGoogleRequestEntry) => { 99 | return ( 100 |
  • 101 | 102 |
  • 103 | ); 104 | }) 105 | : 106 | // NOTHING FOUND 107 |
  • 108 |
    109 | {!_Query ? "Nothing to search" : "No results found"} 110 |
    111 |
  • 112 | ) 113 | } 114 |
115 |
116 | ); 117 | } 118 | 119 | ReactDOM.render( 120 | , 121 | document.getElementById('UVCHBrowser-root') 122 | ); -------------------------------------------------------------------------------- /src/Views/components/SearchBar.tsx: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* SearchBar.tsx :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/27 15:38:37 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/27 15:38:37 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as React from "react"; 14 | 15 | import "./SearchBar.css"; 16 | 17 | export type blurType = "OnExit" | "Dynamicaly" | "OnEnter" | "OnEscape" | "OnIconClicked"; 18 | 19 | export interface ICSearchBarProps { 20 | value?: string, 21 | className?: string; 22 | blurType: blurType[], 23 | onBlur?: (value: string) => void 24 | onChange?: (event: React.ChangeEvent) => void, 25 | onFocus?: (event: React.FocusEvent) => void, 26 | placeholder?: string, 27 | } 28 | 29 | export default function SearchBar(props: ICSearchBarProps) 30 | { 31 | // eslint-disable-next-line @typescript-eslint/naming-convention 32 | const [_Focus, set_Focus] = React.useState(false); 33 | // eslint-disable-next-line @typescript-eslint/naming-convention 34 | const [_BlurType, set_BlurType] = React.useState("OnExit"); 35 | // eslint-disable-next-line @typescript-eslint/naming-convention 36 | const [_Value, set_Value] = React.useState(""); 37 | // eslint-disable-next-line @typescript-eslint/naming-convention 38 | // const [_Timer, set_Timer] = React.useState(undefined); 39 | 40 | function OnBlur() 41 | { 42 | document.onkeydown = function() {}; 43 | 44 | if (props.onBlur && props.blurType.includes(_BlurType)) { 45 | props.onBlur(_Value); 46 | } 47 | set_BlurType("OnExit"); 48 | set_Focus(false); 49 | } 50 | 51 | function OnChange(event: React.ChangeEvent) 52 | { 53 | set_Value(event.target.value); 54 | 55 | // if (props.dynamicSearch) { 56 | // if (_Timer) { 57 | // clearTimeout(_Timer); 58 | // } 59 | // set_Timer(setTimeout(() => { 60 | // OnBlur(); 61 | // })); 62 | // } 63 | } 64 | 65 | function OnFocus() 66 | { 67 | set_Focus(true); 68 | document.onkeydown = (event: KeyboardEvent) => { 69 | if (event.key === "Escape") { 70 | event.preventDefault(); 71 | 72 | set_BlurType("OnEscape"); 73 | } 74 | else if (event.key === "Enter") { 75 | event.preventDefault(); 76 | 77 | set_BlurType("OnEnter"); 78 | } 79 | }; 80 | } 81 | 82 | React.useEffect(() => { 83 | if (_BlurType !== "OnExit") { 84 | if (_BlurType === "OnIconClicked") { 85 | OnBlur(); // Can't blur input if the input is not focused 86 | } 87 | else { 88 | document.getElementById("searchBar")?.blur(); 89 | } 90 | } 91 | }); 92 | 93 | React.useEffect(() => { 94 | if (props.value) { 95 | set_Value(props.value); 96 | } 97 | }, [ props.value ]); 98 | 99 | return ( 100 |
101 |
102 | {/* Search bar icon, look like: > */} 103 | set_BlurType("OnIconClicked")} viewBox="0 0 16 16"> 104 | 105 | 106 |
107 | 117 | {/* Clear bar icon, look like: X */} 118 | {_Value !== "" && 119 | set_Value("")} viewBox="0 0 16 16"> 120 | 121 | 122 | } 123 |
124 |
125 |
126 | ); 127 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "UnrealVsCodeHelper" extension will be documented in this file. 4 | 5 | ## `[Unreleased]` 6 | - Auto completions on unreal's enums 7 | - define tools suggestions 8 | - Infos details on specifique unreal defines/function/modules 9 | - Rename project scripts 10 | - package project scripts 11 | - Clean project scripts 12 | 13 | ## `[1.1.1] - 2022/02/02` 14 | 15 | ### Fix 16 | - `Global` Fix content policy issue 17 | 18 | ## `[1.1.0] - 2022/08/23` 19 | 20 | ### Added 21 | - `Browser` Add new usefull link for Unreal meta tags 22 | - `Global` Add gear icon on every view to access UVCH settings easily 23 | 24 | ### Change 25 | - `Shortcut` QuickSearch triggerable when you focusing a terminal (Not from selection though) 26 | - `Browser` Replace Search method from 'Google API' to the npm package 'GoogleThis' because free tu use 27 | 28 | ### Fix 29 | - `Browser` Fix bug who was duplicating search versions 30 | - `Global` Fix the reload logo on the project view who wasn't showing up 31 | 32 | ## `[1.0.2] - 2022/08/05` 33 | 34 | ### Changed 35 | - `Readme` Add the "video readme" to the readme and some automated badges 36 | 37 | ## `[1.0.1] - 2022/06/25` 38 | ### Added 39 | - `Browser:` You can now change the request format in the settings (eg: if you want to add 'C++' to all your requests or if you don't want the current unreal version to be added) 40 | - `Browser:` New setting to set where you want the browser tab to be oppened 41 | 42 | ### Fix 43 | - `ToolBar:` Now play commands/buttons are waiting for the build to finish before executing 44 | - `SwitchFile:` Resolve crash when no public/private folder in path 45 | - `Interfaces:` Resolve crash when moving any interface panel 46 | 47 | ### Changed 48 | - `Browser:` Error message when the request show no result 49 | - `SwitchFile:` Error message when switching from unsupported file 50 | 51 | ### Remove 52 | - `SwitchFile:` Hide status bar when on unsupported file 53 | 54 | ## `[1.0.0] - 2022/06/13` 55 | ### Added 56 | - `Features:` You can disable any of the feature in the settings INDIVIDUALLY 57 | - `ToolBar:` New settings to add any additional flags to build/play(game)/play(editor) commands 58 | 59 | ### Changed 60 | - `Browser:` Fix RestItems design 61 | 62 | ## `[0.3.3] - 2022/06/01` 63 | ### Changed 64 | - `ToolBar:` Use vscode feature(task/debuger) to: launch editor, build and play game 65 | 66 | ## `[0.3.2] - 2022/05/31` 67 | ### Added 68 | - `Browser:` Highlight very usefull website in the Unreal documentation explorer. (if you want a page to be added, please contact me) 69 | 70 | ### Changed 71 | - `Browser:` Documentation pages ar now open directly in VS Code, instead of opening a new browser tab. 72 | 73 | 74 | ## `[0.3.1] - 2022/05/29` 75 | ### Added 76 | - `Interfaces:` Color responsive to your current theme 77 | 78 | ### Changed 79 | - `Browser:` design and code refactoring 80 | - `Browser:` Search image is hidden when the panel is too small to give more space to the snippets 81 | 82 | ### Fixed 83 | - `Browser:` Fix scroll Y axis 84 | - `Browser:` Resove stuck in loading animation when searching for the same thing 85 | 86 | ## `[0.3.0] - 2022/05/28` 87 | ### Added 88 | - `Browser:` New command ***UVCH.QuickSearch*** who's opening documentation page in your browser 89 | - `Browser:` New shortcut ***alt+f1*** Triggering ***UVCH.QuickSearch*** with the current selection has keyword 90 | - `Browser:` New interface panel ***UVCH Browser***, a small browser for unreal documention INSIDE VsCode 91 | - `Browser:` New shortcut ***alt+shift+f1*** triggering ***UVCH.BrowserSearch*** Allowing you do to do a reseach from the current selection without opening the browser tab 92 | 93 | ## `[0.2.0] - 2022/05/27` 94 | ### Added 95 | - `SwitchFile:` New command ***UVCH.SwitchHeaderCppFile*** Switching between header/cpp files, when on *.h, *.hpp and *.cpp file 96 | - `SwitchFile:` New statusBar showing you which SwitchFile is correponding with the current file 97 | - `SwitchFile:` New shortcut ***alt+o*** Triggering ***UVCH.SwitchHeaderCppFile*** commands 98 | 99 | ## `[0.1.1] - 2022/05/25` 100 | ### Added 101 | - `Global:` Add new AWSOME logo !!!! 102 | 103 | ### fixed 104 | - `Global:` Fix minor bug 105 | - `Global:` Fix ***package.json*** unacurate informations 106 | 107 | ### remove 108 | - `Global:` Remvoe unecessary popup 109 | 110 | ## `[0.1.0] - 2022/05/24` 111 | ### Added 112 | - `ToolBar:` New command ***UVCH.PlayGame*** Start your project has a game 113 | - `ToolBar:` New command ***UVCH.PlayEditor*** Start your project in the editor 114 | - `ToolBar:` New command ***UVCH.BuildEditor*** Build your project for the editor 115 | - `Global:` New ActionBar ***UVCH*** Open UnrealVsCodeHelper side panel 116 | - `ToolBar:` New Panel ***UnrealProjectView*** Showing the ToolBar feature interface 117 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | coding@hugocabel.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/SubSystem/DataSubsystem.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* DataSubsystem.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/21 19:26:06 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/21 19:26:06 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import ASubsystem from "./Subsystem"; 14 | 15 | // Prototype of the listener functions 16 | export type DataListener = (datas: T | undefined) => void; 17 | 18 | /** 19 | * A Class storing data with listeners to those data 20 | */ 21 | export class DataPropertie 22 | { 23 | // All the function to call when the data is changed 24 | private _Listeners: DataListener[] = []; 25 | // The data 26 | private _Data: T | undefined; 27 | 28 | constructor(data: T | undefined = undefined, listeners: DataListener[] | undefined = undefined) { 29 | this._Data = data; 30 | if (listeners) { 31 | this._Listeners = listeners; 32 | for (const listener of this._Listeners) { 33 | listener(this._Data); 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * GET accessor (eg: myDataPropertie.data) 40 | * @return The data 41 | */ 42 | get data(): T | undefined { 43 | return (this._Data); 44 | } 45 | /** 46 | * SET accessor to the data (eg: myDataPropertie.data = newData) 47 | * Set the data then call all the listener 48 | * @param newData The new value of the data 49 | */ 50 | set data(newData: T | undefined) { 51 | this._Data = newData; 52 | this._Listeners.forEach((listener: DataListener) => { 53 | listener(this._Data); 54 | }); 55 | } 56 | 57 | /** 58 | * Add a new listerner function 59 | * 60 | * @param listener The new function to add to the listeners array 61 | */ 62 | public Listen(listener: DataListener) { 63 | this._Listeners = this._Listeners.concat([listener]); 64 | } 65 | 66 | /** 67 | * Remove all the listerner functions 68 | */ 69 | public ClearListeners() { 70 | this._Listeners = []; 71 | } 72 | 73 | /** 74 | * Remove a function from the listeners 75 | * 76 | * @param listenerToRemove The function that you want to remove from the listeners 77 | * @returns If it has successully been removed 78 | */ 79 | public RemoveListener(listenerToRemove: DataListener) { 80 | const oldLength = this._Listeners.length; 81 | this._Listeners = this._Listeners.filter((listener: DataListener) => { 82 | return (listener !== listenerToRemove); 83 | }); 84 | 85 | return (this._Listeners.length + 1 === oldLength); 86 | } 87 | } 88 | 89 | /** 90 | * This class will get store/manage all the data of UVCH 91 | */ 92 | export default class UVCHDataSubsystem extends ASubsystem 93 | { 94 | // Map where are stored all the data and their listeners 95 | private _Datas: Map> = new Map(); 96 | 97 | public Init() {} 98 | 99 | /** 100 | * Get data 101 | * 102 | * @param key The key how's referencing your data 103 | * @returns The data or undefined if key not exist 104 | */ 105 | public static Get(key: string): T | undefined { 106 | return (UVCHDataSubsystem.GetInstance()!._Datas.get(key)?.data); 107 | } 108 | 109 | /** 110 | * This add/update the data at a certain key 111 | * 112 | * @param key The key how's gonna reference your data (or already is if exist) 113 | * @param value The value of the data that you want to add/update 114 | */ 115 | public static Set(key: string, value: T | undefined) { 116 | const data = UVCHDataSubsystem.GetInstance()!._Datas.get(key); 117 | 118 | if (!data) { 119 | // Not already exist, create new one 120 | UVCHDataSubsystem.GetInstance()!._Datas.set(key, new DataPropertie(value)); 121 | } 122 | else { 123 | // Already exist, update value 124 | data.data = value; 125 | } 126 | } 127 | 128 | /** 129 | * Allow you to add a listener function, that will be triggered every time the key value is change 130 | * 131 | * @param key The key how's referencing the data you want to listen 132 | * @param listener Your function that you want to be called when data is changed 133 | */ 134 | public static Listen(key: string, listener: DataListener) { 135 | const data = UVCHDataSubsystem.GetInstance()!._Datas.get(key); 136 | 137 | if (!data) { 138 | // If key doesn't exist create one with the listener and a value of undefined 139 | UVCHDataSubsystem.GetInstance()!._Datas.set(key, new DataPropertie(undefined, [listener])); 140 | } 141 | else { 142 | // Add listener 143 | data.Listen(listener); 144 | listener(data.data); 145 | } 146 | } 147 | 148 | /** 149 | * Remove function from the listening one 150 | * 151 | * @param key, The key how's referencing the data that you want to unlisten 152 | * @param listenerToRemove, The listener function that you want to remove 153 | * @returns true or false, depending on if the listener has been removed successfully 154 | */ 155 | public static RemoveListener(key: string, listenerToRemove: DataListener): boolean { 156 | const data = UVCHDataSubsystem.GetInstance()!._Datas.get(key); 157 | 158 | if (data) { 159 | return (data.RemoveListener(listenerToRemove)); 160 | } 161 | return (false); 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /src/SubSystem/featureSubSystem/FeatureSubSystem.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* FeatureSubSystem.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/06/12 18:37:27 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/06/12 18:37:27 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as vscode from 'vscode'; 14 | import log_uvch from '../../utils/log_uvch'; 15 | import UVCHDataSubsystem from '../DataSubsystem'; 16 | import UVCHSettingsSubsystem, { ConfigPath } from '../SettingsSubsystem'; 17 | import ASubsystem from '../Subsystem'; 18 | import UVCHWebViewSubsystem, { IReactWebView } from '../WebViewSubsystem'; 19 | 20 | export interface IVscodeCommand { 21 | cmd: string, 22 | func: (...args: any[]) => any 23 | } 24 | 25 | /** 26 | * This class purpose is to manage the features of the extension 27 | */ 28 | export default class AFeatureSubSystem extends ASubsystem 29 | { 30 | // Human readable name of the feature 31 | protected _FeatureName: string = "Unknown"; 32 | // Path to the config file to enable/disable the feature 33 | protected _EnableConfigPath: ConfigPath | undefined = undefined; 34 | // list of commands related to the feature 35 | protected _Commands: IVscodeCommand[] = []; 36 | // list of views/panel related to the feature 37 | protected _Views: IReactWebView[] = []; 38 | 39 | /** 40 | * This function allow you to overrite the properties before being initalized 41 | * @note for some reason when I'm doing on the constructor, the properties are not set 42 | */ 43 | protected Assign() 44 | { 45 | this._FeatureName = "Unknown"; 46 | this._EnableConfigPath = undefined; 47 | this._Commands = []; 48 | this._Views = []; 49 | } 50 | 51 | /** 52 | * This function is called when the extension is loaded 53 | */ 54 | protected Init() 55 | { 56 | const activated = (this._EnableConfigPath ? UVCHSettingsSubsystem.Get(this._EnableConfigPath) : false); 57 | log_uvch.log(`[FEATURE] ${activated ? "Enable" : "Disable"} [${this._FeatureName}]`); 58 | if (activated) { 59 | this.RegisterCommands(this._Commands || []); 60 | this.RegisterViews(this._Views || []); 61 | this.Activate(); 62 | } 63 | else { 64 | // Register commands overriding implementation to show an error message 65 | // Do not add a 'when' condition to the command because we want to display this nice message 66 | // with a button allowing user to easily turn the feature back on 67 | this.RegisterCommands((this._Commands || []).map((cmd) => { 68 | // We had to stored it before because calling this in the function wont be equal to the feature subsystem 69 | const enableConfigPath = this._EnableConfigPath!; 70 | return ({ 71 | cmd: cmd.cmd, 72 | func: () => { 73 | log_uvch.log(`[UVCH] ${cmd.cmd} is desactivated`); 74 | vscode.window.showInformationMessage( 75 | `${this._FeatureName} feature is disabled in the settings.`, 76 | "Turn back on", 77 | "Open settings" 78 | ).then((selection) => { 79 | if (selection === "Open settings") { 80 | vscode.commands.executeCommand("workbench.action.openWorkspaceSettings", 81 | { jsonEditor: false, query: `@ext:HugoCabel.uvch` } 82 | ); 83 | } 84 | else if (selection === "Turn back on") { 85 | UVCHSettingsSubsystem.Set(enableConfigPath, true) 86 | .then(() => { 87 | vscode.commands.executeCommand("workbench.action.reloadWindow"); 88 | }); 89 | } 90 | }); 91 | } 92 | }); 93 | })); 94 | // We can't unregister the views because this is controlled by the package json 95 | // when registering view, we actually just assigning HTML/ReactComponent to the view how's automatically 96 | // Registered by vscode. 97 | // /!\ Make sure to add a 'when' condition to your view in the package JSON to avoid vscode to register it 98 | // eg: "when": "congig.path.to.my.setting == true" 99 | 100 | this.Desactivate(); 101 | } 102 | } 103 | 104 | // To override 105 | /** 106 | * This function is called when the extension is activated 107 | */ 108 | protected Activate() {} 109 | /** 110 | * This function is called when the extension is desactivated 111 | */ 112 | protected Desactivate() {} 113 | 114 | /** 115 | * This will register the commands to the vscode 116 | * @param commands, list of commands to register 117 | * @throws if is not context is found in the DataSubsystem 118 | */ 119 | protected RegisterCommands(commands: IVscodeCommand[]): void { 120 | const context = UVCHDataSubsystem.Get("Context"); 121 | if (!context) { 122 | throw Error("[UVCH] Context is not defined"); 123 | } 124 | 125 | commands.forEach((command: IVscodeCommand) => { 126 | log_uvch.log(`[UVHC] Register commands [UVCH.${command.cmd}]`); 127 | context.subscriptions.push(vscode.commands.registerCommand(`UVCH.${command.cmd}`, (...args: any[]) => { 128 | log_uvch.log(`[UVCH.${command.cmd}] Fired`); 129 | return (command.func(...args)); 130 | })); 131 | }); 132 | } 133 | 134 | /** 135 | * This will set the views registered in package.json 136 | * @param views list of views to register 137 | */ 138 | protected RegisterViews(views: IReactWebView[]): void { 139 | views.forEach((reactView: IReactWebView) => { 140 | const view = UVCHWebViewSubsystem.GetView(reactView.viewId); 141 | if (view) { // if the view is already registered, we add a new panel to it 142 | reactView.panelIds.forEach((panelId: string) => { 143 | log_uvch.log(`[VIEW_${reactView.viewId}] Add pannel [VIEW_${panelId}]`); 144 | view.RegisterNewPanel(panelId); 145 | }); 146 | } 147 | else { 148 | log_uvch.log(`[UVHC] Register view [VIEW_${reactView.viewId}]`); 149 | UVCHWebViewSubsystem.RegisterNewView(reactView); 150 | } 151 | }); 152 | } 153 | } -------------------------------------------------------------------------------- /src/Commands/BrowserCommands.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* BrowserCommands.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/27 13:07:59 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/27 13:07:59 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as vscode from 'vscode'; 14 | import GoogleThis from 'googlethis'; 15 | import UVCHDataSubsystem from "../SubSystem/DataSubsystem"; 16 | import { IProjectInfos } from "./ToolbarCommands"; 17 | import log_uvch from '../utils/log_uvch'; 18 | import UVCHSettingsSubsystem from '../SubSystem/SettingsSubsystem'; 19 | 20 | export interface IGoogleRequestEntry { 21 | title: string, 22 | description: string, 23 | url: string, 24 | favicons: { 25 | high_res: string, 26 | low_res: string, 27 | } 28 | }; 29 | export interface IGoogleQuery { 30 | keyword: string, // The keyword originally used 31 | formatedQuery: string, // The query formated with the user format in the settings 32 | fullQuery: string, // formatedQuery + hidden properties (e.g.: allowed websites) 33 | options: object // The options to use for the query 34 | } 35 | 36 | export interface IGoogleRequest { 37 | query: IGoogleQuery, 38 | result: IGoogleRequestEntry[] 39 | } 40 | 41 | async function VerifyKeyword(keyword: string): Promise 42 | { 43 | if (!keyword || keyword === "") { 44 | // If no keyword is provided, we show a input box for the user to enter a keyword 45 | const inputSearch = await vscode.window.showInputBox({ 46 | title: 'DocSearch input box', 47 | placeHolder: 'DocSearch...', 48 | }); 49 | // If the user cancel the input box, we return false 50 | if (!inputSearch) { 51 | return (""); 52 | } 53 | keyword = inputSearch; 54 | } 55 | return (keyword); 56 | } 57 | 58 | function OpenPage(url: string) 59 | { 60 | // Get Settings 61 | const settingOpenPanelLocation = UVCHSettingsSubsystem.Get("Browser.OpenPanelLocation"); 62 | if (!settingOpenPanelLocation) { 63 | throw new Error("OpenPanelLocation setting is not set"); 64 | } 65 | 66 | let viewColumn: number = -1; 67 | switch (settingOpenPanelLocation) 68 | { 69 | case "Active": 70 | viewColumn = -1; break; 71 | case "Beside": 72 | viewColumn = -2; break; 73 | case "One": 74 | viewColumn = 1; break; 75 | case "Two": 76 | viewColumn = 2; break; 77 | case "Three": 78 | viewColumn = 3; break; 79 | case "Four": 80 | viewColumn = 4; break; 81 | case "Five": 82 | viewColumn = 5; break; 83 | case "Six": 84 | viewColumn = 6; break; 85 | case "Seven": 86 | viewColumn = 7; break; 87 | case "Eight": 88 | viewColumn = 8; break; 89 | case "Nine": 90 | viewColumn = 9; break; 91 | default: // Beside 92 | viewColumn = -1; break; 93 | } 94 | 95 | // @TODO: add settings to redirect instead of open in vscode (and maybe choose his browser) 96 | vscode.commands.executeCommand('simpleBrowser.api.open', url, { 97 | preserveFocus: true, 98 | viewColumn: viewColumn 99 | }); 100 | } 101 | 102 | function FormatQuery(keyword: string, websiteList: string[] = []): IGoogleQuery | undefined 103 | { 104 | // Remove extra white spaces 105 | keyword = keyword.replace(/\s\s+/gm, ' '); 106 | keyword = keyword.trim(); 107 | 108 | // If keyword is not valid, we return false 109 | if (!keyword || /^\s*$/.test(keyword)) { 110 | vscode.window.showErrorMessage(`Invalid keyword: '${keyword}'`); 111 | return (undefined); 112 | } 113 | 114 | // Construct query from settings format 115 | const settingSearchFormat = UVCHSettingsSubsystem.Get("Browser.SearchFormat") || ""; 116 | const projectInfos = UVCHDataSubsystem.Get("ProjectInfos"); 117 | 118 | const bKeywordContainVersion: boolean = 119 | RegExp(/(\s|^)((UE[0-9])|((UE)?[0-9]\.[0-9][0-9]?)|((UE)?[0-9]\.[0-9][0-9]\.[0-9][0-9]?))(\s|$)/i) 120 | .test(keyword); 121 | 122 | const query = settingSearchFormat 123 | .replace("%KEYWORD%", keyword) 124 | .replace("%VERSION%", (bKeywordContainVersion ? "" : projectInfos?.UnrealVersion || "")) 125 | .trim(); 126 | 127 | // format all the url into a single string parsable by the google search engine 128 | const allowedWebsiteString = 129 | (websiteList.length > 0 ? ` site:${websiteList.join(" OR site:")}` : ""); 130 | 131 | return ({ 132 | formatedQuery: query, 133 | fullQuery: query + allowedWebsiteString, 134 | keyword: keyword, 135 | options: { 136 | page: 0, 137 | safe: false, 138 | additional_params: {} 139 | } 140 | }); 141 | } 142 | 143 | async function SendQuery(query: IGoogleQuery): Promise 144 | { 145 | // Sending request 146 | const newRequest = await GoogleThis.search(query.fullQuery, query.options); 147 | const result = { 148 | query: query, 149 | result: newRequest.results 150 | }; 151 | console.log({ query: query.fullQuery, res: result.result.map((r: any) => r.url)}); 152 | // Update the value 153 | UVCHDataSubsystem.Set("LastGoogleRequest", result); 154 | 155 | if (!result.result || result.result.length === 0) { 156 | vscode.window.showErrorMessage(`No result found for '${query.formatedQuery}'`); 157 | } 158 | 159 | return (result); 160 | } 161 | 162 | export async function QuickSearch_Implementation(keyword: string = "", open: boolean = false): Promise 163 | { 164 | keyword = await VerifyKeyword(keyword); 165 | if (!keyword) { 166 | return (false); 167 | } 168 | 169 | // Get website list in the settings 170 | const websiteList = UVCHSettingsSubsystem.Get("Browser.QuickSearchAllowedWebsite") || []; 171 | // Format the query 172 | const query = FormatQuery(keyword, websiteList); 173 | if (!query || !query.keyword || !query.formatedQuery || !query.fullQuery) { 174 | vscode.window.showErrorMessage(`Invalid query: '${keyword}'`); 175 | return (false); 176 | } 177 | 178 | // Show UVCHBrowser 179 | vscode.commands.executeCommand("UVCHBrowser.focus"); 180 | 181 | const request = await SendQuery(query); 182 | if (request.result && open) { 183 | log_uvch.log(`[UVHC] open url: '${request.result[0].url}' from keyword '${keyword}'`); 184 | // Open the page into vscode using Simple Browser 185 | OpenPage(request.result[0].url); 186 | } 187 | 188 | return (request.result && request.result.length > 0 ? true : false); 189 | } 190 | 191 | export function QuickSearchFromSelection_Implementation(open: boolean = true) 192 | { 193 | // Find current selection text 194 | const editor = vscode.window.activeTextEditor; 195 | const selection = editor?.document.getText(editor.selection); 196 | 197 | // Send request with keyword = selection 198 | QuickSearch_Implementation(selection || "", open); 199 | } 200 | 201 | export async function BrowserSearch_Implementation(keyword: string = ""): Promise 202 | { 203 | if (!keyword) { 204 | return (false); 205 | } 206 | 207 | // Get website list in the settings 208 | const websiteList = UVCHSettingsSubsystem.Get("Browser.Browser.SearchBarAllowedWebsite") || []; 209 | // Format the query 210 | const query = FormatQuery(keyword, websiteList); 211 | if (!query || !query.keyword || !query.formatedQuery || !query.fullQuery) { 212 | vscode.window.showErrorMessage(`Invalid query: '${query}'`); 213 | return (false); 214 | } 215 | 216 | // Show UVCHBrowser 217 | vscode.commands.executeCommand("UVCHBrowser.focus"); 218 | 219 | const request = await SendQuery(query); 220 | 221 | return (request.result && request.result.length > 0 ? true : false); 222 | } 223 | 224 | export function BrowserSearchFromSelection_Implementation() 225 | { 226 | // Find current selection text 227 | const editor = vscode.window.activeTextEditor; 228 | const selection = editor?.document.getText(editor.selection); 229 | 230 | // Send request with keyword = selection 231 | BrowserSearch_Implementation(selection || ""); 232 | } -------------------------------------------------------------------------------- /src/SubSystem/WebViewSubsystem.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* controllers.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/22 13:34:22 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/22 13:34:22 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as path from 'path'; 14 | import * as vscode from 'vscode'; 15 | import UVCHDataSubsystem from './DataSubsystem'; 16 | import log_uvch from '../utils/log_uvch'; 17 | import ASubsystem from './Subsystem'; 18 | 19 | export interface IAction { 20 | action: string, 21 | content: any 22 | } 23 | 24 | export interface IReactWebView { 25 | viewId: string, 26 | panelIds: string[] 27 | } 28 | 29 | /** 30 | * This class is the handler of a single Section in a webview 31 | */ 32 | export class ViewPanelBase 33 | { 34 | private readonly _BundleFileName: string = ''; 35 | private readonly _Context: vscode.ExtensionContext; 36 | protected readonly _PanelId: string = ''; 37 | private _Panel: vscode.WebviewView | undefined; 38 | protected _ListeningDataKeys: string[] = []; 39 | 40 | constructor(panelId: string) 41 | { 42 | log_uvch.log(`[Panel_${panelId}] Create`); 43 | 44 | this._BundleFileName = `UVCH-${panelId}`; 45 | this._PanelId = panelId; 46 | this._Context = UVCHDataSubsystem.Get('Context')!; 47 | 48 | vscode.window.registerWebviewViewProvider(this._PanelId, { 49 | resolveWebviewView: async(panel: vscode.WebviewView) => { 50 | panel.onDidDispose(() => { 51 | this._Panel = undefined; 52 | }); 53 | this.InitReactPanel(panel); 54 | } 55 | }); 56 | } 57 | 58 | /** 59 | * Init the WebView 60 | * 61 | * @param webView The WebView you want to init 62 | * @returns The WebView initialized 63 | */ 64 | private InitReactPanel(panel: vscode.WebviewView): vscode.WebviewView 65 | { 66 | if (this._Panel === undefined) 67 | { 68 | log_uvch.log(`[Panel_${this._PanelId}] Init`); 69 | 70 | this._Panel = panel; 71 | this._Panel.webview.options = { 72 | enableScripts: true, 73 | localResourceRoots: [ 74 | vscode.Uri.file(path.join(this._Context.extensionPath, 'dist')), 75 | ], 76 | }; 77 | 78 | this.SetOnMessageReceived(); 79 | this._Panel.webview.html = this.GetHTMLHasString(); 80 | } 81 | return (this._Panel); 82 | } 83 | 84 | /** 85 | * Set the function who's gonna receive all the message from the React WebView 86 | */ 87 | private SetOnMessageReceived() 88 | { 89 | this._Panel?.webview.onDidReceiveMessage((command: IAction) => { 90 | switch (command.action) { 91 | case "ExecuteCommand": // Allow React component to execute vscode commands 92 | vscode.commands.executeCommand(command.content.cmd, ...(command.content.args || [])); 93 | return; 94 | case "ListenToDataSubsystem": // Allow React component to listen to datas 95 | for (const entry of command.content) { 96 | if (entry.dataKey && entry.callbackMessageType) { 97 | this.AddDataListener(entry.dataKey, entry.callbackMessageType); 98 | } 99 | } 100 | return; 101 | default: 102 | log_uvch.log(`[Panel_${this._PanelId}] Unknown vscode action: ${command.action}`); 103 | return; 104 | } 105 | }); 106 | } 107 | 108 | /** 109 | * Add a new listerner function to the data referencing by the key in the UVCHDataSubsystem 110 | * 111 | * @param dataKey The key who's referencing the data you want to listen 112 | * @param callbackMessageType The function that you want to be called when the data is changed 113 | */ 114 | private AddDataListener(dataKey: string, callbackMessageType: string) 115 | { 116 | if (this._ListeningDataKeys.includes(dataKey) === false) { 117 | this._ListeningDataKeys = this._ListeningDataKeys.concat([dataKey]); 118 | log_uvch.log(`[Panel_${this._PanelId}] Now listening to ${dataKey}`); 119 | 120 | UVCHDataSubsystem.Listen(dataKey, (data: any) => { 121 | this._Panel!.webview.postMessage({ type: callbackMessageType, data: data }); 122 | }); 123 | } 124 | else { 125 | // If he tried to listen to the same data twice, we trigger the listener to make sure his updated 126 | const data: any | undefined = UVCHDataSubsystem.Get(dataKey); 127 | this._Panel!.webview.postMessage({ type: callbackMessageType, data: data }); 128 | 129 | // @TODO: handle unlistening and callbackMessageType update 130 | log_uvch.log(`[Panel_${this._PanelId}] You tried to listen to a datakey that you were already listening to ${dataKey}`); 131 | } 132 | } 133 | 134 | /** 135 | * Get the base HTML to allow showing a React component 136 | */ 137 | private GetHTMLHasString(): string 138 | { 139 | const reactAppPathOnDisk = vscode.Uri.file( 140 | path.join(this._Context.extensionPath, 'dist', `${this._BundleFileName}.js`) 141 | ); 142 | const reactAppUri = this._Panel!.webview.asWebviewUri(reactAppPathOnDisk); 143 | 144 | this._Panel!.webview.options = { 145 | enableScripts: true, 146 | localResourceRoots: [vscode.Uri.file(path.join(this._Context.extensionPath, 'dist'))], 147 | }; 148 | 149 | function GetNonce() { 150 | let text = ''; 151 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 152 | for (let i = 0; i < 32; i++) { 153 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 154 | } 155 | return text; 156 | } 157 | 158 | const nonce = GetNonce(); 159 | 160 | return (` 161 | 162 | 163 | 164 | 165 | 166 | 175 | 178 | 179 | 180 |
181 | 182 | 183 | 184 | `); 185 | } 186 | 187 | public get PanelId(): string { return (this._PanelId); } 188 | } 189 | 190 | export class WebViewBase 191 | { 192 | protected readonly _ViewId: string = ''; 193 | protected _Panels: ViewPanelBase[] = []; 194 | protected _WebView: vscode.WebviewView | undefined; 195 | 196 | constructor(reactWebView: IReactWebView) 197 | { 198 | log_uvch.log(`[View_${reactWebView.viewId}] Create`); 199 | 200 | this._ViewId = reactWebView.viewId; 201 | this._Panels = reactWebView.panelIds.map((panelId) => { 202 | return (new ViewPanelBase(panelId)); 203 | }); 204 | } 205 | 206 | public RegisterNewPanel(panelId: string): ViewPanelBase 207 | { 208 | for (const panel of this._Panels) { 209 | if (panel.PanelId === panelId) { 210 | return (panel); 211 | } 212 | } 213 | 214 | const panel = new ViewPanelBase(panelId); 215 | this._Panels = this._Panels.concat([panel]); 216 | return (panel); 217 | } 218 | 219 | public get ViewId(): string { return (this._ViewId); } 220 | }; 221 | 222 | export default class UVCHWebViewSubsystem extends ASubsystem 223 | { 224 | // The Map where all the view has stored with there key is his viewId 225 | private _Views: Map = new Map(); 226 | 227 | /** 228 | * Register and create a new WebView 229 | * 230 | * @params context, The vscode context 231 | * @Params viewId, The Id of the new view 232 | * @Params panelIds, The Id of all the panel in the view 233 | * @returns The new WebView id 234 | */ 235 | public static RegisterNewView(reactWebView: IReactWebView): string 236 | { 237 | UVCHWebViewSubsystem.GetInstance()!._Views.set( 238 | reactWebView.viewId, 239 | new WebViewBase(reactWebView) 240 | ); 241 | return (reactWebView.viewId); 242 | } 243 | 244 | /** 245 | * Get a reference on a WebView 246 | * 247 | * @param viewId The Id of the WebView you want to get 248 | * @returns The WebView or undefined if not exist 249 | */ 250 | public static GetView(viewId: string): WebViewBase | undefined { 251 | return (UVCHWebViewSubsystem.GetInstance()!._Views.get(viewId)); 252 | } 253 | 254 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uvch", 3 | "version": "1.1.1", 4 | "displayName": "UnrealVsCodeHelper", 5 | "description": "A helper for Unreal Engine, giving you lots of useful tools in a single extensions", 6 | "publisher": "HugoCabel", 7 | "author": { 8 | "name": "Hugo Cabel", 9 | "email": "coding@hugocabel.com" 10 | }, 11 | "contributors": [], 12 | "icon": "resources/UVCHExtensionIcon.png", 13 | "repository": { 14 | "url": "https://github.com/hcabel/UnrealVsCodeHelper" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/hcabel/UnrealVsCodeHelper/issues", 18 | "email": "coding@hugocabel.com" 19 | }, 20 | "engines": { 21 | "vscode": "^1.67.0" 22 | }, 23 | "categories": [ 24 | "Other" 25 | ], 26 | "activationEvents": [ 27 | "onStartupFinished" 28 | ], 29 | "main": "./dist/extension.js", 30 | "contributes": { 31 | "configuration": [ 32 | { 33 | "id": "uvch-config", 34 | "title": "UVCH Global", 35 | "properties": { 36 | "UVCH.Global.UseSwitchFile": { 37 | "type": "boolean", 38 | "default": true, 39 | "markdownDescription": "Use feature: `switch between Header/Cpp files`", 40 | "order": 0 41 | }, 42 | "UVCH.Global.UseToolbar": { 43 | "type": "boolean", 44 | "default": true, 45 | "markdownDescription": "Use feature: `Toolbar`", 46 | "order": 1 47 | }, 48 | "UVCH.Global.UseBrowser": { 49 | "type": "boolean", 50 | "default": true, 51 | "markdownDescription": "Use feature: `UVCH Browser`", 52 | "order": 2 53 | } 54 | }, 55 | "order": 1000 56 | }, 57 | { 58 | "id": "uvch-toolbar", 59 | "title": "UVCH ToolBar", 60 | "properties": { 61 | "UVCH.Toolbar.BuildParameters": { 62 | "type": "array", 63 | "default": [ 64 | "Win64", 65 | "Development", 66 | "%PROJECT%", 67 | "-waitmutex" 68 | ], 69 | "items": { 70 | "type": "string" 71 | }, 72 | "minItems": 0, 73 | "markdownDescription": "Additional LaunchParameters on `Build`", 74 | "order": 2 75 | }, 76 | "UVCH.Toolbar.PlayGameLaunchParameters": { 77 | "type": "array", 78 | "default": [ 79 | "%PROJECT%", 80 | "-game" 81 | ], 82 | "items": { 83 | "type": "string" 84 | }, 85 | "minItems": 0, 86 | "markdownDescription": "Additional LaunchParameters on `play (Game)`", 87 | "order": 1 88 | }, 89 | "UVCH.Toolbar.PlayEditorLaunchParameters": { 90 | "type": "array", 91 | "default": [ 92 | "%PROJECT%" 93 | ], 94 | "items": { 95 | "type": "string" 96 | }, 97 | "minItems": 0, 98 | "markdownDescription": "Additional LaunchParameters on `play (Editor)`", 99 | "order": 0 100 | } 101 | } 102 | }, 103 | { 104 | "id": "uvch-browser", 105 | "title": "UVCH Browser", 106 | "properties": { 107 | "UVCH.Browser.OpenPanelLocation": { 108 | "type": "string", 109 | "enum": [ 110 | "Active", 111 | "Beside", 112 | "One", 113 | "Two", 114 | "Three", 115 | "Four", 116 | "Five", 117 | "Six", 118 | "Seven", 119 | "Eight", 120 | "Nine" 121 | ], 122 | "enumDescriptions": [ 123 | "Open the browser in the current column", 124 | "Open the browser next to the current column", 125 | "Open the browser in the 1st column", 126 | "Open the browser in the 2nd column", 127 | "Open the browser in the 3rd column", 128 | "Open the browser in the 4th column", 129 | "Open the browser in the 5th column", 130 | "Open the browser in the 6th column", 131 | "Open the browser in the 7th column", 132 | "Open the browser in the 8th column", 133 | "Open the browser in the 9th column" 134 | ], 135 | "default": "Beside", 136 | "markdownDescription": "Define where the browser tab will be opened", 137 | "order": 0 138 | }, 139 | "UVCH.Browser.SearchFormat": { 140 | "type": "string", 141 | "default": "%VERSION% %KEYWORD%", 142 | "markdownDescription": "You can add extra parameters to the search query.\n\nEg: `'%VERSION% C++ %KEYWORD%'` => `'5.0 C++ IOnlineSubsystem'`", 143 | "order": 1 144 | }, 145 | "UVCH.Browser.QuickSearchAllowedWebsite": { 146 | "type": "array", 147 | "default": [ 148 | "docs.unrealengine.com", 149 | "unrealcommunity.wiki", 150 | "dev.epicgames.com" 151 | ], 152 | "items": { 153 | "type": "string" 154 | }, 155 | "minItems": 1, 156 | "markdownDescription": "Allowed website when using the **quick search** cmd *(ctrl+f1)*\n\n`use: '*' to allow every website`", 157 | "order": 2 158 | }, 159 | "UVCH.Browser.SearchBarAllowedWebsite": { 160 | "type": "array", 161 | "default": [ 162 | "docs.unrealengine.com", 163 | "unrealcommunity.wiki", 164 | "dev.epicgames.com" 165 | ], 166 | "items": { 167 | "type": "string" 168 | }, 169 | "minItems": 0, 170 | "markdownDescription": "Allowed website when using the **search bar** *(ctrl+shift+f1)*.\n\n`use: '*' to allow every website`", 171 | "order": 3 172 | } 173 | } 174 | } 175 | ], 176 | "keybindings": [ 177 | { 178 | "command": "UVCH.SwitchHeaderCppFile", 179 | "key": "alt+o", 180 | "when": "" 181 | }, 182 | { 183 | "command": "UVCH.QuickSearch", 184 | "key": "ctrl+f1", 185 | "when": "config.UVCH.Global.UseBrowser && !editorHasSelection" 186 | }, 187 | { 188 | "command": "UVCH.QuickSearchFromSelection", 189 | "key": "ctrl+f1", 190 | "when": "config.UVCH.Global.UseBrowser && editorHasSelection" 191 | }, 192 | { 193 | "command": "UVCH.BrowserSearchFromSelection", 194 | "key": "ctrl+shift+f1", 195 | "when": "config.UVCH.Global.UseBrowser && editorTextFocus && editorHasSelection" 196 | } 197 | ], 198 | "commands": [ 199 | { 200 | "command": "UVCH.GoToSettings", 201 | "title": "Open UVCH settings", 202 | "icon": "$(gear)", 203 | "when": "" 204 | }, 205 | { 206 | "command": "UVCH.GetProjectInfos", 207 | "title": "Refresh", 208 | "icon": "$(refresh)", 209 | "category": "UVCHToolbar", 210 | "when": "isWindows && config.UVCH.Global.UseToolbar" 211 | }, 212 | { 213 | "command": "UVCH.PlayGame", 214 | "title": "Play (Game)", 215 | "icon": "$(play)", 216 | "category": "UVCHToolbar", 217 | "when": "isWindows && config.UVCH.Global.UseToolbar" 218 | }, 219 | { 220 | "command": "UVCH.PlayEditor", 221 | "title": "Play (Editor)", 222 | "icon": "$(play)", 223 | "category": "UVCHToolbar", 224 | "when": "isWindows && config.UVCH.Global.UseToolbar" 225 | }, 226 | { 227 | "command": "UVCH.BuildEditor", 228 | "title": "Build (Editor)", 229 | "icon": "$(package)", 230 | "category": "UVCHToolbar", 231 | "when": "isWindows && config.UVCH.Global.UseToolbar" 232 | }, 233 | { 234 | "command": "UVCH.GetUnrealEnginePath", 235 | "title": "Find Unreal Engine folder path", 236 | "icon": "$(folder)", 237 | "category": "UVCHToolbar", 238 | "when": "isWindows && config.UVCH.Global.UseToolbar" 239 | }, 240 | { 241 | "command": "UVCH.SwitchHeaderCppFile", 242 | "title": "Switch between header/cpp file", 243 | "icon": "$(arrow-swap)", 244 | "category": "UVCHSwitchFile", 245 | "when": "config.UVCH.Global.UseSwitchFile" 246 | }, 247 | { 248 | "command": "UVCH.QuickSearch", 249 | "title": "Open webpage with the most relevant web results", 250 | "icon": "$(search)", 251 | "category": "UVCHBrowser", 252 | "when": "config.UVCH.Global.UseBrowser" 253 | }, 254 | { 255 | "command": "UVCH.QuickSearchFromSelection", 256 | "title": "Open webpage with the most relevant web results from selected word", 257 | "icon": "$(search)", 258 | "category": "UVCHBrowser", 259 | "when": "config.UVCH.Global.UseBrowser" 260 | }, 261 | { 262 | "command": "UVCH.BrowserSearch", 263 | "title": "Search a therme or sentences and show the result in the UVCH Browser", 264 | "icon": "$(search)", 265 | "category": "UVCHBrowser", 266 | "when": "config.UVCH.Global.UseBrowser" 267 | }, 268 | { 269 | "command": "UVCH.BrowserSearchFromSelection", 270 | "title": "Search a therme or sentences and show the result in the UVCH Browser from current selection", 271 | "icon": "$(search)", 272 | "category": "UVCHBrowser", 273 | "when": "config.UVCH.Global.UseBrowser" 274 | } 275 | ], 276 | "viewsContainers": { 277 | "activitybar": [ 278 | { 279 | "id": "UVCH", 280 | "title": "Unreal VsCode Helper", 281 | "icon": "./resources/UnrealIcon.png" 282 | } 283 | ] 284 | }, 285 | "views": { 286 | "UVCH": [ 287 | { 288 | "type": "webview", 289 | "id": "UnrealProjectView", 290 | "name": "Project", 291 | "when": "config.UVCH.Global.UseToolbar && isWindows" 292 | }, 293 | { 294 | "type": "webview", 295 | "id": "UVCHBrowser", 296 | "name": "UVCH Browser", 297 | "when": "config.UVCH.Global.UseBrowser" 298 | } 299 | ] 300 | }, 301 | "menus": { 302 | "view/title": [ 303 | { 304 | "command": "UVCH.GetProjectInfos", 305 | "group": "navigation", 306 | "when": "view == UnrealProjectView" 307 | }, 308 | { 309 | "command": "UVCH.GoToSettings", 310 | "group": "navigation", 311 | "when": "view == UnrealProjectView || view == UVCHBrowser" 312 | } 313 | ] 314 | } 315 | }, 316 | "scripts": { 317 | "build": "webpack", 318 | "vscode:prepublish": "npm run package", 319 | "watch": "webpack --watch", 320 | "package": "npm run build && webpack --mode production --devtool hidden-source-map", 321 | "compile-tests": "tsc -p . --outDir out", 322 | "watch-tests": "tsc -p . -w --outDir out", 323 | "pretest": "npm run compile-tests && npm run compile && npm run lint", 324 | "lint": "eslint src --ext ts", 325 | "test": "node ./out/test/runTest.js" 326 | }, 327 | "devDependencies": { 328 | "@babel/core": "^7.17.5", 329 | "@babel/preset-react": "^7.16.7", 330 | "@babel/preset-typescript": "^7.16.7", 331 | "@types/glob": "^7.2.0", 332 | "@types/mocha": "^9.1.1", 333 | "@types/node": "14.x", 334 | "@types/react": "^18.0.9", 335 | "@types/react-dom": "^18.0.4", 336 | "@types/vscode": "^1.67.0", 337 | "@typescript-eslint/eslint-plugin": "^5.21.0", 338 | "@typescript-eslint/parser": "^5.21.0", 339 | "@vscode/test-electron": "^2.1.3", 340 | "babel-loader": "^8.2.3", 341 | "css-loader": "^6.7.1", 342 | "eslint": "^8.14.0", 343 | "glob": "^8.0.1", 344 | "mocha": "^9.2.2", 345 | "style-loader": "^3.3.1", 346 | "ts-loader": "^9.2.8", 347 | "typescript": "^4.6.4", 348 | "webpack": "^5.70.0", 349 | "webpack-cli": "^4.9.2" 350 | }, 351 | "dependencies": { 352 | "axios": "^0.27.2", 353 | "googlethis": "^1.3.0", 354 | "react": "^17.0.2", 355 | "react-dom": "^17.0.2" 356 | }, 357 | "license": "MIT", 358 | "homepage": "https://github.com/hcabel/UnrealVsCodeHelper/", 359 | "eslintConfig": { 360 | "extends": "./eslintrc.json" 361 | }, 362 | "readme": "https://github.com/hcabel/UnrealVsCodeHelper/blob/DEV/README.md", 363 | "keywords": [ 364 | "Unreal Engine", 365 | "UE4", 366 | "UE5", 367 | "C++", 368 | "Windows", 369 | "Documentation", 370 | "Build in tool", 371 | "Unreal" 372 | ] 373 | } 374 | -------------------------------------------------------------------------------- /src/SubSystem/featureSubSystem/SwitchFileSubsystem.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* SwitchFileSubsystem.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/26 09:54:14 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/26 09:54:14 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as fs from "fs"; 14 | import * as path from "path"; 15 | import * as vscode from "vscode"; 16 | 17 | import { 18 | SwitchHeaderCppFile_Implementation 19 | } from '../../Commands/SwitchFilesCommands'; 20 | 21 | import log_uvch from "../../utils/log_uvch"; 22 | import UVCHDataSubsystem from "../DataSubsystem"; 23 | import AFeatureSubSystem from "./FeatureSubSystem"; 24 | 25 | export interface ISwitchFile { 26 | srcPath: string, 27 | destPath: string 28 | } 29 | 30 | export default class SwitchFileSubsystem extends AFeatureSubSystem 31 | { 32 | protected Assign() 33 | { 34 | this._FeatureName = "SwitchFile"; 35 | this._EnableConfigPath = "Global.UseSwitchFile"; 36 | this._Commands = [ 37 | { cmd: "SwitchHeaderCppFile", func: SwitchHeaderCppFile_Implementation }, 38 | ]; 39 | } 40 | 41 | protected Activate() 42 | { 43 | // CREATE STATUS BAR 44 | const switchFileStatusbar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 1000); 45 | switchFileStatusbar.command = `UVCH.SwitchHeaderCppFile`; 46 | UVCHDataSubsystem.Listen("SwitchFile", (data: ISwitchFile) => { 47 | switchFileStatusbar.show(); 48 | if (data) { 49 | const fileName = path.basename(data.destPath); 50 | switchFileStatusbar.text = `[${fileName}]`; 51 | switchFileStatusbar.tooltip = `Switch to ${fileName}`; 52 | switchFileStatusbar.backgroundColor = undefined; 53 | } 54 | else { 55 | const extension = path.extname(vscode.window.activeTextEditor?.document.fileName || ""); 56 | if ([".h", ".hpp", ".cpp"].includes(extension)) { 57 | switchFileStatusbar.tooltip = `Sorry but we were not able to find matching the file`; 58 | switchFileStatusbar.backgroundColor = new vscode.ThemeColor('statusBarItem.errorBackground'); 59 | switchFileStatusbar.text = `[Unknown]`; 60 | } 61 | else { 62 | // hide when not in a supported file 63 | switchFileStatusbar.hide(); 64 | } 65 | } 66 | }); 67 | switchFileStatusbar.show(); 68 | 69 | // LISTEN when you focusing on a file and update the 'SwitchFile' (To switch quicker between header/cpp) 70 | vscode.window.onDidChangeActiveTextEditor(async(ev: vscode.TextEditor | undefined) => { 71 | if (ev && ev.viewColumn && ev.viewColumn >= vscode.ViewColumn.One) { 72 | SwitchFileSubsystem.RequestFindSwitchFile(ev.document); 73 | } 74 | }); 75 | 76 | // FIND current SwitchFile 77 | const currentDocument = vscode.window.activeTextEditor?.document; 78 | if (currentDocument) { 79 | this.RequestFindSwitchFile(currentDocument); 80 | } 81 | } 82 | 83 | /** 84 | * Switch to the file associated with the current file focused in the editor 85 | * @returns true if the switch file has been found and opened 86 | */ 87 | public async SwitchFile(): Promise 88 | { 89 | const currentFileFilePath = vscode.window.activeTextEditor?.document.fileName.replace('\\', '/') || ""; 90 | const currentFileExtension = path.extname(currentFileFilePath); 91 | if ([".h", ".hpp", ".cpp"].includes(currentFileExtension) === false) { 92 | vscode.window.showErrorMessage("[SWITCH_FILE] Not supported in current file extension !"); 93 | return (false); 94 | } 95 | 96 | const switchFilePath = UVCHDataSubsystem.Get("SwitchFile"); 97 | if (switchFilePath) { 98 | log_uvch.log(`[SWITCH_FILE] Switching to file: '${path.basename(switchFilePath.destPath)}'`); 99 | 100 | // Open switch file from path 101 | const switchFileDoc = await vscode.workspace.openTextDocument(switchFilePath.destPath); 102 | if (switchFileDoc) { 103 | await vscode.window.showTextDocument(switchFileDoc, { preview: false }); 104 | return (true); 105 | } 106 | 107 | vscode.window.showErrorMessage(`[SWITCH_FILE] Failed to open switch file: ${switchFilePath.destPath}`); 108 | } 109 | else { 110 | vscode.window.showErrorMessage("[SWITCH_FILE] No SwitchFile found !"); // @TODO: add report action 111 | } 112 | return (false); 113 | } 114 | public static SwitchFile(): Promise { 115 | return (SwitchFileSubsystem.GetInstance()!.SwitchFile()); 116 | } 117 | 118 | /** 119 | * Get the current file and return the header/cpp file corresponding 120 | * 121 | * @param document The document that you wish find his header/cpp file 122 | */ 123 | public async RequestFindSwitchFile(document: vscode.TextDocument) 124 | { 125 | // If you currently are at the destination of the previous switch file request, swap values instead of doing a new request 126 | const oldSwitchFile: ISwitchFile | undefined = UVCHDataSubsystem.Get("SwitchFile"); 127 | if (oldSwitchFile && path.basename(oldSwitchFile.destPath) === path.basename(document.fileName)) { 128 | UVCHDataSubsystem.Set("SwitchFile", { 129 | srcPath: oldSwitchFile.destPath, 130 | destPath: oldSwitchFile.srcPath 131 | }); 132 | return; 133 | } 134 | 135 | UVCHDataSubsystem.Set("SwitchFile", undefined); 136 | // Create a new request, who's gonna find the switch file and store it in the data subsystem 137 | new Promise(async(resolve) => { 138 | resolve(await this.FindSwitchFileRequest(document)); 139 | }); 140 | 141 | } 142 | public static RequestFindSwitchFile(document: vscode.TextDocument) { 143 | return (SwitchFileSubsystem.GetInstance()!.RequestFindSwitchFile(document)); 144 | } 145 | 146 | private async FindSwitchFileRequest(document: vscode.TextDocument): Promise 147 | { 148 | const documentFullPath = document.fileName.replaceAll('\\', '/'); 149 | const switchSourceFile = path.basename(documentFullPath); 150 | log_uvch.log(`[SWITCH_FILE] Start finding SwitchFile for '${switchSourceFile}'`); 151 | 152 | const switchFileFullPath = await this.FindSwitchFile(documentFullPath); 153 | if (switchFileFullPath) { 154 | log_uvch.log(`[SWITCH_FILE] Switch file found: '${path.basename(switchFileFullPath)}'`); 155 | UVCHDataSubsystem.Set("SwitchFile", { 156 | srcPath: documentFullPath, 157 | destPath: switchFileFullPath 158 | }); 159 | return (true); 160 | } 161 | return (false); 162 | } 163 | 164 | /** 165 | * Find the header/cpp file corresponding to the file pointed by the 'sourceFullPath' 166 | * 167 | * @param sourceFullPath The full path of file you want to find his matching header/cpp file 168 | * @returns The header/cpp file full path corresponding to 'sourceFullPath' 169 | * @returns undefined if no header/cpp file was found 170 | */ 171 | public FindSwitchFile(sourceFullPath: string): Promise | undefined 172 | { 173 | // Get document extension 174 | const extension = path.extname(sourceFullPath); 175 | 176 | if (extension === ".h" || extension === ".hpp") { 177 | return (this.FindCppFileCorresponding(sourceFullPath)); 178 | } 179 | else if (extension === ".cpp") { 180 | return (this.FindHeaderFileCorresponding(sourceFullPath)); 181 | } 182 | return (undefined); 183 | } 184 | 185 | /** 186 | * Find the header file corresponding to the file pointed by the 'sourceFullPath' 187 | * 188 | * @param sourceFullPath The full path of file you want to find his matching header file 189 | * @returns The header file full path corresponding to 'sourceFullPath' 190 | * @returns undefined if no header file was found 191 | */ 192 | public async FindHeaderFileCorresponding(sourceFullPath: string): Promise 193 | { 194 | const switchSourceFileNameNoExtension = path.basename(sourceFullPath).replace(path.extname(sourceFullPath), ''); 195 | const switchSourceFilePath = path.dirname(sourceFullPath); 196 | 197 | // TECHNIQUE: 1, Find the header file in the public folder 198 | const resultOne = this.SearchSwitchFileUsingPublicPrivateFolders( 199 | switchSourceFileNameNoExtension, switchSourceFilePath, [".h", ".hpp"]); 200 | if (resultOne !== undefined) { 201 | return (resultOne); 202 | } 203 | 204 | // TECHNIQUE: 2, Find the header file in the same folder 205 | 206 | // Get all files in the same folder as an array of full path 207 | const filesInCurrentFolder = fs.readdirSync(switchSourceFilePath) 208 | .map((file: string) => `${switchSourceFilePath}/${file}`); 209 | 210 | // For each full path, check if it is the SwitchFile we are looking for 211 | for (const file of filesInCurrentFolder) { 212 | if (file.endsWith(`${switchSourceFileNameNoExtension}.h`) || file.endsWith(`${switchSourceFileNameNoExtension}.hpp`)) { 213 | log_uvch.log(`[SWITCH_FILE] Found header file: '${path.basename(file)}'`); 214 | return (file); 215 | } 216 | } 217 | 218 | // TECHNIQUE: 3, Look on every folder in vscode 219 | // TODO: Add extension settings to exclude folders 220 | return (new Promise( 221 | (resolve) => { 222 | vscode.workspace.findFiles(`**/${switchSourceFileNameNoExtension}.{h,hpp}`, undefined, 1) 223 | .then((fileFound: vscode.Uri[]) => { 224 | resolve(fileFound.length > 0 ? fileFound[0].fsPath : undefined); 225 | }); 226 | } 227 | )); 228 | } 229 | 230 | /** 231 | * Find the cpp file corresponding to document 232 | * 233 | * @param sourceFullPath The full path of file you want to find his matching cpp file 234 | * @returns The header file full path corresponding to 'sourceFullPath' 235 | * @returns undefined if no cpp file was found 236 | */ 237 | public async FindCppFileCorresponding(sourceFullPath: string): Promise 238 | { 239 | const switchSourceFileNameNoExtension = path.basename(sourceFullPath).replace(path.extname(sourceFullPath), ''); 240 | const switchSourceFilePath = path.dirname(sourceFullPath); 241 | 242 | // TECHNIQUE: 1, Find the header file in the public folder 243 | const resultOne = this.SearchSwitchFileUsingPublicPrivateFolders( 244 | switchSourceFileNameNoExtension, switchSourceFilePath, [".cpp"]); 245 | if (resultOne !== undefined) { 246 | return (resultOne); 247 | } 248 | 249 | // TECHNIQUE: 2, Find the header file in the same folder 250 | 251 | // Get all files in the same folder as an array of full path 252 | const filesInCurrentFolder = fs.readdirSync(switchSourceFilePath) 253 | .map((file: string) => `${switchSourceFilePath}/${file}`); 254 | 255 | // For each full path, check if it is the SwitchFile we are looking for 256 | for (const file of filesInCurrentFolder) { 257 | if (file.endsWith(`${switchSourceFileNameNoExtension}.cpp`)) { 258 | log_uvch.log(`[SWITCH_FILE] Found header file: '${path.basename(file)}'`); 259 | return (file); 260 | } 261 | } 262 | 263 | // TECHNIQUE: 3, Look on every folder in vscode 264 | // TODO: Add extension settings to exclude folders 265 | return (new Promise( 266 | (resolve) => { 267 | vscode.workspace.findFiles(`**/${switchSourceFileNameNoExtension}.cpp`, undefined, 1) 268 | .then((fileFound: vscode.Uri[]) => { 269 | resolve(fileFound.length > 0 ? fileFound[0].fsPath : undefined); 270 | }); 271 | } 272 | )); 273 | } 274 | 275 | /** 276 | * Seach the SwitchFile in the public folder, using the SwitchSourceFile private path 277 | * (eg: /Source/Private/MyClass.h => /Source/Public/MyClass.hpp) 278 | * 279 | * @param sourceNameNoExtension The name of the SwitchSourceFile without extension 280 | * @param sourcePath The path of the SwitchSourceFile 281 | * @param extensions The potential extensions of the SwitchFile 282 | * @returns The SwitchFile path if found else undefined 283 | */ 284 | private SearchSwitchFileUsingPublicPrivateFolders(sourceNameNoExtension: string, sourcePath: string, 285 | extensions: string[]): string | undefined 286 | { 287 | let bIsSouceInPrivateFolder = false; 288 | 289 | // Look for the last private/public folder in the path 290 | let folderCoupleA = ""; 291 | const allFoldersName = sourcePath.split('/'); 292 | for (const folderName of allFoldersName) { 293 | if (/private/i.test(folderName)) { // Check for 'private' 294 | bIsSouceInPrivateFolder = true; 295 | folderCoupleA = folderName; 296 | break; 297 | } 298 | else if (/public/i.test(folderName)) { // Check for 'public' 299 | folderCoupleA = folderName; 300 | break; 301 | } 302 | } 303 | 304 | if (!folderCoupleA) { 305 | // private and public folder not found 306 | return (undefined); 307 | } 308 | 309 | // Search the public/private folder at the same path has the private folder 310 | const pathBeforePrivate = sourcePath.substring(0, sourcePath.lastIndexOf(folderCoupleA) - 1); 311 | const folderCoupleB = fs.readdirSync(pathBeforePrivate) 312 | .find((folderName) => { 313 | if (bIsSouceInPrivateFolder) { 314 | return (/public/i.test(folderName)); 315 | } 316 | return (/private/i.test(folderName)); 317 | }); 318 | 319 | if (folderCoupleB) 320 | { 321 | const publicPath = sourcePath.replace(folderCoupleA, folderCoupleB); 322 | const publicRootPath = `${publicPath}/${sourceNameNoExtension}`; 323 | 324 | // Search for the switch file in the public folder using all the extension in params 325 | for (const extension of extensions) { 326 | const switchFile = `${publicRootPath}${extension}`; 327 | if (fs.existsSync(switchFile)) { 328 | return (switchFile); 329 | } 330 | } 331 | } 332 | return (undefined); 333 | } 334 | } -------------------------------------------------------------------------------- /src/Commands/ToolbarCommands.ts: -------------------------------------------------------------------------------- 1 | /* ************************************************************************** */ 2 | /* */ 3 | /* ::: :::::::: */ 4 | /* ToolbarCommands.ts :+: :+: :+: */ 5 | /* +:+ +:+ +:+ */ 6 | /* By: HugoCabel +#+ +:+ +#+ */ 7 | /* +#+#+#+#+#+ +#+ */ 8 | /* Created: 2022/05/22 15:45:22 by HugoCabel #+# #+# */ 9 | /* Updated: 2022/05/22 15:45:22 by HugoCabel ### ########.fr */ 10 | /* */ 11 | /* ************************************************************************** */ 12 | 13 | import * as vscode from "vscode"; 14 | import * as fs from "fs"; 15 | import { IUEProject } from "../utils/UETypes"; 16 | import log_uvch from "../utils/log_uvch"; 17 | import UVCHDataSubsystem from "../SubSystem/DataSubsystem"; 18 | import UVCHSettingsSubsystem from "../SubSystem/SettingsSubsystem"; 19 | 20 | /////////////////////////////////////////////////////////////////////////////// 21 | // GetUnrealEnginePath 22 | 23 | function GetAllMatchDirPath(startPath: string, regex: RegExp): string[] 24 | { 25 | startPath = startPath.replace(/\/$/, ''); 26 | return (fs.readdirSync(startPath, { withFileTypes: true }) 27 | .filter((dirent) => dirent.isDirectory() && regex.test(dirent.name)) 28 | .map((dirent) => `${startPath}/${dirent.name}`) 29 | ); 30 | } 31 | 32 | export async function GetUnrealEnginePath_Implementation(): Promise 33 | { 34 | let uproject: IUEProject | undefined = UVCHDataSubsystem.Get('UProject'); 35 | if (!uproject) { 36 | await vscode.commands.executeCommand("UVCH.GetProjectInfos"); 37 | uproject = UVCHDataSubsystem.Get('UProject'); 38 | if (!uproject) { 39 | return (false); 40 | } 41 | } 42 | 43 | // @TODO: Check if the path is set in the UVCH config 44 | // @TODO: Before looking in all the files make sure the Engine is not in the vscode workspace 45 | 46 | // Find all 'Program Files' folders 47 | const startPath = 'C:/'; 48 | const programFilesPaths = GetAllMatchDirPath(startPath, RegExp(".*Program Files.*")); 49 | 50 | // Find all 'Epic Games' folders in the 'Program Files' folders 51 | let epicGamesPaths: string[] = []; 52 | for (const current of programFilesPaths) { 53 | epicGamesPaths = epicGamesPaths.concat(GetAllMatchDirPath(current, RegExp(".*Epic Games.*"))); 54 | } 55 | 56 | // Find the Engine path corresponding to the engine association, in all the 'Epic Games' folders 57 | let enginePaths: string[] = []; 58 | for (const current of epicGamesPaths) { 59 | enginePaths = enginePaths.concat(GetAllMatchDirPath(current, RegExp(`UE_${uproject.EngineAssociation}`)) 60 | .filter((engineFolders) => { 61 | return (engineFolders.length > 0); 62 | })); 63 | // I assume that people will only have a single version of unreal installed 64 | // If not they can use the Extension Config 65 | if (enginePaths.length >= 1) { 66 | break; 67 | } 68 | } 69 | 70 | UVCHDataSubsystem.Set('EnginePath', enginePaths[0].replaceAll('/', '\\')); 71 | return (true); 72 | } 73 | 74 | /////////////////////////////////////////////////////////////////////////////// 75 | // GetProjectInfos 76 | 77 | /** 78 | * Read a all file parse it has a JSON object then create a IUEProject 79 | * replacing all unvalid value by 'undefined' 80 | * 81 | * @param path The path to the file 82 | * @param file The file name (with the extension) 83 | * @return IUEProject, if found and parsed 84 | * @return undefined, if failed to parse/found 85 | */ 86 | function CastFileHasUEProject(path: string, file: string): IUEProject | undefined 87 | { 88 | const requiredKeys = ["FileVersion", "EngineAssociation"]; 89 | try { 90 | const fileContentJSON: any = JSON.parse(fs.readFileSync(`${path}/${file}`, 'utf8')); 91 | for (const [key, value] of Object.entries(fileContentJSON)) { 92 | if (!value && requiredKeys.includes(key)) { 93 | throw Error("Unexpected undefined value"); 94 | } 95 | fileContentJSON[key] = (value ? value : undefined); 96 | } 97 | return (fileContentJSON as IUEProject); 98 | } catch (e: any) { 99 | log_uvch.log(`[!ERROR!] Unable to parse '${file}'`); 100 | console.error(`[!ERROR!] Unable to parse '${file}'`); 101 | } 102 | } 103 | 104 | // Project infos that we want to keep but who's not referencing into the .uproject 105 | export interface IProjectInfos { 106 | Name: string, 107 | RootPath: string, 108 | UnrealVersion: string, 109 | WorkspaceFoldersIndex: number, 110 | } 111 | 112 | /** 113 | * Search, find and parse a .uproject in the current workspace folders. 114 | * This function auto set the values found in the 'UVCHDataSubsystem' 115 | * 116 | * @return true or false depending if yes or no we succeeded finding and parsing the project 117 | */ 118 | export async function GetProjectInfos_Implementation(): Promise 119 | { 120 | if (!vscode.workspace.workspaceFolders) { 121 | return (false); 122 | } 123 | 124 | // For each folders open in VsCode 125 | let workspaceFoldersIndex: number = 0; 126 | for (const folder of vscode.workspace.workspaceFolders) 127 | { 128 | // Get all the files and loop through them 129 | const files = fs.readdirSync(folder.uri.fsPath); 130 | for (const file of files) 131 | { 132 | // If the file is a '.uproject' 133 | if (file.endsWith('.uproject') === false) { 134 | continue; 135 | } 136 | 137 | // parse the file 138 | const uproject = CastFileHasUEProject(folder.uri.fsPath, file); 139 | const projectInfos: IProjectInfos = { 140 | Name: file.replace(".uproject", ''), 141 | RootPath: folder.uri.fsPath.toString().replaceAll('\\', '/'), 142 | UnrealVersion: uproject?.EngineAssociation || "", 143 | WorkspaceFoldersIndex: workspaceFoldersIndex 144 | }; 145 | 146 | // store the project informations 147 | // Even when undefined(if failed) to refresh all the component who depends of those informations 148 | UVCHDataSubsystem.Set('UProject', uproject); 149 | UVCHDataSubsystem.Set('ProjectInfos', uproject ? projectInfos : undefined); 150 | 151 | if (!uproject) { 152 | vscode.window.showErrorMessage(`[UVCH] Unable to parse '${file}'`); // @TODO: add report action in case it's an error 153 | } 154 | 155 | return (uproject === undefined ? true : false); // @TODO: UVCH is not handling multiple unreal project at the same time YET 156 | } 157 | workspaceFoldersIndex++; 158 | } 159 | 160 | // Refresh all components 161 | UVCHDataSubsystem.Set('UProject', undefined); 162 | UVCHDataSubsystem.Set('ProjectInfos', undefined); 163 | return (false); 164 | } 165 | 166 | /////////////////////////////////////////////////////////////////////////////// 167 | // PlayGame 168 | 169 | /** 170 | * Launch the project in game mode 171 | * @return a promise that resolve if the command as successfully been send to the terminal 172 | */ 173 | export async function PlayGame_Implementation(): Promise 174 | { 175 | return new Promise((resolve, reject) => 176 | { 177 | vscode.commands.executeCommand("UVCH.BuildEditor") 178 | .then(async(succeeded: boolean) => 179 | { 180 | if (!succeeded) { 181 | // This is does not mean that the build failed, just that the command failed 182 | resolve(false); 183 | return; 184 | } 185 | 186 | // Get Project data if not exist, trigger the command then try again 187 | let projectInfos: IProjectInfos | undefined = UVCHDataSubsystem.Get('ProjectInfos'); 188 | if (!projectInfos) { 189 | await vscode.commands.executeCommand("UVCH.GetProjectInfos"); 190 | projectInfos = UVCHDataSubsystem.Get('ProjectInfos'); 191 | if (!projectInfos) { 192 | reject("Unable to get project infos"); 193 | return; 194 | } 195 | } 196 | 197 | let enginePath: string | undefined = UVCHDataSubsystem.Get("EnginePath"); 198 | if (!enginePath) { 199 | await vscode.commands.executeCommand("UVCH.GetUnrealEnginePath"); 200 | enginePath = UVCHDataSubsystem.Get('EnginePath'); 201 | if (!enginePath) { 202 | reject("Unable to get engine path"); 203 | return; 204 | } 205 | } 206 | 207 | // The .exe is changing depending of the UE version 208 | // @TODO: Find a better way to do this 209 | const unrealExeName = projectInfos.UnrealVersion.charAt(0) === '4' ? 'UE4Editor' : 'UnrealEditor'; 210 | const natvisName = (projectInfos.UnrealVersion.charAt(0) === '4' ? 'UE4' : 'Unreal'); 211 | 212 | const settingArgs = 213 | UVCHSettingsSubsystem.Get(`Toolbar.PlayGameLaunchParameters`)! 214 | .map((arg: string) => { 215 | return ( 216 | arg === "%PROJECT%" ? `${projectInfos!.RootPath}/${projectInfos!.Name}.uproject` : arg 217 | ); 218 | }); 219 | 220 | vscode.debug.startDebugging( 221 | vscode.workspace.workspaceFolders![0], 222 | { 223 | name: 'Play Editor', 224 | type: 'cppvsdbg', // @TODO: Add settings for using lldb 225 | request: 'launch', 226 | program: `${enginePath}\\Engine\\Binaries\\Win64\\${unrealExeName}.exe`, 227 | args: [ 228 | ...settingArgs 229 | ], 230 | console: "newExternalWindow", 231 | cwd: enginePath, 232 | visualizerFile: `${enginePath}\\Engine\\Extras\\VisualStudioDebugging\\${natvisName}.natvis`, 233 | sourceFileMap: { 234 | "D:\build\++UE5\Sync": enginePath 235 | } 236 | } 237 | ); 238 | 239 | resolve(true); 240 | }); 241 | }); 242 | } 243 | 244 | /////////////////////////////////////////////////////////////////////////////// 245 | // PlayEditor 246 | 247 | /** 248 | * Launch the Unreal Editor with the current project 249 | * @return a promise that resolve if the command as successfully been send to the terminal 250 | */ 251 | export async function PlayEditor_Implementation(): Promise 252 | { 253 | return new Promise((resolve, reject) => 254 | { 255 | vscode.commands.executeCommand("UVCH.BuildEditor") 256 | .then(async(succeeded: boolean) => 257 | { 258 | if (!succeeded) { 259 | // This is does not mean that the build failed, just that the command failed 260 | // @TODO: find a way to know if the build failed (I tried but it didn't work) 261 | resolve(false); 262 | return; 263 | } 264 | 265 | // Get Project data if not exist, trigger the command then try again 266 | let projectInfos: IProjectInfos | undefined = UVCHDataSubsystem.Get('ProjectInfos'); 267 | if (!projectInfos) { 268 | await vscode.commands.executeCommand("UVCH.GetProjectInfos"); 269 | projectInfos = UVCHDataSubsystem.Get('ProjectInfos'); 270 | if (!projectInfos) { 271 | reject("Unable to get project infos"); 272 | return; 273 | } 274 | } 275 | 276 | let enginePath: string | undefined = UVCHDataSubsystem.Get("EnginePath"); 277 | if (!enginePath) { 278 | await vscode.commands.executeCommand("UVCH.GetUnrealEnginePath"); 279 | enginePath = UVCHDataSubsystem.Get('EnginePath'); 280 | if (!enginePath) { 281 | reject("Unable to get engine path"); 282 | return; 283 | } 284 | } 285 | 286 | // The .exe is changing depending of the UE version 287 | // @TODO: Find a better way to do this 288 | const unrealExeName = (projectInfos.UnrealVersion.charAt(0) === '4' ? 'UE4Editor' : 'UnrealEditor'); 289 | const natvisName = (projectInfos.UnrealVersion.charAt(0) === '4' ? 'UE4' : 'Unreal'); 290 | 291 | const settingArgs = 292 | UVCHSettingsSubsystem.Get(`Toolbar.PlayEditorLaunchParameters`)! 293 | .map((arg: string) => { 294 | return ( 295 | arg === "%PROJECT%" ? `${projectInfos!.RootPath}/${projectInfos!.Name}.uproject` : arg 296 | ); 297 | }); 298 | 299 | vscode.debug.startDebugging( 300 | vscode.workspace.workspaceFolders![0], 301 | { 302 | name: 'Play Editor', 303 | type: 'cppvsdbg', // @TODO: Add settings for using lldb 304 | request: 'launch', 305 | program: `${enginePath}\\Engine\\Binaries\\Win64\\${unrealExeName}.exe`, 306 | args: [ 307 | ...settingArgs 308 | ], 309 | cwd: enginePath, 310 | visualizerFile: `${enginePath}\\Engine\\Extras\\VisualStudioDebugging\\${natvisName}.natvis`, 311 | sourceFileMap: { 312 | "D:\build\++UE5\Sync": enginePath 313 | } 314 | } 315 | ); 316 | 317 | resolve(true); 318 | }); 319 | }); 320 | } 321 | 322 | /////////////////////////////////////////////////////////////////////////////// 323 | // BuildEditor 324 | 325 | /** 326 | * Launch the Unreal builder with the current project 327 | * @returns a promise that resolve if the command as successfully been send to the terminal 328 | */ 329 | export async function BuildEditor_Implementation(): Promise 330 | { 331 | return new Promise(async(resolve, reject) => 332 | { 333 | // Get Project data if not exist, trigger the command then try again 334 | let projectInfos = UVCHDataSubsystem.Get('ProjectInfos'); 335 | if (!projectInfos) { 336 | await vscode.commands.executeCommand("UVCH.GetProjectInfos"); 337 | projectInfos = UVCHDataSubsystem.Get('ProjectInfos'); 338 | if (!projectInfos) { 339 | reject("Unable to get project infos"); 340 | return; 341 | } 342 | } 343 | 344 | let enginePath: string | undefined = UVCHDataSubsystem.Get("EnginePath"); 345 | if (!enginePath) { 346 | await vscode.commands.executeCommand("UVCH.GetUnrealEnginePath"); 347 | enginePath = UVCHDataSubsystem.Get('EnginePath'); 348 | if (!enginePath) { 349 | reject("Unable to get engine path"); 350 | return; 351 | } 352 | } 353 | 354 | const buildCommand: string = `${enginePath}\\Engine\\Build\\BatchFiles\\Build.bat`; 355 | const args: string[] = [ 356 | `${projectInfos.Name}Editor`, 357 | "Win64", 358 | "Development", 359 | `'${projectInfos.RootPath}/${projectInfos.Name}.uproject'`, 360 | "-waitmutex" 361 | ]; 362 | 363 | const task = new vscode.Task( 364 | { type: 'shell' }, 365 | vscode.workspace.workspaceFolders![0], 366 | `Build`, 367 | 'UVCH', 368 | new vscode.ShellExecution(`& '${buildCommand}' ${args.join(' ')}`), 369 | ); 370 | const execution = await vscode.tasks.executeTask(task); 371 | 372 | vscode.tasks.onDidEndTask((event: vscode.TaskEndEvent) => { 373 | if (event.execution.task === execution.task) { 374 | vscode.window.showInformationMessage(`${projectInfos!.Name} build completed`); 375 | resolve(true); 376 | } 377 | }); 378 | }); 379 | } --------------------------------------------------------------------------------