├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── LICENSE ├── README.md ├── media ├── file-change.gif ├── folder-change.gif └── lightning.png ├── package-lock.json ├── package.json ├── src ├── command-runner.ts ├── config-controller.ts ├── config-validator.ts ├── extension.ts ├── file-watcher.ts ├── output-channel.ts ├── status-bar.ts ├── types.ts └── utils.ts ├── syntaxes └── file-watcher-output.tmLanguage ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # js output 2 | out 3 | 4 | # VS Code extension package 5 | *.vsix 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # TypeScript cache 54 | *.tsbuildinfo 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache/ 64 | .rts2_cache_cjs/ 65 | .rts2_cache_es/ 66 | .rts2_cache_umd/ 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variables file 78 | .env 79 | .env.test 80 | 81 | # parcel-bundler cache (https://parceljs.org/) 82 | .cache 83 | 84 | # Next.js build output 85 | .next 86 | 87 | # Nuxt.js build / generate output 88 | .nuxt 89 | dist 90 | 91 | # Gatsby files 92 | .cache/ 93 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 94 | # https://nextjs.org/blog/next-9-1#public-directory-support 95 | # public 96 | 97 | # vuepress build output 98 | .vuepress/dist 99 | 100 | # Serverless directories 101 | .serverless/ 102 | 103 | # FuseBox cache 104 | .fusebox/ 105 | 106 | # DynamoDB Local files 107 | .dynamodb/ 108 | 109 | # TernJS port file 110 | .tern-port 111 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 10 | "sourceMaps": true, 11 | "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ], 12 | "preLaunchTask": "npm: watch" 13 | }, 14 | { 15 | "name": "Extension Tests", 16 | "type": "extensionHost", 17 | "request": "launch", 18 | "runtimeExecutable": "${execPath}", 19 | "args": [ 20 | "--extensionDevelopmentPath=${workspaceFolder}", 21 | "--extensionTestsPath=${workspaceFolder}/out/test" 22 | ], 23 | "outFiles": [ 24 | "${workspaceFolder}/out/test/**/*.js" 25 | ], 26 | "preLaunchTask": "npm: watch" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | src/** 3 | .gitignore 4 | **/tsconfig.json 5 | **/tslint.json 6 | **/*.map 7 | **/*.ts -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Appulate 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 | # File & Folder Watcher 2 | 3 | This extension allows configuring commands that get run whenever a file is saved (changed, deleted, renamed, created) or folder is changed (deleted, created) in vscode. 4 | 5 | ## Features 6 | * Following events: onSaveDocument, onFileDelete, onFileRename, onFileCreate, onFolderChange, onFolderDelete and onFolderCreate: 7 | * **onFileChange** - allows you to track changes to the contents of an existing file when you save the file 8 | * **onFileChangeImmediate** - allows to track changes to the contents of an existing file 9 | * **onFileRename** - allows to track the renaming of file(s) 10 | * **onFileCreate** - allows to track the creation of file(s) 11 | * **onFolderChange** - allows to track the modification of files 12 | * **onFolderDelete** - allows to track the deletion of files 13 | * **onFolderCreate** - allows to track the addition of files 14 | * **case insensitive** workspace root folder 15 | * Configure multiple commands that run when the event happened 16 | * Regex pattern matching for files that trigger commands running 17 | * Sync and async support 18 | 19 | ## Configuration 20 | Add "filewatcher" configuration to user or workspace settings. 21 | * "shell" - (optional) common shell path to be used with child_process.exec options that runs commands. 22 | * "autoClearConsole" - (optional) clear VSCode output console every time commands run. Defaults to false. 23 | * "isClearStatusBar" - (optional) returns the status bar to its normal position (after receiving a 'Success' or 'Error' status) after a some time. commands. 24 | * "statusBarDelay" - (optional) the time after which the status returns to normal. Only works if isClearStatusBar === true. Default is 5000ms 25 | * "isSyncRunEvents" - (optional) launches event handlers of the same name with the appropriate pattern 'match' or 'notMatch' (e.g. onFileChange and onFolderChange) in synchronous or asynchronous mode. 26 | * "successTextColor" - (optional) color of successful completion of the process in the status bar. 27 | * "runTextColor" - (optional) color during the execution of the process in the status bar. 28 | * "commands" - array of commands that will be run whenever a file is saved. 29 | * "shell" - (optional) unique shell to execute the command with (gets passed to child_process.exec as an options arg. e.g. child_process(cmd, { shell }). 30 | * "match" - a regex for matching which files to run commands on. 31 | * "notMatch" - a regex for matching files *not* to run commands on. 32 | > NOTE Since this is a Regex, and also in a JSON string backslashes have to be double escaped such as when targetting folders. e.g. "match": "some\\\\folder\\\\.*" 33 | * "cmd" - command to run. Can include parameters that will be replaced at runtime (see Placeholder Tokens section below). 34 | * "vscodeTask" - Name of a VS Code task defined in tasks.json or commands to execute. Only works if cmd value does not exist. Must be a string (e.g. command id) or array of strings (e.g. ['workbench.action.tasks.runTask', 'some-task-name'] and etc.) 35 | > NOTE Since there is no universal listener for the all execution of the vscode commands and tasks, which will wait for the end of the process execution. The logs in the output and the status bar will only show whether the command was run or not. 36 | * "isAsync" (optional) - defaults to false. If true, next command will be run before this one finishes. 37 | 38 | ## Placeholder Tokens 39 | Commands support placeholders similar to tasks.json. 40 | 41 | * ${workspaceRoot}: **case insensitive** workspace root folder 42 | * ${workspaceRelativeDir}: **case insensitive** is the file relative path to the workspaceRoot 43 | * ${currentWorkspace}: **case insensitive** current working folder 44 | * ${currentRelativeWorkspace}: **case insensitive** file relative path to the current working folder 45 | * ${file}: path of tracking file 46 | * ${fileOld}: path of tracking file when renaming 47 | * ${fileBasename}: saved file's basename 48 | * ${fileDirname}: directory name of tracking file 49 | * ${fileExtname}: extension (including .) of tracking file 50 | * ${fileBasenameNoExt}: tracking file's basename without extension 51 | 52 | Samples 53 | ========= 54 | 55 | **File content** change tracking example. 56 | Configuration in \.vscode\settings.json 57 | 58 | { 59 | "filewatcher.commands": [ 60 | { 61 | "match": "\\.ts*", 62 | "isAsync": true, 63 | "cmd": "echo '${file} file content Changed'", 64 | "event": "onFileChange" 65 | } 66 | ] 67 | } 68 | 69 | 70 | ![ screencast ](media/file-change.gif) 71 | 72 | **Folder content** change tracking example. 73 | Configuration in \.vscode\settings.json 74 | 75 | { 76 | "filewatcher.commands": [ 77 | { 78 | "match": "\\.*", 79 | "isAsync": true, 80 | "cmd": "echo '${file} folder content Changed'", 81 | "event": "onFolderChange" 82 | } 83 | ] 84 | } 85 | 86 | ![ screencast ](media/folder-change.gif) 87 | -------------------------------------------------------------------------------- /media/file-change.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appulate/vscode-file-watcher/e1084661ba133ca782444c217c460f824219b9b3/media/file-change.gif -------------------------------------------------------------------------------- /media/folder-change.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appulate/vscode-file-watcher/e1084661ba133ca782444c217c460f824219b9b3/media/folder-change.gif -------------------------------------------------------------------------------- /media/lightning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appulate/vscode-file-watcher/e1084661ba133ca782444c217c460f824219b9b3/media/lightning.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filewatcher", 3 | "version": "2.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "filewatcher", 9 | "version": "2.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "ajv": "^8.12.0", 13 | "ajv-errors": "^3.0.0" 14 | }, 15 | "devDependencies": { 16 | "@types/node": "^18.7.16", 17 | "@types/vscode": "^1.71.0", 18 | "tslint": "^6.1.3", 19 | "typescript": "^5.1.3" 20 | }, 21 | "engines": { 22 | "vscode": "^1.71.0" 23 | } 24 | }, 25 | "node_modules/@babel/code-frame": { 26 | "version": "7.18.6", 27 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", 28 | "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", 29 | "dev": true, 30 | "dependencies": { 31 | "@babel/highlight": "^7.18.6" 32 | }, 33 | "engines": { 34 | "node": ">=6.9.0" 35 | } 36 | }, 37 | "node_modules/@babel/helper-validator-identifier": { 38 | "version": "7.18.6", 39 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", 40 | "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", 41 | "dev": true, 42 | "engines": { 43 | "node": ">=6.9.0" 44 | } 45 | }, 46 | "node_modules/@babel/highlight": { 47 | "version": "7.18.6", 48 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", 49 | "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", 50 | "dev": true, 51 | "dependencies": { 52 | "@babel/helper-validator-identifier": "^7.18.6", 53 | "chalk": "^2.0.0", 54 | "js-tokens": "^4.0.0" 55 | }, 56 | "engines": { 57 | "node": ">=6.9.0" 58 | } 59 | }, 60 | "node_modules/@types/node": { 61 | "version": "18.11.3", 62 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.3.tgz", 63 | "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==", 64 | "dev": true 65 | }, 66 | "node_modules/@types/vscode": { 67 | "version": "1.71.0", 68 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.71.0.tgz", 69 | "integrity": "sha512-nB50bBC9H/x2CpwW9FzRRRDrTZ7G0/POttJojvN/LiVfzTGfLyQIje1L1QRMdFXK9G41k5UJN/1B9S4of7CSzA==", 70 | "dev": true 71 | }, 72 | "node_modules/ajv": { 73 | "version": "8.12.0", 74 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", 75 | "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", 76 | "dependencies": { 77 | "fast-deep-equal": "^3.1.1", 78 | "json-schema-traverse": "^1.0.0", 79 | "require-from-string": "^2.0.2", 80 | "uri-js": "^4.2.2" 81 | }, 82 | "funding": { 83 | "type": "github", 84 | "url": "https://github.com/sponsors/epoberezkin" 85 | } 86 | }, 87 | "node_modules/ajv-errors": { 88 | "version": "3.0.0", 89 | "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", 90 | "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", 91 | "peerDependencies": { 92 | "ajv": "^8.0.1" 93 | } 94 | }, 95 | "node_modules/ansi-styles": { 96 | "version": "3.2.1", 97 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 98 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 99 | "dev": true, 100 | "dependencies": { 101 | "color-convert": "^1.9.0" 102 | }, 103 | "engines": { 104 | "node": ">=4" 105 | } 106 | }, 107 | "node_modules/argparse": { 108 | "version": "1.0.10", 109 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 110 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 111 | "dev": true, 112 | "dependencies": { 113 | "sprintf-js": "~1.0.2" 114 | } 115 | }, 116 | "node_modules/balanced-match": { 117 | "version": "1.0.2", 118 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 119 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 120 | "dev": true 121 | }, 122 | "node_modules/brace-expansion": { 123 | "version": "1.1.11", 124 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 125 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 126 | "dev": true, 127 | "dependencies": { 128 | "balanced-match": "^1.0.0", 129 | "concat-map": "0.0.1" 130 | } 131 | }, 132 | "node_modules/builtin-modules": { 133 | "version": "1.1.1", 134 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 135 | "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", 136 | "dev": true, 137 | "engines": { 138 | "node": ">=0.10.0" 139 | } 140 | }, 141 | "node_modules/chalk": { 142 | "version": "2.4.2", 143 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 144 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 145 | "dev": true, 146 | "dependencies": { 147 | "ansi-styles": "^3.2.1", 148 | "escape-string-regexp": "^1.0.5", 149 | "supports-color": "^5.3.0" 150 | }, 151 | "engines": { 152 | "node": ">=4" 153 | } 154 | }, 155 | "node_modules/color-convert": { 156 | "version": "1.9.3", 157 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 158 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 159 | "dev": true, 160 | "dependencies": { 161 | "color-name": "1.1.3" 162 | } 163 | }, 164 | "node_modules/color-name": { 165 | "version": "1.1.3", 166 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 167 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", 168 | "dev": true 169 | }, 170 | "node_modules/commander": { 171 | "version": "2.20.3", 172 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 173 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 174 | "dev": true 175 | }, 176 | "node_modules/concat-map": { 177 | "version": "0.0.1", 178 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 179 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 180 | "dev": true 181 | }, 182 | "node_modules/diff": { 183 | "version": "4.0.2", 184 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 185 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 186 | "dev": true, 187 | "engines": { 188 | "node": ">=0.3.1" 189 | } 190 | }, 191 | "node_modules/escape-string-regexp": { 192 | "version": "1.0.5", 193 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 194 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 195 | "dev": true, 196 | "engines": { 197 | "node": ">=0.8.0" 198 | } 199 | }, 200 | "node_modules/esprima": { 201 | "version": "4.0.1", 202 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 203 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 204 | "dev": true, 205 | "bin": { 206 | "esparse": "bin/esparse.js", 207 | "esvalidate": "bin/esvalidate.js" 208 | }, 209 | "engines": { 210 | "node": ">=4" 211 | } 212 | }, 213 | "node_modules/fast-deep-equal": { 214 | "version": "3.1.3", 215 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 216 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 217 | }, 218 | "node_modules/fs.realpath": { 219 | "version": "1.0.0", 220 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 221 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 222 | "dev": true 223 | }, 224 | "node_modules/function-bind": { 225 | "version": "1.1.1", 226 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 227 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 228 | "dev": true 229 | }, 230 | "node_modules/glob": { 231 | "version": "7.2.3", 232 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 233 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 234 | "dev": true, 235 | "dependencies": { 236 | "fs.realpath": "^1.0.0", 237 | "inflight": "^1.0.4", 238 | "inherits": "2", 239 | "minimatch": "^3.1.1", 240 | "once": "^1.3.0", 241 | "path-is-absolute": "^1.0.0" 242 | }, 243 | "engines": { 244 | "node": "*" 245 | }, 246 | "funding": { 247 | "url": "https://github.com/sponsors/isaacs" 248 | } 249 | }, 250 | "node_modules/has": { 251 | "version": "1.0.3", 252 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 253 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 254 | "dev": true, 255 | "dependencies": { 256 | "function-bind": "^1.1.1" 257 | }, 258 | "engines": { 259 | "node": ">= 0.4.0" 260 | } 261 | }, 262 | "node_modules/has-flag": { 263 | "version": "3.0.0", 264 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 265 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 266 | "dev": true, 267 | "engines": { 268 | "node": ">=4" 269 | } 270 | }, 271 | "node_modules/inflight": { 272 | "version": "1.0.6", 273 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 274 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 275 | "dev": true, 276 | "dependencies": { 277 | "once": "^1.3.0", 278 | "wrappy": "1" 279 | } 280 | }, 281 | "node_modules/inherits": { 282 | "version": "2.0.4", 283 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 284 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 285 | "dev": true 286 | }, 287 | "node_modules/is-core-module": { 288 | "version": "2.9.0", 289 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", 290 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", 291 | "dev": true, 292 | "dependencies": { 293 | "has": "^1.0.3" 294 | }, 295 | "funding": { 296 | "url": "https://github.com/sponsors/ljharb" 297 | } 298 | }, 299 | "node_modules/js-tokens": { 300 | "version": "4.0.0", 301 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 302 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 303 | "dev": true 304 | }, 305 | "node_modules/js-yaml": { 306 | "version": "3.14.1", 307 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 308 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 309 | "dev": true, 310 | "dependencies": { 311 | "argparse": "^1.0.7", 312 | "esprima": "^4.0.0" 313 | }, 314 | "bin": { 315 | "js-yaml": "bin/js-yaml.js" 316 | } 317 | }, 318 | "node_modules/json-schema-traverse": { 319 | "version": "1.0.0", 320 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 321 | "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" 322 | }, 323 | "node_modules/minimatch": { 324 | "version": "3.1.2", 325 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 326 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 327 | "dev": true, 328 | "dependencies": { 329 | "brace-expansion": "^1.1.7" 330 | }, 331 | "engines": { 332 | "node": "*" 333 | } 334 | }, 335 | "node_modules/minimist": { 336 | "version": "1.2.6", 337 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 338 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", 339 | "dev": true 340 | }, 341 | "node_modules/mkdirp": { 342 | "version": "0.5.6", 343 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 344 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 345 | "dev": true, 346 | "dependencies": { 347 | "minimist": "^1.2.6" 348 | }, 349 | "bin": { 350 | "mkdirp": "bin/cmd.js" 351 | } 352 | }, 353 | "node_modules/once": { 354 | "version": "1.4.0", 355 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 356 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 357 | "dev": true, 358 | "dependencies": { 359 | "wrappy": "1" 360 | } 361 | }, 362 | "node_modules/path-is-absolute": { 363 | "version": "1.0.1", 364 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 365 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 366 | "dev": true, 367 | "engines": { 368 | "node": ">=0.10.0" 369 | } 370 | }, 371 | "node_modules/path-parse": { 372 | "version": "1.0.7", 373 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 374 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 375 | "dev": true 376 | }, 377 | "node_modules/punycode": { 378 | "version": "2.3.0", 379 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", 380 | "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", 381 | "engines": { 382 | "node": ">=6" 383 | } 384 | }, 385 | "node_modules/require-from-string": { 386 | "version": "2.0.2", 387 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 388 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", 389 | "engines": { 390 | "node": ">=0.10.0" 391 | } 392 | }, 393 | "node_modules/resolve": { 394 | "version": "1.22.1", 395 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 396 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 397 | "dev": true, 398 | "dependencies": { 399 | "is-core-module": "^2.9.0", 400 | "path-parse": "^1.0.7", 401 | "supports-preserve-symlinks-flag": "^1.0.0" 402 | }, 403 | "bin": { 404 | "resolve": "bin/resolve" 405 | }, 406 | "funding": { 407 | "url": "https://github.com/sponsors/ljharb" 408 | } 409 | }, 410 | "node_modules/semver": { 411 | "version": "5.7.1", 412 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 413 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 414 | "dev": true, 415 | "bin": { 416 | "semver": "bin/semver" 417 | } 418 | }, 419 | "node_modules/sprintf-js": { 420 | "version": "1.0.3", 421 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 422 | "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", 423 | "dev": true 424 | }, 425 | "node_modules/supports-color": { 426 | "version": "5.5.0", 427 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 428 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 429 | "dev": true, 430 | "dependencies": { 431 | "has-flag": "^3.0.0" 432 | }, 433 | "engines": { 434 | "node": ">=4" 435 | } 436 | }, 437 | "node_modules/supports-preserve-symlinks-flag": { 438 | "version": "1.0.0", 439 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 440 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 441 | "dev": true, 442 | "engines": { 443 | "node": ">= 0.4" 444 | }, 445 | "funding": { 446 | "url": "https://github.com/sponsors/ljharb" 447 | } 448 | }, 449 | "node_modules/tslib": { 450 | "version": "1.14.1", 451 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 452 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 453 | "dev": true 454 | }, 455 | "node_modules/tslint": { 456 | "version": "6.1.3", 457 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", 458 | "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", 459 | "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", 460 | "dev": true, 461 | "dependencies": { 462 | "@babel/code-frame": "^7.0.0", 463 | "builtin-modules": "^1.1.1", 464 | "chalk": "^2.3.0", 465 | "commander": "^2.12.1", 466 | "diff": "^4.0.1", 467 | "glob": "^7.1.1", 468 | "js-yaml": "^3.13.1", 469 | "minimatch": "^3.0.4", 470 | "mkdirp": "^0.5.3", 471 | "resolve": "^1.3.2", 472 | "semver": "^5.3.0", 473 | "tslib": "^1.13.0", 474 | "tsutils": "^2.29.0" 475 | }, 476 | "bin": { 477 | "tslint": "bin/tslint" 478 | }, 479 | "engines": { 480 | "node": ">=4.8.0" 481 | }, 482 | "peerDependencies": { 483 | "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" 484 | } 485 | }, 486 | "node_modules/tsutils": { 487 | "version": "2.29.0", 488 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", 489 | "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", 490 | "dev": true, 491 | "dependencies": { 492 | "tslib": "^1.8.1" 493 | }, 494 | "peerDependencies": { 495 | "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" 496 | } 497 | }, 498 | "node_modules/typescript": { 499 | "version": "5.1.3", 500 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", 501 | "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", 502 | "dev": true, 503 | "bin": { 504 | "tsc": "bin/tsc", 505 | "tsserver": "bin/tsserver" 506 | }, 507 | "engines": { 508 | "node": ">=14.17" 509 | } 510 | }, 511 | "node_modules/uri-js": { 512 | "version": "4.4.1", 513 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 514 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 515 | "dependencies": { 516 | "punycode": "^2.1.0" 517 | } 518 | }, 519 | "node_modules/wrappy": { 520 | "version": "1.0.2", 521 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 522 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 523 | "dev": true 524 | } 525 | }, 526 | "dependencies": { 527 | "@babel/code-frame": { 528 | "version": "7.18.6", 529 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", 530 | "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", 531 | "dev": true, 532 | "requires": { 533 | "@babel/highlight": "^7.18.6" 534 | } 535 | }, 536 | "@babel/helper-validator-identifier": { 537 | "version": "7.18.6", 538 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", 539 | "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", 540 | "dev": true 541 | }, 542 | "@babel/highlight": { 543 | "version": "7.18.6", 544 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", 545 | "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", 546 | "dev": true, 547 | "requires": { 548 | "@babel/helper-validator-identifier": "^7.18.6", 549 | "chalk": "^2.0.0", 550 | "js-tokens": "^4.0.0" 551 | } 552 | }, 553 | "@types/node": { 554 | "version": "18.11.3", 555 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.3.tgz", 556 | "integrity": "sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==", 557 | "dev": true 558 | }, 559 | "@types/vscode": { 560 | "version": "1.71.0", 561 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.71.0.tgz", 562 | "integrity": "sha512-nB50bBC9H/x2CpwW9FzRRRDrTZ7G0/POttJojvN/LiVfzTGfLyQIje1L1QRMdFXK9G41k5UJN/1B9S4of7CSzA==", 563 | "dev": true 564 | }, 565 | "ajv": { 566 | "version": "8.12.0", 567 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", 568 | "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", 569 | "requires": { 570 | "fast-deep-equal": "^3.1.1", 571 | "json-schema-traverse": "^1.0.0", 572 | "require-from-string": "^2.0.2", 573 | "uri-js": "^4.2.2" 574 | } 575 | }, 576 | "ajv-errors": { 577 | "version": "3.0.0", 578 | "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", 579 | "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", 580 | "requires": {} 581 | }, 582 | "ansi-styles": { 583 | "version": "3.2.1", 584 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 585 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 586 | "dev": true, 587 | "requires": { 588 | "color-convert": "^1.9.0" 589 | } 590 | }, 591 | "argparse": { 592 | "version": "1.0.10", 593 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 594 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 595 | "dev": true, 596 | "requires": { 597 | "sprintf-js": "~1.0.2" 598 | } 599 | }, 600 | "balanced-match": { 601 | "version": "1.0.2", 602 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 603 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 604 | "dev": true 605 | }, 606 | "brace-expansion": { 607 | "version": "1.1.11", 608 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 609 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 610 | "dev": true, 611 | "requires": { 612 | "balanced-match": "^1.0.0", 613 | "concat-map": "0.0.1" 614 | } 615 | }, 616 | "builtin-modules": { 617 | "version": "1.1.1", 618 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 619 | "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", 620 | "dev": true 621 | }, 622 | "chalk": { 623 | "version": "2.4.2", 624 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 625 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 626 | "dev": true, 627 | "requires": { 628 | "ansi-styles": "^3.2.1", 629 | "escape-string-regexp": "^1.0.5", 630 | "supports-color": "^5.3.0" 631 | } 632 | }, 633 | "color-convert": { 634 | "version": "1.9.3", 635 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 636 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 637 | "dev": true, 638 | "requires": { 639 | "color-name": "1.1.3" 640 | } 641 | }, 642 | "color-name": { 643 | "version": "1.1.3", 644 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 645 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", 646 | "dev": true 647 | }, 648 | "commander": { 649 | "version": "2.20.3", 650 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 651 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 652 | "dev": true 653 | }, 654 | "concat-map": { 655 | "version": "0.0.1", 656 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 657 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 658 | "dev": true 659 | }, 660 | "diff": { 661 | "version": "4.0.2", 662 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 663 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 664 | "dev": true 665 | }, 666 | "escape-string-regexp": { 667 | "version": "1.0.5", 668 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 669 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 670 | "dev": true 671 | }, 672 | "esprima": { 673 | "version": "4.0.1", 674 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 675 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 676 | "dev": true 677 | }, 678 | "fast-deep-equal": { 679 | "version": "3.1.3", 680 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 681 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 682 | }, 683 | "fs.realpath": { 684 | "version": "1.0.0", 685 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 686 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 687 | "dev": true 688 | }, 689 | "function-bind": { 690 | "version": "1.1.1", 691 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 692 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 693 | "dev": true 694 | }, 695 | "glob": { 696 | "version": "7.2.3", 697 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 698 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 699 | "dev": true, 700 | "requires": { 701 | "fs.realpath": "^1.0.0", 702 | "inflight": "^1.0.4", 703 | "inherits": "2", 704 | "minimatch": "^3.1.1", 705 | "once": "^1.3.0", 706 | "path-is-absolute": "^1.0.0" 707 | } 708 | }, 709 | "has": { 710 | "version": "1.0.3", 711 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 712 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 713 | "dev": true, 714 | "requires": { 715 | "function-bind": "^1.1.1" 716 | } 717 | }, 718 | "has-flag": { 719 | "version": "3.0.0", 720 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 721 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 722 | "dev": true 723 | }, 724 | "inflight": { 725 | "version": "1.0.6", 726 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 727 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 728 | "dev": true, 729 | "requires": { 730 | "once": "^1.3.0", 731 | "wrappy": "1" 732 | } 733 | }, 734 | "inherits": { 735 | "version": "2.0.4", 736 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 737 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 738 | "dev": true 739 | }, 740 | "is-core-module": { 741 | "version": "2.9.0", 742 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", 743 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", 744 | "dev": true, 745 | "requires": { 746 | "has": "^1.0.3" 747 | } 748 | }, 749 | "js-tokens": { 750 | "version": "4.0.0", 751 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 752 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 753 | "dev": true 754 | }, 755 | "js-yaml": { 756 | "version": "3.14.1", 757 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 758 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 759 | "dev": true, 760 | "requires": { 761 | "argparse": "^1.0.7", 762 | "esprima": "^4.0.0" 763 | } 764 | }, 765 | "json-schema-traverse": { 766 | "version": "1.0.0", 767 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 768 | "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" 769 | }, 770 | "minimatch": { 771 | "version": "3.1.2", 772 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 773 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 774 | "dev": true, 775 | "requires": { 776 | "brace-expansion": "^1.1.7" 777 | } 778 | }, 779 | "minimist": { 780 | "version": "1.2.6", 781 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 782 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", 783 | "dev": true 784 | }, 785 | "mkdirp": { 786 | "version": "0.5.6", 787 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 788 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 789 | "dev": true, 790 | "requires": { 791 | "minimist": "^1.2.6" 792 | } 793 | }, 794 | "once": { 795 | "version": "1.4.0", 796 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 797 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 798 | "dev": true, 799 | "requires": { 800 | "wrappy": "1" 801 | } 802 | }, 803 | "path-is-absolute": { 804 | "version": "1.0.1", 805 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 806 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 807 | "dev": true 808 | }, 809 | "path-parse": { 810 | "version": "1.0.7", 811 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 812 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 813 | "dev": true 814 | }, 815 | "punycode": { 816 | "version": "2.3.0", 817 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", 818 | "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" 819 | }, 820 | "require-from-string": { 821 | "version": "2.0.2", 822 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 823 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" 824 | }, 825 | "resolve": { 826 | "version": "1.22.1", 827 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 828 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 829 | "dev": true, 830 | "requires": { 831 | "is-core-module": "^2.9.0", 832 | "path-parse": "^1.0.7", 833 | "supports-preserve-symlinks-flag": "^1.0.0" 834 | } 835 | }, 836 | "semver": { 837 | "version": "5.7.1", 838 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 839 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 840 | "dev": true 841 | }, 842 | "sprintf-js": { 843 | "version": "1.0.3", 844 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 845 | "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", 846 | "dev": true 847 | }, 848 | "supports-color": { 849 | "version": "5.5.0", 850 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 851 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 852 | "dev": true, 853 | "requires": { 854 | "has-flag": "^3.0.0" 855 | } 856 | }, 857 | "supports-preserve-symlinks-flag": { 858 | "version": "1.0.0", 859 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 860 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 861 | "dev": true 862 | }, 863 | "tslib": { 864 | "version": "1.14.1", 865 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 866 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 867 | "dev": true 868 | }, 869 | "tslint": { 870 | "version": "6.1.3", 871 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", 872 | "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", 873 | "dev": true, 874 | "requires": { 875 | "@babel/code-frame": "^7.0.0", 876 | "builtin-modules": "^1.1.1", 877 | "chalk": "^2.3.0", 878 | "commander": "^2.12.1", 879 | "diff": "^4.0.1", 880 | "glob": "^7.1.1", 881 | "js-yaml": "^3.13.1", 882 | "minimatch": "^3.0.4", 883 | "mkdirp": "^0.5.3", 884 | "resolve": "^1.3.2", 885 | "semver": "^5.3.0", 886 | "tslib": "^1.13.0", 887 | "tsutils": "^2.29.0" 888 | } 889 | }, 890 | "tsutils": { 891 | "version": "2.29.0", 892 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", 893 | "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", 894 | "dev": true, 895 | "requires": { 896 | "tslib": "^1.8.1" 897 | } 898 | }, 899 | "typescript": { 900 | "version": "5.1.3", 901 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", 902 | "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", 903 | "dev": true 904 | }, 905 | "uri-js": { 906 | "version": "4.4.1", 907 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 908 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 909 | "requires": { 910 | "punycode": "^2.1.0" 911 | } 912 | }, 913 | "wrappy": { 914 | "version": "1.0.2", 915 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 916 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 917 | "dev": true 918 | } 919 | } 920 | } 921 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filewatcher", 3 | "displayName": "File Watcher", 4 | "description": "Watch file or folder changes (deletions, creations, renaming), and run matched command scripts.", 5 | "icon": "media/lightning.png", 6 | "galleryBanner": { 7 | "color": "#E4F2FF", 8 | "theme": "light" 9 | }, 10 | "publisher": "appulate", 11 | "license": "MIT", 12 | "version": "2.0.0", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/appulate/vscode-file-watcher.git" 16 | }, 17 | "engines": { 18 | "vscode": "^1.71.0" 19 | }, 20 | "categories": [ 21 | "Other" 22 | ], 23 | "keywords": [ 24 | "run script", 25 | "file watch", 26 | "folder watch", 27 | "case insensitive", 28 | "on change", 29 | "on save", 30 | "on delete", 31 | "on create", 32 | "on rename" 33 | ], 34 | "activationEvents": [ 35 | "*" 36 | ], 37 | "main": "./out/extension.js", 38 | "contributes": { 39 | "colors": [ 40 | { 41 | "id": "filewatcher.success", 42 | "description": "Color for success message in the status bar.", 43 | "defaults": { 44 | "dark": "#25E028", 45 | "light": "#18CE1B", 46 | "highContrast": "#0DC610" 47 | } 48 | }, 49 | { 50 | "id": "filewatcher.run", 51 | "description": "Color for run message in the status bar.", 52 | "defaults": { 53 | "dark": "#00FFFB", 54 | "light": "#02D4D1", 55 | "highContrast": "#03D2CE" 56 | } 57 | } 58 | ], 59 | "languages": [ 60 | { 61 | "id": "file-watcher-output", 62 | "mimetypes": [ 63 | "text/x-code-output" 64 | ] 65 | } 66 | ], 67 | "grammars": [ 68 | { 69 | "language": "file-watcher-output", 70 | "scopeName": "file-watcher.output", 71 | "path": "./syntaxes/file-watcher-output.tmLanguage" 72 | } 73 | ], 74 | "commands": [ 75 | { 76 | "command": "extension.enableFileWatcher", 77 | "title": "File Watcher: Enable" 78 | }, 79 | { 80 | "command": "extension.disableFileWatcher", 81 | "title": "File Watcher: Disable" 82 | }, 83 | { 84 | "command": "extension.focusIntoOutput", 85 | "title": "File Watcher: Focus Output" 86 | } 87 | ], 88 | "configuration": { 89 | "title": "File Watcher command configuration.", 90 | "$schema": "http://json-schema.org/draft-07/schema#", 91 | "type": "object", 92 | "properties": { 93 | "filewatcher.autoClearConsole": { 94 | "type": "boolean", 95 | "description": "Automatically clear the console on each save before running commands.", 96 | "default": false 97 | }, 98 | "filewatcher.shell": { 99 | "type": "string", 100 | "description": "Common shell to execute the command with (gets passed to child_process.exec as an options arg. e.g. child_process(cmd, { shell })." 101 | }, 102 | "filewatcher.isClearStatusBar": { 103 | "type": "boolean", 104 | "description": "Returns the status bar to its normal position (after receiving a 'Success' or 'Error' status) after a some time.", 105 | "default": false 106 | }, 107 | "filewatcher.statusBarDelay": { 108 | "type": "number", 109 | "description": "The time after which the status returns to normal. Only works if isClearStatusBar === true. Default is 5000ms", 110 | "default": 5000, 111 | "minimum": 100 112 | }, 113 | "filewatcher.isSyncRunEvents": { 114 | "type": "boolean", 115 | "description": "Launches event handlers of the same name with the appropriate pattern 'match' or 'notMatch' (e.g. onFileChange and onFolderChange) in synchronous or asynchronous mode.", 116 | "default": false 117 | }, 118 | "filewatcher.successTextColor": { 119 | "type": "string", 120 | "description": "Color for success message in the status bar. Default: dark: '#25E028', light: '#18CE1B', highContrast: '#0DC610'" 121 | }, 122 | "filewatcher.runTextColor": { 123 | "type": "string", 124 | "description": "Color for run message in the status bar. Default: dark: '#00FFFB', light: '#02D4D1', highContrast: '#03D2CE'" 125 | }, 126 | "filewatcher.commands": { 127 | "description": "array of commands", 128 | "type": "array", 129 | "items": { 130 | "type": "object", 131 | "properties": { 132 | "shell": { 133 | "type": "string", 134 | "description": "Unique shell to execute the command with (gets passed to child_process.exec as an options arg. e.g. child_process(cmd, { shell })." 135 | }, 136 | "match": { 137 | "type": "string", 138 | "description": "Regex for matching files to run commands on \n\nNOTE: This is a regex and not a file path spce, so backslashes have to be escaped. They also have to be escaped in json strings, so you may have to double escape them in certain cases such as targetting contents of folders.\n\ne.g.\n\"match\": \"some\\\\\\\\directory\\\\\\\\.*\"", 139 | "default": ".*" 140 | }, 141 | "notMatch": { 142 | "type": "string", 143 | "description": "Regex for matching files *not* to run commands on.", 144 | "default": ".*" 145 | }, 146 | "cmd": { 147 | "type": "string", 148 | "description": "Command to execute.", 149 | "default": "echo ${file}" 150 | }, 151 | "vscodeTask": { 152 | "anyOf": [ 153 | { 154 | "type": "string" 155 | }, 156 | { 157 | "type": "array", 158 | "items": { 159 | "type": "string" 160 | } 161 | } 162 | ], 163 | "description": "Name of a VS Code task defined in tasks.json or commands to execute. Only works if cmd value does not exist. Must be a string (e.g. command id) or array of strings (e.g. ['workbench.action.tasks.runTask', 'some-task-name'] and etc.)" 164 | }, 165 | "isAsync": { 166 | "type": "boolean", 167 | "description": "Run command asynchronously." 168 | }, 169 | "event": { 170 | "type": "string", 171 | "description": "events onFileChange, onFileChangeImmediate, onFileDelete, onFileRename, onFileCreate, onFolderChange, onFolderCreate, onFolderDelete", 172 | "enum": [ 173 | "onFileChange", 174 | "onFileChangeImmediate", 175 | "onFolderChange", 176 | "onFileDelete", 177 | "onFileRename", 178 | "onFileCreate", 179 | "onFolderCreate", 180 | "onFolderDelete" 181 | ] 182 | } 183 | }, 184 | "anyOf": [ 185 | { 186 | "required": [ 187 | "cmd" 188 | ], 189 | "not": { 190 | "required": [ 191 | "vscodeTask" 192 | ] 193 | }, 194 | "errorMessage": "Only one of the cmd or vscodeTask property can exist in an object" 195 | }, 196 | { 197 | "required": [ 198 | "vscodeTask" 199 | ], 200 | "not": { 201 | "required": [ 202 | "cmd" 203 | ] 204 | }, 205 | "errorMessage": "Only one of the cmd or vscodeTask property can exist in an object" 206 | } 207 | ], 208 | "required": [ 209 | "match", 210 | "event" 211 | ] 212 | } 213 | } 214 | }, 215 | "required": [ 216 | "filewatcher.commands" 217 | ] 218 | } 219 | }, 220 | "scripts": { 221 | "vscode:prepublish": "npm run compile", 222 | "compile": "tsc -p ./", 223 | "watch": "tsc -watch", 224 | "prepare": "npm install -g vsce", 225 | "generate": "vsce package", 226 | "publish": "vsce publish" 227 | }, 228 | "devDependencies": { 229 | "@types/node": "^18.7.16", 230 | "@types/vscode": "^1.71.0", 231 | "tslint": "^6.1.3", 232 | "typescript": "^5.1.3" 233 | }, 234 | "dependencies": { 235 | "ajv": "^8.12.0", 236 | "ajv-errors": "^3.0.0" 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/command-runner.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { exec } from "child_process"; 3 | import OutputChannel from "./output-channel"; 4 | import { isCmdShell, getClickableLinksInMsg } from "./utils"; 5 | import { 6 | ICommandValue, 7 | IExecOptions, 8 | Nullable, 9 | PreparedCommand, 10 | StatusType, 11 | } from "./types"; 12 | 13 | type ProcessHandleFn = (command: PreparedCommand) => void; 14 | 15 | interface IProcessHandlers { 16 | onStarted: ProcessHandleFn; 17 | onFinish: ProcessHandleFn; 18 | } 19 | 20 | class CommandRunner { 21 | public isRunProcess: boolean = false; 22 | 23 | public constructor(private outputChannel: OutputChannel) {} 24 | 25 | private resolveProcessSuccess(msg: string): StatusType { 26 | this.outputChannel.showMessage(getClickableLinksInMsg(msg)); 27 | return StatusType.Success; 28 | } 29 | 30 | private resolveProcessError(msg: string): StatusType { 31 | this.outputChannel.showError(getClickableLinksInMsg(msg)); 32 | return StatusType.Error; 33 | } 34 | 35 | private runShellProcess( 36 | cmdVal: string, 37 | execOptions: Nullable 38 | ): Promise { 39 | return new Promise((resolve) => { 40 | exec(cmdVal, execOptions, (_, stdout, stderr) => { 41 | const statusType: StatusType = 42 | stderr !== "" 43 | ? this.resolveProcessError(String(stderr)) 44 | : this.resolveProcessSuccess(String(stdout)); 45 | 46 | resolve(statusType); 47 | }); 48 | }); 49 | } 50 | 51 | private runVscodeTask(cmd: string | string[]): Promise { 52 | const [primaryCmd, ...restOptions] = [cmd].flat(); 53 | return new Promise((resolve) => { 54 | vscode.commands 55 | .executeCommand>(primaryCmd, ...restOptions) 56 | .then( 57 | (fulfillMsg) => { 58 | resolve(this.resolveProcessSuccess(String(fulfillMsg ?? ""))); 59 | }, 60 | (rejectMsg) => { 61 | resolve(this.resolveProcessError(String(rejectMsg))); 62 | } 63 | ); 64 | }); 65 | } 66 | 67 | private runProcess( 68 | command: ICommandValue, 69 | execOptions: Nullable 70 | ): Promise { 71 | const { type, value } = command; 72 | if (isCmdShell(type, value)) { 73 | return this.runShellProcess(value, execOptions); 74 | } 75 | return this.runVscodeTask(value); 76 | } 77 | 78 | private runCommand( 79 | command: PreparedCommand, 80 | handlers: IProcessHandlers 81 | ): Promise { 82 | this.isRunProcess = true; 83 | handlers.onStarted(command); 84 | const proccessPromise: Promise = this.runProcess( 85 | command.cmd, 86 | command.execOptions 87 | ); 88 | proccessPromise.finally(() => { 89 | this.isRunProcess = false; 90 | handlers.onFinish(command); 91 | }); 92 | return proccessPromise; 93 | } 94 | 95 | public async runCommandsAsync( 96 | commands: PreparedCommand[], 97 | handlers: IProcessHandlers 98 | ): Promise>> { 99 | const proccessResult: Array> = []; 100 | 101 | for (const command of commands) { 102 | const proccessPromise: Promise = this.runCommand( 103 | command, 104 | handlers 105 | ); 106 | if (command.isAsync) { 107 | proccessResult.push(proccessPromise); 108 | } else { 109 | proccessResult.push(await proccessPromise); 110 | } 111 | } 112 | 113 | return proccessResult; 114 | } 115 | } 116 | 117 | export default CommandRunner; 118 | -------------------------------------------------------------------------------- /src/config-controller.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import ConfigValidator from "./config-validator"; 3 | import { getReplacedCmd, isCmdShell } from "./utils"; 4 | import { 5 | CommandType, 6 | ICommandValue, 7 | IDocumentUriMap, 8 | IExecOptions, 9 | Nullable, 10 | PreparedCommand, 11 | PreparedConfig, 12 | Event, 13 | ValidCommand, 14 | IPackageConfigSchema, 15 | PartialInitConfig, 16 | ValidInitConfig, 17 | WorkspaceConfig, 18 | } from "./types"; 19 | 20 | class ConfigController { 21 | public data: Nullable = null; 22 | public validator: ConfigValidator; 23 | 24 | public constructor(packageConfigSchema: IPackageConfigSchema) { 25 | this.validator = new ConfigValidator(packageConfigSchema); 26 | } 27 | 28 | private getPreparedCmd( 29 | cmd: ValidCommand["cmd"] = "", 30 | task: ValidCommand["vscodeTask"] = "" 31 | ): ICommandValue { 32 | const isCmd: boolean = Boolean(cmd); 33 | return { 34 | type: isCmd ? CommandType.Shell : CommandType.Vscode, 35 | value: isCmd ? cmd : task, 36 | }; 37 | } 38 | 39 | private getPreparedCommands( 40 | commands: ValidCommand[], 41 | commonShell: PartialInitConfig["shell"] 42 | ): PreparedCommand[] { 43 | const prepareCommand = (command: ValidCommand): PreparedCommand => { 44 | const { 45 | shell: commandShell, 46 | isAsync, 47 | vscodeTask, 48 | cmd, 49 | ...restOptions 50 | } = command; 51 | const shell: string | undefined = commandShell || commonShell; 52 | const execOptions: Nullable = shell ? { shell } : null; 53 | 54 | return { 55 | ...restOptions, 56 | cmd: this.getPreparedCmd(cmd, vscodeTask), 57 | isAsync: Boolean(isAsync), 58 | execOptions, 59 | }; 60 | }; 61 | 62 | return commands.map(prepareCommand); 63 | } 64 | 65 | private getPreparedConfig(config: ValidInitConfig): PreparedConfig { 66 | return { 67 | ...config, 68 | commands: this.getPreparedCommands(config.commands, config.shell), 69 | }; 70 | } 71 | 72 | private isFileNameValid(documentUri: vscode.Uri, pattern?: string): boolean { 73 | return pattern != undefined && new RegExp(pattern).test(documentUri.fsPath); 74 | } 75 | 76 | public isPreparedConfig( 77 | isLoad: boolean, 78 | config: Nullable 79 | ): config is PreparedConfig { 80 | return isLoad && config != null; 81 | } 82 | 83 | public get isClearConsole(): boolean { 84 | return this.data?.autoClearConsole === true; 85 | } 86 | 87 | public get isSyncRunEvents(): boolean { 88 | return this.data?.isSyncRunEvents === true; 89 | } 90 | 91 | public isCommands(config: WorkspaceConfig): config is PartialInitConfig { 92 | return (config.commands || []).length > 0; 93 | } 94 | 95 | public get isValidConfig(): boolean { 96 | return this.validator.isValid; 97 | } 98 | 99 | public get errorMessage(): Nullable { 100 | return this.validator.errorMessage; 101 | } 102 | 103 | private getCommandsByReplacedCmd( 104 | commands: PreparedCommand[], 105 | documentUriMap: IDocumentUriMap 106 | ): PreparedCommand[] { 107 | function replaceCmd(command: PreparedCommand): PreparedCommand { 108 | const { cmd, ...restConfig } = command; 109 | const { type, value } = cmd; 110 | const cmdValResult: string | string[] = isCmdShell(type, value) 111 | ? getReplacedCmd(documentUriMap, value) 112 | : value; 113 | return { 114 | ...restConfig, 115 | cmd: { 116 | type, 117 | value: cmdValResult, 118 | }, 119 | }; 120 | } 121 | 122 | return commands.map(replaceCmd); 123 | } 124 | 125 | public getValidCommandsByEvent( 126 | event: Event, 127 | documentUriMap: IDocumentUriMap 128 | ): PreparedCommand[] { 129 | const isValidCommand = (command: PreparedCommand): boolean => { 130 | return ( 131 | event === command.event && 132 | !this.isFileNameValid(documentUriMap.documentUri, command.notMatch) && 133 | this.isFileNameValid(documentUriMap.documentUri, command.match) 134 | ); 135 | }; 136 | 137 | const commandsByEvent: PreparedCommand[] = 138 | this.data?.commands.filter(isValidCommand) ?? []; 139 | 140 | return this.getCommandsByReplacedCmd(commandsByEvent, documentUriMap); 141 | } 142 | 143 | public load(config: PartialInitConfig): boolean { 144 | if (this.validator.validate(config)) { 145 | this.data = this.getPreparedConfig(config); 146 | return true; 147 | } 148 | return false; 149 | } 150 | } 151 | 152 | export default ConfigController; 153 | -------------------------------------------------------------------------------- /src/config-validator.ts: -------------------------------------------------------------------------------- 1 | import Ajv, { ValidateFunction } from "ajv"; 2 | import ajvErrors from "ajv-errors"; 3 | import { JTDDataType } from "ajv/dist/jtd"; 4 | import { 5 | IPackageConfigSchema, 6 | Nullable, 7 | PartialInitConfig, 8 | ValidInitConfig, 9 | } from "./types"; 10 | 11 | interface IErrorInfo { 12 | instancePath: string; 13 | message?: string; 14 | } 15 | 16 | interface IValidateErrors { 17 | emptySomeProperty: IErrorInfo; 18 | } 19 | 20 | type PrefixToKeys = { 21 | [K in keyof T as `filewatcher.${string & K}`]: T[K]; 22 | }; 23 | 24 | type ConfigSchema = PrefixToKeys; 25 | type SchemaType = JTDDataType; 26 | 27 | class ConfigValidator { 28 | public isValid: boolean = false; 29 | public errorMessage: Nullable = null; 30 | private validator: ValidateFunction; 31 | private errorTemplates: IValidateErrors = { 32 | emptySomeProperty: { 33 | message: 34 | 'some property of "match", "event", ("cmd" or "vscodeTask") are empty in commands', 35 | instancePath: "settings.json", 36 | }, 37 | }; 38 | 39 | public constructor(packageConfigSchema: IPackageConfigSchema) { 40 | const ajv: Ajv = new Ajv({ allErrors: true }); 41 | ajvErrors(ajv); 42 | this.validator = ajv.compile(packageConfigSchema); 43 | } 44 | 45 | private getConfigSchema(config: PartialInitConfig): ConfigSchema { 46 | const prefixFileWatcher: string = "filewatcher."; 47 | const entriesWithPrefix = Object.entries(config).map( 48 | ([key, value]) => [`${prefixFileWatcher}${key}`, value] 49 | ); 50 | return Object.fromEntries(entriesWithPrefix) as ConfigSchema; 51 | } 52 | 53 | private isValidCommands({ commands }: PartialInitConfig): boolean { 54 | return commands.every(({ event, match, cmd, vscodeTask }) => { 55 | const isTask: boolean = Boolean(cmd || vscodeTask); 56 | return event && match && isTask; 57 | }); 58 | } 59 | 60 | private getValidLine(items: Array>): Nullable { 61 | const arr: Array> = items.filter(Boolean); 62 | return arr.length > 0 ? arr.join(", ") : null; 63 | } 64 | 65 | private getLineMessage({ 66 | instancePath, 67 | message, 68 | }: IErrorInfo): Nullable { 69 | const messages: Array> = [ 70 | instancePath || null, 71 | message || null, 72 | ]; 73 | return this.getValidLine(messages); 74 | } 75 | 76 | private getErrorMsg(isValidCommands: boolean): Nullable { 77 | const startOfMsg: string = "Config validation error in settings.json: \n"; 78 | const errorCommands = !isValidCommands 79 | ? [this.errorTemplates.emptySomeProperty] 80 | : []; 81 | const errors = [...errorCommands, ...(this.validator.errors ?? [])]; 82 | return ( 83 | errors.reduce((errorMsg, errorInfo: IErrorInfo) => { 84 | const lineMsg: Nullable = this.getLineMessage(errorInfo); 85 | if (lineMsg != null) { 86 | errorMsg += `${lineMsg} \n`; 87 | } 88 | return errorMsg; 89 | }, startOfMsg) || null 90 | ); 91 | } 92 | 93 | public validate(config: PartialInitConfig): config is ValidInitConfig { 94 | const isValidByScheme: boolean = this.validator( 95 | this.getConfigSchema(config) 96 | ); 97 | const isValidCommands: boolean = this.isValidCommands(config); 98 | this.isValid = isValidByScheme && isValidCommands; 99 | this.errorMessage = this.getErrorMsg(isValidCommands); 100 | return this.isValid; 101 | } 102 | } 103 | 104 | export default ConfigValidator; 105 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import FileWatcher from "./file-watcher"; 3 | import { convertSingleUriToDocArr, convertUriFiles } from "./utils"; 4 | import { RegisterCommands, Event } from "./types"; 5 | 6 | function registerCommands(extension: FileWatcher): void { 7 | vscode.commands.registerCommand(RegisterCommands.Enable, () => { 8 | extension.isEnabled = true; 9 | }); 10 | 11 | vscode.commands.registerCommand(RegisterCommands.Disable, () => { 12 | extension.isEnabled = false; 13 | }); 14 | 15 | vscode.commands.registerCommand(RegisterCommands.FocusOutput, () => { 16 | extension.outputChannel.showChannel(); 17 | }); 18 | } 19 | 20 | function initFileEvents(extension: FileWatcher): void { 21 | vscode.workspace.onDidChangeConfiguration(() => { 22 | extension.loadConfig(); 23 | }); 24 | 25 | vscode.workspace.onDidSaveTextDocument((document: vscode.TextDocument) => { 26 | extension.eventHandlerAsync({ 27 | event: Event.FileChange, 28 | documentsUri: convertSingleUriToDocArr(document.uri), 29 | }); 30 | }); 31 | 32 | vscode.workspace.onDidChangeTextDocument( 33 | ({ document }: vscode.TextDocumentChangeEvent) => { 34 | extension.eventHandlerAsync({ 35 | event: Event.FileChangeImmediate, 36 | documentsUri: convertSingleUriToDocArr(document.uri), 37 | }); 38 | } 39 | ); 40 | 41 | vscode.workspace.onDidCreateFiles((createEvent: vscode.FileCreateEvent) => { 42 | extension.eventHandlerAsync({ 43 | event: Event.FileCreate, 44 | documentsUri: convertUriFiles(createEvent.files), 45 | }); 46 | }); 47 | 48 | vscode.workspace.onDidDeleteFiles((deleteEvent: vscode.FileCreateEvent) => { 49 | extension.eventHandlerAsync({ 50 | event: Event.FileDelete, 51 | documentsUri: convertUriFiles(deleteEvent.files), 52 | }); 53 | }); 54 | 55 | vscode.workspace.onDidRenameFiles((renameEvent: vscode.FileRenameEvent) => { 56 | extension.eventHandlerAsync({ 57 | event: Event.FileRename, 58 | documentsUri: renameEvent.files.map((file) => ({ 59 | documentUri: file.newUri, 60 | documentOldUri: file.oldUri, 61 | })), 62 | }); 63 | }); 64 | } 65 | 66 | function initFolderEvents(extension: FileWatcher): void { 67 | const watcher: vscode.FileSystemWatcher = 68 | vscode.workspace.createFileSystemWatcher("**/*", false, false, false); 69 | 70 | watcher.onDidChange((uri: vscode.Uri) => { 71 | extension.eventHandlerAsync({ 72 | event: Event.FolderChange, 73 | documentsUri: convertSingleUriToDocArr(uri), 74 | }); 75 | }); 76 | 77 | watcher.onDidCreate((uri: vscode.Uri) => { 78 | extension.eventHandlerAsync({ 79 | event: Event.FolderCreate, 80 | documentsUri: convertSingleUriToDocArr(uri), 81 | }); 82 | }); 83 | 84 | watcher.onDidDelete((uri: vscode.Uri) => { 85 | extension.eventHandlerAsync({ 86 | event: Event.FolderDelete, 87 | documentsUri: convertSingleUriToDocArr(uri), 88 | }); 89 | }); 90 | } 91 | 92 | export function activate(context: vscode.ExtensionContext): void { 93 | const extension: FileWatcher = new FileWatcher(context); 94 | registerCommands(extension); 95 | initFileEvents(extension); 96 | initFolderEvents(extension); 97 | } 98 | -------------------------------------------------------------------------------- /src/file-watcher.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import CommandRunner from "./command-runner"; 3 | import ConfigController from "./config-controller"; 4 | import OutputChannel from "./output-channel"; 5 | import StatusBar from "./status-bar"; 6 | import { 7 | IEventConfig, 8 | StatusType, 9 | PreparedCommand, 10 | PreparedConfig, 11 | Nullable, 12 | IPackage, 13 | WorkspaceConfig, 14 | } from "./types"; 15 | 16 | class FileWatcher { 17 | public outputChannel: OutputChannel = new OutputChannel(); 18 | private packageJson: IPackage = this.context.extension.packageJSON; 19 | private name: string = this.packageJson.displayName; 20 | private version: string = this.packageJson.version; 21 | private configController: ConfigController = new ConfigController( 22 | this.packageJson.contributes.configuration 23 | ); 24 | private commandRunner: CommandRunner = new CommandRunner(this.outputChannel); 25 | private statusBar: StatusBar = new StatusBar( 26 | () => this.commandRunner.isRunProcess 27 | ); 28 | private eventRunPromise: Promise = Promise.resolve(); 29 | public constructor(private context: vscode.ExtensionContext) { 30 | this.isEnabled = true; 31 | this.loadConfig(); 32 | } 33 | 34 | public loadConfig(): void { 35 | const config = vscode.workspace.getConfiguration( 36 | "filewatcher" 37 | ) as WorkspaceConfig; 38 | if (this.configController.isCommands(config)) { 39 | const isLoad: boolean = this.configController.load(config); 40 | const preparedConfig: Nullable = 41 | this.configController.data; 42 | if (this.configController.isPreparedConfig(isLoad, preparedConfig)) { 43 | this.statusBar.loadConfig({ 44 | isClearStatusBar: preparedConfig.isClearStatusBar, 45 | statusBarDelay: preparedConfig.statusBarDelay, 46 | successColor: preparedConfig.successTextColor, 47 | runColor: preparedConfig.runTextColor, 48 | }); 49 | this.outputChannel.showConfigReload(); 50 | this.statusBar.showConfigReload(); 51 | } else { 52 | this.showConfigError(); 53 | } 54 | } 55 | } 56 | 57 | private showConfigError(): void { 58 | const { errorMessage } = this.configController; 59 | if (errorMessage != null) { 60 | this.outputChannel.showError(errorMessage); 61 | } 62 | this.statusBar.showError(); 63 | } 64 | 65 | public get isEnabled(): boolean { 66 | return Boolean(this.context.globalState.get("isEnabled", true)); 67 | } 68 | 69 | public set isEnabled(value: boolean) { 70 | this.context.globalState.update("isEnabled", value); 71 | this.outputChannel.showEnabledState({ 72 | name: this.name, 73 | version: this.version, 74 | isEnable: this.isEnabled, 75 | }); 76 | } 77 | 78 | private get isAutoClearConsole(): boolean { 79 | return ( 80 | !this.commandRunner.isRunProcess && this.configController.isClearConsole 81 | ); 82 | } 83 | 84 | private getProcessHandlers() { 85 | return { 86 | onStarted: (command: PreparedCommand) => { 87 | this.statusBar.showRun(); 88 | this.outputChannel.showProcess(command, "started"); 89 | this.outputChannel.showTask(command.cmd); 90 | }, 91 | onFinish: (command: PreparedCommand) => { 92 | this.outputChannel.showProcess(command, "finished"); 93 | }, 94 | }; 95 | } 96 | 97 | private async showStatusResultAsync( 98 | statusResultPromise: Array> 99 | ): Promise { 100 | if (statusResultPromise.length > 0) { 101 | const commandsResult: StatusType[] = await Promise.all( 102 | statusResultPromise 103 | ); 104 | if (commandsResult.includes(StatusType.Error)) { 105 | this.statusBar.showError(); 106 | } else { 107 | this.statusBar.showSuccess(); 108 | } 109 | } 110 | } 111 | 112 | private async runEventHandleAsync({ 113 | event, 114 | documentsUri, 115 | }: IEventConfig): Promise { 116 | return new Promise(async (resolve) => { 117 | const statusResult: Array> = []; 118 | 119 | for (const documentMapUri of documentsUri) { 120 | const validCommands: PreparedCommand[] = 121 | this.configController.getValidCommandsByEvent(event, documentMapUri); 122 | if (validCommands.length > 0) { 123 | if (this.isAutoClearConsole) { 124 | this.outputChannel.clear(); 125 | } 126 | this.outputChannel.showHandleMessage(); 127 | const statusTypes = await this.commandRunner.runCommandsAsync( 128 | validCommands, 129 | this.getProcessHandlers() 130 | ); 131 | statusResult.push(...statusTypes); 132 | } 133 | } 134 | 135 | await this.showStatusResultAsync(statusResult); 136 | resolve(); 137 | }); 138 | } 139 | 140 | public async eventHandlerAsync(eventConfig: IEventConfig): Promise { 141 | if (!this.isEnabled || !this.configController.isValidConfig) { 142 | return; 143 | } 144 | if (this.configController.isSyncRunEvents) { 145 | await this.eventRunPromise; 146 | } 147 | 148 | this.eventRunPromise = this.runEventHandleAsync(eventConfig); 149 | } 150 | } 151 | 152 | export default FileWatcher; 153 | -------------------------------------------------------------------------------- /src/output-channel.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { 3 | EnabledState, 4 | ICommandValue, 5 | OutputReservedKey, 6 | PreparedCommand, 7 | } from "./types"; 8 | import { isCmdShell } from "./utils"; 9 | 10 | class OutputChannel { 11 | private channel: vscode.OutputChannel = 12 | vscode.window.createOutputChannel("File Watcher"); 13 | 14 | public showMessage(message: string): void { 15 | this.channel.appendLine(message); 16 | } 17 | 18 | public showError(message: string): void { 19 | this.showMessage(`${OutputReservedKey.Error} ${message}`); 20 | } 21 | 22 | public showEnabledState({ 23 | version, 24 | name, 25 | isEnable, 26 | }: { 27 | version: string; 28 | name: string; 29 | isEnable: boolean; 30 | }): void { 31 | const stateType: EnabledState = isEnable 32 | ? EnabledState.Enable 33 | : EnabledState.Disable; 34 | this.showMessage(`${name} ${version} is ${stateType}.`); 35 | } 36 | 37 | public showConfigReload(): void { 38 | this.showMessage(OutputReservedKey.Reload); 39 | } 40 | 41 | public showHandleMessage(): void { 42 | this.showMessage(""); 43 | this.showMessage(`${OutputReservedKey.EventHandled} ...`); 44 | } 45 | 46 | public showProcess( 47 | { event, match }: PreparedCommand, 48 | processType: "started" | "finished" 49 | ): void { 50 | this.showMessage(`[${event}] for pattern "${match}" ${processType}`); 51 | } 52 | 53 | public showTask({ type, value }: ICommandValue): void { 54 | const taskType: OutputReservedKey = isCmdShell(type, value) 55 | ? OutputReservedKey.Cmd 56 | : OutputReservedKey.Task; 57 | this.showMessage(`${taskType} ${value}`); 58 | } 59 | 60 | public showChannel(): void { 61 | this.channel.show(true); 62 | } 63 | 64 | public clear(): void { 65 | this.channel.clear(); 66 | } 67 | } 68 | 69 | export default OutputChannel; 70 | -------------------------------------------------------------------------------- /src/status-bar.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { getThemeColors } from "./utils"; 3 | import { IColors, RegisterCommands } from "./types"; 4 | 5 | enum StatusIcon { 6 | Primary = "$(telescope)", 7 | Loading = "$(loading~spin)", 8 | } 9 | 10 | interface IStatusBarConfig { 11 | readonly isClearStatusBar: boolean; 12 | readonly statusBarDelay: number; 13 | readonly successColor: string; 14 | readonly runColor: string; 15 | } 16 | 17 | const STATUS_DELAY_DEFAULT: number = 5000; 18 | const PRIORITY_SHOW: number = 1000; 19 | 20 | export default class StatusBar { 21 | private statusBarItem: vscode.StatusBarItem = 22 | vscode.window.createStatusBarItem( 23 | vscode.StatusBarAlignment.Left, 24 | PRIORITY_SHOW 25 | ); 26 | private defaultColors: IColors = getThemeColors(this.statusBarItem.color); 27 | private colors: IColors = { ...this.defaultColors }; 28 | private delay!: number; 29 | private isAutoClear!: boolean; 30 | 31 | public constructor(private isRunProcess: () => boolean) { 32 | this.initStatusBar(); 33 | } 34 | 35 | public loadConfig({ 36 | isClearStatusBar, 37 | statusBarDelay, 38 | successColor, 39 | runColor, 40 | }: Partial): void { 41 | this.isAutoClear = Boolean(isClearStatusBar); 42 | this.delay = statusBarDelay || STATUS_DELAY_DEFAULT; 43 | this.colors.success = successColor 44 | ? successColor 45 | : this.defaultColors.success; 46 | this.colors.run = runColor ? runColor : this.defaultColors.run; 47 | } 48 | private showMessage(message: string): void { 49 | this.statusBarItem.text = message; 50 | } 51 | 52 | public showConfigReload(): void { 53 | this.normalizeStatusBar(false); 54 | this.showMessage(`${StatusIcon.Primary} ~ config reloaded`); 55 | } 56 | 57 | private setStatusBarColor(color?: vscode.ThemeColor | string): void { 58 | this.statusBarItem.color = color; 59 | } 60 | 61 | private setStatusBarBackground(color?: vscode.ThemeColor | string): void { 62 | this.statusBarItem.backgroundColor = color; 63 | } 64 | 65 | private normalizeStatusBar(isIcon = true): void { 66 | this.setStatusBarBackground(this.colors.default); 67 | this.setStatusBarColor(this.colors.default); 68 | if (isIcon) { 69 | this.showMessage(StatusIcon.Primary); 70 | } 71 | } 72 | 73 | private normalizeStatusWithDelay(): void { 74 | setTimeout(() => { 75 | if (!this.isRunProcess()) { 76 | this.normalizeStatusBar(); 77 | } 78 | }, this.delay); 79 | } 80 | 81 | public showRun(): void { 82 | this.setStatusBarBackground(this.colors.default); 83 | this.setStatusBarColor(this.colors.run); 84 | this.showMessage(`${StatusIcon.Loading} Watcher Run...`); 85 | } 86 | 87 | public showSuccess(): void { 88 | this.setStatusBarBackground(this.colors.default); 89 | this.setStatusBarColor(this.colors.success); 90 | this.showMessage(`${StatusIcon.Primary} Success`); 91 | if (this.isAutoClear) { 92 | this.normalizeStatusWithDelay(); 93 | } 94 | } 95 | 96 | public showError(): void { 97 | this.setStatusBarBackground(this.colors.error); 98 | this.showMessage(`${StatusIcon.Primary} Error`); 99 | if (this.isAutoClear) { 100 | this.normalizeStatusWithDelay(); 101 | } 102 | } 103 | 104 | private initStatusBar(): void { 105 | this.normalizeStatusBar(); 106 | this.statusBarItem.show(); 107 | this.statusBarItem.command = RegisterCommands.FocusOutput; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | export type Nullable = T | null; 4 | 5 | export enum Event { 6 | FileChange = "onFileChange", 7 | FileChangeImmediate = "onFileChangeImmediate", 8 | FileDelete = "onFileDelete", 9 | FileRename = "onFileRename", 10 | FileCreate = "onFileCreate", 11 | FolderChange = "onFolderChange", 12 | FolderCreate = "onFolderCreate", 13 | FolderDelete = "onFolderDelete", 14 | } 15 | 16 | export enum CommandType { 17 | Shell = "shell", 18 | Vscode = "vscode", 19 | } 20 | 21 | export interface ICommandValue { 22 | readonly type: T; 23 | readonly value: T extends CommandType.Shell ? string : string | string[]; 24 | } 25 | 26 | export interface IExecOptions { 27 | readonly shell: string; 28 | } 29 | 30 | interface ICmdOptions { 31 | readonly execOptions: Nullable; 32 | } 33 | 34 | interface IConfig extends vscode.WorkspaceConfiguration { 35 | readonly shell: string; 36 | readonly autoClearConsole: boolean; 37 | readonly commands: T[]; 38 | readonly isClearStatusBar: boolean; 39 | readonly statusBarDelay: number; 40 | readonly isSyncRunEvents: boolean; 41 | readonly successTextColor: string; 42 | readonly runTextColor: string; 43 | } 44 | 45 | interface ICommand { 46 | readonly event: Event; 47 | readonly match: string; 48 | readonly cmd: T; 49 | readonly shell: string; 50 | readonly vscodeTask: string | string[]; 51 | readonly isAsync: boolean; 52 | readonly notMatch?: string; 53 | } 54 | 55 | export type PartialInitCommand = Partial>; 56 | 57 | export type ValidCommand = PartialInitCommand & { 58 | readonly event: ICommand["event"]; 59 | readonly match: ICommand["match"]; 60 | }; 61 | 62 | export type PreparedCommand = Omit< 63 | ICommand, 64 | "shell" | "vscodeTask" 65 | > & 66 | ICmdOptions; 67 | 68 | type InitConfig = Partial> & { 69 | readonly commands: IConfig["commands"]; 70 | }; 71 | 72 | export type WorkspaceConfig = Partial>; 73 | 74 | export type PartialInitConfig = InitConfig; 75 | 76 | export type ValidInitConfig = InitConfig; 77 | 78 | export type PreparedConfig = InitConfig; 79 | 80 | export interface IDocumentUriMap { 81 | readonly documentUri: vscode.Uri; 82 | readonly documentOldUri?: vscode.Uri; 83 | } 84 | 85 | export interface IEventConfig { 86 | readonly event: Event; 87 | readonly documentsUri: readonly IDocumentUriMap[]; 88 | } 89 | 90 | export interface IColors { 91 | readonly default?: vscode.ThemeColor; 92 | readonly error: vscode.ThemeColor; 93 | success: vscode.ThemeColor; 94 | run: vscode.ThemeColor; 95 | } 96 | 97 | export interface IPackageConfigSchema { 98 | readonly title: string; 99 | readonly type: string; 100 | readonly properties: { 101 | [key: string]: { 102 | readonly type: string; 103 | readonly default?: unknown; 104 | readonly description?: string; 105 | }; 106 | }; 107 | } 108 | 109 | export interface IPackage { 110 | readonly displayName: string; 111 | readonly version: string; 112 | readonly contributes: { 113 | readonly configuration: IPackageConfigSchema; 114 | }; 115 | } 116 | 117 | export enum StatusType { 118 | Success = "success", 119 | Error = "error", 120 | } 121 | 122 | export enum EnabledState { 123 | Enable = "enabled", 124 | Disable = "disabled", 125 | } 126 | 127 | export enum OutputReservedKey { 128 | Error = "[error]", 129 | Cmd = "[cmd]", 130 | Task = "[vscode-task]", 131 | Reload = "[Config reloaded]", 132 | EventHandled = "[Event handled]", 133 | } 134 | 135 | export enum RegisterCommands { 136 | Enable = "extension.enableFileWatcher", 137 | Disable = "extension.disableFileWatcher", 138 | FocusOutput = "extension.focusIntoOutput", 139 | } 140 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as path from "path"; 3 | import { CommandType, IColors, ICommandValue, IDocumentUriMap } from "./types"; 4 | 5 | interface ICmdReplaceInfo { 6 | readonly pattern: RegExp; 7 | readonly replaceStr: string; 8 | } 9 | 10 | function getCmdReplaceInfo( 11 | documentUri: vscode.Uri, 12 | documentOldUri?: vscode.Uri 13 | ): ICmdReplaceInfo[] { 14 | const { fsPath } = documentUri; 15 | const workspaceRootPath: string = 16 | vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; 17 | const currentWorkspacePath: string = 18 | vscode.workspace.getWorkspaceFolder(documentUri)?.uri.fsPath ?? ""; 19 | const extName: string = path.extname(fsPath); 20 | 21 | return [ 22 | { 23 | pattern: /\${file}/, 24 | replaceStr: fsPath, 25 | }, 26 | { 27 | pattern: /\${fileOld}/, 28 | replaceStr: documentOldUri?.fsPath ?? "", 29 | }, 30 | { 31 | pattern: /\${workspaceRoot}/, 32 | replaceStr: workspaceRootPath, 33 | }, 34 | { 35 | pattern: /\${workspaceRelativeDir}/, 36 | replaceStr: path.relative(workspaceRootPath, fsPath), 37 | }, 38 | { 39 | pattern: /\${currentWorkspace}/, 40 | replaceStr: currentWorkspacePath, 41 | }, 42 | { 43 | pattern: /\${currentRelativeWorkspace}/, 44 | replaceStr: path.relative(currentWorkspacePath, fsPath), 45 | }, 46 | { 47 | pattern: /\${fileBasename}/, 48 | replaceStr: path.basename(fsPath), 49 | }, 50 | { 51 | pattern: /\${fileDirname}/, 52 | replaceStr: path.dirname(fsPath), 53 | }, 54 | { 55 | pattern: /\${fileExtname}/, 56 | replaceStr: extName, 57 | }, 58 | { 59 | pattern: /\${fileBasenameNoExt}/, 60 | replaceStr: path.basename(fsPath, extName), 61 | }, 62 | ]; 63 | } 64 | 65 | export function getReplacedCmd( 66 | documentUriMap: IDocumentUriMap, 67 | cmd: string 68 | ): string { 69 | const { documentUri, documentOldUri } = documentUriMap; 70 | return getCmdReplaceInfo(documentUri, documentOldUri).reduce( 71 | (cmdResult, { pattern, replaceStr }) => 72 | cmdResult.replace(new RegExp(pattern, "g"), replaceStr), 73 | cmd 74 | ); 75 | } 76 | 77 | export function getThemeColors( 78 | defaultColor?: string | vscode.ThemeColor 79 | ): IColors { 80 | return { 81 | default: defaultColor, 82 | error: new vscode.ThemeColor("statusBarItem.errorBackground"), 83 | success: new vscode.ThemeColor("filewatcher.success"), 84 | run: new vscode.ThemeColor("filewatcher.run"), 85 | }; 86 | } 87 | 88 | export function convertSingleUriToDocArr(uri: vscode.Uri): IDocumentUriMap[] { 89 | return [{ documentUri: uri }]; 90 | } 91 | 92 | export function convertUriFiles( 93 | filesUri: readonly vscode.Uri[] 94 | ): IDocumentUriMap[] { 95 | return filesUri.map((uri) => ({ documentUri: uri })); 96 | } 97 | 98 | export function getClickableLinksInMsg(msg: string): string { 99 | const errorLinkRegex = 100 | /(?:file:\/\/\/)?([a-zA-Z]:(?:\/|\\)[^:"<>|?*\n]+):(\d+):(\d+)/g; 101 | return msg.replace(errorLinkRegex, (_, filePath, line, column) => { 102 | const uri: vscode.Uri = vscode.Uri.file(path.resolve(filePath)).with({ 103 | fragment: `${line}:${column}`, 104 | }); 105 | return `${filePath}:${line}:${column} (${String(uri)})`; 106 | }); 107 | } 108 | 109 | export function isCmdShell( 110 | type: ICommandValue["type"], 111 | value: ICommandValue["value"] 112 | ): value is string { 113 | return type === CommandType.Shell && typeof value === "string"; 114 | } 115 | -------------------------------------------------------------------------------- /syntaxes/file-watcher-output.tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | scopeName 6 | file-watcher.output 7 | name 8 | file-watcher-output 9 | patterns 10 | 11 | 12 | captures 13 | 14 | 1 15 | 16 | name 17 | token.info-token 18 | 19 | 2 20 | 21 | name 22 | token.info-token 23 | 24 | 25 | match 26 | (\[(?:onFolderChange|onFolderCreate|onFolderDelete)\]).*(\".+\") 27 | name 28 | file-watcher.folderChange 29 | 30 | 31 | captures 32 | 33 | 1 34 | 35 | name 36 | token.info-token 37 | 38 | 2 39 | 40 | name 41 | token.info-token 42 | 43 | 44 | match 45 | (\[(?:onFileChange|onFileChangeImmediate|onFileRename|onFileDelete|onFileCreate)\]).*(\".+\") 46 | name 47 | file-watcher.fileChange 48 | 49 | 50 | captures 51 | 52 | 1 53 | 54 | name 55 | token.warn-token 56 | 57 | 2 58 | 59 | name 60 | token.warn-token 61 | 62 | 63 | match 64 | (\[(?:cmd|vscode-task)\])(.*) 65 | name 66 | file-watcher.cmd 67 | 68 | 69 | match 70 | \[(?i:(Down|Error|Failure|Fail|Fatal))\] 71 | name 72 | token.error-token 73 | 74 | 75 | match 76 | \[Config reloaded\] 77 | name 78 | token.info-token 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "noUnusedLocals": true, 6 | "noUnusedParameters": true, 7 | "strict": true, 8 | "outDir": "out", 9 | "lib": [ 10 | "ES2020" 11 | ], 12 | "sourceMap": false, 13 | "rootDir": "src" 14 | }, 15 | "exclude": ["node_modules"], 16 | "include": ["src"] 17 | } 18 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest" 4 | ], 5 | "rules": { 6 | "arrow-parens": [ 7 | true, 8 | "ban-single-arg-parens" 9 | ], 10 | "ban-ts-ignore": true, 11 | "no-unused-expression": true, 12 | "no-duplicate-variable": true, 13 | "curly": true, 14 | "class-name": true, 15 | "semicolon": [ 16 | true, 17 | "always" 18 | ], 19 | "triple-equals": [ 20 | true, 21 | "allow-null-check" 22 | ], 23 | "no-any": true, 24 | "no-magic-numbers": true, 25 | "no-reference": true, 26 | "indent": [ 27 | true, 28 | "tabs" 29 | ], 30 | "no-multi-spaces": true, 31 | "object-literal-sort-keys": false, 32 | "no-duplicate-imports": false, 33 | "no-implicit-dependencies": false, 34 | "prefer-const": true, 35 | "trailing-comma": [ 36 | true, 37 | { 38 | "multiline": "never", 39 | "singleline": "never" 40 | } 41 | ], 42 | "typedef": [ 43 | true, 44 | "call-signature", 45 | "parameter", 46 | "property-declaration", 47 | "variable-declaration" 48 | ], 49 | "variable-name": [ 50 | true, 51 | "ban-keywords", 52 | "check-format", 53 | "allow-leading-underscore" 54 | ], 55 | "whitespace": [ 56 | true, 57 | "check-module", 58 | "check-decl", 59 | "check-operator", 60 | "check-separator" 61 | ] 62 | }, 63 | "jsRules": { 64 | "indent": [ 65 | true, 66 | "tabs" 67 | ], 68 | "no-duplicate-variable": true, 69 | "no-eval": true, 70 | "no-trailing-whitespace": true, 71 | "one-line": [ 72 | true, 73 | "check-open-brace", 74 | "check-whitespace" 75 | ], 76 | "quotemark": [ 77 | true, 78 | "double" 79 | ], 80 | "semicolon": [ 81 | false 82 | ], 83 | "triple-equals": [ 84 | true, 85 | "allow-null-check" 86 | ], 87 | "variable-name": [ 88 | true, 89 | "ban-keywords" 90 | ], 91 | "whitespace": [ 92 | true, 93 | "check-branch", 94 | "check-decl", 95 | "check-operator", 96 | "check-separator", 97 | "check-type" 98 | ] 99 | } 100 | } --------------------------------------------------------------------------------