├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── copyright.txt ├── icon.png ├── img ├── demo1.gif ├── demo2.gif ├── demo3.gif ├── demo4.gif └── screenshot1.png ├── package-lock.json ├── package.json ├── src ├── content.ts ├── contracts.ts ├── controller.ts ├── extension.ts ├── globals.ts ├── helpers.ts ├── quick.ts ├── resources.ts ├── urls.ts └── workspace.ts ├── test ├── extension.test.ts └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | /pushall.sh 4 | /typedoc.cmd 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ], 14 | "preLaunchTask": "npm" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], 25 | "preLaunchTask": "npm" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "deploy":{ 4 | "runBuildTaskOnStartup": true 5 | }, 6 | 7 | "files.exclude": { 8 | "out": false // set this to true to hide the "out" folder with the compiled JS files 9 | }, 10 | "search.exclude": { 11 | "out": true // set this to false to include "out" folder in search results 12 | }, 13 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 14 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isWatching": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json 9 | /pushall.sh 10 | /typedoc.cmd 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [DEPRECATED] Change Log (vs-script-commands) 2 | 3 | ## 8.0.0 (January 9th, 2019; FINAL VERSION => DEPRECATED) 4 | 5 | * **the extension is now marked as DEPRECATED ... it is RECOMMENDED to use [vscode-powertools](https://marketplace.visualstudio.com/items?itemName=ego-digital.vscode-powertools) by [e.GO Digital](https://github.com/egodigital)** 6 | * if you have suggestions and other kind of issues for that new extension, feel free to [open an issue here](https://github.com/egodigital/vscode-powertools/issues) 7 | 8 | ## 7.0.0 (January 6th, 2017; npm packages) 9 | 10 | * updated [npm packages](https://www.npmjs.com): 11 | * [marked](https://www.npmjs.com/package/marked) to `"^0.3.9` 12 | * [moment](https://www.npmjs.com/package/moment) to `"^2.20.1` 13 | * [public-ip](https://www.npmjs.com/package/public-ip) to `"^2.4.0` 14 | 15 | ## 6.1.0 (November 20th, 2017; auto select workspace) 16 | 17 | * added `autoSelectWorkspace` setting, which can select the current workspace by active text editor automatically 18 | * added `onActiveEditorChanged` event setting for commands 19 | 20 | ## 6.0.0 (November 19th, 2017; finished multi root support) 21 | 22 | * added `extension.scriptCommands.selectWorkspace`, which can change between workspaces now 23 | 24 | ## 5.0.2 (October 14th, 2017; multi root support) 25 | 26 | * started to refactor to new, upcoming [Multi Root Workspace API](https://github.com/Microsoft/vscode/wiki/Extension-Authoring:-Adopting-Multi-Root-Workspace-APIs) 27 | 28 | ## 4.14.0 (May 7th, 2017; functions and variables) 29 | 30 | * `$addValue`, `$DELETE`, `$GET`, `$HEAD`, `$htmlDecode`, `$openInTab`, `$OPTIONS`, `$PATCH`, `$POST`, `$PUT`, `$readJSONFrom`, `$REQUEST`, `$sendJSONTo`, `$workflow`, `$xmlDecode`, `$htmlEncode` functions for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 31 | * `$openHtml` can handle async Promise results now 32 | * bugfixes 33 | 34 | ## 4.13.0 (May 6th, 2017; functions and variables) 35 | 36 | * added `$appendFile()`, `$executeForState()`, `$ip()`, `$push()`, `$receiveFrom()`, `$saveJSON`, `$stopReceiveFrom()` and `$sendTo` functions for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 37 | * added `$events`, `$globalEvents`, `$lastResult`, `$previousValue`, `$nextValue` and `$values` variables for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 38 | * added `saveResultsToState` [quick execution setting](https://github.com/mkloubert/vs-script-commands#quick-execution-) 39 | * added `globalEvents` property to [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) interface 40 | 41 | ## 4.12.0 (May 2nd, 2017; REST API and cron jobs) 42 | 43 | * added `$startApi()` and `$stopApi()` functions for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-), that can interact with extensions like [vs-rest-api](https://github.com/mkloubert/vs-rest-api) 44 | * added `$getCronJobs()`, `$restartCronJobs()`, `$startCronJobs()` and `$stopCronJobs()` functions for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-), that can interact with extensions like [vs-cron](https://github.com/mkloubert/vs-cron) 45 | 46 | ## 4.11.0 (May 2nd, 2017; quick execution) 47 | 48 | * added `$clearHistory`, `$history`, `$removeFromHistory` and `$saveToHistory` functions 49 | * added `$doNotShowResult` [symbol](https://www.typescriptlang.org/docs/handbook/symbols.html) 50 | * added `saveToGlobalHistory` and `saveToHistory` settings 51 | 52 | ## 4.10.0 (May 2nd, 2017; quick execution functions) 53 | 54 | * added `$password()` and `$randomString()` functions for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 55 | 56 | ## 4.9.0 (May 2nd, 2017; quick execution functions) 57 | 58 | * added `$rand()` function for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 59 | 60 | ## 4.8.0 (May 2nd, 2017; quick execution functions) 61 | 62 | * added `$` and `$readJSON()` and `$readString()` functions for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 63 | 64 | ## 4.7.0 (May 2nd, 2017; hashes and UUIDs) 65 | 66 | * added `$guid()`, `$hash()`, `$md5()`, `$sha1()`, `$sha256()` and `$uuid()` functions for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 67 | 68 | ## 4.6.0 (May 1st, 2017; hex view of binary files) 69 | 70 | * [Buffer](https://nodejs.org/api/buffer.html) results are displayed in "hex view" now, when displaying in tab 71 | * added `$toHexView()`, `$disableHexView()` functions and `$config` variable for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 72 | * added `toHexView()` method for [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) interface 73 | 74 | ## 4.5.0 (May 1st, 2017; find files) 75 | 76 | * added `$findFiles()` function for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 77 | * added `findFiles()` method for [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) interface 78 | 79 | ## 4.4.0 (May 1st, 2017; added support for Markdown and HTML parsing) 80 | 81 | * added `$fromMarkdown()`, `$htmlEncode()` and `$log()` functions for [quick executions](https://github.com/mkloubert/vs-script-commands#quick-execution-) 82 | * added `fromMarkdown()` and `htmlEncode()` methods for [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) interface 83 | 84 | ## 4.3.0 (May 1st, 2017; quick JavaScript execution - $output) 85 | 86 | * added `$output` variable for [Quick execution](https://github.com/mkloubert/vs-script-commands#quick-execution-) command 87 | 88 | ## 4.2.0 (April 29th, 2017; quick JavaScript execution - $global) 89 | 90 | * added `$globals` variable for [Quick execution](https://github.com/mkloubert/vs-script-commands#quick-execution-) command 91 | 92 | ## 4.1.0 (April 29th, 2017; quick JavaScript execution - $mkdir) 93 | 94 | * added `$mkdir` function for [Quick execution](https://github.com/mkloubert/vs-script-commands#quick-execution-) command 95 | 96 | ## 4.0.0 (April 29th, 2017; quick JavaScript execution) 97 | 98 | * added `Script commands: Quick execution` command that can [execute JavaScript code quickly](https://github.com/mkloubert/vs-script-commands#quick-execution-) ... enter `$help` as first action to get information about available functions and variables 99 | 100 | ## 3.0.0 (April 21st, 2017; execute command before save document) 101 | 102 | * added `onWillSave` setting for commands, which indicates to invoke commands if a file is going to be saved 103 | 104 | ## 2.0.1 (April 11th, 2017; improved execution of scripts) 105 | 106 | * the behavior of executing scripts has been improved ... if you come from version 1.x, have a look at the [wiki](https://github.com/mkloubert/vs-script-commands/wiki#since-version-2x-) first 107 | * if you have problems, you can open an [issue](https://github.com/mkloubert/vs-script-commands/issues) and/or download a version 1.x branch from [here](https://github.com/mkloubert/vs-script-commands/releases) 108 | 109 | ## 1.12.0 (April 10th, 2017; REST API) 110 | 111 | * added `startApi()` and `stopApi()` methods to [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) interface, which make use of commands provided by extensions like [vs-rest-api](https://github.com/mkloubert/vs-rest-api) 112 | 113 | ## 1.11.0 (April 10th, 2017; cron jobs) 114 | 115 | * added `getCronJobs()`, `restartCronJobs()`, `startCronJobs()` and `stopCronJobs()` methods to [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) interface, which make use of commands provided by extensions like [vs-cron](https://github.com/mkloubert/vs-cron) 116 | * added `others` property to [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) interface 117 | 118 | ## 1.10.0 (February 20th, 2017; deploy files) 119 | 120 | * added [deploy](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html#deploy) method to [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) which make use of `extension.deploy.filesTo` command, provided by [vs-deploy](https://github.com/mkloubert/vs-deploy) extension 121 | 122 | ## 1.9.0 (February 20th, 2017; output channel) 123 | 124 | * added [outputChannel](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html#outputChannel) property to [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) that gets the [OutputChannel](https://code.visualstudio.com/Docs/extensionAPI/vscode-api#OutputChannel) instance of that extension 125 | 126 | ## 1.8.0 (February 20th, 2017; log() method) 127 | 128 | * added [log()](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html#log) method to [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) that writes log messages to output window / channel 129 | 130 | ## 1.7.0 (February 20th, 2017; open HTML documents) 131 | 132 | * added [openHtml()](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html#openhtml) method to [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) which can open HTML documents in a new tab 133 | 134 | ## 1.6.0 (February 18th, 2017; command events) 135 | 136 | * added [events](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html#events) property, that can handle events for all commands created by that extension 137 | * added [cached](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommand.html#cached) property 138 | 139 | ## 1.5.0 (February 18th, 2017; extension context and sharing data between two executions) 140 | 141 | * added [previousValue](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html#previousvalue) and [nextValue](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html#nextvalue) properties that can share data between two executions 142 | * added [extension](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html#extension) property that returns the [context of this extension](https://code.visualstudio.com/Docs/extensionAPI/vscode-api#_a-nameextensioncontextaspan-classcodeitem-id988extensioncontextspan) 143 | 144 | ## 1.4.0 (January 23rd, 2017; commandState) 145 | 146 | * added `commandState` to [ScriptCommand](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommand.html) 147 | 148 | ## 1.2.0 (January 23rd, 2017; extended ScriptCommandExecutorArguments) 149 | 150 | * added `arguments` and `suppressArguments` to [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) 151 | 152 | ## 1.1.0 (January 23rd, 2017; extended API) 153 | 154 | * extended [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) interface 155 | 156 | ## 1.0.0 (January 23rd, 2017; first release) 157 | 158 | * first official release 159 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marcel Kloubert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [DEPRECATED] vs-script-commands 2 | 3 | Adds additional commands to [Visual Studio Code](https://code.visualstudio.com/) (VS Code) that uses scripts (JavaScript) for execution. 4 | 5 |
6 | 7 | **The extension is now marked as DEPRECATED ... it is RECOMMENDED to use [vscode-powertools](https://marketplace.visualstudio.com/items?itemName=ego-digital.vscode-powertools) by [e.GO Digital](https://github.com/egodigital).** 8 | 9 | If you have suggestions and other kind of issues for that new extension, feel free to [open an issue here](https://github.com/egodigital/vscode-powertools/issues). 10 | 11 |
12 | 13 | ## Table of contents 14 | 15 | 1. [Install](#install-) 16 | 2. [How to use](#how-to-use-) 17 | * [Changes](#changes-) 18 | * [Settings](#settings-) 19 | * [Commands](#commands-) 20 | * [Key bindinds](#key-bindinds-) 21 | * [Invoke manually](#invoke-manually-) 22 | * [Status bar buttons](#status-bar-buttons-) 23 | * [Quick execution](#quick-execution-) 24 | 3. [Documentation](#documentation-) 25 | 26 | ## Install [[↑](#table-of-contents)] 27 | 28 | Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter: 29 | 30 | ```bash 31 | ext install vs-script-commands 32 | ``` 33 | 34 | Or search for things like `vs-script-commands` in your editor: 35 | 36 | ![Screenshot VSCode Extension search](https://raw.githubusercontent.com/mkloubert/vs-script-commands/master/img/screenshot1.png) 37 | 38 | ## How to use [[↑](#table-of-contents)] 39 | 40 | ### Changes [[↑](#how-to-use-)] 41 | 42 | * if you come from version 1.x, you should take a look at the [wiki](https://github.com/mkloubert/vs-script-commands/wiki#since-version-2x-) first ... if you have problems, you can open an [issue](https://github.com/mkloubert/vs-script-commands/issues) and/or download a version 1.x branch from [here](https://github.com/mkloubert/vs-script-commands/releases) 43 | 44 | ### Settings [[↑](#how-to-use-)] 45 | 46 | Open (or create) your `settings.json` in your `.vscode` subfolder of your workspace. 47 | 48 | Add a `script.commands` section: 49 | 50 | ```json 51 | { 52 | "script.commands": { 53 | } 54 | } 55 | ``` 56 | 57 | | Name | Description | 58 | | ---- | --------- | 59 | | `autoSelectWorkspace` | Select the workspace by active text editor automatically or not. Default `(false)` | 60 | | `commands` | One or more [commands](#commands-) to register. | 61 | | `disableNewVersionPopups` | Disables the display of popups that reports for a new version of that extension. Default `(false)` | 62 | | `globals` | Global data available for ALL commands defined by that extension. | 63 | | `quick` | Settings for [quick execution](#quick-execution-) feature. | 64 | | `showOutput` | Open output on startup or not. Default `(true)` | 65 | | `showInternalVSCommands` | Show internal Visual Studio Code commands in GUI or not. Default `(false)` | 66 | 67 | #### Commands [[↑](#settings-)] 68 | 69 | Define one or more command, by defining its `id` and the script file, which should be executed: 70 | 71 | ```json 72 | { 73 | "script.commands": { 74 | "commands": [ 75 | { 76 | "id": "mkloubert.mycommand", 77 | "script": "./my-command.js" 78 | } 79 | ] 80 | } 81 | } 82 | ``` 83 | 84 | The `./my-command.js` must have a public / exported `execute()` function: 85 | 86 | ```javascript 87 | exports.execute = function (args) { 88 | // access VSCode API (s. https://code.visualstudio.com/Docs/extensionAPI/vscode-api) 89 | var vscode = require('vscode'); 90 | 91 | // access any NodeJS API provided by VSCode (s. https://nodejs.org/api/) 92 | var path = require('path'); 93 | 94 | // import an own module 95 | var myModule = require('./myModule'); 96 | 97 | // use the functions and classes provided by this extension 98 | // s. https://mkloubert.github.io/vs-script-commands/modules/_helpers_.html 99 | var helpers = args.require('./helpers'); 100 | 101 | // access a module that is part of the extentsion 102 | // s. https://github.com/mkloubert/vs-script-commands/blob/master/package.json 103 | var moment = args.require('moment'); 104 | 105 | // access the global data from the settings 106 | var globals = args.globals; 107 | 108 | // access the data of the entry from the settings 109 | var opts = args.options; 110 | 111 | // share / store data (while current session)... 112 | // ... for this script 113 | args.commandState = new Date(); 114 | // ... with other scripts 115 | args.globalState['myCommand'] = new Date(); 116 | 117 | // access permanent data storages 118 | // s. https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/common/memento.ts 119 | var myAppWideValue = args.extension.globalState.get('myAppValue'); // app wide 120 | args.extension.workspaceState.update('myWorkspaceValue', 'New workspace wide value'); // workspace wide 121 | 122 | // share data between two executions 123 | var prevVal = args.previousValue; // data from the previous execution 124 | args.nextValue = 'This is a value only for the next execution'; // data for the next execution 125 | 126 | // registers for a one-time event 127 | args.events.once('myCommandEvent', function(v) { 128 | // 'v' should be 'William Henry Gates' 129 | // if upcoming 'args.events.emit()' is called 130 | args.log("From 'myCommandEvent': " + v); 131 | }); 132 | 133 | // emit 'myCommandEvent' event (s. above) 134 | args.events.emit('myCommandEvent', 'William Henry Gates'); 135 | 136 | // logs a message to output window / channel 137 | args.log('A log message'); 138 | 139 | // write to output channel of that extension 140 | // s. https://code.visualstudio.com/Docs/extensionAPI/vscode-api#OutputChannel 141 | args.outputChannel.appendLine('A message for the output channel.'); 142 | 143 | 144 | var scriptFile = path.basename(__filename); 145 | 146 | // open HTML document in new tab (for reports e.g.) 147 | args.openHtml('Hello from my extension: ' + scriptFile + '', 'My HTML document').then(function() { 148 | // HTML opened 149 | }, function(err) { 150 | // opening HTML document failed 151 | }); 152 | 153 | // deploys 'index.html' to 'My SFTP server' 154 | // s. https://github.com/mkloubert/vs-deploy 155 | args.deploy(['./index.html'], ['My SFTP server']).then(function() { 156 | // file deployed 157 | }, function(err) { 158 | // deployment failed 159 | }); 160 | 161 | vscode.window.showInformationMessage('Hello from my extension: ' + scriptFile); 162 | 163 | // you also can return a Promise 164 | // if your command is executed async 165 | return 666; 166 | } 167 | ``` 168 | 169 | The `args` parameter uses the [ScriptCommandExecutorArguments](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html) interface. 170 | 171 | You can now execute the command by anything that uses the [Visual Studio Code API](https://code.visualstudio.com/docs/extensionAPI/vscode-api#_commands): 172 | 173 | ```javascript 174 | var vscode = require('vscode'); 175 | 176 | vscode.commands.executeCommand('mkloubert.mycommand').then(function(result) { 177 | // if we look at the upper example: 178 | // this should be: 666 179 | }, function(err) { 180 | // handle an error 181 | }); 182 | ``` 183 | 184 | A command entry provides the following properties: 185 | 186 | | Name | Description | 187 | | ---- | --------- | 188 | | `arguments` | One or more arguments for the callbacks. | 189 | | `askForArgument` | Defines if the GUI asks for an argument when invoke manually or not. Default `(false)` | 190 | | `async` | Invokes command async or not. Default `(true)` | 191 | | `button` | Settings for optional [button](#status-bar-buttons-) in the status bar. | 192 | | `cached` | Cache script or not. Default `(false)` | 193 | | `commandState` | The initial value for [commandState](https://mkloubert.github.io/vs-script-commands/interfaces/_contracts_.scriptcommandexecutorarguments.html#commandstate) property. Default `{}` | 194 | | `continueOnError` | Continue on error or cancel. Default `(true)` | 195 | | `description` | The description for the command. | 196 | | `displayName` | The custom display name. | 197 | | `id` | The ID of the command. | 198 | | `onActiveEditorChanged` | Is invoked when the active text editor has been changed. Default `(false)` | 199 | | `onClose` | Executes the command on VSCode closes or not. Default `(false)` | 200 | | `onConfigChanged` | Is invoked after `settings.json` has been changed. Default `(false)` | 201 | | `onEditorChanged` | Is invoked after a text editor changed. Default `(false)` | 202 | | `onFileChanged` | Is invoked when a file has been changed. Default `(false)` | 203 | | `onFileClosed` | Is invoked when a file has been closed. Default `(false)` | 204 | | `onFileDeleted` | Is invoked when a file has been deleted. Default `(false)` | 205 | | `onFileOpened` | Is invoked when a file has been opened. Default `(false)` | 206 | | `onNewFile` | Is invoked when a file has been created. Default `(false)` | 207 | | `onSaved` | Is invoked when a file has been saved. Default `(false)` | 208 | | `onStartup` | Executes the command on startup or not. Default `(false)` | 209 | | `onWillSave` | Is invoked when a file is going to be saved. Default `(false)` | 210 | | `options` | Additional data for the execution. | 211 | | `script` | The path to the script to execute. IF YOU USE A RELATIVE PATH: The path is relative to your workspace. | 212 | | `sortOrder` | The sort order (for the GUI). Default `0` | 213 | | `suppressArguments` | Supress own arguments of the extension or not. Default `(false)` | 214 | 215 | ### Key bindinds [[↑](#how-to-use-)] 216 | 217 | After defining one or more commands, you can open your [keybindings.json](https://code.visualstudio.com/docs/getstarted/keybindings#_advanced-customization) file and set shortcuts for them, by selecting `File > Preferences > Keyboard Shortcuts` (`Code > Preferences > Keyboard Shortcuts` on Mac) in your editor: 218 | 219 | ![Demo Key bindinds](https://raw.githubusercontent.com/mkloubert/vs-script-commands/master/img/demo1.gif) 220 | 221 | ### Invoke manually [[↑](#how-to-use-)] 222 | 223 | Press `F1` to open the list of commands and enter one of the following commands: 224 | 225 | ![Demo Invoke manually](https://raw.githubusercontent.com/mkloubert/vs-script-commands/master/img/demo2.gif) 226 | 227 | | Name | Description | Command | 228 | | ---- | ---- | --------- | 229 | | `Script commands: Execute command` | Executes a command defined by that extension. | `extension.scriptCommands.execute` | 230 | | `Script commands: Execute VSCode command` | Executes another command that is available in VSCode. | `extension.scriptCommands.executeVSCode` | 231 | | `Script commands: Quick execution` | Executes a JavaScript expression quickly. | `extension.scriptCommands.quickExecution` | 232 | | `Script commands: Select workspace` | Changes the current workspace, s. [Multi-root Workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces). | `extension.scriptCommands.selectWorkspace` | 233 | 234 | ### Status bar buttons [[↑](#how-to-use-)] 235 | 236 | You can activate buttons for your commands in the status bar, by defining the `button` property: 237 | 238 | ```json 239 | { 240 | "script.commands": { 241 | "commands": [ 242 | { 243 | "id": "mkloubert.mycommand", 244 | "script": "./my-command.js", 245 | 246 | "button": { 247 | "text": "My command", 248 | "tooltip": "This is a tooltip for my command" 249 | } 250 | } 251 | ] 252 | } 253 | } 254 | ``` 255 | 256 | ![Demo Status bar buttons](https://raw.githubusercontent.com/mkloubert/vs-script-commands/master/img/demo3.gif) 257 | 258 | | Name | Description | 259 | | ---- | --------- | 260 | | `color` | The custom (text) color for the button. | 261 | | `isRight` | Set button on the right side or not. Default `(false)` | 262 | | `priority` | The custom priority. | 263 | | `show` | Show button on startup or not. Default `(true)` | 264 | | `text` | The caption for the button. | 265 | | `tooltip` | The tooltip for the button. | 266 | 267 | ### Quick execution [[↑](#how-to-use-)] 268 | 269 | Press `F1` to open the list of commands and select `Script commands: Quick execution` to execute any JavaScript expression (execute `$help` to open a help tab which lists all available features, like functions and variables): 270 | 271 | ![Demo Quick execution](https://raw.githubusercontent.com/mkloubert/vs-script-commands/master/img/demo4.gif) 272 | 273 | You can define custom settings for the feature: 274 | 275 | ```json 276 | { 277 | "script.commands": { 278 | "quick": { 279 | "noResultInfo": false, 280 | "showResultInTab": true, 281 | 282 | "state": { 283 | "MK": 23979, 284 | "TM": "1979-09-05 23:09:19.079" 285 | } 286 | } 287 | } 288 | } 289 | ``` 290 | 291 | | Name | Description | 292 | | ---- | --------- | 293 | | `cwd` | The initial current directory for the executions. | 294 | | `disableHexView` | Do not show binary data in 'hex view'. Default: `(false)` | 295 | | `noResultInfo` | Do not show results of executions. Default: `(false)` | 296 | | `saveResultsToState` | Save all results to `$state` variable or not. Default: `(false)` | 297 | | `showResultInTab` | Show results in tab instead of a popup or not. Default: `(false)` | 298 | | `saveToGlobalHistory` | Save entries, that are stored automatically, to global history instead to workspace history. Default: `(false)` | 299 | | `saveToHistory` | Automatically save entries to history or not. Default: `(false)` | 300 | | `state` | The initial state value. | 301 | 302 | ## Documentation [[↑](#table-of-contents)] 303 | 304 | The full API documentation can be found [here](https://mkloubert.github.io/vs-script-commands/). 305 | -------------------------------------------------------------------------------- /copyright.txt: -------------------------------------------------------------------------------- 1 | Icons: 2 | - /icon.png (Vecteezy; https://www.iconfinder.com/Vecteezy; License: Creative Commons (Attribution-Share Alike 3.0 Unported)) 3 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-script-commands/38b37c1b3c2af748f14a0e1a34ebeaf1a13308c8/icon.png -------------------------------------------------------------------------------- /img/demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-script-commands/38b37c1b3c2af748f14a0e1a34ebeaf1a13308c8/img/demo1.gif -------------------------------------------------------------------------------- /img/demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-script-commands/38b37c1b3c2af748f14a0e1a34ebeaf1a13308c8/img/demo2.gif -------------------------------------------------------------------------------- /img/demo3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-script-commands/38b37c1b3c2af748f14a0e1a34ebeaf1a13308c8/img/demo3.gif -------------------------------------------------------------------------------- /img/demo4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-script-commands/38b37c1b3c2af748f14a0e1a34ebeaf1a13308c8/img/demo4.gif -------------------------------------------------------------------------------- /img/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-script-commands/38b37c1b3c2af748f14a0e1a34ebeaf1a13308c8/img/screenshot1.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vs-script-commands", 3 | "displayName": "Script Commands", 4 | "description": "Adds additional commands to Visual Studio Code that uses scripts (JavaScript) for execution.", 5 | "version": "8.0.0", 6 | "publisher": "mkloubert", 7 | "engines": { 8 | "vscode": "^1.30.0" 9 | }, 10 | "license": "MIT", 11 | "categories": [ 12 | "Other" 13 | ], 14 | "keywords": [ 15 | "Custom", 16 | "Commands", 17 | "Scripts", 18 | "JavaScript", 19 | "NodeJS" 20 | ], 21 | "activationEvents": [ 22 | "*" 23 | ], 24 | "main": "./out/src/extension", 25 | "contributes": { 26 | "commands": [ 27 | { 28 | "command": "extension.scriptCommands.execute", 29 | "title": "Execute command", 30 | "category": "Script commands" 31 | }, 32 | { 33 | "command": "extension.scriptCommands.executeVSCode", 34 | "title": "Execute VSCode command", 35 | "category": "Script commands" 36 | }, 37 | { 38 | "command": "extension.scriptCommands.quickExecution", 39 | "title": "Quick execution", 40 | "category": "Script commands" 41 | }, 42 | { 43 | "command": "extension.scriptCommands.selectWorkspace", 44 | "title": "Select workspace", 45 | "category": "Script commands" 46 | } 47 | ], 48 | "configuration": { 49 | "properties": { 50 | "script.commands": { 51 | "type": "object", 52 | "scope": "resource", 53 | "properties": { 54 | "autoSelectWorkspace": { 55 | "type": "boolean", 56 | "description": "Select the workspace by active text editor automatically or not.", 57 | "default": false 58 | }, 59 | "commands": { 60 | "description": "One or more command to register.", 61 | "oneOf": [ 62 | { 63 | "type": "object", 64 | "description": "The command to register.", 65 | "properties": { 66 | "arguments": { 67 | "type": "array", 68 | "description": "One or more arguments for the callbacks." 69 | }, 70 | "askForArgument": { 71 | "type": "boolean", 72 | "description": "Defines if the GUI asks for an argument when invoke manually or not.", 73 | "default": false 74 | }, 75 | "async": { 76 | "type": "boolean", 77 | "description": "Invokes command async or not.", 78 | "default": true 79 | }, 80 | "button": { 81 | "description": "Settings for optional button in the status bar.", 82 | "type": "object", 83 | "properties": { 84 | "color": { 85 | "type": "string", 86 | "description": "The custom (text) color for the button.", 87 | "default": "#ffffff" 88 | }, 89 | "isRight": { 90 | "type": "boolean", 91 | "description": "Set button on the right side or not.", 92 | "default": false 93 | }, 94 | "priority": { 95 | "type": "number", 96 | "description": "The custom priority.", 97 | "default": 0 98 | }, 99 | "show": { 100 | "type": "boolean", 101 | "description": "Show button on startup or not.", 102 | "default": true 103 | }, 104 | "text": { 105 | "type": "string", 106 | "description": "The caption for the button." 107 | }, 108 | "tooltip": { 109 | "type": "string", 110 | "description": "The tooltip for the button." 111 | } 112 | } 113 | }, 114 | "cached": { 115 | "type": "boolean", 116 | "description": "Cache script or not.", 117 | "default": false 118 | }, 119 | "continueOnError": { 120 | "type": "boolean", 121 | "description": "Continue on error or cancel.", 122 | "default": true 123 | }, 124 | "commandState": { 125 | "description": "The initial value for ScriptCommandExecutorArguments.commandState property.", 126 | "default": {} 127 | }, 128 | "description": { 129 | "type": "string", 130 | "description": "The description for the command." 131 | }, 132 | "displayName": { 133 | "type": "string", 134 | "description": "The custom display name." 135 | }, 136 | "id": { 137 | "type": "string", 138 | "description": "The ID of the command." 139 | }, 140 | "onActiveEditorChanged": { 141 | "type": "boolean", 142 | "description": "Is invoked when the active text editor has been changed.", 143 | "default": false 144 | }, 145 | "onClose": { 146 | "type": "boolean", 147 | "description": "Executes the command on close or not.", 148 | "default": false 149 | }, 150 | "onConfigChanged": { 151 | "type": "boolean", 152 | "description": "Is invoked after settings.json has been changed.", 153 | "default": false 154 | }, 155 | "onEditorChanged": { 156 | "type": "boolean", 157 | "description": "Is invoked after a text editor changed.", 158 | "default": false 159 | }, 160 | "onFileChanged": { 161 | "type": "boolean", 162 | "description": "Is invoked when a file has been changed.", 163 | "default": false 164 | }, 165 | "onFileClosed": { 166 | "type": "boolean", 167 | "description": "Is invoked when a file has been closed.", 168 | "default": false 169 | }, 170 | "onFileDeleted": { 171 | "type": "boolean", 172 | "description": "Is invoked when a file has been deleted.", 173 | "default": false 174 | }, 175 | "onFileOpened": { 176 | "type": "boolean", 177 | "description": "Is invoked when a file has been opened.", 178 | "default": false 179 | }, 180 | "onNewFile": { 181 | "type": "boolean", 182 | "description": "Is invoked when a file has been created.", 183 | "default": false 184 | }, 185 | "onSaved": { 186 | "type": "boolean", 187 | "description": "Is invoked when a file has been saved.", 188 | "default": false 189 | }, 190 | "onStartup": { 191 | "type": "boolean", 192 | "description": "Executes the command on startup or not.", 193 | "default": false 194 | }, 195 | "onWillSave": { 196 | "type": "boolean", 197 | "description": "Is invoked when a file is going to be saved.", 198 | "default": false 199 | }, 200 | "options": { 201 | "description": "Additional data for the execution." 202 | }, 203 | "script": { 204 | "type": "string", 205 | "description": "The path to the script to execute.", 206 | "default": "./my-command.js" 207 | }, 208 | "sortOrder": { 209 | "type": "number", 210 | "description": "The sort order.", 211 | "default": 0 212 | }, 213 | "suppressArguments": { 214 | "type": "boolean", 215 | "description": "Supress own arguments of the extension or not.", 216 | "default": false 217 | } 218 | }, 219 | "required": [ 220 | "id", 221 | "script" 222 | ] 223 | }, 224 | { 225 | "type": "array", 226 | "description": "The list of commands to register.", 227 | "items": { 228 | "type": "object", 229 | "properties": { 230 | "arguments": { 231 | "type": "array", 232 | "description": "One or more arguments for the callbacks." 233 | }, 234 | "askForArgument": { 235 | "type": "boolean", 236 | "description": "Defines if the GUI asks for an argument when invoke manually or not.", 237 | "default": false 238 | }, 239 | "async": { 240 | "type": "boolean", 241 | "description": "Invokes command async or not.", 242 | "default": true 243 | }, 244 | "button": { 245 | "description": "Settings for optional button in the status bar.", 246 | "type": "object", 247 | "properties": { 248 | "color": { 249 | "type": "string", 250 | "description": "The custom (text) color for the button.", 251 | "default": "#ffffff" 252 | }, 253 | "isRight": { 254 | "type": "boolean", 255 | "description": "Set button on the right side or not.", 256 | "default": false 257 | }, 258 | "priority": { 259 | "type": "number", 260 | "description": "The custom priority.", 261 | "default": 0 262 | }, 263 | "show": { 264 | "type": "boolean", 265 | "description": "Show button on startup or not.", 266 | "default": true 267 | }, 268 | "text": { 269 | "type": "string", 270 | "description": "The caption for the button." 271 | }, 272 | "tooltip": { 273 | "type": "string", 274 | "description": "The tooltip for the button." 275 | } 276 | } 277 | }, 278 | "cached": { 279 | "type": "boolean", 280 | "description": "Cache script or not.", 281 | "default": false 282 | }, 283 | "commandState": { 284 | "description": "The initial value for ScriptCommandExecutorArguments.commandState property.", 285 | "default": {} 286 | }, 287 | "continueOnError": { 288 | "type": "boolean", 289 | "description": "Continue on error or cancel.", 290 | "default": true 291 | }, 292 | "description": { 293 | "type": "string", 294 | "description": "The description for the command." 295 | }, 296 | "displayName": { 297 | "type": "string", 298 | "description": "The custom display name." 299 | }, 300 | "id": { 301 | "type": "string", 302 | "description": "The ID of the command." 303 | }, 304 | "onClose": { 305 | "type": "boolean", 306 | "description": "Executes the command on close or not.", 307 | "default": false 308 | }, 309 | "onConfigChanged": { 310 | "type": "boolean", 311 | "description": "Is invoked after settings.json has been changed.", 312 | "default": false 313 | }, 314 | "onEditorChanged": { 315 | "type": "boolean", 316 | "description": "Is invoked after a text editor changed.", 317 | "default": false 318 | }, 319 | "onFileChanged": { 320 | "type": "boolean", 321 | "description": "Is invoked when a file has been changed.", 322 | "default": false 323 | }, 324 | "onFileClosed": { 325 | "type": "boolean", 326 | "description": "Is invoked when a file has been closed.", 327 | "default": false 328 | }, 329 | "onFileDeleted": { 330 | "type": "boolean", 331 | "description": "Is invoked when a file has been deleted.", 332 | "default": false 333 | }, 334 | "onFileOpened": { 335 | "type": "boolean", 336 | "description": "Is invoked when a file has been opened.", 337 | "default": false 338 | }, 339 | "onNewFile": { 340 | "type": "boolean", 341 | "description": "Is invoked when a file has been created.", 342 | "default": false 343 | }, 344 | "onSaved": { 345 | "type": "boolean", 346 | "description": "Is invoked when a file has been saved.", 347 | "default": false 348 | }, 349 | "onStartup": { 350 | "type": "boolean", 351 | "description": "Executes the command on startup or not.", 352 | "default": false 353 | }, 354 | "onWillSave": { 355 | "type": "boolean", 356 | "description": "Is invoked when a file is going to be saved.", 357 | "default": false 358 | }, 359 | "options": { 360 | "description": "Additional data for the execution." 361 | }, 362 | "script": { 363 | "type": "string", 364 | "description": "The path to the script to execute.", 365 | "default": "./my-command.js" 366 | }, 367 | "sortOrder": { 368 | "type": "number", 369 | "description": "The sort order.", 370 | "default": 0 371 | }, 372 | "suppressArguments": { 373 | "type": "boolean", 374 | "description": "Supress own arguments of the extension or not.", 375 | "default": false 376 | } 377 | }, 378 | "required": [ 379 | "id", 380 | "script" 381 | ] 382 | } 383 | } 384 | ] 385 | }, 386 | "disableNewVersionPopups": { 387 | "description": "Disables the display of popups that reports for a new version of that extension.", 388 | "type": "boolean", 389 | "default": false 390 | }, 391 | "globals": { 392 | "description": "Global data available for ALL commands defined by that extension." 393 | }, 394 | "quick": { 395 | "description": "Settings for 'quick execution'.", 396 | "type": "object", 397 | "properties": { 398 | "cwd": { 399 | "description": "The initial current directory for the executions.", 400 | "type": "string" 401 | }, 402 | "disableHexView": { 403 | "description": "Do not show binary data in 'hex view'.", 404 | "type": "boolean", 405 | "default": false 406 | }, 407 | "noResultInfo": { 408 | "description": "Do not show results of executions.", 409 | "type": "boolean", 410 | "default": false 411 | }, 412 | "saveResultsToState": { 413 | "description": "Save all results to $state variable or not.", 414 | "type": "boolean", 415 | "default": false 416 | }, 417 | "showResultInTab": { 418 | "description": "Show results in tab instead of a popup or not.", 419 | "type": "boolean", 420 | "default": false 421 | }, 422 | "saveToGlobalHistory": { 423 | "description": "Show results in tab instead of a popup or not.", 424 | "type": "boolean", 425 | "default": false 426 | }, 427 | "saveToHistory": { 428 | "description": "Automatically save entries to history or not.", 429 | "type": "boolean", 430 | "default": false 431 | }, 432 | "state": { 433 | "description": "The initial state value." 434 | } 435 | } 436 | }, 437 | "showOutput": { 438 | "type": "boolean", 439 | "description": "Show output on startup or not.", 440 | "default": false 441 | }, 442 | "showInternalVSCommands": { 443 | "type": "boolean", 444 | "description": "Show internal Visual Studio Code commands in GUI or not.", 445 | "default": false 446 | } 447 | } 448 | } 449 | } 450 | } 451 | }, 452 | "scripts": { 453 | "vscode:prepublish": "tsc -p ./", 454 | "compile": "tsc -watch -p ./", 455 | "postinstall": "node ./node_modules/vscode/bin/install", 456 | "test": "node ./node_modules/vscode/bin/test" 457 | }, 458 | "devDependencies": { 459 | "@types/fs-extra": "^2.1.0", 460 | "@types/glob": "^5.0.36", 461 | "@types/html-entities": "^1.2.15", 462 | "@types/marked": "0.0.28", 463 | "@types/mocha": "^2.2.48", 464 | "@types/node": "^6.14.2", 465 | "@types/tmp": "0.0.32", 466 | "@types/uuid": "^2.0.29", 467 | "mocha": "^2.3.3", 468 | "typescript": "^2.9.2", 469 | "vscode": "^1.1.26" 470 | }, 471 | "icon": "icon.png", 472 | "author": { 473 | "name": "Marcel Joachim Kloubert" 474 | }, 475 | "repository": { 476 | "type": "git", 477 | "url": "https://github.com/mkloubert/vs-script-commands" 478 | }, 479 | "bugs": { 480 | "url": "https://github.com/mkloubert/vs-script-commands/issues" 481 | }, 482 | "readmeFilename": "README.md", 483 | "dependencies": { 484 | "fs-extra": "^3.0.0", 485 | "glob": "^7.1.3", 486 | "hexy": "^0.2.11", 487 | "html-entities": "^1.2.1", 488 | "marked": "^0.3.19", 489 | "moment": "^2.23.0", 490 | "node-workflows": "^1.3.1", 491 | "public-ip": "^2.5.0", 492 | "random-int": "^1.0.0", 493 | "tmp": "0.0.31", 494 | "uuid": "^3.3.2" 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /src/content.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-script-commands (https://github.com/mkloubert/vs-script-commands) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | 27 | import * as sc_contracts from './contracts'; 28 | import * as sc_controller from './controller'; 29 | import * as sc_helpers from './helpers'; 30 | import * as vscode from 'vscode'; 31 | 32 | 33 | /** 34 | * HTML content provider. 35 | */ 36 | export class HtmlTextDocumentContentProvider implements vscode.TextDocumentContentProvider { 37 | /** 38 | * Stores the underlying controller. 39 | */ 40 | protected readonly _CONTROLLER: sc_controller.ScriptCommandController; 41 | 42 | /** 43 | * Initializes a new instance of that class. 44 | * 45 | * @param {sc_controller.ScriptCommandController} controller The underlying controller instance. 46 | */ 47 | constructor(controller: sc_controller.ScriptCommandController) { 48 | this._CONTROLLER = controller; 49 | } 50 | 51 | /** 52 | * Gets the underlying controller. 53 | */ 54 | public get controller(): sc_controller.ScriptCommandController { 55 | return this._CONTROLLER; 56 | } 57 | 58 | /** @inheritdoc */ 59 | public provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Thenable { 60 | let me = this; 61 | 62 | return new Promise((resolve, reject) => { 63 | let completed = sc_helpers.createSimplePromiseCompletedAction(resolve, reject); 64 | 65 | try { 66 | let htmlDocs = me.controller.htmlDocuments; 67 | 68 | let doc: sc_contracts.Document; 69 | 70 | let params = sc_helpers.uriParamsToObject(uri); 71 | 72 | let idValue = decodeURIComponent(sc_helpers.getUrlParam(params, 'id')); 73 | 74 | if (!sc_helpers.isEmptyString(idValue)) { 75 | let id = idValue.trim(); 76 | 77 | // search for document 78 | for (let i = 0; i < htmlDocs.length; i++) { 79 | let d = htmlDocs[i]; 80 | 81 | if (sc_helpers.toStringSafe(d.id).trim() === id) { 82 | doc = d; 83 | break; 84 | } 85 | } 86 | } 87 | 88 | let html = ''; 89 | 90 | if (doc) { 91 | if (doc.body) { 92 | let enc = sc_helpers.normalizeString(doc.encoding); 93 | if ('' === enc) { 94 | enc = 'utf8'; 95 | } 96 | 97 | html = doc.body.toString(enc); 98 | } 99 | } 100 | 101 | completed(null, html); 102 | } 103 | catch (e) { 104 | completed(e); 105 | } 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/contracts.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-script-commands (https://github.com/mkloubert/vs-script-commands) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as Events from 'events'; 27 | import * as vscode from 'vscode'; 28 | 29 | 30 | /** 31 | * Describes an action based button of a popup. 32 | */ 33 | export interface ActionMessageItem extends vscode.MessageItem { 34 | /** 35 | * Gets the action of that button. 36 | */ 37 | action: () => void; 38 | } 39 | 40 | /** 41 | * A quick pick item based on an action. 42 | */ 43 | export interface ActionQuickPickItem extends vscode.QuickPickItem { 44 | /** 45 | * The action to invoke. 46 | */ 47 | action: () => any; 48 | /** 49 | * Additional data to store in that object. 50 | */ 51 | tag?: any; 52 | } 53 | 54 | /** 55 | * The configuration data for that extension. 56 | */ 57 | export interface Configuration { 58 | /** 59 | * Select the workspace by active text editor automatically or not. 60 | */ 61 | autoSelectWorkspace?: boolean; 62 | /** 63 | * One or more command to register. 64 | */ 65 | commands?: ScriptCommand | ScriptCommand[]; 66 | /** 67 | * Disables the display of popups that reports for a new version of that extension. 68 | */ 69 | disableNewVersionPopups?: boolean; 70 | /** 71 | * Global data available for ALL commands defined by that extension. 72 | */ 73 | globals?: GlobalVariables; 74 | /** 75 | * Settings for "quick execution". 76 | */ 77 | quick?: { 78 | /** 79 | * The initial current directory for the executions. 80 | */ 81 | cwd?: string; 82 | /** 83 | * Do not show binary data in 'hex view'. 84 | */ 85 | disableHexView?: boolean; 86 | /** 87 | * Show result of execution or not. 88 | */ 89 | noResultInfo?: boolean; 90 | /** 91 | * Save all results to '$state' variable or not. 92 | */ 93 | saveResultsToState?: boolean; 94 | /** 95 | * Save entries, that are stored automatically, to global history instead to workspace history. 96 | */ 97 | saveToGlobalHistory?: boolean; 98 | /** 99 | * Automatically save entries to history or not. 100 | */ 101 | saveToHistory?: boolean; 102 | /** 103 | * Show results in tab instead of a popup or not. 104 | */ 105 | showResultInTab?: boolean; 106 | /** 107 | * The initial state value. 108 | */ 109 | state?: any; 110 | }; 111 | /** 112 | * Open output on startup or not. 113 | */ 114 | showOutput?: boolean; 115 | /** 116 | * Show internal Visual Studio Code commands in GUI or not. 117 | */ 118 | showInternalVSCommands?: boolean; 119 | } 120 | 121 | /** 122 | * Information about a (cron) job. 123 | */ 124 | export interface CronJobInfo { 125 | /** 126 | * Gets the description of the underlying job. 127 | */ 128 | readonly description: string; 129 | /** 130 | * Gets the details for the underlying job. 131 | */ 132 | readonly detail: string; 133 | /** 134 | * Gets if the job is currently running or not. 135 | */ 136 | readonly isRunning: boolean; 137 | /** 138 | * Gets the timestamp of the last execution in ISO format. 139 | */ 140 | readonly lastExecution: string; 141 | /** 142 | * Gets the name of the job. 143 | */ 144 | readonly name: string; 145 | } 146 | 147 | /** 148 | * (Cron) Job name(s). 149 | */ 150 | export type CronJobNames = string | string[]; 151 | 152 | /** 153 | * A document. 154 | */ 155 | export interface Document { 156 | /** 157 | * The body / content of the document. 158 | */ 159 | body: Buffer; 160 | /** 161 | * The encoding. 162 | */ 163 | encoding?: string; 164 | /** 165 | * The ID. 166 | */ 167 | id?: any; 168 | /** 169 | * The MIME type. 170 | */ 171 | mime?: string; 172 | /** 173 | * The title. 174 | */ 175 | title?: string; 176 | } 177 | 178 | /** 179 | * List of file change types. 180 | */ 181 | export enum FileChangeType { 182 | /** 183 | * Files has been changed. 184 | */ 185 | Changed = 0, 186 | /** 187 | * File has been created. 188 | */ 189 | New = 1, 190 | /** 191 | * File has been deleted. 192 | */ 193 | Deleted = 2, 194 | /** 195 | * File has been saved. 196 | */ 197 | Saved = 3, 198 | /** 199 | * File has been opened. 200 | */ 201 | Opened = 4, 202 | /** 203 | * File has been closed. 204 | */ 205 | Closed = 5, 206 | /** 207 | * File has been changed in text editor. 208 | */ 209 | EditorChanged = 6, 210 | /** 211 | * File is going to be saved. 212 | */ 213 | WillSave = 7, 214 | /** 215 | * Active text editor has been changed. 216 | */ 217 | ActiveEditorChanged = 8, 218 | } 219 | 220 | /** 221 | * Global variables. 222 | */ 223 | export type GlobalVariables = Object; 224 | 225 | /** 226 | * Describes the structure of the package file of that extenstion. 227 | */ 228 | export interface PackageFile { 229 | /** 230 | * The display name. 231 | */ 232 | displayName: string; 233 | /** 234 | * The (internal) name. 235 | */ 236 | name: string; 237 | /** 238 | * The version string. 239 | */ 240 | version: string; 241 | } 242 | 243 | /** 244 | * A script command. 245 | */ 246 | export interface ScriptCommand { 247 | /** 248 | * One or more arguments for the callbacks. 249 | */ 250 | arguments?: any[]; 251 | /** 252 | * Defines if the GUI asks for an argument when invoke manually or not. 253 | */ 254 | askForArgument?: boolean; 255 | /** 256 | * Invokes command async or not. 257 | */ 258 | async?: boolean; 259 | /** 260 | * Settings for optional button in the status bar. 261 | */ 262 | button?: { 263 | /** 264 | * The custom (text) color for the button. 265 | */ 266 | color?: string; 267 | /** 268 | * Set button on the right side or not. 269 | */ 270 | isRight?: boolean; 271 | /** 272 | * The custom priority. 273 | */ 274 | priority?: number; 275 | /** 276 | * Show button on startup or not. 277 | */ 278 | show?: boolean; 279 | /** 280 | * The caption for the button. 281 | */ 282 | text?: string; 283 | /** 284 | * The tooltip for the button. 285 | */ 286 | tooltip?: string; 287 | }, 288 | /** 289 | * Cache script or not. 290 | */ 291 | cached?: boolean; 292 | /** 293 | * The initial value for ScriptCommandExecutorArguments.commandState property. 294 | */ 295 | commandState?: any; 296 | /** 297 | * Continue on error or cancel. 298 | */ 299 | continueOnError?: boolean; 300 | /** 301 | * The description for the command. 302 | */ 303 | description?: string; 304 | /** 305 | * The custom display name. 306 | */ 307 | displayName?: string; 308 | /** 309 | * The ID of the command. 310 | */ 311 | id: string; 312 | /** 313 | * Is invoked when the active text editor has been changed. 314 | */ 315 | onActiveEditorChanged?: boolean; 316 | /** 317 | * Executes the command on close or not. 318 | */ 319 | onClose?: boolean; 320 | /** 321 | * Is invoked after settings.json has been changed. 322 | */ 323 | onConfigChanged?: boolean; 324 | /** 325 | * Is invoked after a text editor changed. 326 | */ 327 | onEditorChanged?: boolean; 328 | /** 329 | * Is invoked when a file has been changed. 330 | */ 331 | onFileChanged?: boolean; 332 | /** 333 | * Is invoked when a file has been closed. 334 | */ 335 | onFileClosed?: boolean; 336 | /** 337 | * Is invoked when a file has been deleted. 338 | */ 339 | onFileDeleted?: boolean; 340 | /** 341 | * Is invoked when a file has been opened. 342 | */ 343 | onFileOpened?: boolean; 344 | /** 345 | * Is invoked when a file has been created. 346 | */ 347 | onNewFile?: boolean; 348 | /** 349 | * Is invoked when a file has been saved. 350 | */ 351 | onSaved?: boolean; 352 | /** 353 | * Executes the command on startup or not. 354 | */ 355 | onStartup?: boolean; 356 | /** 357 | * Is invoked when a file is being to be saved. 358 | */ 359 | onWillSave?: boolean; 360 | /** 361 | * Additional data for the execution. 362 | */ 363 | options?: any; 364 | /** 365 | * The path to the script to exeute. 366 | */ 367 | script: string; 368 | /** 369 | * The sort order. 370 | */ 371 | sortOrder?: number; 372 | /** 373 | * Supress own arguments of the extension or not. 374 | */ 375 | suppressArguments?: boolean; 376 | } 377 | 378 | /** 379 | * A context that stores the data when a command is executed when active text editor has been changed. 380 | */ 381 | export interface ScriptCommandActiveEditorChangedContext { 382 | /** 383 | * The new editor. 384 | */ 385 | current: vscode.TextEditor; 386 | /** 387 | * The previous editor. 388 | */ 389 | previous?: vscode.TextEditor; 390 | } 391 | 392 | /** 393 | * Factory for additional arguments for the execution of a script command. 394 | * 395 | * @param {ScriptCommand} sc The underlying command. 396 | * 397 | * @return {Array} The list of additional arguments. 398 | */ 399 | export type ScriptCommandArgumentFactory = (sc: ScriptCommand) => any[]; 400 | 401 | /** 402 | * A script executor. 403 | * 404 | * @param {ScriptCommandExecutorArguments} args Arguments for the execution. 405 | * 406 | * @return {Promise|void|number} The result (with the exit code). 407 | */ 408 | export type ScriptCommandExecutor = (args: ScriptCommandExecutorArguments) => ScriptCommandExecutorResult; 409 | 410 | /** 411 | * Possible results of a script executor. 412 | */ 413 | export type ScriptCommandExecutorResult = any; 414 | 415 | /** 416 | * Arguments for a script executor. 417 | */ 418 | export interface ScriptCommandExecutorArguments { 419 | /** 420 | * Arguments from the callback. 421 | */ 422 | arguments: IArguments; 423 | /** 424 | * The additional status bar button of the underlying command. 425 | */ 426 | button?: vscode.StatusBarItem; 427 | /** 428 | * The ID of the underlying command. 429 | */ 430 | command: string; 431 | /** 432 | * Defines data that should be keeped while the current session 433 | * and is available for ONLY for current command. 434 | */ 435 | commandState: any; 436 | /** 437 | * Deploys one or more file to a list of targets. 438 | * 439 | * This requires 'extension.deploy.filesTo' command as available in extensions like 'vs-deploy' 440 | * s. https://github.com/mkloubert/vs-deploy 441 | * 442 | * @param {string|string[]} files One or more file to deploy. 443 | * @param {string|string[]} targets One or more target (name) to deploy to. 444 | * 445 | * @returns {Promise} The promise. 446 | */ 447 | readonly deploy: (files: string | string[], 448 | targets: string | string[]) => Promise; 449 | /** 450 | * Gets the event emitter that can be used by ALL commands. 451 | */ 452 | readonly events: Events.EventEmitter; 453 | /** 454 | * Gets the context of that extension. 455 | */ 456 | readonly extension: vscode.ExtensionContext; 457 | /** 458 | * Finds files inside current workspace using glob patterns. 459 | * 460 | * @param {string} pattern The pattern. 461 | * @param {string|string[]} [ignore] The pattern(s) of files to ignore. 462 | * 463 | * @return {Promise} The promise. 464 | */ 465 | readonly findFiles: (pattern: string, ignore?: string | string[]) => Promise; 466 | /** 467 | * Converts Markdown to HTML. 468 | * 469 | * @param {string} markdown The Markdown code. 470 | * 471 | * @return {string} The generated HTML. 472 | */ 473 | readonly fromMarkdown: (markdown: string) => string; 474 | /** 475 | * Returns the list of current (cron) jobs. 476 | * 477 | * @return {Promise} The promise. 478 | */ 479 | readonly getCronJobs: () => Promise; 480 | /** 481 | * Gets the event emitter that can be used by all elements and features of that command. 482 | */ 483 | readonly globalEvents: Events.EventEmitter; 484 | /** 485 | * The global variables from the settings. 486 | */ 487 | globals: GlobalVariables; 488 | /** 489 | * Defines data that should be keeped while the current session 490 | * and is available for ALL commands defined by that extension. 491 | */ 492 | readonly globalState: any; 493 | /** 494 | * Encodes the HTML entities in a string. 495 | * 496 | * @param {string} str The string to encode. 497 | * 498 | * @return {string} The encoded string. 499 | */ 500 | readonly htmlEncode: (str: string) => string; 501 | /** 502 | * Logs a message. 503 | * 504 | * @param {any} msg The message to log. 505 | * 506 | * @chainable 507 | */ 508 | readonly log: (msg: any) => ScriptCommandExecutorArguments; 509 | /** 510 | * Gets or sets the value for the next execution. 511 | */ 512 | nextValue: any; 513 | /** 514 | * Opens a HTML document in a new tab. 515 | * 516 | * @param {string} html The HTML document (source code). 517 | * @param {string} [title] The custom title for the tab. 518 | * @param {any} [id] The custom ID for the document in the storage. 519 | * 520 | * @returns {Promise} The promise. 521 | */ 522 | readonly openHtml: (html: string, title?: string, id?: any) => Promise; 523 | /** 524 | * Options for the execution (@see ScriptCommand). 525 | */ 526 | options?: any; 527 | /** 528 | * Gets the list of (other) commands, defined by that extension. 529 | */ 530 | readonly others: string[]; 531 | /** 532 | * Gets the output channel that can be used by the underlying script. 533 | */ 534 | readonly outputChannel: vscode.OutputChannel; 535 | /** 536 | * Gets the value from the previous execution. 537 | */ 538 | readonly previousValue: any; 539 | /** 540 | * Loads a module from the script context. 541 | * 542 | * @param {string} id The ID / path to the module. 543 | * 544 | * @return {any} The loaded module. 545 | */ 546 | require: (id: string) => any; 547 | /** 548 | * Re-starts cron jobs. 549 | * 550 | * This requires 'extension.cronJons.restartJobsByName' command as available in extensions like 'vs-cron' 551 | * s. https://github.com/mkloubert/vs-cron 552 | * 553 | * @param {CronJobNames} jobs The jobs to re-start. 554 | * 555 | * @return {Promise} The promise. 556 | */ 557 | readonly restartCronJobs: (jobs: CronJobNames) => Promise; 558 | /** 559 | * Starts REST API host. 560 | * 561 | * This requires 'extension.restApi.startHost' command as available in extensions like 'vs-rest-api' 562 | * s. https://github.com/mkloubert/vs-rest-api 563 | * 564 | * @return {Promise} The promise. 565 | */ 566 | readonly startApi: () => Promise; 567 | /** 568 | * Starts cron jobs. 569 | * 570 | * This requires 'extension.cronJons.startJobsByName' command as available in extensions like 'vs-cron' 571 | * s. https://github.com/mkloubert/vs-cron 572 | * 573 | * @param {CronJobNames} jobs The jobs to start. 574 | * 575 | * @return {Promise} The promise. 576 | */ 577 | readonly startCronJobs: (jobs: CronJobNames) => Promise; 578 | /** 579 | * Stops REST API host. 580 | * 581 | * This requires 'extension.restApi.stopHost' command as available in extensions like 'vs-rest-api' 582 | * s. https://github.com/mkloubert/vs-rest-api 583 | * 584 | * @return {Promise} The promise. 585 | */ 586 | readonly stopApi: () => Promise; 587 | /** 588 | * Stops cron jobs. 589 | * 590 | * This requires 'extension.cronJons.stopJobsByName' command as available in extensions like 'vs-cron' 591 | * s. https://github.com/mkloubert/vs-cron 592 | * 593 | * @param {CronJobNames} jobs The jobs to stop. 594 | * 595 | * @return {Promise} The promise. 596 | */ 597 | readonly stopCronJobs: (jobs: CronJobNames) => Promise; 598 | /** 599 | * Converts a value, like a buffer or string, to "hex view". 600 | * 601 | * @param {any} val The value to convert. 602 | * 603 | * @return {string} The value in "hex view". 604 | */ 605 | readonly toHexView: (val: any) => string; 606 | } 607 | 608 | /** 609 | * A context that stores the data when a command is executed on editor change. 610 | */ 611 | export interface ScriptCommandEditorChangeContext { 612 | /** 613 | * The list of changes. 614 | */ 615 | changes: vscode.TextDocumentContentChangeEvent[]; 616 | /** 617 | * The underlying document. 618 | */ 619 | document: vscode.TextDocument; 620 | } 621 | 622 | /** 623 | * A context that stores the data when a command is executed on file change. 624 | */ 625 | export interface ScriptCommandFileChangeContext { 626 | /** 627 | * The command. 628 | */ 629 | command: string; 630 | /** 631 | * The file. 632 | */ 633 | file: string; 634 | /** 635 | * The global variables from the settings. 636 | */ 637 | globals?: GlobalVariables; 638 | /** 639 | * Options for the execution (@see ScriptCommand). 640 | */ 641 | options?: any; 642 | /** 643 | * Loads a module from the script context. 644 | * 645 | * @param {string} id The ID / path to the module. 646 | * 647 | * @return {any} The loaded module. 648 | */ 649 | require: (id: string) => any; 650 | /** 651 | * The change type. 652 | */ 653 | type: FileChangeType; 654 | /** 655 | * The URI of the file. 656 | */ 657 | uri: vscode.Uri; 658 | } 659 | 660 | /** 661 | * A script module. 662 | */ 663 | export interface ScriptCommandModule { 664 | /** 665 | * The script executor. 666 | */ 667 | execute?: ScriptCommandExecutor; 668 | } 669 | 670 | /** 671 | * A quick pick item. 672 | */ 673 | export interface ScriptCommandQuickPickItem extends vscode.QuickPickItem { 674 | } 675 | 676 | /** 677 | * Describes a function that provides a value. 678 | * 679 | * @return {TValue} The value. 680 | */ 681 | export type ValueProvider = () => TValue; 682 | -------------------------------------------------------------------------------- /src/controller.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-script-commands (https://github.com/mkloubert/vs-script-commands) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as Events from 'events'; 27 | import * as FSExtra from 'fs-extra'; 28 | import * as Glob from 'glob'; 29 | import * as Globals from './globals'; 30 | const Hexy = require('hexy'); 31 | import * as HtmlEntities from 'html-entities'; 32 | import * as Marked from 'marked'; 33 | import * as Moment from 'moment'; 34 | import * as Path from 'path'; 35 | import * as sc_contracts from './contracts'; 36 | import * as sc_helpers from './helpers'; 37 | import * as sc_quick from './quick'; 38 | import * as sc_urls from './urls'; 39 | import * as sc_workspace from './workspace'; 40 | import * as vscode from 'vscode'; 41 | 42 | 43 | /** 44 | * A script command entry. 45 | */ 46 | export interface ScriptCommandEntry { 47 | /** 48 | * The additional status bar button. 49 | */ 50 | button?: vscode.StatusBarItem; 51 | /** 52 | * The command object. 53 | */ 54 | command: vscode.Disposable; 55 | /** 56 | * The ID / name of the command. 57 | */ 58 | id: string; 59 | /** 60 | * The underlying script command object. 61 | */ 62 | object: sc_contracts.ScriptCommand; 63 | } 64 | 65 | /** 66 | * A command entry. 67 | */ 68 | export interface CommandEntry { 69 | /** 70 | * The list of additional arguments for the callback. 71 | */ 72 | arguments: any[]; 73 | /** 74 | * Defines if the GUI asks for arguments or not. 75 | */ 76 | askForArgument: boolean; 77 | /** 78 | * The scription. 79 | */ 80 | description: string; 81 | /** 82 | * The ID of the command. 83 | */ 84 | id: string; 85 | /** 86 | * The label. 87 | */ 88 | label: string; 89 | /** 90 | * Supress own arguments or not. 91 | */ 92 | suppressArguments: boolean; 93 | } 94 | 95 | /** 96 | * A command entry quick pick item. 97 | */ 98 | export interface CommandEntryQuickPickItem extends sc_contracts.ScriptCommandQuickPickItem { 99 | /** 100 | * The underlying entry. 101 | */ 102 | entry: CommandEntry; 103 | } 104 | 105 | 106 | /** 107 | * The controller class for that extension. 108 | */ 109 | export class ScriptCommandController extends Events.EventEmitter implements vscode.Disposable { 110 | /** 111 | * List of custom commands. 112 | */ 113 | protected readonly _COMMANDS: ScriptCommandEntry[] = []; 114 | /** 115 | * Stores the current configuration. 116 | */ 117 | protected _config: sc_contracts.Configuration; 118 | /** 119 | * Stores the extension context. 120 | */ 121 | protected readonly _CONTEXT: vscode.ExtensionContext; 122 | /** 123 | * Stores the current active text editor. 124 | */ 125 | protected _currentTextEditor: vscode.TextEditor; 126 | /** 127 | * The global file system watcher. 128 | */ 129 | protected _fileSystemWatcher: vscode.FileSystemWatcher; 130 | /** 131 | * Storage for global HTML documents. 132 | */ 133 | protected _htmlDocs: sc_contracts.Document[]; 134 | /** 135 | * Stores if extension is reloading its configuration or not. 136 | */ 137 | protected _isReloadingConfig = false; 138 | /** 139 | * Stores the global output channel. 140 | */ 141 | protected readonly _OUTPUT_CHANNEL: vscode.OutputChannel; 142 | /** 143 | * Stores the package file of that extension. 144 | */ 145 | protected _PACKAGE_FILE: sc_contracts.PackageFile; 146 | /** 147 | * Stores the event emitter for scripts. 148 | */ 149 | protected _scriptEvents: Events.EventEmitter; 150 | 151 | /** 152 | * Initializes a new instance of that class. 153 | * 154 | * @param {vscode.ExtensionContext} context The underlying extension context. 155 | * @param {vscode.OutputChannel} outputChannel The global output channel to use. 156 | * @param {sc_contracts.PackageFile} pkgFile The package file of that extension. 157 | */ 158 | constructor(context: vscode.ExtensionContext, 159 | outputChannel: vscode.OutputChannel, 160 | pkgFile: sc_contracts.PackageFile) { 161 | super(); 162 | 163 | this._CONTEXT = context; 164 | this._OUTPUT_CHANNEL = outputChannel; 165 | this._PACKAGE_FILE = pkgFile; 166 | } 167 | 168 | /** 169 | * Gets the current configuration. 170 | */ 171 | public get config(): sc_contracts.Configuration { 172 | return this._config || {}; 173 | } 174 | 175 | /** 176 | * Gets the underlying extension context. 177 | */ 178 | public get context(): vscode.ExtensionContext { 179 | return this._CONTEXT; 180 | } 181 | 182 | /** @inheritdoc */ 183 | public dispose() { 184 | try { 185 | this.removeAllListeners(); 186 | } 187 | catch (e) { 188 | console.log(`[ERROR] ScriptCommandController.dispose(): ${e}`); 189 | } 190 | } 191 | 192 | /** 193 | * Executes a command defined by this extension. 194 | */ 195 | public executeCommand() { 196 | let entries: CommandEntry[] = this.getCommands().map(x => { 197 | let e: CommandEntry = { 198 | arguments: sc_helpers.asArray(x.arguments || []), 199 | askForArgument: sc_helpers.toBooleanSafe(x.askForArgument), 200 | description: '', 201 | id: x.id, 202 | label: x.id, 203 | suppressArguments: sc_helpers.toBooleanSafe(x.suppressArguments), 204 | }; 205 | 206 | if (!sc_helpers.isEmptyString(x.displayName)) { 207 | e.label = sc_helpers.toStringSafe(x.displayName).trim(); 208 | } 209 | 210 | if (!sc_helpers.isEmptyString(x.description)) { 211 | e.description = sc_helpers.toStringSafe(x.description).trim(); 212 | } 213 | 214 | return e; 215 | }); 216 | 217 | this.selectAndExecuteCommand(entries); 218 | } 219 | 220 | /** 221 | * Executes script commands. 222 | * 223 | * @param {sc_contracts.ScriptCommand[]} commandsToExecute The commands to execute. 224 | * @param {sc_contracts.ScriptCommandArgumentFactory} argsFactory The function that returns additional arguments for the execution of a command. 225 | * 226 | * @return {Promise} The promise. 227 | */ 228 | protected executeScriptCommands(commandsToExecute: sc_contracts.ScriptCommand[], 229 | argsFactory?: sc_contracts.ScriptCommandArgumentFactory): Promise { 230 | let me = this; 231 | 232 | commandsToExecute = sc_helpers.asArray(commandsToExecute) 233 | .filter(x => x) 234 | .map(x => x); 235 | 236 | return new Promise((resolve, reject) => { 237 | let completed = (err?: any) => { 238 | if (err) { 239 | reject(err); 240 | } 241 | else { 242 | resolve(); 243 | } 244 | }; 245 | 246 | try { 247 | let invokeNextCommand: () => void; 248 | invokeNextCommand = () => { 249 | if (commandsToExecute.length < 1) { 250 | completed(); 251 | return; 252 | } 253 | 254 | let c = commandsToExecute.shift(); 255 | let cbArgs = sc_helpers.asArray(c.arguments || []); 256 | let async = sc_helpers.toBooleanSafe(c.async, true); 257 | let continueOnError = sc_helpers.toBooleanSafe(c.async, true); 258 | let suppressArguments = sc_helpers.toBooleanSafe(c.suppressArguments); 259 | 260 | try { 261 | let execArgs = [ c.id ]; 262 | if (!suppressArguments && argsFactory) { 263 | let additionalArgs = argsFactory(c) || []; 264 | 265 | execArgs = execArgs.concat(additionalArgs); 266 | } 267 | execArgs = execArgs.concat(cbArgs); 268 | 269 | vscode.commands.executeCommand.apply(null, execArgs).then((result) => { 270 | let exitCode = parseInt(sc_helpers.toStringSafe(result).trim()); 271 | if (!isNaN(exitCode)) { 272 | console.log(`[vs-script-commands] '${c.id}' returned with exit code ${exitCode}`); 273 | } 274 | 275 | if (!async) { 276 | invokeNextCommand(); 277 | } 278 | }, (err) => { 279 | me.log(`[ERROR] ScriptCommandController.executeScriptCommands(2)(${c.id}): ${sc_helpers.toStringSafe(err)}`); 280 | 281 | if (continueOnError) { 282 | if (!async) { 283 | invokeNextCommand(); 284 | } 285 | } 286 | else { 287 | completed(err); 288 | } 289 | }); 290 | 291 | if (async) { 292 | invokeNextCommand(); 293 | } 294 | } 295 | catch (e) { 296 | me.log(`[ERROR] ScriptCommandController.executeScriptCommands(1)(${c.id}): ${sc_helpers.toStringSafe(e)}`); 297 | 298 | if (continueOnError) { 299 | invokeNextCommand(); 300 | } 301 | else { 302 | completed(e); 303 | } 304 | } 305 | }; 306 | 307 | invokeNextCommand(); 308 | } 309 | catch (e) { 310 | completed(e); 311 | } 312 | }); 313 | } 314 | 315 | /** 316 | * Executes another command of Visual Studio Code. 317 | */ 318 | public executeVSCommand() { 319 | let me = this; 320 | let cfg = me.config; 321 | 322 | let filterInternal = !sc_helpers.toBooleanSafe(cfg.showInternalVSCommands); 323 | 324 | vscode.commands.getCommands(filterInternal).then((commands) => { 325 | let entries = commands.map(x => { 326 | let e: CommandEntry = { 327 | arguments: [], 328 | askForArgument: false, 329 | description: '(VSCode command)', 330 | id: x, 331 | label: x, 332 | suppressArguments: true, 333 | }; 334 | 335 | return e; 336 | }); 337 | 338 | entries.sort((x, y) => { 339 | return sc_helpers.compareValues(sc_helpers.toStringSafe(x.id).toLowerCase().trim(), 340 | sc_helpers.toStringSafe(y.id).toLowerCase().trim()); 341 | }); 342 | 343 | me.selectAndExecuteCommand(entries); 344 | }, (err) => { 345 | me.log(`[ERROR] ScriptCommandController.executeVSCommand(1): ${sc_helpers.toStringSafe(err)}`); 346 | }); 347 | } 348 | 349 | /** 350 | * Returns a sorted list of the commands defined in the settings. 351 | * 352 | * @returns {sc_contracts.ScriptCommand[]} The commands. 353 | */ 354 | public getCommands(): sc_contracts.ScriptCommand[] { 355 | let me = this; 356 | let cfg = me.config; 357 | 358 | let commands: sc_contracts.ScriptCommand[]; 359 | if (cfg.commands) { 360 | commands = sc_helpers.asArray(cfg.commands); 361 | } 362 | 363 | return sc_helpers.sortCommands(commands); 364 | } 365 | 366 | /** 367 | * Returns the global variables defined in settings. 368 | * 369 | * @return {sc_contracts.GlobalVariables} The globals. 370 | */ 371 | public getGlobals(): sc_contracts.GlobalVariables { 372 | let result: sc_contracts.GlobalVariables = {}; 373 | 374 | let cfgGlobals = this.config.globals; 375 | if (cfgGlobals) { 376 | result = sc_helpers.cloneObject(cfgGlobals); 377 | } 378 | 379 | return result; 380 | } 381 | 382 | /** 383 | * Gets the storage of global HTML documents. 384 | */ 385 | public get htmlDocuments(): sc_contracts.Document[] { 386 | return this._htmlDocs; 387 | } 388 | 389 | /** 390 | * Loads a message. 391 | * 392 | * @param {any} msg The message to log. 393 | * 394 | * @chainable 395 | */ 396 | public log(msg: any): ScriptCommandController { 397 | let now = Moment(); 398 | 399 | msg = sc_helpers.toStringSafe(msg); 400 | this.outputChannel 401 | .appendLine(`[${now.format('YYYY-MM-DD HH:mm:ss')}] ${msg}`); 402 | 403 | return this; 404 | } 405 | 406 | /** 407 | * Event after the extension has been activated. 408 | */ 409 | public onActivated() { 410 | this.reloadConfiguration(); 411 | 412 | this.setupFileSystemWatcher(); 413 | } 414 | 415 | /** 416 | * Event when deactivating the extension. 417 | */ 418 | public onDeactivate() { 419 | let me = this; 420 | 421 | // close commands 422 | let commandsToExecute = me.getCommands().filter(x => { 423 | return sc_helpers.toBooleanSafe(x.onClose); 424 | }); 425 | me.executeScriptCommands(commandsToExecute).then(() => { 426 | //TODO 427 | }).catch((err) => { 428 | // TODO 429 | }); 430 | } 431 | 432 | /** 433 | * Is invoked after active text editor has been changed. 434 | * 435 | * @param {vscode.TextEditor} editor The new editor. 436 | */ 437 | public onDidChangeActiveTextEditor(editor: vscode.TextEditor) { 438 | const ME = this; 439 | 440 | const PREVIOUS_EDITOR = ME._currentTextEditor; 441 | ME._currentTextEditor = editor; 442 | 443 | const AUTO_SELECT_WORKSPACE_COMPLETED = (err: any) => { 444 | if (err) { 445 | vscode.window 446 | .showWarningMessage(`[vs-script-commands] Could not auto-select workspace: ${sc_helpers.toStringSafe(err)}`); 447 | } 448 | 449 | let uri: vscode.Uri; 450 | if (editor.document) { 451 | uri = editor.document.uri; 452 | } 453 | 454 | ME.onFileChange(uri, sc_contracts.FileChangeType.ActiveEditorChanged, (sc) => { 455 | let ctx: sc_contracts.ScriptCommandActiveEditorChangedContext = { 456 | current: editor, 457 | previous: PREVIOUS_EDITOR, 458 | }; 459 | 460 | return [ ctx ]; 461 | }); 462 | }; 463 | 464 | // auto select workspace 465 | try { 466 | const MATCHING_FOLDERS: vscode.WorkspaceFolder[] = []; 467 | 468 | const UPDATE_WORKSPACE = (newFolder: vscode.WorkspaceFolder) => { 469 | try { 470 | if (newFolder) { 471 | sc_workspace.setWorkspace(newFolder); 472 | 473 | ME.onDidChangeConfiguration(); 474 | } 475 | } 476 | catch (e) { 477 | AUTO_SELECT_WORKSPACE_COMPLETED(e); 478 | } 479 | }; 480 | 481 | if (editor) { 482 | if (sc_helpers.toBooleanSafe( ME.config.autoSelectWorkspace )) { 483 | const DOC = editor.document; 484 | 485 | if (DOC) { 486 | // only for document 487 | 488 | if (FSExtra.existsSync( DOC.fileName )) { 489 | const FILE = Path.resolve( DOC.fileName ); 490 | const DIR = Path.resolve( Path.dirname(FILE) ); 491 | 492 | const CURRENT_DIR = Path.resolve( sc_workspace.getRootPath() ); 493 | if (CURRENT_DIR !== DIR) { 494 | // folders are different 495 | const WORKSPACE_FOLDERS = (vscode.workspace.workspaceFolders || []).filter(wsf => { 496 | return wsf; 497 | }); 498 | 499 | // try to find matching folder 500 | // only there are at least 2 folders 501 | if (WORKSPACE_FOLDERS.length > 1) { 502 | for (let i = 0; i < WORKSPACE_FOLDERS.length; i++) { 503 | const WSF = WORKSPACE_FOLDERS[i]; 504 | 505 | const URI = WSF.uri; 506 | if (URI) { 507 | let wsfPath = URI.fsPath; 508 | if (!sc_helpers.isEmptyString(wsfPath)) { 509 | wsfPath = Path.resolve(wsfPath); 510 | if (DIR.startsWith(wsfPath)) { 511 | MATCHING_FOLDERS.push(WSF); 512 | } 513 | } 514 | } 515 | } 516 | } 517 | } 518 | } 519 | } 520 | 521 | if (MATCHING_FOLDERS.length > 0) { 522 | if (1 === MATCHING_FOLDERS.length) { 523 | UPDATE_WORKSPACE( 524 | MATCHING_FOLDERS[0] 525 | ); 526 | } 527 | else { 528 | // more than one machting folders 529 | 530 | sc_workspace.selectWorkspace().then((newWorkspaceFolder) => { 531 | UPDATE_WORKSPACE(newWorkspaceFolder); 532 | }).catch((err) => { 533 | AUTO_SELECT_WORKSPACE_COMPLETED(err); 534 | }); 535 | } 536 | } 537 | } 538 | } 539 | } 540 | catch (e) { 541 | AUTO_SELECT_WORKSPACE_COMPLETED(e); 542 | } 543 | } 544 | 545 | /** 546 | * Event after configuration changed. 547 | */ 548 | public onDidChangeConfiguration() { 549 | this.reloadConfiguration(); 550 | } 551 | 552 | /** 553 | * Event after text document has been changed. 554 | * 555 | * @param {vscode.TextDocument} doc The document. 556 | */ 557 | public onDidChangeTextDocument(e: vscode.TextDocumentChangeEvent) { 558 | if (!e) { 559 | return; 560 | } 561 | 562 | let me = this; 563 | 564 | try { 565 | let uri = vscode.Uri.file(e.document.fileName); 566 | 567 | me.onFileChange(uri, sc_contracts.FileChangeType.EditorChanged, (sc) => { 568 | let ctx: sc_contracts.ScriptCommandEditorChangeContext = { 569 | changes: e.contentChanges, 570 | document: e.document, 571 | }; 572 | 573 | return [ ctx ]; 574 | }); 575 | } 576 | catch (e) { 577 | me.log(`[ERROR] ScriptCommandController.onDidCloseTextDocument(1): ${sc_helpers.toStringSafe(e)}`); 578 | } 579 | } 580 | 581 | /** 582 | * Event after a document has closed. 583 | * 584 | * @param {vscode.TextDocument} doc The document. 585 | */ 586 | public onDidCloseTextDocument(doc: vscode.TextDocument) { 587 | if (!doc) { 588 | return; 589 | } 590 | 591 | let me = this; 592 | 593 | try { 594 | let uri = vscode.Uri.file(doc.fileName); 595 | 596 | me.onFileChange(uri, sc_contracts.FileChangeType.Closed); 597 | } 598 | catch (e) { 599 | me.log(`[ERROR] ScriptCommandController.onDidCloseTextDocument(1): ${sc_helpers.toStringSafe(e)}`); 600 | } 601 | } 602 | 603 | /** 604 | * Event after a document has been opened. 605 | * 606 | * @param {vscode.TextDocument} doc The document. 607 | */ 608 | public onDidOpenTextDocument(doc: vscode.TextDocument) { 609 | if (!doc) { 610 | return; 611 | } 612 | 613 | let me = this; 614 | 615 | try { 616 | let uri = vscode.Uri.file(doc.fileName); 617 | 618 | me.onFileChange(uri, sc_contracts.FileChangeType.Opened); 619 | } 620 | catch (e) { 621 | me.log(`[ERROR] ScriptCommandController.onDidOpenTextDocument(1): ${sc_helpers.toStringSafe(e)}`); 622 | } 623 | } 624 | 625 | /** 626 | * Event after a document has been saved. 627 | * 628 | * @param {vscode.TextDocument} doc The document. 629 | */ 630 | public onDidSaveTextDocument(doc: vscode.TextDocument) { 631 | if (!doc) { 632 | return; 633 | } 634 | 635 | let me = this; 636 | 637 | try { 638 | let uri = vscode.Uri.file(doc.fileName); 639 | 640 | me.onFileChange(uri, sc_contracts.FileChangeType.Saved); 641 | } 642 | catch (e) { 643 | me.log(`[ERROR] ScriptCommandController.onDidSaveTextDocument(1): ${sc_helpers.toStringSafe(e)}`); 644 | } 645 | } 646 | 647 | /** 648 | * Is invoked on a file / directory change. 649 | * 650 | * @param {vscode.Uri} e The URI of the item. 651 | * @param {sc_contracts.FileChangeType} type The type of change. 652 | * @param {sc_contracts.ScriptCommandArgumentFactory} [argsFactory] Function that returns additional arguments for the execution. 653 | */ 654 | protected onFileChange(e: vscode.Uri, type: sc_contracts.FileChangeType, 655 | argsFactory?: sc_contracts.ScriptCommandArgumentFactory) { 656 | let me = this; 657 | 658 | let filePath = Path.resolve(e.fsPath); 659 | 660 | let commandsToExecute = me.getCommands().filter(c => { 661 | let doesMatch = false; 662 | 663 | switch (type) { 664 | case sc_contracts.FileChangeType.ActiveEditorChanged: 665 | doesMatch = sc_helpers.toBooleanSafe(c.onActiveEditorChanged); 666 | break; 667 | 668 | case sc_contracts.FileChangeType.Changed: 669 | doesMatch = sc_helpers.toBooleanSafe(c.onFileChanged); 670 | break; 671 | 672 | case sc_contracts.FileChangeType.Deleted: 673 | doesMatch = sc_helpers.toBooleanSafe(c.onFileDeleted); 674 | break; 675 | 676 | case sc_contracts.FileChangeType.New: 677 | doesMatch = sc_helpers.toBooleanSafe(c.onNewFile); 678 | break; 679 | 680 | case sc_contracts.FileChangeType.Saved: 681 | doesMatch = sc_helpers.toBooleanSafe(c.onSaved); 682 | break; 683 | 684 | case sc_contracts.FileChangeType.Opened: 685 | doesMatch = sc_helpers.toBooleanSafe(c.onFileOpened); 686 | break; 687 | 688 | case sc_contracts.FileChangeType.Closed: 689 | doesMatch = sc_helpers.toBooleanSafe(c.onFileClosed); 690 | break; 691 | 692 | case sc_contracts.FileChangeType.EditorChanged: 693 | doesMatch = sc_helpers.toBooleanSafe(c.onEditorChanged); 694 | break; 695 | 696 | case sc_contracts.FileChangeType.WillSave: 697 | doesMatch = sc_helpers.toBooleanSafe(c.onWillSave); 698 | break; 699 | } 700 | 701 | return doesMatch; 702 | }); 703 | 704 | me.executeScriptCommands(commandsToExecute, (c) => { 705 | let ctx: sc_contracts.ScriptCommandFileChangeContext = { 706 | command: c.id, 707 | file: filePath, 708 | globals: me.getGlobals(), 709 | options: sc_helpers.cloneObject(c.options), 710 | require: function(id) { 711 | return require(id); 712 | }, 713 | type: type, 714 | uri: e, 715 | }; 716 | 717 | let additionArgs = [ ctx ]; 718 | if (argsFactory) { 719 | let moreAdditionalArgs = argsFactory(c); 720 | if (moreAdditionalArgs) { 721 | additionArgs = additionArgs.concat(moreAdditionalArgs); 722 | } 723 | } 724 | 725 | return additionArgs; 726 | }).then(() => { 727 | //TODO 728 | }).catch((err) => { 729 | vscode.window.showErrorMessage(`[vs-script-commands] Execution of script commands (${sc_contracts.FileChangeType[type]}) failed: ${sc_helpers.toStringSafe(err)}`); 730 | });; 731 | } 732 | 733 | /** 734 | * Is invoked when a document is going to be saved. 735 | * 736 | * @param {vscode.TextDocumentWillSaveEvent} e The event data. 737 | */ 738 | public onWillSaveTextDocument(e: vscode.TextDocumentWillSaveEvent) { 739 | this.onFileChange(e.document.uri, sc_contracts.FileChangeType.WillSave, 740 | () => [ e ]); 741 | } 742 | 743 | /** 744 | * Opens a HTML document in a new tab. 745 | * 746 | * @param {string} html The HTML document (source code). 747 | * @param {string} [title] The custom title for the tab. 748 | * @param {any} [id] The custom ID for the document in the storage. 749 | * 750 | * @returns {Promise} The promise. 751 | */ 752 | public openHtml(html: string, title?: string, id?: any): Promise { 753 | return sc_helpers.openHtmlDocument(this.htmlDocuments, 754 | html, title, id); 755 | } 756 | 757 | /** 758 | * Gets the global output channel. 759 | */ 760 | public get outputChannel(): vscode.OutputChannel { 761 | return this._OUTPUT_CHANNEL; 762 | } 763 | 764 | /** 765 | * Gets the package file of that extension. 766 | */ 767 | public get packageFile(): sc_contracts.PackageFile { 768 | return this._PACKAGE_FILE; 769 | } 770 | 771 | /** 772 | * Does a "quick execution". 773 | */ 774 | public quickExecution() { 775 | return sc_quick.quickExecution 776 | .apply(this, arguments); 777 | } 778 | 779 | /** 780 | * Reloads the commands. 781 | */ 782 | protected reloadCommands() { 783 | let me = this; 784 | let cfg = me.config; 785 | 786 | try { 787 | // remove old commands 788 | while (this._COMMANDS.length > 0) { 789 | let oldCmd = this._COMMANDS.shift(); 790 | 791 | sc_helpers.tryDispose(oldCmd.command); 792 | sc_helpers.tryDispose(oldCmd.button); 793 | } 794 | 795 | let oldEventEmitter = me._scriptEvents; 796 | if (oldEventEmitter) { 797 | try { 798 | oldEventEmitter.removeAllListeners(); 799 | } 800 | catch (e) { 801 | me.log(`[ERROR] ScriptCommandController.reloadCommands(3): ${sc_helpers.toStringSafe(e)}`); 802 | } 803 | } 804 | oldEventEmitter = undefined; 805 | 806 | let newCommands = me.getCommands(); 807 | 808 | let globalState: any = {}; 809 | 810 | let newEventEmitter = new Events.EventEmitter(); 811 | me._scriptEvents = newEventEmitter; 812 | 813 | newCommands.forEach(c => { 814 | let cmdId = sc_helpers.toStringSafe(c.id); 815 | 816 | let btn: vscode.StatusBarItem; 817 | let cmd: vscode.Disposable; 818 | let disposeItems = false; 819 | 820 | let prevVal: any; 821 | 822 | let doCacheScript = sc_helpers.toBooleanSafe(c.cached); 823 | 824 | try { 825 | let commandState: any = {}; 826 | if ((c).hasOwnProperty("commandState")) { 827 | commandState = sc_helpers.cloneObject(c.commandState); 828 | } 829 | 830 | cmd = vscode.commands.registerCommand(cmdId, function() { 831 | let funcArgs = arguments; 832 | 833 | return new Promise((res, rej) => { 834 | let args: sc_contracts.ScriptCommandExecutorArguments; 835 | let completed = (err: any, result?: any) => { 836 | try { 837 | if (err) { 838 | rej(err); 839 | } 840 | else { 841 | res(result); 842 | } 843 | } 844 | finally { 845 | if (args) { 846 | commandState = args.commandState; 847 | globalState = args.globalState; 848 | prevVal = args.nextValue; 849 | } 850 | } 851 | }; 852 | 853 | try { 854 | let cmdModule = sc_helpers.loadModule(c.script, doCacheScript); 855 | if (cmdModule.execute) { 856 | args = { 857 | arguments: funcArgs, 858 | button: undefined, 859 | command: cmdId, 860 | commandState: commandState, 861 | deploy: (files, targets) => { 862 | // files 863 | files = sc_helpers.asArray(files) 864 | .map(x => sc_helpers.toStringSafe(x)) 865 | .filter(x => !sc_helpers.isEmptyString(x)); 866 | files = sc_helpers.distinctArray(files); 867 | 868 | // targets 869 | targets = sc_helpers.asArray(targets) 870 | .map(x => sc_helpers.normalizeString(x)) 871 | .filter(x => x); 872 | targets = sc_helpers.distinctArray(targets); 873 | 874 | return new Promise((resolve, reject) => { 875 | let completed = sc_helpers.createSimplePromiseCompletedAction(resolve, reject); 876 | 877 | try { 878 | vscode.commands.executeCommand('extension.deploy.filesTo', files, targets).then((result) => { 879 | completed(null, result); 880 | }, (err) => { 881 | completed(err); 882 | }); 883 | } 884 | catch (e) { 885 | completed(e); 886 | } 887 | }); 888 | }, 889 | events: undefined, 890 | extension: undefined, 891 | findFiles: (pattern, ignore?) => { 892 | return new Promise((resolve, reject) => { 893 | try { 894 | pattern = sc_helpers.toStringSafe(pattern); 895 | if ('' === pattern.trim()) { 896 | pattern = '**'; 897 | } 898 | 899 | ignore = sc_helpers.asArray(ignore) 900 | .map(x => sc_helpers.toStringSafe(x)) 901 | .filter(x => '' !== x.trim()); 902 | ignore = sc_helpers.distinctArray(ignore); 903 | 904 | Glob(pattern, { 905 | cwd: sc_workspace.getRootPath(), 906 | root: sc_workspace.getRootPath(), 907 | dot: true, 908 | nocase: true, 909 | nodir: true, 910 | nosort: false, 911 | realpath: false, 912 | ignore: ignore, 913 | absolute: true, 914 | }, (err, files) => { 915 | if (err) { 916 | reject(err); 917 | } 918 | else { 919 | resolve(files); 920 | } 921 | }); 922 | } 923 | catch (e) { 924 | reject(e); 925 | } 926 | }); 927 | }, 928 | fromMarkdown: (markdown) => { 929 | markdown = sc_helpers.toStringSafe(markdown); 930 | 931 | return Marked(markdown, { 932 | gfm: true, 933 | tables: true, 934 | breaks: true, 935 | }); 936 | }, 937 | getCronJobs: () => { 938 | return new Promise((resolve, reject) => { 939 | try { 940 | let callback = (err: any, jobs: sc_contracts.CronJobInfo[]) => { 941 | if (err) { 942 | reject(err); 943 | } 944 | else { 945 | resolve(jobs); 946 | } 947 | }; 948 | 949 | vscode.commands.executeCommand('extension.cronJons.getJobs', callback).then(() => { 950 | //TODO 951 | }, (err) => { 952 | reject(err); 953 | }); 954 | } 955 | catch (e) { 956 | reject(e); 957 | } 958 | }); 959 | }, 960 | globalEvents: undefined, 961 | globals: me.getGlobals(), 962 | globalState: undefined, 963 | htmlEncode: function(str) { 964 | str = sc_helpers.toStringSafe(str); 965 | 966 | return (new HtmlEntities.AllHtmlEntities()).encode(str); 967 | }, 968 | log: function(msg) { 969 | me.log(msg); 970 | return this; 971 | }, 972 | nextValue: undefined, 973 | openHtml: (html, title, docId) => { 974 | return sc_helpers.openHtmlDocument(me.htmlDocuments, 975 | html, title, docId); 976 | }, 977 | options: sc_helpers.cloneObject(c.options), 978 | others: undefined, 979 | outputChannel: undefined, 980 | previousValue: undefined, 981 | require: function(id) { 982 | return require(id); 983 | }, 984 | restartCronJobs: (jobs) => { 985 | return new Promise((resolve, reject) => { 986 | try { 987 | vscode.commands.executeCommand('extension.cronJons.restartJobsByName', jobs).then((result) => { 988 | resolve(result); 989 | }, (err) => { 990 | reject(err); 991 | }); 992 | } 993 | catch (e) { 994 | reject(e); 995 | } 996 | }); 997 | }, 998 | startApi: () => { 999 | return new Promise((resolve, reject) => { 1000 | try { 1001 | vscode.commands.executeCommand('extension.restApi.startHost').then((result) => { 1002 | resolve(result); 1003 | }, (err) => { 1004 | reject(err); 1005 | }); 1006 | } 1007 | catch (e) { 1008 | reject(e); 1009 | } 1010 | }); 1011 | }, 1012 | startCronJobs: (jobs) => { 1013 | return new Promise((resolve, reject) => { 1014 | try { 1015 | vscode.commands.executeCommand('extension.cronJons.startJobsByName', jobs).then((result) => { 1016 | resolve(result); 1017 | }, (err) => { 1018 | reject(err); 1019 | }); 1020 | } 1021 | catch (e) { 1022 | reject(e); 1023 | } 1024 | }); 1025 | }, 1026 | stopApi: () => { 1027 | return new Promise((resolve, reject) => { 1028 | try { 1029 | vscode.commands.executeCommand('extension.restApi.stopHost').then((result) => { 1030 | resolve(result); 1031 | }, (err) => { 1032 | reject(err); 1033 | }); 1034 | } 1035 | catch (e) { 1036 | reject(e); 1037 | } 1038 | }); 1039 | }, 1040 | stopCronJobs: (jobs) => { 1041 | return new Promise((resolve, reject) => { 1042 | try { 1043 | vscode.commands.executeCommand('extension.cronJons.stopJobsByName', jobs).then((result) => { 1044 | resolve(result); 1045 | }, (err) => { 1046 | reject(err); 1047 | }); 1048 | } 1049 | catch (e) { 1050 | reject(e); 1051 | } 1052 | }); 1053 | }, 1054 | toHexView: (val) => { 1055 | return Hexy.hexy(val); 1056 | }, 1057 | }; 1058 | 1059 | // args.button 1060 | Object.defineProperty(args, 'button', { 1061 | configurable: true, 1062 | enumerable: true, 1063 | get: () => { return btn; }, 1064 | }); 1065 | 1066 | // args.events 1067 | Object.defineProperty(args, 'events', { 1068 | enumerable: true, 1069 | get: () => { return newEventEmitter; }, 1070 | }); 1071 | 1072 | // args.extension 1073 | Object.defineProperty(args, 'extension', { 1074 | enumerable: true, 1075 | get: () => { return me._CONTEXT; }, 1076 | }); 1077 | 1078 | // args.globalEvents 1079 | Object.defineProperty(args, 'globalEvents', { 1080 | enumerable: true, 1081 | get: () => { return Globals.EVENTS; }, 1082 | }); 1083 | 1084 | // args.globalState 1085 | Object.defineProperty(args, 'globalState', { 1086 | enumerable: true, 1087 | get: () => { return globalState; }, 1088 | }); 1089 | 1090 | // args.others 1091 | Object.defineProperty(args, 'others', { 1092 | enumerable: true, 1093 | get: () => { return me._COMMANDS.map(c => c.id) 1094 | .filter(c => c !== cmdId); }, 1095 | }); 1096 | 1097 | // args.outputChannel 1098 | Object.defineProperty(args, 'outputChannel', { 1099 | enumerable: true, 1100 | get: () => { return me.outputChannel; }, 1101 | }); 1102 | 1103 | // args.previousValue 1104 | Object.defineProperty(args, 'previousValue', { 1105 | enumerable: true, 1106 | get: () => { return prevVal; }, 1107 | }); 1108 | 1109 | let result = cmdModule.execute(args); 1110 | Promise.resolve(result).then((r) => { 1111 | completed(null, r); 1112 | }).catch((err) => { 1113 | completed(err); 1114 | }); 1115 | } 1116 | else { 1117 | completed(null); // no execute() function found 1118 | } 1119 | } 1120 | catch (e) { 1121 | completed(e); 1122 | } 1123 | }); 1124 | }); 1125 | 1126 | if (c.button) { 1127 | // status bar button 1128 | 1129 | // right alignment? 1130 | let alignment = vscode.StatusBarAlignment.Left; 1131 | if (sc_helpers.toBooleanSafe(c.button.isRight)) { 1132 | alignment = vscode.StatusBarAlignment.Right; 1133 | } 1134 | 1135 | let priority: number; 1136 | if (!sc_helpers.isNullOrUndefined(c.button.priority)) { 1137 | priority = parseFloat(sc_helpers.toStringSafe(c.button.priority).trim()); 1138 | } 1139 | 1140 | btn = vscode.window.createStatusBarItem(alignment, priority); 1141 | btn.command = cmdId; 1142 | 1143 | // caption 1144 | if (sc_helpers.isEmptyString(c.button.text)) { 1145 | btn.text = cmdId; 1146 | } 1147 | else { 1148 | btn.text = sc_helpers.toStringSafe(c.button.text); 1149 | } 1150 | 1151 | // tooltip 1152 | if (sc_helpers.isEmptyString(c.button.tooltip)) { 1153 | btn.tooltip = cmdId; 1154 | } 1155 | else { 1156 | btn.tooltip = sc_helpers.toStringSafe(c.button.tooltip); 1157 | } 1158 | 1159 | // color 1160 | let color = sc_helpers.normalizeString(c.button.color); 1161 | if ('' !== color) { 1162 | btn.color = color; 1163 | } 1164 | 1165 | if (sc_helpers.toBooleanSafe(c.button.show, true)) { 1166 | btn.show(); 1167 | } 1168 | } 1169 | 1170 | me._COMMANDS.push({ 1171 | button: btn, 1172 | command: cmd, 1173 | id: cmdId, 1174 | object: c, 1175 | }); 1176 | } 1177 | catch (e) { 1178 | disposeItems = true; 1179 | 1180 | me.log(`[ERROR] ScriptCommandController.reloadCommands(2)(${cmdId}): ${sc_helpers.toStringSafe(e)}`); 1181 | } 1182 | finally { 1183 | if (disposeItems) { 1184 | sc_helpers.tryDispose(btn); 1185 | sc_helpers.tryDispose(cmd); 1186 | } 1187 | } 1188 | }); 1189 | } 1190 | catch (e) { 1191 | me.log(`[ERROR] ScriptCommandController.reloadCommands(1): ${sc_helpers.toStringSafe(e)}`); 1192 | } 1193 | } 1194 | 1195 | /** 1196 | * Reloads configuration. 1197 | */ 1198 | public reloadConfiguration() { 1199 | const ME = this; 1200 | 1201 | if (ME._isReloadingConfig) { 1202 | return; 1203 | } 1204 | 1205 | try { 1206 | ME._isReloadingConfig = true; 1207 | 1208 | const SETTINGS_FILE = Path.join( 1209 | sc_workspace.getRootPath(), 1210 | './.vscode/settings.json', 1211 | ); 1212 | 1213 | const LOADED_CONFIG: sc_contracts.Configuration = vscode.workspace.getConfiguration('script.commands', 1214 | vscode.Uri.file(SETTINGS_FILE)) || {}; 1215 | 1216 | ME._config = LOADED_CONFIG; 1217 | ME._htmlDocs = []; 1218 | 1219 | ME.reloadCommands(); 1220 | 1221 | // reset all "quick" stuff 1222 | sc_quick.reset 1223 | .apply(ME, []); 1224 | 1225 | // startup commands 1226 | let commandsToExecute = ME.getCommands().filter(x => { 1227 | return sc_helpers.toBooleanSafe(x.onStartup); 1228 | }); 1229 | ME.executeScriptCommands(commandsToExecute).then(() => { 1230 | //TODO 1231 | }).catch((err) => { 1232 | vscode.window.showErrorMessage(`[vs-script-commands] Execution of script commands (ScriptCommandController.reloadConfiguration) failed: ${sc_helpers.toStringSafe(err)}`); 1233 | });; 1234 | 1235 | if (sc_helpers.toBooleanSafe(LOADED_CONFIG.showOutput)) { 1236 | this.outputChannel.show(); 1237 | } 1238 | 1239 | ME.showDeprecatedMessage().then(() => { 1240 | }).catch(() => { 1241 | }); 1242 | } 1243 | catch (e) { 1244 | ME.log(`[ERROR] ScriptCommandController.reloadConfiguration(1): ${sc_helpers.toStringSafe(e)}`); 1245 | } 1246 | finally { 1247 | ME._isReloadingConfig = false; 1248 | 1249 | ME._currentTextEditor = vscode.window.activeTextEditor; 1250 | } 1251 | } 1252 | 1253 | /** 1254 | * Selects a command and executes a command. 1255 | */ 1256 | protected selectAndExecuteCommand(entries: CommandEntry[]) { 1257 | let me = this; 1258 | let cfg = me.config; 1259 | 1260 | let cmdId: string; 1261 | let completed = (err?: any) => { 1262 | if (err) { 1263 | if (cmdId) { 1264 | vscode.window.showErrorMessage(`[vs-script-commands] Manual execution of ${cmdId} failed: ${sc_helpers.toStringSafe(err)}`); 1265 | } 1266 | else { 1267 | vscode.window.showErrorMessage(`[vs-script-commands] Manual execution failed: ${sc_helpers.toStringSafe(err)}`); 1268 | } 1269 | } 1270 | }; 1271 | 1272 | try { 1273 | entries = sc_helpers.asArray(entries) 1274 | .filter(x => x); 1275 | 1276 | if (entries.length > 0) { 1277 | let quickPicks = entries.map(x => { 1278 | let qp: CommandEntryQuickPickItem = { 1279 | description: x.description, 1280 | entry: x, 1281 | label: x.label, 1282 | }; 1283 | 1284 | return qp; 1285 | }); 1286 | 1287 | vscode.window.showQuickPick(quickPicks, { 1288 | placeHolder: `Select one of the ${entries.length} commands...`, 1289 | }).then((item) => { 1290 | if (!item) { 1291 | return; 1292 | } 1293 | 1294 | let args = [ item.entry.id ].concat(item.entry.arguments); 1295 | 1296 | let executeTheCommand = () => { 1297 | try { 1298 | vscode.commands.executeCommand.apply(null, args).then((result) => { 1299 | let exitCode = parseInt(sc_helpers.toStringSafe(result).trim()); 1300 | if (!isNaN(exitCode)) { 1301 | console.log(`[vs-script-commands] '${item.entry.id}' returned with exit code ${exitCode}`); 1302 | } 1303 | }, (err) => { 1304 | completed(err); 1305 | }); 1306 | } 1307 | catch (e) { 1308 | completed(e); 1309 | } 1310 | }; 1311 | 1312 | if (item.entry.askForArgument) { 1313 | vscode.window.showInputBox({ 1314 | placeHolder: 'Input the first argument for the execution here...', 1315 | }).then((value) => { 1316 | args = args.concat([ value ]); 1317 | 1318 | executeTheCommand(); 1319 | }, (err) => { 1320 | completed(err); 1321 | }); 1322 | } 1323 | else { 1324 | executeTheCommand(); 1325 | } 1326 | }, (err) => { 1327 | completed(err); 1328 | }); 1329 | } 1330 | else { 1331 | vscode.window.showWarningMessage(`[vs-script-commands] No command found that can be executed!`); 1332 | } 1333 | } 1334 | catch (e) { 1335 | completed(e); 1336 | } 1337 | } 1338 | 1339 | /** 1340 | * Set ups the file system watcher. 1341 | */ 1342 | protected setupFileSystemWatcher() { 1343 | let me = this; 1344 | 1345 | let createWatcher = () => { 1346 | me._fileSystemWatcher = null; 1347 | 1348 | let newWatcher: vscode.FileSystemWatcher; 1349 | try { 1350 | newWatcher = vscode.workspace.createFileSystemWatcher('**', 1351 | false, false, false); 1352 | newWatcher.onDidChange((e) => { 1353 | me.onFileChange(e, sc_contracts.FileChangeType.Changed); 1354 | }, newWatcher); 1355 | newWatcher.onDidCreate((e) => { 1356 | me.onFileChange(e, sc_contracts.FileChangeType.New); 1357 | }, newWatcher); 1358 | newWatcher.onDidDelete((e) => { 1359 | me.onFileChange(e, sc_contracts.FileChangeType.Deleted); 1360 | }, newWatcher); 1361 | 1362 | me._fileSystemWatcher = newWatcher; 1363 | } 1364 | catch (e) { 1365 | sc_helpers.tryDispose(newWatcher); 1366 | 1367 | me.log(`[ERROR] ScriptCommandController.setupFileSystemWatcher(2): ${sc_helpers.toStringSafe(e)}`); 1368 | } 1369 | }; 1370 | 1371 | try { 1372 | if (sc_helpers.tryDispose(me._fileSystemWatcher)) { 1373 | createWatcher(); 1374 | } 1375 | } 1376 | catch (e) { 1377 | this.log(`[ERROR] ScriptCommandController.setupFileSystemWatcher(1): ${sc_helpers.toStringSafe(e)}`); 1378 | } 1379 | } 1380 | 1381 | private async showDeprecatedMessage() { 1382 | const KEY_SHOW_DEPRECATED_MESSAGE = 'vsscShowDeprecatedMessage'; 1383 | 1384 | try { 1385 | this._CONTEXT 1386 | .globalState 1387 | .update('vsscLastKnownVersion', undefined); 1388 | } catch { } 1389 | 1390 | let newShowMessageValue: boolean; 1391 | try { 1392 | const SHOW_MESSAGE = sc_helpers.toBooleanSafe( 1393 | this._CONTEXT.globalState.get(KEY_SHOW_DEPRECATED_MESSAGE, true), 1394 | true 1395 | ); 1396 | 1397 | newShowMessageValue = SHOW_MESSAGE; 1398 | 1399 | if (SHOW_MESSAGE) { 1400 | const BTN = await vscode.window.showWarningMessage( 1401 | "[vs-script-commands] The extension has been DEPRECATED. You can try out 'vscode-powertools' for the future ...", 1402 | "Open 'vscode-powertools'", "Maybe next time", "Do not show again" 1403 | ); 1404 | 1405 | if ('Maybe next time' === BTN) { 1406 | newShowMessageValue = true; 1407 | } else { 1408 | newShowMessageValue = false; 1409 | } 1410 | 1411 | if ("Open 'vscode-powertools'" === BTN) { 1412 | await sc_helpers.open('https://marketplace.visualstudio.com/items?itemName=ego-digital.vscode-powertools'); 1413 | } 1414 | } 1415 | } catch (e) { 1416 | this.log(`[ERROR] Controller.showDeprecatedMessage(1): ${sc_helpers.toStringSafe(e)}`); 1417 | } finally { 1418 | try { 1419 | await this._CONTEXT.globalState.update( 1420 | KEY_SHOW_DEPRECATED_MESSAGE, 1421 | newShowMessageValue 1422 | ); 1423 | } catch { } 1424 | } 1425 | } 1426 | } 1427 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /// 4 | 5 | // The MIT License (MIT) 6 | // 7 | // vs-script-commands (https://github.com/mkloubert/vs-script-commands) 8 | // Copyright (c) Marcel Joachim Kloubert 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to 12 | // deal in the Software without restriction, including without limitation the 13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 14 | // sell copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in 18 | // all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | // DEALINGS IN THE SOFTWARE. 27 | 28 | import * as FS from 'fs'; 29 | import * as Path from 'path'; 30 | import * as sc_content from './content'; 31 | import * as sc_contracts from './contracts'; 32 | import * as sc_controller from './controller'; 33 | import * as sc_helpers from './helpers'; 34 | import * as sc_workspace from './workspace'; 35 | import * as vscode from 'vscode'; 36 | 37 | 38 | let controller: sc_controller.ScriptCommandController; 39 | 40 | export function activate(context: vscode.ExtensionContext) { 41 | // version 42 | let pkgFile: sc_contracts.PackageFile; 43 | try { 44 | pkgFile = JSON.parse(FS.readFileSync(Path.join(__dirname, '../../package.json'), 'utf8')); 45 | } 46 | catch (e) { 47 | sc_helpers.log(`[ERROR] extension.activate(): ${sc_helpers.toStringSafe(e)}`); 48 | } 49 | 50 | let outputChannel = vscode.window.createOutputChannel("Script Commands"); 51 | 52 | // show infos about the app 53 | { 54 | if (pkgFile) { 55 | outputChannel.appendLine(`${pkgFile.displayName} (${pkgFile.name}) - v${pkgFile.version}`); 56 | } 57 | 58 | outputChannel.appendLine(`Copyright (c) 2017 Marcel Joachim Kloubert `); 59 | outputChannel.appendLine(''); 60 | outputChannel.appendLine(`GitHub : https://github.com/mkloubert/vs-script-commands`); 61 | outputChannel.appendLine(`Twitter: https://twitter.com/mjkloubert`); 62 | outputChannel.appendLine(`Donate : [PayPal] https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UHVN4LRJTEXQS`); 63 | outputChannel.appendLine(` [Flattr] https://flattr.com/submit/auto?fid=o62pkd&url=https%3A%2F%2Fgithub.com%2Fmkloubert%2Fvs-script-commands`); 64 | 65 | outputChannel.appendLine(''); 66 | } 67 | 68 | let controller = new sc_controller.ScriptCommandController(context, outputChannel, pkgFile); 69 | sc_workspace.resetSelectedWorkspaceFolder(); 70 | 71 | // execute script command 72 | let executeCmd = vscode.commands.registerCommand('extension.scriptCommands.execute', () => { 73 | try { 74 | controller.executeCommand(); 75 | } 76 | catch (e) { 77 | vscode.window.showErrorMessage(`[EXECUTE SCRIPT COMMAND ERROR]: ${sc_helpers.toStringSafe(e)}`); 78 | } 79 | }); 80 | 81 | // execute Visual Studio Code command 82 | let executeVSCmd = vscode.commands.registerCommand('extension.scriptCommands.executeVSCode', () => { 83 | try { 84 | controller.executeVSCommand(); 85 | } 86 | catch (e) { 87 | vscode.window.showErrorMessage(`[EXECUTE SCRIPT COMMAND ERROR]: ${sc_helpers.toStringSafe(e)}`); 88 | } 89 | }); 90 | 91 | // quick JavaScript execution 92 | let quickExecution = vscode.commands.registerCommand('extension.scriptCommands.quickExecution', () => { 93 | try { 94 | controller.quickExecution(); 95 | } 96 | catch (e) { 97 | vscode.window.showErrorMessage(`[QUICK EXECUTION ERROR]: ${sc_helpers.toStringSafe(e)}`); 98 | } 99 | }); 100 | 101 | // select workspace 102 | let selectWorkspace = vscode.commands.registerCommand('extension.scriptCommands.selectWorkspace', async () => { 103 | try { 104 | const FOLDER = await sc_workspace.selectWorkspace(); 105 | if (FOLDER) { 106 | await Promise.resolve( 107 | controller.onDidChangeConfiguration() 108 | ); 109 | } 110 | } 111 | catch (e) { 112 | vscode.window.showErrorMessage(`[SELECT WORKSPACE ERROR]: ${sc_helpers.toStringSafe(e)}`); 113 | } 114 | }); 115 | 116 | // open HTML document 117 | let openHtmlDoc = vscode.commands.registerCommand('extension.scriptCommands.openHtmlDoc', (doc: sc_contracts.Document) => { 118 | try { 119 | let htmlDocs = controller.htmlDocuments; 120 | 121 | let url = vscode.Uri.parse(`vs-script-commands-html://authority/?id=${encodeURIComponent(sc_helpers.toStringSafe(doc.id))}` + 122 | `&x=${encodeURIComponent(sc_helpers.toStringSafe(new Date().getTime()))}`); 123 | 124 | let title = sc_helpers.toStringSafe(doc.title).trim(); 125 | if (!title) { 126 | title = `[vs-script-commands] HTML document #${sc_helpers.toStringSafe(doc.id)}`; 127 | } 128 | 129 | vscode.commands.executeCommand('vscode.previewHtml', url, vscode.ViewColumn.One, title).then((success) => { 130 | sc_helpers.removeDocuments(doc, htmlDocs); 131 | }, (err) => { 132 | sc_helpers.removeDocuments(doc, htmlDocs); 133 | 134 | sc_helpers.log(`[ERROR] extension.scriptCommands.openHtmlDoc(2): ${err}`); 135 | }); 136 | } 137 | catch (e) { 138 | sc_helpers.log(`[ERROR] extension.scriptCommands.openHtmlDoc(1): ${e}`); 139 | } 140 | }); 141 | 142 | let htmlViewer = vscode.workspace.registerTextDocumentContentProvider('vs-script-commands-html', 143 | new sc_content.HtmlTextDocumentContentProvider(controller)); 144 | 145 | // notfiy setting changes 146 | context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(controller.onDidChangeConfiguration, 147 | controller)); 148 | // notifiy on document has been saved 149 | context.subscriptions.push(vscode.workspace.onDidSaveTextDocument(controller.onDidSaveTextDocument, 150 | controller)); 151 | 152 | // notifiy on document is going to be saved 153 | context.subscriptions.push(vscode.workspace.onWillSaveTextDocument(controller.onWillSaveTextDocument, 154 | controller)); 155 | 156 | // notfiy open text editor 157 | context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(controller.onDidOpenTextDocument, 158 | controller)); 159 | // notfiy close text editor 160 | context.subscriptions.push(vscode.workspace.onDidCloseTextDocument(controller.onDidCloseTextDocument, 161 | controller)); 162 | // notfiy change text editor 163 | context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(controller.onDidChangeTextDocument, 164 | controller)); 165 | 166 | // notfiy active editor changed 167 | context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(controller.onDidChangeActiveTextEditor, 168 | controller)); 169 | 170 | context.subscriptions.push(controller, 171 | executeCmd, executeVSCmd, quickExecution, selectWorkspace, 172 | openHtmlDoc, 173 | htmlViewer); 174 | 175 | controller.onActivated(); 176 | } 177 | 178 | export function deactivate() { 179 | if (controller) { 180 | controller.onDeactivate(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/globals.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-script-commands (https://github.com/mkloubert/vs-script-commands) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as Events from 'events'; 27 | 28 | 29 | /** 30 | * The global event emitter. 31 | */ 32 | export const EVENTS: NodeJS.EventEmitter = new Events.EventEmitter(); 33 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-script-commands (https://github.com/mkloubert/vs-script-commands) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as ChildProcess from 'child_process'; 27 | import * as Crypto from 'crypto'; 28 | import * as FS from 'fs'; 29 | import * as HTTP from 'http'; 30 | import * as Path from 'path'; 31 | import * as Moment from 'moment'; 32 | import * as sc_contracts from './contracts'; 33 | import * as sc_workspace from './workspace'; 34 | import * as vscode from 'vscode'; 35 | 36 | /** 37 | * Options for open function. 38 | */ 39 | export interface OpenOptions { 40 | /** 41 | * The app (or options) to open. 42 | */ 43 | app?: string | string[]; 44 | /** 45 | * The custom working directory. 46 | */ 47 | cwd?: string; 48 | /** 49 | * Wait until exit or not. 50 | */ 51 | wait?: boolean; 52 | } 53 | 54 | /** 55 | * Describes a simple 'completed' action. 56 | * 57 | * @param {any} [err] The occurred error. 58 | * @param {TResult} [result] The result. 59 | */ 60 | export type SimpleCompletedAction = (err?: any, result?: TResult) => void; 61 | 62 | 63 | let nextHtmlDocId = -1; 64 | 65 | /** 66 | * Returns a value as array. 67 | * 68 | * @param {T | T[]} val The value. 69 | * 70 | * @return {T[]} The value as array. 71 | */ 72 | export function asArray(val: T | T[]): T[] { 73 | if (!Array.isArray(val)) { 74 | return [ val ]; 75 | } 76 | 77 | return val; 78 | } 79 | 80 | /** 81 | * Clones an object / value deep. 82 | * 83 | * @param {T} val The value / object to clone. 84 | * 85 | * @return {T} The cloned value / object. 86 | */ 87 | export function cloneObject(val: T): T { 88 | if (!val) { 89 | return val; 90 | } 91 | 92 | return JSON.parse(JSON.stringify(val)); 93 | } 94 | 95 | /** 96 | * Compares two values for a sort operation. 97 | * 98 | * @param {T} x The left value. 99 | * @param {T} y The right value. 100 | * 101 | * @return {number} The "sort value". 102 | */ 103 | export function compareValues(x: T, y: T): number { 104 | if (x === y) { 105 | return 0; 106 | } 107 | 108 | if (x > y) { 109 | return 1; 110 | } 111 | 112 | if (x < y) { 113 | return -1; 114 | } 115 | 116 | return 0; 117 | } 118 | 119 | /** 120 | * Creates a simple 'completed' callback for a promise. 121 | * 122 | * @param {Function} resolve The 'succeeded' callback. 123 | * @param {Function} reject The 'error' callback. 124 | * 125 | * @return {SimpleCompletedAction} The created action. 126 | */ 127 | export function createSimplePromiseCompletedAction(resolve: (value?: TResult | PromiseLike) => void, 128 | reject?: (reason: any) => void): SimpleCompletedAction { 129 | return (err?, result?) => { 130 | if (err) { 131 | if (reject) { 132 | reject(err); 133 | } 134 | } 135 | else { 136 | if (resolve) { 137 | resolve(result); 138 | } 139 | } 140 | }; 141 | } 142 | 143 | /** 144 | * Removes duplicate entries from an array. 145 | * 146 | * @param {T[]} arr The input array. 147 | * 148 | * @return {T[]} The filtered array. 149 | */ 150 | export function distinctArray(arr: T[]): T[] { 151 | if (!arr) { 152 | return arr; 153 | } 154 | 155 | return arr.filter((x, i) => { 156 | return arr.indexOf(x) === i; 157 | }); 158 | } 159 | 160 | /** 161 | * Removes duplicate entries from an array by using a selector. 162 | * 163 | * @param {T[]} arr The input array. 164 | * @param {Function} selector The selector to use. 165 | * 166 | * @return {T[]} The filtered array. 167 | */ 168 | export function distinctArrayBy(arr: T[], selector: (item: T) => U): T[] { 169 | if (!arr) { 170 | return arr; 171 | } 172 | 173 | if (!selector) { 174 | selector = (item) => item; 175 | } 176 | 177 | return arr.filter((x, i) => { 178 | return arr.map(y => selector(y)) 179 | .indexOf( selector(x) ) === i; 180 | }); 181 | } 182 | 183 | /** 184 | * Returns the value from a "parameter" object. 185 | * 186 | * @param {Object} params The object. 187 | * @param {string} name The name of the parameter. 188 | * 189 | * @return {string} The value of the parameter (if found). 190 | */ 191 | export function getUrlParam(params: Object, name: string): string { 192 | if (params) { 193 | name = normalizeString(name); 194 | 195 | for (let p in params) { 196 | if (normalizeString(p) === name) { 197 | return toStringSafe(params[p]); 198 | } 199 | } 200 | } 201 | } 202 | 203 | /** 204 | * Hashes data. 205 | * 206 | * @param {string} algo The algorithm to use. 207 | * @param {string|Buffer} data 208 | * @param {boolean} [raw] Return hash in binary format or as (hext) string. 209 | */ 210 | export function hash(algo: string, data: string | Buffer, asBuffer = false): string | Buffer { 211 | if (isNullOrUndefined(data)) { 212 | return data; 213 | } 214 | 215 | algo = normalizeString(algo); 216 | if ('' === algo) { 217 | algo = 'sha256'; 218 | } 219 | 220 | asBuffer = toBooleanSafe(asBuffer); 221 | 222 | let hash = Crypto.createHash(algo) 223 | .update(data) 224 | .digest(); 225 | 226 | return asBuffer ? hash : hash.toString('hex'); 227 | } 228 | 229 | /** 230 | * Checks if the string representation of a value is empty 231 | * or contains whitespaces only. 232 | * 233 | * @param {any} val The value to check. 234 | * 235 | * @return {boolean} Is empty or not. 236 | */ 237 | export function isEmptyString(val: any): boolean { 238 | return '' === toStringSafe(val).trim(); 239 | } 240 | 241 | /** 242 | * Checks if a value is (null) or (undefined). 243 | * 244 | * @param {any} val The value to check. 245 | * 246 | * @return {boolean} Is (null)/(undefined) or not. 247 | */ 248 | export function isNullOrUndefined(val: any): boolean { 249 | return null === val || 250 | 'undefined' === typeof val; 251 | } 252 | 253 | /** 254 | * Loads a module. 255 | * 256 | * @param {string} file The path of the module's file. 257 | * @param {boolean} useCache Use cache or not. 258 | * 259 | * @return {TModule} The loaded module. 260 | */ 261 | export function loadModule(file: string, useCache: boolean = false): TModule { 262 | if (!Path.isAbsolute(file)) { 263 | file = Path.join(sc_workspace.getRootPath(), file); 264 | } 265 | file = Path.resolve(file); 266 | 267 | let stats = FS.lstatSync(file); 268 | if (!stats.isFile()) { 269 | throw new Error(`'${file}' is NO file!`); 270 | } 271 | 272 | if (!useCache) { 273 | delete require.cache[file]; // remove from cache 274 | } 275 | 276 | return require(file); 277 | } 278 | 279 | /** 280 | * Logs a message. 281 | * 282 | * @param {any} msg The message to log. 283 | */ 284 | export function log(msg: any) { 285 | let now = Moment(); 286 | 287 | msg = toStringSafe(msg); 288 | console.log(`[vs-script-commands :: ${now.format('YYYY-MM-DD HH:mm:ss')}] => ${msg}`); 289 | } 290 | 291 | /** 292 | * Normalizes a value as string so that is comparable. 293 | * 294 | * @param {any} val The value to convert. 295 | * @param {(str: string) => string} [normalizer] The custom normalizer. 296 | * 297 | * @return {string} The normalized value. 298 | */ 299 | export function normalizeString(val: any, normalizer?: (str: string) => string): string { 300 | if (!normalizer) { 301 | normalizer = (str) => str.toLowerCase().trim(); 302 | } 303 | 304 | return normalizer(toStringSafe(val)); 305 | } 306 | 307 | /** 308 | * Opens a target. 309 | * 310 | * @param {string} target The target to open. 311 | * @param {OpenOptions} [opts] The custom options to set. 312 | * 313 | * @param {Promise} The promise. 314 | */ 315 | export function open(target: string, opts?: OpenOptions): Promise { 316 | let me = this; 317 | 318 | if (!opts) { 319 | opts = {}; 320 | } 321 | 322 | opts.wait = toBooleanSafe(opts.wait, true); 323 | 324 | return new Promise((resolve, reject) => { 325 | let completed = (err?: any, cp?: ChildProcess.ChildProcess) => { 326 | if (err) { 327 | reject(err); 328 | } 329 | else { 330 | resolve(cp); 331 | } 332 | }; 333 | 334 | try { 335 | if (typeof target !== 'string') { 336 | throw new Error('Expected a `target`'); 337 | } 338 | 339 | let cmd: string; 340 | let appArgs: string[] = []; 341 | let args: string[] = []; 342 | let cpOpts: ChildProcess.SpawnOptions = { 343 | cwd: opts.cwd || sc_workspace.getRootPath(), 344 | }; 345 | 346 | if (Array.isArray(opts.app)) { 347 | appArgs = opts.app.slice(1); 348 | opts.app = opts.app[0]; 349 | } 350 | 351 | if (process.platform === 'darwin') { 352 | // Apple 353 | 354 | cmd = 'open'; 355 | 356 | if (opts.wait) { 357 | args.push('-W'); 358 | } 359 | 360 | if (opts.app) { 361 | args.push('-a', opts.app); 362 | } 363 | } 364 | else if (process.platform === 'win32') { 365 | // Microsoft 366 | 367 | cmd = 'cmd'; 368 | args.push('/c', 'start', '""'); 369 | target = target.replace(/&/g, '^&'); 370 | 371 | if (opts.wait) { 372 | args.push('/wait'); 373 | } 374 | 375 | if (opts.app) { 376 | args.push(opts.app); 377 | } 378 | 379 | if (appArgs.length > 0) { 380 | args = args.concat(appArgs); 381 | } 382 | } 383 | else { 384 | // Unix / Linux 385 | 386 | if (opts.app) { 387 | cmd = opts.app; 388 | } else { 389 | cmd = Path.join(__dirname, 'xdg-open'); 390 | } 391 | 392 | if (appArgs.length > 0) { 393 | args = args.concat(appArgs); 394 | } 395 | 396 | if (!opts.wait) { 397 | // xdg-open will block the process unless 398 | // stdio is ignored even if it's unref'd 399 | cpOpts.stdio = 'ignore'; 400 | } 401 | } 402 | 403 | args.push(target); 404 | 405 | if (process.platform === 'darwin' && appArgs.length > 0) { 406 | args.push('--args'); 407 | args = args.concat(appArgs); 408 | } 409 | 410 | let cp = ChildProcess.spawn(cmd, args, cpOpts); 411 | 412 | if (opts.wait) { 413 | cp.once('error', (err) => { 414 | completed(err); 415 | }); 416 | 417 | cp.once('close', function (code) { 418 | if (code > 0) { 419 | completed(new Error('Exited with code ' + code)); 420 | return; 421 | } 422 | 423 | completed(null, cp); 424 | }); 425 | } 426 | else { 427 | cp.unref(); 428 | 429 | completed(null, cp); 430 | } 431 | } 432 | catch (e) { 433 | completed(e); 434 | } 435 | }); 436 | } 437 | 438 | /** 439 | * Opens a HTML document in a new tab for a document storage. 440 | * 441 | * @param {sc_contracts.Document[]} storage The storage to open for. 442 | * @param {string} html The HTML document (source code). 443 | * @param {string} [title] The custom title for the tab. 444 | * @param {any} [id] The custom ID for the document in the storage. 445 | * 446 | * @returns {Promise} The promise. 447 | */ 448 | export function openHtmlDocument(storage: sc_contracts.Document[], 449 | html: string, title?: string, id?: any): Promise { 450 | return new Promise((resolve, reject) => { 451 | let completed = createSimplePromiseCompletedAction(resolve, reject); 452 | 453 | try { 454 | let body: Buffer; 455 | let enc = 'utf8'; 456 | if (html) { 457 | body = new Buffer(toStringSafe(html), enc); 458 | } 459 | 460 | if (isNullOrUndefined(id)) { 461 | id = 'vsscGlobalHtmlDocs::22c1e089-269f-44e8-b112-c56549deaaaa::' + (++nextHtmlDocId); 462 | } 463 | 464 | let doc: sc_contracts.Document = { 465 | body: body, 466 | encoding: enc, 467 | id: id, 468 | mime: 'text/html', 469 | }; 470 | 471 | if (!isEmptyString(title)) { 472 | doc.title = toStringSafe(title).trim(); 473 | } 474 | 475 | if (storage) { 476 | storage.push(doc); 477 | } 478 | 479 | vscode.commands.executeCommand('extension.scriptCommands.openHtmlDoc', doc).then((result: any) => { 480 | completed(null, result); 481 | }, (err) => { 482 | completed(err); 483 | }); 484 | } 485 | catch (e) { 486 | completed(e); 487 | } 488 | }); 489 | } 490 | 491 | /** 492 | * Reads the content of the HTTP request body. 493 | * 494 | * @param {HTTP.IncomingMessag} msg The HTTP message with the body. 495 | * 496 | * @returns {Promise} The promise. 497 | */ 498 | export function readHttpBody(msg: HTTP.IncomingMessage): Promise { 499 | return new Promise((resolve, reject) => { 500 | let buff: Buffer; 501 | let completedInvoked = false; 502 | 503 | let completed = (err: any) => { 504 | if (completedInvoked) { 505 | return; 506 | } 507 | 508 | completedInvoked = true; 509 | 510 | if (err) { 511 | reject(err); 512 | } 513 | else { 514 | resolve(buff); 515 | } 516 | }; 517 | 518 | try { 519 | buff = Buffer.alloc(0); 520 | 521 | msg.once('error', (err) => { 522 | completed(err); 523 | }); 524 | 525 | msg.on('data', (chunk) => { 526 | try { 527 | if (chunk && chunk.length > 0) { 528 | if ('string' === typeof chunk) { 529 | chunk = new Buffer(chunk, 'ascii'); 530 | } 531 | 532 | buff = Buffer.concat([ buff, chunk ]); 533 | } 534 | } 535 | catch (e) { 536 | completed(e); 537 | } 538 | }); 539 | 540 | msg.once('end', () => { 541 | resolve(buff); 542 | }); 543 | } 544 | catch (e) { 545 | completed(e); 546 | } 547 | }); 548 | } 549 | 550 | /** 551 | * Removes documents from a storage. 552 | * 553 | * @param {sc_contracts.Document|sc_contracts.Document[]} docs The document(s) to remove. 554 | * @param {sc_contracts.Document[]} storage The storage. 555 | * 556 | * @return {sc_contracts.Document[]} The removed documents. 557 | */ 558 | export function removeDocuments(docs: sc_contracts.Document | sc_contracts.Document[], 559 | storage: sc_contracts.Document[]): sc_contracts.Document[] { 560 | let ids = asArray(docs).filter(x => x) 561 | .map(x => x.id); 562 | 563 | let removed = []; 564 | 565 | if (storage) { 566 | for (let i = 0; i < storage.length; ) { 567 | let d = storage[i]; 568 | if (ids.indexOf(d.id) > -1) { 569 | removed.push(d); 570 | storage.splice(i, 1); 571 | } 572 | else { 573 | ++i; 574 | } 575 | } 576 | } 577 | 578 | return removed; 579 | } 580 | 581 | /** 582 | * Replaces all occurrences of a string. 583 | * 584 | * @param {any} str The input string. 585 | * @param {any} searchValue The value to search for. 586 | * @param {any} replaceValue The value to replace 'searchValue' with. 587 | * 588 | * @return {string} The output string. 589 | */ 590 | export function replaceAllStrings(str: any, searchValue: any, replaceValue: any) { 591 | str = toStringSafe(str); 592 | searchValue = toStringSafe(searchValue); 593 | replaceValue = toStringSafe(replaceValue); 594 | 595 | return str.split(searchValue) 596 | .join(replaceValue); 597 | } 598 | 599 | /** 600 | * Sorts a list of commands. 601 | * 602 | * @param {deploy_contracts.ScriptCommand[]} pkgs The input list. 603 | * @param {deploy_contracts.ValueProvider} [nameProvider] The custom function that provides the name of the machine. 604 | * 605 | * @return {deploy_contracts.ScriptCommand[]} The sorted list. 606 | */ 607 | export function sortCommands(pkgs: sc_contracts.ScriptCommand[], 608 | nameProvider?: sc_contracts.ValueProvider): sc_contracts.ScriptCommand[] { 609 | if (!pkgs) { 610 | pkgs = []; 611 | } 612 | 613 | return pkgs.filter(x => x) 614 | .map((x, i) => { 615 | let sortValue = x.sortOrder; 616 | if (isNullOrUndefined(sortValue)) { 617 | sortValue = 0; 618 | } 619 | sortValue = parseFloat(toStringSafe(sortValue).trim()); 620 | 621 | if (isNaN(sortValue)) { 622 | sortValue = 0; 623 | } 624 | 625 | return { 626 | index: i, 627 | level0: sortValue, // first sort by "sortOrder" 628 | level1: toStringSafe(x.displayName).toLowerCase().trim(), // then by "displayName" 629 | level2: toStringSafe(x.id).toLowerCase().trim(), // then by "ID" 630 | value: x, 631 | }; 632 | }) 633 | .sort((x, y) => { 634 | let comp0 = compareValues(x.level0, y.level0); 635 | if (0 != comp0) { 636 | return comp0; 637 | } 638 | 639 | let comp1 = compareValues(x.level1, y.level1); 640 | if (0 != comp1) { 641 | return comp1; 642 | } 643 | 644 | let comp2 = compareValues(x.level2, y.level2); 645 | if (0 != comp2) { 646 | return comp2; 647 | } 648 | 649 | return compareValues(x.index, y.index); 650 | }) 651 | .map(x => x.value); 652 | } 653 | 654 | /** 655 | * Returns an array like object as new array. 656 | * 657 | * @param {ArrayLike} arr The input object. 658 | * @param {boolean} [normalize] Returns an empty array, if input object is (null) / undefined. 659 | * 660 | * @return {T[]} The input object as array. 661 | */ 662 | export function toArray(arr: ArrayLike, normalize = true): T[] { 663 | if (isNullOrUndefined(arr)) { 664 | if (toBooleanSafe(normalize)) { 665 | return []; 666 | } 667 | 668 | return arr; 669 | } 670 | 671 | let newArray: T[] = []; 672 | for (let i = 0; i < arr.length; i++) { 673 | newArray.push(arr[i]); 674 | } 675 | 676 | return newArray; 677 | } 678 | 679 | /** 680 | * Converts a value to a boolean. 681 | * 682 | * @param {any} val The value to convert. 683 | * @param {any} defaultValue The value to return if 'val' is (null) or (undefined). 684 | * 685 | * @return {boolean} The converted value. 686 | */ 687 | export function toBooleanSafe(val: any, defaultValue: any = false): boolean { 688 | if (isNullOrUndefined(val)) { 689 | return defaultValue; 690 | } 691 | 692 | return !!val; 693 | } 694 | 695 | /** 696 | * Converts a value to a string that is NOT (null) or (undefined). 697 | * 698 | * @param {any} str The input value. 699 | * @param {any} defValue The default value. 700 | * 701 | * @return {string} The output value. 702 | */ 703 | export function toStringSafe(str: any, defValue: any = ''): string { 704 | if (!str) { 705 | str = ''; 706 | } 707 | str = '' + str; 708 | if (!str) { 709 | str = defValue; 710 | } 711 | 712 | return str; 713 | } 714 | 715 | /** 716 | * Tries to dispose an object. 717 | * 718 | * @param {vscode.Disposable} obj The object to dispose. 719 | * 720 | * @return {boolean} Operation was successful or not. 721 | */ 722 | export function tryDispose(obj: vscode.Disposable): boolean { 723 | try { 724 | if (obj) { 725 | obj.dispose(); 726 | } 727 | 728 | return true; 729 | } 730 | catch (e) { 731 | log(`[ERROR] helpers.tryDispose(): ${toStringSafe(e)}`); 732 | 733 | return false; 734 | } 735 | } 736 | 737 | /** 738 | * Extracts the query parameters of an URI to an object. 739 | * 740 | * @param {vscode.Uri} uri The URI. 741 | * 742 | * @return {Object} The parameters of the URI as object. 743 | */ 744 | export function uriParamsToObject(uri: vscode.Uri): Object { 745 | if (!uri) { 746 | return uri; 747 | } 748 | 749 | let params: any; 750 | if (!isEmptyString(uri.query)) { 751 | // s. https://css-tricks.com/snippets/jquery/get-query-params-object/ 752 | params = uri.query.replace(/(^\?)/,'') 753 | .split("&") 754 | .map(function(n) { return n = n.split("="), this[normalizeString(n[0])] = 755 | toStringSafe(decodeURIComponent(n[1])), this} 756 | .bind({}))[0]; 757 | } 758 | 759 | if (!params) { 760 | params = {}; 761 | } 762 | 763 | return params; 764 | } 765 | -------------------------------------------------------------------------------- /src/resources.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-script-commands (https://github.com/mkloubert/vs-script-commands) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | 27 | export const HTML_FOOTER = ` 28 | 29 | 42 | 43 | `; 44 | 45 | export const HTML_HEADER = ` 46 | 47 | 48 | 49 | 50 | 51 | 52 | [vs-script-commands] Quick execution help 53 | 54 | 146 | 147 | 148 | 149 | 150 | `; 151 | -------------------------------------------------------------------------------- /src/urls.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-script-commands (https://github.com/mkloubert/vs-script-commands) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | 27 | /** 28 | * https://github.com/mkloubert/vs-script-commands/blob/master/CHANGELOG.md 29 | */ 30 | export const CHANGELOG = 'https://goo.gl/i2XOiX'; 31 | -------------------------------------------------------------------------------- /src/workspace.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-script-commands (https://github.com/mkloubert/vs-script-commands) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as Path from 'path'; 27 | import * as vscode from 'vscode'; 28 | 29 | 30 | let currentFolder: vscode.WorkspaceFolder | false = false; 31 | 32 | /** 33 | * Returns the root path of the selected workspace folder. 34 | * 35 | * @return {string} The root path. 36 | */ 37 | export function getRootPath() { 38 | let folder: vscode.WorkspaceFolder; 39 | 40 | if (false === currentFolder) { 41 | if (vscode.workspace.workspaceFolders) { 42 | if (vscode.workspace.workspaceFolders.length > 0) { 43 | folder = vscode.workspace.workspaceFolders[0]; 44 | } 45 | } 46 | } 47 | else { 48 | folder = currentFolder; 49 | } 50 | 51 | let workspace_root: string; 52 | 53 | if (folder) { 54 | workspace_root = vscode.workspace.getWorkspaceFolder(folder.uri) 55 | .uri 56 | .fsPath; 57 | } 58 | else { 59 | try { 60 | workspace_root = vscode.workspace.rootPath; 61 | } 62 | catch (e) { 63 | //TODO: log 64 | 65 | workspace_root = undefined; 66 | } 67 | } 68 | 69 | if ('undefined' !== typeof workspace_root) { 70 | return Path.resolve(workspace_root); 71 | } 72 | } 73 | 74 | /** 75 | * Resets the selected workspace folder. 76 | */ 77 | export function resetSelectedWorkspaceFolder() { 78 | currentFolder = false; 79 | } 80 | 81 | /** 82 | * Selects the workspace. 83 | * 84 | * @return {Promise} The promise with the folder (if selected). 85 | */ 86 | export async function selectWorkspace() { 87 | const FOLDER = await vscode.window.showWorkspaceFolderPick(); 88 | if (FOLDER) { 89 | currentFolder = FOLDER; 90 | } 91 | 92 | return FOLDER; 93 | } 94 | 95 | /** 96 | * Sets the current workspace (folder). 97 | * 98 | * @param {vscode.WorkspaceFolder} wsf The new folder. 99 | * 100 | * @return {vscode.WorkspaceFolder|false} The new folder. 101 | */ 102 | export function setWorkspace(wsf: vscode.WorkspaceFolder) { 103 | return currentFolder = wsf || false; 104 | } 105 | -------------------------------------------------------------------------------- /test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import * as myExtension from '../src/extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", () => { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", () => { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } --------------------------------------------------------------------------------