mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-01 01:25:38 -05:00
Merge VS Code 1.23.1 (#1520)
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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++);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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[] } {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 */
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
46
src/vs/platform/dialogs/common/dialogIpc.ts
Normal file
46
src/vs/platform/dialogs/common/dialogIpc.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
287
src/vs/platform/driver/common/driver.ts
Normal file
287
src/vs/platform/driver/common/driver.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
204
src/vs/platform/driver/electron-browser/driver.ts
Normal file
204
src/vs/platform/driver/electron-browser/driver.ts
Normal 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]);
|
||||
}
|
||||
218
src/vs/platform/driver/electron-main/driver.ts
Normal file
218
src/vs/platform/driver/electron-main/driver.ts
Normal 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]);
|
||||
}
|
||||
17
src/vs/platform/driver/node/driver.ts
Normal file
17
src/vs/platform/driver/node/driver.ts
Normal 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 };
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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).")
|
||||
|
||||
@@ -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, '');
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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[]> {
|
||||
|
||||
@@ -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}"
|
||||
]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
23
src/vs/platform/quickinput/common/quickInput.ts
Normal file
23
src/vs/platform/quickinput/common/quickInput.ts
Normal 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>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import { deepClone } from 'vs/base/common/objects';
|
||||
|
||||
/* __GDPR__FRAGMENT__
|
||||
"IExperiments" : {
|
||||
"deployToAzureQuickLink" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
export interface IExperiments {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user