Archived
Merge from vscode e74405d11443c5361c31e2bc341866d146eee206 (#8740)
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
@@ -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,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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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,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 () => {
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+15
-41
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
+106
-69
@@ -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;
|
||||
+1
-15
@@ -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));
|
||||
|
||||
@@ -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.")
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-3
@@ -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
Reference in New Issue
Block a user