diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index e8c8855da0..e759a39636 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -6,7 +6,6 @@ 'use strict'; import * as fs from 'fs'; -import { execSync } from 'child_process'; import { Readable } from 'stream'; import * as crypto from 'crypto'; import * as azure from 'azure-storage'; @@ -293,15 +292,18 @@ function main(): void { return; } + const commit = process.env['BUILD_SOURCEVERSION']; + + if (!commit) { + console.warn('Skipping publish due to missing BUILD_SOURCEVERSION'); + return; + } + const opts = minimist(process.argv.slice(2), { boolean: ['upload-only'] }); - // {{SQL CARBON EDIT}} - let [quality, platform, type, name, version, _isUpdate, file, commit] = opts._; - if (!commit) { - commit = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); - } + const [quality, platform, type, name, version, _isUpdate, file] = opts._; publish(commit, quality, platform, type, name, version, _isUpdate, file, opts).catch(err => { console.error(err); diff --git a/build/yarn.lock b/build/yarn.lock index b7c297cf93..85b5dbdd7f 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -112,12 +112,12 @@ "@types/node" "*" "@types/gulp-uglify@^3.0.5": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/gulp-uglify/-/gulp-uglify-3.0.6.tgz#7c7c38017680bbb8a3d815e23a7026799432ce54" - integrity sha512-NvnNG0lg0+fJHNDK/b4OvLZkn5uHSIgm1XslRqgHal8CHG85sxmcktnNR1XdIUkwYpNbYZkXfvpzNtAxb6cgyQ== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/gulp-uglify/-/gulp-uglify-3.0.5.tgz#ddcbbb6bd15a84b8a6c5e2218c2efba98102d135" + integrity sha512-LD2b6gCPugrKI1W188nIp0gm+cAnGGwaTFpPdeZYVXwPHdoCQloy3du0JR62MeMjAwUwlcOb+SKYT6Qgw7yBiA== dependencies: "@types/node" "*" - "@types/uglify-js" "*" + "@types/uglify-js" "^2" "@types/gulp@^4.0.5": version "4.0.6" @@ -221,6 +221,13 @@ dependencies: source-map "^0.6.1" +"@types/uglify-js@^2": + version "2.6.32" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-2.6.32.tgz#1b60906946fcf6ee4ceafa812d2b86f1358da904" + integrity sha512-pVD2cG2wsKPvtpcxVZiSvK8eV9CpxXdlvhbAN//1eD3rOodqQLaEZ1qMNPl/+J93HrECsNLItRx5DkZuSW3j3A== + dependencies: + source-map "^0.6.1" + "@types/underscore@^1.8.9": version "1.8.9" resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.8.9.tgz#fef41f800cd23db1b4f262ddefe49cd952d82323" diff --git a/extensions/git/package.json b/extensions/git/package.json index b6f31076e7..30ec14b227 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -240,6 +240,11 @@ "title": "%command.branch%", "category": "Git" }, + { + "command": "git.branchFrom", + "title": "%command.branchFrom%", + "category": "Git" + }, { "command": "git.deleteBranch", "title": "%command.deleteBranch%", @@ -512,6 +517,10 @@ "command": "git.branch", "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, + { + "command": "git.branchFrom", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, { "command": "git.deleteBranch", "when": "config.git.enabled && gitOpenRepositoryCount != 0" diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 21ac86d8fb..892e3519cd 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -32,6 +32,7 @@ "command.undoCommit": "Undo Last Commit", "command.checkout": "Checkout to...", "command.branch": "Create Branch...", + "command.branchFrom": "Create Branch From...", "command.deleteBranch": "Delete Branch...", "command.renameBranch": "Rename Branch...", "command.merge": "Merge Branch...", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 27bb283c4a..3ef0a946f8 100755 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -103,6 +103,20 @@ class CreateBranchItem implements QuickPickItem { } } +class CreateBranchFromItem implements QuickPickItem { + + constructor(private cc: CommandCenter) { } + + get label(): string { return localize('create branch from', '$(plus) Create new branch from...'); } + get description(): string { return ''; } + + get alwaysShow(): boolean { return true; } + + async run(repository: Repository): Promise { + await this.cc.branch(repository); + } +} + class HEADItem implements QuickPickItem { constructor(private repository: Repository) { } @@ -1422,9 +1436,10 @@ export class CommandCenter { await repository.checkout(treeish); return true; } - const createBranch = new CreateBranchItem(this); - const picks = [createBranch, ...createCheckoutItems(repository)]; + const createBranch = new CreateBranchItem(this); + const createBranchFrom = new CreateBranchFromItem(this); + const picks = [createBranch, createBranchFrom, ...createCheckoutItems(repository)]; const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout'); const quickpick = window.createQuickPick(); @@ -1441,6 +1456,8 @@ export class CommandCenter { if (choice === createBranch) { await this._branch(repository, quickpick.value); + } else if (choice === createBranchFrom) { + await this._branch(repository, quickpick.value, true); } else { await (choice as CheckoutItem).run(repository); } @@ -1453,7 +1470,12 @@ export class CommandCenter { await this._branch(repository); } - private async _branch(repository: Repository, defaultName?: string): Promise { + @command('git.branchFrom', { repository: true }) + async branchFrom(repository: Repository): Promise { + await this._branch(repository, undefined, true); + } + + private async _branch(repository: Repository, defaultName?: string, from = false): Promise { const config = workspace.getConfiguration('git'); const branchWhitespaceChar = config.get('branchWhitespaceChar')!; const branchValidationRegex = config.get('branchValidationRegex')!; @@ -1481,15 +1503,21 @@ export class CommandCenter { return; } - const picks = [new HEADItem(repository), ...createCheckoutItems(repository)]; - const placeHolder = localize('select a ref to create a new branch from', 'Select a ref to create the \'{0}\' branch from', branchName); - const target = await window.showQuickPick(picks, { placeHolder }); + let target = 'HEAD'; - if (!target) { - return; + if (from) { + const picks = [new HEADItem(repository), ...createCheckoutItems(repository)]; + const placeHolder = localize('select a ref to create a new branch from', 'Select a ref to create the \'{0}\' branch from', branchName); + const choice = await window.showQuickPick(picks, { placeHolder }); + + if (!choice) { + return; + } + + target = choice.label; } - await repository.branch(branchName, true, target.label); + await repository.branch(branchName, true, target); } @command('git.deleteBranch', { repository: true }) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index b87b08569c..ab377a6b57 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -397,7 +397,7 @@ export class InputBox extends Widget { return errorMsg ? errorMsg.type !== MessageType.ERROR : true; } - private stylesForType(type: MessageType | undefined): { border: Color | undefined | null; background: Color | undefined | null; foreground: Color | undefined | null } { + private stylesForType(type: MessageType | undefined): { border: Color | undefined; background: Color | undefined; foreground: Color | undefined } { switch (type) { case MessageType.INFO: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground, foreground: this.inputValidationInfoForeground }; case MessageType.WARNING: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground, foreground: this.inputValidationWarningForeground }; diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 6b75768313..54fb690450 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -379,6 +379,18 @@ export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean { }; } +export function lastIndex(array: ReadonlyArray, fn: (item: T) => boolean): number { + for (let i = array.length - 1; i >= 0; i--) { + const element = array[i]; + + if (fn(element)) { + return i; + } + } + + return -1; +} + export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean): number { for (let i = 0; i < array.length; i++) { const element = array[i]; diff --git a/src/vs/base/node/console.ts b/src/vs/base/common/console.ts similarity index 100% rename from src/vs/base/node/console.ts rename to src/vs/base/common/console.ts diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index a180438f18..8a52b22f58 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -93,7 +93,6 @@ export function isUndefinedOrNull(obj: any): obj is undefined | null { return isUndefined(obj) || obj === null; } - const hasOwnProperty = Object.prototype.hasOwnProperty; /** diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index d6c4377453..2595bdd241 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -10,7 +10,7 @@ import { deepClone, assign } from 'vs/base/common/objects'; import { Emitter, Event } from 'vs/base/common/event'; import { createQueuedSender } from 'vs/base/node/processes'; import { ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient } from 'vs/base/parts/ipc/node/ipc'; -import { isRemoteConsoleLog, log } from 'vs/base/node/console'; +import { isRemoteConsoleLog, log } from 'vs/base/common/console'; import { CancellationToken } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index df1c3d1bcb..7c6a2894f2 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -248,6 +248,7 @@ class ProtocolReader { class ProtocolWriter { + private _isDisposed: boolean; private readonly _socket: Socket; private readonly _logFile: number; private _data: Buffer[]; @@ -255,6 +256,7 @@ class ProtocolWriter { public lastWriteTime: number; constructor(socket: Socket, logFile: number) { + this._isDisposed = false; this._socket = socket; this._logFile = logFile; this._data = []; @@ -264,6 +266,7 @@ class ProtocolWriter { public dispose(): void { this.flush(); + this._isDisposed = true; } public flush(): void { @@ -272,6 +275,11 @@ class ProtocolWriter { } public write(msg: ProtocolMessage) { + if (this._isDisposed) { + console.warn(`Cannot write message in a disposed ProtocolWriter`); + console.warn(msg); + return; + } if (this._logFile) { log(this._logFile, `send-${ProtocolMessageTypeToString(msg.type)}-${msg.id}-${msg.ack}-`, msg.data); } @@ -373,12 +381,13 @@ export class Protocol implements IDisposable, IMessagePassingProtocol { } dispose(): void { + this._socketWriter.dispose(); this._socketReader.dispose(); this._socket.removeListener('close', this._socketCloseListener); } - end(): void { - this._socket.end(); + getSocket(): Socket { + return this._socket; } send(buffer: Buffer): void { @@ -427,8 +436,9 @@ export class Client extends IPCClient { dispose(): void { super.dispose(); - this.protocol.end(); + const socket = this.protocol.getSocket(); this.protocol.dispose(); + socket.end(); } } @@ -800,10 +810,6 @@ export class PersistentProtocol { } } - end(): void { - this._socket.end(); - } - readEntireBuffer(): Buffer { return this._socketReader.readEntireBuffer(); } diff --git a/src/vs/base/parts/ipc/test/node/ipc.test.ts b/src/vs/base/parts/ipc/test/node/ipc.test.ts index e12cd8c339..1880182472 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.test.ts @@ -143,7 +143,7 @@ class TestChannel implements IServerChannel { constructor(private service: ITestService) { } - call(_, command: string, arg: any, cancellationToken: CancellationToken): Promise { + call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise { switch (command) { case 'marco': return this.service.marco(); case 'error': return this.service.error(arg); @@ -154,7 +154,7 @@ class TestChannel implements IServerChannel { } } - listen(_, event: string, arg?: any): Event { + listen(_: unknown, event: string, arg?: any): Event { switch (event) { case 'pong': return this.service.pong; default: throw new Error('not implemented'); diff --git a/src/vs/base/parts/ipc/test/node/testService.ts b/src/vs/base/parts/ipc/test/node/testService.ts index 39a0e59a85..c6963e93be 100644 --- a/src/vs/base/parts/ipc/test/node/testService.ts +++ b/src/vs/base/parts/ipc/test/node/testService.ts @@ -41,7 +41,7 @@ export class TestChannel implements IServerChannel { constructor(private testService: ITestService) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { switch (event) { case 'marco': return this.testService.onMarco; } @@ -49,7 +49,7 @@ export class TestChannel implements IServerChannel { throw new Error('Event not found'); } - call(_, command: string, ...args: any[]): Promise { + call(_: unknown, command: string, ...args: any[]): Promise { switch (command) { case 'pong': return this.testService.pong(args[0]); case 'cancelMe': return this.testService.cancelMe(); diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index 44551bf171..82d93a8637 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -270,7 +270,7 @@ suite('Arrays', () => { } test('coalesce', () => { - let a: Array = arrays.coalesce([null, 1, null, 2, 3]); + let a: Array = arrays.coalesce([null, 1, null, 2, 3]); assert.equal(a.length, 3); assert.equal(a[0], 1); assert.equal(a[1], 2); @@ -306,7 +306,7 @@ suite('Arrays', () => { }); test('coalesce - inplace', function () { - let a: Array = [null, 1, null, 2, 3]; + let a: Array = [null, 1, null, 2, 3]; arrays.coalesceInPlace(a); assert.equal(a.length, 3); assert.equal(a[0], 1); diff --git a/src/vs/base/test/node/console.test.ts b/src/vs/base/test/node/console.test.ts index c362f48fe4..cd8531d152 100644 --- a/src/vs/base/test/node/console.test.ts +++ b/src/vs/base/test/node/console.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { getFirstFrame } from 'vs/base/node/console'; +import { getFirstFrame } from 'vs/base/common/console'; import { normalize } from 'vs/base/common/path'; suite('Console', () => { diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 0225027b92..d3741098db 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -30,7 +30,7 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProper import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { MainProcessService, IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; +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 BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; @@ -92,7 +92,7 @@ export class IssueReporter extends Disposable { this.previewButton = new Button(issueReporterElement); } - ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_, info) => { + ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_: unknown, info: Partial) => { this.logService.trace('issueReporter: Received performance data'); this.issueReporterModel.update(info); this.receivedPerformanceInfo = true; @@ -103,7 +103,7 @@ export class IssueReporter extends Disposable { this.updatePreviewButtonState(); }); - ipcRenderer.on('vscode:issueSystemInfoResponse', (_, info) => { + ipcRenderer.on('vscode:issueSystemInfoResponse', (_: unknown, info: any) => { this.logService.trace('issueReporter: Received system data'); this.issueReporterModel.update({ systemInfo: info }); this.receivedSystemInfo = true; @@ -899,7 +899,7 @@ export class IssueReporter extends Disposable { return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`; } - private updateSystemInfo = (state) => { + private updateSystemInfo(state: IssueReporterModelData) { const target = document.querySelector('.block-system .block-info'); if (target) { let tableHtml = ''; @@ -968,14 +968,14 @@ export class IssueReporter extends Disposable { } } - private updateProcessInfo = (state) => { + private updateProcessInfo(state: IssueReporterModelData) { const target = document.querySelector('.block-process .block-info'); if (target) { target.innerHTML = `${state.processInfo}`; } } - private updateWorkspaceInfo = (state) => { + private updateWorkspaceInfo(state: IssueReporterModelData) { document.querySelector('.block-workspace .block-info code')!.textContent = '\n' + state.workspaceInfo; } @@ -1075,9 +1075,13 @@ export class IssueReporter extends Disposable { // helper functions -function hide(el) { - el.classList.add('hidden'); +function hide(el: Element | undefined | null) { + if (el) { + el.classList.add('hidden'); + } } -function show(el) { - el.classList.remove('hidden'); +function show(el: Element | undefined | null) { + if (el) { + el.classList.remove('hidden'); + } } diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index f4db2ab110..f29135a6d0 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -38,7 +38,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza import { LocalizationsChannel } from 'vs/platform/localizations/node/localizationsIpc'; import { DialogChannelClient } from 'vs/platform/dialogs/node/dialogIpc'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IDisposable, dispose, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { DownloadService } from 'vs/platform/download/node/downloadService'; import { IDownloadService } from 'vs/platform/download/common/download'; import { StaticRouter } from 'vs/base/parts/ipc/node/ipc'; @@ -161,12 +161,12 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I const localizationsChannel = new LocalizationsChannel(localizationsService); server.registerChannel('localizations', localizationsChannel); + // clean up deprecated extensions + (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(); + // update localizations cache + (localizationsService as LocalizationsService).update(); + // cache clean ups disposables.push(combinedDisposable([ - // clean up deprecated extensions - toDisposable(() => (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions()), - // update localizations cache - toDisposable(() => (localizationsService as LocalizationsService).update()), - // other cache clean ups instantiationService2.createInstance(NodeCachedDataCleaner), instantiationService2.createInstance(LanguagePackCachedDataCleaner), instantiationService2.createInstance(StorageDataCleaner), diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index f87f37b539..e81c07c766 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -10,13 +10,17 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOpenerService, IOpener } from 'vs/platform/opener/common/opener'; import { equalsIgnoreCase } from 'vs/base/common/strings'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { LinkedList } from 'vs/base/common/linkedList'; export class OpenerService implements IOpenerService { _serviceBrand: any; + private readonly _opener = new LinkedList(); + constructor( @ICodeEditorService private readonly _editorService: ICodeEditorService, @ICommandService private readonly _commandService: ICommandService, @@ -24,14 +28,30 @@ export class OpenerService implements IOpenerService { // } - open(resource: URI, options?: { openToSide?: boolean }): Promise { + registerOpener(opener: IOpener): IDisposable { + const remove = this._opener.push(opener); + return { dispose: remove }; + } - const { scheme, path, query, fragment } = resource; - - if (!scheme) { - // no scheme ?!? + async open(resource: URI, options?: { openToSide?: boolean }): Promise { + // no scheme ?!? + if (!resource.scheme) { return Promise.resolve(false); } + // check with contributed openers + for (const opener of this._opener.toArray()) { + const handled = await opener.open(resource, options); + if (handled) { + return true; + } + } + // use default openers + return this._doOpen(resource, options); + } + + private _doOpen(resource: URI, options?: { openToSide?: boolean }): Promise { + + const { scheme, path, query, fragment } = resource; if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https) || equalsIgnoreCase(scheme, Schemas.mailto)) { // open http or default mail application diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 05d0c902fc..e86cbc91c1 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -65,8 +65,8 @@ export interface CharacterMap { [char: string]: string; } -const autoCloseAlways = _ => true; -const autoCloseNever = _ => false; +const autoCloseAlways = () => true; +const autoCloseNever = () => false; const autoCloseBeforeWhitespace = (chr: string) => (chr === ' ' || chr === '\t'); export class CursorConfiguration { diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index 7e7194fe4e..3c6cf0fdd8 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -36,7 +36,7 @@ export interface ITextModelContentProvider { /** * Given a resource, return the content of the resource as `ITextModel`. */ - provideTextContent(resource: URI): Promise | null | undefined; + provideTextContent(resource: URI): Promise | null; } export interface ITextEditorModel extends IEditorModel { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index ca9bb2031b..ec9613caf5 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -94,6 +94,7 @@ export const enum MenuId { SCMSourceControl, SCMTitle, SearchContext, + StatusBarWindowIndicatorMenu, TouchBarContext, ViewItemContext, ViewTitle, diff --git a/src/vs/platform/dialogs/node/dialogIpc.ts b/src/vs/platform/dialogs/node/dialogIpc.ts index 4c79dfa81d..4772ce01f8 100644 --- a/src/vs/platform/dialogs/node/dialogIpc.ts +++ b/src/vs/platform/dialogs/node/dialogIpc.ts @@ -12,11 +12,11 @@ export class DialogChannel implements IServerChannel { constructor(@IDialogService private readonly dialogService: IDialogService) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); } - call(_, command: string, args?: any[]): Promise { + call(_: unknown, command: string, args?: any[]): Promise { switch (command) { case 'show': return this.dialogService.show(args![0], args![1], args![2]); case 'confirm': return this.dialogService.confirm(args![0]); diff --git a/src/vs/platform/download/node/downloadIpc.ts b/src/vs/platform/download/node/downloadIpc.ts index e363268147..38032607a7 100644 --- a/src/vs/platform/download/node/downloadIpc.ts +++ b/src/vs/platform/download/node/downloadIpc.ts @@ -29,7 +29,7 @@ export class DownloadServiceChannel implements IServerChannel { constructor() { } - listen(_, event: string, arg?: any): Event { + listen(_: unknown, event: string, arg?: any): Event { switch (event) { case 'upload': return Event.buffer(upload(URI.revive(arg))); } @@ -37,7 +37,7 @@ export class DownloadServiceChannel implements IServerChannel { throw new Error(`Event not found: ${event}`); } - call(_, command: string): Promise { + call(_: unknown, command: string): Promise { throw new Error(`Call not found: ${command}`); } } diff --git a/src/vs/platform/driver/node/driver.ts b/src/vs/platform/driver/node/driver.ts index df4f9f80df..cc1174c07e 100644 --- a/src/vs/platform/driver/node/driver.ts +++ b/src/vs/platform/driver/node/driver.ts @@ -48,11 +48,11 @@ export class DriverChannel implements IServerChannel { constructor(private driver: IDriver) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error('No event found'); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'getWindowIds': return this.driver.getWindowIds(); case 'capturePage': return this.driver.capturePage(arg); @@ -150,11 +150,11 @@ export class WindowDriverRegistryChannel implements IServerChannel { constructor(private registry: IWindowDriverRegistry) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'registerWindowDriver': return this.registry.registerWindowDriver(arg); case 'reloadWindowDriver': return this.registry.reloadWindowDriver(arg); @@ -195,11 +195,11 @@ export class WindowDriverChannel implements IServerChannel { constructor(private driver: IWindowDriver) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`No event found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'click': return this.driver.click(arg[0], arg[1], arg[2]); case 'doubleClick': return this.driver.doubleClick(arg); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index ae5accf82f..219e52640a 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -153,7 +153,8 @@ export interface ITranslation { export interface IExtensionGalleryService { _serviceBrand: any; isEnabled(): boolean; - query(options?: IQueryOptions): Promise>; + query(token: CancellationToken): Promise>; + query(options: IQueryOptions, token: CancellationToken): Promise>; download(extension: IGalleryExtension, operation: InstallOperation): Promise; reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise; getReadme(extension: IGalleryExtension, token: CancellationToken): Promise; diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index ba89e566b2..b0ad24d593 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -451,7 +451,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService { }); } - query(options: IQueryOptions = {}): Promise> { + query(token: CancellationToken): Promise>; + query(options: IQueryOptions, token: CancellationToken): Promise>; + query(arg1: any, arg2?: any): Promise> { + const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1; + const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2; + if (!this.isEnabled()) { return Promise.reject(new Error('No extension gallery service configured.')); } @@ -511,7 +516,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { query = query.withSortOrder(options.sortOrder); } - return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions, total }) => { + return this.queryGallery(query, token).then(({ galleryExtensions, total }) => { const extensions = galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, query, options.source)); // {{SQL CARBON EDIT}} const pageSize = extensions.length; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 263d53cfb8..47510c3e97 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -318,9 +318,6 @@ export class ExtensionManagementService extends Disposable implements IExtension const existingExtension = installed.filter(i => areSameExtensions(i.identifier, extension.identifier))[0]; if (existingExtension) { operation = InstallOperation.Update; - if (semver.gt(existingExtension.manifest.version, extension.version)) { - await this.uninstall(existingExtension, true); - } } this.downloadInstallableExtension(extension, operation) @@ -329,7 +326,10 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(local => this.installDependenciesAndPackExtensions(local, existingExtension) .then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error)))) .then( - local => { + async local => { + if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) { + await this.setUninstalled(existingExtension); + } this.installingExtensions.delete(key); onDidInstallExtensionSuccess(extension, operation, local); successCallback(null); @@ -529,7 +529,7 @@ export class ExtensionManagementService extends Disposable implements IExtension // filter out installed extensions const names = dependenciesAndPackExtensions.filter(id => installed.every(({ identifier: galleryIdentifier }) => !areSameExtensions(galleryIdentifier, { id }))); if (names.length) { - return this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }) + return this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None) .then(galleryResult => { const extensionsToInstall = galleryResult.firstPage; return Promise.all(extensionsToInstall.map(async e => { @@ -609,11 +609,11 @@ export class ExtensionManagementService extends Disposable implements IExtension } private findGalleryExtensionById(uuid: string): Promise { - return this.galleryService.query({ ids: [uuid], pageSize: 1 }).then(galleryResult => galleryResult.firstPage[0]); + return this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None).then(galleryResult => galleryResult.firstPage[0]); } private findGalleryExtensionByName(name: string): Promise { - return this.galleryService.query({ names: [name], pageSize: 1 }).then(galleryResult => galleryResult.firstPage[0]); + return this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None).then(galleryResult => galleryResult.firstPage[0]); } private joinErrors(errorOrErrors: (Error | string) | (Array)): Error { diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index bd8d89fe22..7c7f952e7a 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -701,7 +701,16 @@ export interface IUpdateContentOptions { } export interface IResolveFileOptions { + + /** + * Automatically continue resolving children of a directory until the provided resources + * are found. + */ resolveTo?: URI[]; + + /** + * Automatically continue resolving children of a directory if the number of children is 1. + */ resolveSingleChildDescendants?: boolean; } @@ -1034,12 +1043,6 @@ export interface ILegacyFileService { onFileChanges: Event; onAfterOperation: Event; - resolveFile(resource: URI, options?: IResolveFileOptions): Promise; - - resolveFiles(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise; - - existsFile(resource: URI): Promise; - resolveContent(resource: URI, options?: IResolveContentOptions): Promise; resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise; @@ -1057,4 +1060,4 @@ export interface ILegacyFileService { watchFileChanges(resource: URI): void; unwatchFileChanges(resource: URI): void; -} \ No newline at end of file +} diff --git a/src/vs/platform/files/test/files.test.ts b/src/vs/platform/files/test/files.test.ts index 293ca20dbc..811bffd4ae 100644 --- a/src/vs/platform/files/test/files.test.ts +++ b/src/vs/platform/files/test/files.test.ts @@ -42,7 +42,7 @@ suite('Files', () => { assert.strictEqual(true, r1.gotDeleted()); }); - function testIsEqual(testMethod: (pA: string | null | undefined, pB: string, ignoreCase: boolean) => boolean): void { + function testIsEqual(testMethod: (pA: string | undefined, pB: string, ignoreCase: boolean) => boolean): void { // corner cases assert(testMethod('', '', true)); diff --git a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts index c90d505d37..a067712f3b 100644 --- a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts +++ b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts @@ -35,12 +35,12 @@ export class TestInstantiationService extends InstantiationService { return this._create(service, { mock: true }); } - public stub(service: ServiceIdentifier, ctor?: any): T; - public stub(service: ServiceIdentifier, obj?: any): T; - public stub(service: ServiceIdentifier, ctor?: any, property?: string, value?: any): sinon.SinonStub; - public stub(service: ServiceIdentifier, obj?: any, property?: string, value?: any): sinon.SinonStub; - public stub(service: ServiceIdentifier, property?: string, value?: any): sinon.SinonStub; - public stub(serviceIdentifier: ServiceIdentifier, arg2?: any, arg3?: string, arg4?: any): sinon.SinonStub { + public stub(service: ServiceIdentifier, ctor: Function): T; + public stub(service: ServiceIdentifier, obj: Partial): T; + public stub(service: ServiceIdentifier, ctor: Function, property: string, value: any): sinon.SinonStub; + public stub(service: ServiceIdentifier, obj: Partial, property: string, value: any): sinon.SinonStub; + public stub(service: ServiceIdentifier, property: string, value: any): sinon.SinonStub; + public stub(serviceIdentifier: ServiceIdentifier, arg2: any, arg3?: string, arg4?: any): sinon.SinonStub { let service = typeof arg2 !== 'string' ? arg2 : undefined; let serviceMock: IServiceMock = { id: serviceIdentifier, service: service }; let property = typeof arg2 === 'string' ? arg2 : arg3; diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index 29eb292f91..4c2998f319 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -50,7 +50,7 @@ export class IssueService implements IIssueService { }); }); - ipcMain.on('vscode:issueReporterConfirmClose', (_) => { + ipcMain.on('vscode:issueReporterConfirmClose', () => { const messageOptions = { message: localize('confirmCloseIssueReporter', "Your input will not be saved. Are you sure you want to close this window?"), type: 'warning', @@ -72,7 +72,7 @@ export class IssueService implements IIssueService { } }); - ipcMain.on('vscode:workbenchCommand', (_, commandInfo) => { + ipcMain.on('vscode:workbenchCommand', (_: unknown, commandInfo) => { const { id, from, args } = commandInfo; let parentWindow: BrowserWindow | null; @@ -92,7 +92,7 @@ export class IssueService implements IIssueService { } }); - ipcMain.on('vscode:openExternal', (_, arg) => { + ipcMain.on('vscode:openExternal', (_: unknown, arg) => { this.windowsService.openExternal(arg); }); diff --git a/src/vs/platform/issue/node/issueIpc.ts b/src/vs/platform/issue/node/issueIpc.ts index 86fc7372ab..5b5513c644 100644 --- a/src/vs/platform/issue/node/issueIpc.ts +++ b/src/vs/platform/issue/node/issueIpc.ts @@ -11,11 +11,11 @@ export class IssueChannel implements IServerChannel { constructor(private service: IIssueService) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'openIssueReporter': return this.service.openReporter(arg); diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 7a4ec7b08e..ed53689bb4 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -70,11 +70,11 @@ export class LaunchChannel implements IServerChannel { constructor(private service: ILaunchService) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg: any): Promise { + call(_: unknown, command: string, arg: any): Promise { switch (command) { case 'start': const { args, userEnv } = arg as IStartArguments; diff --git a/src/vs/platform/localizations/node/localizationsIpc.ts b/src/vs/platform/localizations/node/localizationsIpc.ts index 4707a5d2d1..ba4665e824 100644 --- a/src/vs/platform/localizations/node/localizationsIpc.ts +++ b/src/vs/platform/localizations/node/localizationsIpc.ts @@ -15,7 +15,7 @@ export class LocalizationsChannel implements IServerChannel { this.onDidLanguagesChange = Event.buffer(service.onDidLanguagesChange, true); } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { switch (event) { case 'onDidLanguagesChange': return this.onDidLanguagesChange; } @@ -23,7 +23,7 @@ export class LocalizationsChannel implements IServerChannel { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'getLanguageIds': return this.service.getLanguageIds(arg); } diff --git a/src/vs/platform/log/node/logIpc.ts b/src/vs/platform/log/node/logIpc.ts index 1b998407bd..05fbb23eec 100644 --- a/src/vs/platform/log/node/logIpc.ts +++ b/src/vs/platform/log/node/logIpc.ts @@ -15,7 +15,7 @@ export class LogLevelSetterChannel implements IServerChannel { this.onDidChangeLogLevel = Event.buffer(service.onDidChangeLogLevel, true); } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { switch (event) { case 'onDidChangeLogLevel': return this.onDidChangeLogLevel; } @@ -23,7 +23,7 @@ export class LogLevelSetterChannel implements IServerChannel { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'setLevel': this.service.setLevel(arg); return Promise.resolve(); } diff --git a/src/vs/platform/menubar/node/menubarIpc.ts b/src/vs/platform/menubar/node/menubarIpc.ts index 339124c0a6..b6f9875bef 100644 --- a/src/vs/platform/menubar/node/menubarIpc.ts +++ b/src/vs/platform/menubar/node/menubarIpc.ts @@ -11,11 +11,11 @@ export class MenubarChannel implements IServerChannel { constructor(private service: IMenubarService) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'updateMenubar': return this.service.updateMenubar(arg[0], arg[1]); } diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index ed3031a056..d201aa23ea 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -5,13 +5,21 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const IOpenerService = createDecorator('openerService'); + +export interface IOpener { + open(resource: URI, options?: { openToSide?: boolean }): Promise; +} + export interface IOpenerService { _serviceBrand: any; + registerOpener(opener: IOpener): IDisposable; + /** * Opens a resource, like a webadress, a document uri, or executes command. * @@ -23,5 +31,6 @@ export interface IOpenerService { export const NullOpenerService: IOpenerService = Object.freeze({ _serviceBrand: undefined, + registerOpener() { return { dispose() { } }; }, open() { return Promise.resolve(false); } }); diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index fb7f15e818..159973ea0e 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -104,7 +104,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC return { items: mapToSerializable(items) } as ISerializableItemsChangeEvent; } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { switch (event) { case 'onDidChangeItems': return this.onDidChangeItems; } @@ -112,7 +112,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'getItems': { return this.whenReady.then(() => mapToSerializable(this.storageMainService.items)); diff --git a/src/vs/platform/telemetry/node/telemetryIpc.ts b/src/vs/platform/telemetry/node/telemetryIpc.ts index 897924f8a8..2c7b654606 100644 --- a/src/vs/platform/telemetry/node/telemetryIpc.ts +++ b/src/vs/platform/telemetry/node/telemetryIpc.ts @@ -16,11 +16,11 @@ export class TelemetryAppenderChannel implements IServerChannel { constructor(private appender: ITelemetryAppender) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); } - call(_, command: string, { eventName, data }: ITelemetryLog): Promise { + call(_: unknown, command: string, { eventName, data }: ITelemetryLog): Promise { this.appender.log(eventName, data); return Promise.resolve(null); } diff --git a/src/vs/platform/update/node/updateIpc.ts b/src/vs/platform/update/node/updateIpc.ts index 9683c8bccc..5e887ca29c 100644 --- a/src/vs/platform/update/node/updateIpc.ts +++ b/src/vs/platform/update/node/updateIpc.ts @@ -11,7 +11,7 @@ export class UpdateChannel implements IServerChannel { constructor(private service: IUpdateService) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { switch (event) { case 'onStateChange': return this.service.onStateChange; } @@ -19,7 +19,7 @@ export class UpdateChannel implements IServerChannel { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'checkForUpdates': return this.service.checkForUpdates(arg); case 'downloadUpdate': return this.service.downloadUpdate(); diff --git a/src/vs/platform/url/electron-browser/urlService.ts b/src/vs/platform/url/electron-browser/urlService.ts index 00093099b0..b3d91dd139 100644 --- a/src/vs/platform/url/electron-browser/urlService.ts +++ b/src/vs/platform/url/electron-browser/urlService.ts @@ -8,16 +8,21 @@ import { URI } from 'vs/base/common/uri'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URLServiceChannelClient, URLHandlerChannel } from 'vs/platform/url/node/urlIpc'; import { URLService } from 'vs/platform/url/common/urlService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; export class RelayURLService extends URLService implements IURLHandler { private urlService: IURLService; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { + constructor( + @IMainProcessService mainProcessService: IMainProcessService, + @IOpenerService openerService: IOpenerService + ) { super(); this.urlService = new URLServiceChannelClient(mainProcessService.getChannel('url')); mainProcessService.registerChannel('urlHandler', new URLHandlerChannel(this)); + openerService.registerOpener(this); } open(uri: URI): Promise { diff --git a/src/vs/platform/url/node/urlIpc.ts b/src/vs/platform/url/node/urlIpc.ts index 67a91cf863..f3a5ba8c24 100644 --- a/src/vs/platform/url/node/urlIpc.ts +++ b/src/vs/platform/url/node/urlIpc.ts @@ -13,11 +13,11 @@ export class URLServiceChannel implements IServerChannel { constructor(private service: IURLService) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'open': return this.service.open(URI.revive(arg)); } @@ -45,11 +45,11 @@ export class URLHandlerChannel implements IServerChannel { constructor(private handler: IURLHandler) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'handleURL': return this.handler.handleURL(URI.revive(arg)); } diff --git a/src/vs/platform/windows/node/windowsIpc.ts b/src/vs/platform/windows/node/windowsIpc.ts index 152951159f..4dee713c20 100644 --- a/src/vs/platform/windows/node/windowsIpc.ts +++ b/src/vs/platform/windows/node/windowsIpc.ts @@ -28,7 +28,7 @@ export class WindowsChannel implements IServerChannel { this.onRecentlyOpenedChange = Event.buffer(service.onRecentlyOpenedChange, true); } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { switch (event) { case 'onWindowOpen': return this.onWindowOpen; case 'onWindowFocus': return this.onWindowFocus; @@ -41,7 +41,7 @@ export class WindowsChannel implements IServerChannel { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'pickFileFolderAndOpen': return this.service.pickFileFolderAndOpen(arg); case 'pickFileAndOpen': return this.service.pickFileAndOpen(arg); diff --git a/src/vs/platform/workspaces/node/workspacesIpc.ts b/src/vs/platform/workspaces/node/workspacesIpc.ts index c9fb8848b9..579d8c2106 100644 --- a/src/vs/platform/workspaces/node/workspacesIpc.ts +++ b/src/vs/platform/workspaces/node/workspacesIpc.ts @@ -12,11 +12,11 @@ export class WorkspacesChannel implements IServerChannel { constructor(private service: IWorkspacesMainService) { } - listen(_, event: string): Event { + listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'createUntitledWorkspace': { const rawFolders: IWorkspaceFolderCreationData[] = arg[0]; diff --git a/src/vs/workbench/api/electron-browser/mainThreadConsole.ts b/src/vs/workbench/api/browser/mainThreadConsole.ts similarity index 93% rename from src/vs/workbench/api/electron-browser/mainThreadConsole.ts rename to src/vs/workbench/api/browser/mainThreadConsole.ts index 51271735b1..881eff63e9 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadConsole.ts +++ b/src/vs/workbench/api/browser/mainThreadConsole.ts @@ -6,10 +6,10 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadConsoleShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console'; -import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; +import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console'; +import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; +import { IBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast'; import { EXTENSION_LOG_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost'; @extHostNamedCustomer(MainContext.MainThreadConsole) diff --git a/src/vs/workbench/api/browser/mainThreadDocumentContentProviders.ts b/src/vs/workbench/api/browser/mainThreadDocumentContentProviders.ts index e228003cd5..5aa3b2b080 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentContentProviders.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentContentProviders.ts @@ -41,14 +41,14 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon $registerTextContentProvider(handle: number, scheme: string): void { const registration = this._textModelResolverService.registerTextModelContentProvider(scheme, { - provideTextContent: (uri: URI): Promise => { + provideTextContent: (uri: URI): Promise => { return this._proxy.$provideTextDocumentContent(handle, uri).then(value => { if (typeof value === 'string') { const firstLineText = value.substr(0, 1 + value.search(/\r?\n/)); const languageSelection = this._modeService.createByFilepathOrFirstLine(uri.fsPath, firstLineText); return this._modelService.createModel(value, languageSelection, uri); } - return undefined; + return null; }); } }); diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts similarity index 99% rename from src/vs/workbench/api/electron-browser/mainThreadDocuments.ts rename to src/vs/workbench/api/browser/mainThreadDocuments.ts index 29a64039ba..24eab2b36b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -12,7 +12,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService, shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IFileService } from 'vs/platform/files/common/files'; -import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors'; +import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { ExtHostContext, ExtHostDocumentsShape, IExtHostContext, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ITextEditorModel } from 'vs/workbench/common/editor'; import { ITextFileService, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts similarity index 98% rename from src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts rename to src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 93b6ed6977..b24e33b6fe 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -17,9 +17,9 @@ import { IModelService, shouldSynchronizeModel } from 'vs/editor/common/services import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IFileService } from 'vs/platform/files/common/files'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { MainThreadDocuments } from 'vs/workbench/api/electron-browser/mainThreadDocuments'; -import { MainThreadTextEditor } from 'vs/workbench/api/electron-browser/mainThreadEditor'; -import { MainThreadTextEditors } from 'vs/workbench/api/electron-browser/mainThreadEditors'; +import { MainThreadDocuments } from 'vs/workbench/api/browser/mainThreadDocuments'; +import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor'; +import { MainThreadTextEditors } from 'vs/workbench/api/browser/mainThreadEditors'; import { ExtHostContext, ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IExtHostContext, IModelAddedData, ITextEditorAddData, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { EditorViewColumn, editorGroupToViewColumn } from 'vs/workbench/api/common/shared/editor'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts similarity index 100% rename from src/vs/workbench/api/electron-browser/mainThreadEditor.ts rename to src/vs/workbench/api/browser/mainThreadEditor.ts diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts similarity index 96% rename from src/vs/workbench/api/electron-browser/mainThreadEditors.ts rename to src/vs/workbench/api/browser/mainThreadEditors.ts index e52df3cfdb..e906812a8f 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -18,14 +18,12 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors'; -import { MainThreadTextEditor } from 'vs/workbench/api/electron-browser/mainThreadEditor'; +import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; +import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor'; import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContext, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType, WorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IURLService } from 'vs/platform/url/common/url'; -import product from 'vs/platform/product/node/product'; export class MainThreadTextEditors implements MainThreadTextEditorsShape { @@ -270,7 +268,6 @@ CommandsRegistry.registerCommand('_workbench.open', function (accessor: Services const editorService = accessor.get(IEditorService); const editorGroupService = accessor.get(IEditorGroupsService); const openerService = accessor.get(IOpenerService); - const urlService = accessor.get(IURLService); const [resource, options, position, label] = args; @@ -282,12 +279,8 @@ CommandsRegistry.registerCommand('_workbench.open', function (accessor: Services if (resource && resource.scheme === 'command') { // do not allow to execute commands from here return Promise.resolve(undefined); - } - if (resource && (resource.scheme === product.urlProtocol || /^vscode/.test(resource.scheme))) { - return urlService.open(resource).then(_ => undefined); } - // finally, delegate to opener service return openerService.open(resource).then(_ => undefined); }); diff --git a/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts similarity index 95% rename from src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts rename to src/vs/workbench/api/browser/mainThreadExtensionService.ts index 0bf9330278..554864b56c 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -8,7 +8,6 @@ import Severity from 'vs/base/common/severity'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { IExtensionService, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionService } from 'vs/workbench/services/extensions/electron-browser/extensionService'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; @@ -17,11 +16,12 @@ import { EnablementState } from 'vs/platform/extensionManagement/common/extensio import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; +import { CancellationToken } from 'vs/base/common/cancellation'; @extHostNamedCustomer(MainContext.MainThreadExtensionService) export class MainThreadExtensionService implements MainThreadExtensionServiceShape { - private readonly _extensionService: ExtensionService; + private readonly _extensionService: IExtensionService; private readonly _notificationService: INotificationService; private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService; private readonly _windowService: IWindowService; @@ -33,9 +33,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IWindowService windowService: IWindowService ) { - if (extensionService instanceof ExtensionService) { - this._extensionService = extensionService; - } + this._extensionService = extensionService; this._notificationService = notificationService; this._extensionsWorkbenchService = extensionsWorkbenchService; this._windowService = windowService; @@ -108,7 +106,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha private async _handleMissingNotInstalledDependency(extension: IExtensionDescription, missingDependency: string): Promise { const extName = extension.displayName || extension.name; - const dependencyExtension = (await this._extensionsWorkbenchService.queryGallery({ names: [missingDependency] })).firstPage[0]; + const dependencyExtension = (await this._extensionsWorkbenchService.queryGallery({ names: [missingDependency] }, CancellationToken.None)).firstPage[0]; if (dependencyExtension) { this._notificationService.notify({ severity: Severity.Error, @@ -124,4 +122,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha } } + $onExtensionHostExit(code: number): void { + this._extensionService._onExtensionHostExit(code); + } } diff --git a/src/vs/workbench/api/browser/mainThreadHeapService.ts b/src/vs/workbench/api/browser/mainThreadHeapService.ts new file mode 100644 index 0000000000..73719e58d5 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadHeapService.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ExtHostContext, IExtHostContext } from '../common/extHost.protocol'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { IHeapService } from 'vs/workbench/services/heap/common/heap'; + +@extHostCustomer +export class MainThreadHeapService extends Disposable { + + constructor( + extHostContext: IExtHostContext, + @IHeapService heapService: IHeapService, + ) { + super(); + const proxy = extHostContext.getProxy(ExtHostContext.ExtHostHeapService); + this._register(heapService.onGarbageCollection((ids) => { + // send to ext host + proxy.$onGarbageCollection(ids); + })); + } +} diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts similarity index 92% rename from src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts rename to src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index e28feb6533..d7acd4bf82 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -14,15 +14,14 @@ import { Range as EditorRange } from 'vs/editor/common/core/range'; import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, CodeInsetDto, LinkDto, CallHierarchyDto } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; -import { IHeapService } from './mainThreadHeapService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import { IHeapService } from 'vs/workbench/services/heap/common/heap'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -127,7 +126,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- outline $registerDocumentSymbolProvider(handle: number, selector: ISerializedDocumentFilter[], displayName: string): void { - this._registrations[handle] = modes.DocumentSymbolProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.DocumentSymbolProviderRegistry.register(selector, { displayName, provideDocumentSymbols: (model: ITextModel, token: CancellationToken): Promise => { return this._proxy.$provideDocumentSymbols(handle, model.uri, token); @@ -168,7 +167,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha provider.onDidChange = emitter.event; } - this._registrations[handle] = modes.CodeLensProviderRegistry.register(typeConverters.LanguageSelector.from(selector), provider); + this._registrations[handle] = modes.CodeLensProviderRegistry.register(selector, provider); } $emitCodeLensEvent(eventHandle: number, event?: any): void { @@ -203,14 +202,14 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha provider.onDidChange = emitter.event; } - const langSelector = typeConverters.LanguageSelector.from(selector); + const langSelector = selector; this._registrations[handle] = codeInset.CodeInsetProviderRegistry.register(langSelector, provider); } // --- declaration $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.DefinitionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.DefinitionProviderRegistry.register(selector, { provideDefinition: (model, position, token): Promise => { return this._proxy.$provideDefinition(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); } @@ -218,7 +217,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } $registerDeclarationSupport(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.DeclarationProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.DeclarationProviderRegistry.register(selector, { provideDeclaration: (model, position, token) => { return this._proxy.$provideDeclaration(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); } @@ -226,7 +225,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } $registerImplementationSupport(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.ImplementationProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.ImplementationProviderRegistry.register(selector, { provideImplementation: (model, position, token): Promise => { return this._proxy.$provideImplementation(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); } @@ -234,7 +233,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } $registerTypeDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.TypeDefinitionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.TypeDefinitionProviderRegistry.register(selector, { provideTypeDefinition: (model, position, token): Promise => { return this._proxy.$provideTypeDefinition(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); } @@ -244,7 +243,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- extra info $registerHoverProvider(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.HoverProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.HoverProviderRegistry.register(selector, { provideHover: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { return this._proxy.$provideHover(handle, model.uri, position, token); } @@ -254,7 +253,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- occurrences $registerDocumentHighlightProvider(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.DocumentHighlightProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.DocumentHighlightProviderRegistry.register(selector, { provideDocumentHighlights: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { return this._proxy.$provideDocumentHighlights(handle, model.uri, position, token); } @@ -264,7 +263,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- references $registerReferenceSupport(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.ReferenceProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.ReferenceProviderRegistry.register(selector, { provideReferences: (model: ITextModel, position: EditorPosition, context: modes.ReferenceContext, token: CancellationToken): Promise => { return this._proxy.$provideReferences(handle, model.uri, position, context, token).then(MainThreadLanguageFeatures._reviveLocationDto); } @@ -274,7 +273,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- quick fix $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void { - this._registrations[handle] = modes.CodeActionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, { provideCodeActions: (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise => { return this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token).then(dto => { if (dto) { @@ -290,7 +289,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- formatting $registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier): void { - this._registrations[handle] = modes.DocumentFormattingEditProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.DocumentFormattingEditProviderRegistry.register(selector, { extensionId, provideDocumentFormattingEdits: (model: ITextModel, options: modes.FormattingOptions, token: CancellationToken): Promise => { return this._proxy.$provideDocumentFormattingEdits(handle, model.uri, options, token); @@ -299,7 +298,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier): void { - this._registrations[handle] = modes.DocumentRangeFormattingEditProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.DocumentRangeFormattingEditProviderRegistry.register(selector, { extensionId, provideDocumentRangeFormattingEdits: (model: ITextModel, range: EditorRange, options: modes.FormattingOptions, token: CancellationToken): Promise => { return this._proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options, token); @@ -308,7 +307,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void { - this._registrations[handle] = modes.OnTypeFormattingEditProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.OnTypeFormattingEditProviderRegistry.register(selector, { extensionId, autoFormatTriggerCharacters, provideOnTypeFormattingEdits: (model: ITextModel, position: EditorPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise => { @@ -346,7 +345,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerRenameSupport(handle: number, selector: ISerializedDocumentFilter[], supportResolveLocation: boolean): void { - this._registrations[handle] = modes.RenameProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.RenameProviderRegistry.register(selector, { provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken): Promise => { return this._proxy.$provideRenameEdits(handle, model.uri, position, newName, token).then(reviveWorkspaceEditDto); }, @@ -359,7 +358,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- suggest $registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean): void { - this._registrations[handle] = modes.CompletionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.CompletionProviderRegistry.register(selector, { triggerCharacters, provideCompletionItems: (model: ITextModel, position: EditorPosition, context: modes.CompletionContext, token: CancellationToken): Promise => { return this._proxy.$provideCompletionItems(handle, model.uri, position, context, token).then(result => { @@ -386,7 +385,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- parameter hints $registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], metadata: ISerializedSignatureHelpProviderMetadata): void { - this._registrations[handle] = modes.SignatureHelpProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.SignatureHelpProviderRegistry.register(selector, { signatureHelpTriggerCharacters: metadata.triggerCharacters, signatureHelpRetriggerCharacters: metadata.retriggerCharacters, @@ -400,7 +399,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- links $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.LinkProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.LinkProviderRegistry.register(selector, { provideLinks: (model, token) => { return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => { if (dto) { @@ -428,7 +427,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void { const proxy = this._proxy; - this._registrations[handle] = modes.ColorProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.ColorProviderRegistry.register(selector, { provideDocumentColors: (model, token) => { return proxy.$provideDocumentColors(handle, model.uri, token) .then(documentColors => { @@ -462,7 +461,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void { const proxy = this._proxy; - this._registrations[handle] = modes.FoldingRangeProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.FoldingRangeProviderRegistry.register(selector, { provideFoldingRanges: (model, context, token) => { return proxy.$provideFoldingRanges(handle, model.uri, context, token); } @@ -472,7 +471,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // -- smart select $registerSelectionRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.SelectionRangeRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = modes.SelectionRangeRegistry.register(selector, { provideSelectionRanges: (model, positions, token) => { return this._proxy.$provideSelectionRanges(handle, model.uri, positions, token); } @@ -482,7 +481,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- call hierarchy $registerCallHierarchyProvider(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = callh.CallHierarchyProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + this._registrations[handle] = callh.CallHierarchyProviderRegistry.register(selector, { provideCallHierarchyItem: (document, position, token) => { return this._proxy.$provideCallHierarchyItem(handle, document.uri, position, token).then(MainThreadLanguageFeatures._reviveCallHierarchyItemDto); }, diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index cfc7af127a..0dc3eaea4e 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -263,7 +263,7 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { const timeout = this._configurationService.getValue('editor.formatOnSaveTimeout', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const source = new CancellationTokenSource(); const request = getDocumentFormattingEdits(this._telemetryService, this._editorWorkerService, model, model.getFormattingOptions(), FormatMode.Auto, source.token); diff --git a/src/vs/workbench/api/electron-browser/mainThreadUrls.ts b/src/vs/workbench/api/browser/mainThreadUrls.ts similarity index 98% rename from src/vs/workbench/api/electron-browser/mainThreadUrls.ts rename to src/vs/workbench/api/browser/mainThreadUrls.ts index 2f7476fb46..9c7ab132f0 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadUrls.ts +++ b/src/vs/workbench/api/browser/mainThreadUrls.ts @@ -8,7 +8,7 @@ import { extHostNamedCustomer } from '../common/extHostCustomers'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { URI } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionUrlHandler } from 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; +import { IExtensionUrlHandler } from 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; class ExtensionUrlHandler implements IURLHandler { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4d65f58e50..e0c6f7f5f1 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -44,6 +44,8 @@ import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityReso import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import { IRelativePattern } from 'vs/base/common/glob'; +import { IRemoteConsoleLog } from 'vs/base/common/console'; // {{SQL CARBON EDIT}} import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; @@ -310,7 +312,7 @@ export interface ISerializedDocumentFilter { $serialized: true; language?: string; scheme?: string; - pattern?: GlobPattern; + pattern?: string | IRelativePattern; exclusive?: boolean; } @@ -610,6 +612,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable { $onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string | null): void; $onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise; $onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void; + $onExtensionHostExit(code: number): void; } export interface SCMProviderFeatures { diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 60f8a871f6..578d1ab4ea 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -35,11 +35,13 @@ namespace schema { case 'editor/title/context': return MenuId.EditorTitleContext; case 'debug/callstack/context': return MenuId.DebugCallStackContext; case 'debug/toolbar': return MenuId.DebugToolbar; + case 'menuBar/file': return MenuId.MenubarFileMenu; case 'scm/title': return MenuId.SCMTitle; case 'scm/sourceControl': return MenuId.SCMSourceControl; case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext; case 'scm/resourceState/context': return MenuId.SCMResourceContext; case 'scm/change/title': return MenuId.SCMChangeContext; + case 'statusBar/windowIndicator': return MenuId.StatusBarWindowIndicatorMenu; case 'view/title': return MenuId.ViewTitle; case 'view/item/context': return MenuId.ViewItemContext; // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts index 60d56596d2..047c53aad7 100644 --- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts @@ -14,19 +14,28 @@ import { ColorExtensionPoint } from 'vs/workbench/services/themes/common/colorEx import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint'; // --- mainThread participants -import 'vs/workbench/api/node/apiCommands'; import '../browser/mainThreadClipboard'; import '../browser/mainThreadCommands'; import '../browser/mainThreadConfiguration'; +import '../browser/mainThreadConsole'; // {{SQL CARBON EDIT}} // import '../browser/mainThreadDebugService'; import '../browser/mainThreadDecorations'; import '../browser/mainThreadDiagnostics'; import '../browser/mainThreadDialogs'; import '../browser/mainThreadDocumentContentProviders'; +import '../browser/mainThreadDocuments'; +import '../browser/mainThreadDocumentsAndEditors'; +import '../browser/mainThreadEditor'; +import '../browser/mainThreadEditors'; import '../browser/mainThreadErrors'; +import '../browser/mainThreadExtensionService'; import '../browser/mainThreadFileSystem'; import '../browser/mainThreadFileSystemEventService'; +import '../browser/mainThreadHeapService'; +import '../browser/mainThreadLanguageFeatures'; +import '../browser/mainThreadLanguages'; +import '../browser/mainThreadLogService'; import '../browser/mainThreadMessageService'; import '../browser/mainThreadOutputService'; import '../browser/mainThreadProgress'; @@ -36,25 +45,16 @@ import '../browser/mainThreadSCM'; import '../browser/mainThreadSearch'; import '../browser/mainThreadStatusBar'; import '../browser/mainThreadStorage'; -import './mainThreadComments'; -import './mainThreadConsole'; -import './mainThreadDocuments'; -import './mainThreadDocumentsAndEditors'; -import './mainThreadEditor'; -import './mainThreadEditors'; -import './mainThreadExtensionService'; -import './mainThreadHeapService'; -import './mainThreadLanguageFeatures'; -import '../browser/mainThreadLanguages'; -import '../browser/mainThreadLogService'; -import './mainThreadTask'; import '../browser/mainThreadTelemetry'; import '../browser/mainThreadTerminalService'; import '../browser/mainThreadTreeViews'; -import './mainThreadUrls'; -import './mainThreadWebview'; +import '../browser/mainThreadUrls'; import '../browser/mainThreadWindow'; import '../browser/mainThreadWorkspace'; +import './mainThreadComments'; +import './mainThreadTask'; +import './mainThreadWebview'; +import 'vs/workbench/api/node/apiCommands'; export class ExtensionPoints implements IWorkbenchContribution { diff --git a/src/vs/workbench/api/node/apiCommands.ts b/src/vs/workbench/api/node/apiCommands.ts index d06fa59044..dccc4dc132 100644 --- a/src/vs/workbench/api/node/apiCommands.ts +++ b/src/vs/workbench/api/node/apiCommands.ts @@ -68,6 +68,25 @@ CommandsRegistry.registerCommand({ } }); +interface INewWindowAPICommandOptions { +} + +export class NewWindowAPICommand { + public static ID = 'vscode.newWindow'; + public static execute(executor: ICommandsExecutor, options?: INewWindowAPICommandOptions): Promise { + return executor.executeCommand('_files.newWindow', [options]); + } +} +CommandsRegistry.registerCommand({ + id: NewWindowAPICommand.ID, + handler: adjustHandler(NewWindowAPICommand.execute), + description: { + description: 'Opens an new window', + args: [ + ] + } +}); + export class DiffAPICommand { public static ID = 'vscode.diff'; public static execute(executor: ICommandsExecutor, left: URI, right: URI, label: string, options?: vscode.TextDocumentShowOptions): Promise { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 93b0e1f1b5..e7a4b37e97 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -67,6 +67,7 @@ import * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { originalFSPath } from 'vs/base/common/resources'; import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; +import { withNullAsUndefined } from 'vs/base/common/types'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -537,7 +538,7 @@ export function createApiFactory( return extHostWorkspace.getRelativePath(pathOrUri, includeWorkspace); }, findFiles: (include, exclude, maxResults?, token?) => { - return extHostWorkspace.findFiles(typeConverters.GlobPattern.from(include), typeConverters.GlobPattern.from(exclude), maxResults, extension.identifier, token); + return extHostWorkspace.findFiles(typeConverters.GlobPattern.from(include), typeConverters.GlobPattern.from(withNullAsUndefined(exclude)), maxResults, extension.identifier, token); }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback, callbackOrToken?, token?: vscode.CancellationToken) => { let options: vscode.FindTextInFilesOptions; diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index a91e954a05..590de29b6b 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -642,7 +642,14 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private _gracefulExit(code: number): void { // to give the PH process a chance to flush any outstanding console // messages to the main process, we delay the exit() by some time - setTimeout(() => this._nativeExit(code), 500); + setTimeout(() => { + if (!!this._initData.environment.extensionTestsLocationURI) { + // If extension tests are running, give the exit code to the renderer + this._mainThreadExtensionsProxy.$onExtensionHostExit(code); + return; + } + this._nativeExit(code); + }, 500); } private _startExtensionHost(): Promise { diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 7c7404a382..c4c041b287 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -1082,7 +1082,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { $serialized: true, language: selector.language, scheme: this._transformScheme(selector.scheme), - pattern: selector.pattern, + pattern: typeof selector.pattern === 'undefined' ? undefined : typeConvert.GlobPattern.from(selector.pattern), exclusive: selector.exclusive }; } diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 59a4075854..b3daebbb1d 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -986,9 +986,9 @@ export namespace TextEditorOptions { export namespace GlobPattern { export function from(pattern: vscode.GlobPattern): string | types.RelativePattern; - export function from(pattern: undefined | null): undefined | null; - export function from(pattern: vscode.GlobPattern | undefined | null): string | types.RelativePattern | undefined | null; - export function from(pattern: vscode.GlobPattern | undefined | null): string | types.RelativePattern | undefined | null { + export function from(pattern: undefined): undefined; + export function from(pattern: vscode.GlobPattern | undefined): string | types.RelativePattern | undefined; + export function from(pattern: vscode.GlobPattern | undefined): string | types.RelativePattern | undefined { if (pattern instanceof types.RelativePattern) { return pattern; } diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index de443502a3..7bcdd527af 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -388,7 +388,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac // --- search --- - findFiles(include: string | RelativePattern | undefined | null, exclude: vscode.GlobPattern | undefined | null, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { + findFiles(include: string | RelativePattern | undefined, exclude: vscode.GlobPattern | undefined, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles`); let includePattern: string | undefined; diff --git a/src/vs/workbench/browser/nodeless.simpleservices.ts b/src/vs/workbench/browser/nodeless.simpleservices.ts index b355bee793..4bcde0ffba 100644 --- a/src/vs/workbench/browser/nodeless.simpleservices.ts +++ b/src/vs/workbench/browser/nodeless.simpleservices.ts @@ -288,7 +288,9 @@ export class SimpleExtensionGalleryService implements IExtensionGalleryService { return false; } - query(options?: IQueryOptions): Promise> { + query(token: CancellationToken): Promise>; + query(options: IQueryOptions, token: CancellationToken): Promise>; + query(arg1: any, arg2?: any): Promise> { // @ts-ignore return Promise.resolve(undefined); } diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index 142e6c45ca..4321305812 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -28,7 +28,7 @@ export class BinaryResourceDiffEditor extends SideBySideEditor { super(telemetryService, instantiationService, themeService, storageService); } - getMetadata(): string | null { + getMetadata(): string | undefined { const master = this.masterEditor; const details = this.detailsEditor; @@ -36,6 +36,6 @@ export class BinaryResourceDiffEditor extends SideBySideEditor { return nls.localize('metadataDiff', "{0} ↔ {1}", details.getMetadata(), master.getMetadata()); } - return null; + return undefined; } } diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index 6836de21c9..553eff7941 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -37,7 +37,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { get onDidOpenInPlace(): Event { return this._onDidOpenInPlace.event; } private callbacks: IOpenCallbacks; - private metadata: string | null; + private metadata: string | undefined; private binaryContainer: HTMLElement; private scrollbar: DomScrollableElement; private resourceViewerContext: ResourceViewerContext; @@ -110,20 +110,20 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { }); } - private handleMetadataChanged(meta: string | null): void { + private handleMetadataChanged(meta: string | undefined): void { this.metadata = meta; this._onMetadataChanged.fire(); } - getMetadata() { + getMetadata(): string | undefined { return this.metadata; } clearInput(): void { // Clear Meta - this.handleMetadataChanged(null); + this.handleMetadataChanged(undefined); // Clear Resource Viewer clearNode(this.binaryContainer); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index c07d110874..15a84779e0 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -143,43 +143,35 @@ interface StateDelta { indentation?: string; tabFocusMode?: boolean; screenReaderMode?: boolean; - metadata?: string | null; + metadata?: string | undefined; } class State { - private _selectionStatus: string | null | undefined; - get selectionStatus(): string | null | undefined { return this._selectionStatus; } + private _selectionStatus: string | undefined; + get selectionStatus(): string | undefined { return this._selectionStatus; } - private _mode: string | null | undefined; - get mode(): string | null | undefined { return this._mode; } + private _mode: string | undefined; + get mode(): string | undefined { return this._mode; } - private _encoding: string | null | undefined; - get encoding(): string | null | undefined { return this._encoding; } + private _encoding: string | undefined; + get encoding(): string | undefined { return this._encoding; } - private _EOL: string | null | undefined; - get EOL(): string | null | undefined { return this._EOL; } + private _EOL: string | undefined; + get EOL(): string | undefined { return this._EOL; } - private _indentation: string | null | undefined; - get indentation(): string | null | undefined { return this._indentation; } + private _indentation: string | undefined; + get indentation(): string | undefined { return this._indentation; } - private _tabFocusMode: boolean | null | undefined; - get tabFocusMode(): boolean | null | undefined { return this._tabFocusMode; } + private _tabFocusMode: boolean | undefined; + get tabFocusMode(): boolean | undefined { return this._tabFocusMode; } - private _screenReaderMode: boolean | null | undefined; - get screenReaderMode(): boolean | null | undefined { return this._screenReaderMode; } + private _screenReaderMode: boolean | undefined; + get screenReaderMode(): boolean | undefined { return this._screenReaderMode; } - private _metadata: string | null | undefined; - get metadata(): string | null | undefined { return this._metadata; } + private _metadata: string | undefined; + get metadata(): string | undefined { return this._metadata; } - constructor() { - this._selectionStatus = null; - this._mode = null; - this._encoding = null; - this._EOL = null; - this._tabFocusMode = false; - this._screenReaderMode = false; - this._metadata = null; - } + constructor() { } update(update: StateDelta): StateChange { const change = new StateChange(); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index e81b39f090..18d30c5951 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/statusbarpart'; import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { dispose, IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, toDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -49,6 +49,8 @@ export class StatusbarPart extends Part implements IStatusbarService { private statusMsgDispose: IDisposable; private styleElement: HTMLStyleElement; + private pendingEntries: { entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number, disposable: IDisposable }[] = []; + constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -67,6 +69,18 @@ export class StatusbarPart extends Part implements IStatusbarService { addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IDisposable { + // As long as we have not been created into a container yet, record all entries + // that are pending so that they can get created at a later point + if (!this.element) { + const pendingEntry = { entry, alignment, priority, disposable: Disposable.None }; + this.pendingEntries.push(pendingEntry); + + return toDisposable(() => { + this.pendingEntries = this.pendingEntries.filter(e => e !== pendingEntry); + pendingEntry.disposable.dispose(); + }); + } + // Render entry in status bar const el = this.doCreateStatusItem(alignment, priority, entry.showBeak ? 'has-beak' : undefined); const item = this.instantiationService.createInstance(StatusBarEntryItem, entry); @@ -146,6 +160,14 @@ export class StatusbarPart extends Part implements IStatusbarService { this.element.appendChild(el); } + // Fill in pending entries if any + while (this.pendingEntries.length) { + const entry = this.pendingEntries.shift(); + if (entry) { + entry.disposable = this.addEntry(entry.entry, entry.alignment, entry.priority); + } + } + return this.element; } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index f8b4ffdcb6..40a4cd23cd 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -123,6 +123,9 @@ export class MenubarControl extends Disposable { this.menuUpdater = this._register(new RunOnceScheduler(() => this.doUpdateMenubar(false), 200)); + this._onVisibilityChange = this._register(new Emitter()); + this._onFocusStateChange = this._register(new Emitter()); + if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') { for (const topLevelMenuName of Object.keys(this.topLevelMenus)) { const menu = this.topLevelMenus[topLevelMenuName]; @@ -130,15 +133,14 @@ export class MenubarControl extends Disposable { this._register(menu.onDidChange(() => this.updateMenubar())); } } - - this.doUpdateMenubar(true); } - this._onVisibilityChange = this._register(new Emitter()); - this._onFocusStateChange = this._register(new Emitter()); - this.windowService.getRecentlyOpened().then((recentlyOpened) => { this.recentlyOpened = recentlyOpened; + + if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') { + this.doUpdateMenubar(true); + } }); this.notifyExistingLinuxUser(); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 9cce7e4576..95ab02b3be 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -179,7 +179,7 @@ export class TitlebarPart extends Part implements ITitleService { } private updateRepresentedFilename(): void { - const file = toResource(this.editorService.activeEditor || null, { supportSideBySide: true, filter: 'file' }); + const file = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: 'file' }); const path = file ? file.fsPath : ''; // Apply to window @@ -282,7 +282,7 @@ export class TitlebarPart extends Part implements ITitleService { // Compute folder resource // Single Root Workspace: always the root single workspace in this case // Otherwise: root folder of the currently active file if any - const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor || null, { supportSideBySide: true })!); + const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor, { supportSideBySide: true })!); // Variables const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : ''; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 8eaa17c004..8a3813d465 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -17,7 +17,7 @@ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; - +import { PeekContext } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; const _ctxHasCompletionItemProvider = new RawContextKey('editorHasCallHierarchyProvider', false); const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisible', false); @@ -101,7 +101,6 @@ class CallHierarchyController extends Disposable implements IEditorContribution widget.showMessage(localize('no.item', "No results")); return; } - widget.showItem(item); }); } @@ -131,7 +130,10 @@ registerEditorAction(class extends EditorAction { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H }, - precondition: _ctxHasCompletionItemProvider + precondition: ContextKeyExpr.and( + _ctxHasCompletionItemProvider, + PeekContext.notInPeekEditor + ) }); } @@ -150,7 +152,10 @@ registerEditorCommand(new class extends EditorCommand { weight: KeybindingWeight.WorkbenchContrib + 10, primary: KeyCode.Escape }, - precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, ContextKeyExpr.not('config.editor.stablePeek')) + precondition: ContextKeyExpr.and( + _ctxCallHierarchyVisible, + ContextKeyExpr.not('config.editor.stablePeek') + ) }); } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 28f4ad7cec..5bf4e96e84 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/callHierarchy'; import { PeekViewWidget } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CallHierarchyItem, CallHierarchyProvider, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import { CallHierarchyProvider, CallHierarchyDirection, CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { FuzzyScore } from 'vs/base/common/filters'; import * as callHTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree'; @@ -262,6 +262,22 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { localDispose.push({ dispose: () => this._editor.deltaDecorations(ids, []) }); localDispose.push(value); }); + + let node: callHTree.Call | CallHierarchyItem = element; + let names = [element.item.name]; + while (true) { + let parent = this._tree.getParentElement(node); + if (!(parent instanceof callHTree.Call)) { + break; + } + if (this._direction === CallHierarchyDirection.CallsTo) { + names.push(parent.item.name); + } else { + names.unshift(parent.item.name); + } + node = parent; + } + this.setMetaTitle(localize('meta', " – {0}", names.join(' → '))); } }, undefined, this._disposables); @@ -295,14 +311,14 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { this._tree.onDidChangeSelection(e => { const [element] = e.elements; // don't close on click - if (element && !(e.browserEvent instanceof MouseEvent)) { + if (element && isNonEmptyArray(element.locations) && !(e.browserEvent instanceof MouseEvent)) { this.dispose(); this._editorService.openEditor({ resource: element.item.uri, options: { selection: element.locations[0].range } }); } - }); + }, undefined, this._disposables); } showLoading(): void { @@ -319,30 +335,29 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { this._show(); } - showItem(item: CallHierarchyItem) { - this._parent.dataset['state'] = State.Data; + async showItem(item: CallHierarchyItem): Promise { this._show(); - this._tree.setInput(item).then(() => { + await this._tree.setInput(item); - if (!this._tree.getFirstElementChild(item)) { - // - this.showMessage(this._direction === CallHierarchyDirection.CallsFrom - ? localize('empt.callsFrom', "No calls from '{0}'", item.name) - : localize('empt.callsTo', "No calls to '{0}'", item.name)); + const [root] = this._tree.getNode(item).children; + await this._tree.expand(root.element as callHTree.Call); + const firstChild = this._tree.getFirstElementChild(root.element); + if (!(firstChild instanceof callHTree.Call)) { + // + this.showMessage(this._direction === CallHierarchyDirection.CallsFrom + ? localize('empt.callsFrom', "No calls from '{0}'", item.name) + : localize('empt.callsTo', "No calls to '{0}'", item.name)); - } else { - this._tree.domFocus(); - this._tree.focusFirst(); - this.setTitle( - item.name, - item.detail || this._labelService.getUriLabel(item.uri, { relative: true }), - ); - this.setMetaTitle(this._direction === CallHierarchyDirection.CallsFrom - ? localize('title.from', " – calls from '{0}'", item.name) - : localize('title.to', " – calls to '{0}'", item.name)); - } - }); + } else { + this._parent.dataset['state'] = State.Data; + this._tree.domFocus(); + this._tree.setFocus([firstChild]); + this.setTitle( + item.name, + item.detail || this._labelService.getUriLabel(item.uri, { relative: true }), + ); + } if (!this._toggleDirection) { this._toggleDirection = new ToggleHierarchyDirectionAction( diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index 1c7a043ab6..b7b9dc6be1 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -11,10 +11,10 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { symbolKindToCssClass, Location } from 'vs/editor/common/modes'; import { ILabelService } from 'vs/platform/label/common/label'; +import { Range } from 'vs/editor/common/core/range'; export class Call { constructor( - readonly direction: CallHierarchyDirection, readonly item: CallHierarchyItem, readonly locations: Location[] ) { } @@ -24,22 +24,29 @@ export class SingleDirectionDataSource implements IAsyncDataSource CallHierarchyDirection + public getDirection: () => CallHierarchyDirection ) { } - hasChildren(_element: CallHierarchyItem): boolean { + hasChildren(): boolean { return true; } async getChildren(element: CallHierarchyItem | Call): Promise { if (element instanceof Call) { - element = element.item; + try { + const direction = this.getDirection(); + const calls = await this.provider.resolveCallHierarchyItem(element.item, direction, CancellationToken.None); + if (!calls) { + return []; + } + return calls.map(([item, locations]) => new Call(item, locations)); + } catch { + return []; + } + } else { + // 'root' + return [new Call(element, [{ uri: element.uri, range: Range.lift(element.range).collapseToStart() }])]; } - const direction = this.direction(); - const calls = await this.provider.resolveCallHierarchyItem(element, direction, CancellationToken.None); - return calls - ? calls.map(([item, locations]) => new Call(direction, item, locations)) - : []; } } @@ -50,7 +57,7 @@ export class IdentityProvider implements IIdentityProvider { } class CallRenderingTemplate { - iconLabel: IconLabel; + readonly iconLabel: IconLabel; } export class CallRenderer implements ITreeRenderer { @@ -59,7 +66,9 @@ export class CallRenderer implements ITreeRenderer = this._onInputDidChange.event; private disposables: IDisposable[] = []; - private inputWidget: CodeEditorWidget; + private readonly inputWidget: CodeEditorWidget; + private readonly inputModel: ITextModel; private stylingContainer: HTMLDivElement; private placeholderText: HTMLDivElement; @@ -136,7 +137,8 @@ export class SuggestEnabledInput extends Widget implements IThemable { this.disposables.push(this.inputWidget); let scopeHandle = uri.parse(resourceHandle); - this.inputWidget.setModel(modelService.createModel('', null, scopeHandle, true)); + this.inputModel = modelService.createModel('', null, scopeHandle, true); + this.inputWidget.setModel(this.inputModel); this.disposables.push(this.inputWidget.onDidPaste(() => this.setValue(this.getValue()))); // setter cleanses @@ -203,7 +205,7 @@ export class SuggestEnabledInput extends Widget implements IThemable { public setValue(val: string) { val = val.replace(/\s/g, ' '); - const fullRange = new Range(1, 1, 1, this.getValue().length + 1); + const fullRange = this.inputModel.getFullModelRange(); this.inputWidget.executeEdits('suggestEnabledInput.setValue', [EditOperation.replace(fullRange, val)]); this.inputWidget.setScrollTop(0); this.inputWidget.setPosition(new Position(1, val.length + 1)); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index c85a209dce..c8924c0618 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -97,7 +97,7 @@ export class CallStackView extends ViewletPanel { dom.addClass(container, 'debug-call-stack'); const treeContainer = renderViewTree(container); - this.dataSource = new CallStackDataSource(); + this.dataSource = new CallStackDataSource(this.debugService); this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, new CallStackDelegate(), [ new SessionsRenderer(), new ThreadsRenderer(), @@ -562,6 +562,8 @@ function isDeemphasized(frame: IStackFrame): boolean { class CallStackDataSource implements IAsyncDataSource { deemphasizedStackFramesToShow: IStackFrame[]; + constructor(private debugService: IDebugService) { } + hasChildren(element: IDebugModel | CallStackItem): boolean { return isDebugModel(element) || isDebugSession(element) || (element instanceof Thread && element.stopped); } @@ -573,13 +575,18 @@ class CallStackDataSource implements IAsyncDataSource 1) { - return Promise.resolve(sessions); + return Promise.resolve(sessions.filter(s => !s.parentSession)); } const threads = sessions[0].getAllThreads(); // Only show the threads in the call stack if there is more than 1 thread. return threads.length === 1 ? this.getThreadChildren(threads[0]) : Promise.resolve(threads); } else if (isDebugSession(element)) { + const childSessions = this.debugService.getModel().getSessions().filter(s => s.parentSession === element); + if (childSessions.length) { + return Promise.resolve(childSessions); + } + return Promise.resolve(element.getAllThreads()); } else { return this.getThreadChildren(element); diff --git a/src/vs/workbench/contrib/debug/browser/debugActionItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionItems.ts index 3972f8395e..30a392a074 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionItems.ts @@ -217,7 +217,15 @@ export class FocusSessionActionItem extends SelectActionItem { private update() { const session = this.debugService.getViewModel().focusedSession; const sessions = this.getSessions(); - const names = sessions.map(s => s.getLabel()); + const names = sessions.map(s => { + const label = s.getLabel(); + if (s.parentSession) { + // Indent child sessions so they look like children + return `\u00A0\u00A0${label}`; + } + + return label; + }); this.setOptions(names.map(data => { text: data }), session ? sessions.indexOf(session) : undefined); } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index d57eaafb16..907d373337 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -516,6 +516,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo); this.exceptionWidget.show({ lineNumber, column }, 0); + this.editor.revealLine(lineNumber); } private closeExceptionWidget(): void { diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 90b08a3442..cbe90c6377 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -37,7 +37,6 @@ export class ExceptionWidget extends ZoneWidget { this._applyTheme(themeService.getTheme()); this._disposables.push(themeService.onThemeChange(this._applyTheme.bind(this))); - this.create(); const onDidLayoutChangeScheduler = new RunOnceScheduler(() => this._doLayout(undefined, undefined), 50); this._disposables.push(this.editor.onDidLayoutChange(() => onDidLayoutChangeScheduler.schedule())); diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index df38cbc163..fb42faceb2 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -39,11 +39,6 @@ border: 1px solid white; } -.vs-dark .monaco-workbench .debug-action.start, -.hc-black .monaco-workbench .debug-action.start { - background: url('continue-inverse.svg') center center no-repeat; -} - .vs-dark .monaco-workbench .debug-action.configure, .hc-black .monaco-workbench .debug-action.configure { background: url('configure-inverse.svg') center center no-repeat; diff --git a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css index beed8695a0..d4ffe3099d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css @@ -24,7 +24,6 @@ .monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace { margin-top: 0.5em; - max-height: 500px; } .monaco-editor .zone-widget .zone-widget-container.exception-widget a { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index f4179dbaa8..dfed95f4a5 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -149,6 +149,7 @@ export interface IDebugSession extends ITreeElement { readonly unresolvedConfiguration: IConfig | undefined; readonly state: State; readonly root: IWorkspaceFolder; + readonly parentSession: IDebugSession | undefined; getLabel(): string; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 0d076f5e16..5b29635417 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -12,7 +12,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { RunOnceScheduler } from 'vs/base/common/async'; import severity from 'vs/base/common/severity'; import { isObject, isString, isUndefinedOrNull } from 'vs/base/common/types'; -import { distinct } from 'vs/base/common/arrays'; +import { distinct, lastIndex } from 'vs/base/common/arrays'; import { Range, IRange } from 'vs/editor/common/core/range'; import { ITreeElement, IExpression, IExpressionContainer, IDebugSession, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IDebugModel, IReplElementSource, @@ -797,7 +797,17 @@ export class DebugModel implements IDebugModel { return true; }); - this.sessions.push(session); + + let index = -1; + if (session.parentSession) { + // Make sure that child sessions are placed after the parent session + index = lastIndex(this.sessions, s => s.parentSession === session.parentSession || s === session.parentSession); + } + if (index >= 0) { + this.sessions.splice(index + 1, 0, session); + } else { + this.sessions.push(session); + } this._onDidChangeCallStack.fire(undefined); } diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 024b8bfdb0..92c75fb6ba 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -25,7 +25,7 @@ export class ReplModel { addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { const expression = new Expression(name); - this.addReplElements([expression]); + this.addReplElement(expression); return expression.evaluate(this.session, stackFrame, 'repl'); } @@ -39,26 +39,18 @@ export class ReplModel { } if (typeof data === 'string') { - const previousElement = this.replElements.length && (this.replElements[this.replElements.length - 1] as SimpleReplElement); - - const toAdd = data.split('\n').map((line, index) => new SimpleReplElement(`topReplElement:${topReplElementCounter++}`, line, sev, index === 0 ? source : undefined)); - if (previousElement && previousElement.value === '') { - // remove potential empty lines between different repl types - this.replElements.pop(); - } else if (previousElement instanceof SimpleReplElement && sev === previousElement.severity && toAdd.length && toAdd[0].sourceData === previousElement.sourceData) { - previousElement.value += toAdd.shift()!.value; - } - this.addReplElements(toAdd); + const element = new SimpleReplElement(`topReplElement:${topReplElementCounter++}`, data.trimRight(), sev, source); + this.addReplElement(element); } else { // TODO@Isidor hack, we should introduce a new type which is an output that can fetch children like an expression (data).severity = sev; (data).sourceData = source; - this.addReplElements([data]); + this.addReplElement(data); } } - private addReplElements(newElements: IReplElement[]): void { - this.replElements.push(...newElements); + private addReplElement(newElement: IReplElement): void { + this.replElements.push(newElement); if (this.replElements.length > MAX_REPL_LENGTH) { this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH); } diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts index 2b6ebd3cdb..d14d395246 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts @@ -34,8 +34,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost'; -import { IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; -import { IRemoteConsoleLog, parse, getFirstFrame } from 'vs/base/node/console'; +import { IBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast'; +import { IRemoteConsoleLog, parse, getFirstFrame } from 'vs/base/common/console'; import { TaskEvent, TaskEventKind, TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -315,7 +315,7 @@ export class DebugService implements IDebugService { } } - return this.createSession(launchForName, launchForName!.getConfiguration(name), noDebug); + return this.createSession(launchForName, launchForName!.getConfiguration(name), noDebug, parentSession); })).then(values => values.every(success => !!success)); // Compound launch is a success only if each configuration launched successfully } @@ -325,7 +325,7 @@ export class DebugService implements IDebugService { return Promise.reject(new Error(message)); } - return this.createSession(launch, config, noDebug); + return this.createSession(launch, config, noDebug, parentSession); }); })); }).then(success => { @@ -341,7 +341,7 @@ export class DebugService implements IDebugService { /** * gets the debugger for the type, resolves configurations by providers, substitutes variables and runs prelaunch tasks */ - private createSession(launch: ILaunch | undefined, config: IConfig | undefined, noDebug: boolean): Promise { + private createSession(launch: ILaunch | undefined, config: IConfig | undefined, noDebug: boolean, parentSession?: IDebugSession): Promise { // We keep the debug type in a separate variable 'type' so that a no-folder config has no attributes. // Storing the type in the config would break extensions that assume that the no-folder case is indicated by an empty config. let type: string | undefined; @@ -386,7 +386,7 @@ export class DebugService implements IDebugService { const workspace = launch ? launch.workspace : undefined; return this.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask).then(result => { if (result === TaskRunResult.Success) { - return this.doCreateSession(workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }); + return this.doCreateSession(workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, parentSession); } return false; }); @@ -415,9 +415,9 @@ export class DebugService implements IDebugService { /** * instantiates the new session, initializes the session, registers session listeners and reports telemetry */ - private doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }): Promise { + private doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, parentSession?: IDebugSession): Promise { - const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model); + const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, parentSession); this.model.addSession(session); // register listeners as the very first thing! this.registerSessionListeners(session); diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts index 18568c251a..f5318e3438 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts @@ -57,6 +57,7 @@ export class DebugSession implements IDebugSession { private _configuration: { resolved: IConfig, unresolved: IConfig | undefined }, public root: IWorkspaceFolder, private model: DebugModel, + private _parentSession: IDebugSession | undefined, @IDebugService private readonly debugService: IDebugService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IOutputService private readonly outputService: IOutputService, @@ -83,6 +84,10 @@ export class DebugSession implements IDebugSession { return this._configuration.unresolved; } + get parentSession(): IDebugSession | undefined { + return this._parentSession; + } + setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig | undefined }) { this._configuration = configuration; } @@ -178,6 +183,10 @@ export class DebugSession implements IDebugSession { }); }); }); + }).then(undefined, err => { + this.initialized = true; + this._onDidChangeState.fire(); + return Promise.reject(err); }); } diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index cfc08d64a1..f8edf85345 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -123,6 +123,11 @@ export class MockDebugService implements IDebugService { } export class MockSession implements IDebugSession { + + get parentSession(): IDebugSession | undefined { + return undefined; + } + getReplElements(): IReplElement[] { return []; } diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts index 6057b3d650..6322804447 100644 --- a/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts @@ -13,6 +13,10 @@ import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/contrib/debug/electron-browser/debugSession'; import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; +function createMockSession(model: DebugModel, name = 'mockSession', parentSession?: DebugSession | undefined): DebugSession { + return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); +} + suite('Debug - Model', () => { let model: DebugModel; let rawSession: MockRawSession; @@ -109,7 +113,7 @@ suite('Debug - Model', () => { test('threads simple', () => { const threadId = 1; const threadName = 'firstThread'; - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); + const session = createMockSession(model); model.addSession(session); assert.equal(model.getSessions(true).length, 1); @@ -136,7 +140,7 @@ suite('Debug - Model', () => { const stoppedReason = 'breakpoint'; // Add the threads - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); + const session = createMockSession(model); model.addSession(session); session['raw'] = rawSession; @@ -224,7 +228,7 @@ suite('Debug - Model', () => { const runningThreadId = 2; const runningThreadName = 'runningThread'; const stoppedReason = 'breakpoint'; - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); + const session = createMockSession(model); model.addSession(session); session['raw'] = rawSession; @@ -338,7 +342,7 @@ suite('Debug - Model', () => { }); test('repl expressions', () => { - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); + const session = createMockSession(model); assert.equal(session.getReplElements().length, 0); model.addSession(session); @@ -362,7 +366,7 @@ suite('Debug - Model', () => { }); test('stack frame get specific source name', () => { - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); + const session = createMockSession(model); model.addSession(session); let firstStackFrame: StackFrame; @@ -390,10 +394,33 @@ suite('Debug - Model', () => { assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js'); }); + test('debug child sessions are added in correct order', () => { + const session = createMockSession(model); + model.addSession(session); + const secondSession = createMockSession(model, 'mockSession2'); + model.addSession(secondSession); + const firstChild = createMockSession(model, 'firstChild', session); + model.addSession(firstChild); + const secondChild = createMockSession(model, 'secondChild', session); + model.addSession(secondChild); + const thirdSession = createMockSession(model, 'mockSession3'); + model.addSession(thirdSession); + const anotherChild = createMockSession(model, 'secondChild', secondSession); + model.addSession(anotherChild); + + const sessions = model.getSessions(); + assert.equal(sessions[0].getId(), session.getId()); + assert.equal(sessions[1].getId(), firstChild.getId()); + assert.equal(sessions[2].getId(), secondChild.getId()); + assert.equal(sessions[3].getId(), secondSession.getId()); + assert.equal(sessions[4].getId(), anotherChild.getId()); + assert.equal(sessions[5].getId(), thirdSession.getId()); + }); + // Repl output test('repl output', () => { - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); + const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); const repl = new ReplModel(session); repl.appendToRepl('first line\n', severity.Error); repl.appendToRepl('second line', severity.Error); diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 45b807e6c8..5abcb41837 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -24,7 +24,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { lastSessionDateStorageKey } from 'vs/platform/telemetry/node/workbenchCommonProperties'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; @@ -86,11 +86,11 @@ suite('Experiment Service', () => { testConfigurationService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, testConfigurationService); instantiationService.stub(ILifecycleService, new TestLifecycleService()); - instantiationService.stub(IStorageService, { get: (a, b, c) => c, getBoolean: (a, b, c) => c, store: () => { }, remove: () => { } }); + instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } }); setup(() => { instantiationService.stub(IEnvironmentService, {}); - instantiationService.stub(IStorageService, { get: (a, b, c) => c, getBoolean: (a, b, c) => c, store: () => { }, remove: () => { } }); + instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } }); }); teardown(() => { @@ -196,11 +196,11 @@ suite('Experiment Service', () => { ] }; - instantiationService.stub(IStorageService, { - get: (a, b, c) => { + instantiationService.stub(IStorageService, >{ + get: (a: string, b: StorageScope, c?: string) => { return a === lastSessionDateStorageKey ? 'some-date' : undefined; }, - getBoolean: (a, b, c) => c, store: () => { }, remove: () => { } + getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } }); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { @@ -240,11 +240,11 @@ suite('Experiment Service', () => { ] }; - instantiationService.stub(IStorageService, { - get: (a, b, c) => { + instantiationService.stub(IStorageService, >{ + get: (a: string, b: StorageScope, c: string | undefined) => { return a === lastSessionDateStorageKey ? 'some-date' : undefined; }, - getBoolean: (a, b, c) => c, store: () => { }, remove: () => { } + getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } }); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { @@ -372,9 +372,9 @@ suite('Experiment Service', () => { ] }; - instantiationService.stub(IStorageService, { - get: (a, b, c) => a === 'experiments.experiment1' ? JSON.stringify({ state: ExperimentState.Complete }) : c, - store: (a, b, c) => { } + instantiationService.stub(IStorageService, >{ + get: (a: string, b: StorageScope, c?: string) => a === 'experiments.experiment1' ? JSON.stringify({ state: ExperimentState.Complete }) : c, + store: () => { } }); testObject = instantiationService.createInstance(TestExperimentService); @@ -400,9 +400,9 @@ suite('Experiment Service', () => { ] }; - instantiationService.stub(IStorageService, { - get: (a, b, c) => a === 'experiments.experiment1' ? JSON.stringify({ enabled: true, state: ExperimentState.Run }) : c, - store: (a, b, c) => { } + instantiationService.stub(IStorageService, >{ + get: (a: string, b: StorageScope, c?: string) => a === 'experiments.experiment1' ? JSON.stringify({ enabled: true, state: ExperimentState.Run }) : c, + store: () => { } }); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { @@ -508,8 +508,8 @@ suite('Experiment Service', () => { let storageDataExperiment1: ExperimentSettings | null = { enabled: false }; let storageDataExperiment2: ExperimentSettings | null = { enabled: false }; let storageDataAllExperiments: string[] | null = ['experiment1', 'experiment2', 'experiment3']; - instantiationService.stub(IStorageService, { - get: (a, b, c) => { + instantiationService.stub(IStorageService, >{ + get: (a: string, b: StorageScope, c?: string) => { switch (a) { case 'experiments.experiment1': return JSON.stringify(storageDataExperiment1); @@ -522,7 +522,7 @@ suite('Experiment Service', () => { } return c; }, - store: (a, b, c) => { + store: (a: string, b: any, c: StorageScope) => { switch (a) { case 'experiments.experiment1': storageDataExperiment1 = JSON.parse(b); @@ -537,7 +537,7 @@ suite('Experiment Service', () => { break; } }, - remove: a => { + remove: (a: string) => { switch (a) { case 'experiments.experiment1': storageDataExperiment1 = null; @@ -580,8 +580,8 @@ suite('Experiment Service', () => { let storageDataExperiment3: ExperimentSettings | null = { enabled: true, state: ExperimentState.Evaluating }; let storageDataExperiment4: ExperimentSettings | null = { enabled: true, state: ExperimentState.Complete }; let storageDataAllExperiments: string[] | null = ['experiment1', 'experiment2', 'experiment3', 'experiment4']; - instantiationService.stub(IStorageService, { - get: (a, b, c) => { + instantiationService.stub(IStorageService, >{ + get: (a: string, b: StorageScope, c?: string) => { switch (a) { case 'experiments.experiment1': return JSON.stringify(storageDataExperiment1); @@ -601,19 +601,19 @@ suite('Experiment Service', () => { store: (a, b, c) => { switch (a) { case 'experiments.experiment1': - storageDataExperiment1 = JSON.parse(b); + storageDataExperiment1 = JSON.parse(b + ''); break; case 'experiments.experiment2': - storageDataExperiment2 = JSON.parse(b); + storageDataExperiment2 = JSON.parse(b + ''); break; case 'experiments.experiment3': - storageDataExperiment3 = JSON.parse(b); + storageDataExperiment3 = JSON.parse(b + ''); break; case 'experiments.experiment4': - storageDataExperiment4 = JSON.parse(b); + storageDataExperiment4 = JSON.parse(b + ''); break; case 'allExperiments': - storageDataAllExperiments = JSON.parse(b); + storageDataAllExperiments = JSON.parse(b + ''); break; default: break; @@ -768,8 +768,8 @@ suite('Experiment Service', () => { let storageDataExperiment3 = { enabled: true, state: ExperimentState.Evaluating }; let storageDataExperiment4 = { enabled: true, state: ExperimentState.Evaluating }; - instantiationService.stub(IStorageService, { - get: (a, b, c) => { + instantiationService.stub(IStorageService, >{ + get: (a: string, b: StorageScope, c?: string) => { switch (a) { case 'currentOrPreviouslyRunExperiments': return JSON.stringify(['experiment1', 'experiment2']); @@ -781,10 +781,10 @@ suite('Experiment Service', () => { store: (a, b, c) => { switch (a) { case 'experiments.experiment3': - storageDataExperiment3 = JSON.parse(b); + storageDataExperiment3 = JSON.parse(b + ''); break; case 'experiments.experiment4': - storageDataExperiment4 = JSON.parse(b); + storageDataExperiment4 = JSON.parse(b + ''); break; default: break; diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index 436c168181..3d003ef3f1 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -95,6 +95,7 @@ suite('Experimental Prompts', () => { assert.equal(b, promptText); assert.equal(c.length, 2); c[0].run(); + return undefined!; } }); @@ -119,6 +120,7 @@ suite('Experimental Prompts', () => { assert.equal(b, promptText); assert.equal(c.length, 2); c[1].run(); + return undefined!; } }); @@ -143,6 +145,7 @@ suite('Experimental Prompts', () => { assert.equal(b, promptText); assert.equal(c.length, 2); options.onCancel(); + return undefined!; } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts index 439f17b7c3..04d01ad9c1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts @@ -84,7 +84,7 @@ export class GalleryExtensionsHandler extends QuickOpenHandler { getResults(text: string, token: CancellationToken): Promise> { if (/\./.test(text)) { - return this.galleryService.query({ names: [text], pageSize: 1 }) + return this.galleryService.query({ names: [text], pageSize: 1 }, token) .then(galleryResult => { const entries: SimpleEntry[] = []; const galleryExtension = galleryResult.firstPage[0]; diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 53dd8d23e5..0e63dbcc06 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -85,7 +85,8 @@ export interface IExtensionsWorkbenchService { onChange: Event; local: IExtension[]; queryLocal(): Promise; - queryGallery(options?: IQueryOptions): Promise>; + queryGallery(token: CancellationToken): Promise>; + queryGallery(options: IQueryOptions, token: CancellationToken): Promise>; canInstall(extension: IExtension): boolean; install(vsix: string): Promise; install(extension: IExtension, promptToInstallDependencies?: boolean): Promise; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts index 891d30471d..f09e274cfd 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts @@ -739,7 +739,7 @@ export class ExtensionEditor extends BaseEditor { getChildren(): Promise { if (this.hasChildren) { const names = arrays.distinct(this.extension.extensionPack, e => e.toLowerCase()); - return extensionsWorkbenchService.queryGallery({ names, pageSize: names.length }) + return extensionsWorkbenchService.queryGallery({ names, pageSize: names.length }, CancellationToken.None) .then(result => result.firstPage.map(extension => new ExtensionData(extension, this))); } return Promise.resolve(null); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts index 8472c65b55..a585a7e67f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts @@ -387,7 +387,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (filteredWanted.length) { try { - let validRecommendations = (await this._galleryService.query({ names: filteredWanted, pageSize: filteredWanted.length })).firstPage + let validRecommendations = (await this._galleryService.query({ names: filteredWanted, pageSize: filteredWanted.length }, CancellationToken.None)).firstPage .map(extension => extension.identifier.id.toLowerCase()); if (validRecommendations.length !== filteredWanted.length) { @@ -776,7 +776,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const lookup = product.extensionKeywords || {}; const keywords = lookup[fileExtension] || []; - this._galleryService.query({ text: `tag:"__ext_${fileExtension}" ${keywords.map(tag => `tag:"${tag}"`)}` }).then(pager => { + this._galleryService.query({ text: `tag:"__ext_${fileExtension}" ${keywords.map(tag => `tag:"${tag}"`)}` }, CancellationToken.None).then(pager => { if (!pager || !pager.firstPage || !pager.firstPage.length) { return; } 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 8fa045f09d..3cf970f9be 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -49,6 +49,7 @@ import { ExtensionActivationProgress } from 'vs/workbench/contrib/extensions/ele import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker'; +import { CancellationToken } from 'vs/base/common/cancellation'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -268,7 +269,7 @@ CommandsRegistry.registerCommand('_extensions.manage', (accessor: ServicesAccess CommandsRegistry.registerCommand('extension.open', (accessor: ServicesAccessor, extensionId: string) => { const extensionService = accessor.get(IExtensionsWorkbenchService); - return extensionService.queryGallery({ names: [extensionId], pageSize: 1 }).then(pager => { + return extensionService.queryGallery({ names: [extensionId], pageSize: 1 }, CancellationToken.None).then(pager => { if (pager.total !== 1) { return; } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 5dd49b44d2..a9e42fdc31 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -1538,7 +1538,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { viewlet.search('@recommended '); viewlet.focus(); const names = this.recommendations.map(({ extensionId }) => extensionId); - return this.extensionWorkbenchService.queryGallery({ names, source: 'install-all-workspace-recommendations' }).then(pager => { + return this.extensionWorkbenchService.queryGallery({ names, source: 'install-all-workspace-recommendations' }, CancellationToken.None).then(pager => { let installPromises: Promise[] = []; let model = new PagedModel(pager); for (let i = 0; i < pager.total; i++) { @@ -1580,7 +1580,7 @@ export class InstallRecommendedExtensionAction extends Action { .then(viewlet => { viewlet.search('@recommended '); viewlet.focus(); - return this.extensionWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation', pageSize: 1 }) + return this.extensionWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation', pageSize: 1 }, CancellationToken.None) .then(pager => { if (pager && pager.firstPage && pager.firstPage.length) { const extension = pager.firstPage[0]; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker.ts index 193b450895..142a5bb841 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker.ts @@ -15,6 +15,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { Action } from 'vs/base/common/actions'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { Disposable } from 'vs/base/common/lifecycle'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class ExtensionDependencyChecker extends Disposable implements IWorkbenchContribution { @@ -60,7 +61,7 @@ export class ExtensionDependencyChecker extends Disposable implements IWorkbench private async installMissingDependencies(): Promise { const missingDependencies = await this.getUninstalledMissingDependencies(); if (missingDependencies.length) { - const extensions = (await this.extensionsWorkbenchService.queryGallery({ names: missingDependencies, pageSize: missingDependencies.length })).firstPage; + const extensions = (await this.extensionsWorkbenchService.queryGallery({ names: missingDependencies, pageSize: missingDependencies.length }, CancellationToken.None)).firstPage; if (extensions.length) { await Promise.all(extensions.map(extension => this.extensionsWorkbenchService.install(extension))); this.notificationService.notify({ diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts index 5ae7d42b1e..8e62d3741f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/extensionsViewlet'; import { localize } from 'vs/nls'; -import { ThrottledDelayer, timeout } from 'vs/base/common/async'; +import { timeout, Delayer } from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -271,7 +271,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private recommendedExtensionsContextKey: IContextKey; private defaultRecommendedExtensionsContextKey: IContextKey; - private searchDelayer: ThrottledDelayer; + private searchDelayer: Delayer; private root: HTMLElement; private searchBox: SuggestEnabledInput; @@ -300,7 +300,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio ) { super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - this.searchDelayer = new ThrottledDelayer(500); + this.searchDelayer = new Delayer(500); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); this.searchExtensionsContextKey = SearchExtensionsContext.bindTo(contextKeyService); this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts index e427569d18..ac8f3008b8 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts @@ -44,6 +44,7 @@ import { IAction } from 'vs/base/common/actions'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import product from 'vs/platform/product/node/product'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -69,6 +70,7 @@ export class ExtensionsListView extends ViewletPanel { private badge: CountBadge; protected badgeContainer: HTMLElement; private list: WorkbenchPagedList | null; + private queryRequest: { query: string, request: CancelablePromise> } | null; constructor( private options: IViewletViewOptions, @@ -139,6 +141,14 @@ export class ExtensionsListView extends ViewletPanel { } async show(query: string): Promise> { + if (this.queryRequest) { + if (this.queryRequest.query === query) { + return this.queryRequest.request; + } + this.queryRequest.request.cancel(); + this.queryRequest = null; + } + const parsedQuery = Query.parse(query); let options: IQueryOptions = { @@ -152,21 +162,24 @@ export class ExtensionsListView extends ViewletPanel { } const successCallback = model => { + this.queryRequest = null; this.setModel(model); return model; }; const errorCallback = e => { - console.warn('Error querying extensions gallery', e); const model = new PagedModel([]); - this.setModel(model, true); - return model; + if (!isPromiseCanceledError(e)) { + this.queryRequest = null; + console.warn('Error querying extensions gallery', e); + this.setModel(model, true); + } + return this.list ? this.list.model : model; }; - if (ExtensionsListView.isInstalledExtensionsQuery(query) || /@builtin/.test(query)) { - return await this.queryLocal(parsedQuery, options).then(successCallback).catch(errorCallback); - } - - return await this.queryGallery(parsedQuery, options).then(successCallback).catch(errorCallback); + const isLocalQuery = ExtensionsListView.isInstalledExtensionsQuery(query) || /@builtin/.test(query); + const request = createCancelablePromise(token => (isLocalQuery ? this.queryLocal(parsedQuery, options) : this.queryGallery(parsedQuery, options, token)).then(successCallback).catch(errorCallback)); + this.queryRequest = { query, request }; + return request.then(successCallback).catch(errorCallback); } count(): number { @@ -326,7 +339,7 @@ export class ExtensionsListView extends ViewletPanel { return new PagedModel([]); } - private async queryGallery(query: Query, options: IQueryOptions): Promise> { + private async queryGallery(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { const hasUserDefinedSortOrder = options.sortBy !== undefined; if (!hasUserDefinedSortOrder && !query.value.trim()) { options.sortBy = SortBy.InstallCount; @@ -343,25 +356,25 @@ export class ExtensionsListView extends ViewletPanel { } if (names.length) { - return this.extensionsWorkbenchService.queryGallery({ names, source: 'queryById' }) + return this.extensionsWorkbenchService.queryGallery({ names, source: 'queryById' }, token) .then(pager => this.getPagedModel(pager)); } if (ExtensionsListView.isWorkspaceRecommendedExtensionsQuery(query.value)) { - return this.getWorkspaceRecommendationsModel(query, options); + return this.getWorkspaceRecommendationsModel(query, options, token); } else if (ExtensionsListView.isKeymapsRecommendedExtensionsQuery(query.value)) { - return this.getKeymapRecommendationsModel(query, options); + return this.getKeymapRecommendationsModel(query, options, token); } else if (/@recommended:all/i.test(query.value) || ExtensionsListView.isSearchRecommendedExtensionsQuery(query.value)) { - return this.getAllRecommendationsModel(query, options); + return this.getAllRecommendationsModel(query, options, token); } else if (ExtensionsListView.isRecommendedExtensionsQuery(query.value)) { - return this.getRecommendationsModel(query, options); + return this.getRecommendationsModel(query, options, token); // {{SQL CARBON EDIT}} } else if (ExtensionsListView.isAllMarketplaceExtensionsQuery(query.value)) { - return this.getAllMarketplaceModel(query, options); + return this.getAllMarketplaceModel(query, options, token); } if (/\bcurated:([^\s]+)\b/.test(query.value)) { - return this.getCuratedModel(query, options); + return this.getCuratedModel(query, options, token); } let text = query.value; @@ -385,7 +398,7 @@ export class ExtensionsListView extends ViewletPanel { if (text !== query.value) { options = assign(options, { text: text.substr(0, 350), source: 'file-extension-tags' }); - return this.extensionsWorkbenchService.queryGallery(options).then(pager => this.getPagedModel(pager)); + return this.extensionsWorkbenchService.queryGallery(options, token).then(pager => this.getPagedModel(pager)); } } @@ -406,7 +419,7 @@ export class ExtensionsListView extends ViewletPanel { options.source = 'viewlet'; } - const pager = await this.extensionsWorkbenchService.queryGallery(options); + const pager = await this.extensionsWorkbenchService.queryGallery(options, token); let positionToUpdate = 0; for (const preferredResult of preferredResults) { @@ -453,7 +466,7 @@ export class ExtensionsListView extends ViewletPanel { } // Get All types of recommendations, trimmed to show a max of 8 at any given time - private getAllRecommendationsModel(query: Query, options: IQueryOptions): Promise> { + private getAllRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { const value = query.value.replace(/@recommended:all/g, '').replace(/@recommended/g, '').trim().toLowerCase(); return this.extensionsWorkbenchService.queryLocal() @@ -486,7 +499,7 @@ export class ExtensionsListView extends ViewletPanel { return Promise.resolve(new PagedModel([])); } options.source = 'recommendations-all'; - return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length })) + return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token) .then(pager => { this.sortFirstPage(pager, names); return this.getPagedModel(pager || []); @@ -495,12 +508,12 @@ export class ExtensionsListView extends ViewletPanel { }); } - private async getCuratedModel(query: Query, options: IQueryOptions): Promise> { + private async getCuratedModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { const value = query.value.replace(/curated:/g, '').trim(); const names = await this.experimentService.getCuratedExtensionsList(value); if (Array.isArray(names) && names.length) { options.source = `curated:${value}`; - const pager = await this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length })); + const pager = await this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token); this.sortFirstPage(pager, names); return this.getPagedModel(pager || []); } @@ -508,7 +521,7 @@ export class ExtensionsListView extends ViewletPanel { } // Get All types of recommendations other than Workspace recommendations, trimmed to show a max of 8 at any given time - private getRecommendationsModel(query: Query, options: IQueryOptions): Promise> { + private getRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { const value = query.value.replace(/@recommended/g, '').trim().toLowerCase(); return this.extensionsWorkbenchService.queryLocal() @@ -546,7 +559,7 @@ export class ExtensionsListView extends ViewletPanel { return Promise.resolve(new PagedModel([])); } options.source = 'recommendations'; - return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length })) + return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token) .then(pager => { this.sortFirstPage(pager, names); return this.getPagedModel(pager || []); @@ -556,7 +569,7 @@ export class ExtensionsListView extends ViewletPanel { } // {{SQL CARBON EDIT}} - private getAllMarketplaceModel(query: Query, options: IQueryOptions): Promise> { + private getAllMarketplaceModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { const value = query.value.trim().toLowerCase(); return this.extensionsWorkbenchService.queryLocal() .then(result => result.filter(e => e.type === ExtensionType.User)) @@ -564,7 +577,7 @@ export class ExtensionsListView extends ViewletPanel { return this.tipsService.getOtherRecommendations().then((recommmended) => { const installedExtensions = local.map(x => `${x.publisher}.${x.name}`); options = assign(options, { text: value, source: 'searchText' }); - return this.extensionsWorkbenchService.queryGallery(options).then((pager) => { + return this.extensionsWorkbenchService.queryGallery(options, token).then((pager) => { // filter out installed extensions pager.firstPage = pager.firstPage.filter((p) => { return installedExtensions.indexOf(`${p.publisher}.${p.name}`) === -1; @@ -624,7 +637,7 @@ export class ExtensionsListView extends ViewletPanel { return installed.some(i => areSameExtensions(i.identifier, { id: recommendation.extensionId })); } - private getWorkspaceRecommendationsModel(query: Query, options: IQueryOptions): Promise> { + private getWorkspaceRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { const value = query.value.replace(/@recommended:workspace/g, '').trim().toLowerCase(); return this.tipsService.getWorkspaceRecommendations() .then(recommendations => { @@ -640,12 +653,12 @@ export class ExtensionsListView extends ViewletPanel { return Promise.resolve(new PagedModel([])); } options.source = 'recommendations-workspace'; - return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length })) + return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token) .then(pager => this.getPagedModel(pager || [])); }); } - private getKeymapRecommendationsModel(query: Query, options: IQueryOptions): Promise> { + private getKeymapRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { const value = query.value.replace(/@recommended:keymaps/g, '').trim().toLowerCase(); const names: string[] = this.tipsService.getKeymapRecommendations().map(({ extensionId }) => extensionId) .filter(extensionId => extensionId.toLowerCase().indexOf(value) > -1); @@ -654,7 +667,7 @@ export class ExtensionsListView extends ViewletPanel { return Promise.resolve(new PagedModel([])); } options.source = 'recommendations-keymaps'; - return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length })) + return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token) .then(result => this.getPagedModel(result)); } @@ -735,6 +748,10 @@ export class ExtensionsListView extends ViewletPanel { dispose(): void { super.dispose(); + if (this.queryRequest) { + this.queryRequest.request.cancel(); + this.queryRequest = null; + } this.disposables = dispose(this.disposables); this.list = null; } diff --git a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts index 6a5a02d758..88548197e3 100644 --- a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts @@ -463,12 +463,16 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, }); } - queryGallery(options: IQueryOptions = {}): Promise> { + queryGallery(token: CancellationToken): Promise>; + queryGallery(options: IQueryOptions, token: CancellationToken): Promise>; + queryGallery(arg1: any, arg2?: any): Promise> { + const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1; + const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2; return this.extensionService.getExtensionsReport() .then(report => { const maliciousSet = getMaliciousExtensionsSet(report); - return this.galleryService.query(options) + return this.galleryService.query(options, token) .then(result => mapPager(result, gallery => this.fromGallery(gallery, maliciousSet))) .then(undefined, err => { if (/No extension gallery service configured/.test(err.message)) { @@ -610,10 +614,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, const promises: Promise>[] = []; if (ids.length) { - promises.push(this.queryGallery({ ids, pageSize: ids.length })); + promises.push(this.queryGallery({ ids, pageSize: ids.length }, CancellationToken.None)); } if (names.length) { - promises.push(this.queryGallery({ names, pageSize: names.length })); + promises.push(this.queryGallery({ names, pageSize: names.length }, CancellationToken.None)); } return Promise.all(promises).then(() => undefined); @@ -1056,7 +1060,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, .then(() => this.open(extension)); } - return this.queryGallery({ names: [extensionId], source: 'uri' }).then(result => { + return this.queryGallery({ names: [extensionId], source: 'uri' }, CancellationToken.None).then(result => { if (result.total < 1) { return Promise.resolve(null); } 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 11ed9b0604..41efc1c3b0 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 @@ -38,6 +38,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { ExtensionIdentifier, IExtensionContributions, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { CancellationToken } from 'vs/base/common/cancellation'; suite('ExtensionsActions Test', () => { @@ -110,7 +111,7 @@ suite('ExtensionsActions Test', () => { return workbenchService.queryLocal() .then(() => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: local.identifier }))); - return workbenchService.queryGallery() + return workbenchService.queryGallery(CancellationToken.None) .then((paged) => { testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); @@ -126,7 +127,7 @@ suite('ExtensionsActions Test', () => { instantiationService.get(IExtensionsWorkbenchService).onChange(() => testObject.update()); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return workbenchService.queryGallery() + return workbenchService.queryGallery(CancellationToken.None) .then((paged) => { testObject.extension = paged.firstPage[0]; installEvent.fire({ identifier: gallery.identifier, gallery }); @@ -143,7 +144,7 @@ suite('ExtensionsActions Test', () => { instantiationService.get(IExtensionsWorkbenchService).onChange(() => testObject.update()); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return workbenchService.queryGallery() + return workbenchService.queryGallery(CancellationToken.None) .then((paged) => { testObject.extension = paged.firstPage[0]; assert.ok(testObject.enabled); @@ -257,7 +258,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(paged => { testObject.extension = paged.firstPage[0]; @@ -299,7 +300,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return workbenchService.queryGallery() + return workbenchService.queryGallery(CancellationToken.None) .then((paged) => { testObject.extension = paged.firstPage[0]; assert.ok(testObject.enabled); @@ -329,7 +330,7 @@ suite('ExtensionsActions Test', () => { const workbenchService = instantiationService.get(IExtensionsWorkbenchService); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return workbenchService.queryGallery() + return workbenchService.queryGallery(CancellationToken.None) .then((paged) => { testObject.extension = paged.firstPage[0]; installEvent.fire({ identifier: gallery.identifier, gallery }); @@ -387,7 +388,7 @@ suite('ExtensionsActions Test', () => { instantiationService.get(IExtensionsWorkbenchService).onChange(() => testObject.update()); const gallery = aGalleryExtension('a', { version: '1.0.0' }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then((paged) => { testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); @@ -404,7 +405,7 @@ suite('ExtensionsActions Test', () => { .then(extensions => { testObject.extension = extensions[0]; instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: local.identifier, version: local.manifest.version }))); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(extensions => assert.ok(!testObject.enabled)); }); }); @@ -419,7 +420,7 @@ suite('ExtensionsActions Test', () => { .then(extensions => { testObject.extension = extensions[0]; instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: local.identifier, version: '1.0.1' }))); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(extensions => assert.ok(!testObject.enabled)); }); }); @@ -442,7 +443,7 @@ suite('ExtensionsActions Test', () => { c(); } }); - instantiationService.get(IExtensionsWorkbenchService).queryGallery(); + instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); }); }); }); @@ -458,7 +459,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.1' }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(extensions => { installEvent.fire({ identifier: local.identifier, gallery }); assert.ok(!testObject.enabled); @@ -494,7 +495,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { testObject.extension = page.firstPage[0]; assert.ok(!testObject.enabled); @@ -509,7 +510,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { testObject.extension = page.firstPage[0]; @@ -526,7 +527,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { testObject.extension = page.firstPage[0]; installEvent.fire({ identifier: gallery.identifier, gallery }); @@ -750,7 +751,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { const testObject: ExtensionsActions.EnableDropDownAction = instantiationService.createInstance(ExtensionsActions.EnableDropDownAction); testObject.extension = page.firstPage[0]; @@ -762,7 +763,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { const testObject: ExtensionsActions.EnableDropDownAction = instantiationService.createInstance(ExtensionsActions.EnableDropDownAction); testObject.extension = page.firstPage[0]; @@ -934,7 +935,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); testObject.extension = page.firstPage[0]; @@ -946,7 +947,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); testObject.extension = page.firstPage[0]; @@ -998,7 +999,7 @@ suite('ExtensionsActions Test', () => { c(); } }); - workbenchService.queryGallery(); + workbenchService.queryGallery(CancellationToken.None); }); }); }); @@ -1020,7 +1021,7 @@ suite('ExtensionsActions Test', () => { c(); } }); - workbenchService.queryGallery(); + workbenchService.queryGallery(CancellationToken.None); }); }); }); @@ -1034,7 +1035,7 @@ suite('ExtensionsActions Test', () => { return workbenchService.queryLocal() .then(() => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...gallery)); - return workbenchService.queryGallery() + return workbenchService.queryGallery(CancellationToken.None) .then(() => { installEvent.fire({ identifier: local[0].identifier, gallery: gallery[0] }); installEvent.fire({ identifier: local[1].identifier, gallery: gallery[1] }); @@ -1056,7 +1057,7 @@ suite('ExtensionsActions Test', () => { const workbenchService = instantiationService.get(IExtensionsWorkbenchService); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return workbenchService.queryGallery() + return workbenchService.queryGallery(CancellationToken.None) .then((paged) => { testObject.extension = paged.firstPage[0]; installEvent.fire({ identifier: gallery.identifier, gallery }); @@ -1086,7 +1087,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - const paged = await instantiationService.get(IExtensionsWorkbenchService).queryGallery(); + const paged = await instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); @@ -1108,7 +1109,7 @@ suite('ExtensionsActions Test', () => { instantiationService.get(IExtensionsWorkbenchService).onChange(() => testObject.update()); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery() + return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then((paged) => { testObject.extension = paged.firstPage[0]; const identifier = gallery.identifier; @@ -1320,7 +1321,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - const paged = await instantiationService.get(IExtensionsWorkbenchService).queryGallery(); + const paged = await instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); 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 b4e20ded35..a3ff8b2e7c 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 @@ -38,7 +38,7 @@ import { IExperimentService, ExperimentService, ExperimentState, ExperimentActio import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/electron-browser/extensionManagementServerService'; -import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; @@ -124,13 +124,13 @@ suite('ExtensionsListView Tests', () => { instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', []); instantiationService.stub(IExtensionService, { - getExtensions: () => { + getExtensions: (): Promise => { return Promise.resolve([ - { identifier: new ExtensionIdentifier(localEnabledTheme.identifier.id) }, - { identifier: new ExtensionIdentifier(localEnabledLanguage.identifier.id) }, - { identifier: new ExtensionIdentifier(localRandom.identifier.id) }, - { identifier: new ExtensionIdentifier(builtInTheme.identifier.id) }, - { identifier: new ExtensionIdentifier(builtInBasic.identifier.id) } + toExtensionDescription(localEnabledTheme), + toExtensionDescription(localEnabledLanguage), + toExtensionDescription(localRandom), + toExtensionDescription(builtInTheme), + toExtensionDescription(builtInBasic) ]); } }); @@ -185,8 +185,8 @@ suite('ExtensionsListView Tests', () => { }); }); - test('Test installed query results', () => { - const allInstalledCheck = testableView.show('@installed').then(result => { + test('Test installed query results', async () => { + await testableView.show('@installed').then(result => { assert.equal(result.length, 5, 'Unexpected number of results for @installed query'); const actual = [result.get(0).name, result.get(1).name, result.get(2).name, result.get(3).name, result.get(4).name].sort(); const expected = [localDisabledTheme.manifest.name, localEnabledTheme.manifest.name, localRandom.manifest.name, localDisabledLanguage.manifest.name, localEnabledLanguage.manifest.name]; @@ -195,125 +195,102 @@ suite('ExtensionsListView Tests', () => { } }); - const installedCheck = testableView.show('@installed first').then(result => { + await testableView.show('@installed first').then(result => { assert.equal(result.length, 2, 'Unexpected number of results for @installed query'); assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.'); assert.equal(result.get(1).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.'); }); - const allDisabledCheck = testableView.show('@disabled').then(result => { + await testableView.show('@disabled').then(result => { assert.equal(result.length, 2, 'Unexpected number of results for @disabled query'); assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query.'); assert.equal(result.get(1).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query.'); }); - const allEnabledCheck = testableView.show('@enabled').then(result => { + await testableView.show('@enabled').then(result => { assert.equal(result.length, 3, 'Unexpected number of results for @enabled query'); assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query.'); assert.equal(result.get(1).name, localRandom.manifest.name, 'Unexpected extension for @enabled query.'); assert.equal(result.get(2).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query.'); }); - const allBuiltinThemesCheck = testableView.show('@builtin:themes').then(result => { + await testableView.show('@builtin:themes').then(result => { assert.equal(result.length, 1, 'Unexpected number of results for @builtin:themes query'); assert.equal(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin:themes query.'); }); - const allBuiltinBasicsCheck = testableView.show('@builtin:basics').then(result => { + await testableView.show('@builtin:basics').then(result => { assert.equal(result.length, 1, 'Unexpected number of results for @builtin:basics query'); assert.equal(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin:basics query.'); }); - const allBuiltinCheck = testableView.show('@builtin').then(result => { + await testableView.show('@builtin').then(result => { assert.equal(result.length, 2, 'Unexpected number of results for @builtin query'); assert.equal(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin query.'); assert.equal(result.get(1).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.'); }); - const builtinCheck = testableView.show('@builtin my-theme').then(result => { + await testableView.show('@builtin my-theme').then(result => { assert.equal(result.length, 1, 'Unexpected number of results for @builtin query'); assert.equal(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.'); }); - - return Promise.all([ - allInstalledCheck, - installedCheck, - allDisabledCheck, - allEnabledCheck, - allBuiltinThemesCheck, - allBuiltinBasicsCheck, - allBuiltinCheck, - builtinCheck]); }); - test('Test installed query with category', () => { - const installedCategoryWithoutQuotesCheck = testableView.show('@installed category:themes').then(result => { + test('Test installed query with category', async () => { + await testableView.show('@installed category:themes').then(result => { assert.equal(result.length, 2, 'Unexpected number of results for @installed query with category'); assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with category.'); assert.equal(result.get(1).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with category.'); }); - const installedCategoryWithQuotesCheck = testableView.show('@installed category:"themes"').then(result => { + await testableView.show('@installed category:"themes"').then(result => { assert.equal(result.length, 2, 'Unexpected number of results for @installed query with quoted category'); assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.'); assert.equal(result.get(1).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.'); }); - const installedCategoryWithSpaceCheck = testableView.show('@installed category:"programming languages"').then(result => { + await testableView.show('@installed category:"programming languages"').then(result => { assert.equal(result.length, 2, 'Unexpected number of results for @installed query with quoted category including space'); assert.equal(result.get(0).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category inlcuding space.'); assert.equal(result.get(1).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category including space.'); }); - const installedMultipleCategoryCheck = testableView.show('@installed category:themes category:random').then(result => { + await testableView.show('@installed category:themes category:random').then(result => { assert.equal(result.length, 3, 'Unexpected number of results for @installed query with multiple category'); assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.'); assert.equal(result.get(1).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.'); assert.equal(result.get(2).name, localRandom.manifest.name, 'Unexpected extension for @installed query with multiple category.'); }); - const enabledCategoryWithoutQuotesCheck = testableView.show('@enabled category:themes').then(result => { + await testableView.show('@enabled category:themes').then(result => { assert.equal(result.length, 1, 'Unexpected number of results for @enabled query with category'); assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with category.'); }); - const enabledCategoryWithQuotesCheck = testableView.show('@enabled category:"themes"').then(result => { + await testableView.show('@enabled category:"themes"').then(result => { assert.equal(result.length, 1, 'Unexpected number of results for @enabled query with quoted category'); assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with quoted category.'); }); - const enabledCategoryWithSpaceCheck = testableView.show('@enabled category:"programming languages"').then(result => { + await testableView.show('@enabled category:"programming languages"').then(result => { assert.equal(result.length, 1, 'Unexpected number of results for @enabled query with quoted category inlcuding space'); assert.equal(result.get(0).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query with quoted category including space.'); }); - const disabledCategoryWithoutQuotesCheck = testableView.show('@disabled category:themes').then(result => { + await testableView.show('@disabled category:themes').then(result => { assert.equal(result.length, 1, 'Unexpected number of results for @disabled query with category'); assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with category.'); }); - const disabledCategoryWithQuotesCheck = testableView.show('@disabled category:"themes"').then(result => { + await testableView.show('@disabled category:"themes"').then(result => { assert.equal(result.length, 1, 'Unexpected number of results for @disabled query with quoted category'); assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with quoted category.'); }); - const disabledCategoryWithSpaceCheck = testableView.show('@disabled category:"programming languages"').then(result => { + await testableView.show('@disabled category:"programming languages"').then(result => { assert.equal(result.length, 1, 'Unexpected number of results for @disabled query with quoted category inlcuding space'); assert.equal(result.get(0).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query with quoted category including space.'); }); - - return Promise.resolve([ - installedCategoryWithoutQuotesCheck, - installedCategoryWithQuotesCheck, - installedCategoryWithSpaceCheck, - installedMultipleCategoryCheck, - enabledCategoryWithoutQuotesCheck, - enabledCategoryWithQuotesCheck, - enabledCategoryWithSpaceCheck, - disabledCategoryWithoutQuotesCheck, - disabledCategoryWithQuotesCheck, - disabledCategoryWithSpaceCheck - ]); }); test('Test @recommended:workspace query', () => { @@ -523,5 +500,15 @@ suite('ExtensionsListView Tests', () => { function aPage(...objects: T[]): IPager { return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! }; } + + function toExtensionDescription(local: ILocalExtension): IExtensionDescription { + return { + identifier: new ExtensionIdentifier(local.identifier.id), + isBuiltin: local.type === ExtensionType.System, + isUnderDevelopment: false, + extensionLocation: local.location, + ...local.manifest + }; + } }); 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 5fc2ab1339..88fab804bd 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 @@ -70,10 +70,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IConfigurationService, { - onDidUpdateConfiguration: () => { }, - onDidChangeConfiguration: () => { }, - getConfiguration: () => ({}), - getValue: (key) => { + onDidChangeConfiguration: () => { return undefined!; }, + getValue: (key?) => { return (key === AutoCheckUpdatesConfigurationKey || key === AutoUpdateConfigurationKey) ? true : undefined; } }); @@ -91,7 +89,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.set(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); - instantiationService.stub(INotificationService, { prompt: () => null }); + instantiationService.stub(INotificationService, { prompt: () => null! }); }); setup(async () => { @@ -133,7 +131,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(expected)); - return testObject.queryGallery().then(pagedResponse => { + return testObject.queryGallery(CancellationToken.None).then(pagedResponse => { assert.equal(1, pagedResponse.firstPage.length); const actual = pagedResponse.firstPage[0]; @@ -332,7 +330,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return testObject.queryGallery().then(page => { + return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; assert.equal(ExtensionState.Uninstalled, extension.state); @@ -416,7 +414,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); const target = sinon.spy(); - return testObject.queryGallery().then(page => { + return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; assert.equal(ExtensionState.Uninstalled, extension.state); @@ -437,7 +435,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); const target = sinon.spy(); - return testObject.queryGallery().then(page => { + return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; assert.equal(ExtensionState.Uninstalled, extension.state); @@ -482,7 +480,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); - return testObject.queryGallery().then(page => { + return testObject.queryGallery(CancellationToken.None).then(page => { return testObject.loadDependencies(page.firstPage[0], CancellationToken.None).then(dependencies => { assert.equal(null, dependencies); }); @@ -494,7 +492,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.c', 'pub.d'] }))); instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('b'), aGalleryExtension('c'), aGalleryExtension('d')]); - return testObject.queryGallery().then(page => { + return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; return testObject.loadDependencies(extension, CancellationToken.None).then(actual => { assert.ok(actual!.hasDependencies); @@ -533,7 +531,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] }))); instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('b'), aGalleryExtension('a')]); - return testObject.queryGallery().then(page => { + return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; return testObject.loadDependencies(extension, CancellationToken.None).then(actual => { assert.ok(actual!.hasDependencies); @@ -565,7 +563,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] }))); instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('a')]); - return testObject.queryGallery().then(page => { + return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; return testObject.loadDependencies(extension, CancellationToken.None).then(actual => { assert.ok(actual!.hasDependencies); @@ -599,7 +597,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.inbuilt', 'pub.a'] }))); instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('a')]); - return testObject.queryGallery().then(page => { + return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; return testObject.loadDependencies(extension, CancellationToken.None).then(actual => { assert.ok(actual!.hasDependencies); @@ -637,7 +635,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { aGalleryExtension('d', {}, { dependencies: ['pub.f', 'pub.c'] }), aGalleryExtension('e')]); - return testObject.queryGallery().then(page => { + return testObject.queryGallery(CancellationToken.None).then(page => { const extension = page.firstPage[0]; return testObject.loadDependencies(extension, CancellationToken.None).then(a => { assert.ok(a!.hasDependencies); @@ -726,7 +724,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { .then(async () => { testObject = await aWorkbenchService(); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); - return testObject.queryGallery().then(pagedResponse => { + return testObject.queryGallery(CancellationToken.None).then(pagedResponse => { const actual = pagedResponse.firstPage[0]; assert.equal(actual.enablementState, EnablementState.Enabled); }); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 873a2b6ca7..e71d03b3a7 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -10,7 +10,7 @@ import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTI import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, REVEAL_IN_OS_LABEL, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { openWindowCommand, REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, REVEAL_IN_OS_LABEL, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -43,6 +43,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleAutoSaveAction, // Commands CommandsRegistry.registerCommand('_files.windowOpen', openWindowCommand); +CommandsRegistry.registerCommand('_files.newWindow', newWindowCommand); const explorerCommandsWeightBonus = 10; // give our commands a little bit more weight over other default list/tree commands diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 0e1650239a..54ef1e763f 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -653,7 +653,7 @@ export class GlobalCompareResourcesAction extends Action { override: this.editorService.openEditor({ leftResource: activeResource, rightResource: resource - }).then(() => undefined) + }).then(() => null) }; } @@ -844,7 +844,7 @@ export class ShowActiveFileInExplorer extends Action { } public run(): Promise { - const resource = toResource(this.editorService.activeEditor || null, { supportSideBySide: true }); + const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true }); if (resource) { this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource); } else { @@ -916,7 +916,7 @@ export class ShowOpenedFileInNewWindow extends Action { } public run(): Promise { - const fileResource = toResource(this.editorService.activeEditor || null, { supportSideBySide: true }); + const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: true }); if (fileResource) { if (this.fileService.canHandleResource(fileResource)) { this.windowService.openWindow([{ uri: fileResource, typeHint: 'file' }], { forceNewWindow: true, forceOpenWorkspaceAsFile: true }); @@ -1019,7 +1019,7 @@ export class CompareWithClipboardAction extends Action { } public run(): Promise { - const resource = toResource(this.editorService.activeEditor || null, { supportSideBySide: true }); + const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true }); if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { if (!this.registrationDisposal) { const provider = this.instantiationService.createInstance(ClipboardContentProvider); diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 961bb7cfe6..f484c4467b 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; // {{SQL CARBON EDIT}} - Import EditorInput import { toResource, IEditorCommandsContext, EditorInput } from 'vs/workbench/common/editor'; -import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings, INewWindowOptions } from 'vs/platform/windows/common/windows'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -88,6 +88,11 @@ export const openWindowCommand = (accessor: ServicesAccessor, urisToOpen: IURITo } }; +export const newWindowCommand = (accessor: ServicesAccessor, options?: INewWindowOptions) => { + const windowsService = accessor.get(IWindowsService); + windowsService.openNewWindow(options); +}; + // {{SQL CARBON EDIT}} function save( resource: URI | null, @@ -131,7 +136,7 @@ function save( let viewStateOfSource: IEditorViewState | null; const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); if (activeTextEditorWidget) { - const activeResource = toResource(editorService.activeEditor || null, { supportSideBySide: true }); + const activeResource = toResource(editorService.activeEditor, { supportSideBySide: true }); if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && activeResource.toString() === resource.toString()) { viewStateOfSource = activeTextEditorWidget.saveViewState(); } diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 8beb2ad93b..3ae5fb2184 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -118,7 +118,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo return; } - this.galleryService.query({ text: `tag:lp-${locale}` }).then(tagResult => { + this.galleryService.query({ text: `tag:lp-${locale}` }, CancellationToken.None).then(tagResult => { if (tagResult.total === 0) { return; } diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index de00e033e5..5d2fa3d2c7 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -9,7 +9,7 @@ import { getTotalHeight, getTotalWidth } from 'vs/base/browser/dom'; import { Color } from 'vs/base/common/color'; import { Event } from 'vs/base/common/event'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; +import { IBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { ColorIdentifier, editorBackground, foreground } from 'vs/platform/theme/common/colorRegistry'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 8e6e37061c..cdb22ee824 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -763,7 +763,11 @@ export class TerminalInstance implements ITerminalInstance { if (!this._xterm) { return; } - const text = window.getSelection().toString(); + const selection = window.getSelection(); + if (!selection) { + return; + } + const text = selection.toString(); if (!text || force) { this._xterm.focus(); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 94e20d647e..470d73ce8a 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -72,7 +72,7 @@ class WebviewProtocolProvider extends Disposable { const appRootUri = URI.file(this._environmentService.appRoot); - registerFileProtocol(contents, WebviewProtocol.CoreResource, this._fileService, null, () => [ + registerFileProtocol(contents, WebviewProtocol.CoreResource, this._fileService, undefined, () => [ appRootUri ]); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts index 2996fa4edc..80c69406b3 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts @@ -30,7 +30,7 @@ export function registerFileProtocol( contents: Electron.WebContents, protocol: WebviewProtocol, fileService: IFileService, - extensionLocation: URI | null | undefined, + extensionLocation: URI | undefined, getRoots: () => ReadonlyArray ) { contents.session.protocol.registerBufferProtocol(protocol, (request, callback: any) => { diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts index 0c0ef0aa34..2368e7047e 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts @@ -17,6 +17,7 @@ import { IExperimentService, ExperimentState } from 'vs/workbench/contrib/experi import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { language, locale } from 'vs/base/common/platform'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class TelemetryOptOut implements IWorkbenchContribution { @@ -85,7 +86,7 @@ export class TelemetryOptOut implements IWorkbenchContribution { let queryPromise = Promise.resolve(undefined); if (locale && locale !== language && locale !== 'en' && locale.indexOf('en-') === -1) { - queryPromise = this.galleryService.query({ text: `tag:lp-${locale}` }).then(tagResult => { + queryPromise = this.galleryService.query({ text: `tag:lp-${locale}` }, CancellationToken.None).then(tagResult => { if (!tagResult || !tagResult.total) { return undefined; } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/test/common/gettingStarted.test.ts b/src/vs/workbench/contrib/welcome/gettingStarted/test/common/gettingStarted.test.ts index 7a68c5442d..2fa5afdd84 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/test/common/gettingStarted.test.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/test/common/gettingStarted.test.ts @@ -9,24 +9,14 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; suite('Workbench - GettingStarted', () => { let instantiation: TestInstantiationService | null = null; - let welcomePageEnvConfig: string | null = null; let hideWelcomeSettingsValue: string | null = null; - // let machineId: string | null = null; - let appName: string | null = null; suiteSetup(() => { instantiation = new TestInstantiationService(); instantiation.stub(IWorkspaceContextService, { - getConfiguration: () => { - return { - env: { - welcomePage: welcomePageEnvConfig, - appName: appName - } - }; - } + }); - instantiation.stub(IStorageService, { + instantiation.stub(IStorageService, >{ get: () => hideWelcomeSettingsValue, store: (value) => hideWelcomeSettingsValue = value }); @@ -37,8 +27,6 @@ suite('Workbench - GettingStarted', () => { }); setup(() => { - welcomePageEnvConfig = null; hideWelcomeSettingsValue = null; - appName = null; }); }); \ No newline at end of file diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 720f78278e..90c23757b1 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -40,6 +40,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { joinPath } from 'vs/base/common/resources'; import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, isRecentFolder } from 'vs/platform/history/common/history'; +import { CancellationToken } from 'vs/base/common/cancellation'; used(); @@ -457,7 +458,7 @@ class WelcomePage { this.notificationService.info(strings.alreadyInstalled.replace('{0}', extensionSuggestion.name)); return; } - const foundAndInstalled = installedExtension ? Promise.resolve(installedExtension.local) : this.extensionGalleryService.query({ names: [extensionSuggestion.id], source: telemetryFrom }) + const foundAndInstalled = installedExtension ? Promise.resolve(installedExtension.local) : this.extensionGalleryService.query({ names: [extensionSuggestion.id], source: telemetryFrom }, CancellationToken.None) .then((result): null | Promise => { const [extension] = result.firstPage; if (!extension) { @@ -552,7 +553,7 @@ class WelcomePage { from: telemetryFrom, extensionId: extensionSuggestion.id, }); - this.extensionsWorkbenchService.queryGallery({ names: [extensionSuggestion.id] }) + this.extensionsWorkbenchService.queryGallery({ names: [extensionSuggestion.id] }, CancellationToken.None) .then(result => this.extensionsWorkbenchService.open(result.firstPage[0])) .then(undefined, onUnexpectedError); } diff --git a/src/vs/workbench/services/broadcast/common/broadcast.ts b/src/vs/workbench/services/broadcast/common/broadcast.ts new file mode 100644 index 0000000000..fc9e13031b --- /dev/null +++ b/src/vs/workbench/services/broadcast/common/broadcast.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; + +export const IBroadcastService = createDecorator('broadcastService'); + +export interface IBroadcast { + channel: string; + payload: any; +} + +export interface IBroadcastService { + _serviceBrand: any; + + onBroadcast: Event; + + broadcast(b: IBroadcast): void; +} + +export class NullBroadcastService implements IBroadcastService { + _serviceBrand: any; + onBroadcast: Event = Event.None; + broadcast(_b: IBroadcast): void { + + } +} diff --git a/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts b/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts index 33429832b5..25c10a4e8d 100644 --- a/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts +++ b/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts @@ -3,28 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { ipcRenderer as ipc } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWindowService } from 'vs/platform/windows/common/windows'; - -export const IBroadcastService = createDecorator('broadcastService'); - -export interface IBroadcast { - channel: string; - payload: any; -} - -export interface IBroadcastService { - _serviceBrand: any; - - onBroadcast: Event; - - broadcast(b: IBroadcast): void; -} +import { IBroadcastService, IBroadcast } from 'vs/workbench/services/broadcast/common/broadcast'; export class BroadcastService extends Disposable implements IBroadcastService { _serviceBrand: any; @@ -63,4 +48,4 @@ export class BroadcastService extends Disposable implements IBroadcastService { } } -registerSingleton(IBroadcastService, BroadcastService, true); \ No newline at end of file +registerSingleton(IBroadcastService, BroadcastService, true); diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index e8b5f29cdd..e17f45d51f 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -487,7 +487,7 @@ export class ConfigurationEditingService { return { key, jsonPath, value: config.value, resource: resource || undefined, target }; } - private isWorkspaceConfigurationResource(resource: URI | null | undefined): boolean { + private isWorkspaceConfigurationResource(resource: URI | null): boolean { const workspace = this.contextService.getWorkspace(); return !!(workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath); } diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 04035b544a..2932de2a5b 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -164,7 +164,7 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic const [type, name] = variable.split(':', 2); - let result: string | undefined | null; + let result: string | undefined; switch (type) { diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index d9c19e443f..dec166f697 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -291,7 +291,7 @@ class DecorationProviderWrapper { } } - private _fetchData(uri: URI): IDecorationData | undefined | null { + private _fetchData(uri: URI): IDecorationData | null { // check for pending request and cancel it const pendingRequest = this.data.get(uri.toString()); @@ -319,11 +319,11 @@ class DecorationProviderWrapper { })); this.data.set(uri.toString(), request); - return undefined; + return null; } } - private _keepItem(uri: URI, data: IDecorationData | null | undefined): IDecorationData | null { + private _keepItem(uri: URI, data: IDecorationData | undefined): IDecorationData | null { const deco = data ? data : null; const old = this.data.set(uri.toString(), deco); if (deco || old) { diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 01a7b8a4ab..ef36603368 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -36,7 +36,7 @@ export interface IOpenEditorOverride { * If defined, will prevent the opening of an editor and replace the resulting * promise with the provided promise for the openEditor() call. */ - override?: Promise; + override?: Promise; } export interface IVisibleEditor extends IEditor { diff --git a/src/vs/workbench/services/extensions/common/extensionDevOptions.ts b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts new file mode 100644 index 0000000000..29948328db --- /dev/null +++ b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Schemas } from 'vs/base/common/network'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; + +export interface IExtensionDevOptions { + readonly isExtensionDevHost: boolean; + readonly isExtensionDevDebug: boolean; + readonly isExtensionDevDebugBrk: boolean; + readonly isExtensionDevTestFromCli: boolean; +} + +export function parseExtensionDevOptions(environmentService: IEnvironmentService): IExtensionDevOptions { + // handle extension host lifecycle a bit special when we know we are developing an extension that runs inside + let isExtensionDevHost = environmentService.isExtensionDevelopment; + const extDevLoc = environmentService.extensionDevelopmentLocationURI; + const debugOk = !extDevLoc || extDevLoc.scheme === Schemas.file; + let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number'; + let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break; + let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break; + return { + isExtensionDevHost, + isExtensionDevDebug, + isExtensionDevDebugBrk, + isExtensionDevTestFromCli, + }; +} diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index f3fc22b90a..22b27c5a21 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -218,6 +218,13 @@ export interface IExtensionService extends ICpuProfilerTarget { * Stops the extension host. */ stopExtensionHost(): void; + + _logOrShowMessage(severity: Severity, msg: string): void; + _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise; + _onWillActivateExtension(extensionId: ExtensionIdentifier): void; + _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void; + _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void; + _onExtensionHostExit(code: number): void; } export interface ICpuProfilerTarget { @@ -278,4 +285,10 @@ export class NullExtensionService implements IExtensionService { stopExtensionHost(): void { } canAddExtension(): boolean { return false; } canRemoveExtension(): boolean { return false; } -} \ No newline at end of file + _logOrShowMessage(_severity: Severity, _msg: string): void { } + _activateById(_extensionId: ExtensionIdentifier, _activationEvent: string): Promise { return Promise.resolve(); } + _onWillActivateExtension(_extensionId: ExtensionIdentifier): void { } + _onDidActivateExtension(_extensionId: ExtensionIdentifier, _startup: boolean, _codeLoadingTime: number, _activateCallTime: number, _activateResolvedTime: number, _activationEvent: string): void { } + _onExtensionRuntimeError(_extensionId: ExtensionIdentifier, _err: Error): void { } + _onExtensionHostExit(code: number): void { } +} diff --git a/src/vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler.ts b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts similarity index 99% rename from src/vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler.ts rename to src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts index ccc7516797..1dc53d5a8e 100644 --- a/src/vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts @@ -273,4 +273,4 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { } } -registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler); \ No newline at end of file +registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 1be57ac40f..66c2507679 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -12,16 +12,15 @@ import { timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; import * as objects from 'vs/base/common/objects'; import { isWindows } from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console'; +import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc'; import { PersistentProtocol, generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net'; -import { IBroadcast, IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; +import { IBroadcast, IBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -36,6 +35,7 @@ import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/node/extensionHostProtocol'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { parseExtensionDevOptions } from '../common/extensionDevOptions'; export interface IExtensionHostStarter { readonly onCrashed: Event<[number, string | null]>; @@ -44,28 +44,6 @@ export interface IExtensionHostStarter { dispose(): void; } -export interface IExtensionDevOptions { - readonly isExtensionDevHost: boolean; - readonly isExtensionDevDebug: boolean; - readonly isExtensionDevDebugBrk: boolean; - readonly isExtensionDevTestFromCli: boolean; -} -export function parseExtensionDevOptions(environmentService: IEnvironmentService): IExtensionDevOptions { - // handle extension host lifecycle a bit special when we know we are developing an extension that runs inside - let isExtensionDevHost = environmentService.isExtensionDevelopment; - const extDevLoc = environmentService.extensionDevelopmentLocationURI; - const debugOk = !extDevLoc || extDevLoc.scheme === Schemas.file; - let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number'; - let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break; - let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break; - return { - isExtensionDevHost, - isExtensionDevDebug, - isExtensionDevDebugBrk, - isExtensionDevTestFromCli, - }; -} - export class ExtensionHostProcessWorker implements IExtensionHostStarter { private readonly _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>(); @@ -87,7 +65,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private _inspectPort: number; private _extensionHostProcess: ChildProcess | null; private _extensionHostConnection: Socket | null; - private _messageProtocol: Promise | null; + private _messageProtocol: Promise | null; constructor( private readonly _autoStart: boolean, @@ -341,9 +319,9 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { }); } - private _tryExtHostHandshake(): Promise { + private _tryExtHostHandshake(): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { // Wait for the extension host to connect to our named pipe // and wrap the socket in the message passing protocol @@ -373,7 +351,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // 1) wait for the incoming `ready` event and send the initialization data. // 2) wait for the incoming `initialized` event. - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { let timeoutHandle: NodeJS.Timer; const installTimeoutCheck = () => { @@ -549,6 +527,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // (graceful termination) protocol.send(createMessageOfType(MessageType.Terminate)); + protocol.dispose(); + // Give the extension host 10s, after which we will // try to kill the process and release any resources setTimeout(() => this._cleanResources(), 10 * 1000); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 8d1d0d5d3c..686a6ece7c 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -5,6 +5,7 @@ import * as nls from 'vs/nls'; import * as path from 'vs/base/common/path'; +import { ipcRenderer as ipc } from 'electron'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Barrier, runWhenIdle } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -840,6 +841,10 @@ export class ExtensionService extends Disposable implements IExtensionService { this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err); this._onDidChangeExtensionsStatus.fire([extensionId]); } + + public _onExtensionHostExit(code: number): void { + ipc.send('vscode:exit', code); + } } registerSingleton(IExtensionService, ExtensionService); diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 8c8b0aaa11..ba73aa6bcb 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -450,7 +450,7 @@ export class ExtensionScannerInput { constructor( public readonly ourVersion: string, - public readonly commit: string | null | undefined, + public readonly commit: string | undefined, public readonly locale: string | undefined, public readonly devMode: boolean, public readonly absoluteFolderPath: string, diff --git a/src/vs/workbench/services/files/node/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts index 842dd3fb8d..f4827cb9fe 100644 --- a/src/vs/workbench/services/files/node/fileService.ts +++ b/src/vs/workbench/services/files/node/fileService.ts @@ -217,19 +217,6 @@ export class FileService extends Disposable implements ILegacyFileService { return resource.scheme === Schemas.file; } - resolveFile(resource: uri, options?: IResolveFileOptions): Promise { - return this.resolve(resource, options); - } - - resolveFiles(toResolve: { resource: uri, options?: IResolveFileOptions }[]): Promise { - return Promise.all(toResolve.map(resourceAndOptions => this.resolve(resourceAndOptions.resource, resourceAndOptions.options) - .then(stat => ({ stat, success: true }), error => ({ stat: undefined, success: false })))); - } - - existsFile(resource: uri): Promise { - return this.resolveFile(resource).then(() => true, () => false); - } - resolveContent(resource: uri, options?: IResolveContentOptions): Promise { return this.resolveStreamContent(resource, options).then(streamContent => { return new Promise((resolve, reject) => { @@ -1096,6 +1083,15 @@ export class FileService extends Disposable implements ILegacyFileService { // Tests only + resolveFile(resource: uri, options?: IResolveFileOptions): Promise { + return this.resolve(resource, options); + } + + resolveFiles(toResolve: { resource: uri, options?: IResolveFileOptions }[]): Promise { + return Promise.all(toResolve.map(resourceAndOptions => this.resolve(resourceAndOptions.resource, resourceAndOptions.options) + .then(stat => ({ stat, success: true }), error => ({ stat: undefined, success: false })))); + } + createFolder(resource: uri): Promise { // 1.) Create folder @@ -1112,6 +1108,10 @@ export class FileService extends Disposable implements ILegacyFileService { }); }); } + + existsFile(resource: uri): Promise { + return this.resolveFile(resource).then(() => true, () => false); + } } function etag(stat: fs.Stats): string; diff --git a/src/vs/workbench/services/files/node/remoteFileService.ts b/src/vs/workbench/services/files/node/remoteFileService.ts index 94923c0561..464c9536ec 100644 --- a/src/vs/workbench/services/files/node/remoteFileService.ts +++ b/src/vs/workbench/services/files/node/remoteFileService.ts @@ -223,14 +223,6 @@ export class RemoteFileService extends FileService { }); } - existsFile(resource: URI): Promise { - if (resource.scheme === Schemas.file) { - return super.existsFile(resource); - } else { - return this.resolveFile(resource).then(_data => true, _err => false); - } - } - resolveFile(resource: URI, options?: IResolveFileOptions): Promise { if (resource.scheme === Schemas.file) { return super.resolveFile(resource, options); diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts index 6abd23979b..d88f64255c 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts @@ -12,7 +12,7 @@ export class WatcherChannel implements IServerChannel { constructor(private service: IWatcherService) { } - listen(_, event: string, arg?: any): Event { + listen(_: unknown, event: string, arg?: any): Event { switch (event) { case 'watch': return this.service.watch(arg); } @@ -20,7 +20,7 @@ export class WatcherChannel implements IServerChannel { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'setRoots': return this.service.setRoots(arg); case 'setVerboseLogging': return this.service.setVerboseLogging(arg); diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts index 6abd23979b..d88f64255c 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts @@ -12,7 +12,7 @@ export class WatcherChannel implements IServerChannel { constructor(private service: IWatcherService) { } - listen(_, event: string, arg?: any): Event { + listen(_: unknown, event: string, arg?: any): Event { switch (event) { case 'watch': return this.service.watch(arg); } @@ -20,7 +20,7 @@ export class WatcherChannel implements IServerChannel { throw new Error(`Event not found: ${event}`); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'setRoots': return this.service.setRoots(arg); case 'setVerboseLogging': return this.service.setVerboseLogging(arg); diff --git a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts b/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts index d7c5c87db3..e7b839fec4 100644 --- a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts +++ b/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts @@ -13,7 +13,6 @@ import { URI as uri } from 'vs/base/common/uri'; import * as uuid from 'vs/base/common/uuid'; import * as pfs from 'vs/base/node/pfs'; import * as encodingLib from 'vs/base/node/encoding'; -import * as utils from 'vs/workbench/services/files/test/electron-browser/utils'; import { TestEnvironmentService, TestContextService, TestTextResourceConfigurationService, TestLifecycleService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; @@ -407,92 +406,6 @@ suite('FileService', () => { }); }); - test('deleteFile', () => { - let event: FileOperationEvent; - const toDispose = service.onAfterOperation(e => { - event = e; - }); - - const resource = uri.file(path.join(testDir, 'deep', 'conway.js')); - return service.resolveFile(resource).then(source => { - return service.del(source.resource).then(() => { - assert.equal(fs.existsSync(source.resource.fsPath), false); - - assert.ok(event); - assert.equal(event.resource.fsPath, resource.fsPath); - assert.equal(event.operation, FileOperation.DELETE); - toDispose.dispose(); - }); - }); - }); - - test('deleteFolder (recursive)', function () { - let event: FileOperationEvent; - const toDispose = service.onAfterOperation(e => { - event = e; - }); - - const resource = uri.file(path.join(testDir, 'deep')); - return service.resolveFile(resource).then(source => { - return service.del(source.resource, { recursive: true }).then(() => { - assert.equal(fs.existsSync(source.resource.fsPath), false); - - assert.ok(event); - assert.equal(event.resource.fsPath, resource.fsPath); - assert.equal(event.operation, FileOperation.DELETE); - toDispose.dispose(); - }); - }); - }); - - test('deleteFolder (non recursive)', function () { - const resource = uri.file(path.join(testDir, 'deep')); - return service.resolveFile(resource).then(source => { - return service.del(source.resource).then(() => { - return Promise.reject(new Error('Unexpected')); - }, error => { - return Promise.resolve(true); - }); - }); - }); - - test('resolveFile', () => { - return service.resolveFile(uri.file(testDir), { resolveTo: [uri.file(path.join(testDir, 'deep'))] }).then(r => { - assert.equal(r.children!.length, 8); - - const deep = utils.getByName(r, 'deep')!; - assert.equal(deep.children!.length, 4); - }); - }); - - test('resolveFiles', () => { - return service.resolveFiles([ - { resource: uri.file(testDir), options: { resolveTo: [uri.file(path.join(testDir, 'deep'))] } }, - { resource: uri.file(path.join(testDir, 'deep')) } - ]).then(res => { - const r1 = res[0].stat!; - - assert.equal(r1.children!.length, 8); - - const deep = utils.getByName(r1, 'deep')!; - assert.equal(deep.children!.length, 4); - - const r2 = res[1].stat!; - assert.equal(r2.children!.length, 4); - assert.equal(r2.name, 'deep'); - }); - }); - - test('existsFile', () => { - return service.existsFile(uri.file(testDir)).then((exists) => { - assert.equal(exists, true); - - return service.existsFile(uri.file(testDir + 'something')).then((exists) => { - assert.equal(exists, false); - }); - }); - }); - test('updateContent', () => { const resource = uri.file(path.join(testDir, 'small.txt')); diff --git a/src/vs/workbench/services/files2/common/fileService2.ts b/src/vs/workbench/services/files2/common/fileService2.ts index 5d9426cd93..6d69c7bdc4 100644 --- a/src/vs/workbench/services/files2/common/fileService2.ts +++ b/src/vs/workbench/services/files2/common/fileService2.ts @@ -4,13 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; -import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IUpdateContentOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; +import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IUpdateContentOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { isAbsolutePath, dirname, basename, joinPath, isEqual } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; +import { TernarySearchTree } from 'vs/base/common/map'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { getBaseLabel } from 'vs/base/common/labels'; +import { ILogService } from 'vs/platform/log/common/log'; export class FileService2 extends Disposable implements IFileService { @@ -29,6 +33,10 @@ export class FileService2 extends Disposable implements IFileService { _serviceBrand: ServiceIdentifier; + constructor(@ILogService private logService: ILogService) { + super(); + } + //#region File System Provider private _onDidChangeFileSystemProviderRegistrations: Emitter = this._register(new Emitter()); @@ -69,7 +77,7 @@ export class FileService2 extends Disposable implements IFileService { ]); } - activateProvider(scheme: string): Promise { + async activateProvider(scheme: string): Promise { // Emit an event that we are about to activate a provider with the given scheme. // Listeners can participate in the activation by registering a provider for it. @@ -89,7 +97,7 @@ export class FileService2 extends Disposable implements IFileService { // If the provider is not yet there, make sure to join on the listeners assuming // that it takes a bit longer to register the file system provider. - return Promise.all(joiners).then(() => undefined); + await Promise.all(joiners); } canHandleResource(resource: URI): boolean { @@ -129,16 +137,130 @@ export class FileService2 extends Disposable implements IFileService { //#region File Metadata Resolving - resolveFile(resource: URI, options?: IResolveFileOptions): Promise { - return this._impl.resolveFile(resource, options); + async resolveFile(resource: URI, options?: IResolveFileOptions): Promise { + try { + return await this.doResolveFile(resource, options); + } catch (error) { + + // Specially handle file not found case as file operation result + if (toFileSystemProviderErrorCode(error) === FileSystemProviderErrorCode.FileNotFound) { + throw new FileOperationError( + localize('fileNotFoundError', "File not found ({0})", resource.toString(true)), + FileOperationResult.FILE_NOT_FOUND + ); + } + + // Bubble up any other error as is + throw error; + } } - resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Promise { - return this._impl.resolveFiles(toResolve); + private async doResolveFile(resource: URI, options?: IResolveFileOptions): Promise { + const provider = await this.withProvider(resource); + + // leverage a trie to check for recursive resolving + const to = options && options.resolveTo; + const trie = TernarySearchTree.forPaths(); + trie.set(resource.toString(), true); + if (isNonEmptyArray(to)) { + to.forEach(uri => trie.set(uri.toString(), true)); + } + + const stat = await provider.stat(resource); + + return await this.toFileStat(provider, resource, stat, undefined, (stat, siblings) => { + + // check for recursive resolving + if (Boolean(trie.findSuperstr(stat.resource.toString()) || trie.get(stat.resource.toString()))) { + return true; + } + + // check for resolving single child folders + if (stat.isDirectory && options && options.resolveSingleChildDescendants) { + return siblings === 1; + } + + return false; + }); } - existsFile(resource: URI): Promise { - return this._impl.existsFile(resource); + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat, siblings: number | undefined, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise { + + // convert to file stat + const fileStat: IFileStat = { + resource, + name: getBaseLabel(resource), + isDirectory: (stat.type & FileType.Directory) !== 0, + isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0, + isReadonly: !!(provider.capabilities & FileSystemProviderCapabilities.Readonly), + mtime: stat.mtime, + size: stat.size, + etag: stat.mtime.toString(29) + stat.size.toString(31), + }; + + // check to recurse for directories + if (fileStat.isDirectory && recurse(fileStat, siblings)) { + try { + const entries = await provider.readdir(resource); + + fileStat.children = await Promise.all(entries.map(async entry => { + const childResource = joinPath(resource, entry[0]); + const childStat = await provider.stat(childResource); + + return this.toFileStat(provider, childResource, childStat, entries.length, recurse); + })); + } catch (error) { + this.logService.trace(error); + + fileStat.children = []; // gracefully handle errors, we may not have permissions to read + } + + return fileStat; + } + + return Promise.resolve(fileStat); + } + + async resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Promise { + + // soft-groupBy, keep order, don't rearrange/merge groups + const groups: Array = []; + let group: typeof toResolve | undefined; + for (const request of toResolve) { + if (!group || group[0].resource.scheme !== request.resource.scheme) { + group = []; + groups.push(group); + } + + group.push(request); + } + + // resolve files + const result: IResolveFileResult[] = []; + for (const group of groups) { + for (const groupEntry of group) { + try { + const stat = await this.doResolveFile(groupEntry.resource, groupEntry.options); + result.push({ stat, success: true }); + } catch (error) { + this.logService.trace(error); + + result.push({ stat: undefined, success: false }); + } + } + } + + return result; + } + + async existsFile(resource: URI): Promise { + try { + await this.resolveFile(resource); + + return true; + } catch (error) { + return false; + } } //#endregion @@ -200,11 +322,11 @@ export class FileService2 extends Disposable implements IFileService { } break; // we have hit a directory that exists -> good - } catch (e) { + } catch (error) { // Bubble up any other error that is not file not found - if (toFileSystemProviderErrorCode(e) !== FileSystemProviderErrorCode.FileNotFound) { - throw e; + if (toFileSystemProviderErrorCode(error) !== FileSystemProviderErrorCode.FileNotFound) { + throw error; } // Upon error, remember directories that need to be created @@ -222,8 +344,18 @@ export class FileService2 extends Disposable implements IFileService { } } - del(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise { - return this._impl.del(resource, options); + async del(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise { + if (options && options.useTrash) { + return this._impl.del(resource, options); //TODO@ben this is https://github.com/Microsoft/vscode/issues/48259 + } + + const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource)); + + // Delete through provider + await provider.delete(resource, { recursive: !!(options && options.recursive) }); + + // Events + this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE)); } //#endregion @@ -256,4 +388,4 @@ export class FileService2 extends Disposable implements IFileService { //#endregion } -registerSingleton(IFileService, FileService2); \ No newline at end of file +registerSingleton(IFileService, FileService2); diff --git a/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts b/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts index 75019caa30..383e557d84 100644 --- a/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts +++ b/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts @@ -4,14 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { mkdir } from 'fs'; +import { tmpdir } from 'os'; import { promisify } from 'util'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux } from 'vs/base/common/platform'; -import { statLink } from 'vs/base/node/pfs'; +import { statLink, readdir, unlink, del } from 'vs/base/node/pfs'; import { normalize } from 'vs/base/common/path'; +import { joinPath } from 'vs/base/common/resources'; export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider { @@ -54,8 +56,22 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } } - readdir(resource: URI): Promise<[string, FileType][]> { - throw new Error('Method not implemented.'); + async readdir(resource: URI): Promise<[string, FileType][]> { + try { + const children = await readdir(this.toFilePath(resource)); + + const result: [string, FileType][] = []; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + + const stat = await this.stat(joinPath(resource, child)); + result.push([child, stat.type]); + } + + return result; + } catch (error) { + throw this.toFileSystemProviderError(error); + } } //#endregion @@ -90,12 +106,30 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro //#region Move/Copy/Delete/Create Folder - mkdir(resource: URI): Promise { - return promisify(mkdir)(resource.fsPath); + async mkdir(resource: URI): Promise { + try { + await promisify(mkdir)(this.toFilePath(resource)); + } catch (error) { + throw this.toFileSystemProviderError(error); + } } - delete(resource: URI, opts: FileDeleteOptions): Promise { - throw new Error('Method not implemented.'); + async delete(resource: URI, opts: FileDeleteOptions): Promise { + try { + const filePath = this.toFilePath(resource); + + if (opts.recursive) { + await del(filePath, tmpdir()); + } else { + await unlink(filePath); + } + } catch (error) { + if (error.code === 'ENOENT') { + return Promise.resolve(); // tolerate that the file might not exist + } + + throw this.toFileSystemProviderError(error); + } } rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { @@ -147,4 +181,4 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } //#endregion -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/files2/test/browser/fileService2.test.ts b/src/vs/workbench/services/files2/test/browser/fileService2.test.ts index c12bc5fbc6..f97d7d8a31 100644 --- a/src/vs/workbench/services/files2/test/browser/fileService2.test.ts +++ b/src/vs/workbench/services/files2/test/browser/fileService2.test.ts @@ -9,11 +9,12 @@ import { URI } from 'vs/base/common/uri'; import { IFileSystemProviderRegistrationEvent } from 'vs/platform/files/common/files'; import { IDisposable } from 'vs/base/common/lifecycle'; import { NullFileSystemProvider } from 'vs/workbench/test/workbenchTestServices'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('File Service 2', () => { test('provider registration', async () => { - const service = new FileService2(); + const service = new FileService2(new NullLogService()); assert.equal(service.canHandleResource(URI.parse('test://foo/bar')), false); @@ -56,4 +57,4 @@ suite('File Service 2', () => { assert.equal(registrations[1].scheme, 'test'); assert.equal(registrations[1].added, false); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/files2/test/node/diskFileService.test.ts b/src/vs/workbench/services/files2/test/node/diskFileService.test.ts index f17f255371..9a4e18f19f 100644 --- a/src/vs/workbench/services/files2/test/node/diskFileService.test.ts +++ b/src/vs/workbench/services/files2/test/node/diskFileService.test.ts @@ -10,17 +10,32 @@ import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { generateUuid } from 'vs/base/common/uuid'; -import { join } from 'vs/base/common/path'; +import { join, basename } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { copy, del } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { existsSync } from 'fs'; -import { FileOperation, FileOperationEvent } from 'vs/platform/files/common/files'; +import { FileOperation, FileOperationEvent, IFileStat } from 'vs/platform/files/common/files'; import { FileService } from 'vs/workbench/services/files/node/fileService'; import { TestContextService, TestEnvironmentService, TestTextResourceConfigurationService, TestLifecycleService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { NullLogService } from 'vs/platform/log/common/log'; + +function getByName(root: IFileStat, name: string): IFileStat | null { + if (root.children === undefined) { + return null; + } + + for (const child of root.children) { + if (child.name === name) { + return child; + } + } + + return null; +} suite('Disk File Service', () => { @@ -30,7 +45,7 @@ suite('Disk File Service', () => { let testDir: string; setup(async () => { - service = new FileService2(); + service = new FileService2(new NullLogService()); service.registerProvider(Schemas.file, new DiskFileSystemProvider()); const id = generateUuid(); @@ -49,50 +64,240 @@ suite('Disk File Service', () => { }); test('createFolder', async () => { - let event: FileOperationEvent; + let event: FileOperationEvent | undefined; const toDispose = service.onAfterOperation(e => { event = e; }); - return service.resolveFile(URI.file(testDir)).then(parent => { - const resource = URI.file(join(parent.resource.fsPath, 'newFolder')); + const parent = await service.resolveFile(URI.file(testDir)); - return service.createFolder(resource).then(f => { - assert.equal(f.name, 'newFolder'); - assert.equal(existsSync(f.resource.fsPath), true); + const resource = URI.file(join(parent.resource.fsPath, 'newFolder')); - assert.ok(event); - assert.equal(event.resource.fsPath, resource.fsPath); - assert.equal(event.operation, FileOperation.CREATE); - assert.equal(event.target!.resource.fsPath, resource.fsPath); - assert.equal(event.target!.isDirectory, true); - toDispose.dispose(); - }); - }); + const folder = await service.createFolder(resource); + + assert.equal(folder.name, 'newFolder'); + assert.equal(existsSync(folder.resource.fsPath), true); + + assert.ok(event); + assert.equal(event!.resource.fsPath, resource.fsPath); + assert.equal(event!.operation, FileOperation.CREATE); + assert.equal(event!.target!.resource.fsPath, resource.fsPath); + assert.equal(event!.target!.isDirectory, true); + + toDispose.dispose(); }); - test('createFolder: creating multiple folders at once', function () { + test('createFolder: creating multiple folders at once', async function () { let event: FileOperationEvent; const toDispose = service.onAfterOperation(e => { event = e; }); const multiFolderPaths = ['a', 'couple', 'of', 'folders']; - return service.resolveFile(URI.file(testDir)).then(parent => { - const resource = URI.file(join(parent.resource.fsPath, ...multiFolderPaths)); + const parent = await service.resolveFile(URI.file(testDir)); - return service.createFolder(resource).then(f => { - const lastFolderName = multiFolderPaths[multiFolderPaths.length - 1]; - assert.equal(f.name, lastFolderName); - assert.equal(existsSync(f.resource.fsPath), true); + const resource = URI.file(join(parent.resource.fsPath, ...multiFolderPaths)); - assert.ok(event); - assert.equal(event.resource.fsPath, resource.fsPath); - assert.equal(event.operation, FileOperation.CREATE); - assert.equal(event.target!.resource.fsPath, resource.fsPath); - assert.equal(event.target!.isDirectory, true); - toDispose.dispose(); + const folder = await service.createFolder(resource); + + const lastFolderName = multiFolderPaths[multiFolderPaths.length - 1]; + assert.equal(folder.name, lastFolderName); + assert.equal(existsSync(folder.resource.fsPath), true); + + assert.ok(event!); + assert.equal(event!.resource.fsPath, resource.fsPath); + assert.equal(event!.operation, FileOperation.CREATE); + assert.equal(event!.target!.resource.fsPath, resource.fsPath); + assert.equal(event!.target!.isDirectory, true); + + toDispose.dispose(); + }); + + test('existsFile', async () => { + let exists = await service.existsFile(URI.file(testDir)); + assert.equal(exists, true); + + exists = await service.existsFile(URI.file(testDir + 'something')); + assert.equal(exists, false); + }); + + test('resolveFile', async () => { + const resolved = await service.resolveFile(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] }); + assert.equal(resolved.children!.length, 8); + + const deep = (getByName(resolved, 'deep')!); + assert.equal(deep.children!.length, 4); + }); + + test('resolveFile - directory', async () => { + const testsElements = ['examples', 'other', 'index.html', 'site.css']; + + const result = await service.resolveFile(URI.file(getPathFromAmdModule(require, './fixtures/resolver'))); + + assert.ok(result); + assert.ok(result.children); + assert.ok(result.children!.length > 0); + assert.ok(result!.isDirectory); + assert.equal(result.children!.length, testsElements.length); + + assert.ok(result.children!.every((entry) => { + return testsElements.some((name) => { + return basename(entry.resource.fsPath) === name; }); + })); + + result.children!.forEach((value) => { + assert.ok(basename(value.resource.fsPath)); + if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) { + assert.ok(value.isDirectory); + } else if (basename(value.resource.fsPath) === 'index.html') { + assert.ok(!value.isDirectory); + assert.ok(!value.children); + } else if (basename(value.resource.fsPath) === 'site.css') { + assert.ok(!value.isDirectory); + assert.ok(!value.children); + } else { + assert.ok(!'Unexpected value ' + basename(value.resource.fsPath)); + } }); }); -}); \ No newline at end of file + + test('resolveFile - directory - resolveTo single directory', async () => { + const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver'); + const result = await service.resolveFile(URI.file(resolverFixturesPath), { resolveTo: [URI.file(join(resolverFixturesPath, 'other/deep'))] }); + + assert.ok(result); + assert.ok(result.children); + assert.ok(result.children!.length > 0); + assert.ok(result.isDirectory); + + const children = result.children!; + assert.equal(children.length, 4); + + const other = getByName(result, 'other'); + assert.ok(other); + assert.ok(other!.children!.length > 0); + + const deep = getByName(other!, 'deep'); + assert.ok(deep); + assert.ok(deep!.children!.length > 0); + assert.equal(deep!.children!.length, 4); + }); + + test('resolve directory - resolveTo multiple directories', async () => { + const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver'); + const result = await service.resolveFile(URI.file(resolverFixturesPath), { + resolveTo: [ + URI.file(join(resolverFixturesPath, 'other/deep')), + URI.file(join(resolverFixturesPath, 'examples')) + ] + }); + + assert.ok(result); + assert.ok(result.children); + assert.ok(result.children!.length > 0); + assert.ok(result.isDirectory); + + const children = result.children!; + assert.equal(children.length, 4); + + const other = getByName(result, 'other'); + assert.ok(other); + assert.ok(other!.children!.length > 0); + + const deep = getByName(other!, 'deep'); + assert.ok(deep); + assert.ok(deep!.children!.length > 0); + assert.equal(deep!.children!.length, 4); + + const examples = getByName(result, 'examples'); + assert.ok(examples); + assert.ok(examples!.children!.length > 0); + assert.equal(examples!.children!.length, 4); + }); + + test('resolve directory - resolveSingleChildFolders', async () => { + const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver/other'); + const result = await service.resolveFile(URI.file(resolverFixturesPath), { resolveSingleChildDescendants: true }); + + assert.ok(result); + assert.ok(result.children); + assert.ok(result.children!.length > 0); + assert.ok(result.isDirectory); + + const children = result.children!; + assert.equal(children.length, 1); + + let deep = getByName(result, 'deep'); + assert.ok(deep); + assert.ok(deep!.children!.length > 0); + assert.equal(deep!.children!.length, 4); + }); + + test('resolveFiles', async () => { + const res = await service.resolveFiles([ + { resource: URI.file(testDir), options: { resolveTo: [URI.file(join(testDir, 'deep'))] } }, + { resource: URI.file(join(testDir, 'deep')) } + ]); + + const r1 = (res[0].stat!); + assert.equal(r1.children!.length, 8); + + const deep = (getByName(r1, 'deep')!); + assert.equal(deep.children!.length, 4); + + const r2 = (res[1].stat!); + assert.equal(r2.children!.length, 4); + assert.equal(r2.name, 'deep'); + }); + + test('deleteFile', async () => { + let event: FileOperationEvent; + const toDispose = service.onAfterOperation(e => { + event = e; + }); + + const resource = URI.file(join(testDir, 'deep', 'conway.js')); + const source = await service.resolveFile(resource); + + await service.del(source.resource); + + assert.equal(existsSync(source.resource.fsPath), false); + assert.ok(event!); + assert.equal(event!.resource.fsPath, resource.fsPath); + assert.equal(event!.operation, FileOperation.DELETE); + + toDispose.dispose(); + }); + + test('deleteFolder (recursive)', async () => { + let event: FileOperationEvent; + const toDispose = service.onAfterOperation(e => { + event = e; + }); + + const resource = URI.file(join(testDir, 'deep')); + const source = await service.resolveFile(resource); + + await service.del(source.resource, { recursive: true }); + + assert.equal(existsSync(source.resource.fsPath), false); + assert.ok(event!); + assert.equal(event!.resource.fsPath, resource.fsPath); + assert.equal(event!.operation, FileOperation.DELETE); + + toDispose.dispose(); + }); + + test('deleteFolder (non recursive)', async () => { + const resource = URI.file(join(testDir, 'deep')); + const source = await service.resolveFile(resource); + try { + await service.del(source.resource); + return Promise.reject(new Error('Unexpected')); + } + catch (error) { + return Promise.resolve(true); + } + }); +}); diff --git a/src/vs/workbench/services/heap/common/heap.ts b/src/vs/workbench/services/heap/common/heap.ts new file mode 100644 index 0000000000..4aa4cc0ec7 --- /dev/null +++ b/src/vs/workbench/services/heap/common/heap.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { Event } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IHeapService = createDecorator('heapService'); + +export interface ObjectIdentifier { + $ident?: number; +} + +export interface IHeapService { + _serviceBrand: any; + + readonly onGarbageCollection: Event; + + /** + * Track gc-collection for the given object + */ + trackObject(obj: ObjectIdentifier | undefined): void; +} + + + +export class NullHeapService implements IHeapService { + _serviceBrand: any; + onGarbageCollection = Event.None; + trackObject() { } +} diff --git a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts b/src/vs/workbench/services/heap/node/heap.ts similarity index 66% rename from src/vs/workbench/api/electron-browser/mainThreadHeapService.ts rename to src/vs/workbench/services/heap/node/heap.ts index c6bb60dd52..ba4a8cf9ad 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts +++ b/src/vs/workbench/services/heap/node/heap.ts @@ -3,26 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtHostContext, ObjectIdentifier, IExtHostContext } from '../common/extHost.protocol'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { GCSignal } from 'gc-signals'; - -export const IHeapService = createDecorator('heapService'); - -export interface IHeapService { - _serviceBrand: any; - - readonly onGarbageCollection: Event; - - /** - * Track gc-collection for the given object - */ - trackObject(obj: ObjectIdentifier | undefined): void; -} +import { IHeapService, ObjectIdentifier } from 'vs/workbench/services/heap/common/heap'; export class HeapService implements IHeapService { @@ -93,26 +77,4 @@ export class HeapService implements IHeapService { } } -@extHostCustomer -export class MainThreadHeapService { - - private readonly _toDispose: IDisposable; - - constructor( - extHostContext: IExtHostContext, - @IHeapService heapService: IHeapService, - ) { - const proxy = extHostContext.getProxy(ExtHostContext.ExtHostHeapService); - this._toDispose = heapService.onGarbageCollection((ids) => { - // send to ext host - proxy.$onGarbageCollection(ids); - }); - } - - public dispose(): void { - this._toDispose.dispose(); - } - -} - registerSingleton(IHeapService, HeapService, true); diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 517743fb3c..e7d646fc47 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -577,7 +577,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let schemaId = 'vscode://schemas/keybindings'; let commandsSchemas: IJSONSchema[] = []; let commandsEnum: string[] = []; -let commandsEnumDescriptions: (string | null | undefined)[] = []; +let commandsEnumDescriptions: (string | undefined)[] = []; let schema: IJSONSchema = { 'id': schemaId, 'type': 'array', @@ -636,7 +636,7 @@ function updateSchema() { commandsEnumDescriptions.length = 0; const knownCommands = new Set(); - const addKnownCommand = (commandId: string, description?: string | null) => { + const addKnownCommand = (commandId: string, description?: string | undefined) => { if (!/^_/.test(commandId)) { if (!knownCommands.has(commandId)) { knownCommands.add(commandId); @@ -655,7 +655,7 @@ function updateSchema() { for (let commandId in allCommands) { const commandDescription = allCommands[commandId].description; - addKnownCommand(commandId, commandDescription && commandDescription.description); + addKnownCommand(commandId, commandDescription ? commandDescription.description : undefined); if (!commandDescription || !commandDescription.args || commandDescription.args.length !== 1 || !commandDescription.args[0].schema) { continue; @@ -684,7 +684,6 @@ function updateSchema() { for (let commandId in menuCommands) { addKnownCommand(commandId); } - } const configurationRegistry = Registry.as(ConfigExtensions.Configuration); diff --git a/src/vs/workbench/services/search/node/searchIpc.ts b/src/vs/workbench/services/search/node/searchIpc.ts index f077ebe296..2c15b8e4c9 100644 --- a/src/vs/workbench/services/search/node/searchIpc.ts +++ b/src/vs/workbench/services/search/node/searchIpc.ts @@ -11,7 +11,7 @@ export class SearchChannel implements IServerChannel { constructor(private service: IRawSearchService) { } - listen(_, event: string, arg?: any): Event { + listen(_: unknown, event: string, arg?: any): Event { switch (event) { case 'fileSearch': return this.service.fileSearch(arg); case 'textSearch': return this.service.textSearch(arg); @@ -19,7 +19,7 @@ export class SearchChannel implements IServerChannel { throw new Error('Event not found'); } - call(_, command: string, arg?: any): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'clearCache': return this.service.clearCache(arg); } diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index 1b08c1cbce..f9092c866a 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -17,19 +17,19 @@ import * as pfs from 'vs/base/node/pfs'; import { getNextTickChannel } from 'vs/base/parts/ipc/node/ipc'; import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDebugParams, IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType, ISearchConfiguration, IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, IRawSearchService, ISearchComplete, ISearchConfiguration, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { SearchChannelClient } from './searchIpc'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; export class SearchService extends Disposable implements ISearchService { _serviceBrand: any; @@ -46,7 +46,8 @@ export class SearchService extends Disposable implements ISearchService { @IEnvironmentService environmentService: IEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, - @IExtensionService private readonly extensionService: IExtensionService + @IExtensionService private readonly extensionService: IExtensionService, + @IFileService private readonly fileService: IFileService ) { super(); this.diskSearch = this.instantiationService.createInstance(DiskSearch, !environmentService.isBuilt || environmentService.verbose, environmentService.debugSearch); @@ -376,7 +377,7 @@ export class SearchService extends Disposable implements ISearchService { } // Block walkthrough, webview, etc. - else if (resource.scheme !== Schemas.file && resource.scheme !== REMOTE_HOST_SCHEME) { + else if (!this.fileService.canHandleResource(resource)) { return; } diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index eb6ebba5c0..3688c1c178 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -610,8 +610,14 @@ export class TextFileService extends Disposable implements ITextFileService { private untitledToAssociatedFileResource(untitled: URI): URI { const authority = this.windowService.getConfiguration().remoteAuthority; - - return authority ? untitled.with({ scheme: REMOTE_HOST_SCHEME, authority }) : untitled.with({ scheme: Schemas.file }); + if (authority) { + let path = untitled.path; + if (path && path[0] !== '/') { + path = '/' + path; + } + return untitled.with({ scheme: REMOTE_HOST_SCHEME, authority, path }); + } + return untitled.with({ scheme: Schemas.file }); } private doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 7029dafaa1..3bd26b9f75 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -104,11 +104,11 @@ class ResourceModelCollection extends ReferenceCollection { const resource = URI.parse(key); const providers = this.providers[resource.scheme] || []; - const factories = providers.map(p => () => Promise.resolve(p.provideTextContent(resource))); + const factories = providers.map(p => () => Promise.resolve(p.provideTextContent(resource))); return first(factories).then(model => { if (!model) { - return Promise.reject(new Error('resource is not available')); + return Promise.reject(new Error('resource is not available')); } return model; diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index 5ed4a83ae7..41c75d660f 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -15,8 +15,8 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures'; -import { MainThreadLanguageFeatures } from 'vs/workbench/api/electron-browser/mainThreadLanguageFeatures'; -import { IHeapService } from 'vs/workbench/api/electron-browser/mainThreadHeapService'; +import { MainThreadLanguageFeatures } from 'vs/workbench/api/browser/mainThreadLanguageFeatures'; +import { IHeapService, NullHeapService } from 'vs/workbench/services/heap/common/heap'; import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService'; @@ -67,12 +67,7 @@ suite('ExtHostLanguageFeatureCommands', function () { { let instantiationService = new TestInstantiationService(); rpcProtocol = new TestRPCProtocol(); - instantiationService.stub(IHeapService, { - _serviceBrand: undefined, - trackObject(_obj: any) { - // nothing - } - }); + instantiationService.stub(IHeapService, NullHeapService); instantiationService.stub(ICommandService, { _serviceBrand: undefined, executeCommand(id: string, args: any): any { diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 3801ede318..5c027f2002 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -15,10 +15,10 @@ import { TestRPCProtocol } from './testRPCProtocol'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures'; -import { MainThreadLanguageFeatures } from 'vs/workbench/api/electron-browser/mainThreadLanguageFeatures'; +import { MainThreadLanguageFeatures } from 'vs/workbench/api/browser/mainThreadLanguageFeatures'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { MainThreadCommands } from 'vs/workbench/api/browser/mainThreadCommands'; -import { IHeapService } from 'vs/workbench/api/electron-browser/mainThreadHeapService'; +import { IHeapService, NullHeapService } from 'vs/workbench/services/heap/common/heap'; import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; @@ -81,12 +81,7 @@ suite('ExtHostLanguageFeatures', function () { { let instantiationService = new TestInstantiationService(); instantiationService.stub(IMarkerService, MarkerService); - instantiationService.stub(IHeapService, { - _serviceBrand: undefined, - trackObject(_obj: any) { - // nothing - } - }); + instantiationService.stub(IHeapService, NullHeapService); inst = instantiationService; } diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocuments.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocuments.test.ts index fc61d31c38..ae544bb26b 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocuments.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocuments.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { BoundModelReferenceCollection } from 'vs/workbench/api/electron-browser/mainThreadDocuments'; +import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; import { TextModel } from 'vs/editor/common/model/textModel'; import { timeout } from 'vs/base/common/async'; diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts index 1c8cd4f721..b5a99399b1 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors'; +import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index fb1b609feb..b67404f336 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors'; +import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { SingleProxyRPCProtocol, TestRPCProtocol } from './testRPCProtocol'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; @@ -13,7 +13,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { ExtHostDocumentsAndEditorsShape, ExtHostContext, ExtHostDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { Event } from 'vs/base/common/event'; -import { MainThreadTextEditors } from 'vs/workbench/api/electron-browser/mainThreadEditors'; +import { MainThreadTextEditors } from 'vs/workbench/api/browser/mainThreadEditors'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index a5320b64a9..79f8abf83f 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -100,7 +100,7 @@ import 'vs/workbench/services/hash/node/hashService'; import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import 'vs/workbench/services/workspace/node/workspaceEditingService'; -import 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; +import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; import 'vs/workbench/services/decorations/browser/decorationsService'; import 'vs/workbench/services/search/node/searchService'; import 'vs/workbench/services/progress/browser/progressService2'; @@ -134,6 +134,7 @@ import 'vs/workbench/services/extensions/electron-browser/extensionManagementSer import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/remote/common/remoteEnvironmentService'; +import 'vs/workbench/services/heap/node/heap'; registerSingleton(IMenuService, MenuService, true); registerSingleton(IListService, ListService, true); diff --git a/src/vs/workbench/workbench.nodeless.main.ts b/src/vs/workbench/workbench.nodeless.main.ts index 4135e9f59c..7619d67871 100644 --- a/src/vs/workbench/workbench.nodeless.main.ts +++ b/src/vs/workbench/workbench.nodeless.main.ts @@ -93,6 +93,8 @@ import { ContextViewService } from 'vs/platform/contextview/browser/contextViewS // 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 { IHeapService, NullHeapService } from 'vs/workbench/services/heap/common/heap'; +import { IBroadcastService, NullBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast'; import 'vs/workbench/browser/nodeless.simpleservices'; @@ -138,6 +140,7 @@ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/remote/common/remoteEnvironmentService'; + registerSingleton(IMenuService, MenuService, true); registerSingleton(IListService, ListService, true); registerSingleton(IOpenerService, OpenerService, true); @@ -166,6 +169,8 @@ registerSingleton(IContextViewService, ContextViewService, true); // registerSingleton(IWorkspacesService, WorkspacesService); // registerSingleton(IMenubarService, MenubarService); // registerSingleton(IURLService, RelayURLService); +registerSingleton(IHeapService, NullHeapService); +registerSingleton(IBroadcastService, NullBroadcastService); registerSingleton(IContextMenuService, ContextMenuService);