mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-12 19:18:32 -05:00
This reverts commit d15a3fcc98.
This commit is contained in:
@@ -352,7 +352,7 @@ class BackupFileServiceImpl implements IBackupFileService {
|
||||
|
||||
export class InMemoryBackupFileService implements IBackupFileService {
|
||||
|
||||
_serviceBrand: ServiceIdentifier<IBackupFileService>;
|
||||
_serviceBrand: any;
|
||||
|
||||
private backups: Map<string, ITextSnapshot> = new Map();
|
||||
|
||||
|
||||
30
src/vs/workbench/services/broadcast/common/broadcast.ts
Normal file
30
src/vs/workbench/services/broadcast/common/broadcast.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export const IBroadcastService = createDecorator<IBroadcastService>('broadcastService');
|
||||
|
||||
export interface IBroadcast {
|
||||
channel: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export interface IBroadcastService {
|
||||
_serviceBrand: any;
|
||||
|
||||
onBroadcast: Event<IBroadcast>;
|
||||
|
||||
broadcast(b: IBroadcast): void;
|
||||
}
|
||||
|
||||
export class NullBroadcastService implements IBroadcastService {
|
||||
_serviceBrand: any;
|
||||
onBroadcast: Event<IBroadcast> = Event.None;
|
||||
broadcast(_b: IBroadcast): void {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IBroadcastService, IBroadcast } from 'vs/workbench/services/broadcast/common/broadcast';
|
||||
|
||||
class BroadcastService extends Disposable implements IBroadcastService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly _onBroadcast: Emitter<IBroadcast> = this._register(new Emitter<IBroadcast>());
|
||||
get onBroadcast(): Event<IBroadcast> { return this._onBroadcast.event; }
|
||||
|
||||
private windowId: number;
|
||||
|
||||
constructor(
|
||||
@IWindowService readonly windowService: IWindowService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.windowId = windowService.windowId;
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
ipc.on('vscode:broadcast', (event: unknown, b: IBroadcast) => {
|
||||
this.logService.trace(`Received broadcast from main in window ${this.windowId}: `, b);
|
||||
|
||||
this._onBroadcast.fire(b);
|
||||
});
|
||||
}
|
||||
|
||||
broadcast(b: IBroadcast): void {
|
||||
this.logService.trace(`Sending broadcast to main from window ${this.windowId}: `, b);
|
||||
|
||||
ipc.send('vscode:broadcast', this.windowId, {
|
||||
channel: b.channel,
|
||||
payload: b.payload
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IBroadcastService, BroadcastService, true);
|
||||
@@ -21,8 +21,8 @@ import { extname, join } from 'vs/base/common/path';
|
||||
import { equals } from 'vs/base/common/objects';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IConfigurationModel, compare } from 'vs/platform/configuration/common/configuration';
|
||||
import { createSHA1 } from 'vs/base/browser/hash';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
|
||||
export class RemoteUserConfiguration extends Disposable {
|
||||
|
||||
@@ -672,7 +672,7 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
private configurationModel: ConfigurationModel;
|
||||
private readonly key: ConfigurationKey;
|
||||
private readonly key: Thenable<ConfigurationKey>;
|
||||
|
||||
constructor(
|
||||
folder: URI,
|
||||
@@ -680,13 +680,14 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati
|
||||
private readonly configurationCache: IConfigurationCache
|
||||
) {
|
||||
super();
|
||||
this.key = { type: 'folder', key: hash(join(folder.path, configFolderRelativePath)).toString(16) };
|
||||
this.key = createSHA1(join(folder.path, configFolderRelativePath)).then(key => ({ type: 'folder', key }));
|
||||
this.configurationModel = new ConfigurationModel();
|
||||
}
|
||||
|
||||
async loadConfiguration(): Promise<ConfigurationModel> {
|
||||
try {
|
||||
const contents = await this.configurationCache.read(this.key);
|
||||
const key = await this.key;
|
||||
const contents = await this.configurationCache.read(key);
|
||||
const parsed: IConfigurationModel = JSON.parse(contents.toString());
|
||||
this.configurationModel = new ConfigurationModel(parsed.contents, parsed.keys, parsed.overrides);
|
||||
} catch (e) {
|
||||
@@ -695,10 +696,11 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati
|
||||
}
|
||||
|
||||
async updateConfiguration(configurationModel: ConfigurationModel): Promise<void> {
|
||||
const key = await this.key;
|
||||
if (configurationModel.keys.length) {
|
||||
await this.configurationCache.write(this.key, JSON.stringify(configurationModel.toJSON()));
|
||||
await this.configurationCache.write(key, JSON.stringify(configurationModel.toJSON()));
|
||||
} else {
|
||||
await this.configurationCache.remove(this.key);
|
||||
await this.configurationCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/configuration/common/configuration';
|
||||
|
||||
export class ConfigurationCache implements IConfigurationCache {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
async read(key: ConfigurationKey): Promise<string> {
|
||||
return '';
|
||||
}
|
||||
|
||||
async write(key: ConfigurationKey, content: string): Promise<void> {
|
||||
}
|
||||
|
||||
async remove(key: ConfigurationKey): Promise<void> {
|
||||
}
|
||||
}
|
||||
@@ -528,7 +528,7 @@ export class ConfigurationEditingService {
|
||||
|
||||
private getConfigurationFileResource(target: EditableConfigurationTarget, config: IConfigurationValue, relativePath: string, resource: URI | null | undefined): URI | null {
|
||||
if (target === EditableConfigurationTarget.USER_LOCAL) {
|
||||
return this.environmentService.settingsResource;
|
||||
return URI.file(this.environmentService.appSettingsPath);
|
||||
}
|
||||
if (target === EditableConfigurationTarget.USER_REMOTE) {
|
||||
return this.remoteSettingsResource;
|
||||
|
||||
@@ -39,11 +39,10 @@ export class JSONEditingService implements IJSONEditingService {
|
||||
return Promise.resolve(this.queue.queue(() => this.doWriteConfiguration(resource, value, save))); // queue up writes to prevent race conditions
|
||||
}
|
||||
|
||||
private async doWriteConfiguration(resource: URI, value: IJSONValue, save: boolean): Promise<void> {
|
||||
const reference = await this.resolveAndValidate(resource, save);
|
||||
await this.writeToBuffer(reference.object.textEditorModel, value);
|
||||
|
||||
reference.dispose();
|
||||
private doWriteConfiguration(resource: URI, value: IJSONValue, save: boolean): Promise<void> {
|
||||
return this.resolveAndValidate(resource, save)
|
||||
.then(reference => this.writeToBuffer(reference.object.textEditorModel, value)
|
||||
.then(() => reference.dispose()));
|
||||
}
|
||||
|
||||
private async writeToBuffer(model: ITextModel, value: IJSONValue): Promise<any> {
|
||||
@@ -98,21 +97,21 @@ export class JSONEditingService implements IJSONEditingService {
|
||||
return parseErrors.length > 0;
|
||||
}
|
||||
|
||||
private async resolveAndValidate(resource: URI, checkDirty: boolean): Promise<IReference<IResolvedTextEditorModel>> {
|
||||
const reference = await this.resolveModelReference(resource);
|
||||
private resolveAndValidate(resource: URI, checkDirty: boolean): Promise<IReference<IResolvedTextEditorModel>> {
|
||||
return this.resolveModelReference(resource)
|
||||
.then(reference => {
|
||||
const model = reference.object.textEditorModel;
|
||||
|
||||
const model = reference.object.textEditorModel;
|
||||
if (this.hasParseErrors(model)) {
|
||||
return this.reject<IReference<IResolvedTextEditorModel>>(JSONEditingErrorCode.ERROR_INVALID_FILE);
|
||||
}
|
||||
|
||||
if (this.hasParseErrors(model)) {
|
||||
return this.reject<IReference<IResolvedTextEditorModel>>(JSONEditingErrorCode.ERROR_INVALID_FILE);
|
||||
}
|
||||
|
||||
// Target cannot be dirty if not writing into buffer
|
||||
if (checkDirty && this.textFileService.isDirty(resource)) {
|
||||
return this.reject<IReference<IResolvedTextEditorModel>>(JSONEditingErrorCode.ERROR_FILE_DIRTY);
|
||||
}
|
||||
|
||||
return reference;
|
||||
// Target cannot be dirty if not writing into buffer
|
||||
if (checkDirty && this.textFileService.isDirty(resource)) {
|
||||
return this.reject<IReference<IResolvedTextEditorModel>>(JSONEditingErrorCode.ERROR_FILE_DIRTY);
|
||||
}
|
||||
return reference;
|
||||
});
|
||||
}
|
||||
|
||||
private reject<T>(code: JSONEditingErrorCode): Promise<T> {
|
||||
|
||||
@@ -47,7 +47,7 @@ class SettingsTestEnvironmentService extends EnvironmentService {
|
||||
super(args, _execPath);
|
||||
}
|
||||
|
||||
get settingsResource(): URI { return URI.file(this.customAppSettingsHome); }
|
||||
get appSettingsPath(): string { return this.customAppSettingsHome; }
|
||||
}
|
||||
|
||||
suite('ConfigurationEditingService', () => {
|
||||
@@ -106,7 +106,7 @@ suite('ConfigurationEditingService', () => {
|
||||
instantiationService.stub(IEnvironmentService, environmentService);
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {});
|
||||
instantiationService.stub(IRemoteAgentService, remoteAgentService);
|
||||
const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, new ConfigurationFileService(), remoteAgentService);
|
||||
const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache: new ConfigurationCache(environmentService) }, new ConfigurationFileService(), remoteAgentService);
|
||||
instantiationService.stub(IWorkspaceContextService, workspaceService);
|
||||
return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => {
|
||||
instantiationService.stub(IConfigurationService, workspaceService);
|
||||
@@ -187,7 +187,7 @@ suite('ConfigurationEditingService', () => {
|
||||
test('do not notify error', () => {
|
||||
instantiationService.stub(ITextFileService, 'isDirty', true);
|
||||
const target = sinon.stub();
|
||||
instantiationService.stub(INotificationService, <INotificationService>{ prompt: target, _serviceBrand: null!, notify: null!, error: null!, info: null!, warn: null!, status: null! });
|
||||
instantiationService.stub(INotificationService, <INotificationService>{ prompt: target, _serviceBrand: null, notify: null!, error: null!, info: null!, warn: null! });
|
||||
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true })
|
||||
.then(() => assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'),
|
||||
(error: ConfigurationEditingError) => {
|
||||
|
||||
@@ -44,7 +44,6 @@ import { ConfigurationCache } from 'vs/workbench/services/configuration/node/con
|
||||
import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
class SettingsTestEnvironmentService extends EnvironmentService {
|
||||
|
||||
@@ -52,7 +51,7 @@ class SettingsTestEnvironmentService extends EnvironmentService {
|
||||
super(args, _execPath);
|
||||
}
|
||||
|
||||
get settingsResource(): URI { return URI.file(this.customAppSettingsHome); }
|
||||
get appSettingsPath(): string { return this.customAppSettingsHome; }
|
||||
}
|
||||
|
||||
function setUpFolderWorkspace(folderName: string): Promise<{ parentDir: string, folderDir: string }> {
|
||||
@@ -154,7 +153,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => {
|
||||
const configurationFileService = new ConfigurationFileService();
|
||||
configurationFileService.fileService = fileService;
|
||||
const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() };
|
||||
testObject = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache, remoteAuthority }, configurationFileService, remoteAgentService);
|
||||
testObject = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache, remoteAuthority }, configurationFileService, remoteAgentService);
|
||||
instantiationService.stub(IWorkspaceContextService, testObject);
|
||||
instantiationService.stub(IConfigurationService, testObject);
|
||||
instantiationService.stub(IEnvironmentService, environmentService);
|
||||
@@ -248,26 +247,26 @@ suite('WorkspaceConfigurationService - Remote Folder', () => {
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('update remote settings', async () => {
|
||||
registerRemoteFileSystemProvider();
|
||||
resolveRemoteEnvironment();
|
||||
await initialize();
|
||||
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet');
|
||||
const promise = new Promise((c, e) => {
|
||||
testObject.onDidChangeConfiguration(event => {
|
||||
try {
|
||||
assert.equal(event.source, ConfigurationTarget.USER);
|
||||
assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
|
||||
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
|
||||
c();
|
||||
} catch (error) {
|
||||
e(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
await instantiationService.get(IFileService).writeFile(URI.file(remoteSettingsFile), VSBuffer.fromString('{ "configurationService.remote.machineSetting": "remoteValue" }'));
|
||||
return promise;
|
||||
});
|
||||
// test('update remote settings', async () => {
|
||||
// registerRemoteFileSystemProvider();
|
||||
// resolveRemoteEnvironment();
|
||||
// await initialize();
|
||||
// assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet');
|
||||
// const promise = new Promise((c, e) => {
|
||||
// testObject.onDidChangeConfiguration(event => {
|
||||
// try {
|
||||
// assert.equal(event.source, ConfigurationTarget.USER);
|
||||
// assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
|
||||
// assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
|
||||
// c();
|
||||
// } catch (error) {
|
||||
// e(error);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
|
||||
// return promise;
|
||||
// });
|
||||
|
||||
test('machine settings in local user settings does not override defaults', async () => {
|
||||
fs.writeFileSync(globalSettingsFile, '{ "configurationService.remote.machineSetting": "globalValue" }');
|
||||
|
||||
@@ -115,7 +115,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
|
||||
}
|
||||
}
|
||||
|
||||
private createMenu(delegate: IContextMenuDelegate, entries: ReadonlyArray<IAction | ContextSubMenu>, onHide: () => void): IContextMenuItem[] {
|
||||
private createMenu(delegate: IContextMenuDelegate, entries: Array<IAction | ContextSubMenu>, onHide: () => void): IContextMenuItem[] {
|
||||
const actionRunner = delegate.actionRunner || new ActionRunner();
|
||||
|
||||
return entries.map(entry => this.createMenuItem(delegate, entry, actionRunner, onHide));
|
||||
|
||||
@@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDecorationsService, IDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IDecorationData } from './decorations';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isThenable } from 'vs/base/common/async';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom';
|
||||
@@ -95,20 +95,20 @@ class DecorationRule {
|
||||
}
|
||||
}
|
||||
|
||||
class DecorationStyles extends Disposable {
|
||||
class DecorationStyles {
|
||||
|
||||
private readonly _disposables: IDisposable[];
|
||||
private readonly _styleElement = createStyleSheet();
|
||||
private readonly _decorationRules = new Map<string, DecorationRule>();
|
||||
|
||||
constructor(
|
||||
private _themeService: IThemeService,
|
||||
) {
|
||||
super();
|
||||
this._register(this._themeService.onThemeChange(this._onThemeChange, this));
|
||||
this._disposables = [this._themeService.onThemeChange(this._onThemeChange, this)];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
dispose(this._disposables);
|
||||
|
||||
const parent = this._styleElement.parentElement;
|
||||
if (parent) {
|
||||
|
||||
@@ -86,7 +86,6 @@ export class FileDialogService implements IFileDialogService {
|
||||
|
||||
private shouldUseSimplified(schema: string): boolean {
|
||||
const setting = this.configurationService.getValue('files.simpleDialog.enable');
|
||||
|
||||
return (schema !== Schemas.file) || (setting === true);
|
||||
}
|
||||
|
||||
@@ -94,7 +93,7 @@ export class FileDialogService implements IFileDialogService {
|
||||
return schema !== Schemas.file ? [schema, Schemas.file] : [schema];
|
||||
}
|
||||
|
||||
async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
@@ -104,23 +103,21 @@ export class FileDialogService implements IFileDialogService {
|
||||
if (this.shouldUseSimplified(schema)) {
|
||||
const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder');
|
||||
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
|
||||
|
||||
const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
|
||||
if (uri) {
|
||||
const stat = await this.fileService.resolve(uri);
|
||||
|
||||
const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri };
|
||||
return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
|
||||
return;
|
||||
return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => {
|
||||
if (uri) {
|
||||
return (this.fileService.resolve(uri)).then(stat => {
|
||||
const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri };
|
||||
return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow });
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
async pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
@@ -130,19 +127,18 @@ export class FileDialogService implements IFileDialogService {
|
||||
if (this.shouldUseSimplified(schema)) {
|
||||
const title = nls.localize('openFile.title', 'Open File');
|
||||
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
|
||||
|
||||
const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
|
||||
return;
|
||||
return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => {
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
async pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
@@ -152,19 +148,18 @@ export class FileDialogService implements IFileDialogService {
|
||||
if (this.shouldUseSimplified(schema)) {
|
||||
const title = nls.localize('openFolder.title', 'Open Folder');
|
||||
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
|
||||
|
||||
const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
|
||||
return;
|
||||
return this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => {
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
|
||||
pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
@@ -175,13 +170,12 @@ export class FileDialogService implements IFileDialogService {
|
||||
const title = nls.localize('openWorkspace.title', 'Open Workspace');
|
||||
const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }];
|
||||
const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well
|
||||
|
||||
const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems });
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
|
||||
return;
|
||||
return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }).then(uri => {
|
||||
if (uri) {
|
||||
return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
@@ -196,34 +190,33 @@ export class FileDialogService implements IFileDialogService {
|
||||
};
|
||||
}
|
||||
|
||||
async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
if (this.shouldUseSimplified(schema)) {
|
||||
if (!options.availableFileSystems) {
|
||||
options.availableFileSystems = [schema]; // by default only allow saving in the own file system
|
||||
}
|
||||
|
||||
return this.saveRemoteResource(options);
|
||||
}
|
||||
|
||||
const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options));
|
||||
if (result) {
|
||||
return URI.file(result);
|
||||
}
|
||||
return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => {
|
||||
if (result) {
|
||||
return URI.file(result);
|
||||
}
|
||||
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
async showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
|
||||
showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
if (this.shouldUseSimplified(schema)) {
|
||||
if (!options.availableFileSystems) {
|
||||
options.availableFileSystems = [schema]; // by default only allow loading in the own file system
|
||||
}
|
||||
|
||||
const uri = await this.pickRemoteResource(options);
|
||||
|
||||
return uri ? [uri] : undefined;
|
||||
return this.pickRemoteResource(options).then(uri => {
|
||||
return uri ? [uri] : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
const defaultUri = options.defaultUri;
|
||||
@@ -250,20 +243,16 @@ export class FileDialogService implements IFileDialogService {
|
||||
newOptions.properties!.push('multiSelections');
|
||||
}
|
||||
|
||||
const result = await this.windowService.showOpenDialog(newOptions);
|
||||
|
||||
return result ? result.map(URI.file) : undefined;
|
||||
return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined);
|
||||
}
|
||||
|
||||
private pickRemoteResource(options: IOpenDialogOptions): Promise<URI | undefined> {
|
||||
const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog);
|
||||
|
||||
return remoteFileDialog.showOpenDialog(options);
|
||||
}
|
||||
|
||||
private saveRemoteResource(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog);
|
||||
|
||||
return remoteFileDialog.showSaveDialog(options);
|
||||
}
|
||||
|
||||
@@ -274,6 +263,7 @@ export class FileDialogService implements IFileDialogService {
|
||||
private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string {
|
||||
return options.availableFileSystems && options.availableFileSystems[0] || options.defaultUri && options.defaultUri.scheme || this.getSchemeFilterForWindow();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean {
|
||||
|
||||
@@ -28,8 +28,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { isValidBasename } from 'vs/base/common/extpath';
|
||||
import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
|
||||
interface FileQuickPickItem extends IQuickPickItem {
|
||||
uri: URI;
|
||||
@@ -62,11 +60,6 @@ export class RemoteFileDialog {
|
||||
private badPath: string | undefined;
|
||||
private remoteAgentEnvironment: IRemoteAgentEnvironment | null;
|
||||
private separator: string;
|
||||
private onBusyChangeEmitter = new Emitter<boolean>();
|
||||
|
||||
protected disposables: IDisposable[] = [
|
||||
this.onBusyChangeEmitter
|
||||
];
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@@ -86,17 +79,6 @@ export class RemoteFileDialog {
|
||||
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
set busy(busy: boolean) {
|
||||
if (this.filePickBox.busy !== busy) {
|
||||
this.filePickBox.busy = busy;
|
||||
this.onBusyChangeEmitter.fire(busy);
|
||||
}
|
||||
}
|
||||
|
||||
get busy(): boolean {
|
||||
return this.filePickBox.busy;
|
||||
}
|
||||
|
||||
public async showOpenDialog(options: IOpenDialogOptions = {}): Promise<URI | undefined> {
|
||||
this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems);
|
||||
this.userHome = await this.getUserHome();
|
||||
@@ -203,7 +185,7 @@ export class RemoteFileDialog {
|
||||
|
||||
return new Promise<URI | undefined>(async (resolve) => {
|
||||
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
|
||||
this.busy = true;
|
||||
this.filePickBox.busy = true;
|
||||
this.filePickBox.matchOnLabel = false;
|
||||
this.filePickBox.autoFocusOnList = false;
|
||||
this.filePickBox.ignoreFocusOut = true;
|
||||
@@ -221,7 +203,7 @@ export class RemoteFileDialog {
|
||||
}
|
||||
}
|
||||
|
||||
let isResolving: number = 0;
|
||||
let isResolving = false;
|
||||
let isAcceptHandled = false;
|
||||
this.currentFolder = homedir;
|
||||
this.userEnteredPathSegment = '';
|
||||
@@ -236,16 +218,15 @@ export class RemoteFileDialog {
|
||||
resolve(uri);
|
||||
dialog.contextKey.set(false);
|
||||
dialog.filePickBox.dispose();
|
||||
dispose(dialog.disposables);
|
||||
}
|
||||
|
||||
this.filePickBox.onDidCustom(() => {
|
||||
if (isAcceptHandled || this.busy) {
|
||||
if (isAcceptHandled || this.filePickBox.busy) {
|
||||
return undefined; // {{SQL CARBON EDIT}} @todo anthonydresser return to return; when we do strict null checks
|
||||
}
|
||||
|
||||
isAcceptHandled = true;
|
||||
isResolving++;
|
||||
isResolving = true;
|
||||
if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
|
||||
this.options.availableFileSystems.shift();
|
||||
}
|
||||
@@ -262,38 +243,25 @@ export class RemoteFileDialog {
|
||||
}
|
||||
});
|
||||
|
||||
function handleAccept(dialog: RemoteFileDialog) {
|
||||
if (dialog.busy) {
|
||||
// Save the accept until the file picker is not busy.
|
||||
dialog.onBusyChangeEmitter.event((busy: boolean) => {
|
||||
if (!busy) {
|
||||
handleAccept(dialog);
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else if (isAcceptHandled) {
|
||||
this.filePickBox.onDidAccept(_ => {
|
||||
if (isAcceptHandled || this.filePickBox.busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
isAcceptHandled = true;
|
||||
isResolving++;
|
||||
dialog.onDidAccept().then(resolveValue => {
|
||||
isResolving = true;
|
||||
this.onDidAccept().then(resolveValue => {
|
||||
if (resolveValue) {
|
||||
dialog.filePickBox.hide();
|
||||
doResolve(dialog, resolveValue);
|
||||
} else if (dialog.hidden) {
|
||||
doResolve(dialog, undefined);
|
||||
this.filePickBox.hide();
|
||||
doResolve(this, resolveValue);
|
||||
} else if (this.hidden) {
|
||||
doResolve(this, undefined);
|
||||
} else {
|
||||
isResolving--;
|
||||
isResolving = false;
|
||||
isAcceptHandled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.filePickBox.onDidAccept(_ => {
|
||||
handleAccept(this);
|
||||
});
|
||||
|
||||
this.filePickBox.onDidChangeActive(i => {
|
||||
isAcceptHandled = false;
|
||||
// update input box to match the first selected item
|
||||
@@ -328,7 +296,7 @@ export class RemoteFileDialog {
|
||||
});
|
||||
this.filePickBox.onDidHide(() => {
|
||||
this.hidden = true;
|
||||
if (isResolving === 0) {
|
||||
if (!isResolving) {
|
||||
doResolve(this, undefined);
|
||||
}
|
||||
});
|
||||
@@ -341,7 +309,7 @@ export class RemoteFileDialog {
|
||||
} else {
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
|
||||
}
|
||||
this.busy = false;
|
||||
this.filePickBox.busy = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -372,15 +340,14 @@ export class RemoteFileDialog {
|
||||
const relativePath = resources.relativePath(currentDisplayUri, directUri);
|
||||
const isSameRoot = (this.filePickBox.value.length > 1 && currentPath.length > 1) ? equalsIgnoreCase(this.filePickBox.value.substr(0, 2), currentPath.substr(0, 2)) : false;
|
||||
if (relativePath && isSameRoot) {
|
||||
const path = resources.joinPath(this.currentFolder, relativePath);
|
||||
return resources.hasTrailingPathSeparator(directUri) ? resources.addTrailingPathSeparator(path) : path;
|
||||
return resources.joinPath(this.currentFolder, relativePath);
|
||||
} else {
|
||||
return directUri;
|
||||
}
|
||||
}
|
||||
|
||||
private async onDidAccept(): Promise<URI | undefined> {
|
||||
this.busy = true;
|
||||
this.filePickBox.busy = true;
|
||||
if (this.filePickBox.activeItems.length === 1) {
|
||||
const item = this.filePickBox.selectedItems[0];
|
||||
if (item.isFolder) {
|
||||
@@ -390,9 +357,10 @@ export class RemoteFileDialog {
|
||||
// When possible, cause the update to happen by modifying the input box.
|
||||
// This allows all input box updates to happen first, and uses the same code path as the user typing.
|
||||
const newPath = this.pathFromUri(item.uri);
|
||||
if (startsWithIgnoreCase(newPath, this.filePickBox.value) && (equalsIgnoreCase(item.label, resources.basename(item.uri)))) {
|
||||
this.filePickBox.valueSelection = [this.pathFromUri(this.currentFolder).length, this.filePickBox.value.length];
|
||||
this.insertText(newPath, item.label);
|
||||
if (startsWithIgnoreCase(newPath, this.filePickBox.value)) {
|
||||
const insertValue = newPath.substring(this.filePickBox.value.length, newPath.length);
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
|
||||
this.insertText(newPath, insertValue);
|
||||
} else if ((item.label === '..') && startsWithIgnoreCase(this.filePickBox.value, newPath)) {
|
||||
this.filePickBox.valueSelection = [newPath.length, this.filePickBox.value.length];
|
||||
this.insertText(newPath, '');
|
||||
@@ -400,13 +368,11 @@ export class RemoteFileDialog {
|
||||
await this.updateItems(item.uri, true);
|
||||
}
|
||||
}
|
||||
this.filePickBox.busy = false;
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
}
|
||||
} else {
|
||||
// If the items have updated, don't try to resolve
|
||||
if ((await this.tryUpdateItems(this.filePickBox.value, this.filePickBoxValue())) !== UpdateResult.NotUpdated) {
|
||||
this.filePickBox.busy = false;
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
}
|
||||
}
|
||||
@@ -422,10 +388,10 @@ export class RemoteFileDialog {
|
||||
resolveValue = this.addPostfix(resolveValue);
|
||||
}
|
||||
if (await this.validate(resolveValue)) {
|
||||
this.busy = false;
|
||||
this.filePickBox.busy = false;
|
||||
return resolveValue;
|
||||
}
|
||||
this.busy = false;
|
||||
this.filePickBox.busy = false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -504,7 +470,7 @@ export class RemoteFileDialog {
|
||||
}
|
||||
|
||||
private setAutoComplete(startingValue: string, startingBasename: string, quickPickItem: FileQuickPickItem, force: boolean = false): boolean {
|
||||
if (this.busy) {
|
||||
if (this.filePickBox.busy) {
|
||||
// We're in the middle of something else. Doing an auto complete now can result jumbled or incorrect autocompletes.
|
||||
this.userEnteredPathSegment = startingBasename;
|
||||
this.autoCompletePathSegment = '';
|
||||
@@ -528,6 +494,9 @@ export class RemoteFileDialog {
|
||||
// Changing the active items will trigger the onDidActiveItemsChanged. Clear the autocomplete first, then set it after.
|
||||
this.autoCompletePathSegment = '';
|
||||
this.filePickBox.activeItems = [quickPickItem];
|
||||
this.autoCompletePathSegment = this.trimTrailingSlash(itemBasename.substr(startingBasename.length));
|
||||
this.insertText(startingValue + this.autoCompletePathSegment, this.autoCompletePathSegment);
|
||||
this.filePickBox.valueSelection = [startingValue.length, this.filePickBox.value.length];
|
||||
return true;
|
||||
} else if (force && (!equalsIgnoreCase(quickPickItem.label, (this.userEnteredPathSegment + this.autoCompletePathSegment)))) {
|
||||
this.userEnteredPathSegment = '';
|
||||
@@ -672,7 +641,7 @@ export class RemoteFileDialog {
|
||||
}
|
||||
|
||||
private async updateItems(newFolder: URI, force: boolean = false, trailing?: string) {
|
||||
this.busy = true;
|
||||
this.filePickBox.busy = true;
|
||||
this.userEnteredPathSegment = trailing ? trailing : '';
|
||||
this.autoCompletePathSegment = '';
|
||||
const newValue = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true);
|
||||
@@ -694,7 +663,7 @@ export class RemoteFileDialog {
|
||||
// If there is trailing, we don't move the cursor. If there is no trailing, cursor goes at the end.
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
|
||||
}
|
||||
this.busy = false;
|
||||
this.filePickBox.busy = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -658,17 +658,18 @@ export class DelegatingEditorService extends EditorService {
|
||||
this.editorOpenHandler = handler;
|
||||
}
|
||||
|
||||
protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise<IEditor | null> {
|
||||
protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise<IEditor | null> {
|
||||
if (!this.editorOpenHandler) {
|
||||
return super.doOpenEditor(group, editor, options);
|
||||
}
|
||||
|
||||
const control = await this.editorOpenHandler(group, editor, options);
|
||||
if (control) {
|
||||
return control; // the opening was handled, so return early
|
||||
}
|
||||
return this.editorOpenHandler(group, editor, options).then(control => {
|
||||
if (control) {
|
||||
return control; // the opening was handled, so return early
|
||||
}
|
||||
|
||||
return super.doOpenEditor(group, editor, options);
|
||||
return super.doOpenEditor(group, editor, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
|
||||
const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled';
|
||||
const ENABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/enabled';
|
||||
@@ -37,7 +36,6 @@ export class ExtensionEnablementService extends Disposable implements IExtension
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
) {
|
||||
super();
|
||||
this.storageManger = this._register(new StorageManager(storageService));
|
||||
@@ -139,7 +137,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension
|
||||
return disabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));
|
||||
}
|
||||
if (this.environmentService.configuration.remoteAuthority) {
|
||||
const server = isUIExtension(extension.manifest, this.productService, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer;
|
||||
const server = isUIExtension(extension.manifest, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer;
|
||||
return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -15,7 +15,6 @@ import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ProductService } from 'vs/platform/product/node/productService';
|
||||
|
||||
function storageService(instantiationService: TestInstantiationService): IStorageService {
|
||||
let service = instantiationService.get(IStorageService);
|
||||
@@ -39,9 +38,7 @@ export class TestExtensionEnablementService extends ExtensionEnablementService {
|
||||
instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, { configuration: Object.create(null) } as IWorkbenchEnvironmentService),
|
||||
instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService,
|
||||
{ onDidInstallExtension: new Emitter<DidInstallExtensionEvent>().event, onDidUninstallExtension: new Emitter<DidUninstallExtensionEvent>().event } as IExtensionManagementService),
|
||||
instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService),
|
||||
new ProductService()
|
||||
);
|
||||
instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService));
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService';
|
||||
import { browserWebSocketFactory } from 'vs/platform/remote/browser/browserWebSocketFactory';
|
||||
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager';
|
||||
import { RemoteExtensionHostClient, IInitDataProvider } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
|
||||
|
||||
private _remoteExtensionsEnvironmentData: IRemoteAgentEnvironment | null;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IExtensionEnablementService extensionEnablementService: IExtensionEnablementService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IProductService productService: IProductService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
super(
|
||||
instantiationService,
|
||||
notificationService,
|
||||
environmentService,
|
||||
telemetryService,
|
||||
extensionEnablementService,
|
||||
fileService,
|
||||
productService,
|
||||
);
|
||||
|
||||
this._remoteExtensionsEnvironmentData = null;
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
private _createProvider(remoteAuthority: string): IInitDataProvider {
|
||||
return {
|
||||
remoteAuthority: remoteAuthority,
|
||||
getInitData: () => {
|
||||
return this.whenInstalledExtensionsRegistered().then(() => {
|
||||
return this._remoteExtensionsEnvironmentData!;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] {
|
||||
const result: ExtensionHostProcessManager[] = [];
|
||||
|
||||
const remoteAgentConnection = this._remoteAgentService.getConnection()!;
|
||||
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), browserWebSocketFactory);
|
||||
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
|
||||
result.push(remoteExtHostProcessManager);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async _scanAndHandleExtensions(): Promise<void> {
|
||||
// fetch the remote environment
|
||||
const remoteEnv = (await this._remoteAgentService.getEnvironment())!;
|
||||
|
||||
// enable or disable proposed API per extension
|
||||
this._checkEnableProposedApi(remoteEnv.extensions);
|
||||
|
||||
// remove disabled extensions
|
||||
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension));
|
||||
|
||||
// save for remote extension's init data
|
||||
this._remoteExtensionsEnvironmentData = remoteEnv;
|
||||
|
||||
// this._handleExtensionPoints((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
|
||||
const result = this._registry.deltaExtensions(remoteEnv.extensions, []);
|
||||
if (result.removedDueToLooping.length > 0) {
|
||||
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
|
||||
}
|
||||
|
||||
this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions());
|
||||
}
|
||||
|
||||
public _onExtensionHostExit(code: number): void {
|
||||
console.log(`vscode:exit`, code);
|
||||
// ipc.send('vscode:exit', code);
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IExtensionService, ExtensionService);
|
||||
@@ -1,498 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
import { isEqualOrParent } from 'vs/base/common/resources';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { BetterMergeId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
|
||||
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
|
||||
|
||||
export abstract class AbstractExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
protected readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event;
|
||||
|
||||
protected readonly _onDidChangeExtensionsStatus: Emitter<ExtensionIdentifier[]> = this._register(new Emitter<ExtensionIdentifier[]>());
|
||||
public readonly onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]> = this._onDidChangeExtensionsStatus.event;
|
||||
|
||||
protected readonly _onDidChangeExtensions: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeExtensions: Event<void> = this._onDidChangeExtensions.event;
|
||||
|
||||
protected readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
|
||||
public readonly onWillActivateByEvent: Event<IWillActivateEvent> = this._onWillActivateByEvent.event;
|
||||
|
||||
protected readonly _onDidChangeResponsiveChange = this._register(new Emitter<IResponsiveStateChangeEvent>());
|
||||
public readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = this._onDidChangeResponsiveChange.event;
|
||||
|
||||
protected readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _installedExtensionsReady: Barrier;
|
||||
protected readonly _isDev: boolean;
|
||||
private readonly _extensionsMessages: Map<string, IMessage[]>;
|
||||
protected readonly _allRequestedActivateEvents: { [activationEvent: string]: boolean; };
|
||||
private readonly _proposedApiController: ProposedApiController;
|
||||
private readonly _isExtensionDevHost: boolean;
|
||||
protected readonly _isExtensionDevTestFromCli: boolean;
|
||||
|
||||
// --- Members used per extension host process
|
||||
protected _extensionHostProcessManagers: ExtensionHostProcessManager[];
|
||||
protected _extensionHostActiveExtensions: Map<string, ExtensionIdentifier>;
|
||||
private _extensionHostProcessActivationTimes: Map<string, ActivationTimes>;
|
||||
private _extensionHostExtensionRuntimeErrors: Map<string, Error[]>;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
|
||||
@INotificationService protected readonly _notificationService: INotificationService,
|
||||
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@ITelemetryService protected readonly _telemetryService: ITelemetryService,
|
||||
@IExtensionEnablementService protected readonly _extensionEnablementService: IExtensionEnablementService,
|
||||
@IFileService protected readonly _fileService: IFileService,
|
||||
@IProductService protected readonly _productService: IProductService
|
||||
) {
|
||||
super();
|
||||
|
||||
// help the file service to activate providers by activating extensions by file system event
|
||||
this._register(this._fileService.onWillActivateFileSystemProvider(e => {
|
||||
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
|
||||
}));
|
||||
|
||||
this._registry = new ExtensionDescriptionRegistry([]);
|
||||
this._installedExtensionsReady = new Barrier();
|
||||
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
|
||||
this._extensionsMessages = new Map<string, IMessage[]>();
|
||||
this._allRequestedActivateEvents = Object.create(null);
|
||||
this._proposedApiController = new ProposedApiController(this._environmentService, this._productService);
|
||||
|
||||
this._extensionHostProcessManagers = [];
|
||||
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
|
||||
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
|
||||
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
|
||||
|
||||
const devOpts = parseExtensionDevOptions(this._environmentService);
|
||||
this._isExtensionDevHost = devOpts.isExtensionDevHost;
|
||||
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
|
||||
}
|
||||
|
||||
protected async _initialize(): Promise<void> {
|
||||
perf.mark('willLoadExtensions');
|
||||
this._startExtensionHostProcess(true, []);
|
||||
this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions'));
|
||||
await this._scanAndHandleExtensions();
|
||||
this._releaseBarrier();
|
||||
}
|
||||
|
||||
private _releaseBarrier(): void {
|
||||
perf.mark('extensionHostReady');
|
||||
this._installedExtensionsReady.open();
|
||||
this._onDidRegisterExtensions.fire(undefined);
|
||||
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
|
||||
}
|
||||
|
||||
private _stopExtensionHostProcess(): void {
|
||||
let previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
|
||||
this._extensionHostActiveExtensions.forEach((value) => {
|
||||
previouslyActivatedExtensionIds.push(value);
|
||||
});
|
||||
|
||||
for (const manager of this._extensionHostProcessManagers) {
|
||||
manager.dispose();
|
||||
}
|
||||
this._extensionHostProcessManagers = [];
|
||||
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
|
||||
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
|
||||
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
|
||||
|
||||
if (previouslyActivatedExtensionIds.length > 0) {
|
||||
this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds);
|
||||
}
|
||||
}
|
||||
|
||||
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
|
||||
this._stopExtensionHostProcess();
|
||||
|
||||
const processManagers = this._createExtensionHosts(isInitialStart, initialActivationEvents);
|
||||
processManagers.forEach((processManager) => {
|
||||
processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal));
|
||||
processManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
|
||||
this._extensionHostProcessManagers.push(processManager);
|
||||
});
|
||||
}
|
||||
|
||||
private _onExtensionHostCrashOrExit(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void {
|
||||
|
||||
// Unexpected termination
|
||||
if (!this._isExtensionDevHost) {
|
||||
this._onExtensionHostCrashed(extensionHost, code, signal);
|
||||
return;
|
||||
}
|
||||
|
||||
this._onExtensionHostExit(code);
|
||||
}
|
||||
|
||||
protected _onExtensionHostCrashed(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void {
|
||||
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
|
||||
this._stopExtensionHostProcess();
|
||||
}
|
||||
|
||||
//#region IExtensionService
|
||||
|
||||
public canAddExtension(extension: IExtensionDescription): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public canRemoveExtension(extension: IExtensionDescription): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public restartExtensionHost(): void {
|
||||
this._stopExtensionHostProcess();
|
||||
this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents));
|
||||
}
|
||||
|
||||
public startExtensionHost(): void {
|
||||
this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents));
|
||||
}
|
||||
|
||||
public stopExtensionHost(): void {
|
||||
this._stopExtensionHostProcess();
|
||||
}
|
||||
|
||||
public activateByEvent(activationEvent: string): Promise<void> {
|
||||
if (this._installedExtensionsReady.isOpen()) {
|
||||
// Extensions have been scanned and interpreted
|
||||
|
||||
// Record the fact that this activationEvent was requested (in case of a restart)
|
||||
this._allRequestedActivateEvents[activationEvent] = true;
|
||||
|
||||
if (!this._registry.containsActivationEvent(activationEvent)) {
|
||||
// There is no extension that is interested in this activation event
|
||||
return NO_OP_VOID_PROMISE;
|
||||
}
|
||||
|
||||
return this._activateByEvent(activationEvent);
|
||||
} else {
|
||||
// Extensions have not been scanned yet.
|
||||
|
||||
// Record the fact that this activationEvent was requested (in case of a restart)
|
||||
this._allRequestedActivateEvents[activationEvent] = true;
|
||||
|
||||
return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent));
|
||||
}
|
||||
}
|
||||
|
||||
private _activateByEvent(activationEvent: string): Promise<void> {
|
||||
const result = Promise.all(
|
||||
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent))
|
||||
).then(() => { });
|
||||
this._onWillActivateByEvent.fire({
|
||||
event: activationEvent,
|
||||
activation: result
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public whenInstalledExtensionsRegistered(): Promise<boolean> {
|
||||
return this._installedExtensionsReady.wait();
|
||||
}
|
||||
|
||||
public getExtensions(): Promise<IExtensionDescription[]> {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
return this._registry.getAllExtensionDescriptions();
|
||||
});
|
||||
}
|
||||
|
||||
public getExtension(id: string): Promise<IExtensionDescription | undefined> {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
return this._registry.getExtensionDescription(id);
|
||||
});
|
||||
}
|
||||
|
||||
public readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
let availableExtensions = this._registry.getAllExtensionDescriptions();
|
||||
|
||||
let result: ExtensionPointContribution<T>[] = [], resultLen = 0;
|
||||
for (let i = 0, len = availableExtensions.length; i < len; i++) {
|
||||
let desc = availableExtensions[i];
|
||||
|
||||
if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) {
|
||||
result[resultLen++] = new ExtensionPointContribution<T>(desc, desc.contributes[extPoint.name]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } {
|
||||
let result: { [id: string]: IExtensionsStatus; } = Object.create(null);
|
||||
if (this._registry) {
|
||||
const extensions = this._registry.getAllExtensionDescriptions();
|
||||
for (const extension of extensions) {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extension.identifier);
|
||||
result[extension.identifier.value] = {
|
||||
messages: this._extensionsMessages.get(extensionKey) || [],
|
||||
activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey),
|
||||
runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [],
|
||||
};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getInspectPort(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
// --- impl
|
||||
|
||||
protected _checkEnableProposedApi(extensions: IExtensionDescription[]): void {
|
||||
for (let extension of extensions) {
|
||||
this._proposedApiController.updateEnableProposedApi(extension);
|
||||
}
|
||||
}
|
||||
|
||||
private _isExtensionUnderDevelopment(extension: IExtensionDescription): boolean {
|
||||
if (this._environmentService.isExtensionDevelopment) {
|
||||
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
|
||||
if (extDevLocs) {
|
||||
const extLocation = extension.extensionLocation;
|
||||
for (let p of extDevLocs) {
|
||||
if (isEqualOrParent(extLocation, p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected _isEnabled(extension: IExtensionDescription): boolean {
|
||||
if (this._isExtensionUnderDevelopment(extension)) {
|
||||
// Never disable extensions under development
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ExtensionIdentifier.equals(extension.identifier, BetterMergeId)) {
|
||||
// Check if this is the better merge extension which was migrated to a built-in extension
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._extensionEnablementService.isEnabled(toExtension(extension));
|
||||
}
|
||||
|
||||
protected _doHandleExtensionPoints(affectedExtensions: IExtensionDescription[]): void {
|
||||
const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null);
|
||||
for (let extensionDescription of affectedExtensions) {
|
||||
if (extensionDescription.contributes) {
|
||||
for (let extPointName in extensionDescription.contributes) {
|
||||
if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) {
|
||||
affectedExtensionPoints[extPointName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
|
||||
const availableExtensions = this._registry.getAllExtensionDescriptions();
|
||||
const extensionPoints = ExtensionsRegistry.getExtensionPoints();
|
||||
for (let i = 0, len = extensionPoints.length; i < len; i++) {
|
||||
if (affectedExtensionPoints[extensionPoints[i].name]) {
|
||||
AbstractExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _handleExtensionPointMessage(msg: IMessage) {
|
||||
const extensionKey = ExtensionIdentifier.toKey(msg.extensionId);
|
||||
|
||||
if (!this._extensionsMessages.has(extensionKey)) {
|
||||
this._extensionsMessages.set(extensionKey, []);
|
||||
}
|
||||
this._extensionsMessages.get(extensionKey)!.push(msg);
|
||||
|
||||
const extension = this._registry.getExtensionDescription(msg.extensionId);
|
||||
const strMsg = `[${msg.extensionId.value}]: ${msg.message}`;
|
||||
if (extension && extension.isUnderDevelopment) {
|
||||
// This message is about the extension currently being developed
|
||||
this._showMessageToUser(msg.type, strMsg);
|
||||
} else {
|
||||
this._logMessageInConsole(msg.type, strMsg);
|
||||
}
|
||||
|
||||
if (!this._isDev && msg.extensionId) {
|
||||
const { type, extensionId, extensionPointId, message } = msg;
|
||||
/* __GDPR__
|
||||
"extensionsMessage" : {
|
||||
"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"extensionId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
|
||||
"extensionPointId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
|
||||
"message": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('extensionsMessage', {
|
||||
type, extensionId: extensionId.value, extensionPointId, message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static _handleExtensionPoint<T>(extensionPoint: ExtensionPoint<T>, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void {
|
||||
let users: IExtensionPointUser<T>[] = [], usersLen = 0;
|
||||
for (let i = 0, len = availableExtensions.length; i < len; i++) {
|
||||
let desc = availableExtensions[i];
|
||||
|
||||
if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) {
|
||||
users[usersLen++] = {
|
||||
description: desc,
|
||||
value: desc.contributes[extensionPoint.name],
|
||||
collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
extensionPoint.acceptUsers(users);
|
||||
}
|
||||
|
||||
private _showMessageToUser(severity: Severity, msg: string): void {
|
||||
if (severity === Severity.Error || severity === Severity.Warning) {
|
||||
this._notificationService.notify({ severity, message: msg });
|
||||
} else {
|
||||
this._logMessageInConsole(severity, msg);
|
||||
}
|
||||
}
|
||||
|
||||
private _logMessageInConsole(severity: Severity, msg: string): void {
|
||||
if (severity === Severity.Error) {
|
||||
console.error(msg);
|
||||
} else if (severity === Severity.Warning) {
|
||||
console.warn(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
//#region Called by extension host
|
||||
|
||||
public _logOrShowMessage(severity: Severity, msg: string): void {
|
||||
if (this._isDev) {
|
||||
this._showMessageToUser(severity, msg);
|
||||
} else {
|
||||
this._logMessageInConsole(severity, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise<void> {
|
||||
const results = await Promise.all(
|
||||
this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent))
|
||||
);
|
||||
const activated = results.some(e => e);
|
||||
if (!activated) {
|
||||
throw new Error(`Unknown extension ${extensionId.value}`);
|
||||
}
|
||||
}
|
||||
|
||||
public _onWillActivateExtension(extensionId: ExtensionIdentifier): void {
|
||||
this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId);
|
||||
}
|
||||
|
||||
public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
|
||||
this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent));
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extensionId);
|
||||
if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) {
|
||||
this._extensionHostExtensionRuntimeErrors.set(extensionKey, []);
|
||||
}
|
||||
this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err);
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
protected abstract _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[];
|
||||
protected abstract _scanAndHandleExtensions(): Promise<void>;
|
||||
public abstract _onExtensionHostExit(code: number): void;
|
||||
}
|
||||
|
||||
class ProposedApiController {
|
||||
|
||||
private readonly enableProposedApiFor: string | string[];
|
||||
private readonly enableProposedApiForAll: boolean;
|
||||
private readonly productAllowProposedApi: Set<string>;
|
||||
|
||||
constructor(
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IProductService productService: IProductService
|
||||
) {
|
||||
this.enableProposedApiFor = environmentService.args['enable-proposed-api'] || [];
|
||||
if (this.enableProposedApiFor.length) {
|
||||
// Make enabled proposed API be lowercase for case insensitive comparison
|
||||
if (Array.isArray(this.enableProposedApiFor)) {
|
||||
this.enableProposedApiFor = this.enableProposedApiFor.map(id => id.toLowerCase());
|
||||
} else {
|
||||
this.enableProposedApiFor = this.enableProposedApiFor.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
this.enableProposedApiForAll = !environmentService.isBuilt ||
|
||||
(!!environmentService.extensionDevelopmentLocationURI && productService.nameLong !== 'Visual Studio Code') ||
|
||||
(this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args);
|
||||
|
||||
this.productAllowProposedApi = new Set<string>();
|
||||
if (isNonEmptyArray(productService.extensionAllowedProposedApi)) {
|
||||
productService.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id)));
|
||||
}
|
||||
}
|
||||
|
||||
public updateEnableProposedApi(extension: IExtensionDescription): void {
|
||||
if (this._allowProposedApiFromProduct(extension.identifier)) {
|
||||
// fast lane -> proposed api is available to all extensions
|
||||
// that are listed in product.json-files
|
||||
extension.enableProposedApi = true;
|
||||
|
||||
} else if (extension.enableProposedApi && !extension.isBuiltin) {
|
||||
if (
|
||||
!this.enableProposedApiForAll &&
|
||||
this.enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0
|
||||
) {
|
||||
extension.enableProposedApi = false;
|
||||
console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
|
||||
|
||||
} else {
|
||||
// proposed api is available when developing or when an extension was explicitly
|
||||
// spelled out via a command line argument
|
||||
console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _allowProposedApiFromProduct(id: ExtensionIdentifier): boolean {
|
||||
return this.productAllowProposedApi.has(ExtensionIdentifier.toKey(id));
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
|
||||
|
||||
export class ExtensionHostProcessManager extends Disposable {
|
||||
|
||||
public readonly onDidExit: Event<[number, string | null]>;
|
||||
public readonly onDidCrash: Event<[number, string | null]>;
|
||||
|
||||
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
|
||||
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
|
||||
@@ -54,7 +54,6 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
private _resolveAuthorityAttempt: number;
|
||||
|
||||
constructor(
|
||||
public readonly isLocal: boolean,
|
||||
extensionHostProcessWorker: IExtensionHostStarter,
|
||||
private readonly _remoteAuthority: string,
|
||||
initialActivationEvents: string[],
|
||||
@@ -67,7 +66,7 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
this._extensionHostProcessCustomers = [];
|
||||
|
||||
this._extensionHostProcessWorker = extensionHostProcessWorker;
|
||||
this.onDidExit = this._extensionHostProcessWorker.onExit;
|
||||
this.onDidCrash = this._extensionHostProcessWorker.onCrashed;
|
||||
this._extensionHostProcessProxy = this._extensionHostProcessWorker.start()!.then(
|
||||
(protocol) => {
|
||||
return { value: this._createExtensionHostCustomers(protocol) };
|
||||
|
||||
@@ -12,7 +12,6 @@ export interface IExtHostReadyMessage {
|
||||
export interface IExtHostSocketMessage {
|
||||
type: 'VSCODE_EXTHOST_IPC_SOCKET';
|
||||
initialDataChunk: string;
|
||||
skipWebSocketFrames: boolean;
|
||||
}
|
||||
|
||||
export const enum MessageType {
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export interface Translations {
|
||||
[id: string]: string;
|
||||
}
|
||||
|
||||
export namespace Translations {
|
||||
export function equals(a: Translations, b: Translations): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
let aKeys = Object.keys(a);
|
||||
let bKeys: Set<string> = new Set<string>();
|
||||
for (let key of Object.keys(b)) {
|
||||
bKeys.add(key);
|
||||
}
|
||||
if (aKeys.length !== bKeys.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let key of aKeys) {
|
||||
if (a[key] !== b[key]) {
|
||||
return false;
|
||||
}
|
||||
bKeys.delete(key);
|
||||
}
|
||||
return bKeys.size === 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ILog {
|
||||
error(source: string, message: string): void;
|
||||
warn(source: string, message: string): void;
|
||||
info(source: string, message: string): void;
|
||||
}
|
||||
|
||||
export class Logger implements ILog {
|
||||
|
||||
private readonly _messageHandler: (severity: Severity, source: string, message: string) => void;
|
||||
|
||||
constructor(
|
||||
messageHandler: (severity: Severity, source: string, message: string) => void
|
||||
) {
|
||||
this._messageHandler = messageHandler;
|
||||
}
|
||||
|
||||
public error(source: string, message: string): void {
|
||||
this._messageHandler(Severity.Error, source, message);
|
||||
}
|
||||
|
||||
public warn(source: string, message: string): void {
|
||||
this._messageHandler(Severity.Warning, source, message);
|
||||
}
|
||||
|
||||
public info(source: string, message: string): void {
|
||||
this._messageHandler(Severity.Info, source, message);
|
||||
}
|
||||
}
|
||||
@@ -84,8 +84,7 @@ export interface IExtensionHostProfile {
|
||||
}
|
||||
|
||||
export interface IExtensionHostStarter {
|
||||
readonly onExit: Event<[number, string | null]>;
|
||||
|
||||
readonly onCrashed: Event<[number, string | null]>;
|
||||
start(): Promise<IMessagePassingProtocol> | null;
|
||||
getInspectPort(): number | undefined;
|
||||
dispose(): void;
|
||||
|
||||
@@ -341,19 +341,6 @@ export const schema = {
|
||||
pattern: EXTENSION_IDENTIFIER_PATTERN
|
||||
}
|
||||
},
|
||||
extensionKind: {
|
||||
description: nls.localize('extensionKind', "Define the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote."),
|
||||
type: 'string',
|
||||
enum: [
|
||||
'ui',
|
||||
'workspace'
|
||||
],
|
||||
enumDescriptions: [
|
||||
nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."),
|
||||
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.")
|
||||
],
|
||||
default: 'workspace'
|
||||
},
|
||||
scripts: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { EnablementState, IExtensionEnablementService, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
@@ -70,10 +70,10 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||
this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), true);
|
||||
}
|
||||
|
||||
this.disposable = combinedDisposable(
|
||||
this.disposable = combinedDisposable([
|
||||
urlService.registerHandler(this),
|
||||
toDisposable(() => clearInterval(interval))
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
async handleURL(uri: URI, confirmed?: boolean): Promise<boolean> {
|
||||
|
||||
@@ -21,8 +21,7 @@ import pkg from 'vs/platform/product/node/package';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints';
|
||||
import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints';
|
||||
import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, ILog, IRelaxedExtensionDescription, Translations } from 'vs/workbench/services/extensions/node/extensionPoints';
|
||||
|
||||
interface IExtensionCacheData {
|
||||
input: ExtensionScannerInput;
|
||||
@@ -388,3 +387,26 @@ class NullLogger implements ILog {
|
||||
public info(source: string, message: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export class Logger implements ILog {
|
||||
|
||||
private readonly _messageHandler: (severity: Severity, source: string, message: string) => void;
|
||||
|
||||
constructor(
|
||||
messageHandler: (severity: Severity, source: string, message: string) => void
|
||||
) {
|
||||
this._messageHandler = messageHandler;
|
||||
}
|
||||
|
||||
public error(source: string, message: string): void {
|
||||
this._messageHandler(Severity.Error, source, message);
|
||||
}
|
||||
|
||||
public warn(source: string, message: string): void {
|
||||
this._messageHandler(Severity.Warning, source, message);
|
||||
}
|
||||
|
||||
public info(source: string, message: string): void {
|
||||
this._messageHandler(Severity.Info, source, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { ChildProcess, fork } from 'child_process';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { Server, Socket, createServer } from 'net';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
@@ -41,10 +42,10 @@ import { isEqualOrParent } from 'vs/base/common/resources';
|
||||
|
||||
export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
|
||||
private readonly _onExit: Emitter<[number, string]> = new Emitter<[number, string]>();
|
||||
public readonly onExit: Event<[number, string]> = this._onExit.event;
|
||||
private readonly _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>();
|
||||
public readonly onCrashed: Event<[number, string]> = this._onCrashed.event;
|
||||
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private readonly _toDispose: IDisposable[];
|
||||
|
||||
private readonly _isExtensionDevHost: boolean;
|
||||
private readonly _isExtensionDevDebug: boolean;
|
||||
@@ -91,15 +92,16 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
this._extensionHostConnection = null;
|
||||
this._messageProtocol = null;
|
||||
|
||||
this._toDispose.add(this._onExit);
|
||||
this._toDispose.add(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e)));
|
||||
this._toDispose.add(this._lifecycleService.onShutdown(reason => this.terminate()));
|
||||
this._toDispose.add(this._extensionHostDebugService.onClose(event => {
|
||||
this._toDispose = [];
|
||||
this._toDispose.push(this._onCrashed);
|
||||
this._toDispose.push(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e)));
|
||||
this._toDispose.push(this._lifecycleService.onShutdown(reason => this.terminate()));
|
||||
this._toDispose.push(this._extensionHostDebugService.onClose(event => {
|
||||
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
|
||||
this._windowService.closeWindow();
|
||||
}
|
||||
}));
|
||||
this._toDispose.add(this._extensionHostDebugService.onReload(event => {
|
||||
this._toDispose.push(this._extensionHostDebugService.onReload(event => {
|
||||
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
|
||||
this._windowService.reloadWindow();
|
||||
}
|
||||
@@ -107,7 +109,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
|
||||
const globalExitListener = () => this.terminate();
|
||||
process.once('exit', globalExitListener);
|
||||
this._toDispose.add(toDisposable(() => {
|
||||
this._toDispose.push(toDisposable(() => {
|
||||
process.removeListener('exit', globalExitListener);
|
||||
}));
|
||||
}
|
||||
@@ -449,7 +451,20 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
return;
|
||||
}
|
||||
|
||||
this._onExit.fire([code, signal]);
|
||||
// Unexpected termination
|
||||
if (!this._isExtensionDevHost) {
|
||||
this._onCrashed.fire([code, signal]);
|
||||
}
|
||||
|
||||
// Expected development extension termination: When the extension host goes down we also shutdown the window
|
||||
else if (!this._isExtensionDevTestFromCli) {
|
||||
this._windowService.closeWindow();
|
||||
}
|
||||
|
||||
// When CLI testing make sure to exit with proper exit code
|
||||
else {
|
||||
ipc.send('vscode:exit', code);
|
||||
}
|
||||
}
|
||||
|
||||
public enableInspector(): Promise<void> {
|
||||
@@ -474,7 +489,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
}
|
||||
this._terminating = true;
|
||||
|
||||
this._toDispose.dispose();
|
||||
dispose(this._toDispose);
|
||||
|
||||
if (!this._messageProtocol) {
|
||||
// .start() was not called
|
||||
|
||||
@@ -3,37 +3,62 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
|
||||
import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
|
||||
import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { runWhenIdle } from 'vs/base/common/async';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Barrier, runWhenIdle } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
import { isEqualOrParent } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IExtensionEnablementService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient';
|
||||
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser, schema } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
|
||||
import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
|
||||
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager';
|
||||
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
|
||||
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
|
||||
|
||||
schema.properties.engines.properties.vscode.default = `^${pkg.version}`;
|
||||
|
||||
let productAllowProposedApi: Set<string> | null = null;
|
||||
function allowProposedApiFromProduct(id: ExtensionIdentifier): boolean {
|
||||
// create set if needed
|
||||
if (!productAllowProposedApi) {
|
||||
productAllowProposedApi = new Set<string>();
|
||||
if (isNonEmptyArray(product.extensionAllowedProposedApi)) {
|
||||
product.extensionAllowedProposedApi.forEach((id) => productAllowProposedApi!.add(ExtensionIdentifier.toKey(id)));
|
||||
}
|
||||
}
|
||||
return productAllowProposedApi.has(ExtensionIdentifier.toKey(id));
|
||||
}
|
||||
|
||||
class DeltaExtensionsQueueItem {
|
||||
constructor(
|
||||
@@ -42,38 +67,80 @@ class DeltaExtensionsQueueItem {
|
||||
) { }
|
||||
}
|
||||
|
||||
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
|
||||
export class ExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
private readonly _remoteExtensionsEnvironmentData: Map<string, IRemoteAgentEnvironment>;
|
||||
public _serviceBrand: any;
|
||||
|
||||
private _remoteExtensionsEnvironmentData: Map<string, IRemoteAgentEnvironment>;
|
||||
|
||||
private readonly _extensionHostLogsLocation: URI;
|
||||
private readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _installedExtensionsReady: Barrier;
|
||||
private readonly _isDev: boolean;
|
||||
private readonly _extensionsMessages: Map<string, IMessage[]>;
|
||||
private _allRequestedActivateEvents: { [activationEvent: string]: boolean; };
|
||||
private readonly _extensionScanner: CachedExtensionScanner;
|
||||
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
|
||||
|
||||
private readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event;
|
||||
|
||||
private readonly _onDidChangeExtensionsStatus: Emitter<ExtensionIdentifier[]> = this._register(new Emitter<ExtensionIdentifier[]>());
|
||||
public readonly onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]> = this._onDidChangeExtensionsStatus.event;
|
||||
|
||||
private readonly _onDidChangeExtensions: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeExtensions: Event<void> = this._onDidChangeExtensions.event;
|
||||
|
||||
private readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
|
||||
public readonly onWillActivateByEvent: Event<IWillActivateEvent> = this._onWillActivateByEvent.event;
|
||||
|
||||
private readonly _onDidChangeResponsiveChange = this._register(new Emitter<IResponsiveStateChangeEvent>());
|
||||
public readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = this._onDidChangeResponsiveChange.event;
|
||||
|
||||
// --- Members used per extension host process
|
||||
private _extensionHostProcessManagers: ExtensionHostProcessManager[];
|
||||
private _extensionHostActiveExtensions: Map<string, ExtensionIdentifier>;
|
||||
private _extensionHostProcessActivationTimes: Map<string, ActivationTimes>;
|
||||
private _extensionHostExtensionRuntimeErrors: Map<string, Error[]>;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IExtensionEnablementService extensionEnablementService: IExtensionEnablementService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IProductService productService: IProductService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
|
||||
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
|
||||
@IWindowService private readonly _windowService: IWindowService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
|
||||
@IWindowService protected readonly _windowService: IWindowService,
|
||||
@IFileService fileService: IFileService
|
||||
) {
|
||||
super(
|
||||
instantiationService,
|
||||
notificationService,
|
||||
environmentService,
|
||||
telemetryService,
|
||||
extensionEnablementService,
|
||||
fileService,
|
||||
productService,
|
||||
);
|
||||
super();
|
||||
|
||||
// help the file service to activate providers by activating extensions by file system event
|
||||
this._register(fileService.onWillActivateFileSystemProvider(e => {
|
||||
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
|
||||
}));
|
||||
|
||||
this._remoteExtensionsEnvironmentData = new Map<string, IRemoteAgentEnvironment>();
|
||||
|
||||
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${_windowService.windowId}`));
|
||||
this._registry = new ExtensionDescriptionRegistry([]);
|
||||
this._installedExtensionsReady = new Barrier();
|
||||
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
|
||||
this._extensionsMessages = new Map<string, IMessage[]>();
|
||||
this._allRequestedActivateEvents = Object.create(null);
|
||||
this._extensionScanner = this._instantiationService.createInstance(CachedExtensionScanner);
|
||||
this._deltaExtensionsQueue = [];
|
||||
|
||||
this._extensionHostProcessManagers = [];
|
||||
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
|
||||
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
|
||||
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
|
||||
|
||||
this._startDelayed(this._lifecycleService);
|
||||
|
||||
if (this._extensionEnablementService.allUserExtensionsDisabled) {
|
||||
this._notificationService.prompt(Severity.Info, nls.localize('extensionsDisabled', "All installed extensions are temporarily disabled. Reload the window to return to the previous state."), [{
|
||||
@@ -84,12 +151,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
}]);
|
||||
}
|
||||
|
||||
this._remoteExtensionsEnvironmentData = new Map<string, IRemoteAgentEnvironment>();
|
||||
|
||||
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`));
|
||||
this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner);
|
||||
this._deltaExtensionsQueue = [];
|
||||
|
||||
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
|
||||
let toAdd: IExtension[] = [];
|
||||
let toRemove: string[] = [];
|
||||
@@ -120,24 +181,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
|
||||
}
|
||||
}));
|
||||
|
||||
// delay extension host creation and extension scanning
|
||||
// until the workbench is running. we cannot defer the
|
||||
// extension host more (LifecyclePhase.Restored) because
|
||||
// some editors require the extension host to restore
|
||||
// and this would result in a deadlock
|
||||
// see https://github.com/Microsoft/vscode/issues/41322
|
||||
this._lifecycleService.when(LifecyclePhase.Ready).then(() => {
|
||||
// reschedule to ensure this runs after restoring viewlets, panels, and editors
|
||||
runWhenIdle(() => {
|
||||
this._initialize();
|
||||
}, 50 /*max delay*/);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//#region deltaExtensions
|
||||
|
||||
private _inHandleDeltaExtensions = false;
|
||||
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
|
||||
this._deltaExtensionsQueue.push(item);
|
||||
@@ -229,7 +274,26 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
}
|
||||
|
||||
private _rehandleExtensionPoints(extensionDescriptions: IExtensionDescription[]): void {
|
||||
this._doHandleExtensionPoints(extensionDescriptions);
|
||||
const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null);
|
||||
for (let extensionDescription of extensionDescriptions) {
|
||||
if (extensionDescription.contributes) {
|
||||
for (let extPointName in extensionDescription.contributes) {
|
||||
if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) {
|
||||
affectedExtensionPoints[extPointName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
|
||||
|
||||
const availableExtensions = this._registry.getAllExtensionDescriptions();
|
||||
const extensionPoints = ExtensionsRegistry.getExtensionPoints();
|
||||
for (let i = 0, len = extensionPoints.length; i < len; i++) {
|
||||
if (affectedExtensionPoints[extensionPoints[i].name]) {
|
||||
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public canAddExtension(extension: IExtensionDescription): boolean {
|
||||
@@ -319,20 +383,76 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
private _startDelayed(lifecycleService: ILifecycleService): void {
|
||||
// delay extension host creation and extension scanning
|
||||
// until the workbench is running. we cannot defer the
|
||||
// extension host more (LifecyclePhase.Restored) because
|
||||
// some editors require the extension host to restore
|
||||
// and this would result in a deadlock
|
||||
// see https://github.com/Microsoft/vscode/issues/41322
|
||||
lifecycleService.when(LifecyclePhase.Ready).then(() => {
|
||||
// reschedule to ensure this runs after restoring viewlets, panels, and editors
|
||||
runWhenIdle(() => {
|
||||
perf.mark('willLoadExtensions');
|
||||
this._startExtensionHostProcess(true, []);
|
||||
this._scanAndHandleExtensions();
|
||||
this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions'));
|
||||
}, 50 /*max delay*/);
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this._onWillActivateByEvent.dispose();
|
||||
this._onDidChangeResponsiveChange.dispose();
|
||||
}
|
||||
|
||||
public restartExtensionHost(): void {
|
||||
this._stopExtensionHostProcess();
|
||||
this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents));
|
||||
}
|
||||
|
||||
public startExtensionHost(): void {
|
||||
this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents));
|
||||
}
|
||||
|
||||
public stopExtensionHost(): void {
|
||||
this._stopExtensionHostProcess();
|
||||
}
|
||||
|
||||
private _stopExtensionHostProcess(): void {
|
||||
let previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
|
||||
this._extensionHostActiveExtensions.forEach((value) => {
|
||||
previouslyActivatedExtensionIds.push(value);
|
||||
});
|
||||
|
||||
for (const manager of this._extensionHostProcessManagers) {
|
||||
manager.dispose();
|
||||
}
|
||||
this._extensionHostProcessManagers = [];
|
||||
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
|
||||
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
|
||||
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
|
||||
|
||||
if (previouslyActivatedExtensionIds.length > 0) {
|
||||
this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds);
|
||||
}
|
||||
}
|
||||
|
||||
private _createProvider(remoteAuthority: string): IInitDataProvider {
|
||||
return {
|
||||
remoteAuthority: remoteAuthority,
|
||||
getInitData: () => {
|
||||
return this.whenInstalledExtensionsRegistered().then(() => {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
return this._remoteExtensionsEnvironmentData.get(remoteAuthority)!;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] {
|
||||
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
|
||||
this._stopExtensionHostProcess();
|
||||
|
||||
let autoStart: boolean;
|
||||
let extensions: Promise<IExtensionDescription[]>;
|
||||
if (isInitialStart) {
|
||||
@@ -344,25 +464,27 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
extensions = this.getExtensions().then((extensions) => extensions.filter(ext => ext.extensionLocation.scheme === Schemas.file));
|
||||
}
|
||||
|
||||
const result: ExtensionHostProcessManager[] = [];
|
||||
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation);
|
||||
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, true, extHostProcessWorker, null, initialActivationEvents);
|
||||
result.push(extHostProcessManager);
|
||||
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents);
|
||||
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, true));
|
||||
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
|
||||
this._extensionHostProcessManagers.push(extHostProcessManager);
|
||||
|
||||
const remoteAgentConnection = this._remoteAgentService.getConnection();
|
||||
if (remoteAgentConnection) {
|
||||
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), nodeWebSocketFactory);
|
||||
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
|
||||
result.push(remoteExtHostProcessManager);
|
||||
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority));
|
||||
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
|
||||
remoteExtHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, false));
|
||||
remoteExtHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
|
||||
this._extensionHostProcessManagers.push(remoteExtHostProcessManager);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected _onExtensionHostCrashed(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void {
|
||||
super._onExtensionHostCrashed(extensionHost, code, signal);
|
||||
private _onExtensionHostCrashed(code: number, signal: string | null, showNotification: boolean): void {
|
||||
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
|
||||
this._stopExtensionHostProcess();
|
||||
|
||||
if (extensionHost.isLocal) {
|
||||
if (showNotification) {
|
||||
if (code === 55) {
|
||||
this._notificationService.prompt(
|
||||
Severity.Error,
|
||||
@@ -380,19 +502,118 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
return;
|
||||
}
|
||||
|
||||
this._notificationService.prompt(Severity.Error, nls.localize('extensionService.crash', "Extension host terminated unexpectedly."),
|
||||
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
|
||||
if (code === 87) {
|
||||
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
|
||||
}
|
||||
|
||||
this._notificationService.prompt(Severity.Error, message,
|
||||
[{
|
||||
label: nls.localize('devTools', "Open Developer Tools"),
|
||||
run: () => this._windowService.openDevTools()
|
||||
},
|
||||
{
|
||||
label: nls.localize('restart', "Restart Extension Host"),
|
||||
run: () => this.startExtensionHost()
|
||||
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- begin IExtensionService
|
||||
|
||||
public activateByEvent(activationEvent: string): Promise<void> {
|
||||
if (this._installedExtensionsReady.isOpen()) {
|
||||
// Extensions have been scanned and interpreted
|
||||
|
||||
// Record the fact that this activationEvent was requested (in case of a restart)
|
||||
this._allRequestedActivateEvents[activationEvent] = true;
|
||||
|
||||
if (!this._registry.containsActivationEvent(activationEvent)) {
|
||||
// There is no extension that is interested in this activation event
|
||||
return NO_OP_VOID_PROMISE;
|
||||
}
|
||||
|
||||
return this._activateByEvent(activationEvent);
|
||||
} else {
|
||||
// Extensions have not been scanned yet.
|
||||
|
||||
// Record the fact that this activationEvent was requested (in case of a restart)
|
||||
this._allRequestedActivateEvents[activationEvent] = true;
|
||||
|
||||
return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent));
|
||||
}
|
||||
}
|
||||
|
||||
private _activateByEvent(activationEvent: string): Promise<void> {
|
||||
const result = Promise.all(
|
||||
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent))
|
||||
).then(() => { });
|
||||
this._onWillActivateByEvent.fire({
|
||||
event: activationEvent,
|
||||
activation: result
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public whenInstalledExtensionsRegistered(): Promise<boolean> {
|
||||
return this._installedExtensionsReady.wait();
|
||||
}
|
||||
|
||||
public getExtensions(): Promise<IExtensionDescription[]> {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
return this._registry.getAllExtensionDescriptions();
|
||||
});
|
||||
}
|
||||
|
||||
public getExtension(id: string): Promise<IExtensionDescription | undefined> {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
return this._registry.getExtensionDescription(id);
|
||||
});
|
||||
}
|
||||
|
||||
public readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
let availableExtensions = this._registry.getAllExtensionDescriptions();
|
||||
|
||||
let result: ExtensionPointContribution<T>[] = [], resultLen = 0;
|
||||
for (let i = 0, len = availableExtensions.length; i < len; i++) {
|
||||
let desc = availableExtensions[i];
|
||||
|
||||
if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) {
|
||||
result[resultLen++] = new ExtensionPointContribution<T>(desc, desc.contributes[extPoint.name]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } {
|
||||
let result: { [id: string]: IExtensionsStatus; } = Object.create(null);
|
||||
if (this._registry) {
|
||||
const extensions = this._registry.getAllExtensionDescriptions();
|
||||
for (const extension of extensions) {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extension.identifier);
|
||||
result[extension.identifier.value] = {
|
||||
messages: this._extensionsMessages.get(extensionKey) || [],
|
||||
activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey),
|
||||
runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [],
|
||||
};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getInspectPort(): number {
|
||||
if (this._extensionHostProcessManagers.length > 0) {
|
||||
return this._extensionHostProcessManagers[0].getInspectPort();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- end IExtensionService
|
||||
|
||||
// --- impl
|
||||
|
||||
private createLogger(): Logger {
|
||||
@@ -421,7 +642,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
}
|
||||
}
|
||||
|
||||
protected async _scanAndHandleExtensions(): Promise<void> {
|
||||
private async _scanAndHandleExtensions(): Promise<void> {
|
||||
this._extensionScanner.startScanningExtensions(this.createLogger());
|
||||
|
||||
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
|
||||
@@ -429,12 +650,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
|
||||
let localExtensions = await this._extensionScanner.scannedExtensions;
|
||||
|
||||
// enable or disable proposed API per extension
|
||||
this._checkEnableProposedApi(localExtensions);
|
||||
|
||||
// remove disabled extensions
|
||||
localExtensions = localExtensions.filter(extension => this._isEnabled(extension));
|
||||
|
||||
if (remoteAuthority) {
|
||||
let resolvedAuthority: ResolvedAuthority;
|
||||
|
||||
@@ -478,28 +693,37 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
// fetch the remote environment
|
||||
const remoteEnv = (await this._remoteAgentService.getEnvironment())!;
|
||||
|
||||
// enable or disable proposed API per extension
|
||||
this._checkEnableProposedApi(remoteEnv.extensions);
|
||||
|
||||
// remove disabled extensions
|
||||
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension));
|
||||
// revive URIs
|
||||
remoteEnv.extensions.forEach((extension) => {
|
||||
(<any>extension).extensionLocation = URI.revive(extension.extensionLocation);
|
||||
});
|
||||
|
||||
// remove UI extensions from the remote extensions
|
||||
remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._productService, this._configurationService));
|
||||
remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._configurationService));
|
||||
|
||||
// remove non-UI extensions from the local extensions
|
||||
localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._productService, this._configurationService));
|
||||
localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._configurationService));
|
||||
|
||||
// in case of overlap, the remote wins
|
||||
const isRemoteExtension = new Set<string>();
|
||||
remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
|
||||
// compute enabled extensions
|
||||
const enabledExtensions = await this._getRuntimeExtensions((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
|
||||
|
||||
// remove disabled extensions
|
||||
const isEnabled = new Set<string>();
|
||||
enabledExtensions.forEach(extension => isEnabled.add(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
remoteEnv.extensions = remoteEnv.extensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
localExtensions = localExtensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
|
||||
// save for remote extension's init data
|
||||
this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv);
|
||||
|
||||
this._handleExtensionPoints((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
|
||||
this._handleExtensionPoints(enabledExtensions);
|
||||
extensionHost.start(localExtensions.map(extension => extension.identifier));
|
||||
this._releaseBarrier();
|
||||
|
||||
} else {
|
||||
await this._startLocalExtensionHost(extensionHost, localExtensions);
|
||||
@@ -507,8 +731,11 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
}
|
||||
|
||||
private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, localExtensions: IExtensionDescription[]): Promise<void> {
|
||||
this._handleExtensionPoints(localExtensions);
|
||||
extensionHost.start(localExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
|
||||
const enabledExtensions = await this._getRuntimeExtensions(localExtensions);
|
||||
|
||||
this._handleExtensionPoints(enabledExtensions);
|
||||
extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
|
||||
this._releaseBarrier();
|
||||
}
|
||||
|
||||
private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void {
|
||||
@@ -517,19 +744,234 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
|
||||
}
|
||||
|
||||
this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions());
|
||||
let availableExtensions = this._registry.getAllExtensionDescriptions();
|
||||
let extensionPoints = ExtensionsRegistry.getExtensionPoints();
|
||||
|
||||
let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
|
||||
|
||||
for (let i = 0, len = extensionPoints.length; i < len; i++) {
|
||||
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
|
||||
}
|
||||
}
|
||||
|
||||
public getInspectPort(): number {
|
||||
if (this._extensionHostProcessManagers.length > 0) {
|
||||
return this._extensionHostProcessManagers[0].getInspectPort();
|
||||
private _releaseBarrier(): void {
|
||||
perf.mark('extensionHostReady');
|
||||
this._installedExtensionsReady.open();
|
||||
this._onDidRegisterExtensions.fire(undefined);
|
||||
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
|
||||
}
|
||||
|
||||
private isExtensionUnderDevelopment(extension: IExtensionDescription): boolean {
|
||||
if (this._environmentService.isExtensionDevelopment) {
|
||||
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
|
||||
if (extDevLocs) {
|
||||
const extLocation = extension.extensionLocation;
|
||||
for (let p of extDevLocs) {
|
||||
if (isEqualOrParent(extLocation, p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private async _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise<IExtensionDescription[]> {
|
||||
|
||||
const runtimeExtensions: IExtensionDescription[] = [];
|
||||
const extensionsToDisable: IExtensionDescription[] = [];
|
||||
const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }];
|
||||
|
||||
let enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || [];
|
||||
|
||||
const notFound = (id: string) => nls.localize('notFound', "Extension \`{0}\` cannot use PROPOSED API as it cannot be found", id);
|
||||
|
||||
if (enableProposedApiFor.length) {
|
||||
let allProposed = (enableProposedApiFor instanceof Array ? enableProposedApiFor : [enableProposedApiFor]);
|
||||
allProposed.forEach(id => {
|
||||
if (!allExtensions.some(description => ExtensionIdentifier.equals(description.identifier, id))) {
|
||||
console.error(notFound(id));
|
||||
}
|
||||
});
|
||||
// Make enabled proposed API be lowercase for case insensitive comparison
|
||||
if (Array.isArray(enableProposedApiFor)) {
|
||||
enableProposedApiFor = enableProposedApiFor.map(id => id.toLowerCase());
|
||||
} else {
|
||||
enableProposedApiFor = enableProposedApiFor.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
const enableProposedApiForAll = !this._environmentService.isBuilt ||
|
||||
(!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') ||
|
||||
(enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args);
|
||||
|
||||
|
||||
for (const extension of allExtensions) {
|
||||
|
||||
// Do not disable extensions under development
|
||||
if (!this.isExtensionUnderDevelopment(extension)) {
|
||||
if (!this._extensionEnablementService.isEnabled(toExtension(extension))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!extension.isBuiltin) {
|
||||
// Check if the extension is changed to system extension
|
||||
const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.identifier.value }))[0];
|
||||
if (userMigratedSystemExtension) {
|
||||
extensionsToDisable.push(extension);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
runtimeExtensions.push(this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor));
|
||||
}
|
||||
|
||||
this._telemetryService.publicLog('extensionsScanned', {
|
||||
totalCount: runtimeExtensions.length,
|
||||
disabledCount: allExtensions.length - runtimeExtensions.length
|
||||
});
|
||||
|
||||
if (extensionsToDisable.length) {
|
||||
return this._extensionEnablementService.setEnablement(extensionsToDisable.map(e => toExtension(e)), EnablementState.Disabled)
|
||||
.then(() => runtimeExtensions);
|
||||
} else {
|
||||
return runtimeExtensions;
|
||||
}
|
||||
}
|
||||
|
||||
private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription {
|
||||
if (allowProposedApiFromProduct(extension.identifier)) {
|
||||
// fast lane -> proposed api is available to all extensions
|
||||
// that are listed in product.json-files
|
||||
extension.enableProposedApi = true;
|
||||
|
||||
} else if (extension.enableProposedApi && !extension.isBuiltin) {
|
||||
if (
|
||||
!enableProposedApiForAll &&
|
||||
enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0
|
||||
) {
|
||||
extension.enableProposedApi = false;
|
||||
console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
|
||||
|
||||
} else {
|
||||
// proposed api is available when developing or when an extension was explicitly
|
||||
// spelled out via a command line argument
|
||||
console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`);
|
||||
}
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
private _handleExtensionPointMessage(msg: IMessage) {
|
||||
const extensionKey = ExtensionIdentifier.toKey(msg.extensionId);
|
||||
|
||||
if (!this._extensionsMessages.has(extensionKey)) {
|
||||
this._extensionsMessages.set(extensionKey, []);
|
||||
}
|
||||
this._extensionsMessages.get(extensionKey)!.push(msg);
|
||||
|
||||
const extension = this._registry.getExtensionDescription(msg.extensionId);
|
||||
const strMsg = `[${msg.extensionId.value}]: ${msg.message}`;
|
||||
if (extension && extension.isUnderDevelopment) {
|
||||
// This message is about the extension currently being developed
|
||||
this._showMessageToUser(msg.type, strMsg);
|
||||
} else {
|
||||
this._logMessageInConsole(msg.type, strMsg);
|
||||
}
|
||||
|
||||
if (!this._isDev && msg.extensionId) {
|
||||
const { type, extensionId, extensionPointId, message } = msg;
|
||||
/* __GDPR__
|
||||
"extensionsMessage" : {
|
||||
"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"extensionId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
|
||||
"extensionPointId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
|
||||
"message": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('extensionsMessage', {
|
||||
type, extensionId: extensionId.value, extensionPointId, message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static _handleExtensionPoint<T>(extensionPoint: ExtensionPoint<T>, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void {
|
||||
let users: IExtensionPointUser<T>[] = [], usersLen = 0;
|
||||
for (let i = 0, len = availableExtensions.length; i < len; i++) {
|
||||
let desc = availableExtensions[i];
|
||||
|
||||
if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) {
|
||||
users[usersLen++] = {
|
||||
description: desc,
|
||||
value: desc.contributes[extensionPoint.name],
|
||||
collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
extensionPoint.acceptUsers(users);
|
||||
}
|
||||
|
||||
private _showMessageToUser(severity: Severity, msg: string): void {
|
||||
if (severity === Severity.Error || severity === Severity.Warning) {
|
||||
this._notificationService.notify({ severity, message: msg });
|
||||
} else {
|
||||
this._logMessageInConsole(severity, msg);
|
||||
}
|
||||
}
|
||||
|
||||
private _logMessageInConsole(severity: Severity, msg: string): void {
|
||||
if (severity === Severity.Error) {
|
||||
console.error(msg);
|
||||
} else if (severity === Severity.Warning) {
|
||||
console.warn(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// -- called by extension host
|
||||
|
||||
public _logOrShowMessage(severity: Severity, msg: string): void {
|
||||
if (this._isDev) {
|
||||
this._showMessageToUser(severity, msg);
|
||||
} else {
|
||||
this._logMessageInConsole(severity, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise<void> {
|
||||
const results = await Promise.all(
|
||||
this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent))
|
||||
);
|
||||
const activated = results.some(e => e);
|
||||
if (!activated) {
|
||||
throw new Error(`Unknown extension ${extensionId.value}`);
|
||||
}
|
||||
}
|
||||
|
||||
public _onWillActivateExtension(extensionId: ExtensionIdentifier): void {
|
||||
this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId);
|
||||
}
|
||||
|
||||
public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
|
||||
this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent));
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extensionId);
|
||||
if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) {
|
||||
this._extensionHostExtensionRuntimeErrors.set(extensionKey, []);
|
||||
}
|
||||
this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err);
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
public _onExtensionHostExit(code: number): void {
|
||||
// Expected development extension termination: When the extension host goes down we also shutdown the window
|
||||
if (!this._isExtensionDevTestFromCli) {
|
||||
const devOpts = parseExtensionDevOptions(this._environmentService);
|
||||
if (!devOpts.isExtensionDevTestFromCli) {
|
||||
this._windowService.closeWindow();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions, IWebSocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
@@ -24,8 +28,8 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
|
||||
import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
|
||||
export interface IInitDataProvider {
|
||||
readonly remoteAuthority: string;
|
||||
@@ -34,28 +38,28 @@ export interface IInitDataProvider {
|
||||
|
||||
export class RemoteExtensionHostClient extends Disposable implements IExtensionHostStarter {
|
||||
|
||||
private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());
|
||||
public readonly onExit: Event<[number, string | null]> = this._onExit.event;
|
||||
private _onCrashed: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());
|
||||
public readonly onCrashed: Event<[number, string | null]> = this._onCrashed.event;
|
||||
|
||||
private _protocol: PersistentProtocol | null;
|
||||
|
||||
private readonly _isExtensionDevHost: boolean;
|
||||
private readonly _isExtensionDevTestFromCli: boolean;
|
||||
|
||||
private _terminating: boolean;
|
||||
|
||||
constructor(
|
||||
private readonly _allExtensions: Promise<IExtensionDescription[]>,
|
||||
private readonly _initDataProvider: IInitDataProvider,
|
||||
private readonly _webSocketFactory: IWebSocketFactory,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IWindowService private readonly _windowService: IWindowService,
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
|
||||
@IProductService private readonly _productService: IProductService
|
||||
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService
|
||||
) {
|
||||
super();
|
||||
this._protocol = null;
|
||||
@@ -65,13 +69,14 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
|
||||
|
||||
const devOpts = parseExtensionDevOptions(this._environmentService);
|
||||
this._isExtensionDevHost = devOpts.isExtensionDevHost;
|
||||
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
|
||||
}
|
||||
|
||||
public start(): Promise<IMessagePassingProtocol> {
|
||||
const options: IConnectionOptions = {
|
||||
isBuilt: this._environmentService.isBuilt,
|
||||
commit: this._productService.commit,
|
||||
webSocketFactory: this._webSocketFactory,
|
||||
commit: product.commit,
|
||||
webSocketFactory: nodeWebSocketFactory,
|
||||
addressProvider: {
|
||||
getAddress: async () => {
|
||||
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
|
||||
@@ -163,7 +168,20 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
|
||||
return;
|
||||
}
|
||||
|
||||
this._onExit.fire([0, null]);
|
||||
// Unexpected termination
|
||||
if (!this._isExtensionDevHost) {
|
||||
this._onCrashed.fire([0, null]);
|
||||
}
|
||||
|
||||
// Expected development extension termination: When the extension host goes down we also shutdown the window
|
||||
else if (!this._isExtensionDevTestFromCli) {
|
||||
this._windowService.closeWindow();
|
||||
}
|
||||
|
||||
// When CLI testing make sure to exit with proper exit code
|
||||
else {
|
||||
ipc.send('vscode:exit', 0);
|
||||
}
|
||||
}
|
||||
|
||||
private _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise<IInitData> {
|
||||
@@ -173,15 +191,15 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
|
||||
const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier);
|
||||
const workspace = this._contextService.getWorkspace();
|
||||
const r: IInitData = {
|
||||
commit: this._productService.commit,
|
||||
version: this._productService.version,
|
||||
commit: product.commit,
|
||||
version: pkg.version,
|
||||
parentPid: remoteExtensionHostData.pid,
|
||||
environment: {
|
||||
isExtensionDevelopmentDebug,
|
||||
appRoot: remoteExtensionHostData.appRoot,
|
||||
appSettingsHome: remoteExtensionHostData.appSettingsHome,
|
||||
appName: this._productService.nameLong,
|
||||
appUriScheme: this._productService.urlProtocol,
|
||||
appName: product.nameLong,
|
||||
appUriScheme: product.urlProtocol,
|
||||
appLanguage: platform.language,
|
||||
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
|
||||
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
|
||||
@@ -19,6 +19,7 @@ import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
|
||||
|
||||
// we don't (yet) throw when extensions parse
|
||||
// uris that have no scheme
|
||||
@@ -52,7 +53,9 @@ export class ExtensionHostMain {
|
||||
hostUtils: IHostUtils,
|
||||
consolePatchFn: IConsolePatchFn,
|
||||
logServiceFn: ILogServiceFn,
|
||||
uriTransformer: IURITransformer | null
|
||||
uriTransformer: IURITransformer | null,
|
||||
schemeTransformer: ISchemeTransformer | null,
|
||||
outputChannelName: string,
|
||||
) {
|
||||
this._isTerminating = false;
|
||||
this._hostUtils = hostUtils;
|
||||
@@ -83,7 +86,8 @@ export class ExtensionHostMain {
|
||||
extHostConfiguraiton,
|
||||
initData.environment,
|
||||
this._extHostLogService,
|
||||
uriTransformer
|
||||
schemeTransformer,
|
||||
outputChannelName
|
||||
);
|
||||
|
||||
// error forwarding and stack trace scanning
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { startExtensionHostProcess } from 'vs/workbench/services/extensions/node/extensionHostProcessSetup';
|
||||
|
||||
startExtensionHostProcess().catch((err) => console.log(err));
|
||||
startExtensionHostProcess(
|
||||
_ => null,
|
||||
_ => null,
|
||||
_ => nls.localize('extension host Log', "Extension Host")
|
||||
).catch((err) => console.log(err));
|
||||
|
||||
@@ -5,33 +5,23 @@
|
||||
|
||||
import * as nativeWatchdog from 'native-watchdog';
|
||||
import * as net from 'net';
|
||||
import * as minimist from 'minimist';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { PersistentProtocol, ProtocolConstants, createBufferedEvent } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { PersistentProtocol, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { exists } from 'vs/base/node/pfs';
|
||||
import { realpath } from 'vs/base/node/extpath';
|
||||
import { IHostUtils } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
|
||||
interface ParsedExtHostArgs {
|
||||
uriTransformerPath?: string;
|
||||
}
|
||||
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
string: [
|
||||
'uriTransformerPath'
|
||||
]
|
||||
}) as ParsedExtHostArgs;
|
||||
|
||||
// With Electron 2.x and node.js 8.x the "natives" module
|
||||
// can cause a native crash (see https://github.com/nodejs/node/issues/19891 and
|
||||
@@ -82,7 +72,7 @@ function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
|
||||
};
|
||||
}
|
||||
|
||||
const createLogService: ILogServiceFn = initData => new SpdLogService(ExtensionHostLogFileName, initData.logsLocation.fsPath, initData.logLevel);
|
||||
const createLogService: ILogServiceFn = initData => createBufferSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
|
||||
|
||||
interface IRendererConnection {
|
||||
protocol: IMessagePassingProtocol;
|
||||
@@ -111,41 +101,27 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
|
||||
process.on('message', (msg: IExtHostSocketMessage, handle: net.Socket) => {
|
||||
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
|
||||
const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64'));
|
||||
let socket: NodeSocket | WebSocketNodeSocket;
|
||||
if (msg.skipWebSocketFrames) {
|
||||
socket = new NodeSocket(handle);
|
||||
} else {
|
||||
socket = new WebSocketNodeSocket(new NodeSocket(handle));
|
||||
}
|
||||
if (protocol) {
|
||||
// reconnection case
|
||||
if (disconnectWaitTimer) {
|
||||
clearTimeout(disconnectWaitTimer);
|
||||
disconnectWaitTimer = null;
|
||||
}
|
||||
protocol.beginAcceptReconnection(socket, initialDataChunk);
|
||||
protocol.beginAcceptReconnection(new NodeSocket(handle), initialDataChunk);
|
||||
protocol.endAcceptReconnection();
|
||||
} else {
|
||||
clearTimeout(timer);
|
||||
protocol = new PersistentProtocol(socket, initialDataChunk);
|
||||
protocol = new PersistentProtocol(new NodeSocket(handle), initialDataChunk);
|
||||
protocol.onClose(() => onTerminate());
|
||||
resolve(protocol);
|
||||
|
||||
if (msg.skipWebSocketFrames) {
|
||||
// Wait for rich client to reconnect
|
||||
protocol.onSocketClose(() => {
|
||||
// The socket has closed, let's give the renderer a certain amount of time to reconnect
|
||||
disconnectWaitTimer = setTimeout(() => {
|
||||
disconnectWaitTimer = null;
|
||||
onTerminate();
|
||||
}, ProtocolConstants.ReconnectionGraceTime);
|
||||
});
|
||||
} else {
|
||||
// Do not wait for web companion to reconnect
|
||||
protocol.onSocketClose(() => {
|
||||
protocol.onSocketClose(() => {
|
||||
// The socket has closed, let's give the renderer a certain amount of time to reconnect
|
||||
disconnectWaitTimer = setTimeout(() => {
|
||||
disconnectWaitTimer = null;
|
||||
onTerminate();
|
||||
});
|
||||
}
|
||||
}, ProtocolConstants.ReconnectionGraceTime);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -179,22 +155,16 @@ async function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
|
||||
|
||||
return new class implements IMessagePassingProtocol {
|
||||
|
||||
private readonly _onMessage = new Emitter<VSBuffer>();
|
||||
readonly onMessage: Event<VSBuffer> = createBufferedEvent(this._onMessage.event);
|
||||
private _terminating = false;
|
||||
|
||||
private _terminating: boolean;
|
||||
|
||||
constructor() {
|
||||
this._terminating = false;
|
||||
protocol.onMessage((msg) => {
|
||||
if (isMessageOfType(msg, MessageType.Terminate)) {
|
||||
this._terminating = true;
|
||||
onTerminate();
|
||||
} else {
|
||||
this._onMessage.fire(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
readonly onMessage: Event<any> = Event.filter(protocol.onMessage, msg => {
|
||||
if (!isMessageOfType(msg, MessageType.Terminate)) {
|
||||
return true;
|
||||
}
|
||||
this._terminating = true;
|
||||
onTerminate();
|
||||
return false;
|
||||
});
|
||||
|
||||
send(msg: any): void {
|
||||
if (!this._terminating) {
|
||||
@@ -302,7 +272,11 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
|
||||
}
|
||||
})();
|
||||
|
||||
export async function startExtensionHostProcess(): Promise<void> {
|
||||
export async function startExtensionHostProcess(
|
||||
uriTransformerFn: (initData: IInitData) => IURITransformer | null,
|
||||
schemeTransformerFn: (initData: IInitData) => ISchemeTransformer | null,
|
||||
outputChannelNameFn: (initData: IInitData) => string,
|
||||
): Promise<void> {
|
||||
|
||||
const protocol = await createExtHostProtocol();
|
||||
const renderer = await connectToRenderer(protocol);
|
||||
@@ -317,17 +291,6 @@ export async function startExtensionHostProcess(): Promise<void> {
|
||||
realpath(path: string) { return realpath(path); }
|
||||
};
|
||||
|
||||
// Attempt to load uri transformer
|
||||
let uriTransformer: IURITransformer | null = null;
|
||||
if (initData.remoteAuthority && args.uriTransformerPath) {
|
||||
try {
|
||||
const rawURITransformerFactory = <any>require.__$__nodeRequire(args.uriTransformerPath);
|
||||
const rawURITransformer = <IRawURITransformer>rawURITransformerFactory(initData.remoteAuthority);
|
||||
uriTransformer = new URITransformer(rawURITransformer);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const extensionHostMain = new ExtensionHostMain(
|
||||
renderer.protocol,
|
||||
@@ -335,7 +298,9 @@ export async function startExtensionHostProcess(): Promise<void> {
|
||||
hostUtils,
|
||||
patchPatchedConsole,
|
||||
createLogService,
|
||||
uriTransformer
|
||||
uriTransformerFn(initData),
|
||||
schemeTransformerFn(initData),
|
||||
outputChannelNameFn(initData)
|
||||
);
|
||||
|
||||
// rewrite onTerminate-function to be a proper shutdown
|
||||
|
||||
@@ -12,13 +12,40 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { getGalleryExtensionId, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints';
|
||||
import { ExtensionIdentifier, ExtensionIdentifierWithVersion, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
const MANIFEST_FILE = 'package.json';
|
||||
|
||||
export interface Translations {
|
||||
[id: string]: string;
|
||||
}
|
||||
|
||||
namespace Translations {
|
||||
export function equals(a: Translations, b: Translations): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
let aKeys = Object.keys(a);
|
||||
let bKeys: Set<string> = new Set<string>();
|
||||
for (let key of Object.keys(b)) {
|
||||
bKeys.add(key);
|
||||
}
|
||||
if (aKeys.length !== bKeys.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let key of aKeys) {
|
||||
if (a[key] !== b[key]) {
|
||||
return false;
|
||||
}
|
||||
bKeys.delete(key);
|
||||
}
|
||||
return bKeys.size === 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface NlsConfiguration {
|
||||
readonly devMode: boolean;
|
||||
readonly locale: string | undefined;
|
||||
@@ -26,6 +53,12 @@ export interface NlsConfiguration {
|
||||
readonly translations: Translations;
|
||||
}
|
||||
|
||||
export interface ILog {
|
||||
error(source: string, message: string): void;
|
||||
warn(source: string, message: string): void;
|
||||
info(source: string, message: string): void;
|
||||
}
|
||||
|
||||
abstract class ExtensionManifestHandler {
|
||||
|
||||
protected readonly _ourVersion: string;
|
||||
|
||||
@@ -8,9 +8,9 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
|
||||
export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean {
|
||||
export function isUIExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean {
|
||||
const uiContributions = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name);
|
||||
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
|
||||
const extensionKind = getExtensionKind(manifest, configurationService);
|
||||
@@ -19,7 +19,7 @@ export function isUIExtension(manifest: IExtensionManifest, productService: IPro
|
||||
case 'workspace': return false;
|
||||
default: {
|
||||
// Tagged as UI extension in product
|
||||
if (isNonEmptyArray(productService.uiExtensions) && productService.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
|
||||
if (isNonEmptyArray(product.uiExtensions) && product.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
|
||||
return true;
|
||||
}
|
||||
// Not an UI extension if it has main
|
||||
@@ -16,11 +16,10 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
|
||||
export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService {
|
||||
|
||||
@@ -36,8 +35,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
constructor(
|
||||
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this.servers = this.extensionManagementServerService.remoteExtensionManagementServer ? [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer] : [this.extensionManagementServerService.localExtensionManagementServer];
|
||||
@@ -87,7 +85,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, force?: boolean): Promise<void> {
|
||||
if (server === this.extensionManagementServerService.localExtensionManagementServer) {
|
||||
const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.productService, this.configurationService)
|
||||
const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.configurationService)
|
||||
&& i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
|
||||
if (dependentNonUIExtensions.length) {
|
||||
return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions)));
|
||||
@@ -142,7 +140,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
const [extensionIdentifier] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix)));
|
||||
return extensionIdentifier;
|
||||
}
|
||||
if (isUIExtension(manifest, this.productService, this.configurationService)) {
|
||||
if (isUIExtension(manifest, this.configurationService)) {
|
||||
// Install only on local server
|
||||
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
|
||||
}
|
||||
@@ -163,7 +161,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
// Install on both servers
|
||||
return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(() => undefined);
|
||||
}
|
||||
if (isUIExtension(manifest, this.productService, this.configurationService)) {
|
||||
if (isUIExtension(manifest, this.configurationService)) {
|
||||
// Install only on local server
|
||||
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
|
||||
}
|
||||
@@ -212,7 +210,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
for (let idx = 0; idx < extensions.length; idx++) {
|
||||
const extension = extensions[idx];
|
||||
const manifest = manifests[idx];
|
||||
if (manifest && isUIExtension(manifest, this.productService, this.configurationService)) {
|
||||
if (manifest && isUIExtension(manifest, this.configurationService)) {
|
||||
result.set(extension.identifier.id.toLowerCase(), extension);
|
||||
uiExtensionsManifests.push(manifest);
|
||||
}
|
||||
|
||||
@@ -457,8 +457,8 @@ async function readCaCertificates() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function readWindowsCaCertificates() {
|
||||
const winCA = await import('vscode-windows-ca-certs');
|
||||
function readWindowsCaCertificates() {
|
||||
const winCA = require.__$__nodeRequire<any>('vscode-windows-ca-certs');
|
||||
|
||||
let ders: any[] = [];
|
||||
const store = winCA();
|
||||
@@ -481,14 +481,7 @@ async function readWindowsCaCertificates() {
|
||||
}
|
||||
|
||||
async function readMacCaCertificates() {
|
||||
const stdout = await new Promise<string>((resolve, reject) => {
|
||||
const child = cp.spawn('/usr/bin/security', ['find-certificate', '-a', '-p']);
|
||||
const stdout: string[] = [];
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stdout.on('data', str => stdout.push(str));
|
||||
child.on('error', reject);
|
||||
child.on('exit', code => code ? reject(code) : resolve(stdout.join('')));
|
||||
});
|
||||
const stdout = (await promisify(cp.execFile)('/usr/bin/security', ['find-certificate', '-a', '-p'], { encoding: 'utf8', maxBuffer: 1024 * 1024 })).stdout;
|
||||
const seen = {};
|
||||
const certs = stdout.split(/(?=-----BEGIN CERTIFICATE-----)/g)
|
||||
.filter(pem => !!pem.length && !seen[pem] && (seen[pem] = true));
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
@@ -55,12 +55,14 @@ export class FileService extends Disposable implements IFileService {
|
||||
providerDisposables.push(provider.onDidErrorOccur(error => this._onError.fire(error)));
|
||||
}
|
||||
|
||||
return toDisposable(() => {
|
||||
this._onDidChangeFileSystemProviderRegistrations.fire({ added: false, scheme, provider });
|
||||
this.provider.delete(scheme);
|
||||
return combinedDisposable([
|
||||
toDisposable(() => {
|
||||
this._onDidChangeFileSystemProviderRegistrations.fire({ added: false, scheme, provider });
|
||||
this.provider.delete(scheme);
|
||||
|
||||
dispose(providerDisposables);
|
||||
});
|
||||
dispose(providerDisposables);
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
async activateProvider(scheme: string): Promise<void> {
|
||||
@@ -78,7 +80,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
});
|
||||
|
||||
if (this.provider.has(scheme)) {
|
||||
return; // provider is already here so we can return directly
|
||||
return Promise.resolve(); // provider is already here so we can return directly
|
||||
}
|
||||
|
||||
// If the provider is not yet there, make sure to join on the listeners assuming
|
||||
@@ -235,7 +237,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
return fileStat;
|
||||
}
|
||||
|
||||
return fileStat;
|
||||
return Promise.resolve(fileStat);
|
||||
}
|
||||
|
||||
async resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]>;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameS
|
||||
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat } from 'vs/platform/files/common/files';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
|
||||
|
||||
@@ -125,21 +125,21 @@ suite('Disk File Service', () => {
|
||||
let testProvider: TestDiskFileSystemProvider;
|
||||
let testDir: string;
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
let disposables: IDisposable[] = [];
|
||||
|
||||
setup(async () => {
|
||||
const logService = new NullLogService();
|
||||
|
||||
service = new FileService(logService);
|
||||
disposables.add(service);
|
||||
disposables.push(service);
|
||||
|
||||
fileProvider = new TestDiskFileSystemProvider(logService);
|
||||
disposables.add(service.registerProvider(Schemas.file, fileProvider));
|
||||
disposables.add(fileProvider);
|
||||
disposables.push(service.registerProvider(Schemas.file, fileProvider));
|
||||
disposables.push(fileProvider);
|
||||
|
||||
testProvider = new TestDiskFileSystemProvider(logService);
|
||||
disposables.add(service.registerProvider(testSchema, testProvider));
|
||||
disposables.add(testProvider);
|
||||
disposables.push(service.registerProvider(testSchema, testProvider));
|
||||
disposables.push(testProvider);
|
||||
|
||||
const id = generateUuid();
|
||||
testDir = join(parentDir, id);
|
||||
@@ -149,14 +149,14 @@ suite('Disk File Service', () => {
|
||||
});
|
||||
|
||||
teardown(async () => {
|
||||
disposables.clear();
|
||||
disposables = dispose(disposables);
|
||||
|
||||
await rimraf(parentDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('createFolder', async () => {
|
||||
let event: FileOperationEvent | undefined;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const parent = await service.resolve(URI.file(testDir));
|
||||
|
||||
@@ -176,7 +176,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('createFolder: creating multiple folders at once', async function () {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const multiFolderPaths = ['a', 'couple', 'of', 'folders'];
|
||||
const parent = await service.resolve(URI.file(testDir));
|
||||
@@ -411,7 +411,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('deleteFile', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const resource = URI.file(join(testDir, 'deep', 'conway.js'));
|
||||
const source = await service.resolve(resource);
|
||||
@@ -426,7 +426,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('deleteFolder (recursive)', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const resource = URI.file(join(testDir, 'deep'));
|
||||
const source = await service.resolve(resource);
|
||||
@@ -446,14 +446,15 @@ suite('Disk File Service', () => {
|
||||
await service.del(source.resource);
|
||||
|
||||
return Promise.reject(new Error('Unexpected'));
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('move', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const source = URI.file(join(testDir, 'index.html'));
|
||||
const sourceContents = readFileSync(source.fsPath);
|
||||
@@ -533,7 +534,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
async function testMoveAcrossProviders(sourceFile = 'index.html'): Promise<void> {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const source = URI.file(join(testDir, sourceFile));
|
||||
const sourceContents = readFileSync(source.fsPath);
|
||||
@@ -557,7 +558,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('move - multi folder', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const multiFolderPaths = ['a', 'couple', 'of', 'folders'];
|
||||
const renameToPath = join(...multiFolderPaths, 'other.html');
|
||||
@@ -576,7 +577,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('move - directory', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const source = URI.file(join(testDir, 'deep'));
|
||||
|
||||
@@ -620,7 +621,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
async function testMoveFolderAcrossProviders(): Promise<void> {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const source = URI.file(join(testDir, 'deep'));
|
||||
const sourceChildren = readdirSync(source.fsPath);
|
||||
@@ -645,7 +646,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('move - MIX CASE', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const source = URI.file(join(testDir, 'index.html'));
|
||||
await service.resolve(source);
|
||||
@@ -662,7 +663,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('move - source parent of target', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
await service.resolve(URI.file(join(testDir, 'index.html')));
|
||||
try {
|
||||
@@ -675,7 +676,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('move - FILE_MOVE_CONFLICT', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const source = await service.resolve(URI.file(join(testDir, 'index.html')));
|
||||
try {
|
||||
@@ -690,7 +691,7 @@ suite('Disk File Service', () => {
|
||||
let createEvent: FileOperationEvent;
|
||||
let moveEvent: FileOperationEvent;
|
||||
let deleteEvent: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => {
|
||||
disposables.push(service.onAfterOperation(e => {
|
||||
if (e.operation === FileOperation.CREATE) {
|
||||
createEvent = e;
|
||||
} else if (e.operation === FileOperation.DELETE) {
|
||||
@@ -754,7 +755,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
async function doTestCopy(sourceName: string = 'index.html') {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const source = await service.resolve(URI.file(join(testDir, sourceName)));
|
||||
const target = URI.file(join(testDir, 'other.html'));
|
||||
@@ -779,7 +780,7 @@ suite('Disk File Service', () => {
|
||||
let createEvent: FileOperationEvent;
|
||||
let copyEvent: FileOperationEvent;
|
||||
let deleteEvent: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => {
|
||||
disposables.push(service.onAfterOperation(e => {
|
||||
if (e.operation === FileOperation.CREATE) {
|
||||
createEvent = e;
|
||||
} else if (e.operation === FileOperation.DELETE) {
|
||||
@@ -1168,7 +1169,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('createFile', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const contents = 'Hello World';
|
||||
const resource = URI.file(join(testDir, 'test.txt'));
|
||||
@@ -1199,7 +1200,7 @@ suite('Disk File Service', () => {
|
||||
|
||||
test('createFile (allows to overwrite existing)', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
disposables.push(service.onAfterOperation(e => event = e));
|
||||
|
||||
const contents = 'Hello World';
|
||||
const resource = URI.file(join(testDir, 'test.txt'));
|
||||
|
||||
@@ -28,6 +28,7 @@ import { IKeybindingItem, IKeybindingRule2, KeybindingWeight, KeybindingsRegistr
|
||||
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { keybindingsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
@@ -40,7 +41,6 @@ import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { commandsExtensionPoint } from 'vs/workbench/api/common/menusExtensionPoint';
|
||||
|
||||
export class KeyboardMapperFactory {
|
||||
public static readonly INSTANCE = new KeyboardMapperFactory();
|
||||
@@ -215,7 +215,7 @@ let keybindingType: IJSONSchema = {
|
||||
description: nls.localize('vscode.extension.contributes.keybindings.args', "Arguments to pass to the command to execute.")
|
||||
},
|
||||
key: {
|
||||
description: nls.localize('vscode.extension.contributes.keybindings.key', 'Key or key sequence (separate keys with plus-sign and sequences with space, e.g. Ctrl+O and Ctrl+L L for a chord).'),
|
||||
description: nls.localize('vscode.extension.contributes.keybindings.key', 'Key or key sequence (separate keys with plus-sign and sequences with space, e.g Ctrl+O and Ctrl+L L for a chord).'),
|
||||
type: 'string'
|
||||
},
|
||||
mac: {
|
||||
@@ -239,7 +239,6 @@ let keybindingType: IJSONSchema = {
|
||||
|
||||
const keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint<ContributedKeyBinding | ContributedKeyBinding[]>({
|
||||
extensionPoint: 'keybindings',
|
||||
deps: [commandsExtensionPoint],
|
||||
jsonSchema: {
|
||||
description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."),
|
||||
oneOf: [
|
||||
@@ -276,11 +275,12 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IStatusbarService statusBarService: IStatusbarService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWindowService private readonly windowService: IWindowService,
|
||||
@IExtensionService extensionService: IExtensionService
|
||||
) {
|
||||
super(contextKeyService, commandService, telemetryService, notificationService);
|
||||
super(contextKeyService, commandService, telemetryService, notificationService, statusBarService);
|
||||
|
||||
updateSchema();
|
||||
|
||||
@@ -500,21 +500,10 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
||||
weight = KeybindingWeight.ExternalExtension + idx;
|
||||
}
|
||||
|
||||
let commandAction = MenuRegistry.getCommand(command);
|
||||
let precondition = commandAction && commandAction.precondition;
|
||||
let fullWhen: ContextKeyExpr | undefined;
|
||||
if (when && precondition) {
|
||||
fullWhen = ContextKeyExpr.and(precondition, ContextKeyExpr.deserialize(when));
|
||||
} else if (when) {
|
||||
fullWhen = ContextKeyExpr.deserialize(when);
|
||||
} else if (precondition) {
|
||||
fullWhen = precondition;
|
||||
}
|
||||
|
||||
let desc: IKeybindingRule2 = {
|
||||
id: command,
|
||||
args,
|
||||
when: fullWhen,
|
||||
when: ContextKeyExpr.deserialize(when),
|
||||
weight: weight,
|
||||
primary: KeybindingParser.parseKeybinding(key, OS),
|
||||
mac: mac ? { primary: KeybindingParser.parseKeybinding(mac, OS) } : null,
|
||||
@@ -723,4 +712,4 @@ const keyboardConfiguration: IConfigurationNode = {
|
||||
|
||||
configurationRegistry.registerConfiguration(keyboardConfiguration);
|
||||
|
||||
registerSingleton(IKeybindingService, WorkbenchKeybindingService);
|
||||
registerSingleton(IKeybindingService, WorkbenchKeybindingService);
|
||||
@@ -44,7 +44,6 @@ import { TestBackupFileService, TestContextService, TestEditorGroupsService, Tes
|
||||
import { FileService } from 'vs/workbench/services/files/common/fileService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
interface Modifiers {
|
||||
metaKey?: boolean;
|
||||
@@ -66,7 +65,7 @@ suite('KeybindingsEditing', () => {
|
||||
|
||||
instantiationService = new TestInstantiationService();
|
||||
|
||||
instantiationService.stub(IEnvironmentService, <IEnvironmentService>{ appKeybindingsPath: keybindingsFile, settingsResource: URI.file(path.join(testDir, 'settings.json')) });
|
||||
instantiationService.stub(IEnvironmentService, <IEnvironmentService>{ appKeybindingsPath: keybindingsFile, appSettingsPath: path.join(testDir, 'settings.json') });
|
||||
instantiationService.stub(IConfigurationService, ConfigurationService);
|
||||
instantiationService.stub(IConfigurationService, 'getValue', { 'eol': '\n' });
|
||||
instantiationService.stub(IConfigurationService, 'onDidUpdateConfiguration', () => { });
|
||||
|
||||
@@ -45,21 +45,6 @@ export interface IWorkbenchLayoutService extends ILayoutService {
|
||||
*/
|
||||
readonly onZenModeChange: Event<boolean>;
|
||||
|
||||
/**
|
||||
* Emits when fullscreen is enabled or disabled.
|
||||
*/
|
||||
readonly onFullscreenChange: Event<boolean>;
|
||||
|
||||
/**
|
||||
* Emits when centered layout is enabled or disabled.
|
||||
*/
|
||||
readonly onCenteredLayoutChange: Event<boolean>;
|
||||
|
||||
/**
|
||||
* Emit when panel position changes.
|
||||
*/
|
||||
readonly onPanelPositionChange: Event<string>;
|
||||
|
||||
/**
|
||||
* Asks the part service if all parts have been fully restored. For editor part
|
||||
* this means that the contents of editors have loaded.
|
||||
|
||||
@@ -3,17 +3,15 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification';
|
||||
import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification';
|
||||
import { INotificationsModel, NotificationsModel, ChoiceAction } from 'vs/workbench/common/notifications';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
|
||||
export class NotificationService extends Disposable implements INotificationService {
|
||||
|
||||
_serviceBrand: ServiceIdentifier<INotificationService>;
|
||||
_serviceBrand: any;
|
||||
|
||||
private _model: INotificationsModel = this._register(new NotificationsModel());
|
||||
|
||||
@@ -28,7 +26,7 @@ export class NotificationService extends Disposable implements INotificationServ
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.addNotification({ severity: Severity.Info, message });
|
||||
this.model.notify({ severity: Severity.Info, message });
|
||||
}
|
||||
|
||||
warn(message: NotificationMessage | NotificationMessage[]): void {
|
||||
@@ -38,7 +36,7 @@ export class NotificationService extends Disposable implements INotificationServ
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.addNotification({ severity: Severity.Warning, message });
|
||||
this.model.notify({ severity: Severity.Warning, message });
|
||||
}
|
||||
|
||||
error(message: NotificationMessage | NotificationMessage[]): void {
|
||||
@@ -48,32 +46,37 @@ export class NotificationService extends Disposable implements INotificationServ
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.addNotification({ severity: Severity.Error, message });
|
||||
this.model.notify({ severity: Severity.Error, message });
|
||||
}
|
||||
|
||||
notify(notification: INotification): INotificationHandle {
|
||||
return this.model.addNotification(notification);
|
||||
return this.model.notify(notification);
|
||||
}
|
||||
|
||||
prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle {
|
||||
const toDispose = new DisposableStore();
|
||||
const toDispose: IDisposable[] = [];
|
||||
|
||||
let choiceClicked = false;
|
||||
let handle: INotificationHandle;
|
||||
|
||||
// Convert choices into primary/secondary actions
|
||||
const primaryActions: IAction[] = [];
|
||||
const secondaryActions: IAction[] = [];
|
||||
const actions: INotificationActions = { primary: [], secondary: [] };
|
||||
choices.forEach((choice, index) => {
|
||||
const action = new ChoiceAction(`workbench.dialog.choice.${index}`, choice);
|
||||
if (!choice.isSecondary) {
|
||||
primaryActions.push(action);
|
||||
if (!actions.primary) {
|
||||
actions.primary = [];
|
||||
}
|
||||
actions.primary.push(action);
|
||||
} else {
|
||||
secondaryActions.push(action);
|
||||
if (!actions.secondary) {
|
||||
actions.secondary = [];
|
||||
}
|
||||
actions.secondary.push(action);
|
||||
}
|
||||
|
||||
// React to action being clicked
|
||||
toDispose.add(action.onDidRun(() => {
|
||||
toDispose.push(action.onDidRun(() => {
|
||||
choiceClicked = true;
|
||||
|
||||
// Close notification unless we are told to keep open
|
||||
@@ -82,17 +85,16 @@ export class NotificationService extends Disposable implements INotificationServ
|
||||
}
|
||||
}));
|
||||
|
||||
toDispose.add(action);
|
||||
toDispose.push(action);
|
||||
});
|
||||
|
||||
// Show notification with actions
|
||||
const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions };
|
||||
handle = this.notify({ severity, message, actions, sticky: options && options.sticky, silent: options && options.silent });
|
||||
|
||||
Event.once(handle.onDidClose)(() => {
|
||||
|
||||
// Cleanup when notification gets disposed
|
||||
toDispose.dispose();
|
||||
dispose(toDispose);
|
||||
|
||||
// Indicate cancellation to the outside if no action was executed
|
||||
if (options && typeof options.onCancel === 'function' && !choiceClicked) {
|
||||
@@ -102,10 +104,6 @@ export class NotificationService extends Disposable implements INotificationServ
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
status(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable {
|
||||
return this.model.showStatusMessage(message, options);
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(INotificationService, NotificationService, true);
|
||||
@@ -51,10 +51,10 @@ export abstract class AsbtractOutputChannelModelService {
|
||||
|
||||
export abstract class AbstractFileOutputChannelModel extends Disposable implements IOutputChannelModel {
|
||||
|
||||
protected readonly _onDidAppendedContent = this._register(new Emitter<void>());
|
||||
protected _onDidAppendedContent = new Emitter<void>();
|
||||
readonly onDidAppendedContent: Event<void> = this._onDidAppendedContent.event;
|
||||
|
||||
protected readonly _onDispose = this._register(new Emitter<void>());
|
||||
protected _onDispose = new Emitter<void>();
|
||||
readonly onDispose: Event<void> = this._onDispose.event;
|
||||
|
||||
protected modelUpdater: RunOnceScheduler;
|
||||
@@ -96,11 +96,12 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen
|
||||
} else {
|
||||
this.model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri);
|
||||
this.onModelCreated(this.model);
|
||||
const disposable = this.model.onWillDispose(() => {
|
||||
const disposables: IDisposable[] = [];
|
||||
disposables.push(this.model.onWillDispose(() => {
|
||||
this.onModelWillDispose(this.model);
|
||||
this.model = null;
|
||||
dispose(disposable);
|
||||
});
|
||||
dispose(disposables);
|
||||
}));
|
||||
}
|
||||
return this.model;
|
||||
}
|
||||
@@ -339,10 +340,11 @@ export class BufferredOutputChannel extends Disposable implements IOutputChannel
|
||||
|
||||
private createModel(content: string): ITextModel {
|
||||
const model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri);
|
||||
const disposable = model.onWillDispose(() => {
|
||||
const disposables: IDisposable[] = [];
|
||||
disposables.push(model.onWillDispose(() => {
|
||||
this.model = null;
|
||||
dispose(disposable);
|
||||
});
|
||||
dispose(disposables);
|
||||
}));
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
@@ -524,9 +524,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic
|
||||
switch (configurationTarget) {
|
||||
case ConfigurationTarget.USER:
|
||||
case ConfigurationTarget.USER_LOCAL:
|
||||
return this.environmentService.settingsResource;
|
||||
return URI.file(this.environmentService.appSettingsPath);
|
||||
case ConfigurationTarget.USER_REMOTE:
|
||||
return this.environmentService.settingsResource;
|
||||
return URI.file(this.environmentService.appSettingsPath);
|
||||
case ConfigurationTarget.WORKSPACE:
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
|
||||
return null;
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { ILocalProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
|
||||
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
namespace ProgressState {
|
||||
|
||||
export const enum Type {
|
||||
None,
|
||||
Done,
|
||||
Infinite,
|
||||
While,
|
||||
Work
|
||||
}
|
||||
|
||||
export const None = new class { readonly type = Type.None; };
|
||||
export const Done = new class { readonly type = Type.Done; };
|
||||
export const Infinite = new class { readonly type = Type.Infinite; };
|
||||
|
||||
export class While {
|
||||
readonly type = Type.While;
|
||||
|
||||
constructor(
|
||||
readonly whilePromise: Promise<any>,
|
||||
readonly whileStart: number,
|
||||
readonly whileDelay: number,
|
||||
) { }
|
||||
}
|
||||
|
||||
export class Work {
|
||||
readonly type = Type.Work;
|
||||
|
||||
constructor(
|
||||
readonly total: number | undefined,
|
||||
readonly worked: number | undefined
|
||||
) { }
|
||||
}
|
||||
|
||||
export type State =
|
||||
typeof None
|
||||
| typeof Done
|
||||
| typeof Infinite
|
||||
| While
|
||||
| Work;
|
||||
}
|
||||
|
||||
export abstract class ScopedService extends Disposable {
|
||||
|
||||
constructor(
|
||||
private viewletService: IViewletService,
|
||||
private panelService: IPanelService,
|
||||
private scopeId: string
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners(): void {
|
||||
this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId())));
|
||||
this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId())));
|
||||
|
||||
this._register(this.viewletService.onDidViewletClose(viewlet => this.onScopeClosed(viewlet.getId())));
|
||||
this._register(this.panelService.onDidPanelClose(panel => this.onScopeClosed(panel.getId())));
|
||||
}
|
||||
|
||||
private onScopeClosed(scopeId: string) {
|
||||
if (scopeId === this.scopeId) {
|
||||
this.onScopeDeactivated();
|
||||
}
|
||||
}
|
||||
|
||||
private onScopeOpened(scopeId: string) {
|
||||
if (scopeId === this.scopeId) {
|
||||
this.onScopeActivated();
|
||||
}
|
||||
}
|
||||
|
||||
abstract onScopeActivated(): void;
|
||||
|
||||
abstract onScopeDeactivated(): void;
|
||||
}
|
||||
|
||||
export class ScopedProgressService extends ScopedService implements ILocalProgressService {
|
||||
|
||||
_serviceBrand: ServiceIdentifier<ILocalProgressService>;
|
||||
|
||||
private isActive: boolean;
|
||||
private progressbar: ProgressBar;
|
||||
private progressState: ProgressState.State = ProgressState.None;
|
||||
|
||||
constructor(
|
||||
progressbar: ProgressBar,
|
||||
scopeId: string,
|
||||
isActive: boolean,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IPanelService panelService: IPanelService
|
||||
) {
|
||||
super(viewletService, panelService, scopeId);
|
||||
|
||||
this.progressbar = progressbar;
|
||||
this.isActive = isActive || types.isUndefinedOrNull(scopeId); // If service is unscoped, enable by default
|
||||
}
|
||||
|
||||
onScopeDeactivated(): void {
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
onScopeActivated(): void {
|
||||
this.isActive = true;
|
||||
|
||||
// Return early if progress state indicates that progress is done
|
||||
if (this.progressState.type === ProgressState.Done.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replay Infinite Progress from Promise
|
||||
if (this.progressState.type === ProgressState.Type.While) {
|
||||
let delay: number | undefined;
|
||||
if (this.progressState.whileDelay > 0) {
|
||||
const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart);
|
||||
if (remainingDelay > 0) {
|
||||
delay = remainingDelay;
|
||||
}
|
||||
}
|
||||
|
||||
this.doShowWhile(delay);
|
||||
}
|
||||
|
||||
// Replay Infinite Progress
|
||||
else if (this.progressState.type === ProgressState.Type.Infinite) {
|
||||
this.progressbar.infinite().show();
|
||||
}
|
||||
|
||||
// Replay Finite Progress (Total & Worked)
|
||||
else if (this.progressState.type === ProgressState.Type.Work) {
|
||||
if (this.progressState.total) {
|
||||
this.progressbar.total(this.progressState.total).show();
|
||||
}
|
||||
|
||||
if (this.progressState.worked) {
|
||||
this.progressbar.worked(this.progressState.worked).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
show(infinite: true, delay?: number): IProgressRunner;
|
||||
show(total: number, delay?: number): IProgressRunner;
|
||||
show(infiniteOrTotal: true | number, delay?: number): IProgressRunner {
|
||||
|
||||
// Sort out Arguments
|
||||
if (typeof infiniteOrTotal === 'boolean') {
|
||||
this.progressState = ProgressState.Infinite;
|
||||
} else {
|
||||
this.progressState = new ProgressState.Work(infiniteOrTotal, undefined);
|
||||
}
|
||||
|
||||
// Active: Show Progress
|
||||
if (this.isActive) {
|
||||
|
||||
// Infinite: Start Progressbar and Show after Delay
|
||||
if (this.progressState.type === ProgressState.Type.Infinite) {
|
||||
this.progressbar.infinite().show(delay);
|
||||
}
|
||||
|
||||
// Finite: Start Progressbar and Show after Delay
|
||||
else if (this.progressState.type === ProgressState.Type.Work && typeof this.progressState.total === 'number') {
|
||||
this.progressbar.total(this.progressState.total).show(delay);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
total: (total: number) => {
|
||||
this.progressState = new ProgressState.Work(
|
||||
total,
|
||||
this.progressState.type === ProgressState.Type.Work ? this.progressState.worked : undefined);
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.total(total);
|
||||
}
|
||||
},
|
||||
|
||||
worked: (worked: number) => {
|
||||
|
||||
// Verify first that we are either not active or the progressbar has a total set
|
||||
if (!this.isActive || this.progressbar.hasTotal()) {
|
||||
this.progressState = new ProgressState.Work(
|
||||
this.progressState.type === ProgressState.Type.Work ? this.progressState.total : undefined,
|
||||
this.progressState.type === ProgressState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked);
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.worked(worked);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise the progress bar does not support worked(), we fallback to infinite() progress
|
||||
else {
|
||||
this.progressState = ProgressState.Infinite;
|
||||
this.progressbar.infinite().show();
|
||||
}
|
||||
},
|
||||
|
||||
done: () => {
|
||||
this.progressState = ProgressState.Done;
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async showWhile(promise: Promise<any>, delay?: number): Promise<void> {
|
||||
|
||||
// Join with existing running promise to ensure progress is accurate
|
||||
if (this.progressState.type === ProgressState.Type.While) {
|
||||
promise = Promise.all([promise, this.progressState.whilePromise]);
|
||||
}
|
||||
|
||||
// Keep Promise in State
|
||||
this.progressState = new ProgressState.While(promise, delay || 0, Date.now());
|
||||
|
||||
try {
|
||||
this.doShowWhile(delay);
|
||||
|
||||
await promise;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
} finally {
|
||||
|
||||
// If this is not the last promise in the list of joined promises, skip this
|
||||
if (this.progressState.type !== ProgressState.Type.While || this.progressState.whilePromise === promise) {
|
||||
|
||||
// The while promise is either null or equal the promise we last hooked on
|
||||
this.progressState = ProgressState.None;
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private doShowWhile(delay?: number): void {
|
||||
|
||||
// Show Progress when active
|
||||
if (this.isActive) {
|
||||
this.progressbar.infinite().show(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LocalProgressService implements ILocalProgressService {
|
||||
|
||||
_serviceBrand: ServiceIdentifier<ILocalProgressService>;
|
||||
|
||||
constructor(private progressbar: ProgressBar) { }
|
||||
|
||||
show(infinite: true, delay?: number): IProgressRunner;
|
||||
show(total: number, delay?: number): IProgressRunner;
|
||||
show(infiniteOrTotal: true | number, delay?: number): IProgressRunner {
|
||||
if (typeof infiniteOrTotal === 'boolean') {
|
||||
this.progressbar.infinite().show(delay);
|
||||
} else {
|
||||
this.progressbar.total(infiniteOrTotal).show(delay);
|
||||
}
|
||||
|
||||
return {
|
||||
total: (total: number) => {
|
||||
this.progressbar.total(total);
|
||||
},
|
||||
|
||||
worked: (worked: number) => {
|
||||
if (this.progressbar.hasTotal()) {
|
||||
this.progressbar.worked(worked);
|
||||
} else {
|
||||
this.progressbar.infinite().show();
|
||||
}
|
||||
},
|
||||
|
||||
done: () => {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async showWhile(promise: Promise<any>, delay?: number): Promise<void> {
|
||||
try {
|
||||
this.progressbar.infinite().show(delay);
|
||||
|
||||
await promise;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
} finally {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.progress {
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.progress {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.progress .spinner-container {
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.progress .spinner-container {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
@@ -3,341 +3,294 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/progressService';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity';
|
||||
import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { Dialog } from 'vs/base/browser/ui/dialog/dialog';
|
||||
import { attachDialogStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { EventHelper } from 'vs/base/browser/dom';
|
||||
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
|
||||
|
||||
export class ProgressService implements IProgressService {
|
||||
|
||||
_serviceBrand: ServiceIdentifier<IProgressService>;
|
||||
namespace ProgressState {
|
||||
export const enum Type {
|
||||
None,
|
||||
Done,
|
||||
Infinite,
|
||||
While,
|
||||
Work
|
||||
}
|
||||
|
||||
private readonly _stack: [IProgressOptions, Progress<IProgressStep>][] = [];
|
||||
private _globalStatusEntry: IDisposable;
|
||||
export const None = new class { readonly type = Type.None; };
|
||||
export const Done = new class { readonly type = Type.Done; };
|
||||
export const Infinite = new class { readonly type = Type.Infinite; };
|
||||
|
||||
export class While {
|
||||
public readonly type = Type.While;
|
||||
constructor(
|
||||
public readonly whilePromise: Promise<any>,
|
||||
public readonly whileStart: number,
|
||||
public readonly whileDelay: number,
|
||||
) { }
|
||||
}
|
||||
|
||||
export class Work {
|
||||
public readonly type = Type.Work;
|
||||
constructor(
|
||||
public readonly total: number | undefined,
|
||||
public readonly worked: number | undefined
|
||||
) { }
|
||||
}
|
||||
|
||||
export type State =
|
||||
typeof None
|
||||
| typeof Done
|
||||
| typeof Infinite
|
||||
| While
|
||||
| Work;
|
||||
}
|
||||
|
||||
export abstract class ScopedService extends Disposable {
|
||||
|
||||
constructor(private viewletService: IViewletService, private panelService: IPanelService, private scopeId: string) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners(): void {
|
||||
this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId())));
|
||||
this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId())));
|
||||
|
||||
this._register(this.viewletService.onDidViewletClose(viewlet => this.onScopeClosed(viewlet.getId())));
|
||||
this._register(this.panelService.onDidPanelClose(panel => this.onScopeClosed(panel.getId())));
|
||||
}
|
||||
|
||||
private onScopeClosed(scopeId: string) {
|
||||
if (scopeId === this.scopeId) {
|
||||
this.onScopeDeactivated();
|
||||
}
|
||||
}
|
||||
|
||||
private onScopeOpened(scopeId: string) {
|
||||
if (scopeId === this.scopeId) {
|
||||
this.onScopeActivated();
|
||||
}
|
||||
}
|
||||
|
||||
abstract onScopeActivated(): void;
|
||||
|
||||
abstract onScopeDeactivated(): void;
|
||||
}
|
||||
|
||||
export class ScopedProgressService extends ScopedService implements IProgressService {
|
||||
_serviceBrand: any;
|
||||
private isActive: boolean;
|
||||
private progressbar: ProgressBar;
|
||||
private progressState: ProgressState.State = ProgressState.None;
|
||||
|
||||
constructor(
|
||||
@IActivityService private readonly _activityBar: IActivityService,
|
||||
@IViewletService private readonly _viewletService: IViewletService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IStatusbarService private readonly _statusbarService: IStatusbarService,
|
||||
@ILayoutService private readonly _layoutService: ILayoutService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService
|
||||
) { }
|
||||
progressbar: ProgressBar,
|
||||
scopeId: string,
|
||||
isActive: boolean,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IPanelService panelService: IPanelService
|
||||
) {
|
||||
super(viewletService, panelService, scopeId);
|
||||
|
||||
withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: () => void): Promise<R> {
|
||||
const { location } = options;
|
||||
if (typeof location === 'string') {
|
||||
const viewlet = this._viewletService.getViewlet(location);
|
||||
if (viewlet) {
|
||||
return this._withViewletProgress(location, task, { ...options, location });
|
||||
}
|
||||
this.progressbar = progressbar;
|
||||
this.isActive = isActive || types.isUndefinedOrNull(scopeId); // If service is unscoped, enable by default
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(`Bad progress location: ${location}`));
|
||||
onScopeDeactivated(): void {
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
onScopeActivated(): void {
|
||||
this.isActive = true;
|
||||
|
||||
// Return early if progress state indicates that progress is done
|
||||
if (this.progressState.type === ProgressState.Done.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (location) {
|
||||
case ProgressLocation.Notification:
|
||||
return this._withNotificationProgress({ ...options, location }, task, onDidCancel);
|
||||
case ProgressLocation.Window:
|
||||
return this._withWindowProgress(options, task);
|
||||
case ProgressLocation.Explorer:
|
||||
return this._withViewletProgress('workbench.view.explorer', task, { ...options, location });
|
||||
case ProgressLocation.Scm:
|
||||
return this._withViewletProgress('workbench.view.scm', task, { ...options, location });
|
||||
case ProgressLocation.Extensions:
|
||||
return this._withViewletProgress('workbench.view.extensions', task, { ...options, location });
|
||||
case ProgressLocation.Dialog:
|
||||
return this._withDialogProgress(options, task, onDidCancel);
|
||||
default:
|
||||
return Promise.reject(new Error(`Bad progress location: ${location}`));
|
||||
// Replay Infinite Progress from Promise
|
||||
if (this.progressState.type === ProgressState.Type.While) {
|
||||
let delay: number | undefined;
|
||||
if (this.progressState.whileDelay > 0) {
|
||||
const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart);
|
||||
if (remainingDelay > 0) {
|
||||
delay = remainingDelay;
|
||||
}
|
||||
}
|
||||
|
||||
this.doShowWhile(delay);
|
||||
}
|
||||
|
||||
// Replay Infinite Progress
|
||||
else if (this.progressState.type === ProgressState.Type.Infinite) {
|
||||
this.progressbar.infinite().show();
|
||||
}
|
||||
|
||||
// Replay Finite Progress (Total & Worked)
|
||||
else if (this.progressState.type === ProgressState.Type.Work) {
|
||||
if (this.progressState.total) {
|
||||
this.progressbar.total(this.progressState.total).show();
|
||||
}
|
||||
|
||||
if (this.progressState.worked) {
|
||||
this.progressbar.worked(this.progressState.worked).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _withWindowProgress<R = unknown>(options: IProgressOptions, callback: (progress: IProgress<{ message?: string }>) => Promise<R>): Promise<R> {
|
||||
const task: [IProgressOptions, Progress<IProgressStep>] = [options, new Progress<IProgressStep>(() => this._updateWindowProgress())];
|
||||
show(infinite: true, delay?: number): IProgressRunner;
|
||||
show(total: number, delay?: number): IProgressRunner;
|
||||
show(infiniteOrTotal: true | number, delay?: number): IProgressRunner {
|
||||
// Sort out Arguments
|
||||
if (typeof infiniteOrTotal === 'boolean') {
|
||||
this.progressState = ProgressState.Infinite;
|
||||
} else {
|
||||
this.progressState = new ProgressState.Work(infiniteOrTotal, undefined);
|
||||
}
|
||||
|
||||
const promise = callback(task[1]);
|
||||
// Active: Show Progress
|
||||
if (this.isActive) {
|
||||
|
||||
let delayHandle: any = setTimeout(() => {
|
||||
delayHandle = undefined;
|
||||
this._stack.unshift(task);
|
||||
this._updateWindowProgress();
|
||||
|
||||
// show progress for at least 150ms
|
||||
Promise.all([
|
||||
timeout(150),
|
||||
promise
|
||||
]).finally(() => {
|
||||
const idx = this._stack.indexOf(task);
|
||||
this._stack.splice(idx, 1);
|
||||
this._updateWindowProgress();
|
||||
});
|
||||
}, 150);
|
||||
|
||||
// cancel delay if promise finishes below 150ms
|
||||
return promise.finally(() => clearTimeout(delayHandle));
|
||||
}
|
||||
|
||||
private _updateWindowProgress(idx: number = 0) {
|
||||
dispose(this._globalStatusEntry);
|
||||
|
||||
if (idx < this._stack.length) {
|
||||
const [options, progress] = this._stack[idx];
|
||||
|
||||
let progressTitle = options.title;
|
||||
let progressMessage = progress.value && progress.value.message;
|
||||
let text: string;
|
||||
let title: string;
|
||||
|
||||
if (progressTitle && progressMessage) {
|
||||
// <title>: <message>
|
||||
text = localize('progress.text2', "{0}: {1}", progressTitle, progressMessage);
|
||||
title = options.source ? localize('progress.title3', "[{0}] {1}: {2}", options.source, progressTitle, progressMessage) : text;
|
||||
|
||||
} else if (progressTitle) {
|
||||
// <title>
|
||||
text = progressTitle;
|
||||
title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressTitle) : text;
|
||||
|
||||
} else if (progressMessage) {
|
||||
// <message>
|
||||
text = progressMessage;
|
||||
title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressMessage) : text;
|
||||
|
||||
} else {
|
||||
// no title, no message -> no progress. try with next on stack
|
||||
this._updateWindowProgress(idx + 1);
|
||||
return;
|
||||
// Infinite: Start Progressbar and Show after Delay
|
||||
if (this.progressState.type === ProgressState.Type.Infinite) {
|
||||
this.progressbar.infinite().show(delay);
|
||||
}
|
||||
|
||||
this._globalStatusEntry = this._statusbarService.addEntry({
|
||||
text: `$(sync~spin) ${text}`,
|
||||
tooltip: title
|
||||
}, 'status.progress', localize('status.progress', "Progress Message"), StatusbarAlignment.LEFT);
|
||||
// Finite: Start Progressbar and Show after Delay
|
||||
else if (this.progressState.type === ProgressState.Type.Work && typeof this.progressState.total === 'number') {
|
||||
this.progressbar.total(this.progressState.total).show(delay);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
total: (total: number) => {
|
||||
this.progressState = new ProgressState.Work(
|
||||
total,
|
||||
this.progressState.type === ProgressState.Type.Work ? this.progressState.worked : undefined);
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.total(total);
|
||||
}
|
||||
},
|
||||
|
||||
worked: (worked: number) => {
|
||||
|
||||
// Verify first that we are either not active or the progressbar has a total set
|
||||
if (!this.isActive || this.progressbar.hasTotal()) {
|
||||
this.progressState = new ProgressState.Work(
|
||||
this.progressState.type === ProgressState.Type.Work ? this.progressState.total : undefined,
|
||||
this.progressState.type === ProgressState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked);
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.worked(worked);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise the progress bar does not support worked(), we fallback to infinite() progress
|
||||
else {
|
||||
this.progressState = ProgressState.Infinite;
|
||||
this.progressbar.infinite().show();
|
||||
}
|
||||
},
|
||||
|
||||
done: () => {
|
||||
this.progressState = ProgressState.Done;
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async showWhile(promise: Promise<any>, delay?: number): Promise<void> {
|
||||
|
||||
// Join with existing running promise to ensure progress is accurate
|
||||
if (this.progressState.type === ProgressState.Type.While) {
|
||||
promise = Promise.all([promise, this.progressState.whilePromise]);
|
||||
}
|
||||
|
||||
// Keep Promise in State
|
||||
this.progressState = new ProgressState.While(promise, delay || 0, Date.now());
|
||||
|
||||
try {
|
||||
this.doShowWhile(delay);
|
||||
|
||||
await promise;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
} finally {
|
||||
|
||||
// If this is not the last promise in the list of joined promises, skip this
|
||||
if (this.progressState.type !== ProgressState.Type.While || this.progressState.whilePromise === promise) {
|
||||
|
||||
// The while promise is either null or equal the promise we last hooked on
|
||||
this.progressState = ProgressState.None;
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _withNotificationProgress<P extends Promise<R>, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P {
|
||||
const toDispose = new DisposableStore();
|
||||
private doShowWhile(delay?: number): void {
|
||||
|
||||
const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => {
|
||||
if (!message) {
|
||||
return undefined; // we need a message at least
|
||||
}
|
||||
|
||||
const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : [];
|
||||
const secondaryActions = options.secondaryActions ? Array.from(options.secondaryActions) : [];
|
||||
if (options.cancellable) {
|
||||
const cancelAction = new class extends Action {
|
||||
constructor() {
|
||||
super('progress.cancel', localize('cancel', "Cancel"), undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
if (typeof onDidCancel === 'function') {
|
||||
onDidCancel();
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
};
|
||||
toDispose.add(cancelAction);
|
||||
|
||||
primaryActions.push(cancelAction);
|
||||
}
|
||||
|
||||
const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions };
|
||||
const handle = this._notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message,
|
||||
source: options.source,
|
||||
actions
|
||||
});
|
||||
|
||||
updateProgress(handle, increment);
|
||||
|
||||
Event.once(handle.onDidClose)(() => {
|
||||
toDispose.dispose();
|
||||
});
|
||||
|
||||
return handle;
|
||||
};
|
||||
|
||||
const updateProgress = (notification: INotificationHandle, increment?: number): void => {
|
||||
if (typeof increment === 'number' && increment >= 0) {
|
||||
notification.progress.total(100); // always percentage based
|
||||
notification.progress.worked(increment);
|
||||
} else {
|
||||
notification.progress.infinite();
|
||||
}
|
||||
};
|
||||
|
||||
let handle: INotificationHandle | undefined;
|
||||
const updateNotification = (message?: string, increment?: number): void => {
|
||||
if (!handle) {
|
||||
handle = createNotification(message, increment);
|
||||
} else {
|
||||
if (typeof message === 'string') {
|
||||
let newMessage: string;
|
||||
if (typeof options.title === 'string') {
|
||||
newMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932)
|
||||
} else {
|
||||
newMessage = message;
|
||||
}
|
||||
|
||||
handle.updateMessage(newMessage);
|
||||
}
|
||||
|
||||
if (typeof increment === 'number') {
|
||||
updateProgress(handle, increment);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Show initially
|
||||
updateNotification(options.title);
|
||||
|
||||
// Update based on progress
|
||||
const promise = callback({
|
||||
report: progress => {
|
||||
updateNotification(progress.message, progress.increment);
|
||||
}
|
||||
});
|
||||
|
||||
// Show progress for at least 800ms and then hide once done or canceled
|
||||
Promise.all([timeout(800), promise]).finally(() => {
|
||||
if (handle) {
|
||||
handle.close();
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private _withViewletProgress<P extends Promise<R>, R = unknown>(viewletId: string, task: (progress: IProgress<{ message?: string }>) => P, options: IProgressCompositeOptions): P {
|
||||
const promise = task(emptyProgress);
|
||||
|
||||
// show in viewlet
|
||||
const viewletProgress = this._viewletService.getProgressIndicator(viewletId);
|
||||
if (viewletProgress) {
|
||||
viewletProgress.showWhile(promise, options.delay);
|
||||
// Show Progress when active
|
||||
if (this.isActive) {
|
||||
this.progressbar.infinite().show(delay);
|
||||
}
|
||||
|
||||
// show activity bar
|
||||
let activityProgress: IDisposable;
|
||||
let delayHandle: any = setTimeout(() => {
|
||||
delayHandle = undefined;
|
||||
|
||||
const handle = this._activityBar.showActivity(
|
||||
viewletId,
|
||||
new ProgressBadge(() => ''),
|
||||
'progress-badge',
|
||||
100
|
||||
);
|
||||
|
||||
const startTimeVisible = Date.now();
|
||||
const minTimeVisible = 300;
|
||||
activityProgress = {
|
||||
dispose() {
|
||||
const d = Date.now() - startTimeVisible;
|
||||
if (d < minTimeVisible) {
|
||||
// should at least show for Nms
|
||||
setTimeout(() => handle.dispose(), minTimeVisible - d);
|
||||
} else {
|
||||
// shown long enough
|
||||
handle.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
}, options.delay || 300);
|
||||
|
||||
promise.finally(() => {
|
||||
clearTimeout(delayHandle);
|
||||
dispose(activityProgress);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private _withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P {
|
||||
const disposables = new DisposableStore();
|
||||
const allowableCommands = [
|
||||
'workbench.action.quit',
|
||||
'workbench.action.reloadWindow'
|
||||
];
|
||||
|
||||
let dialog: Dialog;
|
||||
|
||||
const createDialog = (message: string) => {
|
||||
dialog = new Dialog(
|
||||
this._layoutService.container,
|
||||
message,
|
||||
[options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")],
|
||||
{
|
||||
type: 'pending',
|
||||
keyEventProcessor: (event: StandardKeyboardEvent) => {
|
||||
const resolved = this._keybindingService.softDispatch(event, this._layoutService.container);
|
||||
if (resolved && resolved.commandId) {
|
||||
if (allowableCommands.indexOf(resolved.commandId) === -1) {
|
||||
EventHelper.stop(event, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
disposables.add(dialog);
|
||||
disposables.add(attachDialogStyler(dialog, this._themeService));
|
||||
|
||||
dialog.show().then(() => {
|
||||
if (typeof onDidCancel === 'function') {
|
||||
onDidCancel();
|
||||
}
|
||||
|
||||
dispose(dialog);
|
||||
});
|
||||
|
||||
return dialog;
|
||||
};
|
||||
|
||||
const updateDialog = (message?: string) => {
|
||||
if (message && !dialog) {
|
||||
dialog = createDialog(message);
|
||||
} else if (message) {
|
||||
dialog.updateMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
const promise = task({
|
||||
report: progress => {
|
||||
updateDialog(progress.message);
|
||||
}
|
||||
});
|
||||
|
||||
promise.finally(() => {
|
||||
dispose(disposables);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IProgressService, ProgressService, true);
|
||||
export class ProgressService implements IProgressService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(private progressbar: ProgressBar) { }
|
||||
|
||||
show(infinite: true, delay?: number): IProgressRunner;
|
||||
show(total: number, delay?: number): IProgressRunner;
|
||||
show(infiniteOrTotal: true | number, delay?: number): IProgressRunner {
|
||||
if (typeof infiniteOrTotal === 'boolean') {
|
||||
this.progressbar.infinite().show(delay);
|
||||
} else {
|
||||
this.progressbar.total(infiniteOrTotal).show(delay);
|
||||
}
|
||||
|
||||
return {
|
||||
total: (total: number) => {
|
||||
this.progressbar.total(total);
|
||||
},
|
||||
|
||||
worked: (worked: number) => {
|
||||
if (this.progressbar.hasTotal()) {
|
||||
this.progressbar.worked(worked);
|
||||
} else {
|
||||
this.progressbar.infinite().show();
|
||||
}
|
||||
},
|
||||
|
||||
done: () => {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async showWhile(promise: Promise<any>, delay?: number): Promise<void> {
|
||||
try {
|
||||
this.progressbar.infinite().show(delay);
|
||||
|
||||
await promise;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
} finally {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
344
src/vs/workbench/services/progress/browser/progressService2.ts
Normal file
344
src/vs/workbench/services/progress/browser/progressService2.ts
Normal file
@@ -0,0 +1,344 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/progressService2';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IProgressService2, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress, IProgressNotificationOptions } from 'vs/platform/progress/common/progress';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity';
|
||||
import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { Dialog } from 'vs/base/browser/ui/dialog/dialog';
|
||||
import { attachDialogStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { EventHelper } from 'vs/base/browser/dom';
|
||||
|
||||
export class ProgressService2 implements IProgressService2 {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly _stack: [IProgressOptions, Progress<IProgressStep>][] = [];
|
||||
private _globalStatusEntry: IDisposable;
|
||||
|
||||
constructor(
|
||||
@IActivityService private readonly _activityBar: IActivityService,
|
||||
@IViewletService private readonly _viewletService: IViewletService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IStatusbarService private readonly _statusbarService: IStatusbarService,
|
||||
@ILayoutService private readonly _layoutService: ILayoutService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService
|
||||
) { }
|
||||
|
||||
withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: () => void): Promise<R> {
|
||||
|
||||
const { location } = options;
|
||||
if (typeof location === 'string') {
|
||||
const viewlet = this._viewletService.getViewlet(location);
|
||||
if (viewlet) {
|
||||
return this._withViewletProgress(location, task);
|
||||
}
|
||||
return Promise.reject(new Error(`Bad progress location: ${location}`));
|
||||
}
|
||||
|
||||
switch (location) {
|
||||
case ProgressLocation.Notification:
|
||||
return this._withNotificationProgress({ ...options, location: ProgressLocation.Notification }, task, onDidCancel);
|
||||
case ProgressLocation.Window:
|
||||
return this._withWindowProgress(options, task);
|
||||
case ProgressLocation.Explorer:
|
||||
return this._withViewletProgress('workbench.view.explorer', task);
|
||||
case ProgressLocation.Scm:
|
||||
return this._withViewletProgress('workbench.view.scm', task);
|
||||
case ProgressLocation.Extensions:
|
||||
return this._withViewletProgress('workbench.view.extensions', task);
|
||||
case ProgressLocation.Dialog:
|
||||
return this._withDialogProgress(options, task, onDidCancel);
|
||||
default:
|
||||
return Promise.reject(new Error(`Bad progress location: ${location}`));
|
||||
}
|
||||
}
|
||||
|
||||
private _withWindowProgress<R = unknown>(options: IProgressOptions, callback: (progress: IProgress<{ message?: string }>) => Promise<R>): Promise<R> {
|
||||
|
||||
const task: [IProgressOptions, Progress<IProgressStep>] = [options, new Progress<IProgressStep>(() => this._updateWindowProgress())];
|
||||
|
||||
const promise = callback(task[1]);
|
||||
|
||||
let delayHandle: any = setTimeout(() => {
|
||||
delayHandle = undefined;
|
||||
this._stack.unshift(task);
|
||||
this._updateWindowProgress();
|
||||
|
||||
// show progress for at least 150ms
|
||||
Promise.all([
|
||||
timeout(150),
|
||||
promise
|
||||
]).finally(() => {
|
||||
const idx = this._stack.indexOf(task);
|
||||
this._stack.splice(idx, 1);
|
||||
this._updateWindowProgress();
|
||||
});
|
||||
|
||||
}, 150);
|
||||
|
||||
// cancel delay if promise finishes below 150ms
|
||||
return promise.finally(() => clearTimeout(delayHandle));
|
||||
}
|
||||
|
||||
private _updateWindowProgress(idx: number = 0) {
|
||||
|
||||
dispose(this._globalStatusEntry);
|
||||
|
||||
if (idx < this._stack.length) {
|
||||
|
||||
const [options, progress] = this._stack[idx];
|
||||
|
||||
let progressTitle = options.title;
|
||||
let progressMessage = progress.value && progress.value.message;
|
||||
let text: string;
|
||||
let title: string;
|
||||
|
||||
if (progressTitle && progressMessage) {
|
||||
// <title>: <message>
|
||||
text = localize('progress.text2', "{0}: {1}", progressTitle, progressMessage);
|
||||
title = options.source ? localize('progress.title3', "[{0}] {1}: {2}", options.source, progressTitle, progressMessage) : text;
|
||||
|
||||
} else if (progressTitle) {
|
||||
// <title>
|
||||
text = progressTitle;
|
||||
title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressTitle) : text;
|
||||
|
||||
} else if (progressMessage) {
|
||||
// <message>
|
||||
text = progressMessage;
|
||||
title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressMessage) : text;
|
||||
|
||||
} else {
|
||||
// no title, no message -> no progress. try with next on stack
|
||||
this._updateWindowProgress(idx + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
this._globalStatusEntry = this._statusbarService.addEntry({
|
||||
text: `$(sync~spin) ${text}`,
|
||||
tooltip: title
|
||||
}, StatusbarAlignment.LEFT);
|
||||
}
|
||||
}
|
||||
|
||||
private _withNotificationProgress<P extends Promise<R>, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P {
|
||||
const toDispose: IDisposable[] = [];
|
||||
|
||||
const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => {
|
||||
if (!message) {
|
||||
return undefined; // we need a message at least
|
||||
}
|
||||
|
||||
const actions: INotificationActions = { primary: options.primaryActions || [], secondary: options.secondaryActions || [] };
|
||||
if (options.cancellable) {
|
||||
const cancelAction = new class extends Action {
|
||||
constructor() {
|
||||
super('progress.cancel', localize('cancel', "Cancel"), undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
if (typeof onDidCancel === 'function') {
|
||||
onDidCancel();
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
};
|
||||
toDispose.push(cancelAction);
|
||||
|
||||
actions.primary!.push(cancelAction);
|
||||
}
|
||||
|
||||
const handle = this._notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message,
|
||||
source: options.source,
|
||||
actions
|
||||
});
|
||||
|
||||
updateProgress(handle, increment);
|
||||
|
||||
Event.once(handle.onDidClose)(() => {
|
||||
dispose(toDispose);
|
||||
});
|
||||
|
||||
return handle;
|
||||
};
|
||||
|
||||
const updateProgress = (notification: INotificationHandle, increment?: number): void => {
|
||||
if (typeof increment === 'number' && increment >= 0) {
|
||||
notification.progress.total(100); // always percentage based
|
||||
notification.progress.worked(increment);
|
||||
} else {
|
||||
notification.progress.infinite();
|
||||
}
|
||||
};
|
||||
|
||||
let handle: INotificationHandle | undefined;
|
||||
const updateNotification = (message?: string, increment?: number): void => {
|
||||
if (!handle) {
|
||||
handle = createNotification(message, increment);
|
||||
} else {
|
||||
if (typeof message === 'string') {
|
||||
let newMessage: string;
|
||||
if (typeof options.title === 'string') {
|
||||
newMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932)
|
||||
} else {
|
||||
newMessage = message;
|
||||
}
|
||||
|
||||
handle.updateMessage(newMessage);
|
||||
}
|
||||
|
||||
if (typeof increment === 'number') {
|
||||
updateProgress(handle, increment);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Show initially
|
||||
updateNotification(options.title);
|
||||
|
||||
// Update based on progress
|
||||
const p = callback({
|
||||
report: progress => {
|
||||
updateNotification(progress.message, progress.increment);
|
||||
}
|
||||
});
|
||||
|
||||
// Show progress for at least 800ms and then hide once done or canceled
|
||||
Promise.all([timeout(800), p]).finally(() => {
|
||||
if (handle) {
|
||||
handle.close();
|
||||
}
|
||||
});
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
private _withViewletProgress<P extends Promise<R>, R = unknown>(viewletId: string, task: (progress: IProgress<{ message?: string }>) => P): P {
|
||||
|
||||
const promise = task(emptyProgress);
|
||||
|
||||
// show in viewlet
|
||||
const viewletProgress = this._viewletService.getProgressIndicator(viewletId);
|
||||
if (viewletProgress) {
|
||||
viewletProgress.showWhile(promise);
|
||||
}
|
||||
|
||||
// show activity bar
|
||||
let activityProgress: IDisposable;
|
||||
let delayHandle: any = setTimeout(() => {
|
||||
delayHandle = undefined;
|
||||
const handle = this._activityBar.showActivity(
|
||||
viewletId,
|
||||
new ProgressBadge(() => ''),
|
||||
'progress-badge',
|
||||
100
|
||||
);
|
||||
const startTimeVisible = Date.now();
|
||||
const minTimeVisible = 300;
|
||||
activityProgress = {
|
||||
dispose() {
|
||||
const d = Date.now() - startTimeVisible;
|
||||
if (d < minTimeVisible) {
|
||||
// should at least show for Nms
|
||||
setTimeout(() => handle.dispose(), minTimeVisible - d);
|
||||
} else {
|
||||
// shown long enough
|
||||
handle.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
}, 300);
|
||||
|
||||
const onDone = () => {
|
||||
clearTimeout(delayHandle);
|
||||
dispose(activityProgress);
|
||||
};
|
||||
|
||||
promise.then(onDone, onDone);
|
||||
return promise;
|
||||
}
|
||||
|
||||
private _withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P {
|
||||
const disposables: IDisposable[] = [];
|
||||
const allowableCommands = [
|
||||
'workbench.action.quit',
|
||||
'workbench.action.reloadWindow'
|
||||
];
|
||||
|
||||
let dialog: Dialog;
|
||||
|
||||
const createDialog = (message: string) => {
|
||||
dialog = new Dialog(
|
||||
this._layoutService.container,
|
||||
message,
|
||||
[options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")],
|
||||
{
|
||||
type: 'pending',
|
||||
keyEventProcessor: (event: StandardKeyboardEvent) => {
|
||||
const resolved = this._keybindingService.softDispatch(event, this._layoutService.container);
|
||||
if (resolved && resolved.commandId) {
|
||||
if (allowableCommands.indexOf(resolved.commandId) === -1) {
|
||||
EventHelper.stop(event, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
disposables.push(dialog);
|
||||
disposables.push(attachDialogStyler(dialog, this._themeService));
|
||||
|
||||
dialog.show().then(() => {
|
||||
if (typeof onDidCancel === 'function') {
|
||||
onDidCancel();
|
||||
}
|
||||
|
||||
dispose(dialog);
|
||||
});
|
||||
|
||||
return dialog;
|
||||
};
|
||||
|
||||
const updateDialog = (message?: string) => {
|
||||
if (message && !dialog) {
|
||||
dialog = createDialog(message);
|
||||
} else if (message) {
|
||||
dialog.updateMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
const p = task({
|
||||
report: progress => {
|
||||
updateDialog(progress.message);
|
||||
}
|
||||
});
|
||||
|
||||
p.finally(() => {
|
||||
dispose(disposables);
|
||||
});
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IProgressService2, ProgressService2, true);
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { IAction, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { IEditorControl } from 'vs/workbench/common/editor';
|
||||
import { ScopedProgressService, ScopedService } from 'vs/workbench/services/progress/browser/localProgressService';
|
||||
import { ScopedProgressService, ScopedService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
@@ -13,6 +13,7 @@ import { keys, ResourceMap, values } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
@@ -424,7 +425,6 @@ export class DiskSearch implements ISearchResultProvider {
|
||||
searchDebug: IDebugParams | undefined,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IConfigurationService private readonly configService: IConfigurationService,
|
||||
@IFileService private readonly fileService: IFileService
|
||||
) {
|
||||
const timeout = this.configService.getValue<ISearchConfiguration>().search.maintainFileSearchCache ?
|
||||
Number.MAX_VALUE :
|
||||
@@ -465,7 +465,7 @@ export class DiskSearch implements ISearchResultProvider {
|
||||
|
||||
textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): Promise<ISearchComplete> {
|
||||
const folderQueries = query.folderQueries || [];
|
||||
return Promise.all(folderQueries.map(q => this.fileService.exists(q.folder)))
|
||||
return Promise.all(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath)))
|
||||
.then(exists => {
|
||||
if (token && token.isCancellationRequested) {
|
||||
throw canceled();
|
||||
@@ -480,7 +480,7 @@ export class DiskSearch implements ISearchResultProvider {
|
||||
|
||||
fileSearch(query: IFileQuery, token?: CancellationToken): Promise<ISearchComplete> {
|
||||
const folderQueries = query.folderQueries || [];
|
||||
return Promise.all(folderQueries.map(q => this.fileService.exists(q.folder)))
|
||||
return Promise.all(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath)))
|
||||
.then(exists => {
|
||||
if (token && token.isCancellationRequested) {
|
||||
throw canceled();
|
||||
|
||||
@@ -37,7 +37,7 @@ export class TelemetryService extends Disposable implements ITelemetryService {
|
||||
const channel = sharedProcessService.getChannel('telemetryAppender');
|
||||
const config: ITelemetryServiceConfig = {
|
||||
appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)),
|
||||
commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.installSourcePath, environmentService.configuration.remoteAuthority),
|
||||
commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.installSourcePath),
|
||||
piiPaths: [environmentService.appRoot, environmentService.extensionsPath]
|
||||
};
|
||||
|
||||
|
||||
@@ -1,519 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token';
|
||||
import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { nullTokenize2 } from 'vs/editor/common/modes/nullMode';
|
||||
import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint, TokenTypesContribution, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars';
|
||||
import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService';
|
||||
import { ITokenColorizationRule, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, IGrammar, ITokenTypeMap, Registry, StackElement, StandardTokenType, RegistryOptions, IRawGrammar } from 'vscode-textmate';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export class TMScopeRegistry extends Disposable {
|
||||
|
||||
private _scopeNameToLanguageRegistration: { [scopeName: string]: TMLanguageRegistration; };
|
||||
private _encounteredLanguages: boolean[];
|
||||
|
||||
private readonly _onDidEncounterLanguage = this._register(new Emitter<LanguageId>());
|
||||
public readonly onDidEncounterLanguage: Event<LanguageId> = this._onDidEncounterLanguage.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.reset();
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._scopeNameToLanguageRegistration = Object.create(null);
|
||||
this._encounteredLanguages = [];
|
||||
}
|
||||
|
||||
public register(scopeName: string, grammarLocation: URI, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: TokenTypesContribution): void {
|
||||
if (this._scopeNameToLanguageRegistration[scopeName]) {
|
||||
const existingRegistration = this._scopeNameToLanguageRegistration[scopeName];
|
||||
if (!resources.isEqual(existingRegistration.grammarLocation, grammarLocation)) {
|
||||
console.warn(
|
||||
`Overwriting grammar scope name to file mapping for scope ${scopeName}.\n` +
|
||||
`Old grammar file: ${existingRegistration.grammarLocation.toString()}.\n` +
|
||||
`New grammar file: ${grammarLocation.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
this._scopeNameToLanguageRegistration[scopeName] = new TMLanguageRegistration(scopeName, grammarLocation, embeddedLanguages, tokenTypes);
|
||||
}
|
||||
|
||||
public getLanguageRegistration(scopeName: string): TMLanguageRegistration {
|
||||
return this._scopeNameToLanguageRegistration[scopeName] || null;
|
||||
}
|
||||
|
||||
public getGrammarLocation(scopeName: string): URI | null {
|
||||
let data = this.getLanguageRegistration(scopeName);
|
||||
return data ? data.grammarLocation : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when tokenization found/hit an embedded language.
|
||||
*/
|
||||
public onEncounteredLanguage(languageId: LanguageId): void {
|
||||
if (!this._encounteredLanguages[languageId]) {
|
||||
this._encounteredLanguages[languageId] = true;
|
||||
this._onDidEncounterLanguage.fire(languageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TMLanguageRegistration {
|
||||
_topLevelScopeNameDataBrand: void;
|
||||
|
||||
readonly scopeName: string;
|
||||
readonly grammarLocation: URI;
|
||||
readonly embeddedLanguages: IEmbeddedLanguagesMap;
|
||||
readonly tokenTypes: ITokenTypeMap;
|
||||
|
||||
constructor(scopeName: string, grammarLocation: URI, embeddedLanguages: IEmbeddedLanguagesMap | undefined, tokenTypes: TokenTypesContribution | undefined) {
|
||||
this.scopeName = scopeName;
|
||||
this.grammarLocation = grammarLocation;
|
||||
|
||||
// embeddedLanguages handling
|
||||
this.embeddedLanguages = Object.create(null);
|
||||
|
||||
if (embeddedLanguages) {
|
||||
// If embeddedLanguages are configured, fill in `this._embeddedLanguages`
|
||||
let scopes = Object.keys(embeddedLanguages);
|
||||
for (let i = 0, len = scopes.length; i < len; i++) {
|
||||
let scope = scopes[i];
|
||||
let language = embeddedLanguages[scope];
|
||||
if (typeof language !== 'string') {
|
||||
// never hurts to be too careful
|
||||
continue;
|
||||
}
|
||||
this.embeddedLanguages[scope] = language;
|
||||
}
|
||||
}
|
||||
|
||||
this.tokenTypes = Object.create(null);
|
||||
if (tokenTypes) {
|
||||
// If tokenTypes is configured, fill in `this._tokenTypes`
|
||||
const scopes = Object.keys(tokenTypes);
|
||||
for (const scope of scopes) {
|
||||
const tokenType = tokenTypes[scope];
|
||||
switch (tokenType) {
|
||||
case 'string':
|
||||
this.tokenTypes[scope] = StandardTokenType.String;
|
||||
break;
|
||||
case 'other':
|
||||
this.tokenTypes[scope] = StandardTokenType.Other;
|
||||
break;
|
||||
case 'comment':
|
||||
this.tokenTypes[scope] = StandardTokenType.Comment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ICreateGrammarResult {
|
||||
languageId: LanguageId;
|
||||
grammar: IGrammar;
|
||||
initialState: StackElement;
|
||||
containsEmbeddedLanguages: boolean;
|
||||
}
|
||||
|
||||
export abstract class AbstractTextMateService extends Disposable implements ITextMateService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private readonly _onDidEncounterLanguage: Emitter<LanguageId> = this._register(new Emitter<LanguageId>());
|
||||
public readonly onDidEncounterLanguage: Event<LanguageId> = this._onDidEncounterLanguage.event;
|
||||
|
||||
private readonly _styleElement: HTMLStyleElement;
|
||||
private readonly _createdModes: string[];
|
||||
|
||||
protected _scopeRegistry: TMScopeRegistry;
|
||||
private _injections: { [scopeName: string]: string[]; };
|
||||
private _injectedEmbeddedLanguages: { [scopeName: string]: IEmbeddedLanguagesMap[]; };
|
||||
protected _languageToScope: Map<string, string>;
|
||||
private _grammarRegistry: Promise<[Registry, StackElement]> | null;
|
||||
private _tokenizersRegistrations: IDisposable[];
|
||||
private _currentTokenColors: ITokenColorizationRule[] | null;
|
||||
private _themeListener: IDisposable | null;
|
||||
|
||||
constructor(
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
@IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this._styleElement = dom.createStyleSheet();
|
||||
this._styleElement.className = 'vscode-tokens-styles';
|
||||
this._createdModes = [];
|
||||
this._scopeRegistry = new TMScopeRegistry();
|
||||
this._scopeRegistry.onDidEncounterLanguage((language) => this._onDidEncounterLanguage.fire(language));
|
||||
this._injections = {};
|
||||
this._injectedEmbeddedLanguages = {};
|
||||
this._languageToScope = new Map<string, string>();
|
||||
this._grammarRegistry = null;
|
||||
this._tokenizersRegistrations = [];
|
||||
this._currentTokenColors = null;
|
||||
this._themeListener = null;
|
||||
|
||||
grammarsExtPoint.setHandler((extensions) => {
|
||||
this._scopeRegistry.reset();
|
||||
this._injections = {};
|
||||
this._injectedEmbeddedLanguages = {};
|
||||
this._languageToScope = new Map<string, string>();
|
||||
this._grammarRegistry = null;
|
||||
this._tokenizersRegistrations = dispose(this._tokenizersRegistrations);
|
||||
this._currentTokenColors = null;
|
||||
if (this._themeListener) {
|
||||
this._themeListener.dispose();
|
||||
this._themeListener = null;
|
||||
}
|
||||
|
||||
for (const extension of extensions) {
|
||||
let grammars = extension.value;
|
||||
for (const grammar of grammars) {
|
||||
this._handleGrammarExtensionPointUser(extension.description.extensionLocation, grammar, extension.collector);
|
||||
}
|
||||
}
|
||||
|
||||
for (const createMode of this._createdModes) {
|
||||
this._registerDefinitionIfAvailable(createMode);
|
||||
}
|
||||
});
|
||||
|
||||
// Generate some color map until the grammar registry is loaded
|
||||
let colorTheme = this._themeService.getColorTheme();
|
||||
let defaultForeground: Color = Color.transparent;
|
||||
let defaultBackground: Color = Color.transparent;
|
||||
for (let i = 0, len = colorTheme.tokenColors.length; i < len; i++) {
|
||||
let rule = colorTheme.tokenColors[i];
|
||||
if (!rule.scope && rule.settings) {
|
||||
if (rule.settings.foreground) {
|
||||
defaultForeground = Color.fromHex(rule.settings.foreground);
|
||||
}
|
||||
if (rule.settings.background) {
|
||||
defaultBackground = Color.fromHex(rule.settings.background);
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenizationRegistry.setColorMap([null!, defaultForeground, defaultBackground]);
|
||||
|
||||
this._modeService.onDidCreateMode((mode) => {
|
||||
let modeId = mode.getId();
|
||||
this._createdModes.push(modeId);
|
||||
this._registerDefinitionIfAvailable(modeId);
|
||||
});
|
||||
}
|
||||
|
||||
private _registerDefinitionIfAvailable(modeId: string): void {
|
||||
if (this._languageToScope.has(modeId)) {
|
||||
const promise = this._createGrammar(modeId).then((r) => {
|
||||
return new TMTokenization(this._scopeRegistry, r.languageId, r.grammar, r.initialState, r.containsEmbeddedLanguages, this._notificationService, this._configurationService);
|
||||
}, e => {
|
||||
onUnexpectedError(e);
|
||||
return null;
|
||||
});
|
||||
this._tokenizersRegistrations.push(TokenizationRegistry.registerPromise(modeId, promise));
|
||||
}
|
||||
}
|
||||
|
||||
protected _getRegistryOptions(parseRawGrammar: (content: string, filePath: string) => IRawGrammar): RegistryOptions {
|
||||
return {
|
||||
loadGrammar: async (scopeName: string) => {
|
||||
const location = this._scopeRegistry.getGrammarLocation(scopeName);
|
||||
if (!location) {
|
||||
this._logService.trace(`No grammar found for scope ${scopeName}`);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const content = await this._fileService.readFile(location);
|
||||
return parseRawGrammar(content.value.toString(), location.path);
|
||||
} catch (e) {
|
||||
this._logService.error(`Unable to load and parse grammar for scope ${scopeName} from ${location}`, e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getInjections: (scopeName: string) => {
|
||||
const scopeParts = scopeName.split('.');
|
||||
let injections: string[] = [];
|
||||
for (let i = 1; i <= scopeParts.length; i++) {
|
||||
const subScopeName = scopeParts.slice(0, i).join('.');
|
||||
injections = [...injections, ...(this._injections[subScopeName] || [])];
|
||||
}
|
||||
return injections;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async _createGrammarRegistry(): Promise<[Registry, StackElement]> {
|
||||
const { Registry, INITIAL, parseRawGrammar } = await this._loadVSCodeTextmate();
|
||||
const grammarRegistry = new Registry(this._getRegistryOptions(parseRawGrammar));
|
||||
this._updateTheme(grammarRegistry);
|
||||
this._themeListener = this._themeService.onDidColorThemeChange((e) => this._updateTheme(grammarRegistry));
|
||||
return <[Registry, StackElement]>[grammarRegistry, INITIAL];
|
||||
}
|
||||
|
||||
private _getOrCreateGrammarRegistry(): Promise<[Registry, StackElement]> {
|
||||
if (!this._grammarRegistry) {
|
||||
this._grammarRegistry = this._createGrammarRegistry();
|
||||
}
|
||||
return this._grammarRegistry;
|
||||
}
|
||||
|
||||
private static _toColorMap(colorMap: string[]): Color[] {
|
||||
let result: Color[] = [null!];
|
||||
for (let i = 1, len = colorMap.length; i < len; i++) {
|
||||
result[i] = Color.fromHex(colorMap[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _updateTheme(grammarRegistry: Registry): void {
|
||||
let colorTheme = this._themeService.getColorTheme();
|
||||
if (!this.compareTokenRules(colorTheme.tokenColors)) {
|
||||
return;
|
||||
}
|
||||
grammarRegistry.setTheme({ name: colorTheme.label, settings: colorTheme.tokenColors });
|
||||
let colorMap = AbstractTextMateService._toColorMap(grammarRegistry.getColorMap());
|
||||
let cssRules = generateTokensCSSForColorMap(colorMap);
|
||||
this._styleElement.innerHTML = cssRules;
|
||||
TokenizationRegistry.setColorMap(colorMap);
|
||||
}
|
||||
|
||||
private compareTokenRules(newRules: ITokenColorizationRule[]): boolean {
|
||||
let currRules = this._currentTokenColors;
|
||||
this._currentTokenColors = newRules;
|
||||
if (!newRules || !currRules || newRules.length !== currRules.length) {
|
||||
return true;
|
||||
}
|
||||
for (let i = newRules.length - 1; i >= 0; i--) {
|
||||
let r1 = newRules[i];
|
||||
let r2 = currRules[i];
|
||||
if (r1.scope !== r2.scope) {
|
||||
return true;
|
||||
}
|
||||
let s1 = r1.settings;
|
||||
let s2 = r2.settings;
|
||||
if (s1 && s2) {
|
||||
if (s1.fontStyle !== s2.fontStyle || s1.foreground !== s2.foreground || s1.background !== s2.background) {
|
||||
return true;
|
||||
}
|
||||
} else if (!s1 || !s2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _handleGrammarExtensionPointUser(extensionLocation: URI, syntax: ITMSyntaxExtensionPoint, collector: ExtensionMessageCollector): void {
|
||||
if (syntax.language && ((typeof syntax.language !== 'string') || !this._modeService.isRegisteredMode(syntax.language))) {
|
||||
collector.error(nls.localize('invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", grammarsExtPoint.name, String(syntax.language)));
|
||||
return;
|
||||
}
|
||||
if (!syntax.scopeName || (typeof syntax.scopeName !== 'string')) {
|
||||
collector.error(nls.localize('invalid.scopeName', "Expected string in `contributes.{0}.scopeName`. Provided value: {1}", grammarsExtPoint.name, String(syntax.scopeName)));
|
||||
return;
|
||||
}
|
||||
if (!syntax.path || (typeof syntax.path !== 'string')) {
|
||||
collector.error(nls.localize('invalid.path.0', "Expected string in `contributes.{0}.path`. Provided value: {1}", grammarsExtPoint.name, String(syntax.path)));
|
||||
return;
|
||||
}
|
||||
if (syntax.injectTo && (!Array.isArray(syntax.injectTo) || syntax.injectTo.some(scope => typeof scope !== 'string'))) {
|
||||
collector.error(nls.localize('invalid.injectTo', "Invalid value in `contributes.{0}.injectTo`. Must be an array of language scope names. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.injectTo)));
|
||||
return;
|
||||
}
|
||||
if (syntax.embeddedLanguages && !types.isObject(syntax.embeddedLanguages)) {
|
||||
collector.error(nls.localize('invalid.embeddedLanguages', "Invalid value in `contributes.{0}.embeddedLanguages`. Must be an object map from scope name to language. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.embeddedLanguages)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (syntax.tokenTypes && !types.isObject(syntax.tokenTypes)) {
|
||||
collector.error(nls.localize('invalid.tokenTypes', "Invalid value in `contributes.{0}.tokenTypes`. Must be an object map from scope name to token type. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.tokenTypes)));
|
||||
return;
|
||||
}
|
||||
|
||||
const grammarLocation = resources.joinPath(extensionLocation, syntax.path);
|
||||
if (!resources.isEqualOrParent(grammarLocation, extensionLocation)) {
|
||||
collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", grammarsExtPoint.name, grammarLocation.path, extensionLocation.path));
|
||||
}
|
||||
|
||||
this._scopeRegistry.register(syntax.scopeName, grammarLocation, syntax.embeddedLanguages, syntax.tokenTypes);
|
||||
|
||||
if (syntax.injectTo) {
|
||||
for (let injectScope of syntax.injectTo) {
|
||||
let injections = this._injections[injectScope];
|
||||
if (!injections) {
|
||||
this._injections[injectScope] = injections = [];
|
||||
}
|
||||
injections.push(syntax.scopeName);
|
||||
}
|
||||
|
||||
if (syntax.embeddedLanguages) {
|
||||
for (let injectScope of syntax.injectTo) {
|
||||
let injectedEmbeddedLanguages = this._injectedEmbeddedLanguages[injectScope];
|
||||
if (!injectedEmbeddedLanguages) {
|
||||
this._injectedEmbeddedLanguages[injectScope] = injectedEmbeddedLanguages = [];
|
||||
}
|
||||
injectedEmbeddedLanguages.push(syntax.embeddedLanguages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let modeId = syntax.language;
|
||||
if (modeId) {
|
||||
this._languageToScope.set(modeId, syntax.scopeName);
|
||||
}
|
||||
}
|
||||
|
||||
private _resolveEmbeddedLanguages(embeddedLanguages: IEmbeddedLanguagesMap): IEmbeddedLanguagesMap2 {
|
||||
let scopes = Object.keys(embeddedLanguages);
|
||||
let result: IEmbeddedLanguagesMap2 = Object.create(null);
|
||||
for (let i = 0, len = scopes.length; i < len; i++) {
|
||||
let scope = scopes[i];
|
||||
let language = embeddedLanguages[scope];
|
||||
let languageIdentifier = this._modeService.getLanguageIdentifier(language);
|
||||
if (languageIdentifier) {
|
||||
result[scope] = languageIdentifier.id;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async createGrammar(modeId: string): Promise<IGrammar> {
|
||||
const { grammar } = await this._createGrammar(modeId);
|
||||
return grammar;
|
||||
}
|
||||
|
||||
private async _createGrammar(modeId: string): Promise<ICreateGrammarResult> {
|
||||
const scopeName = this._languageToScope.get(modeId);
|
||||
if (typeof scopeName !== 'string') {
|
||||
// No TM grammar defined
|
||||
return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language.")));
|
||||
}
|
||||
const languageRegistration = this._scopeRegistry.getLanguageRegistration(scopeName);
|
||||
if (!languageRegistration) {
|
||||
// No TM grammar defined
|
||||
return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language.")));
|
||||
}
|
||||
let embeddedLanguages = this._resolveEmbeddedLanguages(languageRegistration.embeddedLanguages);
|
||||
let rawInjectedEmbeddedLanguages = this._injectedEmbeddedLanguages[scopeName];
|
||||
if (rawInjectedEmbeddedLanguages) {
|
||||
let injectedEmbeddedLanguages: IEmbeddedLanguagesMap2[] = rawInjectedEmbeddedLanguages.map(this._resolveEmbeddedLanguages.bind(this));
|
||||
for (const injected of injectedEmbeddedLanguages) {
|
||||
for (const scope of Object.keys(injected)) {
|
||||
embeddedLanguages[scope] = injected[scope];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let languageId = this._modeService.getLanguageIdentifier(modeId)!.id;
|
||||
let containsEmbeddedLanguages = (Object.keys(embeddedLanguages).length > 0);
|
||||
|
||||
const [grammarRegistry, initialState] = await this._getOrCreateGrammarRegistry();
|
||||
const grammar = await grammarRegistry.loadGrammarWithConfiguration(scopeName, languageId, { embeddedLanguages, tokenTypes: languageRegistration.tokenTypes });
|
||||
return {
|
||||
languageId: languageId,
|
||||
grammar: grammar,
|
||||
initialState: initialState,
|
||||
containsEmbeddedLanguages: containsEmbeddedLanguages
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract _loadVSCodeTextmate(): Promise<typeof import('vscode-textmate')>;
|
||||
}
|
||||
|
||||
class TMTokenization implements ITokenizationSupport {
|
||||
|
||||
private readonly _scopeRegistry: TMScopeRegistry;
|
||||
private readonly _languageId: LanguageId;
|
||||
private readonly _grammar: IGrammar;
|
||||
private readonly _containsEmbeddedLanguages: boolean;
|
||||
private readonly _seenLanguages: boolean[];
|
||||
private readonly _initialState: StackElement;
|
||||
private _maxTokenizationLineLength: number;
|
||||
private _tokenizationWarningAlreadyShown: boolean;
|
||||
|
||||
constructor(scopeRegistry: TMScopeRegistry, languageId: LanguageId, grammar: IGrammar, initialState: StackElement, containsEmbeddedLanguages: boolean, @INotificationService private readonly notificationService: INotificationService, @IConfigurationService readonly configurationService: IConfigurationService) {
|
||||
this._scopeRegistry = scopeRegistry;
|
||||
this._languageId = languageId;
|
||||
this._grammar = grammar;
|
||||
this._initialState = initialState;
|
||||
this._containsEmbeddedLanguages = containsEmbeddedLanguages;
|
||||
this._seenLanguages = [];
|
||||
this._maxTokenizationLineLength = configurationService.getValue<number>('editor.maxTokenizationLineLength');
|
||||
}
|
||||
|
||||
public getInitialState(): IState {
|
||||
return this._initialState;
|
||||
}
|
||||
|
||||
public tokenize(line: string, state: IState, offsetDelta: number): TokenizationResult {
|
||||
throw new Error('Not supported!');
|
||||
}
|
||||
|
||||
public tokenize2(line: string, state: StackElement, offsetDelta: number): TokenizationResult2 {
|
||||
if (offsetDelta !== 0) {
|
||||
throw new Error('Unexpected: offsetDelta should be 0.');
|
||||
}
|
||||
|
||||
// Do not attempt to tokenize if a line is too long
|
||||
if (line.length >= this._maxTokenizationLineLength) {
|
||||
if (!this._tokenizationWarningAlreadyShown) {
|
||||
this._tokenizationWarningAlreadyShown = true;
|
||||
this.notificationService.warn(nls.localize('too many characters', "Tokenization is skipped for long lines for performance reasons. The length of a long line can be configured via `editor.maxTokenizationLineLength`."));
|
||||
}
|
||||
console.log(`Line (${line.substr(0, 15)}...): longer than ${this._maxTokenizationLineLength} characters, tokenization skipped.`);
|
||||
return nullTokenize2(this._languageId, line, state, offsetDelta);
|
||||
}
|
||||
|
||||
let textMateResult = this._grammar.tokenizeLine2(line, state);
|
||||
|
||||
if (this._containsEmbeddedLanguages) {
|
||||
let seenLanguages = this._seenLanguages;
|
||||
let tokens = textMateResult.tokens;
|
||||
|
||||
// Must check if any of the embedded languages was hit
|
||||
for (let i = 0, len = (tokens.length >>> 1); i < len; i++) {
|
||||
let metadata = tokens[(i << 1) + 1];
|
||||
let languageId = TokenMetadata.getLanguageId(metadata);
|
||||
|
||||
if (!seenLanguages[languageId]) {
|
||||
seenLanguages[languageId] = true;
|
||||
this._scopeRegistry.onEncounteredLanguage(languageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let endState: StackElement;
|
||||
// try to save an object if possible
|
||||
if (state.equals(textMateResult.ruleStack)) {
|
||||
endState = state;
|
||||
} else {
|
||||
endState = textMateResult.ruleStack;
|
||||
|
||||
}
|
||||
|
||||
return new TokenizationResult2(textMateResult.tokens, endState);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService';
|
||||
import * as vscodeTextmate from 'vscode-textmate';
|
||||
import * as onigasm from 'onigasm-umd';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export class TextMateService extends AbstractTextMateService {
|
||||
|
||||
constructor(
|
||||
@IModeService modeService: IModeService,
|
||||
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
|
||||
@IFileService fileService: IFileService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@ILogService logService: ILogService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(modeService, themeService, fileService, notificationService, logService, configurationService);
|
||||
}
|
||||
|
||||
protected _loadVSCodeTextmate(): Promise<typeof import('vscode-textmate')> {
|
||||
return import('vscode-textmate');
|
||||
}
|
||||
|
||||
protected _getRegistryOptions(parseRawGrammar: (content: string, filePath: string) => vscodeTextmate.IRawGrammar): vscodeTextmate.RegistryOptions {
|
||||
const result = super._getRegistryOptions(parseRawGrammar);
|
||||
result.getOnigLib = () => loadOnigasm();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
let onigasmPromise: Promise<vscodeTextmate.IOnigLib> | null = null;
|
||||
async function loadOnigasm(): Promise<vscodeTextmate.IOnigLib> {
|
||||
if (!onigasmPromise) {
|
||||
onigasmPromise = doLoadOnigasm();
|
||||
}
|
||||
return onigasmPromise;
|
||||
}
|
||||
|
||||
async function doLoadOnigasm(): Promise<vscodeTextmate.IOnigLib> {
|
||||
const wasmBytes = await loadOnigasmWASM();
|
||||
await onigasm.loadWASM(wasmBytes);
|
||||
return {
|
||||
createOnigScanner(patterns: string[]) { return new onigasm.OnigScanner(patterns); },
|
||||
createOnigString(s: string) { return new onigasm.OnigString(s); }
|
||||
};
|
||||
}
|
||||
|
||||
async function loadOnigasmWASM(): Promise<ArrayBuffer> {
|
||||
const wasmPath = require.toUrl('onigasm-umd/../onigasm.wasm');
|
||||
const response = await fetch(wasmPath);
|
||||
const bytes = await response.arrayBuffer();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
registerSingleton(ITextMateService, TextMateService);
|
||||
@@ -3,14 +3,512 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token';
|
||||
import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { nullTokenize2 } from 'vs/editor/common/modes/nullMode';
|
||||
import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint, TokenTypesContribution, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars';
|
||||
import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService';
|
||||
import { ITokenColorizationRule, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, IGrammar, ITokenTypeMap, Registry, StackElement, StandardTokenType } from 'vscode-textmate';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService';
|
||||
|
||||
export class TextMateService extends AbstractTextMateService {
|
||||
export class TMScopeRegistry {
|
||||
|
||||
protected _loadVSCodeTextmate(): Promise<typeof import('vscode-textmate')> {
|
||||
return import('vscode-textmate');
|
||||
private _scopeNameToLanguageRegistration: { [scopeName: string]: TMLanguageRegistration; };
|
||||
private _encounteredLanguages: boolean[];
|
||||
|
||||
private readonly _onDidEncounterLanguage = new Emitter<LanguageId>();
|
||||
public readonly onDidEncounterLanguage: Event<LanguageId> = this._onDidEncounterLanguage.event;
|
||||
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._scopeNameToLanguageRegistration = Object.create(null);
|
||||
this._encounteredLanguages = [];
|
||||
}
|
||||
|
||||
public register(scopeName: string, grammarLocation: URI, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: TokenTypesContribution): void {
|
||||
if (this._scopeNameToLanguageRegistration[scopeName]) {
|
||||
const existingRegistration = this._scopeNameToLanguageRegistration[scopeName];
|
||||
if (!resources.isEqual(existingRegistration.grammarLocation, grammarLocation)) {
|
||||
console.warn(
|
||||
`Overwriting grammar scope name to file mapping for scope ${scopeName}.\n` +
|
||||
`Old grammar file: ${existingRegistration.grammarLocation.toString()}.\n` +
|
||||
`New grammar file: ${grammarLocation.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
this._scopeNameToLanguageRegistration[scopeName] = new TMLanguageRegistration(scopeName, grammarLocation, embeddedLanguages, tokenTypes);
|
||||
}
|
||||
|
||||
public getLanguageRegistration(scopeName: string): TMLanguageRegistration {
|
||||
return this._scopeNameToLanguageRegistration[scopeName] || null;
|
||||
}
|
||||
|
||||
public getGrammarLocation(scopeName: string): URI | null {
|
||||
let data = this.getLanguageRegistration(scopeName);
|
||||
return data ? data.grammarLocation : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when tokenization found/hit an embedded language.
|
||||
*/
|
||||
public onEncounteredLanguage(languageId: LanguageId): void {
|
||||
if (!this._encounteredLanguages[languageId]) {
|
||||
this._encounteredLanguages[languageId] = true;
|
||||
this._onDidEncounterLanguage.fire(languageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TMLanguageRegistration {
|
||||
_topLevelScopeNameDataBrand: void;
|
||||
|
||||
readonly scopeName: string;
|
||||
readonly grammarLocation: URI;
|
||||
readonly embeddedLanguages: IEmbeddedLanguagesMap;
|
||||
readonly tokenTypes: ITokenTypeMap;
|
||||
|
||||
constructor(scopeName: string, grammarLocation: URI, embeddedLanguages: IEmbeddedLanguagesMap | undefined, tokenTypes: TokenTypesContribution | undefined) {
|
||||
this.scopeName = scopeName;
|
||||
this.grammarLocation = grammarLocation;
|
||||
|
||||
// embeddedLanguages handling
|
||||
this.embeddedLanguages = Object.create(null);
|
||||
|
||||
if (embeddedLanguages) {
|
||||
// If embeddedLanguages are configured, fill in `this._embeddedLanguages`
|
||||
let scopes = Object.keys(embeddedLanguages);
|
||||
for (let i = 0, len = scopes.length; i < len; i++) {
|
||||
let scope = scopes[i];
|
||||
let language = embeddedLanguages[scope];
|
||||
if (typeof language !== 'string') {
|
||||
// never hurts to be too careful
|
||||
continue;
|
||||
}
|
||||
this.embeddedLanguages[scope] = language;
|
||||
}
|
||||
}
|
||||
|
||||
this.tokenTypes = Object.create(null);
|
||||
if (tokenTypes) {
|
||||
// If tokenTypes is configured, fill in `this._tokenTypes`
|
||||
const scopes = Object.keys(tokenTypes);
|
||||
for (const scope of scopes) {
|
||||
const tokenType = tokenTypes[scope];
|
||||
switch (tokenType) {
|
||||
case 'string':
|
||||
this.tokenTypes[scope] = StandardTokenType.String;
|
||||
break;
|
||||
case 'other':
|
||||
this.tokenTypes[scope] = StandardTokenType.Other;
|
||||
break;
|
||||
case 'comment':
|
||||
this.tokenTypes[scope] = StandardTokenType.Comment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ICreateGrammarResult {
|
||||
languageId: LanguageId;
|
||||
grammar: IGrammar;
|
||||
initialState: StackElement;
|
||||
containsEmbeddedLanguages: boolean;
|
||||
}
|
||||
|
||||
export class TextMateService extends Disposable implements ITextMateService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private readonly _onDidEncounterLanguage: Emitter<LanguageId> = this._register(new Emitter<LanguageId>());
|
||||
public readonly onDidEncounterLanguage: Event<LanguageId> = this._onDidEncounterLanguage.event;
|
||||
|
||||
private readonly _styleElement: HTMLStyleElement;
|
||||
private readonly _createdModes: string[];
|
||||
|
||||
private _scopeRegistry: TMScopeRegistry;
|
||||
private _injections: { [scopeName: string]: string[]; };
|
||||
private _injectedEmbeddedLanguages: { [scopeName: string]: IEmbeddedLanguagesMap[]; };
|
||||
private _languageToScope: Map<string, string>;
|
||||
private _grammarRegistry: Promise<[Registry, StackElement]> | null;
|
||||
private _tokenizersRegistrations: IDisposable[];
|
||||
private _currentTokenColors: ITokenColorizationRule[] | null;
|
||||
private _themeListener: IDisposable | null;
|
||||
|
||||
constructor(
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
@IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this._styleElement = dom.createStyleSheet();
|
||||
this._styleElement.className = 'vscode-tokens-styles';
|
||||
this._createdModes = [];
|
||||
this._scopeRegistry = new TMScopeRegistry();
|
||||
this._scopeRegistry.onDidEncounterLanguage((language) => this._onDidEncounterLanguage.fire(language));
|
||||
this._injections = {};
|
||||
this._injectedEmbeddedLanguages = {};
|
||||
this._languageToScope = new Map<string, string>();
|
||||
this._grammarRegistry = null;
|
||||
this._tokenizersRegistrations = [];
|
||||
this._currentTokenColors = null;
|
||||
this._themeListener = null;
|
||||
|
||||
grammarsExtPoint.setHandler((extensions) => {
|
||||
this._scopeRegistry.reset();
|
||||
this._injections = {};
|
||||
this._injectedEmbeddedLanguages = {};
|
||||
this._languageToScope = new Map<string, string>();
|
||||
this._grammarRegistry = null;
|
||||
this._tokenizersRegistrations = dispose(this._tokenizersRegistrations);
|
||||
this._currentTokenColors = null;
|
||||
if (this._themeListener) {
|
||||
this._themeListener.dispose();
|
||||
this._themeListener = null;
|
||||
}
|
||||
|
||||
for (const extension of extensions) {
|
||||
let grammars = extension.value;
|
||||
for (const grammar of grammars) {
|
||||
this._handleGrammarExtensionPointUser(extension.description.extensionLocation, grammar, extension.collector);
|
||||
}
|
||||
}
|
||||
|
||||
for (const createMode of this._createdModes) {
|
||||
this._registerDefinitionIfAvailable(createMode);
|
||||
}
|
||||
});
|
||||
|
||||
// Generate some color map until the grammar registry is loaded
|
||||
let colorTheme = this._themeService.getColorTheme();
|
||||
let defaultForeground: Color = Color.transparent;
|
||||
let defaultBackground: Color = Color.transparent;
|
||||
for (let i = 0, len = colorTheme.tokenColors.length; i < len; i++) {
|
||||
let rule = colorTheme.tokenColors[i];
|
||||
if (!rule.scope && rule.settings) {
|
||||
if (rule.settings.foreground) {
|
||||
defaultForeground = Color.fromHex(rule.settings.foreground);
|
||||
}
|
||||
if (rule.settings.background) {
|
||||
defaultBackground = Color.fromHex(rule.settings.background);
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenizationRegistry.setColorMap([null!, defaultForeground, defaultBackground]);
|
||||
|
||||
this._modeService.onDidCreateMode((mode) => {
|
||||
let modeId = mode.getId();
|
||||
this._createdModes.push(modeId);
|
||||
this._registerDefinitionIfAvailable(modeId);
|
||||
});
|
||||
}
|
||||
|
||||
private _registerDefinitionIfAvailable(modeId: string): void {
|
||||
if (this._languageToScope.has(modeId)) {
|
||||
const promise = this._createGrammar(modeId).then((r) => {
|
||||
return new TMTokenization(this._scopeRegistry, r.languageId, r.grammar, r.initialState, r.containsEmbeddedLanguages, this._notificationService, this._configurationService);
|
||||
}, e => {
|
||||
onUnexpectedError(e);
|
||||
return null;
|
||||
});
|
||||
this._tokenizersRegistrations.push(TokenizationRegistry.registerPromise(modeId, promise));
|
||||
}
|
||||
}
|
||||
|
||||
private async _createGrammarRegistry(): Promise<[Registry, StackElement]> {
|
||||
const { Registry, INITIAL, parseRawGrammar } = await import('vscode-textmate');
|
||||
const grammarRegistry = new Registry({
|
||||
loadGrammar: async (scopeName: string) => {
|
||||
const location = this._scopeRegistry.getGrammarLocation(scopeName);
|
||||
if (!location) {
|
||||
this._logService.trace(`No grammar found for scope ${scopeName}`);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const content = await this._fileService.readFile(location);
|
||||
return parseRawGrammar(content.value.toString(), location.path);
|
||||
} catch (e) {
|
||||
this._logService.error(`Unable to load and parse grammar for scope ${scopeName} from ${location}`, e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getInjections: (scopeName: string) => {
|
||||
const scopeParts = scopeName.split('.');
|
||||
let injections: string[] = [];
|
||||
for (let i = 1; i <= scopeParts.length; i++) {
|
||||
const subScopeName = scopeParts.slice(0, i).join('.');
|
||||
injections = [...injections, ...(this._injections[subScopeName] || [])];
|
||||
}
|
||||
return injections;
|
||||
}
|
||||
});
|
||||
this._updateTheme(grammarRegistry);
|
||||
this._themeListener = this._themeService.onDidColorThemeChange((e) => this._updateTheme(grammarRegistry));
|
||||
return <[Registry, StackElement]>[grammarRegistry, INITIAL];
|
||||
}
|
||||
|
||||
private _getOrCreateGrammarRegistry(): Promise<[Registry, StackElement]> {
|
||||
if (!this._grammarRegistry) {
|
||||
this._grammarRegistry = this._createGrammarRegistry();
|
||||
}
|
||||
return this._grammarRegistry;
|
||||
}
|
||||
|
||||
private static _toColorMap(colorMap: string[]): Color[] {
|
||||
let result: Color[] = [null!];
|
||||
for (let i = 1, len = colorMap.length; i < len; i++) {
|
||||
result[i] = Color.fromHex(colorMap[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _updateTheme(grammarRegistry: Registry): void {
|
||||
let colorTheme = this._themeService.getColorTheme();
|
||||
if (!this.compareTokenRules(colorTheme.tokenColors)) {
|
||||
return;
|
||||
}
|
||||
grammarRegistry.setTheme({ name: colorTheme.label, settings: colorTheme.tokenColors });
|
||||
let colorMap = TextMateService._toColorMap(grammarRegistry.getColorMap());
|
||||
let cssRules = generateTokensCSSForColorMap(colorMap);
|
||||
this._styleElement.innerHTML = cssRules;
|
||||
TokenizationRegistry.setColorMap(colorMap);
|
||||
}
|
||||
|
||||
private compareTokenRules(newRules: ITokenColorizationRule[]): boolean {
|
||||
let currRules = this._currentTokenColors;
|
||||
this._currentTokenColors = newRules;
|
||||
if (!newRules || !currRules || newRules.length !== currRules.length) {
|
||||
return true;
|
||||
}
|
||||
for (let i = newRules.length - 1; i >= 0; i--) {
|
||||
let r1 = newRules[i];
|
||||
let r2 = currRules[i];
|
||||
if (r1.scope !== r2.scope) {
|
||||
return true;
|
||||
}
|
||||
let s1 = r1.settings;
|
||||
let s2 = r2.settings;
|
||||
if (s1 && s2) {
|
||||
if (s1.fontStyle !== s2.fontStyle || s1.foreground !== s2.foreground || s1.background !== s2.background) {
|
||||
return true;
|
||||
}
|
||||
} else if (!s1 || !s2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _handleGrammarExtensionPointUser(extensionLocation: URI, syntax: ITMSyntaxExtensionPoint, collector: ExtensionMessageCollector): void {
|
||||
if (syntax.language && ((typeof syntax.language !== 'string') || !this._modeService.isRegisteredMode(syntax.language))) {
|
||||
collector.error(nls.localize('invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", grammarsExtPoint.name, String(syntax.language)));
|
||||
return;
|
||||
}
|
||||
if (!syntax.scopeName || (typeof syntax.scopeName !== 'string')) {
|
||||
collector.error(nls.localize('invalid.scopeName', "Expected string in `contributes.{0}.scopeName`. Provided value: {1}", grammarsExtPoint.name, String(syntax.scopeName)));
|
||||
return;
|
||||
}
|
||||
if (!syntax.path || (typeof syntax.path !== 'string')) {
|
||||
collector.error(nls.localize('invalid.path.0', "Expected string in `contributes.{0}.path`. Provided value: {1}", grammarsExtPoint.name, String(syntax.path)));
|
||||
return;
|
||||
}
|
||||
if (syntax.injectTo && (!Array.isArray(syntax.injectTo) || syntax.injectTo.some(scope => typeof scope !== 'string'))) {
|
||||
collector.error(nls.localize('invalid.injectTo', "Invalid value in `contributes.{0}.injectTo`. Must be an array of language scope names. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.injectTo)));
|
||||
return;
|
||||
}
|
||||
if (syntax.embeddedLanguages && !types.isObject(syntax.embeddedLanguages)) {
|
||||
collector.error(nls.localize('invalid.embeddedLanguages', "Invalid value in `contributes.{0}.embeddedLanguages`. Must be an object map from scope name to language. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.embeddedLanguages)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (syntax.tokenTypes && !types.isObject(syntax.tokenTypes)) {
|
||||
collector.error(nls.localize('invalid.tokenTypes', "Invalid value in `contributes.{0}.tokenTypes`. Must be an object map from scope name to token type. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.tokenTypes)));
|
||||
return;
|
||||
}
|
||||
|
||||
const grammarLocation = resources.joinPath(extensionLocation, syntax.path);
|
||||
if (!resources.isEqualOrParent(grammarLocation, extensionLocation)) {
|
||||
collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", grammarsExtPoint.name, grammarLocation.path, extensionLocation.path));
|
||||
}
|
||||
|
||||
this._scopeRegistry.register(syntax.scopeName, grammarLocation, syntax.embeddedLanguages, syntax.tokenTypes);
|
||||
|
||||
if (syntax.injectTo) {
|
||||
for (let injectScope of syntax.injectTo) {
|
||||
let injections = this._injections[injectScope];
|
||||
if (!injections) {
|
||||
this._injections[injectScope] = injections = [];
|
||||
}
|
||||
injections.push(syntax.scopeName);
|
||||
}
|
||||
|
||||
if (syntax.embeddedLanguages) {
|
||||
for (let injectScope of syntax.injectTo) {
|
||||
let injectedEmbeddedLanguages = this._injectedEmbeddedLanguages[injectScope];
|
||||
if (!injectedEmbeddedLanguages) {
|
||||
this._injectedEmbeddedLanguages[injectScope] = injectedEmbeddedLanguages = [];
|
||||
}
|
||||
injectedEmbeddedLanguages.push(syntax.embeddedLanguages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let modeId = syntax.language;
|
||||
if (modeId) {
|
||||
this._languageToScope.set(modeId, syntax.scopeName);
|
||||
}
|
||||
}
|
||||
|
||||
private _resolveEmbeddedLanguages(embeddedLanguages: IEmbeddedLanguagesMap): IEmbeddedLanguagesMap2 {
|
||||
let scopes = Object.keys(embeddedLanguages);
|
||||
let result: IEmbeddedLanguagesMap2 = Object.create(null);
|
||||
for (let i = 0, len = scopes.length; i < len; i++) {
|
||||
let scope = scopes[i];
|
||||
let language = embeddedLanguages[scope];
|
||||
let languageIdentifier = this._modeService.getLanguageIdentifier(language);
|
||||
if (languageIdentifier) {
|
||||
result[scope] = languageIdentifier.id;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async createGrammar(modeId: string): Promise<IGrammar> {
|
||||
const { grammar } = await this._createGrammar(modeId);
|
||||
return grammar;
|
||||
}
|
||||
|
||||
private async _createGrammar(modeId: string): Promise<ICreateGrammarResult> {
|
||||
const scopeName = this._languageToScope.get(modeId);
|
||||
if (typeof scopeName !== 'string') {
|
||||
// No TM grammar defined
|
||||
return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language.")));
|
||||
}
|
||||
const languageRegistration = this._scopeRegistry.getLanguageRegistration(scopeName);
|
||||
if (!languageRegistration) {
|
||||
// No TM grammar defined
|
||||
return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language.")));
|
||||
}
|
||||
let embeddedLanguages = this._resolveEmbeddedLanguages(languageRegistration.embeddedLanguages);
|
||||
let rawInjectedEmbeddedLanguages = this._injectedEmbeddedLanguages[scopeName];
|
||||
if (rawInjectedEmbeddedLanguages) {
|
||||
let injectedEmbeddedLanguages: IEmbeddedLanguagesMap2[] = rawInjectedEmbeddedLanguages.map(this._resolveEmbeddedLanguages.bind(this));
|
||||
for (const injected of injectedEmbeddedLanguages) {
|
||||
for (const scope of Object.keys(injected)) {
|
||||
embeddedLanguages[scope] = injected[scope];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let languageId = this._modeService.getLanguageIdentifier(modeId)!.id;
|
||||
let containsEmbeddedLanguages = (Object.keys(embeddedLanguages).length > 0);
|
||||
|
||||
const [grammarRegistry, initialState] = await this._getOrCreateGrammarRegistry();
|
||||
const grammar = await grammarRegistry.loadGrammarWithConfiguration(scopeName, languageId, { embeddedLanguages, tokenTypes: languageRegistration.tokenTypes });
|
||||
return {
|
||||
languageId: languageId,
|
||||
grammar: grammar,
|
||||
initialState: initialState,
|
||||
containsEmbeddedLanguages: containsEmbeddedLanguages
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TMTokenization implements ITokenizationSupport {
|
||||
|
||||
private readonly _scopeRegistry: TMScopeRegistry;
|
||||
private readonly _languageId: LanguageId;
|
||||
private readonly _grammar: IGrammar;
|
||||
private readonly _containsEmbeddedLanguages: boolean;
|
||||
private readonly _seenLanguages: boolean[];
|
||||
private readonly _initialState: StackElement;
|
||||
private _maxTokenizationLineLength: number;
|
||||
private _tokenizationWarningAlreadyShown: boolean;
|
||||
|
||||
constructor(scopeRegistry: TMScopeRegistry, languageId: LanguageId, grammar: IGrammar, initialState: StackElement, containsEmbeddedLanguages: boolean, @INotificationService private readonly notificationService: INotificationService, @IConfigurationService readonly configurationService: IConfigurationService) {
|
||||
this._scopeRegistry = scopeRegistry;
|
||||
this._languageId = languageId;
|
||||
this._grammar = grammar;
|
||||
this._initialState = initialState;
|
||||
this._containsEmbeddedLanguages = containsEmbeddedLanguages;
|
||||
this._seenLanguages = [];
|
||||
this._maxTokenizationLineLength = configurationService.getValue<number>('editor.maxTokenizationLineLength');
|
||||
}
|
||||
|
||||
public getInitialState(): IState {
|
||||
return this._initialState;
|
||||
}
|
||||
|
||||
public tokenize(line: string, state: IState, offsetDelta: number): TokenizationResult {
|
||||
throw new Error('Not supported!');
|
||||
}
|
||||
|
||||
public tokenize2(line: string, state: StackElement, offsetDelta: number): TokenizationResult2 {
|
||||
if (offsetDelta !== 0) {
|
||||
throw new Error('Unexpected: offsetDelta should be 0.');
|
||||
}
|
||||
|
||||
// Do not attempt to tokenize if a line is too long
|
||||
if (line.length >= this._maxTokenizationLineLength) {
|
||||
if (!this._tokenizationWarningAlreadyShown) {
|
||||
this._tokenizationWarningAlreadyShown = true;
|
||||
this.notificationService.warn(nls.localize('too many characters', "Tokenization is skipped for long lines for performance reasons. The length of a long line can be configured via `editor.maxTokenizationLineLength`."));
|
||||
}
|
||||
console.log(`Line (${line.substr(0, 15)}...): longer than ${this._maxTokenizationLineLength} characters, tokenization skipped.`);
|
||||
return nullTokenize2(this._languageId, line, state, offsetDelta);
|
||||
}
|
||||
|
||||
let textMateResult = this._grammar.tokenizeLine2(line, state);
|
||||
|
||||
if (this._containsEmbeddedLanguages) {
|
||||
let seenLanguages = this._seenLanguages;
|
||||
let tokens = textMateResult.tokens;
|
||||
|
||||
// Must check if any of the embedded languages was hit
|
||||
for (let i = 0, len = (tokens.length >>> 1); i < len; i++) {
|
||||
let metadata = tokens[(i << 1) + 1];
|
||||
let languageId = TokenMetadata.getLanguageId(metadata);
|
||||
|
||||
if (!seenLanguages[languageId]) {
|
||||
seenLanguages[languageId] = true;
|
||||
this._scopeRegistry.onEncounteredLanguage(languageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let endState: StackElement;
|
||||
// try to save an object if possible
|
||||
if (state.equals(textMateResult.ruleStack)) {
|
||||
endState = state;
|
||||
} else {
|
||||
endState = textMateResult.ruleStack;
|
||||
|
||||
}
|
||||
|
||||
return new TokenizationResult2(textMateResult.tokens, endState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
}
|
||||
|
||||
private async onFileChanges(e: FileChangesEvent): Promise<void> {
|
||||
private onFileChanges(e: FileChangesEvent): void {
|
||||
let fileEventImpactsModel = false;
|
||||
let newInOrphanModeGuess: boolean | undefined;
|
||||
|
||||
@@ -167,25 +167,28 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
if (fileEventImpactsModel && this.inOrphanMode !== newInOrphanModeGuess) {
|
||||
let newInOrphanModeValidated: boolean = false;
|
||||
let checkOrphanedPromise: Promise<boolean>;
|
||||
if (newInOrphanModeGuess) {
|
||||
// We have received reports of users seeing delete events even though the file still
|
||||
// exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665).
|
||||
// Since we do not want to mark the model as orphaned, we have to check if the
|
||||
// file is really gone and not just a faulty file event.
|
||||
await timeout(100);
|
||||
checkOrphanedPromise = timeout(100).then(() => {
|
||||
if (this.disposed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.disposed) {
|
||||
newInOrphanModeValidated = true;
|
||||
} else {
|
||||
const exists = await this.fileService.exists(this.resource);
|
||||
newInOrphanModeValidated = !exists;
|
||||
return this.fileService.exists(this.resource).then(exists => !exists);
|
||||
});
|
||||
} else {
|
||||
checkOrphanedPromise = Promise.resolve(false);
|
||||
}
|
||||
|
||||
checkOrphanedPromise.then(newInOrphanModeValidated => {
|
||||
if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) {
|
||||
this.setOrphaned(newInOrphanModeValidated);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) {
|
||||
this.setOrphaned(newInOrphanModeValidated);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,11 +239,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
return this.backupFileService.backupResource<IBackupMetaData>(target, this.createSnapshot(), this.versionId, meta);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async revert(soft?: boolean): Promise<void> {
|
||||
if (!this.isResolved()) {
|
||||
return;
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// Cancel any running auto-save
|
||||
@@ -249,21 +254,25 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// Unset flags
|
||||
const undo = this.setDirty(false);
|
||||
|
||||
// Force read from disk unless reverting soft
|
||||
if (!soft) {
|
||||
try {
|
||||
await this.load({ forceReadFromDisk: true });
|
||||
} catch (error) {
|
||||
|
||||
// Set flags back to previous values, we are still dirty if revert failed
|
||||
undo();
|
||||
|
||||
throw error;
|
||||
}
|
||||
let loadPromise: Promise<unknown>;
|
||||
if (soft) {
|
||||
loadPromise = Promise.resolve();
|
||||
} else {
|
||||
loadPromise = this.load({ forceReadFromDisk: true });
|
||||
}
|
||||
|
||||
// Emit file change event
|
||||
this._onDidStateChange.fire(StateChange.REVERTED);
|
||||
try {
|
||||
await loadPromise;
|
||||
|
||||
// Emit file change event
|
||||
this._onDidStateChange.fire(StateChange.REVERTED);
|
||||
} catch (error) {
|
||||
|
||||
// Set flags back to previous values, we are still dirty if revert failed
|
||||
undo();
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
async load(options?: ILoadOptions): Promise<ITextFileEditorModel> {
|
||||
@@ -591,7 +600,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
save(options: ISaveOptions = Object.create(null)): Promise<void> {
|
||||
if (!this.isResolved()) {
|
||||
return Promise.resolve();
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
this.logService.trace('save() - enter', this.resource);
|
||||
@@ -617,7 +626,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
if (this.saveSequentializer.hasPendingSave(versionId)) {
|
||||
this.logService.trace(`doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource);
|
||||
|
||||
return this.saveSequentializer.pendingSave || Promise.resolve();
|
||||
return this.saveSequentializer.pendingSave || Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// Return early if not dirty (unless forced) or version changed meanwhile
|
||||
@@ -630,7 +639,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
if ((!options.force && !this.dirty) || versionId !== this.versionId) {
|
||||
this.logService.trace(`doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource);
|
||||
|
||||
return Promise.resolve();
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// Return if currently saving by storing this save request as the next save that should happen.
|
||||
@@ -783,7 +792,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
// Check for global settings file
|
||||
if (isEqual(this.resource, this.environmentService.settingsResource, !isLinux)) {
|
||||
if (isEqual(this.resource, URI.file(this.environmentService.appSettingsPath), !isLinux)) {
|
||||
return 'global-settings';
|
||||
}
|
||||
|
||||
|
||||
@@ -443,7 +443,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
|
||||
return this.fileService.del(resource, options);
|
||||
}
|
||||
|
||||
async move(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
|
||||
async move(source: URI, target: URI, overwrite?: boolean): Promise<void> {
|
||||
const waitForPromises: Promise<unknown>[] = [];
|
||||
|
||||
// Event
|
||||
@@ -498,12 +498,10 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
|
||||
|
||||
// Rename to target
|
||||
try {
|
||||
const stat = await this.fileService.move(source, target, overwrite);
|
||||
await this.fileService.move(source, target, overwrite);
|
||||
|
||||
// Load models that were dirty before
|
||||
await Promise.all(dirtyTargetModelUris.map(dirtyTargetModel => this.models.loadOrCreate(dirtyTargetModel)));
|
||||
|
||||
return stat;
|
||||
} catch (error) {
|
||||
|
||||
// In case of an error, discard any dirty target backups that were made
|
||||
|
||||
@@ -125,7 +125,7 @@ export interface ITextFileService extends IDisposable {
|
||||
/**
|
||||
* Move a file. If the file is dirty, its contents will be preserved and restored.
|
||||
*/
|
||||
move(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata>;
|
||||
move(source: URI, target: URI, overwrite?: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Brings up the confirm dialog to either save, don't save or cancel.
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { rimraf, RimRafMode, copy, readFile, exists } from 'vs/base/node/pfs';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { FileService } from 'vs/workbench/services/files/common/fileService';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
@@ -76,7 +76,7 @@ suite('Files - TextFileService i/o', () => {
|
||||
const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'textfileservice');
|
||||
|
||||
let accessor: ServiceAccessor;
|
||||
const disposables = new DisposableStore();
|
||||
let disposables: IDisposable[] = [];
|
||||
let service: ITextFileService;
|
||||
let testDir: string;
|
||||
|
||||
@@ -88,8 +88,8 @@ suite('Files - TextFileService i/o', () => {
|
||||
const fileService = new FileService(logService);
|
||||
|
||||
const fileProvider = new DiskFileSystemProvider(logService);
|
||||
disposables.add(fileService.registerProvider(Schemas.file, fileProvider));
|
||||
disposables.add(fileProvider);
|
||||
disposables.push(fileService.registerProvider(Schemas.file, fileProvider));
|
||||
disposables.push(fileProvider);
|
||||
|
||||
const collection = new ServiceCollection();
|
||||
collection.set(IFileService, fileService);
|
||||
@@ -108,7 +108,7 @@ suite('Files - TextFileService i/o', () => {
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).dispose();
|
||||
accessor.untitledEditorService.revertAll();
|
||||
|
||||
disposables.clear();
|
||||
disposables = dispose(disposables);
|
||||
|
||||
await rimraf(parentDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
@@ -151,10 +151,10 @@ export class TextModelResolverService implements ITextModelService {
|
||||
const cachedModel = this.modelService.getModel(resource);
|
||||
|
||||
if (!cachedModel) {
|
||||
throw new Error('Cant resolve inmemory resource');
|
||||
return Promise.reject(new Error('Cant resolve inmemory resource'));
|
||||
}
|
||||
|
||||
return new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource) as IResolvedTextEditorModel);
|
||||
return Promise.resolve(new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource) as IResolvedTextEditorModel));
|
||||
}
|
||||
|
||||
const ref = this.resourceModelCollection.acquire(resource.toString());
|
||||
|
||||
@@ -45,7 +45,6 @@ const defaultThemeExtensionId = 'sql-theme-carbon';
|
||||
const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults';
|
||||
|
||||
const DEFAULT_ICON_THEME_SETTING_VALUE = 'vs-seti';
|
||||
const DEFAULT_ICON_THEME_ID = 'vscode.vscode-theme-seti-vs-seti';
|
||||
const fileIconsEnabledClass = 'file-icons-enabled';
|
||||
|
||||
const colorThemeRulesClassName = 'contributedColorTheme';
|
||||
@@ -193,10 +192,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
||||
if (!theme) {
|
||||
// current theme is no longer available
|
||||
prevFileIconId = this.currentIconTheme.id;
|
||||
this.setFileIconTheme(DEFAULT_ICON_THEME_ID, 'auto');
|
||||
this.setFileIconTheme(DEFAULT_ICON_THEME_SETTING_VALUE, 'auto');
|
||||
} else {
|
||||
// restore color
|
||||
if (this.currentIconTheme.id === DEFAULT_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) {
|
||||
if (this.currentIconTheme.id === DEFAULT_ICON_THEME_SETTING_VALUE && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) {
|
||||
this.setFileIconTheme(prevFileIconId, 'auto');
|
||||
prevFileIconId = undefined;
|
||||
}
|
||||
@@ -272,7 +271,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
||||
if (devThemes.length) {
|
||||
return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
|
||||
} else {
|
||||
return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined);
|
||||
return this.setFileIconTheme(theme && theme.id || DEFAULT_ICON_THEME_SETTING_VALUE, undefined);
|
||||
}
|
||||
});
|
||||
}),
|
||||
@@ -295,7 +294,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
||||
let iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
|
||||
if (iconThemeSetting !== this.currentIconTheme.settingsId) {
|
||||
this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => {
|
||||
this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined);
|
||||
this.setFileIconTheme(theme && theme.id || DEFAULT_ICON_THEME_SETTING_VALUE, undefined);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -482,7 +481,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
||||
this.doSetFileIconTheme(newIconTheme);
|
||||
|
||||
// remember theme data for a quick restore
|
||||
if (newIconTheme.isLoaded && (!newIconTheme.location || !getRemoteAuthority(newIconTheme.location))) {
|
||||
if (newIconTheme.isLoaded && newIconTheme.location && !getRemoteAuthority(newIconTheme.location)) {
|
||||
this.storageService.store(PERSISTED_ICON_THEME_STORAGE_KEY, newIconTheme.toStorageData(), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ export class ColorThemeData implements IColorTheme {
|
||||
break;
|
||||
case 'themeTokenColors':
|
||||
case 'id': case 'label': case 'settingsId': case 'extensionData': case 'watch':
|
||||
(theme as any)[key] = data[key];
|
||||
theme[key] = data[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ export class FileIconThemeData implements IFileIconTheme {
|
||||
case 'hidesExplorerArrows':
|
||||
case 'hasFolderIcons':
|
||||
case 'watch':
|
||||
(theme as any)[key] = data[key];
|
||||
theme[key] = data[key];
|
||||
break;
|
||||
case 'location':
|
||||
theme.location = URI.revive(data.location);
|
||||
|
||||
@@ -13,7 +13,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
const iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint<IThemeExtensionPoint[]>({
|
||||
extensionPoint: 'iconThemes',
|
||||
@@ -47,16 +46,16 @@ export interface FileIconThemeChangeEvent {
|
||||
added: FileIconThemeData[];
|
||||
}
|
||||
|
||||
export class FileIconThemeStore extends Disposable {
|
||||
export class FileIconThemeStore {
|
||||
|
||||
private knownIconThemes: FileIconThemeData[];
|
||||
private readonly onDidChangeEmitter: Emitter<FileIconThemeChangeEvent>;
|
||||
|
||||
private readonly onDidChangeEmitter = this._register(new Emitter<FileIconThemeChangeEvent>());
|
||||
readonly onDidChange: Event<FileIconThemeChangeEvent> = this.onDidChangeEmitter.event;
|
||||
public get onDidChange(): Event<FileIconThemeChangeEvent> { return this.onDidChangeEmitter.event; }
|
||||
|
||||
constructor(@IExtensionService private readonly extensionService: IExtensionService) {
|
||||
super();
|
||||
this.knownIconThemes = [];
|
||||
this.onDidChangeEmitter = new Emitter<FileIconThemeChangeEvent>();
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
@@ -168,4 +167,5 @@ export class FileIconThemeStore extends Disposable {
|
||||
return this.knownIconThemes;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
@@ -22,12 +23,14 @@ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessi
|
||||
/* __GDPR__FRAGMENT__
|
||||
"IMemoryInfo" : {
|
||||
"workingSetSize" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"peakWorkingSetSize": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"privateBytes": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"sharedBytes": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
export interface IMemoryInfo {
|
||||
readonly workingSetSize: number;
|
||||
readonly peakWorkingSetSize: number;
|
||||
readonly privateBytes: number;
|
||||
readonly sharedBytes: number;
|
||||
}
|
||||
@@ -208,7 +211,7 @@ export interface IStartupMetrics {
|
||||
readonly ellapsedWorkspaceServiceInit: number;
|
||||
|
||||
/**
|
||||
* The time it took to load the main-bundle of the workbench, e.g. `workbench.main.js`.
|
||||
* The time it took to load the main-bundle of the workbench, e.g `workbench.main.js`.
|
||||
*
|
||||
* * Happens in the renderer-process
|
||||
* * Measured with the `willLoadWorkbenchMain` and `didLoadWorkbenchMain` performance marks.
|
||||
@@ -352,13 +355,7 @@ class TimerService implements ITimerService {
|
||||
release = os.release();
|
||||
arch = os.arch();
|
||||
loadavg = os.loadavg();
|
||||
|
||||
const processMemoryInfo = await process.getProcessMemoryInfo();
|
||||
meminfo = {
|
||||
workingSetSize: processMemoryInfo.residentSet,
|
||||
privateBytes: processMemoryInfo.private,
|
||||
sharedBytes: processMemoryInfo.shared
|
||||
};
|
||||
meminfo = process.getProcessMemoryInfo();
|
||||
|
||||
isVMLikelyhood = Math.round((virtualMachineHint.value() * 100));
|
||||
|
||||
@@ -431,19 +428,16 @@ export function didUseCachedData(): boolean {
|
||||
if (!Boolean((<any>global).require.getConfig().nodeCachedData)) {
|
||||
return false;
|
||||
}
|
||||
// There are loader events that signal if cached data was missing, rejected,
|
||||
// or used. The former two mean no cached data.
|
||||
let cachedDataFound = 0;
|
||||
for (const event of require.getStats()) {
|
||||
switch (event.type) {
|
||||
case LoaderEventType.CachedDataRejected:
|
||||
return false;
|
||||
case LoaderEventType.CachedDataFound:
|
||||
cachedDataFound += 1;
|
||||
break;
|
||||
}
|
||||
// whenever cached data is produced or rejected a onNodeCachedData-callback is invoked. That callback
|
||||
// stores data in the `MonacoEnvironment.onNodeCachedData` global. See:
|
||||
// https://github.com/Microsoft/vscode/blob/efe424dfe76a492eab032343e2fa4cfe639939f0/src/vs/workbench/electron-browser/bootstrap/index.js#L299
|
||||
if (isNonEmptyArray(MonacoEnvironment.onNodeCachedData)) {
|
||||
return false;
|
||||
}
|
||||
return cachedDataFound > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
declare type OnNodeCachedDataArgs = [{ errorCode: string, path: string, detail?: string }, { path: string, length: number }];
|
||||
declare const MonacoEnvironment: { onNodeCachedData: OnNodeCachedDataArgs[] };
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { ILocalProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
|
||||
export const IViewletService = createDecorator<IViewletService>('viewletService');
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface IViewletService {
|
||||
/**
|
||||
* Returns the progress indicator for the side bar.
|
||||
*/
|
||||
getProgressIndicator(id: string): ILocalProgressService | null;
|
||||
getProgressIndicator(id: string): IProgressService | null;
|
||||
|
||||
/**
|
||||
* Hide the active viewlet.
|
||||
|
||||
@@ -67,81 +67,74 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
||||
|
||||
}
|
||||
|
||||
private async saveUntitedBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
|
||||
private saveUntitedBeforeShutdown(reason: ShutdownReason): Promise<boolean> | undefined {
|
||||
if (reason !== ShutdownReason.LOAD && reason !== ShutdownReason.CLOSE) {
|
||||
return false; // only interested when window is closing or loading
|
||||
return undefined; // only interested when window is closing or loading
|
||||
}
|
||||
|
||||
const workspaceIdentifier = this.getCurrentWorkspaceIdentifier();
|
||||
if (!workspaceIdentifier || !isEqualOrParent(workspaceIdentifier.configPath, this.environmentService.untitledWorkspacesHome)) {
|
||||
return false; // only care about untitled workspaces to ask for saving
|
||||
return undefined; // only care about untitled workspaces to ask for saving
|
||||
}
|
||||
|
||||
const windowCount = await this.windowsService.getWindowCount();
|
||||
|
||||
if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) {
|
||||
return false; // Windows/Linux: quits when last window is closed, so do not ask then
|
||||
}
|
||||
|
||||
enum ConfirmResult {
|
||||
SAVE,
|
||||
DONT_SAVE,
|
||||
CANCEL
|
||||
}
|
||||
|
||||
const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE };
|
||||
const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE };
|
||||
const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL };
|
||||
|
||||
const buttons: { label: string; result: ConfirmResult; }[] = [];
|
||||
if (isWindows) {
|
||||
buttons.push(save, dontSave, cancel);
|
||||
} else if (isLinux) {
|
||||
buttons.push(dontSave, cancel, save);
|
||||
} else {
|
||||
buttons.push(save, cancel, dontSave);
|
||||
}
|
||||
|
||||
const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?");
|
||||
const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again.");
|
||||
const cancelId = buttons.indexOf(cancel);
|
||||
|
||||
const res = await this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId });
|
||||
|
||||
switch (buttons[res].result) {
|
||||
|
||||
// Cancel: veto unload
|
||||
case ConfirmResult.CANCEL:
|
||||
return true;
|
||||
|
||||
// Don't Save: delete workspace
|
||||
case ConfirmResult.DONT_SAVE:
|
||||
this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier);
|
||||
return false;
|
||||
|
||||
// Save: save workspace, but do not veto unload if path provided
|
||||
case ConfirmResult.SAVE: {
|
||||
const newWorkspacePath = await this.pickNewWorkspacePath();
|
||||
if (!newWorkspacePath) {
|
||||
return true; // keep veto if no target was provided
|
||||
}
|
||||
|
||||
try {
|
||||
await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath);
|
||||
|
||||
const newWorkspaceIdentifier = await this.workspaceService.getWorkspaceIdentifier(newWorkspacePath);
|
||||
|
||||
const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true });
|
||||
this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]);
|
||||
|
||||
this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier);
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false;
|
||||
return this.windowsService.getWindowCount().then(windowCount => {
|
||||
if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) {
|
||||
return false; // Windows/Linux: quits when last window is closed, so do not ask then
|
||||
}
|
||||
}
|
||||
enum ConfirmResult {
|
||||
SAVE,
|
||||
DONT_SAVE,
|
||||
CANCEL
|
||||
}
|
||||
|
||||
const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE };
|
||||
const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE };
|
||||
const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL };
|
||||
|
||||
const buttons: { label: string; result: ConfirmResult; }[] = [];
|
||||
if (isWindows) {
|
||||
buttons.push(save, dontSave, cancel);
|
||||
} else if (isLinux) {
|
||||
buttons.push(dontSave, cancel, save);
|
||||
} else {
|
||||
buttons.push(save, cancel, dontSave);
|
||||
}
|
||||
|
||||
const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?");
|
||||
const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again.");
|
||||
const cancelId = buttons.indexOf(cancel);
|
||||
|
||||
return this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }).then(res => {
|
||||
switch (buttons[res].result) {
|
||||
|
||||
// Cancel: veto unload
|
||||
case ConfirmResult.CANCEL:
|
||||
return true;
|
||||
|
||||
// Don't Save: delete workspace
|
||||
case ConfirmResult.DONT_SAVE:
|
||||
this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier);
|
||||
return false;
|
||||
|
||||
// Save: save workspace, but do not veto unload
|
||||
case ConfirmResult.SAVE: {
|
||||
return this.pickNewWorkspacePath().then(newWorkspacePath => {
|
||||
if (newWorkspacePath) {
|
||||
return this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath).then(_ => {
|
||||
return this.workspaceService.getWorkspaceIdentifier(newWorkspacePath).then(newWorkspaceIdentifier => {
|
||||
const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true });
|
||||
this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]);
|
||||
this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier);
|
||||
return false;
|
||||
});
|
||||
}, () => false);
|
||||
}
|
||||
return true; // keep veto if no target was provided
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pickNewWorkspacePath(): Promise<URI | undefined> {
|
||||
@@ -225,7 +218,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
||||
newWorkspaceFolders = distinct(newWorkspaceFolders, folder => getComparisonKey(folder.uri));
|
||||
|
||||
if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) {
|
||||
return; // return if the operation is a no-op for the current state
|
||||
return Promise.resolve(); // return if the operation is a no-op for the current state
|
||||
}
|
||||
|
||||
return this.createAndEnterWorkspace(newWorkspaceFolders);
|
||||
@@ -274,7 +267,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
||||
|
||||
async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise<void> {
|
||||
if (path && !await this.isValidTargetWorkspacePath(path)) {
|
||||
return;
|
||||
return Promise.reject(null);
|
||||
}
|
||||
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
|
||||
const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority);
|
||||
@@ -288,11 +281,11 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
||||
|
||||
async saveAndEnterWorkspace(path: URI): Promise<void> {
|
||||
if (!await this.isValidTargetWorkspacePath(path)) {
|
||||
return;
|
||||
return Promise.reject(null);
|
||||
}
|
||||
const workspaceIdentifier = this.getCurrentWorkspaceIdentifier();
|
||||
if (!workspaceIdentifier) {
|
||||
return;
|
||||
return Promise.reject(null);
|
||||
}
|
||||
await this.saveWorkspaceAs(workspaceIdentifier, path);
|
||||
|
||||
@@ -325,7 +318,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
||||
|
||||
// Return early if target is same as source
|
||||
if (isEqual(configPathURI, targetConfigPathURI)) {
|
||||
return;
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// Read the contents of the workspace file, update it to new location and save it.
|
||||
@@ -334,17 +327,18 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
||||
await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true });
|
||||
}
|
||||
|
||||
private handleWorkspaceConfigurationEditingError(error: JSONEditingError): void {
|
||||
private handleWorkspaceConfigurationEditingError(error: JSONEditingError): Promise<void> {
|
||||
switch (error.code) {
|
||||
case JSONEditingErrorCode.ERROR_INVALID_FILE:
|
||||
this.onInvalidWorkspaceConfigurationFileError();
|
||||
break;
|
||||
return Promise.resolve();
|
||||
case JSONEditingErrorCode.ERROR_FILE_DIRTY:
|
||||
this.onWorkspaceConfigurationFileDirtyError();
|
||||
break;
|
||||
default:
|
||||
this.notificationService.error(error.message);
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.notificationService.error(error.message);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private onInvalidWorkspaceConfigurationFileError(): void {
|
||||
@@ -368,7 +362,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
||||
|
||||
async enterWorkspace(path: URI): Promise<void> {
|
||||
if (!!this.environmentService.extensionTestsLocationURI) {
|
||||
throw new Error('Entering a new workspace is not possible in tests.');
|
||||
return Promise.reject(new Error('Entering a new workspace is not possible in tests.'));
|
||||
}
|
||||
|
||||
const workspace = await this.workspaceService.getWorkspaceIdentifier(path);
|
||||
|
||||
Reference in New Issue
Block a user