Merge VS Code 1.31.1 (#4283)

This commit is contained in:
Matt Irvine
2019-03-15 13:09:45 -07:00
committed by GitHub
parent 7d31575149
commit 86bac90001
1716 changed files with 53308 additions and 48375 deletions

View File

@@ -1,375 +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 { localize } from 'vs/nls';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import * as resources from 'vs/base/common/resources';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { forEach } from 'vs/base/common/collections';
import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { MenuId, MenuRegistry, ILocalizedString, IMenuItem } from 'vs/platform/actions/common/actions';
import { URI } from 'vs/base/common/uri';
namespace schema {
// --- menus contribution point
export interface IUserFriendlyMenuItem {
command: string;
alt?: string;
when?: string;
group?: string;
}
export function parseMenuId(value: string): MenuId | undefined {
switch (value) {
case 'commandPalette': return MenuId.CommandPalette;
case 'touchBar': return MenuId.TouchBarContext;
case 'editor/title': return MenuId.EditorTitle;
case 'editor/context': return MenuId.EditorContext;
case 'explorer/context': return MenuId.ExplorerContext;
case 'editor/title/context': return MenuId.EditorTitleContext;
case 'debug/callstack/context': return MenuId.DebugCallStackContext;
case 'scm/title': return MenuId.SCMTitle;
case 'scm/sourceControl': return MenuId.SCMSourceControl;
case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext;
case 'scm/resourceState/context': return MenuId.SCMResourceContext;
case 'scm/change/title': return MenuId.SCMChangeContext;
case 'view/title': return MenuId.ViewTitle;
case 'view/item/context': return MenuId.ViewItemContext;
// {{SQL CARBON EDIT}}
case 'objectExplorer/item/context': return MenuId.ObjectExplorerItemContext;
case 'notebook/toolbar': return MenuId.NotebookToolbar;
case 'dataExplorer/context': return MenuId.DataExplorerContext;
}
return void 0;
}
export function isValidMenuItems(menu: IUserFriendlyMenuItem[], collector: ExtensionMessageCollector): boolean {
if (!Array.isArray(menu)) {
collector.error(localize('requirearray', "menu items must be an array"));
return false;
}
for (let item of menu) {
if (typeof item.command !== 'string') {
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
return false;
}
if (item.alt && typeof item.alt !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt'));
return false;
}
if (item.when && typeof item.when !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
}
if (item.group && typeof item.group !== 'string') {
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'group'));
return false;
}
}
return true;
}
const menuItem: IJSONSchema = {
type: 'object',
properties: {
command: {
description: localize('vscode.extension.contributes.menuItem.command', 'Identifier of the command to execute. The command must be declared in the \'commands\'-section'),
type: 'string'
},
alt: {
description: localize('vscode.extension.contributes.menuItem.alt', 'Identifier of an alternative command to execute. The command must be declared in the \'commands\'-section'),
type: 'string'
},
when: {
description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'),
type: 'string'
},
group: {
description: localize('vscode.extension.contributes.menuItem.group', 'Group into which this command belongs'),
type: 'string'
}
}
};
export const menusContribtion: IJSONSchema = {
description: localize('vscode.extension.contributes.menus', "Contributes menu items to the editor"),
type: 'object',
properties: {
'commandPalette': {
description: localize('menus.commandPalette', "The Command Palette"),
type: 'array',
items: menuItem
},
'touchBar': {
description: localize('menus.touchBar', "The touch bar (macOS only)"),
type: 'array',
items: menuItem
},
'editor/title': {
description: localize('menus.editorTitle', "The editor title menu"),
type: 'array',
items: menuItem
},
'editor/context': {
description: localize('menus.editorContext', "The editor context menu"),
type: 'array',
items: menuItem
},
'explorer/context': {
description: localize('menus.explorerContext', "The file explorer context menu"),
type: 'array',
items: menuItem
},
'editor/title/context': {
description: localize('menus.editorTabContext', "The editor tabs context menu"),
type: 'array',
items: menuItem
},
'debug/callstack/context': {
description: localize('menus.debugCallstackContext', "The debug callstack context menu"),
type: 'array',
items: menuItem
},
'scm/title': {
description: localize('menus.scmTitle', "The Source Control title menu"),
type: 'array',
items: menuItem
},
'scm/sourceControl': {
description: localize('menus.scmSourceControl', "The Source Control menu"),
type: 'array',
items: menuItem
},
'scm/resourceGroup/context': {
description: localize('menus.resourceGroupContext', "The Source Control resource group context menu"),
type: 'array',
items: menuItem
},
'scm/resourceState/context': {
description: localize('menus.resourceStateContext', "The Source Control resource state context menu"),
type: 'array',
items: menuItem
},
'view/title': {
description: localize('view.viewTitle', "The contributed view title menu"),
type: 'array',
items: menuItem
},
'view/item/context': {
description: localize('view.itemContext', "The contributed view item context menu"),
type: 'array',
items: menuItem
}
}
};
// --- commands contribution point
export interface IUserFriendlyCommand {
command: string;
title: string | ILocalizedString;
category?: string | ILocalizedString;
icon?: IUserFriendlyIcon;
}
export type IUserFriendlyIcon = string | { light: string; dark: string; };
export function isValidCommand(command: IUserFriendlyCommand, collector: ExtensionMessageCollector): boolean {
if (!command) {
collector.error(localize('nonempty', "expected non-empty value."));
return false;
}
if (isFalsyOrWhitespace(command.command)) {
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
return false;
}
if (!isValidLocalizedString(command.title, collector, 'title')) {
return false;
}
if (command.category && !isValidLocalizedString(command.category, collector, 'category')) {
return false;
}
if (!isValidIcon(command.icon, collector)) {
return false;
}
return true;
}
function isValidIcon(icon: IUserFriendlyIcon | undefined, collector: ExtensionMessageCollector): boolean {
if (typeof icon === 'undefined') {
return true;
}
if (typeof icon === 'string') {
return true;
} else if (typeof icon.dark === 'string' && typeof icon.light === 'string') {
return true;
}
collector.error(localize('opticon', "property `icon` can be omitted or must be either a string or a literal like `{dark, light}`"));
return false;
}
function isValidLocalizedString(localized: string | ILocalizedString, collector: ExtensionMessageCollector, propertyName: string): boolean {
if (typeof localized === 'undefined') {
collector.error(localize('requireStringOrObject', "property `{0}` is mandatory and must be of type `string` or `object`", propertyName));
return false;
} else if (typeof localized === 'string' && isFalsyOrWhitespace(localized)) {
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", propertyName));
return false;
} else if (typeof localized !== 'string' && (isFalsyOrWhitespace(localized.original) || isFalsyOrWhitespace(localized.value))) {
collector.error(localize('requirestrings', "properties `{0}` and `{1}` are mandatory and must be of type `string`", `${propertyName}.value`, `${propertyName}.original`));
return false;
}
return true;
}
const commandType: IJSONSchema = {
type: 'object',
properties: {
command: {
description: localize('vscode.extension.contributes.commandType.command', 'Identifier of the command to execute'),
type: 'string'
},
title: {
description: localize('vscode.extension.contributes.commandType.title', 'Title by which the command is represented in the UI'),
type: 'string'
},
category: {
description: localize('vscode.extension.contributes.commandType.category', '(Optional) Category string by the command is grouped in the UI'),
type: 'string'
},
icon: {
description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path or a themable configuration'),
anyOf: [{
type: 'string'
},
{
type: 'object',
properties: {
light: {
description: localize('vscode.extension.contributes.commandType.icon.light', 'Icon path when a light theme is used'),
type: 'string'
},
dark: {
description: localize('vscode.extension.contributes.commandType.icon.dark', 'Icon path when a dark theme is used'),
type: 'string'
}
}
}]
}
}
};
export const commandsContribution: IJSONSchema = {
description: localize('vscode.extension.contributes.commands', "Contributes commands to the command palette."),
oneOf: [
commandType,
{
type: 'array',
items: commandType
}
]
};
}
ExtensionsRegistry.registerExtensionPoint<schema.IUserFriendlyCommand | schema.IUserFriendlyCommand[]>('commands', [], schema.commandsContribution).setHandler(extensions => {
function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser<any>) {
if (!schema.isValidCommand(userFriendlyCommand, extension.collector)) {
return;
}
const { icon, category, title, command } = userFriendlyCommand;
let absoluteIcon: { dark: URI; light?: URI; } | undefined;
if (icon) {
if (typeof icon === 'string') {
absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon) };
} else {
absoluteIcon = {
dark: resources.joinPath(extension.description.extensionLocation, icon.dark),
light: resources.joinPath(extension.description.extensionLocation, icon.light)
};
}
}
if (MenuRegistry.addCommand({ id: command, title, category, iconLocation: absoluteIcon })) {
extension.collector.info(localize('dup', "Command `{0}` appears multiple times in the `commands` section.", userFriendlyCommand.command));
}
}
for (let extension of extensions) {
const { value } = extension;
if (Array.isArray<schema.IUserFriendlyCommand>(value)) {
for (let command of value) {
handleCommand(command, extension);
}
} else {
handleCommand(value, extension);
}
}
});
ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyMenuItem[] }>('menus', [], schema.menusContribtion).setHandler(extensions => {
for (let extension of extensions) {
const { value, collector } = extension;
forEach(value, entry => {
if (!schema.isValidMenuItems(entry.value, collector)) {
return;
}
const menu = schema.parseMenuId(entry.key);
if (typeof menu !== 'number') {
collector.warn(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry.key));
return;
}
for (let item of entry.value) {
let command = MenuRegistry.getCommand(item.command);
let alt = item.alt && MenuRegistry.getCommand(item.alt);
if (!command) {
collector.error(localize('missing.command', "Menu item references a command `{0}` which is not defined in the 'commands' section.", item.command));
continue;
}
if (item.alt && !alt) {
collector.warn(localize('missing.altCommand', "Menu item references an alt-command `{0}` which is not defined in the 'commands' section.", item.alt));
}
if (item.command === item.alt) {
collector.info(localize('dupe.command', "Menu item references the same command as default and alt-command"));
}
let group: string | undefined;
let order: number | undefined;
if (item.group) {
const idx = item.group.lastIndexOf('@');
if (idx > 0) {
group = item.group.substr(0, idx);
order = Number(item.group.substr(idx + 1)) || undefined;
} else {
group = item.group;
}
}
MenuRegistry.appendMenuItem(menu, {
command,
alt,
group,
order,
when: ContextKeyExpr.deserialize(item.when)
} as IMenuItem);
}
});
}
});

View File

@@ -16,7 +16,7 @@ export class ActivityService implements IActivityService {
constructor(
private activitybarPart: ActivitybarPart,
private panelPart: PanelPart,
@IPanelService private panelService: IPanelService
@IPanelService private readonly panelService: IPanelService
) { }
showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {

View File

@@ -5,7 +5,6 @@
import { URI as Uri } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
import { IResolveContentOptions, IUpdateContentOptions, ITextSnapshot } from 'vs/platform/files/common/files';
import { ITextBufferFactory } from 'vs/editor/common/model';
@@ -23,7 +22,7 @@ export interface IBackupFileService {
/**
* Finds out if there are any backups stored.
*/
hasBackups(): TPromise<boolean>;
hasBackups(): Promise<boolean>;
/**
* Loads the backup resource for a particular resource within the current workspace.
@@ -31,7 +30,7 @@ export interface IBackupFileService {
* @param resource The resource that is backed up.
* @return The backup resource if any.
*/
loadBackupResource(resource: Uri): TPromise<Uri | undefined>;
loadBackupResource(resource: Uri): Promise<Uri | undefined>;
/**
* Given a resource, returns the associated backup resource.
@@ -48,14 +47,14 @@ export interface IBackupFileService {
* @param content The content of the resource as snapshot.
* @param versionId The version id of the resource to backup.
*/
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): TPromise<void>;
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise<void>;
/**
* Gets a list of file backups for the current workspace.
*
* @return The list of backups.
*/
getWorkspaceFileBackups(): TPromise<Uri[]>;
getWorkspaceFileBackups(): Promise<Uri[]>;
/**
* Resolves the backup for the given resource.
@@ -63,18 +62,18 @@ export interface IBackupFileService {
* @param value The contents from a backup resource as stream.
* @return The backup file's backed up content as text buffer factory.
*/
resolveBackupContent(backup: Uri): TPromise<ITextBufferFactory | undefined>;
resolveBackupContent(backup: Uri): Promise<ITextBufferFactory | undefined>;
/**
* Discards the backup associated with a resource if it exists..
*
* @param resource The resource whose backup is being discarded discard to back up.
*/
discardResourceBackup(resource: Uri): TPromise<void>;
discardResourceBackup(resource: Uri): Promise<void>;
/**
* Discards all backups associated with the current workspace and prevents further backups from
* being made.
*/
discardAllWorkspaceBackups(): TPromise<void>;
discardAllWorkspaceBackups(): Promise<void>;
}

View File

@@ -10,14 +10,13 @@ import { URI as Uri } from 'vs/base/common/uri';
import { ResourceQueue } from 'vs/base/common/async';
import { IBackupFileService, BACKUP_FILE_UPDATE_OPTIONS, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
import { IFileService, ITextSnapshot } from 'vs/platform/files/common/files';
import { TPromise } from 'vs/base/common/winjs.base';
import { readToMatchingString } from 'vs/base/node/stream';
import { ITextBufferFactory } from 'vs/editor/common/model';
import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
import { keys } from 'vs/base/common/map';
export interface IBackupFilesModel {
resolve(backupRoot: string): TPromise<IBackupFilesModel>;
resolve(backupRoot: string): Promise<IBackupFilesModel>;
add(resource: Uri, versionId?: number): void;
has(resource: Uri, versionId?: number): boolean;
@@ -51,11 +50,11 @@ export class BackupSnapshot implements ITextSnapshot {
export class BackupFilesModel implements IBackupFilesModel {
private cache: { [resource: string]: number /* version ID */ } = Object.create(null);
resolve(backupRoot: string): TPromise<IBackupFilesModel> {
resolve(backupRoot: string): Promise<IBackupFilesModel> {
return pfs.readDirsInDir(backupRoot).then(backupSchemas => {
// For all supported schemas
return TPromise.join(backupSchemas.map(backupSchema => {
return Promise.all(backupSchemas.map(backupSchema => {
// Read backup directory for backups
const backupSchemaPath = path.join(backupRoot, backupSchema);
@@ -114,12 +113,12 @@ export class BackupFileService implements IBackupFileService {
private backupWorkspacePath: string;
private isShuttingDown: boolean;
private ready: TPromise<IBackupFilesModel>;
private ready: Promise<IBackupFilesModel>;
private ioOperationQueues: ResourceQueue; // queue IO operations to ensure write order
constructor(
backupWorkspacePath: string,
@IFileService private fileService: IFileService
@IFileService private readonly fileService: IFileService
) {
this.isShuttingDown = false;
this.ioOperationQueues = new ResourceQueue();
@@ -133,19 +132,19 @@ export class BackupFileService implements IBackupFileService {
this.ready = this.init();
}
private init(): TPromise<IBackupFilesModel> {
private init(): Promise<IBackupFilesModel> {
const model = new BackupFilesModel();
return model.resolve(this.backupWorkspacePath);
}
hasBackups(): TPromise<boolean> {
hasBackups(): Promise<boolean> {
return this.ready.then(model => {
return model.count() > 0;
});
}
loadBackupResource(resource: Uri): TPromise<Uri | undefined> {
loadBackupResource(resource: Uri): Promise<Uri | undefined> {
return this.ready.then(model => {
// Return directly if we have a known backup with that resource
@@ -154,19 +153,19 @@ export class BackupFileService implements IBackupFileService {
return backupResource;
}
return void 0;
return undefined;
});
}
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): TPromise<void> {
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise<void> {
if (this.isShuttingDown) {
return TPromise.as(void 0);
return Promise.resolve();
}
return this.ready.then(model => {
const backupResource = this.toBackupResource(resource);
if (model.has(backupResource, versionId)) {
return void 0; // return early if backup version id matches requested one
return undefined; // return early if backup version id matches requested one
}
return this.ioOperationQueues.queueFor(backupResource).queue(() => {
@@ -178,7 +177,7 @@ export class BackupFileService implements IBackupFileService {
});
}
discardResourceBackup(resource: Uri): TPromise<void> {
discardResourceBackup(resource: Uri): Promise<void> {
return this.ready.then(model => {
const backupResource = this.toBackupResource(resource);
@@ -188,7 +187,7 @@ export class BackupFileService implements IBackupFileService {
});
}
discardAllWorkspaceBackups(): TPromise<void> {
discardAllWorkspaceBackups(): Promise<void> {
this.isShuttingDown = true;
return this.ready.then(model => {
@@ -196,9 +195,9 @@ export class BackupFileService implements IBackupFileService {
});
}
getWorkspaceFileBackups(): TPromise<Uri[]> {
getWorkspaceFileBackups(): Promise<Uri[]> {
return this.ready.then(model => {
const readPromises: TPromise<Uri>[] = [];
const readPromises: Promise<Uri>[] = [];
model.get().forEach(fileBackup => {
readPromises.push(
@@ -206,11 +205,11 @@ export class BackupFileService implements IBackupFileService {
);
});
return TPromise.join(readPromises);
return Promise.all(readPromises);
});
}
resolveBackupContent(backup: Uri): TPromise<ITextBufferFactory> {
resolveBackupContent(backup: Uri): Promise<ITextBufferFactory> {
return this.fileService.resolveStreamContent(backup, BACKUP_FILE_RESOLVE_OPTIONS).then(content => {
// Add a filter method to filter out everything until the meta marker
@@ -248,49 +247,49 @@ export class InMemoryBackupFileService implements IBackupFileService {
private backups: Map<string, ITextSnapshot> = new Map();
hasBackups(): TPromise<boolean> {
return TPromise.as(this.backups.size > 0);
hasBackups(): Promise<boolean> {
return Promise.resolve(this.backups.size > 0);
}
loadBackupResource(resource: Uri): TPromise<Uri | undefined> {
loadBackupResource(resource: Uri): Promise<Uri | undefined> {
const backupResource = this.toBackupResource(resource);
if (this.backups.has(backupResource.toString())) {
return TPromise.as(backupResource);
return Promise.resolve(backupResource);
}
return TPromise.as(void 0);
return Promise.resolve();
}
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): TPromise<void> {
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise<void> {
const backupResource = this.toBackupResource(resource);
this.backups.set(backupResource.toString(), content);
return TPromise.as(void 0);
return Promise.resolve();
}
resolveBackupContent(backupResource: Uri): TPromise<ITextBufferFactory | undefined> {
resolveBackupContent(backupResource: Uri): Promise<ITextBufferFactory | undefined> {
const snapshot = this.backups.get(backupResource.toString());
if (snapshot) {
return TPromise.as(createTextBufferFactoryFromSnapshot(snapshot));
return Promise.resolve(createTextBufferFactoryFromSnapshot(snapshot));
}
return TPromise.as(void 0);
return Promise.resolve();
}
getWorkspaceFileBackups(): TPromise<Uri[]> {
return TPromise.as(keys(this.backups).map(key => Uri.parse(key)));
getWorkspaceFileBackups(): Promise<Uri[]> {
return Promise.resolve(keys(this.backups).map(key => Uri.parse(key)));
}
discardResourceBackup(resource: Uri): TPromise<void> {
discardResourceBackup(resource: Uri): Promise<void> {
this.backups.delete(this.toBackupResource(resource).toString());
return TPromise.as(void 0);
return Promise.resolve();
}
discardAllWorkspaceBackups(): TPromise<void> {
discardAllWorkspaceBackups(): Promise<void> {
this.backups.clear();
return TPromise.as(void 0);
return Promise.resolve();
}
toBackupResource(resource: Uri): Uri {

View File

@@ -92,7 +92,7 @@ suite('BackupFileService', () => {
service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath);
return service.loadBackupResource(fooFile).then(resource => {
assert.ok(resource);
assert.equal(path.basename(resource.fsPath), path.basename(fooBackupPath));
assert.equal(path.basename(resource!.fsPath), path.basename(fooBackupPath));
return service.hasBackups().then(hasBackups => {
assert.ok(hasBackups);
});

View File

@@ -69,16 +69,23 @@ class ModelEditTask implements IDisposable {
// honor eol-change
this._newEol = edit.eol;
}
if (edit.range || edit.text) {
// create edit operation
let range: Range;
if (!edit.range) {
range = this._model.getFullModelRange();
} else {
range = Range.lift(edit.range);
}
this._edits.push(EditOperation.replaceMove(range, edit.text));
if (!edit.range && !edit.text) {
// lacks both a range and the text
continue;
}
if (Range.isEmpty(edit.range) && !edit.text) {
// no-op edit (replace empty range with empty text)
continue;
}
// create edit operation
let range: Range;
if (!edit.range) {
range = this._model.getFullModelRange();
} else {
range = Range.lift(edit.range);
}
this._edits.push(EditOperation.replaceMove(range, edit.text));
}
}
@@ -121,9 +128,11 @@ class EditorEditTask extends ModelEditTask {
this._editor.pushUndoStop();
}
if (this._newEol !== undefined) {
this._editor.pushUndoStop();
this._editor.getModel().pushEOL(this._newEol);
this._editor.pushUndoStop();
if (this._editor.hasModel()) {
this._editor.pushUndoStop();
this._editor.getModel().pushEOL(this._newEol);
this._editor.pushUndoStop();
}
}
}
}
@@ -132,13 +141,13 @@ class BulkEditModel implements IDisposable {
private _textModelResolverService: ITextModelService;
private _edits = new Map<string, ResourceTextEdit[]>();
private _editor: ICodeEditor;
private _editor: ICodeEditor | undefined;
private _tasks: ModelEditTask[];
private _progress: IProgress<void>;
constructor(
textModelResolverService: ITextModelService,
editor: ICodeEditor,
editor: ICodeEditor | undefined,
edits: ResourceTextEdit[],
progress: IProgress<void>
) {
@@ -169,7 +178,7 @@ class BulkEditModel implements IDisposable {
}
this._tasks = [];
const promises: Thenable<any>[] = [];
const promises: Promise<any>[] = [];
this._edits.forEach((value, key) => {
const promise = this._textModelResolverService.createModelReference(URI.parse(key)).then(ref => {
@@ -180,7 +189,7 @@ class BulkEditModel implements IDisposable {
}
let task: ModelEditTask;
if (this._editor && this._editor.getModel().uri.toString() === model.textEditorModel.uri.toString()) {
if (this._editor && this._editor.hasModel() && this._editor.getModel().uri.toString() === model.textEditorModel.uri.toString()) {
task = new EditorEditTask(ref, this._editor);
} else {
task = new ModelEditTask(ref);
@@ -221,12 +230,12 @@ export type Edit = ResourceFileEdit | ResourceTextEdit;
export class BulkEdit {
private _edits: Edit[] = [];
private _editor: ICodeEditor;
private _progress: IProgressRunner;
private _editor: ICodeEditor | undefined;
private _progress?: IProgressRunner;
constructor(
editor: ICodeEditor,
progress: IProgressRunner,
editor: ICodeEditor | undefined,
progress: IProgressRunner | undefined,
@ILogService private readonly _logService: ILogService,
@ITextModelService private readonly _textModelService: ITextModelService,
@IFileService private readonly _fileService: IFileService,
@@ -264,7 +273,7 @@ export class BulkEdit {
let total = 0;
const groups: Edit[][] = [];
let group: Edit[];
let group: Edit[] | undefined;
for (const edit of this._edits) {
if (!group
|| (isResourceFileEdit(group[0]) && !isResourceFileEdit(edit))
@@ -285,8 +294,10 @@ export class BulkEdit {
// define total work and progress callback
// for child operations
this._progress.total(total);
let progress: IProgress<void> = { report: _ => this._progress.worked(1) };
if (this._progress) {
this._progress.total(total);
}
let progress: IProgress<void> = { report: _ => this._progress && this._progress.worked(1) };
// do it.
for (const group of groups) {
@@ -380,8 +391,7 @@ export class BulkEditService implements IBulkEditService {
let codeEditor = options.editor;
// First check if loaded models were not changed in the meantime
for (let i = 0, len = edits.length; i < len; i++) {
const edit = edits[i];
for (const edit of edits) {
if (!isResourceFileEdit(edit) && typeof edit.modelVersionId === 'number') {
let model = this._modelService.getModel(edit.resource);
if (model && model.getVersionId() !== edit.modelVersionId) {
@@ -400,7 +410,7 @@ export class BulkEditService implements IBulkEditService {
}
}
const bulkEdit = new BulkEdit(options.editor, options.progress, this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService);
const bulkEdit = new BulkEdit(codeEditor, options.progress, this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService);
bulkEdit.add(edits);
return bulkEdit.perform().then(() => {

View File

@@ -14,13 +14,13 @@ import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/
export class CodeEditorService extends CodeEditorServiceImpl {
constructor(
@IEditorService private editorService: IEditorService,
@IEditorService private readonly editorService: IEditorService,
@IThemeService themeService: IThemeService
) {
super(themeService);
}
getActiveCodeEditor(): ICodeEditor {
getActiveCodeEditor(): ICodeEditor | null {
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
if (isCodeEditor(activeTextEditorWidget)) {
return activeTextEditorWidget;
@@ -33,7 +33,7 @@ export class CodeEditorService extends CodeEditorServiceImpl {
return null;
}
openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Thenable<ICodeEditor> {
openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
// Special case: If the active editor is a diff editor and the request to open originates and
// targets the modified side of it, we just apply the request there to prevent opening the modified
@@ -46,7 +46,7 @@ export class CodeEditorService extends CodeEditorServiceImpl {
input.resource && // we need a request resource to compare with
activeTextEditorWidget.getModel() && // we need a target model to compare with
source === activeTextEditorWidget.getModifiedEditor() && // we need the source of this request to be the modified side of the diff editor
input.resource.toString() === activeTextEditorWidget.getModel().modified.uri.toString() // we need the input resources to match with modified side
input.resource.toString() === activeTextEditorWidget.getModel()!.modified.uri.toString() // we need the input resources to match with modified side
) {
const targetEditor = activeTextEditorWidget.getModifiedEditor();
@@ -60,7 +60,7 @@ export class CodeEditorService extends CodeEditorServiceImpl {
return this.doOpenCodeEditor(input, source, sideBySide);
}
private doOpenCodeEditor(input: IResourceInput, source: ICodeEditor, sideBySide?: boolean): Thenable<ICodeEditor> {
private doOpenCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
return this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(control => {
if (control) {
const widget = control.getControl();

View File

@@ -6,7 +6,7 @@
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService, ICommandEvent, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Event, Emitter, filterEvent, toPromise } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
@@ -35,7 +35,7 @@ export class CommandService extends Disposable implements ICommandService {
// we don't wait for it when the extension
// host didn't yet start and the command is already registered
const activation: Thenable<any> = this._extensionService.activateByEvent(`onCommand:${id}`);
const activation: Promise<any> = this._extensionService.activateByEvent(`onCommand:${id}`);
const commandIsRegistered = !!CommandsRegistry.getCommand(id);
if (!this._extensionHostIsReady && commandIsRegistered) {
@@ -46,7 +46,7 @@ export class CommandService extends Disposable implements ICommandService {
waitFor = Promise.race<any>([
// race activation events against command registration
Promise.all([activation, this._extensionService.activateByEvent(`*`)]),
toPromise(filterEvent(CommandsRegistry.onDidRegisterCommand, e => e === id)),
Event.toPromise(Event.filter(CommandsRegistry.onDidRegisterCommand, e => e === id)),
]);
}
return (waitFor as Promise<any>).then(_ => this._tryExecuteCommand(id, args));
@@ -60,7 +60,7 @@ export class CommandService extends Disposable implements ICommandService {
}
try {
this._onWillExecuteCommand.fire({ commandId: id });
const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler].concat(args));
const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler, ...args]);
return Promise.resolve(result);
} catch (err) {
return Promise.reject(err);

View File

@@ -18,9 +18,10 @@ class SimpleExtensionService implements IExtensionService {
get onDidRegisterExtensions(): Event<void> {
return this._onDidRegisterExtensions.event;
}
onDidChangeExtensionsStatus = null;
onWillActivateByEvent = null;
onDidChangeResponsiveChange = null;
onDidChangeExtensionsStatus = null!;
onDidChangeExtensions = null!;
onWillActivateByEvent = null!;
onDidChangeResponsiveChange = null!;
activateByEvent(activationEvent: string): Promise<void> {
return this.whenInstalledExtensionsRegistered().then(() => { });
}
@@ -31,7 +32,7 @@ class SimpleExtensionService implements IExtensionService {
return Promise.resolve([]);
}
getExtensionsStatus() {
return undefined;
return undefined!;
}
getExtensions(): Promise<IExtensionDescription[]> {
return Promise.resolve([]);
@@ -54,6 +55,8 @@ class SimpleExtensionService implements IExtensionService {
}
stopExtensionHost(): void {
}
canAddExtension(): boolean { return false; }
canRemoveExtension(): boolean { return false; }
}
suite('CommandService', function () {
@@ -138,7 +141,7 @@ suite('CommandService', function () {
assert.equal(callCounter, 0);
let reg = CommandsRegistry.registerCommand('bar', () => callCounter += 1);
resolveFunc(true);
resolveFunc!(true);
return r.then(() => {
reg.dispose();

View File

@@ -1,260 +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 objects from 'vs/base/common/objects';
import { Registry } from 'vs/platform/registry/common/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { workspaceSettingsSchemaId, launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { isObject } from 'vs/base/common/types';
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
const configurationEntrySchema: IJSONSchema = {
type: 'object',
defaultSnippets: [{ body: { title: '', properties: {} } }],
properties: {
title: {
description: nls.localize('vscode.extension.contributes.configuration.title', 'A summary of the settings. This label will be used in the settings file as separating comment.'),
type: 'string'
},
properties: {
description: nls.localize('vscode.extension.contributes.configuration.properties', 'Description of the configuration properties.'),
type: 'object',
additionalProperties: {
anyOf: [
{ $ref: 'http://json-schema.org/draft-07/schema#' },
{
type: 'object',
properties: {
isExecutable: {
type: 'boolean',
deprecationMessage: 'This property is deprecated. Instead use `scope` property and set it to `application` value.'
},
scope: {
type: 'string',
enum: ['application', 'window', 'resource'],
default: 'window',
enumDescriptions: [
nls.localize('scope.application.description', "Application specific configuration, which can be configured only in User settings."),
nls.localize('scope.window.description', "Window specific configuration, which can be configured in the User or Workspace settings."),
nls.localize('scope.resource.description', "Resource specific configuration, which can be configured in the User, Workspace or Folder settings.")
],
description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `window` and `resource`.")
},
enumDescriptions: {
type: 'array',
items: {
type: 'string',
},
description: nls.localize('scope.enumDescriptions', 'Descriptions for enum values')
},
markdownEnumDescription: {
type: 'array',
items: {
type: 'string',
},
description: nls.localize('scope.markdownEnumDescription', 'Descriptions for enum values in the markdown format.')
},
markdownDescription: {
type: 'string',
description: nls.localize('scope.markdownDescription', 'The description in the markdown format.')
},
deprecationMessage: {
type: 'string',
description: nls.localize('scope.deprecationMessage', 'If set, the property is marked as deprecated and the given message is shown as an explanation.')
}
}
}
]
}
}
}
};
let registeredDefaultConfigurations: IDefaultConfigurationExtension[] = [];
// BEGIN VSCode extension point `configurationDefaults`
const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IConfigurationNode>('configurationDefaults', [], {
description: nls.localize('vscode.extension.contributes.defaultConfiguration', 'Contributes default editor configuration settings by language.'),
type: 'object',
defaultSnippets: [{ body: {} }],
patternProperties: {
'\\[.*\\]$': {
type: 'object',
default: {},
$ref: editorConfigurationSchemaId,
}
}
});
defaultConfigurationExtPoint.setHandler(extensions => {
registeredDefaultConfigurations = extensions.map(extension => {
const id = extension.description.id;
const name = extension.description.name;
const defaults = objects.deepClone(extension.value);
return <IDefaultConfigurationExtension>{
id, name, defaults
};
});
});
// END VSCode extension point `configurationDefaults`
// BEGIN VSCode extension point `configuration`
const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IConfigurationNode>('configuration', [defaultConfigurationExtPoint], {
description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'),
oneOf: [
configurationEntrySchema,
{
type: 'array',
items: configurationEntrySchema
}
]
});
configurationExtPoint.setHandler(extensions => {
const configurations: IConfigurationNode[] = [];
function handleConfiguration(node: IConfigurationNode, extension: IExtensionPointUser<any>) {
let configuration = objects.deepClone(node);
if (configuration.title && (typeof configuration.title !== 'string')) {
extension.collector.error(nls.localize('invalid.title', "'configuration.title' must be a string"));
}
validateProperties(configuration, extension);
configuration.id = node.id || extension.description.id || extension.description.uuid;
configuration.contributedByExtension = true;
configuration.title = configuration.title || extension.description.displayName || extension.description.id;
configurations.push(configuration);
}
for (let extension of extensions) {
const value = <IConfigurationNode | IConfigurationNode[]>extension.value;
if (!Array.isArray(value)) {
handleConfiguration(value, extension);
} else {
value.forEach(v => handleConfiguration(v, extension));
}
}
configurationRegistry.registerConfigurations(configurations, registeredDefaultConfigurations, false);
});
// END VSCode extension point `configuration`
function validateProperties(configuration: IConfigurationNode, extension: IExtensionPointUser<any>): void {
let properties = configuration.properties;
if (properties) {
if (typeof properties !== 'object') {
extension.collector.error(nls.localize('invalid.properties', "'configuration.properties' must be an object"));
configuration.properties = {};
}
for (let key in properties) {
const message = validateProperty(key);
if (message) {
delete properties[key];
extension.collector.warn(message);
continue;
}
const propertyConfiguration = properties[key];
if (!isObject(propertyConfiguration)) {
delete properties[key];
extension.collector.error(nls.localize('invalid.property', "'configuration.property' must be an object"));
continue;
}
if (propertyConfiguration.scope) {
if (propertyConfiguration.scope.toString() === 'application') {
propertyConfiguration.scope = ConfigurationScope.APPLICATION;
} else if (propertyConfiguration.scope.toString() === 'resource') {
propertyConfiguration.scope = ConfigurationScope.RESOURCE;
} else {
propertyConfiguration.scope = ConfigurationScope.WINDOW;
}
} else {
propertyConfiguration.scope = ConfigurationScope.WINDOW;
}
}
}
let subNodes = configuration.allOf;
if (subNodes) {
extension.collector.error(nls.localize('invalid.allOf', "'configuration.allOf' is deprecated and should no longer be used. Instead, pass multiple configuration sections as an array to the 'configuration' contribution point."));
for (let node of subNodes) {
validateProperties(node, extension);
}
}
}
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', {
allowComments: true,
default: {
folders: [
{
path: ''
}
],
settings: {
}
},
required: ['folders'],
properties: {
'folders': {
minItems: 0,
uniqueItems: true,
description: nls.localize('workspaceConfig.folders.description', "List of folders to be loaded in the workspace."),
items: {
type: 'object',
default: { path: '' },
oneOf: [{
properties: {
path: {
type: 'string',
description: nls.localize('workspaceConfig.path.description', "A file path. e.g. `/root/folderA` or `./folderA` for a relative path that will be resolved against the location of the workspace file.")
},
name: {
type: 'string',
description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ")
}
},
required: ['path']
}, {
properties: {
uri: {
type: 'string',
description: nls.localize('workspaceConfig.uri.description', "URI of the folder")
},
name: {
type: 'string',
description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ")
}
},
required: ['uri']
}]
}
},
'settings': {
type: 'object',
default: {},
description: nls.localize('workspaceConfig.settings.description', "Workspace settings"),
$ref: workspaceSettingsSchemaId
},
'launch': {
type: 'object',
default: { configurations: [], compounds: [] },
description: nls.localize('workspaceConfig.launch.description', "Workspace launch configurations"),
$ref: launchSchemaId
},
'extensions': {
type: 'object',
default: {},
description: nls.localize('workspaceConfig.extensions.description', "Workspace extensions"),
$ref: 'vscode://schemas/extensions'
}
},
additionalProperties: false,
errorMessage: nls.localize('unknownWorkspaceProperty', "Unknown workspace configuration property")
});

View File

@@ -202,7 +202,11 @@ export class Configuration extends BaseConfiguration {
// Do not remove workspace configuration
return new ConfigurationChangeEvent();
}
const keys = this.folders.get(folder).keys;
const folderConfig = this.folders.get(folder);
if (!folderConfig) {
throw new Error('Unknown folder');
}
const keys = folderConfig.keys;
super.deleteFolderConfiguration(folder);
return new ConfigurationChangeEvent().change(keys, folder);
}

View File

@@ -15,11 +15,10 @@ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { RunOnceScheduler, Delayer } from 'vs/base/common/async';
import { FileChangeType, FileChangesEvent, IContent, IFileService } from 'vs/platform/files/common/files';
import { isLinux } from 'vs/base/common/platform';
import { ConfigWatcher } from 'vs/base/node/config';
import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { WorkspaceConfigurationModelParser, FolderSettingsModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels';
import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration';
import { IStoredWorkspace, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import * as extfs from 'vs/base/node/extfs';
import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService';
import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@@ -30,96 +29,287 @@ import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationModel } from 'vs/platform/configuration/common/configuration';
export interface IWorkspaceIdentifier {
id: string;
configPath: URI;
}
export class WorkspaceConfiguration extends Disposable {
private _workspaceConfigPath: URI;
private _workspaceConfigurationWatcher: ConfigWatcher<WorkspaceConfigurationModelParser>;
private _workspaceConfigurationWatcherDisposables: IDisposable[] = [];
private readonly _cachedConfiguration: CachedWorkspaceConfiguration;
private _workspaceConfiguration: IWorkspaceConfiguration | null;
private _workspaceIdentifier: IWorkspaceIdentifier | null = null;
private _fileService: IFileService | null = null;
private readonly _onDidUpdateConfiguration: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidUpdateConfiguration: Event<void> = this._onDidUpdateConfiguration.event;
private _workspaceConfigurationModelParser: WorkspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceConfigPath ? this._workspaceConfigPath.fsPath : '');
private _cache: ConfigurationModel = new ConfigurationModel();
constructor(
environmentService: IEnvironmentService
) {
super();
this._cachedConfiguration = new CachedWorkspaceConfiguration(environmentService);
this._workspaceConfiguration = this._cachedConfiguration;
}
load(workspaceConfigPath: URI): Promise<void> {
if (this._workspaceConfigPath && this._workspaceConfigPath.fsPath === workspaceConfigPath.fsPath) {
return this.reload();
}
this._workspaceConfigPath = workspaceConfigPath;
return new Promise<void>((c, e) => {
const defaultConfig = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath);
defaultConfig.parse(JSON.stringify({ folders: [] } as IStoredWorkspace, null, '\t'));
if (this._workspaceConfigurationWatcher) {
this.disposeConfigurationWatcher();
adopt(fileService: IFileService): Promise<boolean> {
if (!this._fileService) {
this._fileService = fileService;
if (this.adoptWorkspaceConfiguration()) {
if (this._workspaceIdentifier) {
return this._workspaceConfiguration.load(this._workspaceIdentifier).then(() => true);
}
}
this._workspaceConfigurationWatcher = new ConfigWatcher(this._workspaceConfigPath.fsPath, {
changeBufferDelay: 300,
onError: error => errors.onUnexpectedError(error),
defaultConfig,
parse: (content: string, parseErrors: any[]) => {
this._workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath);
this._workspaceConfigurationModelParser.parse(content);
parseErrors = [...this._workspaceConfigurationModelParser.errors];
this.consolidate();
return this._workspaceConfigurationModelParser;
}, initCallback: () => c(null)
});
this.listenToWatcher();
});
}
return Promise.resolve(false);
}
load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
this._workspaceIdentifier = workspaceIdentifier;
this.adoptWorkspaceConfiguration();
return this._workspaceConfiguration.load(this._workspaceIdentifier);
}
reload(): Promise<void> {
this.stopListeningToWatcher();
return new Promise<void>(c => this._workspaceConfigurationWatcher.reload(() => {
this.listenToWatcher();
c(null);
}));
return this._workspaceIdentifier ? this.load(this._workspaceIdentifier) : Promise.resolve();
}
getFolders(): IStoredWorkspaceFolder[] {
return this._workspaceConfiguration.getFolders();
}
setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: JSONEditingService): Promise<void> {
if (this._workspaceIdentifier) {
return jsonEditingService.write(this._workspaceIdentifier.configPath, { key: 'folders', value: folders }, true)
.then(() => this.reload());
}
return Promise.resolve();
}
getConfiguration(): ConfigurationModel {
return this._workspaceConfiguration.getWorkspaceSettings();
}
reprocessWorkspaceSettings(): ConfigurationModel {
this._workspaceConfiguration.reprocessWorkspaceSettings();
return this.getConfiguration();
}
private adoptWorkspaceConfiguration(): boolean {
if (this._workspaceIdentifier) {
if (this._fileService) {
if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) {
dispose(this._workspaceConfiguration);
const nodeBasedWorkspaceConfiguration = this._workspaceConfiguration instanceof NodeBasedWorkspaceConfiguration ? this._workspaceConfiguration : undefined;
this._workspaceConfiguration = new FileServiceBasedWorkspaceConfiguration(this._fileService, nodeBasedWorkspaceConfiguration);
this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange()));
return !nodeBasedWorkspaceConfiguration;
}
return false;
}
if (this._workspaceIdentifier.configPath.scheme === Schemas.file) {
if (!(this._workspaceConfiguration instanceof NodeBasedWorkspaceConfiguration)) {
dispose(this._workspaceConfiguration);
this._workspaceConfiguration = new NodeBasedWorkspaceConfiguration();
return true;
}
return false;
}
}
return false;
}
private onDidWorkspaceConfigurationChange(): void {
this.updateCache();
this.reload().then(() => this._onDidUpdateConfiguration.fire());
}
private updateCache(): Promise<void> {
if (this._workspaceIdentifier && this._workspaceIdentifier.configPath.scheme !== Schemas.file && this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration) {
return this._workspaceConfiguration.load(this._workspaceIdentifier)
.then(() => this._cachedConfiguration.updateWorkspace(this._workspaceIdentifier, this._workspaceConfiguration.getConfigurationModel()));
}
return Promise.resolve(undefined);
}
}
interface IWorkspaceConfiguration extends IDisposable {
readonly onDidChange: Event<void>;
load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void>;
getConfigurationModel(): ConfigurationModel;
getFolders(): IStoredWorkspaceFolder[];
getWorkspaceSettings(): ConfigurationModel;
reprocessWorkspaceSettings(): ConfigurationModel;
}
abstract class AbstractWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration {
private _workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;
private _workspaceSettings: ConfigurationModel;
private _workspaceIdentifier: IWorkspaceIdentifier | null = null;
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(from?: AbstractWorkspaceConfiguration) {
super();
this._workspaceConfigurationModelParser = from ? from._workspaceConfigurationModelParser : new WorkspaceConfigurationModelParser('');
this._workspaceSettings = new ConfigurationModel();
}
get workspaceIdentifier(): IWorkspaceIdentifier | null {
return this._workspaceIdentifier;
}
load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
this._workspaceIdentifier = workspaceIdentifier;
return this.loadWorkspaceConfigurationContents(workspaceIdentifier)
.then(contents => {
this._workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(workspaceIdentifier.id);
this._workspaceConfigurationModelParser.parse(contents);
this.consolidate();
});
}
getConfigurationModel(): ConfigurationModel {
return this._workspaceConfigurationModelParser.configurationModel;
}
getFolders(): IStoredWorkspaceFolder[] {
return this._workspaceConfigurationModelParser.folders;
}
setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: JSONEditingService): Promise<void> {
return jsonEditingService.write(this._workspaceConfigPath, { key: 'folders', value: folders }, true)
.then(() => this.reload());
}
getConfiguration(): ConfigurationModel {
return this._cache;
getWorkspaceSettings(): ConfigurationModel {
return this._workspaceSettings;
}
reprocessWorkspaceSettings(): ConfigurationModel {
this._workspaceConfigurationModelParser.reprocessWorkspaceSettings();
this.consolidate();
return this.getConfiguration();
}
private listenToWatcher() {
this._workspaceConfigurationWatcher.onDidUpdateConfiguration(() => this._onDidUpdateConfiguration.fire(), this, this._workspaceConfigurationWatcherDisposables);
}
private stopListeningToWatcher() {
this._workspaceConfigurationWatcherDisposables = dispose(this._workspaceConfigurationWatcherDisposables);
return this.getWorkspaceSettings();
}
private consolidate(): void {
this._cache = this._workspaceConfigurationModelParser.settingsModel.merge(this._workspaceConfigurationModelParser.launchModel);
this._workspaceSettings = this._workspaceConfigurationModelParser.settingsModel.merge(this._workspaceConfigurationModelParser.launchModel);
}
private disposeConfigurationWatcher(): void {
this.stopListeningToWatcher();
if (this._workspaceConfigurationWatcher) {
this._workspaceConfigurationWatcher.dispose();
protected abstract loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string>;
}
class NodeBasedWorkspaceConfiguration extends AbstractWorkspaceConfiguration {
protected loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string> {
return pfs.readFile(workspaceIdentifier.configPath.fsPath)
.then(contents => contents.toString(), e => {
errors.onUnexpectedError(e);
return '';
});
}
}
class FileServiceBasedWorkspaceConfiguration extends AbstractWorkspaceConfiguration {
private workspaceConfig: URI | null = null;
private readonly reloadConfigurationScheduler: RunOnceScheduler;
constructor(private fileService: IFileService, from?: AbstractWorkspaceConfiguration) {
super(from);
this.workspaceConfig = from ? from.workspaceIdentifier.configPath : null;
this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e)));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50));
}
protected loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string> {
this.workspaceConfig = workspaceIdentifier.configPath;
return this.fileService.resolveContent(this.workspaceConfig)
.then(content => content.value, e => {
errors.onUnexpectedError(e);
return '';
});
}
private handleWorkspaceFileEvents(event: FileChangesEvent): void {
if (this.workspaceConfig) {
const events = event.changes;
let affectedByChanges = false;
// Find changes that affect workspace file
for (let i = 0, len = events.length; i < len && !affectedByChanges; i++) {
affectedByChanges = resources.isEqual(this.workspaceConfig, events[i].resource);
}
if (affectedByChanges) {
this.reloadConfigurationScheduler.schedule();
}
}
}
}
class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration {
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
private cachedWorkspacePath: string;
private cachedConfigurationPath: string;
private _workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;
private _workspaceSettings: ConfigurationModel;
constructor(private environmentService: IEnvironmentService) {
super();
this._workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser('');
this._workspaceSettings = new ConfigurationModel();
}
load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
this.createPaths(workspaceIdentifier);
return pfs.readFile(this.cachedConfigurationPath)
.then(contents => {
this._workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this.cachedConfigurationPath);
this._workspaceConfigurationModelParser.parse(contents.toString());
this._workspaceSettings = this._workspaceConfigurationModelParser.settingsModel.merge(this._workspaceConfigurationModelParser.launchModel);
}, () => null);
}
getConfigurationModel(): ConfigurationModel {
return this._workspaceConfigurationModelParser.configurationModel;
}
getFolders(): IStoredWorkspaceFolder[] {
return this._workspaceConfigurationModelParser.folders;
}
getWorkspaceSettings(): ConfigurationModel {
return this._workspaceSettings;
}
reprocessWorkspaceSettings(): ConfigurationModel {
return this._workspaceSettings;
}
async updateWorkspace(workspaceIdentifier: IWorkspaceIdentifier, configurationModel: ConfigurationModel): Promise<void> {
try {
this.createPaths(workspaceIdentifier);
if (configurationModel.keys.length) {
const exists = await pfs.exists(this.cachedWorkspacePath);
if (!exists) {
await pfs.mkdirp(this.cachedWorkspacePath);
}
const raw = JSON.stringify(configurationModel.toJSON());
await pfs.writeFile(this.cachedConfigurationPath, raw);
} else {
pfs.rimraf(this.cachedWorkspacePath);
}
} catch (error) {
errors.onUnexpectedError(error);
}
}
dispose(): void {
this.disposeConfigurationWatcher();
super.dispose();
private createPaths(workspaceIdentifier: IWorkspaceIdentifier) {
this.cachedWorkspacePath = paths.join(this.environmentService.userDataPath, 'CachedConfigurations', 'workspaces', workspaceIdentifier.id);
this.cachedConfigurationPath = paths.join(this.cachedWorkspacePath, 'workspace.json');
}
}
@@ -133,12 +323,11 @@ function isFolderSettingsConfigurationFile(resource: URI): boolean {
return resources.isEqual(URI.from({ scheme: resource.scheme, path: resources.basename(resource) }), URI.from({ scheme: resource.scheme, path: `${FOLDER_SETTINGS_NAME}.json` }));
}
export interface IFolderConfiguration {
export interface IFolderConfiguration extends IDisposable {
readonly onDidChange: Event<void>;
readonly loaded: boolean;
loadConfiguration(): Promise<ConfigurationModel>;
reprocess(): ConfigurationModel;
dispose(): void;
}
export abstract class AbstractFolderConfiguration extends Disposable implements IFolderConfiguration {
@@ -225,13 +414,16 @@ export class NodeBasedFolderConfiguration extends AbstractFolderConfiguration {
protected loadFolderConfigurationContents(): Promise<{ resource: URI, value: string }[]> {
return this.resolveStat(this.folderConfigurationPath).then(stat => {
if (!stat.isDirectory) {
if (!stat.isDirectory || !stat.children) {
return Promise.resolve([]);
}
return this.resolveContents(stat.children.filter(stat => isFolderConfigurationFile(stat.resource))
.map(stat => stat.resource));
}, err => [] /* never fail this call */)
.then(null, errors.onUnexpectedError);
.then(undefined, e => {
errors.onUnexpectedError(e);
return [];
});
}
private resolveContents(resources: URI[]): Promise<{ resource: URI, value: string }[]> {
@@ -265,7 +457,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura
private reloadConfigurationScheduler: RunOnceScheduler;
private readonly folderConfigurationPath: URI;
private readonly loadConfigurationDelayer: Delayer<{ resource: URI, value: string }[]> = new Delayer<{ resource: URI, value: string }[]>(50);
private readonly loadConfigurationDelayer = new Delayer<Array<{ resource: URI, value: string }>>(50);
constructor(folder: URI, private configFolderRelativePath: string, workbenchState: WorkbenchState, private fileService: IFileService, from?: AbstractFolderConfiguration) {
super(folder, workbenchState, from);
@@ -274,20 +466,25 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura
this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e)));
}
protected loadFolderConfigurationContents(): Promise<{ resource: URI, value: string }[]> {
protected loadFolderConfigurationContents(): Promise<Array<{ resource: URI, value: string }>> {
return Promise.resolve(this.loadConfigurationDelayer.trigger(() => this.doLoadFolderConfigurationContents()));
}
private doLoadFolderConfigurationContents(): Promise<{ resource: URI, value: string }[]> {
private doLoadFolderConfigurationContents(): Promise<Array<{ resource: URI, value: string }>> {
const workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: Promise<IContent> } = Object.create(null);
const bulkContentFetchromise = Promise.resolve(this.fileService.resolveFile(this.folderConfigurationPath))
.then(stat => {
if (stat.isDirectory && stat.children) {
stat.children
.filter(child => isFolderConfigurationFile(child.resource))
.forEach(child => workspaceFilePathToConfiguration[this.toFolderRelativePath(child.resource)] = Promise.resolve(this.fileService.resolveContent(child.resource)).then(null, errors.onUnexpectedError));
.forEach(child => {
const folderRelativePath = this.toFolderRelativePath(child.resource);
if (folderRelativePath) {
workspaceFilePathToConfiguration[folderRelativePath] = Promise.resolve(this.fileService.resolveContent(child.resource)).then(undefined, errors.onUnexpectedError);
}
});
}
}).then(null, err => [] /* never fail this call */);
}).then(undefined, err => [] /* never fail this call */);
return bulkContentFetchromise.then(() => Promise.all(collections.values(workspaceFilePathToConfiguration)));
}
@@ -298,7 +495,6 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura
// Find changes that affect workspace configuration files
for (let i = 0, len = events.length; i < len; i++) {
const resource = events[i].resource;
const basename = resources.basename(resource);
const isJson = paths.extname(basename) === '.json';
@@ -316,6 +512,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura
// Handle case where ".vscode" got deleted
if (isDeletedSettingsFolder) {
affectedByChanges = true;
break;
}
// only valid workspace config files
@@ -323,12 +520,8 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura
continue;
}
switch (events[i].type) {
case FileChangeType.DELETED:
case FileChangeType.UPDATED:
case FileChangeType.ADDED:
affectedByChanges = true;
}
affectedByChanges = true;
break;
}
if (affectedByChanges) {
@@ -336,7 +529,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura
}
}
private toFolderRelativePath(resource: URI): string {
private toFolderRelativePath(resource: URI): string | null {
if (resource.scheme === Schemas.file) {
if (paths.isEqualOrParent(resource.fsPath, this.folderConfigurationPath.fsPath, !isLinux /* ignorecase */)) {
return paths.normalize(relative(this.folderConfigurationPath.fsPath, resource.fsPath));
@@ -366,7 +559,7 @@ export class CachedFolderConfiguration extends Disposable implements IFolderConf
configFolderRelativePath: string,
environmentService: IEnvironmentService) {
super();
this.cachedFolderPath = paths.join(environmentService.appSettingsHome, createHash('md5').update(paths.join(folder.path, configFolderRelativePath)).digest('hex'));
this.cachedFolderPath = paths.join(environmentService.userDataPath, 'CachedConfigurations', 'folders', createHash('md5').update(paths.join(folder.path, configFolderRelativePath)).digest('hex'));
this.cachedConfigurationPath = paths.join(this.cachedFolderPath, 'configuration.json');
this.configurationModel = new ConfigurationModel();
}
@@ -387,7 +580,7 @@ export class CachedFolderConfiguration extends Disposable implements IFolderConf
if (created) {
return configurationModel.keys.length ? pfs.writeFile(this.cachedConfigurationPath, raw) : pfs.rimraf(this.cachedFolderPath);
}
return null;
return undefined;
});
}
@@ -401,7 +594,7 @@ export class CachedFolderConfiguration extends Disposable implements IFolderConf
private createCachedFolder(): Promise<boolean> {
return Promise.resolve(pfs.exists(this.cachedFolderPath))
.then(null, () => false)
.then(undefined, () => false)
.then(exists => exists ? exists : pfs.mkdirp(this.cachedFolderPath).then(() => true, () => false));
}
}
@@ -492,6 +685,6 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat
return this.folderConfiguration.loadConfiguration()
.then(configurationModel => this.cachedFolderConfiguration.updateConfiguration(configurationModel));
}
return Promise.resolve(null);
return Promise.resolve(undefined);
}
}

View File

@@ -122,15 +122,15 @@ export class ConfigurationEditingService {
private queue: Queue<void>;
constructor(
@IConfigurationService private configurationService: IConfigurationService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IFileService private fileService: IFileService,
@ITextModelService private textModelResolverService: ITextModelService,
@ITextFileService private textFileService: ITextFileService,
@INotificationService private notificationService: INotificationService,
@IPreferencesService private preferencesService: IPreferencesService,
@IEditorService private editorService: IEditorService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IFileService private readonly fileService: IFileService,
@ITextModelService private readonly textModelResolverService: ITextModelService,
@ITextFileService private readonly textFileService: ITextFileService,
@INotificationService private readonly notificationService: INotificationService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
@IEditorService private readonly editorService: IEditorService
) {
this.queue = new Queue<void>();
}
@@ -264,7 +264,7 @@ export class ConfigurationEditingService {
this.editorService.openEditor({ resource });
}
private wrapError<T = never>(code: ConfigurationEditingErrorCode, target: ConfigurationTarget, operation: IConfigurationEditOperation): Promise<T> {
private reject<T = never>(code: ConfigurationEditingErrorCode, target: ConfigurationTarget, operation: IConfigurationEditOperation): Promise<T> {
const message = this.toErrorMessage(code, target, operation);
return Promise.reject(new ConfigurationEditingError(message, code));
@@ -377,45 +377,45 @@ export class ConfigurationEditingService {
if (!operation.workspaceStandAloneConfigurationKey) {
const validKeys = this.configurationService.keys().default;
if (validKeys.indexOf(operation.key) < 0 && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) {
return this.wrapError(ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY, target, operation);
return this.reject(ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY, target, operation);
}
}
if (operation.workspaceStandAloneConfigurationKey) {
// Global tasks and launches are not supported
if (target === ConfigurationTarget.USER) {
return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET, target, operation);
return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET, target, operation);
}
// Workspace tasks are not supported
if (operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && operation.target === ConfigurationTarget.WORKSPACE) {
return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_TARGET, target, operation);
return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_TARGET, target, operation);
}
}
// Target cannot be workspace or folder if no workspace opened
if ((target === ConfigurationTarget.WORKSPACE || target === ConfigurationTarget.WORKSPACE_FOLDER) && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
return this.wrapError(ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED, target, operation);
return this.reject(ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED, target, operation);
}
if (target === ConfigurationTarget.WORKSPACE) {
if (!operation.workspaceStandAloneConfigurationKey) {
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
if (configurationProperties[operation.key].scope === ConfigurationScope.APPLICATION) {
return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation);
return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation);
}
}
}
if (target === ConfigurationTarget.WORKSPACE_FOLDER) {
if (!operation.resource) {
return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation);
return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation);
}
if (!operation.workspaceStandAloneConfigurationKey) {
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
if (configurationProperties[operation.key].scope !== ConfigurationScope.RESOURCE) {
return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation);
return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation);
}
}
}
@@ -425,12 +425,12 @@ export class ConfigurationEditingService {
const model = reference.object.textEditorModel;
if (this.hasParseErrors(model, operation)) {
return this.wrapError<typeof reference>(ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION, target, operation);
return this.reject<typeof reference>(ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION, target, operation);
}
// Target cannot be dirty if not writing into buffer
if (checkDirty && this.textFileService.isDirty(operation.resource)) {
return this.wrapError<typeof reference>(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY, target, operation);
return this.reject<typeof reference>(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY, target, operation);
}
return reference;
});
@@ -441,8 +441,7 @@ export class ConfigurationEditingService {
// Check for standalone workspace configurations
if (config.key) {
const standaloneConfigurationKeys = Object.keys(WORKSPACE_STANDALONE_CONFIGURATIONS);
for (let i = 0; i < standaloneConfigurationKeys.length; i++) {
const key = standaloneConfigurationKeys[i];
for (const key of standaloneConfigurationKeys) {
const resource = this.getConfigurationFileResource(target, WORKSPACE_STANDALONE_CONFIGURATIONS[key], overrides.resource);
// Check for prefix

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { dirname } from 'path';
import * as assert from 'vs/base/common/assert';
import { Event, Emitter } from 'vs/base/common/event';
import { ResourceMap } from 'vs/base/common/map';
@@ -36,7 +35,7 @@ import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/works
import { UserConfiguration } from 'vs/platform/configuration/node/configuration';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
import { isEqual } from 'vs/base/common/resources';
import { isEqual, dirname } from 'vs/base/common/resources';
import { mark } from 'vs/base/common/performance';
export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService {
@@ -44,6 +43,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
public _serviceBrand: any;
private workspace: Workspace;
private resolvePromise: Promise<void>;
private resolveCallback: () => void;
private _configuration: Configuration;
private defaultConfiguration: DefaultConfigurationModel;
private userConfiguration: UserConfiguration;
@@ -52,7 +53,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
private workspaceEditingQueue: Queue<void>;
protected readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>({ leakWarningThreshold: 500 }));
protected readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
public readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
protected readonly _onDidChangeWorkspaceFolders: Emitter<IWorkspaceFoldersChangeEvent> = this._register(new Emitter<IWorkspaceFoldersChangeEvent>());
@@ -71,20 +72,25 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
constructor(private environmentService: IEnvironmentService, private workspaceSettingsRootFolder: string = FOLDER_CONFIG_FOLDER_NAME) {
super();
this.resolvePromise = new Promise(c => this.resolveCallback = c);
this.defaultConfiguration = new DefaultConfigurationModel();
this.userConfiguration = this._register(new UserConfiguration(environmentService.appSettingsPath));
this.workspaceConfiguration = this._register(new WorkspaceConfiguration());
this.workspaceConfiguration = this._register(new WorkspaceConfiguration(environmentService));
this._register(this.userConfiguration.onDidChangeConfiguration(userConfiguration => this.onUserConfigurationChanged(userConfiguration)));
this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged()));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidSchemaChange(e => this.registerConfigurationSchemas()));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
this.workspaceEditingQueue = new Queue<void>();
}
// Workspace Context Service Impl
public getCompleteWorkspace(): Promise<Workspace> {
return this.resolvePromise.then(() => this.getWorkspace());
}
public getWorkspace(): Workspace {
return this.workspace;
}
@@ -137,11 +143,11 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
private doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): Promise<void> {
if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
return Promise.resolve(void 0); // we need a workspace to begin with
return Promise.resolve(undefined); // we need a workspace to begin with
}
if (foldersToAdd.length + foldersToRemove.length === 0) {
return Promise.resolve(void 0); // nothing to do
return Promise.resolve(undefined); // nothing to do
}
let foldersHaveChanged = false;
@@ -162,8 +168,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
if (foldersToAdd.length) {
// Recompute current workspace folders if we have folders to add
const workspaceConfigFolder = dirname(this.getWorkspace().configuration.fsPath);
currentWorkspaceFolders = toWorkspaceFolders(newStoredFolders, URI.file(workspaceConfigFolder));
const workspaceConfigFolder = dirname(this.getWorkspace().configuration);
currentWorkspaceFolders = toWorkspaceFolders(newStoredFolders, workspaceConfigFolder);
const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri);
const storedFoldersToAdd: IStoredWorkspaceFolder[] = [];
@@ -214,7 +220,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
return this.setFolders(newStoredFolders);
}
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
private setFolders(folders: IStoredWorkspaceFolder[]): Promise<void> {
@@ -245,8 +251,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
getValue<T>(overrides: IConfigurationOverrides): T;
getValue<T>(section: string, overrides: IConfigurationOverrides): T;
getValue(arg1?: any, arg2?: any): any {
const section = typeof arg1 === 'string' ? arg1 : void 0;
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0;
const section = typeof arg1 === 'string' ? arg1 : undefined;
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : undefined;
return this._configuration.getValue(section, overrides);
}
@@ -257,7 +263,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError: boolean): Promise<void>;
updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): Promise<void> {
assert.ok(this.configurationEditingService, 'Workbench is not initialized yet');
const overrides = isConfigurationOverrides(arg3) ? arg3 : void 0;
const overrides = isConfigurationOverrides(arg3) ? arg3 : undefined;
const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3);
return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError)
: Promise.resolve(null);
@@ -303,17 +309,22 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
acquireFileService(fileService: IFileService): void {
this.fileService = fileService;
const changedWorkspaceFolders: IWorkspaceFolder[] = [];
Promise.all(this.cachedFolderConfigs.values()
Promise.all([this.workspaceConfiguration.adopt(fileService), ...this.cachedFolderConfigs.values()
.map(folderConfiguration => folderConfiguration.adopt(fileService)
.then(result => {
if (result) {
changedWorkspaceFolders.push(folderConfiguration.workspaceFolder);
}
})))
.then(() => {
return result;
}))])
.then(([workspaceChanged]) => {
if (workspaceChanged) {
this.onWorkspaceConfigurationChanged();
}
for (const workspaceFolder of changedWorkspaceFolders) {
this.onWorkspaceFolderConfigurationChanged(workspaceFolder);
}
this.resolveCallback();
});
}
@@ -335,10 +346,10 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
}
private createMultiFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise<Workspace> {
const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
return this.workspaceConfiguration.load(workspaceConfigPath)
return this.workspaceConfiguration.load({ id: workspaceIdentifier.id, configPath: URI.file(workspaceIdentifier.configPath) })
.then(() => {
const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(dirname(workspaceConfigPath.fsPath)));
const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), dirname(workspaceConfigPath));
const workspaceId = workspaceIdentifier.id;
return new Workspace(workspaceId, workspaceFolders, workspaceConfigPath);
});
@@ -369,7 +380,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
if (hasWorkspaceBefore) {
previousState = this.getWorkbenchState();
previousWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
previousWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : undefined;
previousFolders = this.workspace.folders;
this.workspace.update(workspace);
} else {
@@ -387,7 +398,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
this._onDidChangeWorkbenchState.fire(newState);
}
const newWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
const newWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : undefined;
if (previousWorkspacePath && newWorkspacePath !== previousWorkspacePath || newState !== previousState) {
this._onDidChangeWorkspaceName.fire();
}
@@ -436,7 +447,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
if (workbenchState === WorkbenchState.WORKSPACE) {
return this.workspaceConfiguration.reload().then(() => this.onWorkspaceConfigurationChanged());
}
return Promise.resolve(null);
return Promise.resolve(undefined);
}
private reloadWorkspaceFolderConfiguration(folder: IWorkspaceFolder, key?: string): Promise<void> {
@@ -531,7 +542,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
private onWorkspaceConfigurationChanged(): Promise<void> {
if (this.workspace && this.workspace.configuration && this._configuration) {
const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration());
let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(dirname(this.workspace.configuration.fsPath)));
let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), dirname(this.workspace.configuration));
const changes = this.compareFolders(this.workspace.folders, configuredFolders);
if (changes.added.length || changes.removed.length || changes.changed.length) {
this.workspace.folders = configuredFolders;
@@ -544,7 +555,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE);
}
}
return Promise.resolve(null);
return Promise.resolve(undefined);
}
private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): Promise<void> {
@@ -606,7 +617,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
if (target === ConfigurationTarget.MEMORY) {
this._configuration.updateValue(key, value, overrides);
this.triggerConfigurationChange(new ConfigurationChangeEvent().change(overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier)] : [key], overrides.resource), target);
return Promise.resolve(null);
return Promise.resolve(undefined);
}
return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError })
@@ -631,22 +642,22 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
return target;
}
if (value === void 0) {
if (value === undefined) {
// Ignore. But expected is to remove the value from all targets
return void 0;
return undefined;
}
const inspect = this.inspect(key, overrides);
if (equals(value, inspect.value)) {
// No change. So ignore.
return void 0;
return undefined;
}
if (inspect.workspaceFolder !== void 0) {
if (inspect.workspaceFolder !== undefined) {
return ConfigurationTarget.WORKSPACE_FOLDER;
}
if (inspect.workspace !== void 0) {
if (inspect.workspace !== undefined) {
return ConfigurationTarget.WORKSPACE;
}
@@ -693,8 +704,8 @@ export class DefaultConfigurationExportHelper {
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IExtensionService private extensionService: IExtensionService,
@ICommandService private commandService: ICommandService) {
@IExtensionService private readonly extensionService: IExtensionService,
@ICommandService private readonly commandService: ICommandService) {
if (environmentService.args['export-default-configuration']) {
this.writeConfigModelAndQuit(environmentService.args['export-default-configuration']);
}

View File

@@ -28,9 +28,9 @@ export class JSONEditingService implements IJSONEditingService {
private queue: Queue<void>;
constructor(
@IFileService private fileService: IFileService,
@ITextModelService private textModelResolverService: ITextModelService,
@ITextFileService private textFileService: ITextFileService
@IFileService private readonly fileService: IFileService,
@ITextModelService private readonly textModelResolverService: ITextModelService,
@ITextFileService private readonly textFileService: ITextFileService
) {
this.queue = new Queue<void>();
}
@@ -103,18 +103,18 @@ export class JSONEditingService implements IJSONEditingService {
const model = reference.object.textEditorModel;
if (this.hasParseErrors(model)) {
return this.wrapError<IReference<ITextEditorModel>>(JSONEditingErrorCode.ERROR_INVALID_FILE);
return this.reject<IReference<ITextEditorModel>>(JSONEditingErrorCode.ERROR_INVALID_FILE);
}
// Target cannot be dirty if not writing into buffer
if (checkDirty && this.textFileService.isDirty(resource)) {
return this.wrapError<IReference<ITextEditorModel>>(JSONEditingErrorCode.ERROR_FILE_DIRTY);
return this.reject<IReference<ITextEditorModel>>(JSONEditingErrorCode.ERROR_FILE_DIRTY);
}
return reference;
});
}
private wrapError<T>(code: JSONEditingErrorCode): Promise<T> {
private reject<T>(code: JSONEditingErrorCode): Promise<T> {
const message = this.toErrorMessage(code);
return Promise.reject(new JSONEditingError(message, code));
}

View File

@@ -201,7 +201,7 @@ suite('AllKeysConfigurationChangeEvent', () => {
test('changeEvent affects keys for any resource', () => {
const configuraiton = new Configuration(new ConfigurationModel({}, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']),
new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), null);
new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), null!);
let testObject = new AllKeysConfigurationChangeEvent(configuraiton, ConfigurationTarget.USER, null);
assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']);

View File

@@ -123,18 +123,18 @@ suite('ConfigurationEditingService', () => {
if (configuraitonService) {
configuraitonService.dispose();
}
instantiationService = null;
instantiationService = null!;
}
}
function clearWorkspace(): Promise<void> {
return new Promise<void>((c, e) => {
if (parentDir) {
extfs.del(parentDir, os.tmpdir(), () => c(null), () => c(null));
extfs.del(parentDir, os.tmpdir(), () => c(undefined), () => c(undefined));
} else {
c(null);
c(undefined);
}
}).then(() => parentDir = null);
}).then(() => parentDir = null!);
}
test('errors cases - invalid key', () => {
@@ -179,7 +179,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 });
instantiationService.stub(INotificationService, <INotificationService>{ prompt: target, _serviceBrand: null, notify: null!, error: null!, info: null!, warn: null! });
return testObject.writeConfiguration(ConfigurationTarget.USER, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true })
.then(() => assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'),
(error: ConfigurationEditingError) => {

View File

@@ -19,7 +19,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { ISingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/node/configurationEditingService';
import { IFileService } from 'vs/platform/files/common/files';
import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { workbenchInstantiationService, TestTextResourceConfigurationService, TestTextFileService, TestLifecycleService, TestEnvironmentService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
@@ -34,6 +34,7 @@ import { JSONEditingService } from 'vs/workbench/services/configuration/node/jso
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { Uri } from 'vscode';
import { createHash } from 'crypto';
import { Emitter, Event } from 'vs/base/common/event';
class SettingsTestEnvironmentService extends EnvironmentService {

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { IStringDictionary } from 'vs/base/common/collections';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@@ -26,29 +25,39 @@ export interface IConfigurationResolverService {
/**
* Recursively resolves all variables (including commands and user input) in the given config and returns a copy of it with substituted values.
* If a "variables" dictionary (with names -> command ids) is given, command variables are first mapped through it before being resolved.
* @param folder
* @param config
*
* @param section For example, 'tasks' or 'debug'. Used for resolving inputs.
* @param variables Aliases for commands.
*/
resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary<string>): TPromise<any>;
resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary<string>): Promise<any>;
/**
* Similar to resolveWithInteractionReplace, except without the replace. Returns a map of variables and their resolution.
* Keys in the map will be of the format input:variableName or command:variableName.
*/
resolveWithInteraction(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary<string>): TPromise<Map<string, string>>;
resolveWithInteraction(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary<string>): Promise<Map<string, string>>;
}
export const enum ConfiguredInputType {
PromptString,
PickString
}
export interface ConfiguredInput {
export interface PromptStringInputInfo {
id: string;
type: 'promptString';
description: string;
default?: string;
type: ConfiguredInputType;
options?: string[];
}
}
export interface PickStringInputInfo {
id: string;
type: 'pickString';
description: string;
options: string[];
default?: string;
}
export interface CommandInputInfo {
id: string;
type: 'command';
command: string;
args?: any;
}
export type ConfiguredInput = PromptStringInputInfo | PickStringInputInfo | CommandInputInfo;

View File

@@ -5,41 +5,109 @@
import * as nls from 'vs/nls';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
const idDescription = nls.localize('JsonSchema.input.id', "The input's id is used to associate an input with a variable of the form ${input:id}.");
const typeDescription = nls.localize('JsonSchema.input.type', "The type of user input prompt to use.");
const descriptionDescription = nls.localize('JsonSchema.input.description', "The description is shown when the user is prompted for input.");
const defaultDescription = nls.localize('JsonSchema.input.default', "The default value for the input.");
export const inputsSchema: IJSONSchema = {
definitions: {
inputs: {
type: 'array',
description: nls.localize('JsonSchema.inputs', 'User inputs. Used for defining user input prompts, such as free string input or a choice from several options.'),
items: {
type: 'object',
required: ['id', 'type', 'description'],
additionalProperties: false,
properties: {
id: {
type: 'string',
description: nls.localize('JsonSchema.input.id', "The input\'s id is used to specify inputs as ${input:id}.")
oneOf: [
{
type: 'object',
required: ['id', 'type', 'description'],
additionalProperties: false,
properties: {
id: {
type: 'string',
description: idDescription
},
type: {
type: 'string',
description: typeDescription,
enum: ['promptString'],
enumDescriptions: [
nls.localize('JsonSchema.input.type.promptString', "The 'promptString' type opens an input box to ask the user for input."),
]
},
description: {
type: 'string',
description: descriptionDescription
},
default: {
type: 'string',
description: defaultDescription
},
}
},
type: {
type: 'string',
description: nls.localize('JsonSchema.input.type', 'The promptString type opens an input box to ask the user for input. The pickString type shows a selection list.'),
enum: ['promptString', 'pickString']
{
type: 'object',
required: ['id', 'type', 'description', 'options'],
additionalProperties: false,
properties: {
id: {
type: 'string',
description: idDescription
},
type: {
type: 'string',
description: typeDescription,
enum: ['pickString'],
enumDescriptions: [
nls.localize('JsonSchema.input.type.pickString', "The 'pickString' type shows a selection list."),
]
},
description: {
type: 'string',
description: descriptionDescription
},
default: {
type: 'string',
description: defaultDescription
},
options: {
type: 'array',
description: nls.localize('JsonSchema.input.options', 'An array of strings that defines the options for a quick pick.'),
items: {
type: 'string'
}
}
}
},
description: {
type: 'string',
description: nls.localize('JsonSchema.input.description', 'The description is shown when the user is prompted for input.'),
},
default: {
type: 'string',
description: nls.localize('JsonSchema.input.default', 'The default value for the input.'),
},
options: {
type: 'array',
description: nls.localize('JsonSchema.input.options', 'An array of strings that defines the options for a quick pick.'),
items: {
type: 'string'
{
type: 'object',
required: ['id', 'type', 'command'],
additionalProperties: false,
properties: {
id: {
type: 'string',
description: idDescription
},
type: {
type: 'string',
description: typeDescription,
enum: ['command'],
enumDescriptions: [
nls.localize('JsonSchema.input.type.command', "The 'command' type executes a command."),
]
},
command: {
type: 'string',
description: nls.localize('JsonSchema.input.command.command', "The command to execute for this input variable.")
},
args: {
type: 'object',
description: nls.localize('JsonSchema.input.command.args', "Optional arguments passed to the command.")
}
}
}
}
]
}
}
}

View File

@@ -7,11 +7,8 @@ import { URI as uri } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import * as paths from 'vs/base/common/paths';
import * as platform from 'vs/base/common/platform';
import * as Objects from 'vs/base/common/objects';
import * as Types from 'vs/base/common/types';
import { Schemas } from 'vs/base/common/network';
import { TPromise } from 'vs/base/common/winjs.base';
import { sequence } from 'vs/base/common/async';
import { toResource } from 'vs/workbench/common/editor';
import { IStringDictionary, forEach, fromMap } from 'vs/base/common/collections';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -23,18 +20,20 @@ import { AbstractVariableResolverService } from 'vs/workbench/services/configura
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IQuickInputService, IInputOptions, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
import { ConfiguredInput, ConfiguredInputType } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
export class ConfigurationResolverService extends AbstractVariableResolverService {
static INPUT_OR_COMMAND_VARIABLES_PATTERN = /\${((input|command):(.*?))}/g;
constructor(
envVariables: platform.IProcessEnvironment,
@IEditorService editorService: IEditorService,
@IEnvironmentService environmentService: IEnvironmentService,
@IConfigurationService private configurationService: IConfigurationService,
@ICommandService private commandService: ICommandService,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
@IQuickInputService private quickInputService: IQuickInputService
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICommandService private readonly commandService: ICommandService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IQuickInputService private readonly quickInputService: IQuickInputService
) {
super({
getFolderUri: (folderName: string): uri => {
@@ -83,7 +82,7 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic
}, envVariables);
}
public resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary<string>): TPromise<any> {
public resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary<string>): Promise<any> {
// resolve any non-interactive variables
config = this.resolveAny(folder, config);
@@ -100,26 +99,18 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic
});
}
public resolveWithInteraction(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary<string>): TPromise<Map<string, string>> {
public resolveWithInteraction(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary<string>): Promise<Map<string, string>> {
// resolve any non-interactive variables
const resolved = this.resolveAnyMap(folder, config);
config = resolved.newConfig;
const allVariableMapping: Map<string, string> = resolved.resolvedVariables;
// resolve input variables in the order in which they are encountered
return this.resolveWithInputs(folder, config, section).then(inputMapping => {
if (!this.updateMapping(inputMapping, allVariableMapping)) {
return undefined;
}
// resolve commands in the order in which they are encountered
return this.resolveWithCommands(config, variables).then(commandMapping => {
if (!this.updateMapping(commandMapping, allVariableMapping)) {
return undefined;
}
// resolve input and command variables in the order in which they are encountered
return this.resolveWithInputAndCommands(folder, config, variables, section).then(inputOrCommandMapping => {
if (this.updateMapping(inputOrCommandMapping, allVariableMapping)) {
return allVariableMapping;
});
}
return undefined;
});
}
@@ -137,199 +128,163 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic
}
/**
* Finds and executes all command variables in the given configuration and returns their values as a dictionary.
* Please note: this method does not substitute the command variables (so the configuration is not modified).
* The returned dictionary can be passed to "resolvePlatform" for the substitution.
* Finds and executes all input and command variables in the given configuration and returns their values as a dictionary.
* Please note: this method does not substitute the input or command variables (so the configuration is not modified).
* The returned dictionary can be passed to "resolvePlatform" for the actual substitution.
* See #6569.
* @param configuration
*
* @param variableToCommandMap Aliases for commands
*/
private resolveWithCommands(configuration: any, variableToCommandMap: IStringDictionary<string>): TPromise<IStringDictionary<string>> {
private async resolveWithInputAndCommands(folder: IWorkspaceFolder, configuration: any, variableToCommandMap: IStringDictionary<string>, section: string): Promise<IStringDictionary<string>> {
if (!configuration) {
return TPromise.as(undefined);
}
// use an array to preserve order of first appearance
const cmd_var = /\${command:(.*?)}/g;
const commands: string[] = [];
this.findVariables(cmd_var, configuration, commands);
let cancelled = false;
const commandValueMapping: IStringDictionary<string> = Object.create(null);
const factory: { (): TPromise<any> }[] = commands.map(commandVariable => {
return () => {
let commandId = variableToCommandMap ? variableToCommandMap[commandVariable] : undefined;
if (!commandId) {
// Just launch any command if the interactive variable is not contributed by the adapter #12735
commandId = commandVariable;
}
return this.commandService.executeCommand<string>(commandId, configuration).then(result => {
if (typeof result === 'string') {
commandValueMapping['command:' + commandVariable] = result;
} else if (Types.isUndefinedOrNull(result)) {
cancelled = true;
} else {
throw new Error(nls.localize('stringsOnlySupported', "Command '{0}' did not return a string result. Only strings are supported as results for commands used for variable substitution.", commandVariable));
}
});
};
});
return sequence(factory).then(() => cancelled ? undefined : commandValueMapping);
}
/**
* Resolves all inputs in a configuration and returns a map that maps the unresolved input to the resolved input.
* Does not do replacement of inputs.
* @param folder
* @param config
* @param section
*/
public resolveWithInputs(folder: IWorkspaceFolder, config: any, section: string): Promise<IStringDictionary<string>> {
if (!config) {
return Promise.resolve(undefined);
} else if (folder && section) {
// Get all the possible inputs
let result = this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY
? Objects.deepClone(this.configurationService.getValue<any>(section, { resource: folder.uri }))
: undefined;
let inputsArray = result ? this.parseConfigurationInputs(result.inputs) : undefined;
const inputs = new Map<string, ConfiguredInput>();
if (inputsArray) {
inputsArray.forEach(input => {
inputs.set(input.id, input);
});
}
// use an array to preserve order of first appearance
const input_var = /\${input:(.*?)}/g;
const commands: string[] = [];
this.findVariables(input_var, config, commands);
let cancelled = false;
const commandValueMapping: IStringDictionary<string> = Object.create(null);
const factory: { (): Promise<any> }[] = commands.map(commandVariable => {
return () => {
return this.showUserInput(commandVariable, inputs).then(resolvedValue => {
if (resolvedValue) {
commandValueMapping['input:' + commandVariable] = resolvedValue;
} else {
cancelled = true;
}
});
};
}, reason => {
return Promise.reject(reason);
});
return sequence(factory).then(() => cancelled ? undefined : commandValueMapping);
// get all "inputs"
let inputs: ConfiguredInput[] = undefined;
if (folder && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY) {
let result = this.configurationService.getValue<any>(section, { resource: folder.uri });
if (result) {
inputs = result.inputs;
}
}
return Promise.resolve(Object.create(null));
}
// extract and dedupe all "input" and "command" variables and preserve their order in an array
const variables: string[] = [];
this.findVariables(configuration, variables);
/**
* Takes the provided input info and shows the quick pick so the user can provide the value for the input
* @param commandVariable Name of the input.
* @param inputs Information about each possible input.
* @param commandValueMapping
*/
private showUserInput(commandVariable: string, inputs: Map<string, ConfiguredInput>): Promise<string> {
if (inputs && inputs.has(commandVariable)) {
const input = inputs.get(commandVariable);
if (input.type === ConfiguredInputType.PromptString) {
let inputOptions: IInputOptions = { prompt: input.description };
if (input.default) {
inputOptions.value = input.default;
}
const variableValues: IStringDictionary<string> = Object.create(null);
return this.quickInputService.input(inputOptions).then(resolvedInput => {
return resolvedInput ? resolvedInput : undefined;
});
} else { // input.type === ConfiguredInputType.pick
let picks = new Array<IQuickPickItem>();
if (input.options) {
input.options.forEach(pickOption => {
let item: IQuickPickItem = { label: pickOption };
if (input.default && (pickOption === input.default)) {
item.description = nls.localize('defaultInputValue', "Default");
picks.unshift(item);
} else {
picks.push(item);
}
});
}
let pickOptions: IPickOptions<IQuickPickItem> = { placeHolder: input.description };
return this.quickInputService.pick(picks, pickOptions, undefined).then(resolvedInput => {
return resolvedInput ? resolvedInput.label : undefined;
});
for (const variable of variables) {
const [type, name] = variable.split(':', 2);
let result: string | undefined | null;
switch (type) {
case 'input':
result = await this.showUserInput(name, inputs);
break;
case 'command':
// use the name as a command ID #12735
const commandId = (variableToCommandMap ? variableToCommandMap[name] : undefined) || name;
result = await this.commandService.executeCommand(commandId, configuration);
if (typeof result !== 'string' && !Types.isUndefinedOrNull(result)) {
throw new Error(nls.localize('commandVariable.noStringType', "Cannot substitute command variable '{0}' because command did not return a result of type string.", commandId));
}
break;
}
if (typeof result === 'string') {
variableValues[variable] = result;
} else {
return undefined;
}
}
return Promise.reject(new Error(nls.localize('undefinedInputVariable', "Undefined input variable {0} encountered. Remove or define {0} to continue.", commandVariable)));
return variableValues;
}
/**
* Finds all variables in object using cmdVar and pushes them into commands.
* @param cmdVar Regex to use for finding variables.
* Recursively finds all command or input variables in object and pushes them into variables.
* @param object object is searched for variables.
* @param commands All found variables are returned in commands.
* @param variables All found variables are returned in variables.
*/
private findVariables(cmdVar: RegExp, object: any, commands: string[]) {
if (!object) {
return;
} else if (typeof object === 'string') {
private findVariables(object: any, variables: string[]) {
if (typeof object === 'string') {
let matches;
while ((matches = cmdVar.exec(object)) !== null) {
if (matches.length === 2) {
while ((matches = ConfigurationResolverService.INPUT_OR_COMMAND_VARIABLES_PATTERN.exec(object)) !== null) {
if (matches.length === 4) {
const command = matches[1];
if (commands.indexOf(command) < 0) {
commands.push(command);
if (variables.indexOf(command) < 0) {
variables.push(command);
}
}
}
} else if (Types.isArray(object)) {
object.forEach(value => {
this.findVariables(cmdVar, value, commands);
this.findVariables(value, variables);
});
} else {
} else if (object) {
Object.keys(object).forEach(key => {
const value = object[key];
this.findVariables(cmdVar, value, commands);
this.findVariables(value, variables);
});
}
}
/**
* Converts an array of inputs into an actaul array of typed, ConfiguredInputs.
* @param object Array of something that should look like inputs.
* Takes the provided input info and shows the quick pick so the user can provide the value for the input
* @param variable Name of the input variable.
* @param inputInfos Information about each possible input variable.
*/
private parseConfigurationInputs(object: any[]): ConfiguredInput[] | undefined {
let inputs = new Array<ConfiguredInput>();
if (object) {
object.forEach(item => {
if (Types.isString(item.id) && Types.isString(item.description) && Types.isString(item.type)) {
let type: ConfiguredInputType;
switch (item.type) {
case 'promptString': type = ConfiguredInputType.PromptString; break;
case 'pickString': type = ConfiguredInputType.PickString; break;
default: {
throw new Error(nls.localize('unknownInputTypeProvided', "Input '{0}' can only be of type 'promptString' or 'pickString'.", item.id));
}
}
let options: string[];
if (type === ConfiguredInputType.PickString) {
if (Types.isStringArray(item.options)) {
options = item.options;
} else {
throw new Error(nls.localize('pickStringRequiresOptions', "Input '{0}' is of type 'pickString' and must include 'options'.", item.id));
}
}
inputs.push({ id: item.id, description: item.description, type, default: item.default, options });
}
});
}
private showUserInput(variable: string, inputInfos: ConfiguredInput[]): Promise<string> {
return inputs;
// find info for the given input variable
const info = inputInfos.filter(item => item.id === variable).pop();
if (info) {
const missingAttribute = (attrName: string) => {
throw new Error(nls.localize('inputVariable.missingAttribute', "Input variable '{0}' is of type '{1}' and must include '{2}'.", variable, info.type, attrName));
};
switch (info.type) {
case 'promptString': {
if (!Types.isString(info.description)) {
missingAttribute('description');
}
const inputOptions: IInputOptions = { prompt: info.description };
if (info.default) {
inputOptions.value = info.default;
}
return this.quickInputService.input(inputOptions).then(resolvedInput => {
return resolvedInput ? resolvedInput : undefined;
});
}
case 'pickString': {
if (!Types.isString(info.description)) {
missingAttribute('description');
}
if (!Types.isStringArray(info.options)) {
missingAttribute('options');
}
const picks = new Array<IQuickPickItem>();
info.options.forEach(pickOption => {
const item: IQuickPickItem = { label: pickOption };
if (pickOption === info.default) {
item.description = nls.localize('inputVariable.defaultInputValue', "Default");
picks.unshift(item);
} else {
picks.push(item);
}
});
const pickOptions: IPickOptions<IQuickPickItem> = { placeHolder: info.description };
return this.quickInputService.pick(picks, pickOptions, undefined).then(resolvedInput => {
return resolvedInput ? resolvedInput.label : undefined;
});
}
case 'command': {
if (!Types.isString(info.command)) {
missingAttribute('command');
}
return this.commandService.executeCommand<string>(info.command, info.args).then(result => {
if (typeof result === 'string' || Types.isUndefinedOrNull(result)) {
return result;
}
throw new Error(nls.localize('inputVariable.command.noStringType', "Cannot substitute input variable '{0}' because command '{1}' did not return a result of type string.", variable, info.command));
});
}
default:
throw new Error(nls.localize('inputVariable.unknownType', "Input variable '{0}' can only be of type 'promptString', 'pickString', or 'command'.", variable));
}
}
return Promise.reject(new Error(nls.localize('inputVariable.undefinedVariable', "Undefined input variable '{0}' encountered. Remove or define '{0}' to continue.", variable)));
}
}

View File

@@ -14,7 +14,6 @@ import { localize } from 'vs/nls';
import { URI as uri } from 'vs/base/common/uri';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { TPromise } from 'vs/base/common/winjs.base';
export interface IVariableResolveContext {
getFolderUri(folderName: string): uri | undefined;
@@ -83,11 +82,11 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe
return { newConfig, resolvedVariables };
}
public resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any): TPromise<any> {
public resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any): Promise<any> {
throw new Error('resolveWithInteractionReplace not implemented.');
}
public resolveWithInteraction(folder: IWorkspaceFolder, config: any): TPromise<any> {
public resolveWithInteraction(folder: IWorkspaceFolder, config: any): Promise<any> {
throw new Error('resolveWithInteraction not implemented.');
}

View File

@@ -416,6 +416,33 @@ suite('Configuration Resolver Service', () => {
assert.equal(0, mockCommandService.callCount);
});
});
test('a single command input variable', () => {
const configuration = {
'name': 'Attach to Process',
'type': 'node',
'request': 'attach',
'processId': '${input:input4}',
'port': 5858,
'sourceMaps': false,
'outDir': null
};
return configurationResolverService.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => {
assert.deepEqual(result, {
'name': 'Attach to Process',
'type': 'node',
'request': 'attach',
'processId': 'arg for command',
'port': 5858,
'sourceMaps': false,
'outDir': null
});
assert.equal(1, mockCommandService.callCount);
});
});
test('several input variables and command', () => {
const configuration = {
@@ -423,6 +450,7 @@ suite('Configuration Resolver Service', () => {
'type': '${command:command1}',
'request': '${input:input1}',
'processId': '${input:input2}',
'command': '${input:input4}',
'port': 5858,
'sourceMaps': false,
'outDir': null
@@ -435,12 +463,13 @@ suite('Configuration Resolver Service', () => {
'type': 'command1-result',
'request': 'resolvedEnterinput1',
'processId': 'selectedPick',
'command': 'arg for command',
'port': 5858,
'sourceMaps': false,
'outDir': null
});
assert.equal(1, mockCommandService.callCount);
assert.equal(2, mockCommandService.callCount);
});
});
});
@@ -450,7 +479,7 @@ class MockConfigurationService implements IConfigurationService {
public _serviceBrand: any;
public serviceId = IConfigurationService;
public constructor(private configuration: any = {}) { }
public inspect<T>(key: string, overrides?: IConfigurationOverrides): any { return { value: getConfigurationValue<T>(this.getValue(), key), default: getConfigurationValue<T>(this.getValue(), key), user: getConfigurationValue<T>(this.getValue(), key), workspaceFolder: void 0, folder: void 0 }; }
public inspect<T>(key: string, overrides?: IConfigurationOverrides): any { return { value: getConfigurationValue<T>(this.getValue(), key), default: getConfigurationValue<T>(this.getValue(), key), user: getConfigurationValue<T>(this.getValue(), key), workspaceFolder: undefined, folder: undefined }; }
public keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; }
public getValue(): any;
public getValue(value: string): any;
@@ -495,9 +524,9 @@ class MockCommandService implements ICommandService {
class MockQuickInputService implements IQuickInputService {
_serviceBrand: any;
public pick<T extends IQuickPickItem>(picks: Thenable<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: true }, token?: CancellationToken): Promise<T[]>;
public pick<T extends IQuickPickItem>(picks: Thenable<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: false }, token?: CancellationToken): Promise<T>;
public pick<T extends IQuickPickItem>(picks: Thenable<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: Omit<IPickOptions<T>, 'canPickMany'>, token?: CancellationToken): Promise<T> {
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: true }, token?: CancellationToken): Promise<T[]>;
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: false }, token?: CancellationToken): Promise<T>;
public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: Omit<IPickOptions<T>, 'canPickMany'>, token?: CancellationToken): Promise<T> {
if (Types.isArray(picks)) {
return Promise.resolve(<T>{ label: 'selectedPick', description: 'pick description' });
} else {
@@ -568,6 +597,14 @@ class MockInputsConfigurationService extends TestConfigurationService {
type: 'promptString',
description: 'Enterinput3',
default: 'default input3'
},
{
id: 'input4',
type: 'command',
command: 'command1',
args: {
value: 'arg for command'
}
}
]
};

View File

@@ -27,9 +27,9 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
get onDidContextMenu(): Event<void> { return this._onDidContextMenu.event; }
constructor(
@INotificationService private notificationService: INotificationService,
@ITelemetryService private telemetryService: ITelemetryService,
@IKeybindingService private keybindingService: IKeybindingService
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IKeybindingService private readonly keybindingService: IKeybindingService
) {
super();
}
@@ -39,7 +39,7 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
if (actions.length) {
const onHide = once(() => {
if (delegate.onHide) {
delegate.onHide(undefined);
delegate.onHide(false);
}
this._onDidContextMenu.fire();
@@ -67,13 +67,13 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
popup(menu, {
x: Math.floor(x),
y: Math.floor(y),
positioningItem: delegate.autoSelectFirstItem ? 0 : void 0,
positioningItem: delegate.autoSelectFirstItem ? 0 : undefined,
onHide: () => onHide()
});
}
}
private createMenu(delegate: IContextMenuDelegate, entries: (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));
@@ -99,7 +99,7 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
const item: IContextMenuItem = {
label: unmnemonicLabel(entry.label),
checked: !!entry.checked || !!entry.radio,
type: !!entry.checked ? 'checkbox' : !!entry.radio ? 'radio' : void 0,
type: !!entry.checked ? 'checkbox' : !!entry.radio ? 'radio' : undefined,
enabled: !!entry.enabled,
click: event => {
@@ -142,6 +142,6 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
const context = delegate.getActionsContext ? delegate.getActionsContext(event) : event;
const res = actionRunner.run(actionToRun, context) || Promise.resolve(null);
res.then(null, e => this.notificationService.error(e));
res.then(undefined, e => this.notificationService.error(e));
}
}

View File

@@ -59,8 +59,8 @@ export class CrashReporterService implements ICrashReporterService {
private isEnabled: boolean;
constructor(
@ITelemetryService private telemetryService: ITelemetryService,
@IWindowsService private windowsService: IWindowsService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IWindowsService private readonly windowsService: IWindowsService,
@IConfigurationService configurationService: IConfigurationService
) {
const config = configurationService.getValue<ICrashReporterConfig>(TELEMETRY_SECTION_ID);
@@ -121,6 +121,6 @@ export class CrashReporterService implements ICrashReporterService {
return childProcessOptions;
}
return void 0;
return undefined;
}
}

View File

@@ -31,7 +31,7 @@ export interface IDecoration {
export interface IDecorationsProvider {
readonly label: string;
readonly onDidChange: Event<URI[]>;
provideDecorations(uri: URI, token: CancellationToken): IDecorationData | Thenable<IDecorationData> | undefined;
provideDecorations(uri: URI, token: CancellationToken): IDecorationData | Promise<IDecorationData> | undefined;
}
export interface IResourceDecorationChangeEvent {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { Event, Emitter, debounceEvent, anyEvent } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IDecorationsService, IDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IDecorationData } from './decorations';
import { TernarySearchTree } from 'vs/base/common/map';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
@@ -222,7 +222,7 @@ class FileDecorationChangeEvent implements IResourceDecorationChangeEvent {
class DecorationDataRequest {
constructor(
readonly source: CancellationTokenSource,
readonly thenable: Thenable<void>,
readonly thenable: Promise<void>,
) { }
}
@@ -339,13 +339,13 @@ export class FileDecorationsService implements IDecorationsService {
private readonly _data = new LinkedList<DecorationProviderWrapper>();
private readonly _onDidChangeDecorationsDelayed = new Emitter<URI | URI[]>();
private readonly _onDidChangeDecorations = new Emitter<IResourceDecorationChangeEvent>({ leakWarningThreshold: 500 });
private readonly _onDidChangeDecorations = new Emitter<IResourceDecorationChangeEvent>();
private readonly _decorationStyles: DecorationStyles;
private readonly _disposables: IDisposable[];
readonly onDidChangeDecorations: Event<IResourceDecorationChangeEvent> = anyEvent(
readonly onDidChangeDecorations: Event<IResourceDecorationChangeEvent> = Event.any(
this._onDidChangeDecorations.event,
debounceEvent<URI | URI[], FileDecorationChangeEvent>(
Event.debounce<URI | URI[], FileDecorationChangeEvent>(
this._onDidChangeDecorationsDelayed.event,
FileDecorationChangeEvent.debouncer,
undefined, undefined, 500

View File

@@ -7,7 +7,7 @@ import * as assert from 'assert';
import { FileDecorationsService } from 'vs/workbench/services/decorations/browser/decorationsService';
import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations';
import { URI } from 'vs/base/common/uri';
import { Event, toPromise, Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -46,11 +46,11 @@ suite('DecorationsService', function () {
assert.equal(callCounter, 1);
// event when result is computed
return toPromise(service.onDidChangeDecorations).then(e => {
return Event.toPromise(service.onDidChangeDecorations).then(e => {
assert.equal(e.affectsResource(uri), true);
// sync result
assert.deepEqual(service.getDecoration(uri, false).tooltip, 'T');
assert.deepEqual(service.getDecoration(uri, false)!.tooltip, 'T');
assert.equal(callCounter, 1);
});
});
@@ -70,7 +70,7 @@ suite('DecorationsService', function () {
});
// trigger -> sync
assert.deepEqual(service.getDecoration(uri, false).tooltip, 'Z');
assert.deepEqual(service.getDecoration(uri, false)!.tooltip, 'Z');
assert.equal(callCounter, 1);
});
@@ -88,7 +88,7 @@ suite('DecorationsService', function () {
});
// trigger -> sync
assert.deepEqual(service.getDecoration(uri, false).tooltip, 'J');
assert.deepEqual(service.getDecoration(uri, false)!.tooltip, 'J');
assert.equal(callCounter, 1);
// un-register -> ensure good event
@@ -121,10 +121,10 @@ suite('DecorationsService', function () {
let childUri = URI.parse('file:///some/path/some/file.txt');
let deco = service.getDecoration(childUri, false);
let deco = service.getDecoration(childUri, false)!;
assert.equal(deco.tooltip, '.txt');
deco = service.getDecoration(childUri.with({ path: 'some/path/' }), true);
deco = service.getDecoration(childUri.with({ path: 'some/path/' }), true)!;
assert.equal(deco, undefined);
reg.dispose();
@@ -139,10 +139,10 @@ suite('DecorationsService', function () {
}
});
deco = service.getDecoration(childUri, false);
deco = service.getDecoration(childUri, false)!;
assert.equal(deco.tooltip, '.txt.bubble');
deco = service.getDecoration(childUri.with({ path: 'some/path/' }), true);
deco = service.getDecoration(childUri.with({ path: 'some/path/' }), true)!;
assert.equal(typeof deco.tooltip, 'string');
});
@@ -152,7 +152,7 @@ suite('DecorationsService', function () {
let deco = service.getDecoration(someUri, false);
assert.equal(deco, undefined);
deco = service.getDecoration(someUri, false, { tooltip: 'Overwrite' });
deco = service.getDecoration(someUri, false, { tooltip: 'Overwrite' })!;
assert.equal(deco.tooltip, 'Overwrite');
let reg = service.registerDecorationsProvider({
@@ -163,10 +163,10 @@ suite('DecorationsService', function () {
}
});
deco = service.getDecoration(someUri, false);
deco = service.getDecoration(someUri, false)!;
assert.equal(deco.tooltip, 'FromMe');
deco = service.getDecoration(someUri, false, { source: 'foo', tooltip: 'O' });
deco = service.getDecoration(someUri, false, { source: 'foo', tooltip: 'O' })!;
assert.equal(deco.tooltip, 'O');
reg.dispose();
@@ -232,7 +232,7 @@ suite('DecorationsService', function () {
let data1 = service.getDecoration(URI.parse('a:b/'), true);
assert.ok(!data1);
let data2 = service.getDecoration(URI.parse('a:b/c.hello'), false);
let data2 = service.getDecoration(URI.parse('a:b/c.hello'), false)!;
assert.ok(data2.tooltip);
let data3 = service.getDecoration(URI.parse('a:b/'), true);
@@ -259,19 +259,19 @@ suite('DecorationsService', function () {
let uri = URI.parse('foo:/folder/file.ts');
let uri2 = URI.parse('foo:/folder/');
let data = service.getDecoration(uri, true);
let data = service.getDecoration(uri, true)!;
assert.equal(data.tooltip, 'FOO');
data = service.getDecoration(uri2, true);
data = service.getDecoration(uri2, true)!;
assert.ok(data.tooltip); // emphazied items...
gone = true;
emitter.fire([uri]);
data = service.getDecoration(uri, true);
data = service.getDecoration(uri, true)!;
assert.equal(data, undefined);
data = service.getDecoration(uri2, true);
data = service.getDecoration(uri2, true)!;
assert.equal(data, undefined);
reg.dispose();
@@ -294,10 +294,10 @@ suite('DecorationsService', function () {
let uri = URI.parse('foo:/folder/file.ts');
let uri2 = URI.parse('foo:/folder/');
let data = service.getDecoration(uri, true);
let data = service.getDecoration(uri, true)!;
assert.equal(data.tooltip, 'FOO');
data = service.getDecoration(uri2, true);
data = service.getDecoration(uri2, true)!;
assert.ok(data.tooltip); // emphazied items...
return new Promise((resolve, reject) => {

View File

@@ -39,11 +39,11 @@ export class DialogService implements IDialogService {
_serviceBrand: any;
constructor(
@IWindowService private windowService: IWindowService,
@ILogService private logService: ILogService
@IWindowService private readonly windowService: IWindowService,
@ILogService private readonly logService: ILogService
) { }
confirm(confirmation: IConfirmation): Thenable<IConfirmationResult> {
confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
this.logService.trace('DialogService#confirm', confirmation.message);
const { options, buttonIndexMap } = this.massageMessageBoxOptions(this.getConfirmOptions(confirmation));
@@ -93,23 +93,23 @@ export class DialogService implements IDialogService {
return opts;
}
show(severity: Severity, message: string, buttons: string[], dialogOptions?: IDialogOptions): Thenable<number> {
show(severity: Severity, message: string, buttons: string[], dialogOptions?: IDialogOptions): Promise<number> {
this.logService.trace('DialogService#show', message);
const { options, buttonIndexMap } = this.massageMessageBoxOptions({
message,
buttons,
type: (severity === Severity.Info) ? 'question' : (severity === Severity.Error) ? 'error' : (severity === Severity.Warning) ? 'warning' : 'none',
cancelId: dialogOptions ? dialogOptions.cancelId : void 0,
detail: dialogOptions ? dialogOptions.detail : void 0
cancelId: dialogOptions ? dialogOptions.cancelId : undefined,
detail: dialogOptions ? dialogOptions.detail : undefined
});
return this.windowService.showMessageBox(options).then(result => buttonIndexMap[result.button]);
}
private massageMessageBoxOptions(options: Electron.MessageBoxOptions): IMassagedMessageBoxOptions {
let buttonIndexMap = options.buttons.map((button, index) => index);
let buttons = options.buttons.map(button => mnemonicButtonLabel(button));
let buttonIndexMap = (options.buttons || []).map((button, index) => index);
let buttons = (options.buttons || []).map(button => mnemonicButtonLabel(button));
let cancelId = options.cancelId;
// Linux: order of buttons is reverse
@@ -157,45 +157,46 @@ export class FileDialogService implements IFileDialogService {
_serviceBrand: any;
constructor(
@IWindowService private windowService: IWindowService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IHistoryService private historyService: IHistoryService,
@IEnvironmentService private environmentService: IEnvironmentService
@IWindowService private readonly windowService: IWindowService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IHistoryService private readonly historyService: IHistoryService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) { }
public defaultFilePath(schemeFilter: string): URI {
let candidate: URI;
defaultFilePath(schemeFilter: string): URI | undefined {
// Check for last active file first...
candidate = this.historyService.getLastActiveFile(schemeFilter);
let candidate = this.historyService.getLastActiveFile(schemeFilter);
// ...then for last active file root
if (!candidate) {
candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter);
}
return candidate ? resources.dirname(candidate) : void 0;
return candidate && resources.dirname(candidate) || undefined;
}
public defaultFolderPath(schemeFilter: string): URI {
let candidate: URI;
defaultFolderPath(schemeFilter: string): URI | undefined {
// Check for last active file root first...
candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter);
let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter);
// ...then for last active file
if (!candidate) {
candidate = this.historyService.getLastActiveFile(schemeFilter);
}
return candidate ? resources.dirname(candidate) : void 0;
return candidate && resources.dirname(candidate) || undefined;
}
public defaultWorkspacePath(schemeFilter: string): URI {
defaultWorkspacePath(schemeFilter: string): URI | undefined {
// Check for current workspace config file first...
if (schemeFilter === Schemas.file && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && !isUntitledWorkspace(this.contextService.getWorkspace().configuration.fsPath, this.environmentService)) {
return resources.dirname(this.contextService.getWorkspace().configuration);
if (schemeFilter === Schemas.file && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
const configuration = this.contextService.getWorkspace().configuration;
if (configuration && !isUntitledWorkspace(configuration.fsPath, this.environmentService)) {
return resources.dirname(configuration) || undefined;
}
}
// ...then fallback to default folder path
@@ -212,36 +213,39 @@ export class FileDialogService implements IFileDialogService {
};
}
public pickFileFolderAndOpen(options: IPickAndOpenOptions): Thenable<any> {
let defaultUri = options.defaultUri;
pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
const defaultUri = options.defaultUri;
if (!defaultUri) {
options.defaultUri = this.defaultFilePath(Schemas.file);
}
return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options));
return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options));
}
public pickFileAndOpen(options: IPickAndOpenOptions): Thenable<any> {
let defaultUri = options.defaultUri;
pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
const defaultUri = options.defaultUri;
if (!defaultUri) {
options.defaultUri = this.defaultFilePath(Schemas.file);
}
return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options));
}
public pickFolderAndOpen(options: IPickAndOpenOptions): Thenable<any> {
let defaultUri = options.defaultUri;
pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
const defaultUri = options.defaultUri;
if (!defaultUri) {
options.defaultUri = this.defaultFolderPath(Schemas.file);
}
return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options));
}
public pickWorkspaceAndOpen(options: IPickAndOpenOptions): Thenable<void> {
let defaultUri = options.defaultUri;
pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
const defaultUri = options.defaultUri;
if (!defaultUri) {
options.defaultUri = this.defaultWorkspacePath(Schemas.file);
}
return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options));
}
@@ -254,20 +258,22 @@ export class FileDialogService implements IFileDialogService {
};
}
public showSaveDialog(options: ISaveDialogOptions): Thenable<URI> {
showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
const defaultUri = options.defaultUri;
if (defaultUri && defaultUri.scheme !== Schemas.file) {
return Promise.reject(new Error('Not supported - Save-dialogs can only be opened on `file`-uris.'));
}
return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => {
if (result) {
return URI.file(result);
}
return void 0;
return undefined;
});
}
public showOpenDialog(options: IOpenDialogOptions): Thenable<URI[] | undefined> {
showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
const defaultUri = options.defaultUri;
if (defaultUri && defaultUri.scheme !== Schemas.file) {
return Promise.reject(new Error('Not supported - Open-dialogs can only be opened on `file`-uris.'));
@@ -280,17 +286,22 @@ export class FileDialogService implements IFileDialogService {
filters: options.filters,
properties: []
};
newOptions.properties.push('createDirectory');
newOptions.properties!.push('createDirectory');
if (options.canSelectFiles) {
newOptions.properties.push('openFile');
newOptions.properties!.push('openFile');
}
if (options.canSelectFolders) {
newOptions.properties.push('openDirectory');
newOptions.properties!.push('openDirectory');
}
if (options.canSelectMany) {
newOptions.properties.push('multiSelections');
newOptions.properties!.push('multiSelections');
}
return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : void 0);
return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined);
}
}

View File

@@ -13,12 +13,11 @@ import { ResourceMap } from 'vs/base/common/map';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IFileService } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { Event, once, Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { basename } from 'vs/base/common/paths';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/group/common/editorGroupsService';
import { IResourceEditor, ACTIVE_GROUP_TYPE, SIDE_GROUP_TYPE, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -62,12 +61,12 @@ export class EditorService extends Disposable implements EditorServiceImpl {
private lastActiveGroupId: GroupIdentifier;
constructor(
@IEditorGroupsService private editorGroupService: EditorGroupsServiceImpl,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IInstantiationService private instantiationService: IInstantiationService,
@ILabelService private labelService: ILabelService,
@IFileService private fileService: IFileService,
@IConfigurationService private configurationService: IConfigurationService,
@IEditorGroupsService private readonly editorGroupService: EditorGroupsServiceImpl,
@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ILabelService private readonly labelService: ILabelService,
@IFileService private readonly fileService: IFileService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
@@ -141,14 +140,13 @@ export class EditorService extends Disposable implements EditorServiceImpl {
this._onDidOpenEditorFail.fire({ editor, groupId: group.id });
}));
once(group.onWillDispose)(() => {
Event.once(group.onWillDispose)(() => {
dispose(groupDisposeables);
});
}
private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void {
for (let i = 0; i < this.openEditorHandlers.length; i++) {
const handler = this.openEditorHandlers[i];
for (const handler of this.openEditorHandlers) {
const result = handler(event.editor, event.options, group);
if (result && result.override) {
event.prevent((() => result.override));
@@ -160,7 +158,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
get activeControl(): IEditor {
const activeGroup = this.editorGroupService.activeGroup;
return activeGroup ? activeGroup.activeControl : void 0;
return activeGroup ? activeGroup.activeControl : undefined;
}
get activeTextEditorWidget(): ICodeEditor | IDiffEditor {
@@ -172,7 +170,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
}
return void 0;
return undefined;
}
get editors(): IEditorInput[] {
@@ -187,14 +185,14 @@ export class EditorService extends Disposable implements EditorServiceImpl {
get activeEditor(): IEditorInput {
const activeGroup = this.editorGroupService.activeGroup;
return activeGroup ? activeGroup.activeEditor : void 0;
return activeGroup ? activeGroup.activeEditor : undefined;
}
get visibleControls(): IEditor[] {
return coalesce(this.editorGroupService.groups.map(group => group.activeControl));
}
get visibleTextEditorWidgets(): (ICodeEditor | IDiffEditor)[] {
get visibleTextEditorWidgets(): Array<ICodeEditor | IDiffEditor> {
return this.visibleControls.map(control => control.getControl() as ICodeEditor | IDiffEditor).filter(widget => isCodeEditor(widget) || isDiffEditor(widget));
}
@@ -219,11 +217,11 @@ export class EditorService extends Disposable implements EditorServiceImpl {
//#region openEditor()
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<IEditor>;
openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<ITextEditor>;
openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<ITextDiffEditor>;
openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<ITextSideBySideEditor>;
openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: GroupIdentifier): TPromise<IEditor> {
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditor>;
openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextEditor>;
openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextDiffEditor>;
openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextSideBySideEditor>;
openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: GroupIdentifier): Promise<IEditor> {
// Typed Editor Support
if (editor instanceof EditorInput) {
@@ -242,11 +240,11 @@ export class EditorService extends Disposable implements EditorServiceImpl {
return this.doOpenEditor(targetGroup, typedInput, editorOptions);
}
return TPromise.wrap<IEditor>(null);
return Promise.resolve(null);
}
protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): TPromise<IEditor> {
return group.openEditor(editor, options).then(() => group.activeControl);
protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise<IEditor> {
return group.openEditor(editor, options);
}
private findTargetGroup(input: IEditorInput, options?: IEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): IEditorGroup {
@@ -273,8 +271,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Respect option to reveal an editor if it is already visible in any group
if (options && options.revealIfVisible) {
for (let i = 0; i < groupsByLastActive.length; i++) {
const group = groupsByLastActive[i];
for (const group of groupsByLastActive) {
if (input.matches(group.activeEditor)) {
targetGroup = group;
break;
@@ -284,8 +281,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Respect option to reveal an editor if it is open (not necessarily visible)
if ((options && options.revealIfOpened) || this.configurationService.getValue<boolean>('workbench.editor.revealIfOpen')) {
for (let i = 0; i < groupsByLastActive.length; i++) {
const group = groupsByLastActive[i];
for (const group of groupsByLastActive) {
if (group.isOpened(input)) {
targetGroup = group;
break;
@@ -330,9 +326,9 @@ export class EditorService extends Disposable implements EditorServiceImpl {
//#region openEditors()
openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<IEditor[]>;
openEditors(editors: IResourceEditor[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<IEditor[]>;
openEditors(editors: (IEditorInputWithOptions | IResourceEditor)[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<IEditor[]> {
openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditor[]>;
openEditors(editors: IResourceEditor[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditor[]>;
openEditors(editors: Array<IEditorInputWithOptions | IResourceEditor>, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditor[]> {
// Convert to typed editors and options
const typedEditors: IEditorInputWithOptions[] = [];
@@ -363,12 +359,12 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
// Open in targets
const result: TPromise<IEditor>[] = [];
const result: Promise<IEditor>[] = [];
mapGroupToEditors.forEach((editorsWithOptions, group) => {
result.push((group.openEditors(editorsWithOptions)).then(() => group.activeControl));
result.push(group.openEditors(editorsWithOptions));
});
return TPromise.join(result);
return Promise.all(result);
}
//#endregion
@@ -391,7 +387,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
if (!(editor instanceof EditorInput)) {
const resourceInput = editor as IResourceInput | IUntitledResourceInput;
if (!resourceInput.resource) {
return void 0; // we need a resource at least
return undefined; // we need a resource at least
}
}
@@ -405,8 +401,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
// For each editor group
for (let i = 0; i < groups.length; i++) {
const group = groups[i];
for (const group of groups) {
// Typed editor
if (editor instanceof EditorInput) {
@@ -417,8 +412,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Resource editor
else {
for (let j = 0; j < group.editors.length; j++) {
const editorInGroup = group.editors[j];
for (const editorInGroup of group.editors) {
const resource = toResource(editorInGroup, { supportSideBySide: true });
if (!resource) {
continue; // need a resource to compare with
@@ -432,16 +426,16 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
}
return void 0;
return undefined;
}
//#endregion
//#region replaceEditors()
replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): TPromise<void>;
replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): TPromise<void>;
replaceEditors(editors: (IEditorReplacement | IResourceEditorReplacement)[], group: IEditorGroup | GroupIdentifier): TPromise<void> {
replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
replaceEditors(editors: Array<IEditorReplacement | IResourceEditorReplacement>, group: IEditorGroup | GroupIdentifier): Promise<void> {
const typedEditors: IEditorReplacement[] = [];
editors.forEach(replaceEditorArg => {
@@ -584,7 +578,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
EditorService.CACHE.set(resource, input);
once(input.onDispose)(() => {
Event.once(input.onDispose)(() => {
EditorService.CACHE.delete(resource);
});
@@ -607,7 +601,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
export interface IEditorOpenHandler {
(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): TPromise<IEditor>;
(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise<IEditor>;
}
/**
@@ -639,12 +633,14 @@ export class DelegatingEditorService extends EditorService {
this.editorOpenHandler = handler;
}
protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): TPromise<IEditor> {
const handleOpen = this.editorOpenHandler ? this.editorOpenHandler(group, editor, options) : TPromise.as(void 0);
protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise<IEditor> {
if (!this.editorOpenHandler) {
return super.doOpenEditor(group, editor, options);
}
return handleOpen.then(control => {
return this.editorOpenHandler(group, editor, options).then(control => {
if (control) {
return TPromise.as<IEditor>(control); // the opening was handled, so return early
return control; // the opening was handled, so return early
}
return super.doOpenEditor(group, editor, options);

View File

@@ -9,7 +9,6 @@ import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntit
import { Event } from 'vs/base/common/event';
import { IEditor as ICodeEditor } from 'vs/editor/common/editorCommon';
import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/group/common/editorGroupsService';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable } from 'vs/base/common/lifecycle';
export const IEditorService = createDecorator<IEditorService>('editorService');
@@ -37,7 +36,7 @@ export interface IOpenEditorOverride {
* If defined, will prevent the opening of an editor and replace the resulting
* promise with the provided promise for the openEditor() call.
*/
override?: TPromise<any>;
override?: Promise<IEditor>;
}
export interface IEditorService {
@@ -111,11 +110,14 @@ export interface IEditorService {
* @param group the target group. If unspecified, the editor will open in the currently
* active group. Use `SIDE_GROUP_TYPE` to open the editor in a new editor group to the side
* of the currently active group.
*
* @returns the editor that opened or NULL if the operation failed or the editor was not
* opened to be active.
*/
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<IEditor>;
openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<ITextEditor>;
openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<ITextDiffEditor>;
openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<ITextSideBySideEditor>;
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditor>;
openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextEditor>;
openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextDiffEditor>;
openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextSideBySideEditor>;
/**
* Open editors in an editor group.
@@ -124,9 +126,12 @@ export interface IEditorService {
* @param group the target group. If unspecified, the editor will open in the currently
* active group. Use `SIDE_GROUP_TYPE` to open the editor in a new editor group to the side
* of the currently active group.
*
* @returns the editors that opened. The array can be empty or have less elements for editors
* that failed to open or were instructed to open as inactive.
*/
openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<ReadonlyArray<IEditor>>;
openEditors(editors: IResourceEditor[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): TPromise<ReadonlyArray<IEditor>>;
openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ReadonlyArray<IEditor>>;
openEditors(editors: IResourceEditor[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ReadonlyArray<IEditor>>;
/**
* Replaces editors in an editor group with the provided replacement.
@@ -136,8 +141,8 @@ export interface IEditorService {
* @returns a promise that is resolved when the replaced active
* editor (if any) has finished loading.
*/
replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): TPromise<void>;
replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): TPromise<void>;
replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
/**
* Find out if the provided editor (or resource of an editor) is opened in any or

View File

@@ -4,10 +4,610 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as paths from 'vs/base/common/paths';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { URI } from 'vs/base/common/uri';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorInput, EditorOptions, IFileEditorInput, IEditorInput } from 'vs/workbench/common/editor';
import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService';
import { IEditorGroup, IEditorGroupsService, GroupDirection } from 'vs/workbench/services/group/common/editorGroupsService';
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Registry } from 'vs/platform/registry/common/platform';
import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { CancellationToken } from 'vs/base/common/cancellation';
import { timeout } from 'vs/base/common/async';
// {{SQL CARBON EDIT}} - Remove tests
// {{SQL CARBON EDIT}} - Disable editor tests
/*
export class TestEditorControl extends BaseEditor {
constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyTestEditorForEditorService', NullTelemetryService, new TestThemeService(), new TestStorageService()); }
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
super.setInput(input, options, token);
return input.resolve().then(() => undefined);
}
getId(): string { return 'MyTestEditorForEditorService'; }
layout(): void { }
createEditor(): any { }
}
export class TestEditorInput extends EditorInput implements IFileEditorInput {
public gotDisposed: boolean;
private fails: boolean;
constructor(private resource: URI) { super(); }
getTypeId() { return 'testEditorInputForEditorService'; }
resolve(): Promise<IEditorModel> { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); }
matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; }
setEncoding(encoding: string) { }
getEncoding(): string { return null; }
setPreferredEncoding(encoding: string) { }
getResource(): URI { return this.resource; }
setForceOpenAsBinary(): void { }
setFailToOpen(): void {
this.fails = true;
}
dispose(): void {
super.dispose();
this.gotDisposed = true;
}
}
*/
suite('Editor service', () => {
/*
function registerTestEditorInput(): void {
Registry.as<IEditorRegistry>(Extensions.Editors).registerEditor(new EditorDescriptor(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), new SyncDescriptor(TestEditorInput));
}
registerTestEditorInput();
test('basics', function () {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart, 'id', false);
part.create(document.createElement('div'));
part.layout(400, 300);
const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part]));
const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService);
const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-basics'));
const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-basics'));
let activeEditorChangeEventCounter = 0;
const activeEditorChangeListener = service.onDidActiveEditorChange(() => {
activeEditorChangeEventCounter++;
});
let visibleEditorChangeEventCounter = 0;
const visibleEditorChangeListener = service.onDidVisibleEditorsChange(() => {
visibleEditorChangeEventCounter++;
});
let didCloseEditorListenerCounter = 0;
const didCloseEditorListener = service.onDidCloseEditor(editor => {
didCloseEditorListenerCounter++;
});
return part.whenRestored.then(() => {
// Open input
return service.openEditor(input, { pinned: true }).then(editor => {
assert.ok(editor instanceof TestEditorControl);
assert.equal(editor, service.activeControl);
assert.equal(input, service.activeEditor);
assert.equal(service.visibleControls.length, 1);
assert.equal(service.visibleControls[0], editor);
assert.ok(!service.activeTextEditorWidget);
assert.equal(service.visibleTextEditorWidgets.length, 0);
assert.equal(service.isOpen(input), true);
assert.equal(service.getOpened({ resource: input.getResource() }), input);
assert.equal(service.isOpen(input, part.activeGroup), true);
assert.equal(activeEditorChangeEventCounter, 1);
assert.equal(visibleEditorChangeEventCounter, 1);
// Close input
return editor.group.closeEditor(input).then(() => {
assert.equal(didCloseEditorListenerCounter, 1);
assert.equal(activeEditorChangeEventCounter, 2);
assert.equal(visibleEditorChangeEventCounter, 2);
assert.ok(input.gotDisposed);
// Open again 2 inputs
return service.openEditor(input, { pinned: true }).then(editor => {
return service.openEditor(otherInput, { pinned: true }).then(editor => {
assert.equal(service.visibleControls.length, 1);
assert.equal(service.isOpen(input), true);
assert.equal(service.isOpen(otherInput), true);
assert.equal(activeEditorChangeEventCounter, 4);
assert.equal(visibleEditorChangeEventCounter, 4);
activeEditorChangeListener.dispose();
visibleEditorChangeListener.dispose();
didCloseEditorListener.dispose();
});
});
});
});
});
});
test('openEditors() / replaceEditors()', function () {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart, 'id', false);
part.create(document.createElement('div'));
part.layout(400, 300);
const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part]));
const service: IEditorService = testInstantiationService.createInstance(EditorService);
const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-openEditors'));
const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openEditors'));
const replaceInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource3-openEditors'));
return part.whenRestored.then(() => {
// Open editors
return service.openEditors([{ editor: input }, { editor: otherInput }]).then(() => {
assert.equal(part.activeGroup.count, 2);
return service.replaceEditors([{ editor: input, replacement: replaceInput }], part.activeGroup).then(() => {
assert.equal(part.activeGroup.count, 2);
assert.equal(part.activeGroup.getIndexOfEditor(replaceInput), 0);
});
});
});
});
test('caching', function () {
const instantiationService = workbenchInstantiationService();
const service: EditorService = <any>instantiationService.createInstance(EditorService);
// Cached Input (Files)
const fileResource1 = toFileResource(this, '/foo/bar/cache1.js');
const fileInput1 = service.createInput({ resource: fileResource1 });
assert.ok(fileInput1);
const fileResource2 = toFileResource(this, '/foo/bar/cache2.js');
const fileInput2 = service.createInput({ resource: fileResource2 });
assert.ok(fileInput2);
assert.notEqual(fileInput1, fileInput2);
const fileInput1Again = service.createInput({ resource: fileResource1 });
assert.equal(fileInput1Again, fileInput1);
fileInput1Again.dispose();
assert.ok(fileInput1.isDisposed());
const fileInput1AgainAndAgain = service.createInput({ resource: fileResource1 });
assert.notEqual(fileInput1AgainAndAgain, fileInput1);
assert.ok(!fileInput1AgainAndAgain.isDisposed());
// Cached Input (Resource)
const resource1 = toResource.call(this, '/foo/bar/cache1.js');
const input1 = service.createInput({ resource: resource1 });
assert.ok(input1);
const resource2 = toResource.call(this, '/foo/bar/cache2.js');
const input2 = service.createInput({ resource: resource2 });
assert.ok(input2);
assert.notEqual(input1, input2);
const input1Again = service.createInput({ resource: resource1 });
assert.equal(input1Again, input1);
input1Again.dispose();
assert.ok(input1.isDisposed());
const input1AgainAndAgain = service.createInput({ resource: resource1 });
assert.notEqual(input1AgainAndAgain, input1);
assert.ok(!input1AgainAndAgain.isDisposed());
});
test('createInput', function () {
const instantiationService = workbenchInstantiationService();
const service: EditorService = <any>instantiationService.createInstance(EditorService);
// Untyped Input (file)
let input = service.createInput({ resource: toFileResource(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof FileEditorInput);
let contentInput = <FileEditorInput>input;
assert.strictEqual(contentInput.getResource().fsPath, toFileResource(this, '/index.html').fsPath);
// Untyped Input (file, encoding)
input = service.createInput({ resource: toFileResource(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof FileEditorInput);
contentInput = <FileEditorInput>input;
assert.equal(contentInput.getPreferredEncoding(), 'utf16le');
// Untyped Input (untitled)
input = service.createInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof UntitledEditorInput);
// Untyped Input (untitled with contents)
input = service.createInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof UntitledEditorInput);
// Untyped Input (untitled with file path)
input = service.createInput({ filePath: '/some/path.txt', options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof UntitledEditorInput);
assert.ok((input as UntitledEditorInput).hasAssociatedFilePath);
});
test('delegate', function (done) {
const instantiationService = workbenchInstantiationService();
class MyEditor extends BaseEditor {
constructor(id: string) {
super(id, null, new TestThemeService(), new TestStorageService());
}
getId(): string {
return 'myEditor';
}
layout(): void { }
createEditor(): any { }
}
const ed = instantiationService.createInstance(MyEditor, 'my.editor');
const inp = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.parse('my://resource-delegate'));
const delegate = instantiationService.createInstance(DelegatingEditorService);
delegate.setEditorOpenHandler((group: IEditorGroup, input: IEditorInput, options?: EditorOptions) => {
assert.strictEqual(input, inp);
done();
return Promise.resolve(ed);
});
delegate.openEditor(inp);
});
test('close editor does not dispose when editor opened in other group', function () {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart, 'id', false);
part.create(document.createElement('div'));
part.layout(400, 300);
const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part]));
const service: IEditorService = testInstantiationService.createInstance(EditorService);
const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-close1'));
const rootGroup = part.activeGroup;
const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
return part.whenRestored.then(() => {
// Open input
return service.openEditor(input, { pinned: true }).then(editor => {
return service.openEditor(input, { pinned: true }, rightGroup).then(editor => {
const editors = service.editors;
assert.equal(editors.length, 2);
assert.equal(editors[0], input);
assert.equal(editors[1], input);
// Close input
return rootGroup.closeEditor(input).then(() => {
assert.equal(input.isDisposed(), false);
return rightGroup.closeEditor(input).then(() => {
assert.equal(input.isDisposed(), true);
});
});
});
});
});
});
test('open to the side', function () {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart, 'id', false);
part.create(document.createElement('div'));
part.layout(400, 300);
const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part]));
const service: IEditorService = testInstantiationService.createInstance(EditorService);
const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside'));
const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside'));
const rootGroup = part.activeGroup;
return part.whenRestored.then(() => {
return service.openEditor(input1, { pinned: true }, rootGroup).then(editor => {
return service.openEditor(input1, { pinned: true, preserveFocus: true }, SIDE_GROUP).then(editor => {
assert.equal(part.activeGroup, rootGroup);
assert.equal(part.count, 2);
assert.equal(editor.group, part.groups[1]);
// Open to the side uses existing neighbour group if any
return service.openEditor(input2, { pinned: true, preserveFocus: true }, SIDE_GROUP).then(editor => {
assert.equal(part.activeGroup, rootGroup);
assert.equal(part.count, 2);
assert.equal(editor.group, part.groups[1]);
});
});
});
});
});
test('active editor change / visible editor change events', async function () {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart, 'id', false);
part.create(document.createElement('div'));
part.layout(400, 300);
const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part]));
const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService);
const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active'));
const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active'));
let activeEditorChangeEventFired = false;
const activeEditorChangeListener = service.onDidActiveEditorChange(() => {
activeEditorChangeEventFired = true;
});
let visibleEditorChangeEventFired = false;
const visibleEditorChangeListener = service.onDidVisibleEditorsChange(() => {
visibleEditorChangeEventFired = true;
});
function assertActiveEditorChangedEvent(expected: boolean) {
assert.equal(activeEditorChangeEventFired, expected, `Unexpected active editor change state (got ${activeEditorChangeEventFired}, expected ${expected})`);
activeEditorChangeEventFired = false;
}
function assertVisibleEditorsChangedEvent(expected: boolean) {
assert.equal(visibleEditorChangeEventFired, expected, `Unexpected visible editors change state (got ${visibleEditorChangeEventFired}, expected ${expected})`);
visibleEditorChangeEventFired = false;
}
async function closeEditorAndWaitForNextToOpen(group: IEditorGroup, input: EditorInput): Promise<void> {
await group.closeEditor(input);
await timeout(0); // closing an editor will not immediately open the next one, so we need to wait
}
await part.whenRestored;
// 1.) open, open same, open other, close
let editor = await service.openEditor(input, { pinned: true });
const group = editor.group;
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
editor = await service.openEditor(input);
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
editor = await service.openEditor(otherInput);
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
await closeEditorAndWaitForNextToOpen(group, otherInput);
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
await closeEditorAndWaitForNextToOpen(group, input);
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
// 2.) open, open same (forced open)
editor = await service.openEditor(input);
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
editor = await service.openEditor(input, { forceReload: true });
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
await closeEditorAndWaitForNextToOpen(group, input);
// 3.) open, open inactive, close
editor = await service.openEditor(input, { pinned: true });
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
editor = await service.openEditor(otherInput, { inactive: true });
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
await group.closeAllEditors();
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
// 4.) open, open inactive, close inactive
editor = await service.openEditor(input, { pinned: true });
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
editor = await service.openEditor(otherInput, { inactive: true });
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
await closeEditorAndWaitForNextToOpen(group, otherInput);
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
await group.closeAllEditors();
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
// 5.) add group, remove group
editor = await service.openEditor(input, { pinned: true });
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
let rightGroup = part.addGroup(part.activeGroup, GroupDirection.RIGHT);
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
rightGroup.focus();
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(false);
part.removeGroup(rightGroup);
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(false);
await group.closeAllEditors();
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
// 6.) open editor in inactive group
editor = await service.openEditor(input, { pinned: true });
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
rightGroup = part.addGroup(part.activeGroup, GroupDirection.RIGHT);
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
await rightGroup.openEditor(otherInput);
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
await closeEditorAndWaitForNextToOpen(rightGroup, otherInput);
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
await group.closeAllEditors();
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
// 7.) activate group
editor = await service.openEditor(input, { pinned: true });
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
rightGroup = part.addGroup(part.activeGroup, GroupDirection.RIGHT);
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
await rightGroup.openEditor(otherInput);
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
group.focus();
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(false);
await closeEditorAndWaitForNextToOpen(rightGroup, otherInput);
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(true);
await group.closeAllEditors();
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
// 8.) move editor
editor = await service.openEditor(input, { pinned: true });
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
editor = await service.openEditor(otherInput, { pinned: true });
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
group.moveEditor(otherInput, group, { index: 0 });
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
await group.closeAllEditors();
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
// 9.) close editor in inactive group
editor = await service.openEditor(input, { pinned: true });
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
rightGroup = part.addGroup(part.activeGroup, GroupDirection.RIGHT);
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(false);
await rightGroup.openEditor(otherInput);
assertActiveEditorChangedEvent(true);
assertVisibleEditorsChangedEvent(true);
await closeEditorAndWaitForNextToOpen(group, input);
assertActiveEditorChangedEvent(false);
assertVisibleEditorsChangedEvent(true);
// cleanup
activeEditorChangeListener.dispose();
visibleEditorChangeListener.dispose();
});
test('openEditor returns NULL when opening fails or is inactive', async function () {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart, 'id', false);
part.create(document.createElement('div'));
part.layout(400, 300);
const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part]));
const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService);
const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active'));
const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-inactive'));
const failingInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource3-failing'));
failingInput.setFailToOpen();
await part.whenRestored;
let editor = await service.openEditor(input, { pinned: true });
assert.ok(editor);
let otherEditor = await service.openEditor(otherInput, { inactive: true });
assert.ok(!otherEditor);
let failingEditor = await service.openEditor(failingInput);
assert.ok(!failingEditor);
});
*/
});
/*
function toResource(path: string) {
return URI.from({ scheme: 'custom', path });
}
function toFileResource(self: any, path: string) {
return URI.file(paths.join('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path));
}
*/

View File

@@ -8,35 +8,20 @@ import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionIdentifier, IExtensionManifest, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
export interface IExtensionDescription {
readonly id: string;
readonly name: string;
export interface IExtensionDescription extends IExtensionManifest {
readonly identifier: ExtensionIdentifier;
readonly uuid?: string;
readonly displayName?: string;
readonly version: string;
readonly publisher: string;
readonly isBuiltin: boolean;
readonly isUnderDevelopment: boolean;
readonly extensionLocation: URI;
readonly extensionDependencies?: string[];
readonly activationEvents?: string[];
readonly engines: {
vscode: string;
// {{SQL CARBON EDIT}}
azdata?: string;
};
readonly main?: string;
readonly contributes?: { [point: string]: any; };
readonly keywords?: string[];
readonly repository?: {
url: string;
};
enableProposedApi?: boolean;
}
export const nullExtensionDescription = Object.freeze(<IExtensionDescription>{
id: 'nullExtensionDescription',
identifier: new ExtensionIdentifier('nullExtensionDescription'),
name: 'Null Extension Description',
version: '0.0.0',
publisher: 'vscode',
@@ -51,7 +36,7 @@ export const IExtensionService = createDecorator<IExtensionService>('extensionSe
export interface IMessage {
type: Severity;
message: string;
extensionId: string;
extensionId: ExtensionIdentifier;
extensionPointId: string;
}
@@ -104,7 +89,7 @@ export interface IExtensionHostProfile {
/**
* Extension id or one of the four known program states.
*/
export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self';
export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self' | null;
export class ActivationTimes {
constructor(
@@ -131,7 +116,7 @@ export const ExtensionHostLogFileName = 'exthost';
export interface IWillActivateEvent {
readonly event: string;
readonly activation: Thenable<void>;
readonly activation: Promise<void>;
}
export interface IResponsiveStateChangeEvent {
@@ -156,7 +141,12 @@ export interface IExtensionService extends ICpuProfilerTarget {
* Fired when extensions status changes.
* The event contains the ids of the extensions that have changed.
*/
onDidChangeExtensionsStatus: Event<string[]>;
onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]>;
/**
* Fired when the available extensions change (i.e. when extensions are added or removed).
*/
onDidChangeExtensions: Event<void>;
/**
* An event that is fired when activation happens.
@@ -172,7 +162,7 @@ export interface IExtensionService extends ICpuProfilerTarget {
/**
* Send an activation event and activate interested extensions.
*/
activateByEvent(activationEvent: string): Thenable<void>;
activateByEvent(activationEvent: string): Promise<void>;
/**
* An promise that resolves when the installed extensions are registered after
@@ -191,6 +181,18 @@ export interface IExtensionService extends ICpuProfilerTarget {
*/
getExtension(id: string): Promise<IExtensionDescription | undefined>;
/**
* Returns `true` if the given extension can be added. Otherwise `false`.
* @param extension An extension
*/
canAddExtension(extension: IExtensionDescription): boolean;
/**
* Returns `true` if the given extension can be removed. Otherwise `false`.
* @param extension An extension
*/
canRemoveExtension(extension: IExtensionDescription): boolean;
/**
* Read all contributions to an extension point.
*/
@@ -246,5 +248,14 @@ export function checkProposedApiEnabled(extension: IExtensionDescription): void
}
export function throwProposedApiError(extension: IExtensionDescription): never {
throw new Error(`[${extension.id}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.id}`);
throw new Error(`[${extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.identifier.value}`);
}
export function toExtension(extensionDescription: IExtensionDescription): IExtension {
return {
type: extensionDescription.isBuiltin ? ExtensionType.System : ExtensionType.User,
identifier: { id: getGalleryExtensionId(extensionDescription.publisher, extensionDescription.name), uuid: extensionDescription.uuid },
manifest: extensionDescription,
location: extensionDescription.extensionLocation,
};
}

View File

@@ -11,6 +11,7 @@ import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/co
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionDescription, IMessage } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
const hasOwnProperty = Object.hasOwnProperty;
const schemaRegistry = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution);
@@ -35,7 +36,7 @@ export class ExtensionMessageCollector {
this._messageHandler({
type: type,
message: message,
extensionId: this._extension.id,
extensionId: this._extension.identifier,
extensionPointId: this._extensionPointId
});
}
@@ -60,7 +61,7 @@ export interface IExtensionPointUser<T> {
}
export interface IExtensionPointHandler<T> {
(extensions: IExtensionPointUser<T>[]): void;
(extensions: IExtensionPointUser<T>[], delta: ExtensionPointUserDelta<T>): void;
}
export interface IExtensionPoint<T> {
@@ -68,22 +69,60 @@ export interface IExtensionPoint<T> {
setHandler(handler: IExtensionPointHandler<T>): void;
}
export class ExtensionPointUserDelta<T> {
private static _toSet<T>(arr: IExtensionPointUser<T>[]): Set<string> {
const result = new Set<string>();
for (let i = 0, len = arr.length; i < len; i++) {
result.add(ExtensionIdentifier.toKey(arr[i].description.identifier));
}
return result;
}
public static compute<T>(previous: IExtensionPointUser<T>[] | null, current: IExtensionPointUser<T>[]): ExtensionPointUserDelta<T> {
if (!previous || !previous.length) {
return new ExtensionPointUserDelta<T>(current, []);
}
if (!current || !current.length) {
return new ExtensionPointUserDelta<T>([], previous);
}
const previousSet = this._toSet(previous);
const currentSet = this._toSet(current);
let added = current.filter(user => !previousSet.has(ExtensionIdentifier.toKey(user.description.identifier)));
let removed = previous.filter(user => !currentSet.has(ExtensionIdentifier.toKey(user.description.identifier)));
return new ExtensionPointUserDelta<T>(added, removed);
}
constructor(
public readonly added: IExtensionPointUser<T>[],
public readonly removed: IExtensionPointUser<T>[],
) { }
}
export class ExtensionPoint<T> implements IExtensionPoint<T> {
public readonly name: string;
private _handler: IExtensionPointHandler<T> | null;
private _users: IExtensionPointUser<T>[] | null;
private _done: boolean;
public readonly isDynamic: boolean;
constructor(name: string) {
private _handler: IExtensionPointHandler<T> | null;
private _handlerCalled: boolean;
private _users: IExtensionPointUser<T>[] | null;
private _delta: ExtensionPointUserDelta<T> | null;
constructor(name: string, isDynamic: boolean) {
this.name = name;
this.isDynamic = isDynamic;
this._handler = null;
this._handlerCalled = false;
this._users = null;
this._done = false;
this._delta = null;
}
setHandler(handler: IExtensionPointHandler<T>): void {
if (this._handler !== null || this._done) {
if (this._handler !== null) {
throw new Error('Handler already set!');
}
this._handler = handler;
@@ -91,27 +130,23 @@ export class ExtensionPoint<T> implements IExtensionPoint<T> {
}
acceptUsers(users: IExtensionPointUser<T>[]): void {
if (this._users !== null || this._done) {
throw new Error('Users already set!');
}
this._delta = ExtensionPointUserDelta.compute(this._users, users);
this._users = users;
this._handle();
}
private _handle(): void {
if (this._handler === null || this._users === null) {
if (this._handler === null || this._users === null || this._delta === null) {
return;
}
this._done = true;
let handler = this._handler;
this._handler = null;
let users = this._users;
this._users = null;
if (this._handlerCalled && !this.isDynamic) {
throw new Error('The extension point is not dynamic!');
}
try {
handler(users);
this._handlerCalled = true;
this._handler(this._users, this._delta);
} catch (err) {
onUnexpectedError(err);
}
@@ -331,6 +366,13 @@ export const schema = {
}
};
export interface IExtensionPointDescriptor {
isDynamic?: boolean;
extensionPoint: string;
deps?: IExtensionPoint<any>[];
jsonSchema: IJSONSchema;
}
export class ExtensionsRegistryImpl {
private _extensionPoints: { [extPoint: string]: ExtensionPoint<any>; };
@@ -339,14 +381,14 @@ export class ExtensionsRegistryImpl {
this._extensionPoints = {};
}
public registerExtensionPoint<T>(extensionPoint: string, deps: IExtensionPoint<any>[], jsonSchema: IJSONSchema): IExtensionPoint<T> {
if (hasOwnProperty.call(this._extensionPoints, extensionPoint)) {
throw new Error('Duplicate extension point: ' + extensionPoint);
public registerExtensionPoint<T>(desc: IExtensionPointDescriptor): IExtensionPoint<T> {
if (hasOwnProperty.call(this._extensionPoints, desc.extensionPoint)) {
throw new Error('Duplicate extension point: ' + desc.extensionPoint);
}
let result = new ExtensionPoint<T>(extensionPoint);
this._extensionPoints[extensionPoint] = result;
let result = new ExtensionPoint<T>(desc.extensionPoint, desc.isDynamic || false);
this._extensionPoints[desc.extensionPoint] = result;
schema.properties['contributes'].properties[extensionPoint] = jsonSchema;
schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema;
schemaRegistry.registerSchema(schemaId, schema);
return result;
@@ -355,6 +397,10 @@ export class ExtensionsRegistryImpl {
public getExtensionPoints(): ExtensionPoint<any>[] {
return Object.keys(this._extensionPoints).map(point => this._extensionPoints[point]);
}
public getExtensionPointsMap(): { [extPoint: string]: ExtensionPoint<any>; } {
return this._extensionPoints;
}
}
const PRExtensions = {

View File

@@ -16,7 +16,7 @@ import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions';
import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@@ -50,6 +50,7 @@ export class CachedExtensionScanner {
public readonly scannedExtensions: Promise<IExtensionDescription[]>;
private _scannedExtensionsResolve: (result: IExtensionDescription[]) => void;
private _scannedExtensionsReject: (err: any) => void;
public readonly translationConfig: Promise<Translations>;
constructor(
@INotificationService private readonly _notificationService: INotificationService,
@@ -61,31 +62,58 @@ export class CachedExtensionScanner {
this._scannedExtensionsResolve = resolve;
this._scannedExtensionsReject = reject;
});
this.translationConfig = CachedExtensionScanner._readTranslationConfig();
}
public startScanningExtensions(log: ILog): void {
CachedExtensionScanner._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log)
.then(({ system, user, development }) => {
let result: { [extensionId: string]: IExtensionDescription; } = {};
system.forEach((systemExtension) => {
result[systemExtension.id] = systemExtension;
});
user.forEach((userExtension) => {
if (result.hasOwnProperty(userExtension.id)) {
log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionLocation.fsPath, userExtension.extensionLocation.fsPath));
}
result[userExtension.id] = userExtension;
});
development.forEach(developedExtension => {
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath));
if (result.hasOwnProperty(developedExtension.id)) {
log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[developedExtension.id].extensionLocation.fsPath, developedExtension.extensionLocation.fsPath));
}
result[developedExtension.id] = developedExtension;
});
return Object.keys(result).map(name => result[name]);
})
.then(this._scannedExtensionsResolve, this._scannedExtensionsReject);
public async scanSingleExtension(path: string, isBuiltin: boolean, log: ILog): Promise<IExtensionDescription | null> {
const translations = await this.translationConfig;
const version = pkg.version;
const commit = product.commit;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.locale;
const input = new ExtensionScannerInput(version, commit, locale, devMode, path, isBuiltin, false, translations);
return ExtensionScanner.scanSingleExtension(input, log);
}
public async startScanningExtensions(log: ILog): Promise<void> {
try {
const translations = await this.translationConfig;
const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log, translations);
let result = new Map<string, IExtensionDescription>();
system.forEach((systemExtension) => {
const extensionKey = ExtensionIdentifier.toKey(systemExtension.identifier);
const extension = result.get(extensionKey);
if (extension) {
log.warn(systemExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, systemExtension.extensionLocation.fsPath));
}
result.set(extensionKey, systemExtension);
});
user.forEach((userExtension) => {
const extensionKey = ExtensionIdentifier.toKey(userExtension.identifier);
const extension = result.get(extensionKey);
if (extension) {
log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, userExtension.extensionLocation.fsPath));
}
result.set(extensionKey, userExtension);
});
development.forEach(developedExtension => {
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath));
const extensionKey = ExtensionIdentifier.toKey(developedExtension.identifier);
const extension = result.get(extensionKey);
if (extension) {
log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, developedExtension.extensionLocation.fsPath));
}
result.set(extensionKey, developedExtension);
});
let r: IExtensionDescription[] = [];
result.forEach((value) => r.push(value));
this._scannedExtensionsResolve(r);
} catch (err) {
this._scannedExtensionsReject(err);
}
}
private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise<void> {
@@ -198,86 +226,90 @@ export class CachedExtensionScanner {
return result;
}
private static _scanInstalledExtensions(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, extensionEnablementService: IExtensionEnablementService, log: ILog): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
const translationConfig: Promise<Translations> = platform.translationsConfigFile
? pfs.readFile(platform.translationsConfigFile, 'utf8').then((content) => {
try {
return JSON.parse(content) as Translations;
} catch (err) {
return Object.create(null);
}
}, (err) => {
return Object.create(null);
})
: Promise.resolve(Object.create(null));
return translationConfig.then((translations) => {
const version = pkg.version;
const commit = product.commit;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.locale;
const builtinExtensions = this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
BUILTIN_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations),
log
);
let finalBuiltinExtensions: Promise<IExtensionDescription[]> = builtinExtensions;
if (devMode) {
const builtInExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json'));
const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8')
.then<IBuiltInExtension[]>(raw => JSON.parse(raw));
const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json');
const controlFile = pfs.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
finalBuiltinExtensions = ExtensionScanner.mergeBuiltinExtensions(builtinExtensions, extraBuiltinExtensions);
private static async _readTranslationConfig(): Promise<Translations> {
if (platform.translationsConfigFile) {
try {
const content = await pfs.readFile(platform.translationsConfigFile, 'utf8');
return JSON.parse(content) as Translations;
} catch (err) {
// no problemo
}
}
return Object.create(null);
}
const userExtensions = (
extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath
? Promise.resolve([])
: this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
USER_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations),
log
)
private static _scanInstalledExtensions(
windowService: IWindowService,
notificationService: INotificationService,
environmentService: IEnvironmentService,
extensionEnablementService: IExtensionEnablementService,
log: ILog,
translations: Translations
): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
const version = pkg.version;
const commit = product.commit;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.locale;
const builtinExtensions = this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
BUILTIN_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations),
log
);
let finalBuiltinExtensions: Promise<IExtensionDescription[]> = builtinExtensions;
if (devMode) {
const builtInExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json'));
const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8')
.then<IBuiltInExtension[]>(raw => JSON.parse(raw));
const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json');
const controlFile = pfs.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
finalBuiltinExtensions = ExtensionScanner.mergeBuiltinExtensions(builtinExtensions, extraBuiltinExtensions);
}
const userExtensions = (
extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath
? Promise.resolve([])
: this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
USER_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations),
log
)
);
// Always load developed extensions while extensions development
let developedExtensions: Promise<IExtensionDescription[]> = Promise.resolve([]);
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI && environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) {
developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, fsPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log
);
}
// Always load developed extensions while extensions development
let developedExtensions: Promise<IExtensionDescription[]> = Promise.resolve([]);
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI && environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) {
developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, fsPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log
);
}
return Promise.all([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => {
const system = extensionDescriptions[0];
const user = extensionDescriptions[1];
const development = extensionDescriptions[2];
return { system, user, development };
}).then(null, err => {
log.error('', err);
return { system: [], user: [], development: [] };
});
return Promise.all([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => {
const system = extensionDescriptions[0];
const user = extensionDescriptions[1];
const development = extensionDescriptions[2];
return { system, user, development };
}).then(undefined, err => {
log.error('', err);
return { system: [], user: [], development: [] };
});
}
}

View File

@@ -10,8 +10,7 @@ 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 { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event, anyEvent, debounceEvent, fromNodeEventEmitter, mapEvent } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import * as objects from 'vs/base/common/objects';
@@ -21,9 +20,8 @@ import { URI } from 'vs/base/common/uri';
import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console';
import { findFreePort, randomPort } from 'vs/base/node/ports';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { Protocol, generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
import { Protocol, generateRandomPipeName, BufferedProtocol } from 'vs/base/parts/ipc/node/ipc.net';
import { IBroadcast, IBroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService';
import { getScopes } from 'vs/platform/configuration/common/configurationRegistry';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -34,15 +32,14 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IConfigurationInitData, IInitData } from 'vs/workbench/api/node/extHost.protocol';
import { IInitData } from 'vs/workbench/api/node/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ICrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
export interface IExtensionHostStarter {
readonly onCrashed: Event<[number, string]>;
start(): Thenable<IMessagePassingProtocol>;
start(): Promise<IMessagePassingProtocol>;
getInspectPort(): number;
dispose(): void;
}
@@ -103,7 +100,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
@IBroadcastService private readonly _broadcastService: IBroadcastService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IWorkspaceConfigurationService private readonly _configurationService: IWorkspaceConfigurationService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ICrashReporterService private readonly _crashReporterService: ICrashReporterService,
@ILogService private readonly _logService: ILogService,
@@ -176,7 +172,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
const opts = {
env: objects.mixin(objects.deepClone(process.env), {
AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess',
AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
PIPE_LOGGING: 'true',
VERBOSE_LOGGING: true,
VSCODE_IPC_HOOK_EXTHOST: pipeName,
@@ -217,15 +213,15 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
type Output = { data: string, format: string[] };
this._extensionHostProcess.stdout.setEncoding('utf8');
this._extensionHostProcess.stderr.setEncoding('utf8');
const onStdout = fromNodeEventEmitter<string>(this._extensionHostProcess.stdout, 'data');
const onStderr = fromNodeEventEmitter<string>(this._extensionHostProcess.stderr, 'data');
const onOutput = anyEvent(
mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })),
mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
const onStdout = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stdout, 'data');
const onStderr = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stderr, 'data');
const onOutput = Event.any(
Event.map(onStdout, o => ({ data: `%c${o}`, format: [''] })),
Event.map(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
);
// Debounce all output, so we can render it in the Chrome console as a group
const onDebouncedOutput = debounceEvent<Output>(onOutput, (r, o) => {
const onDebouncedOutput = Event.debounce<Output>(onOutput, (r, o) => {
return r
? { data: r.data + o.data, format: [...r.format, ...o.format] }
: { data: o.data, format: o.format };
@@ -356,7 +352,11 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
this._namedPipeServer.close();
this._namedPipeServer = null;
this._extensionHostConnection = socket;
resolve(new Protocol(this._extensionHostConnection));
// using a buffered message protocol here because between now
// and the first time a `then` executes some messages might be lost
// unless we immediately register a listener for `onMessage`.
resolve(new BufferedProtocol(new Protocol(this._extensionHostConnection)));
});
}).then((protocol) => {
@@ -402,10 +402,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
disposable.dispose();
// release this promise
// using a buffered message protocol here because between now
// and the first time a `then` executes some messages might be lost
// unless we immediately register a listener for `onMessage`.
resolve(new BufferedMessagePassingProtocol(protocol));
resolve(protocol);
return;
}
@@ -420,15 +417,14 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
private _createExtHostInitData(): Promise<IInitData> {
return Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions])
.then(([telemetryInfo, extensionDescriptions]) => {
const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: {} };
const workspace = this._contextService.getWorkspace();
const r: IInitData = {
commit: product.commit,
parentPid: process.pid,
environment: {
isExtensionDevelopmentDebug: this._isExtensionDevDebug,
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : void 0,
appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : void 0,
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined,
appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : undefined,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsPath: this._environmentService.extensionTestsPath,
globalStorageHome: URI.file(this._environmentService.globalStorageHome)
@@ -439,9 +435,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
},
resolvedExtensions: [],
extensions: extensionDescriptions,
// Send configurations scopes only in development mode.
configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes() } : configurationData,
telemetryInfo,
logLevel: this._logService.getLevel(),
logsLocation: this._extensionHostLogsLocation,
@@ -574,57 +569,3 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
}
}
}
/**
* Will ensure no messages are lost from creation time until the first user of onMessage comes in.
*/
class BufferedMessagePassingProtocol implements IMessagePassingProtocol {
private readonly _actual: IMessagePassingProtocol;
private _bufferedMessagesListener: IDisposable;
private _bufferedMessages: Buffer[];
constructor(actual: IMessagePassingProtocol) {
this._actual = actual;
this._bufferedMessages = [];
this._bufferedMessagesListener = this._actual.onMessage((buff) => this._bufferedMessages.push(buff));
}
public send(buffer: Buffer): void {
this._actual.send(buffer);
}
public onMessage(listener: (e: Buffer) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable {
if (!this._bufferedMessages) {
// second caller gets nothing
return this._actual.onMessage(listener, thisArgs, disposables);
}
// prepare result
const result = this._actual.onMessage(listener, thisArgs, disposables);
// stop listening to buffered messages
this._bufferedMessagesListener.dispose();
// capture buffered messages
const bufferedMessages = this._bufferedMessages;
this._bufferedMessages = null;
// it is important to deliver these messages after this call, but before
// other messages have a chance to be received (to guarantee in order delivery)
// that's why we're using here nextTick and not other types of timeouts
process.nextTick(() => {
// deliver buffered messages
while (bufferedMessages.length > 0) {
const msg = bufferedMessages.shift();
try {
listener.call(thisArgs, msg);
} catch (e) {
onUnexpectedError(e);
}
}
});
return result;
}
}

View File

@@ -12,18 +12,27 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
import { ProfileSession } from 'vs/workbench/services/extensions/common/extensions';
import { ProfileSession, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUntitledResourceInput } from 'vs/workbench/common/editor';
import { StopWatch } from 'vs/base/common/stopwatch';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
const LOG_USE_COLORS = true;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(void 0);
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
export class ExtensionHostProcessManager extends Disposable {
@@ -42,7 +51,7 @@ export class ExtensionHostProcessManager extends Disposable {
/**
* winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object.
*/
private _extensionHostProcessProxy: Thenable<{ value: ExtHostExtensionServiceShape; }>;
private _extensionHostProcessProxy: Promise<{ value: ExtHostExtensionServiceShape; }>;
constructor(
extensionHostProcessWorker: IExtensionHostStarter,
@@ -70,6 +79,9 @@ export class ExtensionHostProcessManager extends Disposable {
);
this._extensionHostProcessProxy.then(() => {
initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent));
this._register(registerLatencyTestProvider({
measure: () => this.measure()
}));
});
}
@@ -93,12 +105,63 @@ export class ExtensionHostProcessManager extends Disposable {
super.dispose();
}
// {{SQL CARBON EDIT}}
// {{SQL CARBON EDIT}} - Add new getExtensionHostProcessWorker method
public getExtenstionHostProcessWorker(): IExtensionHostStarter {
return this._extensionHostProcessWorker;
}
// {{SQL CARBON EDIT}} - End
private async measure(): Promise<ExtHostLatencyResult> {
const latency = await this._measureLatency();
const down = await this._measureDown();
const up = await this._measureUp();
return {
remoteAuthority: this._remoteAuthority,
latency,
down,
up
};
}
private async _measureLatency(): Promise<number> {
const COUNT = 10;
const { value: proxy } = await this._extensionHostProcessProxy;
let sum = 0;
for (let i = 0; i < COUNT; i++) {
const sw = StopWatch.create(true);
await proxy.$test_latency(i);
sw.stop();
sum += sw.elapsed();
}
return (sum / COUNT);
}
private static _convert(byteCount: number, elapsedMillis: number): number {
return (byteCount * 1000 * 8) / elapsedMillis;
}
private async _measureUp(): Promise<number> {
const SIZE = 10 * 1024 * 1024; // 10MB
const { value: proxy } = await this._extensionHostProcessProxy;
let b = Buffer.alloc(SIZE, Math.random() % 256);
const sw = StopWatch.create(true);
await proxy.$test_up(b);
sw.stop();
return ExtensionHostProcessManager._convert(SIZE, sw.elapsed());
}
private async _measureDown(): Promise<number> {
const SIZE = 10 * 1024 * 1024; // 10MB
const { value: proxy } = await this._extensionHostProcessProxy;
const sw = StopWatch.create(true);
await proxy.$test_down(SIZE);
sw.stop();
return ExtensionHostProcessManager._convert(SIZE, sw.elapsed());
}
public canProfileExtensionHost(): boolean {
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
}
@@ -130,8 +193,7 @@ export class ExtensionHostProcessManager extends Disposable {
// Customers
const customers = ExtHostCustomersRegistry.getCustomers();
for (let i = 0, len = customers.length; i < len; i++) {
const ctor = customers[i];
for (const ctor of customers) {
const instance = this._instantiationService.createInstance(ctor, extHostContext);
this._extensionHostProcessCustomers.push(instance);
}
@@ -143,7 +205,13 @@ export class ExtensionHostProcessManager extends Disposable {
return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService);
}
public activateByEvent(activationEvent: string): Thenable<void> {
public activate(extension: ExtensionIdentifier, activationEvent: string): Promise<void> {
return this._extensionHostProcessProxy.then((proxy) => {
return proxy.value.$activate(extension, activationEvent);
});
}
public activateByEvent(activationEvent: string): Promise<void> {
if (this._extensionHostProcessFinishedActivateEvents[activationEvent] || !this._extensionHostProcessProxy) {
return NO_OP_VOID_PROMISE;
}
@@ -179,13 +247,28 @@ export class ExtensionHostProcessManager extends Disposable {
return 0;
}
public resolveAuthority(remoteAuthority: string): Thenable<ResolvedAuthority> {
public resolveAuthority(remoteAuthority: string): Promise<ResolvedAuthority> {
const authorityPlusIndex = remoteAuthority.indexOf('+');
if (authorityPlusIndex === -1) {
// This authority does not need to be resolved, simply parse the port number
const pieces = remoteAuthority.split(':');
return Promise.resolve({
authority: remoteAuthority,
host: pieces[0],
port: parseInt(pieces[1], 10),
syncExtensions: false
});
}
return this._extensionHostProcessProxy.then(proxy => proxy.value.$resolveAuthority(remoteAuthority));
}
public start(enabledExtensionIds: string[]): Thenable<void> {
public start(enabledExtensionIds: ExtensionIdentifier[]): Promise<void> {
return this._extensionHostProcessProxy.then(proxy => proxy.value.$startExtensionHost(enabledExtensionIds));
}
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
return this._extensionHostProcessProxy.then(proxy => proxy.value.$deltaExtensions(toAdd, toRemove));
}
}
const colorTables = [
@@ -230,7 +313,7 @@ class RPCLogger implements IRPCProtocolLogger {
} else {
args.push(data);
}
console.log.apply(console, args);
console.log.apply(console, args as [string, ...string[]]);
}
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
@@ -243,3 +326,68 @@ class RPCLogger implements IRPCProtocolLogger {
this._log('Win \u2192 Ext', this._totalOutgoing, msgLength, req, initiator, str, data);
}
}
interface ExtHostLatencyResult {
remoteAuthority: string;
up: number;
down: number;
latency: number;
}
interface ExtHostLatencyProvider {
measure(): Promise<ExtHostLatencyResult>;
}
let providers: ExtHostLatencyProvider[] = [];
function registerLatencyTestProvider(provider: ExtHostLatencyProvider): IDisposable {
providers.push(provider);
return {
dispose: () => {
for (let i = 0; i < providers.length; i++) {
if (providers[i] === provider) {
providers.splice(i, 1);
return;
}
}
}
};
}
function getLatencyTestProviders(): ExtHostLatencyProvider[] {
return providers.slice(0);
}
export class MeasureExtHostLatencyAction extends Action {
public static readonly ID = 'editor.action.measureExtHostLatency';
public static readonly LABEL = nls.localize('measureExtHostLatency', "Measure Extension Host Latency");
constructor(
id: string,
label: string,
@IEditorService private readonly _editorService: IEditorService
) {
super(id, label);
}
public async run(): Promise<any> {
const measurements = await Promise.all(getLatencyTestProviders().map(provider => provider.measure()));
this._editorService.openEditor({ contents: measurements.map(MeasureExtHostLatencyAction._print).join('\n\n'), options: { pinned: true } } as IUntitledResourceInput);
}
private static _print(m: ExtHostLatencyResult): string {
return `${m.remoteAuthority ? `Authority: ${m.remoteAuthority}\n` : ``}Roundtrip latency: ${m.latency.toFixed(3)}ms\nUp: ${MeasureExtHostLatencyAction._printSpeed(m.up)}\nDown: ${MeasureExtHostLatencyAction._printSpeed(m.down)}\n`;
}
private static _printSpeed(n: number): string {
if (n <= 1024) {
return `${n} bps`;
}
if (n < 1024 * 1024) {
return `${(n / 1024).toFixed(1)} kbps`;
}
return `${(n / 1024 / 1024).toFixed(1)} Mbps`;
}
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(MeasureExtHostLatencyAction, MeasureExtHostLatencyAction.ID, MeasureExtHostLatencyAction.LABEL), 'Developer: Measure Extension Host Latency', nls.localize('developer', "Developer"));

View File

@@ -15,7 +15,7 @@ export class ExtensionHostProfiler {
public async start(): Promise<ProfileSession> {
const profiler = await import('v8-inspect-profiler');
const session = await profiler.startProfiling({ port: this._port });
const session = await profiler.startProfiling({ port: this._port, checkForPaused: true });
return {
stop: async () => {
const profile = await session.stop();
@@ -56,26 +56,29 @@ export class ExtensionHostProfiler {
} else if (segmentId === 'self' && node.callFrame.url) {
let extension = searchTree.findSubstr(node.callFrame.url);
if (extension) {
segmentId = extension.id;
segmentId = extension.identifier.value;
}
}
idsToSegmentId.set(node.id, segmentId);
if (node.children) {
for (let child of node.children) {
visit(idsToNodes.get(child), segmentId);
for (const child of node.children) {
const childNode = idsToNodes.get(child);
if (childNode) {
visit(childNode, segmentId);
}
}
}
}
visit(nodes[0], null);
let samples = profile.samples;
let timeDeltas = profile.timeDeltas;
const samples = profile.samples || [];
let timeDeltas = profile.timeDeltas || [];
let distilledDeltas: number[] = [];
let distilledIds: ProfileSegmentId[] = [];
let currSegmentTime = 0;
let currSegmentId: string = void 0;
let currSegmentId: string | undefined;
for (let i = 0; i < samples.length; i++) {
let id = samples[i];
let segmentId = idsToSegmentId.get(id);
@@ -84,7 +87,7 @@ export class ExtensionHostProfiler {
distilledIds.push(currSegmentId);
distilledDeltas.push(currSegmentTime);
}
currSegmentId = segmentId;
currSegmentId = segmentId || undefined;
currSegmentTime = 0;
}
currSegmentTime += timeDeltas[i];
@@ -93,9 +96,6 @@ export class ExtensionHostProfiler {
distilledIds.push(currSegmentId);
distilledDeltas.push(currSegmentTime);
}
idsToNodes = null;
idsToSegmentId = null;
searchTree = null;
return {
startTime: profile.startTime,

View File

@@ -13,8 +13,8 @@ import * as perf from 'vs/base/common/performance';
import { isEqualOrParent } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import pkg from 'vs/platform/node/package';
@@ -22,19 +22,40 @@ import product from 'vs/platform/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 { ActivationTimes, ExtensionPointContribution, IExtensionDescription, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent } from 'vs/workbench/services/extensions/common/extensions';
import { ActivationTimes, ExtensionPointContribution, IExtensionDescription, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, 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/node/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/electron-browser/extensionHostProcessManager';
import { ExtensionIdentifier, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { Schemas } from 'vs/base/common/network';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(void 0);
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
schema.properties.engines.properties.vscode.default = `^${pkg.version}`;
let productAllowProposedApi: Set<string> = null;
function allowProposedApiFromProduct(id: ExtensionIdentifier): boolean {
// create set if needed
if (productAllowProposedApi === null) {
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(
public readonly toAdd: IExtension[],
public readonly toRemove: string[]
) { }
}
export class ExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: any;
@@ -43,15 +64,19 @@ export class ExtensionService extends Disposable implements IExtensionService {
private _registry: ExtensionDescriptionRegistry;
private readonly _installedExtensionsReady: Barrier;
private readonly _isDev: boolean;
private readonly _extensionsMessages: { [id: string]: IMessage[] };
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>({ leakWarningThreshold: 500 }));
private readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event;
private readonly _onDidChangeExtensionsStatus: Emitter<string[]> = this._register(new Emitter<string[]>());
public readonly onDidChangeExtensionsStatus: Event<string[]> = this._onDidChangeExtensionsStatus.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;
@@ -61,8 +86,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
// --- Members used per extension host process
private _extensionHostProcessManagers: ExtensionHostProcessManager[];
private _extensionHostProcessActivationTimes: { [id: string]: ActivationTimes; };
private _extensionHostExtensionRuntimeErrors: { [id: string]: Error[]; };
private _extensionHostActiveExtensions: Map<string, ExtensionIdentifier>;
private _extensionHostProcessActivationTimes: Map<string, ActivationTimes>;
private _extensionHostExtensionRuntimeErrors: Map<string, Error[]>;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@@ -70,22 +96,24 @@ export class ExtensionService extends Disposable implements IExtensionService {
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
@IWindowService private readonly _windowService: IWindowService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService
@ILifecycleService private readonly _lifecycleService: ILifecycleService
) {
super();
this._extensionHostLogsLocation = URI.file(path.posix.join(this._environmentService.logsPath, `exthost${this._windowService.getCurrentWindowId()}`));
this._registry = null;
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
this._extensionsMessages = {};
this._extensionsMessages = new Map<string, IMessage[]>();
this._allRequestedActivateEvents = Object.create(null);
this._extensionScanner = this._instantiationService.createInstance(CachedExtensionScanner);
this._deltaExtensionsQueue = [];
this._extensionHostProcessManagers = [];
this._extensionHostProcessActivationTimes = Object.create(null);
this._extensionHostExtensionRuntimeErrors = Object.create(null);
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
this._startDelayed(this._lifecycleService);
@@ -97,6 +125,255 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}]);
}
this._extensionEnablementService.onEnablementChanged((extensions) => {
let toAdd: IExtension[] = [];
let toRemove: string[] = [];
for (const extension of extensions) {
if (this._extensionEnablementService.isEnabled(extension)) {
// an extension has been enabled
toAdd.push(extension);
} else {
// an extension has been disabled
toRemove.push(extension.identifier.id);
}
}
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
});
this._extensionManagementService.onDidInstallExtension((event) => {
if (event.local) {
if (this._extensionEnablementService.isEnabled(event.local)) {
// an extension has been installed
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([event.local], []));
}
}
});
this._extensionManagementService.onDidUninstallExtension((event) => {
if (!event.error) {
// an extension has been uninstalled
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
}
});
}
private _inHandleDeltaExtensions = false;
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
this._deltaExtensionsQueue.push(item);
if (this._inHandleDeltaExtensions) {
// Let the current item finish, the new one will be picked up
return;
}
while (this._deltaExtensionsQueue.length > 0) {
const item = this._deltaExtensionsQueue.shift();
try {
this._inHandleDeltaExtensions = true;
await this._deltaExtensions(item.toAdd, item.toRemove);
} finally {
this._inHandleDeltaExtensions = false;
}
}
}
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
if (this._windowService.getConfiguration().remoteAuthority) {
return;
}
let toAdd: IExtensionDescription[] = [];
for (let i = 0, len = _toAdd.length; i < len; i++) {
const extension = _toAdd[i];
if (extension.location.scheme !== Schemas.file) {
continue;
}
const existingExtensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
if (existingExtensionDescription) {
// this extension is already running (most likely at a different version)
continue;
}
const extensionDescription = await this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System, this.createLogger());
if (!extensionDescription || !this._usesOnlyDynamicExtensionPoints(extensionDescription)) {
// uses non-dynamic extension point
continue;
}
toAdd.push(extensionDescription);
}
let toRemove: IExtensionDescription[] = [];
for (let i = 0, len = _toRemove.length; i < len; i++) {
const extensionId = _toRemove[i];
const extensionDescription = this._registry.getExtensionDescription(extensionId);
if (!extensionDescription) {
// ignore disabling/uninstalling an extension which is not running
continue;
}
if (!this._canRemoveExtension(extensionDescription)) {
// uses non-dynamic extension point or is activated
continue;
}
toRemove.push(extensionDescription);
}
if (toAdd.length === 0 && toRemove.length === 0) {
return;
}
// Update the local registry
this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
// Update extension points
this._rehandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
// Update the extension host
if (this._extensionHostProcessManagers.length > 0) {
await this._extensionHostProcessManagers[0].deltaExtensions(toAdd, toRemove.map(e => e.identifier));
}
this._onDidChangeExtensions.fire(undefined);
for (let i = 0; i < toAdd.length; i++) {
this._activateAddedExtensionIfNeeded(toAdd[i]);
}
}
private _rehandleExtensionPoints(extensionDescriptions: IExtensionDescription[]): void {
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);
}
}
}
private _usesOnlyDynamicExtensionPoints(extension: IExtensionDescription): boolean {
const extensionPoints = ExtensionsRegistry.getExtensionPointsMap();
if (extension.contributes) {
for (let extPointName in extension.contributes) {
if (hasOwnProperty.call(extension.contributes, extPointName)) {
const extPoint = extensionPoints[extPointName];
if (extPoint) {
if (!extPoint.isDynamic) {
return false;
}
} else {
// This extension has a 3rd party (unknown) extension point
// ===> require a reload for now...
return false;
}
}
}
}
return true;
}
public canAddExtension(extension: IExtensionDescription): boolean {
if (this._windowService.getConfiguration().remoteAuthority) {
return false;
}
if (extension.extensionLocation.scheme !== Schemas.file) {
return false;
}
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
if (extensionDescription) {
// ignore adding an extension which is already running and cannot be removed
if (!this._canRemoveExtension(extensionDescription)) {
return false;
}
}
return this._usesOnlyDynamicExtensionPoints(extension);
}
public canRemoveExtension(extension: IExtensionDescription): boolean {
if (this._windowService.getConfiguration().remoteAuthority) {
return false;
}
if (extension.extensionLocation.scheme !== Schemas.file) {
return false;
}
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
if (!extensionDescription) {
// ignore removing an extension which is not running
return false;
}
return this._canRemoveExtension(extensionDescription);
}
private _canRemoveExtension(extension: IExtensionDescription): boolean {
if (this._extensionHostActiveExtensions.has(ExtensionIdentifier.toKey(extension.identifier))) {
// Extension is running, cannot remove it safely
return false;
}
return this._usesOnlyDynamicExtensionPoints(extension);
}
private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise<void> {
let shouldActivate = false;
let shouldActivateReason: string | null = null;
if (Array.isArray(extensionDescription.activationEvents)) {
for (let activationEvent of extensionDescription.activationEvents) {
// TODO@joao: there's no easy way to contribute this
if (activationEvent === 'onUri') {
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
}
if (this._allRequestedActivateEvents[activationEvent]) {
// This activation event was fired before the extension was added
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
if (activationEvent === '*') {
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
if (/^workspaceContains/.test(activationEvent)) {
// do not trigger a search, just activate in this case...
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
}
}
if (shouldActivate) {
await Promise.all(
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, shouldActivateReason))
).then(() => { });
}
}
private _startDelayed(lifecycleService: ILifecycleService): void {
@@ -137,14 +414,18 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
private _stopExtensionHostProcess(): void {
const previouslyActivatedExtensionIds = Object.keys(this._extensionHostProcessActivationTimes);
let previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
this._extensionHostActiveExtensions.forEach((value) => {
previouslyActivatedExtensionIds.push(value);
});
for (let i = 0; i < this._extensionHostProcessManagers.length; i++) {
this._extensionHostProcessManagers[i].dispose();
for (const manager of this._extensionHostProcessManagers) {
manager.dispose();
}
this._extensionHostProcessManagers = [];
this._extensionHostProcessActivationTimes = Object.create(null);
this._extensionHostExtensionRuntimeErrors = Object.create(null);
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);
@@ -154,7 +435,18 @@ export class ExtensionService extends Disposable implements IExtensionService {
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
this._stopExtensionHostProcess();
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, !isInitialStart, this.getExtensions(), this._extensionHostLogsLocation);
let autoStart: boolean;
let extensions: Promise<IExtensionDescription[]>;
if (isInitialStart) {
autoStart = false;
extensions = this._extensionScanner.scannedExtensions;
} else {
// restart case
autoStart = true;
extensions = this.getExtensions();
}
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation);
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents);
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal));
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ target: extHostProcessManager, isResponsive: responsiveState === ResponsiveState.Responsive }); });
@@ -205,14 +497,14 @@ export class ExtensionService extends Disposable implements IExtensionService {
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;
}
// Record the fact that this activationEvent was requested (in case of a restart)
this._allRequestedActivateEvents[activationEvent] = true;
return this._activateByEvent(activationEvent);
} else {
// Extensions have not been scanned yet.
@@ -272,13 +564,12 @@ export class ExtensionService extends Disposable implements IExtensionService {
let result: { [id: string]: IExtensionsStatus; } = Object.create(null);
if (this._registry) {
const extensions = this._registry.getAllExtensionDescriptions();
for (let i = 0, len = extensions.length; i < len; i++) {
const extension = extensions[i];
const id = extension.id;
result[id] = {
messages: this._extensionsMessages[id],
activationTimes: this._extensionHostProcessActivationTimes[id],
runtimeErrors: this._extensionHostExtensionRuntimeErrors[id],
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),
};
}
}
@@ -316,23 +607,29 @@ export class ExtensionService extends Disposable implements IExtensionService {
// --- impl
private async _scanAndHandleExtensions(): Promise<void> {
this._extensionScanner.startScanningExtensions(new Logger((severity, source, message) => {
private createLogger(): Logger {
return new Logger((severity, source, message) => {
if (this._isDev && source) {
this._logOrShowMessage(severity, `[${source}]: ${message}`);
} else {
this._logOrShowMessage(severity, message);
}
}));
});
}
private async _scanAndHandleExtensions(): Promise<void> {
this._extensionScanner.startScanningExtensions(this.createLogger());
const extensionHost = this._extensionHostProcessManagers[0];
const extensions = await this._extensionScanner.scannedExtensions;
const enabledExtensions = await this._getRuntimeExtensions(extensions);
extensionHost.start(enabledExtensions.map(extension => extension.id));
this._onHasExtensions(enabledExtensions);
this._handleExtensionPoints(enabledExtensions);
extensionHost.start(enabledExtensions.map(extension => extension.identifier));
this._releaseBarrier();
}
private _onHasExtensions(allExtensions: IExtensionDescription[]): void {
private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void {
this._registry = new ExtensionDescriptionRegistry(allExtensions);
let availableExtensions = this._registry.getAllExtensionDescriptions();
@@ -343,58 +640,65 @@ export class ExtensionService extends Disposable implements IExtensionService {
for (let i = 0, len = extensionPoints.length; i < len; i++) {
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
}
}
private _releaseBarrier(): void {
perf.mark('extensionHostReady');
this._installedExtensionsReady.open();
this._onDidRegisterExtensions.fire(void 0);
this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id));
this._onDidRegisterExtensions.fire(undefined);
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
}
private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise<IExtensionDescription[]> {
return this._extensionEnablementService.getDisabledExtensions()
.then(disabledExtensions => {
const result: { [extensionId: string]: IExtensionDescription; } = {};
const extensionsToDisable: IExtensionIdentifier[] = [];
const runtimeExtensions: IExtensionDescription[] = [];
const extensionsToDisable: IExtensionDescription[] = [];
const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }];
const enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || [];
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 => description.id === 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.indexOf('Insiders') >= 0) ||
(!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') ||
(enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args);
for (const extension of allExtensions) {
const isExtensionUnderDevelopment = this._environmentService.isExtensionDevelopment && isEqualOrParent(extension.extensionLocation, this._environmentService.extensionDevelopmentLocationURI);
// Do not disable extensions under development
if (!isExtensionUnderDevelopment) {
if (disabledExtensions.some(disabled => areSameExtensions(disabled, extension))) {
if (disabledExtensions.some(disabled => areSameExtensions(disabled, { id: extension.identifier.value }))) {
continue;
}
}
if (!extension.isBuiltin) {
// Check if the extension is changed to system extension
const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.id }))[0];
const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.identifier.value }))[0];
if (userMigratedSystemExtension) {
extensionsToDisable.push(userMigratedSystemExtension);
extensionsToDisable.push(extension);
continue;
}
}
result[extension.id] = this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor);
runtimeExtensions.push(this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor));
}
const runtimeExtensions = Object.keys(result).map(name => result[name]);
this._telemetryService.publicLog('extensionsScanned', {
totalCount: runtimeExtensions.length,
@@ -402,14 +706,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
});
if (extensionsToDisable.length) {
return this._extensionManagementService.getInstalled(LocalExtensionType.User)
.then(installed => {
const toDisable = installed.filter(i => extensionsToDisable.some(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i) }, e)));
return Promise.all(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled)));
})
.then(() => {
return runtimeExtensions;
});
return this._extensionEnablementService.setEnablement(extensionsToDisable.map(e => toExtension(e)), EnablementState.Disabled)
.then(() => runtimeExtensions);
} else {
return runtimeExtensions;
}
@@ -417,9 +715,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription {
if (isNonEmptyArray(product.extensionAllowedProposedApi)
&& product.extensionAllowedProposedApi.indexOf(extension.id) >= 0
) {
if (allowProposedApiFromProduct(extension.identifier)) {
// fast lane -> proposed api is available to all extensions
// that are listed in product.json-files
extension.enableProposedApi = true;
@@ -427,29 +723,30 @@ export class ExtensionService extends Disposable implements IExtensionService {
} else if (extension.enableProposedApi && !extension.isBuiltin) {
if (
!enableProposedApiForAll &&
enableProposedApiFor.indexOf(extension.id) < 0
enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0
) {
extension.enableProposedApi = false;
console.error(`Extension '${extension.id} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
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.id}' uses PROPOSED API which is subject to change and removal without notice.`);
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[msg.extensionId]) {
this._extensionsMessages[msg.extensionId] = [];
if (!this._extensionsMessages.has(extensionKey)) {
this._extensionsMessages.set(extensionKey, []);
}
this._extensionsMessages[msg.extensionId].push(msg);
this._extensionsMessages.get(extensionKey).push(msg);
const extension = this._registry.getExtensionDescription(msg.extensionId);
const strMsg = `[${msg.extensionId}]: ${msg.message}`;
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);
@@ -468,7 +765,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
*/
this._telemetryService.publicLog('extensionsMessage', {
type, extensionId, extensionPointId, message
type, extensionId: extensionId.value, extensionPointId, message
});
}
}
@@ -518,24 +815,30 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
public _onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
this._extensionHostProcessActivationTimes[extensionId] = new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent);
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: string, err: Error): void {
if (!this._extensionHostExtensionRuntimeErrors[extensionId]) {
this._extensionHostExtensionRuntimeErrors[extensionId] = [];
public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void {
const extensionKey = ExtensionIdentifier.toKey(extensionId);
if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) {
this._extensionHostExtensionRuntimeErrors.set(extensionKey, []);
}
this._extensionHostExtensionRuntimeErrors[extensionId].push(err);
this._extensionHostExtensionRuntimeErrors.get(extensionKey).push(err);
this._onDidChangeExtensionsStatus.fire([extensionId]);
}
public _addMessage(extensionId: string, severity: Severity, message: string): void {
if (!this._extensionsMessages[extensionId]) {
this._extensionsMessages[extensionId] = [];
public _addMessage(extensionId: ExtensionIdentifier, severity: Severity, message: string): void {
const extensionKey = ExtensionIdentifier.toKey(extensionId);
if (!this._extensionsMessages.has(extensionKey)) {
this._extensionsMessages.set(extensionKey, []);
}
this._extensionsMessages[extensionId].push({
this._extensionsMessages.get(extensionKey).push({
type: severity,
message: message,
extensionId: null,

View File

@@ -7,7 +7,6 @@ import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { EnablementState, IExtensionEnablementService, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -17,6 +16,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { IURLHandler, IURLService } from 'vs/platform/url/common/url';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_SECONDS = 30 * 1000;
@@ -30,8 +30,8 @@ export const IExtensionUrlHandler = createDecorator<IExtensionUrlHandler>('inact
export interface IExtensionUrlHandler {
readonly _serviceBrand: any;
registerExtensionHandler(extensionId: string, handler: IURLHandler): void;
unregisterExtensionHandler(extensionId: string): void;
registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void;
unregisterExtensionHandler(extensionId: ExtensionIdentifier): void;
}
/**
@@ -53,14 +53,14 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
constructor(
@IURLService urlService: IURLService,
@IExtensionService private extensionService: IExtensionService,
@IDialogService private dialogService: IDialogService,
@INotificationService private notificationService: INotificationService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
@IWindowService private windowService: IWindowService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IStorageService private storageService: IStorageService
@IExtensionService private readonly extensionService: IExtensionService,
@IDialogService private readonly dialogService: IDialogService,
@INotificationService private readonly notificationService: INotificationService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
@IWindowService private readonly windowService: IWindowService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@IStorageService private readonly storageService: IStorageService
) {
const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS);
const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE);
@@ -75,87 +75,80 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
]);
}
handleURL(uri: URI, confirmed?: boolean): TPromise<boolean> {
async handleURL(uri: URI, confirmed?: boolean): Promise<boolean> {
if (!isExtensionId(uri.authority)) {
return TPromise.as(false);
return false;
}
const extensionId = uri.authority;
const wasHandlerAvailable = this.extensionHandlers.has(extensionId);
const wasHandlerAvailable = this.extensionHandlers.has(ExtensionIdentifier.toKey(extensionId));
const extension = await this.extensionService.getExtension(extensionId);
return this.extensionService.getExtension(extensionId).then(extension => {
if (!extension) {
await this.handleUnhandledURL(uri, { id: extensionId });
return true;
}
if (!extension) {
return this.handleUnhandledURL(uri, { id: extensionId }).then(() => false);
}
const handleURL = () => {
const handler = this.extensionHandlers.get(extensionId);
if (handler) {
if (!wasHandlerAvailable) {
// forward it directly
return handler.handleURL(uri);
}
// let the ExtensionUrlHandler instance handle this
return TPromise.as(false);
}
// collect URI for eventual extension activation
const timestamp = new Date().getTime();
let uris = this.uriBuffer.get(extensionId);
if (!uris) {
uris = [];
this.uriBuffer.set(extensionId, uris);
}
uris.push({ timestamp, uri });
// activate the extension
return this.extensionService.activateByEvent(`onUri:${extensionId}`)
.then(() => true);
};
if (confirmed) {
return handleURL();
}
return this.dialogService.confirm({
if (!confirmed) {
const result = await this.dialogService.confirm({
message: localize('confirmUrl', "Allow an extension to open this URL?", extensionId),
detail: `${extension.displayName || extension.name} (${extensionId}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('open', "&&Open"),
type: 'question'
}).then(result => {
if (!result.confirmed) {
return TPromise.as(true);
}
return handleURL();
});
});
if (!result.confirmed) {
return true;
}
}
const handler = this.extensionHandlers.get(ExtensionIdentifier.toKey(extensionId));
if (handler) {
if (!wasHandlerAvailable) {
// forward it directly
return await handler.handleURL(uri);
}
// let the ExtensionUrlHandler instance handle this
return false;
}
// collect URI for eventual extension activation
const timestamp = new Date().getTime();
let uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId));
if (!uris) {
uris = [];
this.uriBuffer.set(ExtensionIdentifier.toKey(extensionId), uris);
}
uris.push({ timestamp, uri });
// activate the extension
await this.extensionService.activateByEvent(`onUri:${ExtensionIdentifier.toKey(extensionId)}`);
return true;
}
registerExtensionHandler(extensionId: string, handler: IURLHandler): void {
this.extensionHandlers.set(extensionId, handler);
registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void {
this.extensionHandlers.set(ExtensionIdentifier.toKey(extensionId), handler);
const uris = this.uriBuffer.get(extensionId) || [];
const uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId)) || [];
for (const { uri } of uris) {
handler.handleURL(uri);
}
this.uriBuffer.delete(extensionId);
this.uriBuffer.delete(ExtensionIdentifier.toKey(extensionId));
}
unregisterExtensionHandler(extensionId: string): void {
this.extensionHandlers.delete(extensionId);
unregisterExtensionHandler(extensionId: ExtensionIdentifier): void {
this.extensionHandlers.delete(ExtensionIdentifier.toKey(extensionId));
}
private async handleUnhandledURL(uri: URI, extensionIdentifier: IExtensionIdentifier): Promise<void> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const extension = installedExtensions.filter(e => areSameExtensions(e.galleryIdentifier, extensionIdentifier))[0];
const extension = installedExtensions.filter(e => areSameExtensions(e.identifier, extensionIdentifier))[0];
// Extension is installed
if (extension) {
@@ -163,88 +156,91 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
// Extension is not running. Reload the window to handle.
if (enabled) {
this.dialogService.confirm({
const result = await this.dialogService.confirm({
message: localize('reloadAndHandle', "Extension '{0}' is not loaded. Would you like to reload the window to load the extension and open the URL?", extension.manifest.displayName || extension.manifest.name),
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('reloadAndOpen', "&&Reload Window and Open"),
type: 'question'
}).then(result => {
if (result.confirmed) {
return this.reloadAndHandle(uri);
}
return null;
});
if (!result.confirmed) {
return;
}
await this.reloadAndHandle(uri);
}
// Extension is disabled. Enable the extension and reload the window to handle.
else {
this.dialogService.confirm({
const result = await this.dialogService.confirm({
message: localize('enableAndHandle', "Extension '{0}' is disabled. Would you like to enable the extension and reload the window to open the URL?", extension.manifest.displayName || extension.manifest.name),
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('enableAndReload', "&&Enable and Open"),
type: 'question'
}).then((result): TPromise<void> | null => {
if (result.confirmed) {
return this.extensionEnablementService.setEnablement(extension, EnablementState.Enabled)
.then(() => this.reloadAndHandle(uri));
}
return null;
});
if (!result.confirmed) {
return;
}
await this.extensionEnablementService.setEnablement([extension], EnablementState.Enabled);
await this.reloadAndHandle(uri);
}
}
// Extension is not installed
else {
const galleryExtension = await this.galleryService.getExtension(extensionIdentifier);
if (galleryExtension) {
// Install the Extension and reload the window to handle.
this.dialogService.confirm({
message: localize('installAndHandle', "Extension '{0}' is not installed. Would you like to install the extension and reload the window to open this URL?", galleryExtension.displayName || galleryExtension.name),
detail: `${galleryExtension.displayName || galleryExtension.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('install', "&&Install"),
type: 'question'
}).then(async result => {
if (result.confirmed) {
let notificationHandle: INotificationHandle | null = this.notificationService.notify({ severity: Severity.Info, message: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) });
notificationHandle.progress.infinite();
notificationHandle.onDidClose(() => notificationHandle = null);
try {
await this.extensionManagementService.installFromGallery(galleryExtension);
const reloadMessage = localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString());
const reloadActionLabel = localize('Reload', "Reload Window and Open");
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateMessage(reloadMessage);
notificationHandle.updateActions({
primary: [new Action('reloadWindow', reloadActionLabel, undefined, true, () => this.reloadAndHandle(uri))]
});
} else {
this.notificationService.prompt(Severity.Info, reloadMessage,
[{
label: reloadActionLabel,
run: () => this.reloadAndHandle(uri)
}],
{ sticky: true }
);
}
} catch (e) {
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateSeverity(Severity.Error);
notificationHandle.updateMessage(e);
} else {
this.notificationService.error(e);
}
}
}
});
const galleryExtension = await this.galleryService.getCompatibleExtension(extensionIdentifier);
if (!galleryExtension) {
return;
}
// Install the Extension and reload the window to handle.
const result = await this.dialogService.confirm({
message: localize('installAndHandle', "Extension '{0}' is not installed. Would you like to install the extension and reload the window to open this URL?", galleryExtension.displayName || galleryExtension.name),
detail: `${galleryExtension.displayName || galleryExtension.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('install', "&&Install"),
type: 'question'
});
if (!result.confirmed) {
return;
}
let notificationHandle: INotificationHandle | null = this.notificationService.notify({ severity: Severity.Info, message: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) });
notificationHandle.progress.infinite();
notificationHandle.onDidClose(() => notificationHandle = null);
try {
await this.extensionManagementService.installFromGallery(galleryExtension);
const reloadMessage = localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString());
const reloadActionLabel = localize('Reload', "Reload Window and Open");
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateMessage(reloadMessage);
notificationHandle.updateActions({
primary: [new Action('reloadWindow', reloadActionLabel, undefined, true, () => this.reloadAndHandle(uri))]
});
} else {
this.notificationService.prompt(Severity.Info, reloadMessage, [{ label: reloadActionLabel, run: () => this.reloadAndHandle(uri) }], { sticky: true });
}
} catch (e) {
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateSeverity(Severity.Error);
notificationHandle.updateMessage(e);
} else {
this.notificationService.error(e);
}
}
}
}
private reloadAndHandle(url: URI): TPromise<void> {
private async reloadAndHandle(url: URI): Promise<void> {
this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE);
return this.windowService.reloadWindow();
await this.windowService.reloadWindow();
}
// forget about all uris buffered more than 5 minutes ago

View File

@@ -30,7 +30,7 @@ export class RuntimeExtensionsInput extends EditorInput {
return true;
}
resolve(): Thenable<any> {
resolve(): Promise<any> {
return Promise.resolve(null);
}

View File

@@ -4,14 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
const hasOwnProperty = Object.hasOwnProperty;
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { Emitter } from 'vs/base/common/event';
export class ExtensionDescriptionRegistry {
private readonly _onDidChange = new Emitter<void>();
public readonly onDidChange = this._onDidChange.event;
private _extensionDescriptions: IExtensionDescription[];
private _extensionsMap: { [extensionId: string]: IExtensionDescription; };
private _extensionsMap: Map<string, IExtensionDescription>;
private _extensionsArr: IExtensionDescription[];
private _activationMap: { [activationEvent: string]: IExtensionDescription[]; };
private _activationMap: Map<string, IExtensionDescription[]>;
constructor(extensionDescriptions: IExtensionDescription[]) {
this._extensionDescriptions = extensionDescriptions;
@@ -19,64 +22,68 @@ export class ExtensionDescriptionRegistry {
}
private _initialize(): void {
this._extensionsMap = {};
this._extensionsMap = new Map<string, IExtensionDescription>();
this._extensionsArr = [];
this._activationMap = {};
this._activationMap = new Map<string, IExtensionDescription[]>();
for (let i = 0, len = this._extensionDescriptions.length; i < len; i++) {
let extensionDescription = this._extensionDescriptions[i];
if (hasOwnProperty.call(this._extensionsMap, extensionDescription.id)) {
for (const extensionDescription of this._extensionDescriptions) {
if (this._extensionsMap.has(ExtensionIdentifier.toKey(extensionDescription.identifier))) {
// No overwriting allowed!
console.error('Extension `' + extensionDescription.id + '` is already registered');
console.error('Extension `' + extensionDescription.identifier.value + '` is already registered');
continue;
}
this._extensionsMap[extensionDescription.id] = extensionDescription;
this._extensionsMap.set(ExtensionIdentifier.toKey(extensionDescription.identifier), extensionDescription);
this._extensionsArr.push(extensionDescription);
if (Array.isArray(extensionDescription.activationEvents)) {
for (let j = 0, lenJ = extensionDescription.activationEvents.length; j < lenJ; j++) {
let activationEvent = extensionDescription.activationEvents[j];
for (let activationEvent of extensionDescription.activationEvents) {
// TODO@joao: there's no easy way to contribute this
if (activationEvent === 'onUri') {
activationEvent = `onUri:${extensionDescription.id}`;
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
}
this._activationMap[activationEvent] = this._activationMap[activationEvent] || [];
this._activationMap[activationEvent].push(extensionDescription);
if (!this._activationMap.has(activationEvent)) {
this._activationMap.set(activationEvent, []);
}
this._activationMap.get(activationEvent)!.push(extensionDescription);
}
}
}
}
public keepOnly(extensionIds: string[]): void {
let toKeep = new Set<string>();
extensionIds.forEach(extensionId => toKeep.add(extensionId));
this._extensionDescriptions = this._extensionDescriptions.filter(extension => toKeep.has(extension.id));
public keepOnly(extensionIds: ExtensionIdentifier[]): void {
const toKeep = new Set<string>();
extensionIds.forEach(extensionId => toKeep.add(ExtensionIdentifier.toKey(extensionId)));
this._extensionDescriptions = this._extensionDescriptions.filter(extension => toKeep.has(ExtensionIdentifier.toKey(extension.identifier)));
this._initialize();
this._onDidChange.fire(undefined);
}
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]) {
this._extensionDescriptions = this._extensionDescriptions.concat(toAdd);
const toRemoveSet = new Set<string>();
toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId)));
this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
this._initialize();
this._onDidChange.fire(undefined);
}
public containsActivationEvent(activationEvent: string): boolean {
return hasOwnProperty.call(this._activationMap, activationEvent);
return this._activationMap.has(activationEvent);
}
public getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[] {
if (!hasOwnProperty.call(this._activationMap, activationEvent)) {
return [];
}
return this._activationMap[activationEvent].slice(0);
const extensions = this._activationMap.get(activationEvent);
return extensions ? extensions.slice(0) : [];
}
public getAllExtensionDescriptions(): IExtensionDescription[] {
return this._extensionsArr.slice(0);
}
public getExtensionDescription(extensionId: string): IExtensionDescription | null {
if (!hasOwnProperty.call(this._extensionsMap, extensionId)) {
return null;
}
return this._extensionsMap[extensionId];
public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | null {
const extension = this._extensionsMap.get(ExtensionIdentifier.toKey(extensionId));
return extension ? extension : null;
}
}

View File

@@ -0,0 +1,162 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { timeout } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Counter } from 'vs/base/common/numbers';
import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { IEnvironment, IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/node/extHost.protocol';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
// we don't (yet) throw when extensions parse
// uris that have no scheme
setUriThrowOnMissingScheme(false);
const nativeExit = process.exit.bind(process);
function patchProcess(allowExit: boolean) {
process.exit = function (code?: number) {
if (allowExit) {
exit(code);
} else {
const err = new Error('An extension called process.exit() and this was prevented.');
console.warn(err.stack);
}
} as (code?: number) => never;
process.crash = function () {
const err = new Error('An extension called process.crash() and this was prevented.');
console.warn(err.stack);
};
}
export function exit(code?: number) {
nativeExit(code);
}
export class ExtensionHostMain {
private _isTerminating: boolean;
private readonly _environment: IEnvironment;
private readonly _extensionService: ExtHostExtensionService;
private readonly _extHostConfiguration: ExtHostConfiguration;
private readonly _extHostLogService: ExtHostLogService;
private disposables: IDisposable[] = [];
private _searchRequestIdProvider: Counter;
constructor(protocol: IMessagePassingProtocol, initData: IInitData) {
this._isTerminating = false;
const uriTransformer: IURITransformer = null;
const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
// ensure URIs are transformed and revived
initData = this.transform(initData, rpcProtocol);
this._environment = initData.environment;
const allowExit = !!this._environment.extensionTestsPath; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708)
patchProcess(allowExit);
this._patchPatchedConsole(rpcProtocol.getProxy(MainContext.MainThreadConsole));
// services
this._extHostLogService = new ExtHostLogService(initData.logLevel, initData.logsLocation.fsPath);
this.disposables.push(this._extHostLogService);
this._searchRequestIdProvider = new Counter();
const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, initData.workspace, this._extHostLogService, this._searchRequestIdProvider);
this._extHostLogService.info('extension host started');
this._extHostLogService.trace('initData', initData);
this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace);
this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService);
// error forwarding and stack trace scanning
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
const extensionErrors = new WeakMap<Error, IExtensionDescription>();
this._extensionService.getExtensionPathIndex().then(map => {
(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
let stackTraceMessage = '';
let extension: IExtensionDescription;
let fileName: string;
for (const call of stackTrace) {
stackTraceMessage += `\n\tat ${call.toString()}`;
fileName = call.getFileName();
if (!extension && fileName) {
extension = map.findSubstr(fileName);
}
}
extensionErrors.set(error, extension);
return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
};
});
const mainThreadExtensions = rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
const mainThreadErrors = rpcProtocol.getProxy(MainContext.MainThreadErrors);
errors.setUnexpectedErrorHandler(err => {
const data = errors.transformErrorForSerialization(err);
const extension = extensionErrors.get(err);
if (extension) {
mainThreadExtensions.$onExtensionRuntimeError(extension.identifier, data);
} else {
mainThreadErrors.$onUnexpectedError(data);
}
});
}
private _patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
// The console is already patched to use `process.send()`
const nativeProcessSend = process.send;
process.send = (...args: any[]) => {
if (args.length === 0 || !args[0] || args[0].type !== '__$console') {
return nativeProcessSend.apply(process, args);
}
mainThreadConsole.$logExtensionHostMessage(args[0]);
};
}
terminate(): void {
if (this._isTerminating) {
// we are already shutting down...
return;
}
this._isTerminating = true;
this.disposables = dispose(this.disposables);
errors.setUnexpectedErrorHandler((err) => {
// TODO: write to log once we have one
});
const extensionsDeactivated = this._extensionService.deactivateAll();
// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds
setTimeout(() => {
Promise.race([timeout(4000), extensionsDeactivated]).then(() => exit(), () => exit());
}, 1000);
}
private transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData {
initData.extensions.forEach((ext) => (<any>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)));
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome));
initData.environment.extensionDevelopmentLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionDevelopmentLocationURI));
initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome));
initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation));
initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace);
return initData;
}
}

View File

@@ -0,0 +1,189 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nativeWatchdog from 'native-watchdog';
import { createConnection } from 'net';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { Protocol } from 'vs/base/parts/ipc/node/ipc.net';
import product from 'vs/platform/node/product';
import { IInitData } from 'vs/workbench/api/node/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol';
import { exit, ExtensionHostMain } from 'vs/workbench/services/extensions/node/extensionHostMain';
// 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
// https://github.com/electron/electron/issues/10905). To prevent this from
// happening we essentially blocklist this module from getting loaded in any
// extension by patching the node require() function.
(function () {
const Module = require.__$__nodeRequire('module') as any;
const originalLoad = Module._load;
Module._load = function (request) {
if (request === 'natives') {
throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
}
return originalLoad.apply(this, arguments);
};
})();
interface IRendererConnection {
protocol: IMessagePassingProtocol;
initData: IInitData;
}
// This calls exit directly in case the initialization is not finished and we need to exit
// Otherwise, if initialization completed we go to extensionHostMain.terminate()
let onTerminate = function () {
exit();
};
function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST;
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
const socket = createConnection(pipeName, () => {
socket.removeListener('error', reject);
resolve(new Protocol(socket));
});
socket.once('error', reject);
}).then(protocol => {
return new class implements IMessagePassingProtocol {
private _terminating = false;
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) {
protocol.send(msg);
}
}
};
});
}
function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRendererConnection> {
return new Promise<IRendererConnection>((c, e) => {
// Listen init data message
const first = protocol.onMessage(raw => {
first.dispose();
const initData = <IInitData>JSON.parse(raw.toString());
const rendererCommit = initData.commit;
const myCommit = product.commit;
if (rendererCommit && myCommit) {
// Running in the built version where commits are defined
if (rendererCommit !== myCommit) {
exit(55);
}
}
// Print a console message when rejection isn't handled within N seconds. For details:
// see https://nodejs.org/api/process.html#process_event_unhandledrejection
// and https://nodejs.org/api/process.html#process_event_rejectionhandled
const unhandledPromises: Promise<any>[] = [];
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
unhandledPromises.push(promise);
setTimeout(() => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
unhandledPromises.splice(idx, 1);
console.warn('rejected promise not handled within 1 second');
onUnexpectedError(reason);
}
}, 1000);
});
process.on('rejectionHandled', (promise: Promise<any>) => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
unhandledPromises.splice(idx, 1);
}
});
// Print a console message when an exception isn't handled.
process.on('uncaughtException', function (err: Error) {
onUnexpectedError(err);
});
// {{SQL CARBON EDIT}}
process.on('SIGPIPE', () => {
onUnexpectedError(new Error('Unexpected SIGPIPE'));
});
// {{SQL CARBON EDIT}} - End
// Kill oneself if one's parent dies. Much drama.
setInterval(function () {
try {
process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
} catch (e) {
onTerminate();
}
}, 1000);
// In certain cases, the event loop can become busy and never yield
// e.g. while-true or process.nextTick endless loops
// So also use the native node module to do it from a separate thread
let watchdog: typeof nativeWatchdog;
try {
watchdog = require.__$__nodeRequire('native-watchdog');
watchdog.start(initData.parentPid);
} catch (err) {
// no problem...
onUnexpectedError(err);
}
// Tell the outside that we are initialized
protocol.send(createMessageOfType(MessageType.Initialized));
c({ protocol, initData });
});
// Tell the outside that we are ready to receive messages
protocol.send(createMessageOfType(MessageType.Ready));
});
}
patchExecArgv();
createExtHostProtocol().then(protocol => {
// connect to main side
return connectToRenderer(protocol);
}).then(renderer => {
// setup things
const extensionHostMain = new ExtensionHostMain(renderer.protocol, renderer.initData);
onTerminate = () => extensionHostMain.terminate();
}).catch(err => console.error(err));
function patchExecArgv() {
// when encountering the prevent-inspect flag we delete this
// and the prior flag
if (process.env.VSCODE_PREVENT_FOREIGN_INSPECT) {
for (let i = 0; i < process.execArgv.length; i++) {
if (process.execArgv[i].match(/--inspect-brk=\d+|--inspect=\d+/)) {
process.execArgv.splice(i, 1);
break;
}
}
}
}

View File

@@ -42,28 +42,4 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
}
return null;
}
}
export class SingleServerExtensionManagementServerService implements IExtensionManagementServerService {
_serviceBrand: any;
constructor(
private readonly extensionManagementServer: IExtensionManagementServer
) {
}
getExtensionManagementServer(location: URI): IExtensionManagementServer | null {
const authority = location.scheme === Schemas.file ? localExtensionManagementServerAuthority : location.authority;
return this.extensionManagementServer.authority === authority ? this.extensionManagementServer : null;
}
get localExtensionManagementServer(): IExtensionManagementServer | null {
return this.extensionManagementServer.authority === localExtensionManagementServerAuthority ? this.extensionManagementServer : null;
}
get remoteExtensionManagementServer(): IExtensionManagementServer | null {
return this.extensionManagementServer.authority !== localExtensionManagementServerAuthority ? this.extensionManagementServer : null;
}
}

View File

@@ -12,10 +12,10 @@ 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, getLocalExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier, ExtensionIdentifierWithVersion } from 'vs/platform/extensions/common/extensions';
const MANIFEST_FILE = 'package.json';
@@ -304,6 +304,8 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
// Relax the readonly properties here, it is the one place where we check and normalize values
export interface IRelaxedExtensionDescription {
id: string;
uuid?: string;
identifier: ExtensionIdentifier;
name: string;
version: string;
publisher: string;
@@ -343,6 +345,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
// id := `publisher.name`
extensionDescription.id = `${extensionDescription.publisher}.${extensionDescription.name}`;
extensionDescription.identifier = new ExtensionIdentifier(extensionDescription.id);
// main := absolutePath(`main`)
if (extensionDescription.main) {
@@ -507,7 +510,7 @@ export class ExtensionScanner {
/**
* Read the extension defined in `absoluteFolderPath`
*/
public static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> {
private static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> {
absoluteFolderPath = path.normalize(absoluteFolderPath);
let parser = new ExtensionManifestParser(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
@@ -557,31 +560,17 @@ export class ExtensionScanner {
refs.sort((a, b) => a.name < b.name ? -1 : 1);
if (!isBuiltin) {
// TODO: align with extensionsService
const nonGallery: IExtensionReference[] = [];
const gallery: IExtensionReference[] = [];
refs.forEach(ref => {
if (ref.name.indexOf('.') !== 0) { // Do not consider user extension folder starting with `.`
const { id, version } = getIdAndVersionFromLocalExtensionId(ref.name);
if (!id || !version) {
nonGallery.push(ref);
} else {
gallery.push(ref);
}
}
});
refs = [...nonGallery, ...gallery];
refs = refs.filter(ref => ref.name.indexOf('.') !== 0); // Do not consider user extension folder starting with `.`
}
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
let _extensionDescriptions = await Promise.all(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig)));
let extensionDescriptions = arrays.coalesce(_extensionDescriptions);
extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[getLocalExtensionId(getGalleryExtensionId(item.publisher, item.name), item.version)]);
extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[new ExtensionIdentifierWithVersion({ id: getGalleryExtensionId(item.publisher, item.name) }, item.version).key()]);
if (!isBuiltin) {
// Filter out outdated extensions
const byExtension: IExtensionDescription[][] = groupByExtension(extensionDescriptions, e => ({ id: e.id, uuid: e.uuid }));
const byExtension: IExtensionDescription[][] = groupByExtension(extensionDescriptions, e => ({ id: e.identifier.value, uuid: e.uuid }));
extensionDescriptions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]);
}
@@ -624,15 +613,23 @@ export class ExtensionScanner {
});
}
public static scanSingleExtension(input: ExtensionScannerInput, log: ILog): Promise<IExtensionDescription | null> {
const absoluteFolderPath = input.absoluteFolderPath;
const isBuiltin = input.isBuiltin;
const isUnderDevelopment = input.isUnderDevelopment;
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig);
}
public static mergeBuiltinExtensions(builtinExtensions: Promise<IExtensionDescription[]>, extraBuiltinExtensions: Promise<IExtensionDescription[]>): Promise<IExtensionDescription[]> {
return Promise.all([builtinExtensions, extraBuiltinExtensions]).then(([builtinExtensions, extraBuiltinExtensions]) => {
let resultMap: { [id: string]: IExtensionDescription; } = Object.create(null);
for (let i = 0, len = builtinExtensions.length; i < len; i++) {
resultMap[builtinExtensions[i].id] = builtinExtensions[i];
resultMap[ExtensionIdentifier.toKey(builtinExtensions[i].identifier)] = builtinExtensions[i];
}
// Overwrite with extensions found in extra
for (let i = 0, len = extraBuiltinExtensions.length; i < len; i++) {
resultMap[extraBuiltinExtensions[i].id] = extraBuiltinExtensions[i];
resultMap[ExtensionIdentifier.toKey(extraBuiltinExtensions[i].identifier)] = extraBuiltinExtensions[i];
}
let resultArr = Object.keys(resultMap).map((id) => resultMap[id]);

View File

@@ -5,7 +5,7 @@
import { onUnexpectedError } from 'vs/base/common/errors';
export class LazyPromise implements Thenable<any> {
export class LazyPromise implements Promise<any> {
private _actual: Promise<any> | null;
private _actualOk: ((value?: any) => any) | null;
@@ -78,4 +78,12 @@ export class LazyPromise implements Thenable<any> {
public then(success: any, error: any): any {
return this._ensureActual().then(success, error);
}
public catch(error: any): any {
return this._ensureActual().then(undefined, error);
}
public finally(callback): any {
return this._ensureActual().finally(callback);
}
}

View File

@@ -0,0 +1,317 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as http from 'http';
import * as https from 'https';
import * as nodeurl from 'url';
import { assign } from 'vs/base/common/objects';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { ExtHostConfigProvider } from 'vs/workbench/api/node/extHostConfiguration';
import { ProxyAgent } from 'vscode-proxy-agent';
import { MainThreadTelemetryShape } from 'vs/workbench/api/node/extHost.protocol';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { URI } from 'vs/base/common/uri';
interface ConnectionResult {
proxy: string;
connection: string;
code: string;
count: number;
}
export function connectProxyResolver(
extHostWorkspace: ExtHostWorkspace,
configProvider: ExtHostConfigProvider,
extensionService: ExtHostExtensionService,
extHostLogService: ExtHostLogService,
mainThreadTelemetry: MainThreadTelemetryShape
) {
const agents = createProxyAgents(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry);
const lookup = createPatchedModules(configProvider, agents);
return configureModuleLoading(extensionService, lookup);
}
const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache'.
function createProxyAgents(
extHostWorkspace: ExtHostWorkspace,
configProvider: ExtHostConfigProvider,
extHostLogService: ExtHostLogService,
mainThreadTelemetry: MainThreadTelemetryShape
) {
let settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http')
.get<string>('proxy'));
configProvider.onDidChangeConfiguration(e => {
settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http')
.get<string>('proxy'));
});
const env = process.env;
let envProxy = proxyFromConfigURL(env.https_proxy || env.HTTPS_PROXY || env.http_proxy || env.HTTP_PROXY); // Not standardized.
let cacheRolls = 0;
let oldCache = new Map<string, string>();
let cache = new Map<string, string>();
function getCacheKey(url: nodeurl.UrlWithStringQuery) {
// Expecting proxies to usually be the same per scheme://host:port. Assuming that for performance.
return nodeurl.format({ ...url, ...{ pathname: undefined, search: undefined, hash: undefined } });
}
function getCachedProxy(key: string) {
let proxy = cache.get(key);
if (proxy) {
return proxy;
}
proxy = oldCache.get(key);
if (proxy) {
oldCache.delete(key);
cacheProxy(key, proxy);
}
return proxy;
}
function cacheProxy(key: string, proxy: string) {
cache.set(key, proxy);
if (cache.size >= maxCacheEntries) {
oldCache = cache;
cache = new Map();
cacheRolls++;
extHostLogService.trace('ProxyResolver#cacheProxy cacheRolls', cacheRolls);
}
}
let timeout: NodeJS.Timer | undefined;
let count = 0;
let duration = 0;
let errorCount = 0;
let cacheCount = 0;
let envCount = 0;
let settingsCount = 0;
let localhostCount = 0;
let results: ConnectionResult[] = [];
function logEvent() {
timeout = undefined;
/* __GDPR__
"resolveProxy" : {
"count": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cacheCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cacheSize": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cacheRolls": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"envCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"settingsCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"localhostCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"results": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
mainThreadTelemetry.$publicLog('resolveProxy', { count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, results });
count = duration = errorCount = cacheCount = envCount = settingsCount = localhostCount = 0;
results = [];
}
function resolveProxy(req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) {
if (!timeout) {
timeout = setTimeout(logEvent, 10 * 60 * 1000);
}
const parsedUrl = nodeurl.parse(url); // Coming from Node's URL, sticking with that.
const hostname = parsedUrl.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '::ffff:127.0.0.1') {
localhostCount++;
callback('DIRECT');
extHostLogService.trace('ProxyResolver#resolveProxy localhost', url, 'DIRECT');
return;
}
if (settingsProxy) {
settingsCount++;
callback(settingsProxy);
extHostLogService.trace('ProxyResolver#resolveProxy settings', url, settingsProxy);
return;
}
if (envProxy) {
envCount++;
callback(envProxy);
extHostLogService.trace('ProxyResolver#resolveProxy env', url, envProxy);
return;
}
const key = getCacheKey(parsedUrl);
const proxy = getCachedProxy(key);
if (proxy) {
cacheCount++;
collectResult(results, proxy, parsedUrl.protocol === 'https:' ? 'HTTPS' : 'HTTP', req);
callback(proxy);
extHostLogService.trace('ProxyResolver#resolveProxy cached', url, proxy);
return;
}
const start = Date.now();
extHostWorkspace.resolveProxy(url) // Use full URL to ensure it is an actually used one.
.then(proxy => {
cacheProxy(key, proxy);
collectResult(results, proxy, parsedUrl.protocol === 'https:' ? 'HTTPS' : 'HTTP', req);
callback(proxy);
extHostLogService.debug('ProxyResolver#resolveProxy', url, proxy);
}).then(() => {
count++;
duration = Date.now() - start + duration;
}, err => {
errorCount++;
callback();
extHostLogService.error('ProxyResolver#resolveProxy', toErrorMessage(err));
});
}
const httpAgent: http.Agent = new ProxyAgent({ resolveProxy });
(<any>httpAgent).defaultPort = 80;
const httpsAgent: http.Agent = new ProxyAgent({ resolveProxy });
(<any>httpsAgent).defaultPort = 443;
return { http: httpAgent, https: httpsAgent };
}
function collectResult(results: ConnectionResult[], resolveProxy: string, connection: string, req: http.ClientRequest) {
const proxy = resolveProxy ? String(resolveProxy).trim().split(/\s+/, 1)[0] : 'EMPTY';
req.on('response', res => {
const code = `HTTP_${res.statusCode}`;
const result = findOrCreateResult(results, proxy, connection, code);
result.count++;
});
req.on('error', err => {
const code = err && typeof (<any>err).code === 'string' && (<any>err).code || 'UNKNOWN_ERROR';
const result = findOrCreateResult(results, proxy, connection, code);
result.count++;
});
}
function findOrCreateResult(results: ConnectionResult[], proxy: string, connection: string, code: string): ConnectionResult | undefined {
for (const result of results) {
if (result.proxy === proxy && result.connection === connection && result.code === code) {
return result;
}
}
const result = { proxy, connection, code, count: 0 };
results.push(result);
return result;
}
function proxyFromConfigURL(configURL: string) {
const url = (configURL || '').trim();
const i = url.indexOf('://');
if (i === -1) {
return undefined;
}
const scheme = url.substr(0, i).toLowerCase();
const proxy = url.substr(i + 3);
if (scheme === 'http') {
return 'PROXY ' + proxy;
} else if (scheme === 'https') {
return 'HTTPS ' + proxy;
} else if (scheme === 'socks') {
return 'SOCKS ' + proxy;
}
return undefined;
}
function createPatchedModules(configProvider: ExtHostConfigProvider, agents: { http: http.Agent; https: http.Agent; }) {
const setting = {
config: configProvider.getConfiguration('http')
.get<string>('proxySupport') || 'off'
};
configProvider.onDidChangeConfiguration(e => {
setting.config = configProvider.getConfiguration('http')
.get<string>('proxySupport') || 'off';
});
return {
http: {
off: assign({}, http, patches(http, agents.http, agents.https, { config: 'off' }, true)),
on: assign({}, http, patches(http, agents.http, agents.https, { config: 'on' }, true)),
override: assign({}, http, patches(http, agents.http, agents.https, { config: 'override' }, true)),
onRequest: assign({}, http, patches(http, agents.http, agents.https, setting, true)),
default: assign(http, patches(http, agents.http, agents.https, setting, false)) // run last
},
https: {
off: assign({}, https, patches(https, agents.https, agents.http, { config: 'off' }, true)),
on: assign({}, https, patches(https, agents.https, agents.http, { config: 'on' }, true)),
override: assign({}, https, patches(https, agents.https, agents.http, { config: 'override' }, true)),
onRequest: assign({}, https, patches(https, agents.https, agents.http, setting, true)),
default: assign(https, patches(https, agents.https, agents.http, setting, false)) // run last
}
};
}
function patches(originals: typeof http | typeof https, agent: http.Agent, otherAgent: http.Agent, setting: { config: string; }, onRequest: boolean) {
return {
get: patch(originals.get),
request: patch(originals.request)
};
function patch(original: typeof http.get) {
function patched(url: string | URL, options?: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest {
if (typeof url !== 'string' && !(url && (<any>url).searchParams)) {
callback = <any>options;
options = url;
url = null;
}
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
const config = onRequest && ((<any>options)._vscodeProxySupport || /* LS */ (<any>options)._vscodeSystemProxy) || setting.config;
if (config === 'off') {
return original.apply(null, arguments as unknown as any[]);
}
if (!options.socketPath && (config === 'override' || config === 'on' && !options.agent) && options.agent !== agent && options.agent !== otherAgent) {
if (url) {
const parsed = typeof url === 'string' ? new nodeurl.URL(url) : url;
const urlOptions = {
protocol: parsed.protocol,
hostname: parsed.hostname.lastIndexOf('[', 0) === 0 ? parsed.hostname.slice(1, -1) : parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`
};
if (parsed.username || parsed.password) {
options.auth = `${parsed.username}:${parsed.password}`;
}
options = { ...urlOptions, ...options };
} else {
options = { ...options };
}
options.agent = agent;
return original(options, callback);
}
return original.apply(null, arguments as unknown as any[]);
}
return patched;
}
}
function configureModuleLoading(extensionService: ExtHostExtensionService, lookup: ReturnType<typeof createPatchedModules>): Promise<void> {
return extensionService.getExtensionPathIndex()
.then(extensionPaths => {
const node_module = <any>require.__$__nodeRequire('module');
const original = node_module._load;
node_module._load = function load(request: string, parent: any, isMain: any) {
if (request !== 'http' && request !== 'https') {
return original.apply(this, arguments);
}
const modules = lookup[request];
const ext = extensionPaths.findSubstr(URI.file(parent.filename).fsPath);
if (ext && ext.enableProposedApi) {
return modules[(<any>ext).proxySupport] || modules.onRequest;
}
return modules.default;
};
});
}

View File

@@ -9,9 +9,7 @@ import { CharCode } from 'vs/base/common/charCode';
import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { MarshalledObject } from 'vs/base/common/marshalling';
import { URI } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { IURITransformer, transformIncomingURIs } from 'vs/base/common/uriIpc';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { LazyPromise } from 'vs/workbench/services/extensions/node/lazyPromise';
import { IRPCProtocol, ProxyIdentifier, getStringIdentifierForProxy } from 'vs/workbench/services/extensions/node/proxyIdentifier';
@@ -40,75 +38,6 @@ function createURIReplacer(transformer: IURITransformer | null): JSONStringifyRe
};
}
function _transformOutgoingURIs(obj: any, transformer: IURITransformer, depth: number): any {
if (!obj || depth > 200) {
return null;
}
if (typeof obj === 'object') {
if (obj instanceof URI) {
return transformer.transformOutgoing(obj);
}
// walk object (or array)
for (let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
const r = _transformOutgoingURIs(obj[key], transformer, depth + 1);
if (r !== null) {
obj[key] = r;
}
}
}
}
return null;
}
export function transformOutgoingURIs<T>(obj: T, transformer: IURITransformer): T {
const result = _transformOutgoingURIs(obj, transformer, 0);
if (result === null) {
// no change
return obj;
}
return result;
}
function _transformIncomingURIs(obj: any, transformer: IURITransformer, depth: number): any {
if (!obj || depth > 200) {
return null;
}
if (typeof obj === 'object') {
if ((<MarshalledObject>obj).$mid === 1) {
return transformer.transformIncoming(obj);
}
// walk object (or array)
for (let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
const r = _transformIncomingURIs(obj[key], transformer, depth + 1);
if (r !== null) {
obj[key] = r;
}
}
}
}
return null;
}
function transformIncomingURIs<T>(obj: T, transformer: IURITransformer): T {
const result = _transformIncomingURIs(obj, transformer, 0);
if (result === null) {
// no change
return obj;
}
return result;
}
export const enum RequestInitiator {
LocalSide = 0,
OtherSide = 1
@@ -354,7 +283,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
}
const callId = String(req);
let promise: Thenable<any>;
let promise: Promise<any>;
let cancel: () => void;
if (usesCancellationToken) {
const cancellationTokenSource = new CancellationTokenSource();
@@ -441,7 +370,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
pendingReply.resolveErr(err);
}
private _invokeHandler(rpcId: number, methodName: string, args: any[]): Thenable<any> {
private _invokeHandler(rpcId: number, methodName: string, args: any[]): Promise<any> {
try {
return Promise.resolve(this._doInvokeHandler(rpcId, methodName, args));
} catch (err) {
@@ -461,7 +390,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
return method.apply(actor, args);
}
private _remoteCall(rpcId: number, methodName: string, args: any[]): Thenable<any> {
private _remoteCall(rpcId: number, methodName: string, args: any[]): Promise<any> {
if (this._isDisposed) {
return Promise.reject<any>(errors.canceled());
}
@@ -592,7 +521,7 @@ class MessageBuffer {
return buff;
}
public static sizeMixedArray(arr: (string | Buffer)[], arrLengths: number[]): number {
public static sizeMixedArray(arr: Array<string | Buffer>, arrLengths: number[]): number {
let size = 0;
size += 1; // arr length
for (let i = 0, len = arr.length; i < len; i++) {
@@ -608,7 +537,7 @@ class MessageBuffer {
return size;
}
public writeMixedArray(arr: (string | Buffer)[], arrLengths: number[]): void {
public writeMixedArray(arr: Array<string | Buffer>, arrLengths: number[]): void {
this._buff.writeUInt8(arr.length, this._offset, true); this._offset += 1;
for (let i = 0, len = arr.length; i < len; i++) {
const el = arr[i];
@@ -623,9 +552,9 @@ class MessageBuffer {
}
}
public readMixedArray(): (string | Buffer)[] {
public readMixedArray(): Array<string | Buffer> {
const arrLen = this._buff.readUInt8(this._offset, true); this._offset += 1;
let arr: (string | Buffer)[] = new Array(arrLen);
let arr: Array<string | Buffer> = new Array(arrLen);
for (let i = 0; i < arrLen; i++) {
const argType = <ArgType>this.readUInt8();
if (argType === ArgType.String) {
@@ -651,7 +580,7 @@ class MessageIO {
public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): Buffer {
if (this._arrayContainsBuffer(args)) {
let massagedArgs: (string | Buffer)[] = new Array(args.length);
let massagedArgs: Array<string | Buffer> = new Array(args.length);
let argsLengths: number[] = new Array(args.length);
for (let i = 0, len = args.length; i < len; i++) {
const arg = args[i];
@@ -695,7 +624,7 @@ class MessageIO {
};
}
private static _requestMixedArgs(req: number, rpcId: number, method: string, args: (string | Buffer)[], argsLengths: number[], usesCancellationToken: boolean): Buffer {
private static _requestMixedArgs(req: number, rpcId: number, method: string, args: Array<string | Buffer>, argsLengths: number[], usesCancellationToken: boolean): Buffer {
const methodByteLength = Buffer.byteLength(method, 'utf8');
let len = 0;

View File

@@ -15,7 +15,7 @@ suite('RPCProtocol', () => {
class MessagePassingProtocol implements IMessagePassingProtocol {
private _pair: MessagePassingProtocol;
private readonly _onMessage: Emitter<Buffer> = new Emitter<Buffer>();
private readonly _onMessage = new Emitter<Buffer>();
public readonly onMessage: Event<Buffer> = this._onMessage.event;
public setPair(other: MessagePassingProtocol) {
@@ -32,7 +32,7 @@ suite('RPCProtocol', () => {
let delegate: (a1: any, a2: any) => any;
let bProxy: BClass;
class BClass {
$m(a1: any, a2: any): Thenable<any> {
$m(a1: any, a2: any): Promise<any> {
return Promise.resolve(delegate.call(null, a1, a2));
}
}
@@ -46,8 +46,6 @@ suite('RPCProtocol', () => {
let A = new RPCProtocol(a_protocol);
let B = new RPCProtocol(b_protocol);
delegate = null;
const bIdentifier = new ProxyIdentifier<BClass>(false, 'bb');
const bInstance = new BClass();
B.set(bIdentifier, bInstance);

View File

@@ -121,8 +121,7 @@ export class ResourceEncodings extends Disposable implements IResourceEncodings
private getEncodingOverride(resource: uri): string | null {
if (resource && this.encodingOverride && this.encodingOverride.length) {
for (let i = 0; i < this.encodingOverride.length; i++) {
const override = this.encodingOverride[i];
for (const override of this.encodingOverride) {
// check if the resource is child of encoding override path
if (override.parent && isParent(resource.fsPath, override.parent.fsPath, !isLinux /* ignorecase */)) {

View File

@@ -121,7 +121,7 @@ export class FileService extends Disposable implements IFileService {
}
private handleError(error: string | Error): void {
const msg = error ? error.toString() : void 0;
const msg = error ? error.toString() : undefined;
if (!msg) {
return;
}
@@ -207,7 +207,7 @@ export class FileService extends Disposable implements IFileService {
throw new Error('not implemented');
}
activateProvider(scheme: string): Thenable<void> {
activateProvider(scheme: string): Promise<void> {
return Promise.reject(new Error('not implemented'));
}
@@ -215,20 +215,20 @@ export class FileService extends Disposable implements IFileService {
return resource.scheme === Schemas.file;
}
resolveFile(resource: uri, options?: IResolveFileOptions): Thenable<IFileStat> {
resolveFile(resource: uri, options?: IResolveFileOptions): Promise<IFileStat> {
return this.resolve(resource, options);
}
resolveFiles(toResolve: { resource: uri, options?: IResolveFileOptions }[]): Thenable<IResolveFileResult[]> {
resolveFiles(toResolve: { resource: uri, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]> {
return Promise.all(toResolve.map(resourceAndOptions => this.resolve(resourceAndOptions.resource, resourceAndOptions.options)
.then(stat => ({ stat, success: true }), error => ({ stat: void 0, success: false }))));
.then(stat => ({ stat, success: true }), error => ({ stat: undefined, success: false }))));
}
existsFile(resource: uri): Thenable<boolean> {
existsFile(resource: uri): Promise<boolean> {
return this.resolveFile(resource).then(() => true, () => false);
}
resolveContent(resource: uri, options?: IResolveContentOptions): Thenable<IContent> {
resolveContent(resource: uri, options?: IResolveContentOptions): Promise<IContent> {
return this.resolveStreamContent(resource, options).then(streamContent => {
return new Promise<IContent>((resolve, reject) => {
@@ -251,7 +251,7 @@ export class FileService extends Disposable implements IFileService {
});
}
resolveStreamContent(resource: uri, options?: IResolveContentOptions): Thenable<IStreamContent> {
resolveStreamContent(resource: uri, options?: IResolveContentOptions): Promise<IStreamContent> {
// Guard early against attempts to resolve an invalid file path
if (resource.scheme !== Schemas.file || !resource.fsPath) {
@@ -263,13 +263,13 @@ export class FileService extends Disposable implements IFileService {
}
const result: IStreamContent = {
resource: void 0,
name: void 0,
mtime: void 0,
etag: void 0,
encoding: void 0,
resource: undefined,
name: undefined,
mtime: undefined,
etag: undefined,
encoding: undefined,
isReadonly: false,
value: void 0
value: undefined
};
const contentResolverTokenSource = new CancellationTokenSource();
@@ -326,7 +326,7 @@ export class FileService extends Disposable implements IFileService {
}
}
return void 0;
return undefined;
}, err => {
// Wrap file not found errors
@@ -341,7 +341,7 @@ export class FileService extends Disposable implements IFileService {
return onStatError(err);
});
let completePromise: Thenable<void>;
let completePromise: Promise<void>;
// await the stat iff we already have an etag so that we compare the
// etag from the stat before we actually read the file again.
@@ -358,8 +358,8 @@ export class FileService extends Disposable implements IFileService {
let contentsError: Error;
completePromise = Promise.all([
statsPromise.then(() => void 0, error => statsError = error),
this.fillInContents(result, resource, options, contentResolverTokenSource.token).then(() => void 0, error => contentsError = error)
statsPromise.then(() => undefined, error => statsError = error),
this.fillInContents(result, resource, options, contentResolverTokenSource.token).then(() => undefined, error => contentsError = error)
]).then(() => {
// Since each file operation can return a FileOperationError
// we want to prefer that one if possible. Otherwise we just
@@ -376,7 +376,7 @@ export class FileService extends Disposable implements IFileService {
return Promise.reject(statsError || contentsError);
}
return void 0;
return undefined;
});
}
@@ -391,19 +391,19 @@ export class FileService extends Disposable implements IFileService {
});
}
private fillInContents(content: IStreamContent, resource: uri, options: IResolveContentOptions, token: CancellationToken): Thenable<void> {
private fillInContents(content: IStreamContent, resource: uri, options: IResolveContentOptions, token: CancellationToken): Promise<void> {
return this.resolveFileData(resource, options, token).then(data => {
content.encoding = data.encoding;
content.value = data.stream;
});
}
private resolveFileData(resource: uri, options: IResolveContentOptions, token: CancellationToken): Thenable<IContentData> {
private resolveFileData(resource: uri, options: IResolveContentOptions, token: CancellationToken): Promise<IContentData> {
const chunkBuffer = Buffer.allocUnsafe(64 * 1024);
const result: IContentData = {
encoding: void 0,
stream: void 0
encoding: undefined,
stream: undefined
};
return new Promise<IContentData>((resolve, reject) => {
@@ -528,7 +528,7 @@ export class FileService extends Disposable implements IFileService {
resolve(result);
handleChunk(bytesRead);
}
}).then(void 0, err => {
}).then(undefined, err => {
// failed to get encoding
finish(err);
});
@@ -542,7 +542,7 @@ export class FileService extends Disposable implements IFileService {
});
}
updateContent(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): Thenable<IFileStat> {
updateContent(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): Promise<IFileStat> {
if (options.writeElevated) {
return this.doUpdateContentElevated(resource, value, options);
}
@@ -550,12 +550,12 @@ export class FileService extends Disposable implements IFileService {
return this.doUpdateContent(resource, value, options);
}
private doUpdateContent(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): Thenable<IFileStat> {
private doUpdateContent(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): Promise<IFileStat> {
const absolutePath = this.toAbsolutePath(resource);
// 1.) check file for writing
return this.checkFileBeforeWriting(absolutePath, options).then(exists => {
let createParentsPromise: Thenable<boolean>;
let createParentsPromise: Promise<boolean>;
if (exists) {
createParentsPromise = Promise.resolve();
} else {
@@ -565,7 +565,7 @@ export class FileService extends Disposable implements IFileService {
// 2.) create parents as needed
return createParentsPromise.then(() => {
const encodingToWrite = this._encoding.getWriteEncoding(resource, options.encoding);
let addBomPromise: Thenable<boolean> = Promise.resolve(false);
let addBomPromise: Promise<boolean> = Promise.resolve(false);
// UTF_16 BE and LE as well as UTF_8 with BOM always have a BOM
if (encodingToWrite === encoding.UTF16be || encodingToWrite === encoding.UTF16le || encodingToWrite === encoding.UTF8_with_bom) {
@@ -599,7 +599,7 @@ export class FileService extends Disposable implements IFileService {
return pfs.truncate(absolutePath, 0).then(() => {
// 5.) set contents (with r+ mode) and resolve
return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { flag: 'r+' }).then(null, error => {
return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { flag: 'r+' }).then(undefined, error => {
if (this.environmentService.verbose) {
console.error(`Truncate succeeded, but save failed (${error}), retrying after 100ms`);
}
@@ -622,7 +622,7 @@ export class FileService extends Disposable implements IFileService {
}
});
});
}).then(null, error => {
}).then(undefined, error => {
if (error.code === 'EACCES' || error.code === 'EPERM') {
return Promise.reject(new FileOperationError(
nls.localize('filePermission', "Permission denied writing to file ({0})", resource.toString(true)),
@@ -635,7 +635,7 @@ export class FileService extends Disposable implements IFileService {
});
}
private doSetContentsAndResolve(resource: uri, absolutePath: string, value: string | ITextSnapshot, addBOM: boolean, encodingToWrite: string, options?: { mode?: number; flag?: string; }): Thenable<IFileStat> {
private doSetContentsAndResolve(resource: uri, absolutePath: string, value: string | ITextSnapshot, addBOM: boolean, encodingToWrite: string, options?: { mode?: number; flag?: string; }): Promise<IFileStat> {
// Configure encoding related options as needed
const writeFileOptions: extfs.IWriteFileOptions = options ? options : Object.create(null);
if (addBOM || encodingToWrite !== encoding.UTF8) {
@@ -645,7 +645,7 @@ export class FileService extends Disposable implements IFileService {
};
}
let writeFilePromise: Thenable<void>;
let writeFilePromise: Promise<void>;
if (typeof value === 'string') {
writeFilePromise = pfs.writeFile(absolutePath, value, writeFileOptions);
} else {
@@ -660,7 +660,7 @@ export class FileService extends Disposable implements IFileService {
});
}
private doUpdateContentElevated(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): Thenable<IFileStat> {
private doUpdateContentElevated(resource: uri, value: string | ITextSnapshot, options: IUpdateContentOptions = Object.create(null)): Promise<IFileStat> {
const absolutePath = this.toAbsolutePath(resource);
// 1.) check file for writing
@@ -678,7 +678,7 @@ export class FileService extends Disposable implements IFileService {
return new Promise<void>((resolve, reject) => {
const promptOptions = {
name: this.environmentService.appNameLong.replace('-', ''),
icns: (isMacintosh && this.environmentService.isBuilt) ? paths.join(paths.dirname(this.environmentService.appRoot), `${product.nameShort}.icns`) : void 0
icns: (isMacintosh && this.environmentService.isBuilt) ? paths.join(paths.dirname(this.environmentService.appRoot), `${product.nameShort}.icns`) : undefined
};
const sudoCommand: string[] = [`"${this.environmentService.cliPath}"`];
@@ -691,7 +691,7 @@ export class FileService extends Disposable implements IFileService {
if (error || stderr) {
reject(error || stderr);
} else {
resolve(void 0);
resolve(undefined);
}
});
});
@@ -705,7 +705,7 @@ export class FileService extends Disposable implements IFileService {
});
});
});
}).then(null, error => {
}).then(undefined, error => {
if (this.environmentService.verbose) {
this.handleError(`Unable to write to file '${resource.toString(true)}' as elevated user (${error})`);
}
@@ -722,10 +722,10 @@ export class FileService extends Disposable implements IFileService {
});
}
createFile(resource: uri, content: string = '', options: ICreateFileOptions = Object.create(null)): Thenable<IFileStat> {
createFile(resource: uri, content: string = '', options: ICreateFileOptions = Object.create(null)): Promise<IFileStat> {
const absolutePath = this.toAbsolutePath(resource);
let checkFilePromise: Thenable<boolean>;
let checkFilePromise: Promise<boolean>;
if (options.overwrite) {
checkFilePromise = Promise.resolve(false);
} else {
@@ -753,13 +753,13 @@ export class FileService extends Disposable implements IFileService {
});
}
readFolder(resource: uri): Thenable<string[]> {
readFolder(resource: uri): Promise<string[]> {
const absolutePath = this.toAbsolutePath(resource);
return pfs.readdir(absolutePath);
}
createFolder(resource: uri): Thenable<IFileStat> {
createFolder(resource: uri): Promise<IFileStat> {
// 1.) Create folder
const absolutePath = this.toAbsolutePath(resource);
@@ -776,7 +776,7 @@ export class FileService extends Disposable implements IFileService {
});
}
private checkFileBeforeWriting(absolutePath: string, options: IUpdateContentOptions = Object.create(null), ignoreReadonly?: boolean): Thenable<boolean /* exists */> {
private checkFileBeforeWriting(absolutePath: string, options: IUpdateContentOptions = Object.create(null), ignoreReadonly?: boolean): Promise<boolean /* exists */> {
return pfs.exists(absolutePath).then(exists => {
if (exists) {
return pfs.stat(absolutePath).then(stat => {
@@ -830,7 +830,7 @@ export class FileService extends Disposable implements IFileService {
});
}
private readOnlyError<T>(options: IUpdateContentOptions): Thenable<T> {
private readOnlyError<T>(options: IUpdateContentOptions): Promise<T> {
return Promise.reject(new FileOperationError(
nls.localize('fileReadOnlyError', "File is Read Only"),
FileOperationResult.FILE_READ_ONLY,
@@ -838,15 +838,15 @@ export class FileService extends Disposable implements IFileService {
));
}
moveFile(source: uri, target: uri, overwrite?: boolean): Thenable<IFileStat> {
moveFile(source: uri, target: uri, overwrite?: boolean): Promise<IFileStat> {
return this.moveOrCopyFile(source, target, false, overwrite);
}
copyFile(source: uri, target: uri, overwrite?: boolean): Thenable<IFileStat> {
copyFile(source: uri, target: uri, overwrite?: boolean): Promise<IFileStat> {
return this.moveOrCopyFile(source, target, true, overwrite);
}
private moveOrCopyFile(source: uri, target: uri, keepCopy: boolean, overwrite: boolean): Thenable<IFileStat> {
private moveOrCopyFile(source: uri, target: uri, keepCopy: boolean, overwrite: boolean): Promise<IFileStat> {
const sourcePath = this.toAbsolutePath(source);
const targetPath = this.toAbsolutePath(target);
@@ -856,25 +856,28 @@ export class FileService extends Disposable implements IFileService {
// 2.) resolve
return this.resolve(target).then(result => {
// Events
this._onAfterOperation.fire(new FileOperationEvent(source, keepCopy ? FileOperation.COPY : FileOperation.MOVE, result));
// Events (unless it was a no-op because paths are identical)
if (sourcePath !== targetPath) {
this._onAfterOperation.fire(new FileOperationEvent(source, keepCopy ? FileOperation.COPY : FileOperation.MOVE, result));
}
return result;
});
});
}
private doMoveOrCopyFile(sourcePath: string, targetPath: string, keepCopy: boolean, overwrite: boolean): Thenable<boolean /* exists */> {
private doMoveOrCopyFile(sourcePath: string, targetPath: string, keepCopy: boolean, overwrite: boolean): Promise<void> {
// 1.) validate operation
if (isParent(targetPath, sourcePath, !isLinux)) {
return Promise.reject(new Error('Unable to move/copy when source path is parent of target path'));
} else if (sourcePath === targetPath) {
return Promise.resolve(); // no-op but not an error
}
// 2.) check if target exists
return pfs.exists(targetPath).then(exists => {
const isCaseRename = sourcePath.toLowerCase() === targetPath.toLowerCase();
const isSameFile = sourcePath === targetPath;
// Return early with conflict if target exists and we are not told to overwrite
if (exists && !isCaseRename && !overwrite) {
@@ -882,7 +885,7 @@ export class FileService extends Disposable implements IFileService {
}
// 3.) make sure target is deleted before we move/copy unless this is a case rename of the same file
let deleteTargetPromise: Thenable<void> = Promise.resolve();
let deleteTargetPromise: Promise<void> = Promise.resolve();
if (exists && !isCaseRename) {
if (isEqualOrParent(sourcePath, targetPath, !isLinux /* ignorecase */)) {
return Promise.reject(new Error(nls.localize('unableToMoveCopyError', "Unable to move/copy. File would replace folder it is contained in."))); // catch this corner case!
@@ -897,19 +900,17 @@ export class FileService extends Disposable implements IFileService {
return pfs.mkdirp(paths.dirname(targetPath)).then(() => {
// 4.) copy/move
if (isSameFile) {
return null;
} else if (keepCopy) {
if (keepCopy) {
return nfcall(extfs.copy, sourcePath, targetPath);
} else {
return nfcall(extfs.mv, sourcePath, targetPath);
}
}).then(() => exists);
});
});
});
}
del(resource: uri, options?: { useTrash?: boolean, recursive?: boolean }): Thenable<void> {
del(resource: uri, options?: { useTrash?: boolean, recursive?: boolean }): Promise<void> {
if (options && options.useTrash) {
return this.doMoveItemToTrash(resource);
}
@@ -917,7 +918,7 @@ export class FileService extends Disposable implements IFileService {
return this.doDelete(resource, options && options.recursive);
}
private doMoveItemToTrash(resource: uri): Thenable<void> {
private doMoveItemToTrash(resource: uri): Promise<void> {
const absolutePath = resource.fsPath;
const shell = (require('electron') as any as Electron.RendererInterface).shell; // workaround for being able to run tests out of VSCode debugger
@@ -931,19 +932,19 @@ export class FileService extends Disposable implements IFileService {
return Promise.resolve();
}
private doDelete(resource: uri, recursive: boolean): Thenable<void> {
private doDelete(resource: uri, recursive: boolean): Promise<void> {
const absolutePath = this.toAbsolutePath(resource);
let assertNonRecursiveDelete: Thenable<void>;
let assertNonRecursiveDelete: Promise<void>;
if (!recursive) {
assertNonRecursiveDelete = pfs.stat(absolutePath).then(stat => {
if (!stat.isDirectory()) {
return Promise.resolve();
return undefined;
}
return pfs.readdir(absolutePath).then(children => {
if (children.length === 0) {
return Promise.resolve();
return undefined;
}
return Promise.reject(new Error(nls.localize('deleteFailed', "Failed to delete non-empty folder '{0}'.", paths.basename(absolutePath))));
@@ -977,15 +978,15 @@ export class FileService extends Disposable implements IFileService {
return paths.normalize(resource.fsPath);
}
private resolve(resource: uri, options: IResolveFileOptions = Object.create(null)): Thenable<IFileStat> {
private resolve(resource: uri, options: IResolveFileOptions = Object.create(null)): Promise<IFileStat> {
return this.toStatResolver(resource).then(model => model.resolve(options));
}
private toStatResolver(resource: uri): Thenable<StatResolver> {
private toStatResolver(resource: uri): Promise<StatResolver> {
const absolutePath = this.toAbsolutePath(resource);
return pfs.statLink(absolutePath).then(({ isSymbolicLink, stat }) => {
return new StatResolver(resource, isSymbolicLink, stat.isDirectory(), stat.mtime.getTime(), stat.size, this.environmentService.verbose ? err => this.handleError(err) : void 0);
return new StatResolver(resource, isSymbolicLink, stat.isDirectory(), stat.mtime.getTime(), stat.size, this.environmentService.verbose ? err => this.handleError(err) : undefined);
});
}
@@ -1141,7 +1142,7 @@ export class StatResolver {
this.etag = etag(size, mtime);
}
resolve(options: IResolveFileOptions): Thenable<IFileStat> {
resolve(options: IResolveFileOptions): Promise<IFileStat> {
// General Data
const fileStat: IFileStat = {

View File

@@ -36,7 +36,7 @@ class TypeOnlyStat implements IStat {
size: number = 0;
}
function toIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], recurse?: (tuple: [URI, IStat]) => boolean): Thenable<IFileStat> {
function toIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], recurse?: (tuple: [URI, IStat]) => boolean): Promise<IFileStat> {
const [resource, stat] = tuple;
const fileStat: IFileStat = {
resource,
@@ -70,7 +70,7 @@ function toIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], recurse
return Promise.resolve(fileStat);
}
export function toDeepIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], to: URI[]): Thenable<IFileStat> {
export function toDeepIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], to: URI[]): Promise<IFileStat> {
const trie = TernarySearchTree.forPaths<true>();
trie.set(tuple[0].toString(), true);
@@ -90,8 +90,8 @@ class WorkspaceWatchLogic extends Disposable {
constructor(
private _fileService: RemoteFileService,
@IConfigurationService private _configurationService: IConfigurationService,
@IWorkspaceContextService private _contextService: IWorkspaceContextService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
) {
super();
@@ -205,7 +205,7 @@ export class RemoteFileService extends FileService {
};
}
activateProvider(scheme: string): Thenable<void> {
activateProvider(scheme: string): Promise<void> {
return this._extensionService.activateByEvent('onFileSystem:' + scheme);
}
@@ -269,7 +269,7 @@ export class RemoteFileService extends FileService {
});
}
existsFile(resource: URI): Thenable<boolean> {
existsFile(resource: URI): Promise<boolean> {
if (resource.scheme === Schemas.file) {
return super.existsFile(resource);
} else {
@@ -277,7 +277,7 @@ export class RemoteFileService extends FileService {
}
}
resolveFile(resource: URI, options?: IResolveFileOptions): Thenable<IFileStat> {
resolveFile(resource: URI, options?: IResolveFileOptions): Promise<IFileStat> {
if (resource.scheme === Schemas.file) {
return super.resolveFile(resource, options);
} else {
@@ -294,10 +294,10 @@ export class RemoteFileService extends FileService {
}
}
resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Thenable<IResolveFileResult[]> {
resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Promise<IResolveFileResult[]> {
// soft-groupBy, keep order, don't rearrange/merge groups
let groups: (typeof toResolve)[] = [];
let groups: Array<typeof toResolve> = [];
let group: typeof toResolve;
for (const request of toResolve) {
if (!group || group[0].resource.scheme !== request.resource.scheme) {
@@ -307,7 +307,7 @@ export class RemoteFileService extends FileService {
group.push(request);
}
const promises: Thenable<IResolveFileResult[]>[] = [];
const promises: Promise<IResolveFileResult[]>[] = [];
for (const group of groups) {
if (group[0].resource.scheme === Schemas.file) {
promises.push(super.resolveFiles(group));
@@ -336,7 +336,7 @@ export class RemoteFileService extends FileService {
// --- resolve
resolveContent(resource: URI, options?: IResolveContentOptions): Thenable<IContent> {
resolveContent(resource: URI, options?: IResolveContentOptions): Promise<IContent> {
if (resource.scheme === Schemas.file) {
return super.resolveContent(resource, options);
} else {
@@ -344,7 +344,7 @@ export class RemoteFileService extends FileService {
}
}
resolveStreamContent(resource: URI, options?: IResolveContentOptions): Thenable<IStreamContent> {
resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent> {
if (resource.scheme === Schemas.file) {
return super.resolveStreamContent(resource, options);
} else {
@@ -438,7 +438,7 @@ export class RemoteFileService extends FileService {
return provider;
}
createFile(resource: URI, content?: string, options?: ICreateFileOptions): Thenable<IFileStat> {
createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise<IFileStat> {
if (resource.scheme === Schemas.file) {
return super.createFile(resource, content, options);
} else {
@@ -461,7 +461,7 @@ export class RemoteFileService extends FileService {
}
}
updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Thenable<IFileStat> {
updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Promise<IFileStat> {
if (resource.scheme === Schemas.file) {
return super.updateContent(resource, value, options);
} else {
@@ -482,7 +482,7 @@ export class RemoteFileService extends FileService {
return new Promise<IFileStat>((resolve, reject) => {
readable.pipe(encoder).pipe(target);
target.once('error', err => reject(err));
target.once('finish', _ => resolve(void 0));
target.once('finish', _ => resolve(undefined));
}).then(_ => {
return this.resolveFile(resource);
});
@@ -507,7 +507,7 @@ export class RemoteFileService extends FileService {
// --- delete
del(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Thenable<void> {
del(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise<void> {
if (resource.scheme === Schemas.file) {
return super.del(resource, options);
} else {
@@ -519,7 +519,7 @@ export class RemoteFileService extends FileService {
}
}
readFolder(resource: URI): Thenable<string[]> {
readFolder(resource: URI): Promise<string[]> {
if (resource.scheme === Schemas.file) {
return super.readFolder(resource);
} else {
@@ -529,7 +529,7 @@ export class RemoteFileService extends FileService {
}
}
createFolder(resource: URI): Thenable<IFileStat> {
createFolder(resource: URI): Promise<IFileStat> {
if (resource.scheme === Schemas.file) {
return super.createFolder(resource);
} else {
@@ -546,7 +546,7 @@ export class RemoteFileService extends FileService {
}
}
moveFile(source: URI, target: URI, overwrite?: boolean): Thenable<IFileStat> {
moveFile(source: URI, target: URI, overwrite?: boolean): Promise<IFileStat> {
if (source.scheme !== target.scheme) {
return this._doMoveAcrossScheme(source, target);
} else if (source.scheme === Schemas.file) {
@@ -563,22 +563,24 @@ export class RemoteFileService extends FileService {
: Promise.resolve(null);
return prepare.then(() => this._withProvider(source)).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => {
return provider.rename(source, target, { overwrite }).then(() => {
return this.resolveFile(target);
}).then(fileStat => {
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.MOVE, fileStat));
return fileStat;
}, err => {
const result = this._tryParseFileOperationResult(err);
if (result === FileOperationResult.FILE_MOVE_CONFLICT) {
throw new FileOperationError(localize('fileMoveConflict', "Unable to move/copy. File already exists at destination."), result);
}
throw err;
return RemoteFileService._mkdirp(provider, resources.dirname(target)).then(() => {
return provider.rename(source, target, { overwrite }).then(() => {
return this.resolveFile(target);
}).then(fileStat => {
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.MOVE, fileStat));
return fileStat;
}, err => {
const result = this._tryParseFileOperationResult(err);
if (result === FileOperationResult.FILE_MOVE_CONFLICT) {
throw new FileOperationError(localize('fileMoveConflict', "Unable to move/copy. File already exists at destination."), result);
}
throw err;
});
});
});
}
private _doMoveAcrossScheme(source: URI, target: URI, overwrite?: boolean): Thenable<IFileStat> {
private _doMoveAcrossScheme(source: URI, target: URI, overwrite?: boolean): Promise<IFileStat> {
return this.copyFile(source, target, overwrite).then(() => {
return this.del(source, { recursive: true });
}).then(() => {
@@ -589,7 +591,7 @@ export class RemoteFileService extends FileService {
});
}
copyFile(source: URI, target: URI, overwrite?: boolean): Thenable<IFileStat> {
copyFile(source: URI, target: URI, overwrite?: boolean): Promise<IFileStat> {
if (source.scheme === target.scheme && source.scheme === Schemas.file) {
return super.copyFile(source, target, overwrite);
}
@@ -646,7 +648,7 @@ export class RemoteFileService extends FileService {
});
}
private _activeWatches = new Map<string, { unwatch: Thenable<IDisposable>, count: number }>();
private _activeWatches = new Map<string, { unwatch: Promise<IDisposable>, count: number }>();
watchFileChanges(resource: URI, opts?: IWatchOptions): void {
if (resource.scheme === Schemas.file) {

View File

@@ -43,14 +43,14 @@ function createSimpleWritable(provider: IFileSystemProvider, resource: URI, opts
function createWritable(provider: IFileSystemProvider, resource: URI, opts: FileWriteOptions): Writable {
return new class extends Writable {
_fd: number;
_pos: number;
_pos: number = 0;
constructor(opts?) {
super(opts);
}
async _write(chunk: Buffer, encoding, callback: Function) {
try {
if (typeof this._fd !== 'number') {
this._fd = await provider.open(resource);
this._fd = await provider.open(resource, { create: true });
}
let bytesWritten = await provider.write(this._fd, this._pos, chunk, 0, chunk.length);
this._pos += bytesWritten;
@@ -59,12 +59,12 @@ function createWritable(provider: IFileSystemProvider, resource: URI, opts: File
callback(err);
}
}
end() {
provider.close(this._fd).then(_ => {
super.end();
}, err => {
this.emit('error', err);
});
_final(callback: (err?: any) => any) {
if (typeof this._fd !== 'number') {
provider.open(resource, { create: true }).then(fd => provider.close(fd)).finally(callback);
} else {
provider.close(this._fd).finally(callback);
}
}
};
}
@@ -84,10 +84,7 @@ function createReadable(provider: IFileSystemProvider, resource: URI, position:
_fd: number;
_pos: number = position;
_reading: boolean = false;
constructor(opts?) {
super(opts);
this.once('close', _ => this._final());
}
async _read(size?: number) {
if (this._reading) {
return;
@@ -95,29 +92,28 @@ function createReadable(provider: IFileSystemProvider, resource: URI, position:
this._reading = true;
try {
if (typeof this._fd !== 'number') {
this._fd = await provider.open(resource);
this._fd = await provider.open(resource, { create: false });
}
let buffer = Buffer.allocUnsafe(64 * 1024);
while (this._reading) {
let buffer = Buffer.allocUnsafe(size);
let bytesRead = await provider.read(this._fd, this._pos, buffer, 0, buffer.length);
if (bytesRead === 0) {
await provider.close(this._fd);
this._reading = false;
this.push(null);
}
else {
} else {
this._reading = this.push(buffer.slice(0, bytesRead));
this._pos += bytesRead;
}
}
}
catch (err) {
} catch (err) {
//
this.emit('error', err);
}
}
async _final() {
_destroy(_err: any, callback: (err?: any) => any) {
if (typeof this._fd === 'number') {
await provider.close(this._fd);
provider.close(this._fd).then(callback, callback);
}
}
};
@@ -125,7 +121,7 @@ function createReadable(provider: IFileSystemProvider, resource: URI, position:
function createSimpleReadable(provider: IFileSystemProvider, resource: URI, position: number): Readable {
return new class extends Readable {
_readOperation: Thenable<any>;
_readOperation: Promise<any>;
_read(size?: number): void {
if (this._readOperation) {
return;

View File

@@ -30,8 +30,7 @@ export function normalize(changes: IRawFileChange[]): IRawFileChange[] {
// Build deltas
let normalizer = new EventNormalizer();
for (let i = 0; i < changes.length; i++) {
let event = changes[i];
for (const event of changes) {
normalizer.processEvent(event);
}

View File

@@ -27,7 +27,7 @@ interface IWatcherObjet {
}
interface IPathWatcher {
ready: Thenable<IWatcherObjet>;
ready: Promise<IWatcherObjet>;
watcher?: IWatcherObjet;
ignored: glob.ParsedPattern[];
}
@@ -49,7 +49,7 @@ export class NsfwWatcherService implements IWatcherService {
private _watch(request: IWatcherRequest): void {
let undeliveredFileEvents: watcher.IRawFileChange[] = [];
const fileEventDelayer = new ThrottledDelayer(NsfwWatcherService.FS_EVENT_DELAY);
const fileEventDelayer = new ThrottledDelayer<void>(NsfwWatcherService.FS_EVENT_DELAY);
let readyPromiseResolve: (watcher: IWatcherObjet) => void;
this._pathWatchers[request.basePath] = {
@@ -99,12 +99,10 @@ export class NsfwWatcherService implements IWatcherService {
}
nsfw(request.basePath, events => {
for (let i = 0; i < events.length; i++) {
const e = events[i];
for (const e of events) {
// Logging
if (this._verboseLogging) {
const logPath = e.action === nsfw.actions.RENAMED ? path.join(e.directory, e.oldFile) + ' -> ' + e.newFile : path.join(e.directory, e.file);
const logPath = e.action === nsfw.actions.RENAMED ? path.join(e.directory, e.oldFile || '') + ' -> ' + e.newFile : path.join(e.directory, e.file || '');
console.log(`${e.action === nsfw.actions.CREATED ? '[CREATED]' : e.action === nsfw.actions.DELETED ? '[DELETED]' : e.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]'} ${logPath}`);
}
@@ -112,20 +110,20 @@ export class NsfwWatcherService implements IWatcherService {
let absolutePath: string;
if (e.action === nsfw.actions.RENAMED) {
// Rename fires when a file's name changes within a single directory
absolutePath = path.join(e.directory, e.oldFile);
absolutePath = path.join(e.directory, e.oldFile || '');
if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.basePath].ignored)) {
undeliveredFileEvents.push({ type: FileChangeType.DELETED, path: absolutePath });
} else if (this._verboseLogging) {
console.log(' >> ignored', absolutePath);
}
absolutePath = path.join(e.directory, e.newFile);
absolutePath = path.join(e.directory, e.newFile || '');
if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.basePath].ignored)) {
undeliveredFileEvents.push({ type: FileChangeType.ADDED, path: absolutePath });
} else if (this._verboseLogging) {
console.log(' >> ignored', absolutePath);
}
} else {
absolutePath = path.join(e.directory, e.file);
absolutePath = path.join(e.directory, e.file || '');
if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.basePath].ignored)) {
undeliveredFileEvents.push({
type: nsfwActionToRawChangeType[e.action],
@@ -166,7 +164,7 @@ export class NsfwWatcherService implements IWatcherService {
});
}
return Promise.resolve(null);
return Promise.resolve(undefined);
});
}).then(watcher => {
this._pathWatchers[request.basePath].watcher = watcher;
@@ -176,8 +174,8 @@ export class NsfwWatcherService implements IWatcherService {
});
}
public setRoots(roots: IWatcherRequest[]): Thenable<void> {
const promises: Thenable<void>[] = [];
public setRoots(roots: IWatcherRequest[]): Promise<void> {
const promises: Promise<void>[] = [];
const normalizedRoots = this._normalizeRoots(roots);
// Gather roots that are not currently being watched
@@ -211,15 +209,15 @@ export class NsfwWatcherService implements IWatcherService {
}
});
return Promise.all(promises).then(() => void 0);
return Promise.all(promises).then(() => undefined);
}
public setVerboseLogging(enabled: boolean): Thenable<void> {
public setVerboseLogging(enabled: boolean): Promise<void> {
this._verboseLogging = enabled;
return Promise.resolve(null);
return Promise.resolve(undefined);
}
public stop(): Thenable<void> {
public stop(): Promise<void> {
for (let path in this._pathWatchers) {
let watcher = this._pathWatchers[path];
watcher.ready.then(watcher => watcher.stop());

View File

@@ -21,7 +21,7 @@ export interface IWatchError {
export interface IWatcherService {
watch(options: IWatcherOptions): Event<IRawFileChange[] | IWatchError>;
setRoots(roots: IWatcherRequest[]): Thenable<void>;
setVerboseLogging(enabled: boolean): Thenable<void>;
stop(): Thenable<void>;
setRoots(roots: IWatcherRequest[]): Promise<void>;
setVerboseLogging(enabled: boolean): Promise<void>;
stop(): Promise<void>;
}

View File

@@ -20,7 +20,7 @@ export class WatcherChannel implements IServerChannel {
throw new Error(`Event not found: ${event}`);
}
call(_, command: string, arg?: any): Thenable<any> {
call(_, command: string, arg?: any): Promise<any> {
switch (command) {
case 'setRoots': return this.service.setRoots(arg);
case 'setVerboseLogging': return this.service.setVerboseLogging(arg);
@@ -39,15 +39,15 @@ export class WatcherChannelClient implements IWatcherService {
return this.channel.listen('watch', options);
}
setVerboseLogging(enable: boolean): Thenable<void> {
setVerboseLogging(enable: boolean): Promise<void> {
return this.channel.call('setVerboseLogging', enable);
}
setRoots(roots: IWatcherRequest[]): Thenable<void> {
setRoots(roots: IWatcherRequest[]): Promise<void> {
return this.channel.call('setRoots', roots);
}
stop(): Thenable<void> {
stop(): Promise<void> {
return this.channel.call('stop');
}
}

View File

@@ -12,7 +12,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { filterEvent } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IWatchError } from 'vs/workbench/services/files/node/watcher/nsfw/watcher';
import { getPathFromAmdModule } from 'vs/base/common/amd';
@@ -69,12 +69,12 @@ export class FileWatcher {
this.service = new WatcherChannelClient(channel);
const options = { verboseLogging: this.verboseLogging };
const onWatchEvent = filterEvent(this.service.watch(options), () => !this.isDisposed);
const onWatchEvent = Event.filter(this.service.watch(options), () => !this.isDisposed);
const onError = filterEvent<any, IWatchError>(onWatchEvent, (e): e is IWatchError => typeof e.message === 'string');
const onError = Event.filter<any, IWatchError>(onWatchEvent, (e): e is IWatchError => typeof e.message === 'string');
onError(err => this.errorLogger(err.message), null, this.toDispose);
const onFileChanges = filterEvent<any, IRawFileChange[]>(onWatchEvent, (e): e is IRawFileChange[] => Array.isArray(e) && e.length > 0);
const onFileChanges = Event.filter<any, IRawFileChange[]>(onWatchEvent, (e): e is IRawFileChange[] => Array.isArray(e) && e.length > 0);
onFileChanges(e => this.onFileChanges(toFileChangesEvent(e)), null, this.toDispose);
// Start watching

View File

@@ -11,7 +11,6 @@ import * as paths from 'vs/base/common/paths';
import * as glob from 'vs/base/common/glob';
import { FileChangeType } from 'vs/platform/files/common/files';
import { ThrottledDelayer } from 'vs/base/common/async';
import * as strings from 'vs/base/common/strings';
import { normalizeNFC } from 'vs/base/common/normalization';
import { realcaseSync } from 'vs/base/node/extfs';
import { isMacintosh } from 'vs/base/common/platform';
@@ -58,13 +57,13 @@ export class ChokidarWatcherService implements IWatcherService {
return this.onWatchEvent;
}
public setVerboseLogging(enabled: boolean): Thenable<void> {
public setVerboseLogging(enabled: boolean): Promise<void> {
this._verboseLogging = enabled;
return Promise.resolve();
}
public setRoots(requests: IWatcherRequest[]): Thenable<void> {
public setRoots(requests: IWatcherRequest[]): Promise<void> {
const watchers = Object.create(null);
const newRequests: string[] = [];
@@ -220,7 +219,7 @@ export class ChokidarWatcherService implements IWatcherService {
this.spamCheckStartTime = now;
} else if (!this.spamWarningLogged && this.spamCheckStartTime + ChokidarWatcherService.EVENT_SPAM_WARNING_THRESHOLD < now) {
this.spamWarningLogged = true;
console.warn(strings.format('Watcher is busy catching up with {0} file changes in 60 seconds. Latest changed path is "{1}"', undeliveredFileEvents.length, event.path));
console.warn(`Watcher is busy catching up with ${undeliveredFileEvents.length} file changes in 60 seconds. Latest changed path is "${event.path}"`);
}
// Add to buffer
@@ -270,7 +269,7 @@ export class ChokidarWatcherService implements IWatcherService {
return watcher;
}
public stop(): Thenable<void> {
public stop(): Promise<void> {
for (let path in this._watchers) {
let watcher = this._watchers[path];
watcher.stop();

View File

@@ -16,7 +16,7 @@ import { Delayer } from 'vs/base/common/async';
import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common';
import { FileChangeType } from 'vs/platform/files/common/files';
function newRequest(basePath: string, ignored = []): IWatcherRequest {
function newRequest(basePath: string, ignored: string[] = []): IWatcherRequest {
return { basePath, ignored };
}
@@ -127,7 +127,7 @@ suite.skip('Chockidar watching', () => {
const service = new ChokidarWatcherService();
const result: IRawFileChange[] = [];
let error = null;
let error: string | null = null;
suiteSetup(async () => {
await pfs.mkdirp(testDir);

View File

@@ -21,7 +21,7 @@ export interface IWatchError {
export interface IWatcherService {
watch(options: IWatcherOptions): Event<IRawFileChange[] | IWatchError>;
setRoots(roots: IWatcherRequest[]): Thenable<void>;
setVerboseLogging(enabled: boolean): Thenable<void>;
stop(): Thenable<void>;
setRoots(roots: IWatcherRequest[]): Promise<void>;
setVerboseLogging(enabled: boolean): Promise<void>;
stop(): Promise<void>;
}

View File

@@ -20,7 +20,7 @@ export class WatcherChannel implements IServerChannel {
throw new Error(`Event not found: ${event}`);
}
call(_, command: string, arg?: any): Thenable<any> {
call(_, command: string, arg?: any): Promise<any> {
switch (command) {
case 'setRoots': return this.service.setRoots(arg);
case 'setVerboseLogging': return this.service.setVerboseLogging(arg);
@@ -39,15 +39,15 @@ export class WatcherChannelClient implements IWatcherService {
return this.channel.listen('watch', options);
}
setVerboseLogging(enable: boolean): Thenable<void> {
setVerboseLogging(enable: boolean): Promise<void> {
return this.channel.call('setVerboseLogging', enable);
}
setRoots(roots: IWatcherRequest[]): Thenable<void> {
setRoots(roots: IWatcherRequest[]): Promise<void> {
return this.channel.call('setRoots', roots);
}
stop(): Thenable<void> {
stop(): Promise<void> {
return this.channel.call('stop');
}
}

View File

@@ -12,7 +12,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Schemas } from 'vs/base/common/network';
import { filterEvent } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IWatchError } from 'vs/workbench/services/files/node/watcher/unix/watcher';
import { getPathFromAmdModule } from 'vs/base/common/amd';
@@ -71,12 +71,12 @@ export class FileWatcher {
this.service = new WatcherChannelClient(channel);
const options = { verboseLogging: this.verboseLogging };
const onWatchEvent = filterEvent(this.service.watch(options), () => !this.isDisposed);
const onWatchEvent = Event.filter(this.service.watch(options), () => !this.isDisposed);
const onError = filterEvent<any, IWatchError>(onWatchEvent, (e): e is IWatchError => typeof e.message === 'string');
const onError = Event.filter<any, IWatchError>(onWatchEvent, (e): e is IWatchError => typeof e.message === 'string');
onError(err => this.errorLogger(err.message), null, this.toDispose);
const onFileChanges = filterEvent<any, IRawFileChange[]>(onWatchEvent, (e): e is IRawFileChange[] => Array.isArray(e) && e.length > 0);
const onFileChanges = Event.filter<any, IRawFileChange[]>(onWatchEvent, (e): e is IRawFileChange[] => Array.isArray(e) && e.length > 0);
onFileChanges(e => this.onFileChanges(toFileChangesEvent(e)), null, this.toDispose);
// Start watching

View File

@@ -58,7 +58,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.CREATE);
assert.equal(event.target.resource.fsPath, resource.fsPath);
assert.equal(event.target!.resource.fsPath, resource.fsPath);
toDispose.dispose();
});
});
@@ -69,7 +69,7 @@ suite('FileService', () => {
fs.writeFileSync(resource.fsPath, ''); // create file
return service.createFile(resource, contents).then(null, error => {
return service.createFile(resource, contents).then(undefined, error => {
assert.ok(error);
});
});
@@ -93,7 +93,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.CREATE);
assert.equal(event.target.resource.fsPath, resource.fsPath);
assert.equal(event.target!.resource.fsPath, resource.fsPath);
toDispose.dispose();
});
});
@@ -114,8 +114,8 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.CREATE);
assert.equal(event.target.resource.fsPath, resource.fsPath);
assert.equal(event.target.isDirectory, true);
assert.equal(event.target!.resource.fsPath, resource.fsPath);
assert.equal(event.target!.isDirectory, true);
toDispose.dispose();
});
});
@@ -139,8 +139,8 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.CREATE);
assert.equal(event.target.resource.fsPath, resource.fsPath);
assert.equal(event.target.isDirectory, true);
assert.equal(event.target!.resource.fsPath, resource.fsPath);
assert.equal(event.target!.isDirectory, true);
toDispose.dispose();
});
});
@@ -161,7 +161,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.MOVE);
assert.equal(event.target.resource.fsPath, renamed.resource.fsPath);
assert.equal(event.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
});
@@ -185,7 +185,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.MOVE);
assert.equal(event.target.resource.fsPath, renamed.resource.fsPath);
assert.equal(event.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
});
@@ -206,7 +206,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.MOVE);
assert.equal(event.target.resource.fsPath, renamed.resource.fsPath);
assert.equal(event.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
});
@@ -230,7 +230,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.MOVE);
assert.equal(event.target.resource.fsPath, renamed.resource.fsPath);
assert.equal(event.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
});
@@ -250,7 +250,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.MOVE);
assert.equal(event.target.resource.fsPath, renamed.resource.fsPath);
assert.equal(event.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
});
@@ -271,7 +271,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.MOVE);
assert.equal(event.target.resource.fsPath, renamed.resource.fsPath);
assert.equal(event.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
});
@@ -284,7 +284,7 @@ suite('FileService', () => {
});
return service.resolveFile(uri.file(path.join(testDir, 'index.html'))).then(source => {
return service.moveFile(uri.file(testDir), uri.file(path.join(testDir, 'binary.txt'))).then(null, (e: Error) => {
return service.moveFile(uri.file(testDir), uri.file(path.join(testDir, 'binary.txt'))).then(undefined, (e: Error) => {
assert.ok(e);
assert.ok(!event);
@@ -300,7 +300,7 @@ suite('FileService', () => {
});
return service.resolveFile(uri.file(path.join(testDir, 'index.html'))).then(source => {
return service.moveFile(source.resource, uri.file(path.join(testDir, 'binary.txt'))).then(null, (e: FileOperationError) => {
return service.moveFile(source.resource, uri.file(path.join(testDir, 'binary.txt'))).then(undefined, (e: FileOperationError) => {
assert.equal(e.fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT);
assert.ok(!event);
@@ -324,7 +324,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.MOVE);
assert.equal(event.target.resource.fsPath, renamed.resource.fsPath);
assert.equal(event.target!.resource.fsPath, renamed.resource.fsPath);
toDispose.dispose();
});
});
@@ -357,7 +357,7 @@ suite('FileService', () => {
assert.ok(moveEvent);
assert.equal(moveEvent.resource.fsPath, resource.fsPath);
assert.equal(moveEvent.target.resource.fsPath, moved.resource.fsPath);
assert.equal(moveEvent!.target!.resource.fsPath, moved.resource.fsPath);
assert.equal(deleteEvent.resource.fsPath, folderResource.fsPath);
@@ -382,7 +382,7 @@ suite('FileService', () => {
assert.ok(event);
assert.equal(event.resource.fsPath, source.resource.fsPath);
assert.equal(event.operation, FileOperation.COPY);
assert.equal(event.target.resource.fsPath, copied.resource.fsPath);
assert.equal(event.target!.resource.fsPath, copied.resource.fsPath);
toDispose.dispose();
});
});
@@ -415,7 +415,7 @@ suite('FileService', () => {
assert.ok(copyEvent);
assert.equal(copyEvent.resource.fsPath, resource.fsPath);
assert.equal(copyEvent.target.resource.fsPath, copied.resource.fsPath);
assert.equal(copyEvent.target!.resource.fsPath, copied.resource.fsPath);
assert.equal(deleteEvent.resource.fsPath, folderResource.fsPath);
@@ -505,10 +505,10 @@ suite('FileService', () => {
test('resolveFile', () => {
return service.resolveFile(uri.file(testDir), { resolveTo: [uri.file(path.join(testDir, 'deep'))] }).then(r => {
assert.equal(r.children.length, 8);
assert.equal(r.children!.length, 8);
const deep = utils.getByName(r, 'deep');
assert.equal(deep.children.length, 4);
const deep = utils.getByName(r, 'deep')!;
assert.equal(deep.children!.length, 4);
});
});
@@ -519,13 +519,13 @@ suite('FileService', () => {
]).then(res => {
const r1 = res[0].stat;
assert.equal(r1.children.length, 8);
assert.equal(r1.children!.length, 8);
const deep = utils.getByName(r1, 'deep');
assert.equal(deep.children.length, 4);
const deep = utils.getByName(r1, 'deep')!;
assert.equal(deep.children!.length, 4);
const r2 = res[1].stat;
assert.equal(r2.children.length, 4);
assert.equal(r2.children!.length, 4);
assert.equal(r2.name, 'deep');
});
});
@@ -710,7 +710,7 @@ suite('FileService', () => {
test('resolveContent - FILE_IS_BINARY', function () {
const resource = uri.file(path.join(testDir, 'binary.txt'));
return service.resolveContent(resource, { acceptTextOnly: true }).then(null, (e: FileOperationError) => {
return service.resolveContent(resource, { acceptTextOnly: true }).then(undefined, (e: FileOperationError) => {
assert.equal(e.fileOperationResult, FileOperationResult.FILE_IS_BINARY);
return service.resolveContent(uri.file(path.join(testDir, 'small.txt')), { acceptTextOnly: true }).then(r => {
@@ -722,7 +722,7 @@ suite('FileService', () => {
test('resolveContent - FILE_IS_DIRECTORY', function () {
const resource = uri.file(path.join(testDir, 'deep'));
return service.resolveContent(resource).then(null, (e: FileOperationError) => {
return service.resolveContent(resource).then(undefined, (e: FileOperationError) => {
assert.equal(e.fileOperationResult, FileOperationResult.FILE_IS_DIRECTORY);
});
});
@@ -730,7 +730,7 @@ suite('FileService', () => {
test('resolveContent - FILE_NOT_FOUND', function () {
const resource = uri.file(path.join(testDir, '404.html'));
return service.resolveContent(resource).then(null, (e: FileOperationError) => {
return service.resolveContent(resource).then(undefined, (e: FileOperationError) => {
assert.equal(e.fileOperationResult, FileOperationResult.FILE_NOT_FOUND);
});
});
@@ -739,7 +739,7 @@ suite('FileService', () => {
const resource = uri.file(path.join(testDir, 'index.html'));
return service.resolveContent(resource).then(c => {
return service.resolveContent(resource, { etag: c.etag }).then(null, (e: FileOperationError) => {
return service.resolveContent(resource, { etag: c.etag }).then(undefined, (e: FileOperationError) => {
assert.equal(e.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE);
});
});
@@ -751,7 +751,7 @@ suite('FileService', () => {
return service.resolveContent(resource).then(c => {
fs.writeFileSync(resource.fsPath, 'Updates Incoming!');
return service.updateContent(resource, c.value, { etag: c.etag, mtime: c.mtime - 1000 }).then(null, (e: FileOperationError) => {
return service.updateContent(resource, c.value, { etag: c.etag, mtime: c.mtime - 1000 }).then(undefined, (e: FileOperationError) => {
assert.equal(e.fileOperationResult, FileOperationResult.FILE_MODIFIED_SINCE);
});
});

View File

@@ -8,8 +8,8 @@ var Workforce;
return Company;
})();
(function (property, Workforce, IEmployee) {
if (property === void 0) { property = employees; }
if (IEmployee === void 0) { IEmployee = []; }
if (property === undefined) { property = employees; }
if (IEmployee === undefined) { IEmployee = []; }
property;
calculateMonthlyExpenses();
{

View File

@@ -7,9 +7,9 @@ var Conway;
return Cell;
})();
(function (property, number, property, number, property, boolean) {
if (property === void 0) { property = row; }
if (property === void 0) { property = col; }
if (property === void 0) { property = live; }
if (property === undefined) { property = row; }
if (property === undefined) { property = col; }
if (property === undefined) { property = live; }
});
var GameOfLife = (function () {
function GameOfLife() {

View File

@@ -7,10 +7,10 @@ var M;
return C;
})();
(function (x, property, number) {
if (property === void 0) { property = w; }
if (property === undefined) { property = w; }
var local = 1;
// unresolved symbol because x is local
//self.x++;
//self.x++;
self.w--; // ok because w is a property
property;
f = function (y) {

View File

@@ -8,8 +8,8 @@ var Workforce;
return Company;
})();
(function (property, Workforce, IEmployee) {
if (property === void 0) { property = employees; }
if (IEmployee === void 0) { IEmployee = []; }
if (property === undefined) { property = employees; }
if (IEmployee === undefined) { IEmployee = []; }
property;
calculateMonthlyExpenses();
{

View File

@@ -7,9 +7,9 @@ var Conway;
return Cell;
})();
(function (property, number, property, number, property, boolean) {
if (property === void 0) { property = row; }
if (property === void 0) { property = col; }
if (property === void 0) { property = live; }
if (property === undefined) { property = row; }
if (property === undefined) { property = col; }
if (property === undefined) { property = live; }
});
var GameOfLife = (function () {
function GameOfLife() {

View File

@@ -7,10 +7,10 @@ var M;
return C;
})();
(function (x, property, number) {
if (property === void 0) { property = w; }
if (property === undefined) { property = w; }
var local = 1;
// unresolved symbol because x is local
//self.x++;
//self.x++;
self.w--; // ok because w is a property
property;
f = function (y) {

View File

@@ -8,8 +8,8 @@ var Workforce;
return Company;
})();
(function (property, Workforce, IEmployee) {
if (property === void 0) { property = employees; }
if (IEmployee === void 0) { IEmployee = []; }
if (property === undefined) { property = employees; }
if (IEmployee === undefined) { IEmployee = []; }
property;
calculateMonthlyExpenses();
{

View File

@@ -7,9 +7,9 @@ var Conway;
return Cell;
})();
(function (property, number, property, number, property, boolean) {
if (property === void 0) { property = row; }
if (property === void 0) { property = col; }
if (property === void 0) { property = live; }
if (property === undefined) { property = row; }
if (property === undefined) { property = col; }
if (property === undefined) { property = live; }
});
var GameOfLife = (function () {
function GameOfLife() {

View File

@@ -7,10 +7,10 @@ var M;
return C;
})();
(function (x, property, number) {
if (property === void 0) { property = w; }
if (property === undefined) { property = w; }
var local = 1;
// unresolved symbol because x is local
//self.x++;
//self.x++;
self.w--; // ok because w is a property
property;
f = function (y) {

View File

@@ -18,7 +18,7 @@ function create(relativePath: string): StatResolver {
let absolutePath = relativePath ? path.join(basePath, relativePath) : basePath;
let fsStat = fs.statSync(absolutePath);
return new StatResolver(uri.file(absolutePath), fsStat.isSymbolicLink(), fsStat.isDirectory(), fsStat.mtime.getTime(), fsStat.size, void 0);
return new StatResolver(uri.file(absolutePath), fsStat.isSymbolicLink(), fsStat.isDirectory(), fsStat.mtime.getTime(), fsStat.size, undefined);
}
function toResource(relativePath: string): uri {

View File

@@ -10,9 +10,9 @@ export function getByName(root: IFileStat, name: string): IFileStat | null {
return null;
}
for (let i = 0; i < root.children.length; i++) {
if (root.children[i].name === name) {
return root.children[i];
for (const child of root.children) {
if (child.name === name) {
return child;
}
}

View File

@@ -7,7 +7,6 @@ import { Event } from 'vs/base/common/event';
import { createDecorator, ServiceIdentifier, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection } from 'vs/workbench/common/editor';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { TPromise } from 'vs/base/common/winjs.base';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export const IEditorGroupsService = createDecorator<IEditorGroupsService>('editorGroupsService');
@@ -377,18 +376,20 @@ export interface IEditorGroup {
/**
* Open an editor in this group.
*
* @returns a promise that is resolved when the active editor (if any)
* has finished loading
* @returns a promise that resolves around an IEditor instance unless
* the call failed, or the editor was not opened as active editor.
*/
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): TPromise<void>;
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise<IEditor>;
/**
* Opens editors in this group.
*
* @returns a promise that is resolved when the active editor (if any)
* has finished loading
* @returns a promise that resolves around an IEditor instance unless
* the call failed, or the editor was not opened as active editor. Since
* a group can only ever have one active editor, even if many editors are
* opened, the result will only be one editor.
*/
openEditors(editors: IEditorInputWithOptions[]): TPromise<void>;
openEditors(editors: IEditorInputWithOptions[]): Promise<IEditor>;
/**
* Find out if the provided editor is opened in the group.
@@ -428,7 +429,7 @@ export interface IEditorGroup {
*
* @returns a promise when the editor is closed.
*/
closeEditor(editor?: IEditorInput): TPromise<void>;
closeEditor(editor?: IEditorInput): Promise<void>;
/**
* Closes specific editors in this group. This may trigger a confirmation dialog if
@@ -436,7 +437,7 @@ export interface IEditorGroup {
*
* @returns a promise when all editors are closed.
*/
closeEditors(editors: IEditorInput[] | ICloseEditorsFilter): TPromise<void>;
closeEditors(editors: IEditorInput[] | ICloseEditorsFilter): Promise<void>;
/**
* Closes all editors from the group. This may trigger a confirmation dialog if
@@ -444,7 +445,7 @@ export interface IEditorGroup {
*
* @returns a promise when all editors are closed.
*/
closeAllEditors(): TPromise<void>;
closeAllEditors(): Promise<void>;
/**
* Replaces editors in this group with the provided replacement.
@@ -454,7 +455,7 @@ export interface IEditorGroup {
* @returns a promise that is resolved when the replaced active
* editor (if any) has finished loading.
*/
replaceEditors(editors: IEditorReplacement[]): TPromise<void>;
replaceEditors(editors: IEditorReplacement[]): Promise<void>;
/**
* Set an editor to be pinned. A pinned editor is not replaced

View File

@@ -63,7 +63,7 @@ export interface IHistoryService {
/**
* Get the entire history of opened editors.
*/
getHistory(): (IEditorInput | IResourceInput)[];
getHistory(): Array<IEditorInput | IResourceInput>;
/**
* Looking at the editor history, returns the workspace root of the last file that was
@@ -71,12 +71,12 @@ export interface IHistoryService {
*
* @param schemeFilter filter to restrict roots by scheme.
*/
getLastActiveWorkspaceRoot(schemeFilter?: string): URI;
getLastActiveWorkspaceRoot(schemeFilter?: string): URI | undefined;
/**
* Looking at the editor history, returns the resource of the last file that was opened.
*
* @param schemeFilter filter to restrict roots by scheme.
*/
getLastActiveFile(schemeFilter: string): URI;
getLastActiveFile(schemeFilter: string): URI | undefined;
}

View File

@@ -16,11 +16,11 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Registry } from 'vs/platform/registry/common/platform';
import { once, debounceEvent } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { getExcludes, ISearchConfiguration } from 'vs/platform/search/common/search';
import { IExpression } from 'vs/base/common/glob';
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
@@ -29,6 +29,7 @@ import { ResourceGlobMatcher } from 'vs/workbench/electron-browser/resources';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { coalesce } from 'vs/base/common/arrays';
/**
* Stores the selection & view state of an editor and allows to compare it to other selection states.
@@ -37,20 +38,20 @@ export class TextEditorState {
private static readonly EDITOR_SELECTION_THRESHOLD = 10; // number of lines to move in editor to justify for new state
private textEditorSelection: ITextEditorSelection;
private textEditorSelection?: ITextEditorSelection;
constructor(private _editorInput: IEditorInput, private _selection: Selection) {
constructor(private _editorInput: IEditorInput, private _selection: Selection | null) {
this.textEditorSelection = Selection.isISelection(_selection) ? {
startLineNumber: _selection.startLineNumber,
startColumn: _selection.startColumn
} : void 0;
} : undefined;
}
get editorInput(): IEditorInput {
return this._editorInput;
}
get selection(): ITextEditorSelection {
get selection(): ITextEditorSelection | undefined {
return this.textEditorSelection;
}
@@ -103,7 +104,7 @@ export class HistoryService extends Disposable implements IHistoryService {
private static readonly MAX_RECENTLY_CLOSED_EDITORS = 20;
private activeEditorListeners: IDisposable[];
private lastActiveEditor: IEditorIdentifier;
private lastActiveEditor?: IEditorIdentifier;
private editorHistoryListeners: Map<EditorInput, IDisposable[]> = new Map();
private editorStackListeners: Map<EditorInput, IDisposable[]> = new Map();
@@ -112,11 +113,11 @@ export class HistoryService extends Disposable implements IHistoryService {
private index: number;
private lastIndex: number;
private navigatingInStack: boolean;
private currentTextEditorState: TextEditorState;
private currentTextEditorState: TextEditorState | null;
private lastEditLocation: IStackEntry;
private history: (IEditorInput | IResourceInput)[];
private history: Array<IEditorInput | IResourceInput>;
private recentlyClosedFiles: IRecentlyClosedFile[];
private loaded: boolean;
private resourceFilter: ResourceGlobMatcher;
@@ -125,18 +126,19 @@ export class HistoryService extends Disposable implements IHistoryService {
private canNavigateBackContextKey: IContextKey<boolean>;
private canNavigateForwardContextKey: IContextKey<boolean>;
private canNavigateToLastEditLocationContextKey: IContextKey<boolean>;
constructor(
@IEditorService private editorService: EditorServiceImpl,
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IStorageService private storageService: IStorageService,
@IConfigurationService private configurationService: IConfigurationService,
@IFileService private fileService: IFileService,
@IWindowsService private windowService: IWindowsService,
@IInstantiationService private instantiationService: IInstantiationService,
@IPartService private partService: IPartService,
@IContextKeyService private contextKeyService: IContextKeyService
@IEditorService private readonly editorService: EditorServiceImpl,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IStorageService private readonly storageService: IStorageService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IFileService private readonly fileService: IFileService,
@IWindowsService private readonly windowService: IWindowsService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IPartService private readonly partService: IPartService,
@IContextKeyService private readonly contextKeyService: IContextKeyService
) {
super();
@@ -144,6 +146,7 @@ export class HistoryService extends Disposable implements IHistoryService {
this.canNavigateBackContextKey = (new RawContextKey<boolean>('canNavigateBack', false)).bindTo(this.contextKeyService);
this.canNavigateForwardContextKey = (new RawContextKey<boolean>('canNavigateForward', false)).bindTo(this.contextKeyService);
this.canNavigateToLastEditLocationContextKey = (new RawContextKey<boolean>('canNavigateToLastEditLocation', false)).bindTo(this.contextKeyService);
this.fileInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).getFileInputFactory();
@@ -154,7 +157,7 @@ export class HistoryService extends Disposable implements IHistoryService {
this.loaded = false;
this.resourceFilter = this._register(instantiationService.createInstance(
ResourceGlobMatcher,
(root: URI) => this.getExcludes(root),
(root?: URI) => this.getExcludes(root),
(event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration('search.exclude')
));
@@ -162,9 +165,9 @@ export class HistoryService extends Disposable implements IHistoryService {
}
private getExcludes(root?: URI): IExpression {
const scope = root ? { resource: root } : void 0;
const scope = root ? { resource: root } : undefined;
return getExcludes(this.configurationService.getValue<ISearchConfiguration>(scope));
return getExcludes(scope ? this.configurationService.getValue<ISearchConfiguration>(scope) : this.configurationService.getValue<ISearchConfiguration>())!;
}
private registerListeners(): void {
@@ -190,7 +193,7 @@ export class HistoryService extends Disposable implements IHistoryService {
}
// Remember as last active editor (can be undefined if none opened)
this.lastActiveEditor = activeControl ? { editor: activeControl.input, groupId: activeControl.group.id } : void 0;
this.lastActiveEditor = activeControl && activeControl.input && activeControl.group ? { editor: activeControl.input, groupId: activeControl.group.id } : undefined;
// Dispose old listeners
dispose(this.activeEditorListeners);
@@ -207,24 +210,27 @@ export class HistoryService extends Disposable implements IHistoryService {
// Debounce the event with a timeout of 0ms so that multiple calls to
// editor.setSelection() are folded into one. We do not want to record
// subsequent history navigations for such API calls.
this.activeEditorListeners.push(debounceEvent(activeTextEditorWidget.onDidChangeCursorPosition, (last, event) => event, 0)((event => {
this.activeEditorListeners.push(Event.debounce(activeTextEditorWidget.onDidChangeCursorPosition, (last, event) => event, 0)((event => {
this.handleEditorSelectionChangeEvent(activeControl, event);
})));
// Track the last edit location by tracking model content change events
// Use a debouncer to make sure to capture the correct cursor position
// after the model content has changed.
this.activeEditorListeners.push(debounceEvent(activeTextEditorWidget.onDidChangeModelContent, (last, event) => event, 0)((event => {
this.lastEditLocation = { input: activeEditor };
this.activeEditorListeners.push(Event.debounce(activeTextEditorWidget.onDidChangeModelContent, (last, event) => event, 0)((event => this.rememberLastEditLocation(activeEditor, activeTextEditorWidget))));
}
}
const position = activeTextEditorWidget.getPosition();
if (position) {
this.lastEditLocation.selection = {
startLineNumber: position.lineNumber,
startColumn: position.column
};
}
})));
private rememberLastEditLocation(activeEditor: IEditorInput, activeTextEditorWidget: ICodeEditor): void {
this.lastEditLocation = { input: activeEditor };
this.canNavigateToLastEditLocationContextKey.set(true);
const position = activeTextEditorWidget.getPosition();
if (position) {
this.lastEditLocation.selection = {
startLineNumber: position.lineNumber,
startColumn: position.column
};
}
}
@@ -250,9 +256,9 @@ export class HistoryService extends Disposable implements IHistoryService {
// Track closing of editor to support to reopen closed editors (unless editor was replaced)
if (!event.replaced) {
const resource = event.editor ? event.editor.getResource() : void 0;
const resource = event.editor ? event.editor.getResource() : undefined;
const supportsReopen = resource && this.fileService.canHandleResource(resource); // we only support file'ish things to reopen
if (supportsReopen) {
if (resource && supportsReopen) {
// Remove all inputs matching and add as last recently closed
this.removeFromRecentlyClosedFiles(event.editor);
@@ -401,16 +407,10 @@ export class HistoryService extends Disposable implements IHistoryService {
private navigate(acrossEditors?: boolean): void {
this.navigatingInStack = true;
this.doNavigate(this.stack[this.index], !acrossEditors).then(() => {
this.navigatingInStack = false;
}, error => {
this.navigatingInStack = false;
onUnexpectedError(error);
});
this.doNavigate(this.stack[this.index], !acrossEditors).finally(() => this.navigatingInStack = false);
}
private doNavigate(location: IStackEntry, withSelection: boolean): Thenable<IBaseEditor> {
private doNavigate(location: IStackEntry, withSelection: boolean): Promise<IBaseEditor> {
const options: ITextEditorOptions = {
revealIfOpened: true // support to navigate across editor groups
};
@@ -439,7 +439,7 @@ export class HistoryService extends Disposable implements IHistoryService {
}
private handleEditorEventInHistory(editor?: IBaseEditor): void {
const input = editor ? editor.input : void 0;
const input = editor ? editor.input : undefined;
// Ensure we have at least a name to show and not configured to exclude input
if (!input || !input.getName() || !this.include(input)) {
@@ -456,7 +456,7 @@ export class HistoryService extends Disposable implements IHistoryService {
// Respect max entries setting
if (this.history.length > HistoryService.MAX_HISTORY_ITEMS) {
this.clearOnEditorDispose(this.history.pop(), this.editorHistoryListeners);
this.clearOnEditorDispose(this.history.pop()!, this.editorHistoryListeners);
}
// Remove this from the history unless the history input is a resource
@@ -467,7 +467,7 @@ export class HistoryService extends Disposable implements IHistoryService {
}
private onEditorDispose(editor: EditorInput, listener: Function, mapEditorToDispose: Map<EditorInput, IDisposable[]>): void {
const toDispose = once(editor.onDispose)(() => listener());
const toDispose = Event.once(editor.onDispose)(() => listener());
let disposables = mapEditorToDispose.get(editor);
if (!disposables) {
@@ -480,7 +480,7 @@ export class HistoryService extends Disposable implements IHistoryService {
private clearOnEditorDispose(editor: IEditorInput | IResourceInput | FileChangesEvent, mapEditorToDispose: Map<EditorInput, IDisposable[]>): void {
if (editor instanceof EditorInput) {
const disposables = this.editorHistoryListeners.get(editor);
const disposables = mapEditorToDispose.get(editor);
if (disposables) {
dispose(disposables);
mapEditorToDispose.delete(editor);
@@ -541,15 +541,15 @@ export class HistoryService extends Disposable implements IHistoryService {
});
}
private handleEditorEventInStack(control: IBaseEditor, event?: ICursorPositionChangedEvent): void {
const codeEditor = control ? getCodeEditor(control.getControl()) : void 0;
private handleEditorEventInStack(control: IBaseEditor | undefined, event?: ICursorPositionChangedEvent): void {
const codeEditor = control ? getCodeEditor(control.getControl()) : undefined;
// treat editor changes that happen as part of stack navigation specially
// we do not want to add a new stack entry as a matter of navigating the
// stack but we need to keep our currentTextEditorState up to date with
// the navigtion that occurs.
if (this.navigatingInStack) {
if (codeEditor && control.input) {
if (codeEditor && control && control.input) {
this.currentTextEditorState = new TextEditorState(control.input, codeEditor.getSelection());
} else {
this.currentTextEditorState = null; // we navigated to a non text editor
@@ -560,7 +560,7 @@ export class HistoryService extends Disposable implements IHistoryService {
else {
// navigation inside text editor
if (codeEditor && control.input) {
if (codeEditor && control && control.input) {
this.handleTextEditorEvent(control, codeEditor, event);
}
@@ -576,6 +576,10 @@ export class HistoryService extends Disposable implements IHistoryService {
}
private handleTextEditorEvent(editor: IBaseEditor, editorControl: IEditor, event?: ICursorPositionChangedEvent): void {
if (!editor.input) {
return;
}
const stateCandidate = new TextEditorState(editor.input, editorControl.getSelection());
// Add to stack if we dont have a current state or this new state justifies a push
@@ -593,6 +597,10 @@ export class HistoryService extends Disposable implements IHistoryService {
}
private handleNonTextEditorEvent(editor: IBaseEditor): void {
if (!editor.input) {
return;
}
const currentStack = this.stack[this.index];
if (currentStack && this.matches(editor.input, currentStack.input)) {
return; // do not push same editor input again
@@ -660,7 +668,7 @@ export class HistoryService extends Disposable implements IHistoryService {
// Check for limit
if (this.stack.length > HistoryService.MAX_STACK_ITEMS) {
removedEntries.push(this.stack.shift()); // remove first
removedEntries.push(this.stack.shift()!); // remove first
if (this.lastIndex >= 0) {
this.lastIndex--;
}
@@ -695,7 +703,7 @@ export class HistoryService extends Disposable implements IHistoryService {
return true;
}
if ((!selectionA && selectionB) || (selectionA && !selectionB)) {
if (!selectionA || !selectionB) {
return false;
}
@@ -797,7 +805,7 @@ export class HistoryService extends Disposable implements IHistoryService {
return resourceInput && resourceInput.resource.toString() === resource.toString();
}
getHistory(): (IEditorInput | IResourceInput)[] {
getHistory(): Array<IEditorInput | IResourceInput> {
this.ensureHistoryLoaded();
return this.history.slice(0);
@@ -818,7 +826,7 @@ export class HistoryService extends Disposable implements IHistoryService {
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
const entries: ISerializedEditorHistoryEntry[] = this.history.map(input => {
const entries: ISerializedEditorHistoryEntry[] = coalesce(this.history.map(input => {
// Editor input: try via factory
if (input instanceof EditorInput) {
@@ -836,8 +844,8 @@ export class HistoryService extends Disposable implements IHistoryService {
return { resourceJSON: (input as IResourceInput).resource.toJSON() } as ISerializedEditorHistoryEntry;
}
return void 0;
}).filter(serialized => !!serialized);
return undefined;
}));
this.storageService.store(HistoryService.STORAGE_KEY, JSON.stringify(entries), StorageScope.WORKSPACE);
}
@@ -847,23 +855,23 @@ export class HistoryService extends Disposable implements IHistoryService {
const entriesRaw = this.storageService.get(HistoryService.STORAGE_KEY, StorageScope.WORKSPACE);
if (entriesRaw) {
entries = JSON.parse(entriesRaw).filter((entry: object) => !!entry);
entries = coalesce(JSON.parse(entriesRaw));
}
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
this.history = entries.map(entry => {
this.history = coalesce(entries.map(entry => {
try {
return this.safeLoadHistoryEntry(registry, entry);
} catch (error) {
onUnexpectedError(error);
return void 0; // https://github.com/Microsoft/vscode/issues/60960
return undefined; // https://github.com/Microsoft/vscode/issues/60960
}
}).filter(input => !!input);
}));
}
private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput {
private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined {
const serializedEditorHistoryEntry = entry as ISerializedEditorHistoryEntry;
// File resource: via URI.revive()
@@ -885,15 +893,15 @@ export class HistoryService extends Disposable implements IHistoryService {
}
}
return void 0;
return undefined;
}
getLastActiveWorkspaceRoot(schemeFilter?: string): URI {
getLastActiveWorkspaceRoot(schemeFilter?: string): URI | undefined {
// No Folder: return early
const folders = this.contextService.getWorkspace().folders;
if (folders.length === 0) {
return void 0;
return undefined;
}
// Single Folder: return early
@@ -903,13 +911,12 @@ export class HistoryService extends Disposable implements IHistoryService {
return resource;
}
return void 0;
return undefined;
}
// Multiple folders: find the last active one
const history = this.getHistory();
for (let i = 0; i < history.length; i++) {
const input = history[i];
for (const input of history) {
if (input instanceof EditorInput) {
continue;
}
@@ -926,22 +933,20 @@ export class HistoryService extends Disposable implements IHistoryService {
}
// fallback to first workspace matching scheme filter if any
for (let i = 0; i < folders.length; i++) {
const resource = folders[i].uri;
for (const folder of folders) {
const resource = folder.uri;
if (!schemeFilter || resource.scheme === schemeFilter) {
return resource;
}
}
return void 0;
return undefined;
}
getLastActiveFile(schemeFilter: string): URI {
getLastActiveFile(schemeFilter: string): URI | undefined {
const history = this.getHistory();
for (let i = 0; i < history.length; i++) {
let resource: URI;
const input = history[i];
for (const input of history) {
let resource: URI | null;
if (input instanceof EditorInput) {
resource = toResource(input, { filter: schemeFilter });
} else {
@@ -953,6 +958,6 @@ export class HistoryService extends Disposable implements IHistoryService {
}
}
return void 0;
return undefined;
}
}

View File

@@ -10,6 +10,6 @@ export const IWorkbenchIssueService = createDecorator<IWorkbenchIssueService>('w
export interface IWorkbenchIssueService {
_serviceBrand: any;
openReporter(dataOverrides?: Partial<IssueReporterData>): Thenable<void>;
openProcessExplorer(): Thenable<void>;
openReporter(dataOverrides?: Partial<IssueReporterData>): Promise<void>;
openProcessExplorer(): Promise<void>;
}

View File

@@ -7,26 +7,26 @@ import { IssueReporterStyles, IIssueService, IssueReporterData, ProcessExplorerD
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, listHighlightForeground, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { IExtensionManagementService, IExtensionEnablementService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { webFrame } from 'electron';
import { assign } from 'vs/base/common/objects';
import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
export class WorkbenchIssueService implements IWorkbenchIssueService {
_serviceBrand: any;
constructor(
@IIssueService private issueService: IIssueService,
@IThemeService private themeService: IThemeService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
@IWindowService private windowService: IWindowService
) {
}
@IIssueService private readonly issueService: IIssueService,
@IThemeService private readonly themeService: IThemeService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
@IWindowService private readonly windowService: IWindowService
) { }
openReporter(dataOverrides: Partial<IssueReporterData> = {}): Promise<void> {
return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => {
return this.extensionManagementService.getInstalled(ExtensionType.User).then(extensions => {
const enabledExtensions = extensions.filter(extension => this.extensionEnablementService.isEnabled(extension));
const extensionData: IssueReporterExtensionData[] = enabledExtensions.map(extension => {
const { manifest } = extension;
@@ -57,17 +57,17 @@ export class WorkbenchIssueService implements IWorkbenchIssueService {
});
}
openProcessExplorer(): Thenable<void> {
openProcessExplorer(): Promise<void> {
const theme = this.themeService.getTheme();
const data: ProcessExplorerData = {
pid: this.windowService.getConfiguration().mainPid,
zoomLevel: webFrame.getZoomLevel(),
styles: {
backgroundColor: theme.getColor(editorBackground) && theme.getColor(editorBackground).toString(),
color: theme.getColor(editorForeground).toString(),
hoverBackground: theme.getColor(listHoverBackground) && theme.getColor(listHoverBackground).toString(),
hoverForeground: theme.getColor(listHoverForeground) && theme.getColor(listHoverForeground).toString(),
highlightForeground: theme.getColor(listHighlightForeground) && theme.getColor(listHighlightForeground).toString()
backgroundColor: getColor(theme, editorBackground),
color: getColor(theme, editorForeground),
hoverBackground: getColor(theme, listHoverBackground),
hoverForeground: getColor(theme, listHoverForeground),
highlightForeground: getColor(theme, listHighlightForeground),
}
};
return this.issueService.openProcessExplorer(data);
@@ -76,20 +76,26 @@ export class WorkbenchIssueService implements IWorkbenchIssueService {
export function getIssueReporterStyles(theme: ITheme): IssueReporterStyles {
return {
backgroundColor: theme.getColor(SIDE_BAR_BACKGROUND) && theme.getColor(SIDE_BAR_BACKGROUND).toString(),
color: theme.getColor(foreground).toString(),
textLinkColor: theme.getColor(textLinkForeground) && theme.getColor(textLinkForeground).toString(),
textLinkActiveForeground: theme.getColor(textLinkActiveForeground) && theme.getColor(textLinkActiveForeground).toString(),
inputBackground: theme.getColor(inputBackground) && theme.getColor(inputBackground).toString(),
inputForeground: theme.getColor(inputForeground) && theme.getColor(inputForeground).toString(),
inputBorder: theme.getColor(inputBorder) && theme.getColor(inputBorder).toString(),
inputActiveBorder: theme.getColor(inputActiveOptionBorder) && theme.getColor(inputActiveOptionBorder).toString(),
inputErrorBorder: theme.getColor(inputValidationErrorBorder) && theme.getColor(inputValidationErrorBorder).toString(),
buttonBackground: theme.getColor(buttonBackground) && theme.getColor(buttonBackground).toString(),
buttonForeground: theme.getColor(buttonForeground) && theme.getColor(buttonForeground).toString(),
buttonHoverBackground: theme.getColor(buttonHoverBackground) && theme.getColor(buttonHoverBackground).toString(),
sliderActiveColor: theme.getColor(scrollbarSliderActiveBackground) && theme.getColor(scrollbarSliderActiveBackground).toString(),
sliderBackgroundColor: theme.getColor(scrollbarSliderBackground) && theme.getColor(scrollbarSliderBackground).toString(),
sliderHoverColor: theme.getColor(scrollbarSliderHoverBackground) && theme.getColor(scrollbarSliderHoverBackground).toString()
backgroundColor: getColor(theme, SIDE_BAR_BACKGROUND),
color: getColor(theme, foreground),
textLinkColor: getColor(theme, textLinkForeground),
textLinkActiveForeground: getColor(theme, textLinkActiveForeground),
inputBackground: getColor(theme, inputBackground),
inputForeground: getColor(theme, inputForeground),
inputBorder: getColor(theme, inputBorder),
inputActiveBorder: getColor(theme, inputActiveOptionBorder),
inputErrorBorder: getColor(theme, inputValidationErrorBorder),
buttonBackground: getColor(theme, buttonBackground),
buttonForeground: getColor(theme, buttonForeground),
buttonHoverBackground: getColor(theme, buttonHoverBackground),
sliderActiveColor: getColor(theme, scrollbarSliderActiveBackground),
sliderBackgroundColor: getColor(theme, scrollbarSliderBackground),
sliderHoverColor: getColor(theme, scrollbarSliderHoverBackground),
};
}
function getColor(theme: ITheme, key: string): string | undefined {
const color = theme.getColor(key);
return color ? color.toString() : undefined;
}

View File

@@ -13,21 +13,25 @@ interface IJSONValidationExtensionPoint {
url: string;
}
let configurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IJSONValidationExtensionPoint[]>('jsonValidation', [], {
description: nls.localize('contributes.jsonValidation', 'Contributes json schema configuration.'),
type: 'array',
defaultSnippets: [{ body: [{ fileMatch: '${1:file.json}', url: '${2:url}' }] }],
items: {
type: 'object',
defaultSnippets: [{ body: { fileMatch: '${1:file.json}', url: '${2:url}' } }],
properties: {
fileMatch: {
type: 'string',
description: nls.localize('contributes.jsonValidation.fileMatch', 'The file pattern to match, for example "package.json" or "*.launch".'),
},
url: {
description: nls.localize('contributes.jsonValidation.url', 'A schema URL (\'http:\', \'https:\') or relative path to the extension folder (\'./\').'),
type: 'string'
const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IJSONValidationExtensionPoint[]>({
extensionPoint: 'jsonValidation',
isDynamic: true,
jsonSchema: {
description: nls.localize('contributes.jsonValidation', 'Contributes json schema configuration.'),
type: 'array',
defaultSnippets: [{ body: [{ fileMatch: '${1:file.json}', url: '${2:url}' }] }],
items: {
type: 'object',
defaultSnippets: [{ body: { fileMatch: '${1:file.json}', url: '${2:url}' } }],
properties: {
fileMatch: {
type: 'string',
description: nls.localize('contributes.jsonValidation.fileMatch', 'The file pattern to match, for example "package.json" or "*.launch".'),
},
url: {
description: nls.localize('contributes.jsonValidation.url', 'A schema URL (\'http:\', \'https:\') or relative path to the extension folder (\'./\').'),
type: 'string'
}
}
}
}
@@ -37,10 +41,10 @@ export class JSONValidationExtensionPoint {
constructor() {
configurationExtPoint.setHandler((extensions) => {
for (let i = 0; i < extensions.length; i++) {
const extensionValue = <IJSONValidationExtensionPoint[]>extensions[i].value;
const collector = extensions[i].collector;
const extensionLocation = extensions[i].description.extensionLocation;
for (const extension of extensions) {
const extensionValue = <IJSONValidationExtensionPoint[]>extension.value;
const collector = extension.collector;
const extensionLocation = extension.description.extensionLocation;
if (!extensionValue || !Array.isArray(extensionValue)) {
collector.error(nls.localize('invalid.jsonValidation', "'configuration.jsonValidation' must be a array"));

View File

@@ -32,11 +32,11 @@ export interface IKeybindingEditingService {
_serviceBrand: ServiceIdentifier<any>;
editKeybinding(key: string, keybindingItem: ResolvedKeybindingItem): Thenable<void>;
editKeybinding(key: string, keybindingItem: ResolvedKeybindingItem): Promise<void>;
removeKeybinding(keybindingItem: ResolvedKeybindingItem): Thenable<void>;
removeKeybinding(keybindingItem: ResolvedKeybindingItem): Promise<void>;
resetKeybinding(keybindingItem: ResolvedKeybindingItem): Thenable<void>;
resetKeybinding(keybindingItem: ResolvedKeybindingItem): Promise<void>;
}
export class KeybindingsEditingService extends Disposable implements IKeybindingEditingService {
@@ -47,29 +47,29 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
private resource: URI = URI.file(this.environmentService.appKeybindingsPath);
constructor(
@ITextModelService private textModelResolverService: ITextModelService,
@ITextFileService private textFileService: ITextFileService,
@IFileService private fileService: IFileService,
@IConfigurationService private configurationService: IConfigurationService,
@IEnvironmentService private environmentService: IEnvironmentService
@ITextModelService private readonly textModelResolverService: ITextModelService,
@ITextFileService private readonly textFileService: ITextFileService,
@IFileService private readonly fileService: IFileService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super();
this.queue = new Queue<void>();
}
editKeybinding(key: string, keybindingItem: ResolvedKeybindingItem): Thenable<void> {
editKeybinding(key: string, keybindingItem: ResolvedKeybindingItem): Promise<void> {
return this.queue.queue(() => this.doEditKeybinding(key, keybindingItem)); // queue up writes to prevent race conditions
}
resetKeybinding(keybindingItem: ResolvedKeybindingItem): Thenable<void> {
resetKeybinding(keybindingItem: ResolvedKeybindingItem): Promise<void> {
return this.queue.queue(() => this.doResetKeybinding(keybindingItem)); // queue up writes to prevent race conditions
}
removeKeybinding(keybindingItem: ResolvedKeybindingItem): Thenable<void> {
removeKeybinding(keybindingItem: ResolvedKeybindingItem): Promise<void> {
return this.queue.queue(() => this.doRemoveKeybinding(keybindingItem)); // queue up writes to prevent race conditions
}
private doEditKeybinding(key: string, keybindingItem: ResolvedKeybindingItem): Thenable<void> {
private doEditKeybinding(key: string, keybindingItem: ResolvedKeybindingItem): Promise<void> {
return this.resolveAndValidate()
.then(reference => {
const model = reference.object.textEditorModel;
@@ -83,7 +83,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
});
}
private doRemoveKeybinding(keybindingItem: ResolvedKeybindingItem): Thenable<void> {
private doRemoveKeybinding(keybindingItem: ResolvedKeybindingItem): Promise<void> {
return this.resolveAndValidate()
.then(reference => {
const model = reference.object.textEditorModel;
@@ -96,7 +96,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
});
}
private doResetKeybinding(keybindingItem: ResolvedKeybindingItem): Thenable<void> {
private doResetKeybinding(keybindingItem: ResolvedKeybindingItem): Promise<void> {
return this.resolveAndValidate()
.then(reference => {
const model = reference.object.textEditorModel;
@@ -108,7 +108,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
});
}
private save(): Thenable<any> {
private save(): Promise<any> {
return this.textFileService.save(this.resource);
}
@@ -130,7 +130,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
const userKeybindingEntries = <IUserFriendlyKeybinding[]>json.parse(model.getValue());
const userKeybindingEntryIndex = this.findUserKeybindingEntryIndex(keybindingItem, userKeybindingEntries);
if (userKeybindingEntryIndex !== -1) {
this.applyEditsToBuffer(setProperty(model.getValue(), [userKeybindingEntryIndex], void 0, { tabSize, insertSpaces, eol })[0], model);
this.applyEditsToBuffer(setProperty(model.getValue(), [userKeybindingEntryIndex], undefined, { tabSize, insertSpaces, eol })[0], model);
}
}
@@ -146,7 +146,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
const userKeybindingEntries = <IUserFriendlyKeybinding[]>json.parse(model.getValue());
const indices = this.findUnassignedDefaultKeybindingEntryIndex(keybindingItem, userKeybindingEntries).reverse();
for (const index of indices) {
this.applyEditsToBuffer(setProperty(model.getValue(), [index], void 0, { tabSize, insertSpaces, eol })[0], model);
this.applyEditsToBuffer(setProperty(model.getValue(), [index], undefined, { tabSize, insertSpaces, eol })[0], model);
}
}
@@ -197,16 +197,16 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
}
private resolveModelReference(): Thenable<IReference<ITextEditorModel>> {
private resolveModelReference(): Promise<IReference<ITextEditorModel>> {
return this.fileService.existsFile(this.resource)
.then(exists => {
const EOL = this.configurationService.getValue('files', { overrideIdentifier: 'json' })['eol'];
const result: Thenable<any> = exists ? Promise.resolve(null) : this.fileService.updateContent(this.resource, this.getEmptyContent(EOL), { encoding: 'utf8' });
const result: Promise<any> = exists ? Promise.resolve(null) : this.fileService.updateContent(this.resource, this.getEmptyContent(EOL), { encoding: 'utf8' });
return result.then(() => this.textModelResolverService.createModelReference(this.resource));
});
}
private resolveAndValidate(): Thenable<IReference<ITextEditorModel>> {
private resolveAndValidate(): Promise<IReference<ITextEditorModel>> {
// Target cannot be dirty if not writing into buffer
if (this.textFileService.isDirty(this.resource)) {

View File

@@ -29,13 +29,14 @@ export class CachedKeyboardMapper implements IKeyboardMapper {
}
public resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[] {
let hashCode = keybinding.getHashCode();
if (!this._cache.has(hashCode)) {
let r = this._actual.resolveKeybinding(keybinding);
const hashCode = keybinding.getHashCode();
const resolved = this._cache.get(hashCode);
if (!resolved) {
const r = this._actual.resolveKeybinding(keybinding);
this._cache.set(hashCode, r);
return r;
}
return this._cache.get(hashCode);
return resolved;
}
public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding {

View File

@@ -445,11 +445,11 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper {
/**
* UI label for a ScanCode.
*/
private readonly _scanCodeToLabel: (string | null)[] = [];
private readonly _scanCodeToLabel: Array<string | null> = [];
/**
* Dispatching string for a ScanCode.
*/
private readonly _scanCodeToDispatch: (string | null)[] = [];
private readonly _scanCodeToDispatch: Array<string | null> = [];
constructor(isUSStandard: boolean, rawMappings: IMacLinuxKeyboardMapping, OS: OperatingSystem) {
this._isUSStandard = isUSStandard;
@@ -800,7 +800,7 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper {
for (let i = 0, len = kbCombos.length; i < len; i++) {
const kbCombo = kbCombos[i];
// find out the priority of this scan code for this key code
let colPriority = '-';
let colPriority: string;
const scanCodeCombos = this._scanCodeKeyCodeMapper.lookupKeyCodeCombo(kbCombo);
if (scanCodeCombos.length === 1) {

View File

@@ -303,7 +303,7 @@ export class WindowsKeyboardMapper implements IKeyboardMapper {
public readonly isUSStandard: boolean;
private readonly _codeInfo: IScanCodeMapping[];
private readonly _scanCodeToKeyCode: KeyCode[];
private readonly _keyCodeToLabel: (string | null)[] = [];
private readonly _keyCodeToLabel: Array<string | null> = [];
private readonly _keyCodeExists: boolean[];
constructor(isUSStandard: boolean, rawMappings: IWindowsKeyboardMapping) {
@@ -460,9 +460,8 @@ export class WindowsKeyboardMapper implements IKeyboardMapper {
const mapping = this._codeInfo[scanCode];
const strCode = ScanCodeUtils.toString(scanCode);
let mods = [0b000, 0b010, 0b101, 0b111];
for (let modIndex = 0; modIndex < mods.length; modIndex++) {
const mod = mods[modIndex];
const mods = [0b000, 0b010, 0b101, 0b111];
for (const mod of mods) {
const ctrlKey = (mod & 0b001) ? true : false;
const shiftKey = (mod & 0b010) ? true : false;
const altKey = (mod & 0b100) ? true : false;

View File

@@ -24,7 +24,7 @@ import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/c
import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService';
import { IKeybindingEvent, IKeyboardEvent, IUserFriendlyKeybinding, KeybindingSource } from 'vs/platform/keybinding/common/keybinding';
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
import { IKeybindingItem, IKeybindingRule2, KeybindingRuleSource, KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IKeybindingItem, IKeybindingRule2, KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -37,6 +37,7 @@ import { CachedKeyboardMapper, IKeyboardMapper } from 'vs/workbench/services/key
import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper';
import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper, macLinuxKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper';
import { IWindowsKeyboardMapping, WindowsKeyboardMapper, windowsKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper';
import { IWindowService } from 'vs/platform/windows/common/windows';
export class KeyboardMapperFactory {
public static readonly INSTANCE = new KeyboardMapperFactory();
@@ -46,7 +47,7 @@ export class KeyboardMapperFactory {
private _keyboardMapper: IKeyboardMapper | null;
private _initialized: boolean;
private readonly _onDidChangeKeyboardMapper: Emitter<void> = new Emitter<void>();
private readonly _onDidChangeKeyboardMapper = new Emitter<void>();
public readonly onDidChangeKeyboardMapper: Event<void> = this._onDidChangeKeyboardMapper.event;
private constructor() {
@@ -155,6 +156,7 @@ export class KeyboardMapperFactory {
interface ContributedKeyBinding {
command: string;
args?: any;
key: string;
when?: string;
mac?: string;
@@ -206,6 +208,9 @@ let keybindingType: IJSONSchema = {
description: nls.localize('vscode.extension.contributes.keybindings.command', 'Identifier of the command to run when keybinding is triggered.'),
type: 'string'
},
args: {
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).'),
type: 'string'
@@ -225,19 +230,23 @@ let keybindingType: IJSONSchema = {
when: {
description: nls.localize('vscode.extension.contributes.keybindings.when', 'Condition when the key is active.'),
type: 'string'
}
},
}
};
let keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint<ContributedKeyBinding | ContributedKeyBinding[]>('keybindings', [], {
description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."),
oneOf: [
keybindingType,
{
type: 'array',
items: keybindingType
}
]
const keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint<ContributedKeyBinding | ContributedKeyBinding[]>({
isDynamic: true,
extensionPoint: 'keybindings',
jsonSchema: {
description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."),
oneOf: [
keybindingType,
{
type: 'array',
items: keybindingType
}
]
}
});
export const enum DispatchConfig {
@@ -266,7 +275,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
@INotificationService notificationService: INotificationService,
@IEnvironmentService environmentService: IEnvironmentService,
@IStatusbarService statusBarService: IStatusbarService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@IWindowService private readonly windowService: IWindowService
) {
super(contextKeyService, commandService, telemetryService, notificationService, statusBarService);
@@ -294,15 +304,14 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
this.userKeybindings = this._register(new ConfigWatcher(environmentService.appKeybindingsPath, { defaultConfig: [], onError: error => onUnexpectedError(error) }));
keybindingsExtPoint.setHandler((extensions) => {
let commandAdded = false;
let keybindings: IKeybindingRule2[] = [];
for (let extension of extensions) {
commandAdded = this._handleKeybindingsExtensionPointUser(extension.description.isBuiltin, extension.value, extension.collector) || commandAdded;
this._handleKeybindingsExtensionPointUser(extension.description.isBuiltin, extension.value, extension.collector, keybindings);
}
if (commandAdded) {
this.updateResolver({ source: KeybindingSource.Default });
}
KeybindingsRegistry.setExtensionKeybindings(keybindings);
this.updateResolver({ source: KeybindingSource.Default });
});
this._register(this.userKeybindings.onDidUpdateConfiguration(event => this.updateResolver({
@@ -367,13 +376,15 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
}
protected _documentHasFocus(): boolean {
return document.hasFocus();
// it is possible that the document has lost focus, but the
// window is still focused, e.g. when a <webview> element
// has focus
return this.windowService.hasFocus;
}
private _resolveKeybindingItems(items: IKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] {
let result: ResolvedKeybindingItem[] = [], resultLen = 0;
for (let i = 0, len = items.length; i < len; i++) {
const item = items[i];
for (const item of items) {
const when = (item.when ? item.when.normalize() : null);
const keybinding = item.keybinding;
if (!keybinding) {
@@ -381,8 +392,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault);
} else {
const resolvedKeybindings = this.resolveKeybinding(keybinding);
for (let j = 0; j < resolvedKeybindings.length; j++) {
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybindings[j], item.command, item.commandArgs, when, isDefault);
for (const resolvedKeybinding of resolvedKeybindings) {
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault);
}
}
}
@@ -392,8 +403,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
private _resolveUserKeybindingItems(items: IUserKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] {
let result: ResolvedKeybindingItem[] = [], resultLen = 0;
for (let i = 0, len = items.length; i < len; i++) {
const item = items[i];
for (const item of items) {
const when = (item.when ? item.when.normalize() : null);
const firstPart = item.firstPart;
const chordPart = item.chordPart;
@@ -402,8 +412,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault);
} else {
const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(firstPart, chordPart);
for (let j = 0; j < resolvedKeybindings.length; j++) {
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybindings[j], item.command, item.commandArgs, when, isDefault);
for (const resolvedKeybinding of resolvedKeybindings) {
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault);
}
}
}
@@ -442,28 +452,24 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
return this._keyboardMapper.resolveUserBinding(firstPart, chordPart);
}
private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings: ContributedKeyBinding | ContributedKeyBinding[], collector: ExtensionMessageCollector): boolean {
private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings: ContributedKeyBinding | ContributedKeyBinding[], collector: ExtensionMessageCollector, result: IKeybindingRule2[]): void {
if (isContributedKeyBindingsArray(keybindings)) {
let commandAdded = false;
for (let i = 0, len = keybindings.length; i < len; i++) {
commandAdded = this._handleKeybinding(isBuiltin, i + 1, keybindings[i], collector) || commandAdded;
this._handleKeybinding(isBuiltin, i + 1, keybindings[i], collector, result);
}
return commandAdded;
} else {
return this._handleKeybinding(isBuiltin, 1, keybindings, collector);
this._handleKeybinding(isBuiltin, 1, keybindings, collector, result);
}
}
private _handleKeybinding(isBuiltin: boolean, idx: number, keybindings: ContributedKeyBinding, collector: ExtensionMessageCollector): boolean {
private _handleKeybinding(isBuiltin: boolean, idx: number, keybindings: ContributedKeyBinding, collector: ExtensionMessageCollector, result: IKeybindingRule2[]): void {
let rejects: string[] = [];
let commandAdded = false;
if (isValidContributedKeyBinding(keybindings, rejects)) {
let rule = this._asCommandRule(isBuiltin, idx++, keybindings);
if (rule) {
KeybindingsRegistry.registerKeybindingRule2(rule, KeybindingRuleSource.Extension);
commandAdded = true;
result.push(rule);
}
}
@@ -475,13 +481,11 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
rejects.join('\n')
));
}
return commandAdded;
}
private _asCommandRule(isBuiltin: boolean, idx: number, binding: ContributedKeyBinding): IKeybindingRule2 | undefined {
let { command, when, key, mac, linux, win } = binding;
let { command, args, when, key, mac, linux, win } = binding;
let weight: number;
if (isBuiltin) {
@@ -492,6 +496,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
let desc: IKeybindingRule2 = {
id: command,
args,
when: ContextKeyExpr.deserialize(when),
weight: weight,
primary: KeybindingParser.parseKeybinding(key, OS),

View File

@@ -111,11 +111,11 @@ suite('KeybindingsEditing', () => {
teardown(() => {
return new Promise<void>((c, e) => {
if (testDir) {
extfs.del(testDir, os.tmpdir(), () => c(null), () => c(null));
extfs.del(testDir, os.tmpdir(), () => c(undefined), () => c(undefined));
} else {
c(null);
c(undefined);
}
}).then(() => testDir = null);
}).then(() => testDir = null!);
});
test('errors cases - parse errors', () => {
@@ -247,10 +247,10 @@ suite('KeybindingsEditing', () => {
function aResolvedKeybindingItem({ command, when, isDefault, firstPart, chordPart }: { command?: string, when?: string, isDefault?: boolean, firstPart?: { keyCode: KeyCode, modifiers?: Modifiers }, chordPart?: { keyCode: KeyCode, modifiers?: Modifiers } }): ResolvedKeybindingItem {
const aSimpleKeybinding = function (part: { keyCode: KeyCode, modifiers?: Modifiers }): SimpleKeybinding {
const { ctrlKey, shiftKey, altKey, metaKey } = part.modifiers || { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false };
return new SimpleKeybinding(ctrlKey, shiftKey, altKey, metaKey, part.keyCode);
return new SimpleKeybinding(ctrlKey!, shiftKey!, altKey!, metaKey!, part.keyCode);
};
const keybinding = firstPart ? chordPart ? new ChordKeybinding(aSimpleKeybinding(firstPart), aSimpleKeybinding(chordPart)) : aSimpleKeybinding(firstPart) : null;
return new ResolvedKeybindingItem(keybinding ? new USLayoutResolvedKeybinding(keybinding, OS) : null, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : null, isDefault === void 0 ? true : isDefault);
return new ResolvedKeybindingItem(keybinding ? new USLayoutResolvedKeybinding(keybinding, OS) : null, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : null, isDefault === undefined ? true : isDefault);
}
});

View File

@@ -16,7 +16,7 @@ suite('keybindingIO', () => {
test('serialize/deserialize', () => {
function testOneSerialization(keybinding: number, expected: string, msg: string, OS: OperatingSystem): void {
let usLayoutResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
let usLayoutResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS);
let actualSerialized = usLayoutResolvedKeybinding.getUserSettingsLabel();
assert.equal(actualSerialized, expected, expected + ' - ' + msg);
}

View File

@@ -34,8 +34,8 @@ function toIResolvedKeybinding(kb: ResolvedKeybinding): IResolvedKeybinding {
};
}
export function assertResolveKeybinding(mapper: IKeyboardMapper, keybinding: Keybinding, expected: IResolvedKeybinding[]): void {
let actual: IResolvedKeybinding[] = mapper.resolveKeybinding(keybinding).map(toIResolvedKeybinding);
export function assertResolveKeybinding(mapper: IKeyboardMapper, keybinding: Keybinding | null, expected: IResolvedKeybinding[]): void {
let actual: IResolvedKeybinding[] = mapper.resolveKeybinding(keybinding!).map(toIResolvedKeybinding);
assert.deepEqual(actual, expected);
}
@@ -44,7 +44,7 @@ export function assertResolveKeyboardEvent(mapper: IKeyboardMapper, keyboardEven
assert.deepEqual(actual, expected);
}
export function assertResolveUserBinding(mapper: IKeyboardMapper, firstPart: SimpleKeybinding | ScanCodeBinding, chordPart: SimpleKeybinding | ScanCodeBinding, expected: IResolvedKeybinding[]): void {
export function assertResolveUserBinding(mapper: IKeyboardMapper, firstPart: SimpleKeybinding | ScanCodeBinding, chordPart: SimpleKeybinding | ScanCodeBinding | null, expected: IResolvedKeybinding[]): void {
let actual: IResolvedKeybinding[] = mapper.resolveUserBinding(firstPart, chordPart).map(toIResolvedKeybinding);
assert.deepEqual(actual, expected);
}

View File

@@ -14,7 +14,7 @@ suite('keyboardMapper - MAC fallback', () => {
let mapper = new MacLinuxFallbackKeyboardMapper(OperatingSystem.Macintosh);
function _assertResolveKeybinding(k: number, expected: IResolvedKeybinding[]): void {
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Macintosh), expected);
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Macintosh)!, expected);
}
test('resolveKeybinding Cmd+Z', () => {
@@ -56,7 +56,7 @@ suite('keyboardMapper - MAC fallback', () => {
altKey: false,
metaKey: true,
keyCode: KeyCode.KEY_Z,
code: null
code: null!
},
{
label: '⌘Z',
@@ -96,7 +96,7 @@ suite('keyboardMapper - MAC fallback', () => {
altKey: false,
metaKey: true,
keyCode: KeyCode.Meta,
code: null
code: null!
},
{
label: '⌘',
@@ -116,7 +116,7 @@ suite('keyboardMapper - LINUX fallback', () => {
let mapper = new MacLinuxFallbackKeyboardMapper(OperatingSystem.Linux);
function _assertResolveKeybinding(k: number, expected: IResolvedKeybinding[]): void {
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Linux), expected);
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Linux)!, expected);
}
test('resolveKeybinding Ctrl+Z', () => {
@@ -158,7 +158,7 @@ suite('keyboardMapper - LINUX fallback', () => {
altKey: false,
metaKey: false,
keyCode: KeyCode.KEY_Z,
code: null
code: null!
},
{
label: 'Ctrl+Z',
@@ -215,7 +215,7 @@ suite('keyboardMapper - LINUX fallback', () => {
altKey: false,
metaKey: false,
keyCode: KeyCode.Ctrl,
code: null
code: null!
},
{
label: 'Ctrl+',

View File

@@ -37,7 +37,7 @@ suite('keyboardMapper - MAC de_ch', () => {
}
function _assertResolveKeybinding(k: number, expected: IResolvedKeybinding[]): void {
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Macintosh), expected);
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Macintosh)!, expected);
}
test('kb => hw', () => {
@@ -463,7 +463,7 @@ suite('keyboardMapper - LINUX de_ch', () => {
}
function _assertResolveKeybinding(k: number, expected: IResolvedKeybinding[]): void {
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Linux), expected);
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Linux)!, expected);
}
test('kb => hw', () => {
@@ -808,7 +808,7 @@ suite('keyboardMapper - LINUX en_us', () => {
});
function _assertResolveKeybinding(k: number, expected: IResolvedKeybinding[]): void {
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Linux), expected);
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Linux)!, expected);
}
test('resolveKeybinding Ctrl+A', () => {
@@ -1224,7 +1224,7 @@ suite('keyboardMapper', () => {
test('issue #24064: NumLock/NumPad keys stopped working in 1.11 on Linux', () => {
let mapper = new MacLinuxKeyboardMapper(false, {}, OperatingSystem.Linux);
function assertNumpadKeyboardEvent(keyCode: KeyCode, code: string, label: string, electronAccelerator: string, userSettingsLabel: string, dispatch: string): void {
function assertNumpadKeyboardEvent(keyCode: KeyCode, code: string, label: string, electronAccelerator: string | null, userSettingsLabel: string, dispatch: string): void {
assertResolveKeyboardEvent(
mapper,
{
@@ -1251,7 +1251,7 @@ suite('keyboardMapper', () => {
assertNumpadKeyboardEvent(KeyCode.DownArrow, 'Numpad2', 'DownArrow', 'Down', 'down', '[ArrowDown]');
assertNumpadKeyboardEvent(KeyCode.PageDown, 'Numpad3', 'PageDown', 'PageDown', 'pagedown', '[PageDown]');
assertNumpadKeyboardEvent(KeyCode.LeftArrow, 'Numpad4', 'LeftArrow', 'Left', 'left', '[ArrowLeft]');
assertNumpadKeyboardEvent(KeyCode.Unknown, 'Numpad5', 'NumPad5', null, 'numpad5', '[Numpad5]');
assertNumpadKeyboardEvent(KeyCode.Unknown, 'Numpad5', 'NumPad5', null!, 'numpad5', '[Numpad5]');
assertNumpadKeyboardEvent(KeyCode.RightArrow, 'Numpad6', 'RightArrow', 'Right', 'right', '[ArrowRight]');
assertNumpadKeyboardEvent(KeyCode.Home, 'Numpad7', 'Home', 'Home', 'home', '[Home]');
assertNumpadKeyboardEvent(KeyCode.UpArrow, 'Numpad8', 'UpArrow', 'Up', 'up', '[ArrowUp]');
@@ -1326,7 +1326,7 @@ suite('keyboardMapper - LINUX ru', () => {
});
function _assertResolveKeybinding(k: number, expected: IResolvedKeybinding[]): void {
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Linux), expected);
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Linux)!, expected);
}
test('resolveKeybinding Ctrl+S', () => {
@@ -1396,7 +1396,7 @@ suite('keyboardMapper - MAC zh_hant', () => {
});
function _assertResolveKeybinding(k: number, expected: IResolvedKeybinding[]): void {
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Macintosh), expected);
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Macintosh)!, expected);
}
test('issue #28237 resolveKeybinding Cmd+C', () => {
@@ -1427,7 +1427,7 @@ function _assertKeybindingTranslation(mapper: MacLinuxKeyboardMapper, OS: Operat
const runtimeKeybinding = createKeybinding(kb, OS);
const keybindingLabel = new USLayoutResolvedKeybinding(runtimeKeybinding, OS).getUserSettingsLabel();
const keybindingLabel = new USLayoutResolvedKeybinding(runtimeKeybinding!, OS).getUserSettingsLabel();
const actualHardwareKeypresses = mapper.simpleKeybindingToScanCodeBinding(<SimpleKeybinding>runtimeKeybinding);
if (actualHardwareKeypresses.length === 0) {

View File

@@ -17,7 +17,8 @@ async function createKeyboardMapper(isUSStandard: boolean, file: string): Promis
}
function _assertResolveKeybinding(mapper: WindowsKeyboardMapper, k: number, expected: IResolvedKeybinding[]): void {
assertResolveKeybinding(mapper, createKeybinding(k, OperatingSystem.Windows), expected);
const keyBinding = createKeybinding(k, OperatingSystem.Windows);
assertResolveKeybinding(mapper, keyBinding!, expected);
}
suite('keyboardMapper - WINDOWS de_ch', () => {
@@ -73,7 +74,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => {
altKey: false,
metaKey: false,
keyCode: KeyCode.KEY_Z,
code: null
code: null!
},
{
label: 'Ctrl+Z',
@@ -112,7 +113,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => {
altKey: false,
metaKey: false,
keyCode: KeyCode.US_CLOSE_SQUARE_BRACKET,
code: null
code: null!
},
{
label: 'Ctrl+^',
@@ -255,7 +256,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => {
altKey: false,
metaKey: false,
keyCode: KeyCode.Home,
code: null
code: null!
},
{
label: 'Ctrl+Home',
@@ -295,7 +296,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => {
altKey: false,
metaKey: false,
keyCode: KeyCode.Ctrl,
code: null
code: null!
},
{
label: 'Ctrl+',
@@ -359,7 +360,7 @@ suite('keyboardMapper - WINDOWS en_us', () => {
assertResolveUserBinding(
mapper,
new ScanCodeBinding(true, false, false, false, ScanCode.Comma),
null,
null!,
[{
label: 'Ctrl+,',
ariaLabel: 'Control+,',
@@ -381,7 +382,7 @@ suite('keyboardMapper - WINDOWS en_us', () => {
altKey: false,
metaKey: false,
keyCode: KeyCode.Ctrl,
code: null
code: null!
},
{
label: 'Ctrl+',
@@ -417,7 +418,7 @@ suite('keyboardMapper - WINDOWS por_ptb', () => {
altKey: false,
metaKey: false,
keyCode: KeyCode.ABNT_C1,
code: null
code: null!
},
{
label: 'Ctrl+/',
@@ -440,7 +441,7 @@ suite('keyboardMapper - WINDOWS por_ptb', () => {
altKey: false,
metaKey: false,
keyCode: KeyCode.ABNT_C2,
code: null
code: null!
},
{
label: 'Ctrl+.',

View File

@@ -0,0 +1,252 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { isEqual, basenameOrAuthority, basename as resourceBasename } from 'vs/base/common/resources';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { tildify, getPathLabel } from 'vs/base/common/labels';
import { ltrim } from 'vs/base/common/strings';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { isParent } from 'vs/platform/files/common/files';
import { basename, dirname, join } from 'vs/base/common/paths';
import { Schemas } from 'vs/base/common/network';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting } from 'vs/platform/label/common/label';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { match } from 'vs/base/common/glob';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoint<ResourceLabelFormatter[]>({
extensionPoint: 'resourceLabelFormatters',
isDynamic: true,
jsonSchema: {
description: localize('vscode.extension.contributes.resourceLabelFormatters', 'Contributes resource label formatting rules.'),
type: 'array',
items: {
type: 'object',
required: ['scheme', 'formatting'],
properties: {
scheme: {
type: 'string',
description: localize('vscode.extension.contributes.resourceLabelFormatters.scheme', 'URI scheme on which to match the formatter on. For example "file". Simple glob patterns are supported.'),
},
authority: {
type: 'string',
description: localize('vscode.extension.contributes.resourceLabelFormatters.authority', 'URI authority on which to match the formatter on. Simple glob patterns are supported.'),
},
formatting: {
description: localize('vscode.extension.contributes.resourceLabelFormatters.formatting', "Rules for formatting uri resource labels."),
type: 'object',
properties: {
label: {
type: 'string',
description: localize('vscode.extension.contributes.resourceLabelFormatters.label', "Label rules to display. For example: myLabel:/${path}. ${path}, ${scheme} and ${authority} are supported as variables.")
},
separator: {
type: 'string',
description: localize('vscode.extension.contributes.resourceLabelFormatters.separator', "Separator to be used in the uri label display. '/' or '\' as an example.")
},
tildify: {
type: 'boolean',
description: localize('vscode.extension.contributes.resourceLabelFormatters.tildify', "Controls if the start of the uri label should be tildified when possible.")
},
workspaceSuffix: {
type: 'string',
description: localize('vscode.extension.contributes.resourceLabelFormatters.formatting.workspaceSuffix', "Suffix appended to the workspace label.")
}
}
}
}
}
}
});
const sepRegexp = /\//g;
const labelMatchingRegexp = /\$\{scheme\}|\$\{authority\}|\$\{path\}/g;
function hasDriveLetter(path: string): boolean {
return !!(isWindows && path && path[2] === ':');
}
class ResourceLabelFormattersHandler implements IWorkbenchContribution {
private formattersDisposables = new Map<ResourceLabelFormatter, IDisposable>();
constructor(@ILabelService labelService: ILabelService) {
resourceLabelFormattersExtPoint.setHandler((extensions, delta) => {
delta.added.forEach(added => added.value.forEach(formatter => {
this.formattersDisposables.set(formatter, labelService.registerFormatter(formatter));
}));
delta.removed.forEach(removed => removed.value.forEach(formatter => {
this.formattersDisposables.get(formatter)!.dispose();
}));
});
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ResourceLabelFormattersHandler, LifecyclePhase.Restored);
export class LabelService implements ILabelService {
_serviceBrand: any;
private formatters: ResourceLabelFormatter[] = [];
private readonly _onDidChangeFormatters = new Emitter<void>();
constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IWindowService private readonly windowService: IWindowService
) { }
get onDidChangeFormatters(): Event<void> {
return this._onDidChangeFormatters.event;
}
findFormatting(resource: URI): ResourceLabelFormatting | undefined {
let bestResult: ResourceLabelFormatter | undefined;
this.formatters.forEach(formatter => {
if (formatter.scheme === resource.scheme) {
if (!bestResult && !formatter.authority) {
bestResult = formatter;
return;
}
if (!formatter.authority) {
return;
}
if (match(formatter.authority, resource.authority) && (!bestResult || !bestResult.authority || formatter.authority.length > bestResult.authority.length)) {
bestResult = formatter;
}
}
});
return bestResult ? bestResult.formatting : undefined;
}
getUriLabel(resource: URI, options: { relative?: boolean, noPrefix?: boolean } = {}): string {
const formatting = this.findFormatting(resource);
if (!formatting) {
return getPathLabel(resource.path, this.environmentService, options.relative ? this.contextService : undefined);
}
if (options.relative) {
const baseResource = this.contextService && this.contextService.getWorkspaceFolder(resource);
if (baseResource) {
let relativeLabel: string;
if (isEqual(baseResource.uri, resource, !isLinux)) {
relativeLabel = ''; // no label if resources are identical
} else {
const baseResourceLabel = this.formatUri(baseResource.uri, formatting, options.noPrefix);
relativeLabel = ltrim(this.formatUri(resource, formatting, options.noPrefix).substring(baseResourceLabel.length), formatting.separator);
}
const hasMultipleRoots = this.contextService.getWorkspace().folders.length > 1;
if (hasMultipleRoots && !options.noPrefix) {
const rootName = (baseResource && baseResource.name) ? baseResource.name : basenameOrAuthority(baseResource.uri);
relativeLabel = relativeLabel ? (rootName + ' • ' + relativeLabel) : rootName; // always show root basename if there are multiple
}
return relativeLabel;
}
}
return this.formatUri(resource, formatting, options.noPrefix);
}
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string {
if (!isWorkspaceIdentifier(workspace) && !isSingleFolderWorkspaceIdentifier(workspace)) {
const identifier = toWorkspaceIdentifier(workspace);
if (!identifier) {
return '';
}
workspace = identifier;
}
// Workspace: Single Folder
if (isSingleFolderWorkspaceIdentifier(workspace)) {
// Folder on disk
const label = options && options.verbose ? this.getUriLabel(workspace) : resourceBasename(workspace) || '/';
if (workspace.scheme === Schemas.file) {
return label;
}
const formatting = this.findFormatting(workspace);
const suffix = formatting && (typeof formatting.workspaceSuffix === 'string') ? formatting.workspaceSuffix : workspace.scheme;
return suffix ? `${label} (${suffix})` : label;
}
// Workspace: Untitled
if (isParent(workspace.configPath, this.environmentService.workspacesHome, !isLinux /* ignore case */)) {
return localize('untitledWorkspace', "Untitled (Workspace)");
}
// Workspace: Saved
const filename = basename(workspace.configPath);
const workspaceName = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
if (options && options.verbose) {
return localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(URI.file(join(dirname(workspace.configPath), workspaceName))));
}
return localize('workspaceName', "{0} (Workspace)", workspaceName);
}
getHostLabel(): string {
if (this.windowService) {
const authority = this.windowService.getConfiguration().remoteAuthority;
if (authority) {
const formatter = this.findFormatting(URI.from({ scheme: REMOTE_HOST_SCHEME, authority }));
if (formatter && formatter.workspaceSuffix) {
return formatter.workspaceSuffix;
}
}
}
return '';
}
registerFormatter(formatter: ResourceLabelFormatter): IDisposable {
this.formatters.push(formatter);
this._onDidChangeFormatters.fire();
return {
dispose: () => {
this.formatters = this.formatters.filter(f => f !== formatter);
this._onDidChangeFormatters.fire();
}
};
}
private formatUri(resource: URI, formatting: ResourceLabelFormatting, forceNoTildify?: boolean): string {
let label = formatting.label.replace(labelMatchingRegexp, match => {
switch (match) {
case '${scheme}': return resource.scheme;
case '${authority}': return resource.authority;
case '${path}': return resource.path;
default: return '';
}
});
// convert \c:\something => C:\something
if (formatting.normalizeDriveLetter && hasDriveLetter(label)) {
label = label.charAt(1).toUpperCase() + label.substr(2);
}
if (formatting.tildify && !forceNoTildify) {
label = tildify(label, this.environmentService.userHome);
}
if (formatting.authorityPrefix && resource.authority) {
label = formatting.authorityPrefix + label;
}
return label.replace(sepRegexp, formatting.separator);
}
}

View File

@@ -0,0 +1,100 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { TestEnvironmentService, TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { URI } from 'vs/base/common/uri';
import { nativeSep } from 'vs/base/common/paths';
import { isWindows } from 'vs/base/common/platform';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
suite('URI Label', () => {
let labelService: LabelService;
setup(() => {
labelService = new LabelService(TestEnvironmentService, new TestContextService(), new TestWindowService());
});
test('file scheme', function () {
labelService.registerFormatter({
scheme: 'file',
formatting: {
label: '${path}',
separator: nativeSep,
tildify: !isWindows,
normalizeDriveLetter: isWindows
}
});
const uri1 = TestWorkspace.folders[0].uri.with({ path: TestWorkspace.folders[0].uri.path.concat('/a/b/c/d') });
assert.equal(labelService.getUriLabel(uri1, { relative: true }), isWindows ? 'a\\b\\c\\d' : 'a/b/c/d');
assert.equal(labelService.getUriLabel(uri1, { relative: false }), isWindows ? 'C:\\testWorkspace\\a\\b\\c\\d' : '/testWorkspace/a/b/c/d');
const uri2 = URI.file('c:\\1/2/3');
assert.equal(labelService.getUriLabel(uri2, { relative: false }), isWindows ? 'C:\\1\\2\\3' : '/c:\\1/2/3');
});
test('custom scheme', function () {
labelService.registerFormatter({
scheme: 'vscode',
formatting: {
label: 'LABEL/${path}/${authority}/END',
separator: '/',
tildify: true,
normalizeDriveLetter: true
}
});
const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5');
assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL//1/2/3/4/5/microsoft.com/END');
});
test('custom authority', function () {
labelService.registerFormatter({
scheme: 'vscode',
authority: 'micro*',
formatting: {
label: 'LABEL/${path}/${authority}/END',
separator: '/'
}
});
const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5');
assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL//1/2/3/4/5/microsoft.com/END');
});
test('mulitple authority', function () {
labelService.registerFormatter({
scheme: 'vscode',
authority: 'not_matching_but_long',
formatting: {
label: 'first',
separator: '/'
}
});
labelService.registerFormatter({
scheme: 'vscode',
authority: 'microsof*',
formatting: {
label: 'second',
separator: '/'
}
});
labelService.registerFormatter({
scheme: 'vscode',
authority: 'mi*',
formatting: {
label: 'third',
separator: '/'
}
});
// Make sure the most specific authority is picked
const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5');
assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'second');
});
});

View File

@@ -27,61 +27,65 @@ export interface IRawLanguageExtensionPoint {
configuration: string;
}
export const languagesExtPoint: IExtensionPoint<IRawLanguageExtensionPoint[]> = ExtensionsRegistry.registerExtensionPoint<IRawLanguageExtensionPoint[]>('languages', [], {
description: nls.localize('vscode.extension.contributes.languages', 'Contributes language declarations.'),
type: 'array',
items: {
type: 'object',
defaultSnippets: [{ body: { id: '${1:languageId}', aliases: ['${2:label}'], extensions: ['${3:extension}'], configuration: './language-configuration.json' } }],
properties: {
id: {
description: nls.localize('vscode.extension.contributes.languages.id', 'ID of the language.'),
type: 'string'
},
aliases: {
description: nls.localize('vscode.extension.contributes.languages.aliases', 'Name aliases for the language.'),
type: 'array',
items: {
export const languagesExtPoint: IExtensionPoint<IRawLanguageExtensionPoint[]> = ExtensionsRegistry.registerExtensionPoint<IRawLanguageExtensionPoint[]>({
isDynamic: true,
extensionPoint: 'languages',
jsonSchema: {
description: nls.localize('vscode.extension.contributes.languages', 'Contributes language declarations.'),
type: 'array',
items: {
type: 'object',
defaultSnippets: [{ body: { id: '${1:languageId}', aliases: ['${2:label}'], extensions: ['${3:extension}'], configuration: './language-configuration.json' } }],
properties: {
id: {
description: nls.localize('vscode.extension.contributes.languages.id', 'ID of the language.'),
type: 'string'
}
},
extensions: {
description: nls.localize('vscode.extension.contributes.languages.extensions', 'File extensions associated to the language.'),
default: ['.foo'],
type: 'array',
items: {
},
aliases: {
description: nls.localize('vscode.extension.contributes.languages.aliases', 'Name aliases for the language.'),
type: 'array',
items: {
type: 'string'
}
},
extensions: {
description: nls.localize('vscode.extension.contributes.languages.extensions', 'File extensions associated to the language.'),
default: ['.foo'],
type: 'array',
items: {
type: 'string'
}
},
filenames: {
description: nls.localize('vscode.extension.contributes.languages.filenames', 'File names associated to the language.'),
type: 'array',
items: {
type: 'string'
}
},
filenamePatterns: {
description: nls.localize('vscode.extension.contributes.languages.filenamePatterns', 'File name glob patterns associated to the language.'),
type: 'array',
items: {
type: 'string'
}
},
mimetypes: {
description: nls.localize('vscode.extension.contributes.languages.mimetypes', 'Mime types associated to the language.'),
type: 'array',
items: {
type: 'string'
}
},
firstLine: {
description: nls.localize('vscode.extension.contributes.languages.firstLine', 'A regular expression matching the first line of a file of the language.'),
type: 'string'
},
configuration: {
description: nls.localize('vscode.extension.contributes.languages.configuration', 'A relative path to a file containing configuration options for the language.'),
type: 'string',
default: './language-configuration.json'
}
},
filenames: {
description: nls.localize('vscode.extension.contributes.languages.filenames', 'File names associated to the language.'),
type: 'array',
items: {
type: 'string'
}
},
filenamePatterns: {
description: nls.localize('vscode.extension.contributes.languages.filenamePatterns', 'File name glob patterns associated to the language.'),
type: 'array',
items: {
type: 'string'
}
},
mimetypes: {
description: nls.localize('vscode.extension.contributes.languages.mimetypes', 'Mime types associated to the language.'),
type: 'array',
items: {
type: 'string'
}
},
firstLine: {
description: nls.localize('vscode.extension.contributes.languages.firstLine', 'A regular expression matching the first line of a file of the language.'),
type: 'string'
},
configuration: {
description: nls.localize('vscode.extension.contributes.languages.configuration', 'A relative path to a file containing configuration options for the language.'),
type: 'string',
default: './language-configuration.json'
}
}
}
@@ -133,7 +137,7 @@ export class WorkbenchModeServiceImpl extends ModeServiceImpl {
}
}
ModesRegistry.registerLanguages(allValidLanguages);
ModesRegistry.setDynamicLanguages(allValidLanguages);
});

View File

@@ -6,7 +6,7 @@
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 { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { once } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
export class NotificationService extends Disposable implements INotificationService {
@@ -90,7 +90,7 @@ export class NotificationService extends Disposable implements INotificationServ
// Show notification with actions
handle = this.notify({ severity, message, actions, sticky: options && options.sticky, silent: options && options.silent });
once(handle.onDidClose)(() => {
Event.once(handle.onDidClose)(() => {
// Cleanup when notification gets disposed
dispose(toDispose);

View File

@@ -12,7 +12,7 @@ export const IPanelService = createDecorator<IPanelService>('panelService');
export interface IPanelIdentifier {
id: string;
name: string;
cssClass: string;
cssClass?: string;
}
export interface IPanelService {
@@ -41,10 +41,4 @@ export interface IPanelService {
* Returns pinned panels following the visual order
*/
getPinnedPanels(): IPanelIdentifier[];
/**
* Enables or disables a panel. Disabled panels are completly hidden from UI.
* By default all panels are enabled.
*/
setPanelEnablement(id: string, enabled: boolean): void;
}

View File

@@ -86,6 +86,12 @@ export interface IPartService {
*/
getTitleBarOffset(): number;
/**
*
* Set editor area hidden or not
*/
setEditorHidden(hidden: boolean): void;
/**
* Set sidebar hidden or not
*/

View File

@@ -33,7 +33,7 @@ import { IWorkspaceConfigurationService } from 'vs/workbench/services/configurat
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { GroupDirection, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, getSettingsTargetName, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, SettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, getSettingsTargetName, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, SettingsEditorOptions, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences';
import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
@@ -45,7 +45,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
private lastOpenedSettingsInput: PreferencesEditorInput | null = null;
private readonly _onDispose: Emitter<void> = new Emitter<void>();
private readonly _onDispose = new Emitter<void>();
private _defaultUserSettingsUriCounter = 0;
private _defaultUserSettingsContentModel: DefaultSettings;
@@ -55,21 +55,21 @@ export class PreferencesService extends Disposable implements IPreferencesServic
private _defaultFolderSettingsContentModel: DefaultSettings;
constructor(
@IEditorService private editorService: IEditorService,
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
@IFileService private fileService: IFileService,
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
@INotificationService private notificationService: INotificationService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IInstantiationService private instantiationService: IInstantiationService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService,
@ITextModelService private textModelResolverService: ITextModelService,
@IEditorService private readonly editorService: IEditorService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IFileService private readonly fileService: IFileService,
@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
@INotificationService private readonly notificationService: INotificationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@ITextModelService private readonly textModelResolverService: ITextModelService,
@IKeybindingService keybindingService: IKeybindingService,
@IModelService private modelService: IModelService,
@IJSONEditingService private jsonEditingService: IJSONEditingService,
@IModeService private modeService: IModeService,
@ILabelService private labelService: ILabelService
@IModelService private readonly modelService: IModelService,
@IJSONEditingService private readonly jsonEditingService: IJSONEditingService,
@IModeService private readonly modeService: IModeService,
@ILabelService private readonly labelService: ILabelService
) {
super();
// The default keybindings.json updates based on keyboard layouts, so here we make sure
@@ -103,7 +103,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, resource);
}
resolveModel(uri: URI): Thenable<ITextModel> {
resolveModel(uri: URI): Promise<ITextModel> {
if (this.isDefaultSettingsResource(uri)) {
const target = this.getConfigurationTargetFromDefaultSettingsResource(uri);
@@ -150,7 +150,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return Promise.resolve(null);
}
createPreferencesEditorModel(uri: URI): Thenable<IPreferencesEditorModel<any>> {
createPreferencesEditorModel(uri: URI): Promise<IPreferencesEditorModel<any>> {
if (this.isDefaultSettingsResource(uri)) {
return this.createDefaultSettingsEditorModel(uri);
}
@@ -171,15 +171,15 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return Promise.resolve<IPreferencesEditorModel<any>>(null);
}
openRawDefaultSettings(): Thenable<IEditor> {
openRawDefaultSettings(): Promise<IEditor> {
return this.editorService.openEditor({ resource: this.defaultSettingsRawResource });
}
openRawUserSettings(): Thenable<IEditor> {
openRawUserSettings(): Promise<IEditor> {
return this.editorService.openEditor({ resource: this.userSettingsResource });
}
openSettings(jsonEditor?: boolean): Thenable<IEditor> {
openSettings(jsonEditor?: boolean): Promise<IEditor> {
jsonEditor = typeof jsonEditor === 'undefined' ?
this.configurationService.getValue('workbench.settings.editor') === 'json' :
jsonEditor;
@@ -194,13 +194,13 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.openOrSwitchSettings(target, resource);
}
private openSettings2(): Thenable<IEditor> {
private openSettings2(): Promise<IEditor> {
const input = this.settingsEditor2Input;
return this.editorGroupService.activeGroup.openEditor(input)
.then(() => this.editorGroupService.activeGroup.activeControl);
}
openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Thenable<IEditor> {
openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditor> {
jsonEditor = typeof jsonEditor === 'undefined' ?
this.configurationService.getValue('workbench.settings.editor') === 'json' :
jsonEditor;
@@ -210,7 +210,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
this.openOrSwitchSettings2(ConfigurationTarget.USER, undefined, options, group);
}
openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Thenable<IEditor> {
openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditor | null> {
jsonEditor = typeof jsonEditor === 'undefined' ?
this.configurationService.getValue('workbench.settings.editor') === 'json' :
jsonEditor;
@@ -225,7 +225,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE, undefined, options, group);
}
openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Thenable<IEditor> {
openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditor> {
jsonEditor = typeof jsonEditor === 'undefined' ?
this.configurationService.getValue('workbench.settings.editor') === 'json' :
jsonEditor;
@@ -235,7 +235,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE_FOLDER, folder, options, group);
}
switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Thenable<void> {
switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise<void> {
if (!jsonEditor) {
return this.doOpenSettings2(target, resource).then(() => null);
}
@@ -248,7 +248,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
}
}
openGlobalKeybindingSettings(textual: boolean): Thenable<void> {
openGlobalKeybindingSettings(textual: boolean): Promise<void> {
/* __GDPR__
"openKeybindings" : {
"textual" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
@@ -268,9 +268,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return Promise.all([
this.editorService.openEditor({ resource: this.defaultKeybindingsResource, options: { pinned: true, preserveFocus: true, revealIfOpened: true }, label: nls.localize('defaultKeybindings', "Default Keybindings"), description: '' }),
this.editorService.openEditor({ resource: editableKeybindings, options: { pinned: true, revealIfOpened: true } }, sideEditorGroup.id)
]).then(editors => void 0);
]).then(editors => undefined);
} else {
return this.editorService.openEditor({ resource: editableKeybindings, options: { pinned: true, revealIfOpened: true } }).then(() => void 0);
return this.editorService.openEditor({ resource: editableKeybindings, options: { pinned: true, revealIfOpened: true } }).then(() => undefined);
}
});
}
@@ -278,7 +278,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { pinned: true, revealIfOpened: true }).then(() => null);
}
openDefaultKeybindingsFile(): Thenable<IEditor> {
openDefaultKeybindingsFile(): Promise<IEditor> {
return this.editorService.openEditor({ resource: this.defaultKeybindingsResource, label: nls.localize('defaultKeybindings', "Default Keybindings") });
}
@@ -300,7 +300,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
}));
}
private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Thenable<IEditor> {
private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise<IEditor> {
const editorInput = this.getActiveSettingsEditorInput(group);
if (editorInput && editorInput.master.getResource().fsPath !== resource.fsPath) {
return this.doSwitchSettings(configurationTarget, resource, editorInput, group, options);
@@ -308,12 +308,18 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.doOpenSettings(configurationTarget, resource, options, group);
}
private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Thenable<IEditor> {
private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise<IEditor> {
return this.doOpenSettings2(configurationTarget, folderUri, options, group);
}
private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Thenable<IEditor> {
private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditor> {
const openSplitJSON = !!this.configurationService.getValue(USE_SPLIT_JSON_SETTING);
if (openSplitJSON) {
return this.doOpenSplitJSON(configurationTarget, resource, options, group);
}
const openDefaultSettings = !!this.configurationService.getValue(DEFAULT_SETTINGS_EDITOR_SETTING);
return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource)
.then(editableSettingsEditorInput => {
if (!options) {
@@ -323,12 +329,31 @@ export class PreferencesService extends Disposable implements IPreferencesServic
}
if (openDefaultSettings) {
const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(configurationTarget));
const preferencesEditorInput = new PreferencesEditorInput(this.getPreferencesEditorInputName(configurationTarget, resource), editableSettingsEditorInput.getDescription(), defaultPreferencesEditorInput, <EditorInput>editableSettingsEditorInput);
this.lastOpenedSettingsInput = preferencesEditorInput;
return this.editorService.openEditor(preferencesEditorInput, SettingsEditorOptions.create(options), group);
const activeEditorGroup = this.editorGroupService.activeGroup;
const sideEditorGroup = this.editorGroupService.addGroup(activeEditorGroup.id, GroupDirection.RIGHT);
return Promise.all([
this.editorService.openEditor({ resource: this.defaultSettingsRawResource, options: { pinned: true, preserveFocus: true, revealIfOpened: true }, label: nls.localize('defaultSettings', "Default Settings"), description: '' }),
this.editorService.openEditor(editableSettingsEditorInput, { pinned: true, revealIfOpened: true }, sideEditorGroup.id)
]).then(() => null);
} else {
return this.editorService.openEditor(editableSettingsEditorInput, SettingsEditorOptions.create(options), group);
}
return this.editorService.openEditor(editableSettingsEditorInput, SettingsEditorOptions.create(options), group);
});
}
private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditor> {
return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource)
.then(editableSettingsEditorInput => {
if (!options) {
options = { pinned: true };
} else {
options = assign(options, { pinned: true });
}
const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(configurationTarget));
const preferencesEditorInput = new PreferencesEditorInput(this.getPreferencesEditorInputName(configurationTarget, resource), editableSettingsEditorInput.getDescription(), defaultPreferencesEditorInput, <EditorInput>editableSettingsEditorInput);
this.lastOpenedSettingsInput = preferencesEditorInput;
return this.editorService.openEditor(preferencesEditorInput, SettingsEditorOptions.create(options), group);
});
}
@@ -336,7 +361,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER));
}
private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Thenable<IEditor> {
private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise<IEditor> {
const input = this.settingsEditor2Input;
const settingsOptions: ISettingsEditorOptions = {
...options,
@@ -347,7 +372,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.editorService.openEditor(input, SettingsEditorOptions.create(settingsOptions), group);
}
private doSwitchSettings(target: ConfigurationTarget, resource: URI, input: PreferencesEditorInput, group: IEditorGroup, options?: ISettingsEditorOptions): Thenable<IEditor> {
private doSwitchSettings(target: ConfigurationTarget, resource: URI, input: PreferencesEditorInput, group: IEditorGroup, options?: ISettingsEditorOptions): Promise<IEditor> {
return this.getOrCreateEditableSettingsEditorInput(target, this.getEditableSettingsURI(target, resource))
.then(toInput => {
return group.openEditor(input).then(() => {
@@ -422,12 +447,12 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return target === ConfigurationTarget.WORKSPACE_FOLDER ? nls.localize('folderSettingsName', "{0} (Folder Settings)", name) : name;
}
private getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget, resource: URI): Thenable<EditorInput> {
private getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget, resource: URI): Promise<EditorInput> {
return this.createSettingsIfNotExists(target, resource)
.then(() => <EditorInput>this.editorService.createInput({ resource }));
}
private createEditableSettingsEditorModel(configurationTarget: ConfigurationTarget, resource: URI): Thenable<SettingsEditorModel> {
private createEditableSettingsEditorModel(configurationTarget: ConfigurationTarget, resource: URI): Promise<SettingsEditorModel> {
const settingsUri = this.getEditableSettingsURI(configurationTarget, resource);
if (settingsUri) {
const workspace = this.contextService.getWorkspace();
@@ -441,7 +466,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return Promise.resolve<SettingsEditorModel>(null);
}
private createDefaultSettingsEditorModel(defaultSettingsUri: URI): Thenable<DefaultSettingsEditorModel> {
private createDefaultSettingsEditorModel(defaultSettingsUri: URI): Promise<DefaultSettingsEditorModel> {
return this.textModelResolverService.createModelReference(defaultSettingsUri)
.then(reference => {
const target = this.getConfigurationTargetFromDefaultSettingsResource(defaultSettingsUri);
@@ -485,17 +510,17 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return null;
}
private createSettingsIfNotExists(target: ConfigurationTarget, resource: URI): Thenable<void> {
private createSettingsIfNotExists(target: ConfigurationTarget, resource: URI): Promise<void> {
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && target === ConfigurationTarget.WORKSPACE) {
const workspaceConfig = this.contextService.getWorkspace().configuration;
if (!workspaceConfig) {
return Promise.resolve(null);
return Promise.resolve(undefined);
}
return this.fileService.resolveContent(workspaceConfig)
.then(content => {
if (Object.keys(parse(content.value)).indexOf('settings') === -1) {
return this.jsonEditingService.write(resource, { key: 'settings', value: {} }, true).then(null, () => { });
return this.jsonEditingService.write(resource, { key: 'settings', value: {} }, true).then(undefined, () => { });
}
return null;
});
@@ -503,10 +528,10 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.createIfNotExists(resource, emptyEditableSettingsContent).then(() => { });
}
private createIfNotExists(resource: URI, contents: string): Thenable<any> {
return this.fileService.resolveContent(resource, { acceptTextOnly: true }).then(null, error => {
private createIfNotExists(resource: URI, contents: string): Promise<any> {
return this.fileService.resolveContent(resource, { acceptTextOnly: true }).then(undefined, error => {
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
return this.fileService.updateContent(resource, contents).then(null, error => {
return this.fileService.updateContent(resource, contents).then(undefined, error => {
return Promise.reject(new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", this.labelService.getUriLabel(resource, { relative: true }), error)));
});
}
@@ -531,7 +556,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
];
}
private addLanguageOverrideEntry(language: string, settingsModel: IPreferencesEditorModel<ISetting>, codeEditor: ICodeEditor): Thenable<IPosition> {
private addLanguageOverrideEntry(language: string, settingsModel: IPreferencesEditorModel<ISetting>, codeEditor: ICodeEditor): Promise<IPosition> {
const languageKey = `[${language}]`;
let setting = settingsModel.getPreference(languageKey);
const model = codeEditor.getModel();

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