├── .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 | [](https://www.npmjs.com/package/vscode-helpers)
2 | [](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 |
--------------------------------------------------------------------------------