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

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