Revert "Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d (#5949)" (#5983)

This reverts commit d15a3fcc98.
This commit is contained in:
Karl Burtram
2019-06-11 12:35:58 -07:00
committed by GitHub
parent 95a50b7892
commit 5a7562a37b
926 changed files with 11394 additions and 19540 deletions

View File

@@ -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();

View 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 {
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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> {
}
}

View File

@@ -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;

View File

@@ -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> {

View File

@@ -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) => {

View File

@@ -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" }');

View File

@@ -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));

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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;
});
}

View File

@@ -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);
});
}
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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));
}
}

View File

@@ -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) };

View File

@@ -12,7 +12,6 @@ export interface IExtHostReadyMessage {
export interface IExtHostSocketMessage {
type: 'VSCODE_EXTHOST_IPC_SOCKET';
initialDataChunk: string;
skipWebSocketFrames: boolean;
}
export const enum MessageType {

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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: {

View File

@@ -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> {

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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));

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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));

View File

@@ -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[]>;

View File

@@ -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'));

View File

@@ -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);

View File

@@ -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', () => { });

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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();
}
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}
}

View 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);

View File

@@ -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';

View File

@@ -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();

View File

@@ -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]
};

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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';
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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);
});

View File

@@ -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());

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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;
});
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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);