Merge from vscode e74405d11443c5361c31e2bc341866d146eee206 (#8740)

This commit is contained in:
Anthony Dresser
2019-12-18 23:36:29 -08:00
committed by GitHub
parent 48dcb7258e
commit 099916bf19
109 changed files with 1327 additions and 910 deletions
+8
View File
@@ -58,6 +58,10 @@
"name": "vs/workbench/contrib/emmet", "name": "vs/workbench/contrib/emmet",
"project": "vscode-workbench" "project": "vscode-workbench"
}, },
{
"name": "vs/workbench/contrib/experiments",
"project": "vscode-workbench"
},
{ {
"name": "vs/workbench/contrib/extensions", "name": "vs/workbench/contrib/extensions",
"project": "vscode-workbench" "project": "vscode-workbench"
@@ -266,6 +270,10 @@
"name": "vs/workbench/services/remote", "name": "vs/workbench/services/remote",
"project": "vscode-workbench" "project": "vscode-workbench"
}, },
{
"name": "vs/workbench/services/search",
"project": "vscode-workbench"
},
{ {
"name": "vs/workbench/services/textfile", "name": "vs/workbench/services/textfile",
"project": "vscode-workbench" "project": "vscode-workbench"
+1
View File
@@ -100,6 +100,7 @@
"@types/plotly.js": "^1.44.9", "@types/plotly.js": "^1.44.9",
"@types/sanitize-html": "^1.18.2", "@types/sanitize-html": "^1.18.2",
"@types/sinon": "^1.16.36", "@types/sinon": "^1.16.36",
"@types/vscode-windows-registry": "^1.0.0",
"@types/webpack": "^4.4.10", "@types/webpack": "^4.4.10",
"@types/windows-foreground-love": "^0.3.0", "@types/windows-foreground-love": "^0.3.0",
"@types/windows-mutex": "^0.4.0", "@types/windows-mutex": "^0.4.0",
@@ -46,7 +46,7 @@ export class OpenDataExplorerViewletAction extends ShowViewletAction {
} }
} }
export const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('dataexplorer.name', "Connections") }, ViewContainerLocation.Sidebar);
export class DataExplorerViewletViewsContribution implements IWorkbenchContribution { export class DataExplorerViewletViewsContribution implements IWorkbenchContribution {
@@ -106,7 +106,7 @@ export class DataExplorerViewPaneContainer extends ViewPaneContainer {
@IMenuService private menuService: IMenuService, @IMenuService private menuService: IMenuService,
@IContextKeyService private contextKeyService: IContextKeyService @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 { create(parent: HTMLElement): void {
-9
View File
@@ -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;
}
@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { DataSource } from 'vs/base/parts/quickopen/browser/quickOpenViewer'; import { DataSource } from 'vs/base/parts/quickopen/browser/quickOpenViewer';
@@ -28,7 +29,7 @@ suite('QuickOpen', () => {
assert.equal(entry2, model.getEntries(true)[0]); assert.equal(entry2, model.getEntries(true)[0]);
}); });
test('QuickOpenDataSource', () => { test('QuickOpenDataSource', async () => {
const model = new QuickOpenModel(); const model = new QuickOpenModel();
const entry1 = new QuickOpenEntry(); const entry1 = new QuickOpenEntry();
@@ -42,8 +43,7 @@ suite('QuickOpen', () => {
assert.equal(true, ds.hasChildren(null!, model)); assert.equal(true, ds.hasChildren(null!, model));
assert.equal(false, ds.hasChildren(null!, entry1)); assert.equal(false, ds.hasChildren(null!, entry1));
ds.getChildren(null!, model).then((children: any[]) => { const children = await ds.getChildren(null!, model);
assert.equal(3, children.length); assert.equal(3, children.length);
}); });
}); });
});
@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
+1
View File
@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import { ok } from 'vs/base/common/assert'; import { ok } from 'vs/base/common/assert';
@@ -6,7 +6,6 @@
import * as assert from 'assert'; import * as assert from 'assert';
import * as collections from 'vs/base/common/collections'; import * as collections from 'vs/base/common/collections';
suite('Collections', () => { suite('Collections', () => {
test('forEach', () => { test('forEach', () => {
+1 -1
View File
@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import * as extpath from 'vs/base/common/extpath'; import * as extpath from 'vs/base/common/extpath';
import * as platform from 'vs/base/common/platform'; 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('http://www/'), 'http://www/');
assert.equal(extpath.getRoot('file:///foo'), 'file:///'); assert.equal(extpath.getRoot('file:///foo'), 'file:///');
assert.equal(extpath.getRoot('file://foo'), ''); assert.equal(extpath.getRoot('file://foo'), '');
}); });
test('isUNC', () => { test('isUNC', () => {
+1
View File
@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import { guessMimeTypes, registerTextMime, suggestFilename } from 'vs/base/common/mime'; import { guessMimeTypes, registerTextMime, suggestFilename } from 'vs/base/common/mime';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
+2
View File
@@ -2,10 +2,12 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import * as types from 'vs/base/common/types'; import * as types from 'vs/base/common/types';
suite('Types', () => { suite('Types', () => {
test('isFunction', () => { test('isFunction', () => {
assert(!types.isFunction(undefined)); assert(!types.isFunction(undefined));
assert(!types.isFunction(null)); assert(!types.isFunction(null));
@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import * as os from 'os'; import * as os from 'os';
import * as path from 'vs/base/common/path'; import * as path from 'vs/base/common/path';
@@ -592,10 +592,10 @@ class SemanticColoringProviderStyling {
public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number { public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number {
const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet); const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet);
let metadata: number | undefined;
if (entry) { if (entry) {
return entry.metadata; metadata = entry.metadata;
} } else {
const tokenType = this._legend.tokenTypes[tokenTypeIndex]; const tokenType = this._legend.tokenTypes[tokenTypeIndex];
const tokenModifiers: string[] = []; const tokenModifiers: string[] = [];
for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) {
@@ -605,17 +605,21 @@ class SemanticColoringProviderStyling {
tokenModifierSet = tokenModifierSet >> 1; tokenModifierSet = tokenModifierSet >> 1;
} }
let metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers);
if (typeof metadata === 'undefined') { if (typeof metadata === 'undefined') {
metadata = Constants.NO_STYLING; metadata = Constants.NO_STYLING;
} }
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)}`);
}
this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata);
}
if (this._logService.getLevel() === LogLevel.Trace) {
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)}`);
}
return metadata; return metadata;
} }
} }
const enum SemanticColoringConstants { const enum SemanticColoringConstants {
@@ -44,16 +44,16 @@ suite('BackupMainService', () => {
this.workspacesJsonPath = backupWorkspacesPath; this.workspacesJsonPath = backupWorkspacesPath;
} }
public toBackupPath(arg: URI | string): string { toBackupPath(arg: URI | string): string {
const id = arg instanceof URI ? super.getFolderHash(arg) : arg; const id = arg instanceof URI ? super.getFolderHash(arg) : arg;
return path.join(this.backupHome, id); return path.join(this.backupHome, id);
} }
public getFolderHash(folderUri: URI): string { getFolderHash(folderUri: URI): string {
return super.getFolderHash(folderUri); return super.getFolderHash(folderUri);
} }
public toLegacyBackupPath(folderPath: string): string { toLegacyBackupPath(folderPath: string): string {
return path.join(this.backupHome, super.getLegacyFolderHash(folderPath)); return path.join(this.backupHome, super.getLegacyFolderHash(folderPath));
} }
} }
@@ -119,18 +119,17 @@ suite('BackupMainService', () => {
let service: TestBackupMainService; let service: TestBackupMainService;
let configService: TestConfigurationService; let configService: TestConfigurationService;
setup(() => { setup(async () => {
// Delete any existing backups completely and then re-create it. // Delete any existing backups completely and then re-create it.
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE).then(() => { await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
return pfs.mkdirp(backupHome); await pfs.mkdirp(backupHome);
}).then(() => {
configService = new TestConfigurationService(); configService = new TestConfigurationService();
service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService); service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService);
return service.initialize(); return service.initialize();
}); });
});
teardown(() => { teardown(() => {
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
@@ -591,72 +590,72 @@ 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())); service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase()));
assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]); assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = <IBackupWorkspacesFormat>JSON.parse(buffer); const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]); 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(); const upperFooPath = fooFile.fsPath.toUpperCase();
service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath)); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath));
assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [URI.file(upperFooPath)]); assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [URI.file(upperFooPath)]);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer); const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]); assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]);
}); });
});
suite('removeBackupPathSync', () => { 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(fooFile);
service.registerFolderBackupSync(barFile); service.registerFolderBackupSync(barFile);
service.unregisterFolderBackupSync(fooFile); service.unregisterFolderBackupSync(fooFile);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer); const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
assert.deepEqual(json.folderURIWorkspaces, [barFile.toString()]); assert.deepEqual(json.folderURIWorkspaces, [barFile.toString()]);
service.unregisterFolderBackupSync(barFile); service.unregisterFolderBackupSync(barFile);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content); const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
assert.deepEqual(json2.folderURIWorkspaces, []); 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); const ws1 = toWorkspaceBackupInfo(fooFile.fsPath);
service.registerWorkspaceBackupSync(ws1); service.registerWorkspaceBackupSync(ws1);
const ws2 = toWorkspaceBackupInfo(barFile.fsPath); const ws2 = toWorkspaceBackupInfo(barFile.fsPath);
service.registerWorkspaceBackupSync(ws2); service.registerWorkspaceBackupSync(ws2);
service.unregisterWorkspaceBackupSync(ws1.workspace); service.unregisterWorkspaceBackupSync(ws1.workspace);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer); const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]); assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]);
service.unregisterWorkspaceBackupSync(ws2.workspace); service.unregisterWorkspaceBackupSync(ws2.workspace);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content); const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
assert.deepEqual(json2.rootURIWorkspaces, []); 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('foo');
service.registerEmptyWindowBackupSync('bar'); service.registerEmptyWindowBackupSync('bar');
service.unregisterEmptyWindowBackupSync('foo'); service.unregisterEmptyWindowBackupSync('foo');
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer); const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
assert.deepEqual(json.emptyWorkspaces, ['bar']); assert.deepEqual(json.emptyWorkspaces, ['bar']);
service.unregisterEmptyWindowBackupSync('bar'); service.unregisterEmptyWindowBackupSync('bar');
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content); const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
assert.deepEqual(json2.emptyWorkspaces, []); assert.deepEqual(json2.emptyWorkspaces, []);
}); });
});
});
test('should fail gracefully when removing a path that doesn\'t exist', async () => { test('should fail gracefully when removing a path that doesn\'t exist', async () => {
+9 -5
View File
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; 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 { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { isAbsolutePath, dirname, basename, joinPath, isEqual, isEqualOrParent } from 'vs/base/common/resources'; 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 //#region File System Provider
private _onDidChangeFileSystemProviderRegistrations: Emitter<IFileSystemProviderRegistrationEvent> = this._register(new Emitter<IFileSystemProviderRegistrationEvent>()); private _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter<IFileSystemProviderRegistrationEvent>());
readonly onDidChangeFileSystemProviderRegistrations: Event<IFileSystemProviderRegistrationEvent> = this._onDidChangeFileSystemProviderRegistrations.event; readonly onDidChangeFileSystemProviderRegistrations = this._onDidChangeFileSystemProviderRegistrations.event;
private _onWillActivateFileSystemProvider: Emitter<IFileSystemProviderActivationEvent> = this._register(new Emitter<IFileSystemProviderActivationEvent>()); private _onWillActivateFileSystemProvider = this._register(new Emitter<IFileSystemProviderActivationEvent>());
readonly onWillActivateFileSystemProvider: Event<IFileSystemProviderActivationEvent> = this._onWillActivateFileSystemProvider.event; readonly onWillActivateFileSystemProvider = this._onWillActivateFileSystemProvider.event;
private _onDidChangeFileSystemProviderCapabilities = this._register(new Emitter<IFileSystemProviderCapabilitiesChangeEvent>());
readonly onDidChangeFileSystemProviderCapabilities = this._onDidChangeFileSystemProviderCapabilities.event;
private readonly provider = new Map<string, IFileSystemProvider>(); private readonly provider = new Map<string, IFileSystemProvider>();
@@ -53,6 +56,7 @@ export class FileService extends Disposable implements IFileService {
// Forward events from provider // Forward events from provider
const providerDisposables = new DisposableStore(); const providerDisposables = new DisposableStore();
providerDisposables.add(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes)))); 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') { if (typeof provider.onDidErrorOccur === 'function') {
providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error)))); providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error))));
} }
+10
View File
@@ -28,6 +28,11 @@ export interface IFileService {
*/ */
readonly onDidChangeFileSystemProviderRegistrations: Event<IFileSystemProviderRegistrationEvent>; readonly onDidChangeFileSystemProviderRegistrations: Event<IFileSystemProviderRegistrationEvent>;
/**
* An even that is fired when a registered file system provider changes it's capabilities.
*/
readonly onDidChangeFileSystemProviderCapabilities: Event<IFileSystemProviderCapabilitiesChangeEvent>;
/** /**
* An event that is fired when a file system provider is about to be activated. Listeners * 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. * can join this event with a long running promise to help in the activation process.
@@ -409,6 +414,11 @@ export interface IFileSystemProviderRegistrationEvent {
provider?: IFileSystemProvider; provider?: IFileSystemProvider;
} }
export interface IFileSystemProviderCapabilitiesChangeEvent {
provider: IFileSystemProvider;
scheme: string;
}
export interface IFileSystemProviderActivationEvent { export interface IFileSystemProviderActivationEvent {
scheme: string; scheme: string;
join(promise: Promise<void>): void; join(promise: Promise<void>): void;
@@ -6,7 +6,7 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { FileService } from 'vs/platform/files/common/fileService'; import { FileService } from 'vs/platform/files/common/fileService';
import { URI } from 'vs/base/common/uri'; 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 { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { NullLogService } from 'vs/platform/log/common/log'; import { NullLogService } from 'vs/platform/log/common/log';
import { timeout } from 'vs/base/common/async'; import { timeout } from 'vs/base/common/async';
@@ -17,6 +17,7 @@ suite('File Service', () => {
test('provider registration', async () => { test('provider registration', async () => {
const service = new FileService(new NullLogService()); const service = new FileService(new NullLogService());
const resource = URI.parse('test://foo/bar'); const resource = URI.parse('test://foo/bar');
const provider = new NullFileSystemProvider();
assert.equal(service.canHandleResource(resource), false); assert.equal(service.canHandleResource(resource), false);
@@ -25,6 +26,11 @@ suite('File Service', () => {
registrations.push(e); registrations.push(e);
}); });
const capabilityChanges: IFileSystemProviderCapabilitiesChangeEvent[] = [];
service.onDidChangeFileSystemProviderCapabilities(e => {
capabilityChanges.push(e);
});
let registrationDisposable: IDisposable | undefined = undefined; let registrationDisposable: IDisposable | undefined = undefined;
let callCount = 0; let callCount = 0;
service.onWillActivateFileSystemProvider(e => { service.onWillActivateFileSystemProvider(e => {
@@ -32,7 +38,7 @@ suite('File Service', () => {
if (e.scheme === 'test' && callCount === 1) { if (e.scheme === 'test' && callCount === 1) {
e.join(new Promise(resolve => { e.join(new Promise(resolve => {
registrationDisposable = service.registerProvider('test', new NullFileSystemProvider()); registrationDisposable = service.registerProvider('test', provider);
resolve(); resolve();
})); }));
@@ -48,6 +54,13 @@ suite('File Service', () => {
assert.equal(registrations[0].added, true); assert.equal(registrations[0].added, true);
assert.ok(registrationDisposable); 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'); await service.activateProvider('test');
assert.equal(callCount, 2); // activation is called again assert.equal(callCount, 2); // activation is called again
@@ -6,14 +6,22 @@
import { URI } from 'vs/base/common/uri'; 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 { 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 { 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 { export class NullFileSystemProvider implements IFileSystemProvider {
capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly; capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly;
onDidChangeCapabilities: Event<void> = Event.None; private readonly _onDidChangeCapabilities = new Emitter<void>();
onDidChangeFile: Event<readonly IFileChange[]> = Event.None; readonly onDidChangeCapabilities: Event<void> = this._onDidChangeCapabilities.event;
setCapabilities(capabilities: FileSystemProviderCapabilities): void {
this.capabilities = capabilities;
this._onDidChangeCapabilities.fire();
}
readonly onDidChangeFile: Event<readonly IFileChange[]> = Event.None;
constructor(private disposableFactory: () => IDisposable = () => Disposable.None) { } constructor(private disposableFactory: () => IDisposable = () => Disposable.None) { }
@@ -18,7 +18,7 @@ export interface ResolvedOptions {
} }
export interface TunnelInformation { export interface TunnelInformation {
detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[]; environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[];
} }
export interface ResolverResult { export interface ResolverResult {
+5 -5
View File
@@ -19,9 +19,9 @@ export interface RemoteTunnel {
} }
export interface TunnelOptions { export interface TunnelOptions {
remote: { port: number, host: string }; remoteAddress: { port: number, host: string };
localPort?: number; localPort?: number;
name?: string; label?: string;
} }
export interface ITunnelProvider { export interface ITunnelProvider {
@@ -33,10 +33,10 @@ export interface ITunnelService {
readonly tunnels: Promise<readonly RemoteTunnel[]>; readonly tunnels: Promise<readonly RemoteTunnel[]>;
readonly onTunnelOpened: Event<RemoteTunnel>; readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<number>; readonly onTunnelClosed: Event<{ host: string, port: number }>;
openTunnel(remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined; openTunnel(remoteHost: string | undefined, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
closeTunnel(remotePort: number): Promise<void>; closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable; setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable;
} }
@@ -13,12 +13,12 @@ export class NoOpTunnelService implements ITunnelService {
public readonly tunnels: Promise<readonly RemoteTunnel[]> = Promise.resolve([]); public readonly tunnels: Promise<readonly RemoteTunnel[]> = Promise.resolve([]);
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter(); private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event; public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<number> = new Emitter(); private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter();
public onTunnelClosed: Event<number> = this._onTunnelClosed.event; public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
openTunnel(_remotePort: number): Promise<RemoteTunnel> | undefined { openTunnel(_remoteHost: string, _remotePort: number): Promise<RemoteTunnel> | undefined {
return undefined; return undefined;
} }
async closeTunnel(_remotePort: number): Promise<void> { async closeTunnel(_remoteHost: string, _remotePort: number): Promise<void> {
} }
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
@@ -89,11 +89,11 @@ export class StorageMainService extends Disposable implements IStorageMainServic
private static readonly STORAGE_NAME = 'state.vscdb'; private static readonly STORAGE_NAME = 'state.vscdb';
private readonly _onDidChangeStorage: Emitter<IStorageChangeEvent> = this._register(new Emitter<IStorageChangeEvent>()); private readonly _onDidChangeStorage = this._register(new Emitter<IStorageChangeEvent>());
readonly onDidChangeStorage: Event<IStorageChangeEvent> = this._onDidChangeStorage.event; readonly onDidChangeStorage = this._onDidChangeStorage.event;
private readonly _onWillSaveState: Emitter<void> = this._register(new Emitter<void>()); private readonly _onWillSaveState = this._register(new Emitter<void>());
readonly onWillSaveState: Event<void> = this._onWillSaveState.event; readonly onWillSaveState = this._onWillSaveState.event;
get items(): Map<string, string> { return this.storage.items; } get items(): Map<string, string> { return this.storage.items; }
@@ -28,11 +28,11 @@ suite('StorageService', () => {
function removeData(scope: StorageScope): void { function removeData(scope: StorageScope): void {
const storage = new InMemoryStorageService(); const storage = new InMemoryStorageService();
storage.store('Monaco.IDE.Core.Storage.Test.remove', 'foobar', scope); storage.store('test.remove', 'foobar', scope);
strictEqual('foobar', storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!)); strictEqual('foobar', storage.get('test.remove', scope, (undefined)!));
storage.remove('Monaco.IDE.Core.Storage.Test.remove', scope); storage.remove('test.remove', scope);
ok(!storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!)); ok(!storage.get('test.remove', scope, (undefined)!));
} }
test('Get Data, Integer, Boolean (global, in-memory)', () => { test('Get Data, Integer, Boolean (global, in-memory)', () => {
@@ -46,34 +46,34 @@ suite('StorageService', () => {
function storeData(scope: StorageScope): void { function storeData(scope: StorageScope): void {
const storage = new InMemoryStorageService(); const storage = new InMemoryStorageService();
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, 'foobar'), 'foobar'); strictEqual(storage.get('test.get', scope, 'foobar'), 'foobar');
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, ''), ''); strictEqual(storage.get('test.get', scope, ''), '');
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 5), 5); strictEqual(storage.getNumber('test.getNumber', scope, 5), 5);
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 0), 0); strictEqual(storage.getNumber('test.getNumber', scope, 0), 0);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, true), true); strictEqual(storage.getBoolean('test.getBoolean', scope, true), true);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, false), false); strictEqual(storage.getBoolean('test.getBoolean', scope, false), false);
storage.store('Monaco.IDE.Core.Storage.Test.get', 'foobar', scope); storage.store('test.get', 'foobar', scope);
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), 'foobar'); strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar');
storage.store('Monaco.IDE.Core.Storage.Test.get', '', scope); storage.store('test.get', '', scope);
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), ''); strictEqual(storage.get('test.get', scope, (undefined)!), '');
storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 5, scope); storage.store('test.getNumber', 5, scope);
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 5); strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5);
storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 0, scope); storage.store('test.getNumber', 0, scope);
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 0); strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 0);
storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', true, scope); storage.store('test.getBoolean', true, scope);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), true); strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), true);
storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', false, scope); storage.store('test.getBoolean', false, scope);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), false); strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), false);
strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getDefault', scope, 'getDefault'), 'getDefault'); strictEqual(storage.get('test.getDefault', scope, 'getDefault'), 'getDefault');
strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumberDefault', scope, 5), 5); strictEqual(storage.getNumber('test.getNumberDefault', scope, 5), 5);
strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBooleanDefault', scope, true), true); strictEqual(storage.getBoolean('test.getBooleanDefault', scope, true), true);
} }
function uniqueStorageDir(): string { function uniqueStorageDir(): string {
@@ -381,12 +381,13 @@ function registerDefaultClassifications(): void {
registerTokenType('parameterType', nls.localize('parameterType', "Style for parameter types."), undefined, 'type'); 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('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('macro', nls.localize('macro', "Style for macros."), undefined, 'function');
registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable'], ['entity.name.variable']]); registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable'], ['entity.name.variable']]);
registerTokenType('constant', nls.localize('constant', "Style for constants."), undefined, 'variable'); registerTokenType('constant', nls.localize('constant', "Style for constants."), undefined, 'variable');
registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), 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); 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('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('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('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('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); tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined);
@@ -13,14 +13,14 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { IStateService } from 'vs/platform/state/node/state'; import { IStateService } from 'vs/platform/state/node/state';
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; 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 { parseLineAndColumnAware } from 'vs/code/node/paths';
import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log'; 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 { 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 { 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 product from 'vs/platform/product/common/product';
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; 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'; 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 readonly windowsState: IWindowsState;
private lastClosedWindowState?: IWindowState; private lastClosedWindowState?: IWindowState;
private shuttingDown = false;
private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>()); private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
readonly onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event; readonly onWindowReady = this._onWindowReady.event;
private readonly _onWindowClose = this._register(new Emitter<number>()); private readonly _onWindowClose = this._register(new Emitter<number>());
readonly onWindowClose: CommonEvent<number> = this._onWindowClose.event; readonly onWindowClose = this._onWindowClose.event;
private readonly _onWindowsCountChanged = this._register(new Emitter<IWindowsCountChangedEvent>()); private readonly _onWindowsCountChanged = this._register(new Emitter<IWindowsCountChangedEvent>());
readonly onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event; readonly onWindowsCountChanged = this._onWindowsCountChanged.event;
constructor( constructor(
private readonly machineId: string, private readonly machineId: string,
@@ -236,6 +238,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
systemPreferences.on('high-contrast-color-scheme-changed', () => onHighContrastChange()); 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 // Handle various lifecycle events around windows
this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown()); 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) // - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
// //
private onBeforeShutdown(): void { private onBeforeShutdown(): void {
this.shuttingDown = true;
this.saveWindowsState();
}
private saveWindowsState(): void {
const currentWindowsState: IWindowsState = { const currentWindowsState: IWindowsState = {
openedWindows: [], openedWindows: [],
lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow, lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
@@ -327,8 +344,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Persist // Persist
const state = getWindowsStateStoreData(currentWindowsState); const state = getWindowsStateStoreData(currentWindowsState);
this.logService.trace('onBeforeShutdown', state);
this.stateService.setItem(WindowsMainService.windowsStateStorageKey, 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 // See note on #onBeforeShutdown() for details how these events are flowing
+14 -40
View File
@@ -34,15 +34,18 @@ declare module 'vscode' {
} }
export interface TunnelOptions { export interface TunnelOptions {
remote: { port: number, host: string }; remoteAddress: { port: number, host: string };
localPort?: number; // The desired local port. If this port can't be used, then another will be chosen.
name?: string; localAddressPort?: number;
label?: string;
} }
export interface Tunnel { export interface Tunnel {
remote: { port: number, host: string }; remoteAddress: { port: number, host: string };
//The complete local address(ex. localhost:1234)
localAddress: string; localAddress: string;
onDispose: Event<void>; // Implementers of Tunnel should fire onDidDispose when dispose is called.
onDidDispose: Event<void>;
dispose(): void; dispose(): void;
} }
@@ -55,7 +58,7 @@ declare module 'vscode' {
* 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. * 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; 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 not implemented, the core will use its default forwarding logic.
* When implemented, the core will use this to forward ports. * When implemented, the core will use this to forward ports.
*/ */
forwardPort?(tunnelOptions: TunnelOptions): Thenable<Tunnel> | undefined; tunnelFactory?: (tunnelOptions: TunnelOptions) => Thenable<Tunnel> | undefined;
} }
export namespace workspace { export namespace workspace {
/** /**
* Forwards a port. Currently only works for a remote host of localhost. * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel.
* @param forward The `localPort` is a suggestion only. If that port is not available another will be chosen. * 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<Tunnel>; export function openTunnel(tunnelOptions: TunnelOptions): Thenable<Tunnel>;
} }
export interface ResourceLabelFormatter { export interface ResourceLabelFormatter {
@@ -1410,31 +1414,6 @@ declare module 'vscode' {
*/ */
export interface WorkspaceConfiguration { export interface WorkspaceConfiguration {
/**
* Return a value from this configuration.
*
* @param section Configuration name, supports _dotted_ names.
* @return The value `section` denotes or `undefined`.
*/
get<T>(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<T>(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 * Retrieve all information about a configuration setting. A configuration value
* often consists of a *default* value, a global or installation-wide 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. * - configuration to workspace folder when [WorkspaceConfiguration](#WorkspaceConfiguration) is not scoped to a resource.
*/ */
update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, scopeToLanguage?: boolean): Thenable<void>; update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, scopeToLanguage?: boolean): Thenable<void>;
/**
* Readable dictionary that backs this configuration.
*/
readonly [key: string]: any;
} }
//#endregion //#endregion
@@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; 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 { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; 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) @extHostNamedCustomer(MainContext.MainThreadTunnelService)
export class MainThreadTunnelService implements MainThreadTunnelServiceShape { export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
@@ -22,15 +22,15 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
} }
async $openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined> { async $openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined> {
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) { if (tunnel) {
return TunnelDto.fromServiceTunnel(tunnel); return TunnelDto.fromServiceTunnel(tunnel);
} }
return undefined; return undefined;
} }
async $closeTunnel(remotePort: number): Promise<void> { async $closeTunnel(remote: { host: string, port: number }): Promise<void> {
return this.remoteExplorerService.close(remotePort); return this.remoteExplorerService.close(remote);
} }
async $registerCandidateFinder(): Promise<void> { async $registerCandidateFinder(): Promise<void> {
@@ -44,11 +44,11 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
if (forward) { if (forward) {
return forward.then(tunnel => { return forward.then(tunnel => {
return { return {
tunnelRemotePort: tunnel.remote.port, tunnelRemotePort: tunnel.remoteAddress.port,
tunnelRemoteHost: tunnel.remote.host, tunnelRemoteHost: tunnel.remoteAddress.host,
localAddress: tunnel.localAddress, localAddress: tunnel.localAddress,
dispose: () => { dispose: () => {
this._proxy.$closeTunnel({ host: tunnel.remote.host, port: tunnel.remote.port }); this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port });
} }
}; };
}); });
@@ -313,7 +313,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
if (!viewContainer) { 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 { class CustomViewPaneContainer extends ViewPaneContainer {
constructor( constructor(
@@ -327,7 +327,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService, @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);
} }
} }
@@ -672,7 +672,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
}, },
getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null): vscode.WorkspaceConfiguration { getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null): vscode.WorkspaceConfiguration {
scope = arguments.length === 1 ? undefined : scope; 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) { registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) {
return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider); 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[]) => { onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => {
return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables); return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables);
}, },
makeTunnel: (forward: vscode.TunnelOptions) => { openTunnel: (forward: vscode.TunnelOptions) => {
checkProposedApiEnabled(extension); checkProposedApiEnabled(extension);
return extHostTunnelService.makeTunnel(forward); return extHostTunnelService.openTunnel(forward);
} }
}; };
@@ -47,7 +47,8 @@ import { createExtHostContextProxyIdentifier as createExtId, createMainContextPr
import * as search from 'vs/workbench/services/search/common/search'; import * as search from 'vs/workbench/services/search/common/search';
import { SaveReason } from 'vs/workbench/common/editor'; import { SaveReason } from 'vs/workbench/common/editor';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; 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}} // {{SQL CARBON EDIT}}
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
@@ -778,7 +779,7 @@ export interface MainThreadWindowShape extends IDisposable {
export interface MainThreadTunnelServiceShape extends IDisposable { export interface MainThreadTunnelServiceShape extends IDisposable {
$openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined>; $openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined>;
$closeTunnel(remotePort: number): Promise<void>; $closeTunnel(remote: { host: string, port: number }): Promise<void>;
$registerCandidateFinder(): Promise<void>; $registerCandidateFinder(): Promise<void>;
$setTunnelProvider(): Promise<void>; $setTunnelProvider(): Promise<void>;
} }
@@ -1399,7 +1400,7 @@ export interface ExtHostStorageShape {
export interface ExtHostTunnelServiceShape { export interface ExtHostTunnelServiceShape {
$findCandidatePorts(): Promise<{ port: number, detail: string }[]>; $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>;
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined; $forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined;
$closeTunnel(remote: { host: string, port: number }): Promise<void>; $closeTunnel(remote: { host: string, port: number }): Promise<void>;
} }
@@ -13,13 +13,14 @@ import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigu
import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { isObject } from 'vs/base/common/types'; 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 { Barrier } from 'vs/base/common/async';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { Workspace } from 'vs/platform/workspace/common/workspace'; import { Workspace } from 'vs/platform/workspace/common/workspace';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
function lookUp(tree: any, key: string) { function lookUp(tree: any, key: string) {
if (key) { if (key) {
@@ -149,14 +150,17 @@ export class ExtHostConfigProvider {
this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); 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) || {}; const overrides = scopeToOverrides(scope) || {};
if (overrides.overrideIdentifier && extensionDescription) {
checkProposedApiEnabled(extensionDescription);
}
const config = this._toReadonlyValue(section const config = this._toReadonlyValue(section
? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section) ? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section)
: this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace)); : this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace));
if (section) { if (section) {
this._validateConfigurationAccess(section, overrides, extensionId); this._validateConfigurationAccess(section, overrides, extensionDescription?.identifier);
} }
function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null { function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null {
@@ -179,7 +183,7 @@ export class ExtHostConfigProvider {
return typeof lookUp(config, key) !== 'undefined'; return typeof lookUp(config, key) !== 'undefined';
}, },
get: <T>(key: string, defaultValue?: T) => { get: <T>(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); let result = lookUp(config, key);
if (typeof result === 'undefined') { if (typeof result === 'undefined') {
result = defaultValue; result = defaultValue;
@@ -662,7 +662,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
value: { value: {
authority, authority,
options, options,
tunnelInformation: { detectedTunnels: result.detectedTunnels } tunnelInformation: { environmentTunnels: result.environmentTunnels }
} }
}; };
} catch (err) { } catch (err) {
@@ -6,27 +6,20 @@
import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as vscode from 'vscode'; 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'; import { IDisposable } from 'vs/base/common/lifecycle';
export interface TunnelOptions {
remote: { port: number, host: string };
localPort?: number;
name?: string;
closeable?: boolean;
}
export interface TunnelDto { export interface TunnelDto {
remote: { port: number, host: string }; remoteAddress: { port: number, host: string };
localAddress: string; localAddress: string;
} }
export namespace TunnelDto { export namespace TunnelDto {
export function fromApiTunnel(tunnel: vscode.Tunnel): 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 { 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 { export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
readonly _serviceBrand: undefined; readonly _serviceBrand: undefined;
makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined>; openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined>;
setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>; setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
} }
@@ -45,10 +38,10 @@ export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IEx
export class ExtHostTunnelService implements IExtHostTunnelService { export class ExtHostTunnelService implements IExtHostTunnelService {
_serviceBrand: undefined; _serviceBrand: undefined;
async makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> { async openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
return undefined; return undefined;
} }
async $findCandidatePorts(): Promise<{ port: number; detail: string; }[]> { async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> {
return []; return [];
} }
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { return { dispose: () => { } }; } async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { return { dispose: () => { } }; }
@@ -13,16 +13,17 @@ import { exec } from 'child_process';
import * as resources from 'vs/base/common/resources'; import * as resources from 'vs/base/common/resources';
import * as fs from 'fs'; import * as fs from 'fs';
import { isLinux } from 'vs/base/common/platform'; 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 { asPromise } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
class ExtensionTunnel implements vscode.Tunnel { class ExtensionTunnel implements vscode.Tunnel {
private _onDispose: Emitter<void> = new Emitter(); private _onDispose: Emitter<void> = new Emitter();
onDispose: Event<void> = this._onDispose.event; onDidDispose: Event<void> = this._onDispose.event;
constructor( constructor(
public readonly remote: { port: number; host: string; }, public readonly remoteAddress: { port: number; host: string; },
public readonly localAddress: string, public readonly localAddress: string,
private readonly _dispose: () => void) { } private readonly _dispose: () => void) { }
@@ -48,11 +49,11 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
this.registerCandidateFinder(); this.registerCandidateFinder();
} }
} }
async makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> { async openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
const tunnel = await this._proxy.$openTunnel(forward); const tunnel = await this._proxy.$openTunnel(forward);
if (tunnel) { if (tunnel) {
const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => { const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => {
return this._proxy.$closeTunnel(tunnel.remote.port); return this._proxy.$closeTunnel(tunnel.remoteAddress);
}); });
this._register(disposableTunnel); this._register(disposableTunnel);
return disposableTunnel; return disposableTunnel;
@@ -65,8 +66,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
} }
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
if (provider && provider.forwardPort) { if (provider && provider.tunnelFactory) {
this._forwardPortProvider = provider.forwardPort; this._forwardPortProvider = provider.tunnelFactory;
await this._proxy.$setTunnelProvider(); await this._proxy.$setTunnelProvider();
} else { } else {
this._forwardPortProvider = undefined; this._forwardPortProvider = undefined;
@@ -91,11 +92,11 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
const providedPort = this._forwardPortProvider!(tunnelOptions); const providedPort = this._forwardPortProvider!(tunnelOptions);
if (providedPort !== undefined) { if (providedPort !== undefined) {
return asPromise(() => providedPort).then(tunnel => { return asPromise(() => providedPort).then(tunnel => {
if (!this._extensionTunnels.has(tunnelOptions.remote.host)) { if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) {
this._extensionTunnels.set(tunnelOptions.remote.host, new Map()); this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map());
} }
this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel); this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, tunnel);
this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote.port))); this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress)));
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel)); 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) { if (!isLinux) {
return []; 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 tcp: string = fs.readFileSync('/proc/net/tcp', 'utf8');
const tcp6: string = fs.readFileSync('/proc/net/tcp6', 'utf8'); const tcp6: string = fs.readFileSync('/proc/net/tcp6', 'utf8');
const procSockets: string = await (new Promise(resolve => { 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 }) => { connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
const command = processMap[socketMap[socket].pid].cmd; const command = processMap[socketMap[socket].pid].cmd;
if (!command.match('.*\.vscode\-server\-[a-zA-Z]+\/bin.*') && (command.indexOf('out/vs/server/main.js') === -1)) { 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 });
} }
}); });
+2 -2
View File
@@ -16,7 +16,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; 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 { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform';
import { PanelPositionContext } from 'vs/workbench/common/panel'; import { PanelPositionContext } from 'vs/workbench/common/panel';
@@ -151,7 +151,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
// Panel Position // Panel Position
this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService); 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(); this.registerListeners();
} }
+32 -26
View File
@@ -14,7 +14,7 @@ import { pathsToEditors } from 'vs/workbench/common/editor';
import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart';
import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart';
import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; 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 { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -58,6 +58,7 @@ enum Storage {
PANEL_HIDDEN = 'workbench.panel.hidden', PANEL_HIDDEN = 'workbench.panel.hidden',
PANEL_POSITION = 'workbench.panel.location', PANEL_POSITION = 'workbench.panel.location',
PANEL_SIZE = 'workbench.panel.size', PANEL_SIZE = 'workbench.panel.size',
PANEL_DIMENSION = 'workbench.panel.dimension',
PANEL_LAST_NON_MAXIMIZED_WIDTH = 'workbench.panel.lastNonMaximizedWidth', PANEL_LAST_NON_MAXIMIZED_WIDTH = 'workbench.panel.lastNonMaximizedWidth',
PANEL_LAST_NON_MAXIMIZED_HEIGHT = 'workbench.panel.lastNonMaximizedHeight', PANEL_LAST_NON_MAXIMIZED_HEIGHT = 'workbench.panel.lastNonMaximizedHeight',
@@ -178,8 +179,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
position: Position.BOTTOM, position: Position.BOTTOM,
lastNonMaximizedWidth: 300, lastNonMaximizedWidth: 300,
lastNonMaximizedHeight: 300, lastNonMaximizedHeight: 300,
panelToRestore: undefined as string | undefined, panelToRestore: undefined as string | undefined
restored: false
}, },
statusBar: { statusBar: {
@@ -571,10 +571,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private updatePanelPosition() { private updatePanelPosition() {
const defaultPanelPosition = this.configurationService.getValue<string>(Settings.PANEL_POSITION); const defaultPanelPosition = this.configurationService.getValue<string>(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 = positionFromString(panelPosition || defaultPanelPosition);
this.state.panel.position = ((panelPosition || defaultPanelPosition) === 'right') ? Position.RIGHT : Position.BOTTOM;
} }
registerPart(part: Part): void { registerPart(part: Part): void {
@@ -667,15 +666,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
} }
getMaximumEditorDimensions(): Dimension { getMaximumEditorDimensions(): Dimension {
const isColumn = this.state.panel.position === Position.RIGHT || this.state.panel.position === Position.LEFT;
const takenWidth = const takenWidth =
(this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) + (this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) +
(this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.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 = const takenHeight =
(this.isVisible(Parts.TITLEBAR_PART) ? this.titleBarPartView.minimumHeight : 0) + (this.isVisible(Parts.TITLEBAR_PART) ? this.titleBarPartView.minimumHeight : 0) +
(this.isVisible(Parts.STATUSBAR_PART) ? this.statusBarPartView.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 availableWidth = this.dimension.width - takenWidth;
const availableHeight = this.dimension.height - takenHeight; 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.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_SIZE, panelSize, StorageScope.GLOBAL);
this.storageService.store(Storage.PANEL_DIMENSION, positionToString(this.state.panel.position), StorageScope.GLOBAL);
const gridSize = grid.getViewSize(); const gridSize = grid.getViewSize();
this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); 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; return this.state.panel.position;
} }
setPanelPosition(position: Position.BOTTOM | Position.RIGHT): void { setPanelPosition(position: Position): void {
if (this.state.panel.hidden) { if (this.state.panel.hidden) {
this.setPanelHidden(false); this.setPanelHidden(false);
} }
const panelPart = this.getPart(Parts.PANEL_PART); const panelPart = this.getPart(Parts.PANEL_PART);
const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right'; const oldPositionValue = positionToString(this.state.panel.position);
const oldPositionValue = (this.state.panel.position === Position.BOTTOM) ? 'bottom' : 'right'; const newPositionValue = positionToString(position);
this.state.panel.position = 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 // 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 // Adjust CSS
const panelContainer = assertIsDefined(panelPart.getContainer()); const panelContainer = assertIsDefined(panelPart.getContainer());
@@ -1250,14 +1243,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
if (position === Position.BOTTOM) { if (position === Position.BOTTOM) {
this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : this.state.panel.lastNonMaximizedHeight, this.editorPartView, Direction.Down); 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); 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 // Reset sidebar to original size before shifting the panel
this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize); this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize);
this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); this._onPanelPositionChange.fire(newPositionValue);
} }
isWindowMaximized() { isWindowMaximized() {
@@ -1275,13 +1270,26 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this._onMaximizeChange.fire(maximized); 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 { private createGridDescriptor(): ISerializedGrid {
const workbenchDimensions = this.getClientArea(); const workbenchDimensions = this.getClientArea();
const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width);
const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); 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 // 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 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 titleBarHeight = this.titleBarPartView.minimumHeight;
const statusBarHeight = this.statusBarPartView.minimumHeight; const statusBarHeight = this.statusBarPartView.minimumHeight;
@@ -1319,9 +1327,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
visible: !this.state.panel.hidden visible: !this.state.panel.hidden
}; };
const editorSectionNode: ISerializedNode[] = this.state.panel.position === Position.BOTTOM const editorSectionNode = this.arrangeEditorNodes(editorNode, panelNode, editorSectionWidth);
? [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }]
: [editorNode, panelNode];
const middleSection: ISerializedNode[] = this.state.sideBar.position === Position.LEFT const middleSection: ISerializedNode[] = this.state.sideBar.position === Position.LEFT
? [activityBarNode, sideBarNode, ...editorSectionNode] ? [activityBarNode, sideBarNode, ...editorSectionNode]
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
@@ -63,11 +63,6 @@ export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEd
return options; return options;
} }
export interface IEditorPartOptionsChangeEvent {
oldPartOptions: IEditorPartOptions;
newPartOptions: IEditorPartOptions;
}
export interface IEditorOpeningEvent extends IEditorIdentifier { export interface IEditorOpeningEvent extends IEditorIdentifier {
options?: IEditorOptions; options?: IEditorOptions;
@@ -6,7 +6,7 @@
import 'vs/css!./media/editorgroupview'; import 'vs/css!./media/editorgroupview';
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; 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 { Event, Emitter, Relay } from 'vs/base/common/event';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; 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'; 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 { RunOnceWorker } from 'vs/base/common/async';
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; 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 { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
@@ -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 { 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 { 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 { 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 { values } from 'vs/base/common/map';
import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme'; import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme';
import { distinct, coalesce } from 'vs/base/common/arrays'; 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 { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; 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<IEditorGroupView>()); private readonly _onDidMoveGroup = this._register(new Emitter<IEditorGroupView>());
readonly onDidMoveGroup = this._onDidMoveGroup.event; readonly onDidMoveGroup = this._onDidMoveGroup.event;
private onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); private readonly onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>());
private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); private readonly _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); } readonly onDidSizeConstraintsChange = Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event);
private readonly _onDidEditorPartOptionsChange = this._register(new Emitter<IEditorPartOptionsChangeEvent>());
readonly onDidEditorPartOptionsChange = this._onDidEditorPartOptionsChange.event;
//#endregion //#endregion
@@ -155,13 +158,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this.registerListeners(); this.registerListeners();
} }
//#region IEditorGroupsAccessor
private enforcedPartOptions: IEditorPartOptions[] = [];
private readonly _onDidEditorPartOptionsChange: Emitter<IEditorPartOptionsChangeEvent> = this._register(new Emitter<IEditorPartOptionsChangeEvent>());
readonly onDidEditorPartOptionsChange: Event<IEditorPartOptionsChangeEvent> = this._onDidEditorPartOptionsChange.event;
private registerListeners(): void { private registerListeners(): void {
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); 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 }); this._onDidEditorPartOptionsChange.fire({ oldPartOptions, newPartOptions });
} }
//#region IEditorGroupsService
private enforcedPartOptions: IEditorPartOptions[] = [];
get partOptions(): IEditorPartOptions { get partOptions(): IEditorPartOptions {
return this._partOptions; return this._partOptions;
} }
@@ -199,10 +199,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
}); });
} }
//#endregion
//#region IEditorGroupsService
private _contentDimension!: Dimension; private _contentDimension!: Dimension;
get contentDimension(): Dimension { return this._contentDimension; } get contentDimension(): Dimension { return this._contentDimension; }
@@ -37,6 +37,15 @@
border-left-width: 0; /* no border when editor area is hiden */ 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 { .monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label {
outline-offset: -2px; outline-offset: -2px;
} }
@@ -121,3 +130,10 @@
.monaco-workbench .part.panel.right .title-actions .codicon-chevron-down { .monaco-workbench .part.panel.right .title-actions .codicon-chevron-down {
transform: rotate(-90deg); 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);
}
@@ -12,11 +12,12 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; 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 { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { IActivity } from 'vs/workbench/common/activity'; import { IActivity } from 'vs/workbench/common/activity';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel'; import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
export class ClosePanelAction extends Action { 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<any> {
const position = this.layoutService.getPanelPosition();
this.layoutService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM);
return Promise.resolve();
}
}
export class ToggleMaximizedPanelAction extends Action { 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<T> {
id: string;
when: ContextKeyExpr;
alias: string;
label: string;
value: T;
}
function createPositionPanelActionConfig(id: string, alias: string, label: string, position: Position): PanelActionConfig<Position> {
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<any> {
const position = positionByActionId.get(this.id);
this.layoutService.setPanelPosition(position === undefined ? Position.BOTTOM : position);
return Promise.resolve();
}
}
export class PanelActivityAction extends ActivityAction { export class PanelActivityAction extends ActivityAction {
constructor( 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(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(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(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(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(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")); 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 order: 5
}); });
function registerPositionPanelActionById(config: PanelActionConfig<Position>) {
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, { MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_workbench_layout_move', group: '3_workbench_layout_move',
command: { command: {
id: TogglePanelPositionAction.ID, id,
title: TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL title: label
}, },
when: PanelPositionContext.isEqualTo('bottom'), when,
order: 5 order: 5
}); });
}
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { // register each position panel action
group: '3_workbench_layout_move', PositionPanelActionConfigs.forEach(registerPositionPanelActionById);
command: {
id: TogglePanelPositionAction.ID,
title: TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL
},
when: PanelPositionContext.isEqualTo('right'),
order: 5
});
@@ -18,7 +18,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; 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 { 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 { 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'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
@@ -52,7 +52,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
//#region IView //#region IView
readonly minimumWidth: number = 300; readonly minimumWidth: number = 420;
readonly maximumWidth: number = Number.POSITIVE_INFINITY; readonly maximumWidth: number = Number.POSITIVE_INFINITY;
readonly minimumHeight: number = 77; readonly minimumHeight: number = 77;
readonly maximumHeight: number = Number.POSITIVE_INFINITY; readonly maximumHeight: number = Number.POSITIVE_INFINITY;
@@ -122,7 +122,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))),
getContextMenuActions: () => [ 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")) this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))
], ],
getDefaultCompositeId: () => Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(), getDefaultCompositeId: () => Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(),
@@ -207,7 +210,9 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
const container = assertIsDefined(this.getContainer()); const container = assertIsDefined(this.getContainer());
container.style.backgroundColor = this.getColor(PANEL_BACKGROUND) || ''; 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(); const title = this.getTitleArea();
if (title) { if (title) {
@@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; 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 { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined } from 'vs/base/common/types'; 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 { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer';
import { Component } from 'vs/workbench/common/component'; 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 { export interface IPaneColors extends IColorMapping {
dropBackground?: ColorIdentifier; dropBackground?: ColorIdentifier;
@@ -234,7 +232,8 @@ export abstract class ViewPane extends Pane implements IView {
} }
export interface IViewPaneContainerOptions extends IPaneViewOptions { export interface IViewPaneContainerOptions extends IPaneViewOptions {
showHeaderInTitleWhenSingleView: boolean; mergeViewWithContainerWhenSingleView: boolean;
donotShowContainerTitleWhenMergedWithContainer?: boolean;
} }
interface IViewPaneItem { interface IViewPaneItem {
@@ -244,6 +243,7 @@ interface IViewPaneItem {
export class ViewPaneContainer extends Component implements IViewPaneContainer { export class ViewPaneContainer extends Component implements IViewPaneContainer {
private readonly viewContainer: ViewContainer;
private lastFocusedPane: ViewPane | undefined; private lastFocusedPane: ViewPane | undefined;
private paneItems: IViewPaneItem[] = []; private paneItems: IViewPaneItem[] = [];
private paneview?: PaneView; private paneview?: PaneView;
@@ -308,6 +308,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
this.options.dnd = new DefaultPaneDndController(); this.options.dnd = new DefaultPaneDndController();
} }
this.viewContainer = container;
this.visibleViewsStorageId = `${id}.numberOfVisibleViews`; this.visibleViewsStorageId = `${id}.numberOfVisibleViews`;
this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined); this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined);
this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables)));
@@ -345,15 +346,15 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
} }
getTitle(): string { getTitle(): string {
const composite = Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).getViewlet(this.getId()) || Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanel(this.getId()); if (this.isViewMergedWithContainer()) {
let title = composite.name;
if (this.isSingleView()) {
const paneItemTitle = this.paneItems[0].pane.title; 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 { private showContextMenu(event: StandardMouseEvent): void {
@@ -402,7 +403,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
} }
getActions(): IAction[] { getActions(): IAction[] {
if (this.isSingleView()) { if (this.isViewMergedWithContainer()) {
return this.paneItems[0].pane.getActions(); return this.paneItems[0].pane.getActions();
} }
@@ -410,7 +411,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
} }
getSecondaryActions(): IAction[] { getSecondaryActions(): IAction[] {
if (this.isSingleView()) { if (this.isViewMergedWithContainer()) {
return this.paneItems[0].pane.getSecondaryActions(); return this.paneItems[0].pane.getSecondaryActions();
} }
@@ -418,7 +419,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
} }
getActionViewItem(action: IAction): IActionViewItem | undefined { getActionViewItem(action: IAction): IActionViewItem | undefined {
if (this.isSingleView()) { if (this.isViewMergedWithContainer()) {
return this.paneItems[0].pane.getActionViewItem(action); 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 { 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) { for (const { pane: pane, size, index } of panes) {
this.addPane(pane, size, index); this.addPane(pane, size, index);
} }
this.updateViewHeaders(); this.updateViewHeaders();
if (this.isSingleView() !== wasSingleView) { if (this.isViewMergedWithContainer() !== wasMerged) {
this.updateTitleArea(); 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 { private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void {
const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane); const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane);
const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => { const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => {
if (this.isSingleView()) { if (this.isViewMergedWithContainer()) {
this.updateTitleArea(); this.updateTitleArea();
} }
}); });
@@ -668,12 +669,12 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
} }
removePanes(panes: ViewPane[]): void { removePanes(panes: ViewPane[]): void {
const wasSingleView = this.isSingleView(); const wasMerged = this.isViewMergedWithContainer();
panes.forEach(pane => this.removePane(pane)); panes.forEach(pane => this.removePane(pane));
this.updateViewHeaders(); this.updateViewHeaders();
if (wasSingleView !== this.isSingleView()) { if (wasMerged !== this.isViewMergedWithContainer()) {
this.updateTitleArea(); this.updateTitleArea();
} }
} }
@@ -726,8 +727,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
return assertIsDefined(this.paneview).getPaneSize(pane); return assertIsDefined(this.paneview).getPaneSize(pane);
} }
protected updateViewHeaders(): void { private updateViewHeaders(): void {
if (this.isSingleView()) { if (this.isViewMergedWithContainer()) {
this.paneItems[0].pane.setExpanded(true); this.paneItems[0].pane.setExpanded(true);
this.paneItems[0].pane.headerVisible = false; this.paneItems[0].pane.headerVisible = false;
} else { } else {
@@ -735,8 +736,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
} }
} }
protected isSingleView(): boolean { private isViewMergedWithContainer(): boolean {
if (!(this.options.showHeaderInTitleWhenSingleView && this.paneItems.length === 1)) { if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) {
return false; return false;
} }
if (!this.areExtensionsReady) { if (!this.areExtensionsReady) {
@@ -655,8 +655,8 @@ export class ViewsService extends Disposable implements IViewsService {
this.viewDisposable.forEach(disposable => disposable.dispose()); this.viewDisposable.forEach(disposable => disposable.dispose());
this.viewDisposable.clear(); this.viewDisposable.clear();
})); }));
this._register(viewContainersRegistry.onDidRegister(viewContainer => this.onDidRegisterViewContainer(viewContainer))); this._register(viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer)));
this._register(viewContainersRegistry.onDidDeregister(viewContainer => this.onDidDeregisterViewContainer(viewContainer))); this._register(viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer)));
this._register(toDisposable(() => { this._register(toDisposable(() => {
this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose()); this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose());
this.viewDescriptorCollections.clear(); this.viewDescriptorCollections.clear();
@@ -46,7 +46,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
@IWorkspaceContextService contextService: IWorkspaceContextService @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._register(onDidChangeFilterValue(newFilterValue => {
this.filterValue = newFilterValue; this.filterValue = newFilterValue;
this.onFilterChanged(newFilterValue); this.onFilterChanged(newFilterValue);
@@ -174,7 +174,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
}, },
'workbench.panel.defaultLocation': { 'workbench.panel.defaultLocation': {
'type': 'string', 'type': 'string',
'enum': ['bottom', 'right'], 'enum': ['left', 'bottom', 'right'],
'default': 'bottom', '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.") '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 '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': { 'workbench.settings.editor': {
'type': 'string', 'type': 'string',
'enum': ['ui', 'json'], '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."), 'description': nls.localize('settings.editor.desc', "Determines which settings editor to use by default."),
'default': 'ui', 'default': 'ui',
'scope': ConfigurationScope.WINDOW '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']
} }
} }
}); });
+2 -2
View File
@@ -18,7 +18,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr
import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions';
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; 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 { IStorageService, WillSaveStateReason, StorageScope } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; 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.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.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.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'] } { id: Parts.STATUSBAR_PART, role: 'contentinfo', classes: ['statusbar'] }
].forEach(({ id, role, classes, options }) => { ].forEach(({ id, role, classes, options }) => {
const partContainer = this.createPart(id, role, classes); const partContainer = this.createPart(id, role, classes);
+5
View File
@@ -1177,6 +1177,11 @@ export interface IEditorPartOptions extends IEditorPartConfiguration {
iconTheme?: string; iconTheme?: string;
} }
export interface IEditorPartOptionsChangeEvent {
oldPartOptions: IEditorPartOptions;
newPartOptions: IEditorPartOptions;
}
export enum SideBySideEditor { export enum SideBySideEditor {
MASTER = 1, MASTER = 1,
DETAILS = 2 DETAILS = 2
+64 -24
View File
@@ -12,11 +12,12 @@ import { IViewlet } from 'vs/workbench/common/viewlet';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/platform/theme/common/themeService'; 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 { Registry } from 'vs/platform/registry/common/platform';
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IAction } from 'vs/base/common/actions'; import { IAction } from 'vs/base/common/actions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; 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 TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
export const FocusedViewContext = new RawContextKey<string>('focusedView', ''); export const FocusedViewContext = new RawContextKey<string>('focusedView', '');
@@ -31,16 +32,30 @@ export enum ViewContainerLocation {
Panel Panel
} }
export interface IViewContainerDescriptor {
readonly id: string;
readonly name: string;
readonly viewOrderDelegate?: ViewOrderDelegate;
readonly hideIfEmpty?: boolean;
readonly extensionId?: ExtensionIdentifier;
}
export interface IViewContainersRegistry { export interface IViewContainersRegistry {
/** /**
* An event that is triggerred when a view container is registered. * An event that is triggerred when a view container is registered.
*/ */
readonly onDidRegister: Event<ViewContainer>; readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
/** /**
* An event that is triggerred when a view container is deregistered. * An event that is triggerred when a view container is deregistered.
*/ */
readonly onDidDeregister: Event<ViewContainer>; readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
/** /**
* All registered view containers * All registered view containers
@@ -48,14 +63,15 @@ export interface IViewContainersRegistry {
readonly all: ViewContainer[]; readonly all: ViewContainer[];
/** /**
* Registers a view container with given id * Registers a view container to given location.
* No op if a view container is already registered with the given id. * 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. * @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 * Deregisters the given view container
@@ -69,6 +85,11 @@ export interface IViewContainersRegistry {
* @returns the view container with given id. * @returns the view container with given id.
*/ */
get(id: string): ViewContainer | undefined; get(id: string): ViewContainer | undefined;
/**
* Returns all view containers in the given location
*/
getViewContainers(location: ViewContainerLocation): ViewContainer[];
} }
interface ViewOrderDelegate { interface ViewOrderDelegate {
@@ -76,49 +97,68 @@ interface ViewOrderDelegate {
} }
export class ViewContainer { 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 { class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry {
private readonly _onDidRegister = this._register(new Emitter<ViewContainer>()); private readonly _onDidRegister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>());
readonly onDidRegister: Event<ViewContainer> = this._onDidRegister.event; readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidRegister.event;
private readonly _onDidDeregister = this._register(new Emitter<ViewContainer>()); private readonly _onDidDeregister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>());
readonly onDidDeregister: Event<ViewContainer> = this._onDidDeregister.event; readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidDeregister.event;
private viewContainers: Map<string, ViewContainer> = new Map<string, ViewContainer>(); private viewContainers: Map<ViewContainerLocation, ViewContainer[]> = new Map<ViewContainerLocation, ViewContainer[]>();
get all(): ViewContainer[] { get all(): ViewContainer[] {
return values(this.viewContainers); return flatten(values(this.viewContainers));
} }
registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer { registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, viewContainerLocation: ViewContainerLocation): ViewContainer {
const existing = this.viewContainers.get(id); const existing = this.get(viewContainerDescriptor.id);
if (existing) { if (existing) {
return existing; return existing;
} }
const viewContainer = new class extends ViewContainer { const viewContainer = new class extends ViewContainer {
constructor() { constructor() {
super(id, location, !!hideIfEmpty, extensionId, viewOrderDelegate); super(viewContainerDescriptor);
} }
}; };
this.viewContainers.set(id, viewContainer); const viewContainers = getOrSet(this.viewContainers, viewContainerLocation, []);
this._onDidRegister.fire(viewContainer); viewContainers.push(viewContainer);
this._onDidRegister.fire({ viewContainer, viewContainerLocation });
return viewContainer; return viewContainer;
} }
deregisterViewContainer(viewContainer: ViewContainer): void { deregisterViewContainer(viewContainer: ViewContainer): void {
const existing = this.viewContainers.get(viewContainer.id); for (const viewContainerLocation of keys(this.viewContainers)) {
if (existing) { const viewContainers = this.viewContainers.get(viewContainerLocation)!;
this.viewContainers.delete(viewContainer.id); const index = viewContainers?.indexOf(viewContainer);
this._onDidDeregister.fire(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 { 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) || [])];
} }
} }
@@ -93,7 +93,9 @@ suite.skip('BackupModelRestorer', () => { // {{SQL CARBON EDIT}} TODO @anthonydr
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); 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 backupFileService = new NodeTestBackupFileService(workspaceBackupPath);
const instantiationService = workbenchInstantiationService(); const instantiationService = workbenchInstantiationService();
instantiationService.stub(IBackupFileService, backupFileService); instantiationService.stub(IBackupFileService, backupFileService);
@@ -83,7 +83,7 @@ class OpenDebugPanelAction extends TogglePanelAction {
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create(
DebugViewlet, DebugViewlet,
VIEWLET_ID, VIEWLET_ID,
nls.localize('debugAndRun', "Debug and Run"), VIEW_CONTAINER.name,
'codicon-debug-alt', 'codicon-debug-alt',
13 // {{SQL CARBON EDIT}} 13 // {{SQL CARBON EDIT}}
)); ));
@@ -7,12 +7,13 @@ import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; 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 { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
export abstract class AbstractDebugAction extends Action { export abstract class AbstractDebugAction extends Action {
@@ -60,7 +61,8 @@ export class ConfigureAction extends AbstractDebugAction {
@IDebugService debugService: IDebugService, @IDebugService debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService, @IKeybindingService keybindingService: IKeybindingService,
@INotificationService private readonly notificationService: INotificationService, @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); super(id, label, 'debug-action codicon codicon-gear', debugService, keybindingService);
this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass()));
@@ -87,10 +89,26 @@ export class ConfigureAction extends AbstractDebugAction {
return; return;
} }
const sideBySide = !!(event && (event.ctrlKey || event.metaKey));
const configurationManager = this.debugService.getConfigurationManager(); const configurationManager = this.debugService.getConfigurationManager();
if (configurationManager.selectedConfiguration.launch) { let launch: ILaunch | undefined;
return configurationManager.selectedConfiguration.launch.openConfigFile(sideBySide, false); 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);
} }
} }
} }
@@ -79,7 +79,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
@IContextKeyService private readonly contextKeyService: IContextKeyService, @IContextKeyService private readonly contextKeyService: IContextKeyService,
@INotificationService private readonly notificationService: INotificationService @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.onDidChangeState(state => this.onDebugServiceStateChange(state)));
this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); this._register(this.debugService.onDidNewSession(() => this.updateToolBar()));
+38 -20
View File
@@ -25,9 +25,9 @@ import { IInstantiationService, createDecorator } from 'vs/platform/instantiatio
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Panel } from 'vs/workbench/browser/panel'; import { Panel } from 'vs/workbench/browser/panel';
import { IThemeService } from 'vs/platform/theme/common/themeService'; 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 { 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 { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; 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'; 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; _serviceBrand: undefined;
private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show 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_LINE_HEIGHT = 19;
private static readonly REPL_INPUT_MAX_HEIGHT = 170;
private history: HistoryNavigator<string>; private history: HistoryNavigator<string>;
private tree!: WorkbenchAsyncDataTree<IDebugSession, IReplElement, FuzzyScore>; private tree!: WorkbenchAsyncDataTree<IDebugSession, IReplElement, FuzzyScore>;
@@ -99,13 +98,14 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
private replInput!: CodeEditorWidget; private replInput!: CodeEditorWidget;
private replInputContainer!: HTMLElement; private replInputContainer!: HTMLElement;
private dimension!: dom.Dimension; private dimension!: dom.Dimension;
private replInputHeight: number; private replInputLineCount = 1;
private model!: ITextModel; private model!: ITextModel;
private historyNavigationEnablement!: IContextKey<boolean>; private historyNavigationEnablement!: IContextKey<boolean>;
private scopedInstantiationService!: IInstantiationService; private scopedInstantiationService!: IInstantiationService;
private replElementsChangeListener: IDisposable | undefined; private replElementsChangeListener: IDisposable | undefined;
private styleElement: HTMLStyleElement | undefined; private styleElement: HTMLStyleElement | undefined;
private completionItemProvider: IDisposable | undefined; private completionItemProvider: IDisposable | undefined;
private modelChangeListener: IDisposable = Disposable.None;
constructor( constructor(
@IDebugService private readonly debugService: IDebugService, @IDebugService private readonly debugService: IDebugService,
@@ -119,11 +119,11 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
@IContextMenuService private readonly contextMenuService: IContextMenuService, @IContextMenuService private readonly contextMenuService: IContextMenuService,
@IConfigurationService private readonly configurationService: IConfigurationService, @IConfigurationService private readonly configurationService: IConfigurationService,
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, @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); 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); this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50);
codeEditorService.registerDecorationType(DECORATION_KEY, {}); codeEditorService.registerDecorationType(DECORATION_KEY, {});
this.registerListeners(); this.registerListeners();
@@ -147,7 +147,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
if (model) { if (model) {
const word = model.getWordAtPosition(position); const word = model.getWordAtPosition(position);
const overwriteBefore = word ? word.word.length : 0; const overwriteBefore = word ? word.word.length : 0;
const text = model.getLineContent(position.lineNumber); const text = model.getValue();
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined; const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined;
const suggestions = await session.completions(frameId, text, position, overwriteBefore, token); 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); dispose(this.model);
} else { } else {
this.model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:replinput`), true); this.model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:replinput`), true);
this.setMode();
this.replInput.setModel(this.model); this.replInput.setModel(this.model);
this.updateInputDecoration(); this.updateInputDecoration();
this.refreshReplElements(true); this.refreshReplElements(true);
@@ -191,6 +192,9 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
this.onDidFontChange(); this.onDidFontChange();
} }
})); }));
this._register(this.editorService.onDidActiveEditorChange(() => {
this.setMode();
}));
} }
get isReadonly(): boolean { get isReadonly(): boolean {
@@ -215,6 +219,21 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
this.tree.domFocus(); 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 { private onDidFontChange(): void {
if (this.styleElement) { if (this.styleElement) {
const debugConsole = this.configurationService.getValue<IDebugConfiguration>('debug').console; const debugConsole = this.configurationService.getValue<IDebugConfiguration>('debug').console;
@@ -303,8 +322,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
revealLastElement(this.tree); revealLastElement(this.tree);
this.history.add(this.replInput.getValue()); this.history.add(this.replInput.getValue());
this.replInput.setValue(''); this.replInput.setValue('');
const shouldRelayout = this.replInputHeight > Repl.REPL_INPUT_INITIAL_HEIGHT; const shouldRelayout = this.replInputLineCount > 1;
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; this.replInputLineCount = 1;
if (shouldRelayout) { if (shouldRelayout) {
// Trigger a layout to shrink a potential multi line input // Trigger a layout to shrink a potential multi line input
this.layout(this.dimension); this.layout(this.dimension);
@@ -330,18 +349,19 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
layout(dimension: dom.Dimension): void { layout(dimension: dom.Dimension): void {
this.dimension = dimension; this.dimension = dimension;
const replInputHeight = Repl.REPL_INPUT_LINE_HEIGHT * this.replInputLineCount;
if (this.tree) { if (this.tree) {
const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight; 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.getHTMLElement().style.height = `${treeHeight}px`;
this.tree.layout(treeHeight, dimension.width); this.tree.layout(treeHeight, dimension.width);
if (lastElementVisible) { if (lastElementVisible) {
revealLastElement(this.tree); 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 { 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.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(() => { this._register(this.replInput.onDidChangeModelContent(() => {
const model = this.replInput.getModel(); const model = this.replInput.getModel();
this.historyNavigationEnablement.set(!!model && model.getValue() === ''); 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 // We add the input decoration only when the focus is in the input #61126
this._register(this.replInput.onDidFocusEditorText(() => this.updateInputDecoration())); this._register(this.replInput.onDidFocusEditorText(() => this.updateInputDecoration()));
@@ -28,7 +28,7 @@ import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewCon
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
export const VIEWLET_ID = 'workbench.view.debug'; export const VIEWLET_ID = 'workbench.view.debug';
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(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 VARIABLES_VIEW_ID = 'workbench.debug.variablesView';
export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView'; export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView';
@@ -118,9 +118,12 @@ export class ExpressionContainer implements IExpressionContainer {
try { try {
const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count); const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count);
return response && response.body && response.body.variables 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) => ? distinct(response.body.variables.filter(v => !!v), v => v.name).map(v => {
new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type)) 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) { } catch (e) {
return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, { kind: 'virtual' }, undefined, false)]; return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, { kind: 'virtual' }, undefined, false)];
} }
@@ -3,13 +3,31 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt'; 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); registerSingleton(IExperimentService, ExperimentService, true);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExperimentalPrompts, LifecyclePhase.Eventually); Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExperimentalPrompts, LifecyclePhase.Eventually);
const registry = Registry.as<IConfigurationRegistry>(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']
}
}
});
@@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; 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 { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
import { import {
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction,
@@ -79,7 +79,7 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
const viewletDescriptor = ViewletDescriptor.create( const viewletDescriptor = ViewletDescriptor.create(
ExtensionsViewlet, ExtensionsViewlet,
VIEWLET_ID, VIEWLET_ID,
localize('extensions', "Extensions"), VIEW_CONTAINER.name,
'codicon-extensions', 'codicon-extensions',
14 // {{SQL CARBON EDIT}} 14 // {{SQL CARBON EDIT}}
); );
@@ -727,7 +727,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]);
if (this.extension) { 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) { if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) {
extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction));
} }
@@ -812,8 +812,8 @@ export class InstallAnotherVersionAction extends ExtensionAction {
export class ExtensionInfoAction extends ExtensionAction { export class ExtensionInfoAction extends ExtensionAction {
static readonly ID = 'extensions.extensionInfo'; static readonly ID = 'workbench.extensions.action.copyExtension';
static readonly LABEL = localize('extensionInfoAction', "Copy Extension Information"); static readonly LABEL = localize('workbench.extensions.action.copyExtension', "Copy");
constructor( constructor(
@IClipboardService private readonly clipboardService: IClipboardService @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<any> {
if (!this.extension) {
return;
}
return this.clipboardService.writeText(this.extension.identifier.id);
}
}
export class ExtensionSettingsAction extends ExtensionAction { export class ExtensionSettingsAction extends ExtensionAction {
static readonly ID = 'extensions.extensionSettings'; static readonly ID = 'extensions.extensionSettings';
@@ -374,7 +374,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService, @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.searchDelayer = new Delayer(500);
this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService);
@@ -16,9 +16,10 @@ import { URI } from 'vs/base/common/uri';
import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer';
import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { localize } from 'vs/nls';
export const VIEWLET_ID = 'workbench.view.extensions'; export const VIEWLET_ID = 'workbench.view.extensions';
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('extensions', "Extensions") }, ViewContainerLocation.Sidebar);
export const EXTENSIONS_CONFIG = '.azuredatastudio/extensions.json'; export const EXTENSIONS_CONFIG = '.azuredatastudio/extensions.json';
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor';
import { ITextFileService, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; 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 { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { Disposable, IDisposable, dispose } from 'vs/base/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 { withNullAsUndefined } from 'vs/base/common/types';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { isEqualOrParent, joinPath } from 'vs/base/common/resources';
import { IExplorerService } from 'vs/workbench/contrib/files/common/files';
// {{SQL CARBON EDIT}} // {{SQL CARBON EDIT}}
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; 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, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IHostService private readonly hostService: IHostService, @IHostService private readonly hostService: IHostService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@IExplorerService private readonly explorerService: IExplorerService
) { ) {
super(); super();
@@ -111,7 +109,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
if (oldResource.toString() === resource.toString()) { if (oldResource.toString() === resource.toString()) {
reopenFileResource = newResource; // file got moved reopenFileResource = newResource; // file got moved
} else { } 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 reopenFileResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved
} }
@@ -183,7 +183,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer {
@IExtensionService extensionService: IExtensionService @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); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService);
@@ -7,7 +7,7 @@ import 'vs/css!./media/fileactions';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { isWindows, isWeb } from 'vs/base/common/platform'; import { isWindows, isWeb } from 'vs/base/common/platform';
import * as extpath from 'vs/base/common/extpath'; 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 * as resources from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { toErrorMessage } from 'vs/base/common/errorMessage'; 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 { mnemonicButtonLabel } from 'vs/base/common/labels';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; 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_COMMAND_ID = 'explorer.newFile';
export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); 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 editorService = accessor.get(IEditorService);
const viewletService = accessor.get(IViewletService); const viewletService = accessor.get(IViewletService);
const notificationService = accessor.get(INotificationService); const notificationService = accessor.get(INotificationService);
const labelService = accessor.get(ILabelService);
await viewletService.openViewlet(VIEWLET_ID, true); await viewletService.openViewlet(VIEWLET_ID, true);
@@ -893,16 +891,14 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
throw new Error('Parent folder is readonly.'); throw new Error('Parent folder is readonly.');
} }
const newStat = new NewExplorerItem(explorerService, folder, isFolder); const newStat = new NewExplorerItem(fileService, folder, isFolder);
await folder.fetchChildren(fileService, explorerService); const sortOrder = explorerService.sortOrder;
await folder.fetchChildren(sortOrder);
folder.addChild(newStat); folder.addChild(newStat);
const onSuccess = (value: string): Promise<void> => { const onSuccess = (value: string): Promise<void> => {
const separator = labelService.getSeparator(folder.resource.scheme); const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value));
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);
return createPromise.then(created => { return createPromise.then(created => {
refreshIfSeparator(value, explorerService); refreshIfSeparator(value, explorerService);
return isFolder ? explorerService.select(created.resource, true) return isFolder ? explorerService.select(created.resource, true)
@@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; 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 { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; 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 { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker';
import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
@@ -77,7 +77,7 @@ class FileUriLabelContribution implements IWorkbenchContribution {
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create(
ExplorerViewlet, ExplorerViewlet,
VIEWLET_ID, VIEWLET_ID,
nls.localize('explore', "Explorer"), VIEW_CONTAINER.name,
'codicon-files', 'codicon-files',
10 // {{SQL CARBON EDIT}} 10 // {{SQL CARBON EDIT}}
)); ));
@@ -397,8 +397,8 @@ configurationRegistry.registerConfiguration({
}, },
'explorer.sortOrder': { 'explorer.sortOrder': {
'type': 'string', 'type': 'string',
'enum': [SortOrderConfiguration.DEFAULT, SortOrderConfiguration.MIXED, SortOrderConfiguration.FILES_FIRST, SortOrderConfiguration.TYPE, SortOrderConfiguration.MODIFIED], 'enum': [SortOrder.Default, SortOrder.Mixed, SortOrder.FilesFirst, SortOrder.Type, SortOrder.Modified],
'default': SortOrderConfiguration.DEFAULT, 'default': SortOrder.Default,
'enumDescriptions': [ 'enumDescriptions': [
nls.localize('sortOrder.default', 'Files and folders are sorted by their names, in alphabetical order. Folders are displayed before files.'), 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.'), nls.localize('sortOrder.mixed', 'Files and folders are sorted by their names, in alphabetical order. Files are interwoven with folders.'),
@@ -412,18 +412,18 @@ export class ExplorerView extends ViewPane {
this._register(explorerNavigator); this._register(explorerNavigator);
// Open when selecting via keyboard // Open when selecting via keyboard
this._register(explorerNavigator.onDidOpenResource(async e => { 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. // 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. // 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; const shiftDown = e.browserEvent instanceof KeyboardEvent && e.browserEvent.shiftKey;
if (element && !shiftDown) { if (selection.length === 1 && !shiftDown) {
if (element.isDirectory || this.explorerService.isEditable(undefined)) { 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 user is clicking on explorer items while some are being edited #70276
// Do not react if clicking on directories // Do not react if clicking on directories
return; return;
} }
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' }); this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('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._register(this.storageService.onWillSaveState(() => {
this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE); this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE);
})); }));
@@ -9,7 +9,7 @@ import * as glob from 'vs/base/common/glob';
import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; 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 { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
@@ -90,12 +90,13 @@ export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | Explo
return Promise.resolve(element); return Promise.resolve(element);
} }
const promise = element.fetchChildren(this.fileService, this.explorerService).then(undefined, e => { const sortOrder = this.explorerService.sortOrder;
const promise = element.fetchChildren(sortOrder).then(undefined, e => {
if (element instanceof ExplorerItem && element.isRoot) { if (element instanceof ExplorerItem && element.isRoot) {
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
// Single folder create a dummy explorer item to show error // 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; placeholder.isError = true;
return [placeholder]; return [placeholder];
} else { } else {
@@ -921,17 +922,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
// Check for name collisions // Check for name collisions
const targetNames = new Set<string>(); const targetNames = new Set<string>();
const caseSensitive = this.fileService.hasCapability(target.resource, FileSystemProviderCapabilities.PathCaseSensitive);
if (targetStat.children) { if (targetStat.children) {
const ignoreCase = this.explorerService.shouldIgnoreCase(target.resource);
targetStat.children.forEach(child => { targetStat.children.forEach(child => {
targetNames.add(ignoreCase ? child.name.toLowerCase() : child.name); targetNames.add(caseSensitive ? child.name : child.name.toLowerCase());
}); });
} }
// Run add in sequence // Run add in sequence
const addPromisesFactory: ITask<Promise<void>>[] = []; const addPromisesFactory: ITask<Promise<void>>[] = [];
await Promise.all(resources.map(async resource => { 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))); const confirmationResult = await this.dialogService.confirm(getFileOverwriteConfirm(basename(resource)));
if (!confirmationResult.confirmed) { if (!confirmationResult.confirmed) {
return; return;
@@ -14,8 +14,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { memoize } from 'vs/base/common/decorators'; import { memoize } from 'vs/base/common/decorators';
import { Emitter, Event } from 'vs/base/common/event'; 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 { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources';
import { SortOrder } from 'vs/workbench/contrib/files/common/files';
export class ExplorerModel implements IDisposable { export class ExplorerModel implements IDisposable {
@@ -25,10 +25,10 @@ export class ExplorerModel implements IDisposable {
constructor( constructor(
private readonly contextService: IWorkspaceContextService, private readonly contextService: IWorkspaceContextService,
explorerService: IExplorerService fileService: IFileService
) { ) {
const setRoots = () => this._roots = this.contextService.getWorkspace().folders 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(); setRoots();
this._listener = this.contextService.onDidChangeWorkspaceFolders(() => { this._listener = this.contextService.onDidChangeWorkspaceFolders(() => {
@@ -83,11 +83,10 @@ export class ExplorerItem {
constructor( constructor(
public resource: URI, public resource: URI,
private readonly explorerService: IExplorerService, private readonly fileService: IFileService,
private _parent: ExplorerItem | undefined, private _parent: ExplorerItem | undefined,
private _isDirectory?: boolean, private _isDirectory?: boolean,
private _isSymbolicLink?: boolean, private _isSymbolicLink?: boolean,
private _isReadonly?: boolean,
private _name: string = basenameOrAuthority(resource), private _name: string = basenameOrAuthority(resource),
private _mtime?: number, private _mtime?: number,
) { ) {
@@ -112,7 +111,7 @@ export class ExplorerItem {
} }
get isReadonly(): boolean { get isReadonly(): boolean {
return !!this._isReadonly; return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly);
} }
get mtime(): number | undefined { get mtime(): number | undefined {
@@ -158,8 +157,8 @@ export class ExplorerItem {
return this === this.root; return this === this.root;
} }
static create(explorerService: IExplorerService, fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { static create(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); const stat = new ExplorerItem(raw.resource, fileService, parent, raw.isDirectory, raw.isSymbolicLink, raw.name, raw.mtime);
// Recursively add children if present // Recursively add children if present
if (stat.isDirectory) { if (stat.isDirectory) {
@@ -174,7 +173,7 @@ export class ExplorerItem {
// Recurse into children // Recurse into children
if (raw.children) { if (raw.children) {
for (let i = 0, len = raw.children.length; i < len; i++) { 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); stat.addChild(child);
} }
} }
@@ -208,7 +207,6 @@ export class ExplorerItem {
local._mtime = disk.mtime; local._mtime = disk.mtime;
local._isDirectoryResolved = disk._isDirectoryResolved; local._isDirectoryResolved = disk._isDirectoryResolved;
local._isSymbolicLink = disk.isSymbolicLink; local._isSymbolicLink = disk.isSymbolicLink;
local._isReadonly = disk.isReadonly;
local.isError = disk.isError; local.isError = disk.isError;
// Merge Children if resolved // Merge Children if resolved
@@ -259,14 +257,14 @@ export class ExplorerItem {
return this.children.get(this.getPlatformAwareName(name)); return this.children.get(this.getPlatformAwareName(name));
} }
async fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise<ExplorerItem[]> { async fetchChildren(sortOrder: SortOrder): Promise<ExplorerItem[]> {
if (!this._isDirectoryResolved) { if (!this._isDirectoryResolved) {
// Resolve metadata only when the mtime is needed since this can be expensive // Resolve metadata only when the mtime is needed since this can be expensive
// Mtime is only used when the sort order is 'modified' // Mtime is only used when the sort order is 'modified'
const resolveMetadata = explorerService.sortOrder === 'modified'; const resolveMetadata = sortOrder === SortOrder.Modified;
try { try {
const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); const stat = await this.fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata });
const resolved = ExplorerItem.create(explorerService, fileService, stat, this); const resolved = ExplorerItem.create(this.fileService, stat, this);
ExplorerItem.mergeLocalWithDisk(resolved, this); ExplorerItem.mergeLocalWithDisk(resolved, this);
} catch (e) { } catch (e) {
this.isError = true; this.isError = true;
@@ -306,7 +304,7 @@ export class ExplorerItem {
} }
private getPlatformAwareName(name: string): string { 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 { find(resource: URI): ExplorerItem | null {
// Return if path found // Return if path found
// For performance reasons try to do the comparison as fast as possible // 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) && 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))) { (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); 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 { export class NewExplorerItem extends ExplorerItem {
constructor(explorerService: IExplorerService, parent: ExplorerItem, isDirectory: boolean) { constructor(fileService: IFileService, parent: ExplorerItem, isDirectory: boolean) {
super(URI.file(''), explorerService, parent, isDirectory); super(URI.file(''), fileService, parent, isDirectory);
} }
} }
@@ -6,11 +6,11 @@
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { DisposableStore } from 'vs/base/common/lifecycle'; 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 { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel';
import { URI } from 'vs/base/common/uri'; 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 { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files';
import { dirname, hasToIgnoreCase } from 'vs/base/common/resources'; import { dirname } from 'vs/base/common/resources';
import { memoize } from 'vs/base/common/decorators'; import { memoize } from 'vs/base/common/decorators';
import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { ResourceGlobMatcher } from 'vs/workbench/common/resources';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -42,7 +42,6 @@ export class ExplorerService implements IExplorerService {
private _sortOrder: SortOrder; private _sortOrder: SortOrder;
private cutItems: ExplorerItem[] | undefined; private cutItems: ExplorerItem[] | undefined;
private contextProvider: IContextProvider | undefined; private contextProvider: IContextProvider | undefined;
private fileSystemProviderCaseSensitivity = new Map<string, boolean>();
private model: ExplorerModel; private model: ExplorerModel;
constructor( constructor(
@@ -55,25 +54,21 @@ export class ExplorerService implements IExplorerService {
) { ) {
this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); 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.model);
this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e))); this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e)));
this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e))); this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e)));
this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IFilesConfiguration>()))); this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IFilesConfiguration>())));
this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(e => {
const provider = e.provider; let affected = false;
if (e.added && provider) { this.model.roots.forEach(r => {
const alreadyRegistered = this.fileSystemProviderCaseSensitivity.has(e.scheme); if (r.resource.scheme === e.scheme) {
const readCapability = () => this.fileSystemProviderCaseSensitivity.set(e.scheme, !!(provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive)); affected = true;
readCapability(); r.forgetChildren();
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()));
} }
});
if (affected) {
this._onDidChangeItem.fire({ recursive: true });
} }
})); }));
this.disposables.add(this.model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); this.disposables.add(this.model.onDidChangeRoots(() => this._onDidChangeRoots.fire()));
@@ -131,15 +126,6 @@ export class ExplorerService implements IExplorerService {
return fileEventsFilter; return fileEventsFilter;
} }
shouldIgnoreCase(resource: URI): boolean {
const caseSensitive = this.fileSystemProviderCaseSensitivity.get(resource.scheme);
if (typeof caseSensitive === 'undefined') {
return hasToIgnoreCase(resource);
}
return !caseSensitive;
}
// IExplorerService methods // IExplorerService methods
findClosest(resource: URI): ExplorerItem | null { findClosest(resource: URI): ExplorerItem | null {
@@ -187,7 +173,7 @@ export class ExplorerService implements IExplorerService {
} }
// Stat needs to be resolved first and then revealed // 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); const workspaceFolder = this.contextService.getWorkspaceFolder(resource);
if (workspaceFolder === null) { if (workspaceFolder === null) {
return Promise.resolve(undefined); return Promise.resolve(undefined);
@@ -200,7 +186,7 @@ export class ExplorerService implements IExplorerService {
const stat = await this.fileService.resolve(rootUri, options); const stat = await this.fileService.resolve(rootUri, options);
// Convert to model // 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 // Update Input with disk Stat
ExplorerItem.mergeLocalWithDisk(modelStat, root); ExplorerItem.mergeLocalWithDisk(modelStat, root);
const item = root.find(resource); const item = root.find(resource);
@@ -244,11 +230,11 @@ export class ExplorerService implements IExplorerService {
const thenable: Promise<IFileStat | undefined> = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata }); const thenable: Promise<IFileStat | undefined> = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata });
thenable.then(stat => { thenable.then(stat => {
if (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); 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 // Make sure to remove any previous version of the file if any
p.removeChild(childElement); p.removeChild(childElement);
p.addChild(childElement); p.addChild(childElement);
@@ -361,7 +347,7 @@ export class ExplorerService implements IExplorerService {
} }
// Handle updated files/folders if we sort by modified // Handle updated files/folders if we sort by modified
if (this._sortOrder === SortOrderConfiguration.MODIFIED) { if (this._sortOrder === SortOrder.Modified) {
const updated = e.getUpdated(); const updated = e.getUpdated();
// Check updated: Refresh if updated file/folder part of resolved root // 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 { private filterToViewRelevantEvents(e: FileChangesEvent): FileChangesEvent {
return new FileChangesEvent(e.changes.filter(change => { 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 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 { 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) { if (this._sortOrder !== configSortOrder) {
const shouldRefresh = this._sortOrder !== undefined; const shouldRefresh = this._sortOrder !== undefined;
this._sortOrder = configSortOrder; this._sortOrder = configSortOrder;
+9 -11
View File
@@ -24,6 +24,7 @@ import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
import { once } from 'vs/base/common/functional'; import { once } from 'vs/base/common/functional';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { localize } from 'vs/nls';
/** /**
* Explorer viewlet id. * Explorer viewlet id.
@@ -33,7 +34,7 @@ export const VIEWLET_ID = 'workbench.view.explorer';
/** /**
* Explorer viewlet container. * Explorer viewlet container.
*/ */
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('explore', "Explorer") }, ViewContainerLocation.Sidebar);
export interface IExplorerService { export interface IExplorerService {
_serviceBrand: undefined; _serviceBrand: undefined;
@@ -55,7 +56,6 @@ export interface IExplorerService {
refresh(): void; refresh(): void;
setToCopy(stats: ExplorerItem[], cut: boolean): void; setToCopy(stats: ExplorerItem[], cut: boolean): void;
isCut(stat: ExplorerItem): boolean; 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. * 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; isDirectory?: boolean;
} }
export const SortOrderConfiguration = { export const enum SortOrder {
DEFAULT: 'default', Default = 'default',
MIXED: 'mixed', Mixed = 'mixed',
FILES_FIRST: 'filesFirst', FilesFirst = 'filesFirst',
TYPE: 'type', Type = 'type',
MODIFIED: 'modified' Modified = 'modified'
}; }
export type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified';
export class TextFileContentProvider extends Disposable implements ITextModelContentProvider { export class TextFileContentProvider extends Disposable implements ITextModelContentProvider {
private readonly fileWatcherDisposable = this._register(new MutableDisposable()); private readonly fileWatcherDisposable = this._register(new MutableDisposable());
@@ -28,7 +28,6 @@ class ServiceAccessor {
} }
suite('Files - FileEditorInput', () => { suite('Files - FileEditorInput', () => {
let instantiationService: IInstantiationService; let instantiationService: IInstantiationService;
let accessor: ServiceAccessor; let accessor: ServiceAccessor;
@@ -10,18 +10,11 @@ import { join } from 'vs/base/common/path';
import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions'; import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions';
import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
import { toResource } from 'vs/base/test/common/utils'; import { toResource } from 'vs/base/test/common/utils';
import { hasToIgnoreCase } from 'vs/base/common/resources'; import { TestFileService } from 'vs/workbench/test/workbenchTestServices';
import { IExplorerService } from 'vs/workbench/contrib/files/common/files';
class MockExplorerService {
shouldIgnoreCase(resource: URI) {
return hasToIgnoreCase(resource);
}
}
const mockExplorerService = new MockExplorerService() as IExplorerService;
const fileService = new TestFileService();
function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { 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 () { suite('Files - View Model', function () {
@@ -252,19 +245,19 @@ suite('Files - View Model', function () {
}); });
test('Merge Local with Disk', 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 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')), mockExplorerService, undefined, true, false, false, 'to', Date.now()); const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, 'to', Date.now());
// Merge Properties // Merge Properties
ExplorerItem.mergeLocalWithDisk(merge2, merge1); ExplorerItem.mergeLocalWithDisk(merge2, merge1);
assert.strictEqual(merge1.mtime, merge2.mtime); assert.strictEqual(merge1.mtime, merge2.mtime);
// Merge Child when isDirectoryResolved=false is a no-op // 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); ExplorerItem.mergeLocalWithDisk(merge2, merge1);
// Merge Child with isDirectoryResolved=true // 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.removeChild(child);
merge2.addChild(child); merge2.addChild(child);
(<any>merge2)._isDirectoryResolved = true; (<any>merge2)._isDirectoryResolved = true;
@@ -7,6 +7,8 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
export default { export default {
MARKERS_PANEL_ID: 'workbench.panel.markers', 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_ACTION_ID: 'problems.action.copy',
MARKER_COPY_MESSAGE_ACTION_ID: 'problems.action.copyMessage', MARKER_COPY_MESSAGE_ACTION_ID: 'problems.action.copyMessage',
RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID: 'problems.action.copyRelatedInformationMessage', RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID: 'problems.action.copyRelatedInformationMessage',
@@ -12,11 +12,11 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; 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 { 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 { 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 Constants from 'vs/workbench/contrib/markers/browser/constants';
import Messages from 'vs/workbench/contrib/markers/browser/messages'; import Messages from 'vs/workbench/contrib/markers/browser/messages';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; 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 { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar';
import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers';
import { CommandsRegistry } from 'vs/platform/commands/common/commands'; 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); registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false);
@@ -41,8 +51,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
primary: KeyMod.WinCtrl | KeyCode.Enter primary: KeyMod.WinCtrl | KeyCode.Enter
}, },
handler: (accessor, args: any) => { handler: (accessor, args: any) => {
const markersPanel = (<MarkersPanel>accessor.get(IPanelService).getActivePanel()); const markersView = getMarkersView(accessor.get(IPanelService))!;
markersPanel.openFileAtElement(markersPanel.getFocusElement(), false, true, true); markersView.openFileAtElement(markersView.getFocusElement(), false, true, true);
} }
}); });
@@ -62,10 +72,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
when: Constants.MarkerFocusContextKey, when: Constants.MarkerFocusContextKey,
primary: KeyMod.CtrlCmd | KeyCode.US_DOT, primary: KeyMod.CtrlCmd | KeyCode.US_DOT,
handler: (accessor, args: any) => { handler: (accessor, args: any) => {
const markersPanel = (<MarkersPanel>accessor.get(IPanelService).getActivePanel()); const markersView = getMarkersView(accessor.get(IPanelService))!;
const focusedElement = markersPanel.getFocusElement(); const focusedElement = markersView.getFocusElement();
if (focusedElement instanceof Marker) { if (focusedElement instanceof Marker) {
markersPanel.showQuickFixes(focusedElement); markersView.showQuickFixes(focusedElement);
} }
} }
}); });
@@ -91,11 +101,45 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfigurat
}); });
// markers view container
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_PANEL_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, ViewContainerLocation.Panel);
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
id: Constants.MARKERS_VIEW_ID,
name: Messages.MARKERS_PANEL_TITLE_PROBLEMS,
canToggleVisibility: false,
ctorDescriptor: { ctor: MarkersView },
}], VIEW_CONTAINER);
// markers panel // 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<PanelRegistry>(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( Registry.as<PanelRegistry>(PanelExtensions.Panels).registerPanel(PanelDescriptor.create(
MarkersPanel, MarkersPanel,
Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_ID,
Messages.MARKERS_PANEL_TITLE_PROBLEMS, VIEW_CONTAINER.name,
'markersPanel', 'markersPanel',
10, 10,
ToggleMarkersPanelAction.ID ToggleMarkersPanelAction.ID
@@ -183,10 +227,9 @@ registerAction({
registerAction({ registerAction({
id: Constants.MARKERS_PANEL_SHOW_MULTILINE_MESSAGE, id: Constants.MARKERS_PANEL_SHOW_MULTILINE_MESSAGE,
handler(accessor) { handler(accessor) {
const panelService = accessor.get(IPanelService); const markersView = getMarkersView(accessor.get(IPanelService));
const panel = panelService.getActivePanel(); if (markersView) {
if (panel instanceof MarkersPanel) { markersView.markersViewModel.multiline = true;
panel.markersViewModel.multiline = true;
} }
}, },
title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' }, title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' },
@@ -199,10 +242,9 @@ registerAction({
registerAction({ registerAction({
id: Constants.MARKERS_PANEL_SHOW_SINGLELINE_MESSAGE, id: Constants.MARKERS_PANEL_SHOW_SINGLELINE_MESSAGE,
handler(accessor) { handler(accessor) {
const panelService = accessor.get(IPanelService); const markersView = getMarkersView(accessor.get(IPanelService));
const panel = panelService.getActivePanel(); if (markersView) {
if (panel instanceof MarkersPanel) { markersView.markersViewModel.multiline = false;
panel.markersViewModel.multiline = false;
} }
}, },
title: { value: localize('show singleline', "Show message in single line"), original: 'Problems: Show message in single line' }, 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) { async function copyMarker(panelService: IPanelService, clipboardService: IClipboardService) {
const activePanel = panelService.getActivePanel(); const markersView = getMarkersView(panelService);
if (activePanel instanceof MarkersPanel) { if (markersView) {
const element = (<MarkersPanel>activePanel).getFocusElement(); const element = markersView.getFocusElement();
if (element instanceof Marker) { if (element instanceof Marker) {
await clipboardService.writeText(`${element}`); await clipboardService.writeText(`${element}`);
} }
@@ -224,9 +266,9 @@ async function copyMarker(panelService: IPanelService, clipboardService: IClipbo
} }
async function copyMessage(panelService: IPanelService, clipboardService: IClipboardService) { async function copyMessage(panelService: IPanelService, clipboardService: IClipboardService) {
const activePanel = panelService.getActivePanel(); const markersView = getMarkersView(panelService);
if (activePanel instanceof MarkersPanel) { if (markersView) {
const element = (<MarkersPanel>activePanel).getFocusElement(); const element = markersView.getFocusElement();
if (element instanceof Marker) { if (element instanceof Marker) {
await clipboardService.writeText(element.marker.message); await clipboardService.writeText(element.marker.message);
} }
@@ -234,9 +276,9 @@ async function copyMessage(panelService: IPanelService, clipboardService: IClipb
} }
async function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService) { async function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService) {
const activePanel = panelService.getActivePanel(); const markersView = getMarkersView(panelService);
if (activePanel instanceof MarkersPanel) { if (markersView) {
const element = (<MarkersPanel>activePanel).getFocusElement(); const element = markersView.getFocusElement();
if (element instanceof RelatedInformation) { if (element instanceof RelatedInformation) {
await clipboardService.writeText(element.raw.message); await clipboardService.writeText(element.raw.message);
} }
@@ -244,16 +286,16 @@ async function copyRelatedInformationMessage(panelService: IPanelService, clipbo
} }
function focusProblemsView(panelService: IPanelService) { function focusProblemsView(panelService: IPanelService) {
const activePanel = panelService.getActivePanel(); const markersView = getMarkersView(panelService);
if (activePanel instanceof MarkersPanel) { if (markersView) {
activePanel.focus(); markersView.focus();
} }
} }
function focusProblemsFilter(panelService: IPanelService) { function focusProblemsFilter(panelService: IPanelService): void {
const activePanel = panelService.getActivePanel(); const markersView = getMarkersView(panelService);
if (activePanel instanceof MarkersPanel) { if (markersView) {
activePanel.focusFilter(); markersView.focusFilter();
} }
} }
@@ -17,7 +17,7 @@ import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; 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 { ILabelService } from 'vs/platform/label/common/label';
import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { dirname, basename, isEqual } from 'vs/base/common/resources';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
@@ -9,12 +9,12 @@ import { URI } from 'vs/base/common/uri';
import * as dom from 'vs/base/browser/dom'; import * as dom from 'vs/base/browser/dom';
import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; import { IAction, IActionViewItem, Action } from 'vs/base/common/actions';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; 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 { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import Constants from 'vs/workbench/contrib/markers/browser/constants'; import Constants from 'vs/workbench/contrib/markers/browser/constants';
import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; 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 { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import Messages from 'vs/workbench/contrib/markers/browser/messages'; import Messages from 'vs/workbench/contrib/markers/browser/messages';
import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; 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 { ResourceLabels } from 'vs/workbench/browser/labels';
import { IMarker } from 'vs/platform/markers/common/markers'; import { IMarker } from 'vs/platform/markers/common/markers';
import { withUndefinedAsNull } from 'vs/base/common/types'; 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 { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { KeyCode } from 'vs/base/common/keyCodes'; import { KeyCode } from 'vs/base/common/keyCodes';
import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; 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 <MarkersView>activePanel.getViewPaneContainer().getView(Constants.MARKERS_VIEW_ID);
}
return undefined;
}
function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator<ITreeElement<TreeElement>> { function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator<ITreeElement<TreeElement>> {
const markersIt = Iterator.fromArray(resourceMarkers.markers); 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 lastSelectedRelativeTop: number = 0;
private currentActiveResource: URI | null = null; private currentActiveResource: URI | null = null;
@@ -69,11 +79,10 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
private readonly rangeHighlightDecorations: RangeHighlightDecorations; private readonly rangeHighlightDecorations: RangeHighlightDecorations;
private readonly filter: Filter; private readonly filter: Filter;
private tree!: MarkersTree; private tree: MarkersTree | undefined;
private filterActionBar!: ActionBar; private filterActionBar: ActionBar | undefined;
private messageBoxContainer!: HTMLElement; private messageBoxContainer: HTMLElement | undefined;
private ariaLabelElement!: HTMLElement; private ariaLabelElement: HTMLElement | undefined;
private readonly collapseAllAction: IAction; private readonly collapseAllAction: IAction;
private readonly filterAction: MarkersFilterAction; private readonly filterAction: MarkersFilterAction;
@@ -88,23 +97,25 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
readonly markersViewModel: MarkersViewModel; readonly markersViewModel: MarkersViewModel;
private isSmallLayout: boolean = false; private isSmallLayout: boolean = false;
readonly onDidChangeVisibility = this.onDidChangeBodyVisibility;
constructor( constructor(
options: IViewPaneOptions,
@IInstantiationService private readonly instantiationService: IInstantiationService, @IInstantiationService private readonly instantiationService: IInstantiationService,
@IEditorService private readonly editorService: IEditorService, @IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService private readonly telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService, @IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService,
@IStorageService storageService: IStorageService,
@IContextKeyService contextKeyService: IContextKeyService, @IContextKeyService contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IContextMenuService private readonly contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IMenuService private readonly menuService: IMenuService, @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.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.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline']));
this._register(this.markersViewModel.onDidChange(marker => this.onDidChangeViewState(marker))); this._register(this.markersViewModel.onDidChange(marker => this.onDidChangeViewState(marker)));
this.setCurrentActiveEditor(); this.setCurrentActiveEditor();
@@ -125,9 +136,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
})); }));
} }
public create(parent: HTMLElement): void { public renderBody(parent: HTMLElement): void {
super.create(parent);
dom.addClass(parent, 'markers-panel'); dom.addClass(parent, 'markers-panel');
@@ -152,35 +161,41 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
})); }));
this.filterActionBar.push(this.filterAction); this.filterActionBar!.push(this.filterAction);
this.render(); this.renderContent();
} }
public getTitle(): string { public getTitle(): string {
return Messages.MARKERS_PANEL_TITLE_PROBLEMS; return Messages.MARKERS_PANEL_TITLE_PROBLEMS;
} }
public layout(dimension: dom.Dimension): void { public layoutBody(height: number, width: number): void {
const wasSmallLayout = this.isSmallLayout; const wasSmallLayout = this.isSmallLayout;
this.isSmallLayout = dimension.width < 600; this.isSmallLayout = width < 600;
if (this.isSmallLayout !== wasSmallLayout) { if (this.isSmallLayout !== wasSmallLayout) {
this.updateTitleArea(); this.updateActions();
if (this.filterActionBar) {
dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout);
} }
const height = this.isSmallLayout ? dimension.height - 44 : dimension.height; }
this.tree.layout(height, dimension.width); const contentHeight = this.isSmallLayout ? height - 44 : height;
this.messageBoxContainer.style.height = `${height}px`; if (this.tree) {
this.filterAction.layout(this.isSmallLayout ? dimension.width : dimension.width - 200); 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 { public focus(): void {
if (this.tree.getHTMLElement() === document.activeElement) { if (this.tree && this.tree.getHTMLElement() === document.activeElement) {
return; return;
} }
if (this.isEmpty()) { if (this.isEmpty() && this.messageBoxContainer) {
this.messageBoxContainer.focus(); this.messageBoxContainer.focus();
} else { } else if (this.tree) {
this.tree.getHTMLElement().focus(); this.tree.getHTMLElement().focus();
} }
} }
@@ -243,7 +258,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
private refreshPanel(markerOrChange?: Marker | MarkerChangesEvent): void { private refreshPanel(markerOrChange?: Marker | MarkerChangesEvent): void {
if (this.isVisible()) { if (this.isVisible() && this.tree) {
this.cachedFilterStats = undefined; this.cachedFilterStats = undefined;
if (markerOrChange) { if (markerOrChange) {
@@ -277,6 +292,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
private resetTree(): void { private resetTree(): void {
if (!this.tree) {
return;
}
let resourceMarkers: ResourceMarkers[] = []; let resourceMarkers: ResourceMarkers[] = [];
if (this.filterAction.activeFile) { if (this.filterAction.activeFile) {
if (this.currentActiveResource) { if (this.currentActiveResource) {
@@ -294,11 +312,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
private updateFilter() { private updateFilter() {
this.cachedFilterStats = undefined; this.cachedFilterStats = undefined;
this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions(), this.filterAction.showWarnings, this.filterAction.showErrors, this.filterAction.showInfos); this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions(), this.filterAction.showWarnings, this.filterAction.showErrors, this.filterAction.showInfos);
if (this.tree) {
this.tree.refilter(); this.tree.refilter();
}
this._onDidFilter.fire(); this._onDidFilter.fire();
const { total, filtered } = this.getFilterStats(); const { total, filtered } = this.getFilterStats();
if (this.tree) {
this.tree.toggleVisibility(total === 0 || filtered === 0); this.tree.toggleVisibility(total === 0 || filtered === 0);
}
this.renderMessage(); this.renderMessage();
} }
@@ -354,7 +376,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
}; };
this.tree = this._register(this.instantiationService.createInstance(MarkersTree, this.tree = this._register(this.instantiationService.createInstance(MarkersTree,
'MarkersPanel', 'MarkersView',
dom.append(parent, dom.$('.tree-container.show-file-icons')), dom.append(parent, dom.$('.tree-container.show-file-icons')),
virtualDelegate, virtualDelegate,
renderers, renderers,
@@ -412,7 +434,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
})); }));
this._register(Event.any<any>(this.tree.onDidChangeSelection, this.tree.onDidChangeFocus)(() => { this._register(Event.any<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) { for (const element of elements) {
if (element instanceof Marker) { if (element instanceof Marker) {
const viewModel = this.markersViewModel.getViewModel(element); const viewModel = this.markersViewModel.getViewModel(element);
@@ -425,12 +447,14 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
private collapseAll(): void { private collapseAll(): void {
if (this.tree) {
this.tree.collapseAll(); this.tree.collapseAll();
this.tree.setSelection([]); this.tree.setSelection([]);
this.tree.setFocus([]); this.tree.setFocus([]);
this.tree.getHTMLElement().focus(); this.tree.getHTMLElement().focus();
this.tree.focusFirst(); this.tree.focusFirst();
} }
}
private createListeners(): void { private createListeners(): void {
this._register(Event.any<MarkerChangesEvent | void>(this.markersWorkbenchService.markersModel.onDidChange, this.editorService.onDidActiveEditorChange)(changes => { this._register(Event.any<MarkerChangesEvent | void>(this.markersWorkbenchService.markersModel.onDidChange, this.editorService.onDidActiveEditorChange)(changes => {
@@ -440,7 +464,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
this.onActiveEditorChanged(); this.onActiveEditorChanged();
} }
})); }));
if (this.tree) {
this._register(this.tree.onDidChangeSelection(() => this.onSelected())); this._register(this.tree.onDidChangeSelection(() => this.onSelected()));
}
this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => {
this.reportFilteringUsed(); this.reportFilteringUsed();
if (event.activeFile) { if (event.activeFile) {
@@ -499,9 +525,11 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
private onSelected(): void { private onSelected(): void {
if (this.tree) {
let selection = this.tree.getSelection(); let selection = this.tree.getSelection();
if (selection && selection.length > 0) { if (selection && selection.length > 0) {
this.lastSelectedRelativeTop = this.tree.getRelativeTop(selection[0]) || 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; return total === 0 || filtered === 0;
} }
private render(): void { private renderContent(): void {
this.cachedFilterStats = undefined; this.cachedFilterStats = undefined;
this.resetTree(); this.resetTree();
if (this.tree) {
this.tree.toggleVisibility(this.isEmpty()); this.tree.toggleVisibility(this.isEmpty());
}
this.renderMessage(); this.renderMessage();
} }
private renderMessage(): void { private renderMessage(): void {
if (!this.messageBoxContainer || !this.ariaLabelElement) {
return;
}
dom.clearNode(this.messageBoxContainer); dom.clearNode(this.messageBoxContainer);
const { total, filtered } = this.getFilterStats(); const { total, filtered } = this.getFilterStats();
@@ -567,19 +600,19 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
e.stopPropagation(); 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) { private renderNoProblemsMessageForActiveFile(container: HTMLElement) {
const span = dom.append(container, dom.$('span')); const span = dom.append(container, dom.$('span'));
span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT; 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) { private renderNoProblemsMessage(container: HTMLElement) {
const span = dom.append(container, dom.$('span')); const span = dom.append(container, dom.$('span'));
span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; 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 { private clearFilters(): void {
@@ -592,7 +625,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
private autoReveal(focus: boolean = false): void { private autoReveal(focus: boolean = false): void {
// No need to auto reveal if active file filter is on // No need to auto reveal if active file filter is on
if (this.filterAction.activeFile) { if (this.filterAction.activeFile || !this.tree) {
return; return;
} }
let autoReveal = this.configurationService.getValue<boolean>('problems.autoReveal'); let autoReveal = this.configurationService.getValue<boolean>('problems.autoReveal');
@@ -625,6 +658,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
private hasSelectedMarkerFor(resource: ResourceMarkers): boolean { private hasSelectedMarkerFor(resource: ResourceMarkers): boolean {
if (this.tree) {
let selectedElement = this.tree.getSelection(); let selectedElement = this.tree.getSelection();
if (selectedElement && selectedElement.length > 0) { if (selectedElement && selectedElement.length > 0) {
if (selectedElement[0] instanceof Marker) { if (selectedElement[0] instanceof Marker) {
@@ -633,18 +667,19 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
} }
} }
}
return false; return false;
} }
private updateRangeHighlights() { private updateRangeHighlights() {
this.rangeHighlightDecorations.removeHighlightRange(); this.rangeHighlightDecorations.removeHighlightRange();
if (this.tree.getHTMLElement() === document.activeElement) { if (this.tree && this.tree.getHTMLElement() === document.activeElement) {
this.highlightCurrentSelectedMarkerRange(); this.highlightCurrentSelectedMarkerRange();
} }
} }
private highlightCurrentSelectedMarkerRange() { private highlightCurrentSelectedMarkerRange() {
const selections = this.tree.getSelection(); const selections = this.tree ? this.tree.getSelection() : [];
if (selections.length !== 1) { if (selections.length !== 1) {
return; return;
@@ -680,7 +715,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
}, },
onHide: (wasCancelled?: boolean) => { onHide: (wasCancelled?: boolean) => {
if (wasCancelled) { 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(); const groups = menu.getActions();
menu.dispose(); menu.dispose();
@@ -715,7 +750,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
public getFocusElement() { public getFocusElement() {
return this.tree.getFocus()[0]; return this.tree ? this.tree.getFocus()[0] : undefined;
} }
public getActionViewItem(action: IAction): IActionViewItem | undefined { public getActionViewItem(action: IAction): IActionViewItem | undefined {
@@ -738,8 +773,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
private computeFilterStats(): { total: number; filtered: number; } { private computeFilterStats(): { total: number; filtered: number; } {
const root = this.tree.getNode();
let filtered = 0; let filtered = 0;
if (this.tree) {
const root = this.tree.getNode();
for (const resourceMarkerNode of root.children) { for (const resourceMarkerNode of root.children) {
for (const markerNode of resourceMarkerNode.children) { for (const markerNode of resourceMarkerNode.children) {
@@ -748,6 +784,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
} }
} }
} }
}
return { total: this.markersWorkbenchService.markersModel.total, filtered }; return { total: this.markersWorkbenchService.markersModel.total, filtered };
} }
@@ -776,7 +813,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
this.telemetryService.publicLog('problems.filter', data); this.telemetryService.publicLog('problems.filter', data);
} }
protected saveState(): void { saveState(): void {
this.panelState['filter'] = this.filterAction.filterText; this.panelState['filter'] = this.filterAction.filterText;
this.panelState['filterHistory'] = this.filterAction.filterHistory; this.panelState['filterHistory'] = this.filterAction.filterHistory;
this.panelState['showErrors'] = this.filterAction.showErrors; this.panelState['showErrors'] = this.filterAction.showErrors;
@@ -10,10 +10,8 @@ import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { KeyCode } from 'vs/base/common/keyCodes'; import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; 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 Messages from 'vs/workbench/contrib/markers/browser/messages';
import Constants from 'vs/workbench/contrib/markers/browser/constants'; 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 { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IThemeService, registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService'; import { IThemeService, registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService';
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; 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 { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; 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 { export class ShowProblemsPanelAction extends Action {
public static readonly ID = 'workbench.action.problems.focus'; public static readonly ID = 'workbench.action.problems.focus';
@@ -304,6 +289,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem {
this.element.className = this.action.class || ''; this.element.className = this.action.class || '';
this.createInput(this.element); this.createInput(this.element);
this.createControls(this.element); this.createControls(this.element);
this.updateClass();
this.adjustInputBox(); this.adjustInputBox();
} }
@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { isEqual } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/resources';
import { endsWith } from 'vs/base/common/strings'; 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 { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; 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 { 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.IJSONContributionRegistry>(JSONContributionRegistry.Extensions.JSONContribution); const schemaRegistry = Registry.as<JSONContributionRegistry.IJSONContributionRegistry>(JSONContributionRegistry.Extensions.JSONContribution);
@@ -147,3 +149,27 @@ export class PreferencesContribution implements IWorkbenchContribution {
dispose(this.settingsListener); dispose(this.settingsListener);
} }
} }
const registry = Registry.as<IConfigurationRegistry>(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
},
}
});
@@ -6,7 +6,3 @@
.customview-tree .tunnel-view-label { .customview-tree .tunnel-view-label {
flex: 1; flex: 1;
} }
.customview-tree .tunnel-view-label .action-label.codicon {
margin-top: 4px;
}
@@ -356,7 +356,7 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer {
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create(
RemoteViewlet, RemoteViewlet,
VIEWLET_ID, VIEWLET_ID,
nls.localize('remote.explorer', "Remote Explorer"), VIEW_CONTAINER.name,
'codicon-remote-explorer', 'codicon-remote-explorer',
4 4
)); ));
@@ -26,7 +26,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { ActionRunner, IAction } from 'vs/base/common/actions'; import { ActionRunner, IAction } from 'vs/base/common/actions';
import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/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 { 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 { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
@@ -105,13 +105,13 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
get forwarded(): TunnelItem[] { get forwarded(): TunnelItem[] {
return Array.from(this.model.forwarded.values()).map(tunnel => { 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[] { get detected(): TunnelItem[] {
return Array.from(this.model.detected.values()).map(tunnel => { 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 => { return this.model.candidates.then(values => {
const candidates: TunnelItem[] = []; const candidates: TunnelItem[] = [];
values.forEach(value => { values.forEach(value => {
if (!this.model.forwarded.has(value.port) && !this.model.detected.has(value.port)) { const key = MakeAddress(value.host, value.port);
candidates.push(new TunnelItem(TunnelType.Candidate, value.port, undefined, false, undefined, value.detail)); 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; return candidates;
@@ -185,7 +186,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
} }
private isTunnelItem(item: ITunnelGroup | ITunnelItem): item is ITunnelItem { private isTunnelItem(item: ITunnelGroup | ITunnelItem): item is ITunnelItem {
return !!((<ITunnelItem>item).remote); return !!((<ITunnelItem>item).remotePort);
} }
renderElement(element: ITreeNode<ITunnelGroup | ITunnelItem, ITunnelGroup | ITunnelItem>, index: number, templateData: ITunnelTemplateData): void { renderElement(element: ITreeNode<ITunnelGroup | ITunnelItem, ITunnelGroup | ITunnelItem>, index: number, templateData: ITunnelTemplateData): void {
@@ -196,7 +197,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
templateData.actionBar.clear(); templateData.actionBar.clear();
let editableData: IEditableData | undefined; let editableData: IEditableData | undefined;
if (this.isTunnelItem(node)) { if (this.isTunnelItem(node)) {
editableData = this.remoteExplorerService.getEditableData(node.remote); editableData = this.remoteExplorerService.getEditableData(node.remoteHost, node.remotePort);
if (editableData) { if (editableData) {
templateData.iconLabel.element.style.display = 'none'; templateData.iconLabel.element.style.display = 'none';
this.renderInputBox(templateData.container, editableData); this.renderInputBox(templateData.container, editableData);
@@ -204,7 +205,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
templateData.iconLabel.element.style.display = 'flex'; templateData.iconLabel.element.style.display = 'flex';
this.renderTunnel(node, templateData); this.renderTunnel(node, templateData);
} }
} else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined))) { } else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined, undefined))) {
templateData.iconLabel.element.style.display = 'none'; templateData.iconLabel.element.style.display = 'none';
this.renderInputBox(templateData.container, editableData); this.renderInputBox(templateData.container, editableData);
} else { } else {
@@ -338,7 +339,8 @@ interface ITunnelGroup {
interface ITunnelItem { interface ITunnelItem {
tunnelType: TunnelType; tunnelType: TunnelType;
remote: number; remoteHost: string;
remotePort: number;
localAddress?: string; localAddress?: string;
name?: string; name?: string;
closeable?: boolean; closeable?: boolean;
@@ -349,7 +351,8 @@ interface ITunnelItem {
class TunnelItem implements ITunnelItem { class TunnelItem implements ITunnelItem {
constructor( constructor(
public tunnelType: TunnelType, public tunnelType: TunnelType,
public remote: number, public remoteHost: string,
public remotePort: number,
public localAddress?: string, public localAddress?: string,
public closeable?: boolean, public closeable?: boolean,
public name?: string, public name?: string,
@@ -359,9 +362,9 @@ class TunnelItem implements ITunnelItem {
if (this.name) { if (this.name) {
return nls.localize('remote.tunnelsView.forwardedPortLabel0', "{0}", this.name); return nls.localize('remote.tunnelsView.forwardedPortLabel0', "{0}", this.name);
} else if (this.localAddress) { } else if (this.localAddress) {
return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remote, this.localAddress); return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remotePort, this.localAddress);
} else { } else {
return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remote); return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remotePort);
} }
} }
@@ -369,7 +372,7 @@ class TunnelItem implements ITunnelItem {
if (this._description) { if (this._description) {
return this._description; return this._description;
} else if (this.name) { } else if (this.name) {
return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} to {1}", this.remote, this.localAddress); return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} to {1}", this.remotePort, this.localAddress);
} }
return undefined; return undefined;
} }
@@ -469,12 +472,12 @@ export class TunnelPanel extends ViewPane {
this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => { this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => {
if (e.element && (e.element.tunnelType === TunnelType.Add)) { 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 => { this._register(this.remoteExplorerService.onDidChangeEditable(async e => {
const isEditing = !!this.remoteExplorerService.getEditableData(e); const isEditing = !!this.remoteExplorerService.getEditableData(e.host, e.port);
if (!isEditing) { if (!isEditing) {
dom.removeClass(treeContainer, 'highlight'); dom.removeClass(treeContainer, 'highlight');
@@ -575,12 +578,12 @@ namespace LabelTunnelAction {
return async (accessor, arg) => { return async (accessor, arg) => {
if (arg instanceof TunnelItem) { if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService); const remoteExplorerService = accessor.get(IRemoteExplorerService);
remoteExplorerService.setEditable(arg.remote, { remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, {
onFinish: (value, success) => { onFinish: (value, success) => {
if (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, validationMessage: () => null,
placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"), placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"),
@@ -595,31 +598,52 @@ namespace LabelTunnelAction {
namespace ForwardPortAction { namespace ForwardPortAction {
export const ID = 'remote.tunnel.forward'; export const ID = 'remote.tunnel.forward';
export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port"); 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 { export function handler(): ICommandHandler {
return async (accessor, arg) => { return async (accessor, arg) => {
const remoteExplorerService = accessor.get(IRemoteExplorerService); const remoteExplorerService = accessor.get(IRemoteExplorerService);
if (arg instanceof TunnelItem) { 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 { } else {
const viewsService = accessor.get(IViewsService); const viewsService = accessor.get(IViewsService);
const quickInputService = accessor.get(IQuickInputService);
await viewsService.openView(TunnelPanel.ID, true); await viewsService.openView(TunnelPanel.ID, true);
remoteExplorerService.setEditable(undefined, { const value = await quickInputService.input({
onFinish: (value, success) => { prompt: forwardPrompt,
if (success) { validateInput: (value) => Promise.resolve(validateInput(value))
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")
}); });
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) => { return async (accessor, arg) => {
if (arg instanceof TunnelItem) { if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService); 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) { if (arg instanceof TunnelItem) {
const model = accessor.get(IRemoteExplorerService).tunnelModel; const model = accessor.get(IRemoteExplorerService).tunnelModel;
const openerService = accessor.get(IOpenerService); 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; 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 openerService.open(URI.parse('http://' + address));
} }
return Promise.resolve(); return Promise.resolve();
@@ -668,7 +693,7 @@ namespace CopyAddressAction {
if (arg instanceof TunnelItem) { if (arg instanceof TunnelItem) {
const model = accessor.get(IRemoteExplorerService).tunnelModel; const model = accessor.get(IRemoteExplorerService).tunnelModel;
const clipboard = accessor.get(IClipboardService); const clipboard = accessor.get(IClipboardService);
const address = model.address(arg.remote); const address = model.address(arg.remoteHost, arg.remotePort);
if (address) { if (address) {
await clipboard.writeText(address.toString()); await clipboard.writeText(address.toString());
} }
@@ -20,11 +20,11 @@ import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExte
export const VIEWLET_ID = 'workbench.view.remote'; export const VIEWLET_ID = 'workbench.view.remote';
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(
VIEWLET_ID,
ViewContainerLocation.Sidebar,
true,
undefined,
{ {
id: VIEWLET_ID,
name: localize('remote.explorer', "Remote Explorer"),
hideIfEmpty: true,
viewOrderDelegate: {
getOrder: (group?: string) => { getOrder: (group?: string) => {
if (!group) { if (!group) {
return undefined; // {{SQL CARBON EDIT}} strict-null-checks return undefined; // {{SQL CARBON EDIT}} strict-null-checks
@@ -44,7 +44,7 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry
return undefined; // {{SQL CARBON EDIT}} strict-null-checks return undefined; // {{SQL CARBON EDIT}} strict-null-checks
} }
} }
); }, ViewContainerLocation.Sidebar);
export class LabelContribution implements IWorkbenchContribution { export class LabelContribution implements IWorkbenchContribution {
constructor( constructor(
@@ -401,6 +401,11 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
type: 'boolean', type: 'boolean',
markdownDescription: nls.localize('remote.downloadExtensionsLocally', "When enabled extensions are downloaded locally and installed on remote."), markdownDescription: nls.localize('remote.downloadExtensionsLocally', "When enabled extensions are downloaded locally and installed on remote."),
default: false default: false
},
'remote.restoreForwardedPorts': {
type: 'boolean',
markdownDescription: nls.localize('remote.restoreForwardedPorts', "Restores the ports you forwarded in a workspace."),
default: false
} }
} }
}); });
@@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { DirtyDiffWorkbenchController } from './dirtydiffDecorator';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; 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 { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
@@ -41,7 +41,7 @@ Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create(
SCMViewlet, SCMViewlet,
VIEWLET_ID, VIEWLET_ID,
localize('source control', "Source Control"), VIEW_CONTAINER.name,
'codicon-source-control', 'codicon-source-control',
12 // {{SQL CARBON EDIT}} 12 // {{SQL CARBON EDIT}}
)); ));
@@ -116,7 +116,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode
@IWorkspaceContextService protected contextService: IWorkspaceContextService, @IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IContextKeyService contextKeyService: IContextKeyService, @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.menus = instantiationService.createInstance(SCMMenus, undefined);
this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this));
+2 -1
View File
@@ -11,9 +11,10 @@ import { Command } from 'vs/editor/common/modes';
import { ISequence } from 'vs/base/common/sequence'; import { ISequence } from 'vs/base/common/sequence';
import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { localize } from 'vs/nls';
export const VIEWLET_ID = 'workbench.view.scm'; export const VIEWLET_ID = 'workbench.view.scm';
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('source control', "Source Control"), }, ViewContainerLocation.Sidebar);
export interface IBaselineResourceProvider { export interface IBaselineResourceProvider {
getBaselineResource(resource: URI): Promise<URI>; getBaselineResource(resource: URI): Promise<URI>;
@@ -508,7 +508,7 @@ class ShowAllSymbolsAction extends Action {
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create(
SearchViewlet, SearchViewlet,
VIEWLET_ID, VIEWLET_ID,
nls.localize('name', "Search"), VIEW_CONTAINER.name,
'codicon-search', 'codicon-search',
1 1
)); ));
@@ -14,8 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search';
import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView';
import { Registry } from 'vs/platform/registry/common/platform'; import { Viewlet } from 'vs/workbench/browser/viewlet';
import { ViewletRegistry, Extensions, Viewlet } from 'vs/workbench/browser/viewlet';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
@@ -48,11 +47,7 @@ export class SearchViewPaneContainer extends ViewPaneContainer {
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService, @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, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
}
getTitle(): string {
return Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet(this.getId()).name;
} }
getSearchView(): SearchView | undefined { getSearchView(): SearchView | undefined {
@@ -68,7 +68,7 @@ export class WebviewPortMappingManager extends Disposable {
if (existing) { if (existing) {
return existing; return existing;
} }
const tunnel = this.tunnelService.openTunnel(remotePort); const tunnel = this.tunnelService.openTunnel(undefined, remotePort);
if (tunnel) { if (tunnel) {
this._tunnels.set(remotePort, tunnel); this._tunnels.set(remotePort, tunnel);
} }
@@ -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.one', "Reopen the last active window."),
nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.") nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.")
], ],
'default': 'one', 'default': 'all',
'scope': ConfigurationScope.APPLICATION, 'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.") 'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.")
}, },
+1 -1
View File
@@ -453,7 +453,7 @@ export class ElectronWindow extends Disposable {
if (options?.allowTunneling) { if (options?.allowTunneling) {
const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri);
if (portMappingRequest) { if (portMappingRequest) {
const tunnel = await this.tunnelService.openTunnel(portMappingRequest.port); const tunnel = await this.tunnelService.openTunnel(undefined, portMappingRequest.port);
if (tunnel) { if (tunnel) {
return { return {
resolved: uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }), resolved: uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }),
@@ -461,7 +461,7 @@ export class ConfigurationEditingService {
if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) {
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties(); const configurationProperties = Registry.as<IConfigurationRegistry>(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); return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation);
} }
} }
@@ -718,7 +718,7 @@ suite.skip('WorkspaceService - Initialization', () => { // {{SQL CARBON EDIT}} s
suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDIT}} skip suite 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<IConfigurationRegistry>(ConfigurationExtensions.Configuration); const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
suiteSetup(() => { suiteSetup(() => {
@@ -745,6 +745,11 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI
'type': 'string', 'type': 'string',
'default': 'isSet', 'default': 'isSet',
scope: ConfigurationScope.RESOURCE 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()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService)); 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(IWorkspaceContextService, workspaceService);
instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IEnvironmentService, environmentService); instantiationService.stub(IEnvironmentService, environmentService);
@@ -794,7 +799,7 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI
}); });
test('defaults', () => { 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', () => { test('globals override defaults', () => {
@@ -1028,6 +1033,16 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI
.then(() => assert.equal(testObject.getValue('tasks.service.testSetting'), 'value')); .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', () => { test('update application setting into workspace configuration in a workspace is not supported', () => {
return testObject.updateValue('configurationService.folder.applicationSetting', 'workspaceValue', {}, ConfigurationTarget.WORKSPACE, true) 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)); .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', 'type': 'string',
'default': 'isSet', 'default': 'isSet',
scope: ConfigurationScope.RESOURCE 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', () => { 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" }'); 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) 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')); .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', () => { test('update workspace folder configuration should trigger change event before promise is resolve', () => {
const workspace = workspaceContextService.getWorkspace(); const workspace = workspaceContextService.getWorkspace();
const target = sinon.spy(); const target = sinon.spy();
@@ -5,7 +5,7 @@
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; 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 { IEditorOptions, ITextEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService';
@@ -344,6 +344,11 @@ export interface IEditorGroupsService {
*/ */
readonly partOptions: IEditorPartOptions; readonly partOptions: IEditorPartOptions;
/**
* An event that notifies when editor part options change.
*/
readonly onDidEditorPartOptionsChange: Event<IEditorPartOptionsChangeEvent>;
/** /**
* Enforce editor part options temporarily. * Enforce editor part options temporarily.
*/ */
@@ -478,7 +478,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
// set the resolved authority // set the resolved authority
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority.authority, resolvedAuthority.options); this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority.authority, resolvedAuthority.options);
this._remoteExplorerService.addDetected(resolvedAuthority.tunnelInformation?.detectedTunnels); this._remoteExplorerService.addEnvironmentTunnels(resolvedAuthority.tunnelInformation?.environmentTunnels);
// monitor for breakage // monitor for breakage
const connection = this._remoteAgentService.getConnection(); const connection = this._remoteAgentService.getConnection();
@@ -27,6 +27,26 @@ export const enum Position {
BOTTOM 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 { export interface IWorkbenchLayoutService extends ILayoutService {
_serviceBrand: undefined; _serviceBrand: undefined;
@@ -17,8 +17,11 @@
width: 14px; width: 14px;
height: 14px; height: 14px;
position: absolute; position: absolute;
top: 1px; top: 0;
left: 1px; right: 0;
bottom: 0;
left: 0;
margin: auto;
background-color: currentColor; background-color: currentColor;
content: ''; content: '';
} }
@@ -13,42 +13,55 @@ import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/e
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { IEditableData } from 'vs/workbench/common/views'; import { IEditableData } from 'vs/workbench/common/views';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('remoteExplorerService'); export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('remoteExplorerService');
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore';
export interface Tunnel { export interface Tunnel {
remote: number; remoteHost: string;
remotePort: number;
localAddress: string; localAddress: string;
local?: number; localPort?: number;
name?: string; name?: string;
description?: string; description?: string;
closeable?: boolean; 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 { export class TunnelModel extends Disposable {
readonly forwarded: Map<number, Tunnel>; readonly forwarded: Map<string, Tunnel>;
readonly detected: Map<number, Tunnel>; readonly detected: Map<string, Tunnel>;
private _onForwardPort: Emitter<Tunnel> = new Emitter(); private _onForwardPort: Emitter<Tunnel> = new Emitter();
public onForwardPort: Event<Tunnel> = this._onForwardPort.event; public onForwardPort: Event<Tunnel> = this._onForwardPort.event;
private _onClosePort: Emitter<number> = new Emitter(); private _onClosePort: Emitter<{ host: string, port: number }> = new Emitter();
public onClosePort: Event<number> = this._onClosePort.event; public onClosePort: Event<{ host: string, port: number }> = this._onClosePort.event;
private _onPortName: Emitter<number> = new Emitter(); private _onPortName: Emitter<{ host: string, port: number }> = new Emitter();
public onPortName: Event<number> = this._onPortName.event; public onPortName: Event<{ host: string, port: number }> = this._onPortName.event;
private _candidateFinder: (() => Promise<{ port: number, detail: string }[]>) | undefined; private _candidateFinder: (() => Promise<{ host: string, port: number, detail: string }[]>) | undefined;
constructor( constructor(
@ITunnelService private readonly tunnelService: ITunnelService @ITunnelService private readonly tunnelService: ITunnelService,
@IStorageService private readonly storageService: IStorageService,
@IConfigurationService private readonly configurationService: IConfigurationService
) { ) {
super(); super();
this.forwarded = new Map(); this.forwarded = new Map();
this.tunnelService.tunnels.then(tunnels => { this.tunnelService.tunnels.then(tunnels => {
tunnels.forEach(tunnel => { tunnels.forEach(tunnel => {
if (tunnel.localAddress) { if (tunnel.localAddress) {
this.forwarded.set(tunnel.tunnelRemotePort, { this.forwarded.set(MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort), {
remote: tunnel.tunnelRemotePort, remotePort: tunnel.tunnelRemotePort,
remoteHost: tunnel.tunnelRemoteHost,
localAddress: tunnel.localAddress, localAddress: tunnel.localAddress,
local: tunnel.tunnelLocalPort localPort: tunnel.tunnelLocalPort
}); });
} }
}); });
@@ -56,75 +69,105 @@ export class TunnelModel extends Disposable {
this.detected = new Map(); this.detected = new Map();
this._register(this.tunnelService.onTunnelOpened(tunnel => { this._register(this.tunnelService.onTunnelOpened(tunnel => {
if (!this.forwarded.has(tunnel.tunnelRemotePort) && tunnel.localAddress) { const key = MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort);
this.forwarded.set(tunnel.tunnelRemotePort, { if ((!this.forwarded.has(key)) && tunnel.localAddress) {
remote: tunnel.tunnelRemotePort, this.forwarded.set(key, {
remoteHost: tunnel.tunnelRemoteHost,
remotePort: tunnel.tunnelRemotePort,
localAddress: tunnel.localAddress, localAddress: tunnel.localAddress,
local: tunnel.tunnelLocalPort, localPort: tunnel.tunnelLocalPort,
closeable: true 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 => { this._register(this.tunnelService.onTunnelClosed(address => {
if (this.forwarded.has(remotePort)) { const key = MakeAddress(address.host, address.port);
this.forwarded.delete(remotePort); if (this.forwarded.has(key)) {
this._onClosePort.fire(remotePort); this.forwarded.delete(key);
this.storeForwarded();
this._onClosePort.fire(address);
} }
})); }));
this.restoreForwarded();
} }
async forward(remote: number, local?: number, name?: string): Promise<RemoteTunnel | void> { private async restoreForwarded() {
if (!this.forwarded.has(remote)) { if (this.configurationService.getValue('remote.restoreForwardedPorts')) {
const tunnel = await this.tunnelService.openTunnel(remote, local); const tunnelsString = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE);
if (tunnelsString) {
(<Tunnel[] | undefined>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<RemoteTunnel | void> {
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) { if (tunnel && tunnel.localAddress) {
const newForward: Tunnel = { const newForward: Tunnel = {
remote: tunnel.tunnelRemotePort, remoteHost: tunnel.tunnelRemoteHost,
local: tunnel.tunnelLocalPort, remotePort: tunnel.tunnelRemotePort,
localPort: tunnel.tunnelLocalPort,
name: name, name: name,
closeable: true, closeable: true,
localAddress: tunnel.localAddress localAddress: tunnel.localAddress
}; };
this.forwarded.set(remote, newForward); this.forwarded.set(key, newForward);
this._onForwardPort.fire(newForward); this._onForwardPort.fire(newForward);
return tunnel; return tunnel;
} }
} }
} }
name(remote: number, name: string) { name(host: string, port: number, name: string) {
if (this.forwarded.has(remote)) { const key = MakeAddress(host, port);
this.forwarded.get(remote)!.name = name; if (this.forwarded.has(key)) {
this._onPortName.fire(remote); this.forwarded.get(key)!.name = name;
} else if (this.detected.has(remote)) { this.storeForwarded();
this.detected.get(remote)!.name = name; this._onPortName.fire({ host, port });
this._onPortName.fire(remote); } else if (this.detected.has(key)) {
this.detected.get(key)!.name = name;
this._onPortName.fire({ host, port });
} }
} }
async close(remote: number): Promise<void> { async close(host: string, port: number): Promise<void> {
return this.tunnelService.closeTunnel(remote); return this.tunnelService.closeTunnel(host, port);
} }
address(remote: number): string | undefined { address(host: string, port: number): string | undefined {
return (this.forwarded.get(remote) || this.detected.get(remote))?.localAddress; 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 => { tunnels.forEach(tunnel => {
this.detected.set(tunnel.remote.port, { this.detected.set(MakeAddress(tunnel.remoteAddress.host, tunnel.remoteAddress.port), {
remote: tunnel.remote.port, remoteHost: tunnel.remoteAddress.host,
remotePort: tunnel.remoteAddress.port,
localAddress: tunnel.localAddress, localAddress: tunnel.localAddress,
closeable: false closeable: false
}); });
}); });
} }
registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void { registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void {
this._candidateFinder = finder; this._candidateFinder = finder;
} }
get candidates(): Promise<{ port: number, detail: string }[]> { get candidates(): Promise<{ host: string, port: number, detail: string }[]> {
if (this._candidateFinder) { if (this._candidateFinder) {
return this._candidateFinder(); return this._candidateFinder();
} }
@@ -138,13 +181,13 @@ export interface IRemoteExplorerService {
targetType: string; targetType: string;
readonly helpInformation: HelpInformation[]; readonly helpInformation: HelpInformation[];
readonly tunnelModel: TunnelModel; readonly tunnelModel: TunnelModel;
onDidChangeEditable: Event<number | undefined>; onDidChangeEditable: Event<{ host: string, port: number | undefined }>;
setEditable(remote: number | undefined, data: IEditableData | null): void; setEditable(remoteHost: string | undefined, remotePort: number | undefined, data: IEditableData | null): void;
getEditableData(remote: number | undefined): IEditableData | undefined; getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined;
forward(remote: number, local?: number, name?: string): Promise<RemoteTunnel | void>; forward(remote: { host: string, port: number }, localPort?: number, name?: string): Promise<RemoteTunnel | void>;
close(remote: number): Promise<void>; close(remote: { host: string, port: number }): Promise<void>;
addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void; addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[] | undefined): void;
registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void; registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void;
} }
export interface HelpInformation { export interface HelpInformation {
@@ -189,14 +232,16 @@ class RemoteExplorerService implements IRemoteExplorerService {
public readonly onDidChangeTargetType: Event<string> = this._onDidChangeTargetType.event; public readonly onDidChangeTargetType: Event<string> = this._onDidChangeTargetType.event;
private _helpInformation: HelpInformation[] = []; private _helpInformation: HelpInformation[] = [];
private _tunnelModel: TunnelModel; private _tunnelModel: TunnelModel;
private _editable: { remote: number | undefined, data: IEditableData } | undefined; private _editable: { remoteHost: string, remotePort: number | undefined, data: IEditableData } | undefined;
private readonly _onDidChangeEditable: Emitter<number | undefined> = new Emitter(); private readonly _onDidChangeEditable: Emitter<{ host: string, port: number | undefined }> = new Emitter();
public readonly onDidChangeEditable: Event<number | undefined> = this._onDidChangeEditable.event; public readonly onDidChangeEditable: Event<{ host: string, port: number | undefined }> = this._onDidChangeEditable.event;
constructor( constructor(
@IStorageService private readonly storageService: IStorageService, @IStorageService private readonly storageService: IStorageService,
@ITunnelService tunnelService: ITunnelService) { @ITunnelService tunnelService: ITunnelService,
this._tunnelModel = new TunnelModel(tunnelService); @IConfigurationService configurationService: IConfigurationService
) {
this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService);
remoteHelpExtPoint.setHandler((extensions) => { remoteHelpExtPoint.setHandler((extensions) => {
let helpInformation: HelpInformation[] = []; let helpInformation: HelpInformation[] = [];
for (let extension of extensions) { for (let extension of extensions) {
@@ -246,34 +291,35 @@ class RemoteExplorerService implements IRemoteExplorerService {
return this._tunnelModel; return this._tunnelModel;
} }
forward(remote: number, local?: number, name?: string): Promise<RemoteTunnel | void> { forward(remote: { host: string, port: number }, local?: number, name?: string): Promise<RemoteTunnel | void> {
return this.tunnelModel.forward(remote, local, name); return this.tunnelModel.forward(remote, local, name);
} }
close(remote: number): Promise<void> { close(remote: { host: string, port: number }): Promise<void> {
return this.tunnelModel.close(remote); 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) { 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) { if (!data) {
this._editable = undefined; this._editable = undefined;
} else { } 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 { getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined {
return this._editable && this._editable.remote === remote ? this._editable.data : 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); this.tunnelModel.registerCandidateFinder(finder);
} }
@@ -103,9 +103,9 @@ export class TunnelService implements ITunnelService {
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter(); private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event; public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<number> = new Emitter(); private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter();
public onTunnelClosed: Event<number> = this._onTunnelClosed.event; public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
private readonly _tunnels = new Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>(); private readonly _tunnels = new Map</*host*/ string, Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>>();
private _tunnelProvider: ITunnelProvider | undefined; private _tunnelProvider: ITunnelProvider | undefined;
public constructor( public constructor(
@@ -130,23 +130,32 @@ export class TunnelService implements ITunnelService {
} }
public get tunnels(): Promise<readonly RemoteTunnel[]> { public get tunnels(): Promise<readonly RemoteTunnel[]> {
return Promise.all(Array.from(this._tunnels.values()).map(x => x.value)); const promises: Promise<RemoteTunnel>[] = [];
Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value)));
return Promise.all(promises);
} }
dispose(): void { dispose(): void {
for (const { value } of this._tunnels.values()) { for (const portMap of this._tunnels.values()) {
for (const { value } of portMap.values()) {
value.then(tunnel => tunnel.dispose()); value.then(tunnel => tunnel.dispose());
} }
portMap.clear();
}
this._tunnels.clear(); this._tunnels.clear();
} }
openTunnel(remotePort: number, localPort: number): Promise<RemoteTunnel> | undefined { openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise<RemoteTunnel> | undefined {
const remoteAuthority = this.environmentService.configuration.remoteAuthority; const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (!remoteAuthority) { if (!remoteAuthority) {
return undefined; 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) { if (!resolvedTunnel) {
return resolvedTunnel; return resolvedTunnel;
} }
@@ -165,48 +174,62 @@ export class TunnelService implements ITunnelService {
tunnelLocalPort: tunnel.tunnelLocalPort, tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress, localAddress: tunnel.localAddress,
dispose: () => { dispose: () => {
const existing = this._tunnels.get(tunnel.tunnelRemotePort); const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost);
if (existingHost) {
const existing = existingHost.get(tunnel.tunnelRemotePort);
if (existing) { if (existing) {
existing.refcount--; existing.refcount--;
this.tryDisposeTunnel(tunnel.tunnelRemotePort, existing); this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing);
}
} }
} }
}; };
} }
private async tryDisposeTunnel(remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel> }): Promise<void> { private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel> }): Promise<void> {
if (tunnel.refcount <= 0) { if (tunnel.refcount <= 0) {
const disposePromise: Promise<void> = tunnel.value.then(tunnel => { const disposePromise: Promise<void> = tunnel.value.then(tunnel => {
tunnel.dispose(); 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; return disposePromise;
} }
} }
async closeTunnel(remotePort: number): Promise<void> { async closeTunnel(remoteHost: string, remotePort: number): Promise<void> {
if (this._tunnels.has(remotePort)) { const portMap = this._tunnels.get(remoteHost);
const value = this._tunnels.get(remotePort)!; if (portMap && portMap.has(remotePort)) {
const value = portMap.get(remotePort)!;
value.refcount = 0; value.refcount = 0;
await this.tryDisposeTunnel(remotePort, value); await this.tryDisposeTunnel(remoteHost, remotePort, value);
} }
} }
private retainOrCreateTunnel(remoteAuthority: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined { private addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise<RemoteTunnel>) {
const existing = this._tunnels.get(remotePort); 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<RemoteTunnel> | undefined {
const portMap = this._tunnels.get(remoteHost);
const existing = portMap ? portMap.get(remotePort) : undefined;
if (existing) { if (existing) {
++existing.refcount; ++existing.refcount;
return existing.value; return existing.value;
} }
if (this._tunnelProvider) { 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) { if (tunnel) {
this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); this.addTunnelToMap(remoteHost, remotePort, tunnel);
} }
return tunnel; return tunnel;
} else { } else if (remoteHost === 'localhost') {
const options: IConnectionOptions = { const options: IConnectionOptions = {
commit: product.commit, commit: product.commit,
socketFactory: nodeSocketFactory, socketFactory: nodeSocketFactory,
@@ -221,9 +244,10 @@ export class TunnelService implements ITunnelService {
}; };
const tunnel = createRemoteTunnel(options, remotePort, localPort); const tunnel = createRemoteTunnel(options, remotePort, localPort);
this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); this.addTunnelToMap(remoteHost, remotePort, tunnel);
return tunnel; return tunnel;
} }
return undefined;
} }
} }
@@ -18,6 +18,7 @@ import { Event } from 'vs/base/common/event';
import { relative } from 'vs/base/common/path'; import { relative } from 'vs/base/common/path';
import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { localize } from 'vs/nls';
export const VIEWLET_ID = 'workbench.view.search'; export const VIEWLET_ID = 'workbench.view.search';
export const PANEL_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. * Search viewlet container.
*/ */
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar, true); export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('name', "Search"), hideIfEmpty: true }, ViewContainerLocation.Sidebar);
export const ISearchService = createDecorator<ISearchService>('searchService'); export const ISearchService = createDecorator<ISearchService>('searchService');

Some files were not shown because too many files have changed in this diff Show More