mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
Merge branch 'master' into sandy081/moveProblemsToView
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"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { DataSource } from 'vs/base/parts/quickopen/browser/quickOpenViewer';
|
||||
@@ -28,7 +29,7 @@ suite('QuickOpen', () => {
|
||||
assert.equal(entry2, model.getEntries(true)[0]);
|
||||
});
|
||||
|
||||
test('QuickOpenDataSource', () => {
|
||||
test('QuickOpenDataSource', async () => {
|
||||
const model = new QuickOpenModel();
|
||||
|
||||
const entry1 = new QuickOpenEntry();
|
||||
@@ -42,8 +43,7 @@ suite('QuickOpen', () => {
|
||||
assert.equal(true, ds.hasChildren(null!, model));
|
||||
assert.equal(false, ds.hasChildren(null!, entry1));
|
||||
|
||||
ds.getChildren(null!, model).then((children: any[]) => {
|
||||
assert.equal(3, children.length);
|
||||
});
|
||||
const children = await ds.getChildren(null!, model);
|
||||
assert.equal(3, children.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
|
||||
@@ -28,4 +29,4 @@ suite('ProgressBar', () => {
|
||||
|
||||
bar.dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ok } from 'vs/base/common/assert';
|
||||
|
||||
|
||||
@@ -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 MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as extpath from 'vs/base/common/extpath';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -28,7 +29,6 @@ suite('Paths', () => {
|
||||
assert.equal(extpath.getRoot('http://www/'), 'http://www/');
|
||||
assert.equal(extpath.getRoot('file:///foo'), 'file:///');
|
||||
assert.equal(extpath.getRoot('file://foo'), '');
|
||||
|
||||
});
|
||||
|
||||
test('isUNC', () => {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { guessMimeTypes, registerTextMime, suggestFilename } from 'vs/base/common/mime';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
suite('Types', () => {
|
||||
|
||||
test('isFunction', () => {
|
||||
assert(!types.isFunction(undefined));
|
||||
assert(!types.isFunction(null));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
+2
-1
@@ -79,7 +79,8 @@ declare module 'vscode' {
|
||||
|
||||
export namespace workspace {
|
||||
/**
|
||||
* Forwards a port. Currently only works for a remote host of localhost.
|
||||
* Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel.
|
||||
* By default, makeTunnel only support localhost; however, RemoteAuthorityResolver:forwardPort can be used to support other ips.
|
||||
* @param forward The `localPort` is a suggestion only. If that port is not available another will be chosen.
|
||||
*/
|
||||
export function makeTunnel(forward: TunnelOptions): Thenable<Tunnel>;
|
||||
|
||||
@@ -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.remote, tunnelOptions.localPort, tunnelOptions.name);
|
||||
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> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -774,7 +774,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>;
|
||||
}
|
||||
@@ -1395,7 +1395,7 @@ export interface ExtHostStorageShape {
|
||||
|
||||
|
||||
export interface ExtHostTunnelServiceShape {
|
||||
$findCandidatePorts(): Promise<{ port: number, detail: string }[]>;
|
||||
$findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>;
|
||||
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined;
|
||||
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
|
||||
async makeTunnel(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: () => { } }; }
|
||||
|
||||
@@ -52,7 +52,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
const tunnel = await this._proxy.$openTunnel(forward);
|
||||
if (tunnel) {
|
||||
const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => {
|
||||
return this._proxy.$closeTunnel(tunnel.remote.port);
|
||||
return this._proxy.$closeTunnel(tunnel.remote);
|
||||
});
|
||||
this._register(disposableTunnel);
|
||||
return disposableTunnel;
|
||||
@@ -95,7 +95,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
this._extensionTunnels.set(tunnelOptions.remote.host, new Map());
|
||||
}
|
||||
this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel);
|
||||
this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote.port)));
|
||||
this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote)));
|
||||
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
|
||||
});
|
||||
}
|
||||
@@ -104,12 +104,12 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
}
|
||||
|
||||
|
||||
async $findCandidatePorts(): Promise<{ port: number, detail: string }[]> {
|
||||
async $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> {
|
||||
if (!isLinux) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ports: { port: number, detail: string }[] = [];
|
||||
const ports: { host: string, port: number, detail: string }[] = [];
|
||||
const tcp: string = fs.readFileSync('/proc/net/tcp', 'utf8');
|
||||
const tcp6: string = fs.readFileSync('/proc/net/tcp6', 'utf8');
|
||||
const procSockets: string = await (new Promise(resolve => {
|
||||
@@ -150,7 +150,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
|
||||
const command = processMap[socketMap[socket].pid].cmd;
|
||||
if (!command.match('.*\.vscode\-server\-[a-zA-Z]+\/bin.*') && (command.indexOf('out/vs/server/main.js') === -1)) {
|
||||
ports.push({ port, detail: processMap[socketMap[socket].pid].cmd });
|
||||
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -234,7 +234,8 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
}
|
||||
|
||||
export interface IViewPaneContainerOptions extends IPaneViewOptions {
|
||||
showHeaderInTitleWhenSingleView: boolean;
|
||||
mergeViewWithContainerWhenSingleView: boolean;
|
||||
donotShowContainerTitleWhenMergedWithContainer?: boolean;
|
||||
}
|
||||
|
||||
interface IViewPaneItem {
|
||||
@@ -346,14 +347,16 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
|
||||
getTitle(): string {
|
||||
const composite = Registry.as<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 ? `${composite.name}: ${paneItemTitle}` : composite.name;
|
||||
}
|
||||
|
||||
return title;
|
||||
return composite.name;
|
||||
}
|
||||
|
||||
private showContextMenu(event: StandardMouseEvent): void {
|
||||
@@ -402,7 +405,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
if (this.isSingleView()) {
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
return this.paneItems[0].pane.getActions();
|
||||
}
|
||||
|
||||
@@ -410,7 +413,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
if (this.isSingleView()) {
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
return this.paneItems[0].pane.getSecondaryActions();
|
||||
}
|
||||
|
||||
@@ -418,7 +421,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (this.isSingleView()) {
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
return this.paneItems[0].pane.getActionViewItem(action);
|
||||
}
|
||||
|
||||
@@ -459,14 +462,14 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
|
||||
addPanes(panes: { pane: ViewPane, size: number, index?: number; }[]): void {
|
||||
const wasSingleView = this.isSingleView();
|
||||
const wasMerged = this.isViewMergedWithContainer();
|
||||
|
||||
for (const { pane: pane, size, index } of panes) {
|
||||
this.addPane(pane, size, index);
|
||||
}
|
||||
|
||||
this.updateViewHeaders();
|
||||
if (this.isSingleView() !== wasSingleView) {
|
||||
if (this.isViewMergedWithContainer() !== wasMerged) {
|
||||
this.updateTitleArea();
|
||||
}
|
||||
}
|
||||
@@ -643,7 +646,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void {
|
||||
const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane);
|
||||
const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => {
|
||||
if (this.isSingleView()) {
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
this.updateTitleArea();
|
||||
}
|
||||
});
|
||||
@@ -668,12 +671,12 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
|
||||
removePanes(panes: ViewPane[]): void {
|
||||
const wasSingleView = this.isSingleView();
|
||||
const wasMerged = this.isViewMergedWithContainer();
|
||||
|
||||
panes.forEach(pane => this.removePane(pane));
|
||||
|
||||
this.updateViewHeaders();
|
||||
if (wasSingleView !== this.isSingleView()) {
|
||||
if (wasMerged !== this.isViewMergedWithContainer()) {
|
||||
this.updateTitleArea();
|
||||
}
|
||||
}
|
||||
@@ -726,8 +729,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
return assertIsDefined(this.paneview).getPaneSize(pane);
|
||||
}
|
||||
|
||||
protected updateViewHeaders(): void {
|
||||
if (this.isSingleView()) {
|
||||
private updateViewHeaders(): void {
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
this.paneItems[0].pane.setExpanded(true);
|
||||
this.paneItems[0].pane.headerVisible = false;
|
||||
} else {
|
||||
@@ -735,8 +738,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
}
|
||||
|
||||
protected isSingleView(): boolean {
|
||||
if (!(this.options.showHeaderInTitleWhenSingleView && this.paneItems.length === 1)) {
|
||||
private isViewMergedWithContainer(): boolean {
|
||||
if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.areExtensionsReady) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -235,12 +235,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
|
||||
'description': nls.localize('settings.editor.desc', "Determines which settings editor to use by default."),
|
||||
'default': 'ui',
|
||||
'scope': ConfigurationScope.WINDOW
|
||||
},
|
||||
'workbench.enableExperiments': {
|
||||
'type': 'boolean',
|
||||
'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."),
|
||||
'default': true,
|
||||
'tags': ['usesOnlineServices']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -93,7 +93,9 @@ suite('BackupModelRestorer', () => {
|
||||
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('Restore backups', async () => {
|
||||
test('Restore backups', async function () {
|
||||
this.timeout(20000);
|
||||
|
||||
const backupFileService = new NodeTestBackupFileService(workspaceBackupPath);
|
||||
const instantiationService = workbenchInstantiationService();
|
||||
instantiationService.stub(IBackupFileService, backupFileService);
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -3,13 +3,31 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
|
||||
|
||||
registerSingleton(IExperimentService, ExperimentService, true);
|
||||
|
||||
Registry.as<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']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -370,7 +370,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
) {
|
||||
super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
|
||||
super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
|
||||
|
||||
this.searchDelayer = new Delayer(500);
|
||||
this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService);
|
||||
|
||||
@@ -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");
|
||||
@@ -855,7 +854,6 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const viewletService = accessor.get(IViewletService);
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const labelService = accessor.get(ILabelService);
|
||||
|
||||
await viewletService.openViewlet(VIEWLET_ID, true);
|
||||
|
||||
@@ -878,10 +876,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
|
||||
folder.addChild(newStat);
|
||||
|
||||
const onSuccess = (value: string): Promise<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, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files';
|
||||
import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker';
|
||||
import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
@@ -396,8 +396,8 @@ configurationRegistry.registerConfiguration({
|
||||
},
|
||||
'explorer.sortOrder': {
|
||||
'type': 'string',
|
||||
'enum': [SortOrderConfiguration.DEFAULT, SortOrderConfiguration.MIXED, SortOrderConfiguration.FILES_FIRST, SortOrderConfiguration.TYPE, SortOrderConfiguration.MODIFIED],
|
||||
'default': SortOrderConfiguration.DEFAULT,
|
||||
'enum': [SortOrder.Default, SortOrder.Mixed, SortOrder.FilesFirst, SortOrder.Type, SortOrder.Modified],
|
||||
'default': SortOrder.Default,
|
||||
'enumDescriptions': [
|
||||
nls.localize('sortOrder.default', 'Files and folders are sorted by their names, in alphabetical order. Folders are displayed before files.'),
|
||||
nls.localize('sortOrder.mixed', 'Files and folders are sorted by their names, in alphabetical order. Files are interwoven with folders.'),
|
||||
|
||||
@@ -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);
|
||||
}));
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IExplorerService } from 'vs/workbench/contrib/files/common/files';
|
||||
import { IExplorerService, SortOrder } from 'vs/workbench/contrib/files/common/files';
|
||||
import { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources';
|
||||
|
||||
export class ExplorerModel implements IDisposable {
|
||||
@@ -263,7 +263,7 @@ export class ExplorerItem {
|
||||
if (!this._isDirectoryResolved) {
|
||||
// Resolve metadata only when the mtime is needed since this can be expensive
|
||||
// Mtime is only used when the sort order is 'modified'
|
||||
const resolveMetadata = explorerService.sortOrder === 'modified';
|
||||
const resolveMetadata = explorerService.sortOrder === SortOrder.Modified;
|
||||
try {
|
||||
const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata });
|
||||
const resolved = ExplorerItem.create(explorerService, fileService, stat, this);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IExplorerService, IFilesConfiguration, SortOrder, SortOrderConfiguration, IContextProvider } from 'vs/workbench/contrib/files/common/files';
|
||||
import { IExplorerService, IFilesConfiguration, SortOrder, IContextProvider } from 'vs/workbench/contrib/files/common/files';
|
||||
import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
@@ -187,7 +187,7 @@ export class ExplorerService implements IExplorerService {
|
||||
}
|
||||
|
||||
// Stat needs to be resolved first and then revealed
|
||||
const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === 'modified' };
|
||||
const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === SortOrder.Modified };
|
||||
const workspaceFolder = this.contextService.getWorkspaceFolder(resource);
|
||||
if (workspaceFolder === null) {
|
||||
return Promise.resolve(undefined);
|
||||
@@ -361,7 +361,7 @@ export class ExplorerService implements IExplorerService {
|
||||
}
|
||||
|
||||
// Handle updated files/folders if we sort by modified
|
||||
if (this._sortOrder === SortOrderConfiguration.MODIFIED) {
|
||||
if (this._sortOrder === SortOrder.Modified) {
|
||||
const updated = e.getUpdated();
|
||||
|
||||
// Check updated: Refresh if updated file/folder part of resolved root
|
||||
@@ -387,7 +387,7 @@ export class ExplorerService implements IExplorerService {
|
||||
|
||||
private filterToViewRelevantEvents(e: FileChangesEvent): FileChangesEvent {
|
||||
return new FileChangesEvent(e.changes.filter(change => {
|
||||
if (change.type === FileChangeType.UPDATED && this._sortOrder !== SortOrderConfiguration.MODIFIED) {
|
||||
if (change.type === FileChangeType.UPDATED && this._sortOrder !== SortOrder.Modified) {
|
||||
return false; // we only are about updated if we sort by modified time
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -6,7 +6,3 @@
|
||||
.customview-tree .tunnel-view-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.customview-tree .tunnel-view-label .action-label.codicon {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -48,7 +48,7 @@ export class SearchViewPaneContainer extends ViewPaneContainer {
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
) {
|
||||
super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
|
||||
super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}` }),
|
||||
|
||||
@@ -18,24 +18,32 @@ export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('r
|
||||
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
|
||||
|
||||
export interface Tunnel {
|
||||
remote: number;
|
||||
remoteHost: string;
|
||||
remotePort: number;
|
||||
localAddress: string;
|
||||
local?: number;
|
||||
localPort?: number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
closeable?: boolean;
|
||||
}
|
||||
|
||||
export function MakeAddress(host: string, port: number): string {
|
||||
if (host = '127.0.0.1') {
|
||||
host = 'localhost';
|
||||
}
|
||||
return host + ':' + port;
|
||||
}
|
||||
|
||||
export class TunnelModel extends Disposable {
|
||||
readonly forwarded: Map<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
|
||||
@@ -45,10 +53,11 @@ export class TunnelModel extends Disposable {
|
||||
this.tunnelService.tunnels.then(tunnels => {
|
||||
tunnels.forEach(tunnel => {
|
||||
if (tunnel.localAddress) {
|
||||
this.forwarded.set(tunnel.tunnelRemotePort, {
|
||||
remote: tunnel.tunnelRemotePort,
|
||||
this.forwarded.set(MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort), {
|
||||
remotePort: tunnel.tunnelRemotePort,
|
||||
remoteHost: tunnel.tunnelRemoteHost,
|
||||
localAddress: tunnel.localAddress,
|
||||
local: tunnel.tunnelLocalPort
|
||||
localPort: tunnel.tunnelLocalPort
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -56,75 +65,83 @@ export class TunnelModel extends Disposable {
|
||||
|
||||
this.detected = new Map();
|
||||
this._register(this.tunnelService.onTunnelOpened(tunnel => {
|
||||
if (!this.forwarded.has(tunnel.tunnelRemotePort) && tunnel.localAddress) {
|
||||
this.forwarded.set(tunnel.tunnelRemotePort, {
|
||||
remote: tunnel.tunnelRemotePort,
|
||||
const key = MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort);
|
||||
if ((!this.forwarded.has(key)) && tunnel.localAddress) {
|
||||
this.forwarded.set(key, {
|
||||
remoteHost: tunnel.tunnelRemoteHost,
|
||||
remotePort: tunnel.tunnelRemotePort,
|
||||
localAddress: tunnel.localAddress,
|
||||
local: tunnel.tunnelLocalPort,
|
||||
localPort: tunnel.tunnelLocalPort,
|
||||
closeable: true
|
||||
});
|
||||
}
|
||||
this._onForwardPort.fire(this.forwarded.get(tunnel.tunnelRemotePort)!);
|
||||
this._onForwardPort.fire(this.forwarded.get(key)!);
|
||||
}));
|
||||
this._register(this.tunnelService.onTunnelClosed(remotePort => {
|
||||
if (this.forwarded.has(remotePort)) {
|
||||
this.forwarded.delete(remotePort);
|
||||
this._onClosePort.fire(remotePort);
|
||||
this._register(this.tunnelService.onTunnelClosed(address => {
|
||||
const key = MakeAddress(address.host, address.port);
|
||||
if (this.forwarded.has(key)) {
|
||||
this.forwarded.delete(key);
|
||||
this._onClosePort.fire(address);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async forward(remote: number, local?: number, name?: string): Promise<RemoteTunnel | void> {
|
||||
if (!this.forwarded.has(remote)) {
|
||||
const tunnel = await this.tunnelService.openTunnel(remote, local);
|
||||
async forward(remote: { host: string, port: number }, local?: number, name?: string): Promise<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._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 {
|
||||
tunnels.forEach(tunnel => {
|
||||
this.detected.set(tunnel.remote.port, {
|
||||
remote: tunnel.remote.port,
|
||||
this.detected.set(MakeAddress(tunnel.remote.host, tunnel.remote.port), {
|
||||
remoteHost: tunnel.remote.host,
|
||||
remotePort: tunnel.remote.port,
|
||||
localAddress: tunnel.localAddress,
|
||||
closeable: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void {
|
||||
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void {
|
||||
this._candidateFinder = finder;
|
||||
}
|
||||
|
||||
get candidates(): Promise<{ port: number, detail: string }[]> {
|
||||
get candidates(): Promise<{ host: string, port: number, detail: string }[]> {
|
||||
if (this._candidateFinder) {
|
||||
return this._candidateFinder();
|
||||
}
|
||||
@@ -138,13 +155,13 @@ export interface IRemoteExplorerService {
|
||||
targetType: string;
|
||||
readonly helpInformation: HelpInformation[];
|
||||
readonly tunnelModel: TunnelModel;
|
||||
onDidChangeEditable: Event<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>;
|
||||
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>;
|
||||
addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void;
|
||||
registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void;
|
||||
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void;
|
||||
}
|
||||
|
||||
export interface HelpInformation {
|
||||
@@ -189,9 +206,9 @@ class RemoteExplorerService implements IRemoteExplorerService {
|
||||
public readonly onDidChangeTargetType: Event<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,
|
||||
@@ -246,12 +263,12 @@ 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 {
|
||||
@@ -260,20 +277,21 @@ class RemoteExplorerService implements IRemoteExplorerService {
|
||||
}
|
||||
}
|
||||
|
||||
setEditable(remote: number | undefined, data: IEditableData | null): void {
|
||||
setEditable(remoteHost: string, remotePort: number | undefined, data: IEditableData | null): void {
|
||||
if (!data) {
|
||||
this._editable = undefined;
|
||||
} else {
|
||||
this._editable = { remote, data };
|
||||
this._editable = { remoteHost, remotePort, data };
|
||||
}
|
||||
this._onDidChangeEditable.fire(remote);
|
||||
this._onDidChangeEditable.fire({ host: remoteHost, port: remotePort });
|
||||
}
|
||||
|
||||
getEditableData(remote: number | undefined): IEditableData | undefined {
|
||||
return this._editable && this._editable.remote === remote ? this._editable.data : undefined;
|
||||
getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined {
|
||||
return (this._editable && (this._editable.remotePort === remotePort) && this._editable.remoteHost === remoteHost) ?
|
||||
this._editable.data : undefined;
|
||||
}
|
||||
|
||||
registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void {
|
||||
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void {
|
||||
this.tunnelModel.registerCandidateFinder(finder);
|
||||
}
|
||||
|
||||
|
||||
@@ -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({ remote: { host: remoteHost, port: remotePort } });
|
||||
if (tunnel) {
|
||||
this._tunnels.set(remotePort, { refcount: 1, value: tunnel });
|
||||
this.addTunnelToMap(remoteHost, remotePort, tunnel);
|
||||
}
|
||||
return tunnel;
|
||||
} else {
|
||||
} else if (remoteHost === 'localhost') {
|
||||
const options: IConnectionOptions = {
|
||||
commit: product.commit,
|
||||
socketFactory: nodeSocketFactory,
|
||||
@@ -221,9 +244,10 @@ export class TunnelService implements ITunnelService {
|
||||
};
|
||||
|
||||
const tunnel = createRemoteTunnel(options, remotePort, localPort);
|
||||
this._tunnels.set(remotePort, { refcount: 1, value: tunnel });
|
||||
this.addTunnelToMap(remoteHost, remotePort, tunnel);
|
||||
return tunnel;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ class ServiceAccessor {
|
||||
|
||||
class BeforeShutdownEventImpl implements BeforeShutdownEvent {
|
||||
|
||||
public value: boolean | Promise<boolean> | undefined;
|
||||
public reason = ShutdownReason.CLOSE;
|
||||
value: boolean | Promise<boolean> | undefined;
|
||||
reason = ShutdownReason.CLOSE;
|
||||
|
||||
veto(value: boolean | Promise<boolean>): void {
|
||||
this.value = value;
|
||||
|
||||
@@ -256,17 +256,17 @@ suite('Workbench base editor', () => {
|
||||
}
|
||||
|
||||
class TestEditorInput extends EditorInput {
|
||||
constructor(private resource: URI, private id = 'testEditorInput') {
|
||||
constructor(private resource: URI, private id = 'testEditorInputForMementoTest') {
|
||||
super();
|
||||
}
|
||||
public getTypeId() { return 'testEditorInput'; }
|
||||
public resolve(): Promise<IEditorModel> { return Promise.resolve(null!); }
|
||||
getTypeId() { return 'testEditorInputForMementoTest'; }
|
||||
resolve(): Promise<IEditorModel> { return Promise.resolve(null!); }
|
||||
|
||||
public matches(other: TestEditorInput): boolean {
|
||||
matches(other: TestEditorInput): boolean {
|
||||
return other && this.id === other.id && other instanceof TestEditorInput;
|
||||
}
|
||||
|
||||
public getResource(): URI {
|
||||
getResource(): URI {
|
||||
return this.resource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenAction, QuickOpenHandler } from 'vs/workbench/browser/quickopen';
|
||||
|
||||
export class TestQuickOpenService implements IQuickOpenService {
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private callback?: (prefix?: string) => void;
|
||||
|
||||
@@ -44,8 +45,8 @@ export class TestQuickOpenService implements IQuickOpenService {
|
||||
return null!;
|
||||
}
|
||||
|
||||
public dispose() { }
|
||||
public navigate(): void { }
|
||||
dispose() { }
|
||||
navigate(): void { }
|
||||
}
|
||||
|
||||
suite('QuickOpen', () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ suite('Viewlets', () => {
|
||||
super('id', null!, null!, null!, null!, null!, null!, null!, null!, null!, null!);
|
||||
}
|
||||
|
||||
public layout(dimension: any): void {
|
||||
layout(dimension: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTe
|
||||
|
||||
class MyEditorModel extends EditorModel { }
|
||||
class MyTextEditorModel extends BaseTextEditorModel {
|
||||
public createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) {
|
||||
createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) {
|
||||
return super.createTextEditorModel(value, resource, preferredMode);
|
||||
}
|
||||
|
||||
|
||||
@@ -245,26 +245,25 @@ export class TestTextFileService extends NativeTextFileService {
|
||||
this.resolveTextContentError = error;
|
||||
}
|
||||
|
||||
readStream(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileStreamContent> {
|
||||
async readStream(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileStreamContent> {
|
||||
if (this.resolveTextContentError) {
|
||||
const error = this.resolveTextContentError;
|
||||
this.resolveTextContentError = null;
|
||||
|
||||
return Promise.reject(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return this.fileService.readFileStream(resource, options).then(async (content): Promise<ITextFileStreamContent> => {
|
||||
return {
|
||||
resource: content.resource,
|
||||
name: content.name,
|
||||
mtime: content.mtime,
|
||||
ctime: content.ctime,
|
||||
etag: content.etag,
|
||||
encoding: 'utf8',
|
||||
value: await createTextBufferFactoryFromStream(content.value),
|
||||
size: 10
|
||||
};
|
||||
});
|
||||
const content = await this.fileService.readFileStream(resource, options);
|
||||
return {
|
||||
resource: content.resource,
|
||||
name: content.name,
|
||||
mtime: content.mtime,
|
||||
ctime: content.ctime,
|
||||
etag: content.etag,
|
||||
encoding: 'utf8',
|
||||
value: await createTextBufferFactoryFromStream(content.value),
|
||||
size: 10
|
||||
};
|
||||
}
|
||||
|
||||
promptForPath(_resource: URI, _defaultPath: URI): Promise<URI> {
|
||||
@@ -1021,12 +1020,14 @@ export class TestFileService implements IFileService {
|
||||
});
|
||||
}
|
||||
|
||||
resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]> {
|
||||
return Promise.all(toResolve.map(resourceAndOption => this.resolve(resourceAndOption.resource, resourceAndOption.options))).then(stats => stats.map(stat => ({ stat, success: true })));
|
||||
async resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]> {
|
||||
const stats = await Promise.all(toResolve.map(resourceAndOption => this.resolve(resourceAndOption.resource, resourceAndOption.options)));
|
||||
|
||||
return stats.map(stat => ({ stat, success: true }));
|
||||
}
|
||||
|
||||
exists(_resource: URI): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
async exists(_resource: URI): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
readFile(resource: URI, options?: IReadFileOptions | undefined): Promise<IFileContent> {
|
||||
@@ -1071,11 +1072,12 @@ export class TestFileService implements IFileService {
|
||||
});
|
||||
}
|
||||
|
||||
writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
|
||||
return timeout(0).then(() => ({
|
||||
async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
|
||||
await timeout(0);
|
||||
|
||||
return ({
|
||||
resource,
|
||||
etag: 'index.txt',
|
||||
encoding: 'utf8',
|
||||
mtime: Date.now(),
|
||||
ctime: Date.now(),
|
||||
size: 42,
|
||||
@@ -1083,7 +1085,7 @@ export class TestFileService implements IFileService {
|
||||
isDirectory: false,
|
||||
isSymbolicLink: false,
|
||||
name: resources.basename(resource)
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
move(_source: URI, _target: URI, _overwrite?: boolean): Promise<IFileStatWithMetadata> {
|
||||
@@ -1153,14 +1155,13 @@ export class TestBackupFileService implements IBackupFileService {
|
||||
return false;
|
||||
}
|
||||
|
||||
loadBackupResource(resource: URI): Promise<URI | undefined> {
|
||||
return this.hasBackup(resource).then(hasBackup => {
|
||||
if (hasBackup) {
|
||||
return this.toBackupResource(resource);
|
||||
}
|
||||
async loadBackupResource(resource: URI): Promise<URI | undefined> {
|
||||
const hasBackup = await this.hasBackup(resource);
|
||||
if (hasBackup) {
|
||||
return this.toBackupResource(resource);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerResourceForBackup(_resource: URI): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user