diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index c088bb8b45..175e3d0eb0 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -107,6 +107,16 @@ steps: displayName: Run integration tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) +- script: | + set -e + cd test/smoke + yarn compile + cd - + yarn smoketest --web --headless + continueOnError: true + displayName: Run smoke tests + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | set -e pushd ../VSCode-darwin && zip -r -X -y ../VSCode-darwin.zip * && popd diff --git a/extensions/git/resources/icons/dark/check.svg b/extensions/git/resources/icons/dark/check.svg index 865cc83c34..2d16f39007 100644 --- a/extensions/git/resources/icons/dark/check.svg +++ b/extensions/git/resources/icons/dark/check.svg @@ -1,3 +1,3 @@ - + diff --git a/extensions/git/resources/icons/light/check.svg b/extensions/git/resources/icons/light/check.svg index e1a546660e..a9f8aa131b 100644 --- a/extensions/git/resources/icons/light/check.svg +++ b/extensions/git/resources/icons/light/check.svg @@ -1,3 +1,3 @@ - + diff --git a/extensions/markdown-language-features/src/features/previewContentProvider.ts b/extensions/markdown-language-features/src/features/previewContentProvider.ts index b13915a14f..23dc342cf1 100644 --- a/extensions/markdown-language-features/src/features/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/features/previewContentProvider.ts @@ -209,7 +209,7 @@ export class MarkdownContentProvider { return ``; case MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent: - return ''; + return ''; case MarkdownPreviewSecurityLevel.Strict: default: diff --git a/package.json b/package.json index 168a40f189..4c8595e3f9 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "gulp-shell": "^0.6.5", "gulp-tsb": "2.0.7", "gulp-tslint": "^8.1.3", - "gulp-uglify": "^3.0.2", + "gulp-uglify": "^3.0.0", "gulp-untar": "^0.0.7", "gulp-vinyl-zip": "^2.1.2", "http-server": "^0.11.1", @@ -155,6 +155,7 @@ "optimist": "0.3.5", "p-all": "^1.0.0", "pump": "^1.0.1", + "puppeteer": "^1.19.0", "queue": "3.0.6", "rcedit": "^1.1.0", "rimraf": "^2.2.8", @@ -168,7 +169,7 @@ "typemoq": "^0.3.2", "typescript": "3.5.2", "typescript-formatter": "7.1.0", - "uglify-es": "^3.3.9", + "uglify-es": "^3.0.18", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", "vsce": "1.48.0", diff --git a/src/sql/workbench/api/node/extHostRequireInterceptor.ts b/src/sql/workbench/api/common/extHostRequireInterceptor.ts similarity index 83% rename from src/sql/workbench/api/node/extHostRequireInterceptor.ts rename to src/sql/workbench/api/common/extHostRequireInterceptor.ts index 1ac8ad4ea0..998fd92b8f 100644 --- a/src/sql/workbench/api/node/extHostRequireInterceptor.ts +++ b/src/sql/workbench/api/common/extHostRequireInterceptor.ts @@ -7,10 +7,10 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { INodeModuleFactory } from 'vs/workbench/api/node/extHostRequireInterceptor'; import * as azdata from 'azdata'; import * as sqlops from 'sqlops'; import { IAzdataExtensionApiFactory, ISqlopsExtensionApiFactory } from 'sql/workbench/api/common/sqlExtHost.api.impl'; +import { INodeModuleFactory } from 'vs/workbench/api/common/extHostRequireInterceptor'; export class AzdataNodeModuleFactory implements INodeModuleFactory { public readonly nodeModuleName = 'azdata'; @@ -24,10 +24,10 @@ export class AzdataNodeModuleFactory implements INodeModuleFactory { ) { } - public load(request: string, parent: { filename: string; }): any { + public load(request: string, parent: URI): any { // get extension id from filename and api for extension - const ext = this._extensionPaths.findSubstr(URI.file(parent.filename).fsPath); + const ext = this._extensionPaths.findSubstr(parent.fsPath); if (ext) { let apiImpl = this._extApiImpl.get(ExtensionIdentifier.toKey(ext.identifier)); if (!apiImpl) { @@ -41,7 +41,7 @@ export class AzdataNodeModuleFactory implements INodeModuleFactory { if (!this._defaultApiImpl) { let extensionPathsPretty = ''; this._extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.identifier.value}\n`); - console.warn(`Could not identify extension for 'azdata' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`); + console.warn(`Could not identify extension for 'azdata' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); this._defaultApiImpl = this._apiFactory(nullExtensionDescription); } return this._defaultApiImpl; @@ -60,10 +60,10 @@ export class SqlopsNodeModuleFactory implements INodeModuleFactory { ) { } - public load(request: string, parent: { filename: string; }): any { + public load(request: string, parent: URI): any { // get extension id from filename and api for extension - const ext = this._extensionPaths.findSubstr(URI.file(parent.filename).fsPath); + const ext = this._extensionPaths.findSubstr(parent.fsPath); if (ext) { let apiImpl = this._extApiImpl.get(ExtensionIdentifier.toKey(ext.identifier)); if (!apiImpl) { @@ -77,7 +77,7 @@ export class SqlopsNodeModuleFactory implements INodeModuleFactory { if (!this._defaultApiImpl) { let extensionPathsPretty = ''; this._extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.identifier.value}\n`); - console.warn(`Could not identify extension for 'sqlops' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`); + console.warn(`Could not identify extension for 'sqlops' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); this._defaultApiImpl = this._apiFactory(nullExtensionDescription); } return this._defaultApiImpl; diff --git a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts index 32a1cbbaf8..2fec9b3d7c 100644 --- a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts @@ -34,6 +34,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ILogService } from 'vs/platform/log/common/log'; +import { IExtensionApiFactory as vsIApiFactory, createApiFactoryAndRegisterActors as vsApiFactory } from 'vs/workbench/api/common/extHost.api.impl'; export interface ISqlopsExtensionApiFactory { (extension: IExtensionDescription): typeof sqlops; @@ -43,11 +44,34 @@ export interface IAzdataExtensionApiFactory { (extension: IExtensionDescription): typeof azdata; } +export interface IExtensionApiFactory { + azdata: IAzdataExtensionApiFactory; + sqlops: ISqlopsExtensionApiFactory; + vscode: vsIApiFactory; +} + export interface IAdsExtensionApiFactory { azdata: IAzdataExtensionApiFactory; sqlops: ISqlopsExtensionApiFactory; } +/** + * This method instantiates and returns the extension API surface + */ +export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): IExtensionApiFactory { + const { azdata, sqlops } = createAdsApiFactory(accessor); + return { + azdata, + sqlops, + vscode: vsApiFactory(accessor) + }; +} + + +export interface IAdsExtensionApiFactory { + azdata: IAzdataExtensionApiFactory; + sqlops: ISqlopsExtensionApiFactory; +} /** * This method instantiates and returns the extension API surface diff --git a/src/sql/workbench/browser/modelComponents/webview.component.ts b/src/sql/workbench/browser/modelComponents/webview.component.ts index fc5dffa7da..0fb85202ae 100644 --- a/src/sql/workbench/browser/modelComponents/webview.component.ts +++ b/src/sql/workbench/browser/modelComponents/webview.component.ts @@ -67,9 +67,7 @@ export default class WebViewComponent extends ComponentBase implements IComponen private _createWebview(): void { this._webview = this.webviewService.createWebview(this.id, - { - allowSvgs: true - }, + {}, { allowScripts: true }); diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts index 99be7ab34c..16fab667b6 100644 --- a/src/sql/workbench/browser/parts/views/customView.ts +++ b/src/sql/workbench/browser/parts/views/customView.ts @@ -171,9 +171,11 @@ export class CustomTreeView extends Disposable implements ITreeView { private domNode: HTMLElement; private treeContainer: HTMLElement; private _messageValue: string | IMarkdownString | undefined; + private _canSelectMany: boolean = false; private messageElement: HTMLDivElement; private tree: WorkbenchAsyncDataTree; private treeLabels: ResourceLabels; + private root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; private menus: TitleMenus; @@ -273,6 +275,14 @@ export class CustomTreeView extends Disposable implements ITreeView { this.updateMessage(); } + get canSelectMany(): boolean { + return this._canSelectMany; + } + + set canSelectMany(canSelectMany: boolean) { + this._canSelectMany = canSelectMany; + } + get hasIconForParentNode(): boolean { return this._hasIconForParentNode; } @@ -391,12 +401,15 @@ export class CustomTreeView extends Disposable implements ITreeView { expandOnlyOnTwistieClick: (e: ITreeItem) => !!e.command, collapseByDefault: (e: ITreeItem): boolean => { return e.collapsibleState !== TreeItemCollapsibleState.Expanded; - } + }, + multipleSelectionSupport: this.canSelectMany, }) as WorkbenchAsyncDataTree); aligner.tree = this.tree; + const actionRunner = new MultipleSelectionActionRunner(() => this.tree!.getSelection()); + renderer.actionRunner = actionRunner; this.tree.contextKeyService.createKey(this.id, true); - this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner))); this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements))); this._register(this.tree.onDidChangeCollapseState(e => { const element: ITreeItem = Array.isArray(e.node.element.element) ? e.node.element.element[0] : e.node.element.element; @@ -425,7 +438,7 @@ export class CustomTreeView extends Disposable implements ITreeView { })); } - private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent): void { + private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent, actionRunner: MultipleSelectionActionRunner): void { const node: ITreeItem | null = treeEvent.element; if (node === null) { return; @@ -461,7 +474,7 @@ export class CustomTreeView extends Disposable implements ITreeView { getActionsContext: () => ({ $treeViewId: this.id, $treeItemHandle: node.handle, $treeItem: node, $treeContainerId: this.treeContainer.id }), - actionRunner: new MultipleSelectionActionRunner(() => this.tree.getSelection()) + actionRunner }); } @@ -724,6 +737,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }); + templateData.actionBar.context = { $treeViewId: this.treeViewId, $treeItemHandle: node.handle }; templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); + if (this._actionRunner) { + templateData.actionBar.actionRunner = this._actionRunner; + } this.setAlignment(templateData.container, node); templateData.elementDisposable = (this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node))); } @@ -860,23 +882,23 @@ class Aligner extends Disposable { class MultipleSelectionActionRunner extends ActionRunner { - constructor(private getSelectedResources: (() => any[])) { + constructor(private getSelectedResources: (() => ITreeItem[])) { super(); } - runAction(action: IAction, context: any): Promise { - if (action instanceof MenuItemAction) { - const selection = this.getSelectedResources(); - const filteredSelection = selection.filter(s => s !== context); - - if (selection.length === filteredSelection.length || selection.length === 1) { - return action.run(context); - } - - return action.run(context, ...filteredSelection); + runAction(action: IAction, context: TreeViewItemHandleArg): Promise { + const selection = this.getSelectedResources(); + let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; + if (selection.length > 1) { + selectionHandleArgs = []; + selection.forEach(selected => { + if (selected.handle !== context.$treeItemHandle) { + selectionHandleArgs!.push({ $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle }); + } + }); } - return super.runAction(action, context); + return action.run(...[context, selectionHandleArgs]); } } diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index 2d070f4a8c..446fd3d0c2 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -5,7 +5,7 @@ "strictNullChecks": true, "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true, - "skipLibCheck": true + "skipLibCheck": true, }, "include": [ "./typings", @@ -18,7 +18,7 @@ "./sql/base/**/*.ts", "./sql/editor/**/*.ts", "./sql/platform/angularEventing/**/*.ts", - "./sql/platform/accounts/**/*.ts", + // "./sql/platform/accounts/**/*.ts", "./sql/platform/clipboard/**/*.ts", "./sql/platform/credentials/**/*.ts", "./sql/platform/theme/**/*.ts", diff --git a/src/vs/base/browser/ui/menu/check.svg b/src/vs/base/browser/ui/menu/check.svg index 865cc83c34..cea818ef59 100644 --- a/src/vs/base/browser/ui/menu/check.svg +++ b/src/vs/base/browser/ui/menu/check.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 4f96e3b4f4..829da18773 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -293,12 +293,9 @@ function topStep(array: ReadonlyArray, compare: (a: T, b: T) => number, re } /** - * @returns a new array with all falsy values removed. The original array IS NOT modified. + * @returns New array with all falsy values removed. The original array IS NOT modified. */ export function coalesce(array: ReadonlyArray): T[] { - if (!array) { - return array as T[]; // {{SQL CARBON EDIT}} @anthonydresser strict-null-checks - } return array.filter(e => !!e); } @@ -306,9 +303,6 @@ export function coalesce(array: ReadonlyArray): T[] { * Remove all falsey values from `array`. The original array IS modified. */ export function coalesceInPlace(array: Array): void { - if (!array) { - return; - } let to = 0; for (let i = 0; i < array.length; i++) { if (!!array[i]) { diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index ca8b49a1f9..2743856b73 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -127,8 +127,7 @@ export class DisposableStore implements IDisposable { markTracked(t); if (this._isDisposed) { - console.warn(new Error('Registering disposable on object that has already been disposed of').stack); - t.dispose(); + console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack); } else { this._toDispose.add(t); } diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 5fc05b6a5b..3a599d62a5 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -49,3 +49,26 @@ export namespace Schemas { export const userData: string = 'vscode-userdata'; } + +class RemoteAuthoritiesImpl { + private readonly _hosts: { [authority: string]: string; }; + private readonly _ports: { [authority: string]: number; }; + private readonly _connectionTokens: { [authority: string]: string; }; + + constructor() { + this._hosts = Object.create(null); + this._ports = Object.create(null); + this._connectionTokens = Object.create(null); + } + + public set(authority: string, host: string, port: number): void { + this._hosts[authority] = host; + this._ports[authority] = port; + } + + public setConnectionToken(authority: string, connectionToken: string): void { + this._connectionTokens[authority] = connectionToken; + } +} + +export const RemoteAuthorities = new RemoteAuthoritiesImpl(); diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index 412f649106..b9e65fe79d 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isWindows, isMacintosh, setImmediate } from 'vs/base/common/platform'; +import { isWindows, isMacintosh, setImmediate, IProcessEnvironment } from 'vs/base/common/platform'; interface IProcess { platform: string; - env: object; + env: IProcessEnvironment; cwd(): string; nextTick(callback: (...args: any[]) => void): number; diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 5ec8df2f7c..eede6caa6e 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -8,8 +8,7 @@ import { IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; import * as platform from 'vs/base/common/platform'; - -declare var process: any; +import * as process from 'vs/base/common/process'; export interface ISocket extends IDisposable { onData(listener: (e: VSBuffer) => void): IDisposable; @@ -434,11 +433,7 @@ export function createBufferedEvent(source: Event): Event { // it is important to deliver these messages after this call, but before // other messages have a chance to be received (to guarantee in order delivery) // that's why we're using here nextTick and not other types of timeouts - if (typeof process !== 'undefined') { - process.nextTick(deliverMessages); - } else { - platform.setImmediate(deliverMessages); - } + process.nextTick(deliverMessages); }, onLastListenerRemove: () => { hasListeners = false; diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 209feebd4e..6edc1fc60d 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -86,7 +86,7 @@ export interface IIPCOptions { export class Client implements IChannelClient, IDisposable { - private disposeDelayer: Delayer; + private disposeDelayer: Delayer | undefined; private activeRequests = new Set(); private child: ChildProcess | null; private _client: IPCClient | null; @@ -137,7 +137,7 @@ export class Client implements IChannelClient, IDisposable { cancellationTokenListener.dispose(); this.activeRequests.delete(disposable); - if (this.activeRequests.size === 0) { + if (this.activeRequests.size === 0 && this.disposeDelayer) { this.disposeDelayer.trigger(() => this.disposeClient()); } }); @@ -271,8 +271,10 @@ export class Client implements IChannelClient, IDisposable { dispose() { this._onDidProcessExit.dispose(); - this.disposeDelayer.cancel(); - this.disposeDelayer = null!; // StrictNullOverride: nulling out ok in dispose + if (this.disposeDelayer) { + this.disposeDelayer.cancel(); + this.disposeDelayer = undefined; + } this.disposeClient(); this.activeRequests.clear(); } diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index 652f898f19..42a597d1a6 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -206,7 +206,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return this.doUpdateItems(recoveryConnection, { insert: recovery() }).then(() => closeRecoveryConnection(), error => { // In case of an error updating items, still ensure to close the connection - // to prevent SQLITE_BUSY errors when the connection is restablished + // to prevent SQLITE_BUSY errors when the connection is reestablished closeRecoveryConnection(); return Promise.reject(error); diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index b713c5912a..9f11e0ce21 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IDisposable, dispose, ReferenceCollection, Disposable as DisposableBase, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, ReferenceCollection } from 'vs/base/common/lifecycle'; class Disposable implements IDisposable { isDisposed = false; @@ -50,38 +50,6 @@ suite('Lifecycle', () => { }); }); -suite('DisposableBase', () => { - test('register should not leak if object has already been disposed', () => { - let aCount = 0; - let bCount = 0; - - const disposable = new class extends DisposableBase { - register(other: IDisposable) { - this._register(other); - } - }; - - disposable.register(toDisposable(() => ++aCount)); - - assert.strictEqual(aCount, 0); - assert.strictEqual(bCount, 0); - - disposable.dispose(); - assert.strictEqual(aCount, 1); - assert.strictEqual(bCount, 0); - - // Any newly added disposables should be disposed of immediately - disposable.register(toDisposable(() => ++bCount)); - assert.strictEqual(aCount, 1); - assert.strictEqual(bCount, 1); - - // Further dispose calls should have no effect - disposable.dispose(); - assert.strictEqual(aCount, 1); - assert.strictEqual(bCount, 1); - }); -}); - suite('Reference Collection', () => { class Collection extends ReferenceCollection { private _count = 0; @@ -118,4 +86,4 @@ suite('Reference Collection', () => { ref4.dispose(); assert.equal(collection.count, 0); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/common/path.test.ts b/src/vs/base/test/common/path.test.ts index 4007dfa3b7..aca6388008 100644 --- a/src/vs/base/test/common/path.test.ts +++ b/src/vs/base/test/common/path.test.ts @@ -30,6 +30,7 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; +import * as process from 'vs/base/common/process'; suite('Paths (Node Implementation)', () => { test('join', () => { diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 44dfc3e8e2..b7a502c738 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -31,7 +31,7 @@ import { WindowsService } from 'vs/platform/windows/electron-browser/windowsServ import { MainProcessService, IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel'; -import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; +import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; @@ -39,7 +39,7 @@ import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; import { Button } from 'vs/base/browser/ui/button/button'; import { withUndefinedAsNull } from 'vs/base/common/types'; -import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; const MAX_URL_LENGTH = 2045; diff --git a/src/vs/code/electron-browser/issue/issueReporterModel.ts b/src/vs/code/electron-browser/issue/issueReporterModel.ts index 82be5aa542..05d964e148 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-browser/issue/issueReporterModel.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { assign } from 'vs/base/common/objects'; -import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; -import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; +import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; export interface IssueReporterData { issueType: IssueType; diff --git a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts index ba315e9583..1bc1a0d9cb 100644 --- a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; -import { IssueType } from 'vs/platform/issue/common/issue'; +import { IssueType } from 'vs/platform/issue/node/issue'; suite('IssueReporter', () => { diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts index 121b3c9af6..a2ac7f6875 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts @@ -9,7 +9,7 @@ import { repeat } from 'vs/base/common/strings'; import { totalmem } from 'os'; import product from 'vs/platform/product/node/product'; import { localize } from 'vs/nls'; -import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/node/issue'; import * as browser from 'vs/base/browser/browser'; import * as platform from 'vs/base/common/platform'; import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; @@ -17,7 +17,7 @@ import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu'; import { ProcessItem } from 'vs/base/common/processes'; import { addDisposableListener } from 'vs/base/browser/dom'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; let mapPidToWindowTitle = new Map(); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 52f7107704..47aba50532 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -48,15 +48,13 @@ import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/ import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; -import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; -import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { DiagnosticsChannel } from 'vs/platform/diagnostics/node/diagnosticsIpc'; import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/product'; -import { ProductService } from 'vs/platform/product/node/productService'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -112,10 +110,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat await configurationService.initialize(); services.set(IEnvironmentService, environmentService); + services.set(IProductService, { _serviceBrand: undefined, ...product }); services.set(ILogService, logService); services.set(IConfigurationService, configurationService); services.set(IRequestService, new SyncDescriptor(RequestService)); - services.set(IProductService, new SyncDescriptor(ProductService)); const mainProcessService = new MainProcessService(server, mainRouter); services.set(IMainProcessService, mainProcessService); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index b9cac66a00..1fef401f99 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -47,7 +47,7 @@ import { getMachineId } from 'vs/base/node/id'; import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32'; import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux'; import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin'; -import { IIssueService } from 'vs/platform/issue/common/issue'; +import { IIssueService } from 'vs/platform/issue/node/issue'; import { IssueChannel } from 'vs/platform/issue/node/issueIpc'; import { IssueService } from 'vs/platform/issue/electron-main/issueService'; import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; @@ -81,8 +81,8 @@ import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { VSBuffer } from 'vs/base/common/buffer'; import { statSync } from 'fs'; import { ISignService } from 'vs/platform/sign/common/sign'; -import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnosticsService'; import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc'; +import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 6ddf0125e8..01ed93aa52 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -47,7 +47,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IProductService } from 'vs/platform/product/common/product'; -import { ProductService } from 'vs/platform/product/node/productService'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); @@ -325,7 +324,7 @@ export async function main(argv: ParsedArgs): Promise { services.set(ILogService, logService); services.set(IConfigurationService, configurationService); services.set(IStateService, new SyncDescriptor(StateService)); - services.set(IProductService, new SyncDescriptor(ProductService)); + services.set(IProductService, { _serviceBrand: undefined, ...product }); // Files const fileService = new FileService(logService); diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 7439ec0622..c099b8be2f 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -13,7 +13,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; @@ -612,7 +612,7 @@ export interface ICodeEditor extends editorCommon.IEditor { * @param edits The edits to execute. * @param endCursorState Cursor state after the edits were applied. */ - executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: Selection[]): boolean; + executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean; /** * Execute multiple (concomitant) commands on the editor. diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 4cc1e4f055..4cc249a04b 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -33,7 +33,7 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { InternalEditorAction } from 'vs/editor/common/editorAction'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model'; import { ClassName } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; @@ -980,7 +980,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return true; } - public executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: Selection[]): boolean { + public executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean { if (!this._modelData) { return false; } @@ -989,14 +989,16 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return false; } - this._modelData.model.pushEditOperations(this._modelData.cursor.getSelections(), edits, () => { - return endCursorState ? endCursorState : null; - }); - - if (endCursorState) { - this._modelData.cursor.setSelections(source, endCursorState); + let cursorStateComputer: ICursorStateComputer; + if (!endCursorState) { + cursorStateComputer = () => null; + } else if (Array.isArray(endCursorState)) { + cursorStateComputer = () => endCursorState; + } else { + cursorStateComputer = endCursorState; } + this._modelData.cursor.executeEdits(source, edits, cursorStateComputer); return true; } diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 9c38bac730..c3fb85785c 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -15,7 +15,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer } from 'vs/editor/common/model'; import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; @@ -429,6 +429,31 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { // ------ auxiliary handling logic + private _pushAutoClosedAction(autoClosedCharactersRanges: Range[], autoClosedEnclosingRanges: Range[]): void { + let autoClosedCharactersDeltaDecorations: IModelDeltaDecoration[] = []; + let autoClosedEnclosingDeltaDecorations: IModelDeltaDecoration[] = []; + + for (let i = 0, len = autoClosedCharactersRanges.length; i < len; i++) { + autoClosedCharactersDeltaDecorations.push({ + range: autoClosedCharactersRanges[i], + options: { + inlineClassName: 'auto-closed-character', + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges + } + }); + autoClosedEnclosingDeltaDecorations.push({ + range: autoClosedEnclosingRanges[i], + options: { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges + } + }); + } + + const autoClosedCharactersDecorations = this._model.deltaDecorations([], autoClosedCharactersDeltaDecorations); + const autoClosedEnclosingDecorations = this._model.deltaDecorations([], autoClosedEnclosingDeltaDecorations); + this._autoClosedActions.push(new AutoClosedAction(this._model, autoClosedCharactersDecorations, autoClosedEnclosingDecorations)); + } + private _executeEditOperation(opResult: EditOperationResult | null): void { if (!opResult) { @@ -446,32 +471,19 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._interpretCommandResult(result); // Check for auto-closing closed characters - let autoClosedCharactersRanges: IModelDeltaDecoration[] = []; - let autoClosedEnclosingRanges: IModelDeltaDecoration[] = []; + let autoClosedCharactersRanges: Range[] = []; + let autoClosedEnclosingRanges: Range[] = []; for (let i = 0; i < opResult.commands.length; i++) { const command = opResult.commands[i]; if (command instanceof TypeWithAutoClosingCommand && command.enclosingRange && command.closeCharacterRange) { - autoClosedCharactersRanges.push({ - range: command.closeCharacterRange, - options: { - inlineClassName: 'auto-closed-character', - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges - } - }); - autoClosedEnclosingRanges.push({ - range: command.enclosingRange, - options: { - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges - } - }); + autoClosedCharactersRanges.push(command.closeCharacterRange); + autoClosedEnclosingRanges.push(command.enclosingRange); } } if (autoClosedCharactersRanges.length > 0) { - const autoClosedCharactersDecorations = this._model.deltaDecorations([], autoClosedCharactersRanges); - const autoClosedEnclosingDecorations = this._model.deltaDecorations([], autoClosedEnclosingRanges); - this._autoClosedActions.push(new AutoClosedAction(this._model, autoClosedCharactersDecorations, autoClosedEnclosingDecorations)); + this._pushAutoClosedAction(autoClosedCharactersRanges, autoClosedEnclosingRanges); } this._prevEditOperationType = opResult.type; @@ -563,6 +575,75 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { // ----------------------------------------------------------------------------------------------------------- // ----- handlers beyond this point + private _findAutoClosingPairs(edits: IIdentifiedSingleEditOperation[]): [number, number][] | null { + if (!edits.length) { + return null; + } + + let indices: [number, number][] = []; + for (let i = 0, len = edits.length; i < len; i++) { + const edit = edits[i]; + if (!edit.text || edit.text.indexOf('\n') >= 0) { + return null; + } + + const m = edit.text.match(/([)\]}>'"`])([^)\]}>'"`]*)$/); + if (!m) { + return null; + } + const closeChar = m[1]; + + const openChar = this.context.config.autoClosingPairsClose[closeChar]; + if (!openChar) { + return null; + } + + const closeCharIndex = edit.text.length - m[2].length - 1; + const openCharIndex = edit.text.lastIndexOf(openChar, closeCharIndex - 1); + if (openCharIndex === -1) { + return null; + } + + indices.push([openCharIndex, closeCharIndex]); + } + + return indices; + } + + public executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): void { + let autoClosingIndices: [number, number][] | null = null; + if (source === 'snippet') { + autoClosingIndices = this._findAutoClosingPairs(edits); + } + + if (autoClosingIndices) { + edits[0]._isTracked = true; + } + let autoClosedCharactersRanges: Range[] = []; + let autoClosedEnclosingRanges: Range[] = []; + const selections = this._model.pushEditOperations(this.getSelections(), edits, (undoEdits) => { + if (autoClosingIndices) { + for (let i = 0, len = autoClosingIndices.length; i < len; i++) { + const [openCharInnerIndex, closeCharInnerIndex] = autoClosingIndices[i]; + const undoEdit = undoEdits[i]; + const lineNumber = undoEdit.range.startLineNumber; + const openCharIndex = undoEdit.range.startColumn - 1 + openCharInnerIndex; + const closeCharIndex = undoEdit.range.startColumn - 1 + closeCharInnerIndex; + + autoClosedCharactersRanges.push(new Range(lineNumber, closeCharIndex + 1, lineNumber, closeCharIndex + 2)); + autoClosedEnclosingRanges.push(new Range(lineNumber, openCharIndex + 1, lineNumber, closeCharIndex + 2)); + } + } + return cursorStateComputer(undoEdits); + }); + if (selections) { + this.setSelections(source, selections); + } + if (autoClosedCharactersRanges.length > 0) { + this._pushAutoClosedAction(autoClosedCharactersRanges, autoClosedEnclosingRanges); + } + } + public trigger(source: string, handlerId: string, payload: any): void { const H = editorCommon.Handler; diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index a7c7419c41..473ba745a5 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -489,21 +489,18 @@ export class SnippetSession { return; } - const model = this._editor.getModel(); - // make insert edit and start with first selections const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace, this._options.clipboardText); this._snippets = snippets; - const selections = model.pushEditOperations(this._editor.getSelections(), edits, undoEdits => { + this._editor.executeEdits('snippet', edits, undoEdits => { if (this._snippets[0].hasPlaceholder) { return this._move(true); } else { return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition())); } - })!; - this._editor.setSelections(selections); - this._editor.revealRange(selections[0]); + }); + this._editor.revealRange(this._editor.getSelections()[0]); } merge(template: string, options: ISnippetSessionInsertOptions = _defaultOptions): void { @@ -513,8 +510,7 @@ export class SnippetSession { this._templateMerges.push([this._snippets[0]._nestingLevel, this._snippets[0]._placeholderGroupsIdx, template]); const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace, options.clipboardText); - this._editor.setSelections(this._editor.getModel().pushEditOperations(this._editor.getSelections(), edits, undoEdits => { - + this._editor.executeEdits('snippet', edits, undoEdits => { for (const snippet of this._snippets) { snippet.merge(snippets); } @@ -525,7 +521,7 @@ export class SnippetSession { } else { return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition())); } - })!); + }); } next(): void { diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 697edbffec..c4eedca3fc 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -4692,6 +4692,28 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #78975 - Parentheses swallowing does not work when parentheses are inserted by autocomplete', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + '
{ + cursor.setSelections('test', [new Selection(1, 8, 1, 8)]); + + cursor.executeEdits('snippet', [{ range: new Range(1, 6, 1, 8), text: 'id=""' }], () => [new Selection(1, 10, 1, 10)]); + assert.strictEqual(model.getLineContent(1), '
{ let mode = new AutoClosingMode(); usingCursor({ diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index f32c6bc9d8..e1a0dbd6d6 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4058,7 +4058,7 @@ declare namespace monaco.editor { * @param edits The edits to execute. * @param endCursorState Cursor state after the edits were applied. */ - executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: Selection[]): boolean; + executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean; /** * Execute multiple (concomitant) commands on the editor. * @param source The source of the call. diff --git a/src/vs/platform/clipboard/browser/clipboardService.ts b/src/vs/platform/clipboard/browser/clipboardService.ts index ecb5a9279d..b077d4531b 100644 --- a/src/vs/platform/clipboard/browser/clipboardService.ts +++ b/src/vs/platform/clipboard/browser/clipboardService.ts @@ -15,10 +15,18 @@ export class BrowserClipboardService implements IClipboardService { private _internalResourcesClipboard: URI[] | undefined; async writeText(text: string, type?: string): Promise { + if (type) { + return; // TODO@sbatten + } + return navigator.clipboard.writeText(text); } async readText(type?: string): Promise { + if (type) { + return ''; // TODO@sbatten + } + return navigator.clipboard.readText(); } diff --git a/src/vs/platform/diagnostics/common/diagnosticsService.ts b/src/vs/platform/diagnostics/common/diagnostics.ts similarity index 69% rename from src/vs/platform/diagnostics/common/diagnosticsService.ts rename to src/vs/platform/diagnostics/common/diagnostics.ts index 678c7e7a09..3eb4e55f34 100644 --- a/src/vs/platform/diagnostics/common/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/common/diagnostics.ts @@ -5,8 +5,6 @@ import { UriComponents } from 'vs/base/common/uri'; import { ProcessItem } from 'vs/base/common/processes'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; import { IWorkspace } from 'vs/platform/workspace/common/workspace'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -67,18 +65,6 @@ export interface IWorkspaceInformation extends IWorkspace { telemetryId: string | undefined; } -export const ID = 'diagnosticsService'; -export const IDiagnosticsService = createDecorator(ID); - -export interface IDiagnosticsService { - _serviceBrand: any; - - getPerformanceInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; - getSystemInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; - getDiagnostics(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; - reportWorkspaceStats(workspace: IWorkspaceInformation): Promise; -} - export function isRemoteDiagnosticError(x: any): x is IRemoteDiagnosticError { return !!x.hostName && !!x.errorMessage; } diff --git a/src/vs/platform/diagnostics/node/diagnosticsIpc.ts b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts index f5699bab33..cdb3e6a7e8 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsIpc.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IDiagnosticsService, IRemoteDiagnosticInfo, IRemoteDiagnosticError, SystemInfo, PerformanceInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IRemoteDiagnosticInfo, IRemoteDiagnosticError, SystemInfo, PerformanceInfo } from 'vs/platform/diagnostics/common/diagnostics'; +import { IDiagnosticsService } from './diagnosticsService'; import { Event } from 'vs/base/common/event'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index b3a90f9eaf..7aa52a1fc5 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as osLib from 'os'; import { virtualMachineHint } from 'vs/base/node/id'; -import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, IDiagnosticsService, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; import { readdir, stat, exists, readFile } from 'fs'; import { join, basename } from 'vs/base/common/path'; import { parse, ParseError } from 'vs/base/common/json'; @@ -17,6 +17,19 @@ import { URI } from 'vs/base/common/uri'; import { ProcessItem } from 'vs/base/common/processes'; import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ID = 'diagnosticsService'; +export const IDiagnosticsService = createDecorator(ID); + +export interface IDiagnosticsService { + _serviceBrand: any; + + getPerformanceInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; + getSystemInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; + getDiagnostics(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; + reportWorkspaceStats(workspace: IWorkspaceInformation): Promise; +} export interface VersionInfo { vscodeVersion: string; diff --git a/src/vs/platform/driver/browser/baseDriver.ts b/src/vs/platform/driver/browser/baseDriver.ts new file mode 100644 index 0000000000..0f4561468d --- /dev/null +++ b/src/vs/platform/driver/browser/baseDriver.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; +import { coalesce } from 'vs/base/common/arrays'; +import { IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; + +function serializeElement(element: Element, recursive: boolean): IElement { + const attributes = Object.create(null); + + for (let j = 0; j < element.attributes.length; j++) { + const attr = element.attributes.item(j); + if (attr) { + attributes[attr.name] = attr.value; + } + } + + const children: IElement[] = []; + + if (recursive) { + for (let i = 0; i < element.children.length; i++) { + const child = element.children.item(i); + if (child) { + children.push(serializeElement(child, true)); + } + } + } + + const { left, top } = getTopLeftOffset(element as HTMLElement); + + return { + tagName: element.tagName, + className: element.className, + textContent: element.textContent || '', + attributes, + children, + left, + top + }; +} + +export abstract class BaseWindowDriver implements IWindowDriver { + + constructor() { } + + abstract click(selector: string, xoffset?: number, yoffset?: number): Promise; + abstract doubleClick(selector: string): Promise; + + async setValue(selector: string, text: string): Promise { + const element = document.querySelector(selector); + + if (!element) { + return Promise.reject(new Error(`Element not found: ${selector}`)); + } + + const inputElement = element as HTMLInputElement; + inputElement.value = text; + + const event = new Event('input', { bubbles: true, cancelable: true }); + inputElement.dispatchEvent(event); + } + + async getTitle(): Promise { + return document.title; + } + + async isActiveElement(selector: string): Promise { + const element = document.querySelector(selector); + + if (element !== document.activeElement) { + const chain: string[] = []; + let el = document.activeElement; + + while (el) { + const tagName = el.tagName; + const id = el.id ? `#${el.id}` : ''; + const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join(''); + chain.unshift(`${tagName}${id}${classes}`); + + el = el.parentElement; + } + + throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`); + } + + return true; + } + + async getElements(selector: string, recursive: boolean): Promise { + const query = document.querySelectorAll(selector); + const result: IElement[] = []; + + for (let i = 0; i < query.length; i++) { + const element = query.item(i); + result.push(serializeElement(element, recursive)); + } + + return result; + } + + async getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }> { + const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined; + return this._getElementXY(selector, offset); + } + + async typeInEditor(selector: string, text: string): Promise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error(`Editor not found: ${selector}`); + } + + const textarea = element as HTMLTextAreaElement; + const start = textarea.selectionStart; + const newStart = start + text.length; + const value = textarea.value; + const newValue = value.substr(0, start) + text + value.substr(start); + + textarea.value = newValue; + textarea.setSelectionRange(newStart, newStart); + + const event = new Event('input', { 'bubbles': true, 'cancelable': true }); + textarea.dispatchEvent(event); + } + + async getTerminalBuffer(selector: string): Promise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error(`Terminal not found: ${selector}`); + } + + const xterm = (element as any).xterm; + + if (!xterm) { + throw new Error(`Xterm not found: ${selector}`); + } + + const lines: string[] = []; + + for (let i = 0; i < xterm.buffer.length; i++) { + lines.push(xterm.buffer.getLine(i)!.translateToString(true)); + } + + return lines; + } + + async writeInTerminal(selector: string, text: string): Promise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error(`Element not found: ${selector}`); + } + + const xterm = (element as any).xterm; + + if (!xterm) { + throw new Error(`Xterm not found: ${selector}`); + } + + xterm._core._coreService.triggerDataEvent(text); + } + + protected async _getElementXY(selector: string, offset?: { x: number, y: number }): Promise<{ x: number; y: number; }> { + const element = document.querySelector(selector); + + if (!element) { + return Promise.reject(new Error(`Element not found: ${selector}`)); + } + + const { left, top } = getTopLeftOffset(element as HTMLElement); + const { width, height } = getClientArea(element as HTMLElement); + let x: number, y: number; + + if (offset) { + x = left + offset.x; + y = top + offset.y; + } else { + x = left + (width / 2); + y = top + (height / 2); + } + + x = Math.round(x); + y = Math.round(y); + + return { x, y }; + } + + abstract async openDevTools(): Promise; +} diff --git a/src/vs/platform/driver/browser/driver.ts b/src/vs/platform/driver/browser/driver.ts new file mode 100644 index 0000000000..613655393e --- /dev/null +++ b/src/vs/platform/driver/browser/driver.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver'; + +class BrowserWindowDriver extends BaseWindowDriver { + click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise { + throw new Error('Method not implemented.'); + } + doubleClick(selector: string): Promise { + throw new Error('Method not implemented.'); + } + openDevTools(): Promise { + throw new Error('Method not implemented.'); + } +} + +export async function registerWindowDriver(): Promise { + (window).driver = new BrowserWindowDriver(); + + return toDisposable(() => { + return { dispose: () => { } }; + }); +} diff --git a/src/vs/platform/driver/common/driver.ts b/src/vs/platform/driver/common/driver.ts new file mode 100644 index 0000000000..694e5bf37d --- /dev/null +++ b/src/vs/platform/driver/common/driver.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +// !! Do not remove the following START and END markers, they are parsed by the smoketest build + +//*START +export interface IElement { + tagName: string; + className: string; + textContent: string; + attributes: { [name: string]: string; }; + children: IElement[]; + top: number; + left: number; +} + +export interface IDriver { + _serviceBrand: any; + + getWindowIds(): Promise; + capturePage(windowId: number): Promise; + reloadWindow(windowId: number): Promise; + exitApplication(): Promise; + dispatchKeybinding(windowId: number, keybinding: string): Promise; + click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; + doubleClick(windowId: number, selector: string): Promise; + setValue(windowId: number, selector: string, text: string): Promise; + getTitle(windowId: number): Promise; + isActiveElement(windowId: number, selector: string): Promise; + getElements(windowId: number, selector: string, recursive?: boolean): Promise; + getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }>; + typeInEditor(windowId: number, selector: string, text: string): Promise; + getTerminalBuffer(windowId: number, selector: string): Promise; + writeInTerminal(windowId: number, selector: string, text: string): Promise; +} +//*END + +export const ID = 'driverService'; +export const IDriver = createDecorator(ID); + +export interface IWindowDriver { + click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; + doubleClick(selector: string): Promise; + setValue(selector: string, text: string): Promise; + getTitle(): Promise; + isActiveElement(selector: string): Promise; + getElements(selector: string, recursive: boolean): Promise; + getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }>; + typeInEditor(selector: string, text: string): Promise; + getTerminalBuffer(selector: string): Promise; + writeInTerminal(selector: string, text: string): Promise; +} diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 920a1d19c8..262692fef9 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -4,55 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver'; +import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; import * as electron from 'electron'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { Terminal } from 'xterm'; import { timeout } from 'vs/base/common/async'; -import { coalesce } from 'vs/base/common/arrays'; +import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver'; -function serializeElement(element: Element, recursive: boolean): IElement { - const attributes = Object.create(null); - - for (let j = 0; j < element.attributes.length; j++) { - const attr = element.attributes.item(j); - if (attr) { - attributes[attr.name] = attr.value; - } - } - - const children: IElement[] = []; - - if (recursive) { - for (let i = 0; i < element.children.length; i++) { - const child = element.children.item(i); - if (child) { - children.push(serializeElement(child, true)); - } - } - } - - const { left, top } = getTopLeftOffset(element as HTMLElement); - - return { - tagName: element.tagName, - className: element.className, - textContent: element.textContent || '', - attributes, - children, - left, - top - }; -} - -class WindowDriver implements IWindowDriver { +class WindowDriver extends BaseWindowDriver { constructor( @IWindowService private readonly windowService: IWindowService - ) { } + ) { + super(); + } click(selector: string, xoffset?: number, yoffset?: number): Promise { const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined; @@ -63,31 +29,6 @@ class WindowDriver implements IWindowDriver { return this._click(selector, 2); } - private async _getElementXY(selector: string, offset?: { x: number, y: number }): Promise<{ x: number; y: number; }> { - const element = document.querySelector(selector); - - if (!element) { - return Promise.reject(new Error(`Element not found: ${selector}`)); - } - - const { left, top } = getTopLeftOffset(element as HTMLElement); - const { width, height } = getClientArea(element as HTMLElement); - let x: number, y: number; - - if (offset) { - x = left + offset.x; - y = top + offset.y; - } else { - x = left + (width / 2); - y = top + (height / 2); - } - - x = Math.round(x); - y = Math.round(y); - - return { x, y }; - } - private async _click(selector: string, clickCount: number, offset?: { x: number, y: number }): Promise { const { x, y } = await this._getElementXY(selector, offset); @@ -99,116 +40,6 @@ class WindowDriver implements IWindowDriver { await timeout(100); } - async setValue(selector: string, text: string): Promise { - const element = document.querySelector(selector); - - if (!element) { - return Promise.reject(new Error(`Element not found: ${selector}`)); - } - - const inputElement = element as HTMLInputElement; - inputElement.value = text; - - const event = new Event('input', { bubbles: true, cancelable: true }); - inputElement.dispatchEvent(event); - } - - async getTitle(): Promise { - return document.title; - } - - async isActiveElement(selector: string): Promise { - const element = document.querySelector(selector); - - if (element !== document.activeElement) { - const chain: string[] = []; - let el = document.activeElement; - - while (el) { - const tagName = el.tagName; - const id = el.id ? `#${el.id}` : ''; - const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join(''); - chain.unshift(`${tagName}${id}${classes}`); - - el = el.parentElement; - } - - throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`); - } - - return true; - } - - async getElements(selector: string, recursive: boolean): Promise { - const query = document.querySelectorAll(selector); - const result: IElement[] = []; - - for (let i = 0; i < query.length; i++) { - const element = query.item(i); - result.push(serializeElement(element, recursive)); - } - - return result; - } - - async typeInEditor(selector: string, text: string): Promise { - const element = document.querySelector(selector); - - if (!element) { - throw new Error(`Editor not found: ${selector}`); - } - - const textarea = element as HTMLTextAreaElement; - const start = textarea.selectionStart; - const newStart = start + text.length; - const value = textarea.value; - const newValue = value.substr(0, start) + text + value.substr(start); - - textarea.value = newValue; - textarea.setSelectionRange(newStart, newStart); - - const event = new Event('input', { 'bubbles': true, 'cancelable': true }); - textarea.dispatchEvent(event); - } - - async getTerminalBuffer(selector: string): Promise { - const element = document.querySelector(selector); - - if (!element) { - throw new Error(`Terminal not found: ${selector}`); - } - - const xterm: Terminal = (element as any).xterm; - - if (!xterm) { - throw new Error(`Xterm not found: ${selector}`); - } - - const lines: string[] = []; - - for (let i = 0; i < xterm.buffer.length; i++) { - lines.push(xterm.buffer.getLine(i)!.translateToString(true)); - } - - return lines; - } - - async writeInTerminal(selector: string, text: string): Promise { - const element = document.querySelector(selector); - - if (!element) { - throw new Error(`Element not found: ${selector}`); - } - - const xterm: Terminal = (element as any).xterm; - - if (!xterm) { - throw new Error(`Xterm not found: ${selector}`); - } - - xterm._core._coreService.triggerDataEvent(text); - } - async openDevTools(): Promise { await this.windowService.openDevTools({ mode: 'detach' }); } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 90e93eae7e..566830d3a1 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDriver, DriverChannel, IElement, WindowDriverChannelClient, IWindowDriverRegistry, WindowDriverRegistryChannel, IWindowDriver, IDriverOptions } from 'vs/platform/driver/node/driver'; +import { DriverChannel, WindowDriverChannelClient, IWindowDriverRegistry, WindowDriverRegistryChannel, IDriverOptions } from 'vs/platform/driver/node/driver'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net'; import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -17,6 +17,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; +import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; function isSilentKeyCode(keyCode: KeyCode) { return keyCode < KeyCode.KEY_0; @@ -163,6 +164,11 @@ export class Driver implements IDriver, IWindowDriverRegistry { return await windowDriver.getElements(selector, recursive); } + async getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }> { + const windowDriver = await this.getWindowDriver(windowId); + return await windowDriver.getElementXY(selector, xoffset, yoffset); + } + async typeInEditor(windowId: number, selector: string, text: string): Promise { const windowDriver = await this.getWindowDriver(windowId); await windowDriver.typeInEditor(selector, text); diff --git a/src/vs/platform/driver/node/driver.ts b/src/vs/platform/driver/node/driver.ts index f3efafb598..2cf10644b4 100644 --- a/src/vs/platform/driver/node/driver.ts +++ b/src/vs/platform/driver/node/driver.ts @@ -5,45 +5,9 @@ import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; - -export const ID = 'driverService'; -export const IDriver = createDecorator(ID); - -// !! Do not remove the following START and END markers, they are parsed by the smoketest build - -//*START -export interface IElement { - tagName: string; - className: string; - textContent: string; - attributes: { [name: string]: string; }; - children: IElement[]; - top: number; - left: number; -} - -export interface IDriver { - _serviceBrand: any; - - getWindowIds(): Promise; - capturePage(windowId: number): Promise; - reloadWindow(windowId: number): Promise; - exitApplication(): Promise; - dispatchKeybinding(windowId: number, keybinding: string): Promise; - click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; - doubleClick(windowId: number, selector: string): Promise; - setValue(windowId: number, selector: string, text: string): Promise; - getTitle(windowId: number): Promise; - isActiveElement(windowId: number, selector: string): Promise; - getElements(windowId: number, selector: string, recursive?: boolean): Promise; - typeInEditor(windowId: number, selector: string, text: string): Promise; - getTerminalBuffer(windowId: number, selector: string): Promise; - writeInTerminal(windowId: number, selector: string, text: string): Promise; -} -//*END +import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; export class DriverChannel implements IServerChannel { @@ -66,6 +30,7 @@ export class DriverChannel implements IServerChannel { case 'getTitle': return this.driver.getTitle(arg[0]); case 'isActiveElement': return this.driver.isActiveElement(arg[0], arg[1]); case 'getElements': return this.driver.getElements(arg[0], arg[1], arg[2]); + case 'getElementXY': return this.driver.getElementXY(arg[0], arg[1], arg[2]); case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1], arg[2]); case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg[0], arg[1]); case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1], arg[2]); @@ -125,6 +90,10 @@ export class DriverChannelClient implements IDriver { return this.channel.call('getElements', [windowId, selector, recursive]); } + getElementXY(windowId: number, selector: string, xoffset: number | undefined, yoffset: number | undefined): Promise<{ x: number, y: number }> { + return this.channel.call('getElementXY', [windowId, selector, xoffset, yoffset]); + } + typeInEditor(windowId: number, selector: string, text: string): Promise { return this.channel.call('typeInEditor', [windowId, selector, text]); } @@ -180,18 +149,6 @@ export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry } } -export interface IWindowDriver { - click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; - doubleClick(selector: string): Promise; - setValue(selector: string, text: string): Promise; - getTitle(): Promise; - isActiveElement(selector: string): Promise; - getElements(selector: string, recursive: boolean): Promise; - typeInEditor(selector: string, text: string): Promise; - getTerminalBuffer(selector: string): Promise; - writeInTerminal(selector: string, text: string): Promise; -} - export class WindowDriverChannel implements IServerChannel { constructor(private driver: IWindowDriver) { } @@ -208,6 +165,7 @@ export class WindowDriverChannel implements IServerChannel { case 'getTitle': return this.driver.getTitle(); case 'isActiveElement': return this.driver.isActiveElement(arg); case 'getElements': return this.driver.getElements(arg[0], arg[1]); + case 'getElementXY': return this.driver.getElementXY(arg[0], arg[1], arg[2]); case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1]); case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg); case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1]); @@ -247,6 +205,10 @@ export class WindowDriverChannelClient implements IWindowDriver { return this.channel.call('getElements', [selector, recursive]); } + getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number, y: number }> { + return this.channel.call('getElementXY', [selector, xoffset, yoffset]); + } + typeInEditor(selector: string, text: string): Promise { return this.channel.call('typeInEditor', [selector, text]); } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index c5d4278be3..fa0cc37363 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -392,10 +392,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService { @IProductService private readonly productService: IProductService, @optional(IStorageService) private readonly storageService: IStorageService, ) { - const config = productService.productConfiguration.extensionsGallery; + const config = productService.extensionsGallery; this.extensionsGalleryUrl = config && config.serviceUrl; this.extensionsControlUrl = config && config.controlUrl; - this.commonHeadersPromise = resolveMarketplaceHeaders(productService.productConfiguration.version, this.environmentService, this.fileService, this.storageService); + this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, this.environmentService, this.fileService, this.storageService); } private api(path = ''): string { @@ -440,7 +440,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { const versionAsset = rawExtension.versions.filter(v => v.version === version)[0]; if (versionAsset) { const extension = toExtension(rawExtension, versionAsset, 0, query); - if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.productConfiguration.version)) { + if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version)) { return extension; } } @@ -788,7 +788,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions }) => { if (galleryExtensions.length) { if (compatible) { - return Promise.all(galleryExtensions[0].versions.map(v => this.getEngine(v).then(engine => isEngineValid(engine, this.productService.productConfiguration.version) ? v : null))) + return Promise.all(galleryExtensions[0].versions.map(v => this.getEngine(v).then(engine => isEngineValid(engine, this.productService.version) ? v : null))) .then(versions => versions .filter(v => !!v) .map(v => ({ version: v!.version, date: v!.lastUpdated }))); @@ -874,7 +874,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { if (!engine) { return null; } - if (isEngineValid(engine, this.productService.productConfiguration.version)) { + if (isEngineValid(engine, this.productService.version)) { return Promise.resolve(version); } } @@ -906,7 +906,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { const version = versions[0]; return this.getEngine(version) .then(engine => { - if (!isEngineValid(engine, this.productService.productConfiguration.version)) { + if (!isEngineValid(engine, this.productService.version)) { return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1)); } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index da3abd0630..85d33b60ed 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -620,26 +620,26 @@ export interface IReadFileOptions { * that have been read already with the same etag. * It is the task of the caller to makes sure to handle this error case from the promise. */ - etag?: string; + readonly etag?: string; /** * Is an integer specifying where to begin reading from in the file. If position is null, * data will be read from the current file position. */ - position?: number; + readonly position?: number; /** * Is an integer specifying how many bytes to read from the file. By default, all bytes * will be read. */ - length?: number; + readonly length?: number; /** * If provided, the size of the file will be checked against the limits. */ limits?: { - size?: number; - memory?: number; + readonly size?: number; + readonly memory?: number; }; } @@ -648,12 +648,12 @@ export interface IWriteFileOptions { /** * The last known modification time of the file. This can be used to prevent dirty writes. */ - mtime?: number; + readonly mtime?: number; /** * The etag of the file. This can be used to prevent dirty writes. */ - etag?: string; + readonly etag?: string; } export interface IResolveFileOptions { @@ -662,22 +662,22 @@ export interface IResolveFileOptions { * Automatically continue resolving children of a directory until the provided resources * are found. */ - resolveTo?: URI[]; + readonly resolveTo?: readonly URI[]; /** * Automatically continue resolving children of a directory if the number of children is 1. */ - resolveSingleChildDescendants?: boolean; + readonly resolveSingleChildDescendants?: boolean; /** * Will resolve mtime, size and etag of files if enabled. This can have a negative impact * on performance and thus should only be used when these values are required. */ - resolveMetadata?: boolean; + readonly resolveMetadata?: boolean; } export interface IResolveMetadataFileOptions extends IResolveFileOptions { - resolveMetadata: true; + readonly resolveMetadata: true; } export interface ICreateFileOptions { @@ -686,7 +686,7 @@ export interface ICreateFileOptions { * Overwrite the file to create if it already exists on disk. Otherwise * an error will be thrown (FILE_MODIFIED_SINCE). */ - overwrite?: boolean; + readonly overwrite?: boolean; } export class FileOperationError extends Error { diff --git a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts index 075c770507..77d4251922 100644 --- a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts @@ -134,7 +134,7 @@ export class OutOfProcessWin32FolderWatcher { public dispose(): void { if (this.handle) { this.handle.kill(); - this.handle = null!; // StrictNullOverride: nulling out ok in dispose + this.handle = undefined; } } } diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts index 109ac4335d..f709d07cfd 100644 --- a/src/vs/platform/issue/electron-browser/issueService.ts +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IIssueService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { IIssueService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/node/issue'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index c98bad3432..49dd08b60b 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -6,10 +6,11 @@ import { localize } from 'vs/nls'; import * as objects from 'vs/base/common/objects'; import { parseArgs } from 'vs/platform/environment/node/argv'; -import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/node/issue'; import { BrowserWindow, ipcMain, screen, Event, dialog } from 'electron'; import { ILaunchService } from 'vs/platform/launch/electron-main/launchService'; -import { PerformanceInfo, IDiagnosticsService, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { PerformanceInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; +import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/node/issue.ts similarity index 100% rename from src/vs/platform/issue/common/issue.ts rename to src/vs/platform/issue/node/issue.ts diff --git a/src/vs/platform/issue/node/issueIpc.ts b/src/vs/platform/issue/node/issueIpc.ts index 7f55676c33..ae64e84335 100644 --- a/src/vs/platform/issue/node/issueIpc.ts +++ b/src/vs/platform/issue/node/issueIpc.ts @@ -5,7 +5,7 @@ import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IIssueService } from 'vs/platform/issue/common/issue'; +import { IIssueService } from 'vs/platform/issue/node/issue'; export class IssueChannel implements IServerChannel { diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 3fdf0812be..33be9edb5e 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -19,7 +19,7 @@ import { BrowserWindow, ipcMain, Event as IpcEvent, app } from 'electron'; import { Event } from 'vs/base/common/event'; import { hasArgs } from 'vs/platform/environment/node/argv'; import { coalesce } from 'vs/base/common/arrays'; -import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/common/launchService'; export const ID = 'launchService'; diff --git a/src/vs/platform/product/browser/productService.ts b/src/vs/platform/product/browser/productService.ts deleted file mode 100644 index fd2cf7d966..0000000000 --- a/src/vs/platform/product/browser/productService.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IProductService, IProductConfiguration } from 'vs/platform/product/common/product'; -import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; - -export class ProductService implements IProductService { - - _serviceBrand!: ServiceIdentifier; - - readonly productConfiguration: IProductConfiguration; - - constructor() { - const element = document.getElementById('vscode-remote-product-configuration'); - this.productConfiguration = { - ...element ? JSON.parse(element.getAttribute('data-settings')!) : { - version: '1.38.0-unknown', - nameLong: 'Unknown', - extensionAllowedProposedApi: [], - }, ...{ urlProtocol: '', enableTelemetry: false }, - ...{ vscodeVersion: '1.35.0' } // {{SQL CARBON EDIT}} add vscodeversion - }; - } - -} diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index d8eaf3de15..2a75ea306e 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -3,19 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const IProductService = createDecorator('productService'); -export interface IProductService { +export interface IProductService extends Readonly { - _serviceBrand: ServiceIdentifier; + _serviceBrand: undefined; - readonly productConfiguration: IProductConfiguration; } export interface IProductConfiguration { - readonly version: string; + version: string; nameShort: string; nameLong: string; readonly applicationName: string; @@ -51,7 +50,6 @@ export interface IProductConfiguration { readonly extensionImportantTips: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; }; readonly exeBasedExtensionTips: { [id: string]: IExeBasedExtensionTip; }; readonly extensionKeywords: { [extension: string]: readonly string[]; }; - readonly extensionAllowedBadgeProviders: readonly string[]; readonly extensionAllowedProposedApi: readonly string[]; readonly keymapExtensionTips: readonly string[]; readonly crashReporter: { diff --git a/src/vs/platform/product/node/product.ts b/src/vs/platform/product/node/product.ts index e53e6a8f65..51de6b2f06 100644 --- a/src/vs/platform/product/node/product.ts +++ b/src/vs/platform/product/node/product.ts @@ -6,6 +6,7 @@ import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { IProductConfiguration } from 'vs/platform/product/common/product'; +import pkg from 'vs/platform/product/node/package'; const rootPath = path.dirname(getPathFromAmdModule(require, '')); const productJsonPath = path.join(rootPath, 'product.json'); @@ -17,4 +18,6 @@ if (process.env['VSCODE_DEV']) { product.dataFolderName += '-dev'; } +product.version = pkg.version; + export default product; diff --git a/src/vs/platform/product/node/productService.ts b/src/vs/platform/product/node/productService.ts deleted file mode 100644 index 9721daf86c..0000000000 --- a/src/vs/platform/product/node/productService.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IProductService, IProductConfiguration } from 'vs/platform/product/common/product'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; -import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; - -export class ProductService implements IProductService { - - _serviceBrand!: ServiceIdentifier; - - readonly productConfiguration: IProductConfiguration; - - constructor() { - this.productConfiguration = { - ...product, ...{ version: pkg.version }, ...{ vscodeVersion: '1.35.0' } // {{SQL CARBON EDIT}} add vscodeversion} - }; - } - -} diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts index e493a5f0c7..1d2cadba27 100644 --- a/src/vs/platform/remote/common/remoteAgentEnvironment.ts +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -9,6 +9,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' export interface IRemoteAgentEnvironment { pid: number; + connectionToken: string; appRoot: URI; appSettingsHome: URI; settingsPath: URI; diff --git a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts index 1f648271d8..b92262cef5 100644 --- a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts @@ -6,6 +6,7 @@ import { ResolvedAuthority, IRemoteAuthorityResolverService, ResolverResult, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ipcRenderer as ipc } from 'electron'; import * as errors from 'vs/base/common/errors'; +import { RemoteAuthorities } from 'vs/base/common/network'; class PendingResolveAuthorityRequest { constructor( @@ -50,6 +51,7 @@ export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverS if (this._resolveAuthorityRequests[resolvedAuthority.authority]) { let request = this._resolveAuthorityRequests[resolvedAuthority.authority]; ipc.send('vscode:remoteAuthorityResolved', resolvedAuthority); + RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.host, resolvedAuthority.port); request.resolve({ authority: resolvedAuthority, options }); } } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 2420981064..2177348b39 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -96,12 +96,12 @@ export interface IWindowsService { _serviceBrand: any; - onWindowOpen: Event; - onWindowFocus: Event; - onWindowBlur: Event; - onWindowMaximize: Event; - onWindowUnmaximize: Event; - onRecentlyOpenedChange: Event; + readonly onWindowOpen: Event; + readonly onWindowFocus: Event; + readonly onWindowBlur: Event; + readonly onWindowMaximize: Event; + readonly onWindowUnmaximize: Event; + readonly onRecentlyOpenedChange: Event; // Dialogs pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 41849216ba..2323c97e43 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -12,14 +12,14 @@ import { IRecent, isRecentFile, isRecentFolder } from 'vs/platform/history/commo export class WindowsChannel implements IServerChannel { - private onWindowOpen: Event; - private onWindowFocus: Event; - private onWindowBlur: Event; - private onWindowMaximize: Event; - private onWindowUnmaximize: Event; - private onRecentlyOpenedChange: Event; + private readonly onWindowOpen: Event; + private readonly onWindowFocus: Event; + private readonly onWindowBlur: Event; + private readonly onWindowMaximize: Event; + private readonly onWindowUnmaximize: Event; + private readonly onRecentlyOpenedChange: Event; - constructor(private service: IWindowsService) { + constructor(private readonly service: IWindowsService) { this.onWindowOpen = Event.buffer(service.onWindowOpen, true); this.onWindowFocus = Event.buffer(service.onWindowFocus, true); this.onWindowBlur = Event.buffer(service.onWindowBlur, true); @@ -120,4 +120,4 @@ export class WindowsChannel implements IServerChannel { throw new Error(`Call not found: ${command}`); } -} \ No newline at end of file +} diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts index 9f6facb91e..0502fedae5 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts @@ -17,6 +17,7 @@ import { URI } from 'vs/base/common/uri'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { isWindows } from 'vs/base/common/platform'; import { normalizeDriveLetter } from 'vs/base/common/labels'; +import { dirname, joinPath } from 'vs/base/common/resources'; suite('WorkspacesMainService', () => { const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'workspacesservice'); @@ -51,13 +52,13 @@ suite('WorkspacesMainService', () => { let service: TestWorkspacesMainService; - setup(() => { + setup(async () => { service = new TestWorkspacesMainService(environmentService, logService); // Delete any existing backups completely and then re-create it. - return pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE).then(() => { - return pfs.mkdirp(untitledWorkspacesHomePath); - }); + await pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE); + + return pfs.mkdirp(untitledWorkspacesHomePath); }); teardown(() => { @@ -77,57 +78,50 @@ suite('WorkspacesMainService', () => { assert.equal(u1.toString(), u2.toString()); } - test('createWorkspace (folders)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath.fsPath)); - assert.ok(service.isUntitledWorkspace(workspace)); + test('createWorkspace (folders)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + assert.ok(workspace); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); + assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 2); // - assertPathEquals((ws.folders[0]).path, process.cwd()); - assertPathEquals((ws.folders[1]).path, os.tmpdir()); - - assert.ok(!(ws.folders[0]).name); - assert.ok(!(ws.folders[1]).name); - }); + const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); + assert.equal(ws.folders.length, 2); + assertPathEquals((ws.folders[0]).path, process.cwd()); + assertPathEquals((ws.folders[1]).path, os.tmpdir()); + assert.ok(!(ws.folders[0]).name); + assert.ok(!(ws.folders[1]).name); }); - test('createWorkspace (folders with name)', () => { - return createWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']).then(workspace => { - assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath.fsPath)); - assert.ok(service.isUntitledWorkspace(workspace)); + test('createWorkspace (folders with name)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']); + assert.ok(workspace); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); + assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 2); // - assertPathEquals((ws.folders[0]).path, process.cwd()); - assertPathEquals((ws.folders[1]).path, os.tmpdir()); - - assert.equal((ws.folders[0]).name, 'currentworkingdirectory'); - assert.equal((ws.folders[1]).name, 'tempdir'); - }); + const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); + assert.equal(ws.folders.length, 2); + assertPathEquals((ws.folders[0]).path, process.cwd()); + assertPathEquals((ws.folders[1]).path, os.tmpdir()); + assert.equal((ws.folders[0]).name, 'currentworkingdirectory'); + assert.equal((ws.folders[1]).name, 'tempdir'); }); - test('createUntitledWorkspace (folders as other resource URIs)', () => { + test('createUntitledWorkspace (folders as other resource URIs)', async () => { const folder1URI = URI.parse('myscheme://server/work/p/f1'); const folder2URI = URI.parse('myscheme://server/work/o/f3'); - return service.createUntitledWorkspace([{ uri: folder1URI }, { uri: folder2URI }], 'server').then(workspace => { - assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath.fsPath)); - assert.ok(service.isUntitledWorkspace(workspace)); + const workspace = await service.createUntitledWorkspace([{ uri: folder1URI }, { uri: folder2URI }], 'server'); + assert.ok(workspace); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); + assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 2); - assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); - assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); - - assert.ok(!(ws.folders[0]).name); - assert.ok(!(ws.folders[1]).name); - - assert.equal(ws.remoteAuthority, 'server'); - }); + const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); + assert.equal(ws.folders.length, 2); + assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); + assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); + assert.ok(!(ws.folders[0]).name); + assert.ok(!(ws.folders[1]).name); + assert.equal(ws.remoteAuthority, 'server'); }); test('createWorkspaceSync (folders)', () => { @@ -178,145 +172,130 @@ suite('WorkspacesMainService', () => { assert.ok(!(ws.folders[1]).name); }); - test('resolveWorkspaceSync', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - assert.ok(service.resolveLocalWorkspaceSync(workspace.configPath)); + test('resolveWorkspaceSync', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + assert.ok(service.resolveLocalWorkspaceSync(workspace.configPath)); - // make it a valid workspace path - const newPath = path.join(path.dirname(workspace.configPath.fsPath), `workspace.${WORKSPACE_EXTENSION}`); - fs.renameSync(workspace.configPath.fsPath, newPath); - workspace.configPath = URI.file(newPath); + // make it a valid workspace path + const newPath = path.join(path.dirname(workspace.configPath.fsPath), `workspace.${WORKSPACE_EXTENSION}`); + fs.renameSync(workspace.configPath.fsPath, newPath); + workspace.configPath = URI.file(newPath); - const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assert.equal(2, resolved!.folders.length); - assertEqualURI(resolved!.configPath, workspace.configPath); - assert.ok(resolved!.id); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assert.equal(2, resolved!.folders.length); + assertEqualURI(resolved!.configPath, workspace.configPath); + assert.ok(resolved!.id); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ something: 'something' })); // invalid workspace - fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ something: 'something' })); // invalid workspace - const resolvedInvalid = service.resolveLocalWorkspaceSync(workspace.configPath); - assert.ok(!resolvedInvalid); - }); + const resolvedInvalid = service.resolveLocalWorkspaceSync(workspace.configPath); + assert.ok(!resolvedInvalid); }); - test('resolveWorkspaceSync (support relative paths)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] })); + test('resolveWorkspaceSync (support relative paths)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] })); - const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); - }); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); - test('resolveWorkspaceSync (support relative paths #2)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] })); + test('resolveWorkspaceSync (support relative paths #2)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] })); - const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'other'))); - }); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'other'))); }); - test('resolveWorkspaceSync (support relative paths #3)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] })); + test('resolveWorkspaceSync (support relative paths #3)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] })); - const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); - }); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); - test('resolveWorkspaceSync (support invalid JSON via fault tolerant parsing)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma + test('resolveWorkspaceSync (support invalid JSON via fault tolerant parsing)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma - const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); - }); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); - test('rewriteWorkspaceFileForNewLocation', () => { + test('rewriteWorkspaceFileForNewLocation', async () => { const folder1 = process.cwd(); // absolute path because outside of tmpDir const tmpDir = os.tmpdir(); const tmpInsideDir = path.join(os.tmpdir(), 'inside'); - return createWorkspace([folder1, tmpInsideDir, path.join(tmpInsideDir, 'somefolder')]).then(workspace => { - const origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + const workspace = await createWorkspace([folder1, tmpInsideDir, path.join(tmpInsideDir, 'somefolder')]); + const origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - let origConfigPath = workspace.configPath; - let workspaceConfigPath = URI.file(path.join(tmpDir, 'inside', 'myworkspace1.code-workspace')); - let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, workspaceConfigPath); + let origConfigPath = workspace.configPath; + let workspaceConfigPath = URI.file(path.join(tmpDir, 'inside', 'myworkspace1.code-workspace')); + let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, workspaceConfigPath); + let ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.equal(ws.folders.length, 3); + assertPathEquals((ws.folders[0]).path, folder1); // absolute path because outside of tmpdir + assertPathEquals((ws.folders[1]).path, '.'); + assertPathEquals((ws.folders[2]).path, 'somefolder'); - let ws = JSON.parse(newContent) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assertPathEquals((ws.folders[0]).path, folder1); // absolute path because outside of tmpdir - assertPathEquals((ws.folders[1]).path, '.'); - assertPathEquals((ws.folders[2]).path, 'somefolder'); + origConfigPath = workspaceConfigPath; + workspaceConfigPath = URI.file(path.join(tmpDir, 'myworkspace2.code-workspace')); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.equal(ws.folders.length, 3); + assertPathEquals((ws.folders[0]).path, folder1); + assertPathEquals((ws.folders[1]).path, 'inside'); + assertPathEquals((ws.folders[2]).path, isWindows ? 'inside\\somefolder' : 'inside/somefolder'); - origConfigPath = workspaceConfigPath; - workspaceConfigPath = URI.file(path.join(tmpDir, 'myworkspace2.code-workspace')); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + origConfigPath = workspaceConfigPath; + workspaceConfigPath = URI.file(path.join(tmpDir, 'other', 'myworkspace2.code-workspace')); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.equal(ws.folders.length, 3); + assertPathEquals((ws.folders[0]).path, folder1); + assertPathEquals((ws.folders[1]).path, tmpInsideDir); + assertPathEquals((ws.folders[2]).path, path.join(tmpInsideDir, 'somefolder')); - ws = JSON.parse(newContent) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assertPathEquals((ws.folders[0]).path, folder1); - assertPathEquals((ws.folders[1]).path, 'inside'); - assertPathEquals((ws.folders[2]).path, isWindows ? 'inside\\somefolder' : 'inside/somefolder'); + origConfigPath = workspaceConfigPath; + workspaceConfigPath = URI.parse('foo://foo/bar/myworkspace2.code-workspace'); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.equal(ws.folders.length, 3); + assert.equal((ws.folders[0]).uri, URI.file(folder1).toString(true)); + assert.equal((ws.folders[1]).uri, URI.file(tmpInsideDir).toString(true)); + assert.equal((ws.folders[2]).uri, URI.file(path.join(tmpInsideDir, 'somefolder')).toString(true)); - origConfigPath = workspaceConfigPath; - workspaceConfigPath = URI.file(path.join(tmpDir, 'other', 'myworkspace2.code-workspace')); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); - - ws = JSON.parse(newContent) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assertPathEquals((ws.folders[0]).path, folder1); - assertPathEquals((ws.folders[1]).path, tmpInsideDir); - assertPathEquals((ws.folders[2]).path, path.join(tmpInsideDir, 'somefolder')); - - origConfigPath = workspaceConfigPath; - workspaceConfigPath = URI.parse('foo://foo/bar/myworkspace2.code-workspace'); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); - - ws = JSON.parse(newContent) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assert.equal((ws.folders[0]).uri, URI.file(folder1).toString(true)); - assert.equal((ws.folders[1]).uri, URI.file(tmpInsideDir).toString(true)); - assert.equal((ws.folders[2]).uri, URI.file(path.join(tmpInsideDir, 'somefolder')).toString(true)); - - service.deleteUntitledWorkspaceSync(workspace); - }); + service.deleteUntitledWorkspaceSync(workspace); }); - test('rewriteWorkspaceFileForNewLocation (preserves comments)', () => { - return createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]).then(workspace => { - const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); + test('rewriteWorkspaceFileForNewLocation (preserves comments)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); + const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); - let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - origContent = `// this is a comment\n${origContent}`; + let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + origContent = `// this is a comment\n${origContent}`; - let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); - - assert.equal(0, newContent.indexOf('// this is a comment')); - - service.deleteUntitledWorkspaceSync(workspace); - }); + let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + assert.equal(0, newContent.indexOf('// this is a comment')); + service.deleteUntitledWorkspaceSync(workspace); }); - test('rewriteWorkspaceFileForNewLocation (preserves forward slashes)', () => { - return createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]).then(workspace => { - const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); - let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - origContent = origContent.replace(/[\\]/g, '/'); // convert backslash to slash + test('rewriteWorkspaceFileForNewLocation (preserves forward slashes)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); + const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); - const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + origContent = origContent.replace(/[\\]/g, '/'); // convert backslash to slash - const ws = JSON.parse(newContent) as IStoredWorkspace; - assert.ok(ws.folders.every(f => (f).path.indexOf('\\') < 0)); - - service.deleteUntitledWorkspaceSync(workspace); - }); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + const ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.ok(ws.folders.every(f => (f).path.indexOf('\\') < 0)); + service.deleteUntitledWorkspaceSync(workspace); }); - test('rewriteWorkspaceFileForNewLocation (unc paths)', () => { + test('rewriteWorkspaceFileForNewLocation (unc paths)', async () => { if (!isWindows) { return Promise.resolve(); } @@ -326,68 +305,58 @@ suite('WorkspacesMainService', () => { const folder2Location = '\\\\server\\share2\\some\\path'; const folder3Location = path.join(os.tmpdir(), 'wsloc', 'inner', 'more'); - return createWorkspace([folder1Location, folder2Location, folder3Location]).then(workspace => { - const workspaceConfigPath = URI.file(path.join(workspaceLocation, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); - let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + const workspace = await createWorkspace([folder1Location, folder2Location, folder3Location]); + const workspaceConfigPath = URI.file(path.join(workspaceLocation, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); + let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + const ws = (JSON.parse(newContent) as IStoredWorkspace); + assertPathEquals((ws.folders[0]).path, folder1Location); + assertPathEquals((ws.folders[1]).path, folder2Location); + assertPathEquals((ws.folders[2]).path, 'inner\\more'); - const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); - - const ws = JSON.parse(newContent) as IStoredWorkspace; - assertPathEquals((ws.folders[0]).path, folder1Location); - assertPathEquals((ws.folders[1]).path, folder2Location); - assertPathEquals((ws.folders[2]).path, 'inner\\more'); - - service.deleteUntitledWorkspaceSync(workspace); - }); + service.deleteUntitledWorkspaceSync(workspace); }); - test('deleteUntitledWorkspaceSync (untitled)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - assert.ok(fs.existsSync(workspace.configPath.fsPath)); - - service.deleteUntitledWorkspaceSync(workspace); - - assert.ok(!fs.existsSync(workspace.configPath.fsPath)); - }); + test('deleteUntitledWorkspaceSync (untitled)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); + service.deleteUntitledWorkspaceSync(workspace); + assert.ok(!fs.existsSync(workspace.configPath.fsPath)); }); - test('deleteUntitledWorkspaceSync (saved)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - service.deleteUntitledWorkspaceSync(workspace); - }); + test('deleteUntitledWorkspaceSync (saved)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + service.deleteUntitledWorkspaceSync(workspace); }); - test('getUntitledWorkspaceSync', () => { + test('getUntitledWorkspaceSync', async () => { let untitled = service.getUntitledWorkspacesSync(); assert.equal(untitled.length, 0); - return createWorkspace([process.cwd(), os.tmpdir()]).then(untitledOne => { - assert.ok(fs.existsSync(untitledOne.configPath.fsPath)); + const untitledOne = await createWorkspace([process.cwd(), os.tmpdir()]); + assert.ok(fs.existsSync(untitledOne.configPath.fsPath)); - untitled = service.getUntitledWorkspacesSync(); + untitled = service.getUntitledWorkspacesSync(); + assert.equal(1, untitled.length); + assert.equal(untitledOne.id, untitled[0].workspace.id); - assert.equal(1, untitled.length); - assert.equal(untitledOne.id, untitled[0].workspace.id); + const untitledTwo = await createWorkspace([os.tmpdir(), process.cwd()]); + assert.ok(fs.existsSync(untitledTwo.configPath.fsPath)); + assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected workspaces count of 1 (expected 2): ${untitledOne.configPath.fsPath} does not exist anymore?`); + untitled = service.getUntitledWorkspacesSync(); + assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected workspaces count of 1 (expected 2): ${untitledOne.configPath.fsPath} does not exist anymore?`); + if (untitled.length === 1) { + const untitledHome = dirname(dirname(untitledTwo.configPath)); + assert.fail(`Unexpected workspaces count of 1 (expected 2), all workspaces:\n ${fs.readdirSync(untitledHome.fsPath).map(name => fs.readFileSync(joinPath(untitledHome, name, 'workspace.json').fsPath, 'utf8'))}`); + } + assert.equal(2, untitled.length); - return createWorkspace([os.tmpdir(), process.cwd()]).then(untitledTwo => { - assert.ok(fs.existsSync(untitledTwo.configPath.fsPath)); + service.deleteUntitledWorkspaceSync(untitledOne); + untitled = service.getUntitledWorkspacesSync(); + assert.equal(1, untitled.length); - untitled = service.getUntitledWorkspacesSync(); - - if (untitled.length === 1) { - assert.fail('Unexpected workspaces count, contents:\n' + fs.readFileSync(untitledTwo.configPath.fsPath, 'utf8')); - } - - assert.equal(2, untitled.length); - - service.deleteUntitledWorkspaceSync(untitledOne); - untitled = service.getUntitledWorkspacesSync(); - assert.equal(1, untitled.length); - - service.deleteUntitledWorkspaceSync(untitledTwo); - untitled = service.getUntitledWorkspacesSync(); - assert.equal(0, untitled.length); - }); - }); + service.deleteUntitledWorkspaceSync(untitledTwo); + untitled = service.getUntitledWorkspacesSync(); + assert.equal(0, untitled.length); }); }); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 35796b7251..21cf23818c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -565,22 +565,6 @@ declare module 'vscode' { //#endregion - //#region Joh: onDidExecuteCommand - - export interface CommandExecutionEvent { - command: string; - arguments: any[]; - } - - export namespace commands { - /** - * An event that is emitted when a [command](#Command) is executed. - */ - export const onDidExecuteCommand: Event; - } - - //#endregion - //#region Joh: decorations //todo@joh -> make class @@ -1046,6 +1030,15 @@ declare module 'vscode' { */ constructor(label: TreeItemLabel, collapsibleState?: TreeItemCollapsibleState); } + + export interface TreeViewOptions2 extends TreeViewOptions { + /** + * Whether the tree supports multi-select. When the tree supports multi-select and a command is executed from the tree, + * the first argument to the command is the tree item that the command was executed on and the second argument is an + * array containing the other selected tree items. + */ + canSelectMany?: boolean; + } //#endregion //#region CustomExecution @@ -1168,11 +1161,11 @@ declare module 'vscode' { /** * Content security policy source for webview resources. * - * This is origin used in a content security policy rule: + * This is the origin that should be used in a content security policy rule: * * ``` * img-src https: ${webview.cspSource} ...; - * ```` + * ``` */ readonly cspSource: string; } diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index ac02f14f9a..f0d144d58a 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -90,7 +90,6 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { const webview = this._webviewService.createWebview('' + handle, { enableFindWidget: false, - allowSvgs: false, extension: { id: extensionId, location: URI.revive(extensionLocation) } }, { allowScripts: options.enableScripts, diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index 81aa57e579..e6e86b909f 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -15,7 +15,6 @@ export class MainThreadCommands implements MainThreadCommandsShape { private readonly _commandRegistrations = new Map(); private readonly _generateCommandsDocumentationRegistration: IDisposable; private readonly _proxy: ExtHostCommandsShape; - private _onDidExecuteCommandListener?: IDisposable; constructor( extHostContext: IExtHostContext, @@ -78,19 +77,6 @@ export class MainThreadCommands implements MainThreadCommandsShape { return this._commandService.executeCommand(id, ...args); } - $registerCommandListener() { - if (!this._onDidExecuteCommandListener) { - this._onDidExecuteCommandListener = this._commandService.onDidExecuteCommand(command => this._proxy.$handleDidExecuteCommand(command)); - } - } - - $unregisterCommandListener() { - if (this._onDidExecuteCommandListener) { - this._onDidExecuteCommandListener.dispose(); - this._onDidExecuteCommandListener = undefined; - } - } - $getCommands(): Promise { return Promise.resolve([...CommandsRegistry.getCommands().keys()]); } diff --git a/src/vs/workbench/api/browser/mainThreadKeytar.ts b/src/vs/workbench/api/browser/mainThreadKeytar.ts index 0cd602a1db..1f6c748ab2 100644 --- a/src/vs/workbench/api/browser/mainThreadKeytar.ts +++ b/src/vs/workbench/api/browser/mainThreadKeytar.ts @@ -5,49 +5,33 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadKeytarShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; @extHostNamedCustomer(MainContext.MainThreadKeytar) export class MainThreadKeytar implements MainThreadKeytarShape { - private readonly _credentialsService?: ICredentialsService; - constructor( _extHostContext: IExtHostContext, - @optional(ICredentialsService) credentialsService: ICredentialsService, - ) { - this._credentialsService = credentialsService; + @ICredentialsService private readonly _credentialsService: ICredentialsService, + ) { } + + async $getPassword(service: string, account: string): Promise { + return this._credentialsService.getPassword(service, account); + } + + async $setPassword(service: string, account: string, password: string): Promise { + return this._credentialsService.setPassword(service, account, password); + } + + async $deletePassword(service: string, account: string): Promise { + return this._credentialsService.deletePassword(service, account); + } + + async $findPassword(service: string): Promise { + return this._credentialsService.findPassword(service); } dispose(): void { // } - - async $getPassword(service: string, account: string): Promise { - if (this._credentialsService) { - return this._credentialsService.getPassword(service, account); - } - return null; - } - - async $setPassword(service: string, account: string, password: string): Promise { - if (this._credentialsService) { - return this._credentialsService.setPassword(service, account, password); - } - } - - async $deletePassword(service: string, account: string): Promise { - if (this._credentialsService) { - return this._credentialsService.deletePassword(service, account); - } - return false; - } - - async $findPassword(service: string): Promise { - if (this._credentialsService) { - return this._credentialsService.findPassword(service); - } - return null; - } } diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 0337069629..e9ac823fa7 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -27,13 +27,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void { + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void { const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService); this._dataProviders.set(treeViewId, dataProvider); const viewer = this.getTreeView(treeViewId); if (viewer) { viewer.dataProvider = dataProvider; viewer.showCollapseAllAction = !!options.showCollapseAll; + viewer.canSelectMany = !!options.canSelectMany; this.registerListeners(treeViewId, viewer); this._proxy.$setVisible(treeViewId, viewer.visible); } else { diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index c718af569e..d181d029dd 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -327,7 +327,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews if (MainThreadWebviews.standardSupportedLinkSchemes.has(link.scheme)) { return true; } - if (this._productService.productConfiguration.urlProtocol === link.scheme) { + if (this._productService.urlProtocol === link.scheme) { return true; } return !!webview.webview.contentOptions.enableCommandUris && link.scheme === 'command'; diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 077a47ea29..b28522521d 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -151,7 +151,7 @@ const viewsContribution: IJSONSchema = { default: [] }, 'remote': { - description: localize('views.remote', "Contributes views to Remote container in the Activity bar"), + description: localize('views.remote', "Contributes views to Remote container in the Activity bar. To contribute to this container, enableProposedApi needs to be turned on"), type: 'array', items: nestableViewDescriptor, default: [] @@ -387,6 +387,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return; } + if (entry.key === 'remote' && !extension.description.enableProposedApi) { + collector.warn(localize('ViewContainerRequiresProposedAPI', "View container '{0}' requires 'enableProposedApi' turned on to be added to 'Remote'.", entry.key)); + return; + } + const viewContainer = this.getViewContainer(entry.key); if (!viewContainer) { collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key)); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 588563d01f..fd09c640e5 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -225,11 +225,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, getCommands(filterInternal: boolean = false): Thenable { return extHostCommands.getCommands(filterInternal); - }, - onDidExecuteCommand: proposedApiFunction(extension, (listener, thisArgs?, disposables?) => { - checkProposedApiEnabled(extension); - return extHostCommands.onDidExecuteCommand(listener, thisArgs, disposables); - }), + } }; // namespace: env diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index de80ece9ae..35f3d0a695 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -21,7 +21,7 @@ import { EndOfLineSequence, ISingleEditOperation } from 'vs/editor/common/model' import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import * as modes from 'vs/editor/common/modes'; import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration'; -import { ICommandHandlerDescription, ICommandEvent } from 'vs/platform/commands/common/commands'; +import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationData, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -119,8 +119,6 @@ export interface MainThreadClipboardShape extends IDisposable { export interface MainThreadCommandsShape extends IDisposable { $registerCommand(id: string): void; - $registerCommandListener(): void; - $unregisterCommandListener(): void; $unregisterCommand(id: string): void; $executeCommand(id: string, args: any[]): Promise; $getCommands(): Promise; @@ -246,7 +244,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise; $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise; $setMessage(treeViewId: string, message: string): void; @@ -739,7 +737,6 @@ export interface MainThreadWindowShape extends IDisposable { export interface ExtHostCommandsShape { $executeContributedCommand(id: string, ...args: any[]): Promise; $getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription }>; - $handleDidExecuteCommand(command: ICommandEvent): void; } export interface ExtHostConfigurationShape { diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 7db7439f55..d2d42b1b80 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { validateConstraint } from 'vs/base/common/types'; -import { ICommandHandlerDescription, ICommandEvent } from 'vs/platform/commands/common/commands'; +import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import { cloneAndChange } from 'vs/base/common/objects'; @@ -17,7 +17,6 @@ import { revive } from 'vs/base/common/marshalling'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { URI } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; @@ -36,9 +35,6 @@ export class ExtHostCommands implements ExtHostCommandsShape { readonly _serviceBrand: any; - private readonly _onDidExecuteCommand: Emitter; - readonly onDidExecuteCommand: Event; - private readonly _commands = new Map(); private readonly _proxy: MainThreadCommandsShape; private readonly _converter: CommandsConverter; @@ -50,11 +46,6 @@ export class ExtHostCommands implements ExtHostCommandsShape { @ILogService logService: ILogService ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadCommands); - this._onDidExecuteCommand = new Emitter({ - onFirstListenerDidAdd: () => this._proxy.$registerCommandListener(), - onLastListenerRemove: () => this._proxy.$unregisterCommandListener(), - }); - this.onDidExecuteCommand = Event.filter(this._onDidExecuteCommand.event, e => e.command[0] !== '_'); // filter 'private' commands this._logService = logService; this._converter = new CommandsConverter(this); this._argumentProcessors = [ @@ -119,22 +110,13 @@ export class ExtHostCommands implements ExtHostCommandsShape { }); } - $handleDidExecuteCommand(command: ICommandEvent): void { - this._onDidExecuteCommand.fire({ - command: command.commandId, - arguments: command.args.map(arg => this._argumentProcessors.reduce((r, p) => p.processArgument(r), arg)) - }); - } - executeCommand(id: string, ...args: any[]): Promise { this._logService.trace('ExtHostCommands#executeCommand', id); if (this._commands.has(id)) { // we stay inside the extension host and support // to pass any kind of parameters around - const res = this._executeContributedCommand(id, args); - this._onDidExecuteCommand.fire({ command: id, arguments: args }); - return res; + return this._executeContributedCommand(id, args); } else { // automagically convert some argument types diff --git a/src/vs/workbench/api/node/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts similarity index 60% rename from src/vs/workbench/api/node/extHostRequireInterceptor.ts rename to src/vs/workbench/api/common/extHostRequireInterceptor.ts index 4355080351..1d2f89ffc7 100644 --- a/src/vs/workbench/api/node/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -5,14 +5,22 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; -import { MainThreadKeytarShape, IEnvironment, MainThreadWindowShape, MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; +import { MainThreadTelemetryShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { endsWith } from 'vs/base/common/strings'; import { IExtensionApiFactory } from 'vs/workbench/api/common/extHost.api.impl'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { platform } from 'vs/base/common/process'; + +import { AzdataNodeModuleFactory, SqlopsNodeModuleFactory } from 'sql/workbench/api/common/extHostRequireInterceptor'; // {{SQL CARBON EDIT}} +import { IExtensionApiFactory as sqlIApiFactory } from 'sql/workbench/api/common/sqlExtHost.api.impl'; // {{SQL CARBON EDIT}} interface LoadFunction { @@ -21,40 +29,45 @@ interface LoadFunction { export interface INodeModuleFactory { //{{SQL CARBON EDIT}} export interface readonly nodeModuleName: string | string[]; - load(request: string, parent: { filename: string; }, isMain: any, original: LoadFunction): any; - alternaiveModuleName?(name: string): string | undefined; + load(request: string, parent: URI, isMain: any, original: LoadFunction): any; + alternativeModuleName?(name: string): string | undefined; } -export class NodeModuleRequireInterceptor { - public static INSTANCE = new NodeModuleRequireInterceptor(); +export abstract class RequireInterceptor { - private readonly _factories: Map; - private readonly _alternatives: ((moduleName: string) => string | undefined)[]; + protected readonly _factories: Map; + protected readonly _alternatives: ((moduleName: string) => string | undefined)[]; - constructor() { + constructor( + private _apiFactory: sqlIApiFactory, // {{SQL CARBON EDIT}} replace with ours + private _extensionRegistry: ExtensionDescriptionRegistry, + @IInstantiationService private readonly _instaService: IInstantiationService, + @IExtHostConfiguration private readonly _extHostConfiguration: IExtHostConfiguration, + @IExtHostExtensionService private readonly _extHostExtensionService: IExtHostExtensionService, + @IExtHostInitDataService private readonly _initData: IExtHostInitDataService + ) { this._factories = new Map(); this._alternatives = []; - this._installInterceptor(this._factories, this._alternatives); } - private _installInterceptor(factories: Map, alternatives: ((moduleName: string) => string | undefined)[]): void { - const node_module = require.__$__nodeRequire('module'); - const original = node_module._load; - node_module._load = function load(request: string, parent: { filename: string; }, isMain: any) { - for (let alternativeModuleName of alternatives) { - let alternative = alternativeModuleName(request); - if (alternative) { - request = alternative; - break; - } - } - if (!factories.has(request)) { - return original.apply(this, arguments); - } - return factories.get(request)!.load(request, parent, isMain, original); - }; + async install(): Promise { + + this._installInterceptor(); + + const configProvider = await this._extHostConfiguration.getConfigProvider(); + const extensionPaths = await this._extHostExtensionService.getExtensionPathIndex(); + + this.register(new VSCodeNodeModuleFactory(this._apiFactory.vscode, extensionPaths, this._extensionRegistry, configProvider)); // {{SQL CARBON EDIT}} // add node module + this.register(new AzdataNodeModuleFactory(this._apiFactory.azdata, extensionPaths)); // {{SQL CARBON EDIT}} // add node module + this.register(new SqlopsNodeModuleFactory(this._apiFactory.sqlops, extensionPaths)); // {{SQL CARBON EDIT}} // add node module + this.register(this._instaService.createInstance(KeytarNodeModuleFactory)); + if (this._initData.remote.isRemote) { + this.register(this._instaService.createInstance(OpenNodeModuleFactory, extensionPaths)); + } } + protected abstract _installInterceptor(): void; + public register(interceptor: INodeModuleFactory): void { if (Array.isArray(interceptor.nodeModuleName)) { for (let moduleName of interceptor.nodeModuleName) { @@ -63,15 +76,17 @@ export class NodeModuleRequireInterceptor { } else { this._factories.set(interceptor.nodeModuleName, interceptor); } - if (typeof interceptor.alternaiveModuleName === 'function') { + if (typeof interceptor.alternativeModuleName === 'function') { this._alternatives.push((moduleName) => { - return interceptor.alternaiveModuleName!(moduleName); + return interceptor.alternativeModuleName!(moduleName); }); } } } -export class VSCodeNodeModuleFactory implements INodeModuleFactory { +//#region --- vscode-module + +class VSCodeNodeModuleFactory implements INodeModuleFactory { public readonly nodeModuleName = 'vscode'; private readonly _extApiImpl = new Map(); @@ -85,10 +100,10 @@ export class VSCodeNodeModuleFactory implements INodeModuleFactory { ) { } - public load(request: string, parent: { filename: string; }): any { + public load(_request: string, parent: URI): any { // get extension id from filename and api for extension - const ext = this._extensionPaths.findSubstr(URI.file(parent.filename).fsPath); + const ext = this._extensionPaths.findSubstr(parent.fsPath); if (ext) { let apiImpl = this._extApiImpl.get(ExtensionIdentifier.toKey(ext.identifier)); if (!apiImpl) { @@ -102,13 +117,18 @@ export class VSCodeNodeModuleFactory implements INodeModuleFactory { if (!this._defaultApiImpl) { let extensionPathsPretty = ''; this._extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.identifier.value}\n`); - console.warn(`Could not identify extension for 'vscode' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`); + console.warn(`Could not identify extension for 'vscode' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); this._defaultApiImpl = this._apiFactory(nullExtensionDescription, this._extensionRegistry, this._configProvider); } return this._defaultApiImpl; } } +//#endregion + + +//#region --- keytar-module + interface IKeytarModule { getPassword(service: string, account: string): Promise; setPassword(service: string, account: string, password: string): Promise; @@ -116,16 +136,23 @@ interface IKeytarModule { findPassword(service: string): Promise; } -export class KeytarNodeModuleFactory implements INodeModuleFactory { +class KeytarNodeModuleFactory implements INodeModuleFactory { public readonly nodeModuleName: string = 'keytar'; private alternativeNames: Set | undefined; private _impl: IKeytarModule; - constructor(mainThreadKeytar: MainThreadKeytarShape, environment: IEnvironment) { + constructor( + @IExtHostRpcService rpcService: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + + ) { + const { environment } = initData; + const mainThreadKeytar = rpcService.getProxy(MainContext.MainThreadKeytar); + if (environment.appRoot) { let appRoot = environment.appRoot.fsPath; - if (process.platform === 'win32') { + if (platform === 'win32') { appRoot = appRoot.replace(/\\/g, '/'); } if (appRoot[appRoot.length - 1] === '/') { @@ -151,11 +178,11 @@ export class KeytarNodeModuleFactory implements INodeModuleFactory { }; } - public load(request: string, parent: { filename: string; }): any { + public load(_request: string, _parent: URI): any { return this._impl; } - public alternaiveModuleName(name: string): string | undefined { + public alternativeModuleName(name: string): string | undefined { const length = name.length; // We need at least something like: `?/keytar` which requires // more than 7 characters. @@ -173,6 +200,11 @@ export class KeytarNodeModuleFactory implements INodeModuleFactory { } } +//#endregion + + +//#region --- opn/open-module + interface OpenOptions { wait: boolean; app: string | string[]; @@ -186,15 +218,23 @@ interface IOpenModule { (target: string, options?: OpenOptions): Thenable; } -export class OpenNodeModuleFactory implements INodeModuleFactory { +class OpenNodeModuleFactory implements INodeModuleFactory { public readonly nodeModuleName: string[] = ['open', 'opn']; private _extensionId: string | undefined; private _original?: IOriginalOpen; private _impl: IOpenModule; + private _mainThreadTelemetry: MainThreadTelemetryShape; + + constructor( + private readonly _extensionPaths: TernarySearchTree, + @IExtHostRpcService rpcService: IExtHostRpcService, + ) { + + this._mainThreadTelemetry = rpcService.getProxy(MainContext.MainThreadTelemetry); + const mainThreadWindow = rpcService.getProxy(MainContext.MainThreadWindow); - constructor(mainThreadWindow: MainThreadWindowShape, private _mainThreadTelemerty: MainThreadTelemetryShape, private readonly _extensionPaths: TernarySearchTree) { this._impl = (target, options) => { const uri: URI = URI.parse(target); // If we have options use the original method. @@ -210,15 +250,15 @@ export class OpenNodeModuleFactory implements INodeModuleFactory { }; } - public load(request: string, parent: { filename: string; }, isMain: any, original: LoadFunction): any { + public load(request: string, parent: URI, isMain: any, original: LoadFunction): any { // get extension id from filename and api for extension - const extension = this._extensionPaths.findSubstr(URI.file(parent.filename).fsPath); + const extension = this._extensionPaths.findSubstr(parent.fsPath); if (extension) { this._extensionId = extension.identifier.value; this.sendShimmingTelemetry(); } - this._original = original(request, parent, isMain); + this._original = original(request, { filename: parent.fsPath }, isMain); return this._impl; } @@ -234,7 +274,7 @@ export class OpenNodeModuleFactory implements INodeModuleFactory { type ShimmingOpenClassification = { extension: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; - this._mainThreadTelemerty.$publicLog2<{ extension: string }, ShimmingOpenClassification>('shimming.open', { extension: this._extensionId }); + this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingOpenClassification>('shimming.open', { extension: this._extensionId }); } private sendNoForwardTelemetry(): void { @@ -244,6 +284,8 @@ export class OpenNodeModuleFactory implements INodeModuleFactory { type ShimmingOpenCallNoForwardClassification = { extension: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; - this._mainThreadTelemerty.$publicLog2<{ extension: string }, ShimmingOpenCallNoForwardClassification>('shimming.open.call.noForward', { extension: this._extensionId }); + this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingOpenCallNoForwardClassification>('shimming.open.call.noForward', { extension: this._extensionId }); } } + +//#endregion diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 9dc99da82c..98902993a7 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -55,10 +55,21 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { private commands: ExtHostCommands, private logService: ILogService ) { + + function isTreeViewItemHandleArg(arg: any): boolean { + return arg && arg.$treeViewId && arg.$treeItemHandle; + } commands.registerArgumentProcessor({ processArgument: arg => { - if (arg && arg.$treeViewId && arg.$treeItemHandle) { + if (isTreeViewItemHandleArg(arg)) { return this.convertArgument(arg); + } else if (Array.isArray(arg) && (arg.length > 0)) { + return arg.map(item => { + if (isTreeViewItemHandleArg(item)) { + return this.convertArgument(item); + } + return item; + }); } return arg; } @@ -187,13 +198,10 @@ export class ExtHostTreeView extends Disposable { private refreshPromise: Promise = Promise.resolve(); private refreshQueue: Promise = Promise.resolve(); - constructor(private viewId: string, options: vscode.TreeViewOptions, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { + constructor(private viewId: string, options: vscode.TreeViewOptions2, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { super(); this.dataProvider = options.treeDataProvider; - // {{SQL CARBON EDIT}} - if (this.proxy) { - this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll }); - } + this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany }); if (this.dataProvider.onDidChangeTreeData) { this._register(this.dataProvider.onDidChangeTreeData(element => this._onDidChangeData.fire({ message: false, element }))); } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 80d7ccaf1e..f3e9aae4d4 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl'; -import { NodeModuleRequireInterceptor, VSCodeNodeModuleFactory, KeytarNodeModuleFactory, OpenNodeModuleFactory } from 'vs/workbench/api/node/extHostRequireInterceptor'; +import { createApiFactoryAndRegisterActors } from 'sql/workbench/api/common/sqlExtHost.api.impl'; // {{SQL CARBON EDIT}} replace with ours +import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; import { MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator'; import { connectProxyResolver } from 'vs/workbench/services/extensions/node/proxyResolver'; @@ -14,15 +14,33 @@ import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { createAdsApiFactory } from 'sql/workbench/api/common/sqlExtHost.api.impl'; // {{SQL CARBON EDIT}} use our extension initalizer -import { AzdataNodeModuleFactory, SqlopsNodeModuleFactory } from 'sql/workbench/api/node/extHostRequireInterceptor'; // {{SQL CARBON EDIT}} use our extension initalizer +class NodeModuleRequireInterceptor extends RequireInterceptor { + + protected _installInterceptor(): void { + const that = this; + const node_module = require.__$__nodeRequire('module'); + const original = node_module._load; + node_module._load = function load(request: string, parent: { filename: string; }, isMain: any) { + for (let alternativeModuleName of that._alternatives) { + let alternative = alternativeModuleName(request); + if (alternative) { + request = alternative; + break; + } + } + if (!that._factories.has(request)) { + return original.apply(this, arguments); + } + return that._factories.get(request)!.load(request, URI.file(parent.filename), isMain, original); + }; + } +} export class ExtHostExtensionService extends AbstractExtHostExtensionService { protected async _beforeAlmostReadyToRunExtensions(): Promise { // initialize API and register actors const extensionApiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors); - const adsExtensionApiFactory = this._instaService.invokeFunction(createAdsApiFactory); // {{SQL CARBON EDIT}} // add factory // Register Download command this._instaService.createInstance(ExtHostDownloadService); @@ -34,21 +52,11 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { } // Module loading tricks - const configProvider = await this._extHostConfiguration.getConfigProvider(); - const extensionPaths = await this.getExtensionPathIndex(); - NodeModuleRequireInterceptor.INSTANCE.register(new AzdataNodeModuleFactory(adsExtensionApiFactory.azdata, extensionPaths)); // {{SQL CARBON EDIT}} // add node module - NodeModuleRequireInterceptor.INSTANCE.register(new SqlopsNodeModuleFactory(adsExtensionApiFactory.sqlops, extensionPaths)); // {{SQL CARBON EDIT}} // add node module - NodeModuleRequireInterceptor.INSTANCE.register(new VSCodeNodeModuleFactory(extensionApiFactory, extensionPaths, this._registry, configProvider)); - NodeModuleRequireInterceptor.INSTANCE.register(new KeytarNodeModuleFactory(this._extHostContext.getProxy(MainContext.MainThreadKeytar), this._initData.environment)); - if (this._initData.remote.isRemote) { - NodeModuleRequireInterceptor.INSTANCE.register(new OpenNodeModuleFactory( - this._extHostContext.getProxy(MainContext.MainThreadWindow), - this._extHostContext.getProxy(MainContext.MainThreadTelemetry), - extensionPaths - )); - } + const interceptor = this._instaService.createInstance(NodeModuleRequireInterceptor, extensionApiFactory, this._registry); + await interceptor.install(); // Do this when extension service exists, but extensions are not being activated yet. + const configProvider = await this._extHostConfiguration.getConfigProvider(); await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy); // Use IPC messages to forward console-calls, note that the console is diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index a47208a3da..410cb06627 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -444,6 +444,14 @@ export class ExtHostTask implements ExtHostTaskShape { if (dto === undefined) { return Promise.reject(new Error('Task is not valid')); } + + // If this task is a custom execution, then we need to save it away + // in the provided custom execution map that is cleaned up after the + // task is executed. + if (CustomExecution2DTO.is(dto.execution)) { + await this.addCustomExecution2(dto, task); + } + return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task)); } } @@ -529,11 +537,6 @@ export class ExtHostTask implements ExtHostTaskShape { return Promise.reject(new Error('no handler found')); } - // For custom execution tasks, we need to store the execution objects locally - // since we obviously cannot send callback functions through the proxy. - // So, clear out any existing ones. - this._providedCustomExecutions2.clear(); - // Set up a list of task ID promises that we can wait on // before returning the provided tasks. The ensures that // our task IDs are calculated for any custom execution tasks. @@ -695,5 +698,17 @@ export class ExtHostTask implements ExtHostTaskShape { if (extensionCallback2) { this._activeCustomExecutions2.delete(execution.id); } + + const lastCustomExecution = this._providedCustomExecutions2.get(execution.id); + // Technically we don't really need to do this, however, if an extension + // is executing a task through "executeTask" over and over again + // with different properties in the task definition, then this list + // could grow indefinitely, something we don't want. + this._providedCustomExecutions2.clear(); + // We do still need to hang on to the last custom execution so that the + // Rerun Task command doesn't choke when it tries to rerun a custom execution + if (lastCustomExecution) { + this._providedCustomExecutions2.set(execution.id, lastCustomExecution); + } } } diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index 0ad4982620..c138c1591c 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -3,118 +3,152 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createApiFactoryAndRegisterActors, IExtensionApiFactory } from 'vs/workbench/api/common/extHost.api.impl'; +import { createApiFactoryAndRegisterActors } from 'sql/workbench/api/common/sqlExtHost.api.impl'; // {{SQL CARBON EDIT}} replace with ours import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator'; import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { endsWith, startsWith } from 'vs/base/common/strings'; -import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; -import * as vscode from 'vscode'; -import { TernarySearchTree } from 'vs/base/common/map'; -import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; +import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; -class ApiInstances { +class ExportsTrap { - private readonly _apiInstances = new Map(); + static readonly Instance = new ExportsTrap(); - constructor( - private readonly _apiFactory: IExtensionApiFactory, - private readonly _extensionPaths: TernarySearchTree, - private readonly _extensionRegistry: ExtensionDescriptionRegistry, - private readonly _configProvider: ExtHostConfigProvider, - ) { - // + private readonly _names: string[] = []; + private readonly _exports = new Map(); + + private constructor() { + + const exportsProxy = new Proxy({}, { + set: (target: any, p: PropertyKey, value: any) => { + // store in target + target[p] = value; + // store in named-bucket + const name = this._names[this._names.length - 1]; + this._exports.get(name)![p] = value; + return true; + } + }); + + + const moduleProxy = new Proxy({}, { + + get: (target: any, p: PropertyKey) => { + if (p === 'exports') { + return exportsProxy; + } + + return target[p]; + }, + + set: (target: any, p: PropertyKey, value: any) => { + // store in target + target[p] = value; + + // override bucket + if (p === 'exports') { + const name = this._names[this._names.length - 1]; + this._exports.set(name, value); + } + return true; + } + }); + + (self).exports = exportsProxy; + (self).module = moduleProxy; } - get(modulePath: string): typeof vscode { - const extension = this._extensionPaths.findSubstr(modulePath) || nullExtensionDescription; - const id = ExtensionIdentifier.toKey(extension.identifier); + add(name: string) { + this._exports.set(name, Object.create(null)); + this._names.push(name); - let apiInstance = this._apiInstances.get(id); - if (!apiInstance) { - apiInstance = this._apiFactory(extension, this._extensionRegistry, this._configProvider); - this._apiInstances.set(id, apiInstance); + return { + claim: () => { + const result = this._exports.get(name); + this._exports.delete(name); + this._names.pop(); + return result; + } + }; + } +} + +class WorkerRequireInterceptor extends RequireInterceptor { + + _installInterceptor() { } + + getModule(request: string, parent: URI): undefined | any { + for (let alternativeModuleName of this._alternatives) { + let alternative = alternativeModuleName(request); + if (alternative) { + request = alternative; + break; + } } - return apiInstance; + + if (this._factories.has(request)) { + return this._factories.get(request)!.load(request, parent, false, () => { throw new Error(); }); + } + return undefined; } } export class ExtHostExtensionService extends AbstractExtHostExtensionService { - private _apiInstances?: ApiInstances; + private _fakeModules: WorkerRequireInterceptor; protected async _beforeAlmostReadyToRunExtensions(): Promise { // initialize API and register actors const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors); - const configProvider = await this._extHostConfiguration.getConfigProvider(); - const extensionPath = await this.getExtensionPathIndex(); - this._apiInstances = new ApiInstances(apiFactory, extensionPath, this._registry, configProvider); + this._fakeModules = this._instaService.createInstance(WorkerRequireInterceptor, apiFactory, this._registry); + await this._fakeModules.install(); } protected _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { - - interface FakeCommonJSSelf { - module?: object; - exports?: object; - require?: (module: string) => any; - window?: object; - __dirname: never; - __filename: never; - } - - // FAKE commonjs world that only collects exports - const patchSelf: FakeCommonJSSelf = self; - patchSelf.window = self; // <- that's improper but might help extensions that aren't authored correctly + (self).window = self; // <- that's improper but might help extensions that aren't authored correctly // FAKE require function that only works for the vscode-module const moduleStack: URI[] = []; - patchSelf.require = (mod: string) => { + (self).require = (mod: string) => { + const parent = moduleStack[moduleStack.length - 1]; - if (mod === 'vscode') { - return this._apiInstances!.get(parent.fsPath); + const result = this._fakeModules.getModule(mod, parent); + + if (result !== undefined) { + return result; } + if (!startsWith(mod, '.')) { throw new Error(`Cannot load module '${mod}'`); } - const exports = Object.create(null); - patchSelf.module = { exports }; - patchSelf.exports = exports; - const next = joinPath(parent, '..', ensureSuffix(mod, '.js')); moduleStack.push(next); + const trap = ExportsTrap.Instance.add(next.toString()); importScripts(asDomUri(next).toString(true)); moduleStack.pop(); - return exports; + return trap.claim(); }; try { activationTimesBuilder.codeLoadingStart(); - - const exports = Object.create(null); - patchSelf.module = { exports }; - patchSelf.exports = exports; - module = module.with({ path: ensureSuffix(module.path, '.js') }); moduleStack.push(module); - + const trap = ExportsTrap.Instance.add(module.toString()); importScripts(asDomUri(module).toString(true)); moduleStack.pop(); + return Promise.resolve(trap.claim()); } finally { activationTimesBuilder.codeLoadingStop(); } - - return Promise.resolve(exports); } - async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise { + async $setRemoteEnvironment(_env: { [key: string]: string | null }): Promise { throw new Error('Not supported'); } } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 7d1ab39a99..6979c02fbc 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -164,9 +164,11 @@ export class CustomTreeView extends Disposable implements ITreeView { private domNode!: HTMLElement; private treeContainer!: HTMLElement; private _messageValue: string | undefined; + private _canSelectMany: boolean = false; private messageElement!: HTMLDivElement; private tree: WorkbenchAsyncDataTree | undefined; private treeLabels: ResourceLabels | undefined; + private root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; private menus: TitleMenus; @@ -253,6 +255,14 @@ export class CustomTreeView extends Disposable implements ITreeView { this.updateMessage(); } + get canSelectMany(): boolean { + return this._canSelectMany; + } + + set canSelectMany(canSelectMany: boolean) { + this._canSelectMany = canSelectMany; + } + get hasIconForParentNode(): boolean { return this._hasIconForParentNode; } @@ -372,12 +382,14 @@ export class CustomTreeView extends Disposable implements ITreeView { collapseByDefault: (e: ITreeItem): boolean => { return e.collapsibleState !== TreeItemCollapsibleState.Expanded; }, - multipleSelectionSupport: false - })); + multipleSelectionSupport: this.canSelectMany, + }) as WorkbenchAsyncDataTree); aligner.tree = this.tree; + const actionRunner = new MultipleSelectionActionRunner(() => this.tree!.getSelection()); + renderer.actionRunner = actionRunner; this.tree.contextKeyService.createKey(this.id, true); - this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner))); this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements))); this._register(this.tree.onDidChangeCollapseState(e => { if (!e.node.element) { @@ -406,7 +418,7 @@ export class CustomTreeView extends Disposable implements ITreeView { })); } - private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent): void { + private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent, actionRunner: MultipleSelectionActionRunner): void { const node: ITreeItem | null = treeEvent.element; if (node === null) { return; @@ -442,7 +454,7 @@ export class CustomTreeView extends Disposable implements ITreeView { getActionsContext: () => ({ $treeViewId: this.id, $treeItemHandle: node.handle }), - actionRunner: new MultipleSelectionActionRunner(() => this.tree!.getSelection()) + actionRunner }); } @@ -686,6 +698,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }); + templateData.actionBar.context = { $treeViewId: this.treeViewId, $treeItemHandle: node.handle }; templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); + if (this._actionRunner) { + templateData.actionBar.actionRunner = this._actionRunner; + } this.setAlignment(templateData.container, node); templateData.elementDisposable = (this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node))); } @@ -822,23 +843,23 @@ class Aligner extends Disposable { class MultipleSelectionActionRunner extends ActionRunner { - constructor(private getSelectedResources: (() => any[])) { + constructor(private getSelectedResources: (() => ITreeItem[])) { super(); } - runAction(action: IAction, context: any): Promise { - if (action instanceof MenuItemAction) { - const selection = this.getSelectedResources(); - const filteredSelection = selection.filter(s => s !== context); - - if (selection.length === filteredSelection.length || selection.length === 1) { - return action.run(context); - } - - return action.run(context, ...filteredSelection); + runAction(action: IAction, context: TreeViewItemHandleArg): Promise { + const selection = this.getSelectedResources(); + let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; + if (selection.length > 1) { + selectionHandleArgs = []; + selection.forEach(selected => { + if (selected.handle !== context.$treeItemHandle) { + selectionHandleArgs!.push({ $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle }); + } + }); } - return super.runAction(action, context); + return action.run(...[context, selectionHandleArgs]); } } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 144d8c51bb..6bee433e83 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -14,7 +14,7 @@ import { Workbench } from 'vs/workbench/browser/workbench'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService, IProductConfiguration } from 'vs/platform/product/common/product'; import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentServiceImpl'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -33,7 +33,6 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/browser/signService'; import { hash } from 'vs/base/common/hash'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; -import { ProductService } from 'vs/platform/product/browser/productService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { BACKUPS } from 'vs/platform/environment/common/environment'; import { joinPath } from 'vs/base/common/resources'; @@ -41,6 +40,7 @@ import { BrowserStorageService } from 'vs/platform/storage/browser/storageServic import { IStorageService } from 'vs/platform/storage/common/storage'; import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService'; import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; +import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; class CodeRendererMain extends Disposable { @@ -83,6 +83,11 @@ class CodeRendererMain extends Disposable { })); this._register(workbench.onShutdown(() => this.dispose())); + // Driver + if (this.configuration.driver) { + registerWindowDriver().then(d => this._register(d)); + } + // Startup workbench.startup(); } @@ -117,16 +122,11 @@ class CodeRendererMain extends Disposable { const payload = await this.resolveWorkspaceInitializationPayload(); // Environment - const environmentService = new BrowserWorkbenchEnvironmentService({ - workspaceId: payload.id, - remoteAuthority: this.configuration.remoteAuthority, - webviewEndpoint: this.configuration.webviewEndpoint, - connectionToken: this.configuration.connectionToken - }); + const environmentService = new BrowserWorkbenchEnvironmentService(payload.id, this.configuration); serviceCollection.set(IWorkbenchEnvironmentService, environmentService); // Product - const productService = new ProductService(); + const productService = this.createProductService(); serviceCollection.set(IProductService, productService); // Remote @@ -192,6 +192,18 @@ class CodeRendererMain extends Disposable { return { serviceCollection, logService, storageService: services[1] }; } + private createProductService(): IProductService { + const element = document.getElementById('vscode-remote-product-configuration'); + const productConfiguration: IProductConfiguration = { + ...element ? JSON.parse(element.getAttribute('data-settings')!) : { + version: '1.38.0-unknown', + nameLong: 'Unknown', + extensionAllowedProposedApi: [], + }, ...{ urlProtocol: '', enableTelemetry: false } + }; + return { _serviceBrand: undefined, ...productConfiguration }; + } + private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise { const storageService = new BrowserStorageService(environmentService, fileService); diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index d6132b940f..79a5aec27e 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -422,6 +422,8 @@ export class SimpleWindowService extends Disposable implements IWindowService { } closeWindow(): Promise { + window.close(); + return Promise.resolve(); } @@ -751,13 +753,13 @@ export class SimpleWindowsService implements IWindowsService { async openAboutDialog(): Promise { const detail = localize('aboutDetail', "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", - this.productService.productConfiguration.version || 'Unknown', - this.productService.productConfiguration.commit || 'Unknown', - this.productService.productConfiguration.date || 'Unknown', + this.productService.version || 'Unknown', + this.productService.commit || 'Unknown', + this.productService.date || 'Unknown', navigator.userAgent ); - const result = await this.dialogService.show(Severity.Info, this.productService.productConfiguration.nameLong, [localize('copy', "Copy"), localize('ok', "OK")], { detail }); + const result = await this.dialogService.show(Severity.Info, this.productService.nameLong, [localize('copy', "Copy"), localize('ok', "OK")], { detail }); if (result === 0) { this.clipboardService.writeText(detail); @@ -857,7 +859,7 @@ registerSingleton(ITunnelService, SimpleTunnelService); //#region workspace stats -class WorkspaceStatsService implements IWorkspaceStatsService { +class SimpleWorkspaceStatsService implements IWorkspaceStatsService { _serviceBrand: any; @@ -875,6 +877,6 @@ class WorkspaceStatsService implements IWorkspaceStatsService { } -registerSingleton(IWorkspaceStatsService, WorkspaceStatsService); +registerSingleton(IWorkspaceStatsService, SimpleWorkspaceStatsService); //#endregion diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 78308d5ba6..8b190469c0 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -310,6 +310,8 @@ export interface ITreeView extends IDisposable { showCollapseAllAction: boolean; + canSelectMany: boolean; + message?: string; readonly visible: boolean; diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 3fa388fd0f..222b389431 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -29,13 +29,14 @@ export abstract class SimpleFindWidget extends Widget { private readonly _findInput: FindInput; private readonly _domNode: HTMLElement; private readonly _innerDomNode: HTMLElement; - private _isVisible: boolean = false; private readonly _focusTracker: dom.IFocusTracker; private readonly _findInputFocusTracker: dom.IFocusTracker; private readonly _updateHistoryDelayer: Delayer; - private prevBtn: SimpleButton; - private nextBtn: SimpleButton; - private foundMatch: boolean; + private readonly prevBtn: SimpleButton; + private readonly nextBtn: SimpleButton; + + private _isVisible: boolean = false; + private foundMatch: boolean = false; constructor( @IContextViewService private readonly _contextViewService: IContextViewService, diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 47822abd71..a1fb2c3511 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -144,7 +144,8 @@ export class CallStackView extends ViewletPanel { return nls.localize('showMoreStackFrames2', "Show More Stack Frames"); } - } + }, + expandOnlyOnTwistieClick: true }); this.tree.setInput(this.debugService.getModel()).then(undefined, onUnexpectedError); @@ -576,7 +577,7 @@ function isDebugModel(obj: any): obj is IDebugModel { } function isDebugSession(obj: any): obj is IDebugSession { - return typeof obj.getAllThreads === 'function'; + return obj && typeof obj.getAllThreads === 'function'; } function isDeemphasized(frame: IStackFrame): boolean { @@ -608,6 +609,10 @@ class CallStackDataSource implements IAsyncDataSource s.parentSession === element); const threads: CallStackItem[] = element.getAllThreads(); + if (threads.length === 1 && childSessions.length === 0) { + // Do not show thread when there is only one to be compact. + return this.getThreadChildren(threads[0]); + } return Promise.resolve(threads.concat(childSessions)); } else { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index db3e8ede58..1a48fdf264 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -35,19 +35,23 @@ class ToggleBreakpointAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const debugService = accessor.get(IDebugService); - - const position = editor.getPosition(); - if (editor.hasModel() && position) { + if (editor.hasModel()) { + const debugService = accessor.get(IDebugService); const modelUri = editor.getModel().uri; - const bps = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }); + const canSet = debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel()); + // Does not account for multi line selections, Set to remove multiple cursor on the same line + const lineNumbers = [...new Set(editor.getSelections().map(s => s.getPosition().lineNumber))]; - if (bps.length) { - return Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId()))); - } - if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber }], 'debugEditorActions.toggleBreakpointAction'); - } + return Promise.all(lineNumbers.map(line => { + const bps = debugService.getModel().getBreakpoints({ lineNumber: line, uri: modelUri }); + if (bps.length) { + return Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId()))); + } else if (canSet) { + return (debugService.addBreakpoints(modelUri, [{ lineNumber: line }], 'debugEditorActions.toggleBreakpointAction')); + } else { + return Promise.resolve([]); + } + })); } return Promise.resolve(); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 75a80f0ee5..b0572dd077 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -175,7 +175,7 @@ export class DebugSession implements IDebugSession { return this.raw!.initialize({ clientID: 'vscode', - clientName: this.productService.productConfiguration.nameLong, + clientName: this.productService.nameLong, adapterID: this.configuration.type, pathFormat: 'path', linesStartAt1: true, diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 51a8d38b4a..edc3aea0c2 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -17,7 +17,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - //@IWindowService windowService: IWindowService, + // @IWindowService windowService: IWindowService, // TODO@weinand TODO@isidorn cyclic dependency? @IEnvironmentService environmentService: IEnvironmentService ) { const connection = remoteAgentService.getConnection(); @@ -30,13 +30,11 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { this._register(this.onReload(event => { if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { - //windowService.reloadWindow(); window.location.reload(); } })); this._register(this.onClose(event => { if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { - //this._windowService.closeWindow(); window.close(); } })); diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 1d8cc31296..3e6771d145 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -16,6 +16,7 @@ import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; +import { env as processEnv } from 'vs/base/common/process'; /** * This interface represents a single command line argument split into a "prefix" and a "path" half. @@ -594,10 +595,7 @@ export class RawDebugSession { let env: IProcessEnvironment = {}; if (vscodeArgs.env) { // merge environment variables into a copy of the process.env - if (typeof process === 'object' && process.env) { - env = objects.mixin(env, process.env); - } - env = objects.mixin(env, vscodeArgs.env); + env = objects.mixin(processEnv, vscodeArgs.env); // and delete some if necessary Object.keys(env).filter(k => env[k] === null).forEach(key => delete env[key]); } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index e86b181af2..d2390c4bcb 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -227,14 +227,14 @@ export class Expression extends ExpressionContainer implements IExpression { const response = await session.evaluate(this.name, stackFrame ? stackFrame.frameId : undefined, context); this.available = !!(response && response.body); if (response && response.body) { - this.value = response.body.result; + this.value = response.body.result || ''; this.reference = response.body.variablesReference; this.namedVariables = response.body.namedVariables; this.indexedVariables = response.body.indexedVariables; this.type = response.body.type || this.type; } } catch (e) { - this.value = e.message; + this.value = e.message || ''; this.available = false; this.reference = 0; } @@ -256,7 +256,7 @@ export class Variable extends ExpressionContainer implements IExpression { reference: number | undefined, public name: string, public evaluateName: string | undefined, - value: string, + value: string | undefined, namedVariables: number | undefined, indexedVariables: number | undefined, public presentationHint: DebugProtocol.VariablePresentationHint | undefined, @@ -265,7 +265,7 @@ export class Variable extends ExpressionContainer implements IExpression { startOfVariables = 0 ) { super(session, reference, `variable:${parent.getId()}:${name}`, namedVariables, indexedVariables, startOfVariables); - this.value = value; + this.value = value || ''; } async setVariable(value: string): Promise { @@ -276,7 +276,7 @@ export class Variable extends ExpressionContainer implements IExpression { try { const response = await this.session.setVariable((this.parent).reference, this.name, value); if (response && response.body) { - this.value = response.body.value; + this.value = response.body.value || ''; this.type = response.body.type || this.type; this.reference = response.body.variablesReference; this.namedVariables = response.body.namedVariables; diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index d88fffd288..6d3cca0245 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -170,10 +170,10 @@ export class ExperimentService extends Disposable implements IExperimentService } protected getExperiments(): Promise { - if (!this.productService.productConfiguration.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) { + if (!this.productService.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) { return Promise.resolve([]); } - return this.requestService.request({ type: 'GET', url: this.productService.productConfiguration.experimentsUrl }, CancellationToken.None).then(context => { + return this.requestService.request({ type: 'GET', url: this.productService.experimentsUrl }, CancellationToken.None).then(context => { if (context.res.statusCode !== 200) { return Promise.resolve(null); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 14ff396630..efe2cbbf0f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -54,6 +54,7 @@ import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/common/we import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderDashboardContributions } from 'sql/workbench/parts/extensions/browser/contributionRenders'; // {{SQL CARBON EDIT}} import { generateUuid } from 'vs/base/common/uuid'; +import { platform } from 'vs/base/common/process'; function removeEmbeddedSVGs(documentContent: string): string { const newDocument = new DOMParser().parseFromString(documentContent, 'text/html'); @@ -582,9 +583,7 @@ export class ExtensionEditor extends BaseEditor { { enableFindWidget: true, }, - { - svgWhiteList: this.extensionsWorkbenchService.allowedBadgeProviders, - }); + {}); webviewElement.mountTo(template.content); this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus())); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, webviewElement); @@ -1287,7 +1286,7 @@ export class ExtensionEditor extends BaseEditor { private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | null { let key: string | undefined; - switch (process.platform) { + switch (platform) { case 'win32': key = rawKeyBinding.win; break; case 'linux': key = rawKeyBinding.linux; break; case 'darwin': key = rawKeyBinding.mac; break; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts similarity index 95% rename from src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts rename to src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts index ddc6e307d7..beb604b3ae 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts @@ -27,9 +27,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet, IExtensionsWorkbenchService, EXTENSIONS_CONFIG, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import * as os from 'os'; import { flatten, distinct, shuffle, coalesce } from 'vs/base/common/arrays'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { guessMimeTypes, MIME_UNKNOWN } from 'vs/base/common/mime'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; @@ -49,6 +47,9 @@ import { timeout } from 'vs/base/common/async'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; // {{SQL CARBON EDIT}} import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; // {{SQL CARBON EDIT}} import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { Platform, setImmediate } from 'vs/base/common/platform'; +import { platform, env as processEnv } from 'vs/base/common/process'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); @@ -106,7 +107,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IExtensionService private readonly extensionService: IExtensionService, @IRequestService private readonly requestService: IRequestService, @IViewletService private readonly viewletService: IViewletService, @@ -126,8 +127,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return; } - if (this.productService.productConfiguration.extensionsGallery && this.productService.productConfiguration.extensionsGallery.recommendationsUrl) { - this._extensionsRecommendationsUrl = this.productService.productConfiguration.extensionsGallery.recommendationsUrl; + if (this.productService.extensionsGallery && this.productService.extensionsGallery.recommendationsUrl) { + this._extensionsRecommendationsUrl = this.productService.extensionsGallery.recommendationsUrl; } this.sessionSeed = +new Date(); @@ -256,7 +257,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } getKeymapRecommendations(): IExtensionRecommendation[] { - return (this.productService.productConfiguration.keymapExtensionTips || []) + return (this.productService.keymapExtensionTips || []) .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)) .map(extensionId => ({ extensionId, sources: ['application'] })); } @@ -613,10 +614,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return Object.keys(this._fileBasedRecommendations) .sort((a, b) => { if (this._fileBasedRecommendations[a].recommendedTime === this._fileBasedRecommendations[b].recommendedTime) { - if (!this.productService.productConfiguration.extensionImportantTips || caseInsensitiveGet(this.productService.productConfiguration.extensionImportantTips, a)) { + if (!this.productService.extensionImportantTips || caseInsensitiveGet(this.productService.extensionImportantTips, a)) { return -1; } - if (caseInsensitiveGet(this.productService.productConfiguration.extensionImportantTips, b)) { + if (caseInsensitiveGet(this.productService.extensionImportantTips, b)) { return 1; } } @@ -627,13 +628,13 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } /** - * Parse all file based recommendations from this.productService.productConfiguration.extensionTips - * Retire existing recommendations if they are older than a week or are not part of this.productService.productConfiguration.extensionTips anymore + * Parse all file based recommendations from this.productService.extensionTips + * Retire existing recommendations if they are older than a week or are not part of this.productService.extensionTips anymore */ private fetchFileBasedRecommendations() { - const extensionTips = this.productService.productConfiguration.extensionTips; + const extensionTips = this.productService.extensionTips; // {{SQL CARBON EDIT}} - this._recommendations = this.productService.productConfiguration.recommendedExtensions; + this._recommendations = this.productService.recommendedExtensions; if (!extensionTips) { return; } @@ -650,7 +651,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } }); - forEach(this.productService.productConfiguration.extensionImportantTips, entry => { + forEach(this.productService.extensionImportantTips, entry => { let { key: id, value } = entry; const { pattern } = value; let ids = this._availableRecommendations[pattern]; @@ -712,7 +713,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe let { key: pattern, value: ids } = entry; if (match(pattern, model.uri.toString())) { for (let id of ids) { - if (caseInsensitiveGet(this.productService.productConfiguration.extensionImportantTips, id)) { + if (caseInsensitiveGet(this.productService.extensionImportantTips, id)) { recommendationsToSuggest.push(id); } const filedBasedRecommendation = this._fileBasedRecommendations[id.toLowerCase()] || { recommendedTime: now, sources: [] }; @@ -766,7 +767,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } const id = recommendationsToSuggest[0]; - const entry = caseInsensitiveGet(this.productService.productConfiguration.extensionImportantTips, id); + const entry = caseInsensitiveGet(this.productService.extensionImportantTips, id); if (!entry) { return false; } @@ -992,13 +993,15 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } /** - * If user has any of the tools listed in this.productService.productConfiguration.exeBasedExtensionTips, fetch corresponding recommendations + * If user has any of the tools listed in this.productService.exeBasedExtensionTips, fetch corresponding recommendations */ - private fetchExecutableRecommendations(important: boolean): Promise { - const homeDir = os.homedir(); - let foundExecutables: Set = new Set(); + private async fetchExecutableRecommendations(important: boolean): Promise { + if (Platform.Web) { + return; + } - let findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => { + const foundExecutables: Set = new Set(); + const findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => { return this.fileService.exists(URI.file(path)).then(exists => { if (exists && !foundExecutables.has(exeName)) { foundExecutables.add(exeName); @@ -1014,9 +1017,9 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); }; - let promises: Promise[] = []; + const promises: Promise[] = []; // Loop through recommended extensions - forEach(this.productService.productConfiguration.exeBasedExtensionTips, entry => { + forEach(this.productService.exeBasedExtensionTips, entry => { if (typeof entry.value !== 'object' || !Array.isArray(entry.value['recommendations'])) { return; } @@ -1024,24 +1027,24 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return; } const exeName = entry.key; - if (process.platform === 'win32') { + if (platform === 'win32') { let windowsPath = entry.value['windowsPath']; if (!windowsPath || typeof windowsPath !== 'string') { return; } - windowsPath = windowsPath.replace('%USERPROFILE%', process.env['USERPROFILE']!) - .replace('%ProgramFiles(x86)%', process.env['ProgramFiles(x86)']!) - .replace('%ProgramFiles%', process.env['ProgramFiles']!) - .replace('%APPDATA%', process.env['APPDATA']!) - .replace('%WINDIR%', process.env['WINDIR']!); + windowsPath = windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!) + .replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!) + .replace('%ProgramFiles%', processEnv['ProgramFiles']!) + .replace('%APPDATA%', processEnv['APPDATA']!) + .replace('%WINDIR%', processEnv['WINDIR']!); promises.push(findExecutable(exeName, entry.value, windowsPath)); } else { promises.push(findExecutable(exeName, entry.value, join('/usr/local/bin', exeName))); - promises.push(findExecutable(exeName, entry.value, join(homeDir, exeName))); + promises.push(findExecutable(exeName, entry.value, join(this.environmentService.userHome, exeName))); } }); - return Promise.all(promises).then(() => undefined); + await Promise.all(promises); } /** @@ -1155,7 +1158,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private isExtensionAllowedToBeRecommended(id: string): boolean { return this._allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1; } - // {{SQL CARBON EDIT}} promptRecommendedExtensionsByScenario(scenarioType: string): void { const storageKey = 'extensionAssistant/RecommendationsIgnore/' + scenarioType; @@ -1246,7 +1248,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return Promise.reject(new Error(localize('scenarioTypeUndefined', 'The scenario type for extension recommendations must be provided.'))); } - return Promise.resolve((this.productService.productConfiguration.recommendedExtensionsByScenario[scenarioType] || []) + return Promise.resolve((this.productService.recommendedExtensionsByScenario[scenarioType] || []) .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)) .map(extensionId => ({ extensionId, sources: ['application'] }))); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 4951a9a595..acf883c6df 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionsLabel, ExtensionsChannelId, PreferencesLabel, IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/contrib/output/common/output'; @@ -45,9 +45,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); +registerSingleton(IExtensionTipsService, ExtensionTipsService); Registry.as(OutputExtensions.OutputChannels) .registerChannel({ id: ExtensionsChannelId, label: ExtensionsLabel, log: false }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts new file mode 100644 index 0000000000..e749720db6 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; + +// Singletons +registerSingleton(IExtensionTipsService, ExtensionTipsService); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index a836fcc061..c03daece76 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -79,10 +79,10 @@ export function toExtensionDescription(local: ILocalExtension): IExtensionDescri const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error, instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService, productService: IProductService) => { - if (!extension || error.name === INSTALL_ERROR_INCOMPATIBLE || error.name === INSTALL_ERROR_MALICIOUS || !productService.productConfiguration.extensionsGallery) { + if (!extension || error.name === INSTALL_ERROR_INCOMPATIBLE || error.name === INSTALL_ERROR_MALICIOUS || !productService.extensionsGallery) { return Promise.reject(error); } else { - const downloadUrl = `${productService.productConfiguration.extensionsGallery.serviceUrl}/publishers/${extension.publisher}/vsextensions/${extension.name}/${extension.version}/vspackage`; + const downloadUrl = `${productService.extensionsGallery.serviceUrl}/publishers/${extension.publisher}/vsextensions/${extension.name}/${extension.version}/vspackage`; notificationService.prompt(Severity.Error, message, [{ label: localize('download', "Download Manually"), run: () => openerService.open(URI.parse(downloadUrl)).then(() => { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 4e411ce202..2914fce186 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -435,7 +435,7 @@ export class ExtensionsListView extends ViewletPanel { // {{SQL CARBON EDIT}} if (this.productService) { let promiseRecommendedExtensionsByScenario: Promise> | undefined; - Object.keys(this.productService.productConfiguration.recommendedExtensionsByScenario).forEach(scenarioType => { + Object.keys(this.productService.recommendedExtensionsByScenario).forEach(scenarioType => { let re = new RegExp('@' + scenarioType, 'i'); if (re.test(query.value)) { promiseRecommendedExtensionsByScenario = this.getRecommendedExtensionsByScenario(token, scenarioType); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index f178284e92..2650e05cfc 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -118,16 +118,16 @@ class Extension implements IExtension { } get url(): string | undefined { - if (!this.productService.productConfiguration.extensionsGallery || !this.gallery) { + if (!this.productService.extensionsGallery || !this.gallery) { return undefined; } - return `${this.productService.productConfiguration.extensionsGallery.itemUrl}?itemName=${this.publisher}.${this.name}`; + return `${this.productService.extensionsGallery.itemUrl}?itemName=${this.publisher}.${this.name}`; } // {{SQL CARBON EDIT}} get downloadPage(): string { - if (!this.productService.productConfiguration.extensionsGallery) { + if (!this.productService.extensionsGallery) { return null; } @@ -499,7 +499,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private readonly _onChange: Emitter = new Emitter(); get onChange(): Event { return this._onChange.event; } - private _extensionAllowedBadgeProviders: string[] | undefined; private installing: IExtension[] = []; constructor( @@ -631,7 +630,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension text = text.replace(extensionRegex, (m, ext) => { // Get curated keywords - const lookup = this.productService.productConfiguration.extensionKeywords || {}; + const lookup = this.productService.extensionKeywords || {}; const keywords = lookup[ext] || []; // Get mode name @@ -831,9 +830,9 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension // This is the execution path for install/update extension from marketplace. // Check both the vscode version and azure data studio version // The check is added here because we want to fail fast instead of downloading the VSIX and then fail. - if (gallery.properties.engine && (!isEngineValid(gallery.properties.engine, this.productService.productConfiguration.vscodeVersion) - || (gallery.properties.azDataEngine && !isEngineValid(gallery.properties.azDataEngine, this.productService.productConfiguration.version)))) { - return Promise.reject(new Error(nls.localize('incompatible2', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", extension.gallery!.identifier.id, this.productService.productConfiguration.version, gallery.version))); + if (gallery.properties.engine && (!isEngineValid(gallery.properties.engine, this.productService.vscodeVersion) + || (gallery.properties.azDataEngine && !isEngineValid(gallery.properties.azDataEngine, this.productService.version)))) { + return Promise.reject(new Error(nls.localize('incompatible2', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", extension.gallery!.identifier.id, this.productService.version, gallery.version))); } return this.installWithProgress(async () => { @@ -1074,13 +1073,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return changed; } - get allowedBadgeProviders(): string[] { - if (!this._extensionAllowedBadgeProviders) { - this._extensionAllowedBadgeProviders = (this.productService.productConfiguration.extensionAllowedBadgeProviders || []).map(s => s.toLowerCase()); - } - return this._extensionAllowedBadgeProviders; - } - private _activityCallBack: (() => void) | null = null; private updateActivity(): void { if ((this.localExtensions && this.localExtensions.local.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 35a6987037..64c389a232 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -93,7 +93,6 @@ export interface IExtensionsWorkbenchService { setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise; open(extension: IExtension, sideByside?: boolean): Promise; checkForUpdates(): Promise; - allowedBadgeProviders: string[]; } export const ConfigurationKey = 'extensions'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index aca125fd1a..6700e78f69 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -7,9 +7,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -25,7 +23,6 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; // Singletons -registerSingleton(IExtensionTipsService, ExtensionTipsService); registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); @@ -134,4 +131,4 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }, group: 'navigation', when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID)) -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index b07bcb735a..d99cf61728 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -16,7 +16,7 @@ import { import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 1f48f715f4..5f9cc135cd 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -15,14 +15,14 @@ import { DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestLifecycleService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestLifecycleService, TestSharedProcessService, productService } from 'vs/workbench/test/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; @@ -36,7 +36,6 @@ import { ConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensi import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { IURLService } from 'vs/platform/url/common/url'; -import product from 'vs/platform/product/node/product'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index c63e9f4a05..d23416f9e2 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -17,7 +17,7 @@ import { import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionTipsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index b2b2c388e9..dd802c3d54 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -17,7 +17,7 @@ import { import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index aa9d9e015b..76ad9b4801 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -73,8 +73,8 @@ export class FeedbackDropdown extends Dropdown { this.feedbackDelegate = options.feedbackService; this.maxFeedbackCharacters = this.feedbackDelegate.getCharacterLimit(this.sentiment); - if (productService.productConfiguration.sendASmile) { - this.requestFeatureLink = productService.productConfiguration.sendASmile.requestFeatureUrl; + if (productService.sendASmile) { + this.requestFeatureLink = productService.sendASmile.requestFeatureUrl; } this.integrityService.isPure().then(result => { diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 5b10eb3202..e725cab005 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -58,7 +58,7 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben ) { super(); - if (productService.productConfiguration.sendASmile) { + if (productService.sendASmile) { this.entry = this._register(statusbarService.addEntry(this.getStatusEntry(), 'status.feedback', localize('status.feedback', "Tweet Feedback"), StatusbarAlignment.RIGHT, -100 /* towards the end of the right hand side */)); CommandsRegistry.registerCommand('_feedback.open', () => this.toggleFeedback()); diff --git a/src/vs/workbench/contrib/files/browser/media/check-dark.svg b/src/vs/workbench/contrib/files/browser/media/check-dark.svg index 865cc83c34..51674695e1 100644 --- a/src/vs/workbench/contrib/files/browser/media/check-dark.svg +++ b/src/vs/workbench/contrib/files/browser/media/check-dark.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/files/browser/media/check-light.svg b/src/vs/workbench/contrib/files/browser/media/check-light.svg index e1a546660e..7b1da6d720 100644 --- a/src/vs/workbench/contrib/files/browser/media/check-light.svg +++ b/src/vs/workbench/contrib/files/browser/media/check-light.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 819c63858f..fe52291e9c 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -148,7 +148,7 @@ export class ExplorerItem { return this === this.root; } - static create(raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: URI[]): ExplorerItem { + static create(raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { const stat = new ExplorerItem(raw.resource, parent, raw.isDirectory, raw.isSymbolicLink, raw.isReadonly, raw.name, raw.mtime); // Recursively add children if present diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts index 7e0709ee2a..acfebcc568 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts @@ -13,7 +13,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; import { WorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issueService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IIssueService } from 'vs/platform/issue/common/issue'; +import { IIssueService } from 'vs/platform/issue/node/issue'; const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' }; const workbenchActionsRegistry = Registry.as(Extensions.WorkbenchActions); diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.ts index 89a700c4f5..9ea00e060c 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issue.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IssueReporterData } from 'vs/platform/issue/common/issue'; +import { IssueReporterData } from 'vs/platform/issue/node/issue'; export const IWorkbenchIssueService = createDecorator('workbenchIssueService'); diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts b/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts index 92ddd2b2f1..b04f8e3818 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts @@ -5,7 +5,7 @@ import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; -import { IssueType } from 'vs/platform/issue/common/issue'; +import { IssueType } from 'vs/platform/issue/node/issue'; import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; export class OpenProcessExplorer extends Action { diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts index db2754dd32..b4b63d7a14 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IssueReporterStyles, IIssueService, IssueReporterData, ProcessExplorerData, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; +import { IssueReporterStyles, IIssueService, IssueReporterData, ProcessExplorerData, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, listHighlightForeground, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; diff --git a/src/vs/workbench/contrib/preferences/browser/media/check-dark.svg b/src/vs/workbench/contrib/preferences/browser/media/check-dark.svg index 865cc83c34..51674695e1 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/check-dark.svg +++ b/src/vs/workbench/contrib/preferences/browser/media/check-dark.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/preferences/browser/media/check-light.svg b/src/vs/workbench/contrib/preferences/browser/media/check-light.svg index e1a546660e..7b1da6d720 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/check-light.svg +++ b/src/vs/workbench/contrib/preferences/browser/media/check-light.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index 31d3292bf7..93719b87ae 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -25,6 +25,7 @@ import { nullRange } from 'vs/workbench/services/preferences/common/preferencesM import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStringDictionary } from 'vs/base/common/collections'; import { IProductService } from 'vs/platform/product/common/product'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export interface IEndpointDetails { urlBase?: string; @@ -73,7 +74,7 @@ export class PreferencesSearchService extends Disposable implements IPreferences }; } else { return { - urlBase: this.productService.productConfiguration.settingsSearchUrl + urlBase: this.productService.settingsSearchUrl }; } } @@ -364,7 +365,7 @@ class RemoteSearchProvider implements ISearchProvider { const extensions = await this.installedExtensions; const filters = this.options.newExtensionsOnly ? [`diminish eq 'latest'`] : - this.getVersionFilters(extensions, this.productService.productConfiguration.settingsSearchBuildId); + this.getVersionFilters(extensions, this.productService.settingsSearchBuildId); const filterStr = filters .slice(filterPage * RemoteSearchProvider.MAX_REQUEST_FILTERS, (filterPage + 1) * RemoteSearchProvider.MAX_REQUEST_FILTERS) @@ -564,3 +565,5 @@ export class SettingMatches { }; } } + +registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); diff --git a/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts index 8ae8f52ebe..f24cb52551 100644 --- a/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts @@ -159,7 +159,7 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IExtensionService extensionService: IExtensionService, - @IWindowService windowSevice: IWindowService, + @IWindowService windowService: IWindowService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { super(); @@ -170,7 +170,7 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor } if (environmentService.configuration.remoteAuthority) { - windowSevice.reloadWindow(); // TODO@aeschli, workaround + windowService.reloadWindow(); // TODO@aeschli, workaround } else { extensionService.restartExtensionHost(); } diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index c367144aa5..2682bec785 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -386,6 +386,10 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { } private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { + if (!extension.description.enableProposedApi) { + return; + } + if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) { return; } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 489bee2678..b8db7a5b09 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -27,7 +27,7 @@ import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; import { ipcRenderer as ipc } from 'electron'; -import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProgressService, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; diff --git a/src/vs/workbench/contrib/search/browser/media/stop-dark.svg b/src/vs/workbench/contrib/search/browser/media/stop-dark.svg index 7e6319104d..890af29835 100644 --- a/src/vs/workbench/contrib/search/browser/media/stop-dark.svg +++ b/src/vs/workbench/contrib/search/browser/media/stop-dark.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/search/browser/media/stop-hc.svg b/src/vs/workbench/contrib/search/browser/media/stop-hc.svg index a879a194c7..1c88dfb60a 100644 --- a/src/vs/workbench/contrib/search/browser/media/stop-hc.svg +++ b/src/vs/workbench/contrib/search/browser/media/stop-hc.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/search/browser/media/stop-light.svg b/src/vs/workbench/contrib/search/browser/media/stop-light.svg index 10d05f5d8a..7e41aeff58 100644 --- a/src/vs/workbench/contrib/search/browser/media/stop-light.svg +++ b/src/vs/workbench/contrib/search/browser/media/stop-light.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts index 0ac0942568..c19d53b804 100644 --- a/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts @@ -14,6 +14,7 @@ import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'v import { IWorkspaceContextService, toWorkspaceFolder, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; +import { isWindows } from 'vs/base/common/platform'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; @@ -1032,7 +1033,7 @@ function getUri(...slashPathParts: string[]): uri { } function fixPath(...slashPathParts: string[]): string { - if (process.platform === 'win32' && slashPathParts.length && !slashPathParts[0].match(/^c:/i)) { + if (isWindows && slashPathParts.length && !slashPathParts[0].match(/^c:/i)) { slashPathParts.unshift('c:'); } diff --git a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts index 3309207177..c87c159415 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -18,6 +18,7 @@ import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchPro import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; +import * as process from 'vs/base/common/process'; const nullEvent = new class { id: number; diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index ced540f7ee..f63024a048 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -15,7 +15,7 @@ import { endsWith } from 'vs/base/common/strings'; import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; -import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 945f08f210..494b2607ee 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -12,6 +12,7 @@ import { RunOnOptions, Task, TaskRunSource } from 'vs/workbench/contrib/tasks/co import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; +import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic'; @@ -131,38 +132,27 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } -export class AllowAutomaticTaskRunning extends Action { +export class ManageAutomaticTaskRunning extends Action { - public static readonly ID = 'workbench.action.tasks.allowAutomaticRunning'; - public static readonly LABEL = nls.localize('workbench.action.tasks.allowAutomaticRunning', "Allow Automatic Tasks in Folder"); + public static readonly ID = 'workbench.action.tasks.manageAutomaticRunning'; + public static readonly LABEL = nls.localize('workbench.action.tasks.manageAutomaticRunning', "Manage Automatic Tasks in Folder"); constructor( id: string, label: string, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(id, label); } - public run(event?: any): Promise { - this.storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, true, StorageScope.WORKSPACE); - return Promise.resolve(undefined); - } -} - -export class DisallowAutomaticTaskRunning extends Action { - - public static readonly ID = 'workbench.action.tasks.disallowAutomaticRunning'; - public static readonly LABEL = nls.localize('workbench.action.tasks.disallowAutomaticRunning', "Disallow Automatic Tasks in Folder"); - - constructor( - id: string, label: string, - @IStorageService private readonly storageService: IStorageService - ) { - super(id, label); - } - - public run(event?: any): Promise { - this.storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, false, StorageScope.WORKSPACE); - return Promise.resolve(undefined); + public async run(event?: any): Promise { + const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.allowAutomaticTasks', "Allow Automatic Tasks in Folder") }; + const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.disallowAutomaticTasks', "Disallow Automatic Tasks in Folder") }; + const value = await this.quickInputService.pick([allowItem, disallowItem], { canPickMany: false }); + if (!value) { + return; + } + + this.storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, value === allowItem, StorageScope.WORKSPACE); } } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index fdd737156f..084a8ca25d 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -32,7 +32,7 @@ import { QuickOpenActionContributor } from '../browser/quickOpen'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { RunAutomaticTasks, AllowAutomaticTaskRunning, DisallowAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; +import { RunAutomaticTasks, ManageAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; let tasksCategory = nls.localize('tasksCategory', "Tasks"); @@ -40,8 +40,7 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowAutomaticTaskRunning, AllowAutomaticTaskRunning.ID, AllowAutomaticTaskRunning.LABEL), 'Tasks: Allow Automatic Tasks in Folder', tasksCategory); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowAutomaticTaskRunning, DisallowAutomaticTaskRunning.ID, DisallowAutomaticTaskRunning.LABEL), 'Tasks: Disallow Automatic Tasks in Folder', tasksCategory); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageAutomaticTaskRunning, ManageAutomaticTaskRunning.ID, ManageAutomaticTaskRunning.LABEL), 'Tasks: Manage Automatic Tasks in Folder', tasksCategory); export class TaskStatusBarContributions extends Disposable implements IWorkbenchContribution { private runningTasksStatusItem: IStatusbarEntryAccessor | undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 76327565de..2fad6bd4f3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -5,6 +5,7 @@ import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; +import { env as processEnv } from 'vs/base/common/process'; import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; import { ILogService } from 'vs/platform/log/common/log'; import { Emitter, Event } from 'vs/base/common/event'; @@ -225,8 +226,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce const envFromConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.env.${platformKey}`); const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(); this._configHelper.showRecommendations(shellLaunchConfig); - const baseEnv = this._configHelper.config.inheritEnv ? process.env as platform.IProcessEnvironment : await this._terminalInstanceService.getMainProcessParentEnv(); - const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.productConfiguration.version, this._configHelper.config.setLocaleVariables, baseEnv); + const baseEnv = this._configHelper.config.inheritEnv ? processEnv : await this._terminalInstanceService.getMainProcessParentEnv(); + const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.setLocaleVariables, baseEnv); const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled; return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty); diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index e8ba6f6e22..0b62195f03 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -285,6 +285,12 @@ applyStyles(newDocument, newDocument.body); + // Check for CSP + const csp = newDocument.querySelector('meta[http-equiv="Content-Security-Policy"]'); + if (!csp) { + host.postMessage('no-csp-found'); + } + // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden return '\n' + newDocument.documentElement.outerHTML; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index 7194e52c22..aeef55b1f6 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -244,7 +244,6 @@ export class WebviewEditorService implements IWebviewEditorService { private createWebiew(id: string, extension: { location: URI; id: ExtensionIdentifier; } | undefined, options: WebviewInputOptions) { return this._webviewService.createWebviewEditorOverlay(id, { - allowSvgs: true, extension: extension, enableFindWidget: options.enableFindWidget, retainContextWhenHidden: options.retainContextWhenHidden diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index da4d1bb6c5..0378d48b03 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -213,7 +213,7 @@ export class IFrameWebview extends Disposable implements Webview { } } - initialScrollProgress: number; + initialScrollProgress: number = 0; private readonly _onDidFocus = this._register(new Emitter()); public readonly onDidFocus = this._onDidFocus.event; diff --git a/src/vs/workbench/contrib/webview/common/webview.ts b/src/vs/workbench/contrib/webview/common/webview.ts index 7136acca40..2ab61bd62c 100644 --- a/src/vs/workbench/contrib/webview/common/webview.ts +++ b/src/vs/workbench/contrib/webview/common/webview.ts @@ -41,7 +41,6 @@ export interface IWebviewService { export const WebviewResourceScheme = 'vscode-resource'; export interface WebviewOptions { - readonly allowSvgs?: boolean; readonly extension?: { readonly location: URI; readonly id?: ExtensionIdentifier; @@ -53,7 +52,6 @@ export interface WebviewOptions { export interface WebviewContentOptions { readonly allowScripts?: boolean; - readonly svgWhiteList?: string[]; readonly localResourceRoots?: ReadonlyArray; readonly portMapping?: ReadonlyArray; readonly enableCommandUris?: boolean; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 512901e5f0..58e9189e23 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -9,13 +9,14 @@ import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; -import { endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping'; import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing'; @@ -135,51 +136,6 @@ class WebviewPortMappingProvider extends Disposable { } } -class SvgBlocker extends Disposable { - - private readonly _onDidBlockSvg = this._register(new Emitter()); - public readonly onDidBlockSvg = this._onDidBlockSvg.event; - - constructor( - session: WebviewSession, - private readonly _options: WebviewContentOptions, - ) { - super(); - - session.onBeforeRequest(async (details) => { - if (details.url.indexOf('.svg') > 0) { - const uri = URI.parse(details.url); - if (uri && !uri.scheme.match(/file/i) && endsWith(uri.path, '.svg') && !this.isAllowedSvg(uri)) { - this._onDidBlockSvg.fire(); - return { cancel: true }; - } - } - - return undefined; - }); - - session.onHeadersReceived((details) => { - const headers: any = details.responseHeaders; - const contentType: string[] = headers['content-type'] || headers['Content-Type']; - if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) { - const uri = URI.parse(details.url); - if (uri && !this.isAllowedSvg(uri)) { - this._onDidBlockSvg.fire(); - return { cancel: true }; - } - } - return undefined; - }); - } - - private isAllowedSvg(uri: URI): boolean { - if (this._options.svgWhiteList) { - return this._options.svgWhiteList.indexOf(uri.authority.toLowerCase()) >= 0; - } - return false; - } -} - class WebviewKeyboardHandler extends Disposable { private _ignoreMenuShortcut = false; @@ -284,6 +240,8 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { @IFileService fileService: IFileService, @ITunnelService tunnelService: ITunnelService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IEnvironmentService private readonly _environementService: IEnvironmentService, ) { super(); this.content = { @@ -331,11 +289,6 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { tunnelService, )); - if (!this._options.allowSvgs) { - const svgBlocker = this._register(new SvgBlocker(session, this.content.options)); - svgBlocker.onDidBlockSvg(() => this.onDidBlockSvg()); - } - this._register(new WebviewKeyboardHandler(this._webview)); this._register(addDisposableListener(this._webview, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) { @@ -412,6 +365,10 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { case 'did-blur': this.handleFocusChange(false); return; + + case 'no-csp-found': + this.handleNoCspFound(); + return; } })); this._register(addDisposableListener(this._webview, 'devtools-opened', () => { @@ -546,14 +503,34 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { } } - public sendMessage(data: any): void { - this._send('message', data); + private _hasAlertedAboutMissingCsp = false; + + private handleNoCspFound(): void { + if (this._hasAlertedAboutMissingCsp) { + return; + } + this._hasAlertedAboutMissingCsp = true; + + if (this._options.extension && this._options.extension.id) { + if (this._environementService.isExtensionDevelopment) { + console.warn(`${this._options.extension.id.value} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`); + } + + type TelemetryClassification = { + extension?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' } + }; + type TelemetryData = { + extension?: string, + }; + + this._telemetryService.publicLog2('webviewMissingCsp', { + extension: this._options.extension.id.value + }); + } } - private onDidBlockSvg() { - this.sendMessage({ - name: 'vscode-did-block-svg' - }); + public sendMessage(data: any): void { + this._send('message', data); } private style(theme: ITheme): void { diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 7d070169c6..4e8156cd7a 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -51,6 +51,8 @@ import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { basename } from 'vs/base/common/resources'; +import { IProductService } from 'vs/platform/product/common/product'; +import product from 'vs/platform/product/node/product'; class CodeRendererMain extends Disposable { @@ -177,6 +179,9 @@ class CodeRendererMain extends Disposable { // Environment serviceCollection.set(IWorkbenchEnvironmentService, this.environmentService); + // Product + serviceCollection.set(IProductService, { _serviceBrand: undefined, ...product }); + // Log const logService = this._register(this.createLogService(mainProcessService, this.environmentService)); serviceCollection.set(ILogService, logService); diff --git a/src/vs/workbench/services/credentials/browser/credentialsService.ts b/src/vs/workbench/services/credentials/browser/credentialsService.ts new file mode 100644 index 0000000000..62feab39ae --- /dev/null +++ b/src/vs/workbench/services/credentials/browser/credentialsService.ts @@ -0,0 +1,132 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export interface ICredentialsProvider { + getPassword(service: string, account: string): Promise; + setPassword(service: string, account: string, password: string): Promise; + deletePassword(service: string, account: string): Promise; + findPassword(service: string): Promise; +} + +export class BrowserCredentialsService implements ICredentialsService { + + _serviceBrand!: ServiceIdentifier; + + private credentialsProvider: ICredentialsProvider; + + constructor(@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService) { + if (environmentService.options && environmentService.options.credentialsProvider) { + this.credentialsProvider = environmentService.options.credentialsProvider; + } else { + this.credentialsProvider = new LocalStorageCredentialsProvider(); + } + } + + async getPassword(service: string, account: string): Promise { + return this.credentialsProvider.getPassword(service, account); + } + + async setPassword(service: string, account: string, password: string): Promise { + return this.credentialsProvider.setPassword(service, account, password); + } + + async deletePassword(service: string, account: string): Promise { + return this.credentialsProvider.deletePassword(service, account); + } + + async findPassword(service: string): Promise { + return this.credentialsProvider.findPassword(service); + } +} + +interface ICredential { + service: string; + account: string; + password: string; +} + +class LocalStorageCredentialsProvider implements ICredentialsProvider { + + static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider'; + + private _credentials: ICredential[]; + private get credentials(): ICredential[] { + if (!this._credentials) { + try { + const serializedCredentials = window.localStorage.getItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY); + if (serializedCredentials) { + this._credentials = JSON.parse(serializedCredentials); + } + } catch (error) { + // ignore + } + + if (!Array.isArray(this._credentials)) { + this._credentials = []; + } + } + + return this._credentials; + } + + private save(): void { + window.localStorage.setItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY, JSON.stringify(this.credentials)); + } + + async getPassword(service: string, account: string): Promise { + return this.doGetPassword(service, account); + } + + private async doGetPassword(service: string, account?: string): Promise { + for (const credential of this.credentials) { + if (credential.service === service) { + if (typeof account !== 'string' || account === credential.account) { + return credential.password; + } + } + } + + return null; + } + + async setPassword(service: string, account: string, password: string): Promise { + this.deletePassword(service, account); + + this.credentials.push({ service, account, password }); + + this.save(); + } + + async deletePassword(service: string, account: string): Promise { + let found = false; + + this._credentials = this.credentials.filter(credential => { + if (credential.service === service && credential.account === account) { + found = true; + + return false; + } + + return true; + }); + + if (found) { + this.save(); + } + + return found; + } + + async findPassword(service: string): Promise { + return this.doGetPassword(service); + } +} + +registerSingleton(ICredentialsService, BrowserCredentialsService, true); diff --git a/src/vs/platform/credentials/common/credentials.ts b/src/vs/workbench/services/credentials/common/credentials.ts similarity index 84% rename from src/vs/platform/credentials/common/credentials.ts rename to src/vs/workbench/services/credentials/common/credentials.ts index f538dd6486..49c19b880b 100644 --- a/src/vs/platform/credentials/common/credentials.ts +++ b/src/vs/workbench/services/credentials/common/credentials.ts @@ -3,12 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export const ICredentialsService = createDecorator('ICredentialsService'); export interface ICredentialsService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; + getPassword(service: string, account: string): Promise; setPassword(service: string, account: string, password: string): Promise; deletePassword(service: string, account: string): Promise; diff --git a/src/vs/platform/credentials/node/credentialsService.ts b/src/vs/workbench/services/credentials/node/credentialsService.ts similarity index 80% rename from src/vs/platform/credentials/node/credentialsService.ts rename to src/vs/workbench/services/credentials/node/credentialsService.ts index c86aece4d2..d3b203c3ce 100644 --- a/src/vs/platform/credentials/node/credentialsService.ts +++ b/src/vs/workbench/services/credentials/node/credentialsService.ts @@ -3,8 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; import { IdleValue } from 'vs/base/common/async'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; type KeytarModule = { getPassword(service: string, account: string): Promise; @@ -15,7 +17,7 @@ type KeytarModule = { export class KeytarCredentialsService implements ICredentialsService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; private readonly _keytar = new IdleValue>(() => import('keytar')); @@ -39,3 +41,5 @@ export class KeytarCredentialsService implements ICredentialsService { return keytar.findPassword(service); } } + +registerSingleton(ICredentialsService, KeytarCredentialsService, true); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 311df6ea9f..88feb8c443 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWindowConfiguration, IPath, IPathsToWaitFor } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService, IExtensionHostDebugParams, IDebugParams, BACKUPS } from 'vs/platform/environment/common/environment'; +import { IExtensionHostDebugParams, IDebugParams, BACKUPS } from 'vs/platform/environment/common/environment'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; @@ -13,6 +13,8 @@ import { ExportData } from 'vs/base/common/performance'; import { LogLevel } from 'vs/platform/log/common/log'; import { joinPath } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; export class BrowserWindowConfiguration implements IWindowConfiguration { @@ -66,26 +68,26 @@ export interface IBrowserWindowConfiguration { connectionToken?: string; } -export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { +export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironmentService { - _serviceBrand!: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration(); - constructor(configuration: IBrowserWindowConfiguration) { + constructor(workspaceId: string, public readonly options: IWorkbenchConstructionOptions) { this.args = { _: [] }; this.appRoot = '/web/'; this.appNameLong = 'Visual Studio Code - Web'; - this.configuration.remoteAuthority = configuration.remoteAuthority; + this.configuration.remoteAuthority = options.remoteAuthority; this.userRoamingDataHome = URI.file('/User').with({ scheme: Schemas.userData }); this.settingsResource = joinPath(this.userRoamingDataHome, 'settings.json'); this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json'); this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json'); this.backupHome = joinPath(this.userRoamingDataHome, BACKUPS); - this.configuration.backupWorkspaceResource = joinPath(this.backupHome, configuration.workspaceId); - this.configuration.connectionToken = configuration.connectionToken || this.getConnectionTokenFromLocation(); + this.configuration.backupWorkspaceResource = joinPath(this.backupHome, workspaceId); + this.configuration.connectionToken = options.connectionToken || this.getConnectionTokenFromLocation(); this.logsPath = '/web/logs'; @@ -94,7 +96,7 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { break: false }; - this.webviewEndpoint = configuration.webviewEndpoint; + this.webviewEndpoint = options.webviewEndpoint; this.untitledWorkspacesHome = URI.from({ scheme: Schemas.untitled, path: 'Workspaces' }); if (document && document.location && document.location.search) { @@ -201,7 +203,7 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { } private getConnectionToken(str: string): string | undefined { - const m = str.match(/[#&]tkn=([^&]+)/); + const m = str.match(/[#&?]tkn=([^&]+)/); return m ? m[1] : undefined; } } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 0ad1ed8659..82f8ba4fb3 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -6,6 +6,7 @@ import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; export const IWorkbenchEnvironmentService = createDecorator('environmentService'); @@ -14,4 +15,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { _serviceBrand: ServiceIdentifier; readonly configuration: IWindowConfiguration; + + readonly options?: IWorkbenchConstructionOptions; } diff --git a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index baa4f189d8..9c0212dd9d 100644 --- a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -16,12 +16,12 @@ import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/ import { isUndefinedOrNull } from 'vs/base/common/types'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ProductService } from 'vs/platform/product/node/productService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { assign } from 'vs/base/common/objects'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { productService } from 'vs/workbench/test/workbenchTestServices'; function storageService(instantiationService: TestInstantiationService): IStorageService { let service = instantiationService.get(IStorageService); @@ -46,7 +46,7 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService), instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService), - new ProductService() + productService ); } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts index 2ae647ca37..07220c8ad9 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts @@ -117,16 +117,16 @@ export class WebWorkerExtensionHostStarter implements IExtensionHostStarter { const [telemetryInfo, extensionDescriptions] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions]); const workspace = this._contextService.getWorkspace(); return { - commit: this._productService.productConfiguration.commit, - version: this._productService.productConfiguration.version, - vscodeVersion: this._productService.productConfiguration.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version + commit: this._productService.commit, + version: this._productService.version, + vscodeVersion: this._productService.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version parentPid: -1, environment: { isExtensionDevelopmentDebug: false, appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined, appSettingsHome: this._environmentService.appSettingsHome ? this._environmentService.appSettingsHome : undefined, - appName: this._productService.productConfiguration.nameLong, - appUriScheme: this._productService.productConfiguration.urlProtocol, + appName: this._productService.nameLong, + appUriScheme: this._productService.urlProtocol, appLanguage: platform.language, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index bb16bfcd8a..9ffd299f00 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -462,12 +462,12 @@ class ProposedApiController { } this.enableProposedApiForAll = !environmentService.isBuilt || - (!!environmentService.extensionDevelopmentLocationURI && productService.productConfiguration.nameLong !== 'Visual Studio Code') || + (!!environmentService.extensionDevelopmentLocationURI && productService.nameLong !== 'Visual Studio Code') || (this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args); this.productAllowProposedApi = new Set(); - if (isNonEmptyArray(productService.productConfiguration.extensionAllowedProposedApi)) { - productService.productConfiguration.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id))); + if (isNonEmptyArray(productService.extensionAllowedProposedApi)) { + productService.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id))); } } diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index 9b902d4246..d54748d0e0 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -24,7 +24,7 @@ export function isUIExtension(manifest: IExtensionManifest, productService: IPro case 'workspace': return false; default: { // Tagged as UI extension in product - if (isNonEmptyArray(productService.productConfiguration.uiExtensions) && productService.productConfiguration.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { + if (isNonEmptyArray(productService.uiExtensions) && productService.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { return true; } // Not an UI extension if it has main diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index b159eb5299..80a0df64f3 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -71,7 +71,7 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH public start(): Promise { const options: IConnectionOptions = { - commit: this._productService.productConfiguration.commit, + commit: this._productService.commit, socketFactory: this._socketFactory, addressProvider: { getAddress: async () => { @@ -181,16 +181,16 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier); const workspace = this._contextService.getWorkspace(); const r: IInitData = { - commit: this._productService.productConfiguration.commit, - version: this._productService.productConfiguration.version, - vscodeVersion: this._productService.productConfiguration.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version + commit: this._productService.commit, + version: this._productService.version, + vscodeVersion: this._productService.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version parentPid: remoteExtensionHostData.pid, environment: { isExtensionDevelopmentDebug, appRoot: remoteExtensionHostData.appRoot, appSettingsHome: remoteExtensionHostData.appSettingsHome, - appName: this._productService.productConfiguration.nameLong, - appUriScheme: this._productService.productConfiguration.urlProtocol, + appName: this._productService.nameLong, + appUriScheme: this._productService.urlProtocol, appLanguage: platform.language, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts index c767f79187..c0bf439392 100644 --- a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts +++ b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts @@ -69,7 +69,7 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC const installed = await this.getInstalled(ExtensionType.User); const compatible = await this.galleryService.getCompatibleExtension(extension); if (!compatible) { - return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.productConfiguration.version))); + return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.version))); } const manifest = await this.galleryService.getManifest(compatible, CancellationToken.None); if (manifest) { diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 81812dfc29..1fa3ce99ce 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -469,24 +469,26 @@ async function readCaCertificates() { } async function readWindowsCaCertificates() { - const winCA = await import('vscode-windows-ca-certs'); + // Not using await to work around minifier bug (https://github.com/microsoft/vscode/issues/79044). + return import('vscode-windows-ca-certs') + .then(winCA => { + let ders: any[] = []; + const store = winCA(); + try { + let der: any; + while (der = store.next()) { + ders.push(der); + } + } finally { + store.done(); + } - let ders: any[] = []; - const store = winCA(); - try { - let der: any; - while (der = store.next()) { - ders.push(der); - } - } finally { - store.done(); - } - - const certs = new Set(ders.map(derToPem)); - return { - certs: Array.from(certs), - append: true - }; + const certs = new Set(ders.map(derToPem)); + return { + certs: Array.from(certs), + append: true + }; + }); } async function readMacCaCertificates() { diff --git a/src/vs/workbench/services/integrity/browser/integrityService.ts b/src/vs/workbench/services/integrity/browser/integrityService.ts new file mode 100644 index 0000000000..7bb17c5510 --- /dev/null +++ b/src/vs/workbench/services/integrity/browser/integrityService.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IIntegrityService, IntegrityTestResult } from 'vs/workbench/services/integrity/common/integrity'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +export class BrowserIntegrityServiceImpl implements IIntegrityService { + + _serviceBrand!: ServiceIdentifier; + + async isPure(): Promise { + return { isPure: true, proof: [] }; + } +} + +registerSingleton(IIntegrityService, BrowserIntegrityServiceImpl, true); diff --git a/src/vs/workbench/services/integrity/node/integrityService.ts b/src/vs/workbench/services/integrity/node/integrityService.ts index 5943f1ba14..befe1eebca 100644 --- a/src/vs/workbench/services/integrity/node/integrityService.ts +++ b/src/vs/workbench/services/integrity/node/integrityService.ts @@ -14,6 +14,7 @@ import product from 'vs/platform/product/node/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; interface IStorageData { dontShowPrompt: boolean; @@ -55,7 +56,7 @@ class IntegrityStorage { export class IntegrityServiceImpl implements IIntegrityService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; private _storage: IntegrityStorage; private _isPurePromise: Promise; @@ -159,4 +160,4 @@ export class IntegrityServiceImpl implements IIntegrityService { } } -registerSingleton(IIntegrityService, IntegrityServiceImpl, true); \ No newline at end of file +registerSingleton(IIntegrityService, IntegrityServiceImpl, true); diff --git a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts index 58f7d8e255..b5345d054f 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts @@ -28,7 +28,7 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR super(environmentService); this.socketFactory = new BrowserSocketFactory(webSocketFactory); - this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority!, productService.productConfiguration.commit, this.socketFactory, remoteAuthorityResolverService, signService)); + this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority!, productService.commit, this.socketFactory, remoteAuthorityResolverService, signService)); } getConnection(): IRemoteAgentConnection | null { diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 1d7c128de3..f1c1678676 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -17,7 +17,7 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } f import { Registry } from 'vs/platform/registry/common/platform'; import { RemoteExtensionEnvironmentChannelClient } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { Emitter } from 'vs/base/common/event'; import { ISignService } from 'vs/platform/sign/common/sign'; diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 6e6b3fa7fa..a04175a44e 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -8,7 +8,8 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; +import { RemoteAuthorities } from 'vs/base/common/network'; export interface IGetEnvironmentDataArguments { language: string; @@ -18,6 +19,7 @@ export interface IGetEnvironmentDataArguments { export interface IRemoteAgentEnvironmentDTO { pid: number; + connectionToken: string; appRoot: UriComponents; appSettingsHome: UriComponents; settingsPath: UriComponents; @@ -43,8 +45,11 @@ export class RemoteExtensionEnvironmentChannelClient { const data = await this.channel.call('getEnvironmentData', args); + RemoteAuthorities.setConnectionToken(remoteAuthority, data.connectionToken); + return { pid: data.pid, + connectionToken: data.connectionToken, appRoot: URI.revive(data.appRoot), appSettingsHome: URI.revive(data.appSettingsHome), settingsPath: URI.revive(data.settingsPath), diff --git a/src/vs/workbench/services/remote/common/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts index 12c52aae9e..9f18d97423 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { Event } from 'vs/base/common/event'; import { PersistenConnectionEvent as PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index bc7f61b01b..fb15d82c28 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -79,11 +79,11 @@ export class TelemetryService extends Disposable implements ITelemetryService { ) { super(); - const aiKey = productService.productConfiguration.aiConfig && productService.productConfiguration.aiConfig.asimovKey; - if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.productConfiguration.enableTelemetry && !!aiKey) { + const aiKey = productService.aiConfig && productService.aiConfig.asimovKey; + if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.enableTelemetry && !!aiKey) { const config: ITelemetryServiceConfig = { appender: combinedAppender(new WebTelemetryAppender(aiKey, logService), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.productConfiguration.commit, productService.productConfiguration.version, environmentService.configuration.machineId, environmentService.configuration.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.configuration.remoteAuthority), piiPaths: [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index eef5d71bab..91e12fbf2f 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -34,11 +34,11 @@ export class TelemetryService extends Disposable implements ITelemetryService { ) { super(); - if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.productConfiguration.enableTelemetry) { + if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.enableTelemetry) { const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.productConfiguration.commit, productService.productConfiguration.version, environmentService.configuration.machineId, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts index 305077d071..871436e0d8 100644 --- a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts +++ b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts @@ -47,7 +47,7 @@ suite('FileUserDataProvider', () => { userDataResource = URI.file(userDataPath).with({ scheme: Schemas.userData }); await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]); - const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: 'workspaceId' }); + const environmentService = new BrowserWorkbenchEnvironmentService('workspaceId', { remoteAuthority: 'remote' }); environmentService.userRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(URI.file(userDataPath), URI.file(backupsPath), diskFileSystemProvider, environmentService); @@ -321,7 +321,7 @@ suite('FileUserDataProvider - Watching', () => { localUserDataResource = URI.file(userDataPath); userDataResource = localUserDataResource.with({ scheme: Schemas.userData }); - const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: 'workspaceId' }); + const environmentService = new BrowserWorkbenchEnvironmentService('workspaceId', { remoteAuthority: 'remote' }); environmentService.userRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(localUserDataResource, localBackupsResource, new TestFileSystemProvider(fileEventEmitter.event), environmentService); @@ -475,4 +475,4 @@ suite('FileUserDataProvider - Watching', () => { type: FileChangeType.DELETED }]); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 01bd3b1585..ad72e7d886 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -84,6 +84,8 @@ import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/n import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { NodeTextFileService } from 'vs/workbench/services/textfile/node/textFileService'; import { Schemas } from 'vs/base/common/network'; +import { IProductService } from 'vs/platform/product/common/product'; +import product from 'vs/platform/product/node/product'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -1338,12 +1340,12 @@ export class TestWindowsService implements IWindowsService { public windowCount = 1; - onWindowOpen: Event; - onWindowFocus: Event; - onWindowBlur: Event; - onWindowMaximize: Event; - onWindowUnmaximize: Event; - onRecentlyOpenedChange: Event; + readonly onWindowOpen: Event = Event.None; + readonly onWindowFocus: Event = Event.None; + readonly onWindowBlur: Event = Event.None; + readonly onWindowMaximize: Event = Event.None; + readonly onWindowUnmaximize: Event = Event.None; + readonly onRecentlyOpenedChange: Event = Event.None; isFocused(_windowId: number): Promise { return Promise.resolve(false); @@ -1631,3 +1633,5 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { private toFileResource(resource: URI): URI { return resource.with({ scheme: Schemas.file, authority: '' }); } } + +export const productService: IProductService = { _serviceBrand: undefined, ...product }; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index ed9319b161..83fb8c8cf8 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -124,9 +124,7 @@ import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; // Preferences import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; -import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences'; -import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; -registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); +import 'vs/workbench/contrib/preferences/browser/preferencesSearch'; // Logs import 'vs/workbench/contrib/logs/common/logs.contribution'; @@ -233,4 +231,7 @@ import 'vs/workbench/contrib/outline/browser/outline.contribution'; // Experiments import 'vs/workbench/contrib/experiments/browser/experiments.contribution'; +// Send a Smile +import 'vs/workbench/contrib/feedback/browser/feedback.contribution'; + //#endregion diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 20823844d4..57e91c5077 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -50,6 +50,7 @@ import 'vs/workbench/services/accessibility/node/accessibilityService'; import 'vs/workbench/services/remote/node/tunnelService'; import 'vs/workbench/services/backup/node/backupFileService'; import 'vs/workbench/services/opener/electron-browser/openerService'; +import 'vs/workbench/services/credentials/node/credentialsService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -61,13 +62,11 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { LocalizationsService } from 'vs/platform/localizations/electron-browser/localizationsService'; import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { IProductService } from 'vs/platform/product/common/product'; -import { ProductService } from 'vs/platform/product/node/productService'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; -import { IIssueService } from 'vs/platform/issue/common/issue'; +import { IIssueService } from 'vs/platform/issue/node/issue'; import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; @@ -75,22 +74,18 @@ import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; import { IURLService } from 'vs/platform/url/common/url'; import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; registerSingleton(IClipboardService, ClipboardService, true); registerSingleton(IRequestService, RequestService, true); registerSingleton(ILifecycleService, LifecycleService); registerSingleton(ILocalizationsService, LocalizationsService); registerSingleton(ISharedProcessService, SharedProcessService, true); -registerSingleton(IProductService, ProductService, true); registerSingleton(IWindowsService, WindowsService); registerSingleton(IUpdateService, UpdateService); registerSingleton(IIssueService, IssueService); registerSingleton(IWorkspacesService, WorkspacesService); registerSingleton(IMenubarService, MenubarService); registerSingleton(IURLService, RelayURLService); -registerSingleton(ICredentialsService, KeytarCredentialsService, true); //#endregion @@ -241,9 +236,6 @@ import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution // Execution import 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; -// Send a Smile -import 'vs/workbench/contrib/feedback/browser/feedback.contribution'; - // Update import 'vs/workbench/contrib/update/electron-browser/update.contribution'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 0fc9ddb19f..c319a043f6 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -8,6 +8,7 @@ import { main } from 'vs/workbench/browser/web.main'; import { UriComponents } from 'vs/base/common/uri'; import { IFileSystemProvider } from 'vs/platform/files/common/files'; import { IWebSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; +import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService'; export interface IWorkbenchConstructionOptions { @@ -53,6 +54,11 @@ export interface IWorkbenchConstructionOptions { * Experimental: Whether to enable the smoke test driver. */ driver?: boolean; + + /** + * Experimental: The credentials provider to store and retrieve secrets. + */ + credentialsProvider?: ICredentialsProvider; } /** diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 812a61c2ce..f61b8a7e3d 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -26,6 +26,7 @@ import 'vs/workbench/browser/web.main'; //#region --- workbench services +import 'vs/workbench/services/integrity/browser/integrityService'; import 'vs/workbench/services/textMate/browser/textMateService'; import 'vs/workbench/services/search/common/searchService'; import 'vs/workbench/services/output/common/outputChannelModelService'; @@ -35,6 +36,7 @@ import 'vs/workbench/services/extensions/browser/extensionService'; import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; import 'vs/workbench/services/telemetry/browser/telemetryService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +import 'vs/workbench/services/credentials/browser/credentialsService'; import 'vs/workbench/browser/web.simpleservices'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/test/smoke/package.json b/test/smoke/package.json index 19378e70a5..185fc07d64 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -18,6 +18,7 @@ "@types/mocha": "2.2.41", "@types/ncp": "2.0.1", "@types/node": "^10.14.8", + "@types/puppeteer": "^1.19.0", "@types/rimraf": "2.0.2", "@types/webdriverio": "4.6.1", "concurrently": "^3.5.1", diff --git a/test/smoke/src/application.ts b/test/smoke/src/application.ts index cf1dd45fbe..aa77a99bf1 100644 --- a/test/smoke/src/application.ts +++ b/test/smoke/src/application.ts @@ -131,7 +131,9 @@ export class Application { verbose: this.options.verbose, log: this.options.log, extraArgs, - remote: this.options.remote + remote: this.options.remote, + web: this.options.web, + headless: this.options.headless }); this._workbench = new Workbench(this._code, this.userDataPath); diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index 4dd62c0743..202d56b03b 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -34,4 +34,4 @@ export function setup() { await app.workbench.settingsEditor.clearUserSettings(); }); }); -} \ No newline at end of file +} diff --git a/test/smoke/src/areas/preferences/settings.ts b/test/smoke/src/areas/preferences/settings.ts index 69f3be1618..b08082d494 100644 --- a/test/smoke/src/areas/preferences/settings.ts +++ b/test/smoke/src/areas/preferences/settings.ts @@ -30,7 +30,7 @@ export class SettingsEditor { async clearUserSettings(): Promise { const settingsPath = path.join(this.userDataPath, 'User', 'settings.json'); - await new Promise((c, e) => fs.writeFile(settingsPath, '{}', 'utf8', err => err ? e(err) : c())); + await new Promise((c, e) => fs.writeFile(settingsPath, '{\n}', 'utf8', err => err ? e(err) : c())); await this.openSettings(); await this.editor.waitForEditorContents('settings.json', c => c === '{}'); @@ -39,4 +39,4 @@ export class SettingsEditor { private async openSettings(): Promise { await this.quickopen.runCommand('Preferences: Open Settings (JSON)'); } -} \ No newline at end of file +} diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 72dd61e255..8dc4e2f764 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -59,7 +59,9 @@ const opts = minimist(args, { ], boolean: [ 'verbose', - 'remote' + 'remote', + 'web', + 'headless' ], default: { verbose: false @@ -140,7 +142,7 @@ if (testCodePath) { process.env.VSCODE_CLI = '1'; } -if (!fs.existsSync(electronPath || '')) { +if (!opts.web && !fs.existsSync(electronPath || '')) { fail(`Can't find Code at ${electronPath}.`); } @@ -219,7 +221,9 @@ function createOptions(): ApplicationOptions { verbose: opts.verbose, log, screenshotsPath, - remote: opts.remote + remote: opts.remote, + web: opts.web, + headless: opts.headless }; } @@ -241,11 +245,16 @@ after(async function () { await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c())); }); +/*//{{SQL CARBON EDIT}} +if (!opts.web) { + setupDataMigrationTests(stableCodePath, testDataPath); +}*/ describe('Running Code', () => { before(async function () { const app = new Application(this.defaultOptions); - await app!.start(); + await app!.start(opts.web ? false : undefined); + this.app = app; //{{SQL CARBON EDIT}} const testExtLoadedText = 'Test Extension Loaded'; const testSetupCompletedText = 'Test Setup Completed'; @@ -260,7 +269,6 @@ describe('Running Code', () => { await app.workbench.quickopen.runCommand(waitForExtensionsCommand); await app.workbench.statusbar.waitForStatusbarText(allExtensionsLoadedText, allExtensionsLoadedText); //{{END}} - this.app = app; }); after(async function () { @@ -287,28 +295,27 @@ describe('Running Code', () => { app.logger.log('*** Test start:', title); }); } - //{{SQL CARBON EDIT}} runProfilerTests(); runQueryEditorTests(); - //Original /* - setupDataLossTests(); + if (!opts.web) { setupDataLossTests(); } setupDataExplorerTests(); - setupDataPreferencesTests(); + if (!opts.web) { setupDataPreferencesTests(); } setupDataSearchTests(); setupDataCSSTests(); setupDataEditorTests(); - setupDataDebugTests(); + if (!opts.web) { setupDataDebugTests(); } setupDataGitTests(); setupDataStatusbarTests(); setupDataExtensionTests(); setupTerminalTests(); - setupDataMultirootTests(); + if (!opts.web) { setupDataMultirootTests(); } setupDataLocalizationTests(); */ //{{END}} }); - -// {{SQL CARBON EDIT}} -// setupLaunchTests(); +/*//{{SQL CARBON EDIT}} +if (!opts.web) { + setupLaunchTests(); +}*/ diff --git a/test/smoke/src/vscode/code.ts b/test/smoke/src/vscode/code.ts index 1715804d3d..00c3a2fefd 100644 --- a/test/smoke/src/vscode/code.ts +++ b/test/smoke/src/vscode/code.ts @@ -9,7 +9,8 @@ import * as os from 'os'; import * as fs from 'fs'; import * as mkdirp from 'mkdirp'; import { tmpName } from 'tmp'; -import { IDriver, connect as connectDriver, IDisposable, IElement, Thenable } from './driver'; +import { IDriver, connect as connectElectronDriver, IDisposable, IElement, Thenable } from './driver'; +import { connect as connectPuppeteerDriver, launch } from './puppeteerDriver'; import { Logger } from '../logger'; import { ncp } from 'ncp'; import { URI } from 'vscode-uri'; @@ -62,7 +63,7 @@ function getBuildOutPath(root: string): string { } } -async function connect(child: cp.ChildProcess, outPath: string, handlePath: string, logger: Logger): Promise { +async function connect(connectDriver: typeof connectElectronDriver, child: cp.ChildProcess | undefined, outPath: string, handlePath: string, logger: Logger): Promise { let errCount = 0; while (true) { @@ -71,7 +72,9 @@ async function connect(child: cp.ChildProcess, outPath: string, handlePath: stri return new Code(client, driver, logger); } catch (err) { if (++errCount > 50) { - child.kill(); + if (child) { + child.kill(); + } throw err; } @@ -94,7 +97,12 @@ export interface SpawnOptions { verbose?: boolean; extraArgs?: string[]; log?: string; + /** Run in the test resolver */ remote?: boolean; + /** Run in the web */ + web?: boolean; + /** Run in headless mode (only applies when web is true) */ + headless?: boolean; } async function createDriverHandle(): Promise { @@ -161,14 +169,20 @@ export async function spawn(options: SpawnOptions): Promise { args.push(...options.extraArgs); } - const spawnOptions: cp.SpawnOptions = { env }; + let child: cp.ChildProcess | undefined; + let connectDriver: typeof connectElectronDriver; - const child = cp.spawn(electronPath, args, spawnOptions); - - instances.add(child); - child.once('exit', () => instances.delete(child)); - - return connect(child, outPath, handle, options.logger); + if (options.web) { + await launch(args); + connectDriver = connectPuppeteerDriver.bind(connectPuppeteerDriver, !!options.headless); + } else { + const spawnOptions: cp.SpawnOptions = { env }; + child = cp.spawn(electronPath, args, spawnOptions); + instances.add(child); + child.once('exit', () => instances.delete(child!)); + connectDriver = connectElectronDriver; + } + return connect(connectDriver, child, outPath, handle, options.logger); } async function poll( @@ -366,4 +380,4 @@ export function findElements(element: IElement, fn: (element: IElement) => boole } return result; -} \ No newline at end of file +} diff --git a/test/smoke/src/vscode/puppeteerDriver.ts b/test/smoke/src/vscode/puppeteerDriver.ts new file mode 100644 index 0000000000..5c58fd556b --- /dev/null +++ b/test/smoke/src/vscode/puppeteerDriver.ts @@ -0,0 +1,191 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as puppeteer from 'puppeteer'; +import { ChildProcess, spawn } from 'child_process'; +import { join } from 'path'; +import { mkdir } from 'fs'; +import { promisify } from 'util'; + +const width = 1200; +const height = 800; + +const vscodeToPuppeteerKey = { + cmd: 'Meta', + ctrl: 'Control', + shift: 'Shift', + enter: 'Enter', + escape: 'Escape', + right: 'ArrowRight', + up: 'ArrowUp', + down: 'ArrowDown', + left: 'ArrowLeft', + home: 'Home' +}; + +function buildDriver(browser: puppeteer.Browser, page: puppeteer.Page): IDriver { + const driver = { + _serviceBrand: undefined, + getWindowIds: () => { + return Promise.resolve([1]); + }, + capturePage: () => Promise.resolve(''), + reloadWindow: (windowId) => Promise.resolve(), + exitApplication: () => browser.close(), + dispatchKeybinding: async (windowId, keybinding) => { + const chords = keybinding.split(' '); + for (let i = 0; i < chords.length; i++) { + const chord = chords[i]; + if (i > 0) { + await timeout(100); + } + const keys = chord.split('+'); + const keysDown: string[] = []; + for (let i = 0; i < keys.length; i++) { + if (keys[i] in vscodeToPuppeteerKey) { + keys[i] = vscodeToPuppeteerKey[keys[i]]; + } + await page.keyboard.down(keys[i]); + keysDown.push(keys[i]); + } + while (keysDown.length > 0) { + await page.keyboard.up(keysDown.pop()!); + } + } + + await timeout(100); + }, + click: async (windowId, selector, xoffset, yoffset) => { + const { x, y } = await driver.getElementXY(windowId, selector, xoffset, yoffset); + await page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0)); + }, + doubleClick: async (windowId, selector) => { + await driver.click(windowId, selector, 0, 0); + await timeout(60); + await driver.click(windowId, selector, 0, 0); + await timeout(100); + }, + setValue: async (windowId, selector, text) => page.evaluate(`window.driver.setValue('${selector}', '${text}')`), + getTitle: (windowId) => page.evaluate(`window.driver.getTitle()`), + isActiveElement: (windowId, selector) => page.evaluate(`window.driver.isActiveElement('${selector}')`), + getElements: (windowId, selector, recursive) => page.evaluate(`window.driver.getElements('${selector}', ${recursive})`), + getElementXY: (windowId, selector, xoffset?, yoffset?) => page.evaluate(`window.driver.getElementXY('${selector}', ${xoffset}, ${yoffset})`), + typeInEditor: (windowId, selector, text) => page.evaluate(`window.driver.typeInEditor('${selector}', '${text}')`), + getTerminalBuffer: (windowId, selector) => page.evaluate(`window.driver.getTerminalBuffer('${selector}')`), + writeInTerminal: (windowId, selector, text) => page.evaluate(`window.driver.writeInTerminal('${selector}', '${text}')`) + }; + return driver; +} + +function timeout(ms: number): Promise { + return new Promise(r => setTimeout(r, ms)); +} + +// function runInDriver(call: string, args: (string | boolean)[]): Promise {} + +let args: string[] | undefined; +let server: ChildProcess | undefined; +let endpoint: string | undefined; + +export async function launch(_args: string[]): Promise { + args = _args; + const webUserDataDir = args.filter(e => e.includes('--user-data-dir='))[0].replace('--user-data-dir=', ''); + await promisify(mkdir)(webUserDataDir); + server = spawn(join(args[0], `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`), ['--browser', 'none', '--driver', 'web', '--web-user-data-dir', webUserDataDir]); + server.stderr.on('data', e => console.log('Server stderr: ' + e)); + process.on('exit', teardown); + process.on('SIGINT', teardown); + endpoint = await waitForEndpoint(); +} + +function teardown(): void { + if (server) { + server.kill(); + server = undefined; + } +} + +function waitForEndpoint(): Promise { + return new Promise(r => { + server!.stdout.on('data', d => { + const matches = d.toString('ascii').match(/Web UI available at (.+)/); + if (matches !== null) { + r(matches[1]); + } + }); + }); +} + +export function connect(headless: boolean, outPath: string, handle: string): Promise<{ client: IDisposable, driver: IDriver }> { + return new Promise(async (c) => { + const browser = await puppeteer.launch({ + // Run in Edge dev on macOS + // executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev', + headless, + slowMo: 80, + args: [`--window-size=${width},${height}`] + }); + const page = (await browser.pages())[0]; + await page.setViewport({ width, height }); + const endpointSplit = endpoint!.split('#'); + await page.goto(`${endpointSplit[0]}?folder=${args![1]}#${endpointSplit[1]}`); + const result = { + client: { dispose: () => teardown }, + driver: buildDriver(browser, page) + }; + c(result); + }); +} + +/** + * Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise, + * and others. This API makes no assumption about what promise library is being used which + * enables reusing existing code without migrating to a specific promise implementation. Still, + * we recommend the use of native promises which are available in this editor. + */ +export interface Thenable { + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; +} + +export interface IElement { + tagName: string; + className: string; + textContent: string; + attributes: { [name: string]: string; }; + children: IElement[]; + top: number; + left: number; +} + +export interface IDriver { + _serviceBrand: any; + + getWindowIds(): Promise; + capturePage(windowId: number): Promise; + reloadWindow(windowId: number): Promise; + exitApplication(): Promise; + dispatchKeybinding(windowId: number, keybinding: string): Promise; + click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; + doubleClick(windowId: number, selector: string): Promise; + setValue(windowId: number, selector: string, text: string): Promise; + getTitle(windowId: number): Promise; + isActiveElement(windowId: number, selector: string): Promise; + getElements(windowId: number, selector: string, recursive?: boolean): Promise; + getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }>; + typeInEditor(windowId: number, selector: string, text: string): Promise; + getTerminalBuffer(windowId: number, selector: string): Promise; + writeInTerminal(windowId: number, selector: string, text: string): Promise; +} + +export interface IDisposable { + dispose(): void; +} diff --git a/test/smoke/tools/copy-driver-definition.js b/test/smoke/tools/copy-driver-definition.js index 8e3fd34efb..4a53732f4e 100644 --- a/test/smoke/tools/copy-driver-definition.js +++ b/test/smoke/tools/copy-driver-definition.js @@ -7,7 +7,7 @@ const fs = require('fs'); const path = require('path'); const root = path.dirname(path.dirname(path.dirname(__dirname))); -const driverPath = path.join(root, 'src/vs/platform/driver/node/driver.ts'); +const driverPath = path.join(root, 'src/vs/platform/driver/common/driver.ts'); let contents = fs.readFileSync(driverPath, 'utf8'); contents = /\/\/\*START([\s\S]*)\/\/\*END/mi.exec(contents)[1].trim(); @@ -47,4 +47,4 @@ export function connect(outPath: string, handle: string): Promise<{ client: IDis const srcPath = path.join(path.dirname(__dirname), 'src/vscode'); const outDriverPath = path.join(srcPath, 'driver.d.ts'); -fs.writeFileSync(outDriverPath, contents); \ No newline at end of file +fs.writeFileSync(outDriverPath, contents); diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 3935a9baa4..5219fecf77 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -54,6 +54,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/puppeteer@^1.19.0": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.19.0.tgz#59f0050bae019cee7c3af2bb840a25892a3078b6" + integrity sha512-Db9LWOuTm2bR/qgPE7PQCmnsCQ6flHdULuIDWTks8YdQ/SGHKg5WGWG54gl0734NDKCTF5MbqAp2qWuvBiyQ3Q== + dependencies: + "@types/node" "*" + "@types/rimraf@2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e" diff --git a/yarn.lock b/yarn.lock index 6016c1acfc..046ef4ad1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1952,7 +1952,7 @@ concat-stream@1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@^1.5.0, concat-stream@^1.6.0: +concat-stream@1.6.2, concat-stream@^1.5.0, concat-stream@^1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -2313,14 +2313,14 @@ debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@3.1.0, debug@^3.1.0: +debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== dependencies: ms "2.0.0" -debug@^3.2.6: +debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -3240,6 +3240,16 @@ extract-zip@^1.6.5: mkdirp "0.5.0" yauzl "2.4.1" +extract-zip@^1.6.6: + version "1.6.7" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" + integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= + dependencies: + concat-stream "1.6.2" + debug "2.6.9" + mkdirp "0.5.1" + yauzl "2.4.1" + extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -4154,18 +4164,15 @@ gulp-tslint@^8.1.3: plugin-error "1.0.1" through "~2.3.8" -gulp-uglify@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.2.tgz#5f5b2e8337f879ca9dec971feb1b82a5a87850b0" - integrity sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg== +gulp-uglify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.0.tgz#0df0331d72a0d302e3e37e109485dddf33c6d1ca" + integrity sha1-DfAzHXKg0wLj434QlIXd3zPG0co= dependencies: - array-each "^1.0.1" - extend-shallow "^3.0.2" gulplog "^1.0.0" has-gulplog "^0.1.0" - isobject "^3.0.1" + lodash "^4.13.1" make-error-cause "^1.1.1" - safe-buffer "^5.1.2" through2 "^2.0.0" uglify-js "^3.0.5" vinyl-sourcemaps-apply "^0.2.0" @@ -5625,7 +5632,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.15.0, lodash@^4.3.0: +lodash@^4.13.1, lodash@^4.15.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= @@ -5984,6 +5991,11 @@ mime@^1.4.1, mime@^1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.0.3: + version "2.4.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" + integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== + mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -7451,7 +7463,7 @@ progress@^1.1.8: resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= -progress@^2.0.0: +progress@^2.0.0, progress@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -7474,6 +7486,11 @@ proxy-addr@~2.0.2: forwarded "~0.1.2" ipaddr.js "1.5.2" +proxy-from-env@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -7556,6 +7573,20 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +puppeteer@^1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.19.0.tgz#e3b7b448c2c97933517078d7a2c53687361bebea" + integrity sha512-2S6E6ygpoqcECaagDbBopoSOPDv0pAZvTbnBgUY+6hq0/XDFDOLEMNlHF/SKJlzcaZ9ckiKjKDuueWI3FN/WXw== + dependencies: + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^2.2.1" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + q@^1.0.1, q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -9472,7 +9503,15 @@ uc.micro@^1.0.1, uc.micro@^1.0.3: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" integrity sha1-ftUNXg+an7ClczeSWfKndFjVAZI= -uglify-es@^3.3.4, uglify-es@^3.3.9: +uglify-es@^3.0.18: + version "3.1.9" + resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.1.9.tgz#6c82df628ac9eb7af9c61fd70c744a084abe6161" + integrity sha512-wVSiJKHDgDDFmxTVVvnbAH6IpamAFHYDI+5JvwPdaqIMnk8kRTX2JKwq1Fx7gb2+Jj5Dus8kzvIpKkWOMNU51w== + dependencies: + commander "~2.11.0" + source-map "~0.6.1" + +uglify-es@^3.3.4: version "3.3.9" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== @@ -10220,6 +10259,13 @@ ws@^3.3.3: safe-buffer "~5.1.0" ultron "~1.1.0" +ws@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + xml-name-validator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-1.0.0.tgz#dcf82ee092322951ef8cc1ba596c9cbfd14a83f1"