Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -14,24 +14,24 @@ import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { domEvent } from 'vs/base/browser/event';
import { Emitter } from 'vs/base/common/event';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { memoize } from 'vs/base/common/decorators';
import { IdGenerator } from 'vs/base/common/idGenerator';
import { createCSSRule } from 'vs/base/browser/dom';
import URI from 'vs/base/common/uri';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { isWindows } from 'vs/base/common/platform';
import { isWindows, isLinux } from 'vs/base/common/platform';
// The alternative key on all platforms is alt. On windows we also support shift as an alternative key #44136
class AlternativeKeyEmitter extends Emitter<boolean> {
private _subscriptions: IDisposable[] = [];
private _isPressed: boolean;
private static instance: AlternativeKeyEmitter;
private constructor(contextMenuService: IContextMenuService) {
super();
this._subscriptions.push(domEvent(document.body, 'keydown')(e => {
this.isPressed = e.altKey || (isWindows && e.shiftKey);
this.isPressed = e.altKey || ((isWindows || isLinux) && e.shiftKey);
}));
this._subscriptions.push(domEvent(document.body, 'keyup')(e => this.isPressed = false));
this._subscriptions.push(domEvent(document.body, 'mouseleave')(e => this.isPressed = false));
@@ -49,9 +49,12 @@ class AlternativeKeyEmitter extends Emitter<boolean> {
this.fire(this._isPressed);
}
@memoize
static getInstance(contextMenuService: IContextMenuService) {
return new AlternativeKeyEmitter(contextMenuService);
if (!AlternativeKeyEmitter.instance) {
AlternativeKeyEmitter.instance = new AlternativeKeyEmitter(contextMenuService);
}
return AlternativeKeyEmitter.instance;
}
dispose() {

View File

@@ -12,7 +12,7 @@ import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDisposable } from 'vs/base/common/lifecycle';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
export interface ILocalizedString {
value: string;
@@ -59,6 +59,7 @@ export class MenuId {
static readonly ViewTitle = new MenuId();
static readonly ViewItemContext = new MenuId();
static readonly TouchBarContext = new MenuId();
static readonly SearchContext = new MenuId();
readonly id: string = String(MenuId.ID++);
}

View File

@@ -5,7 +5,7 @@
'use strict';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';

View File

@@ -7,11 +7,10 @@
import * as assert from 'assert';
import * as platform from 'vs/base/common/platform';
import fs = require('fs');
import os = require('os');
import path = require('path');
import extfs = require('vs/base/node/extfs');
import pfs = require('vs/base/node/pfs');
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
import Uri from 'vs/base/common/uri';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
@@ -86,23 +85,22 @@ suite('BackupMainService', () => {
let service: TestBackupMainService;
let configService: TestConfigurationService;
setup(done => {
setup(() => {
configService = new TestConfigurationService();
service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService);
// Delete any existing backups completely and then re-create it.
extfs.del(backupHome, os.tmpdir(), () => {
pfs.mkdirp(backupHome).then(() => {
done();
});
return pfs.del(backupHome, os.tmpdir()).then(() => {
return pfs.mkdirp(backupHome);
});
});
teardown(done => {
extfs.del(backupHome, os.tmpdir(), done);
teardown(() => {
return pfs.del(backupHome, os.tmpdir());
});
test('service validates backup workspaces on startup and cleans up (folder workspaces)', done => {
test('service validates backup workspaces on startup and cleans up (folder workspaces)', function () {
this.timeout(1000 * 10); // increase timeout for this test
// 1) backup workspace path does not exist
service.registerFolderBackupSync(fooFile.fsPath);
@@ -145,11 +143,10 @@ suite('BackupMainService', () => {
service.loadSync();
assert.equal(service.getFolderBackupPaths().length, 0);
assert.equal(service.getEmptyWindowBackupPaths().length, 1);
done();
});
test('service validates backup workspaces on startup and cleans up (root workspaces)', done => {
test('service validates backup workspaces on startup and cleans up (root workspaces)', function () {
this.timeout(1000 * 10); // increase timeout for this test
// 1) backup workspace path does not exist
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
@@ -192,11 +189,9 @@ suite('BackupMainService', () => {
service.loadSync();
assert.equal(service.getWorkspaceBackups().length, 0);
assert.equal(service.getEmptyWindowBackupPaths().length, 1);
done();
});
test('service supports to migrate backup data from another location', done => {
test('service supports to migrate backup data from another location', () => {
const backupPathToMigrate = service.toBackupPath(fooFile.fsPath);
fs.mkdirSync(backupPathToMigrate);
fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data');
@@ -210,11 +205,9 @@ suite('BackupMainService', () => {
const emptyBackups = service.getEmptyWindowBackupPaths();
assert.equal(0, emptyBackups.length);
done();
});
test('service backup migration makes sure to preserve existing backups', done => {
test('service backup migration makes sure to preserve existing backups', () => {
const backupPathToMigrate = service.toBackupPath(fooFile.fsPath);
fs.mkdirSync(backupPathToMigrate);
fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data');
@@ -234,8 +227,6 @@ suite('BackupMainService', () => {
const emptyBackups = service.getEmptyWindowBackupPaths();
assert.equal(1, emptyBackups.length);
assert.equal(1, fs.readdirSync(path.join(backupHome, emptyBackups[0])).length);
done();
});
suite('loadSync', () => {
@@ -434,18 +425,17 @@ suite('BackupMainService', () => {
});
suite('registerWindowForBackups', () => {
test('should persist paths to workspaces.json (folder workspace)', done => {
test('should persist paths to workspaces.json (folder workspace)', () => {
service.registerFolderBackupSync(fooFile.fsPath);
service.registerFolderBackupSync(barFile.fsPath);
assert.deepEqual(service.getFolderBackupPaths(), [fooFile.fsPath, barFile.fsPath]);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderWorkspaces, [fooFile.fsPath, barFile.fsPath]);
done();
});
});
test('should persist paths to workspaces.json (root workspace)', done => {
test('should persist paths to workspaces.json (root workspace)', () => {
const ws1 = toWorkspace(fooFile.fsPath);
service.registerWorkspaceBackupSync(ws1);
const ws2 = toWorkspace(barFile.fsPath);
@@ -455,98 +445,90 @@ suite('BackupMainService', () => {
assert.equal(ws1.id, service.getWorkspaceBackups()[0].id);
assert.equal(ws2.id, service.getWorkspaceBackups()[1].id);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.rootWorkspaces.map(b => b.configPath), [fooFile.fsPath, barFile.fsPath]);
assert.equal(ws1.id, json.rootWorkspaces[0].id);
assert.equal(ws2.id, json.rootWorkspaces[1].id);
done();
});
});
test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', done => {
test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', () => {
service.registerFolderBackupSync(fooFile.fsPath.toUpperCase());
assert.deepEqual(service.getFolderBackupPaths(), [fooFile.fsPath.toUpperCase()]);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderWorkspaces, [fooFile.fsPath.toUpperCase()]);
done();
});
});
test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', done => {
test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', () => {
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath.toUpperCase()));
assert.deepEqual(service.getWorkspaceBackups().map(b => b.configPath), [fooFile.fsPath.toUpperCase()]);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.rootWorkspaces.map(b => b.configPath), [fooFile.fsPath.toUpperCase()]);
done();
});
});
});
suite('removeBackupPathSync', () => {
test('should remove folder workspaces from workspaces.json (folder workspace)', done => {
test('should remove folder workspaces from workspaces.json (folder workspace)', () => {
service.registerFolderBackupSync(fooFile.fsPath);
service.registerFolderBackupSync(barFile.fsPath);
service.removeBackupPathSync(fooFile.fsPath, service.backupsData.folderWorkspaces);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderWorkspaces, [barFile.fsPath]);
service.removeBackupPathSync(barFile.fsPath, service.backupsData.folderWorkspaces);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.folderWorkspaces, []);
done();
});
});
});
test('should remove folder workspaces from workspaces.json (root workspace)', done => {
test('should remove folder workspaces from workspaces.json (root workspace)', () => {
const ws1 = toWorkspace(fooFile.fsPath);
service.registerWorkspaceBackupSync(ws1);
const ws2 = toWorkspace(barFile.fsPath);
service.registerWorkspaceBackupSync(ws2);
service.removeBackupPathSync(ws1, service.backupsData.rootWorkspaces);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.rootWorkspaces.map(r => r.configPath), [barFile.fsPath]);
service.removeBackupPathSync(ws2, service.backupsData.rootWorkspaces);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.rootWorkspaces, []);
done();
});
});
});
test('should remove empty workspaces from workspaces.json', done => {
test('should remove empty workspaces from workspaces.json', () => {
service.registerEmptyWindowBackupSync('foo');
service.registerEmptyWindowBackupSync('bar');
service.removeBackupPathSync('foo', service.backupsData.emptyWorkspaces);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.emptyWorkspaces, ['bar']);
service.removeBackupPathSync('bar', service.backupsData.emptyWorkspaces);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.emptyWorkspaces, []);
done();
});
});
});
test('should fail gracefully when removing a path that doesn\'t exist', done => {
test('should fail gracefully when removing a path that doesn\'t exist', () => {
const workspacesJson: IBackupWorkspacesFormat = { rootWorkspaces: [], folderWorkspaces: [fooFile.fsPath], emptyWorkspaces: [] };
pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
return pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
service.removeBackupPathSync(barFile.fsPath, service.backupsData.folderWorkspaces);
service.removeBackupPathSync('test', service.backupsData.emptyWorkspaces);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json.folderWorkspaces, [fooFile.fsPath]);
done();
});
});
});
@@ -574,7 +556,7 @@ suite('BackupMainService', () => {
});
suite('mixed path casing', () => {
test('should handle case insensitive paths properly (registerWindowForBackupsSync) (folder workspace)', done => {
test('should handle case insensitive paths properly (registerWindowForBackupsSync) (folder workspace)', () => {
service.registerFolderBackupSync(fooFile.fsPath);
service.registerFolderBackupSync(fooFile.fsPath.toUpperCase());
@@ -583,11 +565,9 @@ suite('BackupMainService', () => {
} else {
assert.equal(service.getFolderBackupPaths().length, 1);
}
done();
});
test('should handle case insensitive paths properly (registerWindowForBackupsSync) (root workspace)', done => {
test('should handle case insensitive paths properly (registerWindowForBackupsSync) (root workspace)', () => {
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath.toUpperCase()));
@@ -596,11 +576,9 @@ suite('BackupMainService', () => {
} else {
assert.equal(service.getWorkspaceBackups().length, 1);
}
done();
});
test('should handle case insensitive paths properly (removeBackupPathSync) (folder workspace)', done => {
test('should handle case insensitive paths properly (removeBackupPathSync) (folder workspace)', () => {
// same case
service.registerFolderBackupSync(fooFile.fsPath);
@@ -616,8 +594,6 @@ suite('BackupMainService', () => {
} else {
assert.equal(service.getFolderBackupPaths().length, 0);
}
done();
});
});
});

View File

@@ -6,7 +6,7 @@
'use strict';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { ipcRenderer as ipc } from 'electron';
import { ILogService } from 'vs/platform/log/common/log';
@@ -29,7 +29,7 @@ export interface IBroadcastService {
export class BroadcastService implements IBroadcastService {
public _serviceBrand: any;
private _onBroadcast: Emitter<IBroadcast>;
private readonly _onBroadcast: Emitter<IBroadcast>;
constructor(
private windowId: number,

View File

@@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable } from 'vs/base/common/lifecycle';
import { TypeConstraint, validateConstraints } from 'vs/base/common/types';
import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { LinkedList } from 'vs/base/common/linkedList';
export const ICommandService = createDecorator<ICommandService>('commandService');

View File

@@ -7,12 +7,12 @@ import { TPromise } from 'vs/base/common/winjs.base';
import * as objects from 'vs/base/common/objects';
import * as types from 'vs/base/common/types';
import URI from 'vs/base/common/uri';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { StrictResourceMap } from 'vs/base/common/map';
import { ResourceMap } from 'vs/base/common/map';
export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');
@@ -47,7 +47,7 @@ export interface IConfigurationChangeEvent {
// Following data is used for Extension host configuration event
changedConfiguration: IConfigurationModel;
changedConfigurationByResource: StrictResourceMap<IConfigurationModel>;
changedConfigurationByResource: ResourceMap<IConfigurationModel>;
}
export interface IConfigurationService {
@@ -112,6 +112,7 @@ export interface IConfigurationData {
user: IConfigurationModel;
workspace: IConfigurationModel;
folders: { [folder: string]: IConfigurationModel };
isComplete: boolean;
}
export function compare(from: IConfigurationModel, to: IConfigurationModel): { added: string[], removed: string[], updated: string[] } {

View File

@@ -5,7 +5,7 @@
'use strict';
import * as json from 'vs/base/common/json';
import { StrictResourceMap } from 'vs/base/common/map';
import { ResourceMap } from 'vs/base/common/map';
import * as arrays from 'vs/base/common/arrays';
import * as types from 'vs/base/common/types';
import * as objects from 'vs/base/common/objects';
@@ -85,7 +85,7 @@ export class ConfigurationModel implements IConfigurationModel {
if (override) {
this.mergeContents(override.contents, otherOverride.contents);
} else {
overrides.push(otherOverride);
overrides.push(objects.deepClone(otherOverride));
}
}
for (const key of other.keys) {
@@ -279,15 +279,16 @@ export class ConfigurationModelParser {
export class Configuration {
private _workspaceConsolidatedConfiguration: ConfigurationModel = null;
private _foldersConsolidatedConfigurations: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>();
private _foldersConsolidatedConfigurations: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>();
constructor(
private _defaultConfiguration: ConfigurationModel,
private _userConfiguration: ConfigurationModel,
private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(),
private _folderConfigurations: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>(),
private _folderConfigurations: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>(),
private _memoryConfiguration: ConfigurationModel = new ConfigurationModel(),
private _memoryConfigurationByResource: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>()) {
private _memoryConfigurationByResource: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>(),
private _freeze: boolean = true) {
}
getValue(section: string, overrides: IConfigurationOverrides, workspace: Workspace): any {
@@ -394,7 +395,7 @@ export class Configuration {
return this._workspaceConfiguration;
}
protected get folders(): StrictResourceMap<ConfigurationModel> {
protected get folders(): ResourceMap<ConfigurationModel> {
return this._folderConfigurations;
}
@@ -422,7 +423,10 @@ export class Configuration {
private getWorkspaceConsolidatedConfiguration(): ConfigurationModel {
if (!this._workspaceConsolidatedConfiguration) {
this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this._userConfiguration).merge(this._workspaceConfiguration).merge(this._memoryConfiguration).freeze();
this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this._userConfiguration, this._workspaceConfiguration, this._memoryConfiguration);
if (this._freeze) {
this._workspaceConfiguration = this._workspaceConfiguration.freeze();
}
}
return this._workspaceConsolidatedConfiguration;
}
@@ -433,7 +437,10 @@ export class Configuration {
const workspaceConsolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();
const folderConfiguration = this._folderConfigurations.get(folder);
if (folderConfiguration) {
folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration).freeze();
folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration);
if (this._freeze) {
folderConsolidatedConfiguration = folderConsolidatedConfiguration.freeze();
}
this._foldersConsolidatedConfigurations.set(folder, folderConsolidatedConfiguration);
} else {
folderConsolidatedConfiguration = workspaceConsolidateConfiguration;
@@ -473,7 +480,8 @@ export class Configuration {
const { contents, overrides, keys } = this._folderConfigurations.get(folder);
result[folder.toString()] = { contents, overrides, keys };
return result;
}, Object.create({}))
}, Object.create({})),
isComplete: true
};
}
@@ -494,21 +502,6 @@ export class Configuration {
}
return all;
}
public static parse(data: IConfigurationData): Configuration {
const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults);
const userConfiguration = Configuration.parseConfigurationModel(data.user);
const workspaceConfiguration = Configuration.parseConfigurationModel(data.workspace);
const folders: StrictResourceMap<ConfigurationModel> = Object.keys(data.folders).reduce((result, key) => {
result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key]));
return result;
}, new StrictResourceMap<ConfigurationModel>());
return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders);
}
private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel {
return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze();
}
}
export class AbstractConfigurationChangeEvent {
@@ -542,7 +535,7 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i
constructor(
private _changedConfiguration: ConfigurationModel = new ConfigurationModel(),
private _changedConfigurationByResource: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>()) {
private _changedConfigurationByResource: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>()) {
super();
}
@@ -550,7 +543,7 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i
return this._changedConfiguration;
}
get changedConfigurationByResource(): StrictResourceMap<IConfigurationModel> {
get changedConfigurationByResource(): ResourceMap<IConfigurationModel> {
return this._changedConfigurationByResource;
}

View File

@@ -4,14 +4,13 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import Event, { Emitter } from 'vs/base/common/event';
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Registry } from 'vs/platform/registry/common/platform';
import types = require('vs/base/common/types');
import * as types from 'vs/base/common/types';
import * as strings from 'vs/base/common/strings';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { deepClone } from 'vs/base/common/objects';
export const Extensions = {
Configuration: 'base.contributions.configuration'
@@ -63,13 +62,13 @@ export interface IConfigurationRegistry {
}
export enum ConfigurationScope {
WINDOW = 1,
RESOURCE
APPLICATION = 1,
WINDOW,
RESOURCE,
}
export interface IConfigurationPropertySchema extends IJSONSchema {
overridable?: boolean;
isExecutable?: boolean;
scope?: ConfigurationScope;
notMultiRootAdopted?: boolean;
included?: boolean;
@@ -93,8 +92,10 @@ export interface IDefaultConfigurationExtension {
defaults: { [key: string]: {} };
}
export const settingsSchema: IJSONSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown configuration setting' };
export const resourceSettingsSchema: IJSONSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown configuration setting' };
export const allSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} };
export const applicationSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} };
export const windowSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} };
export const resourceSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} };
export const editorConfigurationSchemaId = 'vscode://schemas/settings/editor';
const contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
@@ -108,7 +109,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
private overrideIdentifiers: string[] = [];
private overridePropertyPattern: string;
private _onDidRegisterConfiguration: Emitter<string[]> = new Emitter<string[]>();
private readonly _onDidRegisterConfiguration: Emitter<string[]> = new Emitter<string[]>();
readonly onDidRegisterConfiguration: Event<string[]> = this._onDidRegisterConfiguration.event;
constructor() {
@@ -239,10 +240,17 @@ class ConfigurationRegistry implements IConfigurationRegistry {
let properties = configuration.properties;
if (properties) {
for (let key in properties) {
settingsSchema.properties[key] = properties[key];
resourceSettingsSchema.properties[key] = deepClone(properties[key]);
if (properties[key].scope !== ConfigurationScope.RESOURCE) {
resourceSettingsSchema.properties[key].doNotSuggest = true;
allSettings.properties[key] = properties[key];
switch (properties[key].scope) {
case ConfigurationScope.APPLICATION:
applicationSettings.properties[key] = properties[key];
break;
case ConfigurationScope.WINDOW:
windowSettings.properties[key] = properties[key];
break;
case ConfigurationScope.RESOURCE:
resourceSettings.properties[key] = properties[key];
break;
}
}
}
@@ -262,7 +270,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
private updateOverridePropertyPatternKey(): void {
let patternProperties: IJSONSchema = settingsSchema.patternProperties[this.overridePropertyPattern];
let patternProperties: IJSONSchema = allSettings.patternProperties[this.overridePropertyPattern];
if (!patternProperties) {
patternProperties = {
type: 'object',
@@ -271,11 +279,18 @@ class ConfigurationRegistry implements IConfigurationRegistry {
$ref: editorConfigurationSchemaId
};
}
delete settingsSchema.patternProperties[this.overridePropertyPattern];
delete allSettings.patternProperties[this.overridePropertyPattern];
delete applicationSettings.patternProperties[this.overridePropertyPattern];
delete windowSettings.patternProperties[this.overridePropertyPattern];
delete resourceSettings.patternProperties[this.overridePropertyPattern];
this.computeOverridePropertyPattern();
settingsSchema.patternProperties[this.overridePropertyPattern] = patternProperties;
resourceSettingsSchema.patternProperties[this.overridePropertyPattern] = patternProperties;
allSettings.patternProperties[this.overridePropertyPattern] = patternProperties;
applicationSettings.patternProperties[this.overridePropertyPattern] = patternProperties;
windowSettings.patternProperties[this.overridePropertyPattern] = patternProperties;
resourceSettings.patternProperties[this.overridePropertyPattern] = patternProperties;
}
private update(configuration: IConfigurationNode): void {

View File

@@ -7,14 +7,14 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { ConfigWatcher } from 'vs/base/node/config';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
export class UserConfiguration extends Disposable {
private userConfigModelWatcher: ConfigWatcher<ConfigurationModelParser>;
private _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
constructor(settingsPath: string) {

View File

@@ -9,7 +9,7 @@ import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/co
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, compare, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration';
import { DefaultConfigurationModel, Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { TPromise } from 'vs/base/common/winjs.base';
import { equals } from 'vs/base/common/objects';
@@ -23,7 +23,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
private _configuration: Configuration;
private userConfiguration: UserConfiguration;
private _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
private readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
constructor(

View File

@@ -199,6 +199,17 @@ suite('ConfigurationModel', () => {
assert.deepEqual(result.keys, ['a.b', 'f']);
});
test('merge overrides when frozen', () => {
let model1 = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f'], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]).freeze();
let model2 = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } } }]).freeze();
let result = new ConfigurationModel().merge(model1, model2);
assert.deepEqual(result.contents, { 'a': { 'b': 2 }, 'f': 1 });
assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } } }]);
assert.deepEqual(result.override('c').contents, { 'a': { 'b': 2, 'd': 1, 'e': 2 }, 'f': 1 });
assert.deepEqual(result.keys, ['a.b', 'f']);
});
test('Test contents while getting an existing property', () => {
let testObject = new ConfigurationModel({ 'a': 1 });
assert.deepEqual(testObject.getValue('a'), 1);
@@ -349,24 +360,6 @@ suite('CustomConfigurationModel', () => {
});
assert.equal(true, new DefaultConfigurationModel().getValue('a'));
});
test('Test registering the language property', () => {
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
'id': '[a]',
'order': 1,
'title': 'a',
'type': 'object',
'properties': {
'[a]': {
'description': 'a',
'type': 'boolean',
'default': false,
}
}
});
assert.equal(undefined, new DefaultConfigurationModel().getValue('[a]'));
});
});
suite('ConfigurationChangeEvent', () => {

View File

@@ -5,20 +5,19 @@
'use strict';
import assert = require('assert');
import os = require('os');
import path = require('path');
import fs = require('fs');
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';
import { Registry } from 'vs/platform/registry/common/platform';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import extfs = require('vs/base/node/extfs');
import uuid = require('vs/base/common/uuid');
import * as uuid from 'vs/base/common/uuid';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { mkdirp } from 'vs/base/node/pfs';
import { testFile } from 'vs/base/test/node/utils';
class SettingsTestEnvironmentService extends EnvironmentService {
@@ -31,22 +30,11 @@ class SettingsTestEnvironmentService extends EnvironmentService {
suite('ConfigurationService - Node', () => {
function testFile(callback: (path: string, cleanUp: (callback: () => void) => void) => void): void {
const id = uuid.generateUuid();
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
const newDir = path.join(parentDir, 'config', id);
const testFile = path.join(newDir, 'config.json');
test('simple', () => {
return testFile('config', 'config.json').then(res => {
fs.writeFileSync(res.testFile, '{ "foo": "bar" }');
const onMkdirp = error => callback(testFile, (callback) => extfs.del(parentDir, os.tmpdir(), () => { }, callback));
mkdirp(newDir, 493).done(() => onMkdirp(null), error => onMkdirp(error));
}
test('simple', (done: () => void) => {
testFile((testFile, cleanUp) => {
fs.writeFileSync(testFile, '{ "foo": "bar" }');
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, res.testFile));
const config = service.getValue<{ foo: string }>();
assert.ok(config);
@@ -54,15 +42,15 @@ suite('ConfigurationService - Node', () => {
service.dispose();
cleanUp(done);
return res.cleanUp();
});
});
test('config gets flattened', (done: () => void) => {
testFile((testFile, cleanUp) => {
fs.writeFileSync(testFile, '{ "testworkbench.editor.tabs": true }');
test('config gets flattened', () => {
return testFile('config', 'config.json').then(res => {
fs.writeFileSync(res.testFile, '{ "testworkbench.editor.tabs": true }');
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, res.testFile));
const config = service.getValue<{ testworkbench: { editor: { tabs: boolean } } }>();
assert.ok(config);
@@ -72,22 +60,22 @@ suite('ConfigurationService - Node', () => {
service.dispose();
cleanUp(done);
return res.cleanUp();
});
});
test('error case does not explode', (done: () => void) => {
testFile((testFile, cleanUp) => {
fs.writeFileSync(testFile, ',,,,');
test('error case does not explode', () => {
return testFile('config', 'config.json').then(res => {
fs.writeFileSync(res.testFile, ',,,,');
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, res.testFile));
const config = service.getValue<{ foo: string }>();
assert.ok(config);
service.dispose();
cleanUp(done);
return res.cleanUp();
});
});
@@ -105,17 +93,17 @@ suite('ConfigurationService - Node', () => {
service.dispose();
});
test('reloadConfiguration', (done: () => void) => {
testFile((testFile, cleanUp) => {
fs.writeFileSync(testFile, '{ "foo": "bar" }');
test('reloadConfiguration', () => {
return testFile('config', 'config.json').then(res => {
fs.writeFileSync(res.testFile, '{ "foo": "bar" }');
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, res.testFile));
let config = service.getValue<{ foo: string }>();
assert.ok(config);
assert.equal(config.foo, 'bar');
fs.writeFileSync(testFile, '{ "foo": "changed" }');
fs.writeFileSync(res.testFile, '{ "foo": "changed" }');
// still outdated
config = service.getValue<{ foo: string }>();
@@ -123,19 +111,19 @@ suite('ConfigurationService - Node', () => {
assert.equal(config.foo, 'bar');
// force a reload to get latest
service.reloadConfiguration().then(() => {
return service.reloadConfiguration().then(() => {
config = service.getValue<{ foo: string }>();
assert.ok(config);
assert.equal(config.foo, 'changed');
service.dispose();
cleanUp(done);
return res.cleanUp();
});
});
});
test('model defaults', (done: () => void) => {
test('model defaults', () => {
interface ITestSetting {
configuration: {
service: {
@@ -144,7 +132,7 @@ suite('ConfigurationService - Node', () => {
};
}
const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigurationExtensions.Configuration);
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
@@ -162,19 +150,19 @@ suite('ConfigurationService - Node', () => {
assert.ok(setting);
assert.equal(setting.configuration.service.testSetting, 'isSet');
testFile((testFile, cleanUp) => {
fs.writeFileSync(testFile, '{ "testworkbench.editor.tabs": true }');
return testFile('config', 'config.json').then(res => {
fs.writeFileSync(res.testFile, '{ "testworkbench.editor.tabs": true }');
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, res.testFile));
let setting = service.getValue<ITestSetting>();
assert.ok(setting);
assert.equal(setting.configuration.service.testSetting, 'isSet');
fs.writeFileSync(testFile, '{ "configuration.service.testSetting": "isChanged" }');
fs.writeFileSync(res.testFile, '{ "configuration.service.testSetting": "isChanged" }');
service.reloadConfiguration().then(() => {
return service.reloadConfiguration().then(() => {
let setting = service.getValue<ITestSetting>();
assert.ok(setting);
@@ -183,13 +171,13 @@ suite('ConfigurationService - Node', () => {
service.dispose();
serviceWithoutFile.dispose();
cleanUp(done);
return res.cleanUp();
});
});
});
test('lookup', (done: () => void) => {
const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigurationExtensions.Configuration);
test('lookup', () => {
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
@@ -201,8 +189,8 @@ suite('ConfigurationService - Node', () => {
}
});
testFile((testFile, cleanUp) => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
return testFile('config', 'config.json').then(r => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, r.testFile));
let res = service.inspect('something.missing');
assert.strictEqual(res.value, void 0);
@@ -214,7 +202,7 @@ suite('ConfigurationService - Node', () => {
assert.strictEqual(res.value, 'isSet');
assert.strictEqual(res.user, void 0);
fs.writeFileSync(testFile, '{ "lookup.service.testSetting": "bar" }');
fs.writeFileSync(r.testFile, '{ "lookup.service.testSetting": "bar" }');
return service.reloadConfiguration().then(() => {
res = service.inspect('lookup.service.testSetting');
@@ -224,13 +212,13 @@ suite('ConfigurationService - Node', () => {
service.dispose();
cleanUp(done);
return r.cleanUp();
});
});
});
test('lookup with null', (done: () => void) => {
const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigurationExtensions.Configuration);
test('lookup with null', () => {
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': '_testNull',
'type': 'object',
@@ -241,15 +229,15 @@ suite('ConfigurationService - Node', () => {
}
});
testFile((testFile, cleanUp) => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, testFile));
return testFile('config', 'config.json').then(r => {
const service = new ConfigurationService(new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, r.testFile));
let res = service.inspect('lookup.service.testNullSetting');
assert.strictEqual(res.default, null);
assert.strictEqual(res.value, null);
assert.strictEqual(res.user, void 0);
fs.writeFileSync(testFile, '{ "lookup.service.testNullSetting": null }');
fs.writeFileSync(r.testFile, '{ "lookup.service.testNullSetting": null }');
return service.reloadConfiguration().then(() => {
res = service.inspect('lookup.service.testNullSetting');
@@ -259,7 +247,7 @@ suite('ConfigurationService - Node', () => {
service.dispose();
cleanUp(done);
return r.cleanUp();
});
});
});

View File

@@ -7,9 +7,9 @@
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
import { IContextKey, IContext, IContextKeyServiceTarget, IContextKeyService, SET_CONTEXT_COMMAND_ID, ContextKeyExpr, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey';
import { IContextKey, IContext, IContextKeyServiceTarget, IContextKeyService, SET_CONTEXT_COMMAND_ID, ContextKeyExpr, IContextKeyChangeEvent, IReadableSet } from 'vs/platform/contextkey/common/contextkey';
import { IConfigurationService, IConfigurationChangeEvent, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import Event, { Emitter, debounceEvent } from 'vs/base/common/event';
import { Event, Emitter, debounceEvent } from 'vs/base/common/event';
const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context';
@@ -179,7 +179,7 @@ export class ContextKeyChangeEvent implements IContextKeyChangeEvent {
this._keys = this._keys.concat(oneOrManyKeys);
}
affectsSome(keys: Set<string>): boolean {
affectsSome(keys: IReadableSet<string>): boolean {
for (const key of this._keys) {
if (keys.has(key)) {
return true;
@@ -271,7 +271,7 @@ export class ContextKeyService extends AbstractContextKeyService implements ICon
private _toDispose: IDisposable[] = [];
constructor( @IConfigurationService configurationService: IConfigurationService) {
constructor(@IConfigurationService configurationService: IConfigurationService) {
super(0);
this._lastContextId = 0;
this._contexts = Object.create(null);

View File

@@ -5,7 +5,7 @@
'use strict';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
export enum ContextKeyExprType {
@@ -112,8 +112,9 @@ export abstract class ContextKeyExpr {
}
let value = serializedValue.slice(start + 1, end);
let caseIgnoreFlag = serializedValue[end + 1] === 'i' ? 'i' : '';
try {
return new RegExp(value);
return new RegExp(value, caseIgnoreFlag);
} catch (e) {
console.warn(`bad regexp-value '${serializedValue}', parse error: ${e}`);
return null;
@@ -400,7 +401,7 @@ export class ContextKeyRegexExpr implements ContextKeyExpr {
}
public serialize(): string {
return `${this.key} =~ /${this.regexp ? this.regexp.source : '<invalid>'}/`;
return `${this.key} =~ /${this.regexp ? this.regexp.source : '<invalid>'}/${this.regexp.ignoreCase ? 'i' : ''}`;
}
public keys(): string[] {
@@ -553,8 +554,12 @@ export interface IContextKeyServiceTarget {
export const IContextKeyService = createDecorator<IContextKeyService>('contextKeyService');
export interface IReadableSet<T> {
has(value: T): boolean;
}
export interface IContextKeyChangeEvent {
affectsSome(keys: Set<string>): boolean;
affectsSome(keys: IReadableSet<string>): boolean;
}
export interface IContextKeyService {

View File

@@ -81,6 +81,7 @@ suite('ContextKeyExpr', () => {
testExpression(expr + ' != 5', value != <any>'5');
testExpression('!' + expr, !value);
testExpression(expr + ' =~ /d.*/', /d.*/.test(value));
testExpression(expr + ' =~ /D/i', /D/i.test(value));
}
testBatch('a', true);
@@ -92,7 +93,7 @@ suite('ContextKeyExpr', () => {
testExpression('a && !b', true && !false);
testExpression('a && b', true && false);
testExpression('a && !b && c == 5', true && !false && '5' == '5');
testExpression('dddd =~ d.*', false);
testExpression('d =~ /e.*/', false);
/* tslint:enable:triple-equals */
});
});

View File

@@ -7,7 +7,7 @@
import { ContextMenuHandler } from './contextMenuHandler';
import { IContextViewService, IContextMenuService } from './contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';

View File

@@ -5,7 +5,7 @@
'use strict';
import { IDisposable } from 'vs/base/common/lifecycle';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';

View File

@@ -1,40 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
export interface IChoiceChannel extends IChannel {
call(command: 'choose'): TPromise<number>;
call(command: string, arg?: any): TPromise<any>;
}
export class ChoiceChannel implements IChoiceChannel {
constructor( @IChoiceService private choiceService: IChoiceService) {
}
call(command: string, args?: [Severity, string, string[], number, boolean]): TPromise<any> {
switch (command) {
case 'choose': return this.choiceService.choose(args[0], args[1], args[2], args[3], args[4]);
}
return TPromise.wrapError(new Error('invalid command'));
}
}
export class ChoiceChannelClient implements IChoiceService {
_serviceBrand: any;
constructor(private channel: IChoiceChannel) { }
choose(severity: Severity, message: string, options: string[], cancelId?: number, modal?: boolean): TPromise<number> {
return this.channel.call('choose', [severity, message, options, cancelId, modal]);
}
}

View File

@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
export interface IDialogChannel extends IChannel {
call(command: 'show'): TPromise<number>;
call(command: 'confirm'): TPromise<IConfirmationResult>;
call(command: string, arg?: any): TPromise<any>;
}
export class DialogChannel implements IDialogChannel {
constructor(@IDialogService private dialogService: IDialogService) {
}
call(command: string, args?: any[]): TPromise<any> {
switch (command) {
case 'show': return this.dialogService.show(args[0], args[1], args[2]);
case 'confirm': return this.dialogService.confirm(args[0]);
}
return TPromise.wrapError(new Error('invalid command'));
}
}
export class DialogChannelClient implements IDialogService {
_serviceBrand: any;
constructor(private channel: IDialogChannel) { }
show(severity: Severity, message: string, options: string[]): TPromise<number> {
return this.channel.call('show', [severity, message, options]);
}
confirm(confirmation: IConfirmation): TPromise<IConfirmationResult> {
return this.channel.call('confirm', [confirmation]);
}
}

View File

@@ -7,6 +7,9 @@
import { TPromise } from 'vs/base/common/winjs.base';
import Severity from 'vs/base/common/severity';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import URI from 'vs/base/common/uri';
import { basename } from 'vs/base/common/paths';
import { localize } from 'vs/nls';
export interface IConfirmation {
title?: string;
@@ -22,63 +25,66 @@ export interface IConfirmation {
}
export interface IConfirmationResult {
/**
* Will be true if the dialog was confirmed with the primary button
* pressed.
*/
confirmed: boolean;
/**
* This will only be defined if the confirmation was created
* with the checkox option defined.
*/
checkboxChecked?: boolean;
}
export const IConfirmationService = createDecorator<IConfirmationService>('confirmationService');
export const IDialogService = createDecorator<IDialogService>('dialogService');
export interface IConfirmationService {
export interface IDialogOptions {
cancelId?: number;
detail?: string;
}
/**
* A service to bring up modal dialogs.
*
* Note: use the `INotificationService.prompt()` method for a non-modal way to ask
* the user for input.
*/
export interface IDialogService {
_serviceBrand: any;
/**
* Ask the user for confirmation with a modal dialog.
*/
confirm(confirmation: IConfirmation): TPromise<boolean>;
confirm(confirmation: IConfirmation): TPromise<IConfirmationResult>;
/**
* Ask the user for confirmation with a checkbox in a modal dialog.
* Present a modal dialog to the user.
*
* @returns A promise with the selected choice index. If the user refused to choose,
* then a promise with index of `cancelId` option is returned. If there is no such
* option then promise with index `0` is returned.
*/
confirmWithCheckbox(confirmation: IConfirmation): TPromise<IConfirmationResult>;
show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): TPromise<number>;
}
export const IChoiceService = createDecorator<IChoiceService>('choiceService');
const MAX_CONFIRM_FILES = 10;
export function getConfirmMessage(start: string, resourcesToConfirm: URI[]): string {
const message = [start];
message.push('');
message.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => basename(r.fsPath)));
/**
* The choices to present to the user. The `ISecondaryChoice` hint allows to control where
* choices appear when the `modal` option is set to `false`. In that case, the choices
* are presented as part of a notification and secondary choices will appear less
* prominent.
*/
export interface SecondaryChoice {
label: string;
keepOpen?: boolean;
}
export type PrimaryChoice = string;
export type Choice = PrimaryChoice | SecondaryChoice;
if (resourcesToConfirm.length > MAX_CONFIRM_FILES) {
if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) {
message.push(localize('moreFile', "...1 additional file not shown"));
} else {
message.push(localize('moreFiles', "...{0} additional files not shown", resourcesToConfirm.length - MAX_CONFIRM_FILES));
}
}
export interface IChoiceService {
_serviceBrand: any;
/**
* Prompt the user for a choice between multiple choices.
*
* @param choices the choices to present to the user. The `isSecondary` hint allows
* to control where are presented as part of a notification and secondary choices will
* appear less choices appear when the `modal` option is set to `false`. In that case,
* the choices prominent.
*
* @param when `modal` is true, this will block the user until chooses.
*
* @returns A promise with the selected choice index. The promise is cancellable
* which hides the message. The promise can return an error, meaning that
* the user refused to choose.
*
* When `modal` is true and user refused to choose, then promise with index of
* `Cancel` option is returned. If there is no such option then promise with
* `0` index is returned.
*/
choose(severity: Severity, message: string, choices: Choice[], cancelId?: number, modal?: boolean): TPromise<number>;
message.push('');
return message.join('\n');
}

View File

@@ -5,14 +5,15 @@
import * as readline from 'readline';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
export class ChoiceCliService implements IChoiceService {
export class CommandLineDialogService implements IDialogService {
_serviceBrand: any;
choose(severity: Severity, message: string, options: string[]): TPromise<number> {
show(severity: Severity, message: string, options: string[]): TPromise<number> {
const promise = new TPromise<number>((c, e) => {
const rl = readline.createInterface({
input: process.stdin,
@@ -55,4 +56,12 @@ export class ChoiceCliService implements IChoiceService {
}
return -1;
}
confirm(confirmation: IConfirmation): TPromise<IConfirmationResult> {
return this.show(Severity.Info, confirmation.message, [confirmation.primaryButton, confirmation.secondaryButton || localize('cancel', "Cancel")]).then(index => {
return {
confirmed: index === 0
} as IConfirmationResult;
});
}
}

View File

@@ -0,0 +1,287 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
export const ID = 'driverService';
export const IDriver = createDecorator<IDriver>(ID);
// !! Do not remove the following START and END markers, they are parsed by the smoketest build
//*START
export interface IElement {
tagName: string;
className: string;
textContent: string;
attributes: { [name: string]: string; };
children: IElement[];
}
export interface IDriver {
_serviceBrand: any;
getWindowIds(): TPromise<number[]>;
capturePage(windowId: number): TPromise<string>;
reloadWindow(windowId: number): TPromise<void>;
dispatchKeybinding(windowId: number, keybinding: string): TPromise<void>;
click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): TPromise<void>;
doubleClick(windowId: number, selector: string): TPromise<void>;
move(windowId: number, selector: string): TPromise<void>;
setValue(windowId: number, selector: string, text: string): TPromise<void>;
paste(windowId: number, selector: string, text: string): TPromise<void>;
getTitle(windowId: number): TPromise<string>;
isActiveElement(windowId: number, selector: string): TPromise<boolean>;
getElements(windowId: number, selector: string, recursive?: boolean): TPromise<IElement[]>;
typeInEditor(windowId: number, selector: string, text: string): TPromise<void>;
getTerminalBuffer(windowId: number, selector: string): TPromise<string[]>;
}
//*END
export interface IDriverChannel extends IChannel {
call(command: 'getWindowIds'): TPromise<number[]>;
call(command: 'capturePage'): TPromise<string>;
call(command: 'reloadWindow', arg: number): TPromise<void>;
call(command: 'dispatchKeybinding', arg: [number, string]): TPromise<void>;
call(command: 'click', arg: [number, string, number | undefined, number | undefined]): TPromise<void>;
call(command: 'doubleClick', arg: [number, string]): TPromise<void>;
call(command: 'move', arg: [number, string]): TPromise<void>;
call(command: 'setValue', arg: [number, string, string]): TPromise<void>;
call(command: 'paste', arg: [number, string, string]): TPromise<void>;
call(command: 'getTitle', arg: [number]): TPromise<string>;
call(command: 'isActiveElement', arg: [number, string]): TPromise<boolean>;
call(command: 'getElements', arg: [number, string, boolean]): TPromise<IElement[]>;
call(command: 'typeInEditor', arg: [number, string, string]): TPromise<void>;
call(command: 'getTerminalBuffer', arg: [number, string]): TPromise<string[]>;
call(command: string, arg: any): TPromise<any>;
}
export class DriverChannel implements IDriverChannel {
constructor(private driver: IDriver) { }
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'getWindowIds': return this.driver.getWindowIds();
case 'capturePage': return this.driver.capturePage(arg);
case 'reloadWindow': return this.driver.reloadWindow(arg);
case 'dispatchKeybinding': return this.driver.dispatchKeybinding(arg[0], arg[1]);
case 'click': return this.driver.click(arg[0], arg[1], arg[2], arg[3]);
case 'doubleClick': return this.driver.doubleClick(arg[0], arg[1]);
case 'move': return this.driver.move(arg[0], arg[1]);
case 'setValue': return this.driver.setValue(arg[0], arg[1], arg[2]);
case 'paste': return this.driver.paste(arg[0], arg[1], arg[2]);
case 'getTitle': return this.driver.getTitle(arg[0]);
case 'isActiveElement': return this.driver.isActiveElement(arg[0], arg[1]);
case 'getElements': return this.driver.getElements(arg[0], arg[1], arg[2]);
case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1], arg[2]);
case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg[0], arg[1]);
}
return undefined;
}
}
export class DriverChannelClient implements IDriver {
_serviceBrand: any;
constructor(private channel: IDriverChannel) { }
getWindowIds(): TPromise<number[]> {
return this.channel.call('getWindowIds');
}
capturePage(windowId: number): TPromise<string> {
return this.channel.call('capturePage', windowId);
}
reloadWindow(windowId: number): TPromise<void> {
return this.channel.call('reloadWindow', windowId);
}
dispatchKeybinding(windowId: number, keybinding: string): TPromise<void> {
return this.channel.call('dispatchKeybinding', [windowId, keybinding]);
}
click(windowId: number, selector: string, xoffset: number | undefined, yoffset: number | undefined): TPromise<void> {
return this.channel.call('click', [windowId, selector, xoffset, yoffset]);
}
doubleClick(windowId: number, selector: string): TPromise<void> {
return this.channel.call('doubleClick', [windowId, selector]);
}
move(windowId: number, selector: string): TPromise<void> {
return this.channel.call('move', [windowId, selector]);
}
setValue(windowId: number, selector: string, text: string): TPromise<void> {
return this.channel.call('setValue', [windowId, selector, text]);
}
paste(windowId: number, selector: string, text: string): TPromise<void> {
return this.channel.call('paste', [windowId, selector, text]);
}
getTitle(windowId: number): TPromise<string> {
return this.channel.call('getTitle', [windowId]);
}
isActiveElement(windowId: number, selector: string): TPromise<boolean> {
return this.channel.call('isActiveElement', [windowId, selector]);
}
getElements(windowId: number, selector: string, recursive: boolean): TPromise<IElement[]> {
return this.channel.call('getElements', [windowId, selector, recursive]);
}
typeInEditor(windowId: number, selector: string, text: string): TPromise<void> {
return this.channel.call('typeInEditor', [windowId, selector, text]);
}
getTerminalBuffer(windowId: number, selector: string): TPromise<string[]> {
return this.channel.call('getTerminalBuffer', [windowId, selector]);
}
}
export interface IWindowDriverRegistry {
registerWindowDriver(windowId: number): TPromise<void>;
reloadWindowDriver(windowId: number): TPromise<void>;
}
export interface IWindowDriverRegistryChannel extends IChannel {
call(command: 'registerWindowDriver', arg: number): TPromise<void>;
call(command: 'reloadWindowDriver', arg: number): TPromise<void>;
call(command: string, arg: any): TPromise<any>;
}
export class WindowDriverRegistryChannel implements IWindowDriverRegistryChannel {
constructor(private registry: IWindowDriverRegistry) { }
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'registerWindowDriver': return this.registry.registerWindowDriver(arg);
case 'reloadWindowDriver': return this.registry.reloadWindowDriver(arg);
}
return undefined;
}
}
export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry {
_serviceBrand: any;
constructor(private channel: IWindowDriverRegistryChannel) { }
registerWindowDriver(windowId: number): TPromise<void> {
return this.channel.call('registerWindowDriver', windowId);
}
reloadWindowDriver(windowId: number): TPromise<void> {
return this.channel.call('reloadWindowDriver', windowId);
}
}
export interface IWindowDriver {
click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): TPromise<void>;
doubleClick(selector: string): TPromise<void>;
move(selector: string): TPromise<void>;
setValue(selector: string, text: string): TPromise<void>;
paste(selector: string, text: string): TPromise<void>;
getTitle(): TPromise<string>;
isActiveElement(selector: string): TPromise<boolean>;
getElements(selector: string, recursive: boolean): TPromise<IElement[]>;
typeInEditor(selector: string, text: string): TPromise<void>;
getTerminalBuffer(selector: string): TPromise<string[]>;
}
export interface IWindowDriverChannel extends IChannel {
call(command: 'click', arg: [string, number | undefined, number | undefined]): TPromise<void>;
call(command: 'doubleClick', arg: string): TPromise<void>;
call(command: 'move', arg: string): TPromise<void>;
call(command: 'setValue', arg: [string, string]): TPromise<void>;
call(command: 'paste', arg: [string, string]): TPromise<void>;
call(command: 'getTitle'): TPromise<string>;
call(command: 'isActiveElement', arg: string): TPromise<boolean>;
call(command: 'getElements', arg: [string, boolean]): TPromise<IElement[]>;
call(command: 'typeInEditor', arg: [string, string]): TPromise<void>;
call(command: 'getTerminalBuffer', arg: string): TPromise<string[]>;
call(command: string, arg: any): TPromise<any>;
}
export class WindowDriverChannel implements IWindowDriverChannel {
constructor(private driver: IWindowDriver) { }
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'click': return this.driver.click(arg[0], arg[1], arg[2]);
case 'doubleClick': return this.driver.doubleClick(arg);
case 'move': return this.driver.move(arg);
case 'setValue': return this.driver.setValue(arg[0], arg[1]);
case 'paste': return this.driver.paste(arg[0], arg[1]);
case 'getTitle': return this.driver.getTitle();
case 'isActiveElement': return this.driver.isActiveElement(arg);
case 'getElements': return this.driver.getElements(arg[0], arg[1]);
case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1]);
case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg);
}
return undefined;
}
}
export class WindowDriverChannelClient implements IWindowDriver {
_serviceBrand: any;
constructor(private channel: IWindowDriverChannel) { }
click(selector: string, xoffset?: number, yoffset?: number): TPromise<void> {
return this.channel.call('click', [selector, xoffset, yoffset]);
}
doubleClick(selector: string): TPromise<void> {
return this.channel.call('doubleClick', selector);
}
move(selector: string): TPromise<void> {
return this.channel.call('move', selector);
}
setValue(selector: string, text: string): TPromise<void> {
return this.channel.call('setValue', [selector, text]);
}
paste(selector: string, text: string): TPromise<void> {
return this.channel.call('paste', [selector, text]);
}
getTitle(): TPromise<string> {
return this.channel.call('getTitle');
}
isActiveElement(selector: string): TPromise<boolean> {
return this.channel.call('isActiveElement', selector);
}
getElements(selector: string, recursive: boolean): TPromise<IElement[]> {
return this.channel.call('getElements', [selector, recursive]);
}
typeInEditor(selector: string, text: string): TPromise<void> {
return this.channel.call('typeInEditor', [selector, text]);
}
getTerminalBuffer(selector: string): TPromise<string[]> {
return this.channel.call('getTerminalBuffer', selector);
}
}

View File

@@ -0,0 +1,204 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/common/driver';
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom';
import * as electron from 'electron';
function serializeElement(element: Element, recursive: boolean): IElement {
const attributes = Object.create(null);
for (let j = 0; j < element.attributes.length; j++) {
const attr = element.attributes.item(j);
attributes[attr.name] = attr.value;
}
const children = [];
if (recursive) {
for (let i = 0; i < element.children.length; i++) {
children.push(serializeElement(element.children.item(i), true));
}
}
return {
tagName: element.tagName,
className: element.className,
textContent: element.textContent || '',
attributes,
children
};
}
class WindowDriver implements IWindowDriver {
constructor() { }
async click(selector: string, xoffset?: number, yoffset?: number): TPromise<void> {
return this._click(selector, 1, xoffset, yoffset);
}
doubleClick(selector: string): TPromise<void> {
return this._click(selector, 2);
}
private async _getElementXY(selector: string, xoffset?: number, yoffset?: number): TPromise<{ x: number; y: number; }> {
const element = document.querySelector(selector);
if (!element) {
throw new Error('Element not found');
}
const { left, top } = getTopLeftOffset(element as HTMLElement);
const { width, height } = getClientArea(element as HTMLElement);
let x: number, y: number;
if ((typeof xoffset === 'number') || (typeof yoffset === 'number')) {
x = left + xoffset;
y = top + yoffset;
} else {
x = left + (width / 2);
y = top + (height / 2);
}
x = Math.round(x);
y = Math.round(y);
return { x, y };
}
private async _click(selector: string, clickCount: number, xoffset?: number, yoffset?: number): TPromise<void> {
const { x, y } = await this._getElementXY(selector, xoffset, yoffset);
const webContents = electron.remote.getCurrentWebContents();
webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any);
webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any);
await TPromise.timeout(100);
}
async move(selector: string): TPromise<void> {
const { x, y } = await this._getElementXY(selector);
const webContents = electron.remote.getCurrentWebContents();
webContents.sendInputEvent({ type: 'mouseMove', x, y } as any);
await TPromise.timeout(100);
}
async setValue(selector: string, text: string): TPromise<void> {
const element = document.querySelector(selector);
if (!element) {
throw new Error('Element not found');
}
const inputElement = element as HTMLInputElement;
inputElement.value = text;
const event = new Event('input', { bubbles: true, cancelable: true });
inputElement.dispatchEvent(event);
}
async paste(selector: string, text: string): TPromise<void> {
const element = document.querySelector(selector);
if (!element) {
throw new Error('Element not found');
}
const inputElement = element as HTMLInputElement;
const clipboardData = new DataTransfer();
clipboardData.setData('text/plain', text);
const event = new ClipboardEvent('paste', { clipboardData } as any);
inputElement.dispatchEvent(event);
}
async getTitle(): TPromise<string> {
return document.title;
}
async isActiveElement(selector: string): TPromise<boolean> {
const element = document.querySelector(selector);
return element === document.activeElement;
}
async getElements(selector: string, recursive: boolean): TPromise<IElement[]> {
const query = document.querySelectorAll(selector);
const result: IElement[] = [];
for (let i = 0; i < query.length; i++) {
const element = query.item(i);
result.push(serializeElement(element, recursive));
}
return result;
}
async typeInEditor(selector: string, text: string): TPromise<void> {
const element = document.querySelector(selector);
if (!element) {
throw new Error('Editor not found: ' + selector);
}
const textarea = element as HTMLTextAreaElement;
const start = textarea.selectionStart;
const newStart = start + text.length;
const value = textarea.value;
const newValue = value.substr(0, start) + text + value.substr(start);
textarea.value = newValue;
textarea.setSelectionRange(newStart, newStart);
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
textarea.dispatchEvent(event);
}
async getTerminalBuffer(selector: string): TPromise<string[]> {
const element = document.querySelector(selector);
if (!element) {
throw new Error('Terminal not found: ' + selector);
}
const xterm = (element as any).xterm;
if (!xterm) {
throw new Error('Xterm not found: ' + selector);
}
const lines: string[] = [];
for (let i = 0; i < xterm.buffer.lines.length; i++) {
lines.push(xterm.buffer.translateBufferLineToString(i, true));
}
return lines;
}
}
export async function registerWindowDriver(
client: IPCClient,
windowId: number,
instantiationService: IInstantiationService
): TPromise<IDisposable> {
const windowDriver = instantiationService.createInstance(WindowDriver);
const windowDriverChannel = new WindowDriverChannel(windowDriver);
client.registerChannel('windowDriver', windowDriverChannel);
const windowDriverRegistryChannel = client.getChannel('windowDriverRegistry');
const windowDriverRegistry = new WindowDriverRegistryChannelClient(windowDriverRegistryChannel);
await windowDriverRegistry.registerWindowDriver(windowId);
const disposable = toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowId));
return combinedDisposable([disposable, client]);
}

View File

@@ -0,0 +1,218 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDriver, DriverChannel, IElement, IWindowDriverChannel, WindowDriverChannelClient, IWindowDriverRegistry, WindowDriverRegistryChannel, IWindowDriver } from 'vs/platform/driver/common/driver';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net';
import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IPCServer, IClientRouter } from 'vs/base/parts/ipc/common/ipc';
import { SimpleKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { OS } from 'vs/base/common/platform';
import { Emitter, toPromise } from 'vs/base/common/event';
// TODO@joao: bad layering!
import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO';
import { ScanCodeBinding } from 'vs/workbench/services/keybinding/common/scanCode';
import { NativeImage } from 'electron';
class WindowRouter implements IClientRouter {
constructor(private windowId: number) { }
route(command: string, arg: any): string {
return `window:${this.windowId}`;
}
}
function isSilentKeyCode(keyCode: KeyCode) {
return keyCode < KeyCode.KEY_0;
}
export class Driver implements IDriver, IWindowDriverRegistry {
_serviceBrand: any;
private registeredWindowIds = new Set<number>();
private reloadingWindowIds = new Set<number>();
private onDidReloadingChange = new Emitter<void>();
constructor(
private windowServer: IPCServer,
@IWindowsMainService private windowsService: IWindowsMainService
) { }
async registerWindowDriver(windowId: number): TPromise<void> {
this.registeredWindowIds.add(windowId);
this.reloadingWindowIds.delete(windowId);
this.onDidReloadingChange.fire();
}
async reloadWindowDriver(windowId: number): TPromise<void> {
this.reloadingWindowIds.add(windowId);
}
async getWindowIds(): TPromise<number[]> {
return this.windowsService.getWindows()
.map(w => w.id)
.filter(id => this.registeredWindowIds.has(id) && !this.reloadingWindowIds.has(id));
}
async capturePage(windowId: number): TPromise<string> {
await this.whenUnfrozen(windowId);
const window = this.windowsService.getWindowById(windowId);
const webContents = window.win.webContents;
const image = await new Promise<NativeImage>(c => webContents.capturePage(c));
const buffer = image.toPNG();
return buffer.toString('base64');
}
async reloadWindow(windowId: number): TPromise<void> {
await this.whenUnfrozen(windowId);
const window = this.windowsService.getWindowById(windowId);
this.reloadingWindowIds.add(windowId);
this.windowsService.reload(window);
}
async dispatchKeybinding(windowId: number, keybinding: string): TPromise<void> {
await this.whenUnfrozen(windowId);
const [first, second] = KeybindingIO._readUserBinding(keybinding);
await this._dispatchKeybinding(windowId, first);
if (second) {
await this._dispatchKeybinding(windowId, second);
}
}
private async _dispatchKeybinding(windowId: number, keybinding: SimpleKeybinding | ScanCodeBinding): TPromise<void> {
if (keybinding instanceof ScanCodeBinding) {
throw new Error('ScanCodeBindings not supported');
}
const window = this.windowsService.getWindowById(windowId);
const webContents = window.win.webContents;
const noModifiedKeybinding = new SimpleKeybinding(false, false, false, false, keybinding.keyCode);
const resolvedKeybinding = new USLayoutResolvedKeybinding(noModifiedKeybinding, OS);
const keyCode = resolvedKeybinding.getElectronAccelerator();
const modifiers = [];
if (keybinding.ctrlKey) {
modifiers.push('ctrl');
}
if (keybinding.metaKey) {
modifiers.push('meta');
}
if (keybinding.shiftKey) {
modifiers.push('shift');
}
if (keybinding.altKey) {
modifiers.push('alt');
}
webContents.sendInputEvent({ type: 'keyDown', keyCode, modifiers } as any);
if (!isSilentKeyCode(keybinding.keyCode)) {
webContents.sendInputEvent({ type: 'char', keyCode, modifiers } as any);
}
webContents.sendInputEvent({ type: 'keyUp', keyCode, modifiers } as any);
await TPromise.timeout(100);
}
async click(windowId: number, selector: string, xoffset?: number, yoffset?: number): TPromise<void> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.click(selector, xoffset, yoffset);
}
async doubleClick(windowId: number, selector: string): TPromise<void> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.doubleClick(selector);
}
async move(windowId: number, selector: string): TPromise<void> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.move(selector);
}
async setValue(windowId: number, selector: string, text: string): TPromise<void> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.setValue(selector, text);
}
async paste(windowId: number, selector: string, text: string): TPromise<void> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.paste(selector, text);
}
async getTitle(windowId: number): TPromise<string> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.getTitle();
}
async isActiveElement(windowId: number, selector: string): TPromise<boolean> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.isActiveElement(selector);
}
async getElements(windowId: number, selector: string, recursive: boolean): TPromise<IElement[]> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.getElements(selector, recursive);
}
async typeInEditor(windowId: number, selector: string, text: string): TPromise<void> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.typeInEditor(selector, text);
}
async getTerminalBuffer(windowId: number, selector: string): TPromise<string[]> {
const windowDriver = await this.getWindowDriver(windowId);
return windowDriver.getTerminalBuffer(selector);
}
private async getWindowDriver(windowId: number): TPromise<IWindowDriver> {
await this.whenUnfrozen(windowId);
const router = new WindowRouter(windowId);
const windowDriverChannel = this.windowServer.getChannel<IWindowDriverChannel>('windowDriver', router);
return new WindowDriverChannelClient(windowDriverChannel);
}
private async whenUnfrozen(windowId: number): TPromise<void> {
while (this.reloadingWindowIds.has(windowId)) {
await toPromise(this.onDidReloadingChange.event);
}
}
}
export async function serve(
windowServer: IPCServer,
handle: string,
instantiationService: IInstantiationService
): TPromise<IDisposable> {
const driver = instantiationService.createInstance(Driver, windowServer);
const windowDriverRegistryChannel = new WindowDriverRegistryChannel(driver);
windowServer.registerChannel('windowDriverRegistry', windowDriverRegistryChannel);
const server = await serveNet(handle);
const channel = new DriverChannel(driver);
server.registerChannel('driver', channel);
return combinedDisposable([server, windowServer]);
}

View File

@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDriver, DriverChannelClient } from 'vs/platform/driver/common/driver';
import { connect as connectNet, Client } from 'vs/base/parts/ipc/node/ipc.net';
export async function connect(handle: string): TPromise<{ client: Client, driver: IDriver }> {
const client = await connectNet(handle, 'driverClient');
const channel = client.getChannel('driver');
const driver = new DriverChannelClient(channel);
return { client, driver };
}

View File

@@ -7,7 +7,7 @@
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
export const IEditorService = createDecorator<IEditorService>('editorService');
@@ -259,28 +259,28 @@ export interface IEditorOptions {
* Tells the editor to not receive keyboard focus when the editor is being opened. By default,
* the editor will receive keyboard focus on open.
*/
preserveFocus?: boolean;
readonly preserveFocus?: boolean;
/**
* Tells the editor to replace the editor input in the editor even if it is identical to the one
* already showing. By default, the editor will not replace the input if it is identical to the
* one showing.
*/
forceOpen?: boolean;
readonly forceOpen?: boolean;
/**
* Will reveal the editor if it is already opened and visible in any of the opened editor groups. Note
* that this option is just a hint that might be ignored if the user wants to open an editor explicitly
* to the side of another one.
*/
revealIfVisible?: boolean;
readonly revealIfVisible?: boolean;
/**
* Will reveal the editor if it is already opened (even when not visible) in any of the opened editor groups. Note
* that this option is just a hint that might be ignored if the user wants to open an editor explicitly
* to the side of another one.
*/
revealIfOpened?: boolean;
readonly revealIfOpened?: boolean;
/**
* An editor that is pinned remains in the editor stack even when another editor is being opened.
@@ -291,13 +291,13 @@ export interface IEditorOptions {
/**
* The index in the document stack where to insert the editor into when opening.
*/
index?: number;
readonly index?: number;
/**
* An active editor that is opened will show its contents directly. Set to true to open an editor
* in the background.
*/
inactive?: boolean;
readonly inactive?: boolean;
}
export interface ITextEditorSelection {

View File

@@ -57,6 +57,7 @@ export interface ParsedArgs {
'file-write'?: boolean;
'file-chmod'?: boolean;
'upload-logs'?: string;
'driver'?: string;
}
export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');
@@ -130,4 +131,6 @@ export interface IEnvironmentService {
installSourcePath: string;
disableUpdates: boolean;
disableCrashReporter: boolean;
driverHandle: string;
}

View File

@@ -29,7 +29,8 @@ const options: minimist.Opts = {
'enable-proposed-api',
'export-default-configuration',
'install-source',
'upload-logs'
'upload-logs',
'driver'
],
boolean: [
'help',
@@ -153,7 +154,7 @@ const extensionsHelp: { [name: string]: string; } = {
'--show-versions': localize('showVersions', "Show versions of installed extensions, when using --list-extension."),
'--install-extension (<extension-id> | <extension-vsix-path>)': localize('installExtension', "Installs an extension."),
'--uninstall-extension (<extension-id> | <extension-vsix-path>)': localize('uninstallExtension', "Uninstalls an extension."),
'--enable-proposed-api <extension-id>': localize('experimentalApis', "Enables proposed api features for an extension.")
'--enable-proposed-api <extension-id>': localize('experimentalApis', "Enables proposed API features for an extension.")
};
const troubleshootingHelp: { [name: string]: string; } = {
@@ -163,8 +164,8 @@ const troubleshootingHelp: { [name: string]: string; } = {
'-p, --performance': localize('performance', "Start with the 'Developer: Startup Performance' command enabled."),
'--prof-startup': localize('prof-startup', "Run CPU profiler during startup"),
'--disable-extensions': localize('disableExtensions', "Disable all installed extensions."),
'--inspect-extensions': localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection uri."),
'--inspect-brk-extensions': localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection uri."),
'--inspect-extensions': localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI."),
'--inspect-brk-extensions': localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI."),
'--disable-gpu': localize('disableGPU', "Disable GPU hardware acceleration."),
'--upload-logs': localize('uploadLogs', "Uploads logs from current session to a secure endpoint."),
'--max-memory': localize('maxMemory', "Max memory size for a window (in Mbytes).")

View File

@@ -170,6 +170,8 @@ export class EnvironmentService implements IEnvironmentService {
get disableUpdates(): boolean { return !!this._args['disable-updates']; }
get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; }
get driverHandle(): string { return this._args['driver']; }
constructor(private _args: ParsedArgs, private _execPath: string) {
if (!process.env['VSCODE_LOGS']) {
const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '');

View File

@@ -5,11 +5,10 @@
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { distinct, coalesce } from 'vs/base/common/arrays';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, getIdFromLocalExtensionId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getIdFromLocalExtensionId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -24,7 +23,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
private disposables: IDisposable[] = [];
private _onEnablementChanged = new Emitter<IExtensionIdentifier>();
public onEnablementChanged: Event<IExtensionIdentifier> = this._onEnablementChanged.event;
public readonly onEnablementChanged: Event<IExtensionIdentifier> = this._onEnablementChanged.event;
constructor(
@IStorageService private storageService: IStorageService,
@@ -58,10 +57,11 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return TPromise.as(result);
}
getEnablementState(identifier: IExtensionIdentifier): EnablementState {
if (this.environmentService.disableExtensions) {
getEnablementState(extension: ILocalExtension): EnablementState {
if (this.environmentService.disableExtensions && extension.type === LocalExtensionType.User) {
return EnablementState.Disabled;
}
const identifier = this._getIdentifier(extension);
if (this.hasWorkspace) {
if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.WorkspaceEnabled;
@@ -78,18 +78,24 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
}
canChangeEnablement(extension: ILocalExtension): boolean {
return !this.environmentService.disableExtensions && !(extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length);
if (extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) {
return false;
}
if (extension.type === LocalExtensionType.User && this.environmentService.disableExtensions) {
return false;
}
return true;
}
setEnablement(arg: ILocalExtension | IExtensionIdentifier, newState: EnablementState): TPromise<boolean> {
let identifier;
let identifier: IExtensionIdentifier;
if (isIExtensionIdentifier(arg)) {
identifier = arg;
} else {
if (!this.canChangeEnablement(arg)) {
return TPromise.wrap(false);
}
identifier = { id: getGalleryExtensionIdFromLocal(arg), uuid: arg.identifier.uuid };
identifier = this._getIdentifier(arg);
}
const workspace = newState === EnablementState.WorkspaceDisabled || newState === EnablementState.WorkspaceEnabled;
@@ -97,7 +103,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return TPromise.wrapError<boolean>(new Error(localize('noWorkspace', "No workspace.")));
}
const currentState = this.getEnablementState(identifier);
const currentState = this._getEnablementState(identifier);
if (currentState === newState) {
return TPromise.as(false);
@@ -123,16 +129,29 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return TPromise.as(true);
}
isEnabled(identifier: IExtensionIdentifier): boolean {
const enablementState = this.getEnablementState(identifier);
isEnabled(extension: ILocalExtension): boolean {
const enablementState = this.getEnablementState(extension);
return enablementState === EnablementState.WorkspaceEnabled || enablementState === EnablementState.Enabled;
}
migrateToIdentifiers(installed: IExtensionIdentifier[]): void {
this._migrateDisabledExtensions(installed, StorageScope.GLOBAL);
private _getEnablementState(identifier: IExtensionIdentifier): EnablementState {
if (this.hasWorkspace) {
this._migrateDisabledExtensions(installed, StorageScope.WORKSPACE);
if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.WorkspaceEnabled;
}
if (this._getDisabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.WorkspaceDisabled;
}
}
if (this._getDisabledExtensions(StorageScope.GLOBAL).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.Disabled;
}
return EnablementState.Enabled;
}
private _getIdentifier(extension: ILocalExtension): IExtensionIdentifier {
return { id: getGalleryExtensionIdFromLocal(extension), uuid: extension.identifier.uuid };
}
private _enableExtension(identifier: IExtensionIdentifier): void {
@@ -227,8 +246,8 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
}
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions, scope, extension, fireEvent);
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier): void {
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions, scope, extension);
}
private _getExtensions(storageId: string, scope: StorageScope): IExtensionIdentifier[] {
@@ -239,30 +258,12 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return value ? JSON.parse(value) : [];
}
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier): void {
if (extensions.length) {
this.storageService.store(storageId, JSON.stringify(extensions.map(({ id, uuid }) => (<IExtensionIdentifier>{ id, uuid }))), scope);
} else {
this.storageService.remove(storageId, scope);
}
if (fireEvent) {
this._onEnablementChanged.fire(extension);
}
}
private _migrateDisabledExtensions(installedExtensions: IExtensionIdentifier[], scope: StorageScope): void {
const oldValue = this.storageService.get('extensions/disabled', scope, '');
if (oldValue) {
const extensionIdentifiers = coalesce(distinct(oldValue.split(',')).map(id => {
id = adoptToGalleryExtensionId(id);
const matched = installedExtensions.filter(installed => areSameExtensions({ id }, { id: installed.id }))[0];
return matched ? { id: matched.id, uuid: matched.uuid } : null;
}));
if (extensionIdentifiers.length) {
this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, JSON.stringify(extensionIdentifiers), scope);
}
}
this.storageService.remove('extensions/disabled', scope);
}
private _onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {

View File

@@ -7,7 +7,7 @@
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IPager } from 'vs/base/common/paging';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILocalization } from 'vs/platform/localizations/common/localizations';
@@ -118,6 +118,12 @@ export interface IExtensionManifest {
activationEvents?: string[];
extensionDependencies?: string[];
contributes?: IExtensionContributions;
repository?: {
url: string;
};
bugs?: {
url: string;
};
}
export interface IGalleryExtensionProperties {
@@ -149,6 +155,12 @@ export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifie
&& (!thing.uuid || typeof thing.uuid === 'string');
}
/* __GDPR__FRAGMENT__
"ExtensionIdentifier" : {
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"uuid": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
export interface IExtensionIdentifier {
id: string;
uuid?: string;
@@ -274,10 +286,10 @@ export interface IExtensionManagementService {
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
install(zipPath: string): TPromise<void>;
installFromGallery(extension: IGalleryExtension): TPromise<void>;
install(zipPath: string): TPromise<ILocalExtension>;
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension>;
uninstall(extension: ILocalExtension, force?: boolean): TPromise<void>;
reinstall(extension: ILocalExtension): TPromise<void>;
reinstallFromGallery(extension: ILocalExtension): TPromise<ILocalExtension>;
getInstalled(type?: LocalExtensionType): TPromise<ILocalExtension[]>;
getExtensionsReport(): TPromise<IReportedExtension[]>;
@@ -311,7 +323,7 @@ export interface IExtensionEnablementService {
/**
* Returns the enablement state for the given extension
*/
getEnablementState(identifier: IExtensionIdentifier): EnablementState;
getEnablementState(extension: ILocalExtension): EnablementState;
/**
* Returns `true` if the enablement can be changed.
@@ -321,7 +333,7 @@ export interface IExtensionEnablementService {
/**
* Returns `true` if the given extension identifier is enabled.
*/
isEnabled(identifier: IExtensionIdentifier): boolean;
isEnabled(extension: ILocalExtension): boolean;
/**
* Enable or disable the given extension.
@@ -333,12 +345,6 @@ export interface IExtensionEnablementService {
* Throws error if enablement is requested for workspace and there is no workspace
*/
setEnablement(extension: ILocalExtension, state: EnablementState): TPromise<boolean>;
/**
* TODO: @Sandy. Use setEnablement(extension: ILocalExtension, state: EnablementState): TPromise<boolean>. Use one model for extension management and runtime
*/
setEnablement(identifier: IExtensionIdentifier, state: EnablementState): TPromise<boolean>;
migrateToIdentifiers(installed: IExtensionIdentifier[]): void;
}
export const IExtensionTipsService = createDecorator<IExtensionTipsService>('extensionTipsService');

View File

@@ -8,16 +8,17 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from './extensionManagement';
import Event, { buffer } from 'vs/base/common/event';
import { Event, buffer } from 'vs/base/common/event';
export interface IExtensionManagementChannel extends IChannel {
call(command: 'event:onInstallExtension'): TPromise<void>;
call(command: 'event:onDidInstallExtension'): TPromise<void>;
call(command: 'event:onUninstallExtension'): TPromise<void>;
call(command: 'event:onDidUninstallExtension'): TPromise<void>;
call(command: 'install', path: string): TPromise<void>;
call(command: 'installFromGallery', extension: IGalleryExtension): TPromise<void>;
call(command: 'install', path: string): TPromise<ILocalExtension>;
call(command: 'installFromGallery', extension: IGalleryExtension): TPromise<ILocalExtension>;
call(command: 'uninstall', args: [ILocalExtension, boolean]): TPromise<void>;
call(command: 'reinstallFromGallery', args: [ILocalExtension]): TPromise<ILocalExtension>;
call(command: 'getInstalled'): TPromise<ILocalExtension[]>;
call(command: 'getExtensionsReport'): TPromise<IReportedExtension[]>;
call(command: string, arg?: any): TPromise<any>;
@@ -46,7 +47,7 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel {
case 'install': return this.service.install(arg);
case 'installFromGallery': return this.service.installFromGallery(arg[0]);
case 'uninstall': return this.service.uninstall(arg[0], arg[1]);
case 'reinstall': return this.service.reinstall(arg[0]);
case 'reinstallFromGallery': return this.service.reinstallFromGallery(arg[0]);
case 'getInstalled': return this.service.getInstalled(arg);
case 'updateMetadata': return this.service.updateMetadata(arg[0], arg[1]);
case 'getExtensionsReport': return this.service.getExtensionsReport();
@@ -73,11 +74,11 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
private _onDidUninstallExtension = eventFromCall<DidUninstallExtensionEvent>(this.channel, 'event:onDidUninstallExtension');
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this._onDidUninstallExtension; }
install(zipPath: string): TPromise<void> {
install(zipPath: string): TPromise<ILocalExtension> {
return this.channel.call('install', zipPath);
}
installFromGallery(extension: IGalleryExtension): TPromise<void> {
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension> {
return this.channel.call('installFromGallery', [extension]);
}
@@ -85,8 +86,8 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
return this.channel.call('uninstall', [extension, force]);
}
reinstall(extension: ILocalExtension): TPromise<void> {
return this.channel.call('reinstall', [extension]);
reinstallFromGallery(extension: ILocalExtension): TPromise<ILocalExtension> {
return this.channel.call('reinstallFromGallery', [extension]);
}
getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {

View File

@@ -82,10 +82,10 @@ export function getLocalExtensionTelemetryData(extension: ILocalExtension): any
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"name": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"galleryId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"publisherId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"publisherName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"publisherDisplayName": { "classification": "PublicPersonalData", "purpose": "FeatureInsight" },
"dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"publisherId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"publisherName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"${include}": [
"${GalleryExtensionTelemetryData2}"
]

View File

@@ -18,7 +18,7 @@ import { IPager } from 'vs/base/common/paging';
import { IRequestOptions, IRequestContext, download, asJson, asText } from 'vs/base/node/request';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import { isVersionValid } from 'vs/platform/extensions/node/extensionValidator';
import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { readFile } from 'vs/base/node/pfs';
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
@@ -324,7 +324,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
},
/* __GDPR__FRAGMENT__
"GalleryExtensionTelemetryData2" : {
"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"searchText": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"querySource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
@@ -586,7 +586,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
const startTime = new Date().getTime();
/* __GDPR__
"galleryService:downloadVSIX" : {
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
@@ -622,7 +622,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
loadCompatibleVersion(extension: IGalleryExtension): TPromise<IGalleryExtension> {
if (extension.properties.engine && this.isEngineValid(extension.properties.engine)) {
if (extension.properties.engine && isEngineValid(extension.properties.engine)) {
return TPromise.wrap(extension);
}
@@ -738,7 +738,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
/* __GDPR__
"galleryService:requestError" : {
"url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
}
*/
@@ -786,7 +786,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
if (!engine) {
return null;
}
if (this.isEngineValid(engine)) {
if (isEngineValid(engine)) {
return TPromise.wrap(version);
}
}
@@ -807,7 +807,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.then(manifest => {
const engine = manifest.engines.vscode;
if (!this.isEngineValid(engine)) {
if (!isEngineValid(engine)) {
return this.getLastValidExtensionVersionReccursively(extension, versions.slice(1));
}
@@ -817,11 +817,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
private isEngineValid(engine: string): boolean {
// TODO@joao: discuss with alex '*' doesn't seem to be a valid engine version
return engine === '*' || isVersionValid(pkg.version, engine);
}
private static hasExtensionByName(extensions: IGalleryExtension[], name: string): boolean {
for (const extension of extensions) {
if (`${extension.publisher}.${extension.name}` === name) {

View File

@@ -5,14 +5,14 @@
'use strict';
import nls = require('vs/nls');
import * as nls from 'vs/nls';
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
import * as errors from 'vs/base/common/errors';
import { assign } from 'vs/base/common/objects';
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { flatten, distinct, coalesce } from 'vs/base/common/arrays';
import { extract, buffer } from 'vs/base/node/zip';
import { flatten, distinct } from 'vs/base/common/arrays';
import { extract, buffer, ExtractError } from 'vs/base/node/zip';
import { TPromise } from 'vs/base/common/winjs.base';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
@@ -22,21 +22,23 @@ import {
IExtensionIdentifier,
IReportedExtension
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { localizeManifest } from '../common/extensionNls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Limiter } from 'vs/base/common/async';
import Event, { Emitter } from 'vs/base/common/event';
import { Limiter, always } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import * as semver from 'semver';
import URI from 'vs/base/common/uri';
import pkg from 'vs/platform/node/package';
import { isMacintosh } from 'vs/base/common/platform';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator';
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
@@ -48,10 +50,9 @@ const INSTALL_ERROR_VALIDATING = 'validating';
const INSTALL_ERROR_GALLERY = 'gallery';
const INSTALL_ERROR_LOCAL = 'local';
const INSTALL_ERROR_EXTRACTING = 'extracting';
const INSTALL_ERROR_RENAMING = 'renaming';
const INSTALL_ERROR_DELETING = 'deleting';
const INSTALL_ERROR_READING_EXTENSION_FROM_DISK = 'readingExtension';
const INSTALL_ERROR_SAVING_METADATA = 'savingMetadata';
const INSTALL_ERROR_UNKNOWN = 'unknown';
const ERROR_UNKNOWN = 'unknown';
export class ExtensionManagementError extends Error {
constructor(message: string, readonly code: string) {
@@ -101,6 +102,11 @@ interface InstallableExtension {
metadata?: IGalleryMetadata;
}
enum Operation {
Install = 1,
Update
}
export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
_serviceBrand: any;
@@ -110,6 +116,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
private uninstalledFileLimiter: Limiter<void>;
private reportedExtensions: TPromise<IReportedExtension[]> | undefined;
private lastReportTimestamp = 0;
private readonly installationStartTime: Map<string, number> = new Map<string, number>();
private readonly installingExtensions: Map<string, TPromise<ILocalExtension>> = new Map<string, TPromise<ILocalExtension>>();
private readonly manifestCache: ExtensionsManifestCache;
private readonly extensionLifecycle: ExtensionsLifecycle;
@@ -128,9 +135,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IChoiceService private choiceService: IChoiceService,
@IDialogService private dialogService: IDialogService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@ILogService private logService: ILogService
@ILogService private logService: ILogService,
@ITelemetryService private telemetryService: ITelemetryService,
) {
super();
this.extensionsPath = environmentService.extensionsPath;
@@ -141,13 +149,17 @@ export class ExtensionManagementService extends Disposable implements IExtension
this.extensionLifecycle = this._register(new ExtensionsLifecycle(this.logService));
}
install(zipPath: string): TPromise<void> {
install(zipPath: string): TPromise<ILocalExtension> {
zipPath = path.resolve(zipPath);
return validateLocalExtension(zipPath)
.then(manifest => {
const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
return this.unsetUninstalledAndRemove(identifier.id)
// {{SQL CARBON EDIT - Remove VS Code version check}}
// if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) {
// return TPromise.wrapError<ILocalExtension>(new Error(nls.localize('incompatible', "Unable to install Extension '{0}' as it is not compatible with Code '{1}'.", identifier.id, pkg.version)));
// }
return this.removeIfExists(identifier.id)
.then(
() => this.checkOutdated(manifest)
.then(validated => {
@@ -175,18 +187,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private unsetUninstalledAndRemove(id: string): TPromise<void> {
return this.isUninstalled(id)
.then(isUninstalled => {
if (isUninstalled) {
this.logService.trace('Removing the extension:', id);
const extensionPath = path.join(this.extensionsPath, id);
return pfs.rimraf(extensionPath)
.then(() => this.unsetUninstalled(id))
.then(() => this.logService.info('Removed the extension:', id));
}
return null;
});
private removeIfExists(id: string): TPromise<void> {
return this.getInstalled(LocalExtensionType.User)
.then(installed => installed.filter(i => i.identifier.id === id)[0])
.then(existing => existing ? this.removeExtension(existing, 'existing') : null);
}
private checkOutdated(manifest: IExtensionManifest): TPromise<boolean> {
@@ -196,11 +200,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0];
if (newer) {
const message = nls.localize('installingOutdatedExtension', "A newer version of this extension is already installed. Would you like to override this with the older version?");
const options = [
const buttons = [
nls.localize('override', "Override"),
nls.localize('cancel', "Cancel")
];
return this.choiceService.choose(Severity.Info, message, options, 1, true)
return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 })
.then<boolean>(value => {
if (value === 0) {
return this.uninstall(newer, true).then(() => true);
@@ -212,58 +216,65 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise<void> {
private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise<ILocalExtension> {
return this.installExtension({ zipPath, id: identifier.id, metadata })
.then(local => {
if (this.galleryService.isEnabled() && local.manifest.extensionDependencies && local.manifest.extensionDependencies.length) {
return this.getDependenciesToInstall(local.manifest.extensionDependencies)
.then(dependenciesToInstall => this.downloadAndInstallExtensions(metadata ? dependenciesToInstall.filter(d => d.identifier.uuid !== metadata.id) : dependenciesToInstall))
.then(() => local, error => {
this.uninstallExtension(local);
this.setUninstalled(local);
return TPromise.wrapError(new Error(nls.localize('errorInstallingDependencies', "Error while installing dependencies. {0}", error instanceof Error ? error.message : error)));
});
}
return local;
})
.then(
local => this._onDidInstallExtension.fire({ identifier, zipPath, local }),
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
local => { this._onDidInstallExtension.fire({ identifier, zipPath, local }); return local; },
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
);
}
installFromGallery(extension: IGalleryExtension): TPromise<void> {
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension> {
this.onInstallExtensions([extension]);
return this.collectExtensionsToInstall(extension)
.then(
extensionsToInstall => {
if (extensionsToInstall.length > 1) {
this.onInstallExtensions(extensionsToInstall.slice(1));
}
return this.downloadAndInstallExtensions(extensionsToInstall)
.then(
locals => this.onDidInstallExtensions(extensionsToInstall, locals, []),
errors => this.onDidInstallExtensions(extensionsToInstall, [], errors));
},
error => this.onDidInstallExtensions([extension], [], [error]));
return this.getInstalled(LocalExtensionType.User)
.then(installed => this.collectExtensionsToInstall(extension)
.then(
extensionsToInstall => {
if (extensionsToInstall.length > 1) {
this.onInstallExtensions(extensionsToInstall.slice(1));
}
const operataions: Operation[] = extensionsToInstall.map(e => this.getOperation(e, installed));
return this.downloadAndInstallExtensions(extensionsToInstall)
.then(
locals => this.onDidInstallExtensions(extensionsToInstall, locals, operataions, [])
.then(() => locals.filter(l => areSameExtensions({ id: getGalleryExtensionIdFromLocal(l), uuid: l.identifier.uuid }, extension.identifier)[0])),
errors => this.onDidInstallExtensions(extensionsToInstall, [], operataions, errors));
},
error => this.onDidInstallExtensions([extension], [], [this.getOperation(extension, installed)], [error])));
}
reinstall(extension: ILocalExtension): TPromise<void> {
reinstallFromGallery(extension: ILocalExtension): TPromise<ILocalExtension> {
if (!this.galleryService.isEnabled()) {
return TPromise.wrapError(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")));
}
return this.findGalleryExtension(extension)
.then(galleryExtension => {
if (galleryExtension) {
return this.uninstallExtension(extension)
return this.setUninstalled(extension)
.then(() => this.removeUninstalledExtension(extension)
.then(
() => this.installFromGallery(galleryExtension),
e => TPromise.wrapError(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))))));
() => this.installFromGallery(galleryExtension),
e => TPromise.wrapError(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))))));
}
return TPromise.wrapError(new Error(nls.localize('Not Market place extension', "Only Market place Extensions can be reinstalled")));
return TPromise.wrapError(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")));
});
}
private getOperation(extensionToInstall: IGalleryExtension, installed: ILocalExtension[]): Operation {
return installed.some(i => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i), uuid: i.identifier.uuid }, extensionToInstall.identifier)) ? Operation.Update : Operation.Install;
}
private collectExtensionsToInstall(extension: IGalleryExtension): TPromise<IGalleryExtension[]> {
return this.galleryService.loadCompatibleVersion(extension)
.then(compatible => {
@@ -273,10 +284,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
return this.getDependenciesToInstall(compatible.properties.dependencies)
.then(
dependenciesToInstall => ([compatible, ...dependenciesToInstall.filter(d => d.identifier.uuid !== compatible.identifier.uuid)]),
error => TPromise.wrapError<IGalleryExtension[]>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
dependenciesToInstall => ([compatible, ...dependenciesToInstall.filter(d => d.identifier.uuid !== compatible.identifier.uuid)]),
error => TPromise.wrapError<IGalleryExtension[]>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
},
error => TPromise.wrapError<IGalleryExtension[]>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
error => TPromise.wrapError<IGalleryExtension[]>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
}
private downloadAndInstallExtensions(extensions: IGalleryExtension[]): TPromise<ILocalExtension[]> {
@@ -298,8 +309,8 @@ export class ExtensionManagementService extends Disposable implements IExtension
.then(extension => this.downloadInstallableExtension(extension))
.then(installableExtension => this.installExtension(installableExtension))
.then(
local => { this.installingExtensions.delete(extension.identifier.id); return local; },
e => { this.installingExtensions.delete(extension.identifier.id); return TPromise.wrapError(e); }
local => { this.installingExtensions.delete(extension.identifier.id); return local; },
e => { this.installingExtensions.delete(extension.identifier.id); return TPromise.wrapError(e); }
);
this.installingExtensions.set(extension.identifier.id, installingExtension);
@@ -316,36 +327,37 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.galleryService.loadCompatibleVersion(extension)
.then(
compatible => {
if (compatible) {
this.logService.trace('Started downloading extension:', extension.name);
return this.galleryService.download(extension)
.then(
zipPath => {
this.logService.info('Downloaded extension:', extension.name);
return validateLocalExtension(zipPath)
.then(
manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
);
},
error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
} else {
return TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the depending extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
}
},
error => TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
compatible => {
if (compatible) {
this.logService.trace('Started downloading extension:', extension.name);
return this.galleryService.download(extension)
.then(
zipPath => {
this.logService.info('Downloaded extension:', extension.name);
return validateLocalExtension(zipPath)
.then(
manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
);
},
error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
} else {
return TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the depending extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
}
},
error => TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
}
private onInstallExtensions(extensions: IGalleryExtension[]): void {
for (const extension of extensions) {
this.logService.info('Installing extension:', extension.name);
this.installationStartTime.set(extension.identifier.id, new Date().getTime());
const id = getLocalExtensionIdFromGallery(extension, extension.version);
this._onInstallExtension.fire({ identifier: { id, uuid: extension.identifier.uuid }, gallery: extension });
}
}
private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], errors: Error[]): TPromise<any> {
private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], operations: Operation[], errors: Error[]): TPromise<any> {
extensions.forEach((gallery, index) => {
const identifier = { id: getLocalExtensionIdFromGallery(gallery, gallery.version), uuid: gallery.identifier.uuid };
const local = locals[index];
@@ -354,10 +366,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
this.logService.info(`Extensions installed successfully:`, gallery.identifier.id);
this._onDidInstallExtension.fire({ identifier, gallery, local });
} else {
const errorCode = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : INSTALL_ERROR_UNKNOWN;
const errorCode = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ERROR_UNKNOWN;
this.logService.error(`Failed to install extension:`, gallery.identifier.id, error ? error.message : errorCode);
this._onDidInstallExtension.fire({ identifier, gallery, error: errorCode });
}
const startTime = this.installationStartTime.get(gallery.identifier.id);
this.reportTelemetry(operations[index] === Operation.Install ? 'extensionGallery:install' : 'extensionGallery:update', getGalleryExtensionTelemetryData(gallery), startTime ? new Date().getTime() - startTime : void 0, error);
this.installationStartTime.delete(gallery.identifier.id);
});
return errors.length ? TPromise.wrapError(this.joinErrors(errors)) : TPromise.as(null);
}
@@ -383,18 +398,18 @@ export class ExtensionManagementService extends Disposable implements IExtension
private installExtension(installableExtension: InstallableExtension): TPromise<ILocalExtension> {
return this.unsetUninstalledAndGetLocal(installableExtension.id)
.then(
local => {
if (local) {
return local;
}
return this.extractAndInstall(installableExtension);
},
e => {
if (isMacintosh) {
return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
}
return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
});
local => {
if (local) {
return local;
}
return this.extractAndInstall(installableExtension);
},
e => {
if (isMacintosh) {
return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
}
return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
});
}
private unsetUninstalledAndGetLocal(id: string): TPromise<ILocalExtension> {
@@ -415,43 +430,53 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private extractAndInstall({ zipPath, id, metadata }: InstallableExtension): TPromise<ILocalExtension> {
const tempPath = path.join(this.extensionsPath, `.${id}`);
const extensionPath = path.join(this.extensionsPath, id);
return pfs.rimraf(extensionPath)
return this.extractAndRename(id, zipPath, tempPath, extensionPath)
.then(() => {
this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionPath}`);
return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true })
.then(
() => {
this.logService.info(`Extracted extension to ${extensionPath}:`, id);
return this.completeInstall(id, extensionPath, metadata);
},
e => TPromise.wrapError(new ExtensionManagementError(e.message, INSTALL_ERROR_EXTRACTING)))
.then(null, e => {
this.logService.info('Deleting the extracted extension', id);
return pfs.rimraf(extensionPath).then(() => TPromise.wrapError(e), () => TPromise.wrapError(e));
});
}, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
this.logService.info('Installation completed.', id);
return this.scanExtension(id, this.extensionsPath, LocalExtensionType.User);
})
.then(local => {
if (metadata) {
local.metadata = metadata;
return this.saveMetadataForLocalExtension(local);
}
return local;
});
}
private completeInstall(id: string, extensionPath: string, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
return TPromise.join([readManifest(extensionPath), pfs.readdir(extensionPath)])
.then(null, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_READING_EXTENSION_FROM_DISK)))
.then(([{ manifest }, children]) => {
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)).toString() : null;
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)).toString() : null;
const type = LocalExtensionType.User;
const identifier = { id, uuid: metadata ? metadata.id : null };
private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string): TPromise<void> {
return this.extract(id, zipPath, extractPath)
.then(() => this.rename(id, extractPath, renamePath, Date.now() + (30 * 1000) /* Retry for 30 seconds */)
.then(
() => this.logService.info('Renamed to', renamePath),
e => {
this.logService.info('Rename failed. Deleting from extracted location', extractPath);
return always(pfs.rimraf(extractPath), () => null).then(() => TPromise.wrapError(e));
}));
}
const local: ILocalExtension = { type, identifier, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
private extract(id: string, zipPath: string, extractPath: string): TPromise<void> {
this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`);
return pfs.rimraf(extractPath)
.then(
() => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService)
.then(
() => this.logService.info(`Extracted extension to ${extractPath}:`, id),
e => always(pfs.rimraf(extractPath), () => null)
.then(() => TPromise.wrapError(new ExtensionManagementError(e.message, e instanceof ExtractError ? e.type : INSTALL_ERROR_EXTRACTING)))),
e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
}
this.logService.trace(`Updating metadata of the extension:`, id);
return this.saveMetadataForLocalExtension(local)
.then(() => {
this.logService.info(`Updated metadata of the extension:`, id);
return local;
}, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_SAVING_METADATA)));
private rename(id: string, extractPath: string, renamePath: string, retryUntil: number): TPromise<void> {
return pfs.rename(extractPath, renamePath)
.then(null, error => {
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`);
return this.rename(id, extractPath, renamePath, retryUntil);
}
return TPromise.wrapError(new ExtensionManagementError(error.message || nls.localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING));
});
}
@@ -459,7 +484,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.getInstalled(LocalExtensionType.User)
.then(installed =>
TPromise.join(installed.filter(local => extensions.some(galleryExtension => local.identifier.id === getLocalExtensionIdFromGallery(galleryExtension, galleryExtension.version))) // Only check id (pub.name-version) because we want to rollback the exact version
.map(local => this.uninstallExtension(local))))
.map(local => this.setUninstalled(local))))
.then(() => null, () => null);
}
@@ -529,10 +554,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.preUninstallExtension(extension)
.then(() => this.hasDependencies(extension, installed) ? this.promptForDependenciesAndUninstall(extension, installed, force) : this.promptAndUninstall(extension, installed, force))
.then(() => this.postUninstallExtension(extension),
error => {
this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL);
return TPromise.wrapError(error);
});
error => {
this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
return TPromise.wrapError(error);
});
}
private hasDependencies(extension: ILocalExtension, installed: ILocalExtension[]): boolean {
@@ -549,13 +574,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
const message = nls.localize('uninstallDependeciesConfirmation', "Would you like to uninstall '{0}' only or its dependencies also?", extension.manifest.displayName || extension.manifest.name);
const options = [
nls.localize('uninstallOnly', "Only"),
nls.localize('uninstallAll', "All"),
const buttons = [
nls.localize('uninstallOnly', "Extension Only"),
nls.localize('uninstallAll', "Uninstall All"),
nls.localize('cancel', "Cancel")
];
this.logService.info('Requesting for confirmation to uninstall extension with dependencies', extension.identifier.id);
return this.choiceService.choose(Severity.Info, message, options, 2, true)
return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 2 })
.then<void>(value => {
if (value === 0) {
return this.uninstallWithDependencies(extension, [], installed);
@@ -575,12 +600,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
const message = nls.localize('uninstallConfirmation', "Are you sure you want to uninstall '{0}'?", extension.manifest.displayName || extension.manifest.name);
const options = [
const buttons = [
nls.localize('ok', "OK"),
nls.localize('cancel', "Cancel")
];
this.logService.info('Requesting for confirmation to uninstall extension', extension.identifier.id);
return this.choiceService.choose(Severity.Info, message, options, 1, true)
return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 })
.then<void>(value => {
if (value === 0) {
return this.uninstallWithDependencies(extension, [], installed);
@@ -649,10 +674,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.preUninstallExtension(extension)
.then(() => this.uninstallExtension(extension))
.then(() => this.postUninstallExtension(extension),
error => {
this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL);
return TPromise.wrapError(error);
});
error => {
this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
return TPromise.wrapError(error);
});
}
private preUninstallExtension(extension: ILocalExtension): TPromise<void> {
@@ -665,12 +690,14 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private uninstallExtension(local: ILocalExtension): TPromise<void> {
return this.setUninstalled(local.identifier.id);
// Set all versions of the extension as uninstalled
return this.scanUserExtensions(false)
.then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions({ id: getGalleryExtensionIdFromLocal(u), uuid: u.identifier.uuid }, { id: getGalleryExtensionIdFromLocal(local), uuid: local.identifier.uuid }))));
}
private async postUninstallExtension(extension: ILocalExtension, error?: string): TPromise<void> {
private async postUninstallExtension(extension: ILocalExtension, error?: Error): TPromise<void> {
if (error) {
this.logService.error('Failed to uninstall extension:', extension.identifier.id, error);
this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
} else {
this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
// only report if extension has a mapped gallery extension. UUID identifies the gallery extension.
@@ -678,7 +705,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall);
}
}
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error });
this.reportTelemetry('extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), void 0, error);
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0;
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: errorcode });
}
getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {
@@ -721,11 +750,14 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanExtensions(root: string, type: LocalExtensionType): TPromise<ILocalExtension[]> {
const limiter = new Limiter(10);
return pfs.readdir(root)
.then(extensionsFolders => TPromise.join(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
.then(extensions => coalesce(extensions));
.then(extensionsFolders => TPromise.join<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
.then(extensions => extensions.filter(e => e && e.identifier));
}
private scanExtension(folderName: string, root: string, type: LocalExtensionType): TPromise<ILocalExtension> {
if (type === LocalExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user exension folder starting with `.`
return TPromise.as(null);
}
const extensionPath = path.join(root, folderName);
return pfs.readdir(extensionPath)
.then(children => readManifest(extensionPath)
@@ -798,7 +830,8 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private setUninstalled(...ids: string[]): TPromise<void> {
private setUninstalled(...extensions: ILocalExtension[]): TPromise<void> {
const ids = extensions.map(e => e.identifier.id);
return this.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id] = true; return result; }, {})));
}
@@ -852,6 +885,31 @@ export class ExtensionManagementService extends Disposable implements IExtension
return [];
});
}
private reportTelemetry(eventName: string, extensionData: any, duration: number, error?: Error): void {
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0;
/* __GDPR__
"extensionGallery:install" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
/* __GDPR__
"extensionGallery:uninstall" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
}
}
export function getLocalExtensionIdFromGallery(extension: IGalleryExtension, version: string): string {
@@ -860,4 +918,4 @@ export function getLocalExtensionIdFromGallery(extension: IGalleryExtension, ver
export function getLocalExtensionIdFromManifest(manifest: IExtensionManifest): string {
return getLocalExtensionId(getGalleryExtensionId(manifest.publisher, manifest.name), manifest.version);
}
}

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import * as sinon from 'sinon';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, IExtensionContributions, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, IExtensionContributions, ILocalExtension, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Emitter } from 'vs/base/common/event';
@@ -98,13 +98,13 @@ suite('ExtensionEnablementService Test', () => {
test('test state of globally disabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Disabled));
});
test('test state of globally enabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
});
test('test disable an extension for workspace', () => {
@@ -126,59 +126,59 @@ suite('ExtensionEnablementService Test', () => {
test('test state of workspace disabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceDisabled));
});
test('test state of workspace and globally disabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceDisabled));
});
test('test state of workspace enabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceEnabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceEnabled));
});
test('test state of globally disabled and workspace enabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceEnabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceEnabled));
});
test('test state of an extension when disabled for workspace from workspace enabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceDisabled));
});
test('test state of an extension when disabled globally from workspace enabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Disabled));
});
test('test state of an extension when disabled globally from workspace disabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Disabled));
});
test('test state of an extension when enabled globally from workspace enabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
});
test('test state of an extension when enabled globally from workspace disabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
});
test('test disable an extension for workspace and then globally', () => {
@@ -223,11 +223,10 @@ suite('ExtensionEnablementService Test', () => {
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a', uuid: void 0 })));
});
test('test disable an extension for workspace when there is no workspace throws error', (done) => {
test('test disable an extension for workspace when there is no workspace throws error', () => {
instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY);
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => assert.fail('should throw an error'), error => assert.ok(error))
.then(done, done);
.then(() => assert.fail('should throw an error'), error => assert.ok(error));
});
test('test enable an extension globally', () => {
@@ -237,26 +236,23 @@ suite('ExtensionEnablementService Test', () => {
.then(extensions => assert.deepEqual([], extensions));
});
test('test enable an extension globally return truthy promise', (done) => {
testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
test('test enable an extension globally return truthy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(value => assert.ok(value))
.then(done, done);
.then(value => assert.ok(value));
});
test('test enable an extension globally triggers change event', (done) => {
test('test enable an extension globally triggers change event', () => {
const target = sinon.spy();
testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a', uuid: void 0 })))
.then(done, done);
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a', uuid: void 0 })));
});
test('test enable an extension globally when already enabled return falsy promise', (done) => {
testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled)
.then(value => assert.ok(!value))
.then(done, done);
test('test enable an extension globally when already enabled return falsy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled)
.then(value => assert.ok(!value));
});
test('test enable an extension for workspace', () => {
@@ -311,23 +307,37 @@ suite('ExtensionEnablementService Test', () => {
test('test isEnabled return false extension is disabled globally', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => assert.ok(!testObject.isEnabled({ id: 'pub.a' })));
.then(() => assert.ok(!testObject.isEnabled(aLocalExtension('pub.a'))));
});
test('test isEnabled return false extension is disabled in workspace', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => assert.ok(!testObject.isEnabled({ id: 'pub.a' })));
.then(() => assert.ok(!testObject.isEnabled(aLocalExtension('pub.a'))));
});
test('test isEnabled return true extension is not disabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.c'), EnablementState.Disabled))
.then(() => assert.ok(testObject.isEnabled({ id: 'pub.b' })));
.then(() => assert.ok(testObject.isEnabled(aLocalExtension('pub.b'))));
});
test('test canChangeEnablement return false for language packs', () => {
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { localizations: [{ languageId: 'gr', translations: [{ id: 'vscode', path: 'path' }] }] })), false);
});
test('test canChangeEnablement return false when extensions are disabled in environment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: true } as IEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a')), false);
});
test('test canChangeEnablement return true for system extensions when extensions are disabled in environment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: true } as IEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
const extension = aLocalExtension('pub.a');
extension.type = LocalExtensionType.System;
assert.equal(testObject.canChangeEnablement(extension), true);
});
});
function aLocalExtension(id: string, contributes?: IExtensionContributions): ILocalExtension {
@@ -338,6 +348,7 @@ function aLocalExtension(id: string, contributes?: IExtensionContributions): ILo
name,
publisher,
contributes
}
},
type: LocalExtensionType.User
});
}

View File

@@ -7,7 +7,7 @@
import * as assert from 'assert';
import * as os from 'os';
import extfs = require('vs/base/node/extfs');
import * as extfs from 'vs/base/node/extfs';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
@@ -27,7 +27,7 @@ suite('Extension Gallery Service', () => {
extfs.del(marketplaceHome, os.tmpdir(), () => {
mkdirp(marketplaceHome).then(() => {
done();
});
}, error => done(error));
});
});
@@ -35,7 +35,7 @@ suite('Extension Gallery Service', () => {
extfs.del(marketplaceHome, os.tmpdir(), done);
});
test('marketplace machine id', done => {
test('marketplace machine id', () => {
const args = ['--user-data-dir', marketplaceHome];
const environmentService = new EnvironmentService(parseArgs(args), process.execPath);
@@ -44,8 +44,6 @@ suite('Extension Gallery Service', () => {
return resolveMarketplaceHeaders(environmentService).then(headers2 => {
assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']);
done();
});
});
});

View File

@@ -5,6 +5,7 @@
'use strict';
import * as nls from 'vs/nls';
import pkg from 'vs/platform/node/package';
export interface IParsedVersion {
hasCaret: boolean;
@@ -222,11 +223,16 @@ export function isValidExtensionVersion(version: string, extensionDesc: IReduced
return (extensionDesc.engines.sqlops && extensionDesc.engines.sqlops === '*') || isVersionValid(version, extensionDesc.engines.sqlops, notices);
}
export function isEngineValid(engine: string): boolean {
// TODO@joao: discuss with alex '*' doesn't seem to be a valid engine version
return engine === '*' || isVersionValid(pkg.version, engine);
}
export function isVersionValid(currentVersion: string, requestedVersion: string, notices: string[] = []): boolean {
let desiredVersion = normalizeVersion(parseVersion(requestedVersion));
if (!desiredVersion) {
notices.push(nls.localize('versionSyntax', "Could not parse `engines.vscode` value {0}. Please use, for example: ^0.10.0, ^1.2.3, ^0.11.0, ^0.10.x, etc.", requestedVersion));
notices.push(nls.localize('versionSyntax', "Could not parse `engines.vscode` value {0}. Please use, for example: ^1.22.0, ^1.22.x, etc.", requestedVersion));
return false;
}

View File

@@ -5,23 +5,31 @@
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import paths = require('vs/base/common/paths');
import * as paths from 'vs/base/common/paths';
import URI from 'vs/base/common/uri';
import glob = require('vs/base/common/glob');
import * as glob from 'vs/base/common/glob';
import { isLinux } from 'vs/base/common/platform';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import Event from 'vs/base/common/event';
import { beginsWithIgnoreCase } from 'vs/base/common/strings';
import { IProgress } from 'vs/platform/progress/common/progress';
import { Event } from 'vs/base/common/event';
import { startsWithIgnoreCase } from 'vs/base/common/strings';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isEqualOrParent, isEqual } from 'vs/base/common/resources';
import { isUndefinedOrNull } from 'vs/base/common/types';
export const IFileService = createDecorator<IFileService>('fileService');
export interface IResourceEncodings {
getWriteEncoding(resource: URI, preferredEncoding?: string): string;
}
export interface IFileService {
_serviceBrand: any;
/**
* Helper to determine read/write encoding for resources.
*/
encoding: IResourceEncodings;
/**
* Allows to listen for file changes. The event will fire for every file within the opened workspace
* (if any) as well as all files that have been watched explicitly using the #watchFileChanges() API.
@@ -33,15 +41,20 @@ export interface IFileService {
*/
onAfterOperation: Event<FileOperationEvent>;
/**
* An event that is fired when a file system provider is added or removed
*/
onDidChangeFileSystemProviderRegistrations: Event<IFileSystemProviderRegistrationEvent>;
/**
* Registeres a file system provider for a certain scheme.
*/
registerProvider?(scheme: string, provider: IFileSystemProvider): IDisposable;
registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable;
/**
* Checks if this file service can handle the given resource.
*/
canHandleResource?(resource: URI): boolean;
canHandleResource(resource: URI): boolean;
/**
* Resolve the properties of a file identified by the resource.
@@ -120,23 +133,12 @@ export interface IFileService {
*/
rename(resource: URI, newName: string): TPromise<IFileStat>;
/**
* Creates a new empty file if the given path does not exist and otherwise
* will set the mtime and atime of the file to the current date.
*/
touchFile(resource: URI): TPromise<IFileStat>;
/**
* Deletes the provided file. The optional useTrash parameter allows to
* move the file to trash.
*/
del(resource: URI, useTrash?: boolean): TPromise<void>;
/**
* Imports the file to the parent identified by the resource.
*/
importFile(source: URI, targetFolder: URI): TPromise<IImportResult>;
/**
* Allows to start a watcher that reports file change events on the provided resource.
*/
@@ -147,59 +149,83 @@ export interface IFileService {
*/
unwatchFileChanges(resource: URI): void;
/**
* Configures the file service with the provided options.
*/
updateOptions(options: object): void;
/**
* Returns the preferred encoding to use for a given resource.
*/
getEncoding(resource: URI, preferredEncoding?: string): string;
/**
* Frees up any resources occupied by this service.
*/
dispose(): void;
}
export interface FileOverwriteOptions {
overwrite: boolean;
}
export interface FileWriteOptions {
overwrite: boolean;
create: boolean;
}
export enum FileType {
File = 0,
Dir = 1,
Symlink = 2
Unknown = 0,
File = 1,
Directory = 2,
SymbolicLink = 64
}
export interface IStat {
id: number | string;
mtime: number;
size: number;
type: FileType;
mtime: number;
ctime: number;
size: number;
}
export interface IWatchOptions {
recursive: boolean;
excludes: string[];
}
export enum FileSystemProviderCapabilities {
FileReadWrite = 1 << 1,
FileOpenReadWriteClose = 1 << 2,
FileFolderCopy = 1 << 3,
PathCaseSensitive = 1 << 10
}
export interface IFileSystemProvider {
onDidChange?: Event<IFileChange[]>;
readonly capabilities: FileSystemProviderCapabilities;
onDidChangeFile: Event<IFileChange[]>;
watch(resource: URI, opts: IWatchOptions): IDisposable;
// more...
//
utimes(resource: URI, mtime: number, atime: number): TPromise<IStat>;
stat(resource: URI): TPromise<IStat>;
read(resource: URI, offset: number, count: number, progress: IProgress<Uint8Array>): TPromise<number>;
write(resource: URI, content: Uint8Array): TPromise<void>;
move(from: URI, to: URI): TPromise<IStat>;
mkdir(resource: URI): TPromise<IStat>;
readdir(resource: URI): TPromise<[URI, IStat][]>;
rmdir(resource: URI): TPromise<void>;
unlink(resource: URI): TPromise<void>;
mkdir(resource: URI): TPromise<void>;
readdir(resource: URI): TPromise<[string, FileType][]>;
delete(resource: URI): TPromise<void>;
rename(from: URI, to: URI, opts: FileOverwriteOptions): TPromise<void>;
copy?(from: URI, to: URI, opts: FileOverwriteOptions): TPromise<void>;
readFile?(resource: URI): TPromise<Uint8Array>;
writeFile?(resource: URI, content: Uint8Array, opts: FileWriteOptions): TPromise<void>;
open?(resource: URI): TPromise<number>;
close?(fd: number): TPromise<void>;
read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
}
export interface IFileSystemProviderRegistrationEvent {
added: boolean;
scheme: string;
provider?: IFileSystemProvider;
}
export enum FileOperation {
CREATE,
DELETE,
MOVE,
COPY,
IMPORT
COPY
}
export class FileOperationEvent {
@@ -348,31 +374,12 @@ export function isParent(path: string, candidate: string, ignoreCase?: boolean):
}
if (ignoreCase) {
return beginsWithIgnoreCase(path, candidate);
return startsWithIgnoreCase(path, candidate);
}
return path.indexOf(candidate) === 0;
}
export function indexOf(path: string, candidate: string, ignoreCase?: boolean): number {
if (candidate.length > path.length) {
return -1;
}
if (path === candidate) {
return 0;
}
if (ignoreCase) {
path = path.toLowerCase();
candidate = candidate.toLowerCase();
}
return path.indexOf(candidate);
}
export interface IBaseStat {
/**
@@ -474,6 +481,14 @@ export interface ITextSnapshot {
read(): string;
}
export class StringSnapshot implements ITextSnapshot {
constructor(private _value: string) { }
read(): string {
let ret = this._value;
this._value = null;
return ret;
}
}
/**
* Helper method to convert a snapshot into its full string form.
*/
@@ -512,9 +527,10 @@ export interface IResolveContentOptions {
acceptTextOnly?: boolean;
/**
* The optional etag parameter allows to return a 304 (Not Modified) if the etag matches
* with the remote resource. It is the task of the caller to makes sure to handle this
* error case from the promise.
* The optional etag parameter allows to return early from resolving the resource if
* the contents on disk match the etag. This prevents accumulated reading of resources
* that have been read already with the same etag.
* It is the task of the caller to makes sure to handle this error case from the promise.
*/
etag?: string;
@@ -568,6 +584,11 @@ export interface IUpdateContentOptions {
* The etag of the file. This can be used to prevent dirty writes.
*/
etag?: string;
/**
* Run mkdirp before saving.
*/
mkdirp?: boolean;
}
export interface IResolveFileOptions {
@@ -584,11 +605,6 @@ export interface ICreateFileOptions {
overwrite?: boolean;
}
export interface IImportResult {
stat: IFileStat;
isNew: boolean;
}
export class FileOperationError extends Error {
constructor(message: string, public fileOperationResult: FileOperationResult, public options?: IResolveContentOptions & IUpdateContentOptions & ICreateFileOptions) {
super(message);

View File

@@ -8,7 +8,7 @@
import * as assert from 'assert';
import URI from 'vs/base/common/uri';
import { join, isEqual, isEqualOrParent } from 'vs/base/common/paths';
import { FileChangeType, FileChangesEvent, isParent, indexOf } from 'vs/platform/files/common/files';
import { FileChangeType, FileChangesEvent, isParent } from 'vs/platform/files/common/files';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
suite('Files', () => {
@@ -187,16 +187,4 @@ suite('Files', () => {
assert(!isEqualOrParent('foo/bar/test.ts', 'foo/BAR/test.', true));
}
});
test('indexOf (ignorecase)', function () {
assert.equal(indexOf('/some/path', '/some/path', true), 0);
assert.equal(indexOf('/some/path/more', '/some/path', true), 0);
assert.equal(indexOf('c:\\some\\path', 'c:\\some\\path', true), 0);
assert.equal(indexOf('c:\\some\\path\\more', 'c:\\some\\path', true), 0);
assert.equal(indexOf('/some/path', '/some/other/path', true), -1);
assert.equal(indexOf('/some/path', '/some/PATH', true), 0);
});
});

View File

@@ -6,7 +6,7 @@
'use strict';
import { IPath } from 'vs/platform/windows/common/windows';
import CommonEvent from 'vs/base/common/event';
import { Event as CommonEvent } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';

View File

@@ -14,7 +14,7 @@ import { app } from 'electron';
import { ILogService } from 'vs/platform/log/common/log';
import { getPathLabel, getBaseLabel } from 'vs/base/common/labels';
import { IPath } from 'vs/platform/windows/common/windows';
import CommonEvent, { Emitter } from 'vs/base/common/event';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform';
import { IWorkspaceIdentifier, IWorkspacesMainService, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceSavedEvent } from 'vs/platform/workspaces/common/workspaces';
import { IHistoryMainService, IRecentlyOpened } from 'vs/platform/history/common/history';

View File

@@ -5,7 +5,7 @@
'use strict';
import assert = require('assert');
import * as assert from 'assert';
import { createDecorator, optional, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
@@ -56,7 +56,7 @@ interface IDependentService {
class DependentService implements IDependentService {
_serviceBrand: any;
constructor( @IService1 service: IService1) {
constructor(@IService1 service: IService1) {
assert.equal(service.c, 1);
}
@@ -65,7 +65,7 @@ class DependentService implements IDependentService {
class Service1Consumer {
constructor( @IService1 service1: IService1) {
constructor(@IService1 service1: IService1) {
assert.ok(service1);
assert.equal(service1.c, 1);
}
@@ -73,7 +73,7 @@ class Service1Consumer {
class Target2Dep {
constructor( @IService1 service1: IService1, @IService2 service2) {
constructor(@IService1 service1: IService1, @IService2 service2) {
assert.ok(service1 instanceof Service1);
assert.ok(service2 instanceof Service2);
}
@@ -88,12 +88,12 @@ class TargetWithStaticParam {
}
class TargetNotOptional {
constructor( @IService1 service1: IService1, @IService2 service2: IService2) {
constructor(@IService1 service1: IService1, @IService2 service2: IService2) {
}
}
class TargetOptional {
constructor( @IService1 service1: IService1, @optional(IService2) service2: IService2) {
constructor(@IService1 service1: IService1, @optional(IService2) service2: IService2) {
assert.ok(service1);
assert.equal(service1.c, 1);
assert.ok(service2 === void 0);
@@ -101,14 +101,14 @@ class TargetOptional {
}
class DependentServiceTarget {
constructor( @IDependentService d) {
constructor(@IDependentService d) {
assert.ok(d);
assert.equal(d.name, 'farboo');
}
}
class DependentServiceTarget2 {
constructor( @IDependentService d: IDependentService, @IService1 s: IService1) {
constructor(@IDependentService d: IDependentService, @IService1 s: IService1) {
assert.ok(d);
assert.equal(d.name, 'farboo');
assert.ok(s);
@@ -121,7 +121,7 @@ class ServiceLoop1 implements IService1 {
_serviceBrand: any;
c = 1;
constructor( @IService2 s: IService2) {
constructor(@IService2 s: IService2) {
}
}
@@ -130,7 +130,7 @@ class ServiceLoop2 implements IService2 {
_serviceBrand: any;
d = true;
constructor( @IService1 s: IService1) {
constructor(@IService1 s: IService1) {
}
}

View File

@@ -14,7 +14,7 @@ import URI from 'vs/base/common/uri';
import Severity from 'vs/base/common/severity';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
interface IStorageData {
dontShowPrompt: boolean;
@@ -62,7 +62,7 @@ export class IntegrityServiceImpl implements IIntegrityService {
private _isPurePromise: Thenable<IntegrityTestResult>;
constructor(
@IChoiceService private choiceService: IChoiceService,
@INotificationService private notificationService: INotificationService,
@IStorageService storageService: IStorageService,
@ILifecycleService private lifecycleService: ILifecycleService
) {
@@ -82,26 +82,24 @@ export class IntegrityServiceImpl implements IIntegrityService {
private _prompt(): void {
const storedData = this._storage.get();
if (storedData && storedData.dontShowPrompt && storedData.commit === product.commit) {
// Do not prompt
return;
return; // Do not prompt
}
const choices: Choice[] = [nls.localize('integrity.moreInformation', "More Information"), { label: nls.localize('integrity.dontShowAgain', "Don't Show Again") }];
this.choiceService.choose(Severity.Warning, nls.localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", product.nameShort), choices).then(choice => {
switch (choice) {
case 0 /* More Information */:
const uri = URI.parse(product.checksumFailMoreInfoUrl);
window.open(uri.toString(true));
break;
case 1 /* Do not show again */:
this._storage.set({
dontShowPrompt: true,
commit: product.commit
});
break;
}
});
this.notificationService.prompt(
Severity.Warning,
nls.localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", product.nameShort),
[
{
label: nls.localize('integrity.moreInformation', "More Information"),
run: () => window.open(URI.parse(product.checksumFailMoreInfoUrl).toString(true))
},
{
label: nls.localize('integrity.dontShowAgain', "Don't Show Again"),
isSecondary: true,
run: () => this._storage.set({ dontShowPrompt: true, commit: product.commit })
}
]
);
}
public isPure(): Thenable<IntegrityTestResult> {

View File

@@ -11,6 +11,15 @@ import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensio
export const IIssueService = createDecorator<IIssueService>('issueService');
export interface WindowStyles {
backgroundColor: string;
color: string;
}
export interface WindowData {
styles: WindowStyles;
zoomLevel: number;
}
export enum IssueType {
Bug,
PerformanceIssue,
@@ -18,9 +27,7 @@ export enum IssueType {
SettingsSearchIssue
}
export interface IssueReporterStyles {
backgroundColor: string;
color: string;
export interface IssueReporterStyles extends WindowStyles {
textLinkColor: string;
inputBackground: string;
inputForeground: string;
@@ -35,9 +42,8 @@ export interface IssueReporterStyles {
sliderActiveColor: string;
}
export interface IssueReporterData {
export interface IssueReporterData extends WindowData {
styles: IssueReporterStyles;
zoomLevel: number;
enabledExtensions: ILocalExtension[];
issueType?: IssueType;
}
@@ -58,7 +64,18 @@ export interface ISettingsSearchIssueReporterData extends IssueReporterData {
export interface IssueReporterFeatures {
}
export interface ProcessExplorerStyles extends WindowStyles {
hoverBackground: string;
hoverForeground: string;
highlightForeground: string;
}
export interface ProcessExplorerData extends WindowData {
styles: ProcessExplorerStyles;
}
export interface IIssueService {
_serviceBrand: any;
openReporter(data: IssueReporterData): TPromise<void>;
openProcessExplorer(data: ProcessExplorerData): TPromise<void>;
}

View File

@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IIssueService, IssueReporterData } from './issue';
import { IIssueService, IssueReporterData, ProcessExplorerData } from './issue';
export interface IIssueChannel extends IChannel {
call(command: 'openIssueReporter', arg: IssueReporterData): TPromise<void>;
@@ -23,6 +23,8 @@ export class IssueChannel implements IIssueChannel {
switch (command) {
case 'openIssueReporter':
return this.service.openReporter(arg);
case 'openProcessExplorer':
return this.service.openProcessExplorer(arg);
}
return undefined;
}
@@ -37,4 +39,8 @@ export class IssueChannelClient implements IIssueService {
openReporter(data: IssueReporterData): TPromise<void> {
return this.channel.call('openIssueReporter', data);
}
openProcessExplorer(data: ProcessExplorerData): TPromise<void> {
return this.channel.call('openProcessExplorer', data);
}
}

View File

@@ -9,12 +9,12 @@ import { TPromise, Promise } from 'vs/base/common/winjs.base';
import { localize } from 'vs/nls';
import * as objects from 'vs/base/common/objects';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { IIssueService, IssueReporterData, IssueReporterFeatures } from 'vs/platform/issue/common/issue';
import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/common/issue';
import { BrowserWindow, ipcMain, screen } from 'electron';
import { ILaunchService } from 'vs/code/electron-main/launch';
import { getPerformanceInfo, PerformanceInfo, getSystemInfo, SystemInfo } from 'vs/code/electron-main/diagnostics';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { isMacintosh } from 'vs/base/common/platform';
import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
const DEFAULT_BACKGROUND_COLOR = '#1E1E1E';
@@ -22,13 +22,15 @@ const DEFAULT_BACKGROUND_COLOR = '#1E1E1E';
export class IssueService implements IIssueService {
_serviceBrand: any;
_issueWindow: BrowserWindow;
_parentWindow: BrowserWindow;
_issueParentWindow: BrowserWindow;
_processExplorerWindow: BrowserWindow;
constructor(
private machineId: string,
private userEnv: IProcessEnvironment,
@IEnvironmentService private environmentService: IEnvironmentService,
@ILaunchService private launchService: ILaunchService,
@ILogService private logService: ILogService
@ILogService private logService: ILogService,
) { }
openReporter(data: IssueReporterData): TPromise<void> {
@@ -45,11 +47,11 @@ export class IssueService implements IIssueService {
});
ipcMain.on('workbenchCommand', (event, arg) => {
this._parentWindow.webContents.send('vscode:runAction', { id: arg, from: 'issueReporter' });
this._issueParentWindow.webContents.send('vscode:runAction', { id: arg, from: 'issueReporter' });
});
this._parentWindow = BrowserWindow.getFocusedWindow();
const position = this.getWindowPosition();
this._issueParentWindow = BrowserWindow.getFocusedWindow();
const position = this.getWindowPosition(this._issueParentWindow, 800, 900);
this._issueWindow = new BrowserWindow({
width: position.width,
height: position.height,
@@ -72,7 +74,54 @@ export class IssueService implements IIssueService {
return TPromise.as(null);
}
private getWindowPosition() {
openProcessExplorer(data: ProcessExplorerData): TPromise<void> {
// Create as singleton
if (!this._processExplorerWindow) {
const position = this.getWindowPosition(BrowserWindow.getFocusedWindow(), 800, 300);
this._processExplorerWindow = new BrowserWindow({
skipTaskbar: true,
resizable: true,
width: position.width,
height: position.height,
minWidth: 300,
minHeight: 200,
x: position.x,
y: position.y,
backgroundColor: data.styles.backgroundColor,
title: localize('processExplorer', "Process Explorer")
});
this._processExplorerWindow.setMenuBarVisibility(false);
const windowConfiguration = {
appRoot: this.environmentService.appRoot,
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
windowId: this._processExplorerWindow.id,
userEnv: this.userEnv,
machineId: this.machineId,
data
};
const environment = parseArgs(process.argv);
const config = objects.assign(environment, windowConfiguration);
for (let key in config) {
if (config[key] === void 0 || config[key] === null || config[key] === '') {
delete config[key]; // only send over properties that have a true value
}
}
this._processExplorerWindow.loadURL(`${require.toUrl('vs/code/electron-browser/processExplorer/processExplorer.html')}?config=${encodeURIComponent(JSON.stringify(config))}`);
this._processExplorerWindow.on('close', () => this._processExplorerWindow = void 0);
}
// Focus
this._processExplorerWindow.focus();
return TPromise.as(null);
}
private getWindowPosition(parentWindow: BrowserWindow, defaultWidth: number, defaultHeight: number) {
// We want the new window to open on the same display that the parent is in
let displayToUse: Electron.Display;
const displays = screen.getAllDisplays();
@@ -92,8 +141,8 @@ export class IssueService implements IIssueService {
}
// if we have a last active window, use that display for the new window
if (!displayToUse && this._parentWindow) {
displayToUse = screen.getDisplayMatching(this._parentWindow.getBounds());
if (!displayToUse && parentWindow) {
displayToUse = screen.getDisplayMatching(parentWindow.getBounds());
}
// fallback to primary display or first display
@@ -103,8 +152,8 @@ export class IssueService implements IIssueService {
}
let state = {
width: 800,
height: 900,
width: defaultWidth,
height: defaultHeight,
x: undefined,
y: undefined
};
@@ -171,6 +220,7 @@ export class IssueService implements IIssueService {
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
windowId: this._issueWindow.id,
machineId: this.machineId,
userEnv: this.userEnv,
data,
features
};

View File

@@ -6,7 +6,7 @@
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as platform from 'vs/platform/registry/common/platform';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
export const Extensions = {
JSONContribution: 'base.contributions.json'
@@ -55,7 +55,7 @@ class JSONContributionRegistry implements IJSONContributionRegistry {
private schemasById: { [id: string]: IJSONSchema };
private _onDidChangeSchema: Emitter<string> = new Emitter<string>();
private readonly _onDidChangeSchema: Emitter<string> = new Emitter<string>();
readonly onDidChangeSchema: Event<string> = this._onDidChangeSchema.event;
constructor() {

View File

@@ -12,7 +12,7 @@ import { KeybindingResolver, IResolveResult } from 'vs/platform/keybinding/commo
import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';

View File

@@ -9,7 +9,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
export interface IUserFriendlyKeybinding {
key: string;

View File

@@ -21,8 +21,7 @@ export class USLayoutResolvedKeybinding extends ResolvedKeybinding {
super();
this._os = OS;
if (actual === null) {
this._firstPart = null;
this._chordPart = null;
throw new Error(`Invalid USLayoutResolvedKeybinding`);
} else if (actual.type === KeybindingType.Chord) {
this._firstPart = actual.firstPart;
this._chordPart = actual.chordPart;

View File

@@ -139,7 +139,7 @@ suite('AbstractKeybindingService', () => {
showMessageCalls.push({ sev: Severity.Error, message });
return new NoOpNotification();
},
prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): any {
prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void) {
throw new Error('not implemented');
}
};

View File

@@ -5,7 +5,7 @@
'use strict';
import { ResolvedKeybinding, Keybinding, SimpleKeybinding } from 'vs/base/common/keyCodes';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IKeybindingService, IKeybindingEvent, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IContextKey, IContextKeyService, IContextKeyServiceTarget, ContextKeyExpr, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey';
import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
@@ -120,4 +120,8 @@ export class MockKeybindingService implements IKeybindingService {
public softDispatch(keybinding: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult {
return null;
}
dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
return false;
}
}

View File

@@ -5,7 +5,7 @@
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');

View File

@@ -9,7 +9,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ILifecycleService, ShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ipcRenderer as ipc } from 'electron';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { mark } from 'vs/base/common/performance';
import { Barrier } from 'vs/base/common/async';

View File

@@ -9,11 +9,12 @@ import { ipcMain as ipc, app } from 'electron';
import { TPromise, TValueCallback } from 'vs/base/common/winjs.base';
import { ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/common/state';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { ReadyState } from 'vs/platform/windows/common/windows';
import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
@@ -38,12 +39,24 @@ export interface ILifecycleService {
*/
wasRestarted: boolean;
/**
* Will be true if the program was requested to quit.
*/
isQuitRequested: boolean;
/**
* Due to the way we handle lifecycle with eventing, the general app.on('before-quit')
* event cannot be used because it can be called twice on shutdown. Instead the onBeforeQuit
* event cannot be used because it can be called twice on shutdown. Instead the onBeforeShutdown
* handler in this module can be used and it is only called once on a shutdown sequence.
*/
onBeforeQuit: Event<void>;
onBeforeShutdown: Event<void>;
/**
* An event that fires after the onBeforeShutdown event has been fired and after no window has
* vetoed the shutdown sequence. At this point listeners are ensured that the application will
* quit without veto.
*/
onShutdown: Event<void>;
/**
* We provide our own event when we close a window because the general window.on('close')
@@ -65,7 +78,6 @@ export interface ILifecycleService {
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): void;
quit(fromUpdate?: boolean): TPromise<boolean /* veto */>;
isQuitRequested(): boolean;
kill(code?: number): void;
}
@@ -82,9 +94,13 @@ export class LifecycleService implements ILifecycleService {
private pendingQuitPromiseComplete: TValueCallback<boolean>;
private oneTimeListenerTokenGenerator: number;
private _wasRestarted: boolean;
private windowCounter: number;
private _onBeforeQuit = new Emitter<void>();
onBeforeQuit: Event<void> = this._onBeforeQuit.event;
private _onBeforeShutdown = new Emitter<void>();
onBeforeShutdown: Event<void> = this._onBeforeShutdown.event;
private _onShutdown = new Emitter<void>();
onShutdown: Event<void> = this._onShutdown.event;
private _onBeforeWindowClose = new Emitter<ICodeWindow>();
onBeforeWindowClose: Event<ICodeWindow> = this._onBeforeWindowClose.event;
@@ -100,6 +116,7 @@ export class LifecycleService implements ILifecycleService {
this.quitRequested = false;
this.oneTimeListenerTokenGenerator = 0;
this._wasRestarted = false;
this.windowCounter = 0;
this.handleRestarted();
}
@@ -116,6 +133,10 @@ export class LifecycleService implements ILifecycleService {
return this._wasRestarted;
}
public get isQuitRequested(): boolean {
return !!this.quitRequested;
}
public ready(): void {
this.registerListeners();
}
@@ -126,11 +147,23 @@ export class LifecycleService implements ILifecycleService {
app.on('before-quit', e => {
this.logService.trace('Lifecycle#before-quit');
if (!this.quitRequested) {
this._onBeforeQuit.fire(); // only send this if this is the first quit request we have
if (this.quitRequested) {
this.logService.trace('Lifecycle#before-quit - returning because quit was already requested');
return;
}
this.quitRequested = true;
// Emit event to indicate that we are about to shutdown
this.logService.trace('Lifecycle#onBeforeShutdown.fire()');
this._onBeforeShutdown.fire();
// macOS: can run without any window open. in that case we fire
// the onShutdown() event directly because there is no veto to be expected.
if (isMacintosh && this.windowCounter === 0) {
this.logService.trace('Lifecycle#onShutdown.fire()');
this._onShutdown.fire();
}
});
// window-all-closed
@@ -147,6 +180,9 @@ export class LifecycleService implements ILifecycleService {
public registerWindow(window: ICodeWindow): void {
// track window count
this.windowCounter++;
// Window Before Closing: Main -> Renderer
window.win.on('close', e => {
const windowId = window.id;
@@ -166,7 +202,10 @@ export class LifecycleService implements ILifecycleService {
this.unload(window, UnloadReason.CLOSE).done(veto => {
if (!veto) {
this.windowToCloseRequest[windowId] = true;
this.logService.trace('Lifecycle#onBeforeWindowClose.fire()');
this._onBeforeWindowClose.fire(window);
window.close();
} else {
this.quitRequested = false;
@@ -174,6 +213,23 @@ export class LifecycleService implements ILifecycleService {
}
});
});
// Window After Closing
window.win.on('closed', e => {
const windowId = window.id;
this.logService.trace('Lifecycle#window-closed', windowId);
// update window count
this.windowCounter--;
// if there are no more code windows opened, fire the onShutdown event, unless
// we are on macOS where it is perfectly fine to close the last window and
// the application continues running (unless quit was actually requested)
if (this.windowCounter === 0 && (!isMacintosh || this.isQuitRequested)) {
this.logService.trace('Lifecycle#onShutdown.fire()');
this._onShutdown.fire();
}
});
}
public unload(window: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
@@ -279,7 +335,10 @@ export class LifecycleService implements ILifecycleService {
// Store as field to access it from a window cancellation
this.pendingQuitPromiseComplete = c;
// The will-quit event is fired when all windows have closed without veto
app.once('will-quit', () => {
this.logService.trace('Lifecycle#will-quit');
if (this.pendingQuitPromiseComplete) {
if (fromUpdate) {
this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
@@ -291,8 +350,13 @@ export class LifecycleService implements ILifecycleService {
}
});
// Calling app.quit() will trigger the close handlers of each opened window
// and only if no window vetoed the shutdown, we will get the will-quit event
this.logService.trace('Lifecycle#quit() - calling app.quit()');
app.quit();
});
} else {
this.logService.trace('Lifecycle#quit() - a pending quit was found');
}
return this.pendingQuitPromise;
@@ -325,6 +389,19 @@ export class LifecycleService implements ILifecycleService {
app.once('quit', () => {
if (!vetoed) {
this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
// Windows: we are about to restart and as such we need to restore the original
// current working directory we had on startup to get the exact same startup
// behaviour. As such, we briefly change back to the VSCODE_CWD and then when
// Code starts it will set it back to the installation directory again.
try {
if (isWindows) {
process.chdir(process.env['VSCODE_CWD']);
}
} catch (err) {
this.logService.error(err);
}
app.relaunch({ args });
}
});
@@ -333,8 +410,4 @@ export class LifecycleService implements ILifecycleService {
vetoed = veto;
});
}
public isQuitRequested(): boolean {
return !!this.quitRequested;
}
}

View File

@@ -5,25 +5,26 @@
'use strict';
import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
import { List, IListOptions, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, IMultipleSelectionController, IOpenController } from 'vs/base/browser/ui/list/listWidget';
import { List, IListOptions, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, IMultipleSelectionController, IOpenController, DefaultStyleController } from 'vs/base/browser/ui/list/listWidget';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, combinedDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { PagedList, IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { IDelegate, IRenderer, IListMouseEvent, IListTouchEvent } from 'vs/base/browser/ui/list/list';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { attachListStyler, defaultListStyles, computeStyles } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { mixin } from 'vs/base/common/objects';
import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { DefaultController, IControllerOptions, OpenMode, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { DefaultController, IControllerOptions, OpenMode, ClickBehavior, DefaultTreestyler } from 'vs/base/parts/tree/browser/treeDefaults';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { createStyleSheet } from 'vs/base/browser/dom';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
export type ListWidget = List<any> | PagedList<any> | ITree;
@@ -55,7 +56,7 @@ export class ListService implements IListService {
return this._lastFocusedWidget;
}
constructor( @IContextKeyService contextKeyService: IContextKeyService) { }
constructor(@IContextKeyService contextKeyService: IContextKeyService) { }
register(widget: ListWidget, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable {
if (this.lists.some(l => l.widget === widget)) {
@@ -73,7 +74,13 @@ export class ListService implements IListService {
const result = combinedDisposable([
widget.onDidFocus(() => this._lastFocusedWidget = widget),
toDisposable(() => this.lists.splice(this.lists.indexOf(registeredList), 1))
toDisposable(() => this.lists.splice(this.lists.indexOf(registeredList), 1)),
widget.onDidDispose(() => {
this.lists = this.lists.filter(l => l !== registeredList);
if (this._lastFocusedWidget === widget) {
this._lastFocusedWidget = undefined;
}
})
]);
return result;
@@ -99,6 +106,7 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi
export const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';
export const openModeSettingKey = 'workbench.list.openMode';
export const horizontalScrollingKey = 'workbench.tree.horizontalScrolling';
function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {
return configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
@@ -157,11 +165,33 @@ function handleListControllers<T>(options: IListOptions<T>, configurationService
return options;
}
let sharedListStyleSheet: HTMLStyleElement;
function getSharedListStyleSheet(): HTMLStyleElement {
if (!sharedListStyleSheet) {
sharedListStyleSheet = createStyleSheet();
}
return sharedListStyleSheet;
}
let sharedTreeStyleSheet: HTMLStyleElement;
function getSharedTreeStyleSheet(): HTMLStyleElement {
if (!sharedTreeStyleSheet) {
sharedTreeStyleSheet = createStyleSheet();
}
return sharedTreeStyleSheet;
}
function handleTreeController(configuration: ITreeConfiguration, instantiationService: IInstantiationService): ITreeConfiguration {
if (!configuration.controller) {
configuration.controller = instantiationService.createInstance(WorkbenchTreeController, {});
}
if (!configuration.styler) {
configuration.styler = new DefaultTreestyler(getSharedTreeStyleSheet());
}
return configuration;
}
@@ -184,7 +214,15 @@ export class WorkbenchList<T> extends List<T> {
@IThemeService themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService
) {
super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false, selectOnMouseDown: true } as IListOptions<T>, false));
super(container, delegate, renderers,
{
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...handleListControllers(options, configurationService)
} as IListOptions<T>
);
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
@@ -206,10 +244,6 @@ export class WorkbenchList<T> extends List<T> {
this.registerListeners();
}
public get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
private registerListeners(): void {
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
@@ -217,6 +251,10 @@ export class WorkbenchList<T> extends List<T> {
}
}));
}
get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
}
export class WorkbenchPagedList<T> extends PagedList<T> {
@@ -237,7 +275,15 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
@IThemeService themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService
) {
super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false, selectOnMouseDown: true } as IListOptions<T>, false));
super(container, delegate, renderers,
{
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...handleListControllers(options, configurationService)
} as IListOptions<T>
);
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
@@ -252,10 +298,6 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
this.registerListeners();
}
public get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
private registerListeners(): void {
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
@@ -264,7 +306,13 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
}));
}
get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
}
}
@@ -273,7 +321,7 @@ export class WorkbenchTree extends Tree {
readonly contextKeyService: IContextKeyService;
protected disposables: IDisposable[] = [];
protected disposables: IDisposable[];
private listDoubleSelection: IContextKey<boolean>;
private listMultiSelection: IContextKey<boolean>;
@@ -289,10 +337,20 @@ export class WorkbenchTree extends Tree {
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService private configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService
) {
super(container, handleTreeController(configuration, instantiationService), mixin(options, { keyboardSupport: false } as ITreeOptions, false));
const config = handleTreeController(configuration, instantiationService);
const horizontalScrollMode = configurationService.getValue(horizontalScrollingKey) ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden;
const opts = {
horizontalScrollMode,
keyboardSupport: false,
...computeStyles(themeService.getTheme(), defaultListStyles),
...options
};
super(container, config, opts);
this.disposables = [];
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
this.listMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
@@ -306,36 +364,34 @@ export class WorkbenchTree extends Tree {
attachListStyler(this, themeService)
);
this.registerListeners();
}
public get openOnSingleClick(): boolean {
return this._openOnSingleClick;
}
public get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
private registerListeners(): void {
this.disposables.push(this.onDidChangeSelection(() => {
const selection = this.getSelection();
this.listDoubleSelection.set(selection && selection.length === 2);
this.listMultiSelection.set(selection && selection.length > 1);
}));
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => {
this.disposables.push(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(openModeSettingKey)) {
this._openOnSingleClick = useSingleClickToOpen(this.configurationService);
this._openOnSingleClick = useSingleClickToOpen(configurationService);
}
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
}
}));
}
get openOnSingleClick(): boolean {
return this._openOnSingleClick;
}
get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
}
}
@@ -399,8 +455,8 @@ export interface IResourceResultsNavigationOptions {
export class TreeResourceNavigator extends Disposable {
private _openResource: Emitter<IOpenResourceOptions> = new Emitter<IOpenResourceOptions>();
public readonly openResource: Event<IOpenResourceOptions> = this._openResource.event;
private readonly _openResource: Emitter<IOpenResourceOptions> = new Emitter<IOpenResourceOptions>();
readonly openResource: Event<IOpenResourceOptions> = this._openResource.event;
constructor(private tree: WorkbenchTree, private options?: IResourceResultsNavigationOptions) {
super();
@@ -424,7 +480,8 @@ export class TreeResourceNavigator extends Disposable {
const isMouseEvent = payload && payload.origin === 'mouse';
const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2;
if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) {
const preventOpen = payload && payload.preventOpenOnFocus;
if (!preventOpen && (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick)) {
this._openResource.fire({
editorOptions: {
preserveFocus: true,
@@ -477,7 +534,7 @@ configurationRegistry.registerConfiguration({
'title': localize('workbenchConfigurationTitle', "Workbench"),
'type': 'object',
'properties': {
'workbench.list.multiSelectModifier': {
[multiSelectModifierSettingKey]: {
'type': 'string',
'enum': ['ctrlCmd', 'alt'],
'enumDescriptions': [
@@ -493,7 +550,7 @@ configurationRegistry.registerConfiguration({
]
}, "The modifier to be used to add an item in trees and lists to a multi-selection with the mouse (for example in the explorer, open editors and scm view). `ctrlCmd` maps to `Control` on Windows and Linux and to `Command` on macOS. The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.")
},
'workbench.list.openMode': {
[openModeSettingKey]: {
'type': 'string',
'enum': ['singleClick', 'doubleClick'],
'enumDescriptions': [
@@ -505,6 +562,11 @@ configurationRegistry.registerConfiguration({
key: 'openModeModifier',
comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.']
}, "Controls how to open items in trees and lists using the mouse (if supported). Set to `singleClick` to open items with a single mouse click and `doubleClick` to only open via mouse double click. For parents with children in trees, this setting will control if a single click expands the parent or a double click. Note that some trees and lists might choose to ignore this setting if it is not applicable. ")
},
[horizontalScrollingKey]: {
'type': 'boolean',
'default': false,
'description': localize('horizontalScrolling setting', "Controls whether trees support horizontal scrolling in the workbench.")
}
}
});

View File

@@ -6,7 +6,7 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
export interface ILocalization {
languageId: string;
@@ -20,12 +20,17 @@ export interface ITranslation {
path: string;
}
export enum LanguageType {
Core = 1,
Contributed
}
export const ILocalizationsService = createDecorator<ILocalizationsService>('localizationsService');
export interface ILocalizationsService {
_serviceBrand: any;
readonly onDidLanguagesChange: Event<void>;
getLanguageIds(): TPromise<string[]>;
getLanguageIds(type?: LanguageType): TPromise<string[]>;
}
export function isValidLocalization(localization: ILocalization): boolean {

View File

@@ -7,8 +7,8 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import Event, { buffer } from 'vs/base/common/event';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { Event, buffer } from 'vs/base/common/event';
import { ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations';
export interface ILocalizationsChannel extends IChannel {
call(command: 'event:onDidLanguagesChange'): TPromise<void>;
@@ -27,7 +27,7 @@ export class LocalizationsChannel implements ILocalizationsChannel {
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'event:onDidLanguagesChange': return eventToCall(this.onDidLanguagesChange);
case 'getLanguageIds': return this.service.getLanguageIds();
case 'getLanguageIds': return this.service.getLanguageIds(arg);
}
return undefined;
}
@@ -42,7 +42,7 @@ export class LocalizationsChannelClient implements ILocalizationsService {
private _onDidLanguagesChange = eventFromCall<void>(this.channel, 'event:onDidLanguagesChange');
get onDidLanguagesChange(): Event<void> { return this._onDidLanguagesChange; }
getLanguageIds(): TPromise<string[]> {
return this.channel.call('getLanguageIds');
getLanguageIds(type?: LanguageType): TPromise<string[]> {
return this.channel.call('getLanguageIds', type);
}
}

View File

@@ -13,10 +13,10 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { Limiter } from 'vs/base/common/async';
import { areSameExtensions, getGalleryExtensionIdFromLocal, getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ILogService } from 'vs/platform/log/common/log';
import { isValidLocalization, ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { isValidLocalization, ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations';
import product from 'vs/platform/node/product';
import { distinct, equals } from 'vs/base/common/arrays';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
interface ILanguagePack {
hash: string;
@@ -27,7 +27,7 @@ interface ILanguagePack {
translations: { [id: string]: string };
}
const systemLanguages: string[] = ['de', 'en', 'en-US', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-CN', 'zh-TW'];
const systemLanguages: string[] = ['de', 'en', 'en-US', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-CN', 'zh-Hans', 'zh-TW', 'zh-Hant'];
if (product.quality !== 'stable') {
systemLanguages.push('hu');
}
@@ -55,10 +55,13 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe
this.extensionManagementService.getInstalled().then(installed => this.cache.update(installed));
}
getLanguageIds(): TPromise<string[]> {
getLanguageIds(type: LanguageType): TPromise<string[]> {
if (type === LanguageType.Core) {
return TPromise.as([...systemLanguages]);
}
return this.cache.getLanguagePacks()
.then(languagePacks => {
const languages = [...systemLanguages, ...Object.keys(languagePacks)];
const languages = type === LanguageType.Contributed ? Object.keys(languagePacks) : [...systemLanguages, ...Object.keys(languagePacks)];
return TPromise.as(distinct(languages));
});
}

View File

@@ -8,7 +8,7 @@
import { createDecorator as createServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { isWindows } from 'vs/base/common/platform';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
export const ILogService = createServiceDecorator<ILogService>('logService');
@@ -300,7 +300,6 @@ export class NullLogService implements ILogService {
dispose(): void { }
}
export function getLogLevel(environmentService: IEnvironmentService): LogLevel {
if (environmentService.verbose) {
return LogLevel.Trace;

View File

@@ -6,7 +6,7 @@
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import { TPromise } from 'vs/base/common/winjs.base';
import { LogLevel, ILogService, DelegatedLogService } from 'vs/platform/log/common/log';
import Event, { buffer } from 'vs/base/common/event';
import { Event, buffer } from 'vs/base/common/event';
export interface ILogLevelSetterChannel extends IChannel {
call(command: 'event:onDidChangeLogLevel'): TPromise<LogLevel>;

View File

@@ -7,13 +7,15 @@
import * as path from 'path';
import { ILogService, LogLevel, NullLogService, AbstractLogService } from 'vs/platform/log/common/log';
import { RotatingLogger, setAsyncMode } from 'spdlog';
import * as spdlog from 'spdlog';
export function createSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): ILogService {
// Do not crash if spdlog cannot be loaded
try {
setAsyncMode(8192, 2000);
const _spdlog: typeof spdlog = require.__$__nodeRequire('spdlog');
_spdlog.setAsyncMode(8192, 2000);
const logfilePath = path.join(logsFolder, `${processName}.log`);
const logger = new RotatingLogger(processName, logfilePath, 1024 * 1024 * 5, 6);
const logger = new _spdlog.RotatingLogger(processName, logfilePath, 1024 * 1024 * 5, 6);
logger.setLevel(0);
return new SpdLogService(logger, logLevel);
@@ -28,7 +30,7 @@ class SpdLogService extends AbstractLogService implements ILogService {
_serviceBrand: any;
constructor(
private readonly logger: RotatingLogger,
private readonly logger: spdlog.RotatingLogger,
level: LogLevel = LogLevel.Error
) {
super();

View File

@@ -9,9 +9,8 @@ import { Schemas } from 'vs/base/common/network';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isEmptyObject } from 'vs/base/common/types';
import URI from 'vs/base/common/uri';
import Event, { Emitter, debounceEvent } from 'vs/base/common/event';
import Severity from 'vs/base/common/severity';
import { IMarkerService, IMarkerData, IResourceMarker, IMarker, MarkerStatistics } from './markers';
import { Event, Emitter, debounceEvent } from 'vs/base/common/event';
import { IMarkerService, IMarkerData, IResourceMarker, IMarker, MarkerStatistics, MarkerSeverity } from './markers';
interface MapMap<V> {
[key: string]: { [key: string]: V };
@@ -88,11 +87,11 @@ class MarkerStats implements MarkerStatistics {
}
for (const { severity } of this._service.read({ resource })) {
if (severity === Severity.Error) {
if (severity === MarkerSeverity.Error) {
result.errors += 1;
} else if (severity === Severity.Warning) {
} else if (severity === MarkerSeverity.Warning) {
result.warnings += 1;
} else if (severity === Severity.Info) {
} else if (severity === MarkerSeverity.Info) {
result.infos += 1;
} else {
result.unknowns += 1;
@@ -180,7 +179,12 @@ export class MarkerService implements IMarkerService {
}
private static _toMarker(owner: string, resource: URI, data: IMarkerData): IMarker {
let { code, severity, message, source, startLineNumber, startColumn, endLineNumber, endColumn } = data;
let {
code, severity,
message, source,
startLineNumber, startColumn, endLineNumber, endColumn,
relatedInformation
} = data;
if (!message) {
return undefined;
@@ -203,7 +207,8 @@ export class MarkerService implements IMarkerService {
startLineNumber,
startColumn,
endLineNumber,
endColumn
endColumn,
relatedInformation
};
}
@@ -257,9 +262,9 @@ export class MarkerService implements IMarkerService {
}
}
read(filter: { owner?: string; resource?: URI; take?: number; } = Object.create(null)): IMarker[] {
read(filter: { owner?: string; resource?: URI; severities?: number, take?: number; } = Object.create(null)): IMarker[] {
let { owner, resource, take } = filter;
let { owner, resource, severities, take } = filter;
if (!take || take < 0) {
take = -1;
@@ -267,11 +272,20 @@ export class MarkerService implements IMarkerService {
if (owner && resource) {
// exactly one owner AND resource
const result = MapMap.get(this._byResource, resource.toString(), owner);
if (!result) {
const data = MapMap.get(this._byResource, resource.toString(), owner);
if (!data) {
return [];
} else {
return result.slice(0, take > 0 ? take : undefined);
const result: IMarker[] = [];
for (const marker of data) {
if (MarkerService._accept(marker, severities)) {
const newLen = result.push(marker);
if (take > 0 && newLen === take) {
break;
}
}
}
return result;
}
} else if (!owner && !resource) {
@@ -280,10 +294,11 @@ export class MarkerService implements IMarkerService {
for (const key1 in this._byResource) {
for (const key2 in this._byResource[key1]) {
for (const data of this._byResource[key1][key2]) {
const newLen = result.push(data);
if (take > 0 && newLen === take) {
return result;
if (MarkerService._accept(data, severities)) {
const newLen = result.push(data);
if (take > 0 && newLen === take) {
return result;
}
}
}
}
@@ -303,10 +318,11 @@ export class MarkerService implements IMarkerService {
const result: IMarker[] = [];
for (const key in map) {
for (const data of map[key]) {
const newLen = result.push(data);
if (take > 0 && newLen === take) {
return result;
if (MarkerService._accept(data, severities)) {
const newLen = result.push(data);
if (take > 0 && newLen === take) {
return result;
}
}
}
}
@@ -314,6 +330,10 @@ export class MarkerService implements IMarkerService {
}
}
private static _accept(marker: IMarker, severities?: number): boolean {
return severities === void 0 || (severities & marker.severity) === marker.severity;
}
// --- event debounce logic
private static _dedupeMap: { [uri: string]: boolean };

View File

@@ -5,9 +5,10 @@
'use strict';
import URI from 'vs/base/common/uri';
import Severity from 'vs/base/common/severity';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { localize } from 'vs/nls';
import Severity from 'vs/base/common/severity';
export interface IMarkerService {
_serviceBrand: any;
@@ -20,23 +21,68 @@ export interface IMarkerService {
remove(owner: string, resources: URI[]): void;
read(filter?: { owner?: string; resource?: URI; take?: number; }): IMarker[];
read(filter?: { owner?: string; resource?: URI; severities?: number, take?: number; }): IMarker[];
onMarkerChanged: Event<URI[]>;
}
/**
*
*/
export interface IRelatedInformation {
resource: URI;
message: string;
startLineNumber: number;
startColumn: number;
endLineNumber: number;
endColumn: number;
}
export enum MarkerSeverity {
Hint = 1,
Info = 2,
Warning = 4,
Error = 8,
}
export namespace MarkerSeverity {
export function compare(a: MarkerSeverity, b: MarkerSeverity): number {
return b - a;
}
const _displayStrings: { [value: number]: string; } = Object.create(null);
_displayStrings[MarkerSeverity.Error] = localize('sev.error', "Error");
_displayStrings[MarkerSeverity.Warning] = localize('sev.warning', "Warning");
_displayStrings[MarkerSeverity.Info] = localize('sev.info', "Info");
export function toString(a: MarkerSeverity): string {
return _displayStrings[a] || '';
}
export function fromSeverity(severity: Severity): MarkerSeverity {
switch (severity) {
case Severity.Error: return MarkerSeverity.Error;
case Severity.Warning: return MarkerSeverity.Warning;
case Severity.Info: return MarkerSeverity.Info;
case Severity.Ignore: return MarkerSeverity.Hint;
}
}
}
/**
* A structure defining a problem/warning/etc.
*/
export interface IMarkerData {
code?: string;
severity: Severity;
severity: MarkerSeverity;
message: string;
source?: string;
startLineNumber: number;
startColumn: number;
endLineNumber: number;
endColumn: number;
relatedInformation?: IRelatedInformation[];
}
export interface IResourceMarker {
@@ -47,7 +93,7 @@ export interface IResourceMarker {
export interface IMarker {
owner: string;
resource: URI;
severity: Severity;
severity: MarkerSeverity;
code?: string;
message: string;
source?: string;
@@ -55,6 +101,7 @@ export interface IMarker {
startColumn: number;
endLineNumber: number;
endColumn: number;
relatedInformation?: IRelatedInformation[];
}
export interface MarkerStatistics {
@@ -79,7 +126,7 @@ export namespace IMarkerData {
result.push(emptyString);
}
if (markerData.severity !== void 0 && markerData.severity !== null) {
result.push(Severity.toString(markerData.severity));
result.push(MarkerSeverity.toString(markerData.severity));
} else {
result.push(emptyString);
}

View File

@@ -5,14 +5,14 @@
'use strict';
import assert = require('assert');
import * as assert from 'assert';
import URI from 'vs/base/common/uri';
import markerService = require('vs/platform/markers/common/markerService');
import { IMarkerData } from 'vs/platform/markers/common/markers';
import * as markerService from 'vs/platform/markers/common/markerService';
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
function randomMarkerData(): IMarkerData {
function randomMarkerData(severity = MarkerSeverity.Error): IMarkerData {
return {
severity: 1,
severity,
message: Math.random().toString(16),
startLineNumber: 1,
startColumn: 1,
@@ -29,7 +29,7 @@ suite('Marker Service', () => {
service.changeAll('far', [{
resource: URI.parse('file:///c/test/file.cs'),
marker: randomMarkerData()
marker: randomMarkerData(MarkerSeverity.Error)
}]);
assert.equal(service.read().length, 1);
@@ -40,12 +40,18 @@ suite('Marker Service', () => {
service.changeAll('boo', [{
resource: URI.parse('file:///c/test/file.cs'),
marker: randomMarkerData()
marker: randomMarkerData(MarkerSeverity.Warning)
}]);
assert.equal(service.read().length, 2);
assert.equal(service.read({ owner: 'far' }).length, 1);
assert.equal(service.read({ owner: 'boo' }).length, 1);
assert.equal(service.read({ severities: MarkerSeverity.Error }).length, 1);
assert.equal(service.read({ severities: MarkerSeverity.Warning }).length, 1);
assert.equal(service.read({ severities: MarkerSeverity.Hint }).length, 0);
assert.equal(service.read({ severities: MarkerSeverity.Error | MarkerSeverity.Warning }).length, 2);
});

View File

@@ -5,13 +5,12 @@
'use strict';
import Severity from 'vs/base/common/severity';
import BaseSeverity from 'vs/base/common/severity';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IAction } from 'vs/base/common/actions';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
export import Severity = Severity;
export import Severity = BaseSeverity;
export const INotificationService = createDecorator<INotificationService>('notificationService');
@@ -44,7 +43,7 @@ export interface INotification {
* close automatically when invoking a secondary action.
*
* **Note:** If your intent is to show a message with actions to the user, consider
* the `IChoiceService` and `IConfirmationService` instead which are optimized for
* the `INotificationService.prompt()` method instead which are optimized for
* this usecase and much easier to use!
*/
actions?: INotificationActions;
@@ -150,6 +149,11 @@ export interface IPromptChoice {
run: () => void;
}
/**
* A service to bring up notifications and non-modal prompts.
*
* Note: use the `IDialogService` for a modal way to ask the user for input.
*/
export interface INotificationService {
_serviceBrand: any;
@@ -159,8 +163,10 @@ export interface INotificationService {
* can be used to control the notification afterwards.
*
* **Note:** If your intent is to show a message with actions to the user, consider
* the `IChoiceService` and `IConfirmationService` instead which are optimized for
* the `INotificationService.prompt()` method instead which are optimized for
* this usecase and much easier to use!
*
* @returns a handle on the notification to e.g. hide it or update message, buttons, etc.
*/
notify(notification: INotification): INotificationHandle;

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { INotificationService, INotificationHandle, NoOpNotification, Severity, INotification, IPromptChoice } from 'vs/platform/notification/common/notification';
export class TestNotificationService implements INotificationService {
public _serviceBrand: any;
private static readonly NO_OP: INotificationHandle = new NoOpNotification();
public info(message: string): INotificationHandle {
return this.notify({ severity: Severity.Info, message });
}
public warn(message: string): INotificationHandle {
return this.notify({ severity: Severity.Warning, message });
}
public error(error: string | Error): INotificationHandle {
return this.notify({ severity: Severity.Error, message: error });
}
public notify(notification: INotification): INotificationHandle {
return TestNotificationService.NO_OP;
}
public prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle {
return TestNotificationService.NO_OP;
}
}

View File

@@ -66,18 +66,21 @@ export enum ProgressLocation {
Explorer = 1,
Scm = 3,
Extensions = 5,
Window = 10
Window = 10,
Notification = 15
}
export interface IProgressOptions {
location: ProgressLocation;
title?: string;
tooltip?: string;
source?: string;
total?: number;
cancellable?: boolean;
}
export interface IProgressStep {
message?: string;
percentage?: number;
increment?: number;
}
export const IProgressService2 = createDecorator<IProgressService2>('progressService2');
@@ -86,5 +89,5 @@ export interface IProgressService2 {
_serviceBrand: any;
withProgress<P extends Thenable<R>, R=any>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => P): P;
withProgress<P extends Thenable<R>, R=any>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => P, onDidCancel?: () => void): P;
}

View File

@@ -6,7 +6,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import uri from 'vs/base/common/uri';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IQuickNavigateConfiguration, IAutoFocus, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -33,6 +33,7 @@ export interface IPickOpenEntry {
run?: (context: IEntryRunContext) => void;
action?: IAction;
payload?: any;
picked?: boolean;
}
export interface IPickOpenItem {
@@ -84,6 +85,11 @@ export interface IPickOptions {
* a context key to set when this picker is active
*/
contextKey?: string;
/**
* an optional flag to make this picker multi-select (honoured by extension API)
*/
canSelectMany?: boolean;
}
export interface IInputOptions {

View File

@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
import { IPickOptions, IPickOpenEntry, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen';
import { CancellationToken } from 'vs/base/common/cancellation';
export const IQuickInputService = createDecorator<IQuickInputService>('quickInputService');
export interface IQuickInputService {
_serviceBrand: any;
pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IPickOptions, token?: CancellationToken): TPromise<T[]>;
input(options?: IInputOptions, token?: CancellationToken): TPromise<string>;
focus(): void;
accept(): TPromise<void>;
cancel(): TPromise<void>;
}

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Types = require('vs/base/common/types');
import Assert = require('vs/base/common/assert');
import * as Types from 'vs/base/common/types';
import * as Assert from 'vs/base/common/assert';
export interface IRegistry {
@@ -27,7 +27,6 @@ export interface IRegistry {
* Returns the extension functions and properties defined by the specified key or null.
* @param id an extension identifier
*/
as(id: string): any;
as<T>(id: string): T;
}

View File

@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import assert = require('assert');
import Platform = require('vs/platform/registry/common/platform');
import Types = require('vs/base/common/types');
import * as assert from 'assert';
import * as Platform from 'vs/platform/registry/common/platform';
import * as Types from 'vs/base/common/types';
suite('Platform / Registry', () => {
@@ -21,8 +21,8 @@ suite('Platform / Registry', () => {
Platform.Registry.add('foo', { bar: true });
assert.ok(Platform.Registry.knows('foo'));
assert.ok(Platform.Registry.as('foo').bar);
assert.equal(Platform.Registry.as('foo').bar, true);
assert.ok(Platform.Registry.as<any>('foo').bar);
assert.equal(Platform.Registry.as<any>('foo').bar, true);
});
test('registry - knows, as', function () {

View File

@@ -11,7 +11,7 @@ import { IRequestOptions, IRequestContext, IRequestFunction, request } from 'vs/
import { getProxyAgent } from 'vs/base/node/proxy';
import { IRequestService, IHTTPConfiguration } from 'vs/platform/request/node/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from '../../log/common/log';
import { ILogService } from 'vs/platform/log/common/log';
/**
* This service exposes the `request` API, while using the global

View File

@@ -82,11 +82,12 @@ export enum QueryType {
/* __GDPR__FRAGMENT__
"IPatternInfo" : {
"pattern" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
"isRegExp": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"isWordMatch": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"isRegExp": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"isWordMatch": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"wordSeparators": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"isMultiline": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"isCaseSensitive": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"isMultiline": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"isCaseSensitive": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"isSmartCase": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
export interface IPatternInfo {

View File

@@ -92,7 +92,7 @@ export class StateService implements IStateService {
private fileStorage: FileStorage;
constructor( @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService) {
constructor(@IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService) {
this.fileStorage = new FileStorage(path.join(environmentService.userDataPath, 'storage.json'), error => logService.error(error));
}

View File

@@ -6,11 +6,10 @@
'use strict';
import * as assert from 'assert';
import os = require('os');
import path = require('path');
import extfs = require('vs/base/node/extfs');
import * as os from 'os';
import * as path from 'path';
import * as extfs from 'vs/base/node/extfs';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
import { writeFileAndFlushSync, mkdirp } from 'vs/base/node/extfs';
import { FileStorage } from 'vs/platform/state/node/stateService';
suite('StateService', () => {
@@ -21,9 +20,9 @@ suite('StateService', () => {
extfs.del(parentDir, os.tmpdir(), done);
});
test('Basics', done => {
return mkdirp(parentDir).then(() => {
writeFileAndFlushSync(storageFile, '');
test('Basics', () => {
return extfs.mkdirp(parentDir).then(() => {
extfs.writeFileAndFlushSync(storageFile, '');
let service = new FileStorage(storageFile, () => null);
@@ -49,8 +48,6 @@ suite('StateService', () => {
service.setItem('some.null.key', null);
assert.equal(service.getItem('some.null.key', 'some.default'), 'some.default');
done();
});
});
});

View File

@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import types = require('vs/base/common/types');
import errors = require('vs/base/common/errors');
import strings = require('vs/base/common/strings');
import * as types from 'vs/base/common/types';
import * as errors from 'vs/base/common/errors';
import * as strings from 'vs/base/common/strings';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import * as perf from 'vs/base/common/performance';

View File

@@ -9,25 +9,40 @@ import { binarySearch } from 'vs/base/common/arrays';
import { globals } from 'vs/base/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
import Errors = require('vs/base/common/errors');
import * as Errors from 'vs/base/common/errors';
import { safeStringify } from 'vs/base/common/objects';
/* __GDPR__FRAGMENT__
"ErrorEvent" : {
"stack": { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"message" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"filename" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"callstack": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"msg" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"file" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"line": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"column": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"uncaught_error_name": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"uncaught_error_msg": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"count": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true }
}
*/
interface ErrorEvent {
stack: string;
message?: string;
filename?: string;
callstack: string;
msg?: string;
file?: string;
line?: number;
column?: number;
error?: { name: string; message: string; };
uncaught_error_name?: string;
uncaught_error_msg?: string;
count?: number;
}
namespace ErrorEvent {
export function compare(a: ErrorEvent, b: ErrorEvent) {
if (a.stack < b.stack) {
if (a.callstack < b.callstack) {
return -1;
} else if (a.stack > b.stack) {
} else if (a.callstack > b.callstack) {
return 1;
}
return 0;
@@ -89,32 +104,35 @@ export default class ErrorTelemetry {
}
// work around behavior in workerServer.ts that breaks up Error.stack
let stack = Array.isArray(err.stack) ? err.stack.join('\n') : err.stack;
let message = err.message ? err.message : safeStringify(err);
let callstack = Array.isArray(err.stack) ? err.stack.join('\n') : err.stack;
let msg = err.message ? err.message : safeStringify(err);
// errors without a stack are not useful telemetry
if (!stack) {
if (!callstack) {
return;
}
this._enqueue({ message, stack });
this._enqueue({ msg, callstack });
}
private _onUncaughtError(message: string, filename: string, line: number, column?: number, err?: any): void {
private _onUncaughtError(msg: string, file: string, line: number, column?: number, err?: any): void {
let data: ErrorEvent = {
stack: message,
message,
filename,
callstack: msg,
msg,
file,
line,
column
};
if (err) {
let { name, message, stack } = err;
data.error = { name, message };
data.uncaught_error_name = name;
if (message) {
data.uncaught_error_msg = message;
}
if (stack) {
data.stack = Array.isArray(err.stack)
data.callstack = Array.isArray(err.stack)
? err.stack = err.stack.join('\n')
: err.stack;
}
@@ -145,16 +163,10 @@ export default class ErrorTelemetry {
for (let error of this._buffer) {
/* __GDPR__
"UnhandledError" : {
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"name": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"stack": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"id": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"line": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"column": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }
"${include}": [ "${ErrorEvent}" ]
}
*/
// __GDPR__TODO__ what's the complete set of properties?
this._telemetryService.publicLog('UnhandledError', error);
this._telemetryService.publicLog('UnhandledError', error, true);
}
this._buffer.length = 0;
}

View File

@@ -11,7 +11,6 @@ import { deepClone } from 'vs/base/common/objects';
/* __GDPR__FRAGMENT__
"IExperiments" : {
"deployToAzureQuickLink" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
export interface IExperiments {

View File

@@ -29,7 +29,7 @@ export interface ITelemetryService {
* Sends a telemetry event that has been privacy approved.
* Do not call this unless you have been given approval.
*/
publicLog(eventName: string, data?: ITelemetryData): TPromise<void>;
publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): TPromise<void>;
getTelemetryInfo(): TPromise<ITelemetryInfo>;

View File

@@ -37,7 +37,7 @@ export class TelemetryService implements ITelemetryService {
private _userOptIn: boolean;
private _disposables: IDisposable[] = [];
private _cleanupPatterns: [RegExp, string][] = [];
private _cleanupPatterns: RegExp[] = [];
constructor(
config: ITelemetryServiceConfig,
@@ -48,18 +48,11 @@ export class TelemetryService implements ITelemetryService {
this._piiPaths = config.piiPaths || [];
this._userOptIn = typeof config.userOptIn === 'undefined' ? true : config.userOptIn;
// static cleanup patterns for:
// #1 `file:///DANGEROUS/PATH/resources/app/Useful/Information`
// #2 // Any other file path that doesn't match the approved form above should be cleaned.
// #3 "Error: ENOENT; no such file or directory" is often followed with PII, clean it
this._cleanupPatterns.push(
[/file:\/\/\/.*?\/resources\/app\//gi, ''],
[/file:\/\/\/.*/gi, ''],
[/ENOENT: no such file or directory.*?\'([^\']+)\'/gi, 'ENOENT: no such file or directory']
);
// static cleanup pattern for: `file:///DANGEROUS/PATH/resources/app/Useful/Information`
this._cleanupPatterns = [/file:\/\/\/.*?\/resources\/app\//gi];
for (let piiPath of this._piiPaths) {
this._cleanupPatterns.push([new RegExp(escapeRegExpCharacters(piiPath), 'gi'), '']);
this._cleanupPatterns.push(new RegExp(escapeRegExpCharacters(piiPath), 'gi'));
}
if (this._configurationService) {
@@ -67,7 +60,7 @@ export class TelemetryService implements ITelemetryService {
this._configurationService.onDidChangeConfiguration(this._updateUserOptIn, this, this._disposables);
/* __GDPR__
"optInStatus" : {
"optIn" : { "classification": "SystemMetaData", "purpose": "BusinessInsight" }
"optIn" : { "classification": "SystemMetaData", "purpose": "BusinessInsight", "isMeasurement": true }
}
*/
this.publicLog('optInStatus', { optIn: this._userOptIn });
@@ -98,7 +91,7 @@ export class TelemetryService implements ITelemetryService {
this._disposables = dispose(this._disposables);
}
publicLog(eventName: string, data?: ITelemetryData): TPromise<any> {
publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): TPromise<any> {
// don't send events when the user is optout
if (!this._userOptIn) {
return TPromise.as(undefined);
@@ -112,7 +105,7 @@ export class TelemetryService implements ITelemetryService {
// (last) remove all PII from data
data = cloneAndChange(data, value => {
if (typeof value === 'string') {
return this._cleanupInfo(value);
return this._cleanupInfo(value, anonymizeFilePaths);
}
return undefined;
});
@@ -125,15 +118,41 @@ export class TelemetryService implements ITelemetryService {
});
}
private _cleanupInfo(stack: string): string {
private _cleanupInfo(stack: string, anonymizeFilePaths?: boolean): string {
let updatedStack = stack;
// sanitize with configured cleanup patterns
for (let tuple of this._cleanupPatterns) {
let [regexp, replaceValue] = tuple;
stack = stack.replace(regexp, replaceValue);
if (anonymizeFilePaths) {
const cleanUpIndexes: [number, number][] = [];
for (let regexp of this._cleanupPatterns) {
while (true) {
const result = regexp.exec(stack);
if (!result) {
break;
}
cleanUpIndexes.push([result.index, regexp.lastIndex]);
}
}
const nodeModulesRegex = /^[\\\/]?(node_modules|node_modules\.asar)[\\\/]/;
const fileRegex = /(file:\/\/)?([a-zA-Z]:(\\\\|\\|\/)|(\\\\|\\|\/))?([\w-\._]+(\\\\|\\|\/))+[\w-\._]*/g;
while (true) {
const result = fileRegex.exec(stack);
if (!result) {
break;
}
// Anoynimize user file paths that do not need to be retained or cleaned up.
if (!nodeModulesRegex.test(result[0]) && cleanUpIndexes.every(([x, y]) => result.index < x || result.index >= y)) {
updatedStack = updatedStack.slice(0, result.index) + result[0].replace(/./g, 'a') + updatedStack.slice(fileRegex.lastIndex);
}
}
}
return stack;
// sanitize with configured cleanup patterns
for (let regexp of this._cleanupPatterns) {
updatedStack = updatedStack.replace(regexp, '');
}
return updatedStack;
}
}

View File

@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable } from 'vs/base/common/lifecycle';
import { guessMimeTypes } from 'vs/base/common/mime';
import paths = require('vs/base/common/paths');
import * as paths from 'vs/base/common/paths';
import URI from 'vs/base/common/uri';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/common/keybinding';
@@ -42,7 +42,7 @@ export const NullAppender: ITelemetryAppender = { log: () => null };
"URIDescriptor" : {
"mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"path": { "classification": "CustomerContent", "purpose": "FeatureInsight" }
"path": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
export interface URIDescriptor {

View File

@@ -29,13 +29,11 @@ export function resolveCommonProperties(commit: string, version: string, machine
result['version'] = version;
// __GDPR__COMMON__ "common.platformVersion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.platformVersion'] = (os.release() || '').replace(/^(\d+)(\.\d+)?(\.\d+)?(.*)/, '$1$2$3');
// __GDPR__COMMON__ "common.osVersion" : { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
result['common.osVersion'] = result['common.platformVersion']; // TODO: Drop this after the move to Nova
// __GDPR__COMMON__ "common.platform" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.platform'] = Platform.Platform[Platform.platform];
// __GDPR__COMMON__ "common.nodePlatform" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// __GDPR__COMMON__ "common.nodePlatform" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.nodePlatform'] = process.platform;
// __GDPR__COMMON__ "common.nodeArch" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// __GDPR__COMMON__ "common.nodeArch" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.nodeArch'] = process.arch;
// {{SQL CARBON EDIT}}
@@ -50,12 +48,12 @@ export function resolveCommonProperties(commit: string, version: string, machine
get: () => new Date(),
enumerable: true
},
// __GDPR__COMMON__ "common.timesincesessionstart" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// __GDPR__COMMON__ "common.timesincesessionstart" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
'common.timesincesessionstart': {
get: () => Date.now() - startTime,
enumerable: true
},
// __GDPR__COMMON__ "common.sequence" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// __GDPR__COMMON__ "common.sequence" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
'common.sequence': {
get: () => seq++,
enumerable: true

View File

@@ -14,9 +14,9 @@ import * as Utils from 'sql/common/telemetryUtilities';
export function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string, version: string, machineId: string, installSourcePath: string): TPromise<{ [name: string]: string }> {
return resolveCommonProperties(commit, version, machineId, installSourcePath).then(result => {
// __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.version.shell'] = process.versions && (<any>process).versions['electron'];
// __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.version.renderer'] = process.versions && (<any>process).versions['chrome'];
// {{SQL CARBON EDIT}}
result['common.application.name'] = product.nameLong;

View File

@@ -8,13 +8,13 @@ import * as assert from 'assert';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import { TPromise } from 'vs/base/common/winjs.base';
import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties';
import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
import { del } from 'vs/base/node/extfs';
import { mkdirp } from 'vs/base/node/pfs';
import { timeout } from 'vs/base/common/async';
suite('Telemetry - common properties', function () {
const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'telemetryservice');
@@ -97,7 +97,7 @@ suite('Telemetry - common properties', function () {
assert.ok(value1 !== value2, 'timestamp');
value1 = props['common.timesincesessionstart'];
return TPromise.timeout(10).then(_ => {
return timeout(10).then(_ => {
value2 = props['common.timesincesessionstart'];
assert.ok(value1 !== value2, 'timesincesessionstart');
});

View File

@@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
import { NullAppender, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import Errors = require('vs/base/common/errors');
import * as Errors from 'vs/base/common/errors';
import * as sinon from 'sinon';
import { getConfigurationValue } from 'vs/platform/configuration/common/configuration';
@@ -50,6 +50,10 @@ class ErrorTestingSettings {
public noSuchFilePrefix: string;
public noSuchFileMessage: string;
public stack: string[];
public randomUserFile: string = 'a/path/that/doe_snt/con-tain/code/names.js';
public anonymizedRandomUserFile: string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
public nodeModulePathToRetain: string = 'node_modules/path/that/shouldbe/retained/names.js:14:15854';
public nodeModuleAsarPathToRetain: string = 'node_modules.asar/path/that/shouldbe/retained/names.js:14:12354';
constructor() {
this.personalInfo = 'DANGEROUS/PATH';
@@ -64,20 +68,20 @@ class ErrorTestingSettings {
this.noSuchFilePrefix = 'ENOENT: no such file or directory';
this.noSuchFileMessage = this.noSuchFilePrefix + ' \'' + this.personalInfo + '\'';
this.stack = ['at e._modelEvents (a/path/that/doesnt/contain/code/names.js:11:7309)',
' at t.AllWorkers (a/path/that/doesnt/contain/code/names.js:6:8844)',
' at e.(anonymous function) [as _modelEvents] (a/path/that/doesnt/contain/code/names.js:5:29552)',
' at Function.<anonymous> (a/path/that/doesnt/contain/code/names.js:6:8272)',
' at e.dispatch (a/path/that/doesnt/contain/code/names.js:5:26931)',
' at e.request (a/path/that/doesnt/contain/code/names.js:14:1745)',
' at t._handleMessage (another/path/that/doesnt/contain/code/names.js:14:17447)',
' at t._onmessage (another/path/that/doesnt/contain/code/names.js:14:16976)',
' at t.onmessage (another/path/that/doesnt/contain/code/names.js:14:15854)',
' at DedicatedWorkerGlobalScope.self.onmessage',
this.dangerousPathWithImportantInfo,
this.dangerousPathWithoutImportantInfo,
this.missingModelMessage,
this.noSuchFileMessage];
this.stack = [`at e._modelEvents (${this.randomUserFile}:11:7309)`,
` at t.AllWorkers (${this.randomUserFile}:6:8844)`,
` at e.(anonymous function) [as _modelEvents] (${this.randomUserFile}:5:29552)`,
` at Function.<anonymous> (${this.randomUserFile}:6:8272)`,
` at e.dispatch (${this.randomUserFile}:5:26931)`,
` at e.request (/${this.nodeModuleAsarPathToRetain})`,
` at t._handleMessage (${this.nodeModuleAsarPathToRetain})`,
` at t._onmessage (/${this.nodeModulePathToRetain})`,
` at t.onmessage (${this.nodeModulePathToRetain})`,
` at DedicatedWorkerGlobalScope.self.onmessage`,
this.dangerousPathWithImportantInfo,
this.dangerousPathWithoutImportantInfo,
this.missingModelMessage,
this.noSuchFileMessage];
}
}
@@ -224,7 +228,7 @@ suite('TelemetryService', () => {
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.equal(testAppender.getEventsCount(), 1);
assert.equal(testAppender.events[0].eventName, 'UnhandledError');
assert.equal(testAppender.events[0].data.message, 'This is a test.');
assert.equal(testAppender.events[0].data.msg, 'This is a test.');
errorTelemetry.dispose();
service.dispose();
@@ -254,7 +258,7 @@ suite('TelemetryService', () => {
//
// assert.equal(testAppender.getEventsCount(), 1);
// assert.equal(testAppender.events[0].eventName, 'UnhandledError');
// assert.equal(testAppender.events[0].data.message, 'This should get logged');
// assert.equal(testAppender.events[0].data.msg, 'This should get logged');
//
// service.dispose();
// } finally {
@@ -279,11 +283,33 @@ suite('TelemetryService', () => {
assert.equal(testAppender.getEventsCount(), 1);
assert.equal(testAppender.events[0].eventName, 'UnhandledError');
assert.equal(testAppender.events[0].data.message, 'Error Message');
assert.equal(testAppender.events[0].data.filename, 'file.js');
assert.equal(testAppender.events[0].data.msg, 'Error Message');
assert.equal(testAppender.events[0].data.file, 'file.js');
assert.equal(testAppender.events[0].data.line, 2);
assert.equal(testAppender.events[0].data.column, 42);
assert.equal(testAppender.events[0].data.error.message, 'test');
assert.equal(testAppender.events[0].data.uncaught_error_msg, 'test');
errorTelemetry.dispose();
service.dispose();
}));
test('Error Telemetry removes PII from filename with spaces', sinon.test(function (this: any) {
let errorStub = sinon.stub();
window.onerror = errorStub;
let settings = new ErrorTestingSettings();
let testAppender = new TestTelemetryAppender();
let service = new TelemetryService({ appender: testAppender }, undefined);
const errorTelemetry = new ErrorTelemetry(service);
let personInfoWithSpaces = settings.personalInfo.slice(0, 2) + ' ' + settings.personalInfo.slice(2);
let dangerousFilenameError: any = new Error('dangerousFilename');
dangerousFilenameError.stack = settings.stack;
(<any>window.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo.replace(settings.personalInfo, personInfoWithSpaces) + '/test.js', 2, 42, dangerousFilenameError);
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.equal(errorStub.callCount, 1);
assert.equal(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo.replace(settings.personalInfo, personInfoWithSpaces)), -1);
assert.equal(testAppender.events[0].data.file, settings.importantInfo + '/test.js');
errorTelemetry.dispose();
service.dispose();
@@ -303,7 +329,7 @@ suite('TelemetryService', () => {
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.equal(errorStub.callCount, 1);
assert.equal(testAppender.events[0].data.filename.indexOf(settings.dangerousPathWithImportantInfo), -1);
assert.equal(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1);
dangerousFilenameError = new Error('dangerousFilename');
dangerousFilenameError.stack = settings.stack;
@@ -311,8 +337,8 @@ suite('TelemetryService', () => {
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.equal(errorStub.callCount, 2);
assert.equal(testAppender.events[0].data.filename.indexOf(settings.dangerousPathWithImportantInfo), -1);
assert.equal(testAppender.events[0].data.filename, settings.importantInfo + '/test.js');
assert.equal(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1);
assert.equal(testAppender.events[0].data.file, settings.importantInfo + '/test.js');
errorTelemetry.dispose();
service.dispose();
@@ -332,13 +358,13 @@ suite('TelemetryService', () => {
Errors.onUnexpectedError(dangerousPathWithoutImportantInfoError);
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();
@@ -363,12 +389,12 @@ suite('TelemetryService', () => {
assert.equal(errorStub.callCount, 1);
// Test that no file information remains, esp. personal info
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();
@@ -392,14 +418,14 @@ suite('TelemetryService', () => {
Errors.onUnexpectedError(dangerousPathWithImportantInfoError);
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.notEqual(testAppender.events[0].data.message.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();
@@ -424,19 +450,75 @@ suite('TelemetryService', () => {
assert.equal(errorStub.callCount, 1);
// Test that important information remains but personal info does not
assert.notEqual(testAppender.events[0].data.message.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();
}));
test('Unexpected Error Telemetry removes PII but preserves Code file path with node modules', sinon.test(function (this: any) {
let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
Errors.setUnexpectedErrorHandler(() => { });
try {
let settings = new ErrorTestingSettings();
let testAppender = new TestTelemetryAppender();
let service = new TelemetryService({ appender: testAppender }, undefined);
const errorTelemetry = new ErrorTelemetry(service);
let dangerousPathWithImportantInfoError: any = new Error(settings.dangerousPathWithImportantInfo);
dangerousPathWithImportantInfoError.stack = settings.stack;
Errors.onUnexpectedError(dangerousPathWithImportantInfoError);
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.notEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModuleAsarPathToRetain), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModulePathToRetain), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModuleAsarPathToRetain), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModulePathToRetain), -1);
errorTelemetry.dispose();
service.dispose();
}
finally {
Errors.setUnexpectedErrorHandler(origErrorHandler);
}
}));
test('Uncaught Error Telemetry removes PII but preserves Code file path', sinon.test(function (this: any) {
let errorStub = sinon.stub();
window.onerror = errorStub;
let settings = new ErrorTestingSettings();
let testAppender = new TestTelemetryAppender();
let service = new TelemetryService({ appender: testAppender }, undefined);
const errorTelemetry = new ErrorTelemetry(service);
let dangerousPathWithImportantInfoError: any = new Error('dangerousPathWithImportantInfo');
dangerousPathWithImportantInfoError.stack = settings.stack;
(<any>window.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError);
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.equal(errorStub.callCount, 1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModuleAsarPathToRetain), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf('(' + settings.nodeModulePathToRetain), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModuleAsarPathToRetain), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf('(/' + settings.nodeModulePathToRetain), -1);
errorTelemetry.dispose();
service.dispose();
}));
test('Unexpected Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(function (this: any) {
let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
@@ -455,14 +537,14 @@ suite('TelemetryService', () => {
Errors.onUnexpectedError(dangerousPathWithImportantInfoError);
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.notEqual(testAppender.events[0].data.message.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();
@@ -487,14 +569,14 @@ suite('TelemetryService', () => {
assert.equal(errorStub.callCount, 1);
// Test that important information remains but personal info does not
assert.notEqual(testAppender.events[0].data.message.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.importantInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();
@@ -519,14 +601,14 @@ suite('TelemetryService', () => {
Errors.onUnexpectedError(missingModelError);
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.notEqual(testAppender.events[0].data.message.indexOf(settings.missingModelPrefix), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.missingModelPrefix), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.missingModelPrefix), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.missingModelPrefix), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();
@@ -551,14 +633,14 @@ suite('TelemetryService', () => {
assert.equal(errorStub.callCount, 1);
// Test that no file information remains, but this particular
// error message does (Received model events for missing model)
assert.notEqual(testAppender.events[0].data.message.indexOf(settings.missingModelPrefix), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.missingModelPrefix), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.missingModelPrefix), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.missingModelPrefix), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();
@@ -583,14 +665,14 @@ suite('TelemetryService', () => {
Errors.onUnexpectedError(noSuchFileError);
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
assert.notEqual(testAppender.events[0].data.message.indexOf(settings.noSuchFilePrefix), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.noSuchFilePrefix), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.noSuchFilePrefix), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.noSuchFilePrefix), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();
@@ -620,14 +702,14 @@ suite('TelemetryService', () => {
// Test that no file information remains, but this particular
// error message does (ENOENT: no such file or directory)
Errors.onUnexpectedError(noSuchFileError);
assert.notEqual(testAppender.events[0].data.message.indexOf(settings.noSuchFilePrefix), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.noSuchFilePrefix), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1);
assert.equal(testAppender.events[0].data.stack.split('\n').length, settings.stack.length);
assert.notEqual(testAppender.events[0].data.msg.indexOf(settings.noSuchFilePrefix), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.msg.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.noSuchFilePrefix), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.personalInfo), -1);
assert.equal(testAppender.events[0].data.callstack.indexOf(settings.filePrefix), -1);
assert.notEqual(testAppender.events[0].data.callstack.indexOf(settings.stack[4].replace(settings.randomUserFile, settings.anonymizedRandomUserFile)), -1);
assert.equal(testAppender.events[0].data.callstack.split('\n').length, settings.stack.length);
errorTelemetry.dispose();
service.dispose();

View File

@@ -4,12 +4,12 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import platform = require('vs/platform/registry/common/platform');
import * as platform from 'vs/platform/registry/common/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Color, RGBA } from 'vs/base/common/color';
import { ITheme } from 'vs/platform/theme/common/themeService';
import nls = require('vs/nls');
import * as nls from 'vs/nls';
// ------ API types
@@ -86,10 +86,14 @@ class ColorRegistry implements IColorRegistry {
this.colorsById = {};
}
public registerColor(id: string, defaults: ColorDefaults, description: string, needsTransparency = false): ColorIdentifier {
public registerColor(id: string, defaults: ColorDefaults, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier {
let colorContribution = { id, description, defaults, needsTransparency };
this.colorsById[id] = colorContribution;
this.colorSchema.properties[id] = { type: 'string', description, format: 'color-hex', default: '#ff0000' };
let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', default: '#ff0000' };
if (deprecationMessage) {
propertySchema.deprecationMessage = deprecationMessage;
}
this.colorSchema.properties[id] = propertySchema;
this.colorReferenceSchema.enum.push(id);
this.colorReferenceSchema.enumDescriptions.push(description);
return id;
@@ -134,8 +138,8 @@ class ColorRegistry implements IColorRegistry {
const colorRegistry = new ColorRegistry();
platform.Registry.add(Extensions.ColorContribution, colorRegistry);
export function registerColor(id: string, defaults: ColorDefaults, description: string, needsTransparency?: boolean): ColorIdentifier {
return colorRegistry.registerColor(id, defaults, description, needsTransparency);
export function registerColor(id: string, defaults: ColorDefaults, description: string, needsTransparency?: boolean, deprecationMessage?: string): ColorIdentifier {
return colorRegistry.registerColor(id, defaults, description, needsTransparency, deprecationMessage);
}
export function getColorRegistry(): IColorRegistry {
@@ -178,7 +182,7 @@ export const inputPlaceholderForeground = registerColor('input.placeholderForegr
export const inputValidationInfoBackground = registerColor('inputValidation.infoBackground', { dark: '#063B49', light: '#D6ECF2', hc: Color.black }, nls.localize('inputValidationInfoBackground', "Input validation background color for information severity."));
export const inputValidationInfoBorder = registerColor('inputValidation.infoBorder', { dark: '#007acc', light: '#007acc', hc: contrastBorder }, nls.localize('inputValidationInfoBorder', "Input validation border color for information severity."));
export const inputValidationWarningBackground = registerColor('inputValidation.warningBackground', { dark: '#352A05', light: '#F6F5D2', hc: Color.black }, nls.localize('inputValidationWarningBackground', "Input validation background color for information warning."));
export const inputValidationWarningBackground = registerColor('inputValidation.warningBackground', { dark: '#352A05', light: '#F6F5D2', hc: Color.black }, nls.localize('inputValidationWarningBackground', "Input validation background color for warning severity."));
export const inputValidationWarningBorder = registerColor('inputValidation.warningBorder', { dark: '#B89500', light: '#B89500', hc: contrastBorder }, nls.localize('inputValidationWarningBorder', "Input validation border color for warning severity."));
export const inputValidationErrorBackground = registerColor('inputValidation.errorBackground', { dark: '#5A1D1D', light: '#F2DEDE', hc: Color.black }, nls.localize('inputValidationErrorBackground', "Input validation background color for error severity."));
export const inputValidationErrorBorder = registerColor('inputValidation.errorBorder', { dark: '#BE1100', light: '#BE1100', hc: contrastBorder }, nls.localize('inputValidationErrorBorder', "Input validation border color for error severity."));
@@ -195,7 +199,6 @@ export const listActiveSelectionForeground = registerColor('list.activeSelection
export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#3F3F46', light: '#CCCEDB', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
export const listInactiveSelectionForeground = registerColor('list.inactiveSelectionForeground', { dark: null, light: null, hc: null }, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', { dark: '#313135', light: '#d8dae6', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
export const listInactiveFocusForeground = registerColor('list.inactiveFocusForeground', { dark: null, light: null, hc: null }, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
export const listHoverBackground = registerColor('list.hoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hc: null }, nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse."));
export const listHoverForeground = registerColor('list.hoverForeground', { dark: null, light: null, hc: null }, nls.localize('listHoverForeground', "List/Tree foreground when hovering over items using the mouse."));
export const listDropBackground = registerColor('list.dropBackground', { dark: listFocusBackground, light: listFocusBackground, hc: null }, nls.localize('listDropBackground', "List/Tree drag and drop background when moving items around using the mouse."));
@@ -253,10 +256,10 @@ export const editorSelectionHighlightBorder = registerColor('editor.selectionHig
*/
export const editorFindMatch = registerColor('editor.findMatchBackground', { light: '#A8AC94', dark: '#515C6A', hc: null }, nls.localize('editorFindMatch', "Color of the current search match."));
export const editorFindMatchHighlight = registerColor('editor.findMatchHighlightBackground', { light: '#EA5C0055', dark: '#EA5C0055', hc: null }, nls.localize('findMatchHighlight', "Color of the other search matches. The color must not be opaque to not hide underlying decorations."), true);
export const editorFindRangeHighlight = registerColor('editor.findRangeHighlightBackground', { dark: '#3a3d4166', light: '#b4b4b44d', hc: null }, nls.localize('findRangeHighlight', "Color the range limiting the search. The color must not be opaque to not hide underlying decorations."), true);
export const editorFindRangeHighlight = registerColor('editor.findRangeHighlightBackground', { dark: '#3a3d4166', light: '#b4b4b44d', hc: null }, nls.localize('findRangeHighlight', "Color of the range limiting the search. The color must not be opaque to not hide underlying decorations."), true);
export const editorFindMatchBorder = registerColor('editor.findMatchBorder', { light: null, dark: null, hc: activeContrastBorder }, nls.localize('editorFindMatchBorder', "Border color of the current search match."));
export const editorFindMatchHighlightBorder = registerColor('editor.findMatchHighlightBorder', { light: null, dark: null, hc: activeContrastBorder }, nls.localize('findMatchHighlightBorder', "Border color of the other search matches."));
export const editorFindRangeHighlightBorder = registerColor('editor.findRangeHighlightBorder', { dark: null, light: null, hc: transparent(activeContrastBorder, 0.4) }, nls.localize('findRangeHighlightBorder', "Border color the range limiting the search. The color must not be opaque to not hide underlying decorations."), true);
export const editorFindRangeHighlightBorder = registerColor('editor.findRangeHighlightBorder', { dark: null, light: null, hc: transparent(activeContrastBorder, 0.4) }, nls.localize('findRangeHighlightBorder', "Border color of the range limiting the search. The color must not be opaque to not hide underlying decorations."), true);
/**
* Editor hover
@@ -298,7 +301,7 @@ export const mergeCurrentContentBackground = registerColor('merge.currentContent
export const mergeIncomingHeaderBackground = registerColor('merge.incomingHeaderBackground', { dark: incomingBaseColor, light: incomingBaseColor, hc: null }, nls.localize('mergeIncomingHeaderBackground', 'Incoming header background in inline merge-conflicts. The color must not be opaque to not hide underlying decorations.'), true);
export const mergeIncomingContentBackground = registerColor('merge.incomingContentBackground', { dark: transparent(mergeIncomingHeaderBackground, contentTransparency), light: transparent(mergeIncomingHeaderBackground, contentTransparency), hc: transparent(mergeIncomingHeaderBackground, contentTransparency) }, nls.localize('mergeIncomingContentBackground', 'Incoming content background in inline merge-conflicts. The color must not be opaque to not hide underlying decorations.'), true);
export const mergeCommonHeaderBackground = registerColor('merge.commonHeaderBackground', { dark: commonBaseColor, light: commonBaseColor, hc: null }, nls.localize('mergeCommonHeaderBackground', 'Common ancestor header background in inline merge-conflicts. The color must not be opaque to not hide underlying decorations.'), true);
export const mergeCommonContentBackground = registerColor('merge.commonContentBackground', { dark: transparent(mergeCommonHeaderBackground, contentTransparency), light: transparent(mergeCommonHeaderBackground, contentTransparency), hc: transparent(mergeCommonHeaderBackground, contentTransparency) }, nls.localize('mergeCommonContentBackground', 'Common ancester content background in inline merge-conflicts. The color must not be opaque to not hide underlying decorations.'), true);
export const mergeCommonContentBackground = registerColor('merge.commonContentBackground', { dark: transparent(mergeCommonHeaderBackground, contentTransparency), light: transparent(mergeCommonHeaderBackground, contentTransparency), hc: transparent(mergeCommonHeaderBackground, contentTransparency) }, nls.localize('mergeCommonContentBackground', 'Common ancestor content background in inline merge-conflicts. The color must not be opaque to not hide underlying decorations.'), true);
export const mergeBorder = registerColor('merge.border', { dark: null, light: null, hc: '#C3DF6F' }, nls.localize('mergeBorder', 'Border color on headers and the splitter in inline merge-conflicts.'));
@@ -307,9 +310,9 @@ export const overviewRulerIncomingContentForeground = registerColor('editorOverv
export const overviewRulerCommonContentForeground = registerColor('editorOverviewRuler.commonContentForeground', { dark: transparent(mergeCommonHeaderBackground, rulerTransparency), light: transparent(mergeCommonHeaderBackground, rulerTransparency), hc: mergeBorder }, nls.localize('overviewRulerCommonContentForeground', 'Common ancestor overview ruler foreground for inline merge-conflicts.'));
const findMatchColorDefault = new Color(new RGBA(246, 185, 77, 0.7));
export const overviewRulerFindMatchForeground = registerColor('editorOverviewRuler.findMatchForeground', { dark: findMatchColorDefault, light: findMatchColorDefault, hc: findMatchColorDefault }, nls.localize('overviewRulerFindMatchForeground', 'Overview ruler marker color for find matches.'));
export const overviewRulerFindMatchForeground = registerColor('editorOverviewRuler.findMatchForeground', { dark: findMatchColorDefault, light: findMatchColorDefault, hc: findMatchColorDefault }, nls.localize('overviewRulerFindMatchForeground', 'Overview ruler marker color for find matches. The color must not be opaque to not hide underlying decorations.'), true);
export const overviewRulerSelectionHighlightForeground = registerColor('editorOverviewRuler.selectionHighlightForeground', { dark: '#A0A0A0', light: '#A0A0A0', hc: '#A0A0A0' }, nls.localize('overviewRulerSelectionHighlightForeground', 'Overview ruler marker color for selection highlights.'));
export const overviewRulerSelectionHighlightForeground = registerColor('editorOverviewRuler.selectionHighlightForeground', { dark: '#A0A0A0CC', light: '#A0A0A0CC', hc: '#A0A0A0CC' }, nls.localize('overviewRulerSelectionHighlightForeground', 'Overview ruler marker color for selection highlights. The color must not be opaque to not hide underlying decorations.'), true);
// ----- color functions

View File

@@ -6,10 +6,12 @@
'use strict';
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusForeground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, lighten, badgeBackground, badgeForeground, progressBarBackground } from 'vs/platform/theme/common/colorRegistry';
import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, lighten, badgeBackground, badgeForeground, progressBarBackground } from 'vs/platform/theme/common/colorRegistry';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
export type styleFn = (colors: { [name: string]: ColorIdentifier }) => void;
export type styleFn = (colors: { [name: string]: Color }) => void;
export interface IStyleOverrides {
[color: string]: ColorIdentifier;
@@ -23,17 +25,27 @@ export interface IColorMapping {
[optionsKey: string]: ColorIdentifier | ColorFunction | undefined;
}
export function attachStyler<T extends IColorMapping>(themeService: IThemeService, optionsMapping: T, widgetOrCallback: IThemable | styleFn): IDisposable {
function applyStyles(theme: ITheme): void {
const styles = Object.create(null);
for (let key in optionsMapping) {
const value = optionsMapping[key as string];
if (typeof value === 'string') {
styles[key] = theme.getColor(value);
} else if (typeof value === 'function') {
styles[key] = value(theme);
}
export interface IComputedStyles {
[color: string]: Color;
}
export function computeStyles(theme: ITheme, styleMap: IColorMapping): IComputedStyles {
const styles = Object.create(null) as IComputedStyles;
for (let key in styleMap) {
const value = styleMap[key as string];
if (typeof value === 'string') {
styles[key] = theme.getColor(value);
} else if (typeof value === 'function') {
styles[key] = value(theme);
}
}
return styles;
}
export function attachStyler<T extends IColorMapping>(themeService: IThemeService, styleMap: T, widgetOrCallback: IThemable | styleFn): IDisposable {
function applyStyles(theme: ITheme): void {
const styles = computeStyles(themeService.getTheme(), styleMap);
if (typeof widgetOrCallback === 'function') {
widgetOrCallback(styles);
@@ -172,7 +184,6 @@ export function attachQuickOpenStyler(widget: IThemable, themeService: IThemeSer
listInactiveSelectionBackground: (style && style.listInactiveSelectionBackground) || listInactiveSelectionBackground,
listInactiveSelectionForeground: (style && style.listInactiveSelectionForeground) || listInactiveSelectionForeground,
listInactiveFocusBackground: (style && style.listInactiveFocusBackground) || listInactiveFocusBackground,
listInactiveFocusForeground: (style && style.listInactiveFocusForeground) || listInactiveFocusForeground,
listHoverBackground: (style && style.listHoverBackground) || listHoverBackground,
listHoverForeground: (style && style.listHoverForeground) || listHoverForeground,
listDropBackground: (style && style.listDropBackground) || listDropBackground,
@@ -192,7 +203,6 @@ export interface IListStyleOverrides extends IStyleOverrides {
listInactiveSelectionBackground?: ColorIdentifier;
listInactiveSelectionForeground?: ColorIdentifier;
listInactiveFocusBackground?: ColorIdentifier;
listInactiveFocusForeground?: ColorIdentifier;
listHoverBackground?: ColorIdentifier;
listHoverForeground?: ColorIdentifier;
listDropBackground?: ColorIdentifier;
@@ -202,28 +212,28 @@ export interface IListStyleOverrides extends IStyleOverrides {
listHoverOutline?: ColorIdentifier;
}
export function attachListStyler(widget: IThemable, themeService: IThemeService, style?: IListStyleOverrides): IDisposable {
return attachStyler(themeService, {
listFocusBackground: (style && style.listFocusBackground) || listFocusBackground,
listFocusForeground: (style && style.listFocusForeground) || listFocusForeground,
listActiveSelectionBackground: (style && style.listActiveSelectionBackground) || lighten(listActiveSelectionBackground, 0.1),
listActiveSelectionForeground: (style && style.listActiveSelectionForeground) || listActiveSelectionForeground,
listFocusAndSelectionBackground: style && style.listFocusAndSelectionBackground || listActiveSelectionBackground,
listFocusAndSelectionForeground: (style && style.listFocusAndSelectionForeground) || listActiveSelectionForeground,
listInactiveSelectionBackground: (style && style.listInactiveSelectionBackground) || listInactiveSelectionBackground,
listInactiveSelectionForeground: (style && style.listInactiveSelectionForeground) || listInactiveSelectionForeground,
listInactiveFocusBackground: (style && style.listInactiveFocusBackground) || listInactiveFocusBackground,
listInactiveFocusForeground: (style && style.listInactiveFocusForeground) || listInactiveFocusForeground,
listHoverBackground: (style && style.listHoverBackground) || listHoverBackground,
listHoverForeground: (style && style.listHoverForeground) || listHoverForeground,
listDropBackground: (style && style.listDropBackground) || listDropBackground,
listFocusOutline: (style && style.listFocusOutline) || activeContrastBorder,
listSelectionOutline: (style && style.listSelectionOutline) || activeContrastBorder,
listHoverOutline: (style && style.listHoverOutline) || activeContrastBorder,
listInactiveFocusOutline: style && style.listInactiveFocusOutline // not defined by default, only opt-in
} as IListStyleOverrides, widget);
export function attachListStyler(widget: IThemable, themeService: IThemeService, overrides?: IListStyleOverrides): IDisposable {
return attachStyler(themeService, mixin(overrides || Object.create(null), defaultListStyles, false) as IListStyleOverrides, widget);
}
export const defaultListStyles: IColorMapping = {
listFocusBackground: listFocusBackground,
listFocusForeground: listFocusForeground,
listActiveSelectionBackground: lighten(listActiveSelectionBackground, 0.1),
listActiveSelectionForeground: listActiveSelectionForeground,
listFocusAndSelectionBackground: listActiveSelectionBackground,
listFocusAndSelectionForeground: listActiveSelectionForeground,
listInactiveSelectionBackground: listInactiveSelectionBackground,
listInactiveSelectionForeground: listInactiveSelectionForeground,
listInactiveFocusBackground: listInactiveFocusBackground,
listHoverBackground: listHoverBackground,
listHoverForeground: listHoverForeground,
listDropBackground: listDropBackground,
listFocusOutline: activeContrastBorder,
listSelectionOutline: activeContrastBorder,
listHoverOutline: activeContrastBorder
};
export interface IButtonStyleOverrides extends IStyleOverrides {
buttonForeground?: ColorIdentifier;
buttonBackground?: ColorIdentifier;

View File

@@ -7,9 +7,9 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Color } from 'vs/base/common/color';
import { IDisposable } from 'vs/base/common/lifecycle';
import platform = require('vs/platform/registry/common/platform');
import * as platform from 'vs/platform/registry/common/platform';
import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
export const IThemeService = createDecorator<IThemeService>('themeService');
@@ -100,7 +100,7 @@ export interface IThemingRegistry {
class ThemingRegistry implements IThemingRegistry {
private themingParticipants: IThemingParticipant[] = [];
private onThemingParticipantAddedEmitter: Emitter<IThemingParticipant>;
private readonly onThemingParticipantAddedEmitter: Emitter<IThemingParticipant>;
constructor() {
this.themingParticipants = [];

View File

@@ -5,7 +5,7 @@
'use strict';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IThemeService, ITheme, DARK } from 'vs/platform/theme/common/themeService';
import { Color } from 'vs/base/common/color';

View File

@@ -5,7 +5,7 @@
'use strict';
import Event, { NodeEventEmitter } from 'vs/base/common/event';
import { Event, NodeEventEmitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';

View File

@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IUpdateService, State } from './update';

View File

@@ -5,7 +5,7 @@
'use strict';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { Throttler } from 'vs/base/common/async';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';

View File

@@ -7,7 +7,7 @@
import * as electron from 'electron';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Event, { fromNodeEventEmitter } from 'vs/base/common/event';
import { Event, fromNodeEventEmitter } from 'vs/base/common/event';
import { memoize } from 'vs/base/common/decorators';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
@@ -94,7 +94,7 @@ export class DarwinUpdateService extends AbstractUpdateService {
/* __GDPR__
"update:notAvailable" : {
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('update:notAvailable', { explicit: !!this.state.context });

View File

@@ -53,7 +53,7 @@ export class LinuxUpdateService extends AbstractUpdateService {
if (!update || !update.url || !update.version || !update.productVersion) {
/* __GDPR__
"update:notAvailable" : {
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('update:notAvailable', { explicit: !!context });
@@ -68,7 +68,7 @@ export class LinuxUpdateService extends AbstractUpdateService {
/* __GDPR__
"update:notAvailable" : {
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('update:notAvailable', { explicit: !!context });

View File

@@ -90,7 +90,7 @@ export class Win32UpdateService extends AbstractUpdateService {
if (!update || !update.url || !update.version) {
/* __GDPR__
"update:notAvailable" : {
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('update:notAvailable', { explicit: !!context });
@@ -135,7 +135,7 @@ export class Win32UpdateService extends AbstractUpdateService {
this.logService.error(err);
/* __GDPR__
"update:notAvailable" : {
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('update:notAvailable', { explicit: !!context });

View File

@@ -6,11 +6,10 @@
'use strict';
import * as nls from 'vs/nls';
import product from 'vs/platform/node/product';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigurationExtensions.Configuration);
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'update',
'order': 15,
@@ -21,11 +20,13 @@ configurationRegistry.registerConfiguration({
'type': 'string',
'enum': ['none', 'default'],
'default': 'default',
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('updateChannel', "Configure whether you receive automatic updates from an update channel. Requires a restart after change.")
},
'update.enableWindowsBackgroundUpdates': {
'type': 'boolean',
'default': product.quality === 'insider',
'default': true,
'scope': ConfigurationScope.APPLICATION,
'description': nls.localize('enableWindowsBackgroundUpdates', "Enables Windows background updates.")
}
}

View File

@@ -5,15 +5,21 @@
'use strict';
import Event from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
export const ID = 'urlService';
export const IURLService = createDecorator<IURLService>(ID);
export interface IURLHandler {
handleURL(uri: URI): TPromise<boolean>;
}
export interface IURLService {
_serviceBrand: any;
open(url: string): void;
onOpenURL: Event<URI>;
open(url: URI): TPromise<boolean>;
registerHandler(handler: IURLHandler): IDisposable;
}

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