diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index e9a4f27963..d5fd9c166b 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" @@ -266,6 +270,10 @@ "name": "vs/workbench/services/remote", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/search", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/textfile", "project": "vscode-workbench" diff --git a/package.json b/package.json index 3e79e81ad9..c71ff0ec62 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "@types/plotly.js": "^1.44.9", "@types/sanitize-html": "^1.18.2", "@types/sinon": "^1.16.36", + "@types/vscode-windows-registry": "^1.0.0", "@types/webpack": "^4.4.10", "@types/windows-foreground-love": "^0.3.0", "@types/windows-mutex": "^0.4.0", diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts index d32ee7f13b..d80082f2c1 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts @@ -46,7 +46,7 @@ export class OpenDataExplorerViewletAction extends ShowViewletAction { } } -export const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('dataexplorer.name', "Connections") }, ViewContainerLocation.Sidebar); export class DataExplorerViewletViewsContribution implements IWorkbenchContribution { @@ -106,7 +106,7 @@ export class DataExplorerViewPaneContainer extends ViewPaneContainer { @IMenuService private menuService: IMenuService, @IContextKeyService private contextKeyService: IContextKeyService ) { - 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); } create(parent: HTMLElement): void { diff --git a/src/typings/vscode-windows-registry.d.ts b/src/typings/vscode-windows-registry.d.ts deleted file mode 100644 index fad75f2b64..0000000000 --- a/src/typings/vscode-windows-registry.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode-windows-registry' { - export type HKEY = "HKEY_CURRENT_USER" | "HKEY_LOCAL_MACHINE" | "HKEY_CLASSES_ROOT" | "HKEY_USERS" | "HKEY_CURRENT_CONFIG"; - export function GetStringRegKey(hive: HKEY, path: string, name: string): string | undefined; -} 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 39d574d2d1..3f63ae9f56 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 Source EULA. 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 035abdfe77..e97a02c35e 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 3d00b3b40f..cbc2eca690 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 Source EULA. 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 711d9dfe93..fd50c53247 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 Source EULA. 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 321ecbf224..3144a766dc 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 d8bea0acca..67f9ebacee 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 Source EULA. 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 2c51b9226f..e7788ae2eb 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 Source EULA. 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 07e1fc90d0..d4817bc78f 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 Source EULA. 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 502922a989..f6dea5d873 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 Source EULA. 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/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 11addcf0a8..1aa0df8769 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -592,30 +592,34 @@ class SemanticColoringProviderStyling { public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number { const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet); + let metadata: number | undefined; if (entry) { - return entry.metadata; - } - - const tokenType = this._legend.tokenTypes[tokenTypeIndex]; - const tokenModifiers: string[] = []; - for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { - if (tokenModifierSet & 1) { - tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); + metadata = entry.metadata; + } else { + const tokenType = this._legend.tokenTypes[tokenTypeIndex]; + const tokenModifiers: string[] = []; + for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { + if (tokenModifierSet & 1) { + tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); + } + tokenModifierSet = tokenModifierSet >> 1; } - tokenModifierSet = tokenModifierSet >> 1; - } - let metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); - if (typeof metadata === 'undefined') { - metadata = Constants.NO_STYLING; + metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); + if (typeof metadata === 'undefined') { + metadata = Constants.NO_STYLING; + } + this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); } if (this._logService.getLevel() === LogLevel.Trace) { - this._logService.trace(`getTokenStyleMetadata(${tokenType}${tokenModifiers.length ? ', ' + tokenModifiers.join(' ') : ''}): foreground: ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); + const type = this._legend.tokenTypes[tokenTypeIndex]; + const modifiers = tokenModifierSet ? ' ' + this._legend.tokenModifiers.filter((_, i) => tokenModifierSet & (1 << i)).join(' ') : ''; + this._logService.trace(`tokenStyleMetadata ${entry ? '[CACHED] ' : ''}${type}${modifiers}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); } - - this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); return metadata; } + + } const enum SemanticColoringConstants { 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 d888c87bda..21bcc5f3cf 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/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index cc2d2144f5..9889bb49fd 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; -import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError } from 'vs/platform/files/common/files'; +import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError, IFileSystemProviderCapabilitiesChangeEvent } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isAbsolutePath, dirname, basename, joinPath, isEqual, isEqualOrParent } from 'vs/base/common/resources'; @@ -33,11 +33,14 @@ export class FileService extends Disposable implements IFileService { //#region File System Provider - private _onDidChangeFileSystemProviderRegistrations: Emitter = this._register(new Emitter()); - readonly onDidChangeFileSystemProviderRegistrations: Event = this._onDidChangeFileSystemProviderRegistrations.event; + private _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter()); + readonly onDidChangeFileSystemProviderRegistrations = this._onDidChangeFileSystemProviderRegistrations.event; - private _onWillActivateFileSystemProvider: Emitter = this._register(new Emitter()); - readonly onWillActivateFileSystemProvider: Event = this._onWillActivateFileSystemProvider.event; + private _onWillActivateFileSystemProvider = this._register(new Emitter()); + readonly onWillActivateFileSystemProvider = this._onWillActivateFileSystemProvider.event; + + private _onDidChangeFileSystemProviderCapabilities = this._register(new Emitter()); + readonly onDidChangeFileSystemProviderCapabilities = this._onDidChangeFileSystemProviderCapabilities.event; private readonly provider = new Map(); @@ -53,6 +56,7 @@ export class FileService extends Disposable implements IFileService { // Forward events from provider const providerDisposables = new DisposableStore(); providerDisposables.add(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes)))); + providerDisposables.add(provider.onDidChangeCapabilities(() => this._onDidChangeFileSystemProviderCapabilities.fire({ provider, scheme }))); if (typeof provider.onDidErrorOccur === 'function') { providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error)))); } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 18dd21c0a1..4e6f0ceab9 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -28,6 +28,11 @@ export interface IFileService { */ readonly onDidChangeFileSystemProviderRegistrations: Event; + /** + * An even that is fired when a registered file system provider changes it's capabilities. + */ + readonly onDidChangeFileSystemProviderCapabilities: Event; + /** * An event that is fired when a file system provider is about to be activated. Listeners * can join this event with a long running promise to help in the activation process. @@ -409,6 +414,11 @@ export interface IFileSystemProviderRegistrationEvent { provider?: IFileSystemProvider; } +export interface IFileSystemProviderCapabilitiesChangeEvent { + provider: IFileSystemProvider; + scheme: string; +} + export interface IFileSystemProviderActivationEvent { scheme: string; join(promise: Promise): void; diff --git a/src/vs/platform/files/test/browser/fileService.test.ts b/src/vs/platform/files/test/browser/fileService.test.ts index 7b4e7f11a7..19695fc90e 100644 --- a/src/vs/platform/files/test/browser/fileService.test.ts +++ b/src/vs/platform/files/test/browser/fileService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { FileService } from 'vs/platform/files/common/fileService'; import { URI } from 'vs/base/common/uri'; -import { IFileSystemProviderRegistrationEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { IFileSystemProviderRegistrationEvent, FileSystemProviderCapabilities, IFileSystemProviderCapabilitiesChangeEvent } from 'vs/platform/files/common/files'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { NullLogService } from 'vs/platform/log/common/log'; import { timeout } from 'vs/base/common/async'; @@ -17,6 +17,7 @@ suite('File Service', () => { test('provider registration', async () => { const service = new FileService(new NullLogService()); const resource = URI.parse('test://foo/bar'); + const provider = new NullFileSystemProvider(); assert.equal(service.canHandleResource(resource), false); @@ -25,6 +26,11 @@ suite('File Service', () => { registrations.push(e); }); + const capabilityChanges: IFileSystemProviderCapabilitiesChangeEvent[] = []; + service.onDidChangeFileSystemProviderCapabilities(e => { + capabilityChanges.push(e); + }); + let registrationDisposable: IDisposable | undefined = undefined; let callCount = 0; service.onWillActivateFileSystemProvider(e => { @@ -32,7 +38,7 @@ suite('File Service', () => { if (e.scheme === 'test' && callCount === 1) { e.join(new Promise(resolve => { - registrationDisposable = service.registerProvider('test', new NullFileSystemProvider()); + registrationDisposable = service.registerProvider('test', provider); resolve(); })); @@ -48,6 +54,13 @@ suite('File Service', () => { assert.equal(registrations[0].added, true); assert.ok(registrationDisposable); + assert.equal(capabilityChanges.length, 0); + + provider.setCapabilities(FileSystemProviderCapabilities.FileFolderCopy); + assert.equal(capabilityChanges.length, 1); + provider.setCapabilities(FileSystemProviderCapabilities.Readonly); + assert.equal(capabilityChanges.length, 2); + await service.activateProvider('test'); assert.equal(callCount, 2); // activation is called again diff --git a/src/vs/platform/files/test/common/nullFileSystemProvider.ts b/src/vs/platform/files/test/common/nullFileSystemProvider.ts index c1ed081d14..74e8392928 100644 --- a/src/vs/platform/files/test/common/nullFileSystemProvider.ts +++ b/src/vs/platform/files/test/common/nullFileSystemProvider.ts @@ -6,14 +6,22 @@ import { URI } from 'vs/base/common/uri'; import { FileSystemProviderCapabilities, IFileSystemProvider, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, IFileChange } from 'vs/platform/files/common/files'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; export class NullFileSystemProvider implements IFileSystemProvider { capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly; - onDidChangeCapabilities: Event = Event.None; - onDidChangeFile: Event = Event.None; + private readonly _onDidChangeCapabilities = new Emitter(); + readonly onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event; + + setCapabilities(capabilities: FileSystemProviderCapabilities): void { + this.capabilities = capabilities; + + this._onDidChangeCapabilities.fire(); + } + + readonly onDidChangeFile: Event = Event.None; constructor(private disposableFactory: () => IDisposable = () => Disposable.None) { } diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 5cb91ff36e..9c67f2e477 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -18,7 +18,7 @@ export interface ResolvedOptions { } export interface TunnelInformation { - detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[]; + environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[]; } export interface ResolverResult { diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 1caba947b5..a21943c877 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -19,9 +19,9 @@ export interface RemoteTunnel { } export interface TunnelOptions { - remote: { port: number, host: string }; + remoteAddress: { port: number, host: string }; localPort?: number; - name?: string; + label?: string; } export interface ITunnelProvider { @@ -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 dd4a085fca..057adb70f0 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/platform/storage/node/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts index 04525f9f94..c34fd5cbdd 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/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index f49e34a430..ff37382502 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/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 7fd571ff6e..2f79b511cc 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -381,12 +381,13 @@ function registerDefaultClassifications(): void { registerTokenType('parameterType', nls.localize('parameterType', "Style for parameter types."), undefined, 'type'); registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]); + registerTokenType('member', nls.localize('member', "Style for member"), [['entity.name.function'], ['support.function']]); registerTokenType('macro', nls.localize('macro', "Style for macros."), undefined, 'function'); registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable'], ['entity.name.variable']]); registerTokenType('constant', nls.localize('constant', "Style for constants."), undefined, 'variable'); registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), undefined, 'variable'); - registerTokenType('property', nls.localize('propertie', "Style for properties."), undefined, 'variable'); + registerTokenType('property', nls.localize('property', "Style for properties."), undefined, 'variable'); registerTokenType('label', nls.localize('labels', "Style for labels. "), undefined); @@ -394,7 +395,7 @@ function registerDefaultClassifications(): void { tokenClassificationRegistry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); tokenClassificationRegistry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); - tokenClassificationRegistry.registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); + //tokenClassificationRegistry.registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); tokenClassificationRegistry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); tokenClassificationRegistry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 4d352a1914..e8ad2b7cea 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 diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e83908f69e..a5ea245c78 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -34,15 +34,18 @@ declare module 'vscode' { } export interface TunnelOptions { - remote: { port: number, host: string }; - localPort?: number; - name?: string; + remoteAddress: { port: number, host: string }; + // The desired local port. If this port can't be used, then another will be chosen. + localAddressPort?: number; + label?: string; } export interface Tunnel { - remote: { port: number, host: string }; + remoteAddress: { port: number, host: string }; + //The complete local address(ex. localhost:1234) localAddress: string; - onDispose: Event; + // Implementers of Tunnel should fire onDidDispose when dispose is called. + onDidDispose: Event; dispose(): void; } @@ -52,10 +55,10 @@ declare module 'vscode' { export interface TunnelInformation { /** * Tunnels that are detected by the extension. The remotePort is used for display purposes. - * The localAddress should be the complete local address(ex. localhost:1234) for connecting to the port. Tunnels provided through + * The localAddress should be the complete local address (ex. localhost:1234) for connecting to the port. Tunnels provided through * detected are read-only from the forwarded ports UI. */ - detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[]; + environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[]; } export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; @@ -74,15 +77,16 @@ declare module 'vscode' { * When not implemented, the core will use its default forwarding logic. * When implemented, the core will use this to forward ports. */ - forwardPort?(tunnelOptions: TunnelOptions): Thenable | undefined; + tunnelFactory?: (tunnelOptions: TunnelOptions) => Thenable | undefined; } export namespace workspace { /** - * Forwards a port. Currently only works for a remote host of localhost. - * @param forward The `localPort` is a suggestion only. If that port is not available another will be chosen. + * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel. + * By default, openTunnel only support localhost; however, RemoteAuthorityResolver:tunnelFactory can be used to support other ips. + * @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen. */ - export function makeTunnel(forward: TunnelOptions): Thenable; + export function openTunnel(tunnelOptions: TunnelOptions): Thenable; } export interface ResourceLabelFormatter { @@ -1410,31 +1414,6 @@ declare module 'vscode' { */ export interface WorkspaceConfiguration { - /** - * Return a value from this configuration. - * - * @param section Configuration name, supports _dotted_ names. - * @return The value `section` denotes or `undefined`. - */ - get(section: string): T | undefined; - - /** - * Return a value from this configuration. - * - * @param section Configuration name, supports _dotted_ names. - * @param defaultValue A value should be returned when no value could be found, is `undefined`. - * @return The value `section` denotes or the default. - */ - get(section: string, defaultValue: T): T; - - /** - * Check if this configuration has a certain value. - * - * @param section Configuration name, supports _dotted_ names. - * @return `true` if the section doesn't resolve to `undefined`. - */ - has(section: string): boolean; - /** * Retrieve all information about a configuration setting. A configuration value * often consists of a *default* value, a global or installation-wide value, @@ -1492,11 +1471,6 @@ declare module 'vscode' { * - configuration to workspace folder when [WorkspaceConfiguration](#WorkspaceConfiguration) is not scoped to a resource. */ update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, scopeToLanguage?: boolean): Thenable; - - /** - * Readable dictionary that backs this configuration. - */ - readonly [key: string]: any; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 02c77e11b3..936d7d10f5 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; -import { ITunnelProvider, ITunnelService } from 'vs/platform/remote/common/tunnel'; +import { ITunnelProvider, ITunnelService, TunnelOptions } from 'vs/platform/remote/common/tunnel'; @extHostNamedCustomer(MainContext.MainThreadTunnelService) export class MainThreadTunnelService implements MainThreadTunnelServiceShape { @@ -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.remoteAddress, tunnelOptions.localPort, tunnelOptions.label); 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 { @@ -44,11 +44,11 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { if (forward) { return forward.then(tunnel => { return { - tunnelRemotePort: tunnel.remote.port, - tunnelRemoteHost: tunnel.remote.host, + tunnelRemotePort: tunnel.remoteAddress.port, + tunnelRemoteHost: tunnel.remoteAddress.host, localAddress: tunnel.localAddress, dispose: () => { - this._proxy.$closeTunnel({ host: tunnel.remote.host, port: tunnel.remote.port }); + this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }); } }; }); diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 25b42bada7..229540e08c 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -313,7 +313,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!viewContainer) { - viewContainer = this.viewContainersRegistry.registerViewContainer(id, ViewContainerLocation.Sidebar, true, extensionId); + viewContainer = this.viewContainersRegistry.registerViewContainer({ id, hideIfEmpty: true, name: title, extensionId }, ViewContainerLocation.Sidebar); class CustomViewPaneContainer extends ViewPaneContainer { constructor( @@ -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/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 04b9f99be5..e7f843b449 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -672,7 +672,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null): vscode.WorkspaceConfiguration { scope = arguments.length === 1 ? undefined : scope; - return configProvider.getConfiguration(section, scope, extension.identifier); + return configProvider.getConfiguration(section, scope, extension); }, registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) { return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider); @@ -720,9 +720,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables); }, - makeTunnel: (forward: vscode.TunnelOptions) => { + openTunnel: (forward: vscode.TunnelOptions) => { checkProposedApiEnabled(extension); - return extHostTunnelService.makeTunnel(forward); + return extHostTunnelService.openTunnel(forward); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 70b54101e0..145a122eee 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -47,7 +47,8 @@ import { createExtHostContextProxyIdentifier as createExtId, createMainContextPr import * as search from 'vs/workbench/services/search/common/search'; import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; -import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; // {{SQL CARBON EDIT}} import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; @@ -778,7 +779,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; } @@ -1399,7 +1400,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/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 30369cd17e..f53736bd10 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -13,13 +13,14 @@ import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigu import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; import { isObject } from 'vs/base/common/types'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ILogService } from 'vs/platform/log/common/log'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; function lookUp(tree: any, key: string) { if (key) { @@ -149,14 +150,17 @@ export class ExtHostConfigProvider { this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); } - getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionDescription?: IExtensionDescription): vscode.WorkspaceConfiguration { const overrides = scopeToOverrides(scope) || {}; + if (overrides.overrideIdentifier && extensionDescription) { + checkProposedApiEnabled(extensionDescription); + } const config = this._toReadonlyValue(section ? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section) : this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace)); if (section) { - this._validateConfigurationAccess(section, overrides, extensionId); + this._validateConfigurationAccess(section, overrides, extensionDescription?.identifier); } function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null { @@ -179,7 +183,7 @@ export class ExtHostConfigProvider { return typeof lookUp(config, key) !== 'undefined'; }, get: (key: string, defaultValue?: T) => { - this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionId); + this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionDescription?.identifier); let result = lookUp(config, key); if (typeof result === 'undefined') { result = defaultValue; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 0f0c8dd931..6176711177 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -662,7 +662,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio value: { authority, options, - tunnelInformation: { detectedTunnels: result.detectedTunnels } + tunnelInformation: { environmentTunnels: result.environmentTunnels } } }; } catch (err) { diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 2432abfa13..8519b527f4 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -6,27 +6,20 @@ import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as vscode from 'vscode'; -import { RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { RemoteTunnel, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { IDisposable } from 'vs/base/common/lifecycle'; -export interface TunnelOptions { - remote: { port: number, host: string }; - localPort?: number; - name?: string; - closeable?: boolean; -} - export interface TunnelDto { - remote: { port: number, host: string }; + remoteAddress: { port: number, host: string }; localAddress: string; } export namespace TunnelDto { export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto { - return { remote: tunnel.remote, localAddress: tunnel.localAddress }; + return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress }; } export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto { - return { remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress }; + return { remoteAddress: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress }; } } @@ -37,7 +30,7 @@ export interface Tunnel extends vscode.Disposable { export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { readonly _serviceBrand: undefined; - makeTunnel(forward: TunnelOptions): Promise; + openTunnel(forward: TunnelOptions): Promise; setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise; } @@ -45,10 +38,10 @@ export const IExtHostTunnelService = createDecorator('IEx export class ExtHostTunnelService implements IExtHostTunnelService { _serviceBrand: undefined; - async makeTunnel(forward: TunnelOptions): Promise { + async openTunnel(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 6a5d456567..701db5e294 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -13,16 +13,17 @@ import { exec } from 'child_process'; import * as resources from 'vs/base/common/resources'; import * as fs from 'fs'; import { isLinux } from 'vs/base/common/platform'; -import { IExtHostTunnelService, TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { asPromise } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; +import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; class ExtensionTunnel implements vscode.Tunnel { private _onDispose: Emitter = new Emitter(); - onDispose: Event = this._onDispose.event; + onDidDispose: Event = this._onDispose.event; constructor( - public readonly remote: { port: number; host: string; }, + public readonly remoteAddress: { port: number; host: string; }, public readonly localAddress: string, private readonly _dispose: () => void) { } @@ -48,11 +49,11 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe this.registerCandidateFinder(); } } - async makeTunnel(forward: TunnelOptions): Promise { + async openTunnel(forward: TunnelOptions): Promise { 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); + const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => { + return this._proxy.$closeTunnel(tunnel.remoteAddress); }); this._register(disposableTunnel); return disposableTunnel; @@ -65,8 +66,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise { - if (provider && provider.forwardPort) { - this._forwardPortProvider = provider.forwardPort; + if (provider && provider.tunnelFactory) { + this._forwardPortProvider = provider.tunnelFactory; await this._proxy.$setTunnelProvider(); } else { this._forwardPortProvider = undefined; @@ -91,11 +92,11 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe const providedPort = this._forwardPortProvider!(tunnelOptions); if (providedPort !== undefined) { return asPromise(() => providedPort).then(tunnel => { - if (!this._extensionTunnels.has(tunnelOptions.remote.host)) { - this._extensionTunnels.set(tunnelOptions.remote.host, new Map()); + if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) { + this._extensionTunnels.set(tunnelOptions.remoteAddress.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._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, tunnel); + this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress))); return Promise.resolve(TunnelDto.fromApiTunnel(tunnel)); }); } @@ -104,12 +105,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 +151,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/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 17919e7177..1bfcf66ebc 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -16,7 +16,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; -import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; import { PanelPositionContext } from 'vs/workbench/common/panel'; @@ -151,7 +151,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // Panel Position this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService); - this.panelPositionContext.set(this.layoutService.getPanelPosition() === Position.RIGHT ? 'right' : 'bottom'); + this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition())); this.registerListeners(); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 34c8518d48..5a95a8b1df 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -14,7 +14,7 @@ import { pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; -import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, IWorkbenchLayoutService, positionFromString, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -58,6 +58,7 @@ enum Storage { PANEL_HIDDEN = 'workbench.panel.hidden', PANEL_POSITION = 'workbench.panel.location', PANEL_SIZE = 'workbench.panel.size', + PANEL_DIMENSION = 'workbench.panel.dimension', PANEL_LAST_NON_MAXIMIZED_WIDTH = 'workbench.panel.lastNonMaximizedWidth', PANEL_LAST_NON_MAXIMIZED_HEIGHT = 'workbench.panel.lastNonMaximizedHeight', @@ -178,8 +179,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi position: Position.BOTTOM, lastNonMaximizedWidth: 300, lastNonMaximizedHeight: 300, - panelToRestore: undefined as string | undefined, - restored: false + panelToRestore: undefined as string | undefined }, statusBar: { @@ -571,10 +571,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private updatePanelPosition() { const defaultPanelPosition = this.configurationService.getValue(Settings.PANEL_POSITION); - const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, undefined); + const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition); - this.state.panel.restored = panelPosition !== undefined; - this.state.panel.position = ((panelPosition || defaultPanelPosition) === 'right') ? Position.RIGHT : Position.BOTTOM; + this.state.panel.position = positionFromString(panelPosition || defaultPanelPosition); } registerPart(part: Part): void { @@ -667,15 +666,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } getMaximumEditorDimensions(): Dimension { + const isColumn = this.state.panel.position === Position.RIGHT || this.state.panel.position === Position.LEFT; const takenWidth = (this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) + (this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.minimumWidth : 0) + - (this.isVisible(Parts.PANEL_PART) && this.state.panel.position === Position.RIGHT ? this.panelPartView.minimumWidth : 0); + (this.isVisible(Parts.PANEL_PART) && isColumn ? this.panelPartView.minimumWidth : 0); const takenHeight = (this.isVisible(Parts.TITLEBAR_PART) ? this.titleBarPartView.minimumHeight : 0) + (this.isVisible(Parts.STATUSBAR_PART) ? this.statusBarPartView.minimumHeight : 0) + - (this.isVisible(Parts.PANEL_PART) && this.state.panel.position === Position.BOTTOM ? this.panelPartView.minimumHeight : 0); + (this.isVisible(Parts.PANEL_PART) && !isColumn ? this.panelPartView.minimumHeight : 0); const availableWidth = this.dimension.width - takenWidth; const availableHeight = this.dimension.height - takenHeight; @@ -899,6 +899,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL); + this.storageService.store(Storage.PANEL_DIMENSION, positionToString(this.state.panel.position), StorageScope.GLOBAL); const gridSize = grid.getViewSize(); this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); @@ -1202,26 +1203,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return this.state.panel.position; } - setPanelPosition(position: Position.BOTTOM | Position.RIGHT): void { + setPanelPosition(position: Position): void { if (this.state.panel.hidden) { this.setPanelHidden(false); } const panelPart = this.getPart(Parts.PANEL_PART); - const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right'; - const oldPositionValue = (this.state.panel.position === Position.BOTTOM) ? 'bottom' : 'right'; + const oldPositionValue = positionToString(this.state.panel.position); + const newPositionValue = positionToString(position); this.state.panel.position = position; - function positionToString(position: Position): string { - switch (position) { - case Position.LEFT: return 'left'; - case Position.RIGHT: return 'right'; - case Position.BOTTOM: return 'bottom'; - } - } - // Save panel position - this.storageService.store(Storage.PANEL_POSITION, positionToString(this.state.panel.position), StorageScope.WORKSPACE); + this.storageService.store(Storage.PANEL_POSITION, newPositionValue, StorageScope.WORKSPACE); // Adjust CSS const panelContainer = assertIsDefined(panelPart.getContainer()); @@ -1250,14 +1243,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (position === Position.BOTTOM) { this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : this.state.panel.lastNonMaximizedHeight, this.editorPartView, Direction.Down); - } else { + } else if (position === Position.RIGHT) { this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Right); + } else { + this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Left); } // Reset sidebar to original size before shifting the panel this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize); - this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); + this._onPanelPositionChange.fire(newPositionValue); } isWindowMaximized() { @@ -1275,13 +1270,26 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onMaximizeChange.fire(maximized); } + private arrangeEditorNodes(editorNode: ISerializedNode, panelNode: ISerializedNode, editorSectionWidth: number): ISerializedNode[] { + switch (this.state.panel.position) { + case Position.BOTTOM: + return [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }]; + case Position.RIGHT: + return [editorNode, panelNode]; + case Position.LEFT: + return [panelNode, editorNode]; + } + } + private createGridDescriptor(): ISerializedGrid { const workbenchDimensions = this.getClientArea(); const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))); - const panelSize = this.state.panel.restored ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)) : workbenchDimensions.height / 3; + const panelDimension = positionFromString(this.storageService.get(Storage.PANEL_DIMENSION, StorageScope.GLOBAL, 'bottom')); + const fallbackPanelSize = this.state.panel.position === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; + const panelSize = panelDimension === this.state.panel.position ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, fallbackPanelSize)) : fallbackPanelSize; const titleBarHeight = this.titleBarPartView.minimumHeight; const statusBarHeight = this.statusBarPartView.minimumHeight; @@ -1319,9 +1327,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi visible: !this.state.panel.hidden }; - const editorSectionNode: ISerializedNode[] = this.state.panel.position === Position.BOTTOM - ? [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }] - : [editorNode, panelNode]; + const editorSectionNode = this.arrangeEditorNodes(editorNode, panelNode, editorSectionWidth); const middleSection: ISerializedNode[] = this.state.sideBar.position === Position.LEFT ? [activityBarNode, sideBarNode, ...editorSectionNode] diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 9823fada24..23757f7786 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -63,11 +63,6 @@ export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEd return options; } -export interface IEditorPartOptionsChangeEvent { - oldPartOptions: IEditorPartOptions; - newPartOptions: IEditorPartOptions; -} - export interface IEditorOpeningEvent extends IEditorIdentifier { options?: IEditorOptions; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 108c34165e..dbcd594aea 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason, SaveContext } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason, SaveContext, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -31,7 +31,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index ec55711afd..54e29c13a9 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -12,11 +12,11 @@ import { contrastBorder, editorBackground } from 'vs/platform/theme/common/color import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IView, orthogonal, LayoutPriority, IViewSize, Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; -import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; import { values } from 'vs/base/common/map'; import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme'; import { distinct, coalesce } from 'vs/base/common/arrays'; -import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartOptionsChangeEvent, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor'; import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -110,9 +110,12 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private readonly _onDidMoveGroup = this._register(new Emitter()); readonly onDidMoveGroup = this._onDidMoveGroup.event; - private onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); - private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); - get onDidSizeConstraintsChange(): Event<{ width: number; height: number; } | undefined> { return Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); } + private readonly onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); + private readonly _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); + readonly onDidSizeConstraintsChange = Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); + + private readonly _onDidEditorPartOptionsChange = this._register(new Emitter()); + readonly onDidEditorPartOptionsChange = this._onDidEditorPartOptionsChange.event; //#endregion @@ -155,13 +158,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.registerListeners(); } - //#region IEditorGroupsAccessor - - private enforcedPartOptions: IEditorPartOptions[] = []; - - private readonly _onDidEditorPartOptionsChange: Emitter = this._register(new Emitter()); - readonly onDidEditorPartOptionsChange: Event = this._onDidEditorPartOptionsChange.event; - private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); } @@ -185,6 +181,10 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this._onDidEditorPartOptionsChange.fire({ oldPartOptions, newPartOptions }); } + //#region IEditorGroupsService + + private enforcedPartOptions: IEditorPartOptions[] = []; + get partOptions(): IEditorPartOptions { return this._partOptions; } @@ -199,10 +199,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro }); } - //#endregion - - //#region IEditorGroupsService - private _contentDimension!: Dimension; get contentDimension(): Dimension { return this._contentDimension; } diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 33c3a6621c..4eacec1347 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -37,6 +37,15 @@ border-left-width: 0; /* no border when editor area is hiden */ } +.monaco-workbench .part.panel.left { + border-right-width: 1px; + border-right-style: solid; +} + +.monaco-workbench.noeditorarea .part.panel.left { + border-right-width: 0; /* no border when editor area is hiden */ +} + .monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label { outline-offset: -2px; } @@ -121,3 +130,10 @@ .monaco-workbench .part.panel.right .title-actions .codicon-chevron-down { transform: rotate(-90deg); } + +/* Rotate icons when panel is on left */ +.monaco-workbench .part.panel.left .title-actions .codicon-split-horizontal, +.monaco-workbench .part.panel.left .title-actions .codicon-chevron-up, +.monaco-workbench .part.panel.left .title-actions .codicon-chevron-down { + transform: rotate(90deg); +} diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 7200d3b4b5..08f903869d 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -12,11 +12,12 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IActivity } from 'vs/workbench/common/activity'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; export class ClosePanelAction extends Action { @@ -88,42 +89,6 @@ class FocusPanelAction extends Action { } } -export class TogglePanelPositionAction extends Action { - - static readonly ID = 'workbench.action.togglePanelPosition'; - static readonly LABEL = nls.localize('toggledPanelPosition', "Toggle Panel Position"); - - static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move Panel Right"); - static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move Panel to Bottom"); - - private readonly toDispose = this._register(new DisposableStore()); - - constructor( - id: string, - label: string, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IEditorGroupsService editorGroupsService: IEditorGroupsService - ) { - super(id, label, layoutService.getPanelPosition() === Position.RIGHT ? 'move-panel-to-bottom' : 'move-panel-to-right'); - - const setClassAndLabel = () => { - const positionRight = this.layoutService.getPanelPosition() === Position.RIGHT; - this.class = positionRight ? 'move-panel-to-bottom' : 'move-panel-to-right'; - this.label = positionRight ? TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL : TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL; - }; - - this.toDispose.add(editorGroupsService.onDidLayout(() => setClassAndLabel())); - - setClassAndLabel(); - } - - run(): Promise { - const position = this.layoutService.getPanelPosition(); - - this.layoutService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM); - return Promise.resolve(); - } -} export class ToggleMaximizedPanelAction extends Action { @@ -160,6 +125,54 @@ export class ToggleMaximizedPanelAction extends Action { } } +const PositionPanelActionId = { + LEFT: 'workbench.action.positionPanelLeft', + RIGHT: 'workbench.action.positionPanelRight', + BOTTOM: 'workbench.action.positionPanelBottom', +}; + +interface PanelActionConfig { + id: string; + when: ContextKeyExpr; + alias: string; + label: string; + value: T; +} + +function createPositionPanelActionConfig(id: string, alias: string, label: string, position: Position): PanelActionConfig { + return { + id, + alias, + label, + value: position, + when: PanelPositionContext.notEqualsTo(positionToString(position)) + }; +} + +export const PositionPanelActionConfigs = [ + createPositionPanelActionConfig(PositionPanelActionId.LEFT, 'View: Panel Position Left', nls.localize('positionPanelLeft', 'Move Panel Left'), Position.LEFT), + createPositionPanelActionConfig(PositionPanelActionId.RIGHT, 'View: Panel Position Right', nls.localize('positionPanelRight', 'Move Panel Right'), Position.RIGHT), + createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, 'View: Panel Position Bottom', nls.localize('positionPanelBottom', 'Move Panel To Bottom'), Position.BOTTOM), +]; + +const positionByActionId = new Map(PositionPanelActionConfigs.map(config => [config.id, config.value])); + +export class SetPanelPositionAction extends Action { + constructor( + id: string, + label: string, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + ) { + super(id, label); + } + + run(): Promise { + const position = positionByActionId.get(this.id); + this.layoutService.setPanelPosition(position === undefined ? Position.BOTTOM : position); + return Promise.resolve(); + } +} + export class PanelActivityAction extends ActivityAction { constructor( @@ -247,7 +260,6 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelAc actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousPanelViewAction, PreviousPanelViewAction.ID, PreviousPanelViewAction.LABEL), 'View: Previous Panel View', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NextPanelViewAction, NextPanelViewAction.ID, NextPanelViewAction.LABEL), 'View: Next Panel View', nls.localize('view', "View")); @@ -262,22 +274,21 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 5 }); -MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_workbench_layout_move', - command: { - id: TogglePanelPositionAction.ID, - title: TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL - }, - when: PanelPositionContext.isEqualTo('bottom'), - order: 5 -}); +function registerPositionPanelActionById(config: PanelActionConfig) { + const { id, label, alias, when } = config; + // register the workbench action + actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SetPanelPositionAction, id, label), alias, nls.localize('view', "View"), when); + // register as a menu item + MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_workbench_layout_move', + command: { + id, + title: label + }, + when, + order: 5 + }); +} -MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_workbench_layout_move', - command: { - id: TogglePanelPositionAction.ID, - title: TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL - }, - when: PanelPositionContext.isEqualTo('right'), - order: 5 -}); +// register each position panel action +PositionPanelActionConfigs.forEach(registerPositionPanelActionById); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 09578e3730..efcf83a155 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -18,7 +18,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; +import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -52,7 +52,7 @@ export class PanelPart extends CompositePart implements IPanelService { //#region IView - readonly minimumWidth: number = 300; + readonly minimumWidth: number = 420; readonly maximumWidth: number = Number.POSITIVE_INFINITY; readonly minimumHeight: number = 77; readonly maximumHeight: number = Number.POSITIVE_INFINITY; @@ -122,7 +122,10 @@ export class PanelPart extends CompositePart implements IPanelService { getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), getContextMenuActions: () => [ - this.instantiationService.createInstance(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), + ...PositionPanelActionConfigs + // show the contextual menu item if it is not in that position + .filter(({ when }) => contextKeyService.contextMatchesRules(when)) + .map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)), this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) ], getDefaultCompositeId: () => Registry.as(PanelExtensions.Panels).getDefaultPanelId(), @@ -207,7 +210,9 @@ export class PanelPart extends CompositePart implements IPanelService { const container = assertIsDefined(this.getContainer()); container.style.backgroundColor = this.getColor(PANEL_BACKGROUND) || ''; - container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || ''; + const borderColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || ''; + container.style.borderLeftColor = borderColor; + container.style.borderRightColor = borderColor; const title = this.getTitleArea(); if (title) { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 654ea03e57..1fb8651a7a 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -36,8 +36,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { Component } from 'vs/workbench/common/component'; -import { Extensions as ViewletExtensions, ViewletRegistry } from 'vs/workbench/browser/viewlet'; -import { Extensions as PanelExtensions, PanelRegistry } from 'vs/workbench/browser/panel'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -234,7 +232,8 @@ export abstract class ViewPane extends Pane implements IView { } export interface IViewPaneContainerOptions extends IPaneViewOptions { - showHeaderInTitleWhenSingleView: boolean; + mergeViewWithContainerWhenSingleView: boolean; + donotShowContainerTitleWhenMergedWithContainer?: boolean; } interface IViewPaneItem { @@ -244,6 +243,7 @@ interface IViewPaneItem { export class ViewPaneContainer extends Component implements IViewPaneContainer { + private readonly viewContainer: ViewContainer; private lastFocusedPane: ViewPane | undefined; private paneItems: IViewPaneItem[] = []; private paneview?: PaneView; @@ -308,6 +308,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.options.dnd = new DefaultPaneDndController(); } + this.viewContainer = container; this.visibleViewsStorageId = `${id}.numberOfVisibleViews`; this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined); this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); @@ -345,15 +346,15 @@ 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 ? `${this.viewContainer.name}: ${paneItemTitle}` : this.viewContainer.name; } - return title; + return this.viewContainer.name; } private showContextMenu(event: StandardMouseEvent): void { @@ -402,7 +403,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getActions(): IAction[] { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { return this.paneItems[0].pane.getActions(); } @@ -410,7 +411,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getSecondaryActions(): IAction[] { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { return this.paneItems[0].pane.getSecondaryActions(); } @@ -418,7 +419,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 +460,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 +644,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 +669,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 +727,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 +736,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/views.ts b/src/vs/workbench/browser/parts/views/views.ts index ddb3220e6b..8bf4525295 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -655,8 +655,8 @@ export class ViewsService extends Disposable implements IViewsService { this.viewDisposable.forEach(disposable => disposable.dispose()); this.viewDisposable.clear(); })); - this._register(viewContainersRegistry.onDidRegister(viewContainer => this.onDidRegisterViewContainer(viewContainer))); - this._register(viewContainersRegistry.onDidDeregister(viewContainer => this.onDidDeregisterViewContainer(viewContainer))); + this._register(viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); + this._register(viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); this._register(toDisposable(() => { this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose()); this.viewDescriptorCollections.clear(); diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index bf11afe5af..60b0ba90bb 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/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 1ce62d9c64..89c6b44f3b 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -174,7 +174,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio }, 'workbench.panel.defaultLocation': { 'type': 'string', - 'enum': ['bottom', 'right'], + 'enum': ['left', 'bottom', 'right'], 'default': 'bottom', 'description': nls.localize('panelDefaultLocation', "Controls the default location of the panel (terminal, debug console, output, problems). It can either show at the bottom or on the right of the workbench.") }, @@ -207,24 +207,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio ], 'included': isMacintosh }, - 'workbench.settings.enableNaturalLanguageSearch': { - 'type': 'boolean', - 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."), - 'default': true, - 'scope': ConfigurationScope.WINDOW, - 'tags': ['usesOnlineServices'] - }, - 'workbench.settings.settingsSearchTocBehavior': { - 'type': 'string', - 'enum': ['hide', 'filter'], - 'enumDescriptions': [ - nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching."), - nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category."), - ], - 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), - 'default': 'filter', - 'scope': ConfigurationScope.WINDOW - }, 'workbench.settings.editor': { 'type': 'string', 'enum': ['ui', 'json'], @@ -235,12 +217,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/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 6113f3b28e..9e9165c1be 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -18,7 +18,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; -import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, IWorkbenchLayoutService, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, WillSaveStateReason, StorageScope } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -349,7 +349,7 @@ export class Workbench extends Layout { { id: Parts.ACTIVITYBAR_PART, role: 'navigation', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, { id: Parts.SIDEBAR_PART, role: 'complementary', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } }, - { id: Parts.PANEL_PART, role: 'complementary', classes: ['panel', this.state.panel.position === Position.BOTTOM ? 'bottom' : 'right'] }, + { id: Parts.PANEL_PART, role: 'complementary', classes: ['panel', positionToString(this.state.panel.position)] }, { id: Parts.STATUSBAR_PART, role: 'contentinfo', classes: ['statusbar'] } ].forEach(({ id, role, classes, options }) => { const partContainer = this.createPart(id, role, classes); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index f14fe73afe..a2ad8aca25 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1177,6 +1177,11 @@ export interface IEditorPartOptions extends IEditorPartConfiguration { iconTheme?: string; } +export interface IEditorPartOptionsChangeEvent { + oldPartOptions: IEditorPartOptions; + newPartOptions: IEditorPartOptions; +} + export enum SideBySideEditor { MASTER = 1, DETAILS = 2 diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index d139b554cd..a4217b1e2c 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -12,11 +12,12 @@ import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { values, keys } from 'vs/base/common/map'; +import { values, keys, getOrSet } from 'vs/base/common/map'; import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IAction } from 'vs/base/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { flatten } from 'vs/base/common/arrays'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; export const FocusedViewContext = new RawContextKey('focusedView', ''); @@ -31,16 +32,30 @@ export enum ViewContainerLocation { Panel } +export interface IViewContainerDescriptor { + + readonly id: string; + + readonly name: string; + + readonly viewOrderDelegate?: ViewOrderDelegate; + + readonly hideIfEmpty?: boolean; + + readonly extensionId?: ExtensionIdentifier; + +} + export interface IViewContainersRegistry { /** * An event that is triggerred when a view container is registered. */ - readonly onDidRegister: Event; + readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>; /** * An event that is triggerred when a view container is deregistered. */ - readonly onDidDeregister: Event; + readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>; /** * All registered view containers @@ -48,14 +63,15 @@ export interface IViewContainersRegistry { readonly all: ViewContainer[]; /** - * Registers a view container with given id - * No op if a view container is already registered with the given id. + * Registers a view container to given location. + * No op if a view container is already registered. * - * @param id of the view container. + * @param viewContainerDescriptor descriptor of view container + * @param location location of the view container * * @returns the registered ViewContainer. */ - registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer; + registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation): ViewContainer; /** * Deregisters the given view container @@ -69,6 +85,11 @@ export interface IViewContainersRegistry { * @returns the view container with given id. */ get(id: string): ViewContainer | undefined; + + /** + * Returns all view containers in the given location + */ + getViewContainers(location: ViewContainerLocation): ViewContainer[]; } interface ViewOrderDelegate { @@ -76,49 +97,68 @@ interface ViewOrderDelegate { } export class ViewContainer { - protected constructor(readonly id: string, readonly location: ViewContainerLocation, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier, readonly orderDelegate?: ViewOrderDelegate) { } + + protected constructor(private readonly descriptor: IViewContainerDescriptor) { } + + readonly id: string = this.descriptor.id; + readonly name: string = this.descriptor.name; + readonly hideIfEmpty: boolean = !!this.descriptor.hideIfEmpty; + readonly extensionId: ExtensionIdentifier | undefined = this.descriptor.extensionId; + readonly orderDelegate: ViewOrderDelegate | undefined = this.descriptor.viewOrderDelegate; } class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry { - private readonly _onDidRegister = this._register(new Emitter()); - readonly onDidRegister: Event = this._onDidRegister.event; + private readonly _onDidRegister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>()); + readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidRegister.event; - private readonly _onDidDeregister = this._register(new Emitter()); - readonly onDidDeregister: Event = this._onDidDeregister.event; + private readonly _onDidDeregister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>()); + readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidDeregister.event; - private viewContainers: Map = new Map(); + private viewContainers: Map = new Map(); get all(): ViewContainer[] { - return values(this.viewContainers); + return flatten(values(this.viewContainers)); } - registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer { - const existing = this.viewContainers.get(id); + registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, viewContainerLocation: ViewContainerLocation): ViewContainer { + const existing = this.get(viewContainerDescriptor.id); if (existing) { return existing; } const viewContainer = new class extends ViewContainer { constructor() { - super(id, location, !!hideIfEmpty, extensionId, viewOrderDelegate); + super(viewContainerDescriptor); } }; - this.viewContainers.set(id, viewContainer); - this._onDidRegister.fire(viewContainer); + const viewContainers = getOrSet(this.viewContainers, viewContainerLocation, []); + viewContainers.push(viewContainer); + this._onDidRegister.fire({ viewContainer, viewContainerLocation }); return viewContainer; } deregisterViewContainer(viewContainer: ViewContainer): void { - const existing = this.viewContainers.get(viewContainer.id); - if (existing) { - this.viewContainers.delete(viewContainer.id); - this._onDidDeregister.fire(viewContainer); + for (const viewContainerLocation of keys(this.viewContainers)) { + const viewContainers = this.viewContainers.get(viewContainerLocation)!; + const index = viewContainers?.indexOf(viewContainer); + if (index !== -1) { + viewContainers?.splice(index, 1); + if (viewContainers.length === 0) { + this.viewContainers.delete(viewContainerLocation); + } + this._onDidDeregister.fire({ viewContainer, viewContainerLocation }); + return; + } } } get(id: string): ViewContainer | undefined { - return this.viewContainers.get(id); + return this.all.filter(viewContainer => viewContainer.id === id)[0]; + } + + getViewContainers(location: ViewContainerLocation): ViewContainer[] { + return [...(this.viewContainers.get(location) || [])]; } } 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 d48d8f72d9..91974639e9 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.skip('BackupModelRestorer', () => { // {{SQL CARBON EDIT}} TODO @anthonydr 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); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index a12101098d..880bb9696f 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -83,7 +83,7 @@ class OpenDebugPanelAction extends TogglePanelAction { Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( DebugViewlet, VIEWLET_ID, - nls.localize('debugAndRun', "Debug and Run"), + VIEW_CONTAINER.name, 'codicon-debug-alt', 13 // {{SQL CARBON EDIT}} )); diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 43b5db1b9c..c0d461ae45 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -7,12 +7,13 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; import { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export abstract class AbstractDebugAction extends Action { @@ -60,7 +61,8 @@ export class ConfigureAction extends AbstractDebugAction { @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(id, label, 'debug-action codicon codicon-gear', debugService, keybindingService); this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); @@ -87,10 +89,26 @@ export class ConfigureAction extends AbstractDebugAction { return; } - const sideBySide = !!(event && (event.ctrlKey || event.metaKey)); const configurationManager = this.debugService.getConfigurationManager(); - if (configurationManager.selectedConfiguration.launch) { - return configurationManager.selectedConfiguration.launch.openConfigFile(sideBySide, false); + let launch: ILaunch | undefined; + if (configurationManager.selectedConfiguration.name) { + launch = configurationManager.selectedConfiguration.launch; + } else { + const launches = configurationManager.getLaunches().filter(l => !!l.workspace); + if (launches.length === 1) { + launch = launches[0]; + } else { + const picks = launches.map(l => ({ label: l.name, launch: l })); + const picked = await this.quickInputService.pick<{ label: string, launch: ILaunch }>(picks, { activeItem: picks[0], placeHolder: nls.localize('selectWorkspaceFolder', "Select a workspace folder to create a launch.json file in") }); + if (picked) { + launch = picked.launch; + } + } + } + + if (launch) { + const sideBySide = !!(event && (event.ctrlKey || event.metaKey)); + return launch.openConfigFile(sideBySide, false); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index e0217e892d..96383899ef 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/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index f3eb2ae5dd..c2d2247a31 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -25,9 +25,9 @@ import { IInstantiationService, createDecorator } from 'vs/platform/instantiatio import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Panel } from 'vs/workbench/browser/panel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { memoize } from 'vs/base/common/decorators'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IExpressionContainer, IExpression, IReplElementSource, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; @@ -89,8 +89,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati _serviceBrand: undefined; private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show - private static readonly REPL_INPUT_INITIAL_HEIGHT = 19; - private static readonly REPL_INPUT_MAX_HEIGHT = 170; + private static readonly REPL_INPUT_LINE_HEIGHT = 19; private history: HistoryNavigator; private tree!: WorkbenchAsyncDataTree; @@ -99,13 +98,14 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private replInput!: CodeEditorWidget; private replInputContainer!: HTMLElement; private dimension!: dom.Dimension; - private replInputHeight: number; + private replInputLineCount = 1; private model!: ITextModel; private historyNavigationEnablement!: IContextKey; private scopedInstantiationService!: IInstantiationService; private replElementsChangeListener: IDisposable | undefined; private styleElement: HTMLStyleElement | undefined; private completionItemProvider: IDisposable | undefined; + private modelChangeListener: IDisposable = Disposable.None; constructor( @IDebugService private readonly debugService: IDebugService, @@ -119,11 +119,11 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati @IContextMenuService private readonly contextMenuService: IContextMenuService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, - @IClipboardService private readonly clipboardService: IClipboardService + @IClipboardService private readonly clipboardService: IClipboardService, + @IEditorService private readonly editorService: IEditorService ) { super(REPL_ID, telemetryService, themeService, storageService); - this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); codeEditorService.registerDecorationType(DECORATION_KEY, {}); this.registerListeners(); @@ -147,7 +147,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati if (model) { const word = model.getWordAtPosition(position); const overwriteBefore = word ? word.word.length : 0; - const text = model.getLineContent(position.lineNumber); + const text = model.getValue(); const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined; const suggestions = await session.completions(frameId, text, position, overwriteBefore, token); @@ -181,6 +181,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati dispose(this.model); } else { this.model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:replinput`), true); + this.setMode(); this.replInput.setModel(this.model); this.updateInputDecoration(); this.refreshReplElements(true); @@ -191,6 +192,9 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.onDidFontChange(); } })); + this._register(this.editorService.onDidActiveEditorChange(() => { + this.setMode(); + })); } get isReadonly(): boolean { @@ -215,6 +219,21 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.tree.domFocus(); } + private setMode(): void { + if (!this.isVisible()) { + return; + } + + const activeEditor = this.editorService.activeTextEditorWidget; + if (isCodeEditor(activeEditor)) { + this.modelChangeListener.dispose(); + this.modelChangeListener = activeEditor.onDidChangeModelLanguage(() => this.setMode()); + if (activeEditor.hasModel()) { + this.model.setMode(activeEditor.getModel().getLanguageIdentifier()); + } + } + } + private onDidFontChange(): void { if (this.styleElement) { const debugConsole = this.configurationService.getValue('debug').console; @@ -303,8 +322,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati revealLastElement(this.tree); this.history.add(this.replInput.getValue()); this.replInput.setValue(''); - const shouldRelayout = this.replInputHeight > Repl.REPL_INPUT_INITIAL_HEIGHT; - this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; + const shouldRelayout = this.replInputLineCount > 1; + this.replInputLineCount = 1; if (shouldRelayout) { // Trigger a layout to shrink a potential multi line input this.layout(this.dimension); @@ -330,18 +349,19 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati layout(dimension: dom.Dimension): void { this.dimension = dimension; + const replInputHeight = Repl.REPL_INPUT_LINE_HEIGHT * this.replInputLineCount; if (this.tree) { const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight; - const treeHeight = dimension.height - this.replInputHeight; + const treeHeight = dimension.height - replInputHeight; this.tree.getHTMLElement().style.height = `${treeHeight}px`; this.tree.layout(treeHeight, dimension.width); if (lastElementVisible) { revealLastElement(this.tree); } } - this.replInputContainer.style.height = `${this.replInputHeight}px`; + this.replInputContainer.style.height = `${replInputHeight}px`; - this.replInput.layout({ width: dimension.width - 20, height: this.replInputHeight }); + this.replInput.layout({ width: dimension.width - 20, height: replInputHeight }); } focus(): void { @@ -466,16 +486,14 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); - this._register(this.replInput.onDidScrollChange(e => { - if (!e.scrollHeightChanged) { - return; - } - this.replInputHeight = Math.max(Repl.REPL_INPUT_INITIAL_HEIGHT, Math.min(Repl.REPL_INPUT_MAX_HEIGHT, e.scrollHeight, this.dimension.height)); - this.layout(this.dimension); - })); this._register(this.replInput.onDidChangeModelContent(() => { const model = this.replInput.getModel(); this.historyNavigationEnablement.set(!!model && model.getValue() === ''); + const lineCount = model ? Math.min(10, model.getLineCount()) : 1; + if (lineCount !== this.replInputLineCount) { + this.replInputLineCount = lineCount; + this.layout(this.dimension); + } })); // We add the input decoration only when the focus is in the input #61126 this._register(this.replInput.onDidFocusEditorText(() => this.updateInputDecoration())); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index e57986db8a..dfc87fd7a1 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -28,7 +28,7 @@ import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewCon import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.debug'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('debugAndRun', "Debug and Run") }, ViewContainerLocation.Sidebar); export const VARIABLES_VIEW_ID = 'workbench.debug.variablesView'; export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView'; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index d2948904fb..0b17b316ed 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -118,9 +118,12 @@ export class ExpressionContainer implements IExpressionContainer { try { const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count); return response && response.body && response.body.variables - ? distinct(response.body.variables.filter(v => !!v && isString(v.name)), (v: DebugProtocol.Variable) => v.name).map((v: DebugProtocol.Variable) => - new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type)) - : []; + ? distinct(response.body.variables.filter(v => !!v), v => v.name).map(v => { + if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') { + return new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type); + } + return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, { kind: 'virtual' }, undefined, false); + }) : []; } catch (e) { return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, { kind: 'virtual' }, undefined, false)]; } diff --git a/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts index fd13afb805..836eb3c720 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 Source EULA. 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'] + } + } +}); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 35c4bd3dd0..b273a387c4 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, VIEW_CONTAINER } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, @@ -79,7 +79,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( const viewletDescriptor = ViewletDescriptor.create( ExtensionsViewlet, VIEWLET_ID, - localize('extensions', "Extensions"), + VIEW_CONTAINER.name, 'codicon-extensions', 14 // {{SQL CARBON EDIT}} ); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index d3bdf653e3..dcbbe5b3ea 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -727,7 +727,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); if (this.extension) { - const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction)]; + const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction), this.instantiationService.createInstance(CopyExtensionIdAction)]; if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); } @@ -812,8 +812,8 @@ export class InstallAnotherVersionAction extends ExtensionAction { export class ExtensionInfoAction extends ExtensionAction { - static readonly ID = 'extensions.extensionInfo'; - static readonly LABEL = localize('extensionInfoAction', "Copy Extension Information"); + static readonly ID = 'workbench.extensions.action.copyExtension'; + static readonly LABEL = localize('workbench.extensions.action.copyExtension', "Copy"); constructor( @IClipboardService private readonly clipboardService: IClipboardService @@ -844,6 +844,30 @@ export class ExtensionInfoAction extends ExtensionAction { } } +export class CopyExtensionIdAction extends ExtensionAction { + + static readonly ID = 'workbench.extensions.action.copyExtensionId'; + static readonly LABEL = localize('workbench.extensions.action.copyExtensionId', "Copy Extension Id"); + + constructor( + @IClipboardService private readonly clipboardService: IClipboardService + ) { + super(CopyExtensionIdAction.ID, CopyExtensionIdAction.LABEL); + this.update(); + } + + update(): void { + this.enabled = !!this.extension; + } + + async run(): Promise { + if (!this.extension) { + return; + } + return this.clipboardService.writeText(this.extension.identifier.id); + } +} + export class ExtensionSettingsAction extends ExtensionAction { static readonly ID = 'extensions.extensionSettings'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index b6c400abb8..aaec474707 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -374,7 +374,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/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index d47c79679d..cc39f873e5 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -16,9 +16,10 @@ import { URI } from 'vs/base/common/uri'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { localize } from 'vs/nls'; export const VIEWLET_ID = 'workbench.view.extensions'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('extensions', "Extensions") }, ViewContainerLocation.Sidebar); export const EXTENSIONS_CONFIG = '.azuredatastudio/extensions.json'; diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index a266eabd8b..0d6568eb98 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; import { ITextFileService, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; -import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; +import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -25,7 +25,6 @@ import { timeout } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; // {{SQL CARBON EDIT}} import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; @@ -47,7 +46,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IHostService private readonly hostService: IHostService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @IExplorerService private readonly explorerService: IExplorerService ) { super(); @@ -111,7 +109,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut if (oldResource.toString() === resource.toString()) { reopenFileResource = newResource; // file got moved } else { - const index = this.getIndexOfPath(resource.path, oldResource.path, this.explorerService.shouldIgnoreCase(resource)); + const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive); + const index = this.getIndexOfPath(resource.path, oldResource.path, ignoreCase); reopenFileResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index d35a1226f2..e59a408143 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/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 74df5f20dc..47e5afc346 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"); @@ -876,7 +875,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); @@ -893,16 +891,14 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole throw new Error('Parent folder is readonly.'); } - const newStat = new NewExplorerItem(explorerService, folder, isFolder); - await folder.fetchChildren(fileService, explorerService); + const newStat = new NewExplorerItem(fileService, folder, isFolder); + const sortOrder = explorerService.sortOrder; + await folder.fetchChildren(sortOrder); 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) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 6486514eb1..02c7372d4f 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, VIEW_CONTAINER, 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'; @@ -77,7 +77,7 @@ class FileUriLabelContribution implements IWorkbenchContribution { Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( ExplorerViewlet, VIEWLET_ID, - nls.localize('explore', "Explorer"), + VIEW_CONTAINER.name, 'codicon-files', 10 // {{SQL CARBON EDIT}} )); @@ -397,8 +397,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/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index ff75033bd0..7415b42131 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -412,18 +412,18 @@ export class ExplorerView extends ViewPane { this._register(explorerNavigator); // Open when selecting via keyboard this._register(explorerNavigator.onDidOpenResource(async e => { - const element = e.element; + const selection = this.tree.getSelection(); // Do not react if the user is expanding selection via keyboard. // Check if the item was previously also selected, if yes the user is simply expanding / collapsing current selection #66589. const shiftDown = e.browserEvent instanceof KeyboardEvent && e.browserEvent.shiftKey; - if (element && !shiftDown) { - if (element.isDirectory || this.explorerService.isEditable(undefined)) { + if (selection.length === 1 && !shiftDown) { + if (selection[0].isDirectory || this.explorerService.isEditable(undefined)) { // Do not react if user is clicking on explorer items while some are being edited #70276 // Do not react if clicking on directories return; } this.telemetryService.publicLog2('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' }); - await this.editorService.openEditor({ resource: element.resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + await this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } })); @@ -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); })); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 04fc61497b..657144ae01 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -9,7 +9,7 @@ import * as glob from 'vs/base/common/glob'; import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { IFileService, FileKind, FileOperationError, FileOperationResult, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -90,12 +90,13 @@ export class ExplorerDataSource implements IAsyncDataSource { + const sortOrder = this.explorerService.sortOrder; + const promise = element.fetchChildren(sortOrder).then(undefined, e => { if (element instanceof ExplorerItem && element.isRoot) { if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { // Single folder create a dummy explorer item to show error - const placeholder = new ExplorerItem(element.resource, this.explorerService, undefined, false); + const placeholder = new ExplorerItem(element.resource, this.fileService, undefined, false); placeholder.isError = true; return [placeholder]; } else { @@ -921,17 +922,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Check for name collisions const targetNames = new Set(); + const caseSensitive = this.fileService.hasCapability(target.resource, FileSystemProviderCapabilities.PathCaseSensitive); if (targetStat.children) { - const ignoreCase = this.explorerService.shouldIgnoreCase(target.resource); targetStat.children.forEach(child => { - targetNames.add(ignoreCase ? child.name.toLowerCase() : child.name); + targetNames.add(caseSensitive ? child.name : child.name.toLowerCase()); }); } // Run add in sequence const addPromisesFactory: ITask>[] = []; await Promise.all(resources.map(async resource => { - if (targetNames.has(this.explorerService.shouldIgnoreCase(resource) ? basename(resource).toLowerCase() : basename(resource))) { + if (targetNames.has(caseSensitive ? basename(resource) : basename(resource).toLowerCase())) { const confirmationResult = await this.dialogService.confirm(getFileOverwriteConfirm(basename(resource))); if (!confirmationResult.confirmed) { return; diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 13e3f0332d..3f25a2180a 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -14,8 +14,8 @@ 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 { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources'; +import { SortOrder } from 'vs/workbench/contrib/files/common/files'; export class ExplorerModel implements IDisposable { @@ -25,10 +25,10 @@ export class ExplorerModel implements IDisposable { constructor( private readonly contextService: IWorkspaceContextService, - explorerService: IExplorerService + fileService: IFileService ) { const setRoots = () => this._roots = this.contextService.getWorkspace().folders - .map(folder => new ExplorerItem(folder.uri, explorerService, undefined, true, false, false, folder.name)); + .map(folder => new ExplorerItem(folder.uri, fileService, undefined, true, false, folder.name)); setRoots(); this._listener = this.contextService.onDidChangeWorkspaceFolders(() => { @@ -83,11 +83,10 @@ export class ExplorerItem { constructor( public resource: URI, - private readonly explorerService: IExplorerService, + private readonly fileService: IFileService, private _parent: ExplorerItem | undefined, private _isDirectory?: boolean, private _isSymbolicLink?: boolean, - private _isReadonly?: boolean, private _name: string = basenameOrAuthority(resource), private _mtime?: number, ) { @@ -112,7 +111,7 @@ export class ExplorerItem { } get isReadonly(): boolean { - return !!this._isReadonly; + return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } get mtime(): number | undefined { @@ -158,8 +157,8 @@ export class ExplorerItem { return this === this.root; } - static create(explorerService: IExplorerService, fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { - const stat = new ExplorerItem(raw.resource, explorerService, parent, raw.isDirectory, raw.isSymbolicLink, fileService.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime); + static create(fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { + const stat = new ExplorerItem(raw.resource, fileService, parent, raw.isDirectory, raw.isSymbolicLink, raw.name, raw.mtime); // Recursively add children if present if (stat.isDirectory) { @@ -174,7 +173,7 @@ export class ExplorerItem { // Recurse into children if (raw.children) { for (let i = 0, len = raw.children.length; i < len; i++) { - const child = ExplorerItem.create(explorerService, fileService, raw.children[i], stat, resolveTo); + const child = ExplorerItem.create(fileService, raw.children[i], stat, resolveTo); stat.addChild(child); } } @@ -208,7 +207,6 @@ export class ExplorerItem { local._mtime = disk.mtime; local._isDirectoryResolved = disk._isDirectoryResolved; local._isSymbolicLink = disk.isSymbolicLink; - local._isReadonly = disk.isReadonly; local.isError = disk.isError; // Merge Children if resolved @@ -259,14 +257,14 @@ export class ExplorerItem { return this.children.get(this.getPlatformAwareName(name)); } - async fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise { + async fetchChildren(sortOrder: SortOrder): Promise { 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 = sortOrder === SortOrder.Modified; try { - const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); - const resolved = ExplorerItem.create(explorerService, fileService, stat, this); + const stat = await this.fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); + const resolved = ExplorerItem.create(this.fileService, stat, this); ExplorerItem.mergeLocalWithDisk(resolved, this); } catch (e) { this.isError = true; @@ -306,7 +304,7 @@ export class ExplorerItem { } private getPlatformAwareName(name: string): string { - return this.explorerService.shouldIgnoreCase(this.resource) ? name.toLowerCase() : name; + return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.PathCaseSensitive) ? name : name.toLowerCase(); } /** @@ -356,7 +354,7 @@ export class ExplorerItem { find(resource: URI): ExplorerItem | null { // Return if path found // For performance reasons try to do the comparison as fast as possible - const ignoreCase = this.explorerService.shouldIgnoreCase(resource); + const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive); if (resource && this.resource.scheme === resource.scheme && equalsIgnoreCase(this.resource.authority, resource.authority) && (ignoreCase ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) { return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length, ignoreCase); @@ -397,7 +395,7 @@ export class ExplorerItem { } export class NewExplorerItem extends ExplorerItem { - constructor(explorerService: IExplorerService, parent: ExplorerItem, isDirectory: boolean) { - super(URI.file(''), explorerService, parent, isDirectory); + constructor(fileService: IFileService, parent: ExplorerItem, isDirectory: boolean) { + super(URI.file(''), fileService, parent, isDirectory); } } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index fe7d6d9870..cbff88967e 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -6,11 +6,11 @@ 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'; -import { dirname, hasToIgnoreCase } from 'vs/base/common/resources'; +import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; +import { dirname } from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -42,7 +42,6 @@ export class ExplorerService implements IExplorerService { private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; private contextProvider: IContextProvider | undefined; - private fileSystemProviderCaseSensitivity = new Map(); private model: ExplorerModel; constructor( @@ -55,25 +54,21 @@ export class ExplorerService implements IExplorerService { ) { this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); - this.model = new ExplorerModel(this.contextService, this); + this.model = new ExplorerModel(this.contextService, this.fileService); this.disposables.add(this.model); this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e))); this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e))); this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); - this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { - const provider = e.provider; - if (e.added && provider) { - const alreadyRegistered = this.fileSystemProviderCaseSensitivity.has(e.scheme); - const readCapability = () => this.fileSystemProviderCaseSensitivity.set(e.scheme, !!(provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive)); - readCapability(); - - if (alreadyRegistered) { - // A file system provider got re-registered, we should update all file stats since they might change (got read-only) - this.model.roots.forEach(r => r.forgetChildren()); - this._onDidChangeItem.fire({ recursive: true }); - } else { - this.disposables.add(provider.onDidChangeCapabilities(() => readCapability())); + this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(e => { + let affected = false; + this.model.roots.forEach(r => { + if (r.resource.scheme === e.scheme) { + affected = true; + r.forgetChildren(); } + }); + if (affected) { + this._onDidChangeItem.fire({ recursive: true }); } })); this.disposables.add(this.model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); @@ -131,15 +126,6 @@ export class ExplorerService implements IExplorerService { return fileEventsFilter; } - shouldIgnoreCase(resource: URI): boolean { - const caseSensitive = this.fileSystemProviderCaseSensitivity.get(resource.scheme); - if (typeof caseSensitive === 'undefined') { - return hasToIgnoreCase(resource); - } - - return !caseSensitive; - } - // IExplorerService methods findClosest(resource: URI): ExplorerItem | null { @@ -187,7 +173,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); @@ -200,7 +186,7 @@ export class ExplorerService implements IExplorerService { const stat = await this.fileService.resolve(rootUri, options); // Convert to model - const modelStat = ExplorerItem.create(this, this.fileService, stat, undefined, options.resolveTo); + const modelStat = ExplorerItem.create(this.fileService, stat, undefined, options.resolveTo); // Update Input with disk Stat ExplorerItem.mergeLocalWithDisk(modelStat, root); const item = root.find(resource); @@ -244,11 +230,11 @@ export class ExplorerService implements IExplorerService { const thenable: Promise = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata }); thenable.then(stat => { if (stat) { - const modelStat = ExplorerItem.create(this, this.fileService, stat, p.parent); + const modelStat = ExplorerItem.create(this.fileService, stat, p.parent); ExplorerItem.mergeLocalWithDisk(modelStat, p); } - const childElement = ExplorerItem.create(this, this.fileService, addedElement, p.parent); + const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent); // Make sure to remove any previous version of the file if any p.removeChild(childElement); p.addChild(childElement); @@ -361,7 +347,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 +373,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 } @@ -404,7 +390,7 @@ export class ExplorerService implements IExplorerService { } private onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): void { - const configSortOrder = configuration?.explorer?.sortOrder || 'default'; + const configSortOrder = configuration?.explorer?.sortOrder || SortOrder.Default; // {{SQL CARBON EDIT}} strict-null-checks? if (this._sortOrder !== configSortOrder) { const shouldRefresh = this._sortOrder !== undefined; this._sortOrder = configSortOrder; diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 1fbc80a5ba..12da3f0898 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -24,6 +24,7 @@ import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { once } from 'vs/base/common/functional'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { localize } from 'vs/nls'; /** * Explorer viewlet id. @@ -33,7 +34,7 @@ export const VIEWLET_ID = 'workbench.view.explorer'; /** * Explorer viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('explore', "Explorer") }, ViewContainerLocation.Sidebar); export interface IExplorerService { _serviceBrand: undefined; @@ -55,7 +56,6 @@ export interface IExplorerService { refresh(): void; setToCopy(stats: ExplorerItem[], cut: boolean): void; isCut(stat: ExplorerItem): boolean; - shouldIgnoreCase(resource: URI): boolean; /** * Selects and reveal the file element provided by the given resource if its found in the explorer. @@ -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()); 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 a57d9e6d9a..dd94acc80d 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/contrib/files/test/electron-browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts index dee30a5fea..935239a08d 100644 --- a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts @@ -10,18 +10,11 @@ import { join } from 'vs/base/common/path'; import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { toResource } from 'vs/base/test/common/utils'; -import { hasToIgnoreCase } from 'vs/base/common/resources'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; - -class MockExplorerService { - shouldIgnoreCase(resource: URI) { - return hasToIgnoreCase(resource); - } -} -const mockExplorerService = new MockExplorerService() as IExplorerService; +import { TestFileService } from 'vs/workbench/test/workbenchTestServices'; +const fileService = new TestFileService(); function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { - return new ExplorerItem(toResource.call(this, path), mockExplorerService, undefined, isFolder, false, false, name, mtime); + return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, false, name, mtime); } suite('Files - View Model', function () { @@ -252,19 +245,19 @@ suite('Files - View Model', function () { }); test('Merge Local with Disk', function () { - const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now()); - const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now()); + const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, 'to', Date.now()); + const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, 'to', Date.now()); // Merge Properties ExplorerItem.mergeLocalWithDisk(merge2, merge1); assert.strictEqual(merge1.mtime, merge2.mtime); // Merge Child when isDirectoryResolved=false is a no-op - merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now())); + merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, 'foo.html', Date.now())); ExplorerItem.mergeLocalWithDisk(merge2, merge1); // Merge Child with isDirectoryResolved=true - const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now()); + const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, 'foo.html', Date.now()); merge2.removeChild(child); merge2.addChild(child); (merge2)._isDirectoryResolved = true; diff --git a/src/vs/workbench/contrib/markers/browser/constants.ts b/src/vs/workbench/contrib/markers/browser/constants.ts index d7bdabeba9..63fd85eb77 100644 --- a/src/vs/workbench/contrib/markers/browser/constants.ts +++ b/src/vs/workbench/contrib/markers/browser/constants.ts @@ -7,6 +7,8 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export default { MARKERS_PANEL_ID: 'workbench.panel.markers', + MARKERS_PANEL_STORAGE_ID: 'workbench.panel.markers', + MARKERS_VIEW_ID: 'workbench.panel.markers.view', MARKER_COPY_ACTION_ID: 'problems.action.copy', MARKER_COPY_MESSAGE_ACTION_ID: 'problems.action.copyMessage', RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID: 'problems.action.copyRelatedInformationMessage', diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index ed52a509ee..3dc7d87b59 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -12,11 +12,11 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; import { Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; -import { MarkersPanel } from 'vs/workbench/contrib/markers/browser/markersPanel'; +import { MarkersView, getMarkersView } from 'vs/workbench/contrib/markers/browser/markersView'; import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions'; -import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; +import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor, PaneCompositePanel, TogglePanelAction } from 'vs/workbench/browser/panel'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleMarkersPanelAction, ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; +import { ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -29,6 +29,16 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false); @@ -41,8 +51,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.WinCtrl | KeyCode.Enter }, handler: (accessor, args: any) => { - const markersPanel = (accessor.get(IPanelService).getActivePanel()); - markersPanel.openFileAtElement(markersPanel.getFocusElement(), false, true, true); + const markersView = getMarkersView(accessor.get(IPanelService))!; + markersView.openFileAtElement(markersView.getFocusElement(), false, true, true); } }); @@ -62,10 +72,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: Constants.MarkerFocusContextKey, primary: KeyMod.CtrlCmd | KeyCode.US_DOT, handler: (accessor, args: any) => { - const markersPanel = (accessor.get(IPanelService).getActivePanel()); - const focusedElement = markersPanel.getFocusElement(); + const markersView = getMarkersView(accessor.get(IPanelService))!; + const focusedElement = markersView.getFocusElement(); if (focusedElement instanceof Marker) { - markersPanel.showQuickFixes(focusedElement); + markersView.showQuickFixes(focusedElement); } } }); @@ -91,11 +101,45 @@ Registry.as(Extensions.Configuration).registerConfigurat }); +// markers view container +const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_PANEL_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, ViewContainerLocation.Panel); +Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ + id: Constants.MARKERS_VIEW_ID, + name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, + canToggleVisibility: false, + ctorDescriptor: { ctor: MarkersView }, +}], VIEW_CONTAINER); + // markers panel +class MarkersPanel extends PaneCompositePanel { + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService) { + super(Constants.MARKERS_PANEL_ID, instantiationService.createInstance(ViewPaneContainer, Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }), + telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + } +} +class ToggleMarkersPanelAction extends TogglePanelAction { + + public static readonly ID = 'workbench.actions.view.problems'; + public static readonly LABEL = Messages.MARKERS_PANEL_TOGGLE_LABEL; + + constructor(id: string, label: string, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IPanelService panelService: IPanelService + ) { + super(id, label, Constants.MARKERS_PANEL_ID, panelService, layoutService); + } +} Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( MarkersPanel, Constants.MARKERS_PANEL_ID, - Messages.MARKERS_PANEL_TITLE_PROBLEMS, + VIEW_CONTAINER.name, 'markersPanel', 10, ToggleMarkersPanelAction.ID @@ -183,10 +227,9 @@ registerAction({ registerAction({ id: Constants.MARKERS_PANEL_SHOW_MULTILINE_MESSAGE, handler(accessor) { - const panelService = accessor.get(IPanelService); - const panel = panelService.getActivePanel(); - if (panel instanceof MarkersPanel) { - panel.markersViewModel.multiline = true; + const markersView = getMarkersView(accessor.get(IPanelService)); + if (markersView) { + markersView.markersViewModel.multiline = true; } }, title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' }, @@ -199,10 +242,9 @@ registerAction({ registerAction({ id: Constants.MARKERS_PANEL_SHOW_SINGLELINE_MESSAGE, handler(accessor) { - const panelService = accessor.get(IPanelService); - const panel = panelService.getActivePanel(); - if (panel instanceof MarkersPanel) { - panel.markersViewModel.multiline = false; + const markersView = getMarkersView(accessor.get(IPanelService)); + if (markersView) { + markersView.markersViewModel.multiline = false; } }, title: { value: localize('show singleline', "Show message in single line"), original: 'Problems: Show message in single line' }, @@ -214,9 +256,9 @@ registerAction({ }); async function copyMarker(panelService: IPanelService, clipboardService: IClipboardService) { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - const element = (activePanel).getFocusElement(); + const markersView = getMarkersView(panelService); + if (markersView) { + const element = markersView.getFocusElement(); if (element instanceof Marker) { await clipboardService.writeText(`${element}`); } @@ -224,9 +266,9 @@ async function copyMarker(panelService: IPanelService, clipboardService: IClipbo } async function copyMessage(panelService: IPanelService, clipboardService: IClipboardService) { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - const element = (activePanel).getFocusElement(); + const markersView = getMarkersView(panelService); + if (markersView) { + const element = markersView.getFocusElement(); if (element instanceof Marker) { await clipboardService.writeText(element.marker.message); } @@ -234,9 +276,9 @@ async function copyMessage(panelService: IPanelService, clipboardService: IClipb } async function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService) { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - const element = (activePanel).getFocusElement(); + const markersView = getMarkersView(panelService); + if (markersView) { + const element = markersView.getFocusElement(); if (element instanceof RelatedInformation) { await clipboardService.writeText(element.raw.message); } @@ -244,16 +286,16 @@ async function copyRelatedInformationMessage(panelService: IPanelService, clipbo } function focusProblemsView(panelService: IPanelService) { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - activePanel.focus(); + const markersView = getMarkersView(panelService); + if (markersView) { + markersView.focus(); } } -function focusProblemsFilter(panelService: IPanelService) { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - activePanel.focusFilter(); +function focusProblemsFilter(panelService: IPanelService): void { + const markersView = getMarkersView(panelService); + if (markersView) { + markersView.focusFilter(); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index ee3aac6c82..391483267a 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -17,7 +17,7 @@ import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; +import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts similarity index 86% rename from src/vs/workbench/contrib/markers/browser/markersPanel.ts rename to src/vs/workbench/contrib/markers/browser/markersView.ts index 49c53f6a22..667f219dad 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -9,12 +9,12 @@ import { URI } from 'vs/base/common/uri'; import * as dom from 'vs/base/browser/dom'; import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Panel } from 'vs/workbench/browser/panel'; +import { PaneCompositePanel } from 'vs/workbench/browser/panel'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; +import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; @@ -42,12 +42,22 @@ import { domEvent } from 'vs/base/browser/event'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarker } from 'vs/platform/markers/common/markers'; import { withUndefinedAsNull } from 'vs/base/common/types'; -import { MementoObject } from 'vs/workbench/common/memento'; +import { MementoObject, Memento } from 'vs/workbench/common/memento'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { KeyCode } from 'vs/base/common/keyCodes'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; + +export function getMarkersView(panelService: IPanelService): MarkersView | undefined { + const activePanel = panelService.getActivePanel(); + if (activePanel instanceof PaneCompositePanel) { + return activePanel.getViewPaneContainer().getView(Constants.MARKERS_VIEW_ID); + } + return undefined; +} function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator> { const markersIt = Iterator.fromArray(resourceMarkers.markers); @@ -61,7 +71,7 @@ function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterat } -export class MarkersPanel extends Panel implements IMarkerFilterController { +export class MarkersView extends ViewPane implements IMarkerFilterController { private lastSelectedRelativeTop: number = 0; private currentActiveResource: URI | null = null; @@ -69,11 +79,10 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private readonly rangeHighlightDecorations: RangeHighlightDecorations; private readonly filter: Filter; - private tree!: MarkersTree; - private filterActionBar!: ActionBar; - private messageBoxContainer!: HTMLElement; - private ariaLabelElement!: HTMLElement; - + private tree: MarkersTree | undefined; + private filterActionBar: ActionBar | undefined; + private messageBoxContainer: HTMLElement | undefined; + private ariaLabelElement: HTMLElement | undefined; private readonly collapseAllAction: IAction; private readonly filterAction: MarkersFilterAction; @@ -88,23 +97,25 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { readonly markersViewModel: MarkersViewModel; private isSmallLayout: boolean = false; + readonly onDidChangeVisibility = this.onDidChangeBodyVisibility; + constructor( + options: IViewPaneOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ITelemetryService telemetryService: ITelemetryService, - @IThemeService themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, + @ITelemetryService private readonly telemetryService: ITelemetryService, @IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService, - @IStorageService storageService: IStorageService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IContextMenuService contextMenuService: IContextMenuService, @IMenuService private readonly menuService: IMenuService, - @IKeybindingService private readonly keybindingService: IKeybindingService, + @IKeybindingService keybindingService: IKeybindingService, + @IStorageService storageService: IStorageService, ) { - super(Constants.MARKERS_PANEL_ID, telemetryService, themeService, storageService); + super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService); this.panelFoucusContextKey = Constants.MarkerPanelFocusContextKey.bindTo(contextKeyService); - this.panelState = this.getMemento(StorageScope.WORKSPACE); + this.panelState = new Memento(Constants.MARKERS_PANEL_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE); this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); this._register(this.markersViewModel.onDidChange(marker => this.onDidChangeViewState(marker))); this.setCurrentActiveEditor(); @@ -125,9 +136,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { })); } - public create(parent: HTMLElement): void { - super.create(parent); - + public renderBody(parent: HTMLElement): void { dom.addClass(parent, 'markers-panel'); @@ -152,35 +161,41 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } })); - this.filterActionBar.push(this.filterAction); - this.render(); + this.filterActionBar!.push(this.filterAction); + this.renderContent(); } public getTitle(): string { return Messages.MARKERS_PANEL_TITLE_PROBLEMS; } - public layout(dimension: dom.Dimension): void { + public layoutBody(height: number, width: number): void { const wasSmallLayout = this.isSmallLayout; - this.isSmallLayout = dimension.width < 600; + this.isSmallLayout = width < 600; if (this.isSmallLayout !== wasSmallLayout) { - this.updateTitleArea(); - dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + this.updateActions(); + if (this.filterActionBar) { + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + } } - const height = this.isSmallLayout ? dimension.height - 44 : dimension.height; - this.tree.layout(height, dimension.width); - this.messageBoxContainer.style.height = `${height}px`; - this.filterAction.layout(this.isSmallLayout ? dimension.width : dimension.width - 200); + const contentHeight = this.isSmallLayout ? height - 44 : height; + if (this.tree) { + this.tree.layout(contentHeight, width); + } + if (this.messageBoxContainer) { + this.messageBoxContainer.style.height = `${contentHeight}px`; + } + this.filterAction.layout(this.isSmallLayout ? width : width - 200); } public focus(): void { - if (this.tree.getHTMLElement() === document.activeElement) { + if (this.tree && this.tree.getHTMLElement() === document.activeElement) { return; } - if (this.isEmpty()) { + if (this.isEmpty() && this.messageBoxContainer) { this.messageBoxContainer.focus(); - } else { + } else if (this.tree) { this.tree.getHTMLElement().focus(); } } @@ -243,7 +258,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private refreshPanel(markerOrChange?: Marker | MarkerChangesEvent): void { - if (this.isVisible()) { + if (this.isVisible() && this.tree) { this.cachedFilterStats = undefined; if (markerOrChange) { @@ -277,6 +292,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private resetTree(): void { + if (!this.tree) { + return; + } let resourceMarkers: ResourceMarkers[] = []; if (this.filterAction.activeFile) { if (this.currentActiveResource) { @@ -294,11 +312,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private updateFilter() { this.cachedFilterStats = undefined; this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions(), this.filterAction.showWarnings, this.filterAction.showErrors, this.filterAction.showInfos); - this.tree.refilter(); + if (this.tree) { + this.tree.refilter(); + } this._onDidFilter.fire(); const { total, filtered } = this.getFilterStats(); - this.tree.toggleVisibility(total === 0 || filtered === 0); + if (this.tree) { + this.tree.toggleVisibility(total === 0 || filtered === 0); + } this.renderMessage(); } @@ -354,7 +376,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { }; this.tree = this._register(this.instantiationService.createInstance(MarkersTree, - 'MarkersPanel', + 'MarkersView', dom.append(parent, dom.$('.tree-container.show-file-icons')), virtualDelegate, renderers, @@ -412,7 +434,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { })); this._register(Event.any(this.tree.onDidChangeSelection, this.tree.onDidChangeFocus)(() => { - const elements = [...this.tree.getSelection(), ...this.tree.getFocus()]; + const elements = [...this.tree!.getSelection(), ...this.tree!.getFocus()]; for (const element of elements) { if (element instanceof Marker) { const viewModel = this.markersViewModel.getViewModel(element); @@ -425,11 +447,13 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private collapseAll(): void { - this.tree.collapseAll(); - this.tree.setSelection([]); - this.tree.setFocus([]); - this.tree.getHTMLElement().focus(); - this.tree.focusFirst(); + if (this.tree) { + this.tree.collapseAll(); + this.tree.setSelection([]); + this.tree.setFocus([]); + this.tree.getHTMLElement().focus(); + this.tree.focusFirst(); + } } private createListeners(): void { @@ -440,7 +464,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.onActiveEditorChanged(); } })); - this._register(this.tree.onDidChangeSelection(() => this.onSelected())); + if (this.tree) { + this._register(this.tree.onDidChangeSelection(() => this.onSelected())); + } this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { this.reportFilteringUsed(); if (event.activeFile) { @@ -499,9 +525,11 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private onSelected(): void { - let selection = this.tree.getSelection(); - if (selection && selection.length > 0) { - this.lastSelectedRelativeTop = this.tree.getRelativeTop(selection[0]) || 0; + if (this.tree) { + let selection = this.tree.getSelection(); + if (selection && selection.length > 0) { + this.lastSelectedRelativeTop = this.tree!.getRelativeTop(selection[0]) || 0; + } } } @@ -510,14 +538,19 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return total === 0 || filtered === 0; } - private render(): void { + private renderContent(): void { this.cachedFilterStats = undefined; this.resetTree(); - this.tree.toggleVisibility(this.isEmpty()); + if (this.tree) { + this.tree.toggleVisibility(this.isEmpty()); + } this.renderMessage(); } private renderMessage(): void { + if (!this.messageBoxContainer || !this.ariaLabelElement) { + return; + } dom.clearNode(this.messageBoxContainer); const { total, filtered } = this.getFilterStats(); @@ -567,19 +600,19 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { e.stopPropagation(); } }); - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); + this.ariaLabelElement!.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); } private renderNoProblemsMessageForActiveFile(container: HTMLElement) { const span = dom.append(container, dom.$('span')); span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT; - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT); + this.ariaLabelElement!.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT); } private renderNoProblemsMessage(container: HTMLElement) { const span = dom.append(container, dom.$('span')); span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); + this.ariaLabelElement!.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); } private clearFilters(): void { @@ -592,7 +625,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private autoReveal(focus: boolean = false): void { // No need to auto reveal if active file filter is on - if (this.filterAction.activeFile) { + if (this.filterAction.activeFile || !this.tree) { return; } let autoReveal = this.configurationService.getValue('problems.autoReveal'); @@ -625,11 +658,13 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private hasSelectedMarkerFor(resource: ResourceMarkers): boolean { - let selectedElement = this.tree.getSelection(); - if (selectedElement && selectedElement.length > 0) { - if (selectedElement[0] instanceof Marker) { - if (resource.resource.toString() === (selectedElement[0]).marker.resource.toString()) { - return true; + if (this.tree) { + let selectedElement = this.tree.getSelection(); + if (selectedElement && selectedElement.length > 0) { + if (selectedElement[0] instanceof Marker) { + if (resource.resource.toString() === (selectedElement[0]).marker.resource.toString()) { + return true; + } } } } @@ -638,13 +673,13 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private updateRangeHighlights() { this.rangeHighlightDecorations.removeHighlightRange(); - if (this.tree.getHTMLElement() === document.activeElement) { + if (this.tree && this.tree.getHTMLElement() === document.activeElement) { this.highlightCurrentSelectedMarkerRange(); } } private highlightCurrentSelectedMarkerRange() { - const selections = this.tree.getSelection(); + const selections = this.tree ? this.tree.getSelection() : []; if (selections.length !== 1) { return; @@ -680,7 +715,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { }, onHide: (wasCancelled?: boolean) => { if (wasCancelled) { - this.tree.domFocus(); + this.tree!.domFocus(); } } }); @@ -700,7 +735,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } } - const menu = this.menuService.createMenu(MenuId.ProblemsPanelContext, this.tree.contextKeyService); + const menu = this.menuService.createMenu(MenuId.ProblemsPanelContext, this.tree!.contextKeyService); const groups = menu.getActions(); menu.dispose(); @@ -715,7 +750,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } public getFocusElement() { - return this.tree.getFocus()[0]; + return this.tree ? this.tree.getFocus()[0] : undefined; } public getActionViewItem(action: IAction): IActionViewItem | undefined { @@ -738,13 +773,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private computeFilterStats(): { total: number; filtered: number; } { - const root = this.tree.getNode(); let filtered = 0; + if (this.tree) { + const root = this.tree.getNode(); - for (const resourceMarkerNode of root.children) { - for (const markerNode of resourceMarkerNode.children) { - if (resourceMarkerNode.visible && markerNode.visible) { - filtered++; + for (const resourceMarkerNode of root.children) { + for (const markerNode of resourceMarkerNode.children) { + if (resourceMarkerNode.visible && markerNode.visible) { + filtered++; + } } } } @@ -776,7 +813,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.telemetryService.publicLog('problems.filter', data); } - protected saveState(): void { + saveState(): void { this.panelState['filter'] = this.filterAction.filterText; this.panelState['filterHistory'] = this.filterAction.filterHistory; this.panelState['showErrors'] = this.filterAction.showErrors; diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts similarity index 96% rename from src/vs/workbench/contrib/markers/browser/markersPanelActions.ts rename to src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 4d9c062ab9..eaf6f64ce0 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -10,10 +10,8 @@ import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { TogglePanelAction } from 'vs/workbench/browser/panel'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IThemeService, registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; @@ -30,19 +28,6 @@ import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilte import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -export class ToggleMarkersPanelAction extends TogglePanelAction { - - public static readonly ID = 'workbench.actions.view.problems'; - public static readonly LABEL = Messages.MARKERS_PANEL_TOGGLE_LABEL; - - constructor(id: string, label: string, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IPanelService panelService: IPanelService - ) { - super(id, label, Constants.MARKERS_PANEL_ID, panelService, layoutService); - } -} - export class ShowProblemsPanelAction extends Action { public static readonly ID = 'workbench.action.problems.focus'; @@ -304,6 +289,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this.element.className = this.action.class || ''; this.createInput(this.element); this.createControls(this.element); + this.updateClass(); this.adjustInputBox(); } diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index 3ed82c8db4..79e66623a4 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { endsWith } from 'vs/base/common/strings'; @@ -22,6 +23,7 @@ import { IEditorInput } from 'vs/workbench/common/editor'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { FOLDER_SETTINGS_PATH, IPreferencesService, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences'; +import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); @@ -147,3 +149,27 @@ export class PreferencesContribution implements IWorkbenchContribution { dispose(this.settingsListener); } } + +const registry = Registry.as(Extensions.Configuration); +registry.registerConfiguration({ + 'properties': { + 'workbench.settings.enableNaturalLanguageSearch': { + 'type': 'boolean', + 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."), + 'default': true, + 'scope': ConfigurationScope.WINDOW, + 'tags': ['usesOnlineServices'] + }, + 'workbench.settings.settingsSearchTocBehavior': { + 'type': 'string', + 'enum': ['hide', 'filter'], + 'enumDescriptions': [ + nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching."), + nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category."), + ], + 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), + 'default': 'filter', + 'scope': ConfigurationScope.WINDOW + }, + } +}); diff --git a/src/vs/workbench/contrib/remote/browser/media/tunnelView.css b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css index 258a252ffb..6a680d9e40 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; -} diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index eb1d7dcef6..20e2fc0776 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -356,7 +356,7 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer { Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( RemoteViewlet, VIEWLET_ID, - nls.localize('remote.explorer', "Remote Explorer"), + VIEW_CONTAINER.name, 'codicon-remote-explorer', 4 )); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 56dcc4c267..273c05fb20 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 event, 75, true)(e => { if (e.element && (e.element.tunnelType === TunnelType.Add)) { - this.commandService.executeCommand(ForwardPortAction.ID); + this.commandService.executeCommand(ForwardPortAction.ID, 'inline add'); } })); this._register(this.remoteExplorerService.onDidChangeEditable(async e => { - 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"), @@ -595,31 +598,52 @@ 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]+)$/); + if (!matches) { + return undefined; + } + 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.tunnelModel.forward(arg.remote); + remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }); + } else if (arg) { + remoteExplorerService.setEditable(undefined, undefined, { + onFinish: (value, success) => { + let parsed: { host: string, port: number } | undefined; + if (success && (parsed = parseInput(value))) { + remoteExplorerService.forward({ host: parsed.host, port: parsed.port }); + } + remoteExplorerService.setEditable(undefined, undefined, null); + }, + validationMessage: validateInput, + placeholder: forwardPrompt + }); } else { const viewsService = accessor.get(IViewsService); + const quickInputService = accessor.get(IQuickInputService); await viewsService.openView(TunnelPanel.ID, true); - remoteExplorerService.setEditable(undefined, { - onFinish: (value, success) => { - if (success) { - remoteExplorerService.tunnelModel.forward(Number(value)); - } - remoteExplorerService.setEditable(undefined, null); - }, - validationMessage: (value) => { - const asNumber = Number(value); - if ((value === '') || isNaN(asNumber) || (asNumber < 0) || (asNumber > 65535)) { - return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); - } - return null; - }, - placeholder: nls.localize('remote.tunnelsView.forwardPortPlaceholder', "Port number") + 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 }); + } } }; } @@ -633,7 +657,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 +672,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 +693,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/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 299ecd2fdb..7553d0912e 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -20,31 +20,31 @@ import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExte export const VIEWLET_ID = 'workbench.view.remote'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( - VIEWLET_ID, - ViewContainerLocation.Sidebar, - true, - undefined, { - getOrder: (group?: string) => { - if (!group) { + id: VIEWLET_ID, + name: localize('remote.explorer', "Remote Explorer"), + hideIfEmpty: true, + viewOrderDelegate: { + getOrder: (group?: string) => { + if (!group) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + let matches = /^targets@(\d+)$/.exec(group); + if (matches) { + return -1000; + } + + matches = /^details(@(\d+))?$/.exec(group); + + if (matches) { + return -500; + } + return undefined; // {{SQL CARBON EDIT}} strict-null-checks } - - let matches = /^targets@(\d+)$/.exec(group); - if (matches) { - return -1000; - } - - matches = /^details(@(\d+))?$/.exec(group); - - if (matches) { - return -500; - } - - return undefined; // {{SQL CARBON EDIT}} strict-null-checks } - } -); + }, ViewContainerLocation.Sidebar); export class LabelContribution implements IWorkbenchContribution { constructor( diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 6811573515..79bd50725c 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -401,6 +401,11 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', markdownDescription: nls.localize('remote.downloadExtensionsLocally', "When enabled extensions are downloaded locally and installed on remote."), default: false + }, + 'remote.restoreForwardedPorts': { + type: 'boolean', + markdownDescription: nls.localize('remote.restoreForwardedPorts', "Restores the ports you forwarded in a workspace."), + default: false } } }); diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 156887639b..b2f4478c9b 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; -import { VIEWLET_ID, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; +import { VIEWLET_ID, VIEW_CONTAINER, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; @@ -41,7 +41,7 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( SCMViewlet, VIEWLET_ID, - localize('source control', "Source Control"), + VIEW_CONTAINER.name, 'codicon-source-control', 12 // {{SQL CARBON EDIT}} )); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index a43a915d88..14e8145edc 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/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 615cf4fd4f..de9e2a2add 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -11,9 +11,10 @@ import { Command } from 'vs/editor/common/modes'; import { ISequence } from 'vs/base/common/sequence'; import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { localize } from 'vs/nls'; export const VIEWLET_ID = 'workbench.view.scm'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('source control', "Source Control"), }, ViewContainerLocation.Sidebar); export interface IBaselineResourceProvider { getBaselineResource(resource: URI): Promise; diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index a80ec500aa..0e795f76f8 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -508,7 +508,7 @@ class ShowAllSymbolsAction extends Action { Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( SearchViewlet, VIEWLET_ID, - nls.localize('name', "Search"), + VIEW_CONTAINER.name, 'codicon-search', 1 )); diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts index a76dc6b3ad..50d3ab2617 100644 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.ts +++ b/src/vs/workbench/contrib/search/browser/searchViewlet.ts @@ -14,8 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { ViewletRegistry, Extensions, Viewlet } from 'vs/workbench/browser/viewlet'; +import { Viewlet } from 'vs/workbench/browser/viewlet'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -48,11 +47,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); - } - - getTitle(): string { - return Registry.as(Extensions.Viewlets).getViewlet(this.getId()).name; + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); } getSearchView(): SearchView | undefined { diff --git a/src/vs/workbench/contrib/webview/common/portMapping.ts b/src/vs/workbench/contrib/webview/common/portMapping.ts index 7541317f72..a3c4f12d46 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/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 2a699005e3..6df839d7ce 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -249,7 +249,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten nls.localize('window.reopenFolders.one', "Reopen the last active window."), nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.") ], - 'default': 'one', + 'default': 'all', 'scope': ConfigurationScope.APPLICATION, 'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.") }, diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 13921cafb2..8ff5d53b0f 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/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 284f50ed82..f0a73994c0 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -461,7 +461,7 @@ export class ConfigurationEditingService { if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); - if (configurationProperties[operation.key].scope !== ConfigurationScope.RESOURCE) { + if (!(configurationProperties[operation.key].scope === ConfigurationScope.RESOURCE || configurationProperties[operation.key].scope === ConfigurationScope.RESOURCE_LANGUAGE)) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation); } } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index d7a01f5aab..a7c5be58e6 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -718,7 +718,7 @@ suite.skip('WorkspaceService - Initialization', () => { // {{SQL CARBON EDIT}} s suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDIT}} skip suite - let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string, globalTasksFile: string; + let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string, globalTasksFile: string, workspaceService: WorkspaceService; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); suiteSetup(() => { @@ -745,6 +745,11 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI 'type': 'string', 'default': 'isSet', scope: ConfigurationScope.RESOURCE + }, + 'configurationService.folder.languageSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.RESOURCE_LANGUAGE } } }); @@ -767,7 +772,7 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService)); - const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); + workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IEnvironmentService, environmentService); @@ -794,7 +799,7 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI }); test('defaults', () => { - assert.deepEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet' } }); + assert.deepEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet', 'languageSetting': 'isSet' } }); }); test('globals override defaults', () => { @@ -1028,6 +1033,16 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI .then(() => assert.equal(testObject.getValue('tasks.service.testSetting'), 'value')); }); + test('update resource configuration', () => { + return testObject.updateValue('configurationService.folder.testSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) + .then(() => assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'value')); + }); + + test('update resource language configuration', () => { + return testObject.updateValue('configurationService.folder.languageSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) + .then(() => assert.equal(testObject.getValue('configurationService.folder.languageSetting'), 'value')); + }); + test('update application setting into workspace configuration in a workspace is not supported', () => { return testObject.updateValue('configurationService.folder.applicationSetting', 'workspaceValue', {}, ConfigurationTarget.WORKSPACE, true) .then(() => assert.fail('Should not be supported'), (e) => assert.equal(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION)); @@ -1122,6 +1137,11 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED 'type': 'string', 'default': 'isSet', scope: ConfigurationScope.RESOURCE + }, + 'configurationService.workspace.testLanguageSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.RESOURCE_LANGUAGE } } }); @@ -1299,6 +1319,26 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED }); }); + test('resource language setting in folder is read after it is registered later', () => { + fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testNewResourceLanguageSetting2": "workspaceFolderValue" }'); + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.testNewResourceLanguageSetting2': 'workspaceValue' } }], true) + .then(() => testObject.reloadConfiguration()) + .then(() => { + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.workspace.testNewResourceLanguageSetting2': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.RESOURCE_LANGUAGE + } + } + }); + assert.equal(testObject.getValue('configurationService.workspace.testNewResourceLanguageSetting2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); + }); + }); + test('machine overridable setting in folder is read after it is registered later', () => { fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testNewMachineOverridableSetting2": "workspaceFolderValue" }'); return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.testNewMachineOverridableSetting2': 'workspaceValue' } }], true) @@ -1504,6 +1544,12 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED .then(() => assert.equal(testObject.getValue('configurationService.workspace.testResourceSetting', { resource: workspace.folders[0].uri }), 'workspaceFolderValue')); }); + test('update resource language configuration in workspace folder', () => { + const workspace = workspaceContextService.getWorkspace(); + return testObject.updateValue('configurationService.workspace.testLanguageSetting', 'workspaceFolderValue', { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) + .then(() => assert.equal(testObject.getValue('configurationService.workspace.testLanguageSetting', { resource: workspace.folders[0].uri }), 'workspaceFolderValue')); + }); + test('update workspace folder configuration should trigger change event before promise is resolve', () => { const workspace = workspaceContextService.getWorkspace(); const target = sinon.spy(); diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 061a27da53..48d937b026 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; import { IEditorOptions, ITextEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; @@ -344,6 +344,11 @@ export interface IEditorGroupsService { */ readonly partOptions: IEditorPartOptions; + /** + * An event that notifies when editor part options change. + */ + readonly onDidEditorPartOptionsChange: Event; + /** * Enforce editor part options temporarily. */ diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 1a83f08241..55d86a2a9d 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -478,7 +478,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten // set the resolved authority this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority.authority, resolvedAuthority.options); - this._remoteExplorerService.addDetected(resolvedAuthority.tunnelInformation?.detectedTunnels); + this._remoteExplorerService.addEnvironmentTunnels(resolvedAuthority.tunnelInformation?.environmentTunnels); // monitor for breakage const connection = this._remoteAgentService.getConnection(); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 39d37c14ef..9c531193ca 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -27,6 +27,26 @@ export const enum Position { BOTTOM } +export function positionToString(position: Position): string { + switch (position) { + case Position.LEFT: return 'left'; + case Position.RIGHT: return 'right'; + case Position.BOTTOM: return 'bottom'; + } + + return 'bottom'; +} + +const positionsByString: { [key: string]: Position } = { + [positionToString(Position.LEFT)]: Position.LEFT, + [positionToString(Position.RIGHT)]: Position.RIGHT, + [positionToString(Position.BOTTOM)]: Position.BOTTOM +}; + +export function positionFromString(str: string): Position { + return positionsByString[str]; +} + export interface IWorkbenchLayoutService extends ILayoutService { _serviceBrand: undefined; diff --git a/src/vs/workbench/services/progress/browser/media/progressService.css b/src/vs/workbench/services/progress/browser/media/progressService.css index d86980fa1e..69d2202c42 100644 --- a/src/vs/workbench/services/progress/browser/media/progressService.css +++ b/src/vs/workbench/services/progress/browser/media/progressService.css @@ -17,8 +17,11 @@ width: 14px; height: 14px; position: absolute; - top: 1px; - left: 1px; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: auto; background-color: currentColor; content: ''; } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index da5c672aa5..9b81c343ce 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -13,42 +13,55 @@ import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/e import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const IRemoteExplorerService = createDecorator('remoteExplorerService'); export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; +const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; 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 + @ITunnelService private readonly tunnelService: ITunnelService, + @IStorageService private readonly storageService: IStorageService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); this.forwarded = new Map(); 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 +69,105 @@ 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.storeForwarded(); } - 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.storeForwarded(); + this._onClosePort.fire(address); } })); + + this.restoreForwarded(); } - async forward(remote: number, local?: number, name?: string): Promise { - if (!this.forwarded.has(remote)) { - const tunnel = await this.tunnelService.openTunnel(remote, local); + private async restoreForwarded() { + if (this.configurationService.getValue('remote.restoreForwardedPorts')) { + const tunnelsString = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE); + if (tunnelsString) { + (JSON.parse(tunnelsString))?.forEach(tunnel => { + this.forward({ host: tunnel.remoteHost, port: tunnel.remotePort }, tunnel.localPort, tunnel.name); + }); + } + } + } + + private storeForwarded() { + if (this.configurationService.getValue('remote.restoreForwardedPorts')) { + this.storageService.store(TUNNELS_TO_RESTORE, JSON.stringify(Array.from(this.forwarded.values())), StorageScope.WORKSPACE); + } + } + + 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.storeForwarded(); + 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 { + addEnvironmentTunnels(tunnels: { remoteAddress: { 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.remoteAddress.host, tunnel.remoteAddress.port), { + remoteHost: tunnel.remoteAddress.host, + remotePort: tunnel.remoteAddress.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 +181,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; - addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void; - registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void; + 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; + addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[] | undefined): void; + registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void; } export interface HelpInformation { @@ -189,14 +232,16 @@ 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, - @ITunnelService tunnelService: ITunnelService) { - this._tunnelModel = new TunnelModel(tunnelService); + @ITunnelService tunnelService: ITunnelService, + @IConfigurationService configurationService: IConfigurationService + ) { + this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService); remoteHelpExtPoint.setHandler((extensions) => { let helpInformation: HelpInformation[] = []; for (let extension of extensions) { @@ -246,34 +291,35 @@ 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 { + addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[] | undefined): void { if (tunnels) { - this.tunnelModel.addDetected(tunnels); + this.tunnelModel.addEnvironmentTunnels(tunnels); } } - 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 1ad9588492..12988d07f4 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({ remoteAddress: { 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; } } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 4a2ffdee5c..0997bbd794 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -18,6 +18,7 @@ import { Event } from 'vs/base/common/event'; import { relative } from 'vs/base/common/path'; import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { localize } from 'vs/nls'; export const VIEWLET_ID = 'workbench.view.search'; export const PANEL_ID = 'workbench.view.search'; @@ -25,7 +26,7 @@ export const VIEW_ID = 'workbench.view.search'; /** * Search viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar, true); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('name', "Search"), hideIfEmpty: true }, ViewContainerLocation.Sidebar); export const ISearchService = createDecorator('searchService'); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index afe68058c4..4bd0c15a52 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 89667215c4..88ffb01046 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/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index 0a325988c8..6036c87139 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -15,7 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import sinon = require('sinon'); -const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer('test', ViewContainerLocation.Sidebar); +const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test', name: 'test' }, ViewContainerLocation.Sidebar); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { diff --git a/src/vs/workbench/test/browser/quickopen.test.ts b/src/vs/workbench/test/browser/quickopen.test.ts index d08e23984d..730b59a072 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 8f377996b9..c3d8a14ba9 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 b6b5862fdd..7b8ca58333 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 6835a2fa44..b829e9232a 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 { @@ -690,6 +689,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { onDidMoveGroup: Event = Event.None; onDidGroupIndexChange: Event = Event.None; onDidLayout: Event = Event.None; + onDidEditorPartOptionsChange = Event.None; orientation: any; whenRestored: Promise = Promise.resolve(undefined); @@ -967,6 +967,7 @@ export class TestFileService implements IFileService { private readonly _onAfterOperation: Emitter; readonly onWillActivateFileSystemProvider = Event.None; + readonly onDidChangeFileSystemProviderCapabilities = Event.None; readonly onError: Event = Event.None; private content = 'Hello Html'; @@ -1021,12 +1022,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 +1074,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 +1087,7 @@ export class TestFileService implements IFileService { isDirectory: false, isSymbolicLink: false, name: resources.basename(resource) - })); + }); } move(_source: URI, _target: URI, _overwrite?: boolean): Promise { @@ -1120,7 +1124,13 @@ export class TestFileService implements IFileService { return resource.scheme === 'file' || this.providers.has(resource.scheme); } - hasCapability(resource: URI, capability: FileSystemProviderCapabilities): boolean { return false; } + hasCapability(resource: URI, capability: FileSystemProviderCapabilities): boolean { + if (capability === FileSystemProviderCapabilities.PathCaseSensitive && isLinux) { + return true; + } + + return false; + } del(_resource: URI, _options?: { useTrash?: boolean, recursive?: boolean }): Promise { return Promise.resolve(); @@ -1153,14 +1163,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 { diff --git a/yarn.lock b/yarn.lock index 1274034ef7..924e219f0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -270,6 +270,11 @@ dependencies: source-map "^0.6.1" +"@types/vscode-windows-registry@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/vscode-windows-registry/-/vscode-windows-registry-1.0.0.tgz#333eea7fd5743fa4c99dff13af16de2c08a586d0" + integrity sha512-gyq9tIMbxry5GL2gY7J30E6R3EUx0cAin/k3wfsQez4C5uDWVJmJw142x6KFXtYX7xYQL/IXmm4cRqi4ghg05A== + "@types/webpack@^4.4.10": version "4.4.10" resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.10.tgz#2ecf12589142bc531549140612815b7d8b076358"