From 1b293495e76d71ef4d38d2da57a88eac339ae468 Mon Sep 17 00:00:00 2001 From: rzj17 Date: Mon, 16 Dec 2019 21:38:32 +0000 Subject: [PATCH 01/12] Refactor SortOrder & SortOrderConfiguration into one enum --- .../contrib/files/browser/files.contribution.ts | 6 +++--- .../contrib/files/common/explorerModel.ts | 4 ++-- .../contrib/files/common/explorerService.ts | 8 ++++---- src/vs/workbench/contrib/files/common/files.ts | 16 +++++++--------- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 615c14812d8..7d0ec14feb3 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; -import { VIEWLET_ID, SortOrderConfiguration, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -396,8 +396,8 @@ configurationRegistry.registerConfiguration({ }, 'explorer.sortOrder': { 'type': 'string', - 'enum': [SortOrderConfiguration.DEFAULT, SortOrderConfiguration.MIXED, SortOrderConfiguration.FILES_FIRST, SortOrderConfiguration.TYPE, SortOrderConfiguration.MODIFIED], - 'default': SortOrderConfiguration.DEFAULT, + 'enum': [SortOrder.Default, SortOrder.Mixed, SortOrder.FilesFirst, SortOrder.Type, SortOrder.Modified], + 'default': SortOrder.Default, 'enumDescriptions': [ nls.localize('sortOrder.default', 'Files and folders are sorted by their names, in alphabetical order. Folders are displayed before files.'), nls.localize('sortOrder.mixed', 'Files and folders are sorted by their names, in alphabetical order. Files are interwoven with folders.'), diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index e7b364edb86..d6bb9594120 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -14,7 +14,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { IExplorerService, SortOrder } from 'vs/workbench/contrib/files/common/files'; import { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources'; export class ExplorerModel implements IDisposable { @@ -263,7 +263,7 @@ export class ExplorerItem { if (!this._isDirectoryResolved) { // Resolve metadata only when the mtime is needed since this can be expensive // Mtime is only used when the sort order is 'modified' - const resolveMetadata = explorerService.sortOrder === 'modified'; + const resolveMetadata = explorerService.sortOrder === SortOrder.Modified; try { const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); const resolved = ExplorerItem.create(explorerService, fileService, stat, this); diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index d2eed899780..60d9ed77e97 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -6,7 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IExplorerService, IFilesConfiguration, SortOrder, SortOrderConfiguration, IContextProvider } from 'vs/workbench/contrib/files/common/files'; +import { IExplorerService, IFilesConfiguration, SortOrder, IContextProvider } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; @@ -187,7 +187,7 @@ export class ExplorerService implements IExplorerService { } // Stat needs to be resolved first and then revealed - const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === 'modified' }; + const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === SortOrder.Modified }; const workspaceFolder = this.contextService.getWorkspaceFolder(resource); if (workspaceFolder === null) { return Promise.resolve(undefined); @@ -361,7 +361,7 @@ export class ExplorerService implements IExplorerService { } // Handle updated files/folders if we sort by modified - if (this._sortOrder === SortOrderConfiguration.MODIFIED) { + if (this._sortOrder === SortOrder.Modified) { const updated = e.getUpdated(); // Check updated: Refresh if updated file/folder part of resolved root @@ -387,7 +387,7 @@ export class ExplorerService implements IExplorerService { private filterToViewRelevantEvents(e: FileChangesEvent): FileChangesEvent { return new FileChangesEvent(e.changes.filter(change => { - if (change.type === FileChangeType.UPDATED && this._sortOrder !== SortOrderConfiguration.MODIFIED) { + if (change.type === FileChangeType.UPDATED && this._sortOrder !== SortOrder.Modified) { return false; // we only are about updated if we sort by modified time } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 82c461f53ac..b9900702c0d 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -133,15 +133,13 @@ export interface IFileResource { isDirectory?: boolean; } -export const SortOrderConfiguration = { - DEFAULT: 'default', - MIXED: 'mixed', - FILES_FIRST: 'filesFirst', - TYPE: 'type', - MODIFIED: 'modified' -}; - -export type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified'; +export const enum SortOrder { + Default = 'default', + Mixed = 'mixed', + FilesFirst = 'filesFirst', + Type = 'type', + Modified = 'modified' +} export class TextFileContentProvider extends Disposable implements ITextModelContentProvider { private readonly fileWatcherDisposable = this._register(new MutableDisposable()); From 7659a39d676022553759d118d8ca34fafce1c5c5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 09:53:45 +0100 Subject: [PATCH 02/12] :lipstick: tests --- .../quickopen/test/browser/quickopen.test.ts | 10 +- .../test/common/quickOpenScorer.test.ts | 2 +- src/vs/base/test/browser/progressBar.test.ts | 3 +- src/vs/base/test/common/assert.test.ts | 1 + src/vs/base/test/common/collections.test.ts | 1 - src/vs/base/test/common/extpath.test.ts | 2 +- src/vs/base/test/common/mime.test.ts | 1 + src/vs/base/test/common/types.test.ts | 2 + .../electron-main/windowsStateStorage.test.ts | 1 + .../electron-main/backupMainService.test.ts | 101 +++++++++--------- .../storage/test/node/storageService.test.ts | 50 ++++----- .../test/browser/fileEditorInput.test.ts | 1 - .../textfile/test/textFileService.test.ts | 4 +- .../browser/parts/editor/baseEditor.test.ts | 10 +- .../workbench/test/browser/quickopen.test.ts | 7 +- src/vs/workbench/test/browser/viewlet.test.ts | 2 +- .../test/common/editor/editorModel.test.ts | 2 +- .../workbench/test/workbenchTestServices.ts | 59 +++++----- 18 files changed, 132 insertions(+), 127 deletions(-) diff --git a/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts b/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts index f53d0b4e1ed..f857c0b64a7 100644 --- a/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts +++ b/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { DataSource } from 'vs/base/parts/quickopen/browser/quickOpenViewer'; @@ -28,7 +29,7 @@ suite('QuickOpen', () => { assert.equal(entry2, model.getEntries(true)[0]); }); - test('QuickOpenDataSource', () => { + test('QuickOpenDataSource', async () => { const model = new QuickOpenModel(); const entry1 = new QuickOpenEntry(); @@ -42,8 +43,7 @@ suite('QuickOpen', () => { assert.equal(true, ds.hasChildren(null!, model)); assert.equal(false, ds.hasChildren(null!, entry1)); - ds.getChildren(null!, model).then((children: any[]) => { - assert.equal(3, children.length); - }); + const children = await ds.getChildren(null!, model); + assert.equal(3, children.length); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts b/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts index 548cc9489f0..a7bbe6fb2a0 100644 --- a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts +++ b/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts @@ -833,4 +833,4 @@ suite('Quick Open Scorer', () => { assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false); assert.equal(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/browser/progressBar.test.ts b/src/vs/base/test/browser/progressBar.test.ts index 03e0a061419..f43082a0bc6 100644 --- a/src/vs/base/test/browser/progressBar.test.ts +++ b/src/vs/base/test/browser/progressBar.test.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; @@ -28,4 +29,4 @@ suite('ProgressBar', () => { bar.dispose(); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/common/assert.test.ts b/src/vs/base/test/common/assert.test.ts index c7d3343ba29..a925cd0437a 100644 --- a/src/vs/base/test/common/assert.test.ts +++ b/src/vs/base/test/common/assert.test.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { ok } from 'vs/base/common/assert'; diff --git a/src/vs/base/test/common/collections.test.ts b/src/vs/base/test/common/collections.test.ts index 353b5d0147d..e5449f46333 100644 --- a/src/vs/base/test/common/collections.test.ts +++ b/src/vs/base/test/common/collections.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import * as collections from 'vs/base/common/collections'; - suite('Collections', () => { test('forEach', () => { diff --git a/src/vs/base/test/common/extpath.test.ts b/src/vs/base/test/common/extpath.test.ts index da6da32873f..eb3d8da7a46 100644 --- a/src/vs/base/test/common/extpath.test.ts +++ b/src/vs/base/test/common/extpath.test.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import * as extpath from 'vs/base/common/extpath'; import * as platform from 'vs/base/common/platform'; @@ -28,7 +29,6 @@ suite('Paths', () => { assert.equal(extpath.getRoot('http://www/'), 'http://www/'); assert.equal(extpath.getRoot('file:///foo'), 'file:///'); assert.equal(extpath.getRoot('file://foo'), ''); - }); test('isUNC', () => { diff --git a/src/vs/base/test/common/mime.test.ts b/src/vs/base/test/common/mime.test.ts index 7c99c964c75..3d163580a6d 100644 --- a/src/vs/base/test/common/mime.test.ts +++ b/src/vs/base/test/common/mime.test.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { guessMimeTypes, registerTextMime, suggestFilename } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index 90e174dd000..0bec27fcd84 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -2,10 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import * as types from 'vs/base/common/types'; suite('Types', () => { + test('isFunction', () => { assert(!types.isFunction(undefined)); assert(!types.isFunction(null)); diff --git a/src/vs/code/test/electron-main/windowsStateStorage.test.ts b/src/vs/code/test/electron-main/windowsStateStorage.test.ts index 6094456c6f5..419caee0d71 100644 --- a/src/vs/code/test/electron-main/windowsStateStorage.test.ts +++ b/src/vs/code/test/electron-main/windowsStateStorage.test.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import * as os from 'os'; import * as path from 'vs/base/common/path'; diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index b4dc3452beb..0bffaa82992 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -44,16 +44,16 @@ suite('BackupMainService', () => { this.workspacesJsonPath = backupWorkspacesPath; } - public toBackupPath(arg: URI | string): string { + toBackupPath(arg: URI | string): string { const id = arg instanceof URI ? super.getFolderHash(arg) : arg; return path.join(this.backupHome, id); } - public getFolderHash(folderUri: URI): string { + getFolderHash(folderUri: URI): string { return super.getFolderHash(folderUri); } - public toLegacyBackupPath(folderPath: string): string { + toLegacyBackupPath(folderPath: string): string { return path.join(this.backupHome, super.getLegacyFolderHash(folderPath)); } } @@ -119,17 +119,16 @@ suite('BackupMainService', () => { let service: TestBackupMainService; let configService: TestConfigurationService; - setup(() => { + setup(async () => { // Delete any existing backups completely and then re-create it. - return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE).then(() => { - return pfs.mkdirp(backupHome); - }).then(() => { - configService = new TestConfigurationService(); - service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService); + await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + await pfs.mkdirp(backupHome); - return service.initialize(); - }); + configService = new TestConfigurationService(); + service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService); + + return service.initialize(); }); teardown(() => { @@ -591,71 +590,71 @@ suite('BackupMainService', () => { }); }); - test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', () => { + test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', async () => { service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = JSON.parse(buffer); + assert.deepEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]); }); - test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', () => { + test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', async () => { const upperFooPath = fooFile.fsPath.toUpperCase(); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath)); assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [URI.file(upperFooPath)]); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = (JSON.parse(buffer)); + assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]); }); suite('removeBackupPathSync', () => { - test('should remove folder workspaces from workspaces.json (folder workspace)', () => { + test('should remove folder workspaces from workspaces.json (folder workspace)', async () => { service.registerFolderBackupSync(fooFile); service.registerFolderBackupSync(barFile); service.unregisterFolderBackupSync(fooFile); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.folderURIWorkspaces, [barFile.toString()]); - service.unregisterFolderBackupSync(barFile); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => { - const json2 = JSON.parse(content); - assert.deepEqual(json2.folderURIWorkspaces, []); - }); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = (JSON.parse(buffer)); + assert.deepEqual(json.folderURIWorkspaces, [barFile.toString()]); + service.unregisterFolderBackupSync(barFile); + + const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json2 = (JSON.parse(content)); + assert.deepEqual(json2.folderURIWorkspaces, []); }); - test('should remove folder workspaces from workspaces.json (root workspace)', () => { + test('should remove folder workspaces from workspaces.json (root workspace)', async () => { const ws1 = toWorkspaceBackupInfo(fooFile.fsPath); service.registerWorkspaceBackupSync(ws1); const ws2 = toWorkspaceBackupInfo(barFile.fsPath); service.registerWorkspaceBackupSync(ws2); service.unregisterWorkspaceBackupSync(ws1.workspace); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]); - service.unregisterWorkspaceBackupSync(ws2.workspace); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => { - const json2 = JSON.parse(content); - assert.deepEqual(json2.rootURIWorkspaces, []); - }); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = (JSON.parse(buffer)); + assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]); + service.unregisterWorkspaceBackupSync(ws2.workspace); + + const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json2 = (JSON.parse(content)); + assert.deepEqual(json2.rootURIWorkspaces, []); }); - test('should remove empty workspaces from workspaces.json', () => { + test('should remove empty workspaces from workspaces.json', async () => { service.registerEmptyWindowBackupSync('foo'); service.registerEmptyWindowBackupSync('bar'); service.unregisterEmptyWindowBackupSync('foo'); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.emptyWorkspaces, ['bar']); - service.unregisterEmptyWindowBackupSync('bar'); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => { - const json2 = JSON.parse(content); - assert.deepEqual(json2.emptyWorkspaces, []); - }); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = (JSON.parse(buffer)); + assert.deepEqual(json.emptyWorkspaces, ['bar']); + service.unregisterEmptyWindowBackupSync('bar'); + + const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json2 = (JSON.parse(content)); + assert.deepEqual(json2.emptyWorkspaces, []); }); test('should fail gracefully when removing a path that doesn\'t exist', async () => { diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index 5f9271b43c5..67eabfb6b66 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -28,11 +28,11 @@ suite('StorageService', () => { function removeData(scope: StorageScope): void { const storage = new InMemoryStorageService(); - storage.store('Monaco.IDE.Core.Storage.Test.remove', 'foobar', scope); - strictEqual('foobar', storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!)); + storage.store('test.remove', 'foobar', scope); + strictEqual('foobar', storage.get('test.remove', scope, (undefined)!)); - storage.remove('Monaco.IDE.Core.Storage.Test.remove', scope); - ok(!storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!)); + storage.remove('test.remove', scope); + ok(!storage.get('test.remove', scope, (undefined)!)); } test('Get Data, Integer, Boolean (global, in-memory)', () => { @@ -46,34 +46,34 @@ suite('StorageService', () => { function storeData(scope: StorageScope): void { const storage = new InMemoryStorageService(); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, 'foobar'), 'foobar'); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, ''), ''); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 5), 5); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 0), 0); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, true), true); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, false), false); + strictEqual(storage.get('test.get', scope, 'foobar'), 'foobar'); + strictEqual(storage.get('test.get', scope, ''), ''); + strictEqual(storage.getNumber('test.getNumber', scope, 5), 5); + strictEqual(storage.getNumber('test.getNumber', scope, 0), 0); + strictEqual(storage.getBoolean('test.getBoolean', scope, true), true); + strictEqual(storage.getBoolean('test.getBoolean', scope, false), false); - storage.store('Monaco.IDE.Core.Storage.Test.get', 'foobar', scope); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), 'foobar'); + storage.store('test.get', 'foobar', scope); + strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar'); - storage.store('Monaco.IDE.Core.Storage.Test.get', '', scope); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), ''); + storage.store('test.get', '', scope); + strictEqual(storage.get('test.get', scope, (undefined)!), ''); - storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 5, scope); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 5); + storage.store('test.getNumber', 5, scope); + strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5); - storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 0, scope); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 0); + storage.store('test.getNumber', 0, scope); + strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 0); - storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', true, scope); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), true); + storage.store('test.getBoolean', true, scope); + strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), true); - storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', false, scope); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), false); + storage.store('test.getBoolean', false, scope); + strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), false); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getDefault', scope, 'getDefault'), 'getDefault'); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumberDefault', scope, 5), 5); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBooleanDefault', scope, true), true); + strictEqual(storage.get('test.getDefault', scope, 'getDefault'), 'getDefault'); + strictEqual(storage.getNumber('test.getNumberDefault', scope, 5), 5); + strictEqual(storage.getBoolean('test.getBooleanDefault', scope, true), true); } function uniqueStorageDir(): string { diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 932c54fa018..d1a86fceb9a 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -28,7 +28,6 @@ class ServiceAccessor { } suite('Files - FileEditorInput', () => { - let instantiationService: IInstantiationService; let accessor: ServiceAccessor; diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 79c3a8052dc..1c04252cdd7 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -40,8 +40,8 @@ class ServiceAccessor { class BeforeShutdownEventImpl implements BeforeShutdownEvent { - public value: boolean | Promise | undefined; - public reason = ShutdownReason.CLOSE; + value: boolean | Promise | undefined; + reason = ShutdownReason.CLOSE; veto(value: boolean | Promise): void { this.value = value; diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index b6abedc45d5..a2d778aeb88 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -256,17 +256,17 @@ suite('Workbench base editor', () => { } class TestEditorInput extends EditorInput { - constructor(private resource: URI, private id = 'testEditorInput') { + constructor(private resource: URI, private id = 'testEditorInputForMementoTest') { super(); } - public getTypeId() { return 'testEditorInput'; } - public resolve(): Promise { return Promise.resolve(null!); } + getTypeId() { return 'testEditorInputForMementoTest'; } + resolve(): Promise { return Promise.resolve(null!); } - public matches(other: TestEditorInput): boolean { + matches(other: TestEditorInput): boolean { return other && this.id === other.id && other instanceof TestEditorInput; } - public getResource(): URI { + getResource(): URI { return this.resource; } } diff --git a/src/vs/workbench/test/browser/quickopen.test.ts b/src/vs/workbench/test/browser/quickopen.test.ts index cbd8854f8eb..8303a042e22 100644 --- a/src/vs/workbench/test/browser/quickopen.test.ts +++ b/src/vs/workbench/test/browser/quickopen.test.ts @@ -11,7 +11,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenAction, QuickOpenHandler } from 'vs/workbench/browser/quickopen'; export class TestQuickOpenService implements IQuickOpenService { - public _serviceBrand: undefined; + + _serviceBrand: undefined; private callback?: (prefix?: string) => void; @@ -44,8 +45,8 @@ export class TestQuickOpenService implements IQuickOpenService { return null!; } - public dispose() { } - public navigate(): void { } + dispose() { } + navigate(): void { } } suite('QuickOpen', () => { diff --git a/src/vs/workbench/test/browser/viewlet.test.ts b/src/vs/workbench/test/browser/viewlet.test.ts index 7ca46667704..15a1cb526eb 100644 --- a/src/vs/workbench/test/browser/viewlet.test.ts +++ b/src/vs/workbench/test/browser/viewlet.test.ts @@ -16,7 +16,7 @@ suite('Viewlets', () => { super('id', null!, null!, null!, null!, null!, null!, null!, null!, null!, null!); } - public layout(dimension: any): void { + layout(dimension: any): void { throw new Error('Method not implemented.'); } } diff --git a/src/vs/workbench/test/common/editor/editorModel.test.ts b/src/vs/workbench/test/common/editor/editorModel.test.ts index 5b368da847a..c442c63736b 100644 --- a/src/vs/workbench/test/common/editor/editorModel.test.ts +++ b/src/vs/workbench/test/common/editor/editorModel.test.ts @@ -21,7 +21,7 @@ import { TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTe class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { - public createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) { + createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) { return super.createTextEditorModel(value, resource, preferredMode); } diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index b088fe4928a..2ff6ff19bc2 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -245,26 +245,25 @@ export class TestTextFileService extends NativeTextFileService { this.resolveTextContentError = error; } - readStream(resource: URI, options?: IReadTextFileOptions): Promise { + async readStream(resource: URI, options?: IReadTextFileOptions): Promise { if (this.resolveTextContentError) { const error = this.resolveTextContentError; this.resolveTextContentError = null; - return Promise.reject(error); + throw error; } - return this.fileService.readFileStream(resource, options).then(async (content): Promise => { - return { - resource: content.resource, - name: content.name, - mtime: content.mtime, - ctime: content.ctime, - etag: content.etag, - encoding: 'utf8', - value: await createTextBufferFactoryFromStream(content.value), - size: 10 - }; - }); + const content = await this.fileService.readFileStream(resource, options); + return { + resource: content.resource, + name: content.name, + mtime: content.mtime, + ctime: content.ctime, + etag: content.etag, + encoding: 'utf8', + value: await createTextBufferFactoryFromStream(content.value), + size: 10 + }; } promptForPath(_resource: URI, _defaultPath: URI): Promise { @@ -1021,12 +1020,14 @@ export class TestFileService implements IFileService { }); } - resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise { - return Promise.all(toResolve.map(resourceAndOption => this.resolve(resourceAndOption.resource, resourceAndOption.options))).then(stats => stats.map(stat => ({ stat, success: true }))); + async resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise { + const stats = await Promise.all(toResolve.map(resourceAndOption => this.resolve(resourceAndOption.resource, resourceAndOption.options))); + + return stats.map(stat => ({ stat, success: true })); } - exists(_resource: URI): Promise { - return Promise.resolve(true); + async exists(_resource: URI): Promise { + return true; } readFile(resource: URI, options?: IReadFileOptions | undefined): Promise { @@ -1071,11 +1072,12 @@ export class TestFileService implements IFileService { }); } - writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise { - return timeout(0).then(() => ({ + async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise { + await timeout(0); + + return ({ resource, etag: 'index.txt', - encoding: 'utf8', mtime: Date.now(), ctime: Date.now(), size: 42, @@ -1083,7 +1085,7 @@ export class TestFileService implements IFileService { isDirectory: false, isSymbolicLink: false, name: resources.basename(resource) - })); + }); } move(_source: URI, _target: URI, _overwrite?: boolean): Promise { @@ -1153,14 +1155,13 @@ export class TestBackupFileService implements IBackupFileService { return false; } - loadBackupResource(resource: URI): Promise { - return this.hasBackup(resource).then(hasBackup => { - if (hasBackup) { - return this.toBackupResource(resource); - } + async loadBackupResource(resource: URI): Promise { + const hasBackup = await this.hasBackup(resource); + if (hasBackup) { + return this.toBackupResource(resource); + } - return undefined; - }); + return undefined; } registerResourceForBackup(_resource: URI): Promise { From c7ab68cc80fad42df1a9deeb9371204086e32269 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 10:09:53 +0100 Subject: [PATCH 03/12] state :lipstick: --- src/vs/platform/storage/node/storageMainService.ts | 8 ++++---- .../workbench/contrib/files/browser/views/explorerView.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/storage/node/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts index 81c9511a239..3c34616f373 100644 --- a/src/vs/platform/storage/node/storageMainService.ts +++ b/src/vs/platform/storage/node/storageMainService.ts @@ -89,11 +89,11 @@ export class StorageMainService extends Disposable implements IStorageMainServic private static readonly STORAGE_NAME = 'state.vscdb'; - private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); - readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; + private readonly _onDidChangeStorage = this._register(new Emitter()); + readonly onDidChangeStorage = this._onDidChangeStorage.event; - private readonly _onWillSaveState: Emitter = this._register(new Emitter()); - readonly onWillSaveState: Event = this._onWillSaveState.event; + private readonly _onWillSaveState = this._register(new Emitter()); + readonly onWillSaveState = this._onWillSaveState.event; get items(): Map { return this.storage.items; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 2f6c9edc6f4..7f05dc279a9 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -436,7 +436,7 @@ export class ExplorerView extends ViewPane { } })); - // save view state on shutdown + // save view state this._register(this.storageService.onWillSaveState(() => { this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE); })); From 8e2b3047ebdfc9b0fa79023da0fcfc81b8532aff Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 10:14:36 +0100 Subject: [PATCH 04/12] Enable more than just localhost for port forwarding providers Part of #81388 --- src/vs/platform/remote/common/tunnel.ts | 6 +- .../platform/remote/common/tunnelService.ts | 8 +- .../api/browser/mainThreadTunnelService.ts | 6 +- .../workbench/api/common/extHost.protocol.ts | 4 +- .../api/common/extHostTunnelService.ts | 2 +- .../api/node/extHostTunnelService.ts | 10 +- .../contrib/remote/browser/tunnelView.ts | 68 +++++---- .../contrib/webview/common/portMapping.ts | 2 +- src/vs/workbench/electron-browser/window.ts | 2 +- .../remote/common/remoteExplorerService.ts | 138 ++++++++++-------- .../services/remote/node/tunnelService.ts | 74 ++++++---- 11 files changed, 187 insertions(+), 133 deletions(-) diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 7693ad596c6..b5fdfa8bff0 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -33,10 +33,10 @@ export interface ITunnelService { readonly tunnels: Promise; readonly onTunnelOpened: Event; - readonly onTunnelClosed: Event; + readonly onTunnelClosed: Event<{ host: string, port: number }>; - openTunnel(remotePort: number, localPort?: number): Promise | undefined; - closeTunnel(remotePort: number): Promise; + openTunnel(remoteHost: string | undefined, remotePort: number, localPort?: number): Promise | undefined; + closeTunnel(remoteHost: string, remotePort: number): Promise; setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable; } diff --git a/src/vs/platform/remote/common/tunnelService.ts b/src/vs/platform/remote/common/tunnelService.ts index 2501ebc90f1..a5fa07ac174 100644 --- a/src/vs/platform/remote/common/tunnelService.ts +++ b/src/vs/platform/remote/common/tunnelService.ts @@ -13,12 +13,12 @@ export class NoOpTunnelService implements ITunnelService { public readonly tunnels: Promise = Promise.resolve([]); private _onTunnelOpened: Emitter = new Emitter(); public onTunnelOpened: Event = this._onTunnelOpened.event; - private _onTunnelClosed: Emitter = new Emitter(); - public onTunnelClosed: Event = this._onTunnelClosed.event; - openTunnel(_remotePort: number): Promise | undefined { + private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); + public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; + openTunnel(_remoteHost: string, _remotePort: number): Promise | undefined { return undefined; } - async closeTunnel(_remotePort: number): Promise { + async closeTunnel(_remoteHost: string, _remotePort: number): Promise { } setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 482fe0f8808..3ab437aeef4 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -22,15 +22,15 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { } async $openTunnel(tunnelOptions: TunnelOptions): Promise { - const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote.port, tunnelOptions.localPort, tunnelOptions.name); + const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote, tunnelOptions.localPort, tunnelOptions.name); if (tunnel) { return TunnelDto.fromServiceTunnel(tunnel); } return undefined; } - async $closeTunnel(remotePort: number): Promise { - return this.remoteExplorerService.close(remotePort); + async $closeTunnel(remote: { host: string, port: number }): Promise { + return this.remoteExplorerService.close(remote); } async $registerCandidateFinder(): Promise { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2e879ed1eda..b4c6ebbb625 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -774,7 +774,7 @@ export interface MainThreadWindowShape extends IDisposable { export interface MainThreadTunnelServiceShape extends IDisposable { $openTunnel(tunnelOptions: TunnelOptions): Promise; - $closeTunnel(remotePort: number): Promise; + $closeTunnel(remote: { host: string, port: number }): Promise; $registerCandidateFinder(): Promise; $setTunnelProvider(): Promise; } @@ -1395,7 +1395,7 @@ export interface ExtHostStorageShape { export interface ExtHostTunnelServiceShape { - $findCandidatePorts(): Promise<{ port: number, detail: string }[]>; + $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>; $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined; $closeTunnel(remote: { host: string, port: number }): Promise; } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 40c37a6c00c..a606b3820f0 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -48,7 +48,7 @@ export class ExtHostTunnelService implements IExtHostTunnelService { async makeTunnel(forward: TunnelOptions): Promise { return undefined; } - async $findCandidatePorts(): Promise<{ port: number; detail: string; }[]> { + async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> { return []; } async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise { return { dispose: () => { } }; } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 9dbde0ddabb..93b333980a5 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -52,7 +52,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe const tunnel = await this._proxy.$openTunnel(forward); if (tunnel) { const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => { - return this._proxy.$closeTunnel(tunnel.remote.port); + return this._proxy.$closeTunnel(tunnel.remote); }); this._register(disposableTunnel); return disposableTunnel; @@ -95,7 +95,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe this._extensionTunnels.set(tunnelOptions.remote.host, new Map()); } this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel); - this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote.port))); + this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote))); return Promise.resolve(TunnelDto.fromApiTunnel(tunnel)); }); } @@ -104,12 +104,12 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } - async $findCandidatePorts(): Promise<{ port: number, detail: string }[]> { + async $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> { if (!isLinux) { return []; } - const ports: { port: number, detail: string }[] = []; + const ports: { host: string, port: number, detail: string }[] = []; const tcp: string = fs.readFileSync('/proc/net/tcp', 'utf8'); const tcp6: string = fs.readFileSync('/proc/net/tcp6', 'utf8'); const procSockets: string = await (new Promise(resolve => { @@ -150,7 +150,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => { const command = processMap[socketMap[socket].pid].cmd; if (!command.match('.*\.vscode\-server\-[a-zA-Z]+\/bin.*') && (command.indexOf('out/vs/server/main.js') === -1)) { - ports.push({ port, detail: processMap[socketMap[socket].pid].cmd }); + ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd }); } }); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 1e881877649..4f6ebb31e5e 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -26,7 +26,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IRemoteExplorerService, TunnelModel } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, TunnelModel, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -105,13 +105,13 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { get forwarded(): TunnelItem[] { return Array.from(this.model.forwarded.values()).map(tunnel => { - return new TunnelItem(TunnelType.Forwarded, tunnel.remote, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description); + return new TunnelItem(TunnelType.Forwarded, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description); }); } get detected(): TunnelItem[] { return Array.from(this.model.detected.values()).map(tunnel => { - return new TunnelItem(TunnelType.Detected, tunnel.remote, tunnel.localAddress, false, tunnel.name, tunnel.description); + return new TunnelItem(TunnelType.Detected, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, false, tunnel.name, tunnel.description); }); } @@ -119,8 +119,9 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { return this.model.candidates.then(values => { const candidates: TunnelItem[] = []; values.forEach(value => { - if (!this.model.forwarded.has(value.port) && !this.model.detected.has(value.port)) { - candidates.push(new TunnelItem(TunnelType.Candidate, value.port, undefined, false, undefined, value.detail)); + const key = MakeAddress(value.host, value.port); + if (!this.model.forwarded.has(key) && !this.model.detected.has(key)) { + candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, false, undefined, value.detail)); } }); return candidates; @@ -185,7 +186,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRendereritem).remote); + return !!((item).remotePort); } renderElement(element: ITreeNode, index: number, templateData: ITunnelTemplateData): void { @@ -196,7 +197,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer { - const isEditing = !!this.remoteExplorerService.getEditableData(e); + const isEditing = !!this.remoteExplorerService.getEditableData(e.host, e.port); if (!isEditing) { dom.removeClass(treeContainer, 'highlight'); @@ -575,12 +578,12 @@ namespace LabelTunnelAction { return async (accessor, arg) => { if (arg instanceof TunnelItem) { const remoteExplorerService = accessor.get(IRemoteExplorerService); - remoteExplorerService.setEditable(arg.remote, { + remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, { onFinish: (value, success) => { if (success) { - remoteExplorerService.tunnelModel.name(arg.remote, value); + remoteExplorerService.tunnelModel.name(arg.remoteHost, arg.remotePort, value); } - remoteExplorerService.setEditable(arg.remote, null); + remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, null); }, validationMessage: () => null, placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"), @@ -596,24 +599,32 @@ namespace ForwardPortAction { export const ID = 'remote.tunnel.forward'; export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port"); + function parseInput(value: string): { host: string, port: number } | undefined { + const matches = value.match(/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\:|localhost:)?([0-9]+)$/); + if (!matches) { + return undefined; + } + return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) }; + } + export function handler(): ICommandHandler { return async (accessor, arg) => { const remoteExplorerService = accessor.get(IRemoteExplorerService); if (arg instanceof TunnelItem) { - remoteExplorerService.tunnelModel.forward(arg.remote); + remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }); } else { const viewsService = accessor.get(IViewsService); await viewsService.openView(TunnelPanel.ID, true); - remoteExplorerService.setEditable(undefined, { + remoteExplorerService.setEditable(undefined, undefined, { onFinish: (value, success) => { - if (success) { - remoteExplorerService.tunnelModel.forward(Number(value)); + let parsed: { host: string, port: number } | undefined; + if (success && (parsed = parseInput(value))) { + remoteExplorerService.forward({ host: parsed.host, port: parsed.port }); } - remoteExplorerService.setEditable(undefined, null); + remoteExplorerService.setEditable(undefined, undefined, null); }, validationMessage: (value) => { - const asNumber = Number(value); - if ((value === '') || isNaN(asNumber) || (asNumber < 0) || (asNumber > 65535)) { + if (!parseInput(value)) { return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); } return null; @@ -633,7 +644,7 @@ namespace ClosePortAction { return async (accessor, arg) => { if (arg instanceof TunnelItem) { const remoteExplorerService = accessor.get(IRemoteExplorerService); - await remoteExplorerService.tunnelModel.close(arg.remote); + await remoteExplorerService.close({ host: arg.remoteHost, port: arg.remotePort }); } }; } @@ -648,9 +659,10 @@ namespace OpenPortInBrowserAction { if (arg instanceof TunnelItem) { const model = accessor.get(IRemoteExplorerService).tunnelModel; const openerService = accessor.get(IOpenerService); - const tunnel = model.forwarded.has(arg.remote) ? model.forwarded.get(arg.remote) : model.detected.get(arg.remote); + const key = MakeAddress(arg.remoteHost, arg.remotePort); + const tunnel = model.forwarded.get(key) || model.detected.get(key); let address: string | undefined; - if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remote))) { + if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remoteHost, tunnel.remotePort))) { return openerService.open(URI.parse('http://' + address)); } return Promise.resolve(); @@ -668,7 +680,7 @@ namespace CopyAddressAction { if (arg instanceof TunnelItem) { const model = accessor.get(IRemoteExplorerService).tunnelModel; const clipboard = accessor.get(IClipboardService); - const address = model.address(arg.remote); + const address = model.address(arg.remoteHost, arg.remotePort); if (address) { await clipboard.writeText(address.toString()); } diff --git a/src/vs/workbench/contrib/webview/common/portMapping.ts b/src/vs/workbench/contrib/webview/common/portMapping.ts index 69c216631ba..fe5a6d21962 100644 --- a/src/vs/workbench/contrib/webview/common/portMapping.ts +++ b/src/vs/workbench/contrib/webview/common/portMapping.ts @@ -68,7 +68,7 @@ export class WebviewPortMappingManager extends Disposable { if (existing) { return existing; } - const tunnel = this.tunnelService.openTunnel(remotePort); + const tunnel = this.tunnelService.openTunnel(undefined, remotePort); if (tunnel) { this._tunnels.set(remotePort, tunnel); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 5e7ff1184b5..02735f4a472 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -453,7 +453,7 @@ export class ElectronWindow extends Disposable { if (options?.allowTunneling) { const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); if (portMappingRequest) { - const tunnel = await this.tunnelService.openTunnel(portMappingRequest.port); + const tunnel = await this.tunnelService.openTunnel(undefined, portMappingRequest.port); if (tunnel) { return { resolved: uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }), diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 01f41b09a1c..f83efc87dc3 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -18,24 +18,32 @@ export const IRemoteExplorerService = createDecorator('r export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; export interface Tunnel { - remote: number; + remoteHost: string; + remotePort: number; localAddress: string; - local?: number; + localPort?: number; name?: string; description?: string; closeable?: boolean; } +export function MakeAddress(host: string, port: number): string { + if (host = '127.0.0.1') { + host = 'localhost'; + } + return host + ':' + port; +} + export class TunnelModel extends Disposable { - readonly forwarded: Map; - readonly detected: Map; + readonly forwarded: Map; + readonly detected: Map; private _onForwardPort: Emitter = new Emitter(); public onForwardPort: Event = this._onForwardPort.event; - private _onClosePort: Emitter = new Emitter(); - public onClosePort: Event = this._onClosePort.event; - private _onPortName: Emitter = new Emitter(); - public onPortName: Event = this._onPortName.event; - private _candidateFinder: (() => Promise<{ port: number, detail: string }[]>) | undefined; + private _onClosePort: Emitter<{ host: string, port: number }> = new Emitter(); + public onClosePort: Event<{ host: string, port: number }> = this._onClosePort.event; + private _onPortName: Emitter<{ host: string, port: number }> = new Emitter(); + public onPortName: Event<{ host: string, port: number }> = this._onPortName.event; + private _candidateFinder: (() => Promise<{ host: string, port: number, detail: string }[]>) | undefined; constructor( @ITunnelService private readonly tunnelService: ITunnelService @@ -45,10 +53,11 @@ export class TunnelModel extends Disposable { this.tunnelService.tunnels.then(tunnels => { tunnels.forEach(tunnel => { if (tunnel.localAddress) { - this.forwarded.set(tunnel.tunnelRemotePort, { - remote: tunnel.tunnelRemotePort, + this.forwarded.set(MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort), { + remotePort: tunnel.tunnelRemotePort, + remoteHost: tunnel.tunnelRemoteHost, localAddress: tunnel.localAddress, - local: tunnel.tunnelLocalPort + localPort: tunnel.tunnelLocalPort }); } }); @@ -56,75 +65,83 @@ export class TunnelModel extends Disposable { this.detected = new Map(); this._register(this.tunnelService.onTunnelOpened(tunnel => { - if (!this.forwarded.has(tunnel.tunnelRemotePort) && tunnel.localAddress) { - this.forwarded.set(tunnel.tunnelRemotePort, { - remote: tunnel.tunnelRemotePort, + const key = MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); + if ((!this.forwarded.has(key)) && tunnel.localAddress) { + this.forwarded.set(key, { + remoteHost: tunnel.tunnelRemoteHost, + remotePort: tunnel.tunnelRemotePort, localAddress: tunnel.localAddress, - local: tunnel.tunnelLocalPort, + localPort: tunnel.tunnelLocalPort, closeable: true }); } - this._onForwardPort.fire(this.forwarded.get(tunnel.tunnelRemotePort)!); + this._onForwardPort.fire(this.forwarded.get(key)!); })); - this._register(this.tunnelService.onTunnelClosed(remotePort => { - if (this.forwarded.has(remotePort)) { - this.forwarded.delete(remotePort); - this._onClosePort.fire(remotePort); + this._register(this.tunnelService.onTunnelClosed(address => { + const key = MakeAddress(address.host, address.port); + if (this.forwarded.has(key)) { + this.forwarded.delete(key); + this._onClosePort.fire(address); } })); } - async forward(remote: number, local?: number, name?: string): Promise { - if (!this.forwarded.has(remote)) { - const tunnel = await this.tunnelService.openTunnel(remote, local); + async forward(remote: { host: string, port: number }, local?: number, name?: string): Promise { + const key = MakeAddress(remote.host, remote.port); + if (!this.forwarded.has(key)) { + const tunnel = await this.tunnelService.openTunnel(remote.host, remote.port, local); if (tunnel && tunnel.localAddress) { const newForward: Tunnel = { - remote: tunnel.tunnelRemotePort, - local: tunnel.tunnelLocalPort, + remoteHost: tunnel.tunnelRemoteHost, + remotePort: tunnel.tunnelRemotePort, + localPort: tunnel.tunnelLocalPort, name: name, closeable: true, localAddress: tunnel.localAddress }; - this.forwarded.set(remote, newForward); + this.forwarded.set(key, newForward); this._onForwardPort.fire(newForward); return tunnel; } } } - name(remote: number, name: string) { - if (this.forwarded.has(remote)) { - this.forwarded.get(remote)!.name = name; - this._onPortName.fire(remote); - } else if (this.detected.has(remote)) { - this.detected.get(remote)!.name = name; - this._onPortName.fire(remote); + name(host: string, port: number, name: string) { + const key = MakeAddress(host, port); + if (this.forwarded.has(key)) { + this.forwarded.get(key)!.name = name; + this._onPortName.fire({ host, port }); + } else if (this.detected.has(key)) { + this.detected.get(key)!.name = name; + this._onPortName.fire({ host, port }); } } - async close(remote: number): Promise { - return this.tunnelService.closeTunnel(remote); + async close(host: string, port: number): Promise { + return this.tunnelService.closeTunnel(host, port); } - address(remote: number): string | undefined { - return (this.forwarded.get(remote) || this.detected.get(remote))?.localAddress; + address(host: string, port: number): string | undefined { + const key = MakeAddress(host, port); + return (this.forwarded.get(key) || this.detected.get(key))?.localAddress; } addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): void { tunnels.forEach(tunnel => { - this.detected.set(tunnel.remote.port, { - remote: tunnel.remote.port, + this.detected.set(MakeAddress(tunnel.remote.host, tunnel.remote.port), { + remoteHost: tunnel.remote.host, + remotePort: tunnel.remote.port, localAddress: tunnel.localAddress, closeable: false }); }); } - registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void { + registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void { this._candidateFinder = finder; } - get candidates(): Promise<{ port: number, detail: string }[]> { + get candidates(): Promise<{ host: string, port: number, detail: string }[]> { if (this._candidateFinder) { return this._candidateFinder(); } @@ -138,13 +155,13 @@ export interface IRemoteExplorerService { targetType: string; readonly helpInformation: HelpInformation[]; readonly tunnelModel: TunnelModel; - onDidChangeEditable: Event; - setEditable(remote: number | undefined, data: IEditableData | null): void; - getEditableData(remote: number | undefined): IEditableData | undefined; - forward(remote: number, local?: number, name?: string): Promise; - close(remote: number): Promise; + onDidChangeEditable: Event<{ host: string, port: number | undefined }>; + setEditable(remoteHost: string | undefined, remotePort: number | undefined, data: IEditableData | null): void; + getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined; + forward(remote: { host: string, port: number }, localPort?: number, name?: string): Promise; + close(remote: { host: string, port: number }): Promise; addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void; - registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void; + registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void; } export interface HelpInformation { @@ -189,9 +206,9 @@ class RemoteExplorerService implements IRemoteExplorerService { public readonly onDidChangeTargetType: Event = this._onDidChangeTargetType.event; private _helpInformation: HelpInformation[] = []; private _tunnelModel: TunnelModel; - private _editable: { remote: number | undefined, data: IEditableData } | undefined; - private readonly _onDidChangeEditable: Emitter = new Emitter(); - public readonly onDidChangeEditable: Event = this._onDidChangeEditable.event; + private _editable: { remoteHost: string, remotePort: number | undefined, data: IEditableData } | undefined; + private readonly _onDidChangeEditable: Emitter<{ host: string, port: number | undefined }> = new Emitter(); + public readonly onDidChangeEditable: Event<{ host: string, port: number | undefined }> = this._onDidChangeEditable.event; constructor( @IStorageService private readonly storageService: IStorageService, @@ -246,12 +263,12 @@ class RemoteExplorerService implements IRemoteExplorerService { return this._tunnelModel; } - forward(remote: number, local?: number, name?: string): Promise { + forward(remote: { host: string, port: number }, local?: number, name?: string): Promise { return this.tunnelModel.forward(remote, local, name); } - close(remote: number): Promise { - return this.tunnelModel.close(remote); + close(remote: { host: string, port: number }): Promise { + return this.tunnelModel.close(remote.host, remote.port); } addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void { @@ -260,20 +277,21 @@ class RemoteExplorerService implements IRemoteExplorerService { } } - setEditable(remote: number | undefined, data: IEditableData | null): void { + setEditable(remoteHost: string, remotePort: number | undefined, data: IEditableData | null): void { if (!data) { this._editable = undefined; } else { - this._editable = { remote, data }; + this._editable = { remoteHost, remotePort, data }; } - this._onDidChangeEditable.fire(remote); + this._onDidChangeEditable.fire({ host: remoteHost, port: remotePort }); } - getEditableData(remote: number | undefined): IEditableData | undefined { - return this._editable && this._editable.remote === remote ? this._editable.data : undefined; + getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined { + return (this._editable && (this._editable.remotePort === remotePort) && this._editable.remoteHost === remoteHost) ? + this._editable.data : undefined; } - registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void { + registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void { this.tunnelModel.registerCandidateFinder(finder); } diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 4e60ad01e15..79f473813dc 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -103,9 +103,9 @@ export class TunnelService implements ITunnelService { private _onTunnelOpened: Emitter = new Emitter(); public onTunnelOpened: Event = this._onTunnelOpened.event; - private _onTunnelClosed: Emitter = new Emitter(); - public onTunnelClosed: Event = this._onTunnelClosed.event; - private readonly _tunnels = new Map }>(); + private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); + public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; + private readonly _tunnels = new Map }>>(); private _tunnelProvider: ITunnelProvider | undefined; public constructor( @@ -130,23 +130,32 @@ export class TunnelService implements ITunnelService { } public get tunnels(): Promise { - return Promise.all(Array.from(this._tunnels.values()).map(x => x.value)); + const promises: Promise[] = []; + Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value))); + return Promise.all(promises); } dispose(): void { - for (const { value } of this._tunnels.values()) { - value.then(tunnel => tunnel.dispose()); + for (const portMap of this._tunnels.values()) { + for (const { value } of portMap.values()) { + value.then(tunnel => tunnel.dispose()); + } + portMap.clear(); } this._tunnels.clear(); } - openTunnel(remotePort: number, localPort: number): Promise | undefined { + openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise | undefined { const remoteAuthority = this.environmentService.configuration.remoteAuthority; if (!remoteAuthority) { return undefined; } - const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort, localPort); + if (!remoteHost || (remoteHost === '127.0.0.1')) { + remoteHost = 'localhost'; + } + + const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort); if (!resolvedTunnel) { return resolvedTunnel; } @@ -165,48 +174,62 @@ export class TunnelService implements ITunnelService { tunnelLocalPort: tunnel.tunnelLocalPort, localAddress: tunnel.localAddress, dispose: () => { - const existing = this._tunnels.get(tunnel.tunnelRemotePort); - if (existing) { - existing.refcount--; - this.tryDisposeTunnel(tunnel.tunnelRemotePort, existing); + const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost); + if (existingHost) { + const existing = existingHost.get(tunnel.tunnelRemotePort); + if (existing) { + existing.refcount--; + this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing); + } } } }; } - private async tryDisposeTunnel(remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { + private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { if (tunnel.refcount <= 0) { const disposePromise: Promise = tunnel.value.then(tunnel => { tunnel.dispose(); - this._onTunnelClosed.fire(tunnel.tunnelRemotePort); + this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); }); - this._tunnels.delete(remotePort); + if (this._tunnels.has(remoteHost)) { + this._tunnels.get(remoteHost)!.delete(remotePort); + } return disposePromise; } } - async closeTunnel(remotePort: number): Promise { - if (this._tunnels.has(remotePort)) { - const value = this._tunnels.get(remotePort)!; + async closeTunnel(remoteHost: string, remotePort: number): Promise { + const portMap = this._tunnels.get(remoteHost); + if (portMap && portMap.has(remotePort)) { + const value = portMap.get(remotePort)!; value.refcount = 0; - await this.tryDisposeTunnel(remotePort, value); + await this.tryDisposeTunnel(remoteHost, remotePort, value); } } - private retainOrCreateTunnel(remoteAuthority: string, remotePort: number, localPort?: number): Promise | undefined { - const existing = this._tunnels.get(remotePort); + private addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { + if (!this._tunnels.has(remoteHost)) { + this._tunnels.set(remoteHost, new Map()); + } + this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel }); + } + + private retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined { + const portMap = this._tunnels.get(remoteHost); + const existing = portMap ? portMap.get(remotePort) : undefined; if (existing) { ++existing.refcount; return existing.value; } if (this._tunnelProvider) { - const tunnel = this._tunnelProvider.forwardPort({ remote: { host: 'localhost', port: remotePort } }); + const tunnel = this._tunnelProvider.forwardPort({ remote: { host: remoteHost, port: remotePort } }); if (tunnel) { - this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); + this.addTunnelToMap(remoteHost, remotePort, tunnel); } return tunnel; - } else { + } else if (remoteHost === 'localhost') { const options: IConnectionOptions = { commit: product.commit, socketFactory: nodeSocketFactory, @@ -221,9 +244,10 @@ export class TunnelService implements ITunnelService { }; const tunnel = createRemoteTunnel(options, remotePort, localPort); - this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); + this.addTunnelToMap(remoteHost, remotePort, tunnel); return tunnel; } + return undefined; } } From a6652eec0719f01f2f57299a3a855db0e4e0bbc8 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 10:18:26 +0100 Subject: [PATCH 05/12] Update vscode.proposed.d.ts to reflect new state of forwardPort and makeTunnel --- src/vs/vscode.proposed.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4d0c545db31..ad7d468f6fb 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -79,7 +79,8 @@ declare module 'vscode' { export namespace workspace { /** - * Forwards a port. Currently only works for a remote host of localhost. + * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel. + * By default, makeTunnel only support localhost; however, RemoteAuthorityResolver:forwardPort can be used to support other ips. * @param forward The `localPort` is a suggestion only. If that port is not available another will be chosen. */ export function makeTunnel(forward: TunnelOptions): Thenable; From d79953fa273a80b118041eef93342d1ff088378c Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 10:22:59 +0100 Subject: [PATCH 06/12] Fix alignment of action icons in Forwarded Ports view --- src/vs/workbench/contrib/remote/browser/media/tunnelView.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/media/tunnelView.css b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css index 0b34a1d1259..659c875492f 100644 --- a/src/vs/workbench/contrib/remote/browser/media/tunnelView.css +++ b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css @@ -6,7 +6,3 @@ .customview-tree .tunnel-view-label { flex: 1; } - -.customview-tree .tunnel-view-label .action-label.codicon { - margin-top: 4px; -} From a62ea85438935c69d4f49f7c990bb829064a6d53 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 10:55:18 +0100 Subject: [PATCH 07/12] Polish forward port command --- .../contrib/remote/browser/tunnelView.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 4f6ebb31e5e..daedf64538d 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -472,7 +472,7 @@ export class TunnelPanel extends ViewPane { this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => { if (e.element && (e.element.tunnelType === TunnelType.Add)) { - this.commandService.executeCommand(ForwardPortAction.ID); + this.commandService.executeCommand(ForwardPortAction.ID, 'inline add'); } })); @@ -598,6 +598,7 @@ namespace LabelTunnelAction { namespace ForwardPortAction { export const ID = 'remote.tunnel.forward'; export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port"); + const forwardPrompt = nls.localize('remote.tunnel.forwardPrompt', "Port number or address (eg. 3000 or 10.10.10.10:2000)."); function parseInput(value: string): { host: string, port: number } | undefined { const matches = value.match(/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\:|localhost:)?([0-9]+)$/); @@ -607,14 +608,19 @@ namespace ForwardPortAction { return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) }; } + function validateInput(value: string): string | null { + if (!parseInput(value)) { + return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); + } + return null; + } + export function handler(): ICommandHandler { return async (accessor, arg) => { const remoteExplorerService = accessor.get(IRemoteExplorerService); if (arg instanceof TunnelItem) { remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }); - } else { - const viewsService = accessor.get(IViewsService); - await viewsService.openView(TunnelPanel.ID, true); + } else if (arg) { remoteExplorerService.setEditable(undefined, undefined, { onFinish: (value, success) => { let parsed: { host: string, port: number } | undefined; @@ -623,14 +629,21 @@ namespace ForwardPortAction { } remoteExplorerService.setEditable(undefined, undefined, null); }, - validationMessage: (value) => { - if (!parseInput(value)) { - return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); - } - return null; - }, - placeholder: nls.localize('remote.tunnelsView.forwardPortPlaceholder', "Port number") + validationMessage: validateInput, + placeholder: forwardPrompt }); + } else { + const viewsService = accessor.get(IViewsService); + const quickInputService = accessor.get(IQuickInputService); + await viewsService.openView(TunnelPanel.ID, true); + const value = await quickInputService.input({ + prompt: forwardPrompt, + validateInput: (value) => Promise.resolve(validateInput(value)) + }); + let parsed: { host: string, port: number } | undefined; + if (value && (parsed = parseInput(value))) { + remoteExplorerService.forward({ host: parsed.host, port: parsed.port }); + } } }; } From 26f5dfcd76c9a02f116a706844611aeb09ae8c0b Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 10:57:54 +0100 Subject: [PATCH 08/12] Revert "Windows ssh remote: Cannot use `\` in explorer new file dialog to create directories" This reverts commit 85f587ab646c2d7bcbe902aee0cb48ccfcfdb4fe. --- src/vs/workbench/contrib/files/browser/fileActions.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 5c0bb70b565..a2ae86d7408 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/fileactions'; import * as nls from 'vs/nls'; import { isWindows, isWeb } from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; -import { extname, basename, posix, win32 } from 'vs/base/common/path'; +import { extname, basename } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -45,7 +45,6 @@ import { asDomUri, triggerDownload } from 'vs/base/browser/dom'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ILabelService } from 'vs/platform/label/common/label'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -855,7 +854,6 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole const editorService = accessor.get(IEditorService); const viewletService = accessor.get(IViewletService); const notificationService = accessor.get(INotificationService); - const labelService = accessor.get(ILabelService); await viewletService.openViewlet(VIEWLET_ID, true); @@ -878,10 +876,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole folder.addChild(newStat); const onSuccess = (value: string): Promise => { - const separator = labelService.getSeparator(folder.resource.scheme); - const resource = folder.resource.with({ path: separator === '/' ? posix.join(folder.resource.path, value) : win32.join(folder.resource.path, value) }); - const createPromise = isFolder ? fileService.createFolder(resource) : textFileService.create(resource); - + const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); return createPromise.then(created => { refreshIfSeparator(value, explorerService); return isFolder ? explorerService.select(created.resource, true) From 728416a0dbbdc88f30f803f8a5a9f22b55b9c6e2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 11:26:57 +0100 Subject: [PATCH 09/12] window - save window state on focus lost (fix #87171) --- .../electron-main/windowsMainService.ts | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 101dd334ad3..6515a6325f1 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -13,14 +13,14 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; -import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display } from 'electron'; +import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display, app } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows'; import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/platform/windows/node/window'; -import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; @@ -160,14 +160,16 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly windowsState: IWindowsState; private lastClosedWindowState?: IWindowState; + private shuttingDown = false; + private readonly _onWindowReady = this._register(new Emitter()); - readonly onWindowReady: CommonEvent = this._onWindowReady.event; + readonly onWindowReady = this._onWindowReady.event; private readonly _onWindowClose = this._register(new Emitter()); - readonly onWindowClose: CommonEvent = this._onWindowClose.event; + readonly onWindowClose = this._onWindowClose.event; private readonly _onWindowsCountChanged = this._register(new Emitter()); - readonly onWindowsCountChanged: CommonEvent = this._onWindowsCountChanged.event; + readonly onWindowsCountChanged = this._onWindowsCountChanged.event; constructor( private readonly machineId: string, @@ -236,6 +238,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic systemPreferences.on('high-contrast-color-scheme-changed', () => onHighContrastChange()); } + // When a window looses focus, save all windows state. This allows to + // prevent loss of window-state data when OS is restarted without properly + // shutting down the application (https://github.com/microsoft/vscode/issues/87171) + app.on('browser-window-blur', () => { + if (!this.shuttingDown) { + this.saveWindowsState(); + } + }); + // Handle various lifecycle events around windows this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown()); @@ -292,6 +303,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0) // private onBeforeShutdown(): void { + this.shuttingDown = true; + + this.saveWindowsState(); + } + + private saveWindowsState(): void { const currentWindowsState: IWindowsState = { openedWindows: [], lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow, @@ -327,8 +344,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Persist const state = getWindowsStateStoreData(currentWindowsState); - this.logService.trace('onBeforeShutdown', state); this.stateService.setItem(WindowsMainService.windowsStateStorageKey, state); + + if (this.shuttingDown) { + this.logService.trace('onBeforeShutdown', state); + } } // See note on #onBeforeShutdown() for details how these events are flowing From 19062a297616055203708da0f86381d7d4a35572 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 11:42:43 +0100 Subject: [PATCH 10/12] debt - move workbench.enableExperiments to correct place --- build/lib/i18n.resources.json | 4 ++++ .../browser/workbench.contribution.ts | 6 ------ .../browser/experiments.contribution.ts | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index e9a4f279631..e82324735d7 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -58,6 +58,10 @@ "name": "vs/workbench/contrib/emmet", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/experiments", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/extensions", "project": "vscode-workbench" diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 854db17c376..2cd6fba7098 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -235,12 +235,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio 'description': nls.localize('settings.editor.desc', "Determines which settings editor to use by default."), 'default': 'ui', 'scope': ConfigurationScope.WINDOW - }, - 'workbench.enableExperiments': { - 'type': 'boolean', - 'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), - 'default': true, - 'tags': ['usesOnlineServices'] } } }); diff --git a/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts index 67b6159734f..cc24a8695e8 100644 --- a/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts +++ b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts @@ -3,13 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; registerSingleton(IExperimentService, ExperimentService, true); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExperimentalPrompts, LifecyclePhase.Eventually); + +const registry = Registry.as(ConfigurationExtensions.Configuration); + +// Configuration +registry.registerConfiguration({ + ...workbenchConfigurationNodeBase, + 'properties': { + 'workbench.enableExperiments': { + 'type': 'boolean', + 'description': localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), + 'default': true, + 'tags': ['usesOnlineServices'] + } + } +}); From b6ae21e6e2e7255978993df45c1317a869170d21 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 11:46:26 +0100 Subject: [PATCH 11/12] tests - increase timeout for backup test --- .../backup/test/electron-browser/backupRestorer.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index 1f55662a66d..19054f498f3 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -93,7 +93,9 @@ suite('BackupModelRestorer', () => { return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); }); - test('Restore backups', async () => { + test('Restore backups', async function () { + this.timeout(20000); + const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); const instantiationService = workbenchInstantiationService(); instantiationService.stub(IBackupFileService, backupFileService); From 67a6aac599cdabff3c5aa31b150ef2b05c9305ee Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 12:12:53 +0100 Subject: [PATCH 12/12] #87243 Support hiding view container title when merged --- .../api/browser/viewsExtensionPoint.ts | 2 +- .../browser/parts/views/viewPaneContainer.ts | 37 ++++++++++--------- .../browser/parts/views/viewsViewlet.ts | 2 +- .../contrib/debug/browser/debugViewlet.ts | 2 +- .../extensions/browser/extensionsViewlet.ts | 2 +- .../contrib/files/browser/explorerViewlet.ts | 2 +- .../contrib/scm/browser/scmViewlet.ts | 2 +- .../contrib/search/browser/searchViewlet.ts | 2 +- 8 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 16b29f60591..ff322f7237f 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -327,7 +327,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, ) { - super(id, `${id}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); } } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 5d2b3bbefa6..bd80bbfd037 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -234,7 +234,8 @@ export abstract class ViewPane extends Pane implements IView { } export interface IViewPaneContainerOptions extends IPaneViewOptions { - showHeaderInTitleWhenSingleView: boolean; + mergeViewWithContainerWhenSingleView: boolean; + donotShowContainerTitleWhenMergedWithContainer?: boolean; } interface IViewPaneItem { @@ -346,14 +347,16 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { getTitle(): string { const composite = Registry.as(ViewletExtensions.Viewlets).getViewlet(this.getId()) || Registry.as(PanelExtensions.Panels).getPanel(this.getId()); - let title = composite.name; - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { const paneItemTitle = this.paneItems[0].pane.title; - title = paneItemTitle ? `${title}: ${paneItemTitle}` : title; + if (this.options.donotShowContainerTitleWhenMergedWithContainer) { + return this.paneItems[0].pane.title; + } + return paneItemTitle ? `${composite.name}: ${paneItemTitle}` : composite.name; } - return title; + return composite.name; } private showContextMenu(event: StandardMouseEvent): void { @@ -402,7 +405,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getActions(): IAction[] { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { return this.paneItems[0].pane.getActions(); } @@ -410,7 +413,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getSecondaryActions(): IAction[] { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { return this.paneItems[0].pane.getSecondaryActions(); } @@ -418,7 +421,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getActionViewItem(action: IAction): IActionViewItem | undefined { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { return this.paneItems[0].pane.getActionViewItem(action); } @@ -459,14 +462,14 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } addPanes(panes: { pane: ViewPane, size: number, index?: number; }[]): void { - const wasSingleView = this.isSingleView(); + const wasMerged = this.isViewMergedWithContainer(); for (const { pane: pane, size, index } of panes) { this.addPane(pane, size, index); } this.updateViewHeaders(); - if (this.isSingleView() !== wasSingleView) { + if (this.isViewMergedWithContainer() !== wasMerged) { this.updateTitleArea(); } } @@ -643,7 +646,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane); const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { this.updateTitleArea(); } }); @@ -668,12 +671,12 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } removePanes(panes: ViewPane[]): void { - const wasSingleView = this.isSingleView(); + const wasMerged = this.isViewMergedWithContainer(); panes.forEach(pane => this.removePane(pane)); this.updateViewHeaders(); - if (wasSingleView !== this.isSingleView()) { + if (wasMerged !== this.isViewMergedWithContainer()) { this.updateTitleArea(); } } @@ -726,8 +729,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return assertIsDefined(this.paneview).getPaneSize(pane); } - protected updateViewHeaders(): void { - if (this.isSingleView()) { + private updateViewHeaders(): void { + if (this.isViewMergedWithContainer()) { this.paneItems[0].pane.setExpanded(true); this.paneItems[0].pane.headerVisible = false; } else { @@ -735,8 +738,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } - protected isSingleView(): boolean { - if (!(this.options.showHeaderInTitleWhenSingleView && this.paneItems.length === 1)) { + private isViewMergedWithContainer(): boolean { + if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) { return false; } if (!this.areExtensionsReady) { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index fa22f5d0b97..078939500ef 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -46,7 +46,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { @IWorkspaceContextService contextService: IWorkspaceContextService ) { - super(viewletId, `${viewletId}.state`, { showHeaderInTitleWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this._register(onDidChangeFilterValue(newFilterValue => { this.filterValue = newFilterValue; this.onFilterChanged(newFilterValue); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 86fb545750b..562559bbd05 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -79,7 +79,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { @IContextKeyService private readonly contextKeyService: IContextKeyService, @INotificationService private readonly notificationService: INotificationService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 32944476223..72c39d72b82 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -370,7 +370,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this.searchDelayer = new Delayer(500); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 601ed0673d3..083cd8bd13d 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -183,7 +183,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { @IExtensionService extensionService: IExtensionService ) { - super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 145596f7591..95fbff130af 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -116,7 +116,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this.menus = instantiationService.createInstance(SCMMenus, undefined); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts index a216091af0c..4b153f60e47 100644 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.ts +++ b/src/vs/workbench/contrib/search/browser/searchViewlet.ts @@ -48,7 +48,7 @@ export class SearchViewPaneContainer extends ViewPaneContainer { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); } getTitle(): string {