├── .deepsource.toml ├── .eslintrc.json ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.d.ts ├── package-lock.json ├── package.json ├── resources ├── demo │ ├── auto_complete.gif │ ├── execute_code.gif │ └── nodes_panel.gif ├── icons │ ├── dark │ │ └── run_code.svg │ ├── light │ │ └── run_code.svg │ ├── nuke.svg │ └── nuke_icon.png └── language │ ├── blinkscript.tmLanguage.json │ ├── blinkscript_completion.json │ ├── language-configuration.json │ ├── nuke.tmLanguage.json │ └── saturation_sample.blink ├── src ├── blinkscript │ ├── blink_completion.ts │ ├── blink_format.ts │ └── blink_snippet.ts ├── config.ts ├── constants.ts ├── create_project.ts ├── extension.ts ├── launch_executable.ts ├── notification.ts ├── nuke.ts ├── nuke │ ├── completitions.ts │ └── nodes_tree.ts ├── packages.ts ├── packages_fetch.ts ├── socket.ts ├── stubs.ts ├── test │ └── runTest.ts └── version.ts └── tsconfig.json /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | test_patterns = ["src/test/suite/*test.ts"] 4 | 5 | exclude_patterns = [ 6 | "src/test/suite/index.ts", 7 | ] 8 | 9 | [[analyzers]] 10 | name = "javascript" 11 | enabled = true 12 | -------------------------------------------------------------------------------- /.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 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/recommended" 14 | ], 15 | "rules": { 16 | "@typescript-eslint/naming-convention": "warn", 17 | "@typescript-eslint/semi": "warn", 18 | "no-undef": "off", 19 | "curly": "warn", 20 | "eqeqeq": "warn", 21 | "no-throw-literal": "warn", 22 | "semi": 1 23 | }, 24 | "ignorePatterns": [ 25 | "out", 26 | "dist", 27 | "demo", 28 | "**/*.d.ts", 29 | "src/test/suite/index.ts", 30 | "src/test/runTest.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | assets 3 | .nuketools 4 | included_assets.json 5 | 6 | coverage 7 | .nyc_output 8 | 9 | node_modules 10 | .vscode-test/ 11 | .vscode 12 | *.vsix 13 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 100 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--disable-extensions", 14 | "${workspaceFolder}/demo", 15 | "--extensionDevelopmentPath=${workspaceFolder}" 16 | ], 17 | "outFiles": [ 18 | "${workspaceFolder}/out/**/*.js" 19 | ], 20 | "preLaunchTask": "${defaultBuildTask}" 21 | }, 22 | { 23 | "name": "Extension Tests", 24 | "type": "extensionHost", 25 | "request": "launch", 26 | "args": [ 27 | "--disable-extensions", 28 | "${workspaceFolder}/demo", 29 | "--extensionDevelopmentPath=${workspaceFolder}", 30 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 31 | ], 32 | "outFiles": [ 33 | "${workspaceFolder}/out/test/**/*.js" 34 | ], 35 | "preLaunchTask": "${defaultBuildTask}" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | }, 19 | { 20 | "label": "Build Package", 21 | "type": "shell", 22 | "command": "vsce package", 23 | "problemMatcher": [] 24 | }, 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | 5 | node_modules/** 6 | !node_modules/keyv 7 | !node_modules/json-buffer 8 | 9 | out/ 10 | !out/main.js 11 | 12 | other/ 13 | .pytest_cache/ 14 | .nyc_output/ 15 | coverage/ 16 | demo/ 17 | tmp/ 18 | 19 | .deepsource.toml 20 | .pre-commit-config.yaml 21 | 22 | resources/included_assets.json 23 | resources/assets 24 | resources/demo 25 | 26 | src/** 27 | .gitignore 28 | .yarnrc 29 | vsc-extension-quickstart.md 30 | **/tsconfig.json 31 | **/.eslintrc.json 32 | **/*.map 33 | **/*.ts 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.16.0] - 07/07/2024 4 | 5 | ### Changed 6 | 7 | - Changed the `nuketools.environmentVariables` keys to use an array of strings rather than a single string (`{"VAR_NAME": ["value1", "value2", ...]}`) 8 | - Added new placeholders for the `nuketools.environmentVariables` setting: `${workspaceFolderBasename}` and `${userHome}`. 9 | - Added the ability to use any system environment variable in the `nuketools.environmentVariables` setting. 10 | 11 | ### Fixed 12 | 13 | - Fixed light theme icon 14 | - Extensions commands for extra and packages now properly show a label rather than a variable name. 15 | 16 | ## [0.15.1] - 07/07/2024 17 | 18 | ### Fixed 19 | 20 | - Placeholder replacement (`${workspaceFolder}`) now works correctly in every command. 21 | 22 | ## [0.15.0] - 06/07/2024 23 | 24 | The main goal of this release is to simplify the extension commands and settings, making it easier to use and understand. This caused some breaking changes, but the extension is now more user-friendly. 25 | 26 | ### Added 27 | 28 | Commands: 29 | 30 | - `NukeTools: Open Script in Nuke`: A new command that opens the current active Nuke script with the main executable. 31 | - `NukeTools: Show Executables`: A new command that shows the list of executables added to the extension. via the new setting `nukeTools.executablesMap`. 32 | - `NukeTools: Add Packages`: Show the list of packages available to add to the extension. 33 | - `NukeTools: Extras`: Show the list of extra commands available in the extension. 34 | 35 | Settings: 36 | 37 | - `nukeTools.executablesMap`: A new setting that allows the user to add multiple executables to the extension. 38 | 39 | ```json 40 | { 41 | "nuke": { 42 | "path": "/path/to/nuke", 43 | "args": "-t", 44 | }, 45 | "nukex": { 46 | "path": "/path/to/nuke", 47 | "args": "--nukex", 48 | }, 49 | "maya": { 50 | "path": "/path/to/maya", 51 | "args": "", 52 | } 53 | } 54 | ``` 55 | 56 | ### Changed 57 | 58 | - All the settings namespaces have been updated thus breaking the previous saved ones. 59 | - The packages commands (Stubs, NukeServerSocket, etc.) are now part of the `NukeTools: Add Packages` command. 60 | - Merged other commands into the `NukeTools: Extras` command. 61 | - Network settings (`host`, `port` and `enable manual connection`) were merged into the new settings `nukeTools.network.manualConnection`. 62 | 63 | ```json 64 | { 65 | "nukeTools.network.manualConnection": { 66 | "active": false, 67 | "host": "localhost", 68 | "port": "49512" 69 | } 70 | } 71 | ``` 72 | 73 | ### Removed 74 | 75 | Commands: 76 | 77 | - `NukeTools: Launch Alternative Executable` 78 | - `nukeTools.other.UseSystemEnvVars`: Removed the setting that allows the use of system environment variables. The settings `nukeTools.environmentVariables` now allows the use of the system environment variables directly in the settings. 79 | 80 | ```json 81 | { 82 | "nukeTools.environmentVariables": { 83 | "NUKE_PATH": "${workspaceFolder}/bin:$NUKE_PATH", 84 | } 85 | } 86 | ``` 87 | 88 | ## [0.14.0] - 02/18/2024 89 | 90 | ### Changed 91 | 92 | - Added a monthly check for the latest included packages. 93 | - Changed the command `Nuke: Add Pyside2 Plugin` functionality to reflect the new changes in the included template. 94 | 95 | ## [0.13.0] - 03/12/2023 96 | 97 | ### Added 98 | 99 | - New command `Nuke: Add VimDcc` that adds the VimDcc plugin to the current Nuke installation. 100 | 101 | ## [0.12.1] - 11/19/2023 102 | 103 | ### Changed 104 | 105 | - Update nukeserversocket to version to latest 106 | - Update socket debug functionallity. 107 | 108 | ## [0.12.0] - 09/23/2023 109 | 110 | ### Added 111 | 112 | - New settings `nukeTools.other.useSystemEnvVars` that allows to use the system environment variables together with the ones defined in the extension settings. 113 | 114 | ### Changed 115 | 116 | - The stubs now are added to the `~/.nuke/NukeTools/stubs` folder and the LSP config path is added at a user level instead of workspace level. 117 | 118 | ## [0.11.0] 02/07/2023 119 | 120 | ### Added 121 | 122 | - Initial support for some completions providers. Currently only works for the `nuke.toNode` function. 123 | 124 | ### Changed 125 | 126 | - Update the handling of the socket connection to resolve as soon as it receives the data. 127 | 128 | ## [0.10.0] 04/02/2023 129 | 130 | ### Added 131 | 132 | - Nodes Panel: A new panel that allows interaction with the nodes in the current Nuke script. 133 | 134 | ## [0.9.0] 03/25/2023 135 | 136 | ### Changed 137 | 138 | - Fetch the latests release of the included packages via the GitHub release page. 139 | 140 | ## [0.8.12] 03/19/2023 141 | 142 | ### Added 143 | 144 | - New and improved nuke stubs files. 145 | 146 | ### Changed 147 | 148 | - Fallback on Pylance Python Server if Default. 149 | - Include stubs inside zip file. 150 | - Default command line argument command now only works for the second executable. 151 | 152 | ## [0.8.4] 09/01/2023 153 | 154 | ### Changed 155 | 156 | - Changed Python version for the pyside2 plugin to `~3.7.7` 157 | 158 | ### Fixed 159 | 160 | - Fixed placeholder substitution for pyside2 template name with spaces. 161 | - Remove unnecessary socket connection timeout. 162 | 163 | ## [0.8.2] 11/20/2022 164 | 165 | ### Fixed 166 | 167 | - Update Hiero stubs with proper `__init__.py` file. 168 | 169 | ## [0.8.1] 11/20/2022 170 | 171 | ### Added 172 | 173 | - Hiero stubs 174 | 175 | ## [0.8.0] 10/23/2022 176 | 177 | ### Added 178 | 179 | - New command to create a pyside2 plugin from included template. 180 | 181 | ### Changed 182 | 183 | - Can now add multiple values as environmental variables before launching Nuke, instead of only to NUKE_PATH. 184 | 185 | ## [0.7.2] 09/04/2022 186 | 187 | ### Added 188 | 189 | - Syntax color for `.nk` and `.gizmo` files. 190 | 191 | ## [0.7.0] 08/02/2022 192 | 193 | ### Added 194 | 195 | - New settings for adding paths to NUKE_PATH. 196 | 197 | ## [0.6.2] 06/14/2022 198 | 199 | ### Added 200 | 201 | - Support the Jedi Language Server for Python when adding the stubs files. 202 | 203 | ## [0.6.0] 03/22/2022 204 | 205 | ### Added 206 | 207 | - New settings to enable/disable the button in the toolbar. 208 | 209 | ## [0.5.0] 02/27/2022 210 | 211 | ### Added 212 | 213 | - New command that adds nukeserversocket plugin inside Nuke's directory (`.nuke`) & `menu.py`. 214 | 215 | ## [0.4.5] 02/18/2022 216 | 217 | ### Fixed 218 | 219 | - Fixed formatting error in stubs main init file. 220 | 221 | ## [0.4.4] 02/17/2022 222 | 223 | ### Fixed 224 | 225 | - Correctly import `nukescripts` module when using the stubs path. 226 | 227 | ## [0.4.3] 02/02/2022 228 | 229 | ### Fixed 230 | 231 | - Stubs path now will automatically update to reflect the extension path when a new version is released. 232 | 233 | ### Changed 234 | 235 | - Some stubs file will now return `[Node]` instead of `list()`. 236 | 237 | ## [0.4.1] 02/02/2022 238 | 239 | ### Fixed 240 | 241 | - BlinkScript sample script incorrect components. 242 | 243 | ## [0.4.0] 12/12/2021 244 | 245 | ### Added 246 | 247 | - BlinkScript syntax highlighting, formatting and simple code suggestion. 248 | 249 | ### Removed 250 | 251 | - `Auto add stubs path` setting. 252 | 253 | ## [0.3.3] 11/08/2021 254 | 255 | Mostly code refactoring and test suite. 256 | 257 | ### Fixed 258 | 259 | - Adding the stubs path will not create a duplicate if extension version is different. 260 | 261 | ### Removed 262 | 263 | - Setting `Auto add stubs path` is now deprecated. Path should be added manually when needed with the command `Add Python Stubs`. 264 | 265 | ## [0.3.0] 10/04/2021 266 | 267 | ### Added 268 | 269 | - Add python stubs folder to root project. 270 | - New command `Add Python Stubs` that adds the stubs to the project settings.json. 271 | - New settings `Auto add stubs path` that adds the stubs to the project settings.json when workspace contains a `*.py` file. 272 | 273 | ## [0.2.0] 09/15/2021 274 | 275 | ### Added 276 | 277 | - Support for BlinkScript. 278 | 279 | ## [0.0.14] 09/12/2021 280 | 281 | > This version was wrongfully uploaded as 0.1.0 282 | 283 | ### Added 284 | 285 | - Better check for the .ini file in case has wrong/invalid values. 286 | 287 | ## [0.0.13] 08/16/2021 288 | 289 | ### Changed 290 | 291 | - Remove Nuke Python Snippets from extension pack. 292 | 293 | ## [0.0.10] - [0.0.12] 294 | 295 | - Minor internal maintenance. 296 | 297 | ## [0.0.9] - 08/15/2021 298 | 299 | ### Fixed 300 | 301 | - Fixed file name reference that didn't allow vscode to pick nukeserversocket port settings automatically. 302 | 303 | ## [0.0.2] - [0.0.8] 304 | 305 | - Minor internal maintenance. 306 | 307 | ## [0.0.1] 308 | 309 | - Initial release. 310 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Virgil Sisoe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1. Nuke Tools README 2 | 3 | 6 | 7 | [![vscode-marketplace](https://img.shields.io/badge/vscode-marketplace-blue)](https://marketplace.visualstudio.com/items?itemName=virgilsisoe.nuke-tools) 8 | [![vscode-version](https://img.shields.io/visual-studio-marketplace/v/virgilsisoe.nuke-tools)](https://marketplace.visualstudio.com/items?itemName=virgilsisoe.nuke-tools&ssr=false#version-history) 9 | [![vscode-installs](https://img.shields.io/visual-studio-marketplace/i/virgilsisoe.nuke-tools)](https://marketplace.visualstudio.com/items?itemName=virgilsisoe.nuke-tools) 10 | [![vscode-ratings](https://img.shields.io/visual-studio-marketplace/r/virgilsisoe.nuke-tools)](https://marketplace.visualstudio.com/items?itemName=virgilsisoe.nuke-tools&ssr=false#review-details) 11 | [![vscode-last-update](https://img.shields.io/visual-studio-marketplace/last-updated/virgilsisoe.nuke-tools)](https://marketplace.visualstudio.com/items?itemName=virgilsisoe.nuke-tools) 12 | 13 | [![openvsx-marketplace](https://img.shields.io/badge/openvsx-marketplace-C160EF)](https://open-vsx.org/extension/virgilsisoe/nuke-tools) 14 | [![openvsx-version](https://img.shields.io/open-vsx/v/virgilsisoe/nuke-tools?label=version)](https://open-vsx.org/extension/virgilsisoe/nuke-tools/changes) 15 | [![openvsx-downloads](https://img.shields.io/open-vsx/dt/virgilsisoe/nuke-tools)](https://open-vsx.org/extension/virgilsisoe/nuke-tools) 16 | [![openvsx-rating](https://img.shields.io/open-vsx/rating/virgilsisoe/nuke-tools)](https://open-vsx.org/extension/virgilsisoe/nuke-tools/reviews) 17 | 18 | Includes the following packages: 19 | 20 | [![nukeserversocket](https://img.shields.io/github/v/release/sisoe24/nukeserversocket?label=nukeserversocket)](https://github.com/sisoe24/nukeserversocket/releases) 21 | [![stubs](https://img.shields.io/github/v/release/sisoe24/nuke-python-stubs?label=nuke-python-stubs)](https://github.com/sisoe24/nuke-python-stubs/releases) 22 | [![pysidetemplate](https://img.shields.io/github/v/release/sisoe24/pyside2-template?label=pyside2-template)](https://github.com/sisoe24/pyside2-template/releases) 23 | [![vimdcc](https://img.shields.io/github/v/release/sisoe24/vimdcc?label=vimdcc)](https://github.com/sisoe24/vimdcc/releases) 24 | 25 | --- 26 | 27 | VS Code extension for running Nuke/Houdini Python code directly from your editor. 28 | 29 | - [1. Nuke Tools README](#1-nuke-tools-readme) 30 | - [1.1. Features](#11-features) 31 | - [1.2. Requirements](#12-requirements) 32 | - [1.3. Execute code](#13-execute-code) 33 | - [1.4. Houdini support](#14-houdini-support) 34 | - [1.5. Python stubs](#15-python-stubs) 35 | - [1.5.1. Stubs are not working?](#151-stubs-are-not-working) 36 | - [1.6. BlinkScript](#16-blinkscript) 37 | - [1.7. Available Commands](#17-available-commands) 38 | - [1.8. Environment Variables](#18-environment-variables) 39 | - [1.8.1. Placeholders and Variables](#181-placeholders-and-variables) 40 | - [1.9. Additional Settings](#19-additional-settings) 41 | - [1.9.1. Network Settings](#191-network-settings) 42 | - [1.10. Windows Users](#110-windows-users) 43 | - [1.11. Included packages](#111-included-packages) 44 | - [1.12. Nodes Panel](#112-nodes-panel) 45 | - [1.12.1. Usage](#1121-usage) 46 | - [1.12.2. Known Issues and Limitations](#1122-known-issues-and-limitations) 47 | - [1.13. Contributing](#113-contributing) 48 | 49 | ## 1.1. Features 50 | 51 | - NEW: Houdini Python support. 52 | - Execute code and view output in Visual Studio Code. Simply connect `nukeserversocket` - no config needed on the same machine. 53 | - Nuke/Hiero Python stubs for auto-complete suggestions. 54 | - BlinkScript support. 55 | - PySide2 plugin template. 56 | - Commands for launching executables with optional arguments and environment variables. 57 | - And more... 58 | 59 | ## 1.2. Requirements 60 | 61 | Some commands require `nukeserversocket` to be installed and running. 62 | 63 | ## 1.3. Execute code 64 | 65 | 1. Download and install the companion plugin `nukeserversocket` via the command: `Nuke: Add Packages` -> `Nuke Server Socket`. 66 | 2. Connect `nukeserversocket` inside Nuke/Houdini. 67 | 3. With an active Python/BlinkScript file, use the command `Nuke: Run Inside Nuke` from the Command Palette or use the dedicated button in the editor's top right corner (see Key Bindings for Visual Studio Code for more information). 68 | 69 | ![CodeExecution](/resources/demo/execute_code.gif) 70 | 71 | ## 1.4. Houdini support 72 | 73 | `nukeserversocket >= 1.2.0` works with Houdini! Note that, while we still uses Nuke-style installation paths and naming conventions, Nuke itself isn't required. Check [nukeserversocket#houdini-installation](https://github.com/sisoe24/nukeserversocket/tree/main?tab=readme-ov-file#122-houdini-installation) for setup instructions. 74 | 75 | ## 1.5. Python stubs 76 | 77 | 1. Use the command `Nuke: Add Packages` -> `Python Stubs` to add the stubs to your user `python.analysis.extraPaths` setting. The stubs will be located in the `~/.nuke/NukeTools/stubs` directory. 78 | 2. Write `import nuke` into your script and you should see the auto-complete suggestions. 79 | 80 | ![PythonStubs](/resources/demo/auto_complete.gif) 81 | 82 | ### 1.5.1. Stubs are not working? 83 | 84 | If you're experiencing issues with the stubs in the latest versions of VSCode, you may find it helpful to adjust the `python.analysis.packageIndexDepths` setting. 85 | 86 | ```json 87 | "python.analysis.packageIndexDepths": [ 88 | { 89 | "depth": 5, 90 | "includeAllSymbols": true, 91 | "name": "nuke" 92 | }, 93 | { 94 | "depth": 5, 95 | "includeAllSymbols": true, 96 | "name": "hiero" 97 | }, 98 | ] 99 | ``` 100 | 101 | ## 1.6. BlinkScript 102 | 103 | BlinkScript's features include code execution, syntax highlighting, and suggestions. Use Material Icon extension for icons. 104 | 105 | Create/update BlinkScript nodes by running code with `.cpp` or `.blink` extensions via `Nuke: Run code inside nuke`. When running the code, a node with the same name as the file will be created in the current DAG. If the node already exists, the code will be updated and recompiled. 106 | 107 | ## 1.7. Available Commands 108 | 109 | - All commands are available by opening the Command Palette (`Command+Shift+P` on macOS and `Ctrl+Shift+P` on Windows/Linux) and typing in one of the following Command Name: 110 | 111 | | Command Name | Command ID | Description | 112 | | ------------------------------------- | ------------------------------ | ----------------------------------------------- | 113 | | `Nuke: Run Inside Nuke` | `nuke-tools.runCodeInsideNuke` | Execute code inside Nuke | 114 | | `Nuke: Launch main executable` | `nuke-tools.launchNuke` | Launch main executable | 115 | | `Nuke: Launch executable with prompt` | `nuke-tools.launchNukeOptArgs` | Launch executable with prompt for optional args | 116 | | `Nuke: Add Packages` | `nuke-tools.addPackages` | Add packages to `.nuke/NukeTools` dir | 117 | | `Nuke: Extras` | `nuke-tools.extras` | Show extra commands | 118 | | `Nuke: Show Executables` | `nuke-tools.showExecutables` | Show available executables | 119 | | `Nuke: Open Nuke Script` | `nuke-tools.openNukeScript` | Open the current script in Nuke | 120 | 121 | NOTES: 122 | 123 | - Running `Nuke: Add Package` will add the corresponding plugin to `$HOME/.nuke/NukeTools` and generate an import statement in the menu.py file. If menu.py doesn't exist, it will be created. 124 | - By default, the extension does not provide any shortcut. But you can assign each command to one. (see [Key Bindings for Visual Studio Code](https://code.visualstudio.com/docs/getstarted/keybindings) for more information). 125 | 126 | Example `keybindings.json` : 127 | 128 | ```json 129 | [ 130 | { 131 | "key": "alt+shift+n", 132 | "command": "nuke-tools.launchNuke" 133 | }, 134 | { 135 | "key": "alt+shift+r", 136 | "command": "nuke-tools.runCodeInsideNuke", 137 | "when": "editorTextFocus" 138 | } 139 | ] 140 | ``` 141 | 142 | ## 1.8. Environment Variables 143 | 144 | Add environment variables to the terminal instance using the `nukeTools.environmentVariables` setting. The extension assumes that if the key has multiple values, it is to be considered a path and it joins them using the appropriate separator for the detected shell and operating system. Additionally, you can choose to prepend or append the original key as needed. 145 | 146 | ```json 147 | { 148 | "nukeTools.environmentVariables": { 149 | "PATH": ["path1", "path2", "$PATH"] 150 | } 151 | } 152 | ``` 153 | 154 | >[!TIP] 155 | > These variables apply to all executables. To apply them to specific ones, use the executablesMap option. 156 | 157 | ### 1.8.1. Placeholders and Variables 158 | 159 | - `${workspaceFolder}`: Current workspace folder 160 | - `${workspaceFolderBasename}`: Name of the workspace folder 161 | - `${userHome}`: User's home directory 162 | - `$VAR_NAME`: System environment variables 163 | 164 | Example 165 | 166 | ```json 167 | { 168 | "nukeTools.environmentVariables": { 169 | "NUKE_PATH": [ 170 | "${workspaceFolder}/gizmo", 171 | "$NUKE_PATH" 172 | ], 173 | "PYTHONPATH": [ 174 | "$MYLIB/path/to/python/lib" 175 | ], 176 | "API_KEY": [ 177 | "0a9f0381-aebb-4e40-a77a-2c381b08e0ea" 178 | ] 179 | } 180 | } 181 | ``` 182 | 183 | ## 1.9. Additional Settings 184 | 185 | You can specify multiple executables for the extension by defining their names, paths (bin), command-line arguments (args), and environment variables (env). 186 | 187 | ```json 188 | { 189 | "nukeTools.executablesMap": { 190 | "NukeX": { 191 | "bin": "/usr/local/Nuke15.0v4/Nuke15.0", 192 | "args": "--nukex", 193 | "env": { 194 | "NUKE_PATH": [ 195 | "/my/nodes", 196 | "$NUKE_PATH" 197 | ] 198 | } 199 | }, 200 | "Maya20": { 201 | "bin": "/usr/autodesk/maya2020/bin/maya", 202 | "args": "" 203 | }, 204 | "Houdini": { 205 | "bin": "/opt/hfs19.5/bin/houdini", 206 | "args": "", 207 | "env": { 208 | "PYTHONPATH": [ 209 | "/my/scripts", 210 | "$PYTHONPATH" 211 | ], 212 | "HOUDINI_PATH": [ 213 | "&", 214 | "/my/houdini/assets" 215 | ] 216 | } 217 | } 218 | } 219 | } 220 | ``` 221 | 222 | Use `Nuke: Show Executables` to choose an executable from a quick pick menu. You can also assign keybindings with `nuke-tools.`. 223 | 224 | ```json 225 | { 226 | "key": "alt+shift+m", 227 | "command": "nuke-tools.Maya20", 228 | } 229 | ``` 230 | 231 | ### 1.9.1. Network Settings 232 | 233 | If you want to manually connect to a difference NukeServerSocket instance, you can set the `active` key to `true` and add the `host` and `port` keys. 234 | 235 | ```json 236 | { 237 | "nukeTools.network.manualConnection": { 238 | "active": false, 239 | "host": "localhost", 240 | "port": "49512" 241 | } 242 | } 243 | ``` 244 | 245 | ## 1.10. Windows Users 246 | 247 | From NukeTools 0.15.0, the extension supports environment variables in PowerShell and Command Prompt, auto-detects the shell, and handles Windows-style paths. If you encounter any issues, please open an issue on the GitHub repository. 248 | 249 | ## 1.11. Included packages 250 | 251 | The extension includes the following packages: 252 | 253 | - [nukeserversocket](https://github.com/sisoe24/nukeserversocket) - A Python plugin for Nuke that allows you to execute code from an external source. 254 | - [nuke-python-stubs](https://github.com/sisoe24/nuke-python-stubs) - Python stubs for Nuke and Hiero. 255 | - [pyside2-template](https://github.com/sisoe24/pyside2-template#readme) - A PySide2 template project for Nuke. 256 | - [vimdcc](https://github.com/sisoe24/vimdcc) - A Vim-like experience for Nuke's default Script Editor. 257 | 258 | > The extension auto-downloads and installs the latest package versions from GitHub, updating monthly. Use `Nuke Extras` -> `Clear Packages Cache` for version issues. 259 | 260 | ## 1.12. Nodes Panel 261 | 262 | > **Preview Feature:** The nodes panel is in Preview and may not work as expected. Report issues or suggest features on GitHub via issues or PRs. 263 | 264 | ![NodesPanel](/resources/demo/nodes_panel.gif) 265 | 266 | The nodes panel enables interaction with nodes in the current Nuke DAG. It currently supports adding and editing Python Knobs, including `knobChanged`. 267 | 268 | ### 1.12.1. Usage 269 | 270 | - Open the nodes panel via the Nuke icon in the Activity Bar. 271 | - Connect to `nukeserversocket` to access DAG nodes. 272 | - Add knobs with the `+` button and sync using "Send code to Knob." 273 | - Use "Refresh" for new knobs and "Sync Nodes" for renamed nodes. 274 | 275 | ### 1.12.2. Known Issues and Limitations 276 | 277 | - Knob scripts are stored in the Workspace directory (`$workspace/.nuketools`). Changing Workspace breaks knob file references. 278 | - Use alphanumeric characters and underscores for knob names. 279 | - After syncing a knob value, Nuke may require a command in the Script Editor to execute it. This is a Nuke-specific issue under investigation—contributions or ideas are welcome. 280 | 281 | ## 1.13. Contributing 282 | 283 | Contributions are welcome! If you have any ideas or suggestions, please open an issue or a pull request. At the moment, the extension tests are broken. I will try to fix them as soon as possible. 284 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // typings.d.ts 2 | declare module "nyc"; 3 | declare module "@istanbuljs/nyc-config-typescript"; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuke-tools", 3 | "displayName": "Nuke Tools", 4 | "publisher": "virgilsisoe", 5 | "description": "A set of tools related to Nuke that simplify the process of writing code for it.", 6 | "version": "0.16.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/sisoe24/Nuke-Tools" 10 | }, 11 | "icon": "resources/icons/nuke_icon.png", 12 | "engines": { 13 | "vscode": "^1.58.0" 14 | }, 15 | "activationEvents": [ 16 | "onStartupFinished", 17 | "onView:nuke-tools" 18 | ], 19 | "categories": [ 20 | "Other", 21 | "Programming Languages" 22 | ], 23 | "contributes": { 24 | "commands": [ 25 | { 26 | "command": "nuke-tools.refreshNodes", 27 | "icon": "$(extensions-refresh)", 28 | "title": "Refresh Viewer" 29 | }, 30 | { 31 | "command": "nuke-tools.syncNodes", 32 | "title": "Sync Nodes" 33 | }, 34 | { 35 | "command": "nuke-tools.addKnob", 36 | "title": "Add Python Knob", 37 | "icon": "$(add)" 38 | }, 39 | { 40 | "command": "nuke-tools.syncKnob", 41 | "title": "Send code to Knob", 42 | "icon": "$(sync)" 43 | }, 44 | { 45 | "command": "nuke-tools.deleteEntry", 46 | "title": "Delete" 47 | }, 48 | { 49 | "category": "Nuke", 50 | "command": "nuke-tools.launchNuke", 51 | "shortTitle": "Nuke", 52 | "title": "Launch main executable" 53 | }, 54 | { 55 | "category": "Nuke", 56 | "command": "nuke-tools.launchNukeOptArgs", 57 | "shortTitle": "Launch Nuke", 58 | "title": "Launch main executable with prompt" 59 | }, 60 | { 61 | "category": "Nuke", 62 | "command": "nuke-tools.addPackages", 63 | "shortTitle": "Add Packages", 64 | "title": "Add Packages" 65 | }, 66 | { 67 | "category": "Nuke", 68 | "command": "nuke-tools.extras", 69 | "shortTitle": "Extras", 70 | "title": "Extras" 71 | }, 72 | { 73 | "category": "Nuke", 74 | "command": "nuke-tools.openNukeScript", 75 | "shortTitle": "Open Script in Nuke", 76 | "title": "Open Script in Nuke" 77 | }, 78 | { 79 | "category": "Nuke", 80 | "command": "nuke-tools.showExecutables", 81 | "shortTitle": "Show Executables", 82 | "title": "Show Executables" 83 | }, 84 | { 85 | "category": "Nuke", 86 | "command": "nuke-tools.runCodeInsideNuke", 87 | "enablement": "resourceLangId =~ /python|cpp/ || resourceExtname == .blink || resourceScheme == untitled", 88 | "icon": { 89 | "dark": "resources/icons/dark/run_code.svg", 90 | "light": "resources/icons/light/run_code.svg" 91 | }, 92 | "title": "Run Code Inside Nuke" 93 | } 94 | ], 95 | "configuration": [ 96 | { 97 | "properties": { 98 | "nukeTools.network.debug": { 99 | "default": false, 100 | "markdownDescription": "Show network debug information in the output window. Enabling this option will not clean the console output after code execution.", 101 | "type": "boolean" 102 | }, 103 | "nukeTools.network.manualConnection": { 104 | "markdownDescription": "Connect to a different NukeServerSocket instance (not your computer).", 105 | "type": "object", 106 | "default": { 107 | "active": false, 108 | "host": "localhost", 109 | "port": "54321" 110 | } 111 | }, 112 | "nukeTools.executablePath": { 113 | "description": "A path to an executable to be used by the command `Nuke: Launch main executable`.", 114 | "type": "string" 115 | }, 116 | "nukeTools.executableArgs": { 117 | "markdownDescription": "Command line arguments to be added when launching `#nukeTools.executablePath#`. [Nuke documentation](https://learn.foundry.com/nuke/content/comp_environment/configuring_nuke/command_line_operations.html). (e.g. `--nukex`)", 118 | "type": "string" 119 | }, 120 | "nukeTools.executablesMap": { 121 | "description": "A map of executables to be used by the command `Nuke: Show Executables`.", 122 | "type": "object", 123 | "examples": [ 124 | { 125 | "nukeX": { 126 | "bin": "/usr/local/Nuke15.0v4/Nuke15.0", 127 | "args": "--nukex", 128 | "env": { 129 | "NUKE_PATH": [ 130 | "/my/nodes", 131 | "$NUKE_PATH" 132 | ] 133 | } 134 | }, 135 | "houdini": { 136 | "bin": "/opt/hfs19.5/bin/houdini", 137 | "args": "", 138 | "env": { 139 | "PYTHONPATH": [ 140 | "/my/scripts", 141 | "$PYTHONPATH" 142 | ], 143 | "HOUDINI_PATH": [ 144 | "&", 145 | "/my/houdini/assets" 146 | ] 147 | } 148 | } 149 | } 150 | ] 151 | }, 152 | "nukeTools.restartTerminalInstance": { 153 | "default": false, 154 | "markdownDescription": "Restart the terminal instance instead of creating new one. **NOTE:** This will terminate all Nuke processes spawned by the extension (helpful for quick GUI plugin testing).", 155 | "type": "boolean" 156 | }, 157 | "nukeTools.environmentVariables": { 158 | "description": "An object with environment variables that will be added when running an executable.", 159 | "type": "object", 160 | "additionalProperties": { 161 | "type": "array", 162 | "items": { 163 | "type": "string" 164 | } 165 | }, 166 | "examples": [ 167 | { 168 | "NUKE_PATH": [ 169 | "${workspaceFolder}/${workspaceFolderBasename}/bin", 170 | "${userHome}/.nuke", 171 | "$NUKE_PATH" 172 | ] 173 | } 174 | ] 175 | }, 176 | "nukeTools.clearPreviousOutput": { 177 | "default": false, 178 | "description": "Clear previous console output before next code execution.", 179 | "type": "boolean" 180 | } 181 | }, 182 | "title": "Nuke Tools" 183 | } 184 | ], 185 | "grammars": [ 186 | { 187 | "language": "blinkscript", 188 | "path": "./language/blinkscript.tmLanguage.json", 189 | "scopeName": "source.blink" 190 | }, 191 | { 192 | "language": "nuke", 193 | "path": "./language/nuke.tmLanguage.json", 194 | "scopeName": "source.nk" 195 | } 196 | ], 197 | "languages": [ 198 | { 199 | "aliases": [ 200 | "BlinkScript", 201 | "blinkscript" 202 | ], 203 | "configuration": "./language/language-configuration.json", 204 | "extensions": [ 205 | ".blink" 206 | ], 207 | "id": "blinkscript" 208 | }, 209 | { 210 | "aliases": [ 211 | "nuke" 212 | ], 213 | "extensions": [ 214 | ".nk", 215 | ".gizmo" 216 | ], 217 | "id": "nuke" 218 | } 219 | ], 220 | "menus": { 221 | "editor/title": [ 222 | { 223 | "command": "nuke-tools.runCodeInsideNuke", 224 | "group": "navigation", 225 | "when": "resourceLangId =~ /python|cpp/ || resourceExtname == .blink || resourceScheme == untitled" 226 | } 227 | ], 228 | "view/item/context": [ 229 | { 230 | "command": "nuke-tools.addKnob", 231 | "when": "view == nuke-tools && viewItem == node", 232 | "group": "inline" 233 | }, 234 | { 235 | "command": "nuke-tools.syncKnob", 236 | "when": "view == nuke-tools && viewItem == knob", 237 | "group": "inline" 238 | } 239 | ], 240 | "view/title": [ 241 | { 242 | "command": "nuke-tools.refreshNodes", 243 | "group": "navigation", 244 | "when": "view == nuke-tools" 245 | }, 246 | { 247 | "command": "nuke-tools.syncNodes", 248 | "when": "view == nuke-tools" 249 | } 250 | ] 251 | }, 252 | "views": { 253 | "nodes-inspector": [ 254 | { 255 | "contextualTitle": "Nuke Tools", 256 | "icon": "resources/icons/nuke.svg", 257 | "id": "nuke-tools", 258 | "name": "Nodes Panel" 259 | } 260 | ] 261 | }, 262 | "viewsContainers": { 263 | "activitybar": [ 264 | { 265 | "icon": "resources/icons/nuke.svg", 266 | "id": "nodes-inspector", 267 | "title": "Nuke Tools" 268 | } 269 | ] 270 | }, 271 | "viewsWelcome": [ 272 | { 273 | "contents": "Nodes Panel is currently in beta. Some features may not work as expected.\nPlease connect nukeserversocket and refresh. [Learn more](https://github.com/sisoe24/nukeserversocket#141-execute-code).\n[Refresh](command:nuke-tools.refreshNodes)", 274 | "view": "nuke-tools" 275 | } 276 | ] 277 | }, 278 | "dependencies": { 279 | "@terascope/fetch-github-release": "^0.8.7", 280 | "extract-zip": "^2.0.1" 281 | }, 282 | "devDependencies": { 283 | "@types/glob": "^7.1.3", 284 | "@types/mocha": "^8.2.2", 285 | "@types/node": "14.x", 286 | "@types/uuid": "^9.0.1", 287 | "@types/vscode": "^1.58.0", 288 | "@typescript-eslint/eslint-plugin": "^4.26.0", 289 | "@typescript-eslint/parser": "^4.26.0", 290 | "esbuild": "^0.12.29", 291 | "eslint": "^7.32.0", 292 | "glob": "^7.1.7", 293 | "mocha": "^10.2.0", 294 | "source-map-support": "^0.5.20", 295 | "ts-node": "^10.4.0", 296 | "typescript": "^4.3.2", 297 | "vscode-test": "^1.5.2" 298 | }, 299 | "keywords": [ 300 | "nuke", 301 | "python", 302 | "thefoundry", 303 | "foundry", 304 | "blinkscript", 305 | "blink", 306 | "stubs", 307 | "gizmo", 308 | "pyside" 309 | ], 310 | "license": "MIT", 311 | "main": "./out/main.js", 312 | "scripts": { 313 | "compile": "tsc -p ./", 314 | "esbuild": "npm run esbuild-base -- --sourcemap", 315 | "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --external:keyv --format=cjs --platform=node --log-level=debug", 316 | "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", 317 | "test-compile": "tsc -p ./", 318 | "vscode:prepublish": "npm run esbuild-base -- --minify", 319 | "lint": "eslint src --ext ts", 320 | "pretest": "npm run compile && npm run lint", 321 | "test": "node ./out/test/runTest.js", 322 | "watch": "tsc -watch -p ./" 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /resources/demo/auto_complete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sisoe24/Nuke-Tools/982f3393b3b6008aa1a69dabd7c7e4fb61730f23/resources/demo/auto_complete.gif -------------------------------------------------------------------------------- /resources/demo/execute_code.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sisoe24/Nuke-Tools/982f3393b3b6008aa1a69dabd7c7e4fb61730f23/resources/demo/execute_code.gif -------------------------------------------------------------------------------- /resources/demo/nodes_panel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sisoe24/Nuke-Tools/982f3393b3b6008aa1a69dabd7c7e4fb61730f23/resources/demo/nodes_panel.gif -------------------------------------------------------------------------------- /resources/icons/dark/run_code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Nuke 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/icons/light/run_code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Nuke 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/icons/nuke.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/icons/nuke_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sisoe24/Nuke-Tools/982f3393b3b6008aa1a69dabd7c7e4fb61730f23/resources/icons/nuke_icon.png -------------------------------------------------------------------------------- /resources/language/blinkscript.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmsyntax/master/tmlanguage.json", 3 | "name": "BlinkScript", 4 | "patterns": [ 5 | { 6 | "include": "#comments" 7 | }, 8 | { 9 | "include": "#strings" 10 | }, 11 | { 12 | "include": "#keywords" 13 | }, 14 | { 15 | "include": "#operators" 16 | }, 17 | { 18 | "include": "#builtins" 19 | }, 20 | { 21 | "include": "#variables" 22 | } 23 | ], 24 | "repository": { 25 | "variables": { 26 | "patterns": [ 27 | { 28 | "name": "constant.language.blink", 29 | "match": "(?<=#define\\s+)\\w+" 30 | }, 31 | { 32 | "name": "entity.name.function.blink", 33 | "match": "\\w+?(?=\\()" 34 | }, 35 | { 36 | "name": "entity.name.class.blink", 37 | "match": "(?<=(struct|class|kernel)\\s*)\\w+" 38 | }, 39 | { 40 | "name": "constant.numeric.blink", 41 | "match": "\\b\\d+" 42 | }, 43 | { 44 | "name": "variable.other.object.blink", 45 | "match": "\\w+(?=\\.)" 46 | }, 47 | { 48 | "name": "variable.other.property.blink", 49 | "match": "(?<=\\.|->)\\w+" 50 | }, 51 | { 52 | "name": "variable.other.readwrite.blink", 53 | "match": "\\w+" 54 | } 55 | ] 56 | }, 57 | "builtins": { 58 | "patterns": [ 59 | { 60 | "name": "entity.other.inherited-class.input-edge-type.blink", 61 | "match": "\\be(EdgeNull|EdgeClamped|EdgeConstant)\\b" 62 | }, 63 | { 64 | "name": "entity.other.inherited-class.input-access.blink", 65 | "match": "\\beAccess(Point|Ranged1D|Ranged2D|Random)\\b" 66 | }, 67 | { 68 | "name": "entity.other.inherited-class.image-access-type.blink", 69 | "match": "\\be(Read|Write|ReadWrite)\\b" 70 | }, 71 | { 72 | "name": "entity.other.inherited-class.kernel-type.blink", 73 | "match": "\\b(ImageComputationKernel|ImageRollingKernel|ImageReductionKernel)\\b" 74 | }, 75 | { 76 | "name": "entity.other.inherited-class.kernel-granularity.blink", 77 | "match": "\\be(ComponentWise|PixelWise)\\b" 78 | }, 79 | { 80 | "name": "entity.name.class.image.blink", 81 | "match": "\\b(Image)\\b" 82 | }, 83 | { 84 | "name": "keyword.other.blink", 85 | "match": "\\b(kernel|param|local)\\b" 86 | } 87 | ] 88 | }, 89 | "operators": { 90 | "patterns": [ 91 | { 92 | "name": "keyword.operator.arithmetic.blink", 93 | "match": "(\\+|\\*|/|-|=|%)" 94 | }, 95 | { 96 | "name": "keyword.operator.ternary.blink", 97 | "match": "\\?" 98 | }, 99 | { 100 | "name": "keyword.operator.comparison.blink", 101 | "match": "(<=|>=|<|>|==|!=)" 102 | } 103 | ] 104 | }, 105 | "keywords": { 106 | "patterns": [ 107 | { 108 | "name": "variable.language.this.blink", 109 | "match": "\\bthis\\b" 110 | }, 111 | { 112 | "name": "keyword.control.blink", 113 | "match": "\\b(if|else|while|break|for|return)\\b" 114 | }, 115 | { 116 | "name": "keyword.control.directive.blink", 117 | "match": "#(define|include)" 118 | }, 119 | { 120 | "name": "storage.type.builtin.primitive.blink", 121 | "match": "\\b(float[1-4]?|int|bool|long|short|void|class|struct|double|signed|unsigned)\\b" 122 | }, 123 | { 124 | "name": "storage.type.modifiers.blink", 125 | "match": "\\b(public|private|protected|typename|typedef)\\b" 126 | }, 127 | { 128 | "name": "keyword.other.mix.blink", 129 | "match": "\\b(inline|static|template|union|namespace|explicit)\\b" 130 | }, 131 | { 132 | "name": "storage.type.blink", 133 | "match": "\\b(const|enum)\\b" 134 | }, 135 | { 136 | "name": "keyword.other.unit.suffix.floating-point.blink", 137 | "match": "(?<=\\d\\.\\d*)f" 138 | } 139 | ] 140 | }, 141 | "comments": { 142 | "patterns": [ 143 | { 144 | "name": "comment.line.double-slash.blink", 145 | "match": "//.*$" 146 | }, 147 | { 148 | "name": "comment.block.blink", 149 | "begin": "/\\*", 150 | "end": "\\*/" 151 | } 152 | ] 153 | }, 154 | "strings": { 155 | "patterns": [ 156 | { 157 | "name": "string.quoted.other.include.blink", 158 | "match": "(?<=#include\\s+)<\\w+?>" 159 | }, 160 | { 161 | "name": "string.quoted.double.blink", 162 | "begin": "\"", 163 | "end": "\"", 164 | "patterns": [ 165 | { 166 | "name": "constant.character.escape.blink", 167 | "match": "\\\\." 168 | } 169 | ] 170 | }, 171 | { 172 | "name": "string.quoted.single.blink", 173 | "begin": "'", 174 | "end": "'", 175 | "patterns": [ 176 | { 177 | "name": "constant.character.escape.blink", 178 | "match": "\\\\." 179 | } 180 | ] 181 | } 182 | ] 183 | } 184 | }, 185 | "scopeName": "source.blink" 186 | } 187 | -------------------------------------------------------------------------------- /resources/language/blinkscript_completion.json: -------------------------------------------------------------------------------- 1 | { 2 | "kernelTypes": { 3 | "ImageComputationKernel": "Used for image processing, this takes zero or more images as input and produces one or more images as output.", 4 | "ImageRollingKernel": "Used for image processing, where there is a data dependency between the output at different points in the output space. With an ImageComputationKernel, there are no guarantees about the order in which the output pixels will be filled in. With an ImageRollingKernel, you can choose to \"roll\" the kernel either horizontally or vertically over the iteration bounds, allowing you to carry data along rows or down columns respectively.", 5 | "ImageReductionKernel": "Used to \"reduce\" an image down to a value or set of values that represent it, for example to calculate statistics such as the mean or variance of an image." 6 | }, 7 | "kernelGranularity": { 8 | "ePixelWise": "At each pixel, a pixel wise kernel can write to all the channels in the output and can access any channel in the input. You would use a pixelwise kernel for any operation where there is interdependence between the channels, for example a saturation.", 9 | "eComponentWise": "Componentwise processing means that each channel in the output image will be processed independently. When processing each channel, only values from the corresponding channel in the input(s) can be accessed." 10 | }, 11 | "imageAccess": { 12 | "eEdgeClamped": "", 13 | "eEdgeConstant": "", 14 | "eAccessPoint": "", 15 | "eAccessRanged1D": "", 16 | "eAccessRanged2D": "", 17 | "eAccessRangedRandom": "", 18 | "eRead": "", 19 | "eWrite": "", 20 | "eReadWrite": "" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/language/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | "blockComment": [ 5 | "/*", 6 | "*/" 7 | ] 8 | }, 9 | "brackets": [ 10 | [ 11 | "{", 12 | "}" 13 | ], 14 | [ 15 | "[", 16 | "]" 17 | ], 18 | [ 19 | "(", 20 | ")" 21 | ] 22 | ], 23 | "autoClosingPairs": [ 24 | { 25 | "open": "{", 26 | "close": "}" 27 | }, 28 | { 29 | "open": "[", 30 | "close": "]" 31 | }, 32 | { 33 | "open": "(", 34 | "close": ")" 35 | }, 36 | { 37 | "open": "'", 38 | "close": "'", 39 | "notIn": [ 40 | "string", 41 | "comment" 42 | ] 43 | }, 44 | { 45 | "open": "\"", 46 | "close": "\"", 47 | "notIn": [ 48 | "string" 49 | ] 50 | }, 51 | { 52 | "open": "`", 53 | "close": "`", 54 | "notIn": [ 55 | "string", 56 | "comment" 57 | ] 58 | }, 59 | { 60 | "open": "/**", 61 | "close": " */", 62 | "notIn": [ 63 | "string" 64 | ] 65 | } 66 | ], 67 | "autoCloseBefore": ";:.,=}])>` \n\t", 68 | "surroundingPairs": [ 69 | [ 70 | "{", 71 | "}" 72 | ], 73 | [ 74 | "[", 75 | "]" 76 | ], 77 | [ 78 | "(", 79 | ")" 80 | ], 81 | [ 82 | "'", 83 | "'" 84 | ], 85 | [ 86 | "\"", 87 | "\"" 88 | ], 89 | [ 90 | "`", 91 | "`" 92 | ] 93 | ], 94 | "folding": { 95 | "markers": { 96 | "start": "^\\s*//\\s*#?region\\b", 97 | "end": "^\\s*//\\s*#?endregion\\b" 98 | } 99 | }, 100 | "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", 101 | "indentationRules": { 102 | "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", 103 | "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /resources/language/nuke.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmsyntax/master/tmlanguage.json", 3 | "name": "nuke", 4 | "patterns": [ 5 | { 6 | "include": "#comments" 7 | }, 8 | { 9 | "include": "#strings" 10 | }, 11 | { 12 | "include": "#operators" 13 | }, 14 | { 15 | "include": "#variables" 16 | } 17 | ], 18 | "repository": { 19 | "variables": { 20 | "patterns": [ 21 | { 22 | "name": "constant.numeric.hex.nk", 23 | "match": "0[xX][0-9a-fA-F]+" 24 | }, 25 | { 26 | "name": "constant.numeric.dec.nk", 27 | "match": "\\b\\d+\\b" 28 | }, 29 | { 30 | "name": "entity.name.tag.nk", 31 | "match": "(?<=<)\\w+|\\w+(?=>)" 32 | }, 33 | { 34 | "name": "entity.other.attribute-name.nk", 35 | "match": "\\w+(?==)" 36 | }, 37 | { 38 | "name": "variable.other.nk", 39 | "match": "(?<=^\\s+)\\b\\w+" 40 | }, 41 | { 42 | "name": "entity.name.class.nk", 43 | "match": "\\b[A-Z]\\w+" 44 | }, 45 | { 46 | "name": "entity.name.function.nk", 47 | "match": "\\b(cut_paste_input|end_group|stack)\\b" 48 | }, 49 | { 50 | "name": "entity.name.type.nk", 51 | "match": "[A-Za-z]+(?=\\})" 52 | }, 53 | { 54 | "name": "storage.type.builtin.primitive.nk", 55 | "match": "\\b(True|False|none|true|false|set|push|\\$)\\b" 56 | }, 57 | { 58 | "name": "keyword.other.unit.suffix.floating-point.nk", 59 | "match": "(?<=\\d\\.\\d*)f" 60 | } 61 | ] 62 | }, 63 | "operators": { 64 | "patterns": [ 65 | { 66 | "name": "keyword.operator.arithmetic.nk", 67 | "match": "(\\+|\\*|/|-|=|%)" 68 | }, 69 | { 70 | "name": "keyword.operator.ternary.nk", 71 | "match": "\\?" 72 | }, 73 | { 74 | "name": "keyword.operator.comparison.nk", 75 | "match": "(<=|>=|<|>|==|!=|\\$)" 76 | } 77 | ] 78 | }, 79 | "comments": { 80 | "patterns": [ 81 | { 82 | "name": "comment.line.double-slash.nk", 83 | "match": "//.*$" 84 | }, 85 | { 86 | "name": "comment.line.nk", 87 | "match": "(\/.+\/.+(\/|$))+" 88 | }, 89 | { 90 | "name": "comment.line.nk", 91 | "match": "#!.+" 92 | }, 93 | { 94 | "name": "comment.block.nk", 95 | "begin": "/\\*", 96 | "end": "\\*/" 97 | } 98 | ] 99 | }, 100 | "strings": { 101 | "patterns": [ 102 | { 103 | "name": "string.quoted.double.nk", 104 | "begin": "\"", 105 | "end": "\"", 106 | "patterns": [ 107 | { 108 | "name": "constant.character.escape.nk", 109 | "match": "\\\\." 110 | } 111 | ] 112 | }, 113 | { 114 | "name": "string.quoted.single.nk", 115 | "begin": "'", 116 | "end": "'", 117 | "patterns": [ 118 | { 119 | "name": "constant.character.escape.nk", 120 | "match": "\\\\." 121 | } 122 | ] 123 | } 124 | ] 125 | } 126 | }, 127 | "scopeName": "source.nk" 128 | } 129 | -------------------------------------------------------------------------------- /resources/language/saturation_sample.blink: -------------------------------------------------------------------------------- 1 | kernel ${1:name} : ImageComputationKernel { 2 | Image src; // the input image 3 | Image dst; // the output image 4 | 5 | param: 6 | float saturation; // This parameter is made available to the user. 7 | 8 | local: 9 | float3 coefficients; // This local variable is not exposed to the user. 10 | 11 | // In define(), parameters can be given labels and default values. 12 | void define() { 13 | defineParam(saturation, "Saturation", 1.2f); 14 | } 15 | 16 | // The init() function is run before any calls to process(). 17 | // Local variables can be initialized here. 18 | void init() { 19 | // Initialize coefficients according to rec. 709 standard. 20 | coefficients.x = 0.2126f; 21 | coefficients.y = 0.7152f; 22 | coefficients.z = 0.0722f; 23 | } 24 | 25 | void process() { 26 | // Read the input image 27 | SampleType(src) input = src(); 28 | 29 | // Isolate the RGB components 30 | float3 srcPixel(input.x, input.y, input.z); 31 | 32 | // Calculate luma 33 | float luma = srcPixel.x * coefficients.x 34 | + srcPixel.y * coefficients.y 35 | + srcPixel.z * coefficients.z; 36 | // Apply saturation 37 | float3 saturatedPixel = (srcPixel - luma) * saturation + luma; 38 | 39 | // Write the result to the output image 40 | dst() = float4(saturatedPixel.x, saturatedPixel.y, saturatedPixel.z, input.w); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/blinkscript/blink_completion.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { readFileSync } from "fs"; 3 | 4 | import * as vscode from "vscode"; 5 | import { PACKAGE_RESOURCES_PATH } from "../constants"; 6 | 7 | /** 8 | * The completion file for blinkscript. 9 | */ 10 | export const completionFile = JSON.parse( 11 | readFileSync( 12 | path.join(PACKAGE_RESOURCES_PATH, "language", "blinkscript_completion.json"), 13 | "utf-8" 14 | ) 15 | ); 16 | 17 | /** 18 | * Create the array for the completion item provider. 19 | * 20 | * Each item would have a label and an optional documentation that could be an 21 | * empty string. All of items will be of kind `Property`. 22 | * 23 | * @param object object of key value pair with the label and description for the 24 | * item completion. eg `{ePixelWise: "This method.."}` 25 | * 26 | * @returns a `CompletionItem` array. 27 | */ 28 | export function createCompletions(object: { [s: string]: string }): vscode.CompletionItem[] { 29 | const completionArray: vscode.CompletionItem[] = []; 30 | for (const [key, description] of Object.entries(object)) { 31 | const item = new vscode.CompletionItem(key, vscode.CompletionItemKind.Property); 32 | item.documentation = description; 33 | completionArray.push(item); 34 | } 35 | return completionArray; 36 | } 37 | 38 | /** 39 | * Get completion items. 40 | * 41 | * This serves only for internal verification. Function will check if the key is 42 | * present and if yes will return the completion array. Otherwise will throw an error invalid key 43 | * 44 | * @param key key to check if present 45 | * @returns 46 | */ 47 | export function getCompletions(key: string): vscode.CompletionItem[] { 48 | if (!Object.prototype.hasOwnProperty.call(completionFile, key)) { 49 | throw new Error(`Invalid completion item: ${key}`); 50 | } 51 | return createCompletions(completionFile[key]); 52 | } 53 | 54 | const kernelType = { 55 | items: getCompletions("kernelTypes"), 56 | match: RegExp(/kernel\s\w+\s*:\s*\w+(?!<)$/), 57 | }; 58 | 59 | const kernelGranularity = { 60 | items: getCompletions("kernelGranularity"), 61 | match: RegExp(/kernel\s\w+\s*:\s*\w+\s* { 68 | const lines = document.lineCount; 69 | const text = formatFile(document.getText()); 70 | 71 | return [vscode.TextEdit.replace(new vscode.Range(0, 0, lines - 1, 0), text)]; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/blinkscript/blink_snippet.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as vscode from "vscode"; 3 | 4 | import { readFileSync } from "fs"; 5 | import { PACKAGE_RESOURCES_PATH } from "../constants"; 6 | 7 | 8 | /** 9 | * Sample blinkscript file. same as Nuke default blinkscript node. 10 | */ 11 | export const saturationTemplate = readFileSync( 12 | path.join(PACKAGE_RESOURCES_PATH, "language", "saturation_sample.blink"), 13 | "utf-8" 14 | ); 15 | 16 | /** 17 | * BlinkScript snippet provider. 18 | */ 19 | export class BlinkSnippets implements vscode.CompletionItemProvider { 20 | /** 21 | * Initialize the snippet provider. 22 | * @returns an array of completion items. 23 | */ 24 | provideCompletionItems(): vscode.CompletionItem[] { 25 | const kernelCompletion = new vscode.CompletionItem("kernel"); 26 | kernelCompletion.documentation = "Saturation sample script."; 27 | kernelCompletion.insertText = new vscode.SnippetString(saturationTemplate); 28 | 29 | return [kernelCompletion]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | export type EnvVars = { [key: string]: Array }; 4 | 5 | export type ExecutableConfig = { 6 | bin: string; 7 | args?: string; 8 | env?: EnvVars; 9 | }; 10 | 11 | type ExecutableMap = { 12 | [key: string]: ExecutableConfig; 13 | }; 14 | 15 | type CommandMappings = "executablesMap"; 16 | 17 | type StringConfig = 18 | | "executablePath" 19 | | "executableArgs"; 20 | 21 | type ObjectConfig = "environmentVariables"; 22 | 23 | type BooleanConfig = 24 | | "restartTerminalInstance" 25 | | "clearPreviousOutput" 26 | | "network.enableManualConnection" 27 | | "network.debug"; 28 | 29 | export type ManualConnection = { 30 | active: boolean; 31 | host: string; 32 | port: string; 33 | }; 34 | 35 | type ManualConnectionConfig = "network.manualConnection"; 36 | 37 | type ConfigProperty = 38 | | StringConfig 39 | | BooleanConfig 40 | | CommandMappings 41 | | ObjectConfig 42 | | ManualConnectionConfig; 43 | 44 | /** 45 | * Get a configuration property. 46 | * 47 | * This is a wrapper around vscode.workspace.getConfiguration to avoid having some 48 | * boilerplate code. It calls the root configuration and then get the property. 49 | * 50 | * @param property - name of the configuration property to get. 51 | * @returns - the value of the property. 52 | * @throws Error if the property doesn't exist. 53 | */ 54 | export function getConfig(property: ObjectConfig): EnvVars; 55 | export function getConfig(property: BooleanConfig): boolean; 56 | export function getConfig(property: StringConfig): string; 57 | export function getConfig(property: CommandMappings): ExecutableMap; 58 | export function getConfig(property: ManualConnectionConfig): ManualConnection; 59 | export function getConfig(property: ConfigProperty): unknown { 60 | const config = vscode.workspace.getConfiguration("nukeTools"); 61 | const subConfig = config.get(property); 62 | 63 | if (typeof subConfig === "undefined") { 64 | throw new Error(`Configuration: ${property} doesn't exist`); 65 | } 66 | 67 | return subConfig; 68 | } 69 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import * as os from "os"; 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import * as vscode from "vscode"; 5 | 6 | 7 | export const NUKE_DIR = path.join(os.homedir(), ".nuke"); 8 | export const NUKE_TOOLS_DIR = path.join(NUKE_DIR, "NukeTools"); 9 | 10 | export const PACKAGE_ROOT = vscode.extensions.getExtension("virgilsisoe.nuke-tools") 11 | ?.extensionPath as string; 12 | 13 | export const PACKAGE_RESOURCES_PATH = path.join(PACKAGE_ROOT, "resources"); 14 | export const ASSETS_PATH = path.join(PACKAGE_RESOURCES_PATH, "assets"); 15 | if (!fs.existsSync(ASSETS_PATH )) { 16 | fs.mkdirSync(ASSETS_PATH ); 17 | } 18 | 19 | export const ASSETS_LOG_PATH = path.join(PACKAGE_RESOURCES_PATH, "included_assets.json"); 20 | -------------------------------------------------------------------------------- /src/create_project.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | 4 | import * as vscode from "vscode"; 5 | 6 | import * as nuke from "./nuke"; 7 | import { PackageIds, addPackage, packageMap } from "./packages"; 8 | 9 | /** 10 | * Ask user to fill the data that we are going to use to replace the placeholders 11 | * inside the pyside2 template project. 12 | * 13 | * @returns a placeholders object. 14 | */ 15 | export async function askUser(): Promise { 16 | const projectName = (await vscode.window.showInputBox({ 17 | title: "Project Name", 18 | value: "Project Name", 19 | })) as string; 20 | 21 | return projectName.replace(/\s/g, "").toLowerCase(); 22 | } 23 | 24 | /** 25 | * Walk recursively a directory to get all of its files. 26 | * 27 | * @param dir Path for the directory to parse. 28 | * @returns A list of files 29 | */ 30 | const osWalk = function (dir: string): string[] { 31 | let results: string[] = []; 32 | 33 | fs.readdirSync(dir).forEach(function (file) { 34 | file = dir + path.sep + file; 35 | const stat = fs.statSync(file); 36 | if (stat && stat.isDirectory()) { 37 | /* Recurse into a subdirectory */ 38 | results = results.concat(osWalk(file)); 39 | } else { 40 | results.push(file); 41 | } 42 | }); 43 | return results; 44 | }; 45 | 46 | /** 47 | * Substitute placeholders values. 48 | * 49 | * @param files a list of files to apply substitution 50 | * @param projectSlug the name of the project to use as substitution 51 | */ 52 | export function substitutePlaceholders(files: string[], projectSlug: string): void { 53 | for (const file of files) { 54 | let fileContent = fs.readFileSync(file, "utf-8"); 55 | fileContent = fileContent.replace(RegExp("projectslug", "g"), projectSlug); 56 | fs.writeFileSync(file, fileContent); 57 | } 58 | } 59 | 60 | /** 61 | * Ask user to open the newly created pyside2 template in vscode. 62 | * 63 | * @param destination Path of the folder to open in vscode. 64 | */ 65 | async function openProjectFolder(destination: vscode.Uri): Promise { 66 | const openProjectFolder = (await vscode.window.showQuickPick(["Yes", "No"], { 67 | title: "Open Project Folder?", 68 | })) as string; 69 | 70 | if (openProjectFolder === "Yes") { 71 | vscode.commands.executeCommand("vscode.openFolder", destination); 72 | } 73 | } 74 | 75 | /** 76 | * Ask user confirmation before importing the pyside2 template package inside the menu.py 77 | * 78 | * @param module The name of the module to import inside the menu.py 79 | */ 80 | async function importStatementMenu(module: string): Promise { 81 | const loadNukeInit = (await vscode.window.showQuickPick(["Yes", "No"], { 82 | title: "Import into Nuke's menu.py?", 83 | })) as string; 84 | 85 | if (loadNukeInit === "Yes") { 86 | nuke.addMenuImport(`from NukeTools import ${module}`); 87 | } 88 | } 89 | 90 | /** 91 | * Create a PySide2 template Nuke plugin. 92 | */ 93 | export async function createTemplate(): Promise { 94 | const projectSlug = await askUser(); 95 | 96 | const pkgData = packageMap.get(PackageIds.pySide2Template); 97 | if (!pkgData) { 98 | vscode.window.showErrorMessage(`NukeTools: Package not found: ${pkgData}`); 99 | return; 100 | } 101 | 102 | const destination = path.join(pkgData.destination, projectSlug); 103 | 104 | if (fs.existsSync(destination)) { 105 | await vscode.window.showErrorMessage("Directory exists already."); 106 | return; 107 | } 108 | 109 | await addPackage(PackageIds.pySide2Template, destination); 110 | 111 | substitutePlaceholders(osWalk(destination), projectSlug); 112 | 113 | fs.renameSync(path.join(destination, "projectslug"), path.join(destination, projectSlug)); 114 | 115 | await importStatementMenu(projectSlug); 116 | await openProjectFolder(vscode.Uri.file(destination)); 117 | 118 | vscode.window.showInformationMessage( 119 | `Project ${projectSlug} created. For more information, please read 120 | the official [README](https://github.com/sisoe24/pyside2-template#readme).` 121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | import * as vscode from "vscode"; 4 | 5 | import * as stubs from "./stubs"; 6 | import * as nuke from "./nuke"; 7 | import * as socket from "./socket"; 8 | import { Version } from "./version"; 9 | 10 | import * as execs from "./launch_executable"; 11 | import * as nukeTemplate from "./create_project"; 12 | 13 | import { BlinkSnippets } from "./blinkscript/blink_snippet"; 14 | import { BlinkScriptFormat } from "./blinkscript/blink_format"; 15 | import { BlinkScriptCompletionProvider } from "./blinkscript/blink_completion"; 16 | 17 | import { NukeCompletionProvider } from "./nuke/completitions"; 18 | import { NukeNodesInspectorProvider } from "./nuke/nodes_tree"; 19 | 20 | import { showNotification } from "./notification"; 21 | import { fetchPackagesLatestVersion } from "./packages_fetch"; 22 | import { initializePackageLog } from "./packages"; 23 | import { ExecutableConfig, getConfig } from "./config"; 24 | 25 | function registerNodesInspectorCommands(context: vscode.ExtensionContext): void { 26 | const nukeProvider = new NukeNodesInspectorProvider(); 27 | 28 | vscode.window.registerTreeDataProvider("nuke-tools", nukeProvider); 29 | 30 | context.subscriptions.push( 31 | vscode.commands.registerCommand("nuke-tools.refreshNodes", () => nukeProvider.refresh()) 32 | ); 33 | 34 | context.subscriptions.push( 35 | vscode.commands.registerCommand("nuke-tools.syncNodes", () => nukeProvider.syncNodes()) 36 | ); 37 | 38 | context.subscriptions.push( 39 | vscode.commands.registerCommand("nuke-tools.on_itemClicked", (item) => 40 | nukeProvider.itemClicked(item) 41 | ) 42 | ); 43 | 44 | context.subscriptions.push( 45 | vscode.commands.registerCommand("nuke-tools.addKnob", (item) => nukeProvider.addKnob(item)) 46 | ); 47 | 48 | context.subscriptions.push( 49 | vscode.commands.registerCommand("nuke-tools.syncKnob", (item) => 50 | nukeProvider.syncKnob(item) 51 | ) 52 | ); 53 | } 54 | 55 | function registerBlinkScriptCommands(context: vscode.ExtensionContext): void { 56 | context.subscriptions.push( 57 | vscode.languages.registerDocumentFormattingEditProvider( 58 | "blinkscript", 59 | new BlinkScriptFormat() 60 | ) 61 | ); 62 | 63 | context.subscriptions.push( 64 | vscode.languages.registerCompletionItemProvider( 65 | "blinkscript", 66 | new BlinkScriptCompletionProvider() 67 | ) 68 | ); 69 | 70 | context.subscriptions.push( 71 | vscode.languages.registerCompletionItemProvider("blinkscript", new BlinkSnippets()) 72 | ); 73 | } 74 | 75 | type Action = { 76 | label: string; 77 | execute: () => void; 78 | }; 79 | 80 | interface ActionItem extends vscode.QuickPickItem { 81 | execute: () => void; 82 | } 83 | 84 | function showActionPicker(items: Array) { 85 | const picker = vscode.window.createQuickPick(); 86 | picker.items = items.map((item) => { 87 | return { 88 | label: item.label, 89 | execute: item.execute, 90 | }; 91 | }); 92 | 93 | picker.onDidChangeSelection((selection) => { 94 | const selected = selection[0] as ActionItem; 95 | if (selected) { 96 | selected.execute(); 97 | picker.hide(); 98 | } 99 | }); 100 | 101 | picker.onDidHide(() => picker.dispose()); 102 | picker.show(); 103 | } 104 | 105 | function registerPackagesCommands(context: vscode.ExtensionContext): void { 106 | const actions: Array = [ 107 | { 108 | label: "Pyside Template", 109 | execute: nukeTemplate.createTemplate, 110 | }, 111 | { 112 | label: "Python Stubs", 113 | execute: stubs.addStubs, 114 | }, 115 | { 116 | label: "Nuke Server Socket", 117 | execute: nuke.addNukeServerSocket, 118 | }, 119 | { 120 | label: "Vim DCC", 121 | execute: nuke.addVimDcc, 122 | }, 123 | ]; 124 | 125 | context.subscriptions.push( 126 | vscode.commands.registerCommand("nuke-tools.addPackages", () => { 127 | showActionPicker(actions); 128 | }) 129 | ); 130 | } 131 | 132 | function registerExtraCommands(context: vscode.ExtensionContext): void { 133 | const actions: Array = [ 134 | { 135 | label: "Clear Package Cache", 136 | execute: () => { 137 | initializePackageLog(); 138 | fetchPackagesLatestVersion(); 139 | vscode.window.showInformationMessage("Packages cached cleared."); 140 | }, 141 | }, 142 | { 143 | label: "Send Debug Message", 144 | execute: socket.sendDebugMessage, 145 | }, 146 | { 147 | label: "Show Network Addresses", 148 | execute: () => { 149 | vscode.window.showInformationMessage(socket.getAddresses()); 150 | }, 151 | }, 152 | ]; 153 | 154 | context.subscriptions.push( 155 | vscode.commands.registerCommand("nuke-tools.extras", () => { 156 | showActionPicker(actions); 157 | }) 158 | ); 159 | } 160 | 161 | class ExecutablePickItem implements vscode.QuickPickItem { 162 | detail?: string | undefined; 163 | description?: string | undefined; 164 | constructor(public label: string, public config: ExecutableConfig) { 165 | this.detail = config.bin; 166 | this.description = config.args; 167 | } 168 | } 169 | 170 | function registerExecutablesCommands(context: vscode.ExtensionContext): void { 171 | context.subscriptions.push( 172 | vscode.commands.registerCommand("nuke-tools.showExecutables", () => { 173 | const picker = vscode.window.createQuickPick(); 174 | 175 | const items: ExecutablePickItem[] = []; 176 | for (const [name, config] of Object.entries(getConfig("executablesMap"))) { 177 | items.push(new ExecutablePickItem(name, config)); 178 | } 179 | 180 | picker.items = items; 181 | picker.onDidChangeSelection((selection) => { 182 | if (selection[0]) { 183 | const item = selection[0] as ExecutablePickItem; 184 | execs.launchExecutable(item.label, item.config); 185 | picker.hide(); 186 | } 187 | }); 188 | 189 | picker.show(); 190 | picker.onDidHide(() => picker.dispose()); 191 | }) 192 | ); 193 | 194 | context.subscriptions.push( 195 | vscode.commands.registerCommand("nuke-tools.launchNuke", () => { 196 | execs.launchPrimaryExecutable(); 197 | }) 198 | ); 199 | 200 | context.subscriptions.push( 201 | vscode.commands.registerCommand("nuke-tools.launchNukeOptArgs", () => { 202 | void execs.launchPromptExecutable(); 203 | }) 204 | ); 205 | 206 | // register the exectables to commands so user can add a shortcut 207 | for (const [name, config] of Object.entries(getConfig("executablesMap"))) { 208 | context.subscriptions.push( 209 | vscode.commands.registerCommand(`nuke-tools.${name}`, () => { 210 | execs.launchExecutable(name, config); 211 | }) 212 | ); 213 | } 214 | } 215 | 216 | export function activate(context: vscode.ExtensionContext): void { 217 | Version.update(context); 218 | 219 | fetchPackagesLatestVersion(); 220 | 221 | showNotification(context); 222 | 223 | registerNodesInspectorCommands(context); 224 | registerBlinkScriptCommands(context); 225 | registerPackagesCommands(context); 226 | registerExecutablesCommands(context); 227 | registerExtraCommands(context); 228 | 229 | context.subscriptions.push( 230 | vscode.languages.registerCompletionItemProvider("python", new NukeCompletionProvider(), "(") 231 | ); 232 | 233 | context.subscriptions.push( 234 | vscode.commands.registerCommand("nuke-tools.runCodeInsideNuke", () => { 235 | void socket.sendMessage(); 236 | }) 237 | ); 238 | 239 | context.subscriptions.push( 240 | vscode.commands.registerCommand("nuke-tools.openNukeScript", () => { 241 | const editor = vscode.window.activeTextEditor; 242 | if (!editor) { 243 | return null; 244 | } 245 | const file = editor.document.fileName; 246 | 247 | if (file === null || !file.endsWith(".nk")) { 248 | vscode.window.showWarningMessage("Not a Nuke script (.nk)"); 249 | return; 250 | } 251 | 252 | execs.launchExecutable(path.basename(file), { 253 | bin: getConfig("executablePath"), 254 | args: file, 255 | }); 256 | }) 257 | ); 258 | } 259 | -------------------------------------------------------------------------------- /src/launch_executable.ts: -------------------------------------------------------------------------------- 1 | import * as os from "os"; 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import * as vscode from "vscode"; 5 | 6 | import { getConfig, EnvVars, ExecutableConfig } from "./config"; 7 | const IS_WINDOWS = os.type() === "Windows_NT"; 8 | 9 | const isPowerShell = () => { 10 | const { shell } = vscode.env; 11 | return shell.includes("powershell") || shell.includes("pwsh"); 12 | }; 13 | 14 | const isCmdShell = () => { 15 | return vscode.env.shell.includes("cmd"); 16 | }; 17 | 18 | const isUnixShell = () => { 19 | return !isPowerShell() && !isCmdShell(); 20 | }; 21 | 22 | /** 23 | * ExecutablePath object class. 24 | */ 25 | export class ExecutablePath { 26 | name: string; 27 | path: string; 28 | args: string; 29 | env: EnvVars; 30 | 31 | /** 32 | * Init method for the ExecutablePath object. 33 | * 34 | * @param name - The name of the executable. 35 | * @param path - The path for the executable file. 36 | * @param args - Optional arguments for the command line 37 | * @param env - Optional environment variables 38 | */ 39 | constructor(name: string, path: string, args = "", env: EnvVars = {}) { 40 | this.name = name; 41 | this.path = path; 42 | this.args = args; 43 | this.env = env; 44 | } 45 | 46 | /** 47 | * Check if path exists. If not, will show an error message to user. 48 | * 49 | * @returns - True if does, False otherwise 50 | */ 51 | exists(): boolean { 52 | if (!fs.existsSync(this.path)) { 53 | vscode.window.showErrorMessage(`Cannot find path: ${this.path}.`); 54 | return false; 55 | } 56 | return true; 57 | } 58 | 59 | /** 60 | * Create the cli command to be executed inside the terminal. 61 | * 62 | * @returns - string like command for the terminal. 63 | */ 64 | buildExecutableCommand(): string { 65 | let cmd = `"${this.path}" ${this.args}`; 66 | 67 | if (isPowerShell()) { 68 | cmd = `& ${cmd}`; 69 | } 70 | 71 | return cmd.trim(); 72 | } 73 | } 74 | 75 | /** 76 | * Replace placeholders in a string with their corresponding values. 77 | * 78 | * @example 79 | * resolveEnvVariables("foo ${workspaceFolder} $SHELL bar"); 80 | * // => "foo /home/user /bin/bash bar" 81 | * 82 | * @param text - The text to resolve the placeholders in. 83 | * @return - The text with the placeholders resolved. 84 | */ 85 | function resolveEnvVariables(text: string): string { 86 | let workspaceFolder = vscode.workspace.workspaceFolders?.[0].uri.fsPath || ""; 87 | 88 | // on windows we need to convert the path to a unix-like path 89 | if (IS_WINDOWS && isUnixShell()) { 90 | workspaceFolder = workspaceFolder.replace(/\\/g, "/"); 91 | // Convert the drive letter to lowercase and add a leading slash (e.g. C: -> /c) 92 | workspaceFolder = workspaceFolder.replace(/^([a-zA-Z]):/, (_, driveLetter) => { 93 | return `/${driveLetter.toLowerCase()}`; 94 | }); 95 | } 96 | 97 | const placeholders: { [key: string]: string } = { 98 | workspaceFolder, 99 | workspaceFolderBasename: path.basename(workspaceFolder), 100 | userHome: os.homedir(), 101 | }; 102 | 103 | for (const [placeholder, replacement] of Object.entries(placeholders)) { 104 | text = text.replace(new RegExp(`\\$\\{${placeholder}\\}`, "g"), replacement); 105 | } 106 | 107 | for (const match of text.match(/\$\w+/g) || []) { 108 | text = text.replace(match, process.env[match.replace("$", "")] || ""); 109 | } 110 | 111 | return text; 112 | } 113 | 114 | /** 115 | * Stringify the environment variables into a string that can be used in the terminal. 116 | * 117 | * @param env EnvVars object containing the environment variables to stringify 118 | * @returns a string containing the environment variables 119 | */ 120 | function stringifyEnv(env: EnvVars): string { 121 | let envString = ""; 122 | 123 | for (const [k, v] of Object.entries(env)) { 124 | const envPath = v.join(path.delimiter); 125 | if (isPowerShell()) { 126 | envString += `$env:${k}="${envPath}"; `; 127 | } else if (isUnixShell()) { 128 | envString += `${k}=${envPath} `; 129 | } else if (isCmdShell()) { 130 | envString += `set ${k}=${envPath}&&`; 131 | } else { 132 | envString += `${k}=${envPath} `; 133 | vscode.window.showWarningMessage( 134 | `Unknown shell detected ${vscode.env.shell}. Environment variables may not be set correctly.` 135 | ); 136 | } 137 | } 138 | 139 | return envString; 140 | } 141 | 142 | /** 143 | * Execute the command in the terminal. Before executing the command, if restartInstance 144 | * is enabled, will dispose of the previous terminal instance. 145 | * 146 | * @param execPath - ExecutablePath object. 147 | */ 148 | function execCommand(execPath: ExecutablePath): void { 149 | const terminalName = `${path.basename(execPath.path)} ${execPath.name}`; 150 | 151 | if (getConfig("restartTerminalInstance")) { 152 | vscode.window.terminals.forEach((terminal) => { 153 | if (terminal.name === terminalName) { 154 | terminal.dispose(); 155 | } 156 | }); 157 | } 158 | 159 | const globalEnv = stringifyEnv(getConfig("environmentVariables")); 160 | const localEnv = stringifyEnv(execPath.env); 161 | // we dont care if user writes duplicate keys 162 | const env = `${globalEnv} ${localEnv}`.trim(); 163 | const command = resolveEnvVariables(`${env} ${execPath.buildExecutableCommand()}`.trim()); 164 | 165 | const terminal = vscode.window.createTerminal(terminalName); 166 | 167 | terminal.sendText(command); 168 | terminal.show(true); 169 | } 170 | 171 | /** 172 | * Launch primary executable from configuration. 173 | * 174 | * @returns - the executable path object created. 175 | */ 176 | export function launchPrimaryExecutable(): ExecutablePath { 177 | const execObj = new ExecutablePath( 178 | "Main", 179 | getConfig("executablePath"), 180 | getConfig("executableArgs") 181 | ); 182 | 183 | if (execObj.exists()) { 184 | execCommand(execObj); 185 | } 186 | 187 | return execObj; 188 | } 189 | 190 | /** 191 | * Launch main executable with prompt for optional arguments. 192 | * 193 | * @returns - the executable path object created. 194 | */ 195 | export async function launchPromptExecutable(): Promise { 196 | const execObj = new ExecutablePath("Main Prompt", getConfig("executablePath")); 197 | 198 | if (execObj.exists()) { 199 | const optArgs = await vscode.window.showInputBox({ 200 | ignoreFocusOut: true, 201 | placeHolder: "Optional arguments for current instance", 202 | }); 203 | 204 | if (optArgs) { 205 | execObj.args = optArgs; 206 | } 207 | execCommand(execObj); 208 | } 209 | return execObj; 210 | } 211 | 212 | export function launchExecutable(name: string, executableConfig: ExecutableConfig): void { 213 | const execObj = new ExecutablePath( 214 | name, 215 | executableConfig.bin, 216 | executableConfig.args, 217 | executableConfig.env 218 | ); 219 | 220 | if (execObj.exists()) { 221 | execCommand(execObj); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/notification.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | const _msg = ` 4 | 0.17.0. Houdini Python Support: NukeTools/NukeServerSocket now works on Houdini! 5 | `; 6 | 7 | 8 | /** 9 | * Show update message box if version is newer or update message is different. 10 | * 11 | * @param context vscode ExtensionContext 12 | */ 13 | export function showNotification(context: vscode.ExtensionContext, msg: string = _msg): void { 14 | const extensionId = context.extension.id; 15 | 16 | // get the value stored inside the global state key: _value['extension.version'] 17 | const extVersion = extensionId + ".version"; 18 | const previousVersion = context.globalState.get(extVersion) as string; 19 | 20 | // update the value stored inside the global state key: _value['extension.updateMsg'] 21 | const extUpdateMsg = extensionId + ".updateMsg"; 22 | const previousMsg = context.globalState.get(extUpdateMsg) as string; 23 | context.globalState.update(extUpdateMsg, msg); 24 | 25 | // get the package.json version and store in the global state key _value['extensionId.version'] 26 | const currentVersion = context.extension.packageJSON.version; 27 | context.globalState.update(extVersion, currentVersion); 28 | 29 | if (currentVersion > previousVersion && msg !== previousMsg) { 30 | vscode.window.showInformationMessage(msg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/nuke.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | 4 | import * as packages from "./packages"; 5 | import { NUKE_DIR } from "./constants"; 6 | 7 | const MENU_PY = path.join(NUKE_DIR, "menu.py"); 8 | 9 | /** 10 | * Add a menu import to the menu.py file in the .nuke folder. 11 | * 12 | * Only adds the import if it doesn't already exist. 13 | * 14 | * @param importText The import statement to add to the menu.py file. 15 | */ 16 | export function addMenuImport(importText: string): void { 17 | if (fs.existsSync(MENU_PY)) { 18 | const fileContent = fs.readFileSync(MENU_PY, "utf-8"); 19 | if (!fileContent.includes(importText)) { 20 | fs.appendFileSync(MENU_PY, `\n${importText}\n`); 21 | } 22 | } else { 23 | fs.writeFileSync(MENU_PY, importText); 24 | } 25 | } 26 | 27 | /** 28 | * Cleanup the legacy NukeServerSocket import from the menu.py file. 29 | * 30 | * NukeServerSocket < 1.0.0 used to have a different import statement which is now deprecated 31 | * and should be removed from the menu.py file if it exists. 32 | */ 33 | function cleanupLegacyNukeServerSocket(): void { 34 | if (!fs.existsSync(MENU_PY)) { 35 | return; 36 | } 37 | 38 | const fileContent = fs.readFileSync(MENU_PY, "utf-8"); 39 | 40 | const legacyImport = "from NukeTools import NukeServerSocket"; 41 | 42 | if (!fileContent.includes(legacyImport)) { 43 | return; 44 | } 45 | 46 | fs.writeFileSync(MENU_PY, fileContent.replace(legacyImport, "")); 47 | } 48 | 49 | /** 50 | * Add nukeserversocket to the .nuke folder and import it inside the menu.py 51 | */ 52 | export function addNukeServerSocket(): void { 53 | cleanupLegacyNukeServerSocket(); 54 | packages.addPackage(packages.PackageIds.nukeServerSocket); 55 | addMenuImport( 56 | "from NukeTools.nukeserversocket import nukeserversocket\nnukeserversocket.install_nuke()" 57 | ); 58 | } 59 | 60 | /** 61 | * Add vimdcc to the .nuke folder and import it inside the menu.py 62 | */ 63 | export function addVimDcc(): void { 64 | packages.addPackage(packages.PackageIds.vimdcc); 65 | addMenuImport("from NukeTools.vimdcc import vimdcc\nvimdcc.install_nuke()"); 66 | } 67 | -------------------------------------------------------------------------------- /src/nuke/completitions.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import { sendCommand } from "../socket"; 4 | 5 | export class NukeCompletionProvider implements vscode.CompletionItemProvider { 6 | provideCompletionItems( 7 | document: vscode.TextDocument, 8 | position: vscode.Position, 9 | token: vscode.CancellationToken, 10 | context: vscode.CompletionContext 11 | ): vscode.ProviderResult< 12 | vscode.CompletionItem[] | vscode.CompletionList 13 | > { 14 | const linePrefix = document.lineAt(position).text.substring(0, position.character); 15 | if (linePrefix.endsWith("nuke.toNode(")) { 16 | return this.getAllNodes(); 17 | } 18 | return []; 19 | } 20 | 21 | private async getAllNodes(): Promise { 22 | return sendCommand( 23 | JSON.stringify({ text: "[n.name() for n in nuke.allNodes()]", formatText: "0" }) 24 | ).then((data) => { 25 | const nodes = JSON.parse(data.message.replace(/'/g, '"')); 26 | 27 | const items: vscode.CompletionItem[] = []; 28 | for (const node of nodes) { 29 | items.push(new vscode.CompletionItem(`"${node}"`, vscode.CompletionItemKind.Class)); 30 | } 31 | return items; 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/nuke/nodes_tree.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import * as fs from "fs"; 4 | import * as path from "path"; 5 | 6 | import { isConnected, sendCommand } from "../socket"; 7 | 8 | const setupCodeSnippet = (knobFile: KnobFile) => ` 9 | nuketools_tab = nuke.Tab_Knob('nuketools', 'NukeTools') 10 | 11 | node = nuke.toNode('${knobFile.node}') 12 | 13 | if not node.knob('nuketools'): 14 | node.addKnob(nuketools_tab) 15 | 16 | script_knob = nuke.PyScript_Knob('${knobFile.knob}_${knobFile.id}', '${knobFile.knob}') 17 | 18 | if not node.knob('${knobFile.knob}'): 19 | node.addKnob(script_knob) 20 | `; 21 | 22 | const syncKnobChangedSnippet = (knobFile: KnobFile) => ` 23 | node = nuke.toNode('${knobFile.node}') 24 | node.knob('knobChanged').setValue('''${knobFile.content()}''') 25 | `; 26 | 27 | const syncKnobCodeSnippet = (knobFile: KnobFile) => ` 28 | node = nuke.toNode('${knobFile.node}') 29 | node.knob('${knobFile.knob}_${knobFile.id}').setValue('''${knobFile.content()}''') 30 | `; 31 | 32 | const syncNodeSnippet = (knobFile: KnobFile) => ` 33 | def getnodes(): 34 | for node in nuke.allNodes('${knobFile.nodeClass}'): 35 | for knob in node.allKnobs(): 36 | if knob.name() == '${knobFile.knob}_${knobFile.id}': 37 | return node.name() 38 | return False 39 | print(getnodes()) 40 | `; 41 | 42 | /** 43 | * Walk recursively a directory to get all of its files. 44 | * 45 | * @param dir Path for the directory to parse. 46 | * @returns A list of files 47 | */ 48 | const osWalk = function (dir: string): string[] { 49 | const results: string[] = []; 50 | 51 | fs.readdirSync(dir).forEach(function (file) { 52 | results.push(path.join(dir, file)); 53 | }); 54 | return results; 55 | }; 56 | 57 | // get vscode workspace path 58 | function getWorkspacePath() { 59 | const workspaceFolders = vscode.workspace.workspaceFolders; 60 | if (workspaceFolders) { 61 | return workspaceFolders[0].uri.fsPath; 62 | } 63 | return ""; 64 | } 65 | 66 | // create .nuketools directory in the workspace 67 | const KNOBS_DIR = path.join(getWorkspacePath(), ".nuketools"); 68 | 69 | async function sendToNuke(text: string) { 70 | return await sendCommand( 71 | JSON.stringify({ 72 | text: text, 73 | file: "", 74 | formatText: "0", 75 | }) 76 | ); 77 | } 78 | 79 | class KnobFile { 80 | knob: string; 81 | node: string; 82 | nodeClass: string; 83 | id: string; 84 | path: string; 85 | filename: string; 86 | 87 | /** 88 | * Constructor for the KnobFile class. 89 | * 90 | * @param knobFile name to the knob file 91 | */ 92 | constructor(knobFile: string) { 93 | this.filename = path.basename(knobFile); 94 | this.path = knobFile; 95 | 96 | const split = this.filename.split("_"); 97 | this.node = split[0]; 98 | this.nodeClass = split[1]; 99 | this.knob = split[2]; 100 | this.id = split[3].replace(".py", ""); 101 | } 102 | 103 | /** 104 | * The signature of the knob file. 105 | * 106 | * @param node node name 107 | * @param nodeClass node class 108 | * @param knob knob name 109 | * @param id uuid of the knob 110 | * @returns the signature of the knob file 111 | */ 112 | private static fileSignature(node: string, nodeClass: string, knob: string, id: string) { 113 | return `${node}_${nodeClass}_${knob.replace(" ", "_")}_${id}`; 114 | } 115 | 116 | /** 117 | * Create a new knob file. 118 | * 119 | * @param item A dictionary with the node name and class. 120 | * @param knobName The name of the knob. 121 | * @returns A new knob file. 122 | */ 123 | static create(item: { node: string; class: string }, knobName: string) { 124 | const fileSignature = KnobFile.fileSignature( 125 | item.node, 126 | item.class, 127 | knobName, 128 | Date.now().toString() 129 | ); 130 | 131 | const filePath = path.join(KNOBS_DIR, `${fileSignature}.py`); 132 | return new KnobFile(filePath); 133 | } 134 | 135 | /** 136 | * Set the new name of the node in the knob file. 137 | * 138 | * @param name New node name 139 | * @returns New file name 140 | */ 141 | newName(name: string) { 142 | return `${KnobFile.fileSignature(name, this.nodeClass, this.knob, this.id)}.py`; 143 | } 144 | 145 | /** 146 | * Get the content of the knob file. 147 | * 148 | * @returns The content of the knob file. 149 | */ 150 | content() { 151 | return fs.readFileSync(path.join(KNOBS_DIR, this.path), { encoding: "utf-8" }); 152 | } 153 | } 154 | 155 | /** 156 | * An object containing the context value and the icon of the tree item. 157 | */ 158 | const itemContext = { 159 | node: { context: "node", icon: "symbol-misc" }, 160 | knob: { context: "knob", icon: "symbol-file" }, 161 | }; 162 | 163 | /** 164 | * A tree node item representing a dependency. The dependency can be a Node or a Knob. 165 | */ 166 | class Dependency extends vscode.TreeItem { 167 | constructor( 168 | public readonly label: string, 169 | private version: string, 170 | context: { context: string; icon: string }, 171 | public readonly collapsibleState: vscode.TreeItemCollapsibleState 172 | ) { 173 | super(label, collapsibleState); 174 | this.tooltip = `${this.label}-${this.version}`; 175 | this.description = this.version; 176 | this.contextValue = context.context; 177 | 178 | // TODO: add command only to knobs 179 | this.command = { 180 | command: "nuke-tools.on_itemClicked", 181 | title: label, 182 | arguments: [this], 183 | }; 184 | this.iconPath = new vscode.ThemeIcon(context.icon); 185 | } 186 | } 187 | 188 | export class NukeNodesInspectorProvider implements vscode.TreeDataProvider { 189 | private _onDidChangeTreeData: vscode.EventEmitter = 190 | new vscode.EventEmitter(); 191 | readonly onDidChangeTreeData: vscode.Event = 192 | this._onDidChangeTreeData.event; 193 | 194 | /** 195 | * Refresh the tree view. 196 | */ 197 | refresh(): void { 198 | this._onDidChangeTreeData.fire(); 199 | } 200 | 201 | /** 202 | * Sync the files in the .nuketools directory with the nodes in Nuke. Because the user can rename 203 | * a node in Nuke, when syncing, the file will be renamed in the .nuketools directory. 204 | * 205 | * The new name is obtained from the return of the socket command. The socket command returns the 206 | * name of the node or 'False' as a string if the node doesn't exist. 207 | */ 208 | async syncNodes(): Promise { 209 | const files = osWalk(KNOBS_DIR); 210 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 211 | 212 | // TODO: send all the code at once and then rename the files. 213 | 214 | for (let i = 0; i < files.length; i++) { 215 | const file = files[i]; 216 | if (!file.endsWith(".py")) { 217 | continue; 218 | } 219 | 220 | const knobFile = new KnobFile(file); 221 | const result = await sendToNuke(syncNodeSnippet(knobFile)); 222 | if (result.error) { 223 | vscode.window.showErrorMessage(`Failed to sync ${file}: ${result.errorMessage}`); 224 | continue; 225 | } 226 | const newName = knobFile.newName(result.message); 227 | 228 | try { 229 | fs.renameSync(file, path.join(KNOBS_DIR, newName)); 230 | } catch (error) { 231 | vscode.window.showErrorMessage(`Failed to rename ${file} to ${newName}: ${error}`); 232 | } 233 | 234 | sleep(1000); 235 | } 236 | 237 | this.refresh(); 238 | } 239 | 240 | /** 241 | * Sync the knob code content inside Nuke. When the knob is 'knobChanged', the code will be 242 | * act differently since it does not need to be setup as a knob. 243 | * 244 | * @param item A Node dependency item. 245 | */ 246 | syncKnob(item: Dependency): void { 247 | const knobFile = new KnobFile(item.label); 248 | 249 | const codeSnippet = 250 | knobFile.knob === "knobChanged" 251 | ? syncKnobChangedSnippet(knobFile) 252 | : syncKnobCodeSnippet(knobFile); 253 | 254 | sendToNuke(codeSnippet); 255 | } 256 | 257 | /** 258 | * Add a new knob to a node. 259 | * 260 | * The knob is created as a new file in the .nuketools directory and it will follow the signature of 261 | * KnobFile.fileSignature. If the knob already exists, it will not be created. 262 | * 263 | * To create a new knob, the user will be prompted to enter the name of the knob. The name will be used 264 | * to create the file and the knob in Nuke. 265 | * 266 | * @param item A Node dependency item. 267 | */ 268 | async addKnob(item: Dependency): Promise { 269 | const knobName = await vscode.window.showInputBox({ 270 | title: "Add knob", 271 | placeHolder: "Enter the name of the knob", 272 | prompt: "Use only alphanumeric characters and underscores.", 273 | }); 274 | if (!knobName) { 275 | return; 276 | } 277 | 278 | const knobFile = KnobFile.create( 279 | { node: item.label, class: item.description as string }, 280 | knobName 281 | ); 282 | 283 | const files = osWalk(KNOBS_DIR); 284 | for (let i = 0; i < files.length; i++) { 285 | const file = files[i]; 286 | 287 | // Get the knob name file parts without the id to check if the knob already exists. 288 | const knobFileName = knobFile.filename.split("_").slice(0, 3).join("_"); 289 | 290 | if (path.basename(file).startsWith(knobFileName)) { 291 | vscode.window.showErrorMessage("Knob already exists"); 292 | return; 293 | } 294 | } 295 | 296 | fs.writeFileSync(knobFile.path, ""); 297 | vscode.window.showTextDocument(vscode.Uri.file(knobFile.path), { preview: false }); 298 | 299 | this.refresh(); 300 | 301 | // If the knob is knobChanged, we don't need to send the code to Nuke. 302 | // because the knob already exists. 303 | if (knobName === "knobChanged") { 304 | return; 305 | } 306 | 307 | sendToNuke(setupCodeSnippet(knobFile)); 308 | } 309 | 310 | /** 311 | * Open the file in the editor when the user clicks on it only if it's a python file. 312 | * 313 | * @param item The item that was clicked 314 | */ 315 | itemClicked(item: Dependency): void { 316 | if (item.label.endsWith(".py")) { 317 | vscode.window.showTextDocument(vscode.Uri.file(path.join(KNOBS_DIR, item.label)), { 318 | preview: false, 319 | }); 320 | } 321 | } 322 | 323 | getTreeItem(element: Dependency): vscode.TreeItem | Thenable { 324 | return element; 325 | } 326 | 327 | /** 328 | * Get the knobs files for the node in the tree view. 329 | * 330 | * The function will iterate over all the files in the .nuketools folder and check if 331 | * the file name starts with the node name and class. If it does, will add it to the list. 332 | * 333 | * @param element The node that was clicked 334 | * @returns A list of Dependency objects that represent the knobs files. 335 | */ 336 | private getKnobs(element: Dependency) { 337 | const items: vscode.ProviderResult = []; 338 | osWalk(KNOBS_DIR).forEach((file) => { 339 | const filename = path.basename(file); 340 | // label is the node name and description is the node class 341 | if (filename.startsWith(`${element.label}_${element.description}`)) { 342 | items.push( 343 | new Dependency( 344 | filename, 345 | "", 346 | itemContext.knob, 347 | vscode.TreeItemCollapsibleState.None 348 | ) 349 | ); 350 | } 351 | }); 352 | return items; 353 | } 354 | 355 | /** 356 | * Get the nodes in the current Nuke script. 357 | * 358 | * The nodes are retrieved by sending python code to the Nuke server socket. 359 | * 360 | * @returns A list of Dependency objects that represent the nodes in the current Nuke script. 361 | */ 362 | private async getNodes(): Promise { 363 | const data = await sendToNuke( 364 | "import nuke;import json;json.dumps({n.name():n.Class() for n in nuke.allNodes()})" 365 | ); 366 | 367 | // If the connection was refused, it means that Nuke server socket is not running. 368 | if (data.error) { 369 | vscode.window.showErrorMessage(`Failed to get nodes: ${data.errorMessage}`); 370 | return []; 371 | } 372 | 373 | if (!data.message) { 374 | vscode.window.showErrorMessage(`Failed to get nodes: no message received from Nuke`); 375 | return []; 376 | } 377 | 378 | // For some reason, the JSON is wrapped in single quotes, so we need to remove them 379 | const nodes: { string: string } = JSON.parse(data.message.replace(/'/g, "")); 380 | 381 | const items: vscode.ProviderResult = []; 382 | for (const [key, value] of Object.entries(nodes)) { 383 | items.push( 384 | new Dependency( 385 | key, 386 | value, 387 | itemContext.node, 388 | vscode.TreeItemCollapsibleState.Collapsed 389 | ) 390 | ); 391 | } 392 | return items; 393 | } 394 | 395 | getChildren(element?: Dependency): Thenable { 396 | if (!fs.existsSync(KNOBS_DIR)) { 397 | fs.mkdirSync(KNOBS_DIR); 398 | } 399 | if (element) { 400 | return Promise.resolve(this.getKnobs(element)); 401 | } 402 | return Promise.resolve(this.getNodes()); 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /src/packages.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | 4 | import * as vscode from "vscode"; 5 | import extract = require("extract-zip"); 6 | import { GithubRelease } from "@terascope/fetch-github-release/dist/src/interfaces"; 7 | import { downloadRelease } from "@terascope/fetch-github-release"; 8 | 9 | import { Version } from "./version"; 10 | import { NUKE_TOOLS_DIR, ASSETS_PATH, ASSETS_LOG_PATH } from "./constants"; 11 | 12 | type PackageType = { 13 | name: string; 14 | destination: string; 15 | }; 16 | 17 | export enum PackageIds { 18 | nukeServerSocket = "nukeserversocket", 19 | nukePythonStubs = "nuke-python-stubs", 20 | pySide2Template = "pyside2-template", 21 | vimdcc = "vimdcc", 22 | } 23 | 24 | export function initializePackageLog(): void { 25 | const keys = Object.values(PackageIds); 26 | const currentPackages = keys.reduce((obj, key) => ({ ...obj, [key]: "v0.0.0" }), {}); 27 | const initial = { 28 | lastCheck: "2021-01-01T00:00:00.000Z", 29 | packages: { 30 | current: currentPackages, 31 | lastest: {}, 32 | }, 33 | }; 34 | fs.writeFileSync(ASSETS_LOG_PATH, JSON.stringify(initial, null, 4)); 35 | } 36 | 37 | if (!fs.existsSync(ASSETS_LOG_PATH)) { 38 | initializePackageLog(); 39 | } 40 | 41 | export const packageMap = new Map([ 42 | [ 43 | PackageIds.nukeServerSocket, 44 | { 45 | name: "nukeserversocket", 46 | destination: path.join(NUKE_TOOLS_DIR, "nukeserversocket"), 47 | }, 48 | ], 49 | [ 50 | PackageIds.nukePythonStubs, 51 | { 52 | name: "nuke-python-stubs", 53 | destination: path.join(NUKE_TOOLS_DIR, "stubs"), 54 | }, 55 | ], 56 | [ 57 | PackageIds.pySide2Template, 58 | { 59 | name: "pyside2-template", 60 | destination: NUKE_TOOLS_DIR, 61 | }, 62 | ], 63 | [ 64 | PackageIds.vimdcc, 65 | { 66 | name: "vimdcc", 67 | destination: path.join(NUKE_TOOLS_DIR, "vimdcc"), 68 | }, 69 | ], 70 | ]); 71 | 72 | /** 73 | * Extract a zip file to a destination. 74 | * 75 | * Because the zip file will completely replace the destination folder, 76 | * the destination folder will be renamed to destination + "-master" 77 | * to prevent the destination folder from being replaced if something goes wrong during the extraction. 78 | * 79 | * @param source Source zip file. 80 | * @param destination Destination folder. 81 | */ 82 | function extractPackage(source: string, destination: string): Promise { 83 | return new Promise((resolve, reject) => { 84 | try { 85 | extract(source, { dir: destination + "-master" }) 86 | .then(() => { 87 | if (fs.existsSync(destination)) { 88 | fs.rmSync(destination, { recursive: true }); 89 | } 90 | fs.renameSync(destination + "-master", destination); 91 | resolve(); 92 | }) 93 | .catch((err) => { 94 | vscode.window.showErrorMessage( 95 | `NukeTools: Failed to extract package: ${source}. ${err}` 96 | ); 97 | reject(err); 98 | }); 99 | } catch (err) { 100 | vscode.window.showErrorMessage(err as string); 101 | reject(err); 102 | } 103 | }); 104 | } 105 | 106 | /** 107 | * Check if the package should be updated. 108 | * 109 | * The package will be updated if the current extension version is greater than the previous version or 110 | * if the latest version of the package is greater than the current version. 111 | * 112 | * @param packageName the package to check 113 | * @returns 114 | */ 115 | function shouldUpdate(packageName: string): boolean { 116 | if (Version.extCurrentVersion > Version.extPreviousVersion) { 117 | return true; 118 | } 119 | 120 | const packages = JSON.parse(fs.readFileSync(ASSETS_LOG_PATH, "utf8"))["packages"]; 121 | 122 | if (packages["lastest"][packageName] > packages["current"][packageName]) { 123 | return true; 124 | } 125 | 126 | return false; 127 | } 128 | 129 | /** 130 | * Add a package to the .nuke/NukeTools folder. 131 | * 132 | * Adding a package can be done in two ways: 133 | * - Download the latest release from GitHub 134 | * - Extract the package from the assets folder 135 | * 136 | * When the package is downloaded from GitHub, it will be extracted to the assets folder. 137 | * So the next time the package is added, it will be extracted from the assets folder. 138 | * 139 | * If the package is already in the assets folder and the current version is not greater than the previous version, 140 | * the package will be extracted from the assets folder. 141 | * 142 | * @param packageId the package to add 143 | * @param destination the destination folder. If not provided, the package's default destination will be used. 144 | * @ param force if true, the package will be downloaded from GitHub, even if it already exists in the assets folder. 145 | * @returns 146 | */ 147 | export async function addPackage( 148 | packageId: PackageIds, 149 | destination?: string 150 | ): Promise { 151 | const pkg = packageMap.get(packageId); 152 | 153 | if (!pkg) { 154 | throw new Error(`Package ${packageId} not found`); 155 | } 156 | 157 | if (!destination) { 158 | destination = pkg.destination; 159 | } 160 | 161 | const archivedPackage = path.join(ASSETS_PATH, `${pkg.name}.zip`); 162 | 163 | // every new version the package will be downloaded from GitHub 164 | if (fs.existsSync(archivedPackage) && !shouldUpdate(pkg.name)) { 165 | await extractPackage(archivedPackage, destination); 166 | vscode.window.showInformationMessage(`NukeTools: Package added: ${pkg.name}`); 167 | return pkg; 168 | } 169 | 170 | let tagName = "v0.0.0"; 171 | 172 | const filterRelease = (release: GithubRelease) => { 173 | const isRelease = release.prerelease === false; 174 | 175 | if (isRelease && tagName < release.tag_name) { 176 | tagName = release.tag_name; 177 | } 178 | 179 | return isRelease; 180 | }; 181 | 182 | return downloadRelease("sisoe24", packageId, ASSETS_PATH, filterRelease, undefined, true) 183 | .then(async function () { 184 | await extractPackage(archivedPackage, destination as string); 185 | vscode.window.showInformationMessage(`NukeTools: Package updated: ${pkg.name}`); 186 | 187 | // update the package version in the log file 188 | const packages = JSON.parse(fs.readFileSync(ASSETS_LOG_PATH, "utf8")); 189 | packages["packages"]["current"][pkg.name] = tagName; 190 | fs.writeFileSync(ASSETS_LOG_PATH, JSON.stringify(packages, null, 4)); 191 | 192 | return pkg; 193 | }) 194 | .catch(function (err: { message: unknown }) { 195 | vscode.window.showWarningMessage( 196 | "NukeTools: Failed to download package from GitHub: " + err.message 197 | ); 198 | return null; 199 | }); 200 | } 201 | -------------------------------------------------------------------------------- /src/packages_fetch.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as https from "https"; 3 | import * as vscode from "vscode"; 4 | 5 | import { ASSETS_LOG_PATH } from "./constants"; 6 | 7 | interface GithubRelease { 8 | zipball_url?: string; 9 | tag_name: string; 10 | name: string; 11 | } 12 | 13 | 14 | function getLatestRelease(repo: string): Promise { 15 | return new Promise((resolve, reject) => { 16 | const options = { 17 | hostname: "api.github.com", 18 | path: `/repos/sisoe24/${repo}/releases/latest`, 19 | method: "GET", 20 | headers: { 21 | "User-Agent": "Node.js https module", 22 | }, 23 | }; 24 | 25 | const req = https.request(options, (res) => { 26 | let data = ""; 27 | 28 | res.on("data", (chunk) => { 29 | data += chunk; 30 | }); 31 | 32 | res.on("end", () => { 33 | resolve(JSON.parse(data)); 34 | }); 35 | }); 36 | 37 | req.on("error", (e) => { 38 | reject(e); 39 | }); 40 | 41 | req.end(); 42 | }); 43 | } 44 | 45 | type IncludedPackages = { 46 | current: { [key: string]: string }; 47 | lastest: { [key: string]: string }; 48 | }; 49 | 50 | type IncludedPackagesLog = { 51 | lastCheck: string; 52 | packages: IncludedPackages; 53 | }; 54 | 55 | /** 56 | * Fetch the latest release of a package from github api and save it to the log file. 57 | * 58 | * @see IncludedPackagesLog 59 | * @param packages a list of packages to fetch the latest release 60 | */ 61 | async function fetchLatestRelease(packages: IncludedPackages): Promise { 62 | 63 | const fetch = async () => { 64 | const versions: IncludedPackagesLog = { 65 | lastCheck: new Date().toISOString(), 66 | packages: { 67 | current: packages.current, 68 | lastest: {}, 69 | }, 70 | }; 71 | 72 | for (const key of Object.keys(packages.current)) { 73 | try { 74 | const releaseData = await getLatestRelease(key); 75 | versions["packages"]["lastest"][key] = releaseData["tag_name"]; 76 | } catch (err) { 77 | vscode.window.showErrorMessage(err as string); 78 | } 79 | } 80 | 81 | return versions; 82 | }; 83 | 84 | fetch().then((versions) => { 85 | fs.writeFileSync(ASSETS_LOG_PATH, JSON.stringify(versions, null, 4)); 86 | }); 87 | } 88 | 89 | const DAY = 24 * 60 * 60 * 1000; 90 | 91 | const T = { 92 | day: DAY, 93 | week: 7 * DAY, 94 | month: 30 * DAY, 95 | }; 96 | 97 | export function fetchPackagesLatestVersion(frequency: number = T.month): void { 98 | 99 | const logData = JSON.parse(fs.readFileSync(ASSETS_LOG_PATH, "utf8")) as IncludedPackagesLog; 100 | 101 | const lastUpdated = new Date(logData["lastCheck"]).getTime(); 102 | const now = new Date().getTime(); 103 | 104 | if (now - lastUpdated > frequency) { 105 | fetchLatestRelease(logData["packages"]); 106 | return; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/socket.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as os from "os"; 3 | import * as path from "path"; 4 | import * as vscode from "vscode"; 5 | 6 | import { Socket } from "net"; 7 | import { NUKE_DIR } from "./constants"; 8 | import { getConfig } from "./config"; 9 | 10 | const outputWindow = vscode.window.createOutputChannel("Nuke Tools"); 11 | 12 | /** 13 | * Get the value from the nukeserversocket configuration. 14 | * 15 | * @param value value to get from the configuration. 16 | * @param defaultValue default value to fallback in case property is undefined. 17 | * @returns 18 | */ 19 | export function getNssConfig(value: string, defaultValue: string): string { 20 | const nssConfigJSON = path.join(NUKE_DIR, "nukeserversocket.json"); 21 | if (fs.existsSync(nssConfigJSON)) { 22 | const fileContent = fs.readFileSync(nssConfigJSON, "utf-8"); 23 | return JSON.parse(fileContent)[value] || defaultValue; 24 | } 25 | 26 | // Legacy support for NukeServerSocket < 1.0.0 27 | const nssConfigIni = path.join(NUKE_DIR, "NukeServerSocket.ini"); 28 | if (fs.existsSync(nssConfigIni)) { 29 | const fileContent = fs.readFileSync(nssConfigIni, "utf-8"); 30 | const match = new RegExp(`${value}=(.+)`).exec(fileContent); 31 | if (match) { 32 | return match[1]; 33 | } 34 | } 35 | 36 | return defaultValue; 37 | } 38 | 39 | /** 40 | * Get the port address. 41 | * 42 | * Default port address will be 54321. If manual connection is enabled 43 | * value will be taken from the settings. 44 | * 45 | * @returns - port address 46 | */ 47 | export function getPort(): number { 48 | let port = getNssConfig("port", "54321"); 49 | 50 | const userConfig = getConfig("network.manualConnection"); 51 | if (userConfig.active) { 52 | port = userConfig.port; 53 | } 54 | 55 | return parseInt(port); 56 | } 57 | 58 | /** 59 | * Get the host address. 60 | * 61 | * Default host address will be local: 127.0.0.1. If manual connection is enabled 62 | * value will be taken from the settings. 63 | * 64 | * @returns - host address 65 | */ 66 | function getHost(): string { 67 | let host = "127.0.0.1"; 68 | 69 | const userConfig = getConfig("network.manualConnection"); 70 | if (userConfig.active) { 71 | host = userConfig.host; 72 | } 73 | 74 | return host; 75 | } 76 | 77 | /** 78 | * Get the network addresses. 79 | * 80 | * This will create a simple string: `host: "hostname" port: "port"` 81 | * 82 | * @returns - string with the network addresses information. 83 | */ 84 | export function getAddresses(): string { 85 | return `host: ${getHost()} port: ${getPort()}`; 86 | } 87 | 88 | /** 89 | * Write received data from the socket to the output window. 90 | * 91 | * @param data text data to write into the output window. 92 | * @param filePath path to the file that is being executed. 93 | * @param showDebug if true, the output window will not be cleared despite the settings. 94 | */ 95 | export function writeToOutputWindow(data: string, filePath: string): string { 96 | if (getConfig("clearPreviousOutput") && !getConfig("network.debug")) { 97 | outputWindow.clear(); 98 | } 99 | 100 | const msg = `> Executing: ${filePath}\n${data}`; 101 | 102 | outputWindow.appendLine(msg); 103 | outputWindow.show(true); 104 | 105 | return msg.replace(/\n/g, "\\n"); 106 | } 107 | 108 | /** 109 | * Write debug information to the output window. 110 | * 111 | * @param showDebug if true, will output debug information to the output window. 112 | * @param data text data to write into the output window. 113 | */ 114 | export function logDebugNetwork(data: string): string { 115 | if (!getConfig("network.debug")) { 116 | return ""; 117 | } 118 | 119 | const timestamp = new Date(); 120 | const msg = `[${timestamp.toISOString()}] - ${data}`; 121 | outputWindow.appendLine(msg); 122 | return msg; 123 | } 124 | 125 | /** 126 | * Send data over TCP network. 127 | * 128 | * @param host - host address for the connection. 129 | * @param port - port address for the connection. 130 | * @param text - Stringified text to sent as code to be executed inside Nuke. 131 | * @param timeout - time for the timeout connection. Defaults to 10000 ms (10sec). 132 | */ 133 | export async function sendData( 134 | host: string, 135 | port: number, 136 | text: string, 137 | timeout = 10000 138 | ): Promise<{ message: string; error: boolean; errorMessage: string }> { 139 | return new Promise((resolve, reject) => { 140 | const client = new Socket(); 141 | const status = { 142 | message: "", 143 | error: false, 144 | errorMessage: "", 145 | }; 146 | 147 | logDebugNetwork(`Try connecting to ${host}:${port}`); 148 | 149 | /** 150 | * Set connection timeout. 151 | * 152 | * Once emitted will close the socket with an error: 'connection timeout'. 153 | */ 154 | client.setTimeout(timeout, () => { 155 | logDebugNetwork("Connection timeout."); 156 | client.destroy(new Error("Connection timeout")); 157 | reject(status); 158 | }); 159 | 160 | try { 161 | /** 162 | * Initiate a connection on a given socket. 163 | * 164 | * If host is undefined, will fallback to localhost. 165 | */ 166 | client.connect(port, host, function () { 167 | logDebugNetwork("Connected."); 168 | client.write(text); 169 | }); 170 | } catch (error) { 171 | const msg = `Unknown exception. ${String(error)}`; 172 | logDebugNetwork(msg); 173 | client.destroy(new Error(msg)); 174 | status.errorMessage = msg; 175 | status.error = true; 176 | reject(status); 177 | } 178 | 179 | /** 180 | * Emitted when data is received. 181 | * 182 | * The argument data will be a Buffer or String. Encoding of data is set by socket.setEncoding(). 183 | */ 184 | client.on("data", function (data: string | Buffer) { 185 | const textData = data.toString(); 186 | 187 | const oneLineData = textData.replace(/\n/g, "\\n"); 188 | logDebugNetwork(`Received: "${oneLineData}"\n`); 189 | 190 | const filePath = JSON.parse(text)["file"]; 191 | writeToOutputWindow(textData, filePath); 192 | 193 | status.message = data.toString().trim(); 194 | resolve(status); 195 | client.end(); 196 | }); 197 | 198 | /** 199 | * Emitted after resolving the host name but before connecting. 200 | */ 201 | client.on( 202 | "lookup", 203 | function (error: Error | null, address: string, family: string, host: string) { 204 | logDebugNetwork( 205 | "Socket Lookup :: " + 206 | JSON.stringify({ address, family, host, error }, null, " ") 207 | ); 208 | 209 | if (error) { 210 | logDebugNetwork(`${error.message}`); 211 | status.errorMessage = error.message; 212 | reject(status); 213 | } 214 | } 215 | ); 216 | 217 | /** 218 | * Emitted when an error occurs. 219 | * 220 | * The 'close' event will be called directly following this event. 221 | */ 222 | client.on("error", function (error: Error) { 223 | const msg = ` 224 | Couldn't connect to nukeserversocket. Check the plugin and try again. 225 | If manual connection is enable, verify that the port and host address are correct. 226 | ${error.message}`; 227 | vscode.window.showErrorMessage(msg); 228 | 229 | status.errorMessage = "Connection refused"; 230 | status.error = true; 231 | reject(status); 232 | }); 233 | 234 | /** 235 | * Emitted when a socket is ready to be used. 236 | * 237 | * Triggered immediately after 'connect'. 238 | */ 239 | client.on("ready", function () { 240 | logDebugNetwork("Message ready."); 241 | }); 242 | 243 | /** 244 | * Emitted once the socket is fully closed. 245 | */ 246 | client.on("close", function (hadError: boolean) { 247 | logDebugNetwork(`Connection closed. Had Errors: ${hadError.toString()}`); 248 | }); 249 | 250 | /** 251 | * Emitted when the other end of the socket signals the end of transmission. 252 | */ 253 | client.on("end", function () { 254 | logDebugNetwork("Connection ended."); 255 | }); 256 | }); 257 | } 258 | 259 | /** 260 | * Prepare a debug message to send to the socket. 261 | * 262 | * Message contains some valid python code and will show the user information. 263 | * 264 | * @returns - data object 265 | */ 266 | export function prepareDebugMsg(): { text: string; file: string } { 267 | const random = () => Math.round(Math.random() * 10); 268 | const r1 = random(); 269 | const r2 = random(); 270 | 271 | const code = ` 272 | from __future__ import print_function 273 | print("Hostname: ${os.hostname() as string} User: ${os.userInfo()["username"] as string}") 274 | print("Connected to ${getAddresses()}") 275 | print("${r1} * ${r2} =", ${r1 * r2}) 276 | `; 277 | 278 | return { 279 | text: code.trim().replace(/\n/g, ";"), 280 | file: "tmp_file", 281 | }; 282 | } 283 | 284 | /** 285 | * Send a debug test message to the socket connection. 286 | */ 287 | export async function sendDebugMessage(): Promise<{ 288 | message: string; 289 | error: boolean; 290 | errorMessage: string; 291 | }> { 292 | return await sendData(getHost(), getPort(), JSON.stringify(prepareDebugMsg())); 293 | } 294 | 295 | /** 296 | * Send the current active file to the socket. 297 | * 298 | * The data to be sent over will the current active file name and its content. 299 | * Data will be wrapped inside a stringified object before being sent. 300 | * 301 | */ 302 | export async function sendMessage(): Promise< 303 | | Promise<{ 304 | message: string; 305 | error: boolean; 306 | errorMessage: string; 307 | }> 308 | | undefined 309 | > { 310 | const editor = vscode.window.activeTextEditor; 311 | if (!editor) { 312 | return; 313 | } 314 | 315 | // for some reason, the output window is considered as an active editor. 316 | if (editor.document.uri.scheme === "output") { 317 | vscode.window.showInformationMessage( 318 | "You currently have the Output window in focus. Return the focus on the text editor." 319 | ); 320 | return; 321 | } 322 | 323 | const data = { 324 | file: editor.document.fileName, 325 | text: editor.document.getText(editor.selection) || editor.document.getText(), 326 | }; 327 | 328 | return await sendData(getHost(), getPort(), JSON.stringify(data)); 329 | } 330 | 331 | /** 332 | * Send an arbitrary command to the socket. 333 | * 334 | * @param command a stringified command to be sent to the socket. 335 | * @returns 336 | */ 337 | export function sendCommand(command: string): Promise<{ 338 | message: string; 339 | error: boolean; 340 | errorMessage: string; 341 | }> { 342 | return sendData(getHost(), getPort(), command); 343 | } 344 | 345 | /** 346 | * Check if the socket is connected by sending a simple command. 347 | * 348 | * @returns A promise that resolves to true if the socket is connected, false otherwise. 349 | */ 350 | export function isConnected(): Promise { 351 | return sendCommand( 352 | JSON.stringify({ 353 | file: "tmp_file", 354 | text: "print('test')", 355 | formatText: "0", 356 | }) 357 | ).then( 358 | () => true, 359 | () => false 360 | ); 361 | } 362 | -------------------------------------------------------------------------------- /src/stubs.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import { PackageIds, addPackage } from "./packages"; 4 | 5 | /** 6 | * Determine the configuration for extraPaths based on the current Python server. 7 | * 8 | * - Pylance: `python.analysis` 9 | * - Jedi: `python.autoComplete` 10 | * 11 | * @returns the settings name for the extra paths 12 | */ 13 | export function getPythonLspKey(): string { 14 | // when pythonServer is Default, we assume that Pylance is installed. 15 | const pythonServer = vscode.workspace.getConfiguration("python").get("languageServer"); 16 | 17 | if (pythonServer === "Jedi") { 18 | return "python.autoComplete"; 19 | } 20 | 21 | return "python.analysis"; 22 | } 23 | 24 | /** 25 | * Update the python.analysis.extraPaths setting with the stubs path. 26 | * 27 | * @param nukeToolsStubsPath - path to the stubs directory 28 | * 29 | */ 30 | function updatePythonExtraPaths(nukeToolsStubsPath: string) { 31 | const config = vscode.workspace.getConfiguration(getPythonLspKey(), null); 32 | const extraPaths = config.get("extraPaths") as string[]; 33 | 34 | if (!extraPaths.includes(nukeToolsStubsPath)) { 35 | extraPaths.push(nukeToolsStubsPath); 36 | config.update("extraPaths", extraPaths, vscode.ConfigurationTarget.Global); 37 | } 38 | } 39 | 40 | /** 41 | * Add the stubs path to the python.analysis.extraPaths setting. 42 | */ 43 | export async function addStubs(): Promise { 44 | if (!vscode.extensions.getExtension("ms-python.python")) { 45 | vscode.window.showErrorMessage( 46 | "Python extension is not installed. Could not add stubs path." 47 | ); 48 | return; 49 | } 50 | 51 | const pkg = await addPackage(PackageIds.nukePythonStubs); 52 | if (!pkg) { 53 | vscode.window.showErrorMessage("Could not add stubs path."); 54 | return; 55 | } 56 | 57 | updatePythonExtraPaths(pkg.destination); 58 | 59 | vscode.window.showInformationMessage("Python stubs added."); 60 | } 61 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 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/version.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | export class Version { 4 | private static _extPreviousVersion: string; 5 | private static _extCurrentVersion: string; 6 | 7 | /** 8 | * Get the previous version of the extension. 9 | */ 10 | public static get extPreviousVersion(): string { 11 | return Version._extPreviousVersion; 12 | } 13 | 14 | /** 15 | * Get the current version of the extension. 16 | */ 17 | public static get extCurrentVersion(): string { 18 | return Version._extCurrentVersion; 19 | } 20 | 21 | public static update(cxt: vscode.ExtensionContext): void { 22 | Version._extPreviousVersion = 23 | (cxt.globalState.get(cxt.extension.id + ".version") as string) || "0.0.0"; 24 | Version._extCurrentVersion = cxt.extension.packageJSON.version; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "include": [ 18 | "src/**/*.ts", 19 | "index.d.ts" 20 | ], 21 | "exclude": [ 22 | "node_modules", 23 | ".vscode-test", 24 | "other", 25 | "include" 26 | ] 27 | } 28 | --------------------------------------------------------------------------------