├── .github ├── FUNDING.yml └── workflows │ └── publish.yml ├── .gitignore ├── .npmignore ├── tsconfig.json ├── src ├── scm │ ├── index.ts │ └── git.ts ├── html │ └── index.ts ├── events │ └── index.ts ├── workflows │ └── index.ts ├── cache │ └── index.ts ├── notifications │ └── index.ts ├── progress │ └── index.ts ├── disposable │ └── index.ts ├── timers │ └── index.ts ├── logging │ └── index.ts ├── http │ └── index.ts ├── workspaces │ └── index.ts ├── devtools │ └── index.ts ├── fs │ └── index.ts └── index.ts ├── .vscode ├── tasks.json.old └── tasks.json ├── tslint.json ├── package.json ├── LICENSE └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://paypal.me/MarcelKloubert'] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /node_modules 3 | /tsdoc/**/* 4 | /pushall.sh 5 | /typedoc.sh 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.vscode/**/* 2 | /lib/**/*.map 3 | /src/**/* 4 | /typedoc/**/* 5 | /.gitignore 6 | /pushall.sh 7 | /tsconfig.json 8 | /tslint.json 9 | /typedoc.sh 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "outDir": "lib", 6 | "lib": [ 7 | "es2019" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "declaration": true 12 | }, 13 | "exclude": [ 14 | "node_modules" 15 | ] 16 | } -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2.0.0 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: 14 15 | registry-url: https://registry.npmjs.org/ 16 | - run: npm install 17 | - run: npm run build 18 | - run: npm publish --access public 19 | env: 20 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} -------------------------------------------------------------------------------- /src/scm/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * A client for a source control system. 20 | */ 21 | export interface SourceControlClient { 22 | /** 23 | * Gets the current working directory. 24 | */ 25 | readonly cwd: string; 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/tasks.json.old: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isBackground": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "curly": true, 9 | "indent": [ 10 | true, 11 | "spaces" 12 | ], 13 | "no-console": [ 14 | true, 15 | "log" 16 | ], 17 | "no-duplicate-variable": true, 18 | "no-trailing-whitespace": true, 19 | "no-unused-variable": true, 20 | "no-unused-expression": true, 21 | "no-var-keyword": true, 22 | "one-line": [ 23 | true, 24 | "check-catch", 25 | "check-finally", 26 | "check-else", 27 | "check-open-brace", 28 | "check-whitespace" 29 | ], 30 | "semicolon": [ 31 | true, 32 | "always" 33 | ], 34 | "variable-name": [ 35 | true, 36 | "ban-keywords" 37 | ], 38 | "triple-equals": true, 39 | "whitespace": [ 40 | true, 41 | "check-branch", 42 | "check-decl", 43 | "check-operator", 44 | "check-separator", 45 | "check-type" 46 | ] 47 | } 48 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "2.0.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // we run the custom script "compile" as defined in package.json 17 | "args": ["run", "compile", "--loglevel", "silent"], 18 | 19 | // The tsc compiler is started in watching mode 20 | "isBackground": true, 21 | 22 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 23 | "problemMatcher": "$tsc-watch", 24 | "tasks": [ 25 | { 26 | "label": "npm", 27 | "type": "shell", 28 | "command": "npm", 29 | "args": [ 30 | "run", 31 | "compile", 32 | "--loglevel", 33 | "silent" 34 | ], 35 | "isBackground": true, 36 | "problemMatcher": "$tsc-watch", 37 | "group": { 38 | "_id": "build", 39 | "isDefault": false 40 | } 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /src/html/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as Marked from 'marked'; 19 | const MergeDeep = require('merge-deep'); 20 | import * as vscode_helpers from '../index'; 21 | 22 | /** 23 | * Generates HTML from Markdown. 24 | * 25 | * @param {any} md The value with Markdown data. 26 | * @param {Marked.MarkedOptions} [opts] Custom options. 27 | * 28 | * @return {string} The generated HTML. 29 | */ 30 | export function fromMarkdown(md: any, opts?: Marked.marked.MarkedOptions): string { 31 | if (!opts) { 32 | opts = opts; 33 | } 34 | 35 | const DEFAULT_OPTS: Marked.marked.MarkedOptions = { 36 | breaks: true, 37 | gfm: true, 38 | langPrefix: '', 39 | }; 40 | 41 | md = vscode_helpers.toStringSafe(md); 42 | 43 | return Marked.marked( 44 | vscode_helpers.toStringSafe(md), 45 | MergeDeep(DEFAULT_OPTS, opts) as Marked.marked.MarkedOptions, 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/events/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as Events from 'events'; 19 | import * as vscode from 'vscode'; 20 | 21 | /** 22 | * Stores the global event emitter. 23 | */ 24 | export const EVENTS: NodeJS.EventEmitter = new Events.EventEmitter(); 25 | 26 | /** 27 | * Disposes the event emitter, stored in 'EVENTS'. 28 | */ 29 | export const EVENT_DISPOSER: vscode.Disposable = { 30 | /** @inheritdoc */ 31 | dispose: () => { 32 | EVENTS.removeAllListeners(); 33 | } 34 | }; 35 | 36 | /** 37 | * Tries to remove all listeners from an event emitter. 38 | * 39 | * @param {NodeJS.EventEmitter} obj The emitter. 40 | * @param {string|symbol} [ev] The optional event. 41 | * 42 | * @return {boolean} Operation was successfull or not. 43 | */ 44 | export function tryRemoveAllListeners(obj: NodeJS.EventEmitter, ev?: string | symbol) { 45 | try { 46 | if (obj && obj.removeAllListeners) { 47 | obj.removeAllListeners(ev); 48 | } 49 | 50 | return true; 51 | } catch { 52 | return false; 53 | } 54 | } 55 | 56 | /** 57 | * Tries to remove a listener from an event emitter. 58 | * 59 | * @param {NodeJS.EventEmitter} obj The emitter. 60 | * @param {string|symbol} ev The event. 61 | * @param {Function} listener The listener. 62 | * 63 | * @return {boolean} Operation was successfull or not. 64 | */ 65 | export function tryRemoveListener( 66 | obj: NodeJS.EventEmitter, 67 | ev: string | symbol, 68 | listener: (...args: any[]) => void, 69 | ) { 70 | try { 71 | if (obj && obj.removeListener) { 72 | obj.removeListener(ev, listener); 73 | } 74 | 75 | return true; 76 | } catch { 77 | return false; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-helpers", 3 | "author": "Marcel Joachim Kloubert", 4 | "version": "10.0.1", 5 | "description": "Helper functions and classes for own VS Code (extensions)", 6 | "tags": [ 7 | "functions", 8 | "classes", 9 | "typescript", 10 | "tools", 11 | "helpers", 12 | "vscode" 13 | ], 14 | "keywords": [ 15 | "functions", 16 | "classes", 17 | "js", 18 | "ecmascript", 19 | "javascript", 20 | "typescript", 21 | "tools", 22 | "helpers", 23 | "vscode", 24 | "visual", 25 | "studio", 26 | "code", 27 | "extensions", 28 | "modules" 29 | ], 30 | "main": "lib/index.js", 31 | "types": "lib/index.d.ts", 32 | "license": "LGPL-3.0", 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/mkloubert/vscode-helpers" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/mkloubert/vscode-helpers/issues" 39 | }, 40 | "homepage": "https://github.com/mkloubert/vscode-helpers", 41 | "scripts": { 42 | "build": "del ./lib && tsc", 43 | "prepack": "del ./lib && tsc -p ./", 44 | "compile": "del ./lib && tsc -watch -p ./", 45 | "lint": "tslint --project tsconfig.json -e src/*.ts -t verbose", 46 | "prepare": "node ./node_modules/vscode/bin/install", 47 | "test": "echo 'Test is not enabled for this package'" 48 | }, 49 | "dependencies": { 50 | "@types/glob": "8.0.0", 51 | "@types/lodash": "4.14.186", 52 | "@types/marked": "4.0.7", 53 | "@types/minimatch": "5.1.2", 54 | "@types/node": "16.11.62", 55 | "@types/tmp": "0.2.3", 56 | "@types/uuid": "8.3.4", 57 | "@types/ws": "8.5.3", 58 | "compare-versions": "5.0.1", 59 | "fast-glob": "3.2.12", 60 | "glob": "8.0.3", 61 | "header-case-normalizer": "1.0.3", 62 | "is-stream": "3.0.0", 63 | "isbinaryfile": "5.0.0", 64 | "lodash": "4.17.21", 65 | "marked": "4.1.1", 66 | "merge-deep": "3.0.3", 67 | "minimatch": "5.1.0", 68 | "moment": "2.29.4", 69 | "moment-timezone": "0.5.37", 70 | "node-enumerable": "6.0.0", 71 | "p-queue": "6.6.2", 72 | "tmp": "0.2.1", 73 | "uuid": "9.0.0", 74 | "ws": "8.9.0" 75 | }, 76 | "devDependencies": { 77 | "del-cli": "5.0.0", 78 | "tslint": "6.1.3", 79 | "typescript": "4.8.4", 80 | "vsce": "2.11.0", 81 | "vscode": "^0.9.9" 82 | }, 83 | "engines": { 84 | "vscode": "^1.62.0" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/workflows/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as vscode from 'vscode'; 19 | 20 | /** 21 | * A workflow builder. 22 | */ 23 | export interface WorkflowBuilder { 24 | /** 25 | * Adds the next action to invoke. 26 | * 27 | * @param {WorkflowAction} action The next action. 28 | * 29 | * @chainable 30 | */ 31 | next(action: WorkflowAction): WorkflowBuilder; 32 | 33 | /** 34 | * Starts the workflow. 35 | * 36 | * @param {any} [initialPrevValue] The initial 'previous value'. 37 | * 38 | * @return {PromiseLike} The promise with the result. 39 | */ 40 | start(initialPrevValue?: any): PromiseLike; 41 | } 42 | 43 | /** 44 | * The context of a workflow action. 45 | */ 46 | export interface WorkflowActionContext { 47 | /** 48 | * The zero-based index of the current action. 49 | */ 50 | readonly index: number; 51 | /** 52 | * Gets or sets the value for the custom result for 'IWorkflowBuilder.start()'. 53 | */ 54 | result: any; 55 | /** 56 | * Gets or sets the value for the execution chain. 57 | */ 58 | value: any; 59 | } 60 | 61 | /** 62 | * A workflow action. 63 | * 64 | * @param {TPrev} prevValue The previous value. 65 | * @param {IWorkflowActionContext} context The current context. 66 | */ 67 | export type WorkflowAction = (prevValue: TPrev, context: WorkflowActionContext) => TNext | PromiseLike; 68 | 69 | /** 70 | * A symbol that indicates that 'IWorkflowActionContext.result' will NOT be used 71 | * as result value of 'IWorkflowBuilder.start()'. 72 | */ 73 | export const NO_CUSTOM_RESULT = Symbol('NO_CUSTOM_RESULT'); 74 | 75 | class WorkflowBuilderImpl implements WorkflowBuilder { 76 | private readonly _ACTIONS: WorkflowAction[] = []; 77 | private readonly _INITIAL_VALUE: any; 78 | 79 | constructor(initialValue?: any) { 80 | this._INITIAL_VALUE = initialValue; 81 | } 82 | 83 | public next(action: WorkflowAction) { 84 | this._ACTIONS.push(action); 85 | 86 | return this; 87 | } 88 | 89 | public async start(initialPrevValue?: any): Promise { 90 | let index = 0; 91 | let prevValue = initialPrevValue; 92 | let result: any = NO_CUSTOM_RESULT; 93 | let value = this._INITIAL_VALUE; 94 | while (index < this._ACTIONS.length) { 95 | const ACTION = this._ACTIONS[index]; 96 | 97 | const CTX: WorkflowActionContext = { 98 | index: index, 99 | result: result, 100 | value: value, 101 | }; 102 | 103 | try { 104 | if (ACTION) { 105 | prevValue = await Promise.resolve( 106 | ACTION(prevValue, CTX) 107 | ); 108 | } 109 | } finally { 110 | value = CTX.value; 111 | result = CTX.result; 112 | 113 | ++index; 114 | } 115 | } 116 | 117 | return NO_CUSTOM_RESULT === result ? prevValue 118 | : result; 119 | } 120 | } 121 | 122 | /** 123 | * Starts building a workflows. 124 | * 125 | * @param {any} [initialValue] The initial value for 'WorkflowActionContext.value'. 126 | * 127 | * @return {WorkflowBuilder} The new workflow builder. 128 | */ 129 | export function buildWorkflow(initialValue?: any): WorkflowBuilder { 130 | return new WorkflowBuilderImpl( initialValue ); 131 | } 132 | -------------------------------------------------------------------------------- /src/cache/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as _ from 'lodash'; 19 | import * as vscode_helpers from '../index'; 20 | 21 | /** 22 | * A cache provider. 23 | */ 24 | export interface CacheProvider { 25 | /** 26 | * Clears the cache. 27 | * 28 | * @returns this 29 | * 30 | * @chainable 31 | */ 32 | clear(): this; 33 | 34 | /** 35 | * Returns a value from the cache. 36 | * 37 | * @param {any} key The key of the value. 38 | * @param {TValue} [defaultValue] The default value. 39 | * 40 | * @returns {TValue|TDefault} The value. 41 | */ 42 | get(key: any, defaultValue?: TDefault): TValue | TDefault; 43 | 44 | /** 45 | * Checks if the cache contains a value. 46 | * 47 | * @param {string} key The key of the value. 48 | * 49 | * @return {boolean} Contains value or not. 50 | */ 51 | has(key: any): boolean; 52 | 53 | /** 54 | * Sets a value for an object. 55 | * 56 | * @param {string} key The key of the value. 57 | * @param {TValue} value The value to set. 58 | * 59 | * @returns this 60 | * 61 | * @chainable 62 | */ 63 | set(key: any, value: TValue): this; 64 | 65 | /** 66 | * Sets a value for an object. 67 | * 68 | * @param {string} name The name of the value. 69 | * @param {TValue} value The value to set. 70 | * 71 | * @returns this 72 | * 73 | * @chainable 74 | */ 75 | unset(name: string): this; 76 | } 77 | 78 | /** 79 | * A basic cache provider. 80 | */ 81 | export abstract class CacheProviderBase implements CacheProvider { 82 | /** @inheritdoc */ 83 | public abstract clear(): this; 84 | 85 | /** @inheritdoc */ 86 | public abstract get(key: any, defaultValue?: TDefault): TValue | TDefault; 87 | 88 | /** @inheritdoc */ 89 | public abstract has(key: any): boolean; 90 | 91 | /** @inheritdoc */ 92 | public abstract set(key: any, value: TValue): this; 93 | 94 | /** @inheritdoc */ 95 | public abstract unset(key: any): this; 96 | } 97 | 98 | /** 99 | * A cache provider, which stores values in memory. 100 | */ 101 | export class MemoryCache extends CacheProviderBase { 102 | /** 103 | * The storage object with values. 104 | */ 105 | protected _storage: { [key: string]: any } = {}; 106 | 107 | /** @inheritdoc */ 108 | public clear(): this { 109 | this._storage = {}; 110 | return this; 111 | } 112 | 113 | /** @inheritdoc */ 114 | public get(key: any, defaultValue?: TDefault): TValue | TDefault { 115 | key = this.normalizeKey(key); 116 | 117 | return this.has(key) ? this._storage[ key ] 118 | : defaultValue; 119 | } 120 | 121 | /** @inheritdoc */ 122 | public has(key: any): boolean { 123 | key = this.normalizeKey(key); 124 | 125 | return _.has(this._storage, key); 126 | } 127 | 128 | /** 129 | * Normalizes a key value. 130 | * 131 | * @param {any} key The input value. 132 | * 133 | * @return {string} The normalized value. 134 | */ 135 | protected normalizeKey(key: any): string { 136 | return vscode_helpers.normalizeString(key); 137 | } 138 | 139 | /** @inheritdoc */ 140 | public set(key: any, value: TValue): this { 141 | key = this.normalizeKey(key); 142 | 143 | this._storage[ key ] = value; 144 | return this; 145 | } 146 | 147 | /** @inheritdoc */ 148 | public unset(key: any): this { 149 | key = this.normalizeKey(key); 150 | 151 | delete this._storage[ key ]; 152 | return this; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/notifications/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | const CompareVersions = require('compare-versions'); 19 | import * as Moment from 'moment'; 20 | import * as vscode_helpers from '../index'; 21 | import * as vscode_helpers_http from '../http/index'; 22 | 23 | /** 24 | * An extension notification. 25 | */ 26 | export interface ExtensionNotification { 27 | /** 28 | * The content. 29 | */ 30 | content: string; 31 | /** 32 | * The ID. 33 | */ 34 | id: string; 35 | /** 36 | * Link 37 | */ 38 | link?: { 39 | /** 40 | * The URL of the link. 41 | */ 42 | href: string; 43 | /** 44 | * The display text of the link. 45 | */ 46 | text?: string; 47 | }; 48 | /** 49 | * The maximum extension version. 50 | */ 51 | max_version?: string; 52 | /** 53 | * The minimum extension version. 54 | */ 55 | min_version?: string; 56 | /** 57 | * The time stamp. 58 | */ 59 | time: string; 60 | /** 61 | * The optional title. 62 | */ 63 | title?: string; 64 | /** 65 | * The time, the message starts to "live". 66 | */ 67 | valid_from?: string; 68 | /** 69 | * The time, while the message "lives". 70 | */ 71 | valid_until?: string; 72 | /** 73 | * The type. 74 | */ 75 | type?: string; 76 | } 77 | 78 | /** 79 | * Options for 'filterExtensionNotifications()' function. 80 | */ 81 | export interface FilterExtensionNotificationsOptions { 82 | /** 83 | * The version of the extension. 84 | */ 85 | version?: string; 86 | } 87 | 88 | /** 89 | * Filters extension notifications for display. 90 | * 91 | * @param {ExtensionNotification | ExtensionNotification[]} notifications The notifications to filter. 92 | * @param {FilterExtensionNotificationsOptions} [opts] Custom filter options. 93 | */ 94 | export function filterExtensionNotifications( 95 | notifications: ExtensionNotification | ExtensionNotification[], 96 | opts?: FilterExtensionNotificationsOptions 97 | ) { 98 | if (!opts) { 99 | opts = {}; 100 | } 101 | 102 | const NOW = Moment.utc(); 103 | 104 | return vscode_helpers.asArray( 105 | notifications 106 | ).filter(n => { 107 | // versions 108 | try { 109 | const CURRENT_VERSION = vscode_helpers.toStringSafe(opts.version).trim(); 110 | if ('' !== CURRENT_VERSION) { 111 | // min_version 112 | try { 113 | const MIN_VERSION = vscode_helpers.toStringSafe(n.min_version).trim(); 114 | if ('' !== MIN_VERSION) { 115 | if (CompareVersions(CURRENT_VERSION, MIN_VERSION) < 0) { 116 | return false; 117 | } 118 | } 119 | } catch { } 120 | 121 | // max_version 122 | try { 123 | const MAX_VERSION = vscode_helpers.toStringSafe(n.max_version).trim(); 124 | if ('' !== MAX_VERSION) { 125 | if (CompareVersions(CURRENT_VERSION, MAX_VERSION) > 0) { 126 | return false; 127 | } 128 | } 129 | } catch { } 130 | } 131 | } catch { } 132 | 133 | // valid_from 134 | try { 135 | const VALID_FROM = vscode_helpers.toStringSafe( n.valid_from ); 136 | if (!vscode_helpers.isEmptyString(VALID_FROM)) { 137 | let validFrom = Moment.utc(VALID_FROM); 138 | if (validFrom.isValid()) { 139 | if (NOW.isBefore(validFrom)) { 140 | return false; 141 | } 142 | } 143 | } 144 | } catch { } 145 | 146 | // valid_until 147 | try { 148 | const VALID_UNTIL = vscode_helpers.toStringSafe( n.valid_until ); 149 | if (!vscode_helpers.isEmptyString(VALID_UNTIL)) { 150 | let validUntil = Moment.utc(VALID_UNTIL); 151 | if (validUntil.isValid()) { 152 | if (NOW.isAfter(validUntil)) { 153 | return false; 154 | } 155 | } 156 | } 157 | } catch { } 158 | 159 | return true; 160 | }); 161 | } 162 | 163 | /** 164 | * Returns the notifications for an extension. 165 | * 166 | * @param {vscode_helpers_http.HTTPRequestURL} url The URL of the JSON file, which contains the notifications. 167 | * 168 | * @return {Promise} The promise with the notifications. 169 | */ 170 | export async function getExtensionNotifications( 171 | url: vscode_helpers_http.HTTPRequestURL 172 | ): Promise { 173 | const RESP = await vscode_helpers_http.GET( 174 | vscode_helpers.toStringSafe(url) 175 | ); 176 | 177 | return vscode_helpers.asArray( 178 | JSON.parse( 179 | (await RESP.readBody()).toString('utf8') 180 | ) 181 | ); 182 | } 183 | -------------------------------------------------------------------------------- /src/progress/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as _ from 'lodash'; 19 | import * as vscode from 'vscode'; 20 | import * as vscode_helpers from '../index'; 21 | 22 | /** 23 | * A progress context. 24 | */ 25 | export interface ProgressContext { 26 | /** 27 | * The base context provided by Visual Studio Code. 28 | */ 29 | baseContext: vscode.Progress<{ message?: string; increment?: number }>; 30 | /** 31 | * If cancellable, this contains the "cancellation token". 32 | */ 33 | cancellationToken?: vscode.CancellationToken; 34 | /** 35 | * The increment value. 36 | */ 37 | increment: number; 38 | /** 39 | * Increments the progress value only if an item has not been handled yet. 40 | * 41 | * @param {any} item The item to check. 42 | * @param {string} message The new message. 43 | * 44 | * @return {boolean} Value has been increased or not. 45 | */ 46 | incrementIfNeeded: (item: any, message: string) => boolean; 47 | /** 48 | * Gets or sets the status message. 49 | */ 50 | message: string; 51 | } 52 | 53 | /** 54 | * Progress options. 55 | */ 56 | export interface ProgressOptions { 57 | /** 58 | * Show cancel button or not. 59 | */ 60 | cancellable?: boolean; 61 | /** 62 | * The location. 63 | */ 64 | location?: vscode.ProgressLocation; 65 | /** 66 | * The title. 67 | */ 68 | title?: string; 69 | } 70 | 71 | /** 72 | * A progress result. 73 | */ 74 | export type ProgressResult = TResult | PromiseLike; 75 | 76 | /** 77 | * A progress task. 78 | * 79 | * @param {ProgressContext} context The underlying context. 80 | * 81 | * @return {ProgressResult} The result. 82 | */ 83 | export type ProgressTask = (context: ProgressContext) => ProgressResult; 84 | 85 | /** 86 | * Runs a task with progress information. 87 | * 88 | * @param {ProgressTask} task The task to execute. 89 | * @param {ProgressOptions} [options] Additional options. 90 | * 91 | * @return {Promise} The promise with the result. 92 | */ 93 | export async function withProgress(task: ProgressTask, 94 | options?: ProgressOptions): Promise { 95 | if (!options) { 96 | options = {}; 97 | } 98 | 99 | const OPTS: vscode.ProgressOptions = { 100 | cancellable: vscode_helpers.toBooleanSafe(options.cancellable), 101 | location: _.isNil(options.location) ? vscode.ProgressLocation.Notification : options.location, 102 | title: vscode_helpers.toStringSafe( options.title ), 103 | }; 104 | 105 | return vscode.window.withProgress(OPTS, (p, ct) => { 106 | let handledItems: any[] = []; 107 | 108 | try { 109 | let msg: string; 110 | let increment: number; 111 | 112 | const CTX: ProgressContext = { 113 | baseContext: p, 114 | cancellationToken: ct, 115 | increment: undefined, 116 | incrementIfNeeded: function(item, msg) { 117 | if (handledItems.indexOf(item) < 0) { 118 | handledItems.push(item); 119 | this.message = msg; 120 | 121 | return true; 122 | } 123 | 124 | return false; 125 | }, 126 | message: undefined, 127 | }; 128 | 129 | const UPDATE_PROGRESS = () => { 130 | p.report({ 131 | increment: increment, 132 | message: msg, 133 | }); 134 | }; 135 | 136 | // CTX.increment 137 | Object.defineProperty(CTX, 'increment', { 138 | enumerable: true, 139 | 140 | get: () => { 141 | return increment; 142 | }, 143 | 144 | set: (newValue) => { 145 | if (!_.isNil(newValue)) { 146 | newValue = parseFloat( vscode_helpers.toStringSafe(newValue).trim() ); 147 | } 148 | 149 | increment = newValue; 150 | } 151 | }); 152 | 153 | // CTX.message 154 | Object.defineProperty(CTX, 'message', { 155 | enumerable: true, 156 | 157 | get: () => { 158 | return msg; 159 | }, 160 | 161 | set: (newValue) => { 162 | if (_.isNil(newValue)) { 163 | newValue = undefined; 164 | } else { 165 | newValue = vscode_helpers.toStringSafe(newValue); 166 | } 167 | 168 | msg = newValue; 169 | UPDATE_PROGRESS(); 170 | } 171 | }); 172 | 173 | return Promise.resolve( 174 | task(CTX) 175 | ); 176 | } finally { 177 | handledItems = null; 178 | } 179 | }); 180 | } 181 | -------------------------------------------------------------------------------- /src/disposable/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as _ from 'lodash'; 19 | import * as Events from 'events'; 20 | import * as vscode from 'vscode'; 21 | import * as vscode_helpers from '../index'; 22 | 23 | /** 24 | * Name of the event, when an object has been disposed. 25 | */ 26 | export const EVENT_DISPOSED = 'disposed'; 27 | /** 28 | * Name of an event, when an object is going to be disposed. 29 | */ 30 | export const EVENT_DISPOSING = 'disposing'; 31 | 32 | /** 33 | * A disposable object. 34 | */ 35 | export abstract class DisposableBase extends Events.EventEmitter implements vscode.Disposable { 36 | /** 37 | * Stores disposable sub objects. 38 | */ 39 | protected readonly _DISPOSABLES: vscode.Disposable[] = []; 40 | /** 41 | * Stores intervals. 42 | */ 43 | protected readonly _INTERVALS: NodeJS.Timer[] = []; 44 | private _isDisposed = false; 45 | private _isDisposing = false; 46 | /** 47 | * Stores timeouts. 48 | */ 49 | protected readonly _TIMEOUTS: NodeJS.Timer[] = []; 50 | 51 | /** 52 | * Cleansup all timeouts. 53 | */ 54 | protected cleanupIntervals() { 55 | while (this._TIMEOUTS.length > 0) { 56 | vscode_helpers.tryClearInterval( 57 | this._TIMEOUTS.shift() 58 | ); 59 | } 60 | } 61 | 62 | /** 63 | * Cleansup all timeouts. 64 | */ 65 | protected cleanupTimeouts() { 66 | while (this._TIMEOUTS.length > 0) { 67 | vscode_helpers.tryClearTimeout( 68 | this._TIMEOUTS.shift() 69 | ); 70 | } 71 | } 72 | 73 | /** @inheritdoc */ 74 | public dispose() { 75 | if (this.isInFinalizeState) { 76 | return; 77 | } 78 | 79 | try { 80 | this._isDisposing = true; 81 | this.emit( EVENT_DISPOSING ); 82 | 83 | this.cleanupIntervals(); 84 | this.cleanupTimeouts(); 85 | 86 | this.removeAllListeners(); 87 | 88 | while (this._DISPOSABLES.length > 0) { 89 | tryDispose( 90 | this._DISPOSABLES.shift() 91 | ); 92 | } 93 | 94 | this.onDispose(); 95 | 96 | this._isDisposed = true; 97 | this.emit( EVENT_DISPOSED ); 98 | } finally { 99 | this._isDisposing = false; 100 | } 101 | } 102 | 103 | /** 104 | * Gets if object has been disposed or not. 105 | */ 106 | public get isDisposed() { 107 | return this._isDisposed; 108 | } 109 | 110 | /** 111 | * Gets if the 'dispose()' method is currently executed or not. 112 | */ 113 | public get isDisposing() { 114 | return this._isDisposing; 115 | } 116 | 117 | /** 118 | * Gets if the object is disposed or currently disposing. 119 | */ 120 | public get isInFinalizeState() { 121 | return this.isDisposed || this.isDisposing; 122 | } 123 | 124 | /** 125 | * Additional logic for the 'dispose()' method. 126 | */ 127 | protected onDispose(): void { 128 | } 129 | } 130 | 131 | /** 132 | * Clones an object and makes it non disposable. 133 | * 134 | * @param {TObj} obj The object to clone. 135 | * @param {boolean} [throwOnDispose] Throw error when coll 'dispose()' method or not. 136 | * 137 | * @return {TObj} The cloned object. 138 | */ 139 | export function makeNonDisposable( 140 | obj: TObj, 141 | throwOnDispose = true, 142 | ): TObj { 143 | throwOnDispose = vscode_helpers.toBooleanSafe(throwOnDispose, true); 144 | 145 | const CLONED_OBJ: any = vscode_helpers.cloneObjectFlat(obj); 146 | 147 | if (CLONED_OBJ) { 148 | if (_.isFunction(CLONED_OBJ.dispose)) { 149 | CLONED_OBJ.dispose = () => { 150 | if (throwOnDispose) { 151 | throw new Error('Disposing object is not allowed!'); 152 | } 153 | }; 154 | } 155 | } 156 | 157 | return CLONED_OBJ; 158 | } 159 | 160 | /** 161 | * Tries to dispose an object. 162 | * 163 | * @param {object} obj The object to dispose. 164 | * 165 | * @return {boolean} Operation was successful or not. 166 | */ 167 | export function tryDispose(obj: vscode.Disposable): boolean { 168 | try { 169 | if (obj && obj.dispose) { 170 | obj.dispose(); 171 | } 172 | 173 | return true; 174 | } catch { 175 | return false; 176 | } 177 | } 178 | 179 | /** 180 | * Tries to dispose an object inside another, parent object and deletes it there. 181 | * 182 | * @param {any} obj The "other" / parent object. 183 | * @param {PropertyKey} key The key inside 'obj', where the disposable object is stored and should be removed. 184 | * @param {boolean} [alwaysDelete] Delete even if operation failed or not. 185 | * 186 | * @return {vscode.Disposable|false} The disposed and removed object or (false) if failed. 187 | */ 188 | export function tryDisposeAndDelete(obj: any, key: PropertyKey, alwaysDelete = true): false | vscode.Disposable { 189 | alwaysDelete = vscode_helpers.toBooleanSafe(alwaysDelete, true); 190 | 191 | let result: false | vscode.Disposable; 192 | 193 | try { 194 | if (obj) { 195 | let deleteObject = true; 196 | result = obj[key]; 197 | 198 | if (!tryDispose( result )) { 199 | deleteObject = alwaysDelete; 200 | } 201 | 202 | if (deleteObject) { 203 | delete obj[key]; 204 | } else { 205 | result = false; 206 | } 207 | } 208 | } catch { 209 | result = false; 210 | } 211 | 212 | return result; 213 | } 214 | 215 | /** 216 | * Invokes a function for a disposable object and keeps sure, that this object will be disposed, 217 | * even on error. 218 | * 219 | * @param {TObj} obj The object. 220 | * @param {Function} func The function to invoke. 221 | * @param {any[]} [args] One or more additional arguments for the function. 222 | * 223 | * @return Promise The promise with the result of the function. 224 | */ 225 | export async function using( 226 | obj: TObj, 227 | func: (o: TObj, ...args: any[]) => TResult | PromiseLike, 228 | ...args: any[] 229 | ): Promise { 230 | try { 231 | return await Promise.resolve( 232 | func.apply(null, 233 | [ obj ].concat(args)) 234 | ); 235 | } finally { 236 | if (obj) { 237 | obj.dispose(); 238 | } 239 | } 240 | } 241 | 242 | /** 243 | * Invokes a function for a disposable object sync and keeps sure, that this object will be disposed, 244 | * even on error. 245 | * 246 | * @param {TObj} obj The object. 247 | * @param {Function} func The function to invoke. 248 | * @param {any[]} [args] One or more additional arguments for the function. 249 | * 250 | * @return TResult The result of the function. 251 | */ 252 | export function usingSync( 253 | obj: TObj, 254 | func: (o: TObj, ...args: any[]) => TResult, 255 | ...args: any[] 256 | ): TResult { 257 | try { 258 | return func.apply(null, 259 | [ obj ].concat(args)); 260 | } finally { 261 | if (obj) { 262 | obj.dispose(); 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /src/timers/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as _ from 'lodash'; 19 | import * as Moment from 'moment'; 20 | import * as vscode from 'vscode'; 21 | import * as vscode_helpers from '../index'; 22 | 23 | /** 24 | * An action for 'invokeAfter()' function. 25 | * 26 | * @param {any[]} [args] The arguments for the action. 27 | * 28 | * @return {TResult|PromiseLike} The result of the action. 29 | */ 30 | export type InvokeAfterAction = (...args: any[]) => TResult | PromiseLike; 31 | 32 | /** 33 | * Additional options for 'waitWhile()' function. 34 | */ 35 | export interface WaitWhileOptions { 36 | /** 37 | * A timeout, in milliseconds. 38 | */ 39 | timeout?: number; 40 | /** 41 | * The optional time, in milliseconds, to wait until next check. 42 | */ 43 | timeUntilNextCheck?: number; 44 | } 45 | 46 | /** 47 | * A stopwatch. 48 | */ 49 | export class StopWatch { 50 | private _startTime: Moment.Moment | false = false; 51 | 52 | /** 53 | * Gets if the stop watch is running or not. 54 | */ 55 | public get isRunning(): boolean { 56 | return Moment.isMoment(this._startTime); 57 | } 58 | 59 | /** 60 | * (Re-)Starts the stop watch. 61 | * 62 | * @return this 63 | */ 64 | public start(): this { 65 | this._startTime = Moment.utc(); 66 | return this; 67 | } 68 | 69 | /** 70 | * Stops the watch. 71 | * 72 | * @return {number} The number of milliseconds. 73 | */ 74 | public stop(): number { 75 | const NOW = Moment.utc(); 76 | const START_TIME = this._startTime; 77 | 78 | this._startTime = false; 79 | 80 | if (Moment.isMoment(START_TIME)) { 81 | return NOW.diff(START_TIME, 'ms', true); 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * Creates a disposable interval. 88 | * 89 | * @param {Function} callback The callback. 90 | * @param {number} ms The interval in milliseconds. 91 | * @param {any[]} [args] The arguments for the callback. 92 | * 93 | * @return {vscode.Disposable} The disposable for the interval. 94 | */ 95 | export function createInterval(callback: Function, ms: number, ...args: any[]): vscode.Disposable { 96 | ms = parseInt( vscode_helpers.toStringSafe(ms).trim() ); 97 | if (isNaN(ms)) { 98 | ms = 1000; 99 | } 100 | 101 | const TIMER = setInterval.apply( 102 | null, 103 | [ callback, ms ].concat( vscode_helpers.asArray(args, false) ) 104 | ); 105 | 106 | return { 107 | dispose: () => { 108 | clearInterval(TIMER); 109 | }, 110 | }; 111 | } 112 | 113 | /** 114 | * Creates a disposable timeout. 115 | * 116 | * @param {Function} callback The callback. 117 | * @param {number} ms The timeout in milliseconds. 118 | * @param {any[]} [args] The arguments for the callback. 119 | * 120 | * @return {vscode.Disposable} The disposable for the timeout. 121 | */ 122 | export function createTimeout(callback: Function, ms: number, ...args: any[]): vscode.Disposable { 123 | ms = parseInt( vscode_helpers.toStringSafe(ms).trim() ); 124 | if (isNaN(ms)) { 125 | ms = 1000; 126 | } 127 | 128 | const TIMER = setTimeout.apply( 129 | null, 130 | [ callback, ms ].concat( vscode_helpers.asArray(args, false) ) 131 | ); 132 | 133 | return { 134 | dispose: () => { 135 | clearTimeout(TIMER); 136 | }, 137 | }; 138 | } 139 | 140 | /** 141 | * Invokes an action after a timeout. 142 | * 143 | * @param {Function} action The action to invoke. 144 | * @param {number} [ms] The custom time, in milliseconds, after the action should be invoked. 145 | * @param {any[]} [args] One or more arguments for the action. 146 | * 147 | * @return {Promise} The promise with the result. 148 | */ 149 | export function invokeAfter(action: InvokeAfterAction, ms?: number, ...args: any[]) { 150 | const ACTION_ARGS = args.filter((x, index) => { 151 | return index >= 2; 152 | }); 153 | 154 | ms = parseInt( 155 | vscode_helpers.toStringSafe(ms).trim() 156 | ); 157 | if (isNaN(ms)) { 158 | ms = 1000; 159 | } 160 | 161 | return new Promise((resolve, reject) => { 162 | const COMPLETED = vscode_helpers.createCompletedAction(resolve, reject); 163 | 164 | try { 165 | setTimeout(() => { 166 | try { 167 | Promise.resolve( 168 | action.apply(null, ACTION_ARGS), 169 | ).then((result: TResult) => { 170 | COMPLETED(null, result); 171 | }).catch((err) => { 172 | COMPLETED(err); 173 | }); 174 | } catch (e) { 175 | COMPLETED(e); 176 | } 177 | }, ms); 178 | } catch (e) { 179 | COMPLETED(e); 180 | } 181 | }); 182 | } 183 | 184 | /** 185 | * Waits a number of milliseconds. 186 | * 187 | * @param {number} [ms] The custom time, in milliseconds, to wait. 188 | */ 189 | export async function sleep(ms?: number) { 190 | await invokeAfter(() => {}, ms); 191 | } 192 | 193 | /** 194 | * Creates and starts a new stop watch. 195 | * 196 | * @return {StopWatch} The new, started watch. 197 | */ 198 | export function startWatch() { 199 | return (new StopWatch()).start(); 200 | } 201 | 202 | /** 203 | * Tries to clear an interval. 204 | * 205 | * @param {NodeJS.Timer} intervalId The interval (ID). 206 | * 207 | * @return {boolean} Operation was successfull or not. 208 | */ 209 | export function tryClearInterval(intervalId: NodeJS.Timer): boolean { 210 | try { 211 | if (!_.isNil(intervalId)) { 212 | clearInterval(intervalId); 213 | } 214 | 215 | return true; 216 | } catch (e) { 217 | return false; 218 | } 219 | } 220 | 221 | /** 222 | * Tries to clear a timeout. 223 | * 224 | * @param {NodeJS.Timer} timeoutId The timeout (ID). 225 | * 226 | * @return {boolean} Operation was successfull or not. 227 | */ 228 | export function tryClearTimeout(timeoutId: NodeJS.Timer): boolean { 229 | try { 230 | if (!_.isNil(timeoutId)) { 231 | clearTimeout(timeoutId); 232 | } 233 | 234 | return true; 235 | } catch (e) { 236 | return false; 237 | } 238 | } 239 | 240 | /** 241 | * Waits while a predicate matches. 242 | * 243 | * @param {Function} predicate The predicate. 244 | * @param {WaitWhileOptions} {opts} Additional options. 245 | * 246 | * @return {Promise} The promise that indicates if timeout reached (false) or not (true). 247 | */ 248 | export async function waitWhile(predicate: () => boolean | PromiseLike, 249 | opts?: WaitWhileOptions) { 250 | if (!opts) { 251 | opts = {}; 252 | } 253 | 254 | const TIME_UNTIL_NEXT_CHECK = parseInt( 255 | vscode_helpers.toStringSafe(opts.timeUntilNextCheck).trim() 256 | ); 257 | 258 | const TIMEOUT = parseInt( 259 | vscode_helpers.toStringSafe(opts.timeout).trim() 260 | ); 261 | 262 | let runUntil: Moment.Moment | false = false; 263 | if (!isNaN(TIMEOUT)) { 264 | runUntil = Moment.utc() 265 | .add(TIMEOUT, 'ms'); 266 | } 267 | 268 | let wait: boolean; 269 | do { 270 | const NOW = Moment.utc(); 271 | 272 | if (false !== runUntil) { 273 | if (NOW.isAfter(runUntil)) { 274 | return false; 275 | } 276 | } 277 | 278 | wait = vscode_helpers.toBooleanSafe( 279 | await Promise.resolve( 280 | predicate() 281 | ) 282 | ); 283 | 284 | if (wait) { 285 | if (!isNaN(TIME_UNTIL_NEXT_CHECK)) { 286 | await sleep(TIME_UNTIL_NEXT_CHECK); // wait before next check 287 | } 288 | } 289 | } 290 | while (wait); 291 | 292 | return true; 293 | } 294 | -------------------------------------------------------------------------------- /src/logging/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as Events from 'events'; 19 | import * as Moment from 'moment'; 20 | import * as vscode from 'vscode'; 21 | import * as vscode_helpers from '../index'; 22 | 23 | /** 24 | * A log action. 25 | * 26 | * @param {LogContext} context The log context. 27 | */ 28 | export type LogAction = (context: LogContext) => any; 29 | 30 | /** 31 | * A log context. 32 | */ 33 | export interface LogContext { 34 | /** 35 | * The message. 36 | */ 37 | readonly message: any; 38 | /** 39 | * The tag. 40 | */ 41 | readonly tag?: string; 42 | /** 43 | * The time. 44 | */ 45 | readonly time: Moment.Moment; 46 | /** 47 | * The type. 48 | */ 49 | readonly type?: LogType; 50 | } 51 | 52 | /** 53 | * A log filter. 54 | */ 55 | export type LogFilter = (context: LogContext) => any; 56 | 57 | /** 58 | * A logger. 59 | */ 60 | export interface Logger extends NodeJS.EventEmitter { 61 | /** 62 | * Logs an alert message. 63 | */ 64 | readonly alert: TypedLogAction; 65 | /** 66 | * Logs a critical message. 67 | */ 68 | readonly crit: TypedLogAction; 69 | /** 70 | * Logs a debug message. 71 | */ 72 | readonly debug: TypedLogAction; 73 | /** 74 | * Logs an emergency message. 75 | */ 76 | readonly emerg: TypedLogAction; 77 | /** 78 | * Logs an error message. 79 | */ 80 | readonly err: TypedLogAction; 81 | /** 82 | * Logs an info message. 83 | */ 84 | readonly info: TypedLogAction; 85 | /** 86 | * Logs a message. 87 | * 88 | * @param {LogType} The type. 89 | * @param {any} msg The message to log. 90 | * @param {string} [tag] The additional tag. 91 | */ 92 | readonly log: (type: LogType, 93 | msg: any, tag?: string) => PromiseLike | void; 94 | /** 95 | * Logs a note message. 96 | */ 97 | readonly notice: TypedLogAction; 98 | /** 99 | * Logs a trace message. 100 | */ 101 | readonly trace: TypedLogAction; 102 | /** 103 | * Logs a warning message. 104 | */ 105 | readonly warn: TypedLogAction; 106 | } 107 | 108 | /** 109 | * A typed log action. 110 | * 111 | * @param {any} msg The message to log. 112 | * @param {string} [tag] An additional, optional tag. 113 | * 114 | * @return {Logger} The logger instance. 115 | * 116 | * @chainable 117 | */ 118 | export type TypedLogAction = (msg: any, tag?: string) => Logger; 119 | 120 | /** 121 | * List of log types. 122 | */ 123 | export enum LogType { 124 | /** 125 | * Emergency 126 | */ 127 | Emerg = 0, 128 | /** 129 | * Alert 130 | */ 131 | Alert = 1, 132 | /** 133 | * Critical 134 | */ 135 | Crit = 2, 136 | /** 137 | * Error 138 | */ 139 | Err = 3, 140 | /** 141 | * Warning 142 | */ 143 | Warn = 4, 144 | /** 145 | * Notice 146 | */ 147 | Notice = 5, 148 | /** 149 | * Informational 150 | */ 151 | Info = 6, 152 | /** 153 | * Debug 154 | */ 155 | Debug = 7, 156 | /** 157 | * Trace 158 | */ 159 | Trace = 8, 160 | } 161 | 162 | /** 163 | * A basic logger. 164 | */ 165 | export abstract class LoggerBase extends Events.EventEmitter implements Logger { 166 | /** @inheritdoc */ 167 | public alert(msg: any, tag?: string): this { 168 | return this.logSync(LogType.Alert, 169 | msg, tag); 170 | } 171 | 172 | /** @inheritdoc */ 173 | public crit(msg: any, tag?: string): this { 174 | return this.logSync(LogType.Crit, 175 | msg, tag); 176 | } 177 | 178 | /** @inheritdoc */ 179 | public debug(msg: any, tag?: string): this { 180 | return this.logSync(LogType.Debug, 181 | msg, tag); 182 | } 183 | 184 | /** @inheritdoc */ 185 | public emerg(msg: any, tag?: string): this { 186 | return this.logSync(LogType.Emerg, 187 | msg, tag); 188 | } 189 | 190 | /** @inheritdoc */ 191 | public err(msg: any, tag?: string): this { 192 | return this.logSync(LogType.Err, 193 | msg, tag); 194 | } 195 | 196 | /** @inheritdoc */ 197 | public info(msg: any, tag?: string): this { 198 | return this.logSync(LogType.Info, 199 | msg, tag); 200 | } 201 | 202 | /** @inheritdoc */ 203 | public async log(type: LogType, msg: any, tag?: string) { 204 | const CONTEXT: LogContext = { 205 | message: msg, 206 | tag: this.normalizeTag(tag), 207 | time: Moment(), 208 | type: type, 209 | }; 210 | 211 | const RAISE_EVENT = await Promise.resolve( 212 | vscode_helpers.toBooleanSafe(await this.onLog(CONTEXT), 213 | true), 214 | ); 215 | 216 | if (RAISE_EVENT) { 217 | this.emit('log', 218 | CONTEXT); 219 | } 220 | } 221 | 222 | /** 223 | * Sync logging. 224 | * 225 | * @param {LogType} type The type. 226 | * @param {any} msg The message. 227 | * @param {string} [tag] The optional tag. 228 | */ 229 | protected logSync(type: LogType, msg: any, tag?: string): this { 230 | this.log(type, msg, tag); 231 | 232 | return this; 233 | } 234 | 235 | /** 236 | * Normalizes a tag value. 237 | * 238 | * @param {string} tag The input value. 239 | * 240 | * @return {string} The output value. 241 | */ 242 | protected normalizeTag(tag: string): string { 243 | tag = vscode_helpers.normalizeString(tag, s => s.toUpperCase().trim()); 244 | if ('' === tag) { 245 | tag = undefined; 246 | } 247 | 248 | return tag; 249 | } 250 | 251 | /** @inheritdoc */ 252 | public notice(msg: any, tag?: string): this { 253 | return this.logSync(LogType.Notice, 254 | msg, tag); 255 | } 256 | 257 | /** 258 | * The logic for logging a message. 259 | * 260 | * @param {LogContext} context The context. 261 | * 262 | * @return {Promise} Invoke log event or not. 263 | */ 264 | protected abstract onLog(context: LogContext): Promise; 265 | 266 | /** @inheritdoc */ 267 | public trace(msg: any, tag?: string): this { 268 | return this.logSync(LogType.Trace, 269 | msg, tag); 270 | } 271 | 272 | /** @inheritdoc */ 273 | public warn(msg: any, tag?: string): this { 274 | return this.logSync(LogType.Warn, 275 | msg, tag); 276 | } 277 | } 278 | 279 | /** 280 | * A logger based on actions. 281 | */ 282 | export class ActionLogger extends LoggerBase { 283 | private _actions: LogAction[] = []; 284 | private _filters: LogFilter[] = []; 285 | 286 | /** 287 | * Adds a new action. 288 | * 289 | * @param {LogAction} action The action to add. 290 | * 291 | * @return this 292 | * 293 | * @chainable 294 | */ 295 | public addAction(action: LogAction): this { 296 | this._actions 297 | .push(action); 298 | 299 | return this; 300 | } 301 | 302 | /** 303 | * Adds a new filter. 304 | * 305 | * @param {LogFilter} filter The filter to add. 306 | * 307 | * @return this 308 | * 309 | * @chainable 310 | */ 311 | public addFilter(filter: LogFilter): this { 312 | this._filters 313 | .push(filter); 314 | 315 | return this; 316 | } 317 | 318 | /** 319 | * Clears anything of that logger. 320 | * 321 | * @return this 322 | * 323 | * @chainable 324 | */ 325 | public clear(): this { 326 | return this.clearActions() 327 | .clearFilters(); 328 | } 329 | 330 | /** 331 | * Clears the action list. 332 | * 333 | * @return this 334 | * 335 | * @chainable 336 | */ 337 | public clearActions(): this { 338 | this._actions = []; 339 | 340 | return this; 341 | } 342 | 343 | /** 344 | * Clears the filter list. 345 | * 346 | * @return this 347 | * 348 | * @chainable 349 | */ 350 | public clearFilters(): this { 351 | this._filters = []; 352 | 353 | return this; 354 | } 355 | 356 | /** @inheritdoc */ 357 | protected async onLog(context: LogContext) { 358 | const ACTIONS = this._actions || []; 359 | const FILTERS = this._filters || []; 360 | 361 | for (let i = 0; i < ACTIONS.length; i++) { 362 | try { 363 | const LOG_ACTION = ACTIONS[i]; 364 | 365 | let doLog = true; 366 | for (let j = 0; j < FILTERS.length; j++) { 367 | try { 368 | const LOG_FILTER = FILTERS[j]; 369 | 370 | doLog = vscode_helpers.toBooleanSafe( 371 | await Promise.resolve( 372 | LOG_FILTER(context) 373 | ), true 374 | ); 375 | } catch { } 376 | 377 | if (!doLog) { 378 | break; 379 | } 380 | } 381 | 382 | if (doLog) { 383 | LOG_ACTION(context); 384 | } 385 | } catch { } 386 | } 387 | } 388 | } 389 | 390 | /** 391 | * Creates a new logger instance. 392 | * 393 | * @param {LogAction[]} [actions] One or more initial actions to define. 394 | * 395 | * @return {vscode_helpers_logging.ActionLogger} The new logger. 396 | */ 397 | export function createLogger(...actions: LogAction[]): ActionLogger { 398 | const NEW_LOGGER = new ActionLogger(); 399 | actions.forEach(a => { 400 | NEW_LOGGER.addAction(a); 401 | }); 402 | 403 | return NEW_LOGGER; 404 | } 405 | -------------------------------------------------------------------------------- /src/http/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as _ from 'lodash'; 19 | import * as HTTP from 'http'; 20 | import * as HTTPs from 'https'; 21 | import * as IsStream from 'is-stream'; 22 | const MergeDeep = require('merge-deep'); 23 | const NormalizeHeaderCase = require("header-case-normalizer"); 24 | import * as Stream from 'stream'; 25 | import * as URL from 'url'; 26 | import * as vscode from 'vscode'; 27 | import * as vscode_helpers from '../index'; 28 | 29 | /** 30 | * A possible value for a HTTP request body. 31 | */ 32 | export type HTTPRequestBody = string | Buffer | NodeJS.ReadableStream; 33 | 34 | /** 35 | * HTTP(s) request options. 36 | */ 37 | export type HTTPRequestOptions = HTTP.RequestOptions | HTTPs.RequestOptions; 38 | 39 | /** 40 | * A result of a HTTP request. 41 | */ 42 | export interface HTTPRequestResult { 43 | /** 44 | * The (status) code. 45 | */ 46 | code: number; 47 | /** 48 | * Reads and returns the body (data). 49 | */ 50 | readBody: () => PromiseLike; 51 | /** 52 | * The options of the request. 53 | */ 54 | request: HTTP.RequestOptions | HTTPs.RequestOptions; 55 | /** 56 | * The response context. 57 | */ 58 | response: HTTP.IncomingMessage; 59 | /** 60 | * The status (message). 61 | */ 62 | status: string; 63 | /** 64 | * The request URL. 65 | */ 66 | url: URL.Url; 67 | /** 68 | * The HTTP version. 69 | */ 70 | version: string; 71 | } 72 | 73 | /** 74 | * A possible value for a HTTP request URL. 75 | */ 76 | export type HTTPRequestURL = string | vscode.Uri | URL.Url; 77 | 78 | /** 79 | * Does a HTTP 'DELETE' request. 80 | * 81 | * @param {HTTPRequestURL} url The URL. 82 | * @param {HTTPRequestBody} [body] The data of the request body. 83 | * @param {any} [headers] A key-value-pair of headers to send. 84 | * @param {HTTPRequestOptions} [opts] Custom options for the request. 85 | * 86 | * @return {Promise} The promsie with the HTTP response / result. 87 | */ 88 | export function DELETE(url: HTTPRequestURL, body?: HTTPRequestBody, headers?: any, opts?: HTTPRequestOptions) { 89 | return request('DELETE', url, body, headers, opts); 90 | } 91 | 92 | /** 93 | * Does a HTTP 'GET' request. 94 | * 95 | * @param {HTTPRequestURL} url The URL. 96 | * @param {any} [headers] A key-value-pair of headers to send. 97 | * @param {HTTPRequestOptions} [opts] Custom options for the request. 98 | * 99 | * @return {Promise} The promsie with the HTTP response / result. 100 | */ 101 | export function GET(url: HTTPRequestURL, headers?: any, opts?: HTTPRequestOptions) { 102 | return request('GET', url, null, headers, opts); 103 | } 104 | 105 | /** 106 | * Does a HTTP 'PATCH' request. 107 | * 108 | * @param {HTTPRequestURL} url The URL. 109 | * @param {HTTPRequestBody} [body] The data of the request body. 110 | * @param {any} [headers] A key-value-pair of headers to send. 111 | * @param {HTTPRequestOptions} [opts] Custom options for the request. 112 | * 113 | * @return {Promise} The promsie with the HTTP response / result. 114 | */ 115 | export function PATCH(url: HTTPRequestURL, body?: HTTPRequestBody, headers?: any, opts?: HTTPRequestOptions) { 116 | return request('PATCH', url, body, headers, opts); 117 | } 118 | 119 | /** 120 | * Does a HTTP 'POST' request. 121 | * 122 | * @param {HTTPRequestURL} url The URL. 123 | * @param {HTTPRequestBody} [body] The data of the request body. 124 | * @param {any} [headers] A key-value-pair of headers to send. 125 | * @param {HTTPRequestOptions} [opts] Custom options for the request. 126 | * 127 | * @return {Promise} The promsie with the HTTP response / result. 128 | */ 129 | export function POST(url: HTTPRequestURL, body?: HTTPRequestBody, headers?: any, opts?: HTTPRequestOptions) { 130 | return request('POST', url, body, headers, opts); 131 | } 132 | 133 | /** 134 | * Does a HTTP 'PUT' request. 135 | * 136 | * @param {HTTPRequestURL} url The URL. 137 | * @param {HTTPRequestBody} [body] The data of the request body. 138 | * @param {any} [headers] A key-value-pair of headers to send. 139 | * @param {HTTPRequestOptions} [opts] Custom options for the request. 140 | * 141 | * @return {Promise} The promsie with the HTTP response / result. 142 | */ 143 | export function PUT(url: HTTPRequestURL, body?: HTTPRequestBody, headers?: any, opts?: HTTPRequestOptions) { 144 | return request('PUT', url, body, headers, opts); 145 | } 146 | 147 | /** 148 | * Does a HTTP request. 149 | * 150 | * @param {string} method The method to use. 151 | * @param {HTTPRequestURL} url The URL. 152 | * @param {HTTPRequestBody} [body] The data of the request body. 153 | * @param {any} [headers] A key-value-pair of headers to send. 154 | * @param {HTTPRequestOptions} [opts] Custom options for the request. 155 | * 156 | * @return {Promise} The promsie with the HTTP response / result. 157 | */ 158 | export function request(method: string, url: HTTPRequestURL, body?: HTTPRequestBody, headers?: any, opts?: HTTPRequestOptions) { 159 | method = vscode_helpers.toStringSafe(method).toUpperCase().trim(); 160 | if ('' === method) { 161 | method = 'GET'; 162 | } 163 | 164 | let reqURL: URL.Url; 165 | if (_.isNil(url)) { 166 | url = URL.parse('http://localhost:80/'); 167 | } else { 168 | if (_.isObject(url)) { 169 | if (Object.getOwnPropertyNames(url).indexOf('_fsPath') > -1) { 170 | reqURL = URL.parse(`${ url }`); // vscode.Uri 171 | } else { 172 | reqURL = url; 173 | } 174 | } else { 175 | reqURL = URL.parse(vscode_helpers.toStringSafe(url)); 176 | } 177 | } 178 | 179 | return new Promise((resolve, reject) => { 180 | const COMPLETED = vscode_helpers.createCompletedAction(resolve, reject); 181 | 182 | try { 183 | const REQUEST_OPTS: HTTP.RequestOptions | HTTPs.RequestOptions = { 184 | auth: reqURL.auth, 185 | headers: {}, 186 | hostname: vscode_helpers.toStringSafe(reqURL.hostname).trim(), 187 | port: parseInt( 188 | vscode_helpers.toStringSafe(reqURL.port).trim() 189 | ), 190 | method: method, 191 | path: reqURL.path, 192 | }; 193 | 194 | const CALLBACK = (response: any) => { 195 | let body: false | Buffer = false; 196 | 197 | const RESP: HTTPRequestResult = { 198 | code: response.statusCode, 199 | readBody: async () => { 200 | if (false === body) { 201 | body = await vscode_helpers.readAll(response); 202 | } 203 | 204 | return body; 205 | }, 206 | request: REQUEST_OPTS, 207 | response: response, 208 | status: response.statusMessage, 209 | url: reqURL, 210 | version: response.httpVersion, 211 | }; 212 | 213 | COMPLETED(null, RESP); 214 | }; 215 | 216 | let requestFactory: (() => HTTP.ClientRequest) | false = false; 217 | 218 | if ('' === REQUEST_OPTS.hostname) { 219 | REQUEST_OPTS.hostname = 'localhost'; 220 | } 221 | 222 | if (!_.isNil(headers)) { 223 | for (const H in headers) { 224 | const NAME = NormalizeHeaderCase( 225 | vscode_helpers.toStringSafe(H).trim() 226 | ); 227 | const VALUE = vscode_helpers.toStringSafe(headers[H]); 228 | 229 | REQUEST_OPTS.headers[ NAME ] = VALUE; 230 | } 231 | } 232 | 233 | const PROTOCOL = vscode_helpers.normalizeString(reqURL.protocol); 234 | switch (PROTOCOL) { 235 | case '': 236 | case ':': 237 | case 'http:': 238 | requestFactory = () => { 239 | const HTTP_OPTS = REQUEST_OPTS; 240 | HTTP_OPTS.protocol = 'http:'; 241 | 242 | if (isNaN(HTTP_OPTS.port)) { 243 | HTTP_OPTS.port = 80; 244 | } 245 | 246 | return HTTP.request(MergeDeep(HTTP_OPTS, opts), 247 | CALLBACK); 248 | }; 249 | break; 250 | 251 | case 'https:': 252 | requestFactory = () => { 253 | const HTTPs_OPTS = REQUEST_OPTS; 254 | HTTPs_OPTS.protocol = 'https:'; 255 | HTTPs_OPTS.rejectUnauthorized = false; 256 | 257 | if (isNaN(HTTPs_OPTS.port)) { 258 | HTTPs_OPTS.port = 443; 259 | } 260 | 261 | return HTTPs.request(MergeDeep(HTTPs_OPTS, opts), 262 | CALLBACK); 263 | }; 264 | break; 265 | } 266 | 267 | if (false === requestFactory) { 268 | throw new Error(`Protocol '${ PROTOCOL }' not supported!`); 269 | } 270 | 271 | const REQUEST = requestFactory(); 272 | 273 | if (!_.isNil(body)) { 274 | if (IsStream.isReadableStream(body)) { 275 | body.pipe( REQUEST ); 276 | } else if (Buffer.isBuffer(body)) { 277 | REQUEST.write( body ); 278 | } else { 279 | REQUEST.write( Buffer.from(vscode_helpers.toStringSafe(body), 'utf8') ); 280 | } 281 | } 282 | 283 | REQUEST.end(); 284 | } catch (e) { 285 | COMPLETED(e); 286 | } 287 | }); 288 | } 289 | -------------------------------------------------------------------------------- /src/workspaces/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as _ from 'lodash'; 19 | import * as Path from 'path'; 20 | import * as vscode from 'vscode'; 21 | import * as vscode_helpers from '../index'; 22 | import * as vscode_disposable from '../disposable'; 23 | 24 | /** 25 | * A workspace. 26 | */ 27 | export interface Workspace extends vscode.Disposable, NodeJS.EventEmitter { 28 | /** 29 | * Gets the config source of that workspace. 30 | */ 31 | readonly configSource: WorkspaceConfigSource; 32 | /** 33 | * The underlying folder. 34 | */ 35 | readonly folder: vscode.WorkspaceFolder; 36 | /** 37 | * Is invoked when the configuration for that workspace changed. 38 | */ 39 | readonly onDidChangeConfiguration: (e: vscode.ConfigurationChangeEvent) => void | PromiseLike; 40 | /** 41 | * Gets the root path of that workspace. 42 | */ 43 | readonly rootPath: string; 44 | } 45 | 46 | /** 47 | * Stores data of configuration source. 48 | */ 49 | export interface WorkspaceConfigSource { 50 | /** 51 | * Gets the resource URI. 52 | */ 53 | readonly resource?: vscode.Uri; 54 | /** 55 | * Gets the name of the section. 56 | */ 57 | readonly section: string; 58 | } 59 | 60 | /** 61 | * A workspace context. 62 | */ 63 | export interface WorkspaceContext { 64 | /** 65 | * The underlying extension context. 66 | */ 67 | readonly extension: vscode.ExtensionContext; 68 | /** 69 | * The list of all available workspaces. 70 | */ 71 | readonly workspaces: WorkspaceBase[]; 72 | } 73 | 74 | /** 75 | * A workspace watcher. 76 | * 77 | * @param {WorkspaceEvent} event The event. 78 | * @param {vscode.WorkspaceFolder} folder The underlying folder. 79 | * @param {TWorkspace} [workspace] The workspace to remove. 80 | */ 81 | export type WorkspaceWatcher = ( 82 | event: WorkspaceWatcherEvent, 83 | folder: vscode.WorkspaceFolder, 84 | workspace?: TWorkspace, 85 | ) => WorkspaceWatcherResult | PromiseLike; 86 | 87 | /** 88 | * A workspace watcher 'complete action'. 89 | * 90 | * @param {any} err The error (if occurred). 91 | * @param {WorkspaceEvent} event The event. 92 | * @param {vscode.WorkspaceFolder} folder The underlying folder. 93 | * @param {TWorkspace} [workspace] The workspace to remove. 94 | */ 95 | export type WorkspaceWatcherCompleteAction = ( 96 | err: any, 97 | event: WorkspaceWatcherEvent, 98 | folder: vscode.WorkspaceFolder, 99 | workspace?: TWorkspace 100 | ) => void | PromiseLike; 101 | 102 | /** 103 | * A workspace watcher context. 104 | */ 105 | export interface WorkspaceWatcherContext extends vscode.Disposable { 106 | /** 107 | * The underlying extension (context). 108 | */ 109 | readonly extension: vscode.ExtensionContext; 110 | /** 111 | * Reloads all workspaces. 112 | */ 113 | readonly reload: () => PromiseLike; 114 | /** 115 | * The list of all available workspaces. 116 | */ 117 | readonly workspaces: TWorkspace[]; 118 | } 119 | 120 | /** 121 | * Possible results of a workspace watcher. 122 | */ 123 | export type WorkspaceWatcherResult = TWorkspace | void | null | undefined; 124 | 125 | /** 126 | * List of workspace watcher events. 127 | */ 128 | export enum WorkspaceWatcherEvent { 129 | /** 130 | * A workspace is going to be added. 131 | */ 132 | Added = 1, 133 | /** 134 | * A workspace is going to be removed. 135 | */ 136 | Removed = 2, 137 | } 138 | 139 | /** 140 | * A basic workspace. 141 | */ 142 | export abstract class WorkspaceBase extends vscode_disposable.DisposableBase implements Workspace { 143 | /** 144 | * Initializes a new instance of that class. 145 | * 146 | * @param {vscode.WorkspaceFolder} folder The underlying folder. 147 | */ 148 | public constructor(public readonly folder: vscode.WorkspaceFolder) { 149 | super(); 150 | } 151 | 152 | /** @inheritdoc */ 153 | public abstract get configSource(): WorkspaceConfigSource; 154 | 155 | /** @inheritdoc */ 156 | public async onDidChangeConfiguration(e: vscode.ConfigurationChangeEvent) { 157 | } 158 | 159 | /** @inheritdoc */ 160 | public get rootPath(): string { 161 | return Path.resolve( 162 | this.folder.uri.fsPath 163 | ); 164 | } 165 | } 166 | 167 | /** 168 | * Registers a workspace watcher. 169 | * 170 | * @param {vscode.ExtensionContext} extension The underlying extension (context). 171 | * @param {WorkspaceWatcher} watcher The watcher. 172 | * @param {WorkspaceWatcherCompleteAction} [complete] Optional 'complete action'. 173 | * 174 | * @return {WorkspaceWatcherContext} The watcher context. 175 | */ 176 | export function registerWorkspaceWatcher( 177 | extension: vscode.ExtensionContext, 178 | watcher: WorkspaceWatcher, 179 | complete?: WorkspaceWatcherCompleteAction, 180 | ): WorkspaceWatcherContext { 181 | let workspaces: TWorkspace[] = []; 182 | 183 | const DISPOSE_WORKSPACES = () => { 184 | while (workspaces.length > 0) { 185 | vscode_disposable.tryDispose( 186 | workspaces.pop() 187 | ); 188 | } 189 | }; 190 | 191 | const CONFIG_CHANGED_LISTENER = async (e: vscode.ConfigurationChangeEvent) => { 192 | for (const WS of workspaces) { 193 | try { 194 | if (e.affectsConfiguration(WS.configSource.section, WS.configSource.resource)) { 195 | await Promise.resolve( 196 | WS.onDidChangeConfiguration(e) 197 | ); 198 | } 199 | } catch { } 200 | } 201 | }; 202 | 203 | const WORKSPACE_FOLDERS_CHANGED_LISTENER = async (added: ReadonlyArray, removed?: ReadonlyArray) => { 204 | if (removed) { 205 | for (const WF of removed) { 206 | try { 207 | const MATCHING_WORKSPACES = workspaces.filter(ws => { 208 | return ws.folder.uri.fsPath === WF.uri.fsPath; 209 | }); 210 | 211 | for (const MWS of MATCHING_WORKSPACES) { 212 | let watcherErr: any; 213 | try { 214 | workspaces = workspaces.filter(ws => { 215 | return ws !== MWS; 216 | }); 217 | 218 | vscode_disposable.tryDispose( MWS ); 219 | 220 | await Promise.resolve( 221 | watcher( 222 | WorkspaceWatcherEvent.Removed, 223 | MWS.folder, 224 | MWS, 225 | ) 226 | ); 227 | } catch (e) { 228 | watcherErr = e; 229 | } finally { 230 | if (complete) { 231 | await Promise.resolve( 232 | complete( 233 | watcherErr, 234 | WorkspaceWatcherEvent.Removed, 235 | MWS.folder, 236 | MWS, 237 | ) 238 | ); 239 | } 240 | } 241 | } 242 | } catch { } 243 | } 244 | } 245 | 246 | if (added) { 247 | for (const WF of added) { 248 | let watcherErr: any; 249 | let newWorkspace: any; 250 | try { 251 | newWorkspace = await Promise.resolve( 252 | watcher( 253 | WorkspaceWatcherEvent.Added, 254 | WF, 255 | ) 256 | ); 257 | 258 | if (!_.isNil(newWorkspace)) { 259 | workspaces.push( newWorkspace ); 260 | } 261 | } catch (e) { 262 | watcherErr = e; 263 | } finally { 264 | if (complete) { 265 | await Promise.resolve( 266 | complete( 267 | watcherErr, 268 | WorkspaceWatcherEvent.Added, 269 | WF, 270 | newWorkspace, 271 | ) 272 | ); 273 | } 274 | } 275 | } 276 | } 277 | }; 278 | 279 | let onDidChangeWorkspaceFolders: vscode.Disposable; 280 | let onDidChangeConfiguration: vscode.Disposable; 281 | 282 | const CTX: WorkspaceWatcherContext = { 283 | extension: extension, 284 | dispose: () => { 285 | vscode_disposable.tryDispose( onDidChangeConfiguration ); 286 | vscode_disposable.tryDispose( onDidChangeWorkspaceFolders ); 287 | 288 | DISPOSE_WORKSPACES(); 289 | }, 290 | reload: async () => { 291 | DISPOSE_WORKSPACES(); 292 | 293 | await WORKSPACE_FOLDERS_CHANGED_LISTENER( 294 | vscode_helpers.asArray( vscode.workspace.workspaceFolders ), 295 | ); 296 | }, 297 | workspaces: undefined, 298 | }; 299 | 300 | // CTX.workspaces 301 | Object.defineProperty(CTX, 'workspaces', { 302 | enumerable: true, 303 | 304 | get: () => workspaces.map(ws => ws), 305 | }); 306 | 307 | onDidChangeWorkspaceFolders = vscode.workspace.onDidChangeWorkspaceFolders((e) => { 308 | WORKSPACE_FOLDERS_CHANGED_LISTENER(e.added, e.removed).then(() => { 309 | }, (err) => { 310 | }); 311 | }); 312 | 313 | onDidChangeConfiguration = vscode.workspace.onDidChangeConfiguration((e) => { 314 | CONFIG_CHANGED_LISTENER(e).then(() => { 315 | }, (err) => { 316 | }); 317 | }); 318 | 319 | return CTX; 320 | } 321 | -------------------------------------------------------------------------------- /src/scm/git.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * Most of the code has been taken from 'vscode-gitlens': 20 | * https://github.com/eamodio/vscode-gitlens 21 | * 22 | * LICENSE: 23 | * https://github.com/eamodio/vscode-gitlens/blob/master/LICENSE 24 | */ 25 | 26 | import * as _ from 'lodash'; 27 | import * as ChildProcess from 'child_process'; 28 | import * as FS from 'fs'; 29 | const MergeDeep = require('merge-deep'); 30 | import * as Path from 'path'; 31 | import * as vscode_helpers from '../index'; 32 | import * as vscode_helpers_scm from './index'; 33 | 34 | interface Executable { 35 | cmd: string; 36 | args: string[]; 37 | } 38 | 39 | /** 40 | * Stores the data of a git executable. 41 | */ 42 | export interface GitExecutable { 43 | /** 44 | * The path to the executable. 45 | */ 46 | readonly path: string; 47 | /** 48 | * The version. 49 | */ 50 | readonly version: string; 51 | } 52 | 53 | /** 54 | * A git client. 55 | */ 56 | export class GitClient implements vscode_helpers_scm.SourceControlClient { 57 | /** 58 | * Initializes a new instance of that class. 59 | * 60 | * @param {GitExecutable} executable The data of the executable. 61 | * @param {string} [cwd] The optional working directory. 62 | */ 63 | constructor(public readonly executable: GitExecutable, 64 | cwd?: string) { 65 | this.cwd = vscode_helpers.toStringSafe(cwd); 66 | 67 | if (vscode_helpers.isEmptyString(this.cwd)) { 68 | this.cwd = undefined; 69 | } else { 70 | if (!Path.isAbsolute(this.cwd)) { 71 | this.cwd = Path.join( 72 | process.cwd(), this.cwd 73 | ); 74 | } 75 | 76 | this.cwd = Path.resolve( 77 | this.cwd 78 | ); 79 | } 80 | } 81 | 82 | /** @inheritdoc */ 83 | public readonly cwd: string; 84 | 85 | /** 86 | * Executes the Git client. 87 | * 88 | * @param {any[]} args Arguments for the execution. 89 | * @param {ChildProcess.ExecFileOptions} [opts] Custom options. 90 | * 91 | * @return {Promise} The promise with the result. 92 | */ 93 | public exec(args: any[], opts?: ChildProcess.ExecFileOptions) { 94 | const DEFAULT_OPTS: ChildProcess.ExecFileOptions = { 95 | cwd: this.cwd, 96 | }; 97 | 98 | return vscode_helpers.execFile( 99 | this.executable.path, 100 | args, 101 | MergeDeep(DEFAULT_OPTS, opts), 102 | ); 103 | } 104 | 105 | /** 106 | * Executes the Git client (sync). 107 | * 108 | * @param {any[]} args Arguments for the execution. 109 | * @param {ChildProcess.ExecFileOptions} [opts] Custom options. 110 | * 111 | * @return {string} The result. 112 | */ 113 | public execSync(args: any[], opts?: ChildProcess.ExecFileOptions): string { 114 | const DEFAULT_OPTS: ChildProcess.ExecFileOptions = { 115 | cwd: this.cwd, 116 | }; 117 | 118 | return asString( 119 | ChildProcess.execFileSync( 120 | this.executable.path, 121 | vscode_helpers.asArray(args, false) 122 | .map(x => vscode_helpers.toStringSafe(x)), 123 | MergeDeep(DEFAULT_OPTS, opts), 124 | ) 125 | ); 126 | } 127 | } 128 | 129 | function asString(val: any) { 130 | if (!_.isNil(val)) { 131 | if (Buffer.isBuffer(val)) { 132 | val = val.toString('utf8'); 133 | } 134 | } 135 | 136 | return vscode_helpers.toStringSafe(val); 137 | } 138 | 139 | function findExecutableSync(exe: string, args: string[]): Executable { 140 | // POSIX can just execute scripts directly, no need for silly goosery 141 | if (!vscode_helpers.IS_WINDOWS) { 142 | return { 143 | cmd: runDownPathSync(exe), 144 | args: args, 145 | }; 146 | } 147 | 148 | if (!FS.existsSync(exe)) { 149 | // NB: When you write something like `surf-client ... -- surf-build` on Windows, 150 | // a shell would normally convert that to surf-build.cmd, but since it's passed 151 | // in as an argument, it doesn't happen 152 | const POSSIBLE_EXTENSIONS = ['.exe', '.bat', '.cmd', '.ps1']; 153 | 154 | for (const EXT of POSSIBLE_EXTENSIONS) { 155 | const FULL_PATH = runDownPathSync(`${exe}${EXT}`); 156 | 157 | if (FS.existsSync(FULL_PATH)) { 158 | return findExecutableSync(FULL_PATH, args); 159 | } 160 | } 161 | } 162 | 163 | if (exe.match(/\.ps1$/i)) { // PowerShell 164 | const CMD = Path.join(process.env.SYSTEMROOT!, 165 | 'System32', 'WindowsPowerShell', 'v1.0', 'PowerShell.exe'); 166 | const PS_ARGS = [ '-ExecutionPolicy', 'Unrestricted', '-NoLogo', '-NonInteractive', '-File', exe ]; 167 | 168 | return { 169 | cmd: CMD, 170 | args: PS_ARGS.concat(args), 171 | }; 172 | } 173 | 174 | if (exe.match(/\.(bat|cmd)$/i)) { // Windows batch? 175 | const CMD = Path.join(process.env.SYSTEMROOT!, 'System32', 'cmd.exe'); 176 | const CMD_ARGS = ['/C', exe, ...args]; 177 | 178 | return { 179 | cmd: CMD, 180 | args: CMD_ARGS, 181 | }; 182 | } 183 | 184 | if (exe.match(/\.(js)$/i)) { // NodeJS? 185 | const CMD = process.execPath; 186 | const NODE_ARGS = [exe]; 187 | 188 | return { 189 | cmd: CMD, 190 | args: NODE_ARGS.concat(args) 191 | }; 192 | } 193 | 194 | return { 195 | cmd: exe, 196 | args: args 197 | }; 198 | } 199 | 200 | function findGitDarwinSync(): GitExecutable { 201 | let path = runCommandSync('which', ['git']); 202 | path = path.replace(/^\s+|\s+$/g, ''); 203 | 204 | if (path !== '/usr/bin/git') { 205 | return findSpecificGitSync(path); 206 | } 207 | 208 | try { 209 | runCommandSync('xcode-select', ['-p']); 210 | 211 | return findSpecificGitSync(path); 212 | } catch (e) { 213 | if (2 === e.code) { 214 | throw new Error('Unable to find git'); 215 | } 216 | 217 | return findSpecificGitSync(path); 218 | } 219 | } 220 | 221 | function findGitPathSync(path: string): GitExecutable | false { 222 | path = vscode_helpers.toStringSafe(path); 223 | if (vscode_helpers.isEmptyString(path)) { 224 | path = 'git'; // default 225 | } 226 | 227 | try { 228 | return findSpecificGitSync(path); 229 | } catch { } 230 | 231 | // fallback: platform specific 232 | try { 233 | if (vscode_helpers.IS_MAC) { 234 | return findGitDarwinSync(); 235 | } 236 | 237 | if (vscode_helpers.IS_WINDOWS) { 238 | return findGitWin32Sync(); 239 | } 240 | } catch { } 241 | 242 | return false; 243 | } 244 | 245 | function findGitWin32Sync(): GitExecutable { 246 | try { 247 | return findSystemGitWin32Sync(process.env['ProgramW6432']!); 248 | } catch { 249 | try { 250 | return findSystemGitWin32Sync(process.env['ProgramFiles(x86)']!); 251 | } catch { 252 | try { 253 | return findSystemGitWin32Sync(process.env['ProgramFiles']!); 254 | } catch { 255 | return findSpecificGitSync('git'); 256 | } 257 | } 258 | } 259 | } 260 | 261 | function findSpecificGitSync(path: string): GitExecutable { 262 | const VERSION = runCommandSync(path, [ '--version' ]); 263 | 264 | // If needed, let's update our path to avoid the search on every command 265 | if (vscode_helpers.isEmptyString(path) || path === 'git') { 266 | path = (findExecutableSync(path, [ '--version' ])).cmd; 267 | } 268 | 269 | return { 270 | path, 271 | version: parseVersion(VERSION.trim()), 272 | }; 273 | } 274 | 275 | function findSystemGitWin32Sync(basePath: string): GitExecutable { 276 | if (vscode_helpers.isEmptyString(basePath)) { 277 | throw new Error('Unable to find git'); 278 | } 279 | 280 | return findSpecificGitSync(Path.join(basePath, 281 | 'Git', 'cmd', 'git.exe')); 282 | } 283 | 284 | function parseVersion(raw: string) { 285 | return raw.replace(/^git version /, ''); 286 | } 287 | 288 | function runCommandSync(command: string, args: any[]): string { 289 | return asString( 290 | ChildProcess.execFileSync( 291 | vscode_helpers.toStringSafe(command), 292 | vscode_helpers.asArray(args, false) 293 | .map(x => vscode_helpers.toStringSafe(x)), 294 | ) 295 | ); 296 | } 297 | 298 | function runDownPathSync(exe: string): string { 299 | // NB: Windows won't search PATH looking for executables in spawn like 300 | // Posix does 301 | // Files with any directory path don't get this applied 302 | if (exe.match(/[\\\/]/)) { 303 | return exe; 304 | } 305 | 306 | const TARGET = Path.join('.', exe); 307 | try { 308 | if (FS.statSync(TARGET)) { 309 | return TARGET; 310 | } 311 | } catch { } 312 | 313 | const HAYSTACK = process.env.PATH!.split(vscode_helpers.IS_WINDOWS ? ';' : ':'); 314 | for (const P of HAYSTACK) { 315 | const NEEDLE = Path.join(P, exe); 316 | 317 | try { 318 | if (FS.statSync(NEEDLE)) { 319 | return NEEDLE; 320 | } 321 | } catch { } 322 | } 323 | 324 | return exe; 325 | } 326 | 327 | /** 328 | * Tries to find the path of the Git executable. 329 | * 330 | * @param {string} [path] The optional specific path where to search first. 331 | * 332 | * @return {Promise} The promise with the executable or (false) if not found. 333 | */ 334 | export function tryFindGitPath(path?: string) { 335 | return Promise.resolve( 336 | tryFindGitPathSync(path) 337 | ); 338 | } 339 | 340 | /** 341 | * Tries to find the path of the Git executable (sync). 342 | * 343 | * @param {string} [path] The optional specific path where to search first. 344 | * 345 | * @return {GitExecutable|false} The executable or (false) if not found. 346 | */ 347 | export function tryFindGitPathSync(path?: string): GitExecutable | false { 348 | let git: GitExecutable | false; 349 | try { 350 | git = findGitPathSync(path); 351 | 352 | if (false !== git) { 353 | git = { 354 | path: Path.resolve(git.path), 355 | version: git.version, 356 | }; 357 | } 358 | } catch { 359 | git = false; 360 | } 361 | 362 | return git; 363 | } 364 | -------------------------------------------------------------------------------- /src/devtools/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as _ from 'lodash'; 19 | import * as vscode from 'vscode'; 20 | import * as vscode_helpers from '../index'; 21 | import * as vscode_helpers_disposable from '../disposable/index'; 22 | import * as vscode_helpers_events from '../events/index'; 23 | import * as vscode_helpers_http from '../http/index'; 24 | import * as WebSocket from 'ws'; 25 | 26 | /** 27 | * A browser item. 28 | */ 29 | export interface BrowserItem extends NodeJS.EventEmitter, vscode.Disposable { 30 | /** 31 | * The description. 32 | */ 33 | readonly description?: string; 34 | /** 35 | * Closes the connection to the item. 36 | * 37 | * @return {PromiseLike} The promise that indicates if operation was successful or not. 38 | */ 39 | readonly close: () => PromiseLike; 40 | /** 41 | * Options a connection to the item. 42 | * 43 | * @return {PromiseLike} The promise that indicates if operation was successful or not. 44 | */ 45 | readonly connect: () => PromiseLike; 46 | /** 47 | * The ID of the item. 48 | */ 49 | readonly id: string; 50 | /** 51 | * Indicates if a connection to the item has been established or not. 52 | */ 53 | readonly isConnected: boolean; 54 | /** 55 | * Invokes a method for the item. 56 | * 57 | * @param {string} method The method to invoke. 58 | * @param {any} [params] Parameters for the method. 59 | * @param {SendToBrowserItemCallback} [callback] The optional callback. 60 | */ 61 | readonly send: (method: string, params?: any, callback?: SendToBrowserItemCallback) => PromiseLike; 62 | /** 63 | * Gets the underyling (web) socket URI. 64 | */ 65 | readonly socketUri: string; 66 | } 67 | 68 | /** 69 | * A browser IFrame. 70 | */ 71 | export interface BrowserFrame extends BrowserPage { 72 | /** 73 | * Gets the ID of the parent. 74 | */ 75 | readonly parentId: string; 76 | } 77 | 78 | /** 79 | * A browser page. 80 | */ 81 | export interface BrowserPage extends BrowserItem { 82 | /** 83 | * Gets the URI of the FavIcon. 84 | */ 85 | readonly favIcon: string; 86 | /** 87 | * The title of the page. 88 | */ 89 | readonly title: string; 90 | } 91 | 92 | /** 93 | * Options for a DevTools client. 94 | */ 95 | export interface DevToolsClientOptions { 96 | /** 97 | * The host address. 98 | */ 99 | readonly host?: string; 100 | /** 101 | * The TCP host. 102 | */ 103 | readonly port?: number; 104 | } 105 | 106 | export type SendToBrowserItemCallback = (message: any) => any; 107 | 108 | /** 109 | * A DevTools client. 110 | */ 111 | export class DevToolsClient extends vscode_helpers_disposable.DisposableBase { 112 | /** 113 | * Initializes a new instance of that class. 114 | * 115 | * @param {DevToolsClientOptions} [opts] Custom options. 116 | */ 117 | public constructor(opts?: DevToolsClientOptions) { 118 | super(); 119 | 120 | this.options = opts || {}; 121 | } 122 | 123 | private async getBrowserItems(): Promise { 124 | const RESP = await vscode_helpers_http.GET(`http://${ this.host }:${ this.port }/json`); 125 | 126 | if (200 !== RESP.code) { 127 | throw new Error(`Unexpected response ${ RESP.code }: '${ RESP.status }'`); 128 | } 129 | 130 | return vscode_helpers.asArray( 131 | JSON.parse( 132 | (await RESP.readBody()).toString('utf8') 133 | ) 134 | ).filter(i => { 135 | return !vscode_helpers.isEmptyString( i['webSocketDebuggerUrl'] ); 136 | }); 137 | } 138 | 139 | /** 140 | * Returns a list of all IFrames. 141 | * 142 | * @return {Promise} The promise with the frames. 143 | */ 144 | public async getFrames() { 145 | const IFRAMES: BrowserFrame[] = []; 146 | 147 | const IFRAME_ITEMS = ( 148 | await this.getBrowserItems() 149 | ).filter(i => { 150 | return 'iframe' === vscode_helpers.normalizeString(i['type']); 151 | }); 152 | 153 | for (const FI of IFRAME_ITEMS) { 154 | const NEW_FRAME = new BrowserFrameImpl(this); 155 | NEW_FRAME.id = vscode_helpers.toStringSafe( FI['id'] ); 156 | NEW_FRAME.parentId = vscode_helpers.toStringSafe( FI['parentId'] ); 157 | NEW_FRAME.favIcon = vscode_helpers.toStringSafe( FI['faviconUrl'] ); 158 | NEW_FRAME.title = vscode_helpers.toStringSafe( FI['title'] ); 159 | NEW_FRAME.description = vscode_helpers.toStringSafe( FI['description'] ); 160 | NEW_FRAME.socketUri = vscode_helpers.toStringSafe( FI['webSocketDebuggerUrl'] ); 161 | 162 | IFRAMES.push( NEW_FRAME ); 163 | } 164 | 165 | return IFRAMES; 166 | } 167 | 168 | /** 169 | * Returns a list of all pages. 170 | * 171 | * @return {Promise} The promise with the pages. 172 | */ 173 | public async getPages() { 174 | const PAGES: BrowserPage[] = []; 175 | 176 | const PAGE_ITEMS = ( 177 | await this.getBrowserItems() 178 | ).filter(i => { 179 | return 'page' === vscode_helpers.normalizeString(i['type']); 180 | }); 181 | 182 | for (const PI of PAGE_ITEMS) { 183 | const NEW_PAGE = new BrowserPageImpl(this); 184 | NEW_PAGE.id = vscode_helpers.toStringSafe( PI['id'] ); 185 | NEW_PAGE.favIcon = vscode_helpers.toStringSafe( PI['faviconUrl'] ); 186 | NEW_PAGE.title = vscode_helpers.toStringSafe( PI['title'] ); 187 | NEW_PAGE.description = vscode_helpers.toStringSafe( PI['description'] ); 188 | NEW_PAGE.socketUri = vscode_helpers.toStringSafe( PI['webSocketDebuggerUrl'] ); 189 | 190 | PAGES.push( NEW_PAGE ); 191 | } 192 | 193 | return PAGES; 194 | } 195 | 196 | /** 197 | * Gets the host address. 198 | */ 199 | public get host() { 200 | let hostAddr = vscode_helpers.toStringSafe(this.options.host); 201 | if ('' === hostAddr) { 202 | hostAddr = '127.0.0.1'; 203 | } 204 | 205 | return hostAddr; 206 | } 207 | 208 | /** 209 | * Gets the options for the client. 210 | */ 211 | public readonly options: DevToolsClientOptions; 212 | 213 | /** 214 | * Gets the TCP port. 215 | */ 216 | public get port() { 217 | let tcpPort = parseInt( 218 | vscode_helpers.toStringSafe(this.options.port).trim() 219 | ); 220 | if (isNaN(tcpPort)) { 221 | tcpPort = 9222; 222 | } 223 | 224 | return tcpPort; 225 | } 226 | } 227 | 228 | abstract class BrowserItemBase extends vscode_helpers_disposable.DisposableBase implements BrowserItem { 229 | private _nextId = 0; 230 | private _sendCallbacks: { [id: number]: SendToBrowserItemCallback }; 231 | private _socket: WebSocket; 232 | 233 | public constructor(public readonly client: DevToolsClient) { 234 | super(); 235 | } 236 | 237 | public close() { 238 | return new Promise((resolve, reject) => { 239 | const COMPLETED = vscode_helpers.createCompletedAction(resolve, reject); 240 | 241 | const CUR_SOCKET = this._socket; 242 | if (_.isNil(CUR_SOCKET)) { 243 | COMPLETED(null, false); 244 | return; 245 | } 246 | 247 | try { 248 | CUR_SOCKET.close(); 249 | vscode_helpers_events.tryRemoveAllListeners(CUR_SOCKET); 250 | 251 | this._socket = null; 252 | 253 | COMPLETED(null); 254 | } catch (e) { 255 | COMPLETED(e); 256 | } 257 | }); 258 | } 259 | 260 | public connect() { 261 | return new Promise((resolve, reject) => { 262 | const COMPLETED = vscode_helpers.createCompletedAction(resolve, reject); 263 | 264 | if (this.isInFinalizeState) { 265 | COMPLETED( 266 | new Error('Object is or is going to be disposed') 267 | ); 268 | return; 269 | } 270 | 271 | if (!_.isNil(this._socket)) { 272 | COMPLETED(null, false); 273 | return; 274 | } 275 | 276 | try { 277 | const NEW_SOCKET = new WebSocket( this.socketUri ); 278 | 279 | NEW_SOCKET.once('error', (err) => { 280 | if (err) { 281 | COMPLETED(err); 282 | } 283 | }); 284 | 285 | NEW_SOCKET.once('close', () => { 286 | this._socket = null; 287 | 288 | this.emit('close', 289 | NEW_SOCKET); 290 | }); 291 | 292 | NEW_SOCKET.once('open', () => { 293 | this._sendCallbacks = {}; 294 | this._socket = NEW_SOCKET; 295 | 296 | COMPLETED(null, true); 297 | }); 298 | 299 | NEW_SOCKET.on('message', (data) => { 300 | const ALL_CALLBACKS = this._sendCallbacks; 301 | if (!_.isNil(ALL_CALLBACKS)) { 302 | try { 303 | let msg: any; 304 | if (!_.isNil(data)) { 305 | msg = JSON.parse( 306 | vscode_helpers.toStringSafe(data) 307 | ); 308 | } 309 | 310 | if (_.isPlainObject(msg)) { 311 | const MSG_ID = parseInt( 312 | vscode_helpers.toStringSafe(msg.id).trim() 313 | ); 314 | if (!isNaN(MSG_ID)) { 315 | const DELETE_CALLBACK = (err?: any) => { 316 | delete ALL_CALLBACKS[MSG_ID]; 317 | }; 318 | 319 | try { 320 | const CALLBACK = ALL_CALLBACKS[MSG_ID]; 321 | if (!_.isNil(CALLBACK)) { 322 | Promise.resolve( 323 | CALLBACK(msg) 324 | ).then(() => { 325 | DELETE_CALLBACK(); 326 | }, (err) => { 327 | DELETE_CALLBACK(err); 328 | }); 329 | } 330 | } finally { 331 | DELETE_CALLBACK(); 332 | } 333 | } 334 | } 335 | } catch { } 336 | } 337 | 338 | this.emit('message', 339 | NEW_SOCKET, data); 340 | }); 341 | } catch (e) { 342 | COMPLETED(e); 343 | } 344 | }); 345 | } 346 | 347 | public description: string; 348 | 349 | public id: string; 350 | 351 | public get isConnected() { 352 | return !_.isNil(this._socket); 353 | } 354 | 355 | public onDispose() { 356 | const CUR_SOCKET = this._socket; 357 | if (!_.isNil(CUR_SOCKET)) { 358 | CUR_SOCKET.close(); 359 | vscode_helpers_events.tryRemoveAllListeners(CUR_SOCKET); 360 | 361 | this._socket = null; 362 | } 363 | 364 | this._sendCallbacks = null; 365 | } 366 | 367 | public send(method: string, params?: any, callback?: SendToBrowserItemCallback) { 368 | method = vscode_helpers.toStringSafe(method).trim(); 369 | 370 | return new Promise((resolve, reject) => { 371 | const COMPLETED = vscode_helpers.createCompletedAction(resolve, reject); 372 | 373 | let id = 0; 374 | try { 375 | id = ++this._nextId; 376 | 377 | if (!_.isNil(callback)) { 378 | this._sendCallbacks[id] = callback; 379 | } 380 | 381 | this._socket.send( 382 | JSON.stringify({ 383 | id: id, 384 | method: method, 385 | params: params, 386 | }), 387 | (err) => { 388 | COMPLETED(err); 389 | } 390 | ); 391 | } catch (e) { 392 | delete this._sendCallbacks[id]; 393 | 394 | COMPLETED(e); 395 | } 396 | }); 397 | } 398 | 399 | public socketUri: string; 400 | } 401 | 402 | class BrowserPageImpl extends BrowserItemBase implements BrowserPage { 403 | public favIcon: string; 404 | 405 | public title: string; 406 | } 407 | 408 | class BrowserFrameImpl extends BrowserPageImpl implements BrowserFrame { 409 | public parentId: string; 410 | } 411 | -------------------------------------------------------------------------------- /src/fs/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as FastGlob from 'fast-glob'; 19 | import * as FS from 'fs'; 20 | import * as Glob from 'glob'; 21 | const MergeDeep = require('merge-deep'); 22 | import * as Mkdirp from 'mkdirp'; 23 | import * as Path from 'path'; 24 | import * as TMP from 'tmp'; 25 | import * as vscode_helpers from '../index'; 26 | import * as vscode_workflows from '../workflows'; 27 | 28 | export declare type FastGlobEntryItem = string | IFastGlobEntry; 29 | 30 | export type FastGlobOptions = FastGlob.Options; 31 | 32 | export interface IFastGlobEntry extends FS.Stats { 33 | path: string; 34 | depth: number; 35 | } 36 | 37 | /** 38 | * Options for a temp file. 39 | */ 40 | export interface TempFileOptions { 41 | /** 42 | * The custom directory for the file. 43 | */ 44 | dir?: string; 45 | /** 46 | * Keep temp file or not. 47 | */ 48 | keep?: boolean; 49 | /** 50 | * The optional prefix for the name of the file. 51 | */ 52 | prefix?: string; 53 | /** 54 | * The optional suffix for the name of the file. 55 | */ 56 | suffix?: string; 57 | } 58 | 59 | const lstat = FS.promises.lstat; 60 | const stat = FS.promises.stat; 61 | 62 | type TempFilePath = string | false; 63 | 64 | /** 65 | * Creates a directory (if needed). 66 | * 67 | * @param {string} dir The path of the directory to create. 68 | * 69 | * @return {Promise} The promise that indicates if directory has been created or not. 70 | */ 71 | export async function createDirectoryIfNeeded(dir: string) { 72 | dir = vscode_helpers.toStringSafe(dir); 73 | 74 | if (!(await exists(dir))) { 75 | await Mkdirp(dir); 76 | 77 | return true; 78 | } 79 | 80 | return false; 81 | } 82 | 83 | /** 84 | * Promise version of 'FS.exists()' function. 85 | * 86 | * @param {string|Buffer} path The path. 87 | * 88 | * @return {Promise} The promise that indicates if path exists or not. 89 | */ 90 | export function exists(path: string | Buffer) { 91 | return new Promise((resolve, reject) => { 92 | const COMPLETED = vscode_helpers.createCompletedAction(resolve, reject); 93 | 94 | try { 95 | FS.exists(path, (doesExist) => { 96 | COMPLETED(null, doesExist); 97 | }); 98 | } catch (e) { 99 | COMPLETED(e); 100 | } 101 | }); 102 | } 103 | 104 | /** 105 | * Fast version of 'node-glob'. 106 | * 107 | * @param {string|string[]} patterns One or more patterns to search for. 108 | * @param {FastGlob.Options} [opts] Custom options. 109 | * 110 | * @return {Promise} Promise with the found files / directories. 111 | */ 112 | export function fastGlob(patterns: string | string[], opts?: FastGlob.Options): Promise { 113 | return FastGlob(patterns, opts); 114 | } 115 | 116 | /** 117 | * Fast version of 'node-glob' (sync). 118 | * 119 | * @param {string|string[]} patterns One or more patterns to search for. 120 | * @param {FastGlob.Options} [opts] Custom options. 121 | * 122 | * @return {FastGlobEntryItem[]} The found files / directories. 123 | */ 124 | export function fastGlobSync(patterns: string | string[], opts?: FastGlob.Options): FastGlobEntryItem[] { 125 | return FastGlob.sync(patterns, opts); 126 | } 127 | 128 | /** 129 | * Promise version of 'Glob()' function. 130 | * 131 | * @param {string|string[]} patterns One or more patterns. 132 | * @param {Glob.IOptions} [opts] Custom options. 133 | * 134 | * @return {Promise} The promise with the matches. 135 | */ 136 | export async function glob(patterns: string | string[], opts?: Glob.IOptions) { 137 | opts = normalizeGlobOptions(opts, { 138 | sync: false, 139 | }); 140 | 141 | const WF = vscode_workflows.buildWorkflow(); 142 | 143 | WF.next(() => { 144 | return []; 145 | }); 146 | 147 | vscode_helpers.asArray(patterns).forEach(p => { 148 | WF.next((allMatches: string[]) => { 149 | return new Promise((res, rej) => { 150 | const COMPLETED = vscode_helpers.createCompletedAction(res, rej); 151 | 152 | try { 153 | Glob(p, opts, (err, matches) => { 154 | if (err) { 155 | COMPLETED(err); 156 | } else { 157 | allMatches.push 158 | .apply(allMatches, matches); 159 | 160 | COMPLETED(null, allMatches); 161 | } 162 | }); 163 | } catch (e) { 164 | COMPLETED(e); 165 | } 166 | }); 167 | }); 168 | }); 169 | 170 | return vscode_helpers.from( await WF.start() ) 171 | .select(m => Path.resolve(m)) 172 | .distinct() 173 | .toArray(); 174 | } 175 | 176 | /** 177 | * Multi pattern version of 'Glob.sync()' function. 178 | * 179 | * @param {string|string[]} patterns One or more patterns. 180 | * @param {Glob.IOptions} [opts] Custom options. 181 | * 182 | * @return {string[]} The matches. 183 | */ 184 | export function globSync(patterns: string | string[], opts?: Glob.IOptions) { 185 | opts = normalizeGlobOptions(opts, { 186 | sync: true, 187 | }); 188 | 189 | const ALL_MATCHES: string[] = []; 190 | 191 | vscode_helpers.asArray(patterns).forEach(p => { 192 | ALL_MATCHES.push 193 | .apply(ALL_MATCHES, Glob.sync(p, opts)); 194 | }); 195 | 196 | return vscode_helpers.from( ALL_MATCHES ) 197 | .select(m => Path.resolve(m)) 198 | .distinct() 199 | .toArray(); 200 | } 201 | 202 | async function invokeForStats( 203 | path: string, useLSTAT, 204 | func: (stats: FS.Stats) => TResult, 205 | defaultValue?: TResult, 206 | ): Promise { 207 | path = vscode_helpers.toStringSafe(path); 208 | useLSTAT = vscode_helpers.toBooleanSafe(useLSTAT, true); 209 | 210 | if (await exists(path)) { 211 | const STATS = useLSTAT ? (await lstat(path)) 212 | : (await stat(path)); 213 | 214 | if (STATS) { 215 | return func(STATS); 216 | } 217 | } 218 | 219 | return defaultValue; 220 | } 221 | 222 | function invokeForStatsSync( 223 | path: string, useLSTAT, 224 | func: (stats: FS.Stats) => TResult, 225 | defaultValue?: TResult, 226 | ): TResult { 227 | path = vscode_helpers.toStringSafe(path); 228 | useLSTAT = vscode_helpers.toBooleanSafe(useLSTAT, true); 229 | 230 | if (FS.existsSync(path)) { 231 | const STATS = useLSTAT ? FS.lstatSync(path) 232 | : FS.statSync(path); 233 | 234 | if (STATS) { 235 | return func(STATS); 236 | } 237 | } 238 | 239 | return defaultValue; 240 | } 241 | 242 | /** 243 | * Checks if a path exists and is a block device. 244 | * 245 | * @param {string} path The path to check. 246 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 247 | * 248 | * @return {Promise} The promise with the value that indicates if condition matches or not. 249 | */ 250 | export async function isBlockDevice(path: string, useLSTAT = true) { 251 | return invokeForStats( 252 | path, useLSTAT, 253 | (stats) => stats.isBlockDevice(), 254 | false 255 | ); 256 | } 257 | 258 | /** 259 | * Checks if a path exists and is a block device. 260 | * 261 | * @param {string} path The path to check. 262 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 263 | * 264 | * @return {boolean} A value that indicates if condition matches or not. 265 | */ 266 | export function isBlockDeviceSync(path: string, useLSTAT = true) { 267 | return invokeForStatsSync( 268 | path, useLSTAT, 269 | (stats) => stats.isBlockDevice(), 270 | false 271 | ); 272 | } 273 | 274 | /** 275 | * Checks if a path exists and is a character device. 276 | * 277 | * @param {string} path The path to check. 278 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 279 | * 280 | * @return {Promise} The promise with the value that indicates if condition matches or not. 281 | */ 282 | export async function isCharacterDevice(path: string, useLSTAT = true) { 283 | return invokeForStats( 284 | path, useLSTAT, 285 | (stats) => stats.isCharacterDevice(), 286 | false 287 | ); 288 | } 289 | 290 | /** 291 | * Checks if a path exists and is a character device. 292 | * 293 | * @param {string} path The path to check. 294 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 295 | * 296 | * @return {boolean} A value that indicates if condition matches or not. 297 | */ 298 | export function isCharacterDeviceSync(path: string, useLSTAT = true) { 299 | return invokeForStatsSync( 300 | path, useLSTAT, 301 | (stats) => stats.isCharacterDevice(), 302 | false 303 | ); 304 | } 305 | 306 | /** 307 | * Checks if a path exists and is a directory. 308 | * 309 | * @param {string} path The path to check. 310 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 311 | * 312 | * @return {Promise} The promise with the value that indicates if condition matches or not. 313 | */ 314 | export async function isDirectory(path: string, useLSTAT = true) { 315 | return invokeForStats( 316 | path, useLSTAT, 317 | (stats) => stats.isDirectory(), 318 | false 319 | ); 320 | } 321 | 322 | /** 323 | * Checks if a path exists and is a directory. 324 | * 325 | * @param {string} path The path to check. 326 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 327 | * 328 | * @return {boolean} A value that indicates if condition matches or not. 329 | */ 330 | export function isDirectorySync(path: string, useLSTAT = true) { 331 | return invokeForStatsSync( 332 | path, useLSTAT, 333 | (stats) => stats.isDirectory(), 334 | false 335 | ); 336 | } 337 | 338 | /** 339 | * Checks if a path exists and is FIFO. 340 | * 341 | * @param {string} path The path to check. 342 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 343 | * 344 | * @return {Promise} The promise with the value that indicates if condition matches or not. 345 | */ 346 | export async function isFIFO(path: string, useLSTAT = true) { 347 | return invokeForStats( 348 | path, useLSTAT, 349 | (stats) => stats.isFIFO(), 350 | false 351 | ); 352 | } 353 | 354 | /** 355 | * Checks if a path exists and is FIFO. 356 | * 357 | * @param {string} path The path to check. 358 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 359 | * 360 | * @return {boolean} A value that indicates if condition matches or not. 361 | */ 362 | export function isFIFOSync(path: string, useLSTAT = true) { 363 | return invokeForStatsSync( 364 | path, useLSTAT, 365 | (stats) => stats.isFIFO(), 366 | false 367 | ); 368 | } 369 | 370 | /** 371 | * Checks if a path exists and is a file. 372 | * 373 | * @param {string} path The path to check. 374 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 375 | * 376 | * @return {Promise} The promise with the value that indicates if condition matches or not. 377 | */ 378 | export async function isFile(path: string, useLSTAT = true) { 379 | return invokeForStats( 380 | path, useLSTAT, 381 | (stats) => stats.isFile(), 382 | false 383 | ); 384 | } 385 | 386 | /** 387 | * Checks if a path exists and is a file. 388 | * 389 | * @param {string} path The path to check. 390 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 391 | * 392 | * @return {boolean} A value that indicates if condition matches or not. 393 | */ 394 | export function isFileSync(path: string, useLSTAT = true) { 395 | return invokeForStatsSync( 396 | path, useLSTAT, 397 | (stats) => stats.isFile(), 398 | false 399 | ); 400 | } 401 | 402 | /** 403 | * Checks if a path exists and is a socket. 404 | * 405 | * @param {string} path The path to check. 406 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 407 | * 408 | * @return {Promise} The promise with the value that indicates if condition matches or not. 409 | */ 410 | export async function isSocket(path: string, useLSTAT = true) { 411 | return invokeForStats( 412 | path, useLSTAT, 413 | (stats) => stats.isSocket(), 414 | false 415 | ); 416 | } 417 | 418 | /** 419 | * Checks if a path exists and is a socket. 420 | * 421 | * @param {string} path The path to check. 422 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 423 | * 424 | * @return {boolean} A value that indicates if condition matches or not. 425 | */ 426 | export function isSocketSync(path: string, useLSTAT = true) { 427 | return invokeForStatsSync( 428 | path, useLSTAT, 429 | (stats) => stats.isSocket(), 430 | false 431 | ); 432 | } 433 | 434 | /** 435 | * Checks if a path exists and is a symbolic link. 436 | * 437 | * @param {string} path The path to check. 438 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 439 | * 440 | * @return {Promise} The promise with the value that indicates if condition matches or not. 441 | */ 442 | export async function isSymbolicLink(path: string, useLSTAT = true) { 443 | return invokeForStats( 444 | path, useLSTAT, 445 | (stats) => stats.isSymbolicLink(), 446 | false 447 | ); 448 | } 449 | 450 | /** 451 | * Checks if a path exists and is a symbolic link. 452 | * 453 | * @param {string} path The path to check. 454 | * @param {boolean} [useLSTAT] If (true) use 'FS.lstat()' function, otherwise 'FS.stat()'. 455 | * 456 | * @return {boolean} A value that indicates if condition matches or not. 457 | */ 458 | export function isSymbolicLinkSync(path: string, useLSTAT = true) { 459 | return invokeForStatsSync( 460 | path, useLSTAT, 461 | (stats) => stats.isSymbolicLink(), 462 | false 463 | ); 464 | } 465 | 466 | function normalizeGlobOptions(opts: Glob.IOptions, callerDefaultOpts: Glob.IOptions): Glob.IOptions { 467 | const DEFAULT_OPTS: Glob.IOptions = { 468 | absolute: true, 469 | dot: false, 470 | nocase: true, 471 | nodir: true, 472 | nonull: false, 473 | nosort: false, 474 | }; 475 | 476 | return MergeDeep({}, 477 | DEFAULT_OPTS, callerDefaultOpts, 478 | opts); 479 | } 480 | 481 | function normalizeTempFileOptions(opts: TempFileOptions) { 482 | const DEFAULT_OPTS: TempFileOptions = { 483 | }; 484 | 485 | opts = MergeDeep({}, 486 | DEFAULT_OPTS, opts); 487 | 488 | opts.dir = vscode_helpers.toStringSafe(opts.dir); 489 | if (vscode_helpers.isEmptyString(opts.dir)) { 490 | opts.dir = undefined; 491 | } 492 | 493 | opts.prefix = vscode_helpers.toStringSafe(opts.prefix); 494 | if ('' === opts.prefix) { 495 | opts.prefix = undefined; 496 | } 497 | 498 | opts.suffix = vscode_helpers.toStringSafe(opts.suffix); 499 | if ('' === opts.suffix) { 500 | opts.suffix = undefined; 501 | } 502 | 503 | return opts; 504 | } 505 | 506 | /** 507 | * Returns the size of a file system element. 508 | * 509 | * @param {string|Buffer} path The path to the element. 510 | * @param {boolean} [useLSTAT] Use 'lstat()' (true) or 'stat()' (false) function. 511 | * 512 | * @return {Promise} The promise with the size. 513 | */ 514 | export async function size(path: string | Buffer, useLSTAT = true) { 515 | useLSTAT = vscode_helpers.toBooleanSafe(useLSTAT, true); 516 | 517 | return useLSTAT ? (await lstat(path)).size 518 | : (await stat(path)).size; 519 | } 520 | 521 | /** 522 | * Returns the size of a file system element (sync). 523 | * 524 | * @param {string|Buffer} path The path to the element. 525 | * @param {boolean} [useLSTAT] Use 'lstatSync()' (true) or 'statSync()' (false) function. 526 | * 527 | * @return {number} The size. 528 | */ 529 | export function sizeSync(path: string | Buffer, useLSTAT = true) { 530 | useLSTAT = vscode_helpers.toBooleanSafe(useLSTAT, true); 531 | 532 | return useLSTAT ? FS.lstatSync(path).size 533 | : FS.statSync(path).size; 534 | } 535 | 536 | /** 537 | * Invokes an action for a temp file. 538 | * 539 | * @param {Function} action The action to invoke. 540 | * @param {TempFileOptions} [opts] The custom options. 541 | * 542 | * @return {Promise} The promise with the result of the action. 543 | */ 544 | export function tempFile( 545 | action: (file: string) => TResult | PromiseLike, 546 | opts?: TempFileOptions, 547 | ): Promise { 548 | opts = normalizeTempFileOptions(opts); 549 | 550 | return new Promise((resolve, reject) => { 551 | let completedInvoked = false; 552 | let tempFile: TempFilePath = false; 553 | const COMPLETED = (err: any, result?: TResult) => { 554 | if (completedInvoked) { 555 | return; 556 | } 557 | completedInvoked = true; 558 | 559 | try { 560 | if (err) { 561 | reject( err ); 562 | } else { 563 | resolve( result ); 564 | } 565 | } finally { 566 | tryUnlinkTempFile(tempFile, opts); 567 | } 568 | }; 569 | 570 | try { 571 | TMP.tmpName(toTmpSimpleOptions(opts), (err, path) => { 572 | if (err) { 573 | COMPLETED(err); 574 | } else { 575 | tempFile = path; 576 | 577 | try { 578 | Promise.resolve( action(tempFile) ).then((result) => { 579 | COMPLETED(null, result); 580 | }).catch((e) => { 581 | COMPLETED(e); 582 | }); 583 | } catch (e) { 584 | COMPLETED(e); 585 | } 586 | } 587 | }); 588 | } catch (e) { 589 | COMPLETED(e); 590 | } 591 | }); 592 | } 593 | 594 | function toTmpSimpleOptions(opts: TempFileOptions): any { 595 | return { 596 | dir: opts.dir, 597 | keep: true, 598 | prefix: opts.prefix, 599 | postfix: opts.suffix, 600 | }; 601 | } 602 | 603 | /** 604 | * Invokes an action for a temp file (sync). 605 | * 606 | * @param {Function} action The action to invoke. 607 | * @param {TempFileOptions} [opts] The custom options. 608 | * 609 | * @return {TResult} The result of the action. 610 | */ 611 | export function tempFileSync( 612 | action: (file: string) => TResult, opts?: TempFileOptions 613 | ): TResult { 614 | opts = normalizeTempFileOptions(opts); 615 | 616 | let tempFile: TempFilePath = false; 617 | try { 618 | tempFile = TMP.tmpNameSync( 619 | toTmpSimpleOptions(opts) 620 | ); 621 | 622 | return action(tempFile); 623 | } finally { 624 | tryUnlinkTempFile(tempFile, opts); 625 | } 626 | } 627 | 628 | function tryUnlinkTempFile(file: TempFilePath, opts?: TempFileOptions) { 629 | try { 630 | if (false !== file) { 631 | if (!vscode_helpers.toBooleanSafe(opts.keep)) { 632 | if (isFileSync(file)) { 633 | FS.unlinkSync( file ); 634 | } 635 | } 636 | } 637 | 638 | return true; 639 | } catch { 640 | return false; 641 | } 642 | } 643 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the vscode-helpers distribution. 3 | * Copyright (c) Marcel Joachim Kloubert. 4 | * 5 | * vscode-helpers is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, version 3. 8 | * 9 | * vscode-helpers is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import * as _ from 'lodash'; 19 | import * as ChildProcess from 'child_process'; 20 | import * as Crypto from 'crypto'; 21 | import * as Enumerable from 'node-enumerable'; 22 | import * as FS from 'fs'; 23 | const IsBinaryFile = require("isbinaryfile"); 24 | import * as IsStream from 'is-stream'; 25 | const MergeDeep = require('merge-deep'); 26 | import * as Minimatch from 'minimatch'; 27 | import * as Moment from 'moment'; 28 | import * as OS from 'os'; 29 | import * as Path from 'path'; 30 | import { default as PQueue, QueueAddOptions } from 'p-queue'; 31 | import * as Stream from 'stream'; 32 | import * as vscode from 'vscode'; 33 | import * as vscode_helpers_devtools from './devtools'; 34 | import * as vscode_helpers_events from './events'; 35 | import * as vscode_helpers_scm_git from './scm/git'; 36 | 37 | // !!!THESE MUST BE INCLUDED AFTER UPPER INCLUDED MODULES!!! 38 | import * as MomentTimeZone from 'moment-timezone'; 39 | 40 | // sub modules 41 | export * from './cache'; 42 | export * from './devtools'; 43 | export * from './disposable'; 44 | export * from './events'; 45 | export * from './fs'; 46 | export * from './html'; 47 | export * from './http'; 48 | export * from './logging'; 49 | export { from, range, repeat } from 'node-enumerable'; 50 | export * from './notifications'; 51 | export * from './progress'; 52 | export * from './timers'; 53 | export * from './workflows'; 54 | export * from './workspaces'; 55 | 56 | /** 57 | * Result of a file execution. 58 | */ 59 | export interface ExecFileResult { 60 | /** 61 | * The output from 'standard error' stream. 62 | */ 63 | stdErr: Buffer; 64 | /** 65 | * The output from 'standard output' stream. 66 | */ 67 | stdOut: Buffer; 68 | /** 69 | * The underlying process. 70 | */ 71 | process: ChildProcess.ChildProcess; 72 | } 73 | 74 | /** 75 | * Action for 'forEachAsync()' function. 76 | * 77 | * @param {T} item The current item. 78 | * @param {number} index The zero based index. 79 | * @param {T[]} array The array of all elements. 80 | * 81 | * @return {TResult|PromiseLike} The result. 82 | */ 83 | export type ForEachAsyncAction = (item: T, index: number, array: T[]) => TResult | PromiseLike; 84 | 85 | /** 86 | * Describes the structure of the package file (package.json). 87 | */ 88 | export interface PackageFile { 89 | /** 90 | * The display name. 91 | */ 92 | displayName?: string; 93 | /** 94 | * The (internal) name. 95 | */ 96 | name?: string; 97 | /** 98 | * The version string. 99 | */ 100 | version?: string; 101 | } 102 | 103 | /** 104 | * Options for 'openAndShowTextDocument()' function. 105 | */ 106 | export type OpenAndShowTextDocumentOptions = string | { 107 | /** 108 | * The initial content. 109 | */ 110 | content?: string; 111 | /** 112 | * The language. 113 | */ 114 | language?: string; 115 | }; 116 | 117 | /** 118 | * Describes a simple 'completed' action. 119 | * 120 | * @param {any} err The occurred error. 121 | * @param {TResult} [result] The result. 122 | */ 123 | export type SimpleCompletedAction = (err: any, result?: TResult) => void; 124 | 125 | /** 126 | * Normalizes a string. 127 | * 128 | * @param {TStr} str The value to normalize. 129 | * 130 | * @return {string} The normalized string. 131 | */ 132 | export type StringNormalizer = (str: TStr) => string; 133 | 134 | const readFile = FS.promises.readFile; 135 | 136 | let extensionRoot: string; 137 | 138 | /** 139 | * Is AIX or not. 140 | */ 141 | export const IS_AIX = process.platform === 'aix'; 142 | /** 143 | * Is Free BSD or not. 144 | */ 145 | export const IS_FREE_BSD = process.platform === 'freebsd'; 146 | /** 147 | * Is Linux or not. 148 | */ 149 | export const IS_LINUX = process.platform === 'linux'; 150 | /** 151 | * Is Sun OS or not. 152 | */ 153 | export const IS_MAC = process.platform === 'darwin'; 154 | /** 155 | * Is Open BSD or not. 156 | */ 157 | export const IS_OPEN_BSD = process.platform === 'openbsd'; 158 | /** 159 | * Is Sun OS or not. 160 | */ 161 | export const IS_SUNOS = process.platform === 'sunos'; 162 | /** 163 | * Is Windows or not. 164 | */ 165 | export const IS_WINDOWS = process.platform === 'win32'; 166 | 167 | /** 168 | * Global execution queue, which only allows one execution at the same time. 169 | */ 170 | export const QUEUE = new PQueue({ 171 | autoStart: true, 172 | concurrency: 1, 173 | }); 174 | 175 | /** 176 | * Stores global data for the current extension session. 177 | */ 178 | export const SESSION: { [key: string]: any } = {}; 179 | 180 | /** 181 | * Disposes 'SESSION', by removing its data. 182 | */ 183 | export const SESSION_DISPOSER: vscode.Disposable = { 184 | /** @inheritdoc */ 185 | dispose: () => { 186 | for (const P of Object.keys(SESSION)) { 187 | delete SESSION[ P ]; 188 | } 189 | } 190 | }; 191 | 192 | /** 193 | * Applies a function for a specific object / value. 194 | * 195 | * @param {TFunc} func The function. 196 | * @param {any} [thisArgs] The object to apply to the function. 197 | * 198 | * @return {TFunc} The wrapped function. 199 | */ 200 | export function applyFuncFor( 201 | func: TFunc, thisArgs: any 202 | ): TFunc { 203 | return function() { 204 | return func.apply(thisArgs, arguments); 205 | }; 206 | } 207 | 208 | /** 209 | * Returns a value as array. 210 | * 211 | * @param {T|T[]|ReadonlyArray} val The value. 212 | * @param {boolean} [removeEmpty] Remove items that are (null) / (undefined) or not. 213 | * 214 | * @return {T[]} The value as (new) array. 215 | */ 216 | export function asArray(val: T | T[] | ReadonlyArray, removeEmpty = true): T[] { 217 | removeEmpty = toBooleanSafe(removeEmpty, true); 218 | 219 | return (_.isArrayLike(val) ? val : [ val ]).filter(i => { 220 | if (removeEmpty) { 221 | return !_.isNil(i); 222 | } 223 | 224 | return true; 225 | }); 226 | } 227 | 228 | /** 229 | * Returns a value as buffer. 230 | * 231 | * @param {any} val The value to convert / cast. 232 | * @param {string} enc The custom encoding for the string parsers. 233 | * @param {number} [maxDepth] The custom value for the max depth of wrapped functions. Default: 63 234 | * 235 | * @return {Promise} The promise with the buffer. 236 | */ 237 | export async function asBuffer(val: any, enc?: string, maxDepth?: number): Promise { 238 | return await asBufferInner(val, enc, null, maxDepth); 239 | } 240 | 241 | async function asBufferInner(val: any, enc?: string, 242 | funcDepth?: number, maxDepth?: number): Promise { 243 | enc = normalizeString(enc); 244 | if ('' === enc) { 245 | enc = undefined; 246 | } 247 | 248 | if (isNaN(funcDepth)) { 249 | funcDepth = 0; 250 | } 251 | 252 | if (isNaN(maxDepth)) { 253 | maxDepth = 63; 254 | } 255 | 256 | if (funcDepth > maxDepth) { 257 | throw new Error(`Maximum depth of ${maxDepth} reached!`); 258 | } 259 | 260 | if (Buffer.isBuffer(val) || _.isNil(val)) { 261 | return val; 262 | } 263 | 264 | if (_.isFunction(val)) { 265 | // wrapped 266 | 267 | return asBufferInner( 268 | await Promise.resolve( 269 | val(enc, funcDepth, maxDepth), 270 | ), 271 | enc, 272 | funcDepth + 1, maxDepth, 273 | ); 274 | } 275 | 276 | if (IsStream.isReadableStream(val)) { 277 | // stream 278 | return await readAll(val); 279 | } 280 | 281 | if (_.isObject(val)) { 282 | // JSON object 283 | return Buffer.from( 284 | JSON.stringify(val), 285 | enc as BufferEncoding 286 | ); 287 | } 288 | 289 | // handle as string 290 | return Buffer.from( 291 | toStringSafe(val), 292 | enc as BufferEncoding 293 | ); 294 | } 295 | 296 | /** 297 | * Returns a value as local Moment instance. 298 | * 299 | * @param {any} val The input value. 300 | * 301 | * @return {Moment.Moment} The output value. 302 | */ 303 | export function asLocalTime(val: any): Moment.Moment { 304 | let localTime: Moment.Moment; 305 | 306 | if (!_.isNil(val)) { 307 | if (Moment.isMoment(val)) { 308 | localTime = val; 309 | } else if (Moment.isDate(val)) { 310 | localTime = Moment( val ); 311 | } else { 312 | localTime = Moment( toStringSafe(val) ); 313 | } 314 | } 315 | 316 | if (localTime) { 317 | if (!localTime.isLocal()) { 318 | localTime = localTime.local(); 319 | } 320 | } 321 | 322 | return localTime; 323 | } 324 | 325 | /** 326 | * Returns a value as UTC Moment instance. 327 | * 328 | * @param {any} val The input value. 329 | * 330 | * @return {Moment.Moment} The output value. 331 | */ 332 | export function asUTC(val: any): Moment.Moment { 333 | let utcTime: Moment.Moment; 334 | 335 | if (!_.isNil(val)) { 336 | if (Moment.isMoment(val)) { 337 | utcTime = val; 338 | } else if (Moment.isDate(val)) { 339 | utcTime = Moment( val ); 340 | } else { 341 | utcTime = Moment( toStringSafe(val) ); 342 | } 343 | } 344 | 345 | if (utcTime) { 346 | if (!utcTime.isUTC()) { 347 | utcTime = utcTime.utc(); 348 | } 349 | } 350 | 351 | return utcTime; 352 | } 353 | 354 | /** 355 | * Clones an object / value deep. 356 | * 357 | * @param {T} val The value / object to clone. 358 | * 359 | * @return {T} The cloned value / object. 360 | */ 361 | export function cloneObject(val: T): T { 362 | if (!val) { 363 | return val; 364 | } 365 | 366 | return JSON.parse( 367 | JSON.stringify(val) 368 | ); 369 | } 370 | 371 | /** 372 | * Clones an value flat. 373 | * 374 | * @param {T} val The object to clone. 375 | * @param {boolean} [useNewObjectForFunctions] Use new object as 'thisArg' for functions (true) or 376 | * the original 'val' (false). 377 | * 378 | * @return {T} The cloned object. 379 | */ 380 | export function cloneObjectFlat(val: T, 381 | useNewObjectForFunctions = true): T { 382 | useNewObjectForFunctions = toBooleanSafe(useNewObjectForFunctions, true); 383 | 384 | if (_.isNil(val)) { 385 | return val; 386 | } 387 | 388 | const CLONED_OBJ: T = {}; 389 | const THIS_ARG: any = useNewObjectForFunctions ? CLONED_OBJ : val; 390 | 391 | const ADD_PROPERTY = (prop: string, value: any) => { 392 | Object.defineProperty(CLONED_OBJ, prop, { 393 | configurable: true, 394 | enumerable: true, 395 | 396 | get: () => { 397 | return value; 398 | }, 399 | set: (newValue) => { 400 | value = newValue; 401 | }, 402 | }); 403 | }; 404 | 405 | _.forIn(val, (value, prop) => { 406 | let valueToSet: any = value; 407 | if (_.isFunction(valueToSet)) { 408 | const FUNC = valueToSet; 409 | 410 | valueToSet = function() { 411 | return FUNC.apply(THIS_ARG, arguments); 412 | }; 413 | } 414 | 415 | ADD_PROPERTY(prop, valueToSet); 416 | }); 417 | 418 | return CLONED_OBJ; 419 | } 420 | 421 | /** 422 | * Compares two values for a sort operation. 423 | * 424 | * @param {T} x The left value. 425 | * @param {T} y The right value. 426 | * 427 | * @return {number} The "sort value". 428 | */ 429 | export function compareValues(x: T, y: T): number { 430 | if (x !== y) { 431 | if (x > y) { 432 | return 1; 433 | } else if (x < y) { 434 | return -1; 435 | } 436 | } 437 | 438 | return 0; 439 | } 440 | 441 | /** 442 | * Compares values by using a selector. 443 | * 444 | * @param {T} x The left value. 445 | * @param {T} y The right value. 446 | * @param {Function} selector The selector. 447 | * 448 | * @return {number} The "sort value". 449 | */ 450 | export function compareValuesBy(x: T, y: T, 451 | selector: (t: T) => U): number { 452 | return compareValues(selector(x), 453 | selector(y)); 454 | } 455 | 456 | /** 457 | * Alias for 'createDevToolsClient'. 458 | */ 459 | export function createChromeClient(opts?: vscode_helpers_devtools.DevToolsClientOptions): vscode_helpers_devtools.DevToolsClient { 460 | return createDevToolsClient.apply(null, arguments); 461 | } 462 | 463 | /** 464 | * Creates a simple 'completed' callback for a promise. 465 | * 466 | * @param {Function} resolve The 'succeeded' callback. 467 | * @param {Function} reject The 'error' callback. 468 | * 469 | * @return {SimpleCompletedAction} The created action. 470 | */ 471 | export function createCompletedAction(resolve: (value?: TResult | PromiseLike) => void, 472 | reject?: (reason: any) => void): SimpleCompletedAction { 473 | let completedInvoked = false; 474 | 475 | return (err, result?) => { 476 | if (completedInvoked) { 477 | return; 478 | } 479 | completedInvoked = true; 480 | 481 | if (err) { 482 | if (reject) { 483 | reject(err); 484 | } 485 | } else { 486 | if (resolve) { 487 | resolve(result); 488 | } 489 | } 490 | }; 491 | } 492 | 493 | /** 494 | * Creates a new instance of a client, which can connect to a DevTools compatible 495 | * browser like Google Chrome. 496 | * 497 | * @param {vscode_helpers_devtools.DevToolsClientOptions} [opts] Custom options. 498 | * 499 | * @return {vscode_helpers_devtools.DevToolsClient} The new client instance. 500 | */ 501 | export function createDevToolsClient(opts?: vscode_helpers_devtools.DevToolsClientOptions) { 502 | return new vscode_helpers_devtools.DevToolsClient( opts ); 503 | } 504 | 505 | /** 506 | * Creates a Git client. 507 | * 508 | * @param {string} [cwd] The custom working directory. 509 | * @param {string} [path] The optional specific path where to search first. 510 | * 511 | * @return {Promise} The promise with the client or (false) if no client found. 512 | */ 513 | export function createGitClient(cwd?: string, path?: string) { 514 | return Promise.resolve( 515 | createGitClientSync(cwd, path) 516 | ); 517 | } 518 | 519 | /** 520 | * Creates a Git client (sync). 521 | * 522 | * @param {string} [cwd] The custom working directory. 523 | * @param {string} [path] The optional specific path where to search first. 524 | * 525 | * @return {vscode_helpers_scm_git.GitClient|false} The client or (false) if no client found. 526 | */ 527 | export function createGitClientSync(cwd?: string, path?: string): vscode_helpers_scm_git.GitClient { 528 | const CLIENT = tryCreateGitClientSync(cwd, path); 529 | if (false === CLIENT) { 530 | throw new Error('No git client found!'); 531 | } 532 | 533 | return CLIENT; 534 | } 535 | 536 | /** 537 | * Creates a new queue. 538 | * 539 | * @param {TOpts} [opts] The custom options. 540 | * 541 | * @return {PQueue} The new queue. 542 | */ 543 | export function createQueue( 544 | opts?: TOpts 545 | ) { 546 | const DEFAULT_OPTS: any = { 547 | autoStart: true, 548 | concurrency: 1, 549 | }; 550 | 551 | return new PQueue( 552 | MergeDeep(DEFAULT_OPTS, opts) 553 | ); 554 | } 555 | 556 | /** 557 | * Handles a value as string and checks if it does match at least one (minimatch) pattern. 558 | * 559 | * @param {any} val The value to check. 560 | * @param {string|string[]} patterns One or more patterns. 561 | * @param {Minimatch.IOptions} [options] Additional options. 562 | * 563 | * @return {boolean} Does match or not. 564 | */ 565 | export function doesMatch(val: any, patterns: string | string[], options?: Minimatch.IOptions): boolean { 566 | val = toStringSafe(val); 567 | 568 | patterns = asArray(patterns).map(p => { 569 | return toStringSafe(p); 570 | }); 571 | 572 | for (const P of patterns) { 573 | if (Minimatch(val, P, options)) { 574 | return true; 575 | } 576 | } 577 | 578 | return false; 579 | } 580 | 581 | /** 582 | * Executes a file. 583 | * 584 | * @param {string} command The thing / command to execute. 585 | * @param {any[]} [args] One or more argument for the execution. 586 | * @param {ChildProcess.ExecFileOptions} [opts] Custom options. 587 | * 588 | * @return {Promise} The promise with the result. 589 | */ 590 | export async function execFile(command: string, args?: any[], opts?: ChildProcess.ExecFileOptions) { 591 | command = toStringSafe(command); 592 | 593 | args = asArray(args, false).map(a => { 594 | return toStringSafe(a); 595 | }); 596 | 597 | if (!opts) { 598 | opts = {}; 599 | } 600 | 601 | if (_.isNil(opts.env)) { 602 | opts.env = process.env; 603 | } 604 | 605 | return new Promise((resolve, reject) => { 606 | const RESULT: ExecFileResult = { 607 | stdErr: undefined, 608 | stdOut: undefined, 609 | process: undefined, 610 | }; 611 | 612 | let completedInvoked = false; 613 | const COMPLETED = (err: any) => { 614 | if (completedInvoked) { 615 | return; 616 | } 617 | completedInvoked = true; 618 | 619 | if (err) { 620 | reject(err); 621 | } else { 622 | resolve(RESULT); 623 | } 624 | }; 625 | 626 | try { 627 | const P = ChildProcess.execFile(command, args, opts, (err, stdout, stderr) => { 628 | if (err) { 629 | COMPLETED(err); 630 | } else { 631 | try { 632 | RESULT.process = P; 633 | 634 | (async () => { 635 | RESULT.stdErr = await asBuffer(stderr, 'utf8'); 636 | RESULT.stdOut = await asBuffer(stdout, 'utf8'); 637 | })().then(() => { 638 | COMPLETED(null); 639 | }, (err) => { 640 | COMPLETED(err); 641 | }); 642 | } catch (e) { 643 | COMPLETED(e); 644 | } 645 | } 646 | }); 647 | } catch (e) { 648 | COMPLETED(e); 649 | } 650 | }); 651 | } 652 | 653 | /** 654 | * Async 'forEach'. 655 | * 656 | * @param {Enumerable.Sequence} items The items to iterate. 657 | * @param {Function} action The item action. 658 | * @param {any} [thisArg] The underlying object / value for the item action. 659 | * 660 | * @return {TResult} The result of the last action call. 661 | */ 662 | export async function forEachAsync(items: Enumerable.Sequence, 663 | action: ForEachAsyncAction, 664 | thisArg?: any) { 665 | if (!_.isArrayLike(items)) { 666 | items = Enumerable.from(items) 667 | .toArray(); 668 | } 669 | 670 | let lastResult: TResult; 671 | 672 | for (let i = 0; i < (>items).length; i++) { 673 | lastResult = await Promise.resolve( 674 | action.apply(thisArg, 675 | [ items[i], i, items ]), 676 | ); 677 | } 678 | 679 | return lastResult; 680 | } 681 | 682 | /** 683 | * Formats a string. 684 | * 685 | * @param {any} formatStr The value that represents the format string. 686 | * @param {any[]} [args] The arguments for 'formatStr'. 687 | * 688 | * @return {string} The formated string. 689 | */ 690 | export function format(formatStr: any, ...args: any[]): string { 691 | return formatArray(formatStr, args); 692 | } 693 | 694 | /** 695 | * Formats a string. 696 | * 697 | * @param {any} formatStr The value that represents the format string. 698 | * @param {Enumerable.Sequence} [args] The arguments for 'formatStr'. 699 | * 700 | * @return {string} The formated string. 701 | */ 702 | export function formatArray(formatStr: any, args: Enumerable.Sequence): string { 703 | formatStr = toStringSafe(formatStr); 704 | 705 | if (!_.isArrayLike(args)) { 706 | args = Enumerable.from(args) 707 | .toArray(); 708 | } 709 | 710 | // apply arguments in 711 | // placeholders 712 | return formatStr.replace(/{(\d+)(\:)?([^}]*)}/g, (match, index, formatSeparator, formatExpr) => { 713 | index = parseInt( 714 | toStringSafe(index) 715 | ); 716 | 717 | let resultValue = (>args)[index]; 718 | 719 | if (':' === formatSeparator) { 720 | // collect "format providers" 721 | const FORMAT_PROVIDERS = toStringSafe(formatExpr).split(',') 722 | .map(x => x.toLowerCase().trim()) 723 | .filter(x => x); 724 | 725 | // transform argument by 726 | // format providers 727 | FORMAT_PROVIDERS.forEach(fp => { 728 | switch (fp) { 729 | case 'ending_space': 730 | resultValue = toStringSafe(resultValue); 731 | if ('' !== resultValue) { 732 | resultValue = resultValue + ' '; 733 | } 734 | break; 735 | 736 | case 'leading_space': 737 | resultValue = toStringSafe(resultValue); 738 | if ('' !== resultValue) { 739 | resultValue = ' ' + resultValue; 740 | } 741 | break; 742 | 743 | case 'lower': 744 | resultValue = toStringSafe(resultValue).toLowerCase(); 745 | break; 746 | 747 | case 'trim': 748 | resultValue = toStringSafe(resultValue).trim(); 749 | break; 750 | 751 | case 'upper': 752 | resultValue = toStringSafe(resultValue).toUpperCase(); 753 | break; 754 | 755 | case 'surround': 756 | resultValue = toStringSafe(resultValue); 757 | if ('' !== resultValue) { 758 | resultValue = "'" + toStringSafe(resultValue) + "'"; 759 | } 760 | break; 761 | } 762 | }); 763 | } 764 | 765 | if (_.isUndefined(resultValue)) { 766 | return match; 767 | } 768 | 769 | return toStringSafe(resultValue); 770 | }); 771 | } 772 | 773 | /** 774 | * Gets the root directory of the extension. 775 | * 776 | * @return {string} The root directory of the extension. 777 | */ 778 | export function getExtensionRoot() { 779 | return extensionRoot; 780 | } 781 | 782 | /** 783 | * Loads the package file (package.json) of the extension. 784 | * 785 | * @param {string} [packageJson] The custom path to the file. 786 | * 787 | * @return {Promise} The promise with the meta data of the file. 788 | */ 789 | export async function getPackageFile( 790 | packageJson = '../package.json' 791 | ): Promise { 792 | return JSON.parse( 793 | (await readFile( 794 | getPackageFilePath(packageJson) 795 | )).toString('utf8') 796 | ); 797 | } 798 | 799 | function getPackageFilePath( 800 | packageJson?: string 801 | ) { 802 | packageJson = toStringSafe(packageJson); 803 | if ('' === packageJson.trim()) { 804 | packageJson = '../package.json'; 805 | } 806 | 807 | if (!Path.isAbsolute(packageJson)) { 808 | packageJson = Path.join( 809 | getExtensionRoot(), packageJson 810 | ); 811 | } 812 | 813 | return Path.resolve( packageJson ); 814 | } 815 | 816 | /** 817 | * Loads the package file (package.json) of the extension sync. 818 | * 819 | * @param {string} [packageJson] The custom path to the file. 820 | * 821 | * @return {PackageFile} The meta data of the file. 822 | */ 823 | export function getPackageFileSync( 824 | packageJson = '../package.json' 825 | ): PackageFile { 826 | return JSON.parse( 827 | (FS.readFileSync( 828 | getPackageFilePath(packageJson) 829 | )).toString('utf8') 830 | ); 831 | } 832 | 833 | /** 834 | * Alias for 'uuid'. 835 | */ 836 | export function guid(ver?: string, ...args: any[]): string { 837 | return uuid.apply(this, 838 | arguments); 839 | } 840 | 841 | /** 842 | * Checks if data is binary or text content. 843 | * 844 | * @param {Buffer} data The data to check. 845 | * 846 | * @returns {Promise} The promise that indicates if content is binary or not. 847 | */ 848 | export function isBinaryContent(data: Buffer): Promise { 849 | return new Promise((resolve, reject) => { 850 | const COMPLETED = createCompletedAction(resolve, reject); 851 | 852 | try { 853 | IsBinaryFile(data, data.length, (err, result) => { 854 | COMPLETED(err, result); 855 | }); 856 | } catch (e) { 857 | COMPLETED(e); 858 | } 859 | }); 860 | } 861 | 862 | /** 863 | * Checks if data is binary or text content (sync). 864 | * 865 | * @param {Buffer} data The data to check. 866 | * 867 | * @returns {boolean} Content is binary or not. 868 | */ 869 | export function isBinaryContentSync(data: Buffer): boolean { 870 | return IsBinaryFile.sync(data, data.length); 871 | } 872 | 873 | /** 874 | * Checks if the string representation of a value is empty 875 | * or contains whitespaces only. 876 | * 877 | * @param {any} val The value to check. 878 | * 879 | * @return {boolean} Is empty or not. 880 | */ 881 | export function isEmptyString(val: any): boolean { 882 | return '' === toStringSafe(val).trim(); 883 | } 884 | 885 | /** 886 | * Loads a module from a script. 887 | * 888 | * @param {string} file The path to the script. 889 | * @param {boolean} [fromCache] Cache module or not. 890 | * 891 | * @return {TModule} The loaded module. 892 | */ 893 | export function loadModule(file: string, fromCache = false): TModule { 894 | file = toStringSafe(file); 895 | if (isEmptyString(file)) { 896 | file = './module.js'; 897 | } 898 | if (!Path.isAbsolute(file)) { 899 | file = Path.join(process.cwd(), file); 900 | } 901 | file = Path.resolve(file); 902 | 903 | fromCache = toBooleanSafe(fromCache); 904 | 905 | if (!fromCache) { 906 | delete require.cache[file]; 907 | } 908 | 909 | return require(file); 910 | } 911 | 912 | /** 913 | * Normalizes a value as string so that is comparable. 914 | * 915 | * @param {any} val The value to convert. 916 | * @param {StringNormalizer} [normalizer] The custom normalizer. 917 | * 918 | * @return {string} The normalized value. 919 | */ 920 | export function normalizeString(val: any, normalizer?: StringNormalizer): string { 921 | if (!normalizer) { 922 | normalizer = (str) => str.toLowerCase().trim(); 923 | } 924 | 925 | return normalizer( toStringSafe(val) ); 926 | } 927 | 928 | /** 929 | * Returns the current time. 930 | * 931 | * @param {string} [timezone] The custom timezone to use. 932 | * 933 | * @return {Moment.Moment} The current time. 934 | */ 935 | export function now(timezone?: string): Moment.Moment { 936 | timezone = toStringSafe(timezone).trim(); 937 | 938 | const NOW = Moment(); 939 | return '' === timezone ? NOW 940 | : NOW.tz(timezone); 941 | } 942 | 943 | /** 944 | * Opens and shows a new text document / editor. 945 | * 946 | * @param {OpenAndShowTextDocumentOptions} [filenameOrOpts] The custom options or the path to the file to open. 947 | * 948 | * @return {vscode.TextEditor} The promise with the new, opened text editor. 949 | */ 950 | export async function openAndShowTextDocument(filenameOrOpts?: OpenAndShowTextDocumentOptions) { 951 | if (_.isNil(filenameOrOpts)) { 952 | filenameOrOpts = { 953 | content: '', 954 | language: 'plaintext', 955 | }; 956 | } 957 | 958 | return await vscode.window.showTextDocument( 959 | await vscode.workspace.openTextDocument( filenameOrOpts ) 960 | ); 961 | } 962 | 963 | /** 964 | * Promise version of 'crypto.randomBytes()' function. 965 | * 966 | * @param {number} size The size of the result. 967 | * 968 | * @return {Promise} The buffer with the random bytes. 969 | */ 970 | export function randomBytes(size: number) { 971 | size = parseInt( 972 | toStringSafe(size).trim() 973 | ); 974 | 975 | return new Promise((resolve, reject) => { 976 | const COMPLETED = createCompletedAction(resolve, reject); 977 | 978 | Crypto.randomBytes(size, (err, buf) => { 979 | COMPLETED(err, buf); 980 | }); 981 | }); 982 | } 983 | 984 | /** 985 | * Reads the content of a stream. 986 | * 987 | * @param {Stream.Readable} stream The stream. 988 | * @param {string} [enc] The custom (string) encoding to use. 989 | * 990 | * @returns {Promise} The promise with the content. 991 | */ 992 | export function readAll(stream: Stream.Readable, enc?: string): Promise { 993 | enc = normalizeString(enc); 994 | if ('' === enc) { 995 | enc = undefined; 996 | } 997 | 998 | return new Promise((resolve, reject) => { 999 | let buff: Buffer; 1000 | 1001 | let dataListener: (chunk: Buffer | string) => void; 1002 | let endListener: () => void; 1003 | let errorListener: (err: any) => void; 1004 | 1005 | let completedInvoked = false; 1006 | const COMPLETED = (err: any) => { 1007 | if (completedInvoked) { 1008 | return; 1009 | } 1010 | completedInvoked = true; 1011 | 1012 | vscode_helpers_events.tryRemoveListener(stream, 'data', dataListener); 1013 | vscode_helpers_events.tryRemoveListener(stream, 'end', endListener); 1014 | vscode_helpers_events.tryRemoveListener(stream, 'error', errorListener); 1015 | 1016 | if (err) { 1017 | reject(err); 1018 | } else { 1019 | resolve(buff); 1020 | } 1021 | }; 1022 | 1023 | if (_.isNil(stream)) { 1024 | buff = stream; 1025 | 1026 | COMPLETED(null); 1027 | return; 1028 | } 1029 | 1030 | errorListener = (err: any) => { 1031 | if (err) { 1032 | COMPLETED(err); 1033 | } 1034 | }; 1035 | 1036 | dataListener = (chunk: Buffer | string) => { 1037 | try { 1038 | if (!chunk || chunk.length < 1) { 1039 | return; 1040 | } 1041 | 1042 | if (_.isString(chunk)) { 1043 | chunk = Buffer.from(chunk, enc as BufferEncoding); 1044 | } 1045 | 1046 | buff = Buffer.concat([ buff, chunk ]); 1047 | } catch (e) { 1048 | COMPLETED(e); 1049 | } 1050 | }; 1051 | 1052 | endListener = () => { 1053 | COMPLETED(null); 1054 | }; 1055 | 1056 | try { 1057 | stream.on('error', errorListener); 1058 | 1059 | buff = Buffer.alloc(0); 1060 | 1061 | stream.once('end', endListener); 1062 | 1063 | stream.on('data', dataListener); 1064 | } catch (e) { 1065 | COMPLETED(e); 1066 | } 1067 | }); 1068 | } 1069 | 1070 | /** 1071 | * Sets the root directory of the extension. 1072 | * 1073 | * @param {string} path The path of the extension. 1074 | * 1075 | * @return {string} The new value. 1076 | */ 1077 | export function setExtensionRoot(path: string) { 1078 | path = toStringSafe(path); 1079 | if ('' === path.trim()) { 1080 | path = undefined; 1081 | } else { 1082 | if (!Path.isAbsolute(path)) { 1083 | path = Path.join( 1084 | process.cwd(), path 1085 | ); 1086 | } 1087 | 1088 | path = Path.resolve( path ); 1089 | } 1090 | 1091 | extensionRoot = path; 1092 | return path; 1093 | } 1094 | 1095 | /** 1096 | * Returns a sequence object as new array. 1097 | * 1098 | * @param {Enumerable.Sequence} seq The input object. 1099 | * @param {boolean} [normalize] Returns an empty array, if input object is (null) / (undefined). 1100 | * 1101 | * @return {T[]} The input object as array. 1102 | */ 1103 | export function toArray(seq: Enumerable.Sequence, normalize = true): T[] { 1104 | if (_.isNil(seq)) { 1105 | if (toBooleanSafe(normalize, true)) { 1106 | return []; 1107 | } 1108 | 1109 | return seq; 1110 | } 1111 | 1112 | if (_.isArrayLike(seq)) { 1113 | const NEW_ARRAY: T[] = []; 1114 | 1115 | for (let i = 0; i < seq.length; i++) { 1116 | NEW_ARRAY.push( seq[i] ); 1117 | } 1118 | 1119 | return NEW_ARRAY; 1120 | } 1121 | 1122 | return Enumerable.from( seq ) 1123 | .toArray(); 1124 | } 1125 | 1126 | 1127 | /** 1128 | * Returns a value as boolean, which is not (null) and (undefined). 1129 | * 1130 | * @param {any} val The value to convert. 1131 | * @param {boolean} [defaultVal] The custom default value if 'val' is (null) or (undefined). 1132 | * 1133 | * @return {boolean} 'val' as boolean. 1134 | */ 1135 | export function toBooleanSafe(val: any, defaultVal = false): boolean { 1136 | if (_.isBoolean(val)) { 1137 | return val; 1138 | } 1139 | 1140 | if (_.isNil(val)) { 1141 | return !!defaultVal; 1142 | } 1143 | 1144 | return !!val; 1145 | } 1146 | 1147 | /** 1148 | * Converts an EOL enum value to a string. 1149 | * 1150 | * @param {vscode.EndOfLine} [eol] The (optional) enum value. 1151 | * 1152 | * @return string The EOL string. 1153 | */ 1154 | export function toEOL(eol?: vscode.EndOfLine): string { 1155 | switch (eol) { 1156 | case vscode.EndOfLine.CRLF: 1157 | return "\r\n"; 1158 | 1159 | case vscode.EndOfLine.LF: 1160 | return "\n"; 1161 | } 1162 | 1163 | return OS.EOL; 1164 | } 1165 | 1166 | /** 1167 | * Returns a value as string, which is not (null) and (undefined). 1168 | * 1169 | * @param {any} val The value to convert. 1170 | * @param {string} [defaultVal] The custom default value if 'val' is (null) or (undefined). 1171 | * 1172 | * @return {string} 'val' as string. 1173 | */ 1174 | export function toStringSafe(val: any, defaultVal = ''): string { 1175 | if (_.isString(val)) { 1176 | return val; 1177 | } 1178 | 1179 | if (_.isNil(val)) { 1180 | return '' + defaultVal; 1181 | } 1182 | 1183 | try { 1184 | if (val instanceof Error) { 1185 | return '' + val.message; 1186 | } 1187 | 1188 | if (_.isFunction(val['toString'])) { 1189 | return '' + val.toString(); 1190 | } 1191 | 1192 | if (_.isObject(val)) { 1193 | return JSON.stringify(val); 1194 | } 1195 | } catch { } 1196 | 1197 | return '' + val; 1198 | } 1199 | 1200 | /** 1201 | * Tries to create a Git client. 1202 | * 1203 | * @param {string} [cwd] The custom working directory. 1204 | * @param {string} [path] The optional specific path where to search first. 1205 | * 1206 | * @return {Promise} The promise with the client or (false) if no client found. 1207 | */ 1208 | export function tryCreateGitClient(cwd?: string, path?: string): Promise { 1209 | return Promise.resolve( 1210 | tryCreateGitClientSync(cwd, path) 1211 | ); 1212 | } 1213 | 1214 | /** 1215 | * Tries to create a Git client (sync). 1216 | * 1217 | * @param {string} [cwd] The custom working directory. 1218 | * @param {string} [path] The optional specific path where to search first. 1219 | * 1220 | * @return {vscode_helpers_scm_git.GitClient|false} The client or (false) if no client found. 1221 | */ 1222 | export function tryCreateGitClientSync(cwd?: string, path?: string): vscode_helpers_scm_git.GitClient | false { 1223 | const GIT_EXEC = vscode_helpers_scm_git.tryFindGitPathSync(path); 1224 | if (false !== GIT_EXEC) { 1225 | return new vscode_helpers_scm_git.GitClient(GIT_EXEC, cwd); 1226 | } 1227 | 1228 | return false; 1229 | } 1230 | 1231 | /** 1232 | * Returns the current UTC time. 1233 | * 1234 | * @return {Moment.Moment} The current UTC time. 1235 | */ 1236 | export function utcNow(): Moment.Moment { 1237 | return Moment.utc(); 1238 | } 1239 | 1240 | /** 1241 | * Generates a new unique ID. 1242 | * 1243 | * @param {string} [ver] The custom version to use. Default: '4'. 1244 | * @param {any[]} [args] Additional arguments for the function. 1245 | * 1246 | * @return {string} The generated ID. 1247 | */ 1248 | export function uuid(ver?: string, ...args: any[]): string { 1249 | const UUID = require('uuid'); 1250 | 1251 | ver = normalizeString(ver); 1252 | 1253 | let func: Function | false = false; 1254 | switch (ver) { 1255 | case '': 1256 | case '4': 1257 | case 'v4': 1258 | func = UUID.v4; 1259 | break; 1260 | 1261 | case '1': 1262 | case 'v1': 1263 | func = UUID.v1; 1264 | break; 1265 | 1266 | case '5': 1267 | case 'v5': 1268 | func = UUID.v5; 1269 | break; 1270 | } 1271 | 1272 | if (false === func) { 1273 | throw new Error(`Version '${ ver }' is not supported!`); 1274 | } 1275 | 1276 | return func.apply(null, args); 1277 | } 1278 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/vscode-helpers.svg)](https://www.npmjs.com/package/vscode-helpers) 2 | [![npm](https://img.shields.io/npm/dt/vscode-helpers.svg?label=npm%20downloads)](https://www.npmjs.com/package/vscode-helpers) 3 | 4 | # vscode-helpers 5 | 6 | Helper functions and classes for [Visual Studio Code extensions](https://code.visualstudio.com/docs/extensions/overview). 7 | 8 | ## Table of contents 9 | 10 | 1. [Install](#install-) 11 | 2. [Usage](#usage-) 12 | 3. [Examples](#examples-) 13 | * [Functions](#functions-) 14 | * [applyFuncFor](#applyfuncfor-) 15 | * [asArray](#asarray-) 16 | * [asBuffer](#asbuffer-) 17 | * [asLocalTime](#aslocaltime-) 18 | * [asUTC](#asutc-) 19 | * [buildWorkflow](#buildworkflow-) 20 | * [cloneObject](#cloneobject-) 21 | * [cloneObjectFlat](#cloneobjectflat-) 22 | * [compareValues](#comparevalues-) 23 | * [compareValuesBy](#comparevaluesby-) 24 | * [createChromeClient](#createchromeclient-) 25 | * [createCompletedAction](#createcompletedaction-) 26 | * [createDevToolsClient](#createdevtoolsclient-) 27 | * [createDirectoryIfNeeded](#createdirectoryifneeded-) 28 | * [createGitClient](#creategitclient-) 29 | * [createGitClientSync](#creategitclientsync-) 30 | * [createInterval](#createinterval-) 31 | * [createLogger](#createlogger-) 32 | * [createQueue](#createqueue-) 33 | * [createTimeout](#createtimeout-) 34 | * [DELETE](#delete-) 35 | * [doesMatch](#doesmatch-) 36 | * [execFile](#execfile-) 37 | * [exists](#exists-) 38 | * [fastGlob](#fastglob-) 39 | * [fastGlobSync](#fastglobsync-) 40 | * [filterExtensionNotifications](#filterextensionnotifications-) 41 | * [forEachAsync](#foreachasync-) 42 | * [format](#format-) 43 | * [formatArray](#formatarray-) 44 | * [from](#from-) 45 | * [fromMarkdown](#frommarkdown-) 46 | * [GET](#get-) 47 | * [getExtensionNotifications](#getextensionnotifications-) 48 | * [getExtensionRoot](#getextensionroot-) 49 | * [getPackageFile](#getpackagefile-) 50 | * [getPackageFileSync](#getpackagefilesync-) 51 | * [glob](#glob-) 52 | * [globSync](#globsync-) 53 | * [guid](#guid-) 54 | * [invokeAfter](#invokeafter-) 55 | * [isBinaryContent](#isbinarycontent-) 56 | * [isBinaryContentSync](#isbinarycontentsync-) 57 | * [isBlockDevice](#isblockdevice-) 58 | * [isBlockDeviceSync](#isblockdevicesync-) 59 | * [isCharacterDevice](#ischaracterdevice-) 60 | * [isCharacterDeviceSync](#ischaracterdevicesync-) 61 | * [isDirectory](#isdirectory-) 62 | * [isDirectorySync](#isdirectorysync-) 63 | * [isEmptyString](#isemptystring-) 64 | * [isFIFO](#isfifo-) 65 | * [isFIFOSync](#isfifosync-) 66 | * [isFile](#isfile-) 67 | * [isFileSync](#isfilesync-) 68 | * [isSocket](#issocket-) 69 | * [isSocketSync](#issocketsync-) 70 | * [isSymbolicLink](#issymboliclink-) 71 | * [isSymbolicLinkSync](#issymboliclinksync-) 72 | * [loadModule](#loadmodule-) 73 | * [makeNonDisposable](#makenondisposable-) 74 | * [normalizeString](#normalizestring-) 75 | * [now](#now-) 76 | * [openAndShowTextDocument](#openandshowtextdocument-) 77 | * [PATCH](#patch-) 78 | * [POST](#post-) 79 | * [PUT](#put-) 80 | * [randomBytes](#randombytes-) 81 | * [range](#range-) 82 | * [registerWorkspaceWatcher](#registerworkspacewatcher-) 83 | * [readAll](#readall-) 84 | * [repeat](#repeat-) 85 | * [request](#request-) 86 | * [setExtensionRoot](#setextensionroot-) 87 | * [size](#size-) 88 | * [sizeSync](#sizeSync-) 89 | * [sleep](#sleep-) 90 | * [startWatch](#startwatch-) 91 | * [tempFile](#tempfile-) 92 | * [tempFileSync](#tempfilesync-) 93 | * [toArray](#toarray-) 94 | * [toBooleanSafe](#tobooleansafe-) 95 | * [toEOL](#toeol-) 96 | * [toStringSafe](#tostringsafe-) 97 | * [tryClearInterval](#tryclearinterval-) 98 | * [tryClearTimeout](#trycleartimeout-) 99 | * [tryDispose](#trydispose-) 100 | * [tryDisposeAndDelete](#trydisposeanddelete-) 101 | * [tryCreateGitClient](#trycreategitclient-) 102 | * [tryCreateGitClientSync](#trycreategitclientsync-) 103 | * [tryRemoveAllListeners](#tryremovealllisteners-) 104 | * [tryRemoveListener](#tryremovelistener-) 105 | * [using](#using-) 106 | * [usingSync](#usingsync-) 107 | * [utcNow](#utcnow-) 108 | * [uuid](#uuid-) 109 | * [waitWhile](#waitwhile-) 110 | * [withProgress](#withprogress-) 111 | * [Classes](#classes-) 112 | * [CacheProviderBase](#cacheproviderbase-) 113 | * [DisposableBase](#disposablebase-) 114 | * [MemoryCache](#memorycache-) 115 | * [StopWatch](#stopwatch-) 116 | * [WorkspaceBase](#workspacebase-) 117 | * [Constants and variables](#constants-and-variables-) 118 | * [EVENTS](#events-) 119 | * [IS_*](#is_-) 120 | * [QUEUE](#queue-) 121 | * [SESSION](#session-) 122 | 4. [Branches](#branches-) 123 | 5. [Support and contribute](#support-and-contribute-) 124 | 6. [Documentation](#documentation-) 125 | 126 | ## Install [[↑](#table-of-contents)] 127 | 128 | From your project, run the following command: 129 | 130 | ```bash 131 | npm install --save vscode-helpers 132 | ``` 133 | 134 | ## Usage [[↑](#table-of-contents)] 135 | 136 | ```typescript 137 | // plain JavaScript 138 | const vscode_helpers = require('vscode-helpers'); 139 | 140 | // the TypeScript way 141 | import * as vscode_helpers from 'vscode-helpers'; 142 | ``` 143 | 144 | ## Examples [[↑](#table-of-contents)] 145 | 146 | An example of a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) ready extension (`extension.ts`): 147 | 148 | ```typescript 149 | 'use strict'; 150 | 151 | import * as Path from 'path'; 152 | import * as vscode from 'vscode'; 153 | import * as vscode_helpers from 'vscode-helpers'; 154 | 155 | class MyWorkspace extends vscode_helpers.WorkspaceBase { 156 | private _configSrc: vscode_helpers.WorkspaceConfigSource; 157 | 158 | // this is important for 'onDidChangeConfiguration' (s. below) 159 | public get configSource() { 160 | return this._configSrc; 161 | } 162 | 163 | public async initialize() { 164 | // initialize your workspace here 165 | 166 | this._configSrc = { 167 | section: 'my.extension', 168 | resource: Uri.file( Path.join(this.rootPath, 169 | '.vscode/settings.json') ), 170 | }; 171 | } 172 | 173 | public async onDidChangeConfiguration(e) { 174 | const NEW_CONFIG = vscode.workspace.getConfiguration( 175 | this.configSource.section, 176 | this.configSource.resource 177 | ); 178 | 179 | // handle new config here 180 | } 181 | } 182 | 183 | let workspaceWatcher: vscode_helpers.WorkspaceWatcherContext; 184 | 185 | export async function activate(context: vscode.ExtensionContext) { 186 | context.subscriptions.push( 187 | workspaceWatcher = 188 | vscode_helpers.registerWorkspaceWatcher(context, async (ev, folder) => { 189 | if (ev === vscode_helpers.WorkspaceWatcherEvent.Added) { 190 | const NEW_WORKSPACE = new MyWorkspace(folder); 191 | 192 | await NEW_WORKSPACE.initialize(); 193 | 194 | return NEW_WORKSPACE; 195 | } 196 | }), 197 | ); 198 | 199 | await workspaceWatcher.reload(); 200 | } 201 | 202 | export async function deactivate() { 203 | //TODO 204 | } 205 | ``` 206 | 207 | ### Functions [[↑](#examples-)] 208 | 209 | #### applyFuncFor [[↑](#functions-)] 210 | 211 | ```typescript 212 | const OBJ = { factor: 1000 }; 213 | 214 | function myTestFunc(a, b) { 215 | return (a + b) * this.factor; 216 | } 217 | 218 | const APPLIED_FUNC = vscode_helpers.applyFuncFor( 219 | myTestFunc, OBJ 220 | ); 221 | 222 | APPLIED_FUNC(5979, 23979); // 29958000 223 | ``` 224 | 225 | #### asArray [[↑](#functions-)] 226 | 227 | ```typescript 228 | const ARR_1 = vscode_helpers.asArray([ 0, 1, null, 3, 4, undefined ]); // [ 0, 1, 3, 4 ] 229 | const ARR_2 = vscode_helpers.asArray([ 0, 1, null, 3, 4, undefined ], false); // [ 0, 1, null, 3, 4, undefined ] 230 | const ARR_3 = vscode_helpers.asArray( 5979 ); // [ 5979 ] 231 | const ARR_4 = vscode_helpers.asArray( null ); // [ ] 232 | ``` 233 | 234 | #### asBuffer [[↑](#functions-)] 235 | 236 | ```typescript 237 | import * as fs from 'fs'; 238 | 239 | const STREAM = fs.createReadStream('./my-file.txt'); 240 | 241 | asBuffer( STREAM ).then((data: Buffer) => { 242 | // all data read 243 | }, (err) => { 244 | // error 245 | }); 246 | ``` 247 | 248 | #### asLocalTime [[↑](#functions-)] 249 | 250 | ```typescript 251 | import * as Moment from 'moment'; 252 | 253 | let utcNow = Moment.utc(); 254 | let localNow = vscode_helpers.asLocalTime( utcNow ); // can also be a string 255 | // or Date object 256 | ``` 257 | 258 | #### asUTC [[↑](#functions-)] 259 | 260 | ```typescript 261 | import * as Moment from 'moment'; 262 | 263 | let localNow = Moment(); 264 | let utcNow = vscode_helpers.asUTC( localNow ); // can also be a string 265 | // or Date object 266 | ``` 267 | 268 | #### buildWorkflow [[↑](#functions-)] 269 | 270 | ```typescript 271 | const WORKFLOW = vscode_helpers.buildWorkflow() 272 | .next((prevValue: undefined, context: vscode_helpers.WorkflowActionContext) => { 273 | context.value = 1000; 274 | 275 | return 5979; 276 | }) 277 | .next((prevValue: number, context: vscode_helpers.WorkflowActionContext) => { 278 | return prevValue + 23979; // prevValue === 5979 279 | }) 280 | .next((prevValue: number, context: vscode_helpers.WorkflowActionContext) => { 281 | // prevValue === 29958 282 | // context.value === 1000 283 | return '' + (prevValue * context.value); 284 | }); 285 | 286 | WORKFLOW.start().then((result: string) => { 287 | // result === '29958000' 288 | }, (err) => { 289 | // this only happens on error 290 | }); 291 | ``` 292 | 293 | #### cloneObject [[↑](#functions-)] 294 | 295 | ```typescript 296 | const CLONED_OBJ = vscode_helpers.cloneObject({ 297 | mk: 23979, 298 | tm: 5979, 299 | }); 300 | ``` 301 | 302 | #### cloneObjectFlat [[↑](#functions-)] 303 | 304 | ```typescript 305 | const CLONED_OBJ = vscode_helpers.cloneObjectFlat({ 306 | mk: 23979, 307 | tm: function(a) { 308 | return a * (5979 * this.mk); 309 | }, 310 | }); 311 | 312 | CLONED_OBJ.mk = 1000; 313 | CLONED_OBJ.tm(2000); // 11.958.000.000 === 2000 * (5979 * 1000) 314 | ``` 315 | 316 | #### compareValues [[↑](#functions-)] 317 | 318 | ```typescript 319 | const VAL_1 = 1; 320 | const VAL_2 = 2; 321 | 322 | // SORTED_VALUES[0] === VAL_2 323 | // SORTED_VALUES[1] === VAL_1 324 | const SORTED_VALUES = [ VAL_1, VAL_2 ].sort((x, y) => { 325 | return vscode_helpers.compareValues(y, x); 326 | }); 327 | ``` 328 | 329 | #### compareValuesBy [[↑](#functions-)] 330 | 331 | ```typescript 332 | const OBJ_1 = { sortValue: 1 }; 333 | const OBJ_2 = { sortValue: 2 }; 334 | 335 | // SORTED_OBJS[0] === OBJ_2 336 | // SORTED_OBJS[1] === OBJ_1 337 | const SORTED_OBJS = [ OBJ_1, OBJ_2 ].sort((x, y) => { 338 | return vscode_helpers.compareValuesBy(y, x, 339 | i => i.sortValue); 340 | }); 341 | ``` 342 | 343 | #### createChromeClient [[↑](#functions-)] 344 | 345 | ```typescript 346 | const CLIENT = vscode_helpers.createChromeClient({ 347 | host: 'localhost', 348 | port: 9222, 349 | }); 350 | 351 | const PAGES = await CLIENT.getPages(); 352 | for (const P of PAGES) { 353 | //TODO 354 | } 355 | ``` 356 | 357 | #### createCompletedAction [[↑](#functions-)] 358 | 359 | ```typescript 360 | import * as fs from 'fs'; 361 | 362 | function loadMyFileAsync() { 363 | return new Promise(async (resolve, reject) => { 364 | const COMPLETED = vscode_helpers.createCompletedAction(resolve, reject); 365 | 366 | fs.readFile('./MyFile.txt', (err: NodeJS.ErrnoException, data: Buffer) => { 367 | COMPLETED(err, data); 368 | }); 369 | }); 370 | } 371 | ``` 372 | 373 | #### createDevToolsClient [[↑](#functions-)] 374 | 375 | ```typescript 376 | const CLIENT = vscode_helpers.createDevToolsClient({ 377 | host: 'localhost', 378 | port: 9222, 379 | }); 380 | 381 | const PAGES = await CLIENT.getPages(); 382 | for (const P of PAGES) { 383 | //TODO 384 | } 385 | ``` 386 | 387 | #### createDirectoryIfNeeded [[↑](#functions-)] 388 | 389 | ```typescript 390 | vscode_helpers.createDirectoryIfNeeded('/dir/to/create').then((hasBeenCreated: boolean) => { 391 | // hasBeenCreated === (false), if directory already exists 392 | }, (err) => { 393 | // error 394 | }); 395 | ``` 396 | 397 | #### createGitClient [[↑](#functions-)] 398 | 399 | ```typescript 400 | try { 401 | const CLIENT = await vscode_helpers.createGitClient(); 402 | 403 | const STD_OUT: string = (await CLIENT.exec([ '--version' ])).stdOut; 404 | 405 | console.log( STD_OUT ); 406 | } catch (e) { 407 | // no git client found 408 | } 409 | ``` 410 | 411 | #### createGitClientSync [[↑](#functions-)] 412 | 413 | ```typescript 414 | try { 415 | const CLIENT = vscode_helpers.createGitClientSync(); 416 | 417 | console.log( 418 | CLIENT.execSync([ '--version' ]); 419 | ); 420 | } catch (e) { 421 | // no git client found 422 | } 423 | ``` 424 | 425 | #### createInterval [[↑](#functions-)] 426 | 427 | ```typescript 428 | const INTERVAL = vscode_helpers.createInterval(() => { 429 | //TODO 430 | }, 1000); 431 | 432 | INTERVAL.dispose(); // same as 'clearInterval' 433 | ``` 434 | 435 | #### createLogger [[↑](#functions-)] 436 | 437 | ```typescript 438 | import * as fs from 'fs'; 439 | 440 | const LOGGER = vscode_helpers.createLogger((log) => { 441 | fs.appendFileSync('./logFile.txt', log.message + "\r\n", 'utf8'); 442 | }); 443 | 444 | LOGGER.info('Hello, LOG!'); 445 | ``` 446 | 447 | #### createQueue [[↑](#functions-)] 448 | 449 | ```typescript 450 | /** 451 | * (Default) Options: 452 | * 453 | * { 454 | * autoStart: true, 455 | * concurrency: 1, 456 | * } 457 | */ 458 | const MY_QUEUE = vscode_helpers.createQueue(); 459 | 460 | vscode_helpers.range(0, 23979).forEach((x) => { 461 | 462 | MY_QUEUE.add(async () => { 463 | return await vscode_helpers.invokeAfter(() => { 464 | return x * 5979; 465 | }, 100)); 466 | }).then((result: number) => { 467 | // succeeded 468 | 469 | console.log( `MY_QUEUE result of '${ x }': ${ result }` ); 470 | }).catch((err) => { 471 | // error 472 | }); 473 | 474 | }); 475 | ``` 476 | 477 | #### createTimeout [[↑](#functions-)] 478 | 479 | ```typescript 480 | const TIMEOUT = vscode_helpers.createTimeout(() => { 481 | //TODO 482 | }, 10000); 483 | 484 | TIMEOUT.dispose(); // same as 'clearTimeout' 485 | ``` 486 | 487 | #### DELETE [[↑](#functions-)] 488 | 489 | ```typescript 490 | const RESULT = await vscode_helpers.DELETE('https://example.com/api/users/19861222'); 491 | ``` 492 | 493 | #### doesMatch [[↑](#functions-)] 494 | 495 | ```typescript 496 | vscode_helpers.doesMatch('my-file.txt', '*.txt'); // (true) 497 | vscode_helpers.doesMatch('my-picture.jpg', [ '*.txt' ]); // (false) 498 | vscode_helpers.doesMatch('my-picture.jpg', [ '*.txt', '*.jpg' ]); // (true) 499 | ``` 500 | 501 | #### execFile [[↑](#functions-)] 502 | 503 | ```typescript 504 | const RESULT = await vscode_helpers.execFile('/path/to/execiutable', [ '--version' ]); 505 | 506 | const STD_ERR = RESULT.stdErr; 507 | const STD_OUT = RESULT.stdOut; 508 | ``` 509 | 510 | #### exists [[↑](#functions-)] 511 | 512 | ```typescript 513 | vscode_helpers.exists('/path/of/thing/to/check', (doesExist: boolean) => { 514 | //TODO 515 | }, (err) => { 516 | // error 517 | }); 518 | ``` 519 | 520 | #### fastGlob [[↑](#functions-)] 521 | 522 | ```typescript 523 | const MATCHES = await vscode_helpers.fastGlob([ '**/*.txt' ], { 524 | cwd: '/path/to/directory', 525 | ignore: [ '/log/**/*' ], 526 | }); 527 | ``` 528 | 529 | #### fastGlobSync [[↑](#functions-)] 530 | 531 | ```typescript 532 | const MATCHES = vscode_helpers.fastGlobSync([ '**/*.txt' ], { 533 | cwd: '/path/to/directory', 534 | ignore: [ '/log/**/*' ], 535 | }); 536 | ``` 537 | 538 | #### filterExtensionNotifications [[↑](#functions-)] 539 | 540 | ```typescript 541 | const ALL_NOTIFICATIONS: vscode_helpers.ExtensionNotification[] = 542 | await vscode_helpers.getExtensionNotifications('https://mkloubert.github.io/notifications/vscode-deploy-reloaded.json'); 543 | 544 | const FILTERED_NOTIFICATION = vscode_helpers.filterExtensionNotifications( 545 | ALL_NOTIFICATIONS, { 546 | 'version': '1.0.0' // version of the current extension 547 | } 548 | ); 549 | 550 | for (const NOTE of FILTERED_NOTIFICATION) { 551 | console.log( 552 | NOTE.title 553 | ); 554 | } 555 | ``` 556 | 557 | #### forEachAsync [[↑](#functions-)] 558 | 559 | ```typescript 560 | vscode_helpers.forEachAsync([ 5979, 23979 ], async (item, index) => { 561 | // [index === 0] => item === 5979 562 | // [index === 1] => item === 23979 563 | 564 | return item * 1000; 565 | }).then((lastResult) => { 566 | // lastResult === 23979000 567 | }, (err) => { 568 | // error 569 | }); 570 | ``` 571 | 572 | #### format [[↑](#functions-)] 573 | 574 | ```typescript 575 | // "MK:23979 + TM: '5979'" 576 | let str_1 = vscode_helpers.format( 577 | 'MK:{1} + TM:{0:trim,surround,leading_space}', 578 | 5979, 579 | 23979 580 | ); 581 | ``` 582 | 583 | #### formatArray [[↑](#functions-)] 584 | 585 | ```typescript 586 | // "MK:23979 + TM: '5979'" 587 | let str_1 = vscode_helpers.formatArray( 588 | 'MK:{1} + TM:{0:trim,surround,leading_space}', 589 | [ 5979, 23979 ] 590 | ); 591 | ``` 592 | 593 | #### from [[↑](#functions-)] 594 | 595 | s. [node-enumerable](https://github.com/mkloubert/node-enumerable) 596 | 597 | ```typescript 598 | let seq = vscode_helpers.from([ 1, 2, 3 ]) // can also be a generator 599 | // or string 600 | .select(x => '' + x) 601 | .where(x => x !== '2') 602 | .reverse(); 603 | 604 | for (const ITEM of seq) { 605 | // [0] '3' 606 | // [1] '1' 607 | } 608 | ``` 609 | 610 | #### fromMarkdown [[↑](#functions-)] 611 | 612 | ```typescript 613 | let htmlFromMarkdown = vscode_helpers.fromMarkdown( 614 | 'Vessel | Captain\n-----------|-------------\nNCC-1701 | James T Kirk\nNCC-1701 A | James T Kirk\nNCC-1701 D | Picard' 615 | ); 616 | ``` 617 | 618 | #### GET [[↑](#functions-)] 619 | 620 | ```typescript 621 | const RESULT = await vscode_helpers.GET('https://example.com/api/users/5979'); 622 | 623 | const USER_DATA = JSON.parse( 624 | (await RESULT.readBody()).toString('utf8') 625 | ); 626 | ``` 627 | 628 | #### getExtensionNotifications [[↑](#functions-)] 629 | 630 | ```typescript 631 | const NOTIFICATIONS: vscode_helpers.ExtensionNotification[] = 632 | await vscode_helpers.getExtensionNotifications('https://mkloubert.github.io/notifications/vscode-deploy-reloaded.json'); 633 | 634 | for (const NOTE of NOTIFICATIONS) { 635 | console.log( 636 | NOTE.title 637 | ); 638 | } 639 | ``` 640 | 641 | #### getExtensionRoot [[↑](#functions-)] 642 | 643 | ```typescript 644 | console.log( 645 | vscode_helpers.getExtensionRoot() 646 | ); 647 | ``` 648 | 649 | #### getPackageFile [[↑](#functions-)] 650 | 651 | ```typescript 652 | const PACKAGE_JSON: vscode_helpers.PackageFile = 653 | await vscode_helpers.getPackageFile(); 654 | 655 | console.log( 656 | PACKAGE_JSON.name + ' ' + PACKAGE_JSON.version 657 | ); 658 | ``` 659 | 660 | #### getPackageFileSync [[↑](#functions-)] 661 | 662 | ```typescript 663 | const PACKAGE_JSON: vscode_helpers.PackageFile = 664 | vscode_helpers.getPackageFileSync(); 665 | 666 | console.log( 667 | PACKAGE_JSON.name + ' ' + PACKAGE_JSON.version 668 | ); 669 | ``` 670 | 671 | #### glob [[↑](#functions-)] 672 | 673 | ```typescript 674 | vscode_helpers.glob([ '**/*.txt' ], { 675 | cwd: '/path/to/directory', 676 | ignore: [ '/log/**/*' ], 677 | root: '/path/to/directory', 678 | }).then((matches: string[]) => { 679 | // 'matches' contains the found files 680 | }, (err) => { 681 | // error 682 | }); 683 | ``` 684 | 685 | #### globSync [[↑](#functions-)] 686 | 687 | ```typescript 688 | let matches: string[] = vscode_helpers.globSync([ '**/*.txt' ], { 689 | cwd: '/path/to/directory', 690 | ignore: [ '/log/**/*' ], 691 | root: '/path/to/directory', 692 | }); 693 | ``` 694 | 695 | #### guid [[↑](#functions-)] 696 | 697 | ```typescript 698 | let guid_v4_1 = vscode_helpers.guid(); 699 | let guid_v4_2 = vscode_helpers.guid('4'); 700 | let guid_v4_3 = vscode_helpers.guid('v4'); 701 | 702 | let guid_v5_1 = vscode_helpers.guid('5'); 703 | let guid_v5_2 = vscode_helpers.guid('v5'); 704 | 705 | let guid_v1_1 = vscode_helpers.guid('1'); 706 | let guid_v1_2 = vscode_helpers.guid('v1'); 707 | ``` 708 | 709 | #### invokeAfter [[↑](#functions-)] 710 | 711 | ```typescript 712 | vscode_helpers.invokeAfter(() => { 713 | // this is invoked after 5979 milliseconds 714 | return 23979; 715 | }, 5979).then((res) => { 716 | // res === 23979 717 | }, (err) => { 718 | // is invoked on error 719 | }); 720 | ``` 721 | #### isBinaryContent [[↑](#functions-)] 722 | 723 | ```typescript 724 | import * as fs from 'fs'; 725 | 726 | vscode_helpers.isBinaryContent( fs.readFileSync('./myPic.jpg') ).then((isBinary) => { 727 | // should be (true) 728 | }, (err) => { 729 | // error 730 | }); 731 | vscode_helpers.isBinaryContent( fs.readFileSync('./myText.txt') ).then((isBinary) => { 732 | // should be (false) 733 | }, (err) => { 734 | // error 735 | }); 736 | ``` 737 | 738 | #### isBinaryContentSync [[↑](#functions-)] 739 | 740 | ```typescript 741 | import * as fs from 'fs'; 742 | 743 | // should be (true) 744 | vscode_helpers.isBinaryContentSync( fs.readFileSync('./myPic.jpeg') ); 745 | // should be (false) 746 | vscode_helpers.isBinaryContentSync( fs.readFileSync('./myText.txt') ); 747 | ``` 748 | 749 | #### isBlockDevice [[↑](#functions-)] 750 | 751 | ```typescript 752 | vscode_helpers.isBlockDevice('/path/to/check').then((isABlockDevice) => { 753 | // TODO 754 | }, (err) => { 755 | // error 756 | }) 757 | ``` 758 | 759 | #### isBlockDeviceSync [[↑](#functions-)] 760 | 761 | ```typescript 762 | const IS_A_BLOCK_DEVICE: boolean = vscode_helpers.isBlockDeviceSync('/path/to/check'); 763 | ``` 764 | 765 | #### isCharacterDevice [[↑](#functions-)] 766 | 767 | ```typescript 768 | vscode_helpers.isCharacterDevice('/path/to/check').then((isACharacterDevice) => { 769 | // TODO 770 | }, (err) => { 771 | // error 772 | }) 773 | ``` 774 | 775 | #### isCharacterDeviceSync [[↑](#functions-)] 776 | 777 | ```typescript 778 | const IS_A_CHARACTER_DEVICE: boolean = vscode_helpers.isCharacterDeviceSync('/path/to/check'); 779 | ``` 780 | 781 | #### isDirectory [[↑](#functions-)] 782 | 783 | ```typescript 784 | vscode_helpers.isDirectory('/path/to/check').then((isADirectory) => { 785 | // TODO 786 | }, (err) => { 787 | // error 788 | }) 789 | ``` 790 | 791 | #### isDirectorySync [[↑](#functions-)] 792 | 793 | ```typescript 794 | const IS_A_DIRECTORY: boolean = vscode_helpers.isDirectorySync('/path/to/check'); 795 | ``` 796 | 797 | #### isEmptyString [[↑](#functions-)] 798 | 799 | ```typescript 800 | vscode_helpers.isEmptyString( null ); // (true) 801 | vscode_helpers.isEmptyString( undefined ); // (true) 802 | vscode_helpers.isEmptyString( '123' ); // (false) 803 | ``` 804 | 805 | #### isFIFO [[↑](#functions-)] 806 | 807 | ```typescript 808 | vscode_helpers.isFIFO('/path/to/check').then((isAFIFO) => { 809 | // TODO 810 | }, (err) => { 811 | // error 812 | }) 813 | ``` 814 | 815 | #### isFIFOSync [[↑](#functions-)] 816 | 817 | ```typescript 818 | const IS_A_FIFO: boolean = vscode_helpers.isFIFOSync('/path/to/check'); 819 | ``` 820 | 821 | #### isFile [[↑](#functions-)] 822 | 823 | ```typescript 824 | vscode_helpers.isFile('/path/to/check').then((isAFile) => { 825 | // TODO 826 | }, (err) => { 827 | // error 828 | }) 829 | ``` 830 | 831 | #### isFileSync [[↑](#functions-)] 832 | 833 | ```typescript 834 | const IS_A_FILE: boolean = vscode_helpers.isFileSync('/path/to/check'); 835 | ``` 836 | 837 | #### isSocket [[↑](#functions-)] 838 | 839 | ```typescript 840 | vscode_helpers.isSocket('/path/to/check').then((isASocket) => { 841 | // TODO 842 | }, (err) => { 843 | // error 844 | }) 845 | ``` 846 | 847 | #### isSocketSync [[↑](#functions-)] 848 | 849 | ```typescript 850 | const IS_A_SOCKET: boolean = vscode_helpers.isSocketSync('/path/to/check'); 851 | ``` 852 | 853 | #### isSymbolicLink [[↑](#functions-)] 854 | 855 | ```typescript 856 | vscode_helpers.isSymbolicLink('/path/to/check').then((isASymbolicLink) => { 857 | // TODO 858 | }, (err) => { 859 | // error 860 | }) 861 | ``` 862 | 863 | #### isSymbolicLinkSync [[↑](#functions-)] 864 | 865 | ```typescript 866 | const IS_A_SYMBOLIC_LINK: boolean = vscode_helpers.isSymbolicLinkSync('/path/to/check'); 867 | ``` 868 | 869 | #### loadModule [[↑](#functions-)] 870 | 871 | ```typescript 872 | interface MyModule { 873 | execute(): any; 874 | } 875 | 876 | let mod = vscode_helpers.loadModule('/path/to/module.js'); 877 | 878 | let modResult = mod.execute(); 879 | ``` 880 | 881 | ### makeNonDisposable [[↑](#functions-)] 882 | 883 | ```typescript 884 | const OBJ = { 885 | dispose: () => { 886 | console.log('Disposed!'); 887 | } 888 | }; 889 | 890 | const OBJ_1 = vscode_helpers.makeNonDisposable( OBJ, false ); 891 | OBJ_1.dispose(); // does nothing 892 | 893 | const OBJ_2 = vscode_helpers.makeNonDisposable( OBJ ); 894 | OBJ_2.dispose(); // throws an exception 895 | ``` 896 | 897 | #### normalizeString [[↑](#functions-)] 898 | 899 | ```typescript 900 | const str_1 = vscode_helpers.normalizeString('aBc'); // 'abc' 901 | const str_2 = vscode_helpers.normalizeString(null); // '' 902 | const str_3 = vscode_helpers.normalizeString('aBc', s => s.toUpperCase()); // 'ABC' 903 | ``` 904 | 905 | #### now [[↑](#functions-)] 906 | 907 | s. [Moment Timezone](https://momentjs.com/timezone/) for more information about using (optional) timezones. 908 | 909 | ```typescript 910 | const NOW = vscode_helpers.now('America/New_York') // optional 911 | .format('DD.MM.YYYY HH:mm:ss'); 912 | ``` 913 | 914 | #### openAndShowTextDocument [[↑](#functions-)] 915 | 916 | ```typescript 917 | // empty (plain text) 918 | const EDITOR_1 = await vscode_helpers.openAndShowTextDocument(); 919 | 920 | // from file 921 | const EDITOR_2 = await vscode_helpers.openAndShowTextDocument('/path/to/file'); 922 | 923 | // with initial content 924 | const EDITOR_3 = await vscode_helpers.openAndShowTextDocument({ 925 | language: 'typescript', 926 | content: `interface Pet { 927 | name: string; 928 | owner: string; 929 | }`, 930 | }); 931 | ``` 932 | 933 | #### PATCH [[↑](#functions-)] 934 | 935 | ```typescript 936 | const RESULT = await vscode_helpers.PATCH('https://example.com/api/users/23979', JSON.stringify({ 937 | displayName: 'Marcel Kloubert', 938 | }), { 939 | 'Content-Type': 'application/json; charset=utf8', 940 | }); 941 | ``` 942 | 943 | #### POST [[↑](#functions-)] 944 | 945 | ```typescript 946 | const RESULT = await vscode_helpers.POST('https://example.com/api/users/23979', JSON.stringify({ 947 | displayName: 'Marcel Kloubert', 948 | userName: 'mkloubert', 949 | country: 'Germany', 950 | }), { 951 | 'Content-Type': 'application/json; charset=utf8', 952 | }); 953 | ``` 954 | 955 | #### PUT [[↑](#functions-)] 956 | 957 | ```typescript 958 | const RESULT = await vscode_helpers.PUT('https://example.com/api/users/23979', JSON.stringify({ 959 | displayName: 'Marcel Kloubert', 960 | }), { 961 | 'Content-Type': 'application/json; charset=utf8', 962 | }); 963 | ``` 964 | 965 | #### randomBytes [[↑](#functions-)] 966 | 967 | ```typescript 968 | vscode_helpers.randomBytes(5979).then((bytes: Buffer) => { 969 | // 5979 random bytes are stored 970 | // in 'bytes' now 971 | }, (err) => { 972 | // error 973 | }); 974 | ``` 975 | 976 | #### range [[↑](#functions-)] 977 | 978 | s. [node-enumerable](https://github.com/mkloubert/node-enumerable) 979 | 980 | ```typescript 981 | vscode_helpers.range(1, 5).forEach((x) => { 982 | // x[0] === 1 983 | // x[1] === 2 984 | // x[2] === 3 985 | // x[3] === 4 986 | // x[4] === 5 987 | }); 988 | ``` 989 | 990 | #### readAll [[↑](#functions-)] 991 | 992 | ```typescript 993 | import * as fs from 'fs'; 994 | 995 | const STREAM = fs.createReadStream('./my-file.txt'); 996 | 997 | readAll( STREAM ).then((data: Buffer) => { 998 | // all data read 999 | }, (err) => { 1000 | // error 1001 | }); 1002 | ``` 1003 | 1004 | #### registerWorkspaceWatcher [[↑](#functions-)] 1005 | 1006 | ```typescript 1007 | import * as Path from 'path'; 1008 | import { ConfigurationChangeEvent, Uri } from 'vscode'; 1009 | 1010 | class MyWorkspace extends vscode_helpers.WorkspaceBase { 1011 | private _configSrc: vscode_helpers.WorkspaceConfigSource; 1012 | 1013 | // this is important for 'onDidChangeConfiguration' (s. below) 1014 | public get configSource() { 1015 | return this._configSrc; 1016 | } 1017 | 1018 | public async initialize() { 1019 | // initialize your workspace here 1020 | 1021 | this._configSrc = { 1022 | section: 'my.extension', 1023 | resource: Uri.file( Path.join(this.rootPath, 1024 | '.vscode/settings.json') ), 1025 | }; 1026 | } 1027 | 1028 | public async onDidChangeConfiguration(e: ConfigurationChangeEvent) { 1029 | // is invoked when workspace config changed 1030 | } 1031 | } 1032 | 1033 | vscode_helpers.registerWorkspaceWatcher(async (event, folder, workspace?) => { 1034 | if (event == vscode_helpers.WorkspaceWatcherEvent.Added) { 1035 | const NEW_WORKSPACE = new MyWorkspace( folder ); 1036 | 1037 | await NEW_WORKSPACE.initialize(); 1038 | 1039 | return NEW_WORKSPACE; 1040 | } 1041 | }); 1042 | ``` 1043 | 1044 | #### repeat [[↑](#functions-)] 1045 | 1046 | s. [node-enumerable](https://github.com/mkloubert/node-enumerable) 1047 | 1048 | ```typescript 1049 | // 5979 'TM' strings 1050 | vscode_helpers.repeat('TM', 5979).forEach((x) => { 1051 | //TODO 1052 | }); 1053 | ``` 1054 | 1055 | #### request [[↑](#functions-)] 1056 | 1057 | ```typescript 1058 | const RESULT = await vscode_helpers.request('POST', 'https://example.com/api/users/23979', JSON.stringify({ 1059 | displayName: 'Marcel Kloubert', 1060 | userName: 'mkloubert', 1061 | country: 'Germany', 1062 | }), { 1063 | 'Content-Type': 'application/json; charset=utf8', 1064 | }); 1065 | ``` 1066 | 1067 | #### setExtensionRoot [[↑](#functions-)] 1068 | 1069 | ```typescript 1070 | vscode_helpers.setExtensionRoot( 1071 | __dirname 1072 | ); 1073 | 1074 | console.log( 1075 | vscode_helpers.getExtensionRoot() 1076 | ); 1077 | ``` 1078 | 1079 | #### size [[↑](#functions-)] 1080 | 1081 | ```typescript 1082 | vscode_helpers.size('/path/to/a/file').then((fileSize: number) => { 1083 | // 'fileSize' stores the file size in bytes 1084 | }, (err) => { 1085 | // ERROR 1086 | }); 1087 | 1088 | // use 'stat()' function instead 1089 | // s. https://nodejs.org/api/fs.html#fs_fs_stat_path_callback 1090 | vscode_helpers.size('/path/to/a/file', false).then((fileSize: number) => { 1091 | }, (err) => { 1092 | }); 1093 | ``` 1094 | 1095 | #### sizeSync [[↑](#functions-)] 1096 | 1097 | ```typescript 1098 | const FILESIZE_1 = vscode_helpers.sizeSync('/path/to/a/file'); 1099 | 1100 | // use 'statSync()' function instead 1101 | // s. https://nodejs.org/api/fs.html#fs_fs_statsync_path 1102 | const FILESIZE_2 = vscode_helpers.sizeSync('/path/to/a/file', false); 1103 | ``` 1104 | 1105 | #### sleep [[↑](#functions-)] 1106 | 1107 | ```typescript 1108 | vscode_helpers.sleep(23979).then(() => { 1109 | // 23979 milliseconds gone 1110 | }, (err) => { 1111 | // is invoked on error 1112 | }); 1113 | ``` 1114 | 1115 | #### startWatch [[↑](#functions-)] 1116 | 1117 | ```typescript 1118 | const WATCH = vscode_helpers.startWatch(); 1119 | 1120 | vscode_helpers.sleep(1000).then(() => { 1121 | const MS = WATCH.stop(); // 'MS' should be a least 1000 1122 | }); 1123 | ``` 1124 | 1125 | #### toBooleanSafe [[↑](#functions-)] 1126 | 1127 | ```typescript 1128 | const bool_1 = vscode_helpers.toBooleanSafe( true ); // (true) 1129 | const bool_2 = vscode_helpers.toBooleanSafe( null ); // (false) 1130 | const bool_3 = vscode_helpers.toBooleanSafe( undefined, true ); // (true) 1131 | ``` 1132 | 1133 | #### tempFile [[↑](#functions-)] 1134 | 1135 | ```typescript 1136 | vscode_helpers.tempFile((pathToTempFile: string) => { 1137 | //TODO 1138 | 1139 | return 5979; 1140 | }).then((result) => { 1141 | // result === 5979 1142 | }, (err) => { 1143 | // ERROR! 1144 | }); 1145 | ``` 1146 | 1147 | #### tempFileSync [[↑](#functions-)] 1148 | 1149 | ```typescript 1150 | let result = vscode_helpers.tempFileSync((pathToTempFile: string) => { 1151 | //TODO 1152 | 1153 | return 23979; 1154 | }); 1155 | 1156 | // result === 23979 1157 | ``` 1158 | 1159 | #### toArray [[↑](#functions-)] 1160 | 1161 | ```typescript 1162 | let myGenerator = function* () { 1163 | yield 5979; 1164 | yield 23979; 1165 | }; 1166 | 1167 | let arr_1 = vscode_helpers.toArray( myGenerator() ); 1168 | let arr_2 = vscode_helpers.toArray( [ 19861222, 'PZSUX' ] ); // new array 1169 | ``` 1170 | 1171 | #### toEOL [[↑](#functions-)] 1172 | 1173 | ```typescript 1174 | import { EndOfLine } from 'vscode'; 1175 | 1176 | const eol_1 = vscode_helpers.toEOL(); // system's EOL 1177 | const eol_2 = vscode_helpers.toEOL( EndOfLine.CRLF ); // \r\n 1178 | ``` 1179 | 1180 | #### toStringSafe [[↑](#functions-)] 1181 | 1182 | ```typescript 1183 | const str_1 = vscode_helpers.toStringSafe( 123 ); // '123' 1184 | const str_2 = vscode_helpers.toStringSafe( null ); // '' 1185 | const str_3 = vscode_helpers.toStringSafe( undefined, 'abc' ); // 'abc' 1186 | ``` 1187 | 1188 | #### tryClearInterval [[↑](#functions-)] 1189 | 1190 | ```typescript 1191 | let timer = setInterval(() => { 1192 | // do something 1193 | }, 5979); 1194 | 1195 | vscode_helpers.tryClearInterval( timer ); 1196 | ``` 1197 | 1198 | #### tryClearTimeout [[↑](#functions-)] 1199 | 1200 | ```typescript 1201 | let timer = setTimeout(() => { 1202 | // do something 1203 | }, 23979); 1204 | 1205 | vscode_helpers.tryClearTimeout( timer ); 1206 | ``` 1207 | 1208 | #### tryCreateGitClient [[↑](#functions-)] 1209 | 1210 | ```typescript 1211 | const CLIENT = await vscode_helpers.tryCreateGitClient(); 1212 | 1213 | if (false !== CLIENT) { 1214 | const STD_OUT: string = (await CLIENT.exec([ '--version' ])).stdOut; 1215 | 1216 | console.log( STD_OUT ); 1217 | } else { 1218 | // no git client found 1219 | } 1220 | ``` 1221 | 1222 | #### tryCreateGitClientSync [[↑](#functions-)] 1223 | 1224 | ```typescript 1225 | const CLIENT = vscode_helpers.tryCreateGitClientSync(); 1226 | 1227 | if (false !== CLIENT) { 1228 | console.log( 1229 | CLIENT.execSync([ '--version' ]); 1230 | ); 1231 | } else { 1232 | // no git client found 1233 | } 1234 | ``` 1235 | 1236 | #### tryDispose [[↑](#functions-)] 1237 | 1238 | ```typescript 1239 | const OBJ = { 1240 | dispose: () => { 1241 | throw new Error( 'Could not dispose!' ); 1242 | } 1243 | }; 1244 | 1245 | // (false) 1246 | vscode_helpers.tryDispose( OBJ ); 1247 | ``` 1248 | 1249 | #### tryDisposeAndDelete [[↑](#functions-)] 1250 | 1251 | ```typescript 1252 | const OBJ = { 1253 | dispose: () => { 1254 | //TODO 1255 | } 1256 | }; 1257 | 1258 | const PARENT = { 'obj_key': OBJ }; 1259 | 1260 | vscode_helpers.tryDisposeAndDelete( PARENT, 'obj_key' ); 1261 | // 'PARENT' should not contain an object in 'obj_key' anymore 1262 | ``` 1263 | 1264 | #### tryRemoveAllListeners [[↑](#functions-)] 1265 | 1266 | ```typescript 1267 | import * as fs from 'fs'; 1268 | 1269 | const STREAM = fs.createReadStream('./my-file.txt'); 1270 | 1271 | STREAM.once('error', (err) => { 1272 | //TODO 1273 | 1274 | vscode_helpers.tryRemoveAllListeners(STREAM); 1275 | }); 1276 | 1277 | STREAM.once('end', () => { 1278 | vscode_helpers.tryRemoveAllListeners(STREAM); 1279 | }); 1280 | 1281 | STREAM.on('data', (chunk) => { 1282 | //TODO 1283 | }); 1284 | ``` 1285 | 1286 | #### tryRemoveListener [[↑](#functions-)] 1287 | 1288 | ```typescript 1289 | import * as fs from 'fs'; 1290 | 1291 | const STREAM = fs.createReadStream('./my-file.txt'); 1292 | 1293 | const DATA_LISTENER = (chunk) => { 1294 | //TODO 1295 | }; 1296 | 1297 | STREAM.on('data', DATA_LISTENER); 1298 | 1299 | STREAM.once('end', () => { 1300 | vscode_helpers.tryRemoveListener(STREAM, 1301 | 'data', DATA_LISTENER); 1302 | }); 1303 | ``` 1304 | 1305 | #### using [[↑](#functions-)] 1306 | 1307 | ```typescript 1308 | const MY_OBJECT = { 1309 | value: 5979, 1310 | 1311 | dispose: function() { 1312 | console.log("I have been disposed with value " + this.value); 1313 | } 1314 | }; 1315 | 1316 | vscode_helpers.using(MY_OBJECT, (obj) => { 1317 | return obj.value + 23979; 1318 | }).then((result) => { 1319 | // result === 29958 1320 | }, (err) => { 1321 | // on error 1322 | }); 1323 | ``` 1324 | 1325 | #### usingSync [[↑](#functions-)] 1326 | 1327 | ```typescript 1328 | const MY_OBJECT = { 1329 | value: 23979, 1330 | 1331 | dispose: function() { 1332 | console.log("I have been disposed with value " + this.value); 1333 | } 1334 | }; 1335 | 1336 | // RESULT === 29958 1337 | const RESULT = vscode_helpers.usingSync(MY_OBJECT, (obj) => { 1338 | return obj.value + 5979; 1339 | }); 1340 | ``` 1341 | 1342 | #### utcNow [[↑](#functions-)] 1343 | 1344 | ```typescript 1345 | const UTC_NOW = vscode_helpers.utcNow() 1346 | .format('DD.MM.YYYY HH:mm:ss'); 1347 | ``` 1348 | 1349 | #### uuid [[↑](#functions-)] 1350 | 1351 | ```typescript 1352 | let uuid_v4_1 = vscode_helpers.uuid(); 1353 | let uuid_v4_2 = vscode_helpers.uuid('4'); 1354 | let uuid_v4_3 = vscode_helpers.uuid('v4'); 1355 | 1356 | let uuid_v5_1 = vscode_helpers.uuid('5'); 1357 | let uuid_v5_2 = vscode_helpers.uuid('v5'); 1358 | 1359 | let uuid_v1_1 = vscode_helpers.uuid('1'); 1360 | let uuid_v1_2 = vscode_helpers.uuid('v1'); 1361 | ``` 1362 | 1363 | #### waitWhile [[↑](#functions-)] 1364 | 1365 | ```typescript 1366 | let counter = 5979; 1367 | 1368 | vscode_helpers.waitWhile(() => { 1369 | return --counter < 1; 1370 | }, { 1371 | timeUntilNextCheck: 100, 1372 | timeout: 60000, 1373 | }).then((isTimeout: boolean) => { 1374 | // counter === 0 1375 | }, (err) => { 1376 | // error occurred 1377 | }); 1378 | ``` 1379 | 1380 | #### withProgress [[↑](#functions-)] 1381 | 1382 | ```typescript 1383 | import { ProgressLocation } from 'vscode'; 1384 | 1385 | vscode_helpers.withProgress((context) => { 1386 | let res = 0; 1387 | 1388 | context.increment = 10; // increment by 10% after each update 1389 | 1390 | for (let i = 0; i < 10; i++) { 1391 | context.message = `Task ${i + 1} of 10 ...`; 1392 | 1393 | // do something 1394 | 1395 | ++res; 1396 | } 1397 | 1398 | return res; 1399 | }, { 1400 | location: ProgressLocation.Window, 1401 | title: 'My operation', 1402 | }).then((res) => { 1403 | // res === 10 1404 | }, (err) => { 1405 | // error 1406 | }); 1407 | ``` 1408 | 1409 | ### Classes [[↑](#examples-)] 1410 | 1411 | #### CacheProviderBase [[↑](#classes-)] 1412 | 1413 | ```typescript 1414 | class MyCache extends vscode_helpers.CacheProviderBase { 1415 | // implement abstract members here 1416 | } 1417 | ``` 1418 | 1419 | #### DisposableBase [[↑](#classes-)] 1420 | 1421 | ```typescript 1422 | class MyDisposable extends vscode_helpers.MyDisposable { 1423 | protected onDispose() { 1424 | // your custom logic 1425 | } 1426 | } 1427 | 1428 | vscode_helpers.tryDispose( new MyDisposable() ); 1429 | ``` 1430 | 1431 | #### MemoryCache [[↑](#classes-)] 1432 | 1433 | ```typescript 1434 | const CACHE = new vscode_helpers.MemoryCache(); 1435 | 1436 | CACHE.get('a', 23979); // 23979 1437 | CACHE.set('a', 5979); // 5979 1438 | CACHE.has('a'); // (true) 1439 | CACHE.unset('a'); 1440 | CACHE.has('a'); // (false) 1441 | ``` 1442 | 1443 | #### StopWatch [[↑](#classes-)] 1444 | 1445 | ```typescript 1446 | const WATCH = new vscode_helpers.StopWatch(); 1447 | WATCH.start(); 1448 | 1449 | vscode_helpers.sleep(1000).then(() => { 1450 | const MS = WATCH.stop(); // 'MS' should be a least 1000 1451 | }); 1452 | ``` 1453 | 1454 | #### WorkspaceBase [[↑](#classes-)] 1455 | 1456 | ```typescript 1457 | import { ConfigurationChangeEvent, Uri } from 'vscode'; 1458 | 1459 | class MyWorkspace extends vscode_helpers.WorkspaceBase { 1460 | private _configSrc: vscode_helpers.WorkspaceConfigSource; 1461 | 1462 | // this is important for 'onDidChangeConfiguration' (s. below) 1463 | public get configSource() { 1464 | return this._configSrc; 1465 | } 1466 | 1467 | public async initialize() { 1468 | // initialize your workspace here 1469 | 1470 | this._configSrc = { 1471 | section: 'my.extension', 1472 | resource: Uri.file( Path.join(this.rootPath, 1473 | '.vscode/settings.json') ), 1474 | }; 1475 | } 1476 | 1477 | public async onDidChangeConfiguration(e: ConfigurationChangeEvent) { 1478 | // is invoked when workspace config changed 1479 | } 1480 | } 1481 | ``` 1482 | 1483 | ### Constants and variables [[↑](#examples-)] 1484 | 1485 | #### EVENTS [[↑](#constants-and-variables-)] 1486 | 1487 | ```typescript 1488 | vscode_helpers.EVENTS.on('myEvent', (a, b) => { 1489 | console.log('myEvent: ' + (a + b)); 1490 | }); 1491 | 1492 | vscode_helpers.EVENTS 1493 | .emit('myEvent', 5979, 23979); 1494 | ``` 1495 | 1496 | #### IS_* [[↑](#constants-and-variables-)] 1497 | 1498 | ```typescript 1499 | vscode_helpers.IS_AIX; // AIX 1500 | vscode_helpers.IS_FREE_BSD; // Free BSD 1501 | vscode_helpers.IS_LINUX; // Linux 1502 | vscode_helpers.IS_MAC; // Mac OS 1503 | vscode_helpers.IS_OPEN_BSD; // Open BSD 1504 | vscode_helpers.IS_SUNOS; // Sun OS 1505 | vscode_helpers.IS_WINDOWS; // Windows 1506 | ``` 1507 | 1508 | #### QUEUE [[↑](#constants-and-variables-)] 1509 | 1510 | ```typescript 1511 | vscode_helpers.range(0, 5979).forEach((x) => { 1512 | 1513 | vscode_helpers.QUEUE.add(async () => { 1514 | return await vscode_helpers.invokeAfter(() => { 1515 | return x * 23979; 1516 | }, 100)); 1517 | }).then((result: number) => { 1518 | // succeeded 1519 | 1520 | console.log( `QUEUE result of '${ x }': ${ result }` ); 1521 | }).catch((err) => { 1522 | // error 1523 | }); 1524 | 1525 | }); 1526 | ``` 1527 | 1528 | #### SESSION [[↑](#constants-and-variables-)] 1529 | 1530 | ```typescript 1531 | let var_1 = vscode_helpers.SESSION['a']; // undefined (at the beginning) 1532 | 1533 | vscode_helpers.SESSION['a'] = 5979; 1534 | let var_2 = vscode_helpers.SESSION['a']; // 5979 1535 | 1536 | delete vscode_helpers.SESSION['a']; 1537 | let var_3 = vscode_helpers.SESSION['a']; // undefined 1538 | ``` 1539 | 1540 | ## Branches [[↑](#table-of-contents)] 1541 | 1542 | | Name | minimum Visual Studio Code version | 1543 | | ---- | --------- | 1544 | | [v10](https://github.com/mkloubert/vscode-helpers/tree/v10) (current) | `^1.62.0` | 1545 | | [v9](https://github.com/mkloubert/vscode-helpers/tree/v9) | `^1.62.0` | 1546 | | [v8](https://github.com/mkloubert/vscode-helpers/tree/v8) | `^1.50.0` | 1547 | | [v7](https://github.com/mkloubert/vscode-helpers/tree/v7) | `^1.42.0` | 1548 | | [v6](https://github.com/mkloubert/vscode-helpers/tree/v6) | `^1.38.0` | 1549 | | [v5](https://github.com/mkloubert/vscode-helpers/tree/v5) | `^1.36.0` | 1550 | | [v4](https://github.com/mkloubert/vscode-helpers/tree/v4) | `^1.30.0` | 1551 | | [v3](https://github.com/mkloubert/vscode-helpers/tree/v3) | `^1.30.0` | 1552 | | [v2](https://github.com/mkloubert/vscode-helpers/tree/v2) | `^1.23.0` | 1553 | | [v1](https://github.com/mkloubert/vscode-helpers/tree/v1) | `^1.22.0` | 1554 | | [beta](https://github.com/mkloubert/vscode-helpers/tree/beta) | `^1.20.0` | 1555 | 1556 | ## Support and contribute [[↑](#table-of-contents)] 1557 | 1558 | If you like the module, you can support the project by sending a [donation via PayPal](https://paypal.me/MarcelKloubert) to [me](https://github.com/mkloubert). 1559 | 1560 | To contribute, you can [open an issue](https://github.com/mkloubert/vscode-helpers/issues) and/or fork this repository. 1561 | 1562 | To work with the code: 1563 | 1564 | * clone [this repository](https://github.com/mkloubert/vscode-helpers) 1565 | * create and change to a new branch, like `git checkout -b my_new_feature` 1566 | * run `npm install` from your project folder 1567 | * open that project folder in Visual Studio Code 1568 | * now you can edit and debug there 1569 | * commit your changes to your new branch and sync it with your forked GitHub repo 1570 | * make a [pull request](https://github.com/mkloubert/vscode-helpers/pulls) 1571 | 1572 | ## Documentation [[↑](#table-of-contents)] 1573 | 1574 | The API documentation can be found [here](https://mkloubert.github.io/vscode-helpers/). 1575 | --------------------------------------------------------------------------------