Merge VS Code 1.31.1 (#4283)

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

View File

@@ -83,14 +83,14 @@ export function fillInContextMenuActions(menu: IMenu, options: IMenuActionOption
fillInActions(groups, target, getAlternativeActions, isPrimaryGroup);
}
export function fillInActionBarActions(menu: IMenu, options: IMenuActionOptions, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): void {
export function fillInActionBarActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): void {
const groups = menu.getActions(options);
// Action bars handle alternative actions on their own so the alternative actions should be ignored
fillInActions(groups, target, false, isPrimaryGroup);
}
// {{SQL CARBON EDIT}} add export modifier
export function fillInActions(groups: [string, (MenuItemAction | SubmenuItemAction)[]][], target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, getAlternativeActions, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
export function fillInActions(groups: [string, Array<MenuItemAction | SubmenuItemAction>][], target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, getAlternativeActions, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
for (let tuple of groups) {
let [group, actions] = tuple;
if (getAlternativeActions) {
@@ -114,7 +114,7 @@ export function fillInActions(groups: [string, (MenuItemAction | SubmenuItemActi
}
export function createActionItem(action: IAction, keybindingService: IKeybindingService, notificationService: INotificationService, contextMenuService: IContextMenuService): ActionItem {
export function createActionItem(action: IAction, keybindingService: IKeybindingService, notificationService: INotificationService, contextMenuService: IContextMenuService): ActionItem | undefined {
if (action instanceof MenuItemAction) {
return new MenuItemActionItem(action, keybindingService, notificationService, contextMenuService);
}
@@ -128,7 +128,7 @@ export class MenuItemActionItem extends ActionItem {
static readonly ICON_PATH_TO_CSS_RULES: Map<string /* path*/, string /* CSS rule */> = new Map<string, string>();
private _wantsAltCommand: boolean;
private _itemClassDispose: IDisposable;
private _itemClassDispose?: IDisposable;
private readonly _altKey: AlternativeKeyEmitter;
constructor(
@@ -213,7 +213,9 @@ export class MenuItemActionItem extends ActionItem {
updateClass(): void {
if (this.options.icon) {
if (this._commandAction !== this._action) {
this._updateItemClass(this._action.alt.item);
if (this._action.alt) {
this._updateItemClass(this._action.alt.item);
}
} else if ((<MenuItemAction>this._action).alt) {
this._updateItemClass(this._action.item);
}
@@ -230,7 +232,7 @@ export class MenuItemActionItem extends ActionItem {
const iconPathMapKey = item.iconLocation.dark.toString();
if (MenuItemActionItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
iconClass = MenuItemActionItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey);
iconClass = MenuItemActionItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!;
} else {
iconClass = ids.nextId();
createCSSRule(`.icon.${iconClass}`, `background-image: url("${(item.iconLocation.light || item.iconLocation.dark).toString()}")`);

View File

@@ -108,8 +108,8 @@ export interface IMenuActionOptions {
}
export interface IMenu extends IDisposable {
onDidChange: Event<IMenu>;
getActions(options?: IMenuActionOptions): [string, (MenuItemAction | SubmenuItemAction)[]][];
readonly onDidChange: Event<IMenu | undefined>;
getActions(options?: IMenuActionOptions): [string, Array<MenuItemAction | SubmenuItemAction>][];
}
export const IMenuService = createDecorator<IMenuService>('menuService');
@@ -122,12 +122,12 @@ export interface IMenuService {
}
export interface IMenuRegistry {
addCommand(userCommand: ICommandAction): boolean;
addCommand(userCommand: ICommandAction): IDisposable;
getCommand(id: string): ICommandAction;
getCommands(): ICommandsMap;
appendMenuItem(menu: MenuId, item: IMenuItem | ISubmenuItem): IDisposable;
getMenuItems(loc: MenuId): (IMenuItem | ISubmenuItem)[];
onDidChangeMenu: Event<MenuId>;
getMenuItems(loc: MenuId): Array<IMenuItem | ISubmenuItem>;
readonly onDidChangeMenu: Event<MenuId>;
}
export interface ICommandsMap {
@@ -137,15 +137,21 @@ export interface ICommandsMap {
export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {
private readonly _commands: { [id: string]: ICommandAction } = Object.create(null);
private readonly _menuItems: { [loc: string]: (IMenuItem | ISubmenuItem)[] } = Object.create(null);
private readonly _menuItems: { [loc: number]: Array<IMenuItem | ISubmenuItem> } = Object.create(null);
private readonly _onDidChangeMenu = new Emitter<MenuId>();
readonly onDidChangeMenu: Event<MenuId> = this._onDidChangeMenu.event;
addCommand(command: ICommandAction): boolean {
const old = this._commands[command.id];
addCommand(command: ICommandAction): IDisposable {
this._commands[command.id] = command;
return old !== void 0;
this._onDidChangeMenu.fire(MenuId.CommandPalette);
return {
dispose: () => {
if (delete this._commands[command.id]) {
this._onDidChangeMenu.fire(MenuId.CommandPalette);
}
}
};
}
getCommand(id: string): ICommandAction {
@@ -179,8 +185,8 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {
};
}
getMenuItems(id: MenuId): (IMenuItem | ISubmenuItem)[] {
const result = this._menuItems[id] || [];
getMenuItems(id: MenuId): Array<IMenuItem | ISubmenuItem> {
const result = (this._menuItems[id] || []).slice(0);
if (id === MenuId.CommandPalette) {
// CommandPalette is special because it shows
@@ -190,7 +196,7 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {
return result;
}
private _appendImplicitItems(result: (IMenuItem | ISubmenuItem)[]) {
private _appendImplicitItems(result: Array<IMenuItem | ISubmenuItem>) {
const set = new Set<string>();
const temp = result.filter(item => { return isIMenuItem(item); }) as IMenuItem[];

View File

@@ -1,159 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter, filterEvent, debounceEvent } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { MenuId, MenuRegistry, MenuItemAction, IMenu, IMenuItem, IMenuActionOptions, ISubmenuItem, SubmenuItemAction, isIMenuItem } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
type MenuItemGroup = [string, (IMenuItem | ISubmenuItem)[]];
export class Menu implements IMenu {
private readonly _onDidChange = new Emitter<IMenu>();
private readonly _disposables: IDisposable[] = [];
private _menuGroups: MenuItemGroup[];
private _contextKeys: Set<string>;
constructor(
private readonly _id: MenuId,
@ICommandService private readonly _commandService: ICommandService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService
) {
this._build();
// rebuild this menu whenever the menu registry reports an
// event for this MenuId
debounceEvent(
filterEvent(MenuRegistry.onDidChangeMenu, menuId => menuId === this._id),
() => { },
50
)(this._build, this, this._disposables);
// when context keys change we need to check if the menu also
// has changed
debounceEvent(
this._contextKeyService.onDidChangeContext,
(last, event) => last || event.affectsSome(this._contextKeys),
50
)(e => e && this._onDidChange.fire(), this, this._disposables);
}
private _build(): void {
// reset
this._menuGroups = [];
this._contextKeys = new Set();
const menuItems = MenuRegistry.getMenuItems(this._id);
let group: MenuItemGroup | undefined;
menuItems.sort(Menu._compareMenuItems);
for (let item of menuItems) {
// group by groupId
const groupName = item.group || '';
if (!group || group[0] !== groupName) {
group = [groupName, []];
this._menuGroups.push(group);
}
group![1].push(item);
// keep keys for eventing
Menu._fillInKbExprKeys(item.when, this._contextKeys);
// keep precondition keys for event if applicable
if (isIMenuItem(item) && item.command.precondition) {
Menu._fillInKbExprKeys(item.command.precondition, this._contextKeys);
}
// keep toggled keys for event if applicable
if (isIMenuItem(item) && item.command.toggled) {
Menu._fillInKbExprKeys(item.command.toggled, this._contextKeys);
}
}
this._onDidChange.fire(this);
}
dispose() {
dispose(this._disposables);
this._onDidChange.dispose();
}
get onDidChange(): Event<IMenu> {
return this._onDidChange.event;
}
getActions(options: IMenuActionOptions): [string, (MenuItemAction | SubmenuItemAction)[]][] {
const result: [string, (MenuItemAction | SubmenuItemAction)[]][] = [];
for (let group of this._menuGroups) {
const [id, items] = group;
const activeActions: (MenuItemAction | SubmenuItemAction)[] = [];
for (const item of items) {
if (this._contextKeyService.contextMatchesRules(item.when || null)) {
const action = isIMenuItem(item) ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) : new SubmenuItemAction(item);
activeActions.push(action);
}
}
if (activeActions.length > 0) {
result.push([id, activeActions]);
}
}
return result;
}
private static _fillInKbExprKeys(exp: ContextKeyExpr | undefined, set: Set<string>): void {
if (exp) {
for (let key of exp.keys()) {
set.add(key);
}
}
}
private static _compareMenuItems(a: IMenuItem, b: IMenuItem): number {
let aGroup = a.group;
let bGroup = b.group;
if (aGroup !== bGroup) {
// Falsy groups come last
if (!aGroup) {
return 1;
} else if (!bGroup) {
return -1;
}
// 'navigation' group comes first
if (aGroup === 'navigation') {
return -1;
} else if (bGroup === 'navigation') {
return 1;
}
// lexical sort for groups
let value = aGroup.localeCompare(bGroup);
if (value !== 0) {
return value;
}
}
// sort on priority - default is 0
let aPrio = a.order || 0;
let bPrio = b.order || 0;
if (aPrio < bPrio) {
return -1;
} else if (aPrio > bPrio) {
return 1;
}
// sort on titles
const aTitle = typeof a.command.title === 'string' ? a.command.title : a.command.title.value;
const bTitle = typeof b.command.title === 'string' ? b.command.title : b.command.title.value;
return aTitle.localeCompare(bTitle);
}
}

View File

@@ -3,10 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions';
import { Menu } from 'vs/platform/actions/common/menu';
import { Emitter, Event } from 'vs/base/common/event';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
export class MenuService implements IMenuService {
@@ -22,3 +23,153 @@ export class MenuService implements IMenuService {
return new Menu(id, this._commandService, contextKeyService);
}
}
type MenuItemGroup = [string, Array<IMenuItem | ISubmenuItem>];
class Menu implements IMenu {
private readonly _onDidChange = new Emitter<IMenu | undefined>();
private readonly _disposables: IDisposable[] = [];
private _menuGroups: MenuItemGroup[];
private _contextKeys: Set<string>;
constructor(
private readonly _id: MenuId,
@ICommandService private readonly _commandService: ICommandService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService
) {
this._build();
// rebuild this menu whenever the menu registry reports an
// event for this MenuId
Event.debounce(
Event.filter(MenuRegistry.onDidChangeMenu, menuId => menuId === this._id),
() => { },
50
)(this._build, this, this._disposables);
// when context keys change we need to check if the menu also
// has changed
Event.debounce(
this._contextKeyService.onDidChangeContext,
(last, event) => last || event.affectsSome(this._contextKeys),
50
)(e => e && this._onDidChange.fire(undefined), this, this._disposables);
}
private _build(): void {
// reset
this._menuGroups = [];
this._contextKeys = new Set();
const menuItems = MenuRegistry.getMenuItems(this._id);
let group: MenuItemGroup | undefined;
menuItems.sort(Menu._compareMenuItems);
for (let item of menuItems) {
// group by groupId
const groupName = item.group || '';
if (!group || group[0] !== groupName) {
group = [groupName, []];
this._menuGroups.push(group);
}
group![1].push(item);
// keep keys for eventing
Menu._fillInKbExprKeys(item.when, this._contextKeys);
// keep precondition keys for event if applicable
if (isIMenuItem(item) && item.command.precondition) {
Menu._fillInKbExprKeys(item.command.precondition, this._contextKeys);
}
// keep toggled keys for event if applicable
if (isIMenuItem(item) && item.command.toggled) {
Menu._fillInKbExprKeys(item.command.toggled, this._contextKeys);
}
}
this._onDidChange.fire(this);
}
dispose() {
dispose(this._disposables);
this._onDidChange.dispose();
}
get onDidChange(): Event<IMenu | undefined> {
return this._onDidChange.event;
}
getActions(options: IMenuActionOptions): [string, Array<MenuItemAction | SubmenuItemAction>][] {
const result: [string, Array<MenuItemAction | SubmenuItemAction>][] = [];
for (let group of this._menuGroups) {
const [id, items] = group;
const activeActions: Array<MenuItemAction | SubmenuItemAction> = [];
for (const item of items) {
if (this._contextKeyService.contextMatchesRules(item.when || null)) {
const action = isIMenuItem(item) ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) : new SubmenuItemAction(item);
activeActions.push(action);
}
}
if (activeActions.length > 0) {
result.push([id, activeActions]);
}
}
return result;
}
private static _fillInKbExprKeys(exp: ContextKeyExpr | undefined, set: Set<string>): void {
if (exp) {
for (let key of exp.keys()) {
set.add(key);
}
}
}
private static _compareMenuItems(a: IMenuItem, b: IMenuItem): number {
let aGroup = a.group;
let bGroup = b.group;
if (aGroup !== bGroup) {
// Falsy groups come last
if (!aGroup) {
return 1;
} else if (!bGroup) {
return -1;
}
// 'navigation' group comes first
if (aGroup === 'navigation') {
return -1;
} else if (bGroup === 'navigation') {
return 1;
}
// lexical sort for groups
let value = aGroup.localeCompare(bGroup);
if (value !== 0) {
return value;
}
}
// sort on priority - default is 0
let aPrio = a.order || 0;
let bPrio = b.order || 0;
if (aPrio < bPrio) {
return -1;
} else if (aPrio > bPrio) {
return 1;
}
// sort on titles
const aTitle = typeof a.command.title === 'string' ? a.command.title : a.command.title.value;
const bTitle = typeof b.command.title === 'string' ? b.command.title : b.command.title.value;
return aTitle.localeCompare(bTitle);
}
}

View File

@@ -7,43 +7,90 @@ import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import * as platform from 'vs/base/common/platform';
import * as extfs from 'vs/base/node/extfs';
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
import * as arrays from 'vs/base/common/arrays';
import { IBackupMainService, IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/common/backup';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFilesConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { isEqual as areResourcesEquals, getComparisonKey, hasToIgnoreCase } from 'vs/base/common/resources';
import { isEqual } from 'vs/base/common/paths';
import { Schemas } from 'vs/base/common/network';
import { writeFile, readFile, readdir, exists, del, rename } from 'vs/base/node/pfs';
export class BackupMainService implements IBackupMainService {
public _serviceBrand: any;
_serviceBrand: any;
protected backupHome: string;
protected workspacesJsonPath: string;
protected rootWorkspaces: IWorkspaceIdentifier[];
protected folderWorkspaces: URI[];
protected emptyWorkspaces: IEmptyWindowBackupInfo[];
private rootWorkspaces: IWorkspaceIdentifier[];
private folderWorkspaces: URI[];
private emptyWorkspaces: IEmptyWindowBackupInfo[];
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IConfigurationService private configurationService: IConfigurationService,
@ILogService private logService: ILogService
@IConfigurationService private readonly configurationService: IConfigurationService,
@ILogService private readonly logService: ILogService
) {
this.backupHome = environmentService.backupHome;
this.workspacesJsonPath = environmentService.backupWorkspacesPath;
this.loadSync();
}
public getWorkspaceBackups(): IWorkspaceIdentifier[] {
async initialize(): Promise<void> {
let backups: IBackupWorkspacesFormat;
try {
backups = JSON.parse(await readFile(this.workspacesJsonPath, 'utf8')); // invalid JSON or permission issue can happen here
} catch (error) {
backups = Object.create(null);
}
// read empty workspaces backups first
if (backups.emptyWorkspaceInfos) {
this.emptyWorkspaces = await this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos);
} else if (Array.isArray(backups.emptyWorkspaces)) {
// read legacy entries
this.emptyWorkspaces = await this.validateEmptyWorkspaces(backups.emptyWorkspaces.map(backupFolder => ({ backupFolder })));
} else {
this.emptyWorkspaces = [];
}
// read workspace backups
this.rootWorkspaces = await this.validateWorkspaces(backups.rootWorkspaces);
// read folder backups
let workspaceFolders: URI[] = [];
try {
if (Array.isArray(backups.folderURIWorkspaces)) {
workspaceFolders = backups.folderURIWorkspaces.map(f => URI.parse(f));
} else if (Array.isArray(backups.folderWorkspaces)) {
// migrate legacy folder paths
workspaceFolders = [];
for (const folderPath of backups.folderWorkspaces) {
const oldFolderHash = this.getLegacyFolderHash(folderPath);
const folderUri = URI.file(folderPath);
const newFolderHash = this.getFolderHash(folderUri);
if (newFolderHash !== oldFolderHash) {
await this.moveBackupFolder(this.getBackupPath(newFolderHash), this.getBackupPath(oldFolderHash));
}
workspaceFolders.push(folderUri);
}
}
} catch (e) {
// ignore URI parsing exceptions
}
this.folderWorkspaces = await this.validateFolders(workspaceFolders);
// save again in case some workspaces or folders have been removed
await this.save();
}
getWorkspaceBackups(): IWorkspaceIdentifier[] {
if (this.isHotExitOnExitAndWindowClose()) {
// Only non-folder windows are restored on main process launch when
// hot exit is configured as onExitAndWindowClose.
@@ -53,16 +100,17 @@ export class BackupMainService implements IBackupMainService {
return this.rootWorkspaces.slice(0); // return a copy
}
public getFolderBackupPaths(): URI[] {
getFolderBackupPaths(): URI[] {
if (this.isHotExitOnExitAndWindowClose()) {
// Only non-folder windows are restored on main process launch when
// hot exit is configured as onExitAndWindowClose.
return [];
}
return this.folderWorkspaces.slice(0); // return a copy
}
public isHotExitEnabled(): boolean {
isHotExitEnabled(): boolean {
return this.getHotExitConfig() !== HotExitConfiguration.OFF;
}
@@ -76,11 +124,11 @@ export class BackupMainService implements IBackupMainService {
return (config && config.files && config.files.hotExit) || HotExitConfiguration.ON_EXIT;
}
public getEmptyWindowBackupPaths(): IEmptyWindowBackupInfo[] {
getEmptyWindowBackupPaths(): IEmptyWindowBackupInfo[] {
return this.emptyWorkspaces.slice(0); // return a copy
}
public registerWorkspaceBackupSync(workspace: IWorkspaceIdentifier, migrateFrom?: string): string {
registerWorkspaceBackupSync(workspace: IWorkspaceIdentifier, migrateFrom?: string): string {
if (!this.rootWorkspaces.some(w => w.id === workspace.id)) {
this.rootWorkspaces.push(workspace);
this.saveSync();
@@ -99,7 +147,7 @@ export class BackupMainService implements IBackupMainService {
// Target exists: make sure to convert existing backups to empty window backups
if (fs.existsSync(backupPath)) {
this.convertToEmptyWindowBackup(backupPath);
this.convertToEmptyWindowBackupSync(backupPath);
}
// When we have data to migrate from, move it over to the target location
@@ -112,7 +160,24 @@ export class BackupMainService implements IBackupMainService {
}
}
public unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void {
private async moveBackupFolder(backupPath: string, moveFromPath: string): Promise<void> {
// Target exists: make sure to convert existing backups to empty window backups
if (await exists(backupPath)) {
await this.convertToEmptyWindowBackup(backupPath);
}
// When we have data to migrate from, move it over to the target location
if (await exists(moveFromPath)) {
try {
await rename(moveFromPath, backupPath);
} catch (ex) {
this.logService.error(`Backup: Could not move backup folder to new location: ${ex.toString()}`);
}
}
}
unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void {
let index = arrays.firstIndex(this.rootWorkspaces, w => w.id === workspace.id);
if (index !== -1) {
this.rootWorkspaces.splice(index, 1);
@@ -120,15 +185,16 @@ export class BackupMainService implements IBackupMainService {
}
}
public registerFolderBackupSync(folderUri: URI): string {
registerFolderBackupSync(folderUri: URI): string {
if (!this.folderWorkspaces.some(uri => areResourcesEquals(folderUri, uri))) {
this.folderWorkspaces.push(folderUri);
this.saveSync();
}
return this.getBackupPath(this.getFolderHash(folderUri));
}
public unregisterFolderBackupSync(folderUri: URI): void {
unregisterFolderBackupSync(folderUri: URI): void {
let index = arrays.firstIndex(this.folderWorkspaces, uri => areResourcesEquals(folderUri, uri));
if (index !== -1) {
this.folderWorkspaces.splice(index, 1);
@@ -136,22 +202,24 @@ export class BackupMainService implements IBackupMainService {
}
}
public registerEmptyWindowBackupSync(backupInfo: IEmptyWindowBackupInfo): string {
registerEmptyWindowBackupSync(backupInfo: IEmptyWindowBackupInfo): string {
let backupFolder = backupInfo.backupFolder;
let remoteAuthority = backupInfo.remoteAuthority;
// Generate a new folder if this is a new empty workspace
if (!backupFolder) {
backupFolder = this.getRandomEmptyWindowId();
}
if (!this.emptyWorkspaces.some(w => isEqual(w.backupFolder, backupFolder, !platform.isLinux))) {
this.emptyWorkspaces.push({ backupFolder, remoteAuthority });
this.saveSync();
}
return this.getBackupPath(backupFolder);
}
public unregisterEmptyWindowBackupSync(backupFolder: string): void {
unregisterEmptyWindowBackupSync(backupFolder: string): void {
let index = arrays.firstIndex(this.emptyWorkspaces, w => isEqual(w.backupFolder, backupFolder, !platform.isLinux));
if (index !== -1) {
this.emptyWorkspaces.splice(index, 1);
@@ -159,61 +227,11 @@ export class BackupMainService implements IBackupMainService {
}
}
protected loadSync(): void {
let backups: IBackupWorkspacesFormat;
try {
backups = JSON.parse(fs.readFileSync(this.workspacesJsonPath, 'utf8').toString()); // invalid JSON or permission issue can happen here
} catch (error) {
backups = Object.create(null);
}
// read empty workspaces backups first
if (backups.emptyWorkspaceInfos) {
this.emptyWorkspaces = this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos);
} else if (Array.isArray(backups.emptyWorkspaces)) {
// read legacy entries
this.emptyWorkspaces = this.validateEmptyWorkspaces(backups.emptyWorkspaces.map(backupFolder => ({ backupFolder })));
} else {
this.emptyWorkspaces = [];
}
// read workspace backups
this.rootWorkspaces = this.validateWorkspaces(backups.rootWorkspaces);
// read folder backups
let workspaceFolders: URI[] = [];
try {
if (Array.isArray(backups.folderURIWorkspaces)) {
workspaceFolders = backups.folderURIWorkspaces.map(f => URI.parse(f));
} else if (Array.isArray(backups.folderWorkspaces)) {
// migrate legacy folder paths
workspaceFolders = [];
for (const folderPath of backups.folderWorkspaces) {
const oldFolderHash = this.getLegacyFolderHash(folderPath);
const folderUri = URI.file(folderPath);
const newFolderHash = this.getFolderHash(folderUri);
if (newFolderHash !== oldFolderHash) {
this.moveBackupFolderSync(this.getBackupPath(newFolderHash), this.getBackupPath(oldFolderHash));
}
workspaceFolders.push(folderUri);
}
}
} catch (e) {
// ignore URI parsing exceptions
}
this.folderWorkspaces = this.validateFolders(workspaceFolders);
// save again in case some workspaces or folders have been removed
this.saveSync();
}
private getBackupPath(oldFolderHash: string): string {
return path.join(this.backupHome, oldFolderHash);
}
private validateWorkspaces(rootWorkspaces: IWorkspaceIdentifier[]): IWorkspaceIdentifier[] {
private async validateWorkspaces(rootWorkspaces: IWorkspaceIdentifier[]): Promise<IWorkspaceIdentifier[]> {
if (!Array.isArray(rootWorkspaces)) {
return [];
}
@@ -231,57 +249,58 @@ export class BackupMainService implements IBackupMainService {
seenIds[workspace.id] = true;
const backupPath = this.getBackupPath(workspace.id);
const hasBackups = this.hasBackupsSync(backupPath);
const hasBackups = await this.hasBackups(backupPath);
// If the workspace has no backups, ignore it
if (hasBackups) {
if (fs.existsSync(workspace.configPath)) {
if (await exists(workspace.configPath)) {
result.push(workspace);
} else {
// If the workspace has backups, but the target workspace is missing, convert backups to empty ones
this.convertToEmptyWindowBackup(backupPath);
await this.convertToEmptyWindowBackup(backupPath);
}
} else {
this.deleteStaleBackup(backupPath);
await this.deleteStaleBackup(backupPath);
}
}
}
return result;
}
private validateFolders(folderWorkspaces: URI[]): URI[] {
private async validateFolders(folderWorkspaces: URI[]): Promise<URI[]> {
if (!Array.isArray(folderWorkspaces)) {
return [];
}
const result: URI[] = [];
const seen: { [id: string]: boolean } = Object.create(null);
for (let folderURI of folderWorkspaces) {
const key = getComparisonKey(folderURI);
if (!seen[key]) {
seen[key] = true;
const backupPath = this.getBackupPath(this.getFolderHash(folderURI));
const hasBackups = this.hasBackupsSync(backupPath);
const hasBackups = await this.hasBackups(backupPath);
// If the folder has no backups, ignore it
if (hasBackups) {
if (folderURI.scheme !== Schemas.file || fs.existsSync(folderURI.fsPath)) {
if (folderURI.scheme !== Schemas.file || await exists(folderURI.fsPath)) {
result.push(folderURI);
} else {
// If the folder has backups, but the target workspace is missing, convert backups to empty ones
this.convertToEmptyWindowBackup(backupPath);
await this.convertToEmptyWindowBackup(backupPath);
}
} else {
this.deleteStaleBackup(backupPath);
await this.deleteStaleBackup(backupPath);
}
}
}
return result;
}
private validateEmptyWorkspaces(emptyWorkspaces: IEmptyWindowBackupInfo[]): IEmptyWindowBackupInfo[] {
private async validateEmptyWorkspaces(emptyWorkspaces: IEmptyWindowBackupInfo[]): Promise<IEmptyWindowBackupInfo[]> {
if (!Array.isArray(emptyWorkspaces)) {
return [];
}
@@ -300,10 +319,10 @@ export class BackupMainService implements IBackupMainService {
seen[backupFolder] = true;
const backupPath = this.getBackupPath(backupFolder);
if (this.hasBackupsSync(backupPath)) {
if (await this.hasBackups(backupPath)) {
result.push(backupInfo);
} else {
this.deleteStaleBackup(backupPath);
await this.deleteStaleBackup(backupPath);
}
}
}
@@ -311,17 +330,38 @@ export class BackupMainService implements IBackupMainService {
return result;
}
private deleteStaleBackup(backupPath: string) {
private async deleteStaleBackup(backupPath: string): Promise<void> {
try {
if (fs.existsSync(backupPath)) {
extfs.delSync(backupPath);
if (await exists(backupPath)) {
await del(backupPath);
}
} catch (ex) {
this.logService.error(`Backup: Could not delete stale backup: ${ex.toString()}`);
}
}
private convertToEmptyWindowBackup(backupPath: string): boolean {
private async convertToEmptyWindowBackup(backupPath: string): Promise<boolean> {
// New empty window backup
let newBackupFolder = this.getRandomEmptyWindowId();
while (this.emptyWorkspaces.some(w => isEqual(w.backupFolder, newBackupFolder, platform.isLinux))) {
newBackupFolder = this.getRandomEmptyWindowId();
}
// Rename backupPath to new empty window backup path
const newEmptyWindowBackupPath = this.getBackupPath(newBackupFolder);
try {
await rename(backupPath, newEmptyWindowBackupPath);
} catch (ex) {
this.logService.error(`Backup: Could not rename backup folder: ${ex.toString()}`);
return false;
}
this.emptyWorkspaces.push({ backupFolder: newBackupFolder });
return true;
}
private convertToEmptyWindowBackupSync(backupPath: string): boolean {
// New empty window backup
let newBackupFolder = this.getRandomEmptyWindowId();
@@ -342,56 +382,66 @@ export class BackupMainService implements IBackupMainService {
return true;
}
private hasBackupsSync(backupPath: string): boolean {
private async hasBackups(backupPath: string): Promise<boolean> {
try {
const backupSchemas = extfs.readdirSync(backupPath);
if (backupSchemas.length === 0) {
return false; // empty backups
}
const backupSchemas = await readdir(backupPath);
return backupSchemas.some(backupSchema => {
for (const backupSchema of backupSchemas) {
try {
return extfs.readdirSync(path.join(backupPath, backupSchema)).length > 0;
const backupSchemaChildren = await readdir(path.join(backupPath, backupSchema));
if (backupSchemaChildren.length > 0) {
return true;
}
} catch (error) {
return false; // invalid folder
// invalid folder
}
});
}
} catch (error) {
return false; // backup path does not exist
// backup path does not exist
}
return false;
}
private saveSync(): void {
try {
// The user data directory must exist so only the Backup directory needs to be checked.
if (!fs.existsSync(this.backupHome)) {
fs.mkdirSync(this.backupHome);
}
const backups: IBackupWorkspacesFormat = {
rootWorkspaces: this.rootWorkspaces,
folderURIWorkspaces: this.folderWorkspaces.map(f => f.toString()),
emptyWorkspaceInfos: this.emptyWorkspaces,
emptyWorkspaces: this.emptyWorkspaces.map(info => info.backupFolder)
};
extfs.writeFileAndFlushSync(this.workspacesJsonPath, JSON.stringify(backups));
writeFileAndFlushSync(this.workspacesJsonPath, JSON.stringify(this.serializeBackups()));
} catch (ex) {
this.logService.error(`Backup: Could not save workspaces.json: ${ex.toString()}`);
}
}
private async save(): Promise<void> {
try {
await writeFile(this.workspacesJsonPath, JSON.stringify(this.serializeBackups()));
} catch (ex) {
this.logService.error(`Backup: Could not save workspaces.json: ${ex.toString()}`);
}
}
private serializeBackups(): IBackupWorkspacesFormat {
return {
rootWorkspaces: this.rootWorkspaces,
folderURIWorkspaces: this.folderWorkspaces.map(f => f.toString()),
emptyWorkspaceInfos: this.emptyWorkspaces,
emptyWorkspaces: this.emptyWorkspaces.map(info => info.backupFolder)
} as IBackupWorkspacesFormat;
}
private getRandomEmptyWindowId(): string {
return (Date.now() + Math.round(Math.random() * 1000)).toString();
}
protected getFolderHash(folderUri: URI): string {
let key;
let key: string;
if (folderUri.scheme === Schemas.file) {
// for backward compatibility, use the fspath as key
key = platform.isLinux ? folderUri.fsPath : folderUri.fsPath.toLowerCase();
} else {
key = hasToIgnoreCase(folderUri) ? folderUri.toString().toLowerCase() : folderUri.toString();
}
return crypto.createHash('md5').update(key).digest('hex');
}

View File

@@ -41,13 +41,6 @@ suite('BackupMainService', () => {
this.backupHome = backupHome;
this.workspacesJsonPath = backupWorkspacesPath;
// Force a reload with the new paths
this.loadSync();
}
public loadSync(): void {
super.loadSync();
}
public toBackupPath(arg: Uri | string): string {
@@ -109,12 +102,15 @@ suite('BackupMainService', () => {
let configService: TestConfigurationService;
setup(() => {
configService = new TestConfigurationService();
service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService);
// Delete any existing backups completely and then re-create it.
return pfs.del(backupHome, os.tmpdir()).then(() => {
return pfs.mkdirp(backupHome);
}).then(() => {
configService = new TestConfigurationService();
service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService);
return service.initialize();
});
});
@@ -122,13 +118,13 @@ suite('BackupMainService', () => {
return pfs.del(backupHome, os.tmpdir());
});
test('service validates backup workspaces on startup and cleans up (folder workspaces)', function () {
test('service validates backup workspaces on startup and cleans up (folder workspaces)', async function () {
this.timeout(1000 * 10); // increase timeout for this test
// 1) backup workspace path does not exist
service.registerFolderBackupSync(fooFile);
service.registerFolderBackupSync(barFile);
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
// 2) backup workspace path exists with empty contents within
@@ -136,7 +132,7 @@ suite('BackupMainService', () => {
fs.mkdirSync(service.toBackupPath(barFile));
service.registerFolderBackupSync(fooFile);
service.registerFolderBackupSync(barFile);
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
assert.ok(!fs.existsSync(service.toBackupPath(fooFile)));
assert.ok(!fs.existsSync(service.toBackupPath(barFile)));
@@ -148,7 +144,7 @@ suite('BackupMainService', () => {
fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled));
service.registerFolderBackupSync(fooFile);
service.registerFolderBackupSync(barFile);
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
assert.ok(!fs.existsSync(service.toBackupPath(fooFile)));
assert.ok(!fs.existsSync(service.toBackupPath(barFile)));
@@ -163,18 +159,18 @@ suite('BackupMainService', () => {
assert.equal(service.getFolderBackupPaths().length, 1);
assert.equal(service.getEmptyWindowBackupPaths().length, 0);
fs.writeFileSync(path.join(fileBackups, 'backup.txt'), '');
service.loadSync();
await service.initialize();
assert.equal(service.getFolderBackupPaths().length, 0);
assert.equal(service.getEmptyWindowBackupPaths().length, 1);
});
test('service validates backup workspaces on startup and cleans up (root workspaces)', function () {
test('service validates backup workspaces on startup and cleans up (root workspaces)', async function () {
this.timeout(1000 * 10); // increase timeout for this test
// 1) backup workspace path does not exist
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath));
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
// 2) backup workspace path exists with empty contents within
@@ -182,7 +178,7 @@ suite('BackupMainService', () => {
fs.mkdirSync(service.toBackupPath(barFile));
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath));
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
assert.ok(!fs.existsSync(service.toBackupPath(fooFile)));
assert.ok(!fs.existsSync(service.toBackupPath(barFile)));
@@ -194,7 +190,7 @@ suite('BackupMainService', () => {
fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled));
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath));
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
assert.ok(!fs.existsSync(service.toBackupPath(fooFile)));
assert.ok(!fs.existsSync(service.toBackupPath(barFile)));
@@ -209,7 +205,7 @@ suite('BackupMainService', () => {
assert.equal(service.getWorkspaceBackups().length, 1);
assert.equal(service.getEmptyWindowBackupPaths().length, 0);
fs.writeFileSync(path.join(fileBackups, 'backup.txt'), '');
service.loadSync();
await service.initialize();
assert.equal(service.getWorkspaceBackups().length, 0);
assert.equal(service.getEmptyWindowBackupPaths().length, 1);
});
@@ -284,17 +280,15 @@ suite('BackupMainService', () => {
}
const workspacesJson = { rootWorkspaces: [], folderWorkspaces: [path1, path2], emptyWorkspaces: [] };
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
service.loadSync();
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json.folderURIWorkspaces, [uri1.toString(), uri2.toString()]);
const newBackupFolder1 = service.toBackupPath(uri1);
assert.ok(fs.existsSync(path.join(newBackupFolder1, Schemas.file, 'unsaved1.txt')));
const newBackupFolder2 = service.toBackupPath(uri2);
assert.ok(fs.existsSync(path.join(newBackupFolder2, Schemas.file, 'unsaved2.txt')));
});
});
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
await service.initialize();
const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(content));
assert.deepEqual(json.folderURIWorkspaces, [uri1.toString(), uri2.toString()]);
const newBackupFolder1 = service.toBackupPath(uri1);
assert.ok(fs.existsSync(path.join(newBackupFolder1, Schemas.file, 'unsaved1.txt')));
const newBackupFolder2 = service.toBackupPath(uri2);
assert.ok(fs.existsSync(path.join(newBackupFolder2, Schemas.file, 'unsaved2.txt')));
});
});
@@ -303,50 +297,50 @@ suite('BackupMainService', () => {
assertEqualUris(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON', () => {
test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON', async () => {
fs.writeFileSync(backupWorkspacesPath, '');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{]');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, 'foo');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when folderWorkspaces in workspaces.json is absent', () => {
test('getFolderBackupPaths() should return [] when folderWorkspaces in workspaces.json is absent', async () => {
fs.writeFileSync(backupWorkspacesPath, '{}');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', () => {
test('getFolderBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{}}');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": ["bar"]}}');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": []}}');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": "bar"}}');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":"foo"}');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":1}');
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
});
test('getFolderBackupPaths() should return [] when files.hotExit = "onExitAndWindowClose"', () => {
test('getFolderBackupPaths() should return [] when files.hotExit = "onExitAndWindowClose"', async () => {
service.registerFolderBackupSync(Uri.file(fooFile.fsPath.toUpperCase()));
assertEqualUris(service.getFolderBackupPaths(), [Uri.file(fooFile.fsPath.toUpperCase())]);
configService.setUserConfiguration('files.hotExit', HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE);
service.loadSync();
await service.initialize();
assertEqualUris(service.getFolderBackupPaths(), []);
});
@@ -354,51 +348,51 @@ suite('BackupMainService', () => {
assert.deepEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON', () => {
test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON', async () => {
fs.writeFileSync(backupWorkspacesPath, '');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{]');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, 'foo');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when folderWorkspaces in workspaces.json is absent', () => {
test('getWorkspaceBackups() should return [] when folderWorkspaces in workspaces.json is absent', async () => {
fs.writeFileSync(backupWorkspacesPath, '{}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array', () => {
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array', async () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{}}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": ["bar"]}}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": []}}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": "bar"}}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":"foo"}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":1}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackups() should return [] when files.hotExit = "onExitAndWindowClose"', () => {
test('getWorkspaceBackups() should return [] when files.hotExit = "onExitAndWindowClose"', async () => {
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath.toUpperCase()));
assert.equal(service.getWorkspaceBackups().length, 1);
assert.deepEqual(service.getWorkspaceBackups().map(r => r.configPath), [fooFile.fsPath.toUpperCase()]);
configService.setUserConfiguration('files.hotExit', HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE);
service.loadSync();
await service.initialize();
assert.deepEqual(service.getWorkspaceBackups(), []);
});
@@ -406,43 +400,43 @@ suite('BackupMainService', () => {
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON', () => {
test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON', async () => {
fs.writeFileSync(backupWorkspacesPath, '');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{]');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, 'foo');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is absent', () => {
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is absent', async () => {
fs.writeFileSync(backupWorkspacesPath, '{}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', function () {
test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', async function () {
this.timeout(5000);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{}}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": ["bar"]}}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": []}}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": "bar"}}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":"foo"}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":1}');
service.loadSync();
await service.initialize();
assert.deepEqual(service.getEmptyWindowBackupPaths(), []);
});
});
@@ -457,13 +451,12 @@ suite('BackupMainService', () => {
folderURIWorkspaces: [existingTestFolder1.toString(), existingTestFolder1.toString()],
emptyWorkspaceInfos: []
};
return pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
service.loadSync();
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]);
});
});
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
await service.initialize();
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]);
});
test('should ignore duplicates on Windows and Mac (folder workspace)', async () => {
@@ -475,13 +468,11 @@ suite('BackupMainService', () => {
folderURIWorkspaces: [existingTestFolder1.toString(), existingTestFolder1.toString().toLowerCase()],
emptyWorkspaceInfos: []
};
return pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
service.loadSync();
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]);
});
});
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
await service.initialize();
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]);
});
test('should ignore duplicates on Windows and Mac (root workspace)', async () => {
@@ -500,34 +491,31 @@ suite('BackupMainService', () => {
folderURIWorkspaces: [],
emptyWorkspaceInfos: []
};
return pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
service.loadSync();
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.equal(json.rootWorkspaces.length, platform.isLinux ? 3 : 1);
if (platform.isLinux) {
assert.deepEqual(json.rootWorkspaces.map(r => r.configPath), [workspacePath, workspacePath.toUpperCase(), workspacePath.toLowerCase()]);
} else {
assert.deepEqual(json.rootWorkspaces.map(r => r.configPath), [workspacePath], 'should return the first duplicated entry');
}
});
});
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
await service.initialize();
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.equal(json.rootWorkspaces.length, platform.isLinux ? 3 : 1);
if (platform.isLinux) {
assert.deepEqual(json.rootWorkspaces.map(r => r.configPath), [workspacePath, workspacePath.toUpperCase(), workspacePath.toLowerCase()]);
} else {
assert.deepEqual(json.rootWorkspaces.map(r => r.configPath), [workspacePath], 'should return the first duplicated entry');
}
});
});
suite('registerWindowForBackups', () => {
test('should persist paths to workspaces.json (folder workspace)', () => {
test('should persist paths to workspaces.json (folder workspace)', async () => {
service.registerFolderBackupSync(fooFile);
service.registerFolderBackupSync(barFile);
assertEqualUris(service.getFolderBackupPaths(), [fooFile, barFile]);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [fooFile.toString(), barFile.toString()]);
});
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [fooFile.toString(), barFile.toString()]);
});
test('should persist paths to workspaces.json (root workspace)', () => {
test('should persist paths to workspaces.json (root workspace)', async () => {
const ws1 = toWorkspace(fooFile.fsPath);
service.registerWorkspaceBackupSync(ws1);
const ws2 = toWorkspace(barFile.fsPath);
@@ -537,31 +525,30 @@ suite('BackupMainService', () => {
assert.equal(ws1.id, service.getWorkspaceBackups()[0].id);
assert.equal(ws2.id, service.getWorkspaceBackups()[1].id);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
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);
});
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);
});
});
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(Uri.file(fooFile.fsPath.toUpperCase()));
assertEqualUris(service.getFolderBackupPaths(), [Uri.file(fooFile.fsPath.toUpperCase())]);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [Uri.file(fooFile.fsPath.toUpperCase()).toString()]);
});
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(Uri.file(fooFile.fsPath.toUpperCase()));
assertEqualUris(service.getFolderBackupPaths(), [Uri.file(fooFile.fsPath.toUpperCase())]);
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderURIWorkspaces, [Uri.file(fooFile.fsPath.toUpperCase()).toString()]);
});
});
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()]);
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()]);
});
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()]);
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()]);
});
});
@@ -618,15 +605,13 @@ suite('BackupMainService', () => {
await ensureFolderExists(existingTestFolder1); // make sure backup folder exists, so the folder is not removed on loadSync
const workspacesJson: IBackupWorkspacesFormat = { rootWorkspaces: [], folderURIWorkspaces: [existingTestFolder1.toString()], emptyWorkspaceInfos: [] };
return pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
service.loadSync();
service.unregisterFolderBackupSync(barFile);
service.unregisterEmptyWindowBackupSync('test');
return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]);
});
});
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
await service.initialize();
service.unregisterFolderBackupSync(barFile);
service.unregisterEmptyWindowBackupSync('test');
const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
const json = (<IBackupWorkspacesFormat>JSON.parse(content));
assert.deepEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]);
});
});

View File

@@ -31,7 +31,7 @@ export class BroadcastService implements IBroadcastService {
constructor(
private windowId: number,
@ILogService private logService: ILogService
@ILogService private readonly logService: ILogService
) {
this._onBroadcast = new Emitter<IBroadcast>();

View File

@@ -72,7 +72,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR
// add argument validation if rich command metadata is provided
if (idOrCommand.description) {
const constraints: (TypeConstraint | undefined)[] = [];
const constraints: Array<TypeConstraint | undefined> = [];
for (let arg of idOrCommand.description.args) {
constraints.push(arg.constraint);
}
@@ -96,7 +96,8 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR
let ret = toDisposable(() => {
removeFn();
if (this._commands.get(id).isEmpty()) {
const command = this._commands.get(id);
if (command && command.isEmpty()) {
this._commands.delete(id);
}
});
@@ -108,9 +109,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR
}
registerCommandAlias(oldId: string, newId: string): IDisposable {
return CommandsRegistry.registerCommand(oldId, (accessor, ...args) => {
accessor.get(ICommandService).executeCommand(newId, ...args);
});
return CommandsRegistry.registerCommand(oldId, (accessor, ...args) => accessor.get(ICommandService).executeCommand(newId, ...args));
}
getCommand(id: string): ICommand | undefined {

View File

@@ -8,13 +8,13 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
suite('Command Tests', function () {
test('register command - no handler', function () {
assert.throws(() => CommandsRegistry.registerCommand('foo', null));
assert.throws(() => CommandsRegistry.registerCommand('foo', null!));
});
test('register/dispose', () => {
const command = function () { };
const reg = CommandsRegistry.registerCommand('foo', command);
assert.ok(CommandsRegistry.getCommand('foo').handler === command);
assert.ok(CommandsRegistry.getCommand('foo')!.handler === command);
reg.dispose();
assert.ok(CommandsRegistry.getCommand('foo') === undefined);
});
@@ -25,26 +25,26 @@ suite('Command Tests', function () {
// dispose overriding command
let reg1 = CommandsRegistry.registerCommand('foo', command1);
assert.ok(CommandsRegistry.getCommand('foo').handler === command1);
assert.ok(CommandsRegistry.getCommand('foo')!.handler === command1);
let reg2 = CommandsRegistry.registerCommand('foo', command2);
assert.ok(CommandsRegistry.getCommand('foo').handler === command2);
assert.ok(CommandsRegistry.getCommand('foo')!.handler === command2);
reg2.dispose();
assert.ok(CommandsRegistry.getCommand('foo').handler === command1);
assert.ok(CommandsRegistry.getCommand('foo')!.handler === command1);
reg1.dispose();
assert.ok(CommandsRegistry.getCommand('foo') === void 0);
assert.ok(CommandsRegistry.getCommand('foo') === undefined);
// dispose override command first
reg1 = CommandsRegistry.registerCommand('foo', command1);
reg2 = CommandsRegistry.registerCommand('foo', command2);
assert.ok(CommandsRegistry.getCommand('foo').handler === command2);
assert.ok(CommandsRegistry.getCommand('foo')!.handler === command2);
reg1.dispose();
assert.ok(CommandsRegistry.getCommand('foo').handler === command2);
assert.ok(CommandsRegistry.getCommand('foo')!.handler === command2);
reg2.dispose();
assert.ok(CommandsRegistry.getCommand('foo') === void 0);
assert.ok(CommandsRegistry.getCommand('foo') === undefined);
});
test('command with description', function () {
@@ -68,10 +68,10 @@ suite('Command Tests', function () {
}
});
CommandsRegistry.getCommands()['test'].handler.apply(undefined, [undefined, 'string']);
CommandsRegistry.getCommands()['test2'].handler.apply(undefined, [undefined, 'string']);
assert.throws(() => CommandsRegistry.getCommands()['test3'].handler.apply(undefined, [undefined, 'string']));
assert.equal(CommandsRegistry.getCommands()['test3'].handler.apply(undefined, [undefined, 1]), true);
CommandsRegistry.getCommands()['test'].handler.apply(undefined, [undefined!, 'string']);
CommandsRegistry.getCommands()['test2'].handler.apply(undefined, [undefined!, 'string']);
assert.throws(() => CommandsRegistry.getCommands()['test3'].handler.apply(undefined, [undefined!, 'string']));
assert.equal(CommandsRegistry.getCommands()['test3'].handler.apply(undefined, [undefined!, 1]), true);
});
});

View File

@@ -24,7 +24,7 @@ export function isConfigurationOverrides(thing: any): thing is IConfigurationOve
export interface IConfigurationOverrides {
overrideIdentifier?: string | null;
resource?: URI;
resource?: URI | null;
}
export const enum ConfigurationTarget {
@@ -227,11 +227,11 @@ function doRemoveFromValueTree(valueTree: any, segments: string[]): void {
export function getConfigurationValue<T>(config: any, settingPath: string, defaultValue?: T): T {
function accessSetting(config: any, path: string[]): any {
let current = config;
for (let i = 0; i < path.length; i++) {
for (const component of path) {
if (typeof current !== 'object' || current === null) {
return undefined;
}
current = current[path[i]];
current = current[component];
}
return <T>current;
}

View File

@@ -204,10 +204,12 @@ export class ConfigurationModelParser {
return this._parseErrors;
}
public parse(content: string): void {
const raw = this.parseContent(content);
const configurationModel = this.parseRaw(raw);
this._configurationModel = new ConfigurationModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides);
public parse(content: string | null | undefined): void {
if (content) {
const raw = this.parseContent(content);
const configurationModel = this.parseRaw(raw);
this._configurationModel = new ConfigurationModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides);
}
}
protected parseContent(content: string): any {
@@ -296,7 +298,7 @@ export class Configuration {
}
updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void {
let memoryConfiguration: ConfigurationModel;
let memoryConfiguration: ConfigurationModel | undefined;
if (overrides.resource) {
memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource);
if (!memoryConfiguration) {
@@ -307,7 +309,7 @@ export class Configuration {
memoryConfiguration = this._memoryConfiguration;
}
if (value === void 0) {
if (value === undefined) {
memoryConfiguration.removeValue(key);
} else {
memoryConfiguration.setValue(key, value);
@@ -332,8 +334,8 @@ export class Configuration {
return {
default: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key),
user: overrides.overrideIdentifier ? this._userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._userConfiguration.freeze().getValue(key),
workspace: workspace ? overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._workspaceConfiguration.freeze().getValue(key) : void 0, //Check on workspace exists or not because _workspaceConfiguration is never null
workspaceFolder: folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : void 0,
workspace: workspace ? overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._workspaceConfiguration.freeze().getValue(key) : undefined, //Check on workspace exists or not because _workspaceConfiguration is never null
workspaceFolder: folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : undefined,
memory: overrides.overrideIdentifier ? memoryConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.freeze().getValue(key),
value: consolidateConfigurationModel.getValue(key)
};
@@ -448,11 +450,11 @@ export class Configuration {
return folderConsolidatedConfiguration;
}
private getFolderConfigurationModelForResource(resource: URI | undefined, workspace: Workspace | null): ConfigurationModel | null {
private getFolderConfigurationModelForResource(resource: URI | null | undefined, workspace: Workspace | null): ConfigurationModel | null {
if (workspace && resource) {
const root = workspace.getFolder(resource);
if (root) {
return this._folderConfigurations.get(root.uri);
return this._folderConfigurations.get(root.uri) || null;
}
}
return null;
@@ -476,7 +478,7 @@ export class Configuration {
keys: this._workspaceConfiguration.keys
},
folders: this._folderConfigurations.keys().reduce((result, folder) => {
const { contents, overrides, keys } = this._folderConfigurations.get(folder);
const { contents, overrides, keys } = this._folderConfigurations.get(folder)!;
result[folder.toString()] = { contents, overrides, keys };
return result;
}, Object.create({})),
@@ -497,7 +499,7 @@ export class Configuration {
addKeys(keys.user);
addKeys(keys.workspace);
for (const resource of this.folders.keys()) {
addKeys(this.folders.get(resource).keys);
addKeys(this.folders.get(resource)!.keys);
}
return all;
}
@@ -553,7 +555,7 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i
this._changedConfiguration = this._changedConfiguration.merge(arg1._changedConfiguration);
for (const resource of arg1._changedConfigurationByResource.keys()) {
let changedConfigurationByResource = this.getOrSetChangedConfigurationForResource(resource);
changedConfigurationByResource = changedConfigurationByResource.merge(arg1._changedConfigurationByResource.get(resource));
changedConfigurationByResource = changedConfigurationByResource.merge(arg1._changedConfigurationByResource.get(resource)!);
this._changedConfigurationByResource.set(resource, changedConfigurationByResource);
}
} else {

View File

@@ -10,6 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
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 { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
export const Extensions = {
Configuration: 'base.contributions.configuration'
@@ -25,13 +26,28 @@ export interface IConfigurationRegistry {
/**
* Register multiple configurations to the registry.
*/
registerConfigurations(configurations: IConfigurationNode[], defaultConfigurations: IDefaultConfigurationExtension[], validate?: boolean): void;
registerConfigurations(configurations: IConfigurationNode[], validate?: boolean): void;
/**
* Deregister multiple configurations from the registry.
*/
deregisterConfigurations(configurations: IConfigurationNode[]): void;
/**
* Register multiple default configurations to the registry.
*/
registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void;
/**
* Deregister multiple default configurations from the registry.
*/
deregisterDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void;
/**
* Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values.
* Property or default value changes are not allowed.
*/
notifyConfigurationSchemaUpdated(configuration: IConfigurationNode): void;
notifyConfigurationSchemaUpdated(...configurations: IConfigurationNode[]): void;
/**
* Event that fires whenver a configuration has been
@@ -43,7 +59,7 @@ export interface IConfigurationRegistry {
* Event that fires whenver a configuration has been
* registered.
*/
onDidRegisterConfiguration: Event<string[]>;
onDidUpdateConfiguration: Event<string[]>;
/**
* Returns all configuration nodes contributed to this registry.
@@ -93,7 +109,7 @@ export interface IConfigurationNode {
}
export interface IDefaultConfigurationExtension {
id: string;
id: ExtensionIdentifier;
name: string;
defaults: { [key: string]: {} };
}
@@ -108,21 +124,27 @@ const contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensio
class ConfigurationRegistry implements IConfigurationRegistry {
private configurationContributors: IConfigurationNode[];
private configurationProperties: { [qualifiedKey: string]: IJSONSchema };
private excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema };
private editorConfigurationSchema: IJSONSchema;
private overrideIdentifiers: string[] = [];
private readonly defaultOverridesConfigurationNode: IConfigurationNode;
private readonly configurationContributors: IConfigurationNode[];
private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema };
private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema };
private readonly editorConfigurationSchema: IJSONSchema;
private readonly overrideIdentifiers: string[] = [];
private overridePropertyPattern: string;
private readonly _onDidSchemaChange: Emitter<void> = new Emitter<void>();
private readonly _onDidSchemaChange = new Emitter<void>();
readonly onDidSchemaChange: Event<void> = this._onDidSchemaChange.event;
private readonly _onDidRegisterConfiguration: Emitter<string[]> = new Emitter<string[]>();
readonly onDidRegisterConfiguration: Event<string[]> = this._onDidRegisterConfiguration.event;
private readonly _onDidUpdateConfiguration: Emitter<string[]> = new Emitter<string[]>();
readonly onDidUpdateConfiguration: Event<string[]> = this._onDidUpdateConfiguration.event;
constructor() {
this.configurationContributors = [];
this.defaultOverridesConfigurationNode = {
id: 'defaultOverrides',
title: nls.localize('defaultConfigurations.title', "Default Configuration Overrides"),
properties: {}
};
this.configurationContributors = [this.defaultOverridesConfigurationNode];
this.editorConfigurationSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting' };
this.configurationProperties = {};
this.excludedConfigurationProperties = {};
@@ -132,15 +154,10 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void {
this.registerConfigurations([configuration], [], validate);
this.registerConfigurations([configuration], validate);
}
public registerConfigurations(configurations: IConfigurationNode[], defaultConfigurations: IDefaultConfigurationExtension[], validate: boolean = true): void {
const configurationNode = this.toConfiguration(defaultConfigurations);
if (configurationNode) {
configurations.push(configurationNode);
}
public registerConfigurations(configurations: IConfigurationNode[], validate: boolean = true): void {
const properties: string[] = [];
configurations.forEach(configuration => {
properties.push(...this.validateAndRegisterProperties(configuration, validate)); // fills in defaults
@@ -149,38 +166,98 @@ class ConfigurationRegistry implements IConfigurationRegistry {
this.updateSchemaForOverrideSettingsConfiguration(configuration);
});
this._onDidRegisterConfiguration.fire(properties);
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
public notifyConfigurationSchemaUpdated(configuration: IConfigurationNode) {
contributionRegistry.notifySchemaChanged(editorConfigurationSchemaId);
}
public deregisterConfigurations(configurations: IConfigurationNode[]): void {
const properties: string[] = [];
const deregisterConfiguration = (configuration: IConfigurationNode) => {
if (configuration.properties) {
for (const key in configuration.properties) {
properties.push(key);
public registerOverrideIdentifiers(overrideIdentifiers: string[]): void {
this.overrideIdentifiers.push(...overrideIdentifiers);
this.updateOverridePropertyPatternKey();
}
delete this.configurationProperties[key];
delete this.editorConfigurationSchema.properties![key];
private toConfiguration(defaultConfigurations: IDefaultConfigurationExtension[]): IConfigurationNode | null {
const configurationNode: IConfigurationNode = {
id: 'defaultOverrides',
title: nls.localize('defaultConfigurations.title', "Default Configuration Overrides"),
properties: {}
// Delete from schema
delete allSettings.properties[key];
switch (configuration.properties[key].scope) {
case ConfigurationScope.APPLICATION:
delete applicationSettings.properties[key];
break;
case ConfigurationScope.WINDOW:
delete windowSettings.properties[key];
break;
case ConfigurationScope.RESOURCE:
delete resourceSettings.properties[key];
break;
}
}
}
if (configuration.allOf) {
configuration.allOf.forEach(node => deregisterConfiguration(node));
}
};
for (const configuration of configurations) {
deregisterConfiguration(configuration);
const index = this.configurationContributors.indexOf(configuration);
if (index !== -1) {
this.configurationContributors.splice(index, 1);
}
}
contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema);
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
public registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void {
const properties: string[] = [];
for (const defaultConfiguration of defaultConfigurations) {
for (const key in defaultConfiguration.defaults) {
const defaultValue = defaultConfiguration.defaults[key];
if (OVERRIDE_PROPERTY_PATTERN.test(key) && typeof defaultValue === 'object') {
configurationNode.properties![key] = {
const propertySchema: IConfigurationPropertySchema = {
type: 'object',
default: defaultValue,
description: nls.localize('overrideSettings.description', "Configure editor settings to be overridden for {0} language.", key),
$ref: editorConfigurationSchemaId
};
allSettings.properties[key] = propertySchema;
this.defaultOverridesConfigurationNode.properties![key] = propertySchema;
this.configurationProperties[key] = propertySchema;
properties.push(key);
}
}
}
return Object.keys(configurationNode.properties!).length ? configurationNode : null;
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
public deregisterDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void {
const properties: string[] = [];
for (const defaultConfiguration of defaultConfigurations) {
for (const key in defaultConfiguration.defaults) {
properties.push(key);
delete allSettings.properties[key];
delete this.defaultOverridesConfigurationNode.properties![key];
delete this.configurationProperties[key];
}
}
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
public notifyConfigurationSchemaUpdated(...configurations: IConfigurationNode[]) {
this._onDidSchemaChange.fire();
}
public registerOverrideIdentifiers(overrideIdentifiers: string[]): void {
this.overrideIdentifiers.push(...overrideIdentifiers);
this.updateOverridePropertyPatternKey();
}
private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false): string[] {
@@ -208,7 +285,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
property.scope = void 0; // No scope for overridable properties `[${identifier}]`
property.scope = undefined; // No scope for overridable properties `[${identifier}]`
} else {
property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope;
}
@@ -272,7 +349,6 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
}
register(configuration);
this._onDidSchemaChange.fire();
}
private updateSchemaForOverrideSettingsConfiguration(configuration: IConfigurationNode): void {
@@ -360,7 +436,7 @@ export function validateProperty(property: string): string | null {
if (OVERRIDE_PROPERTY_PATTERN.test(property)) {
return nls.localize('config.property.languageDefault', "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property);
}
if (configurationRegistry.getConfigurationProperties()[property] !== void 0) {
if (configurationRegistry.getConfigurationProperties()[property] !== undefined) {
return nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", property);
}
return null;

View File

@@ -30,7 +30,7 @@ export class UserConfiguration extends Disposable {
userConfigModelParser.parse(content);
parseErrors = [...userConfigModelParser.errors];
return userConfigModelParser;
}, initCallback: () => c(void 0)
}, initCallback: () => c(undefined)
});
this._register(this.userConfigModelWatcher);

View File

@@ -37,7 +37,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
// Listeners
this._register(this.userConfiguration.onDidChangeConfiguration(userConfigurationModel => this.onDidChangeUserConfiguration(userConfigurationModel)));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDidRegisterConfiguration(configurationProperties)));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidUpdateConfiguration(configurationProperties => this.onDidDefaultConfigurationChange(configurationProperties)));
}
get configuration(): Configuration {
@@ -53,7 +53,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
getValue<T>(overrides: IConfigurationOverrides): T;
getValue<T>(section: string, overrides: IConfigurationOverrides): T;
getValue(arg1?: any, arg2?: any): any {
const section = typeof arg1 === 'string' ? arg1 : void 0;
const section = typeof arg1 === 'string' ? arg1 : undefined;
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {};
return this.configuration.getValue(section, overrides, null);
}
@@ -86,7 +86,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
}
reloadConfiguration(folder?: IWorkspaceFolder): Promise<void> {
return folder ? Promise.resolve(null) :
return folder ? Promise.resolve(undefined) :
this.userConfiguration.reload().then(userConfigurationModel => this.onDidChangeUserConfiguration(userConfigurationModel));
}
@@ -99,7 +99,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
}
}
private onDidRegisterConfiguration(keys: string[]): void {
private onDidDefaultConfigurationChange(keys: string[]): void {
this._configuration.updateDefaultConfiguration(new DefaultConfigurationModel());
this.trigger(keys, ConfigurationTarget.DEFAULT);
}

View File

@@ -41,7 +41,7 @@ suite('Configuration', () => {
});
test('removeFromValueTree: remove a single segemented key when its value is undefined', () => {
let target = { 'a': void 0 };
let target = { 'a': undefined };
removeFromValueTree(target, 'a');

View File

@@ -331,12 +331,12 @@ suite('CustomConfigurationModel', () => {
assert.deepEqual(testObject.configurationModel.contents, {});
assert.deepEqual(testObject.configurationModel.keys, []);
testObject.parse(null);
testObject.parse(null!);
assert.deepEqual(testObject.configurationModel.contents, {});
assert.deepEqual(testObject.configurationModel.keys, []);
testObject.parse(undefined);
testObject.parse(undefined!);
assert.deepEqual(testObject.configurationModel.contents, {});
assert.deepEqual(testObject.configurationModel.keys, []);

View File

@@ -20,7 +20,7 @@ export class TestConfigurationService implements IConfigurationService {
public getValue(arg1?: any, arg2?: any): any {
let configuration;
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0;
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : undefined;
if (overrides) {
if (overrides.resource) {
configuration = this.configurationByRoot.findSubstr(overrides.resource.fsPath);
@@ -34,10 +34,10 @@ export class TestConfigurationService implements IConfigurationService {
}
public updateValue(key: string, overrides?: IConfigurationOverrides): Promise<void> {
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
public setUserConfiguration(key: any, value: any, root?: URI): Thenable<void> {
public setUserConfiguration(key: any, value: any, root?: URI): Promise<void> {
if (root) {
const configForRoot = this.configurationByRoot.get(root.fsPath) || Object.create(null);
configForRoot[key] = value;
@@ -46,7 +46,7 @@ export class TestConfigurationService implements IConfigurationService {
this.configuration[key] = value;
}
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
public onDidChangeConfiguration() {

View File

@@ -207,14 +207,14 @@ suite('ConfigurationService - Node', () => {
const r = await testFile('config', 'config.json');
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);
assert.strictEqual(res.default, void 0);
assert.strictEqual(res.user, void 0);
assert.strictEqual(res.value, undefined);
assert.strictEqual(res.default, undefined);
assert.strictEqual(res.user, undefined);
res = service.inspect('lookup.service.testSetting');
assert.strictEqual(res.default, 'isSet');
assert.strictEqual(res.value, 'isSet');
assert.strictEqual(res.user, void 0);
assert.strictEqual(res.user, undefined);
fs.writeFileSync(r.testFile, '{ "lookup.service.testSetting": "bar" }');
@@ -245,7 +245,7 @@ suite('ConfigurationService - Node', () => {
let res = service.inspect('lookup.service.testNullSetting');
assert.strictEqual(res.default, null);
assert.strictEqual(res.value, null);
assert.strictEqual(res.user, void 0);
assert.strictEqual(res.user, undefined);
fs.writeFileSync(r.testFile, '{ "lookup.service.testNullSetting": null }');

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event, mapEvent } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { keys } from 'vs/base/common/map';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
@@ -236,7 +236,7 @@ export abstract class AbstractContextKeyService implements IContextKeyService {
public get onDidChangeContext(): Event<IContextKeyChangeEvent> {
if (!this._onDidChangeContext) {
this._onDidChangeContext = mapEvent(this._onDidChangeContextKey.event, ((changedKeyOrKeys): IContextKeyChangeEvent => {
this._onDidChangeContext = Event.map(this._onDidChangeContextKey.event, ((changedKeyOrKeys): IContextKeyChangeEvent => {
return typeof changedKeyOrKeys === 'string'
? new SimpleContextKeyChangeEvent(changedKeyOrKeys)
: new ArrayContextKeyChangeEvent(changedKeyOrKeys);

View File

@@ -42,7 +42,7 @@ export abstract class ContextKeyExpr {
return new ContextKeyNotExpr(key);
}
public static and(...expr: (ContextKeyExpr | undefined | null)[]): ContextKeyExpr {
public static and(...expr: Array<ContextKeyExpr | undefined | null>): ContextKeyExpr {
return new ContextKeyAndExpr(expr);
}
@@ -447,7 +447,7 @@ export class ContextKeyRegexExpr implements ContextKeyExpr {
export class ContextKeyAndExpr implements ContextKeyExpr {
public readonly expr: ContextKeyExpr[];
constructor(expr: (ContextKeyExpr | null | undefined)[]) {
constructor(expr: Array<ContextKeyExpr | null | undefined>) {
this.expr = ContextKeyAndExpr._normalizeArr(expr);
}
@@ -479,7 +479,7 @@ export class ContextKeyAndExpr implements ContextKeyExpr {
return true;
}
private static _normalizeArr(arr: (ContextKeyExpr | null | undefined)[]): ContextKeyExpr[] {
private static _normalizeArr(arr: Array<ContextKeyExpr | null | undefined>): ContextKeyExpr[] {
let expr: ContextKeyExpr[] = [];
if (arr) {

View File

@@ -61,10 +61,10 @@ suite('ContextKeyExpr', () => {
let key1IsFalse = ContextKeyExpr.equals('key1', false);
let key1IsNotTrue = ContextKeyExpr.notEquals('key1', true);
assert.ok(key1IsTrue.normalize().equals(ContextKeyExpr.has('key1')));
assert.ok(key1IsNotFalse.normalize().equals(ContextKeyExpr.has('key1')));
assert.ok(key1IsFalse.normalize().equals(ContextKeyExpr.not('key1')));
assert.ok(key1IsNotTrue.normalize().equals(ContextKeyExpr.not('key1')));
assert.ok(key1IsTrue.normalize()!.equals(ContextKeyExpr.has('key1')));
assert.ok(key1IsNotFalse.normalize()!.equals(ContextKeyExpr.has('key1')));
assert.ok(key1IsFalse.normalize()!.equals(ContextKeyExpr.not('key1')));
assert.ok(key1IsNotTrue.normalize()!.equals(ContextKeyExpr.not('key1')));
});
test('evaluate', () => {
@@ -78,7 +78,7 @@ suite('ContextKeyExpr', () => {
function testExpression(expr: string, expected: boolean): void {
// console.log(expr + ' ' + expected);
let rules = ContextKeyExpr.deserialize(expr);
assert.equal(rules.evaluate(context), expected, expr);
assert.equal(rules!.evaluate(context), expected, expr);
}
function testBatch(expr: string, value: any): void {
testExpression(expr, !!value);

View File

@@ -21,9 +21,9 @@ import { attachMenuStyler } from 'vs/platform/theme/common/styler';
import { domEvent } from 'vs/base/browser/event';
export class ContextMenuHandler {
private element: HTMLElement;
private element: HTMLElement | null;
private elementDisposable: IDisposable;
private menuContainerElement: HTMLElement;
private menuContainerElement: HTMLElement | null;
private focusToReturn: HTMLElement;
constructor(
@@ -37,7 +37,7 @@ export class ContextMenuHandler {
this.setContainer(element);
}
setContainer(container: HTMLElement): void {
setContainer(container: HTMLElement | null): void {
if (this.element) {
this.elementDisposable = dispose(this.elementDisposable);
this.element = null;
@@ -96,12 +96,18 @@ export class ContextMenuHandler {
},
focus: () => {
menu.focus(!!delegate.autoSelectFirstItem);
if (menu) {
menu.focus(!!delegate.autoSelectFirstItem);
}
},
onHide: (didCancel?: boolean) => {
if (delegate.onHide) {
delegate.onHide(didCancel);
delegate.onHide(!!didCancel);
}
if (this.focusToReturn) {
this.focusToReturn.focus();
}
this.menuContainerElement = null;
@@ -140,7 +146,7 @@ export class ContextMenuHandler {
}
let event = new StandardMouseEvent(e);
let element = event.target;
let element: HTMLElement | null = event.target;
while (element) {
if (element === this.menuContainerElement) {

View File

@@ -17,7 +17,7 @@ export class ContextViewService extends Disposable implements IContextViewServic
constructor(
container: HTMLElement,
@ITelemetryService telemetryService: ITelemetryService,
@ILogService private logService: ILogService
@ILogService private readonly logService: ILogService
) {
super();

View File

@@ -26,6 +26,7 @@ suite('Keytar', () => {
try {
await keytar.deletePassword(name, 'foo');
} finally {
// tslint:disable-next-line: no-unsafe-finally
throw err;
}
}

View File

@@ -127,7 +127,7 @@ export interface IDialogService {
/**
* Ask the user for confirmation with a modal dialog.
*/
confirm(confirmation: IConfirmation): Thenable<IConfirmationResult>;
confirm(confirmation: IConfirmation): Promise<IConfirmationResult>;
/**
* Present a modal dialog to the user.
@@ -136,7 +136,7 @@ export interface IDialogService {
* then a promise with index of `cancelId` option is returned. If there is no such
* option then promise with index `0` is returned.
*/
show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Thenable<number>;
show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise<number>;
}
export const IFileDialogService = createDecorator<IFileDialogService>('fileDialogService');
@@ -152,49 +152,49 @@ export interface IFileDialogService {
* The default path for a new file based on previously used files.
* @param schemeFilter The scheme of the file path.
*/
defaultFilePath(schemeFilter: string): URI;
defaultFilePath(schemeFilter: string): URI | undefined;
/**
* The default path for a new folder based on previously used folders.
* @param schemeFilter The scheme of the folder path.
*/
defaultFolderPath(schemeFilter: string): URI;
defaultFolderPath(schemeFilter: string): URI | undefined;
/**
* The default path for a new workspace based on previously used workspaces.
* @param schemeFilter The scheme of the workspace path.
*/
defaultWorkspacePath(schemeFilter: string): URI;
defaultWorkspacePath(schemeFilter: string): URI | undefined;
/**
* Shows a file-folder selection dialog and opens the selected entry.
*/
pickFileFolderAndOpen(options: IPickAndOpenOptions): Thenable<any>;
pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any>;
/**
* Shows a file selection dialog and opens the selected entry.
*/
pickFileAndOpen(options: IPickAndOpenOptions): Thenable<any>;
pickFileAndOpen(options: IPickAndOpenOptions): Promise<any>;
/**
* Shows a folder selection dialog and opens the selected entry.
*/
pickFolderAndOpen(options: IPickAndOpenOptions): Thenable<any>;
pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any>;
/**
* Shows a workspace selection dialog and opens the selected entry.
*/
pickWorkspaceAndOpen(options: IPickAndOpenOptions): Thenable<any>;
pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<any>;
/**
* Shows a save file dialog and returns the chosen file URI.
*/
showSaveDialog(options: ISaveDialogOptions): Thenable<URI>;
showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined>;
/**
* Shows a open file dialog and returns the chosen file URI.
*/
showOpenDialog(options: IOpenDialogOptions): Thenable<URI[] | undefined>;
showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined>;
}

View File

@@ -10,13 +10,13 @@ import { Event } from 'vs/base/common/event';
export class DialogChannel implements IServerChannel {
constructor(@IDialogService private dialogService: IDialogService) { }
constructor(@IDialogService private readonly dialogService: IDialogService) { }
listen<T>(_, event: string): Event<T> {
throw new Error(`Event not found: ${event}`);
}
call(_, command: string, args?: any[]): Thenable<any> {
call(_, command: string, args?: any[]): Promise<any> {
switch (command) {
case 'show': return this.dialogService.show(args![0], args![1], args![2]);
case 'confirm': return this.dialogService.confirm(args![0]);
@@ -31,11 +31,11 @@ export class DialogChannelClient implements IDialogService {
constructor(private channel: IChannel) { }
show(severity: Severity, message: string, options: string[]): Thenable<number> {
show(severity: Severity, message: string, options: string[]): Promise<number> {
return this.channel.call('show', [severity, message, options]);
}
confirm(confirmation: IConfirmation): Thenable<IConfirmationResult> {
confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
return this.channel.call('confirm', [confirmation]);
}
}

View File

@@ -1,67 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as readline from 'readline';
import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
import { canceled } from 'vs/base/common/errors';
export class CommandLineDialogService implements IDialogService {
_serviceBrand: any;
show(severity: Severity, message: string, options: string[]): Promise<number> {
const promise = new Promise<number>((c, e) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: true
});
rl.prompt();
rl.write(this.toQuestion(message, options));
rl.prompt();
rl.once('line', (answer) => {
rl.close();
c(this.toOption(answer, options));
});
rl.once('SIGINT', () => {
rl.close();
e(canceled());
});
});
return promise;
}
private toQuestion(message: string, options: string[]): string {
return options.reduce((previousValue: string, currentValue: string, currentIndex: number) => {
return previousValue + currentValue + '(' + currentIndex + ')' + (currentIndex < options.length - 1 ? ' | ' : '\n');
}, message + ' ');
}
private toOption(answer: string, options: string[]): number {
const value = parseInt(answer);
if (!isNaN(value)) {
return value;
}
answer = answer.toLocaleLowerCase();
for (let i = 0; i < options.length; i++) {
if (options[i].toLocaleLowerCase() === answer) {
return i;
}
}
return -1;
}
confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
return this.show(Severity.Info, confirmation.message, [confirmation.primaryButton || localize('ok', "Ok"), confirmation.secondaryButton || localize('cancel', "Cancel")]).then(index => {
return {
confirmed: index === 0
} as IConfirmationResult;
});
}
}

View File

@@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri';
import * as path from 'path';
import * as fs from 'fs';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
import { Event, Emitter, buffer } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IDownloadService } from 'vs/platform/download/common/download';
import { mkdirp } from 'vs/base/node/pfs';
import { IURITransformer } from 'vs/base/common/uriIpc';
@@ -19,7 +19,7 @@ export function upload(uri: URI): Event<UploadResponse> {
const readstream = fs.createReadStream(uri.fsPath);
readstream.on('data', data => stream.fire(data));
readstream.on('error', error => stream.fire(error.toString()));
readstream.on('close', () => stream.fire());
readstream.on('close', () => stream.fire(undefined));
return stream.event;
}
@@ -29,13 +29,13 @@ export class DownloadServiceChannel implements IServerChannel {
listen(_, event: string, arg?: any): Event<any> {
switch (event) {
case 'upload': return buffer(upload(URI.revive(arg)));
case 'upload': return Event.buffer(upload(URI.revive(arg)));
}
throw new Error(`Event not found: ${event}`);
}
call(_, command: string): Thenable<any> {
call(_, command: string): Promise<any> {
throw new Error(`Call not found: ${command}`);
}
}
@@ -47,7 +47,7 @@ export class DownloadServiceChannelClient implements IDownloadService {
constructor(private channel: IChannel, private getUriTransformer: () => IURITransformer) { }
download(from: URI, to: string): Promise<void> {
from = this.getUriTransformer().transformOutgoing(from);
from = this.getUriTransformer().transformOutgoingURI(from);
const dirName = path.dirname(to);
let out: fs.WriteStream;
return new Promise((c, e) => {
@@ -58,7 +58,7 @@ export class DownloadServiceChannelClient implements IDownloadService {
out.once('error', e);
const uploadStream = this.channel.listen<UploadResponse>('upload', from);
const disposable = uploadStream(result => {
if (result === void 0) {
if (result === undefined) {
disposable.dispose();
out.end(c);
} else if (Buffer.isBuffer(result)) {

View File

@@ -16,7 +16,7 @@ export class DownloadService implements IDownloadService {
_serviceBrand: any;
constructor(
@IRequestService private requestService: IRequestService
@IRequestService private readonly requestService: IRequestService
) { }
download(uri: URI, target: string, cancellationToken: CancellationToken = CancellationToken.None): Promise<void> {

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver';
import { IPCClient } from 'vs/base/parts/ipc/node/ipc';
@@ -13,20 +12,26 @@ import * as electron from 'electron';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { Terminal } from 'vscode-xterm';
import { timeout } from 'vs/base/common/async';
import { coalesce } from 'vs/base/common/arrays';
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;
if (attr) {
attributes[attr.name] = attr.value;
}
}
const children: IElement[] = [];
if (recursive) {
for (let i = 0; i < element.children.length; i++) {
children.push(serializeElement(element.children.item(i), true));
const child = element.children.item(i);
if (child) {
children.push(serializeElement(child, true));
}
}
}
@@ -46,31 +51,32 @@ function serializeElement(element: Element, recursive: boolean): IElement {
class WindowDriver implements IWindowDriver {
constructor(
@IWindowService private windowService: IWindowService
@IWindowService private readonly windowService: IWindowService
) { }
click(selector: string, xoffset?: number, yoffset?: number): TPromise<void> {
return this._click(selector, 1, xoffset, yoffset);
click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
return this._click(selector, 1, offset);
}
doubleClick(selector: string): TPromise<void> {
doubleClick(selector: string): Promise<void> {
return this._click(selector, 2);
}
private _getElementXY(selector: string, xoffset?: number, yoffset?: number): TPromise<{ x: number; y: number; }> {
private async _getElementXY(selector: string, offset?: { x: number, y: number }): Promise<{ x: number; y: number; }> {
const element = document.querySelector(selector);
if (!element) {
return TPromise.wrapError(new Error(`Element not found: ${selector}`));
return Promise.reject(new Error(`Element not found: ${selector}`));
}
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;
if (offset) {
x = left + offset.x;
y = top + offset.y;
} else {
x = left + (width / 2);
y = top + (height / 2);
@@ -79,27 +85,25 @@ class WindowDriver implements IWindowDriver {
x = Math.round(x);
y = Math.round(y);
return TPromise.as({ x, y });
return { x, y };
}
private _click(selector: string, clickCount: number, xoffset?: number, yoffset?: number): TPromise<void> {
return this._getElementXY(selector, xoffset, yoffset).then(({ x, y }) => {
private async _click(selector: string, clickCount: number, offset?: { x: number, y: number }): Promise<void> {
const { x, y } = await this._getElementXY(selector, offset);
const webContents: electron.WebContents = (electron as any).remote.getCurrentWebContents();
webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any);
const webContents: electron.WebContents = (electron as any).remote.getCurrentWebContents();
webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any);
await timeout(10);
return TPromise.wrap(timeout(10)).then(() => {
webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any);
return TPromise.wrap(timeout(100));
});
});
webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any);
await timeout(100);
}
setValue(selector: string, text: string): TPromise<void> {
async setValue(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector);
if (!element) {
return TPromise.wrapError(new Error(`Element not found: ${selector}`));
return Promise.reject(new Error(`Element not found: ${selector}`));
}
const inputElement = element as HTMLInputElement;
@@ -107,15 +111,13 @@ class WindowDriver implements IWindowDriver {
const event = new Event('input', { bubbles: true, cancelable: true });
inputElement.dispatchEvent(event);
return TPromise.as(null);
}
getTitle(): TPromise<string> {
return TPromise.as(document.title);
async getTitle(): Promise<string> {
return document.title;
}
isActiveElement(selector: string): TPromise<boolean> {
async isActiveElement(selector: string): Promise<boolean> {
const element = document.querySelector(selector);
if (element !== document.activeElement) {
@@ -125,19 +127,19 @@ class WindowDriver implements IWindowDriver {
while (el) {
const tagName = el.tagName;
const id = el.id ? `#${el.id}` : '';
const classes = el.className.split(/\s+/g).map(c => c.trim()).filter(c => !!c).map(c => `.${c}`).join('');
const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join('');
chain.unshift(`${tagName}${id}${classes}`);
el = el.parentElement;
}
return TPromise.wrapError(new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`));
throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
}
return TPromise.as(true);
return true;
}
getElements(selector: string, recursive: boolean): TPromise<IElement[]> {
async getElements(selector: string, recursive: boolean): Promise<IElement[]> {
const query = document.querySelectorAll(selector);
const result: IElement[] = [];
@@ -146,14 +148,14 @@ class WindowDriver implements IWindowDriver {
result.push(serializeElement(element, recursive));
}
return TPromise.as(result);
return result;
}
typeInEditor(selector: string, text: string): TPromise<void> {
async typeInEditor(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector);
if (!element) {
return TPromise.wrapError(new Error(`Editor not found: ${selector}`));
throw new Error(`Editor not found: ${selector}`);
}
const textarea = element as HTMLTextAreaElement;
@@ -167,21 +169,19 @@ class WindowDriver implements IWindowDriver {
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
textarea.dispatchEvent(event);
return TPromise.as(null);
}
getTerminalBuffer(selector: string): TPromise<string[]> {
async getTerminalBuffer(selector: string): Promise<string[]> {
const element = document.querySelector(selector);
if (!element) {
return TPromise.wrapError(new Error(`Terminal not found: ${selector}`));
throw new Error(`Terminal not found: ${selector}`);
}
const xterm: Terminal = (element as any).xterm;
if (!xterm) {
return TPromise.wrapError(new Error(`Xterm not found: ${selector}`));
throw new Error(`Xterm not found: ${selector}`);
}
const lines: string[] = [];
@@ -190,29 +190,27 @@ class WindowDriver implements IWindowDriver {
lines.push(xterm._core.buffer.translateBufferLineToString(i, true));
}
return TPromise.as(lines);
return lines;
}
writeInTerminal(selector: string, text: string): TPromise<void> {
async writeInTerminal(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector);
if (!element) {
return TPromise.wrapError(new Error(`Element not found: ${selector}`));
throw new Error(`Element not found: ${selector}`);
}
const xterm: Terminal = (element as any).xterm;
if (!xterm) {
return TPromise.wrapError(new Error(`Xterm not found: ${selector}`));
throw new Error(`Xterm not found: ${selector}`);
}
xterm._core.handler(text);
return TPromise.as(null);
}
openDevTools(): TPromise<void> {
return this.windowService.openDevTools({ mode: 'detach' });
async openDevTools(): Promise<void> {
await this.windowService.openDevTools({ mode: 'detach' });
}
}

View File

@@ -12,7 +12,7 @@ import { IPCServer, StaticRouter } from 'vs/base/parts/ipc/node/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';
import { Emitter, Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ScanCodeBinding } from 'vs/base/common/scanCode';
import { KeybindingParser } from 'vs/base/common/keybindingParser';
@@ -33,7 +33,7 @@ export class Driver implements IDriver, IWindowDriverRegistry {
constructor(
private windowServer: IPCServer,
private options: IDriverOptions,
@IWindowsMainService private windowsService: IWindowsMainService
@IWindowsMainService private readonly windowsService: IWindowsMainService
) { }
async registerWindowDriver(windowId: number): Promise<IDriverOptions> {
@@ -71,6 +71,10 @@ export class Driver implements IDriver, IWindowDriverRegistry {
this.windowsService.reload(window);
}
async exitApplication(): Promise<void> {
return this.windowsService.quit();
}
async dispatchKeybinding(windowId: number, keybinding: string): Promise<void> {
await this.whenUnfrozen(windowId);
@@ -183,7 +187,7 @@ export class Driver implements IDriver, IWindowDriverRegistry {
private async whenUnfrozen(windowId: number): Promise<void> {
while (this.reloadingWindowIds.has(windowId)) {
await toPromise(this.onDidReloadingChange.event);
await Event.toPromise(this.onDidReloadingChange.event);
}
}
}

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { connect as connectNet, Client } from 'vs/base/parts/ipc/node/ipc.net';
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
import { Event } from 'vs/base/common/event';
@@ -28,19 +27,20 @@ export interface 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>;
setValue(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[]>;
writeInTerminal(windowId: number, selector: string, text: string): TPromise<void>;
getWindowIds(): Promise<number[]>;
capturePage(windowId: number): Promise<string>;
reloadWindow(windowId: number): Promise<void>;
exitApplication(): Promise<void>;
dispatchKeybinding(windowId: number, keybinding: string): Promise<void>;
click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
doubleClick(windowId: number, selector: string): Promise<void>;
setValue(windowId: number, selector: string, text: string): Promise<void>;
getTitle(windowId: number): Promise<string>;
isActiveElement(windowId: number, selector: string): Promise<boolean>;
getElements(windowId: number, selector: string, recursive?: boolean): Promise<IElement[]>;
typeInEditor(windowId: number, selector: string, text: string): Promise<void>;
getTerminalBuffer(windowId: number, selector: string): Promise<string[]>;
writeInTerminal(windowId: number, selector: string, text: string): Promise<void>;
}
//*END
@@ -52,11 +52,12 @@ export class DriverChannel implements IServerChannel {
throw new Error('No event found');
}
call(_, command: string, arg?: any): TPromise<any> {
call(_, command: string, arg?: any): Promise<any> {
switch (command) {
case 'getWindowIds': return this.driver.getWindowIds();
case 'capturePage': return this.driver.capturePage(arg);
case 'reloadWindow': return this.driver.reloadWindow(arg);
case 'exitApplication': return this.driver.exitApplication();
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]);
@@ -79,56 +80,60 @@ export class DriverChannelClient implements IDriver {
constructor(private channel: IChannel) { }
getWindowIds(): TPromise<number[]> {
return TPromise.wrap(this.channel.call('getWindowIds'));
getWindowIds(): Promise<number[]> {
return this.channel.call('getWindowIds');
}
capturePage(windowId: number): TPromise<string> {
return TPromise.wrap(this.channel.call('capturePage', windowId));
capturePage(windowId: number): Promise<string> {
return this.channel.call('capturePage', windowId);
}
reloadWindow(windowId: number): TPromise<void> {
return TPromise.wrap(this.channel.call('reloadWindow', windowId));
reloadWindow(windowId: number): Promise<void> {
return this.channel.call('reloadWindow', windowId);
}
dispatchKeybinding(windowId: number, keybinding: string): TPromise<void> {
return TPromise.wrap(this.channel.call('dispatchKeybinding', [windowId, keybinding]));
exitApplication(): Promise<void> {
return this.channel.call('exitApplication');
}
click(windowId: number, selector: string, xoffset: number | undefined, yoffset: number | undefined): TPromise<void> {
return TPromise.wrap(this.channel.call('click', [windowId, selector, xoffset, yoffset]));
dispatchKeybinding(windowId: number, keybinding: string): Promise<void> {
return this.channel.call('dispatchKeybinding', [windowId, keybinding]);
}
doubleClick(windowId: number, selector: string): TPromise<void> {
return TPromise.wrap(this.channel.call('doubleClick', [windowId, selector]));
click(windowId: number, selector: string, xoffset: number | undefined, yoffset: number | undefined): Promise<void> {
return this.channel.call('click', [windowId, selector, xoffset, yoffset]);
}
setValue(windowId: number, selector: string, text: string): TPromise<void> {
return TPromise.wrap(this.channel.call('setValue', [windowId, selector, text]));
doubleClick(windowId: number, selector: string): Promise<void> {
return this.channel.call('doubleClick', [windowId, selector]);
}
getTitle(windowId: number): TPromise<string> {
return TPromise.wrap(this.channel.call('getTitle', [windowId]));
setValue(windowId: number, selector: string, text: string): Promise<void> {
return this.channel.call('setValue', [windowId, selector, text]);
}
isActiveElement(windowId: number, selector: string): TPromise<boolean> {
return TPromise.wrap(this.channel.call('isActiveElement', [windowId, selector]));
getTitle(windowId: number): Promise<string> {
return this.channel.call('getTitle', [windowId]);
}
getElements(windowId: number, selector: string, recursive: boolean): TPromise<IElement[]> {
return TPromise.wrap(this.channel.call('getElements', [windowId, selector, recursive]));
isActiveElement(windowId: number, selector: string): Promise<boolean> {
return this.channel.call('isActiveElement', [windowId, selector]);
}
typeInEditor(windowId: number, selector: string, text: string): TPromise<void> {
return TPromise.wrap(this.channel.call('typeInEditor', [windowId, selector, text]));
getElements(windowId: number, selector: string, recursive: boolean): Promise<IElement[]> {
return this.channel.call('getElements', [windowId, selector, recursive]);
}
getTerminalBuffer(windowId: number, selector: string): TPromise<string[]> {
return TPromise.wrap(this.channel.call('getTerminalBuffer', [windowId, selector]));
typeInEditor(windowId: number, selector: string, text: string): Promise<void> {
return this.channel.call('typeInEditor', [windowId, selector, text]);
}
writeInTerminal(windowId: number, selector: string, text: string): TPromise<void> {
return TPromise.wrap(this.channel.call('writeInTerminal', [windowId, selector, text]));
getTerminalBuffer(windowId: number, selector: string): Promise<string[]> {
return this.channel.call('getTerminalBuffer', [windowId, selector]);
}
writeInTerminal(windowId: number, selector: string, text: string): Promise<void> {
return this.channel.call('writeInTerminal', [windowId, selector, text]);
}
}
@@ -137,8 +142,8 @@ export interface IDriverOptions {
}
export interface IWindowDriverRegistry {
registerWindowDriver(windowId: number): TPromise<IDriverOptions>;
reloadWindowDriver(windowId: number): TPromise<void>;
registerWindowDriver(windowId: number): Promise<IDriverOptions>;
reloadWindowDriver(windowId: number): Promise<void>;
}
export class WindowDriverRegistryChannel implements IServerChannel {
@@ -149,7 +154,7 @@ export class WindowDriverRegistryChannel implements IServerChannel {
throw new Error(`Event not found: ${event}`);
}
call(_, command: string, arg?: any): Thenable<any> {
call(_, command: string, arg?: any): Promise<any> {
switch (command) {
case 'registerWindowDriver': return this.registry.registerWindowDriver(arg);
case 'reloadWindowDriver': return this.registry.reloadWindowDriver(arg);
@@ -165,25 +170,25 @@ export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry
constructor(private channel: IChannel) { }
registerWindowDriver(windowId: number): TPromise<IDriverOptions> {
return TPromise.wrap(this.channel.call('registerWindowDriver', windowId));
registerWindowDriver(windowId: number): Promise<IDriverOptions> {
return this.channel.call('registerWindowDriver', windowId);
}
reloadWindowDriver(windowId: number): TPromise<void> {
return TPromise.wrap(this.channel.call('reloadWindowDriver', windowId));
reloadWindowDriver(windowId: number): Promise<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>;
setValue(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[]>;
writeInTerminal(selector: string, text: string): TPromise<void>;
click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
doubleClick(selector: string): Promise<void>;
setValue(selector: string, text: string): Promise<void>;
getTitle(): Promise<string>;
isActiveElement(selector: string): Promise<boolean>;
getElements(selector: string, recursive: boolean): Promise<IElement[]>;
typeInEditor(selector: string, text: string): Promise<void>;
getTerminalBuffer(selector: string): Promise<string[]>;
writeInTerminal(selector: string, text: string): Promise<void>;
}
export class WindowDriverChannel implements IServerChannel {
@@ -194,7 +199,7 @@ export class WindowDriverChannel implements IServerChannel {
throw new Error(`No event found: ${event}`);
}
call(_, command: string, arg?: any): Thenable<any> {
call(_, command: string, arg?: any): Promise<any> {
switch (command) {
case 'click': return this.driver.click(arg[0], arg[1], arg[2]);
case 'doubleClick': return this.driver.doubleClick(arg);
@@ -217,40 +222,40 @@ export class WindowDriverChannelClient implements IWindowDriver {
constructor(private channel: IChannel) { }
click(selector: string, xoffset?: number, yoffset?: number): TPromise<void> {
return TPromise.wrap(this.channel.call('click', [selector, xoffset, yoffset]));
click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
return this.channel.call('click', [selector, xoffset, yoffset]);
}
doubleClick(selector: string): TPromise<void> {
return TPromise.wrap(this.channel.call('doubleClick', selector));
doubleClick(selector: string): Promise<void> {
return this.channel.call('doubleClick', selector);
}
setValue(selector: string, text: string): TPromise<void> {
return TPromise.wrap(this.channel.call('setValue', [selector, text]));
setValue(selector: string, text: string): Promise<void> {
return this.channel.call('setValue', [selector, text]);
}
getTitle(): TPromise<string> {
return TPromise.wrap(this.channel.call('getTitle'));
getTitle(): Promise<string> {
return this.channel.call('getTitle');
}
isActiveElement(selector: string): TPromise<boolean> {
return TPromise.wrap(this.channel.call('isActiveElement', selector));
isActiveElement(selector: string): Promise<boolean> {
return this.channel.call('isActiveElement', selector);
}
getElements(selector: string, recursive: boolean): TPromise<IElement[]> {
return TPromise.wrap(this.channel.call('getElements', [selector, recursive]));
getElements(selector: string, recursive: boolean): Promise<IElement[]> {
return this.channel.call('getElements', [selector, recursive]);
}
typeInEditor(selector: string, text: string): TPromise<void> {
return TPromise.wrap(this.channel.call('typeInEditor', [selector, text]));
typeInEditor(selector: string, text: string): Promise<void> {
return this.channel.call('typeInEditor', [selector, text]);
}
getTerminalBuffer(selector: string): TPromise<string[]> {
return TPromise.wrap(this.channel.call('getTerminalBuffer', selector));
getTerminalBuffer(selector: string): Promise<string[]> {
return this.channel.call('getTerminalBuffer', selector);
}
writeInTerminal(selector: string, text: string): TPromise<void> {
return TPromise.wrap(this.channel.call('writeInTerminal', [selector, text]));
writeInTerminal(selector: string, text: string): Promise<void> {
return this.channel.call('writeInTerminal', [selector, text]);
}
}

View File

@@ -11,12 +11,12 @@ export interface IEditorModel {
/**
* Emitted when the model is disposed.
*/
onDispose: Event<void>;
readonly onDispose: Event<void>;
/**
* Loads the model.
*/
load(): Thenable<IEditorModel>;
load(): Promise<IEditorModel>;
/**
* Dispose associated resources
@@ -34,12 +34,12 @@ export interface IBaseResourceInput {
/**
* Label to show for the diff editor
*/
label?: string;
readonly label?: string;
/**
* Description to show for the diff editor
*/
description?: string;
readonly description?: string;
/**
* Hint to indicate that this input should be treated as a file
@@ -48,7 +48,7 @@ export interface IBaseResourceInput {
* Without this hint, the editor service will make a guess by
* looking at the scheme of the resource(s).
*/
forceFile?: boolean;
readonly forceFile?: boolean;
}
export interface IResourceInput extends IBaseResourceInput {
@@ -61,7 +61,7 @@ export interface IResourceInput extends IBaseResourceInput {
/**
* The encoding of the text input if known.
*/
encoding?: string;
readonly encoding?: string;
}
export interface IEditorOptions {
@@ -112,10 +112,10 @@ export interface IEditorOptions {
}
export interface ITextEditorSelection {
startLineNumber: number;
startColumn: number;
endLineNumber?: number;
endColumn?: number;
readonly startLineNumber: number;
readonly startColumn: number;
readonly endLineNumber?: number;
readonly endColumn?: number;
}
export interface ITextEditorOptions extends IEditorOptions {

View File

@@ -25,10 +25,10 @@ export interface ParsedArgs {
'reuse-window'?: boolean;
locale?: string;
'user-data-dir'?: string;
performance?: boolean;
'prof-startup'?: string;
'prof-startup-prefix'?: string;
'prof-append-timers'?: string;
'prof-modules'?: string;
verbose?: boolean;
trace?: boolean;
'trace-category-filter'?: string;
@@ -134,7 +134,6 @@ export interface IEnvironmentService {
isBuilt: boolean;
wait: boolean;
status: boolean;
performance: boolean;
// logging
log?: string;

View File

@@ -3,234 +3,173 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as minimist from 'minimist';
import * as assert from 'assert';
import { firstIndex } from 'vs/base/common/arrays';
import * as os from 'os';
import { localize } from 'vs/nls';
import { ParsedArgs } from '../common/environment';
import { isWindows } from 'vs/base/common/platform';
import product from 'vs/platform/node/product';
import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files';
const options: minimist.Opts = {
string: [
// {{SQL CARBON EDIT}}
'database',
'server',
'user',
// {{SQL CARBON EDIT}} - End
'locale',
'user-data-dir',
'extensions-dir',
'folder-uri',
'file-uri',
'remote',
'extensionDevelopmentPath',
'extensionTestsPath',
'install-extension',
'disable-extension',
'uninstall-extension',
'debugId',
'debugPluginHost',
'debugBrkPluginHost',
'debugSearch',
'debugBrkSearch',
'enable-proposed-api',
'export-default-configuration',
'install-source',
'upload-logs',
'driver',
'trace-category-filter',
'trace-options',
'_',
// {{SQL CARBON EDIT}}
'database',
'server',
'user',
'command'
// {{SQL CARBON EDIT}}
],
boolean: [
// {{SQL CARBON EDIT}}
'aad',
'integrated',
// {{SQL CARBON EDIT}} - End
'help',
'version',
'wait',
'diff',
'add',
'goto',
'new-window',
'unity-launch',
'reuse-window',
'open-url',
'performance',
'prof-startup',
'verbose',
'logExtensionHostCommunication',
'disable-extensions',
'list-extensions',
'show-versions',
'nolazy',
'issue',
'skip-getting-started',
'skip-release-notes',
'sticky-quickopen',
'disable-restore-windows',
'disable-telemetry',
'disable-updates',
'disable-crash-reporter',
'skip-add-to-recently-opened',
'status',
'file-write',
'file-chmod',
'driver-verbose',
'trace'
],
alias: {
add: 'a',
help: 'h',
version: 'v',
wait: 'w',
diff: 'd',
goto: 'g',
status: 's',
'new-window': 'n',
'reuse-window': 'r',
performance: 'p',
'disable-extensions': 'disableExtensions',
'extensions-dir': 'extensionHomePath',
'debugPluginHost': 'inspect-extensions',
'debugBrkPluginHost': 'inspect-brk-extensions',
'debugSearch': 'inspect-search',
'debugBrkSearch': 'inspect-brk-search',
// {{SQL CARBON EDIT}}
database: 'D',
integrated: 'E',
server: 'S',
user: 'U',
command : 'c',
// {{SQL CARBON EDIT}}
}
};
function validate(args: ParsedArgs): ParsedArgs {
if (args.goto) {
args._.forEach(arg => assert(/^(\w:)?[^:]+(:\d*){0,2}$/.test(arg), localize('gotoValidation', "Arguments in `--goto` mode should be in the format of `FILE(:LINE(:CHARACTER))`.")));
}
if (args['max-memory']) {
assert(args['max-memory'] >= MIN_MAX_MEMORY_SIZE_MB, `The max-memory argument cannot be specified lower than ${MIN_MAX_MEMORY_SIZE_MB} MB.`);
}
return args;
}
function stripAppPath(argv: string[]): string[] | undefined {
const index = firstIndex(argv, a => !/^-/.test(a));
if (index > -1) {
return [...argv.slice(0, index), ...argv.slice(index + 1)];
}
return undefined;
}
import { ParsedArgs } from 'vs/platform/environment/common/environment';
/**
* Use this to parse raw code process.argv such as: `Electron . --verbose --wait`
* This code is also used by standalone cli's. Avoid adding any other dependencies.
*/
export function parseMainProcessArgv(processArgv: string[]): ParsedArgs {
let [, ...args] = processArgv;
// If dev, remove the first non-option argument: it's the app location
if (process.env['VSCODE_DEV']) {
args = stripAppPath(args) || [];
class HelpCategories {
o = localize('optionsUpperCase', "Options");
e = localize('extensionsManagement', "Extensions Management");
t = localize('troubleshooting', "Troubleshooting");
}
export interface Option {
id: string;
type: 'boolean' | 'string';
alias?: string;
args?: string | string[];
description?: string;
cat?: keyof HelpCategories;
}
export const options: Option[] = [
{ id: 'diff', type: 'boolean', cat: 'o', alias: 'd', args: ['file', 'file'], description: localize('diff', "Compare two files with each other.") },
{ id: 'add', type: 'boolean', cat: 'o', alias: 'a', args: 'folder', description: localize('add', "Add folder(s) to the last active window.") },
{ id: 'goto', type: 'boolean', cat: 'o', alias: 'g', args: 'file:line[:character]', description: localize('goto', "Open a file at the path on the specified line and character position.") },
{ id: 'new-window', type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindow', "Force to open a new window.") },
{ id: 'reuse-window', type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindow', "Force to open a file or folder in an already opened window.") },
{ id: 'wait', type: 'boolean', cat: 'o', alias: 'w', description: localize('wait', "Wait for the files to be closed before returning.") },
{ id: 'locale', type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") },
{ id: 'user-data-dir', type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") },
{ id: 'version', type: 'boolean', cat: 'o', alias: 'v', description: localize('version', "Print version.") },
{ id: 'help', type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
{ id: 'folder-uri', type: 'string', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") },
{ id: 'file-uri', type: 'string', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") },
{ id: 'extensions-dir', type: 'string', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
{ id: 'list-extensions', type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
{ id: 'show-versions', type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
{ id: 'install-extension', type: 'string', cat: 'e', args: 'extension-id', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts.") },
{ id: 'uninstall-extension', type: 'string', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") },
{ id: 'enable-proposed-api', type: 'string', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") },
{ id: 'verbose', type: 'boolean', cat: 't', description: localize('verbose', "Print verbose output (implies --wait).") },
{ id: 'log', type: 'string', cat: 't', args: 'level', description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.") },
{ id: 'status', type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") },
{ id: 'prof-modules', type: 'boolean', alias: 'p', cat: 't', description: localize('prof-modules', "Capture performance markers while loading JS modules and print them with 'F1 > Developer: Startup Performance") },
{ id: 'prof-startup', type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") },
{ id: 'disable-extensions', type: 'boolean', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") },
{ id: 'disable-extension', type: 'string', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") },
{ id: 'inspect-extensions', type: 'string', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") },
{ id: 'inspect-brk-search', type: 'string', args: 'port', cat: 't', description: 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.") },
{ id: 'disable-gpu', type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") },
{ id: 'upload-logs', type: 'string', cat: 't', description: localize('uploadLogs', "Uploads logs from current session to a secure endpoint.") },
{ id: 'max-memory', type: 'boolean', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") },
{ id: 'remote', type: 'string' },
{ id: 'extensionDevelopmentPath', type: 'string' },
{ id: 'extensionTestsPath', type: 'string' },
{ id: 'debugId', type: 'string' },
{ id: 'inspect-search', type: 'string' },
{ id: 'inspect-brk-extensions', type: 'string' },
{ id: 'export-default-configuration', type: 'string' },
{ id: 'install-source', type: 'string' },
{ id: 'driver', type: 'string' },
{ id: 'logExtensionHostCommunication', type: 'boolean' },
{ id: 'skip-getting-started', type: 'boolean' },
{ id: 'skip-release-notes', type: 'boolean' },
{ id: 'sticky-quickopen', type: 'boolean' },
{ id: 'disable-restore-windows', type: 'boolean' },
{ id: 'disable-telemetry', type: 'boolean' },
{ id: 'disable-updates', type: 'boolean' },
{ id: 'disable-crash-reporter', type: 'boolean' },
{ id: 'skip-add-to-recently-opened', type: 'boolean' },
{ id: 'unity-launch', type: 'boolean' },
{ id: 'open-url', type: 'boolean' },
{ id: 'nolazy', type: 'boolean' },
{ id: 'issue', type: 'boolean' },
{ id: 'file-write', type: 'boolean' },
{ id: 'file-chmod', type: 'boolean' },
{ id: 'driver-verbose', type: 'boolean' },
{ id: 'force', type: 'boolean' },
{ id: 'trace-category-filter', type: 'string' },
{ id: 'trace-options', type: 'string' },
{ id: 'prof-code-loading', type: 'boolean' },
{ id: 'debugPluginHost', type: 'string', alias: 'inspect-extensions' },
{ id: 'debugBrkPluginHost', type: 'string', alias: 'inspect-brk-extensions' },
// {{SQL CARBON EDIT}}
{ id: 'database', type: 'string', alias: 'D' },
{ id: 'server', type: 'string', alias: 'S' },
{ id: 'user', type: 'string', alias: 'U' },
{ id: 'command', type: 'string', alias: 'c' },
{ id: 'aad', type: 'boolean' },
{ id: 'integrated', type: 'boolean', alias: 'E' }
// {{SQL CARBON EDIT}} - End
];
export function parseArgs(args: string[], isOptionSupported = (_: Option) => true): ParsedArgs {
const alias: { [key: string]: string } = {};
const string: string[] = [];
const boolean: string[] = [];
for (let o of options) {
if (o.alias && isOptionSupported(o)) {
alias[o.id] = o.alias;
}
if (o.type === 'string') {
string.push(o.id);
} else if (o.type === 'boolean') {
boolean.push(o.id);
}
}
return validate(parseArgs(args));
}
/**
* Use this to parse raw code CLI process.argv such as: `Electron cli.js . --verbose --wait`
*/
export function parseCLIProcessArgv(processArgv: string[]): ParsedArgs {
let [, , ...args] = processArgv;
if (process.env['VSCODE_DEV']) {
args = stripAppPath(args) || [];
// remote aliases to avoid confusion
const parsedArgs = minimist(args, { string, boolean, alias }) as ParsedArgs;
for (let o of options) {
if (o.alias) {
delete parsedArgs[o.alias];
}
}
return validate(parseArgs(args));
return parsedArgs;
}
/**
* Use this to parse code arguments such as `--verbose --wait`
*/
export function parseArgs(args: string[]): ParsedArgs {
return minimist(args, options) as ParsedArgs;
function formatUsage(option: Option) {
let args = '';
if (option.args) {
if (Array.isArray(option.args)) {
args = ` <${option.args.join('> <')}>`;
} else {
args = ` <${option.args}>`;
}
}
if (option.alias) {
return `-${option.alias} --${option.id}${args}`;
}
return `--${option.id}${args}`;
}
const optionsHelp: { [name: string]: string; } = {
'-d, --diff <file> <file>': localize('diff', "Compare two files with each other."),
'-a, --add <dir>': localize('add', "Add folder(s) to the last active window."),
'-g, --goto <file:line[:character]>': localize('goto', "Open a file at the path on the specified line and character position."),
'-n, --new-window': localize('newWindow', "Force to open a new window."),
'-r, --reuse-window': localize('reuseWindow', "Force to open a file or folder in an already opened window."),
'-w, --wait': localize('wait', "Wait for the files to be closed before returning."),
'--locale <locale>': localize('locale', "The locale to use (e.g. en-US or zh-TW)."),
'--user-data-dir <dir>': localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code."),
'-v, --version': localize('version', "Print version."),
'-h, --help': localize('help', "Print usage.")
};
const extensionsHelp: { [name: string]: string; } = {
'--extensions-dir <dir>': localize('extensionHomePath', "Set the root path for extensions."),
'--list-extensions': localize('listExtensions', "List the installed extensions."),
'--show-versions': localize('showVersions', "Show versions of installed extensions, when using --list-extension."),
'--uninstall-extension (<extension-id> | <extension-vsix-path>)': localize('uninstallExtension', "Uninstalls an extension."),
'--install-extension (<extension-id> | <extension-vsix-path>)': localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts."),
'--enable-proposed-api (<extension-id>)': localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.")
};
const troubleshootingHelp: { [name: string]: string; } = {
'--verbose': localize('verbose', "Print verbose output (implies --wait)."),
'--log <level>': localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'."),
'-s, --status': localize('status', "Print process usage and diagnostics information."),
'-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."),
'--disable-extension <extension-id>': localize('disableExtension', "Disable an extension."),
'--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).")
};
export function formatOptions(options: { [name: string]: string; }, columns: number): string {
let keys = Object.keys(options);
let argLength = Math.max.apply(null, keys.map(k => k.length)) + 2/*left padding*/ + 1/*right padding*/;
// exported only for testing
export function formatOptions(docOptions: Option[], columns: number): string[] {
let usageTexts = docOptions.map(formatUsage);
let argLength = Math.max.apply(null, usageTexts.map(k => k.length)) + 2/*left padding*/ + 1/*right padding*/;
if (columns - argLength < 25) {
// Use a condensed version on narrow terminals
return keys.reduce((r, key) => r.concat([` ${key}`, ` ${options[key]}`]), [] as string[]).join('\n');
return docOptions.reduce<string[]>((r, o, i) => r.concat([` ${usageTexts[i]}`, ` ${o.description}`]), []);
}
let descriptionColumns = columns - argLength - 1;
let result = '';
keys.forEach(k => {
let wrappedDescription = wrapText(options[k], descriptionColumns);
let keyPadding = (<any>' ').repeat(argLength - k.length - 2/*left padding*/);
if (result.length > 0) {
result += '\n';
}
result += ' ' + k + keyPadding + wrappedDescription[0];
let result: string[] = [];
docOptions.forEach((o, i) => {
let usage = usageTexts[i];
let wrappedDescription = wrapText(o.description!, descriptionColumns);
let keyPadding = indent(argLength - usage.length - 2/*left padding*/);
result.push(' ' + usage + keyPadding + wrappedDescription[0]);
for (let i = 1; i < wrappedDescription.length; i++) {
result += '\n' + (<any>' ').repeat(argLength) + wrappedDescription[i];
result.push(indent(argLength) + wrappedDescription[i]);
}
});
return result;
}
function indent(count: number): string {
return (<any>' ').repeat(count);
}
function wrapText(text: string, columns: number): string[] {
let lines: string[] = [];
while (text.length) {
@@ -242,24 +181,34 @@ function wrapText(text: string, columns: number): string[] {
return lines;
}
export function buildHelpMessage(fullName: string, name: string, version: string): string {
const columns = (<any>process.stdout).isTTY ? (<any>process.stdout).columns : 80;
const executable = `${name}${os.platform() === 'win32' ? '.exe' : ''}`;
export function buildHelpMessage(productName: string, executableName: string, version: string, isOptionSupported = (_: Option) => true): string {
const columns = (process.stdout).isTTY && (process.stdout).columns || 80;
return `${fullName} ${version}
let categories = new HelpCategories();
${ localize('usage', "Usage")}: ${executable} [${localize('options', "options")}] [${localize('paths', 'paths')}...]
let help = [`${productName} ${version}`];
help.push('');
help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}][${localize('paths', 'paths')}...]`);
help.push('');
if (os.platform() === 'win32') {
help.push(localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {0} -')", executableName));
} else {
help.push(localize('stdinUnix', "To read from stdin, append '-' (e.g. 'ps aux | grep code | {0} -')", executableName));
}
help.push('');
for (let key in categories) {
let categoryOptions = options.filter(o => !!o.description && o.cat === key && isOptionSupported(o));
if (categoryOptions.length) {
help.push(categories[key]);
help.push(...formatOptions(categoryOptions, columns));
help.push('');
}
}
return help.join('\n');
}
${ isWindows ? localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {0} -')", product.applicationName) : localize('stdinUnix', "To read from stdin, append '-' (e.g. 'ps aux | grep code | {0} -')", product.applicationName)}
${ localize('optionsUpperCase', "Options")}:
${formatOptions(optionsHelp, columns)}
${ localize('extensionsManagement', "Extensions Management")}:
${formatOptions(extensionsHelp, columns)}
${ localize('troubleshooting', "Troubleshooting")}:
${formatOptions(troubleshootingHelp, columns)}`;
export function buildVersionMessage(version: string | undefined, commit: string | undefined): string {
return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`;
}
/**
@@ -287,4 +236,4 @@ export function hasArgs(arg: string | string[] | undefined): boolean {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { firstIndex } from 'vs/base/common/arrays';
import { localize } from 'vs/nls';
import { ParsedArgs } from '../common/environment';
import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files';
import { parseArgs } from 'vs/platform/environment/node/argv';
function validate(args: ParsedArgs): ParsedArgs {
if (args.goto) {
args._.forEach(arg => assert(/^(\w:)?[^:]+(:\d*){0,2}$/.test(arg), localize('gotoValidation', "Arguments in `--goto` mode should be in the format of `FILE(:LINE(:CHARACTER))`.")));
}
if (args['max-memory']) {
assert(args['max-memory'] >= MIN_MAX_MEMORY_SIZE_MB, `The max-memory argument cannot be specified lower than ${MIN_MAX_MEMORY_SIZE_MB} MB.`);
}
return args;
}
function stripAppPath(argv: string[]): string[] | undefined {
const index = firstIndex(argv, a => !/^-/.test(a));
if (index > -1) {
return [...argv.slice(0, index), ...argv.slice(index + 1)];
}
return undefined;
}
/**
* Use this to parse raw code process.argv such as: `Electron . --verbose --wait`
*/
export function parseMainProcessArgv(processArgv: string[]): ParsedArgs {
let [, ...args] = processArgv;
// If dev, remove the first non-option argument: it's the app location
if (process.env['VSCODE_DEV']) {
args = stripAppPath(args) || [];
}
return validate(parseArgs(args));
}
/**
* Use this to parse raw code CLI process.argv such as: `Electron cli.js . --verbose --wait`
*/
export function parseCLIProcessArgv(processArgv: string[]): ParsedArgs {
let [, , ...args] = processArgv;
if (process.env['VSCODE_DEV']) {
args = stripAppPath(args) || [];
}
return validate(parseArgs(args));
}

View File

@@ -180,7 +180,7 @@ export class EnvironmentService implements IEnvironmentService {
}
return URI.file(path.normalize(s));
}
return void 0;
return undefined;
}
@memoize
@@ -222,7 +222,6 @@ export class EnvironmentService implements IEnvironmentService {
get logExtensionHostCommunication(): boolean { return !!this._args.logExtensionHostCommunication; }
get performance(): boolean { return !!this._args.performance; }
get status(): boolean { return !!this._args.status; }
@memoize

View File

@@ -5,33 +5,38 @@
import { localize } from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier, LocalExtensionType, DidInstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getIdFromLocalExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { Disposable } from 'vs/base/common/lifecycle';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, DidInstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } 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 { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled';
const ENABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/enabled';
export class ExtensionEnablementService implements IExtensionEnablementService {
export class ExtensionEnablementService extends Disposable implements IExtensionEnablementService {
_serviceBrand: any;
private disposables: IDisposable[] = [];
private _onEnablementChanged = new Emitter<IExtension[]>();
public readonly onEnablementChanged: Event<IExtension[]> = this._onEnablementChanged.event;
private _onEnablementChanged = new Emitter<IExtensionIdentifier>();
public readonly onEnablementChanged: Event<IExtensionIdentifier> = this._onEnablementChanged.event;
private readonly storageManger: StorageManager;
constructor(
@IStorageService private storageService: IStorageService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService
@IStorageService storageService: IStorageService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService
) {
extensionManagementService.onDidInstallExtension(this._onDidInstallExtension, this, this.disposables);
extensionManagementService.onDidUninstallExtension(this._onDidUninstallExtension, this, this.disposables);
super();
this.storageManger = this._register(new StorageManager(storageService));
this._register(this.storageManger.onDidChange(extensions => this.onDidChangeStorage(extensions)));
this._register(extensionManagementService.onDidInstallExtension(this._onDidInstallExtension, this));
this._register(extensionManagementService.onDidUninstallExtension(this._onDidUninstallExtension, this));
}
private get hasWorkspace(): boolean {
@@ -62,8 +67,8 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
const allInstalledExtensions = await this.extensionManagementService.getInstalled();
for (const installedExtension of allInstalledExtensions) {
if (this._isExtensionDisabledInEnvironment(installedExtension)) {
if (!result.some(r => areSameExtensions(r, installedExtension.galleryIdentifier))) {
result.push(installedExtension.galleryIdentifier);
if (!result.some(r => areSameExtensions(r, installedExtension.identifier))) {
result.push(installedExtension.identifier);
}
}
}
@@ -72,11 +77,11 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return result;
}
getEnablementState(extension: ILocalExtension): EnablementState {
getEnablementState(extension: IExtension): EnablementState {
if (this._isExtensionDisabledInEnvironment(extension)) {
return EnablementState.Disabled;
}
const identifier = extension.galleryIdentifier;
const identifier = extension.identifier;
if (this.hasWorkspace) {
if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.WorkspaceEnabled;
@@ -92,70 +97,69 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return EnablementState.Enabled;
}
canChangeEnablement(extension: ILocalExtension): boolean {
canChangeEnablement(extension: IExtension): boolean {
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) {
if (extension.type === ExtensionType.User && this.environmentService.disableExtensions) {
return false;
}
return true;
}
setEnablement(arg: ILocalExtension | IExtensionIdentifier, newState: EnablementState): Promise<boolean> {
let identifier: IExtensionIdentifier;
if (isIExtensionIdentifier(arg)) {
identifier = arg;
} else {
if (!this.canChangeEnablement(arg)) {
return Promise.resolve(false);
}
identifier = arg.galleryIdentifier;
}
async setEnablement(extensions: IExtension[], newState: EnablementState): Promise<boolean[]> {
const workspace = newState === EnablementState.WorkspaceDisabled || newState === EnablementState.WorkspaceEnabled;
if (workspace && !this.hasWorkspace) {
return Promise.reject(new Error(localize('noWorkspace', "No workspace.")));
}
const currentState = this._getEnablementState(identifier);
const result = await Promise.all(extensions.map(e => this._setEnablement(e, newState)));
const changedExtensions = extensions.filter((e, index) => result[index]);
if (changedExtensions.length) {
this._onEnablementChanged.fire(changedExtensions);
}
return result;
}
private _setEnablement(extension: IExtension, newState: EnablementState): Promise<boolean> {
const currentState = this._getEnablementState(extension.identifier);
if (currentState === newState) {
return Promise.resolve(false);
}
switch (newState) {
case EnablementState.Enabled:
this._enableExtension(identifier);
this._enableExtension(extension.identifier);
break;
case EnablementState.Disabled:
this._disableExtension(identifier);
this._disableExtension(extension.identifier);
break;
case EnablementState.WorkspaceEnabled:
this._enableExtensionInWorkspace(identifier);
this._enableExtensionInWorkspace(extension.identifier);
break;
case EnablementState.WorkspaceDisabled:
this._disableExtensionInWorkspace(identifier);
this._disableExtensionInWorkspace(extension.identifier);
break;
}
this._onEnablementChanged.fire(identifier);
return Promise.resolve(true);
}
isEnabled(extension: ILocalExtension): boolean {
isEnabled(extension: IExtension): boolean {
const enablementState = this.getEnablementState(extension);
return enablementState === EnablementState.WorkspaceEnabled || enablementState === EnablementState.Enabled;
}
private _isExtensionDisabledInEnvironment(extension: ILocalExtension): boolean {
private _isExtensionDisabledInEnvironment(extension: IExtension): boolean {
if (this.allUserExtensionsDisabled) {
return extension.type === LocalExtensionType.User;
return extension.type === ExtensionType.User;
}
const disabledExtensions = this.environmentService.disableExtensions;
if (Array.isArray(disabledExtensions)) {
return disabledExtensions.some(id => areSameExtensions({ id }, extension.galleryIdentifier));
return disabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));
}
return false;
}
@@ -205,7 +209,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
let disabledExtensions = this._getDisabledExtensions(scope);
if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {
disabledExtensions.push(identifier);
this._setDisabledExtensions(disabledExtensions, scope, identifier);
this._setDisabledExtensions(disabledExtensions, scope);
return Promise.resolve(true);
}
return Promise.resolve(false);
@@ -220,7 +224,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
const disabledExtension = disabledExtensions[index];
if (areSameExtensions(disabledExtension, identifier)) {
disabledExtensions.splice(index, 1);
this._setDisabledExtensions(disabledExtensions, scope, identifier);
this._setDisabledExtensions(disabledExtensions, scope);
return true;
}
}
@@ -234,7 +238,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
let enabledExtensions = this._getEnabledExtensions(scope);
if (enabledExtensions.every(e => !areSameExtensions(e, identifier))) {
enabledExtensions.push(identifier);
this._setEnabledExtensions(enabledExtensions, scope, identifier);
this._setEnabledExtensions(enabledExtensions, scope);
return true;
}
return false;
@@ -249,7 +253,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
const disabledExtension = enabledExtensions[index];
if (areSameExtensions(disabledExtension, identifier)) {
enabledExtensions.splice(index, 1);
this._setEnabledExtensions(enabledExtensions, scope, identifier);
this._setEnabledExtensions(enabledExtensions, scope);
return true;
}
}
@@ -260,51 +264,48 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return this._getExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, scope);
}
private _setEnabledExtensions(enabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier): void {
this._setExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, enabledExtensions, scope, extension);
private _setEnabledExtensions(enabledExtensions: IExtensionIdentifier[], scope: StorageScope): void {
this._setExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, enabledExtensions, scope);
}
private _getDisabledExtensions(scope: StorageScope): IExtensionIdentifier[] {
return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
}
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier): void {
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions, scope, extension);
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope): void {
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions, scope);
}
private _getExtensions(storageId: string, scope: StorageScope): IExtensionIdentifier[] {
if (scope === StorageScope.WORKSPACE && !this.hasWorkspace) {
return [];
}
const value = this.storageService.get(storageId, scope, '');
return value ? JSON.parse(value) : [];
return this.storageManger.get(storageId, scope);
}
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);
}
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[], scope: StorageScope): void {
this.storageManger.set(storageId, extensions, scope);
}
private async onDidChangeStorage(extensionIdentifiers: IExtensionIdentifier[]): Promise<void> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const extensions = installedExtensions.filter(installedExtension => extensionIdentifiers.some(identifier => areSameExtensions(identifier, installedExtension.identifier)));
this._onEnablementChanged.fire(extensions);
}
private _onDidInstallExtension(event: DidInstallExtensionEvent): void {
if (event.local && event.operation === InstallOperation.Install) {
const wasDisabled = !this.isEnabled(event.local);
this._reset(event.local.galleryIdentifier);
this._reset(event.local.identifier);
if (wasDisabled) {
this._onEnablementChanged.fire(event.local.galleryIdentifier);
this._onEnablementChanged.fire([event.local]);
}
}
}
private _onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
if (!error) {
const id = getIdFromLocalExtensionId(identifier.id);
if (id) {
const extension = { id, uuid: identifier.uuid };
this._reset(extension);
}
this._reset(identifier);
}
}
@@ -313,8 +314,76 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
this._removeFromEnabledExtensions(extension, StorageScope.WORKSPACE);
this._removeFromDisabledExtensions(extension, StorageScope.GLOBAL);
}
}
dispose(): void {
this.disposables = dispose(this.disposables);
class StorageManager extends Disposable {
private storage: { [key: string]: string } = Object.create(null);
private _onDidChange: Emitter<IExtensionIdentifier[]> = this._register(new Emitter<IExtensionIdentifier[]>());
readonly onDidChange: Event<IExtensionIdentifier[]> = this._onDidChange.event;
constructor(private storageService: IStorageService) {
super();
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
}
get(key: string, scope: StorageScope): IExtensionIdentifier[] {
let value: string;
if (scope === StorageScope.GLOBAL) {
if (isUndefinedOrNull(this.storage[key])) {
this.storage[key] = this._get(key, scope);
}
value = this.storage[key];
} else {
value = this._get(key, scope);
}
return JSON.parse(value);
}
set(key: string, value: IExtensionIdentifier[], scope: StorageScope): void {
let newValue: string = JSON.stringify(value.map(({ id, uuid }) => (<IExtensionIdentifier>{ id, uuid })));
const oldValue = this._get(key, scope);
if (oldValue !== newValue) {
if (scope === StorageScope.GLOBAL) {
if (value.length) {
this.storage[key] = newValue;
} else {
delete this.storage[key];
}
}
this._set(key, value.length ? newValue : undefined, scope);
}
}
private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void {
if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) {
if (!isUndefinedOrNull(this.storage[workspaceStorageChangeEvent.key])) {
const newValue = this._get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope);
if (newValue !== this.storage[workspaceStorageChangeEvent.key]) {
const oldValues = this.get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope);
delete this.storage[workspaceStorageChangeEvent.key];
const newValues = this.get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope);
const added = oldValues.filter(oldValue => !newValues.some(newValue => areSameExtensions(oldValue, newValue)));
const removed = newValues.filter(newValue => !oldValues.some(oldValue => areSameExtensions(oldValue, newValue)));
if (added.length || removed.length) {
this._onDidChange.fire([...added, ...removed]);
}
}
}
}
}
private _get(key: string, scope: StorageScope): string {
return this.storageService.get(key, scope, '[]');
}
private _set(key: string, value: string | undefined, scope: StorageScope): void {
if (value) {
this.storageService.store(key, value, scope);
} else {
this.storageService.remove(key, scope);
}
}
}

View File

@@ -7,136 +7,14 @@ import { localize } from 'vs/nls';
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';
import { URI } from 'vs/base/common/uri';
import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IExtensionManifest, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$';
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
export interface ICommand {
command: string;
title: string;
category?: string;
}
export interface IConfigurationProperty {
description: string;
type: string | string[];
default?: any;
}
export interface IConfiguration {
properties: { [key: string]: IConfigurationProperty; };
}
export interface IDebugger {
label?: string;
type: string;
runtime: string;
}
export interface IGrammar {
language: string;
}
export interface IJSONValidation {
fileMatch: string;
url: string;
}
export interface IKeyBinding {
command: string;
key: string;
when?: string;
mac?: string;
linux?: string;
win?: string;
}
export interface ILanguage {
id: string;
extensions: string[];
aliases: string[];
}
export interface IMenu {
command: string;
alt?: string;
when?: string;
group?: string;
}
export interface ISnippet {
language: string;
}
export interface ITheme {
label: string;
}
export interface IViewContainer {
id: string;
title: string;
}
export interface IView {
id: string;
name: string;
}
export interface IColor {
id: string;
description: string;
defaults: { light: string, dark: string, highContrast: string };
}
export interface IExtensionContributions {
commands?: ICommand[];
configuration?: IConfiguration | IConfiguration[];
debuggers?: IDebugger[];
grammars?: IGrammar[];
jsonValidation?: IJSONValidation[];
keybindings?: IKeyBinding[];
languages?: ILanguage[];
menus?: { [context: string]: IMenu[] };
snippets?: ISnippet[];
themes?: ITheme[];
iconThemes?: ITheme[];
viewsContainers?: { [location: string]: IViewContainer[] };
views?: { [location: string]: IView[] };
colors?: IColor[];
localizations?: ILocalization[];
}
export type ExtensionKind = 'ui' | 'workspace';
export interface IExtensionManifest {
name: string;
publisher: string;
version: string;
// {{SQL CARBON EDIT}}
engines: { vscode: string; azdata?: string };
displayName?: string;
description?: string;
main?: string;
icon?: string;
categories?: string[];
keywords?: string[];
activationEvents?: string[];
extensionDependencies?: string[];
extensionPack?: string[];
extensionKind?: ExtensionKind;
contributes?: IExtensionContributions;
repository?: {
url: string;
};
bugs?: {
url: string;
};
}
export interface IGalleryExtensionProperties {
dependencies?: string[];
extensionPack?: string[];
@@ -152,15 +30,15 @@ export interface IGalleryExtensionAsset {
}
export interface IGalleryExtensionAssets {
manifest: IGalleryExtensionAsset;
readme: IGalleryExtensionAsset;
changelog: IGalleryExtensionAsset;
manifest: IGalleryExtensionAsset | null;
readme: IGalleryExtensionAsset | null;
changelog: IGalleryExtensionAsset | null;
license: IGalleryExtensionAsset | null;
repository: IGalleryExtensionAsset | null;
download: IGalleryExtensionAsset;
// {{SQL CARBON EDIT}}
downloadPage?: IGalleryExtensionAsset;
icon: IGalleryExtensionAsset;
license: IGalleryExtensionAsset;
repository: IGalleryExtensionAsset;
coreTranslations: { [languageId: string]: IGalleryExtensionAsset };
}
@@ -182,6 +60,10 @@ export interface IExtensionIdentifier {
uuid?: string;
}
export interface IGalleryExtensionIdentifier extends IExtensionIdentifier {
uuid: string;
}
export interface IGalleryExtensionVersion {
version: string;
date: string;
@@ -189,7 +71,7 @@ export interface IGalleryExtensionVersion {
export interface IGalleryExtension {
name: string;
identifier: IExtensionIdentifier;
identifier: IGalleryExtensionIdentifier;
version: string;
date: string;
displayName: string;
@@ -212,20 +94,11 @@ export interface IGalleryMetadata {
publisherDisplayName: string;
}
export const enum LocalExtensionType {
System,
User
}
export interface ILocalExtension {
type: LocalExtensionType;
identifier: IExtensionIdentifier;
galleryIdentifier: IExtensionIdentifier;
manifest: IExtensionManifest;
export interface ILocalExtension extends IExtension {
readonly manifest: IExtensionManifest;
metadata: IGalleryMetadata;
location: URI;
readmeUrl: string;
changelogUrl: string;
readmeUrl: URI | null;
changelogUrl: URI | null;
}
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
@@ -284,14 +157,14 @@ export interface IExtensionGalleryService {
download(extension: IGalleryExtension, operation: InstallOperation): Promise<string>;
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void>;
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest>;
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null>;
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation>;
loadCompatibleVersion(extension: IGalleryExtension, fromVersion?: string): Promise<IGalleryExtension>;
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null>;
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]>;
loadAllDependencies(dependencies: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]>;
getExtensionsReport(): Promise<IReportedExtension[]>;
getExtension(id: IExtensionIdentifier, version?: string): Promise<IGalleryExtension>;
getCompatibleExtension(extension: IGalleryExtension): Promise<IGalleryExtension | null>;
getCompatibleExtension(id: IExtensionIdentifier, version?: string): Promise<IGalleryExtension | null>;
}
export interface InstallExtensionEvent {
@@ -326,12 +199,12 @@ export interface IExtensionManagementService {
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
zip(extension: ILocalExtension): Promise<URI>;
unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier>;
unzip(zipLocation: URI, type: ExtensionType): Promise<IExtensionIdentifier>;
install(vsix: URI): Promise<IExtensionIdentifier>;
installFromGallery(extension: IGalleryExtension): Promise<void>;
uninstall(extension: ILocalExtension, force?: boolean): Promise<void>;
reinstallFromGallery(extension: ILocalExtension): Promise<void>;
getInstalled(type?: LocalExtensionType): Promise<ILocalExtension[]>;
getInstalled(type?: ExtensionType): Promise<ILocalExtension[]>;
getExtensionsReport(): Promise<IReportedExtension[]>;
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
@@ -347,7 +220,7 @@ export interface IExtensionManagementServer {
export interface IExtensionManagementServerService {
_serviceBrand: any;
readonly localExtensionManagementServer: IExtensionManagementServer | null;
readonly localExtensionManagementServer: IExtensionManagementServer;
readonly remoteExtensionManagementServer: IExtensionManagementServer | null;
getExtensionManagementServer(location: URI): IExtensionManagementServer | null;
}
@@ -361,7 +234,6 @@ export const enum EnablementState {
export const IExtensionEnablementService = createDecorator<IExtensionEnablementService>('extensionEnablementService');
// TODO: @sandy: Merge this into IExtensionManagementService when we have a storage service available in Shared process
export interface IExtensionEnablementService {
_serviceBrand: any;
@@ -370,7 +242,7 @@ export interface IExtensionEnablementService {
/**
* Event to listen on for extension enablement changes
*/
onEnablementChanged: Event<IExtensionIdentifier>;
onEnablementChanged: Event<IExtension[]>;
/**
* Returns all disabled extension identifiers for current workspace
@@ -381,17 +253,17 @@ export interface IExtensionEnablementService {
/**
* Returns the enablement state for the given extension
*/
getEnablementState(extension: ILocalExtension): EnablementState;
getEnablementState(extension: IExtension): EnablementState;
/**
* Returns `true` if the enablement can be changed.
*/
canChangeEnablement(extension: ILocalExtension): boolean;
canChangeEnablement(extension: IExtension): boolean;
/**
* Returns `true` if the given extension identifier is enabled.
*/
isEnabled(extension: ILocalExtension): boolean;
isEnabled(extension: IExtension): boolean;
/**
* Enable or disable the given extension.
@@ -402,7 +274,7 @@ export interface IExtensionEnablementService {
*
* Throws error if enablement is requested for workspace and there is no workspace
*/
setEnablement(extension: ILocalExtension, state: EnablementState): Promise<boolean>;
setEnablement(extensions: IExtension[], state: EnablementState): Promise<boolean[]>;
}
export interface IExtensionsConfigContent {

View File

@@ -24,24 +24,6 @@ export function getGalleryExtensionId(publisher: string, name: string): string {
return `${publisher.toLocaleLowerCase()}.${name.toLocaleLowerCase()}`;
}
export function getGalleryExtensionIdFromLocal(local: ILocalExtension): string {
return local.manifest ? getGalleryExtensionId(local.manifest.publisher, local.manifest.name) : local.identifier.id;
}
export const LOCAL_EXTENSION_ID_REGEX = /^([^.]+\..+)-(\d+\.\d+\.\d+(-.*)?)$/;
export function getIdFromLocalExtensionId(localExtensionId: string): string {
const matches = LOCAL_EXTENSION_ID_REGEX.exec(localExtensionId);
if (matches && matches[1]) {
return adoptToGalleryExtensionId(matches[1]);
}
return adoptToGalleryExtensionId(localExtensionId);
}
export function getLocalExtensionId(id: string, version: string): string {
return `${id}-${version}`;
}
export function groupByExtension<T>(extensions: T[], getExtensionIdentifier: (t: T) => IExtensionIdentifier): T[][] {
const byExtension: T[][] = [];
const findGroup = extension => {
@@ -65,7 +47,7 @@ export function groupByExtension<T>(extensions: T[], getExtensionIdentifier: (t:
export function getLocalExtensionTelemetryData(extension: ILocalExtension): any {
return {
id: getGalleryExtensionIdFromLocal(extension),
id: extension.identifier.id,
name: extension.manifest.name,
galleryId: null,
publisherId: extension.metadata ? extension.metadata.publisherId : null,

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { cloneAndChange } from 'vs/base/common/objects';
import { IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
const nlsRegex = /^%([\w\d.-]+)%$/i;

View File

@@ -7,7 +7,7 @@ import { tmpdir } from 'os';
import * as path from 'path';
import { distinct } from 'vs/base/common/arrays';
import { getErrorMessage, isPromiseCanceledError, canceled } from 'vs/base/common/errors';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion, IGalleryExtensionAssets, isIExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { assign, getOrDefault } from 'vs/base/common/objects';
import { IRequestService } from 'vs/platform/request/node/request';
@@ -28,6 +28,7 @@ import { ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/parts/extens
// {{SQL CARBON EDIT}} - End
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
interface IRawGalleryExtensionFile {
assetType: string;
@@ -204,7 +205,7 @@ class Query {
get searchText(): string {
const criterium = this.state.criteria.filter(criterium => criterium.filterType === FilterType.SearchText)[0];
return criterium ? criterium.value : '';
return criterium && criterium.value ? criterium.value : '';
}
}
@@ -222,7 +223,41 @@ function getCoreTranslationAssets(version: IRawGalleryExtensionVersion): { [lang
}, {});
}
function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset {
function getRepositoryAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset | null {
if (version.properties) {
const results = version.properties.filter(p => p.key === AssetType.Repository);
const gitRegExp = new RegExp('((git|ssh|http(s)?)|(git@[\w\.]+))(:(//)?)([\w\.@\:/\-~]+)(\.git)(/)?');
const uri = results.filter(r => gitRegExp.test(r.value))[0];
return uri ? { uri: uri.value, fallbackUri: uri.value } : null;
}
return getVersionAsset(version, AssetType.Repository);
}
function getDownloadAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset {
// {{SQL CARBON EDIT}} - Use the extension VSIX download URL if present
const asset = getVersionAsset(version, AssetType.VSIX);
if (asset) {
return asset;
}
// {{SQL CARBON EDIT}} - End
return {
uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true`,
fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}`
};
}
function getIconAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset {
const asset = getVersionAsset(version, AssetType.Icon);
if (asset) {
return asset;
}
const uri = require.toUrl('./media/defaultIcon.png');
return { uri, fallbackUri: uri };
}
function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset | null {
const result = version.files.filter(f => f.assetType === type)[0];
// {{SQL CARBON EDIT}}
@@ -230,63 +265,22 @@ function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IG
if (result) {
uriFromSource = result.source;
}
if (type === AssetType.Repository) {
if (version.properties) {
const results = version.properties.filter(p => p.key === type);
const gitRegExp = new RegExp('((git|ssh|http(s)?)|(git@[\w\.]+))(:(//)?)([\w\.@\:/\-~]+)(\.git)(/)?');
const uri = results.filter(r => gitRegExp.test(r.value))[0];
if (!uri) {
return {
uri: null,
fallbackUri: null
};
}
return {
uri: uri.value,
fallbackUri: uri.value,
};
}
}
if (!result) {
if (type === AssetType.Icon) {
const uri = require.toUrl('./media/defaultIcon.png');
return { uri, fallbackUri: uri };
}
if (type === AssetType.Repository) {
return {
uri: null,
fallbackUri: null
};
}
return null;
}
if (type === AssetType.VSIX) {
return {
// {{SQL CARBON EDIT}}
uri: uriFromSource || `${version.fallbackAssetUri}/${type}?redirect=true`,
fallbackUri: `${version.fallbackAssetUri}/${type}`
};
}
// {{SQL CARBON EDIT}}
if (version.assetUri) {
return {
uri: `${version.assetUri}/${type}`,
fallbackUri: `${version.fallbackAssetUri}/${type}`
};
} else {
return {
uri: uriFromSource,
fallbackUri: `${version.fallbackAssetUri}/${type}`
};
return result ? { uri: uriFromSource, fallbackUri: `${version.fallbackAssetUri}/${type}` } : null;
}
// return result ? { uri: `${version.assetUri}/${type}`, fallbackUri: `${version.fallbackAssetUri}/${type}` } : null;
// {{SQL CARBON EDIT}} - End
}
function getExtensions(version: IRawGalleryExtensionVersion, property: string): string[] {
@@ -317,16 +311,16 @@ function getIsPreview(flags: string): boolean {
}
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, index: number, query: Query, querySource?: string): IGalleryExtension {
const assets = {
const assets = <IGalleryExtensionAssets>{
manifest: getVersionAsset(version, AssetType.Manifest),
readme: getVersionAsset(version, AssetType.Details),
changelog: getVersionAsset(version, AssetType.Changelog),
download: getVersionAsset(version, AssetType.VSIX),
license: getVersionAsset(version, AssetType.License),
repository: getRepositoryAsset(version),
download: getDownloadAsset(version),
// {{SQL CARBON EDIT}} - Add downloadPage
downloadPage: getVersionAsset(version, AssetType.DownloadPage),
icon: getVersionAsset(version, AssetType.Icon),
license: getVersionAsset(version, AssetType.License),
repository: getVersionAsset(version, AssetType.Repository),
icon: getIconAsset(version),
coreTranslations: getCoreTranslationAssets(version)
};
@@ -386,10 +380,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
private readonly commonHeadersPromise: Promise<{ [key: string]: string; }>;
constructor(
@IRequestService private requestService: IRequestService,
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService,
@IRequestService private readonly requestService: IRequestService,
@ILogService private readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
// {{SQL CARBON EDIT}}
@IConfigurationService private configurationService: IConfigurationService
) {
@@ -408,12 +402,18 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return !!this.extensionsGalleryUrl;
}
getExtension({ id, uuid }: IExtensionIdentifier, version?: string): Promise<IGalleryExtension> {
getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise<IGalleryExtension | null> {
const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1;
if (extension && extension.properties.engine && isEngineValid(extension.properties.engine)) {
return Promise.resolve(extension);
}
const { id, uuid } = extension ? extension.identifier : <IExtensionIdentifier>arg1;
let query = new Query()
.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties, Flags.ExcludeNonValidated)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished));
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished))
.withAssetTypes(AssetType.Manifest, AssetType.VSIX);
if (uuid) {
query = query.withFilter(FilterType.ExtensionId, uuid);
@@ -421,16 +421,30 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
query = query.withFilter(FilterType.ExtensionName, id);
}
return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions }) => {
if (galleryExtensions.length) {
const galleryExtension = galleryExtensions[0];
const versionAsset = version ? galleryExtension.versions.filter(v => v.version === version)[0] : galleryExtension.versions[0];
if (versionAsset) {
return toExtension(galleryExtension, versionAsset, 0, query);
return this.queryGallery(query, CancellationToken.None)
.then(({ galleryExtensions }) => {
const [rawExtension] = galleryExtensions;
if (!rawExtension || !rawExtension.versions.length) {
return null;
}
}
return null;
});
if (version) {
const versionAsset = rawExtension.versions.filter(v => v.version === version)[0];
if (versionAsset) {
const extension = toExtension(rawExtension, versionAsset, 0, query);
if (extension.properties.engine && isEngineValid(extension.properties.engine)) {
return extension;
}
}
return null;
}
return this.getLastValidExtensionVersion(rawExtension, rawExtension.versions)
.then(rawVersion => {
if (rawVersion) {
return toExtension(rawExtension, rawVersion, 0, query);
}
return null;
});
});
}
query(options: IQueryOptions = {}): Promise<IPager<IGalleryExtension>> {
@@ -592,6 +606,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
private queryGallery(query: Query, token: CancellationToken): Promise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
if (!this.isEnabled()) {
return Promise.reject(new Error('No extension gallery service configured.'));
}
return this.commonHeadersPromise.then(commonHeaders => {
const data = JSON.stringify(query.raw);
const headers = assign({}, commonHeaders, {
@@ -611,20 +628,24 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
// {{SQL CARBON EDIT}}
let extensionPolicy: string = this.configurationService.getValue<string>(ExtensionsPolicyKey);
if (context.res.statusCode >= 400 && context.res.statusCode < 500 || extensionPolicy === ExtensionsPolicy.allowNone) {
if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500 || extensionPolicy === ExtensionsPolicy.allowNone) {
return { galleryExtensions: [], total: 0 };
}
return asJson<IRawGalleryQueryResult>(context).then(result => {
const r = result.results[0];
const galleryExtensions = r.extensions;
const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0];
const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0;
if (result) {
const r = result.results[0];
const galleryExtensions = r.extensions;
const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0];
const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0;
// {{SQL CARBON EDIT}}
let filteredExtensionsResult = this.createQueryResult(query, galleryExtensions);
// {{SQL CARBON EDIT}}
let filteredExtensionsResult = this.createQueryResult(query, galleryExtensions);
return { galleryExtensions: filteredExtensionsResult.galleryExtensions, total: filteredExtensionsResult.total };
return { galleryExtensions: filteredExtensionsResult.galleryExtensions, total: filteredExtensionsResult.total };
// {{SQL CARBON EDIT}} - End
}
return { galleryExtensions: [], total: 0 };
});
});
});
@@ -632,7 +653,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void> {
if (!this.isEnabled()) {
return Promise.resolve(null);
return Promise.resolve(undefined);
}
return this.commonHeadersPromise.then(commonHeaders => {
@@ -642,7 +663,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
type: 'POST',
url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`),
headers
}, CancellationToken.None).then(null, () => null);
}, CancellationToken.None).then(undefined, () => undefined);
});
}
@@ -676,17 +697,24 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
return this.getAsset(extension.assets.readme, {}, token)
.then(asText);
if (extension.assets.readme) {
return this.getAsset(extension.assets.readme, {}, token)
.then(context => asText(context))
.then(content => content || '');
}
return Promise.resolve('');
}
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest> {
return this.getAsset(extension.assets.manifest, {}, token)
.then(asText)
.then(JSON.parse);
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null> {
if (extension.assets.manifest) {
return this.getAsset(extension.assets.manifest, {}, token)
.then(asText)
.then(JSON.parse);
}
return Promise.resolve(null);
}
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation> {
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null> {
const asset = extension.assets.coreTranslations[languageId.toUpperCase()];
if (asset) {
return this.getAsset(asset)
@@ -697,8 +725,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
return this.getAsset(extension.assets.changelog, {}, token)
.then(asText);
if (extension.assets.changelog) {
return this.getAsset(extension.assets.changelog, {}, token)
.then(context => asText(context))
.then(content => content || '');
}
return Promise.resolve('');
}
loadAllDependencies(extensions: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]> {
@@ -707,7 +739,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]> {
let query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties, Flags.ExcludeNonValidated)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished));
@@ -724,7 +756,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return Promise.all(galleryExtensions[0].versions.map(v => this.getEngine(v).then(engine => isEngineValid(engine) ? v : null)))
.then(versions => versions
.filter(v => !!v)
.map(v => ({ version: v.version, date: v.lastUpdated })));
.map(v => ({ version: v!.version, date: v!.lastUpdated })));
} else {
return galleryExtensions[0].versions.map(v => ({ version: v.version, date: v.lastUpdated }));
}
@@ -733,65 +765,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
loadCompatibleVersion(extension: IGalleryExtension, fromVersion: string = extension.version): Promise<IGalleryExtension> {
// {{SQL CARBON EDIT}}
// Change to original version: removed the extension version validation
// Reason: This method is used to find the matching gallery extension for the locally installed extension,
// since we only have one entry for each extension (not in-scope to enable mutiple version support for now),
// if the new version of extension is not compatible, the extension won't be displayed properly.
if (extension.version === fromVersion) {
return Promise.resolve(extension);
}
const query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished))
.withAssetTypes(AssetType.Manifest, AssetType.VSIX)
.withFilter(FilterType.ExtensionId, extension.identifier.uuid);
return this.queryGallery(query, CancellationToken.None)
.then(({ galleryExtensions }) => {
const [rawExtension] = galleryExtensions;
if (!rawExtension) {
return null;
}
const versions: IRawGalleryExtensionVersion[] = this.getVersionsFrom(rawExtension.versions, fromVersion);
if (!versions.length) {
return null;
}
return this.getLastValidExtensionVersion(rawExtension, versions)
.then(rawVersion => {
if (rawVersion) {
return toExtension(rawExtension, rawVersion, 0, query);
}
return null;
});
});
}
private getVersionsFrom(versions: IRawGalleryExtensionVersion[], version: string): IRawGalleryExtensionVersion[] {
if (versions[0].version === version) {
return versions;
}
const result: IRawGalleryExtensionVersion[] = [];
let currentVersion: IRawGalleryExtensionVersion = null;
for (const v of versions) {
if (!currentVersion) {
if (v.version === version) {
currentVersion = v;
}
}
if (currentVersion) {
result.push(v);
}
}
return result;
}
private loadDependencies(extensionNames: string[], token: CancellationToken): Promise<IGalleryExtension[]> {
if (!extensionNames || extensionNames.length === 0) {
return Promise.resolve([]);
@@ -863,7 +836,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return asText(context)
.then(message => Promise.reject(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)));
})
.then(null, err => {
.then(undefined, err => {
if (isPromiseCanceledError(err)) {
return Promise.reject(err);
}
@@ -886,7 +859,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
this.telemetryService.publicLog('galleryService:cdnFallback', { url, message });
const fallbackOptions = assign({}, options, { url: fallbackUrl });
return this.requestService.request(fallbackOptions, token).then(null, err => {
return this.requestService.request(fallbackOptions, token).then(undefined, err => {
if (isPromiseCanceledError(err)) {
return Promise.reject(err);
}
@@ -906,7 +879,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
private getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> {
private getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion | null> {
const version = this.getLastValidExtensionVersionFromProperties(extension, versions);
if (version) {
return version;
@@ -914,7 +887,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return this.getLastValidExtensionVersionReccursively(extension, versions);
}
private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> {
private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> | null {
for (const version of versions) {
const engine = getEngine(version);
if (!engine) {
@@ -933,17 +906,20 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return Promise.resolve(engine);
}
const asset = getVersionAsset(version, AssetType.Manifest);
const headers = { 'Accept-Encoding': 'gzip' };
const manifest = getVersionAsset(version, AssetType.Manifest);
if (!manifest) {
return Promise.reject('Manifest was not found');
}
return this.getAsset(asset, { headers })
const headers = { 'Accept-Encoding': 'gzip' };
return this.getAsset(manifest, { headers })
.then(context => asJson<IExtensionManifest>(context))
.then(manifest => manifest.engines.vscode);
.then(manifest => manifest ? manifest.engines.vscode : Promise.reject('Error while reading manifest'));
}
private getLastValidExtensionVersionReccursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> {
private getLastValidExtensionVersionReccursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion | null> {
if (!versions.length) {
return null;
return Promise.resolve(null);
}
const version = versions[0];
@@ -985,10 +961,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return asJson<IRawExtensionsReport>(context).then(result => {
const map = new Map<string, IReportedExtension>();
for (const id of result.malicious) {
const ext = map.get(id) || { id: { id }, malicious: true, slow: false };
ext.malicious = true;
map.set(id, ext);
if (result) {
for (const id of result.malicious) {
const ext = map.get(id) || { id: { id }, malicious: true, slow: false };
ext.malicious = true;
map.set(id, ext);
}
}
return Promise.resolve(values(map));
@@ -1000,29 +978,21 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
export function resolveMarketplaceHeaders(environmentService: IEnvironmentService): Promise<{ [key: string]: string; }> {
const marketplaceMachineIdFile = path.join(environmentService.userDataPath, 'machineid');
return readFile(marketplaceMachineIdFile, 'utf8').then(contents => {
if (isUUID(contents)) {
return contents;
}
return Promise.resolve(null); // invalid marketplace UUID
}, error => {
return Promise.resolve(null); // error reading ID file
}).then(uuid => {
if (!uuid) {
uuid = generateUuid();
try {
writeFileAndFlushSync(marketplaceMachineIdFile, uuid);
} catch (error) {
//noop
return readFile(marketplaceMachineIdFile, 'utf8')
.then<string | null>(contents => isUUID(contents) ? contents : Promise.resolve(null), () => Promise.resolve(null) /* error reading ID file */)
.then(uuid => {
if (!uuid) {
uuid = generateUuid();
try {
writeFileAndFlushSync(marketplaceMachineIdFile, uuid);
} catch (error) {
//noop
}
}
}
return {
'X-Market-Client-Id': `VSCode ${pkg.version}`,
'User-Agent': `VSCode ${pkg.version}`,
'X-Market-User-Id': uuid
};
});
return {
'X-Market-Client-Id': `VSCode ${pkg.version}`,
'User-Agent': `VSCode ${pkg.version}`,
'X-Market-User-Id': uuid
};
});
}

View File

@@ -10,7 +10,7 @@ import { fork, ChildProcess } from 'child_process';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { posix } from 'path';
import { Limiter } from 'vs/base/common/async';
import { fromNodeEventEmitter, anyEvent, mapEvent, debounceEvent } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { rimraf } from 'vs/base/node/pfs';
@@ -29,12 +29,12 @@ export class ExtensionsLifecycle extends Disposable {
async postUninstall(extension: ILocalExtension): Promise<void> {
const script = this.parseScript(extension, 'uninstall');
if (script) {
this.logService.info(extension.identifier.id, `Running post uninstall script`);
this.logService.info(extension.identifier.id, extension.manifest.version, `Running post uninstall script`);
await this.processesLimiter.queue(() =>
this.runLifecycleHook(script.script, 'uninstall', script.args, true, extension)
.then(() => this.logService.info(extension.identifier.id, `Finished running post uninstall script`), err => this.logService.error(extension.identifier.id, `Failed to run post uninstall script: ${err}`)));
.then(() => this.logService.info(extension.identifier.id, extension.manifest.version, `Finished running post uninstall script`), err => this.logService.error(extension.identifier.id, extension.manifest.version, `Failed to run post uninstall script: ${err}`)));
}
return rimraf(this.getExtensionStoragePath(extension)).then(null, e => this.logService.error('Error while removing extension storage path', e));
return rimraf(this.getExtensionStoragePath(extension)).then(undefined, e => this.logService.error('Error while removing extension storage path', e));
}
private parseScript(extension: ILocalExtension, type: string): { script: string, args: string[] } | null {
@@ -42,7 +42,7 @@ export class ExtensionsLifecycle extends Disposable {
if (extension.location.scheme === Schemas.file && extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts'][scriptKey] === 'string') {
const script = (<string>extension.manifest['scripts'][scriptKey]).split(' ');
if (script.length < 2 || script[0] !== 'node' || !script[1]) {
this.logService.warn(extension.identifier.id, `${scriptKey} should be a node script`);
this.logService.warn(extension.identifier.id, extension.manifest.version, `${scriptKey} should be a node script`);
return null;
}
return { script: posix.join(extension.location.fsPath, script[1]), args: script.slice(2) || [] };
@@ -50,7 +50,7 @@ export class ExtensionsLifecycle extends Disposable {
return null;
}
private runLifecycleHook(lifecycleHook: string, lifecycleType: string, args: string[], timeout: boolean, extension: ILocalExtension): Thenable<void> {
private runLifecycleHook(lifecycleHook: string, lifecycleType: string, args: string[], timeout: boolean, extension: ILocalExtension): Promise<void> {
return new Promise<void>((c, e) => {
const extensionLifecycleProcess = this.start(lifecycleHook, lifecycleType, args, extension);
@@ -64,7 +64,7 @@ export class ExtensionsLifecycle extends Disposable {
if (error) {
e(error);
} else {
c(void 0);
c(undefined);
}
};
@@ -75,7 +75,7 @@ export class ExtensionsLifecycle extends Disposable {
// on exit
extensionLifecycleProcess.on('exit', (code: number, signal: string) => {
onexit(code ? `post-${lifecycleType} process exited with code ${code}` : void 0);
onexit(code ? `post-${lifecycleType} process exited with code ${code}` : undefined);
});
if (timeout) {
@@ -101,19 +101,19 @@ export class ExtensionsLifecycle extends Disposable {
extensionUninstallProcess.stdout.setEncoding('utf8');
extensionUninstallProcess.stderr.setEncoding('utf8');
const onStdout = fromNodeEventEmitter<string>(extensionUninstallProcess.stdout, 'data');
const onStderr = fromNodeEventEmitter<string>(extensionUninstallProcess.stderr, 'data');
const onStdout = Event.fromNodeEventEmitter<string>(extensionUninstallProcess.stdout, 'data');
const onStderr = Event.fromNodeEventEmitter<string>(extensionUninstallProcess.stderr, 'data');
// Log output
onStdout(data => this.logService.info(extension.identifier.id, `post-${lifecycleType}`, data));
onStderr(data => this.logService.error(extension.identifier.id, `post-${lifecycleType}`, data));
onStdout(data => this.logService.info(extension.identifier.id, extension.manifest.version, `post-${lifecycleType}`, data));
onStderr(data => this.logService.error(extension.identifier.id, extension.manifest.version, `post-${lifecycleType}`, data));
const onOutput = anyEvent(
mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })),
mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
const onOutput = Event.any(
Event.map(onStdout, o => ({ data: `%c${o}`, format: [''] })),
Event.map(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
);
// Debounce all output, so we can render it in the Chrome console as a group
const onDebouncedOutput = debounceEvent<Output>(onOutput, (r, o) => {
const onDebouncedOutput = Event.debounce<Output>(onOutput, (r, o) => {
return r
? { data: r.data + o.data, format: [...r.format, ...o.format] }
: { data: o.data, format: o.format };
@@ -130,6 +130,6 @@ export class ExtensionsLifecycle extends Disposable {
}
private getExtensionStoragePath(extension: ILocalExtension): string {
return posix.join(this.environmentService.globalStorageHome, extension.identifier.id.toLocaleLowerCase());
return posix.join(this.environmentService.globalStorageHome, extension.identifier.id.toLowerCase());
}
}

View File

@@ -4,10 +4,31 @@
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from '../common/extensionManagement';
import { Event, buffer, mapEvent } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from '../common/extensionManagement';
import { Event } from 'vs/base/common/event';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
import { cloneAndChange } from 'vs/base/common/objects';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI {
return URI.revive(transformer ? transformer.transformIncoming(uri) : uri);
}
function transformOutgoingURI(uri: URI, transformer: IURITransformer | null): URI {
return transformer ? transformer.transformOutgoingURI(uri) : uri;
}
function transformIncomingExtension(extension: ILocalExtension, transformer: IURITransformer | null): ILocalExtension {
transformer = transformer ? transformer : DefaultURITransformer;
const manifest = extension.manifest;
const transformed = transformAndReviveIncomingURIs({ ...extension, ...{ manifest: undefined } }, transformer);
return { ...transformed, ...{ manifest } };
}
function transformOutgoingExtension(extension: ILocalExtension, transformer: IURITransformer | null): ILocalExtension {
return transformer ? cloneAndChange(extension, value => value instanceof URI ? transformer.transformOutgoingURI(value) : undefined) : extension;
}
export class ExtensionManagementChannel implements IServerChannel {
@@ -16,18 +37,18 @@ export class ExtensionManagementChannel implements IServerChannel {
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
constructor(private service: IExtensionManagementService, private getUriTransformer: (requestContext: any) => IURITransformer) {
this.onInstallExtension = buffer(service.onInstallExtension, true);
this.onDidInstallExtension = buffer(service.onDidInstallExtension, true);
this.onUninstallExtension = buffer(service.onUninstallExtension, true);
this.onDidUninstallExtension = buffer(service.onDidUninstallExtension, true);
constructor(private service: IExtensionManagementService, private getUriTransformer: (requestContext: any) => IURITransformer | null) {
this.onInstallExtension = Event.buffer(service.onInstallExtension, true);
this.onDidInstallExtension = Event.buffer(service.onDidInstallExtension, true);
this.onUninstallExtension = Event.buffer(service.onUninstallExtension, true);
this.onDidUninstallExtension = Event.buffer(service.onDidUninstallExtension, true);
}
listen(context, event: string): Event<any> {
const uriTransformer = this.getUriTransformer(context);
switch (event) {
case 'onInstallExtension': return this.onInstallExtension;
case 'onDidInstallExtension': return mapEvent(this.onDidInstallExtension, i => ({ ...i, local: this._transformOutgoing(i.local, uriTransformer) }));
case 'onDidInstallExtension': return Event.map(this.onDidInstallExtension, i => ({ ...i, local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local }));
case 'onUninstallExtension': return this.onUninstallExtension;
case 'onDidUninstallExtension': return this.onDidUninstallExtension;
}
@@ -35,32 +56,22 @@ export class ExtensionManagementChannel implements IServerChannel {
throw new Error('Invalid listen');
}
call(context, command: string, args?: any): Thenable<any> {
const uriTransformer = this.getUriTransformer(context);
call(context, command: string, args?: any): Promise<any> {
const uriTransformer: IURITransformer | null = this.getUriTransformer(context);
switch (command) {
case 'zip': return this.service.zip(this._transformIncoming(args[0], uriTransformer)).then(uri => uriTransformer.transformOutgoing(uri));
case 'unzip': return this.service.unzip(URI.revive(uriTransformer.transformIncoming(args[0])), args[1]);
case 'install': return this.service.install(URI.revive(uriTransformer.transformIncoming(args[0])));
case 'zip': return this.service.zip(transformIncomingExtension(args[0], uriTransformer)).then(uri => transformOutgoingURI(uri, uriTransformer));
case 'unzip': return this.service.unzip(transformIncomingURI(args[0], uriTransformer), args[1]);
case 'install': return this.service.install(transformIncomingURI(args[0], uriTransformer));
case 'installFromGallery': return this.service.installFromGallery(args[0]);
case 'uninstall': return this.service.uninstall(this._transformIncoming(args[0], uriTransformer), args[1]);
case 'reinstallFromGallery': return this.service.reinstallFromGallery(this._transformIncoming(args[0], uriTransformer));
case 'getInstalled': return this.service.getInstalled(args[0]).then(extensions => extensions.map(e => this._transformOutgoing(e, uriTransformer)));
case 'updateMetadata': return this.service.updateMetadata(this._transformIncoming(args[0], uriTransformer), args[1]).then(e => this._transformOutgoing(e, uriTransformer));
case 'uninstall': return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), args[1]);
case 'reinstallFromGallery': return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer));
case 'getInstalled': return this.service.getInstalled(args[0]).then(extensions => extensions.map(e => transformOutgoingExtension(e, uriTransformer)));
case 'updateMetadata': return this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1]).then(e => transformOutgoingExtension(e, uriTransformer));
case 'getExtensionsReport': return this.service.getExtensionsReport();
}
throw new Error('Invalid call');
}
private _transformIncoming(extension: ILocalExtension, uriTransformer: IURITransformer): ILocalExtension {
return extension ? { ...extension, ...{ location: URI.revive(uriTransformer.transformIncoming(extension.location)) } } : extension;
}
private _transformOutgoing(extension: ILocalExtension, uriTransformer: IURITransformer): ILocalExtension;
private _transformOutgoing(extension: ILocalExtension | undefined, uriTransformer: IURITransformer): ILocalExtension | undefined;
private _transformOutgoing(extension: ILocalExtension | undefined, uriTransformer: IURITransformer): ILocalExtension | undefined {
return extension ? { ...extension, ...{ location: uriTransformer.transformOutgoing(extension.location) } } : extension;
}
}
export class ExtensionManagementChannelClient implements IExtensionManagementService {
@@ -70,7 +81,7 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
constructor(private channel: IChannel) { }
get onInstallExtension(): Event<InstallExtensionEvent> { return this.channel.listen('onInstallExtension'); }
get onDidInstallExtension(): Event<DidInstallExtensionEvent> { return mapEvent(this.channel.listen<DidInstallExtensionEvent>('onDidInstallExtension'), i => ({ ...i, local: this._transformIncoming(i.local) })); }
get onDidInstallExtension(): Event<DidInstallExtensionEvent> { return Event.map(this.channel.listen<DidInstallExtensionEvent>('onDidInstallExtension'), i => ({ ...i, local: i.local ? transformIncomingExtension(i.local, null) : i.local })); }
get onUninstallExtension(): Event<IExtensionIdentifier> { return this.channel.listen('onUninstallExtension'); }
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this.channel.listen('onDidUninstallExtension'); }
@@ -78,7 +89,7 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(result)));
}
unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier> {
unzip(zipLocation: URI, type: ExtensionType): Promise<IExtensionIdentifier> {
return Promise.resolve(this.channel.call('unzip', [zipLocation, type]));
}
@@ -98,23 +109,17 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
return Promise.resolve(this.channel.call('reinstallFromGallery', [extension]));
}
getInstalled(type: LocalExtensionType | null = null): Promise<ILocalExtension[]> {
getInstalled(type: ExtensionType | null = null): Promise<ILocalExtension[]> {
return Promise.resolve(this.channel.call<ILocalExtension[]>('getInstalled', [type]))
.then(extensions => extensions.map(extension => this._transformIncoming(extension)));
.then(extensions => extensions.map(extension => transformIncomingExtension(extension, null)));
}
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
return Promise.resolve(this.channel.call<ILocalExtension>('updateMetadata', [local, metadata]))
.then(extension => this._transformIncoming(extension));
.then(extension => transformIncomingExtension(extension, null));
}
getExtensionsReport(): Promise<IReportedExtension[]> {
return Promise.resolve(this.channel.call('getExtensionsReport'));
}
private _transformIncoming(extension: ILocalExtension): ILocalExtension;
private _transformIncoming(extension: ILocalExtension | undefined): ILocalExtension | undefined;
private _transformIncoming(extension: ILocalExtension | undefined): ILocalExtension | undefined {
return extension ? { ...extension, ...{ location: URI.revive(extension.location) } } : extension;
}
}

View File

@@ -10,11 +10,10 @@ import { assign } from 'vs/base/common/objects';
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { flatten } from 'vs/base/common/arrays';
import { extract, ExtractError, zip, IFile } from 'vs/platform/node/zip';
import { ValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
IGalleryExtension, IExtensionManifest, IGalleryMetadata,
InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType,
IGalleryExtension, IGalleryMetadata,
InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent,
StatisticType,
IExtensionIdentifier,
IReportedExtension,
@@ -22,7 +21,7 @@ import {
INSTALL_ERROR_MALICIOUS,
INSTALL_ERROR_INCOMPATIBLE
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { localizeManifest } from '../common/extensionNls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Limiter, always, createCancelablePromise, CancelablePromise, Queue } from 'vs/base/common/async';
@@ -45,6 +44,9 @@ import { Schemas } from 'vs/base/common/network';
import { CancellationToken } from 'vs/base/common/cancellation';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion } from 'vs/platform/extensions/common/extensions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isUIExtension } from 'vs/platform/extensions/node/extensionsUtil';
// {{SQL CARBON EDIT}
import product from 'vs/platform/node/product';
@@ -84,7 +86,7 @@ function readManifest(extensionPath: string): Promise<{ manifest: IExtensionMani
pfs.readFile(path.join(extensionPath, 'package.json'), 'utf8')
.then(raw => parseManifest(raw)),
pfs.readFile(path.join(extensionPath, 'package.nls.json'), 'utf8')
.then(null, err => err.code !== 'ENOENT' ? Promise.reject<string>(err) : '{}')
.then(undefined, err => err.code !== 'ENOENT' ? Promise.reject<string>(err) : '{}')
.then(raw => JSON.parse(raw))
];
@@ -98,8 +100,8 @@ function readManifest(extensionPath: string): Promise<{ manifest: IExtensionMani
interface InstallableExtension {
zipPath: string;
id: string;
metadata?: IGalleryMetadata;
identifierWithVersion: ExtensionIdentifierWithVersion;
metadata: IGalleryMetadata | null;
}
export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
@@ -130,11 +132,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
constructor(
@IEnvironmentService private environmentService: IEnvironmentService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@ILogService private logService: ILogService,
private readonly remote: boolean,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@ILogService private readonly logService: ILogService,
@optional(IDownloadService) private downloadService: IDownloadService,
@ITelemetryService private telemetryService: ITelemetryService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
super();
this.systemExtensionsPath = environmentService.builtinExtensionsPath;
@@ -159,7 +163,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
.then(path => URI.file(path));
}
unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier> {
unzip(zipLocation: URI, type: ExtensionType): Promise<IExtensionIdentifier> {
this.logService.trace('ExtensionManagementService#unzip', zipLocation.toString());
return this.install(zipLocation, type);
}
@@ -190,62 +194,58 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
install(vsix: URI, type: LocalExtensionType = LocalExtensionType.User): Promise<IExtensionIdentifier> {
// {{SQL CARBON EDIT}}
let startTime = new Date().getTime();
install(vsix: URI, type: ExtensionType = ExtensionType.User): Promise<IExtensionIdentifier> {
this.logService.trace('ExtensionManagementService#install', vsix.toString());
return createCancelablePromise(token => {
return this.downloadVsix(vsix)
.then(downloadLocation => {
const zipPath = path.resolve(downloadLocation.fsPath);
return this.downloadVsix(vsix).then(downloadLocation => {
const zipPath = path.resolve(downloadLocation.fsPath);
return getManifest(zipPath)
.then(manifest => {
const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
// {{SQL CARBON EDIT - Check VSCode and ADS version}}
if (manifest.engines && (!isEngineValid(manifest.engines.vscode, product.vscodeVersion)
|| (manifest.engines.azdata && !isEngineValid(manifest.engines.azdata, pkg.version)))) {
return Promise.reject(new Error(nls.localize('incompatible', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", identifier.id, pkg.version, manifest.version)));
}
return this.removeIfExists(identifier.id)
return getManifest(zipPath)
.then(manifest => {
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
let operation: InstallOperation = InstallOperation.Install;
// {{SQL CARBON EDIT - Check VSCode and ADS version}}
if (manifest.engines && manifest.engines.vscode && (!isEngineValid(manifest.engines.vscode, product.vscodeVersion) || (manifest.engines.azdata && !isEngineValid(manifest.engines.azdata, pkg.version)))) {
return Promise.reject(new Error(nls.localize('incompatible', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", identifier.id, pkg.version, manifest.version)));
}
const identifierWithVersion = new ExtensionIdentifierWithVersion(identifier, manifest.version);
return this.getInstalled(ExtensionType.User)
.then(installedExtensions => {
const existing = installedExtensions.filter(i => areSameExtensions(identifier, i.identifier))[0];
if (existing) {
operation = InstallOperation.Update;
if (identifierWithVersion.equals(new ExtensionIdentifierWithVersion(existing.identifier, existing.manifest.version))) {
return this.removeExtension(existing, 'existing').then(null, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name))));
} else if (semver.gt(existing.manifest.version, manifest.version)) {
return this.uninstall(existing, true);
}
}
return undefined;
})
.then(() => {
this.logService.info('Installing the extension:', identifier.id);
this._onInstallExtension.fire({ identifier, zipPath });
// {{SQL CARBON EDIT}}
// Until there's a gallery for SQL Ops Studio, skip retrieving the metadata from the gallery
return this.installExtension({ zipPath, identifierWithVersion, metadata: null }, type, token)
.then(
() => {
const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
return this.getInstalled(LocalExtensionType.User)
.then(installedExtensions => {
const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0];
return newer ? this.uninstall(newer, true) : null;
})
.then(() => {
this.logService.info('Installing the extension:', identifier.id);
this._onInstallExtension.fire({ identifier, zipPath });
// {{SQL CARBON EDIT}}
// Until there's a gallery for SQL Ops Studio, skip retrieving the metadata from the gallery
return this.installExtension({ zipPath, id: identifier.id, metadata: null }, type, token)
.then(
local => {
this.reportTelemetry(this.getTelemetryEvent(InstallOperation.Install), getLocalExtensionTelemetryData(local), new Date().getTime() - startTime, void 0);
this._onDidInstallExtension.fire({ identifier, zipPath, local, operation: InstallOperation.Install });
},
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error, operation: InstallOperation.Install }); return Promise.reject(error); }
);
// return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name))
// .then(
// metadata => this.installFromZipPath(identifier, zipPath, metadata, type, token),
// error => this.installFromZipPath(identifier, zipPath, null, type, token))
// .then(
// () => { this.logService.info('Successfully installed the extension:', identifier.id); return identifier; },
// e => {
// this.logService.error('Failed to install the extension:', identifier.id, e.message);
// return Promise.reject(e);
// });
});
},
// {{SQL CARBON EDIT}}
e => Promise.reject(new Error(nls.localize('restartCode', "Please restart Azure Data Studio before reinstalling {0}.", manifest.displayName || manifest.name))));
});
});
local => this._onDidInstallExtension.fire({ identifier, zipPath, local, operation: InstallOperation.Install }),
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error, operation: InstallOperation.Install }); return Promise.reject(error); }
);
// return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name))
// .then(
// metadata => this.installFromZipPath(identifierWithVersion, zipPath, metadata, type, operation, token),
// () => this.installFromZipPath(identifierWithVersion, zipPath, null, type, operation, token))
// .then(
// () => { this.logService.info('Successfully installed the extension:', identifier.id); return identifier; },
// e => {
// this.logService.error('Failed to install the extension:', identifier.id, e.message);
// return Promise.reject(e);
// });
// {{SQL CARBON EDIT}} - End
});
});
});
});
}
@@ -260,35 +260,25 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.downloadService.download(vsix, downloadedLocation).then(() => URI.file(downloadedLocation));
}
private removeIfExists(id: string): Promise<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 installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
return this.toNonCancellablePromise(this.getInstalled()
.then(installed => {
const operation = this.getOperation({ id: getIdFromLocalExtensionId(identifier.id), uuid: identifier.uuid }, installed);
return this.installExtension({ zipPath, id: identifier.id, metadata }, type, token)
.then(local => this.installDependenciesAndPackExtensions(local, null).then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
.then(
local => { this._onDidInstallExtension.fire({ identifier, zipPath, local, operation }); return local; },
error => { this._onDidInstallExtension.fire({ identifier, zipPath, operation, error }); return Promise.reject(error); }
);
}));
private installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IGalleryMetadata | null, type: ExtensionType, operation: InstallOperation, token: CancellationToken): Promise<ILocalExtension> {
return this.toNonCancellablePromise(this.installExtension({ zipPath, identifierWithVersion, metadata }, type, token)
.then(local => this.installDependenciesAndPackExtensions(local, null).then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
.then(
local => { this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, local, operation }); return local; },
error => { this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, operation, error }); return Promise.reject(error); }
));
}
async installFromGallery(extension: IGalleryExtension): Promise<void> {
const startTime = new Date().getTime();
this.logService.info('Installing extension:', extension.name);
this.logService.info('Installing extension:', extension.identifier.id);
this._onInstallExtension.fire({ identifier: extension.identifier, gallery: extension });
const onDidInstallExtensionSuccess = (extension: IGalleryExtension, operation: InstallOperation, local: ILocalExtension) => {
this.logService.info(`Extensions installed successfully:`, extension.identifier.id);
this._onDidInstallExtension.fire({ identifier: { id: getLocalExtensionIdFromGallery(extension, extension.version), uuid: extension.identifier.uuid }, gallery: extension, local, operation });
this.reportTelemetry(this.getTelemetryEvent(operation), getGalleryExtensionTelemetryData(extension), new Date().getTime() - startTime, void 0);
this._onDidInstallExtension.fire({ identifier: extension.identifier, gallery: extension, local, operation });
this.reportTelemetry(this.getTelemetryEvent(operation), getGalleryExtensionTelemetryData(extension), new Date().getTime() - startTime, undefined);
};
const onDidInstallExtensionFailure = (extension: IGalleryExtension, operation: InstallOperation, error) => {
@@ -308,18 +298,17 @@ export class ExtensionManagementService extends Disposable implements IExtension
return Promise.reject(error);
}
const key = getLocalExtensionId(extension.identifier.id, extension.version);
const key = new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key();
let cancellablePromise = this.installingExtensions.get(key);
if (!cancellablePromise) {
let operation: InstallOperation = InstallOperation.Install;
let cancellationToken: CancellationToken, successCallback: ValueCallback<void>, errorCallback: ErrorCallback;
let cancellationToken: CancellationToken, successCallback: (a?: any) => void, errorCallback: (e?: any) => any | null;
cancellablePromise = createCancelablePromise(token => { cancellationToken = token; return new Promise((c, e) => { successCallback = c; errorCallback = e; }); });
this.installingExtensions.set(key, cancellablePromise);
try {
const installed = await this.getInstalled(LocalExtensionType.User);
const existingExtension = installed.filter(i => areSameExtensions(i.galleryIdentifier, extension.identifier))[0];
const installed = await this.getInstalled(ExtensionType.User);
const existingExtension = installed.filter(i => areSameExtensions(i.identifier, extension.identifier))[0];
if (existingExtension) {
operation = InstallOperation.Update;
if (semver.gt(existingExtension.manifest.version, extension.version)) {
@@ -328,7 +317,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
this.downloadInstallableExtension(extension, operation)
.then(installableExtension => this.installExtension(installableExtension, LocalExtensionType.User, cancellationToken)
.then(installableExtension => this.installExtension(installableExtension, ExtensionType.User, cancellationToken)
.then(local => always(pfs.rimraf(installableExtension.zipPath), () => null).then(() => local)))
.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
.then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
@@ -347,7 +336,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
} catch (error) {
this.installingExtensions.delete(key);
onDidInstallExtensionFailure(extension, operation, error);
errorCallback(error);
return Promise.reject(error);
}
}
@@ -360,13 +349,20 @@ export class ExtensionManagementService extends Disposable implements IExtension
return Promise.reject(new ExtensionManagementError(nls.localize('malicious extension', "Can't install extension since it was reported to be problematic."), INSTALL_ERROR_MALICIOUS));
}
extension = await this.galleryService.loadCompatibleVersion(extension);
const compatibleExtension = await this.galleryService.getCompatibleExtension(extension);
if (!extension) {
if (!compatibleExtension) {
return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
}
return extension;
if (this.remote) {
const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
if (manifest && isUIExtension(manifest, this.configurationService)) {
return Promise.reject(new Error(nls.localize('notSupportedUIExtension', "Can't install extension {0} since UI Extensions are not supported", extension.identifier.id)));
}
}
return compatibleExtension;
}
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
@@ -387,10 +383,6 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private getOperation(extensionToInstall: IExtensionIdentifier, installed: ILocalExtension[]): InstallOperation {
return installed.some(i => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i), uuid: i.identifier.uuid }, extensionToInstall)) ? InstallOperation.Update : InstallOperation.Install;
}
private getTelemetryEvent(operation: InstallOperation): string {
return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install';
}
@@ -407,22 +399,22 @@ export class ExtensionManagementService extends Disposable implements IExtension
publisherDisplayName: extension.publisherDisplayName,
};
this.logService.trace('Started downloading extension:', extension.name);
this.logService.trace('Started downloading extension:', extension.identifier.id);
return this.galleryService.download(extension, operation)
.then(
zipPath => {
this.logService.info('Downloaded extension:', extension.name, zipPath);
this.logService.info('Downloaded extension:', extension.identifier.id, zipPath);
return getManifest(zipPath)
.then(
manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
manifest => (<InstallableExtension>{ zipPath, identifierWithVersion: new ExtensionIdentifierWithVersion(extension.identifier, manifest.version), metadata }),
error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
);
},
error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
}
private installExtension(installableExtension: InstallableExtension, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
return this.unsetUninstalledAndGetLocal(installableExtension.id)
private installExtension(installableExtension: InstallableExtension, type: ExtensionType, token: CancellationToken): Promise<ILocalExtension> {
return this.unsetUninstalledAndGetLocal(installableExtension.identifierWithVersion)
.then(
local => {
if (local) {
@@ -438,32 +430,37 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private unsetUninstalledAndGetLocal(id: string): Promise<ILocalExtension> {
return this.isUninstalled(id)
.then(isUninstalled => {
private unsetUninstalledAndGetLocal(identifierWithVersion: ExtensionIdentifierWithVersion): Promise<ILocalExtension | null> {
return this.isUninstalled(identifierWithVersion)
.then<ILocalExtension | null>(isUninstalled => {
if (isUninstalled) {
this.logService.trace('Removing the extension from uninstalled list:', id);
this.logService.trace('Removing the extension from uninstalled list:', identifierWithVersion.identifier.id);
// If the same version of extension is marked as uninstalled, remove it from there and return the local.
return this.unsetUninstalled(id)
return this.unsetUninstalled(identifierWithVersion)
.then(() => {
this.logService.info('Removed the extension from uninstalled list:', id);
return this.getInstalled(LocalExtensionType.User);
this.logService.info('Removed the extension from uninstalled list:', identifierWithVersion.identifier.id);
return this.getInstalled(ExtensionType.User);
})
.then(installed => installed.filter(i => i.identifier.id === id)[0]);
.then(installed => installed.filter(i => new ExtensionIdentifierWithVersion(i.identifier, i.manifest.version).equals(identifierWithVersion))[0]);
}
return null;
});
}
private extractAndInstall({ zipPath, id, metadata }: InstallableExtension, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
const location = type === LocalExtensionType.User ? this.extensionsPath : this.systemExtensionsPath;
const tempPath = path.join(location, `.${id}`);
const extensionPath = path.join(location, id);
private extractAndInstall({ zipPath, identifierWithVersion, metadata }: InstallableExtension, type: ExtensionType, token: CancellationToken): Promise<ILocalExtension> {
const { identifier } = identifierWithVersion;
const location = type === ExtensionType.User ? this.extensionsPath : this.systemExtensionsPath;
const folderName = identifierWithVersion.key();
const tempPath = path.join(location, `.${folderName}`);
const extensionPath = path.join(location, folderName);
return pfs.rimraf(extensionPath)
.then(() => this.extractAndRename(id, zipPath, tempPath, extensionPath, token), e => Promise.reject(new ExtensionManagementError(nls.localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionPath, id), INSTALL_ERROR_DELETING)))
.then(() => this.scanExtension(id, location, type))
.then(() => this.extractAndRename(identifier, zipPath, tempPath, extensionPath, token), e => Promise.reject(new ExtensionManagementError(nls.localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionPath, identifier.id), INSTALL_ERROR_DELETING)))
.then(() => this.scanExtension(folderName, location, type))
.then(local => {
this.logService.info('Installation completed.', id);
if (!local) {
return Promise.reject(nls.localize('cannot read', "Cannot read the extension from {0}", location));
}
this.logService.info('Installation completed.', identifier.id);
if (metadata) {
local.metadata = metadata;
return this.saveMetadataForLocalExtension(local);
@@ -472,9 +469,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
}, error => pfs.rimraf(extensionPath).then(() => Promise.reject(error), () => Promise.reject(error)));
}
private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string, token: CancellationToken): Promise<void> {
return this.extract(id, zipPath, extractPath, token)
.then(() => this.rename(id, extractPath, renamePath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */)
private extractAndRename(identifier: IExtensionIdentifier, zipPath: string, extractPath: string, renamePath: string, token: CancellationToken): Promise<void> {
return this.extract(identifier, zipPath, extractPath, token)
.then(() => this.rename(identifier, extractPath, renamePath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */)
.then(
() => this.logService.info('Renamed to', renamePath),
e => {
@@ -483,30 +480,30 @@ export class ExtensionManagementService extends Disposable implements IExtension
}));
}
private extract(id: string, zipPath: string, extractPath: string, token: CancellationToken): Promise<void> {
private extract(identifier: IExtensionIdentifier, zipPath: string, extractPath: string, token: CancellationToken): Promise<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, token)
.then(
() => this.logService.info(`Extracted extension to ${extractPath}:`, id),
() => this.logService.info(`Extracted extension to ${extractPath}:`, identifier.id),
e => always(pfs.rimraf(extractPath), () => null)
.then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError ? e.type : INSTALL_ERROR_EXTRACTING)))),
.then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING)))),
e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
}
private rename(id: string, extractPath: string, renamePath: string, retryUntil: number): Promise<void> {
private rename(identfier: IExtensionIdentifier, extractPath: string, renamePath: string, retryUntil: number): Promise<void> {
return pfs.rename(extractPath, renamePath)
.then(null, error => {
.then(undefined, 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);
this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identfier.id);
return this.rename(identfier, extractPath, renamePath, retryUntil);
}
return Promise.reject(new ExtensionManagementError(error.message || nls.localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING));
});
}
private installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension): Promise<void> {
private async installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension | null): Promise<void> {
if (this.galleryService.isEnabled()) {
const dependenciesAndPackExtensions: string[] = installed.manifest.extensionDependencies || [];
if (installed.manifest.extensionPack) {
@@ -522,13 +519,22 @@ export class ExtensionManagementService extends Disposable implements IExtension
if (dependenciesAndPackExtensions.length) {
return this.getInstalled()
.then(installed => {
// filter out installing and installed extensions
const names = dependenciesAndPackExtensions.filter(id => !this.installingExtensions.has(adoptToGalleryExtensionId(id)) && installed.every(({ galleryIdentifier }) => !areSameExtensions(galleryIdentifier, { id })));
// filter out installed extensions
const names = dependenciesAndPackExtensions.filter(id => installed.every(({ identifier: galleryIdentifier }) => !areSameExtensions(galleryIdentifier, { id })));
if (names.length) {
return this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length })
.then(galleryResult => {
const extensionsToInstall = galleryResult.firstPage;
return Promise.all(extensionsToInstall.map(e => this.installFromGallery(e)))
return Promise.all(extensionsToInstall.map(async e => {
if (this.remote) {
const manifest = await this.galleryService.getManifest(e, CancellationToken.None);
if (manifest && isUIExtension(manifest, this.configurationService)) {
this.logService.info('Ignored installing the UI dependency', e.identifier.id);
return;
}
}
return this.installFromGallery(e);
}))
.then(() => null, errors => this.rollback(extensionsToInstall).then(() => Promise.reject(errors), () => Promise.reject(errors)));
});
}
@@ -536,26 +542,24 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
}
return Promise.resolve(null);
return Promise.resolve(undefined);
}
private rollback(extensions: IGalleryExtension[]): Promise<void> {
return this.getInstalled(LocalExtensionType.User)
return this.getInstalled(ExtensionType.User)
.then(installed =>
Promise.all(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
Promise.all(installed.filter(local => extensions.some(galleryExtension => new ExtensionIdentifierWithVersion(local.identifier, local.manifest.version).equals(new ExtensionIdentifierWithVersion(galleryExtension.identifier, galleryExtension.version)))) // Check with version because we want to rollback the exact version
.map(local => this.uninstall(local, true))))
.then(() => null, () => null);
.then(() => undefined, () => undefined);
}
uninstall(extension: ILocalExtension, force = false): Promise<void> {
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
return this.toNonCancellablePromise(this.getInstalled(LocalExtensionType.User)
return this.toNonCancellablePromise(this.getInstalled(ExtensionType.User)
.then(installed => {
const extensionsToUninstall = installed
.filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name);
if (extensionsToUninstall.length) {
const promises = extensionsToUninstall.map(e => this.checkForDependenciesAndUninstall(e, installed));
return Promise.all(promises).then(() => null, error => Promise.reject(this.joinErrors(error)));
const extensionToUninstall = installed.filter(e => areSameExtensions(e.identifier, extension.identifier))[0];
if (extensionToUninstall) {
return this.checkForDependenciesAndUninstall(extensionToUninstall, installed).then(() => null, error => Promise.reject(this.joinErrors(error)));
} else {
return Promise.reject(new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name)));
}
@@ -576,7 +580,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
if (!local.metadata) {
return Promise.resolve(local);
}
const manifestPath = path.join(this.extensionsPath, local.identifier.id, 'package.json');
const manifestPath = path.join(local.location.fsPath, 'package.json');
return pfs.readFile(manifestPath, 'utf8')
.then(raw => parseManifest(raw))
.then(({ manifest }) => assign(manifest, { __metadata: local.metadata }))
@@ -584,7 +588,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
.then(() => local);
}
private getMetadata(extensionName: string): Promise<IGalleryMetadata> {
private getMetadata(extensionName: string): Promise<IGalleryMetadata | null> {
return this.findGalleryExtensionByName(extensionName)
.then(galleryExtension => galleryExtension ? <IGalleryMetadata>{ id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : null);
}
@@ -592,9 +596,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
private findGalleryExtension(local: ILocalExtension): Promise<IGalleryExtension> {
if (local.identifier.uuid) {
return this.findGalleryExtensionById(local.identifier.uuid)
.then(galleryExtension => galleryExtension ? galleryExtension : this.findGalleryExtensionByName(getGalleryExtensionIdFromLocal(local)));
.then(galleryExtension => galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id));
}
return this.findGalleryExtensionByName(getGalleryExtensionIdFromLocal(local));
return this.findGalleryExtensionByName(local.identifier.id);
}
private findGalleryExtensionById(uuid: string): Promise<IGalleryExtension> {
@@ -605,7 +609,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.galleryService.query({ names: [name], pageSize: 1 }).then(galleryResult => galleryResult.firstPage[0]);
}
private joinErrors(errorOrErrors: (Error | string) | ((Error | string)[])): Error {
private joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error {
const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
if (errors.length === 1) {
return errors[0] instanceof Error ? <Error>errors[0] : new Error(<string>errors[0]);
@@ -639,7 +643,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
return Promise.reject(new Error(this.getDependentsErrorMessage(extension, remainingDependents)));
}
}
return Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]).then(() => null);
return Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]).then(() => undefined);
}
private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string {
@@ -660,19 +664,20 @@ export class ExtensionManagementService extends Disposable implements IExtension
return [];
}
checked.push(extension);
if (!extension.manifest.extensionPack || extension.manifest.extensionPack.length === 0) {
return [];
const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : [];
if (extensionsPack.length) {
const packedExtensions = installed.filter(i => extensionsPack.some(id => areSameExtensions({ id }, i.identifier)));
const packOfPackedExtensions: ILocalExtension[] = [];
for (const packedExtension of packedExtensions) {
packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
}
return [...packedExtensions, ...packOfPackedExtensions];
}
const packedExtensions = installed.filter(i => extension.manifest.extensionPack.some(id => areSameExtensions({ id }, i.galleryIdentifier)));
const packOfPackedExtensions: ILocalExtension[] = [];
for (const packedExtension of packedExtensions) {
packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
}
return [...packedExtensions, ...packOfPackedExtensions];
return [];
}
private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] {
return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.galleryIdentifier)));
return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
}
private doUninstall(extension: ILocalExtension): Promise<void> {
@@ -695,14 +700,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private uninstallExtension(local: ILocalExtension): Promise<void> {
const id = getGalleryExtensionIdFromLocal(local);
let promise = this.uninstallingExtensions.get(id);
let promise = this.uninstallingExtensions.get(local.identifier.id);
if (!promise) {
// Set all versions of the extension as uninstalled
promise = createCancelablePromise(token => this.scanUserExtensions(false)
.then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions({ id: getGalleryExtensionIdFromLocal(u), uuid: u.identifier.uuid }, { id, uuid: local.identifier.uuid }))))
.then(() => { this.uninstallingExtensions.delete(id); }));
this.uninstallingExtensions.set(id, promise);
.then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions(u.identifier, local.identifier))))
.then(() => { this.uninstallingExtensions.delete(local.identifier.id); }));
this.uninstallingExtensions.set(local.identifier.id, promise);
}
return promise;
}
@@ -717,20 +721,20 @@ export class ExtensionManagementService extends Disposable implements IExtension
await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall);
}
}
this.reportTelemetry('extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), void 0, error);
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0;
this.reportTelemetry('extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), undefined, error);
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined;
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: errorcode });
}
getInstalled(type: LocalExtensionType | null = null): Promise<ILocalExtension[]> {
const promises = [];
getInstalled(type: ExtensionType | null = null): Promise<ILocalExtension[]> {
const promises: Promise<ILocalExtension[]>[] = [];
if (type === null || type === LocalExtensionType.System) {
promises.push(this.scanSystemExtensions().then(null, e => new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_SYS_EXTENSIONS)));
if (type === null || type === ExtensionType.System) {
promises.push(this.scanSystemExtensions().then(null, e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_SYS_EXTENSIONS))));
}
if (type === null || type === LocalExtensionType.User) {
promises.push(this.scanUserExtensions(true).then(null, e => new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_USER_EXTENSIONS)));
if (type === null || type === ExtensionType.User) {
promises.push(this.scanUserExtensions(true).then(null, e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_USER_EXTENSIONS))));
}
return Promise.all<ILocalExtension[]>(promises).then(flatten, errors => Promise.reject(this.joinErrors(errors)));
@@ -738,7 +742,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanSystemExtensions(): Promise<ILocalExtension[]> {
this.logService.trace('Started scanning system extensions');
const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, LocalExtensionType.System)
const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, ExtensionType.System)
.then(result => {
this.logService.info('Scanned system extensions:', result.length);
return result;
@@ -751,10 +755,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
const devSystemExtensionsPromise = this.getDevSystemExtensionsList()
.then(devSystemExtensionsList => {
if (devSystemExtensionsList.length) {
return this.scanExtensions(this.devSystemExtensionsPath, LocalExtensionType.System)
return this.scanExtensions(this.devSystemExtensionsPath, ExtensionType.System)
.then(result => {
this.logService.info('Scanned dev system extensions:', result.length);
return result.filter(r => devSystemExtensionsList.some(id => areSameExtensions(r.galleryIdentifier, { id })));
return result.filter(r => devSystemExtensionsList.some(id => areSameExtensions(r.identifier, { id })));
});
} else {
return [];
@@ -766,11 +770,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
this.logService.trace('Started scanning user extensions');
return Promise.all([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, LocalExtensionType.User)])
return Promise.all([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, ExtensionType.User)])
.then(([uninstalled, extensions]) => {
extensions = extensions.filter(e => !uninstalled[e.identifier.id]);
extensions = extensions.filter(e => !uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]);
if (excludeOutdated) {
const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid }));
const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => e.identifier);
extensions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]);
}
this.logService.info('Scanned user extensions:', extensions.length);
@@ -778,36 +782,29 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private scanExtensions(root: string, type: LocalExtensionType): Promise<ILocalExtension[]> {
private scanExtensions(root: string, type: ExtensionType): Promise<ILocalExtension[]> {
const limiter = new Limiter<any>(10);
return pfs.readdir(root)
.then(extensionsFolders => Promise.all<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): Promise<ILocalExtension> {
if (type === LocalExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user extension folder starting with `.`
private scanExtension(folderName: string, root: string, type: ExtensionType): Promise<ILocalExtension | null> {
if (type === ExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user extension folder starting with `.`
return Promise.resolve(null);
}
const extensionPath = path.join(root, folderName);
return pfs.readdir(extensionPath)
.then(children => readManifest(extensionPath)
.then<ILocalExtension>(({ manifest, metadata }) => {
.then(({ manifest, metadata }) => {
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)).toString() : null;
const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)) : null;
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)).toString() : null;
if (manifest.extensionDependencies) {
manifest.extensionDependencies = manifest.extensionDependencies.map(id => adoptToGalleryExtensionId(id));
}
if (manifest.extensionPack) {
manifest.extensionPack = manifest.extensionPack.map(id => adoptToGalleryExtensionId(id));
}
const identifier = { id: type === LocalExtensionType.System ? folderName : getLocalExtensionIdFromManifest(manifest), uuid: metadata ? metadata.id : null };
const galleryIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: identifier.uuid };
return { type, identifier, galleryIdentifier, manifest, metadata, location: URI.file(extensionPath), readmeUrl, changelogUrl };
const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)) : null;
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: metadata ? metadata.id : null };
return <ILocalExtension>{ type, identifier, manifest, metadata, location: URI.file(extensionPath), readmeUrl, changelogUrl };
}))
.then(null, () => null);
.then(undefined, () => null);
}
removeDeprecatedExtensions(): Promise<any> {
@@ -817,48 +814,48 @@ export class ExtensionManagementService extends Disposable implements IExtension
private removeUninstalledExtensions(): Promise<void> {
return this.getUninstalledExtensions()
.then(uninstalled => this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
.then(uninstalled => this.scanExtensions(this.extensionsPath, ExtensionType.User) // All user extensions
.then(extensions => {
const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[e.identifier.id]);
const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]);
return Promise.all(toRemove.map(e => this.extensionLifecycle.postUninstall(e).then(() => this.removeUninstalledExtension(e))));
})
).then(() => null);
).then(() => undefined);
}
private removeOutdatedExtensions(): Promise<void> {
return this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
return this.scanExtensions(this.extensionsPath, ExtensionType.User) // All user extensions
.then(extensions => {
const toRemove: ILocalExtension[] = [];
// Outdated extensions
const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid }));
const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => e.identifier);
toRemove.push(...flatten(byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version)).slice(1))));
return Promise.all(toRemove.map(extension => this.removeExtension(extension, 'outdated')));
}).then(() => null);
}).then(() => undefined);
}
private removeUninstalledExtension(extension: ILocalExtension): Promise<void> {
return this.removeExtension(extension, 'uninstalled')
.then(() => this.withUninstalledExtensions(uninstalled => delete uninstalled[extension.identifier.id]))
.then(() => null);
.then(() => this.withUninstalledExtensions(uninstalled => delete uninstalled[new ExtensionIdentifierWithVersion(extension.identifier, extension.manifest.version).key()]))
.then(() => undefined);
}
private removeExtension(extension: ILocalExtension, type: string): Promise<void> {
this.logService.trace(`Deleting ${type} extension from disk`, extension.identifier.id);
return pfs.rimraf(extension.location.fsPath).then(() => this.logService.info('Deleted from disk', extension.identifier.id));
this.logService.trace(`Deleting ${type} extension from disk`, extension.identifier.id, extension.location.fsPath);
return pfs.rimraf(extension.location.fsPath).then(() => this.logService.info('Deleted from disk', extension.identifier.id, extension.location.fsPath));
}
private isUninstalled(id: string): Promise<boolean> {
return this.filterUninstalled(id).then(uninstalled => uninstalled.length === 1);
private isUninstalled(identfier: ExtensionIdentifierWithVersion): Promise<boolean> {
return this.filterUninstalled(identfier).then(uninstalled => uninstalled.length === 1);
}
private filterUninstalled(...ids: string[]): Promise<string[]> {
private filterUninstalled(...identifiers: ExtensionIdentifierWithVersion[]): Promise<string[]> {
return this.withUninstalledExtensions(allUninstalled => {
const uninstalled: string[] = [];
for (const id of ids) {
if (!!allUninstalled[id]) {
uninstalled.push(id);
for (const identifier of identifiers) {
if (!!allUninstalled[identifier.key()]) {
uninstalled.push(identifier.key());
}
}
return uninstalled;
@@ -866,12 +863,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private setUninstalled(...extensions: ILocalExtension[]): Promise<{ [id: string]: boolean }> {
const ids = extensions.map(e => e.identifier.id);
return this.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id] = true; return result; }, {} as { [id: string]: boolean })));
const ids: ExtensionIdentifierWithVersion[] = extensions.map(e => new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version));
return this.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id.key()] = true; return result; }, {} as { [id: string]: boolean })));
}
private unsetUninstalled(id: string): Promise<void> {
return this.withUninstalledExtensions<void>(uninstalled => delete uninstalled[id]);
private unsetUninstalled(extensionIdentifier: ExtensionIdentifierWithVersion): Promise<void> {
return this.withUninstalledExtensions<void>(uninstalled => delete uninstalled[extensionIdentifier.key()]);
}
private getUninstalledExtensions(): Promise<{ [id: string]: boolean; }> {
@@ -882,7 +879,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
return await this.uninstalledFileLimiter.queue(() => {
let result: T | null = null;
return pfs.readFile(this.uninstalledPath, 'utf8')
.then(null, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err))
.then(undefined, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err))
.then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } })
.then(uninstalled => { result = fn(uninstalled); return uninstalled; })
.then(uninstalled => {
@@ -949,8 +946,8 @@ export class ExtensionManagementService extends Disposable implements IExtension
return new Promise((c, e) => promise.then(result => c(result), error => e(error)));
}
private reportTelemetry(eventName: string, extensionData: any, duration: number, error?: Error): void {
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0;
private reportTelemetry(eventName: string, extensionData: any, duration?: number, error?: Error): void {
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined;
/* __GDPR__
"extensionGallery:install" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
@@ -984,12 +981,4 @@ export class ExtensionManagementService extends Disposable implements IExtension
*/
this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
}
}
export function getLocalExtensionIdFromGallery(extension: IGalleryExtension, version: string): string {
return getLocalExtensionId(extension.identifier.id, version);
}
export function getLocalExtensionIdFromManifest(manifest: IExtensionManifest): string {
return getLocalExtensionId(getGalleryExtensionId(manifest.publisher, manifest.name), manifest.version);
}
}

View File

@@ -3,25 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as semver from 'semver';
import { adoptToGalleryExtensionId, LOCAL_EXTENSION_ID_REGEX } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { buffer } from 'vs/platform/node/zip';
import { localize } from 'vs/nls';
export function getIdAndVersionFromLocalExtensionId(localExtensionId: string): { id: string, version: string | null } {
const matches = LOCAL_EXTENSION_ID_REGEX.exec(localExtensionId);
if (matches && matches[1] && matches[2]) {
const version = semver.valid(matches[2]);
if (version) {
return { id: adoptToGalleryExtensionId(matches[1]), version };
}
}
return {
id: adoptToGalleryExtensionId(localExtensionId),
version: null
};
}
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
export function getManifest(vsix: string): Promise<IExtensionManifest> {
return buffer(vsix, 'extension/package.json')

View File

@@ -5,19 +5,23 @@
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
IExtensionManagementService, ILocalExtension, IGalleryExtension, LocalExtensionType, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata,
IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata,
IExtensionManagementServerService, IExtensionManagementServer, IExtensionGalleryService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { flatten } from 'vs/base/common/arrays';
import { isUIExtension } from 'vs/platform/extensions/common/extensions';
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IRemoteAuthorityResolverService, ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { ILogService } from 'vs/platform/log/common/log';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { localize } from 'vs/nls';
import { isUIExtension } from 'vs/platform/extensions/node/extensionsUtil';
export class MulitExtensionManagementService extends Disposable implements IExtensionManagementService {
export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService {
_serviceBrand: any;
@@ -29,10 +33,11 @@ export class MulitExtensionManagementService extends Disposable implements IExte
private readonly servers: IExtensionManagementServer[];
constructor(
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService,
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService,
@IConfigurationService private configurationService: IConfigurationService,
@IRemoteAuthorityResolverService private remoteAuthorityResolverService: IRemoteAuthorityResolverService
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@ILogService private readonly logService: ILogService
) {
super();
this.servers = this.extensionManagementServerService.remoteExtensionManagementServer ? [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer] : [this.extensionManagementServerService.localExtensionManagementServer];
@@ -43,65 +48,159 @@ export class MulitExtensionManagementService extends Disposable implements IExte
this.onDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<DidUninstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onDidUninstallExtension); return emitter; }, new EventMultiplexer<DidUninstallExtensionEvent>())).event;
}
getInstalled(type?: LocalExtensionType): Promise<ILocalExtension[]> {
getInstalled(type?: ExtensionType): Promise<ILocalExtension[]> {
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type)))
.then(result => flatten(result));
}
uninstall(extension: ILocalExtension, force?: boolean): Promise<void> {
return this.getServer(extension).extensionManagementService.uninstall(extension, force);
async uninstall(extension: ILocalExtension, force?: boolean): Promise<void> {
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
const server = this.getServer(extension);
if (!server) {
return Promise.reject(`Invalid location ${extension.location.toString()}`);
}
const syncExtensions = await this.hasToSyncExtensions();
return syncExtensions ? this.uninstallEverywhere(extension, force) : this.uninstallInServer(extension, server, force);
}
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.uninstall(extension, force);
}
private async uninstallEverywhere(extension: ILocalExtension, force?: boolean): Promise<void> {
const server = this.getServer(extension);
if (!server) {
return Promise.reject(`Invalid location ${extension.location.toString()}`);
}
const promise = server.extensionManagementService.uninstall(extension);
const anotherServer: IExtensionManagementServer = server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer! : this.extensionManagementServerService.localExtensionManagementServer;
const installed = await anotherServer.extensionManagementService.getInstalled(ExtensionType.User);
extension = installed.filter(i => areSameExtensions(i.identifier, extension.identifier))[0];
if (extension) {
await anotherServer.extensionManagementService.uninstall(extension);
}
return promise;
}
private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, force?: boolean): Promise<void> {
if (server === this.extensionManagementServerService.localExtensionManagementServer) {
const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User);
const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.configurationService)
&& i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
if (dependentNonUIExtensions.length) {
return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions)));
}
}
return server.extensionManagementService.uninstall(extension, force);
}
private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string {
if (dependents.length === 1) {
return localize('singleDependentError', "Cannot uninstall extension '{0}'. Extension '{1}' depends on this.",
extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
}
if (dependents.length === 2) {
return localize('twoDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}' and '{2}' depend on this.",
extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
return localize('multipleDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}', '{2}' and others depend on this.",
extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
return this.getServer(extension).extensionManagementService.reinstallFromGallery(extension);
const server = this.getServer(extension);
if (server) {
return server.extensionManagementService.reinstallFromGallery(extension);
}
return Promise.reject(`Invalid location ${extension.location.toString()}`);
}
updateMetadata(extension: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
return this.getServer(extension).extensionManagementService.updateMetadata(extension, metadata);
const server = this.getServer(extension);
if (server) {
return server.extensionManagementService.updateMetadata(extension, metadata);
}
return Promise.reject(`Invalid location ${extension.location.toString()}`);
}
zip(extension: ILocalExtension): Promise<URI> {
throw new Error('Not Supported');
}
unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier> {
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.unzip(zipLocation, type))).then(() => null);
unzip(zipLocation: URI, type: ExtensionType): Promise<IExtensionIdentifier> {
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.unzip(zipLocation, type))).then(([extensionIdentifier]) => extensionIdentifier);
}
install(vsix: URI): Promise<IExtensionIdentifier> {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
async install(vsix: URI): Promise<IExtensionIdentifier> {
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
const syncExtensions = await this.hasToSyncExtensions();
if (syncExtensions) {
// Install on both servers
const [extensionIdentifier] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix)));
return extensionIdentifier;
}
const manifest = await getManifest(vsix.fsPath);
if (isUIExtension(manifest, this.configurationService)) {
// Install only on local server
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
}
// Install only on remote server
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix);
// Install UI Dependencies on local server
await this.installUIDependencies(manifest);
return promise;
}
return Promise.all([getManifest(vsix.fsPath), this.hasToSyncExtensions()])
.then(([manifest, syncExtensions]) => {
const servers = isUIExtension(manifest, this.configurationService) ? [this.extensionManagementServerService.localExtensionManagementServer] : syncExtensions ? this.servers : [this.extensionManagementServerService.remoteExtensionManagementServer];
return Promise.all(servers.map(server => server.extensionManagementService.install(vsix)))
.then(() => null);
});
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
}
installFromGallery(gallery: IGalleryExtension): Promise<void> {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
async installFromGallery(gallery: IGalleryExtension): Promise<void> {
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
const [manifest, syncExtensions] = await Promise.all([this.extensionGalleryService.getManifest(gallery, CancellationToken.None), this.hasToSyncExtensions()]);
if (manifest) {
if (syncExtensions) {
// Install on both servers
return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(() => undefined);
}
if (isUIExtension(manifest, this.configurationService)) {
// Install only on local server
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
}
// Install only on remote server
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
// Install UI Dependencies on local server
await this.installUIDependencies(manifest);
return promise;
} else {
this.logService.info('Manifest was not found. Hence installing only in local server');
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
}
}
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
}
private async installUIDependencies(manifest: IExtensionManifest): Promise<void> {
if (manifest.extensionDependencies && manifest.extensionDependencies.length) {
const dependencies = await this.extensionGalleryService.loadAllDependencies(manifest.extensionDependencies.map(id => ({ id })), CancellationToken.None);
if (dependencies.length) {
await Promise.all(dependencies.map(async d => {
const manifest = await this.extensionGalleryService.getManifest(d, CancellationToken.None);
if (manifest && isUIExtension(manifest, this.configurationService)) {
await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d);
}
}));
}
}
return Promise.all([this.extensionGalleryService.getManifest(gallery, CancellationToken.None), this.hasToSyncExtensions()])
.then(([manifest, syncExtensions]) => {
const servers = isUIExtension(manifest, this.configurationService) ? [this.extensionManagementServerService.localExtensionManagementServer] : syncExtensions ? this.servers : [this.extensionManagementServerService.remoteExtensionManagementServer];
return Promise.all(servers.map(server => server.extensionManagementService.installFromGallery(gallery)))
.then(() => null);
});
}
getExtensionsReport(): Promise<IReportedExtension[]> {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsReport();
}
private getServer(extension: ILocalExtension): IExtensionManagementServer {
private getServer(extension: ILocalExtension): IExtensionManagementServer | null {
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
}
private _remoteAuthorityResolverPromise: Thenable<ResolvedAuthority>;
private hasToSyncExtensions(): Thenable<boolean> {
private _remoteAuthorityResolverPromise: Promise<ResolvedAuthority>;
private hasToSyncExtensions(): Promise<boolean> {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return Promise.resolve(false);
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as sinon from 'sinon';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, IExtensionContributions, ILocalExtension, LocalExtensionType, DidInstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, ILocalExtension, DidInstallExtensionEvent, InstallOperation } 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';
@@ -12,6 +12,8 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
import { isUndefinedOrNull } from 'vs/base/common/types';
function storageService(instantiationService: TestInstantiationService): IStorageService {
let service = instantiationService.get(IStorageService);
@@ -36,7 +38,7 @@ export class TestExtensionEnablementService extends ExtensionEnablementService {
}
public async reset(): Promise<void> {
return this.getDisabledExtensions().then(extensions => extensions.forEach(d => this.setEnablement(aLocalExtension(d.id), EnablementState.Enabled)));
return this.getDisabledExtensions().then(extensions => extensions.forEach(d => this.setEnablement([aLocalExtension(d.id)], EnablementState.Enabled)));
}
}
@@ -45,12 +47,12 @@ suite('ExtensionEnablementService Test', () => {
let instantiationService: TestInstantiationService;
let testObject: IExtensionEnablementService;
const didUninstallEvent: Emitter<DidUninstallExtensionEvent> = new Emitter<DidUninstallExtensionEvent>();
const didInstallEvent: Emitter<DidInstallExtensionEvent> = new Emitter<DidInstallExtensionEvent>();
const didUninstallEvent = new Emitter<DidUninstallExtensionEvent>();
const didInstallEvent = new Emitter<DidInstallExtensionEvent>();
setup(() => {
instantiationService = new TestInstantiationService();
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, onDidInstallExtension: didInstallEvent.event, getInstalled: () => Promise.resolve([]) } as IExtensionManagementService);
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, onDidInstallExtension: didInstallEvent.event, getInstalled: () => Promise.resolve([] as ILocalExtension[]) } as IExtensionManagementService);
testObject = new TestExtensionEnablementService(instantiationService);
});
@@ -63,7 +65,7 @@ suite('ExtensionEnablementService Test', () => {
});
test('test when no extensions are disabled for workspace when there is no workspace', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => {
instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY);
return testObject.getDisabledExtensions().then(extensions => assert.deepEqual([], extensions));
@@ -71,303 +73,318 @@ suite('ExtensionEnablementService Test', () => {
});
test('test disable an extension globally', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions));
});
test('test disable an extension globally should return truthy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(value => assert.ok(value));
});
test('test disable an extension globally triggers the change event', () => {
const target = sinon.spy();
testObject.onEnablementChanged(target);
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a', uuid: void 0 })));
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(() => {
assert.ok(target.calledOnce);
assert.deepEqual((<IExtension>target.args[0][0][0]).identifier, { id: 'pub.a' });
});
});
test('test disable an extension globally again should return a falsy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
.then(value => assert.ok(!value));
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled))
.then(value => assert.ok(!value[0]));
});
test('test state of globally disabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
return testObject.setEnablement([aLocalExtension('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))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
});
test('test disable an extension for workspace', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions));
});
test('test disable an extension for workspace returns a truthy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(value => assert.ok(value));
});
test('test disable an extension for workspace again should return a falsy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
.then(value => assert.ok(!value));
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled))
.then(value => assert.ok(!value[0]));
});
test('test state of workspace disabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
return testObject.setEnablement([aLocalExtension('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))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(() => testObject.setEnablement([aLocalExtension('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))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('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))
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(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))
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(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))
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(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))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('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))
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(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))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
});
test('test disable an extension for workspace and then globally', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions));
});
test('test disable an extension for workspace and then globally return a truthy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled))
.then(value => assert.ok(value));
});
test('test disable an extension for workspace and then globally trigger the change event', () => {
const target = sinon.spy();
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a', uuid: void 0 })));
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled))
.then(() => {
assert.ok(target.calledOnce);
assert.deepEqual((<IExtension>target.args[0][0][0]).identifier, { id: 'pub.a' });
});
});
test('test disable an extension globally and then for workspace', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([{ id: 'pub.a' }], extensions));
});
test('test disable an extension globally and then for workspace return a truthy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled))
.then(value => assert.ok(value));
});
test('test disable an extension globally and then for workspace triggers the change event', () => {
const target = sinon.spy();
return 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.WorkspaceDisabled))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a', uuid: void 0 })));
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled))
.then(() => {
assert.ok(target.calledOnce);
assert.deepEqual((<IExtension>target.args[0][0][0]).identifier, { id: 'pub.a' });
});
});
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)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => assert.fail('should throw an error'), error => assert.ok(error));
});
test('test enable an extension globally', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Enabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
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))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Enabled))
.then(value => assert.ok(value));
});
test('test enable an extension globally triggers change event', () => {
const target = sinon.spy();
return 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(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Enabled))
.then(() => {
assert.ok(target.calledOnce);
assert.deepEqual((<IExtension>target.args[0][0][0]).identifier, { id: 'pub.a' });
});
});
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));
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Enabled)
.then(value => assert.ok(!value[0]));
});
test('test enable an extension for workspace', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceEnabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
test('test enable an extension for workspace return truthy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceEnabled))
.then(value => assert.ok(value));
});
test('test enable an extension for workspace triggers change event', () => {
const target = sinon.spy();
return testObject.setEnablement(aLocalExtension('pub.b'), EnablementState.WorkspaceDisabled)
return testObject.setEnablement([aLocalExtension('pub.b')], EnablementState.WorkspaceDisabled)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement(aLocalExtension('pub.b'), EnablementState.WorkspaceEnabled))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.b', uuid: void 0 })));
.then(() => testObject.setEnablement([aLocalExtension('pub.b')], EnablementState.WorkspaceEnabled))
.then(() => {
assert.ok(target.calledOnce);
assert.deepEqual((<IExtension>target.args[0][0][0]).identifier, { id: 'pub.b' });
});
});
test('test enable an extension for workspace when already enabled return truthy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceEnabled)
.then(value => assert.ok(value));
});
test('test enable an extension for workspace when disabled in workspace and gloablly', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled))
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceEnabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
test('test enable an extension globally when disabled in workspace and gloablly', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled))
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Enabled))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
test('test installing an extension re-eanbles it when disabled globally', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.Disabled);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
await testObject.setEnablement([local], EnablementState.Disabled);
didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install });
const extensions = await testObject.getDisabledExtensions();
assert.deepEqual([], extensions);
});
test('test updating an extension does not re-eanbles it when disabled globally', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.Disabled);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Update });
await testObject.setEnablement([local], EnablementState.Disabled);
didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update });
const extensions = await testObject.getDisabledExtensions();
assert.deepEqual([{ id: 'pub.a' }], extensions);
});
test('test installing an extension fires enablement change event when disabled globally', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.Disabled);
await testObject.setEnablement([local], EnablementState.Disabled);
return new Promise((c, e) => {
testObject.onEnablementChanged(e => {
if (e.id === local.galleryIdentifier.id) {
testObject.onEnablementChanged(([e]) => {
if (e.identifier.id === local.identifier.id) {
c();
}
});
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install });
});
});
test('test updating an extension does not fires enablement change event when disabled globally', async () => {
const target = sinon.spy();
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.Disabled);
await testObject.setEnablement([local], EnablementState.Disabled);
testObject.onEnablementChanged(target);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Update });
didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update });
assert.ok(!target.called);
});
test('test installing an extension re-eanbles it when workspace disabled', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.WorkspaceDisabled);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
await testObject.setEnablement([local], EnablementState.WorkspaceDisabled);
didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install });
const extensions = await testObject.getDisabledExtensions();
assert.deepEqual([], extensions);
});
test('test updating an extension does not re-eanbles it when workspace disabled', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.WorkspaceDisabled);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Update });
await testObject.setEnablement([local], EnablementState.WorkspaceDisabled);
didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update });
const extensions = await testObject.getDisabledExtensions();
assert.deepEqual([{ id: 'pub.a' }], extensions);
});
test('test installing an extension fires enablement change event when workspace disabled', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.WorkspaceDisabled);
await testObject.setEnablement([local], EnablementState.WorkspaceDisabled);
return new Promise((c, e) => {
testObject.onEnablementChanged(e => {
if (e.id === local.galleryIdentifier.id) {
testObject.onEnablementChanged(([e]) => {
if (e.identifier.id === local.identifier.id) {
c();
}
});
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install });
});
});
test('test updating an extension does not fires enablement change event when workspace disabled', async () => {
const target = sinon.spy();
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.WorkspaceDisabled);
await testObject.setEnablement([local], EnablementState.WorkspaceDisabled);
testObject.onEnablementChanged(target);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Update });
didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update });
assert.ok(!target.called);
});
@@ -375,31 +392,31 @@ suite('ExtensionEnablementService Test', () => {
const target = sinon.spy();
const local = aLocalExtension('pub.a');
testObject.onEnablementChanged(target);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install });
assert.ok(!target.called);
});
test('test remove an extension from disablement list when uninstalled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
.then(() => didUninstallEvent.fire({ identifier: { id: 'pub.a-1.0.0' } }))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled))
.then(() => didUninstallEvent.fire({ identifier: { id: 'pub.a' } }))
.then(() => testObject.getDisabledExtensions())
.then(extensions => assert.deepEqual([], extensions));
});
test('test isEnabled return false extension is disabled globally', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.Disabled)
.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)
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.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))
return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement([aLocalExtension('pub.c')], EnablementState.Disabled))
.then(() => assert.ok(testObject.isEnabled(aLocalExtension('pub.b'))));
});
@@ -422,16 +439,14 @@ suite('ExtensionEnablementService Test', () => {
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;
const extension = aLocalExtension('pub.a', undefined, ExtensionType.System);
assert.equal(testObject.canChangeEnablement(extension), true);
});
test('test canChangeEnablement return false for system extensions when extension is disabled in environment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
const extension = aLocalExtension('pub.a');
extension.type = LocalExtensionType.System;
const extension = aLocalExtension('pub.a', undefined, ExtensionType.System);
assert.equal(testObject.canChangeEnablement(extension), true);
});
@@ -448,16 +463,17 @@ suite('ExtensionEnablementService Test', () => {
});
function aLocalExtension(id: string, contributes?: IExtensionContributions): ILocalExtension {
function aLocalExtension(id: string, contributes?: IExtensionContributions, type?: ExtensionType): ILocalExtension {
const [publisher, name] = id.split('.');
type = isUndefinedOrNull(type) ? ExtensionType.User : type;
return <ILocalExtension>Object.create({
identifier: { id },
galleryIdentifier: { id, uuid: void 0 },
galleryIdentifier: { id, uuid: undefined },
manifest: {
name,
publisher,
contributes
},
type: LocalExtensionType.User
type
});
}

View File

@@ -3,31 +3,227 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import * as strings from 'vs/base/common/strings';
import { ILocalization } from 'vs/platform/localizations/common/localizations';
import { URI } from 'vs/base/common/uri';
export const MANIFEST_CACHE_FOLDER = 'CachedExtensions';
export const USER_MANIFEST_CACHE_FILE = 'user';
export const BUILTIN_MANIFEST_CACHE_FILE = 'builtin';
const uiExtensions = new Set<string>();
uiExtensions.add('msjsdiag.debugger-for-chrome');
export interface ICommand {
command: string;
title: string;
category?: string;
}
export function isUIExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean {
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
const configuredUIExtensions = configurationService.getValue<string[]>('_workbench.uiExtensions') || [];
if (configuredUIExtensions.length) {
if (configuredUIExtensions.indexOf(extensionId) !== -1) {
return true;
}
if (configuredUIExtensions.indexOf(`-${extensionId}`) !== -1) {
export interface IConfigurationProperty {
description: string;
type: string | string[];
default?: any;
}
export interface IConfiguration {
properties: { [key: string]: IConfigurationProperty; };
}
export interface IDebugger {
label?: string;
type: string;
runtime?: string;
}
export interface IGrammar {
language: string;
}
export interface IJSONValidation {
fileMatch: string;
url: string;
}
export interface IKeyBinding {
command: string;
key: string;
when?: string;
mac?: string;
linux?: string;
win?: string;
}
export interface ILanguage {
id: string;
extensions: string[];
aliases: string[];
}
export interface IMenu {
command: string;
alt?: string;
when?: string;
group?: string;
}
export interface ISnippet {
language: string;
}
export interface ITheme {
label: string;
}
export interface IViewContainer {
id: string;
title: string;
}
export interface IView {
id: string;
name: string;
}
export interface IColor {
id: string;
description: string;
defaults: { light: string, dark: string, highContrast: string };
}
export interface IExtensionContributions {
commands?: ICommand[];
configuration?: IConfiguration | IConfiguration[];
debuggers?: IDebugger[];
grammars?: IGrammar[];
jsonValidation?: IJSONValidation[];
keybindings?: IKeyBinding[];
languages?: ILanguage[];
menus?: { [context: string]: IMenu[] };
snippets?: ISnippet[];
themes?: ITheme[];
iconThemes?: ITheme[];
viewsContainers?: { [location: string]: IViewContainer[] };
views?: { [location: string]: IView[] };
colors?: IColor[];
localizations?: ILocalization[];
}
export type ExtensionKind = 'ui' | 'workspace';
export class ExtensionIdentifierWithVersion {
constructor(
readonly identifier: IExtensionIdentifier,
readonly version: string
) { }
key(): string {
return `${this.identifier.id}-${this.version}`;
}
equals(o: any): boolean {
if (!(o instanceof ExtensionIdentifierWithVersion)) {
return false;
}
}
switch (manifest.extensionKind) {
case 'ui': return true;
case 'workspace': return false;
default: return uiExtensions.has(extensionId) || !manifest.main;
return areSameExtensions(this.identifier, o.identifier) && this.version === o.version;
}
}
export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier {
return thing
&& typeof thing === 'object'
&& typeof thing.id === 'string'
&& (!thing.uuid || typeof thing.uuid === 'string');
}
export interface IExtensionIdentifier {
id: string;
uuid?: string;
}
export interface IExtensionManifest {
readonly name: string;
readonly displayName?: string;
readonly publisher: string;
readonly version: string;
// {{SQL CARBON EDIT}}
engines: { vscode: string; azdata?: string };
readonly description?: string;
readonly main?: string;
readonly icon?: string;
readonly categories?: string[];
readonly keywords?: string[];
readonly activationEvents?: string[];
readonly extensionDependencies?: string[];
readonly extensionPack?: string[];
readonly extensionKind?: ExtensionKind;
readonly contributes?: IExtensionContributions;
readonly repository?: { url: string; };
readonly bugs?: { url: string; };
readonly enableProposedApi?: boolean;
}
export const enum ExtensionType {
System,
User
}
export interface IExtension {
readonly type: ExtensionType;
readonly identifier: IExtensionIdentifier;
readonly manifest: IExtensionManifest;
readonly location: URI;
}
/**
* **!Do not construct directly!**
*
* **!Only static methods because it gets serialized!**
*
* This represents the "canonical" version for an extension identifier. Extension ids
* have to be case-insensitive (due to the marketplace), but we must ensure case
* preservation because the extension API is already public at this time.
*
* For example, given an extension with the publisher `"Hello"` and the name `"World"`,
* its canonical extension identifier is `"Hello.World"`. This extension could be
* referenced in some other extension's dependencies using the string `"hello.world"`.
*
* To make matters more complicated, an extension can optionally have an UUID. When two
* extensions have the same UUID, they are considered equal even if their identifier is different.
*/
export class ExtensionIdentifier {
public readonly value: string;
private readonly _lower: string;
constructor(value: string) {
this.value = value;
this._lower = value.toLowerCase();
}
public static equals(a: ExtensionIdentifier | string | null | undefined, b: ExtensionIdentifier | string | null | undefined) {
if (typeof a === 'undefined' || a === null) {
return (typeof b === 'undefined' || b === null);
}
if (typeof b === 'undefined' || b === null) {
return false;
}
if (typeof a === 'string' || typeof b === 'string') {
// At least one of the arguments is an extension id in string form,
// so we have to use the string comparison which ignores case.
let aValue = (typeof a === 'string' ? a : a.value);
let bValue = (typeof b === 'string' ? b : b.value);
return strings.equalsIgnoreCase(aValue, bValue);
}
// Now we know both arguments are ExtensionIdentifier
return (a._lower === b._lower);
}
/**
* Gives the value by which to index (for equality).
*/
public static toKey(id: ExtensionIdentifier | string): string {
if (typeof id === 'string') {
return id.toLowerCase();
}
return id._lower;
}
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import product from 'vs/platform/node/product';
export function isUIExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean {
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
const configuredUIExtensions = configurationService.getValue<string[]>('_workbench.uiExtensions') || [];
if (configuredUIExtensions.length) {
if (configuredUIExtensions.indexOf(extensionId) !== -1) {
return true;
}
if (configuredUIExtensions.indexOf(`-${extensionId}`) !== -1) {
return false;
}
}
switch (manifest.extensionKind) {
case 'ui': return true;
case 'workspace': return false;
default: {
if (isNonEmptyArray(product.uiExtensions) && product.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
return true;
}
if (manifest.main) {
return false;
}
if (manifest.contributes && isNonEmptyArray(manifest.contributes.debuggers)) {
return false;
}
// Default is UI Extension
return true;
}
}
}

View File

@@ -27,7 +27,7 @@ suite('Extension Version Validator', () => {
});
test('parseVersion', () => {
function assertParseVersion(version: string, hasCaret: boolean, hasGreaterEquals: boolean, majorBase: number, majorMustEqual: boolean, minorBase: number, minorMustEqual: boolean, patchBase: number, patchMustEqual: boolean, preRelease: string): void {
function assertParseVersion(version: string, hasCaret: boolean, hasGreaterEquals: boolean, majorBase: number, majorMustEqual: boolean, minorBase: number, minorMustEqual: boolean, patchBase: number, patchMustEqual: boolean, preRelease: string | null): void {
const actual = parseVersion(version);
const expected: IParsedVersion = { hasCaret, hasGreaterEquals, majorBase, majorMustEqual, minorBase, minorMustEqual, patchBase, patchMustEqual, preRelease };

View File

@@ -52,7 +52,7 @@ export interface IFileService {
/**
* Tries to activate a provider with the given scheme.
*/
activateProvider(scheme: string): Thenable<void>;
activateProvider(scheme: string): Promise<void>;
/**
* Checks if this file service can handle the given resource.
@@ -70,51 +70,51 @@ export interface IFileService {
* the stat service is asked to automatically resolve child folders that only
* contain a single element.
*/
resolveFile(resource: URI, options?: IResolveFileOptions): Thenable<IFileStat>;
resolveFile(resource: URI, options?: IResolveFileOptions): Promise<IFileStat>;
/**
* Same as resolveFile but supports resolving multiple resources in parallel.
* If one of the resolve targets fails to resolve returns a fake IFileStat instead of making the whole call fail.
*/
resolveFiles(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Thenable<IResolveFileResult[]>;
resolveFiles(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]>;
/**
* Finds out if a file identified by the resource exists.
*/
existsFile(resource: URI): Thenable<boolean>;
existsFile(resource: URI): Promise<boolean>;
/**
* Resolve the contents of a file identified by the resource.
*
* The returned object contains properties of the file and the full value as string.
*/
resolveContent(resource: URI, options?: IResolveContentOptions): Thenable<IContent>;
resolveContent(resource: URI, options?: IResolveContentOptions): Promise<IContent>;
/**
* Resolve the contents of a file identified by the resource.
*
* The returned object contains properties of the file and the value as a readable stream.
*/
resolveStreamContent(resource: URI, options?: IResolveContentOptions): Thenable<IStreamContent>;
resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent>;
/**
* Updates the content replacing its previous value.
*/
updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Thenable<IFileStat>;
updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Promise<IFileStat>;
/**
* Moves the file to a new path identified by the resource.
*
* The optional parameter overwrite can be set to replace an existing file at the location.
*/
moveFile(source: URI, target: URI, overwrite?: boolean): Thenable<IFileStat>;
moveFile(source: URI, target: URI, overwrite?: boolean): Promise<IFileStat>;
/**
* Copies the file to a path identified by the resource.
*
* The optional parameter overwrite can be set to replace an existing file at the location.
*/
copyFile(source: URI, target: URI, overwrite?: boolean): Thenable<IFileStat>;
copyFile(source: URI, target: URI, overwrite?: boolean): Promise<IFileStat>;
/**
* Creates a new file with the given path. The returned promise
@@ -122,26 +122,26 @@ export interface IFileService {
*
* The optional parameter content can be used as value to fill into the new file.
*/
createFile(resource: URI, content?: string, options?: ICreateFileOptions): Thenable<IFileStat>;
createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise<IFileStat>;
/**
* Reads a folder's content with the given path. The returned promise
* will have the list of children as a result.
*/
readFolder(resource: URI): Thenable<string[]>;
readFolder(resource: URI): Promise<string[]>;
/**
* Creates a new folder with the given path. The returned promise
* will have the stat model object as a result.
*/
createFolder(resource: URI): Thenable<IFileStat>;
createFolder(resource: URI): Promise<IFileStat>;
/**
* Deletes the provided file. The optional useTrash parameter allows to
* move the file to trash. The optional recursive parameter allows to delete
* non-empty folders recursively.
*/
del(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Thenable<void>;
del(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise<void>;
/**
* Allows to start a watcher that reports file change events on the provided resource.
@@ -168,6 +168,10 @@ export interface FileWriteOptions {
create: boolean;
}
export interface FileOpenOptions {
create: boolean;
}
export interface FileDeleteOptions {
recursive: boolean;
}
@@ -208,21 +212,21 @@ export interface IFileSystemProvider {
onDidChangeFile: Event<IFileChange[]>;
watch(resource: URI, opts: IWatchOptions): IDisposable;
stat(resource: URI): Thenable<IStat>;
mkdir(resource: URI): Thenable<void>;
readdir(resource: URI): Thenable<[string, FileType][]>;
delete(resource: URI, opts: FileDeleteOptions): Thenable<void>;
stat(resource: URI): Promise<IStat>;
mkdir(resource: URI): Promise<void>;
readdir(resource: URI): Promise<[string, FileType][]>;
delete(resource: URI, opts: FileDeleteOptions): Promise<void>;
rename(from: URI, to: URI, opts: FileOverwriteOptions): Thenable<void>;
copy?(from: URI, to: URI, opts: FileOverwriteOptions): Thenable<void>;
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void>;
copy?(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void>;
readFile?(resource: URI): Thenable<Uint8Array>;
writeFile?(resource: URI, content: Uint8Array, opts: FileWriteOptions): Thenable<void>;
readFile?(resource: URI): Promise<Uint8Array>;
writeFile?(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void>;
open?(resource: URI): Thenable<number>;
close?(fd: number): Thenable<void>;
read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Thenable<number>;
write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Thenable<number>;
open?(resource: URI, opts: FileOpenOptions): Promise<number>;
close?(fd: number): Promise<void>;
read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number>;
write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number>;
}
export interface IFileSystemProviderRegistrationEvent {

View File

@@ -45,12 +45,12 @@ suite('Files', () => {
assert.strictEqual(true, r1.gotDeleted());
});
function testIsEqual(testMethod: (pA: string, pB: string, ignoreCase: boolean) => boolean): void {
function testIsEqual(testMethod: (pA: string | null | undefined, pB: string, ignoreCase: boolean) => boolean): void {
// corner cases
assert(testMethod('', '', true));
assert(!testMethod(null, '', true));
assert(!testMethod(void 0, '', true));
assert(!testMethod(null!, '', true));
assert(!testMethod(undefined!, '', true));
// basics (string)
assert(testMethod('/', '/', true));

View File

@@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
export const IHistoryMainService = createDecorator<IHistoryMainService>('historyMainService');
export interface IRecentlyOpened {
workspaces: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)[];
workspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier>;
files: URI[];
}
@@ -21,9 +21,9 @@ export interface IHistoryMainService {
onRecentlyOpenedChange: CommonEvent<void>;
addRecentlyOpened(workspaces: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)[], files: URI[]): void;
addRecentlyOpened(workspaces: undefined | Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier>, files: URI[]): void;
getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened;
removeFromRecentlyOpened(paths: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string)[]): void;
removeFromRecentlyOpened(paths: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string>): void;
clearRecentlyOpened(): void;
updateWindowsJumpList(): void;

View File

@@ -8,33 +8,35 @@ import * as arrays from 'vs/base/common/arrays';
import { IStateService } from 'vs/platform/state/common/state';
import { app } from 'electron';
import { ILogService } from 'vs/platform/log/common/log';
import { getBaseLabel } from 'vs/base/common/labels';
import { getBaseLabel, getPathLabel } from 'vs/base/common/labels';
import { IPath } from 'vs/platform/windows/common/windows';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform';
import { IWorkspaceIdentifier, IWorkspacesMainService, IWorkspaceSavedEvent, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, IWorkspacesMainService, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IHistoryMainService, IRecentlyOpened } from 'vs/platform/history/common/history';
import { isEqual } from 'vs/base/common/paths';
import { RunOnceScheduler } from 'vs/base/common/async';
import { getComparisonKey, isEqual as areResourcesEqual, dirname } from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { ILabelService } from 'vs/platform/label/common/label';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label';
interface ISerializedRecentlyOpened {
workspaces2: (IWorkspaceIdentifier | string)[]; // IWorkspaceIdentifier or URI.toString()
workspaces2: Array<IWorkspaceIdentifier | string>; // IWorkspaceIdentifier or URI.toString()
files2: string[]; // files as URI.toString()
}
interface ILegacySerializedRecentlyOpened {
workspaces: (IWorkspaceIdentifier | string | UriComponents)[]; // legacy (UriComponents was also supported for a few insider builds)
workspaces: Array<IWorkspaceIdentifier | string | UriComponents>; // legacy (UriComponents was also supported for a few insider builds)
files: string[]; // files as paths
}
export class HistoryMainService implements IHistoryMainService {
private static readonly MAX_TOTAL_RECENT_ENTRIES = 100;
private static readonly MAX_MACOS_DOCK_RECENT_ENTRIES = 10;
private static readonly MAX_MACOS_DOCK_RECENT_FOLDERS = 10;
private static readonly MAX_MACOS_DOCK_RECENT_FILES = 5;
private static readonly recentlyOpenedStorageKey = 'openedPathsList';
@@ -46,28 +48,15 @@ export class HistoryMainService implements IHistoryMainService {
private macOSRecentDocumentsUpdater: RunOnceScheduler;
constructor(
@IStateService private stateService: IStateService,
@ILogService private logService: ILogService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
@ILabelService private labelService: ILabelService
@IStateService private readonly stateService: IStateService,
@ILogService private readonly logService: ILogService,
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
this.macOSRecentDocumentsUpdater = new RunOnceScheduler(() => this.updateMacOSRecentDocuments(), 800);
this.registerListeners();
}
private registerListeners(): void {
this.workspacesMainService.onWorkspaceSaved(e => this.onWorkspaceSaved(e));
this.labelService.onDidRegisterFormatter(() => this._onRecentlyOpenedChange.fire());
}
private onWorkspaceSaved(e: IWorkspaceSavedEvent): void {
// Make sure to add newly saved workspaces to the list of recent workspaces
this.addRecentlyOpened([e.workspace], []);
}
addRecentlyOpened(workspaces: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)[], files: URI[]): void {
addRecentlyOpened(workspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier>, files: URI[]): void {
if ((workspaces && workspaces.length > 0) || (files && files.length > 0)) {
const mru = this.getRecentlyOpened();
@@ -114,7 +103,7 @@ export class HistoryMainService implements IHistoryMainService {
}
}
removeFromRecentlyOpened(pathsToRemove: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string)[]): void {
removeFromRecentlyOpened(pathsToRemove: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string>): void {
const mru = this.getRecentlyOpened();
let update = false;
@@ -179,34 +168,32 @@ export class HistoryMainService implements IHistoryMainService {
// out of sync quickly over time. the attempted fix is to always set the list fresh
// from our MRU history data. So we clear the documents first and then set the documents
// again.
app.clearRecentDocuments();
const mru = this.getRecentlyOpened();
let maxEntries = HistoryMainService.MAX_MACOS_DOCK_RECENT_ENTRIES;
// Take up to maxEntries/2 workspaces
let nEntries = 0;
for (let i = 0; i < mru.workspaces.length && nEntries < HistoryMainService.MAX_MACOS_DOCK_RECENT_ENTRIES / 2; i++) {
// Fill in workspaces
let entries = 0;
for (let i = 0; i < mru.workspaces.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FOLDERS; i++) {
const workspace = mru.workspaces[i];
if (isSingleFolderWorkspaceIdentifier(workspace)) {
if (workspace.scheme === Schemas.file) {
app.addRecentDocument(workspace.fsPath);
nEntries++;
entries++;
}
} else {
app.addRecentDocument(workspace.configPath);
nEntries++;
entries++;
}
}
// Take up to maxEntries files
for (let i = 0; i < mru.files.length && nEntries < maxEntries; i++) {
// Fill in files
entries = 0;
for (let i = 0; i < mru.files.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FILES; i++) {
const file = mru.files[i];
if (file.scheme === Schemas.file) {
app.addRecentDocument(file.fsPath);
nEntries++;
entries++;
}
}
}
@@ -220,7 +207,7 @@ export class HistoryMainService implements IHistoryMainService {
}
getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened {
let workspaces: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)[];
let workspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier>;
let files: URI[];
// Get from storage
@@ -257,6 +244,7 @@ export class HistoryMainService implements IHistoryMainService {
if (workspaceOrFile instanceof URI) {
return getComparisonKey(workspaceOrFile);
}
return workspaceOrFile.id;
}
@@ -286,6 +274,7 @@ export class HistoryMainService implements IHistoryMainService {
}
}
}
if (Array.isArray(storedRecents.files2)) {
for (const file of storedRecents.files2) {
if (typeof file === 'string') {
@@ -300,11 +289,13 @@ export class HistoryMainService implements IHistoryMainService {
}
}
}
return result;
}
private saveRecentlyOpened(recent: IRecentlyOpened): void {
const serialized: ISerializedRecentlyOpened = { workspaces2: [], files2: [] };
for (const workspace of recent.workspaces) {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
serialized.workspaces2.push(workspace.toString());
@@ -312,9 +303,11 @@ export class HistoryMainService implements IHistoryMainService {
serialized.workspaces2.push(workspace);
}
}
for (const file of recent.files) {
serialized.files2.push(file.toString());
}
this.stateService.setItem(HistoryMainService.recentlyOpenedStorageKey, serialized);
}
@@ -348,7 +341,7 @@ export class HistoryMainService implements IHistoryMainService {
// so we need to update our list of recent paths with the choice of the user to not add them again
// Also: Windows will not show our custom category at all if there is any entry which was removed
// by the user! See https://github.com/Microsoft/vscode/issues/15052
let toRemove: (ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier)[] = [];
let toRemove: Array<ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier> = [];
for (let item of app.getJumpListSettings().removedItems) {
const args = item.args;
if (args) {
@@ -369,13 +362,13 @@ export class HistoryMainService implements IHistoryMainService {
jumpList.push({
type: 'custom',
name: nls.localize('recentFolders', "Recent Workspaces"),
items: this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(workspace => {
const title = this.labelService.getWorkspaceLabel(workspace);
items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(workspace => {
const title = getSimpleWorkspaceLabel(workspace, this.environmentService.workspacesHome);
let description;
let args;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
const parentFolder = dirname(workspace);
description = parentFolder ? nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), this.labelService.getUriLabel(parentFolder)) : getBaseLabel(workspace);
description = parentFolder ? nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(parentFolder, this.environmentService)) : getBaseLabel(workspace);
args = `--folder-uri "${workspace.toString()}"`;
} else {
description = nls.localize('codeWorkspace', "Code Workspace");
@@ -390,7 +383,7 @@ export class HistoryMainService implements IHistoryMainService {
iconPath: 'explorer.exe', // simulate folder icon
iconIndex: 0
};
}).filter(i => !!i)
}))
});
}

View File

@@ -128,7 +128,7 @@ function storeServiceDependency(id: Function, target: Function, index: number, o
export function createDecorator<T>(serviceId: string): { (...args: any[]): void; type: T; } {
if (_util.serviceIds.has(serviceId)) {
return _util.serviceIds.get(serviceId);
return _util.serviceIds.get(serviceId)!;
}
const id = <any>function (target: Function, key: string, index: number): any {

View File

@@ -37,7 +37,7 @@ export class InstantiationService implements IInstantiationService {
let _trace = Trace.traceInvocation(fn);
let _done = false;
try {
let accessor = {
const accessor: ServicesAccessor = {
get: <T>(id: ServiceIdentifier<T>, isOptional?: typeof optional) => {
if (_done) {
@@ -51,7 +51,7 @@ export class InstantiationService implements IInstantiationService {
return result;
}
};
return fn.apply(undefined, [accessor].concat(args));
return fn.apply(undefined, [accessor, ...args]);
} finally {
_done = true;
_trace.stop();
@@ -186,7 +186,7 @@ export class InstantiationService implements IInstantiationService {
for (let { data } of roots) {
// create instance and overwrite the service collections
const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, false, data._trace);
const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, data.desc.supportsDelayedInstantiation, data._trace);
this._setServiceInstance(data.id, instance);
graph.removeNode(data);
}
@@ -205,7 +205,7 @@ export class InstantiationService implements IInstantiationService {
}
}
protected _createServiceInstance<T>(ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
protected _createServiceInstance<T>(ctor: any, args: any[] = [], _supportsDelayedInstantiation: boolean, _trace: Trace): T {
return this._createInstance(ctor, args, _trace);
}
}

View File

@@ -6,7 +6,7 @@ import * as assert from 'assert';
import { Graph } from 'vs/platform/instantiation/common/graph';
suite('Graph', () => {
var graph: Graph<string>;
let graph: Graph<string>;
setup(() => {
graph = new Graph<string>(s => s);
@@ -34,7 +34,7 @@ suite('Graph', () => {
test('root', () => {
graph.insertEdge('1', '2');
var roots = graph.roots();
let roots = graph.roots();
assert.equal(roots.length, 1);
assert.equal(roots[0].data, '2');
@@ -48,7 +48,7 @@ suite('Graph', () => {
graph.insertEdge('1', '3');
graph.insertEdge('3', '4');
var roots = graph.roots();
let roots = graph.roots();
assert.equal(roots.length, 2);
assert(['2', '4'].every(n => roots.some(node => node.data === n)));
});

View File

@@ -94,7 +94,7 @@ class TargetOptional {
constructor(@IService1 service1: IService1, @optional(IService2) service2: IService2) {
assert.ok(service1);
assert.equal(service1.c, 1);
assert.ok(service2 === void 0);
assert.ok(service2 === undefined);
}
}
@@ -137,7 +137,7 @@ suite('Instantiation Service', () => {
test('service collection, cannot overwrite', function () {
let collection = new ServiceCollection();
let result = collection.set(IService1, null);
let result = collection.set(IService1, null!);
assert.equal(result, undefined);
result = collection.set(IService1, new Service1());
assert.equal(result, null);
@@ -145,10 +145,10 @@ suite('Instantiation Service', () => {
test('service collection, add/has', function () {
let collection = new ServiceCollection();
collection.set(IService1, null);
collection.set(IService1, null!);
assert.ok(collection.has(IService1));
collection.set(IService2, null);
collection.set(IService2, null!);
assert.ok(collection.has(IService1));
assert.ok(collection.has(IService2));
});

View File

@@ -4,11 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as sinon from 'sinon';
import * as types from 'vs/base/common/types';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
interface IServiceMock<T> {
id: ServiceIdentifier<T>;
@@ -37,13 +35,13 @@ export class TestInstantiationService extends InstantiationService {
return <T>this._create(service, { mock: true });
}
public stub<T>(service?: ServiceIdentifier<T>, ctor?: any): T;
public stub<T>(service?: ServiceIdentifier<T>, obj?: any): T;
public stub<T>(service?: ServiceIdentifier<T>, ctor?: any, property?: string, value?: any): sinon.SinonStub;
public stub<T>(service?: ServiceIdentifier<T>, obj?: any, property?: string, value?: any): sinon.SinonStub;
public stub<T>(service?: ServiceIdentifier<T>, property?: string, value?: any): sinon.SinonStub;
public stub<T>(serviceIdentifier?: ServiceIdentifier<T>, arg2?: any, arg3?: string, arg4?: any): sinon.SinonStub {
let service = typeof arg2 !== 'string' ? arg2 : void 0;
public stub<T>(service: ServiceIdentifier<T>, ctor?: any): T;
public stub<T>(service: ServiceIdentifier<T>, obj?: any): T;
public stub<T>(service: ServiceIdentifier<T>, ctor?: any, property?: string, value?: any): sinon.SinonStub;
public stub<T>(service: ServiceIdentifier<T>, obj?: any, property?: string, value?: any): sinon.SinonStub;
public stub<T>(service: ServiceIdentifier<T>, property?: string, value?: any): sinon.SinonStub;
public stub<T>(serviceIdentifier: ServiceIdentifier<T>, arg2?: any, arg3?: string, arg4?: any): sinon.SinonStub {
let service = typeof arg2 !== 'string' ? arg2 : undefined;
let serviceMock: IServiceMock<any> = { id: serviceIdentifier, service: service };
let property = typeof arg2 === 'string' ? arg2 : arg3;
let value = typeof arg2 === 'string' ? arg3 : arg4;
@@ -72,8 +70,8 @@ export class TestInstantiationService extends InstantiationService {
public stubPromise<T>(service?: ServiceIdentifier<T>, ctor?: any, fnProperty?: string, value?: any): sinon.SinonStub;
public stubPromise<T>(service?: ServiceIdentifier<T>, obj?: any, fnProperty?: string, value?: any): sinon.SinonStub;
public stubPromise(arg1?: any, arg2?: any, arg3?: any, arg4?: any): sinon.SinonStub {
arg3 = typeof arg2 === 'string' ? TPromise.as(arg3) : arg3;
arg4 = typeof arg2 !== 'string' && typeof arg3 === 'string' ? TPromise.as(arg4) : arg4;
arg3 = typeof arg2 === 'string' ? Promise.resolve(arg3) : arg3;
arg4 = typeof arg2 !== 'string' && typeof arg3 === 'string' ? Promise.resolve(arg4) : arg4;
return this.stub(arg1, arg2, arg3, arg4);
}
@@ -123,13 +121,6 @@ export class TestInstantiationService extends InstantiationService {
}
}
export function stubFunction<T>(ctor: any, fnProperty: string, value: any): T | sinon.SinonStub {
let stub = sinon.createStubInstance(ctor);
stub[fnProperty].restore();
sinon.stub(stub, fnProperty, types.isFunction(value) ? value : () => { return value; });
return stub;
}
interface SinonOptions {
mock?: boolean;
stub?: boolean;

View File

@@ -23,5 +23,5 @@ export interface IntegrityTestResult {
export interface IIntegrityService {
_serviceBrand: any;
isPure(): Thenable<IntegrityTestResult>;
isPure(): Promise<IntegrityTestResult>;
}

View File

@@ -57,12 +57,12 @@ export class IntegrityServiceImpl implements IIntegrityService {
_serviceBrand: any;
private _storage: IntegrityStorage;
private _isPurePromise: Thenable<IntegrityTestResult>;
private _isPurePromise: Promise<IntegrityTestResult>;
constructor(
@INotificationService private notificationService: INotificationService,
@INotificationService private readonly notificationService: INotificationService,
@IStorageService storageService: IStorageService,
@ILifecycleService private lifecycleService: ILifecycleService
@ILifecycleService private readonly lifecycleService: ILifecycleService
) {
this._storage = new IntegrityStorage(storageService);
@@ -101,11 +101,11 @@ export class IntegrityServiceImpl implements IIntegrityService {
);
}
isPure(): Thenable<IntegrityTestResult> {
isPure(): Promise<IntegrityTestResult> {
return this._isPurePromise;
}
private _isPure(): Thenable<IntegrityTestResult> {
private _isPure(): Promise<IntegrityTestResult> {
const expectedChecksums = product.checksums || {};
return this.lifecycleService.when(LifecyclePhase.Eventually).then(() => {
@@ -115,7 +115,7 @@ export class IntegrityServiceImpl implements IIntegrityService {
return Promise.all(asyncResults).then<IntegrityTestResult>((allResults) => {
let isPure = true;
for (let i = 0, len = allResults.length; isPure && i < len; i++) {
for (let i = 0, len = allResults.length; i < len; i++) {
if (!allResults[i].isPure) {
isPure = false;
break;

View File

@@ -10,8 +10,8 @@ export const IIssueService = createDecorator<IIssueService>('issueService');
// Since data sent through the service is serialized to JSON, functions will be lost, so Color objects
// should not be sent as their 'toString' method will be stripped. Instead convert to strings before sending.
export interface WindowStyles {
backgroundColor: string;
color: string;
backgroundColor?: string;
color?: string;
}
export interface WindowData {
styles: WindowStyles;
@@ -26,19 +26,19 @@ export const enum IssueType {
}
export interface IssueReporterStyles extends WindowStyles {
textLinkColor: string;
textLinkActiveForeground: string;
inputBackground: string;
inputForeground: string;
inputBorder: string;
inputErrorBorder: string;
inputActiveBorder: string;
buttonBackground: string;
buttonForeground: string;
buttonHoverBackground: string;
sliderBackgroundColor: string;
sliderHoverColor: string;
sliderActiveColor: string;
textLinkColor?: string;
textLinkActiveForeground?: string;
inputBackground?: string;
inputForeground?: string;
inputBorder?: string;
inputErrorBorder?: string;
inputActiveBorder?: string;
buttonBackground?: string;
buttonForeground?: string;
buttonHoverBackground?: string;
sliderBackgroundColor?: string;
sliderHoverColor?: string;
sliderActiveColor?: string;
}
export interface IssueReporterExtensionData {
@@ -75,9 +75,9 @@ export interface IssueReporterFeatures {
}
export interface ProcessExplorerStyles extends WindowStyles {
hoverBackground: string;
hoverForeground: string;
highlightForeground: string;
hoverBackground?: string;
hoverForeground?: string;
highlightForeground?: string;
}
export interface ProcessExplorerData extends WindowData {
@@ -87,6 +87,6 @@ export interface ProcessExplorerData extends WindowData {
export interface IIssueService {
_serviceBrand: any;
openReporter(data: IssueReporterData): Thenable<void>;
openProcessExplorer(data: ProcessExplorerData): Thenable<void>;
openReporter(data: IssueReporterData): Promise<void>;
openProcessExplorer(data: ProcessExplorerData): Promise<void>;
}

View File

@@ -28,11 +28,11 @@ export class IssueService implements IIssueService {
constructor(
private machineId: string,
private userEnv: IProcessEnvironment,
@IEnvironmentService private environmentService: IEnvironmentService,
@ILaunchService private launchService: ILaunchService,
@ILogService private logService: ILogService,
@IDiagnosticsService private diagnosticsService: IDiagnosticsService,
@IWindowsService private windowsService: IWindowsService
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILaunchService private readonly launchService: ILaunchService,
@ILogService private readonly logService: ILogService,
@IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService,
@IWindowsService private readonly windowsService: IWindowsService
) {
this.registerListeners();
}
@@ -118,6 +118,7 @@ export class IssueService implements IIssueService {
const position = this.getWindowPosition(this._issueParentWindow, 700, 800);
this._issueWindow = new BrowserWindow({
fullscreen: false,
width: position.width,
height: position.height,
minWidth: 300,
@@ -163,6 +164,7 @@ export class IssueService implements IIssueService {
this._processExplorerWindow = new BrowserWindow({
skipTaskbar: true,
resizable: true,
fullscreen: false,
width: position.width,
height: position.height,
minWidth: 300,
@@ -187,7 +189,7 @@ export class IssueService implements IIssueService {
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] === '') {
if (config[key] === undefined || config[key] === null || config[key] === '') {
delete config[key]; // only send over properties that have a true value
}
}
@@ -321,11 +323,11 @@ export class IssueService implements IIssueService {
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] === '') {
if (config[key] === undefined || config[key] === null || config[key] === '') {
delete config[key]; // only send over properties that have a true value
}
}
return `${require.toUrl('vs/code/electron-browser/issue/issueReporter.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
}
}
}

View File

@@ -15,7 +15,7 @@ export class IssueChannel implements IServerChannel {
throw new Error(`Event not found: ${event}`);
}
call(_, command: string, arg?: any): Thenable<any> {
call(_, command: string, arg?: any): Promise<any> {
switch (command) {
case 'openIssueReporter':
return this.service.openReporter(arg);
@@ -33,11 +33,11 @@ export class IssueChannelClient implements IIssueService {
constructor(private channel: IChannel) { }
openReporter(data: IssueReporterData): Thenable<void> {
openReporter(data: IssueReporterData): Promise<void> {
return this.channel.call('openIssueReporter', data);
}
openProcessExplorer(data: ProcessExplorerData): Thenable<void> {
openProcessExplorer(data: ProcessExplorerData): Promise<void> {
return this.channel.call('openProcessExplorer', data);
}
}

View File

@@ -26,7 +26,7 @@ export interface IJSONContributionRegistry {
/**
* Notifies all listeneres that the content of the given schema has changed.
* Notifies all listeners that the content of the given schema has changed.
* @param uri The id of the schema
*/
notifySchemaChanged(uri: string): void;
@@ -52,7 +52,7 @@ class JSONContributionRegistry implements IJSONContributionRegistry {
private schemasById: { [id: string]: IJSONSchema };
private readonly _onDidChangeSchema: Emitter<string> = new Emitter<string>();
private readonly _onDidChangeSchema = new Emitter<string>();
readonly onDidChangeSchema: Event<string> = this._onDidChangeSchema.event;
constructor() {

View File

@@ -93,12 +93,16 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
);
}
public lookupKeybinding(commandId: string): ResolvedKeybinding | null {
public lookupKeybinding(commandId: string): ResolvedKeybinding | undefined {
let result = this._getResolver().lookupPrimaryKeybinding(commandId);
if (!result) {
return null;
return undefined;
}
return result.resolvedKeybinding;
return result.resolvedKeybinding || undefined;
}
public dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
return this._dispatch(e, target);
}
public softDispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult | null {
@@ -152,10 +156,20 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
this._currentChord = null;
}
public dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void {
const keybindings = this.resolveUserBinding(userSettingsLabel);
if (keybindings.length >= 1) {
this._doDispatch(keybindings[0], target);
}
}
protected _dispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
return this._doDispatch(this.resolveKeyboardEvent(e), target);
}
private _doDispatch(keybinding: ResolvedKeybinding, target: IContextKeyServiceTarget): boolean {
let shouldPreventDefault = false;
const keybinding = this.resolveKeyboardEvent(e);
if (keybinding.isChord()) {
console.warn('Unexpected keyboard event mapped to a chord');
return false;

View File

@@ -52,11 +52,18 @@ export interface IKeybindingService {
resolveUserBinding(userBinding: string): ResolvedKeybinding[];
/**
* Resolve and dispatch `keyboardEvent` and invoke the command.
*/
dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean;
/**
* Resolve and dispatch `keyboardEvent`, but do not invoke the command or change inner state.
*/
softDispatch(keyboardEvent: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult | null;
dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void;
/**
* Look up keybindings for a command.
* Use `lookupKeybinding` if you are interested in the preferred keybinding.
@@ -67,7 +74,7 @@ export interface IKeybindingService {
* Look up the preferred (last defined) keybinding for a command.
* @returns The preferred keybinding or null if the command is not bound.
*/
lookupKeybinding(commandId: string): ResolvedKeybinding | null;
lookupKeybinding(commandId: string): ResolvedKeybinding | undefined;
getDefaultKeybindingsContent(): string;

View File

@@ -77,8 +77,7 @@ export class KeybindingResolver {
public static combine(defaults: ResolvedKeybindingItem[], rawOverrides: ResolvedKeybindingItem[]): ResolvedKeybindingItem[] {
defaults = defaults.slice(0);
let overrides: ResolvedKeybindingItem[] = [];
for (let i = 0, len = rawOverrides.length; i < len; i++) {
const override = rawOverrides[i];
for (const override of rawOverrides) {
if (!override.command || override.command.length === 0 || override.command.charAt(0) !== '-') {
overrides.push(override);
continue;
@@ -180,8 +179,7 @@ export class KeybindingResolver {
const bExpressions: ContextKeyExpr[] = ((b instanceof ContextKeyAndExpr) ? b.expr : [b]);
let aIndex = 0;
for (let bIndex = 0; bIndex < bExpressions.length; bIndex++) {
let bExpr = bExpressions[bIndex];
for (const bExpr of bExpressions) {
let bExprMatched = false;
while (!bExprMatched && aIndex < aExpressions.length) {
let aExpr = aExpressions[aIndex];

View File

@@ -47,15 +47,11 @@ export interface IKeybindingRule2 {
linux?: { primary: Keybinding | null; } | null;
mac?: { primary: Keybinding | null; } | null;
id: string;
args?: any;
weight: number;
when: ContextKeyExpr | null;
}
export const enum KeybindingRuleSource {
Core = 0,
Extension = 1
}
export const enum KeybindingWeight {
EditorCore = 0,
EditorContrib = 100,
@@ -70,20 +66,22 @@ export interface ICommandAndKeybindingRule extends IKeybindingRule {
}
export interface IKeybindingsRegistry {
registerKeybindingRule(rule: IKeybindingRule, source?: KeybindingRuleSource): void;
registerKeybindingRule2(rule: IKeybindingRule2, source?: KeybindingRuleSource): void;
registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule, source?: KeybindingRuleSource): void;
registerKeybindingRule(rule: IKeybindingRule): void;
setExtensionKeybindings(rules: IKeybindingRule2[]): void;
registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule): void;
getDefaultKeybindings(): IKeybindingItem[];
}
class KeybindingsRegistryImpl implements IKeybindingsRegistry {
private _keybindings: IKeybindingItem[];
private _keybindingsSorted: boolean;
private _coreKeybindings: IKeybindingItem[];
private _extensionKeybindings: IKeybindingItem[];
private _cachedMergedKeybindings: IKeybindingItem[] | null;
constructor() {
this._keybindings = [];
this._keybindingsSorted = true;
this._coreKeybindings = [];
this._extensionKeybindings = [];
this._cachedMergedKeybindings = null;
}
/**
@@ -128,13 +126,13 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
return kb;
}
public registerKeybindingRule(rule: IKeybindingRule, source: KeybindingRuleSource = KeybindingRuleSource.Core): void {
let actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform(rule);
public registerKeybindingRule(rule: IKeybindingRule): void {
const actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform(rule);
if (actualKb && actualKb.primary) {
const kk = createKeybinding(actualKb.primary, OS);
if (kk) {
this._registerDefaultKeybinding(kk, rule.id, rule.weight, 0, rule.when, source);
this._registerDefaultKeybinding(kk, rule.id, undefined, rule.weight, 0, rule.when);
}
}
@@ -143,22 +141,36 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
const k = actualKb.secondary[i];
const kk = createKeybinding(k, OS);
if (kk) {
this._registerDefaultKeybinding(kk, rule.id, rule.weight, -i - 1, rule.when, source);
this._registerDefaultKeybinding(kk, rule.id, undefined, rule.weight, -i - 1, rule.when);
}
}
}
}
public registerKeybindingRule2(rule: IKeybindingRule2, source: KeybindingRuleSource = KeybindingRuleSource.Core): void {
let actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform2(rule);
public setExtensionKeybindings(rules: IKeybindingRule2[]): void {
let result: IKeybindingItem[] = [], keybindingsLen = 0;
for (let i = 0, len = rules.length; i < len; i++) {
const rule = rules[i];
let actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform2(rule);
if (actualKb && actualKb.primary) {
this._registerDefaultKeybinding(actualKb.primary, rule.id, rule.weight, 0, rule.when, source);
if (actualKb && actualKb.primary) {
result[keybindingsLen++] = {
keybinding: actualKb.primary,
command: rule.id,
commandArgs: rule.args,
when: rule.when,
weight1: rule.weight,
weight2: 0
};
}
}
this._extensionKeybindings = result;
this._cachedMergedKeybindings = null;
}
public registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule, source: KeybindingRuleSource = KeybindingRuleSource.Core): void {
this.registerKeybindingRule(desc, source);
public registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule): void {
this.registerKeybindingRule(desc);
CommandsRegistry.registerCommand(desc);
}
@@ -196,31 +208,31 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
}
}
private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, weight1: number, weight2: number, when: ContextKeyExpr | null | undefined, source: KeybindingRuleSource): void {
if (source === KeybindingRuleSource.Core && OS === OperatingSystem.Windows) {
private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, commandArgs: any, weight1: number, weight2: number, when: ContextKeyExpr | null | undefined): void {
if (OS === OperatingSystem.Windows) {
if (keybinding.type === KeybindingType.Chord) {
this._assertNoCtrlAlt(keybinding.firstPart, commandId);
} else {
this._assertNoCtrlAlt(keybinding, commandId);
}
}
this._keybindings.push({
this._coreKeybindings.push({
keybinding: keybinding,
command: commandId,
commandArgs: undefined,
commandArgs: commandArgs,
when: when,
weight1: weight1,
weight2: weight2
});
this._keybindingsSorted = false;
this._cachedMergedKeybindings = null;
}
public getDefaultKeybindings(): IKeybindingItem[] {
if (!this._keybindingsSorted) {
this._keybindings.sort(sorter);
this._keybindingsSorted = true;
if (!this._cachedMergedKeybindings) {
this._cachedMergedKeybindings = (<IKeybindingItem[]>[]).concat(this._coreKeybindings).concat(this._extensionKeybindings);
this._cachedMergedKeybindings.sort(sorter);
}
return this._keybindings.slice(0);
return this._cachedMergedKeybindings.slice(0);
}
}
export const KeybindingsRegistry: IKeybindingsRegistry = new KeybindingsRegistryImpl();

View File

@@ -77,15 +77,15 @@ suite('AbstractKeybindingService', () => {
altKey: keybinding.altKey,
metaKey: keybinding.metaKey,
keyCode: keybinding.keyCode,
code: null
}, null);
code: null!
}, null!);
}
}
let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null;
let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null!;
let currentContextValue: IContext | null = null;
let executeCommandCalls: { commandId: string; args: any[]; }[] = null;
let showMessageCalls: { sev: Severity, message: any; }[] = null;
let executeCommandCalls: { commandId: string; args: any[]; }[] = null!;
let showMessageCalls: { sev: Severity, message: any; }[] = null!;
let statusMessageCalls: string[] | null = null;
let statusMessageCallsDisposed: string[] | null = null;
@@ -99,12 +99,12 @@ suite('AbstractKeybindingService', () => {
let contextKeyService: IContextKeyService = {
_serviceBrand: undefined,
dispose: undefined,
onDidChangeContext: undefined,
createKey: undefined,
contextMatchesRules: undefined,
getContextKeyValue: undefined,
createScoped: undefined,
dispose: undefined!,
onDidChangeContext: undefined!,
createKey: undefined!,
contextMatchesRules: undefined!,
getContextKeyValue: undefined!,
createScoped: undefined!,
getContext: (target: IContextKeyServiceTarget): any => {
return currentContextValue;
}
@@ -118,7 +118,7 @@ suite('AbstractKeybindingService', () => {
commandId: commandId,
args: args
});
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
};
@@ -147,12 +147,12 @@ suite('AbstractKeybindingService', () => {
let statusbarService: IStatusbarService = {
_serviceBrand: undefined,
addEntry: undefined,
addEntry: undefined!,
setStatusMessage: (message: string, autoDisposeAfter?: number, delayBy?: number): IDisposable => {
statusMessageCalls.push(message);
statusMessageCalls!.push(message);
return {
dispose: () => {
statusMessageCallsDisposed.push(message);
statusMessageCallsDisposed!.push(message);
}
};
}
@@ -166,15 +166,15 @@ suite('AbstractKeybindingService', () => {
teardown(() => {
currentContextValue = null;
executeCommandCalls = null;
showMessageCalls = null;
createTestKeybindingService = null;
executeCommandCalls = null!;
showMessageCalls = null!;
createTestKeybindingService = null!;
statusMessageCalls = null;
statusMessageCallsDisposed = null;
});
function kbItem(keybinding: number, command: string, when: ContextKeyExpr | null = null): ResolvedKeybindingItem {
const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS) : null);
const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : null);
return new ResolvedKeybindingItem(
resolvedKeybinding,
command,
@@ -185,8 +185,8 @@ suite('AbstractKeybindingService', () => {
}
function toUsLabel(keybinding: number): string {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
return usResolvedKeybinding.getLabel();
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS);
return usResolvedKeybinding.getLabel()!;
}
test('issue #16498: chord mode is quit for invalid chords', () => {

View File

@@ -10,7 +10,7 @@ import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayo
suite('KeybindingLabels', () => {
function assertUSLabel(OS: OperatingSystem, keybinding: number, expected: string): void {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS);
assert.equal(usResolvedKeybinding.getLabel(), expected);
}
@@ -115,7 +115,7 @@ suite('KeybindingLabels', () => {
test('Aria label', () => {
function assertAriaLabel(OS: OperatingSystem, keybinding: number, expected: string): void {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS);
assert.equal(usResolvedKeybinding.getAriaLabel(), expected);
}
@@ -125,8 +125,8 @@ suite('KeybindingLabels', () => {
});
test('Electron Accelerator label', () => {
function assertElectronAcceleratorLabel(OS: OperatingSystem, keybinding: number, expected: string): void {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
function assertElectronAcceleratorLabel(OS: OperatingSystem, keybinding: number, expected: string | null): void {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS);
assert.equal(usResolvedKeybinding.getElectronAccelerator(), expected);
}
@@ -153,7 +153,7 @@ suite('KeybindingLabels', () => {
test('User Settings label', () => {
function assertElectronAcceleratorLabel(OS: OperatingSystem, keybinding: number, expected: string): void {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS);
assert.equal(usResolvedKeybinding.getUserSettingsLabel(), expected);
}

View File

@@ -21,7 +21,7 @@ function createContext(ctx: any) {
suite('KeybindingResolver', () => {
function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr, isDefault: boolean): ResolvedKeybindingItem {
const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS) : null);
const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : null);
return new ResolvedKeybindingItem(
resolvedKeybinding,
command,
@@ -32,7 +32,7 @@ suite('KeybindingResolver', () => {
}
function getDispatchStr(runtimeKb: SimpleKeybinding): string {
return USLayoutResolvedKeybinding.getDispatchStr(runtimeKb);
return USLayoutResolvedKeybinding.getDispatchStr(runtimeKb)!;
}
test('resolve key', function () {
@@ -45,7 +45,7 @@ suite('KeybindingResolver', () => {
assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'bz' }), contextRules), false);
let resolver = new KeybindingResolver([keybindingItem], []);
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(<SimpleKeybinding>runtimeKeybinding)).commandId, 'yes');
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(<SimpleKeybinding>runtimeKeybinding))!.commandId, 'yes');
assert.equal(resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(<SimpleKeybinding>runtimeKeybinding)), null);
});
@@ -57,7 +57,7 @@ suite('KeybindingResolver', () => {
let keybindingItem = kbItem(keybinding, 'yes', commandArgs, contextRules, true);
let resolver = new KeybindingResolver([keybindingItem], []);
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(<SimpleKeybinding>runtimeKeybinding)).commandArgs, commandArgs);
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(<SimpleKeybinding>runtimeKeybinding))!.commandArgs, commandArgs);
});
test('KeybindingResolver.combine simple 1', function () {
@@ -154,7 +154,7 @@ suite('KeybindingResolver', () => {
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(KeyCode.KEY_A, '-yes1', null, null, false)
kbItem(KeyCode.KEY_A, '-yes1', null, null!, false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
@@ -168,7 +168,7 @@ suite('KeybindingResolver', () => {
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(0, '-yes1', null, null, false)
kbItem(0, '-yes1', null, null!, false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
@@ -182,7 +182,7 @@ suite('KeybindingResolver', () => {
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(KeyCode.KEY_A, '-yes1', null, null, false)
kbItem(KeyCode.KEY_A, '-yes1', null, null!, false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
@@ -210,7 +210,7 @@ suite('KeybindingResolver', () => {
let key3IsTrue = ContextKeyExpr.equals('key3', true);
let key4IsTrue = ContextKeyExpr.equals('key4', true);
assertIsIncluded([key1IsTrue], null);
assertIsIncluded([key1IsTrue], null!);
assertIsIncluded([key1IsTrue], []);
assertIsIncluded([key1IsTrue], [key1IsTrue]);
assertIsIncluded([key1IsTrue], [key1IsNotFalse]);
@@ -243,7 +243,7 @@ suite('KeybindingResolver', () => {
assertIsNotIncluded([key1IsTrue, key2IsNotFalse], [key4IsTrue]);
assertIsNotIncluded([key1IsTrue], [key2IsTrue]);
assertIsNotIncluded([], [key2IsTrue]);
assertIsNotIncluded(null, [key2IsTrue]);
assertIsNotIncluded(null!, [key2IsTrue]);
});
test('resolve command', function () {
@@ -272,7 +272,7 @@ suite('KeybindingResolver', () => {
_kbItem(
KeyCode.KEY_Z,
'second',
null
null!
),
// This one sometimes overwrites first
_kbItem(
@@ -290,43 +290,43 @@ suite('KeybindingResolver', () => {
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z),
'fifth',
null
null!
),
// This one has no keybinding
_kbItem(
0,
'sixth',
null
null!
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U),
'seventh',
null
null!
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K),
'seventh',
null
null!
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U),
'uncomment lines',
null
null!
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_C),
'comment lines',
null
null!
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_G, KeyMod.CtrlCmd | KeyCode.KEY_C),
'unreachablechord',
null
null!
),
_kbItem(
KeyMod.CtrlCmd | KeyCode.KEY_G,
'eleven',
null
null!
)
];
@@ -337,30 +337,30 @@ suite('KeybindingResolver', () => {
let lookupResult = resolver.lookupKeybindings(commandId);
assert.equal(lookupResult.length, expectedKeys.length, 'Length mismatch @ commandId ' + commandId + '; GOT: ' + JSON.stringify(lookupResult, null, '\t'));
for (let i = 0, len = lookupResult.length; i < len; i++) {
const expected = new USLayoutResolvedKeybinding(createKeybinding(expectedKeys[i], OS), OS);
const expected = new USLayoutResolvedKeybinding(createKeybinding(expectedKeys[i], OS)!, OS);
assert.equal(lookupResult[i].resolvedKeybinding.getUserSettingsLabel(), expected.getUserSettingsLabel(), 'value mismatch @ commandId ' + commandId);
assert.equal(lookupResult[i].resolvedKeybinding!.getUserSettingsLabel(), expected.getUserSettingsLabel(), 'value mismatch @ commandId ' + commandId);
}
};
let testResolve = (ctx: IContext, _expectedKey: number, commandId: string) => {
const expectedKey = createKeybinding(_expectedKey, OS);
const expectedKey = createKeybinding(_expectedKey, OS)!;
if (expectedKey.type === KeybindingType.Chord) {
let firstPart = getDispatchStr(expectedKey.firstPart);
let chordPart = getDispatchStr(expectedKey.chordPart);
let result = resolver.resolve(ctx, null, firstPart);
let result = resolver.resolve(ctx, null, firstPart)!;
assert.ok(result !== null, 'Enters chord for ' + commandId);
assert.equal(result.commandId, null, 'Enters chord for ' + commandId);
assert.equal(result.enterChord, true, 'Enters chord for ' + commandId);
result = resolver.resolve(ctx, firstPart, chordPart);
result = resolver.resolve(ctx, firstPart, chordPart)!;
assert.ok(result !== null, 'Enters chord for ' + commandId);
assert.equal(result.commandId, commandId, 'Finds chorded command ' + commandId);
assert.equal(result.enterChord, false, 'Finds chorded command ' + commandId);
} else {
let result = resolver.resolve(ctx, null, getDispatchStr(expectedKey));
let result = resolver.resolve(ctx, null, getDispatchStr(expectedKey))!;
assert.ok(result !== null, 'Finds command ' + commandId);
assert.equal(result.commandId, commandId, 'Finds command ' + commandId);
assert.equal(result.enterChord, false, 'Finds command ' + commandId);

View File

@@ -54,8 +54,9 @@ export class MockContextKeyService implements IContextKeyService {
return Event.None;
}
public getContextKeyValue(key: string) {
if (this._keys.has(key)) {
return this._keys.get(key).get();
const value = this._keys.get(key);
if (value) {
return value.get();
}
}
public getContext(domNode: HTMLElement): any {
@@ -108,8 +109,8 @@ export class MockKeybindingService implements IKeybindingService {
return [];
}
public lookupKeybinding(commandId: string): ResolvedKeybinding | null {
return null;
public lookupKeybinding(commandId: string): ResolvedKeybinding | undefined {
return undefined;
}
public customKeybindingsCount(): number {
@@ -120,11 +121,15 @@ export class MockKeybindingService implements IKeybindingService {
return null;
}
dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
public dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void {
}
public dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
return false;
}
mightProducePrintableCharacter(e: IKeyboardEvent): boolean {
public mightProducePrintableCharacter(e: IKeyboardEvent): boolean {
return false;
}
}

View File

@@ -5,26 +5,15 @@
import { URI } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { Event } from 'vs/base/common/event';
import { IWorkspace } from 'vs/platform/workspace/common/workspace';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { isEqual, basenameOrAuthority } from 'vs/base/common/resources';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { tildify, getPathLabel } from 'vs/base/common/labels';
import { ltrim, startsWith } from 'vs/base/common/strings';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { basename as resourceBasename } from 'vs/base/common/resources';
import { isLinux } from 'vs/base/common/platform';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { localize } from 'vs/nls';
import { isParent } from 'vs/platform/files/common/files';
import { basename, dirname, join } from 'vs/base/common/paths';
import { Schemas } from 'vs/base/common/network';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
export interface RegisterFormatterEvent {
selector: string;
formatter: LabelRules;
}
import { basename } from 'vs/base/common/paths';
export interface ILabelService {
_serviceBrand: any;
@@ -36,175 +25,40 @@ export interface ILabelService {
getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean }): string;
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string;
getHostLabel(): string;
registerFormatter(selector: string, formatter: LabelRules): IDisposable;
onDidRegisterFormatter: Event<RegisterFormatterEvent>;
registerFormatter(formatter: ResourceLabelFormatter): IDisposable;
onDidChangeFormatters: Event<void>;
}
export interface LabelRules {
uri: {
label: string; // myLabel:/${path}
separator: '/' | '\\' | '';
tildify?: boolean;
normalizeDriveLetter?: boolean;
authorityPrefix?: string;
};
workspace?: {
suffix: string;
};
export interface ResourceLabelFormatter {
scheme: string;
authority?: string;
formatting: ResourceLabelFormatting;
}
export interface ResourceLabelFormatting {
label: string; // myLabel:/${path}
separator: '/' | '\\' | '';
tildify?: boolean;
normalizeDriveLetter?: boolean;
workspaceSuffix?: string;
authorityPrefix?: string;
}
const LABEL_SERVICE_ID = 'label';
const sepRegexp = /\//g;
const labelMatchingRegexp = /\$\{scheme\}|\$\{authority\}|\$\{path\}/g;
function hasDriveLetter(path: string): boolean {
return !!(isWindows && path && path[2] === ':');
export function getSimpleWorkspaceLabel(workspace: IWorkspaceIdentifier | URI, workspaceHome: string): string {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
return resourceBasename(workspace);
}
// Workspace: Untitled
if (isParent(workspace.configPath, workspaceHome, !isLinux /* ignore case */)) {
return localize('untitledWorkspace', "Untitled (Workspace)");
}
const filename = basename(workspace.configPath);
const workspaceName = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
return localize('workspaceName', "{0} (Workspace)", workspaceName);
}
export class LabelService implements ILabelService {
_serviceBrand: any;
private readonly formatters: { [prefix: string]: LabelRules } = Object.create(null);
private readonly _onDidRegisterFormatter = new Emitter<RegisterFormatterEvent>();
constructor(
@IEnvironmentService private environmentService: IEnvironmentService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IWindowService private windowService: IWindowService
) { }
get onDidRegisterFormatter(): Event<RegisterFormatterEvent> {
return this._onDidRegisterFormatter.event;
}
findFormatter(resource: URI): LabelRules | undefined {
const path = `${resource.scheme}://${resource.authority}`;
let bestPrefix = '';
for (let prefix in this.formatters) {
if (startsWith(path, prefix) && prefix.length > bestPrefix.length) {
bestPrefix = prefix;
}
}
if (bestPrefix.length) {
return this.formatters[bestPrefix];
}
return void 0;
}
getUriLabel(resource: URI, options: { relative?: boolean, noPrefix?: boolean } = {}): string {
const formatter = this.findFormatter(resource);
if (!formatter) {
return getPathLabel(resource.path, this.environmentService, options.relative ? this.contextService : undefined);
}
if (options.relative) {
const baseResource = this.contextService && this.contextService.getWorkspaceFolder(resource);
if (baseResource) {
let relativeLabel: string;
if (isEqual(baseResource.uri, resource, !isLinux)) {
relativeLabel = ''; // no label if resources are identical
} else {
const baseResourceLabel = this.formatUri(baseResource.uri, formatter, options.noPrefix);
relativeLabel = ltrim(this.formatUri(resource, formatter, options.noPrefix).substring(baseResourceLabel.length), formatter.uri.separator);
}
const hasMultipleRoots = this.contextService.getWorkspace().folders.length > 1;
if (hasMultipleRoots && !options.noPrefix) {
const rootName = (baseResource && baseResource.name) ? baseResource.name : basenameOrAuthority(baseResource.uri);
relativeLabel = relativeLabel ? (rootName + ' • ' + relativeLabel) : rootName; // always show root basename if there are multiple
}
return relativeLabel;
}
}
return this.formatUri(resource, formatter, options.noPrefix);
}
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string {
if (!isWorkspaceIdentifier(workspace) && !isSingleFolderWorkspaceIdentifier(workspace)) {
const identifier = toWorkspaceIdentifier(workspace);
if (!identifier) {
return '';
}
workspace = identifier;
}
// Workspace: Single Folder
if (isSingleFolderWorkspaceIdentifier(workspace)) {
// Folder on disk
const formatter = this.findFormatter(workspace);
const label = options && options.verbose ? this.getUriLabel(workspace) : basenameOrAuthority(workspace);
if (workspace.scheme === Schemas.file) {
return label;
}
const suffix = formatter && formatter.workspace && (typeof formatter.workspace.suffix === 'string') ? formatter.workspace.suffix : workspace.scheme;
return suffix ? `${label} (${suffix})` : label;
}
// Workspace: Untitled
if (isParent(workspace.configPath, this.environmentService.workspacesHome, !isLinux /* ignore case */)) {
return localize('untitledWorkspace', "Untitled (Workspace)");
}
// Workspace: Saved
const filename = basename(workspace.configPath);
const workspaceName = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
if (options && options.verbose) {
return localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(URI.file(join(dirname(workspace.configPath), workspaceName))));
}
return localize('workspaceName', "{0} (Workspace)", workspaceName);
}
getHostLabel(): string {
if (this.windowService) {
const authority = this.windowService.getConfiguration().remoteAuthority;
if (authority) {
const formatter = this.findFormatter(URI.from({ scheme: REMOTE_HOST_SCHEME, authority }));
if (formatter && formatter.workspace) {
return formatter.workspace.suffix;
}
}
}
return '';
}
registerFormatter(selector: string, formatter: LabelRules): IDisposable {
this.formatters[selector] = formatter;
this._onDidRegisterFormatter.fire({ selector, formatter });
return {
dispose: () => delete this.formatters[selector]
};
}
private formatUri(resource: URI, formatter: LabelRules, forceNoTildify?: boolean): string {
let label = formatter.uri.label.replace(labelMatchingRegexp, match => {
switch (match) {
case '${scheme}': return resource.scheme;
case '${authority}': return resource.authority;
case '${path}': return resource.path;
default: return '';
}
});
// convert \c:\something => C:\something
if (formatter.uri.normalizeDriveLetter && hasDriveLetter(label)) {
label = label.charAt(1).toUpperCase() + label.substr(2);
}
if (formatter.uri.tildify && !forceNoTildify) {
label = tildify(label, this.environmentService.userHome);
}
if (formatter.uri.authorityPrefix && resource.authority) {
label = formatter.uri.authorityPrefix + label;
}
return label.replace(sepRegexp, formatter.uri.separator);
}
}
export const ILabelService = createDecorator<ILabelService>(LABEL_SERVICE_ID);

View File

@@ -1,27 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// TODO@Isidor bad layering
// tslint:disable-next-line:import-patterns
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { ILabelService } from 'vs/platform/label/common/label';
import { ipcRenderer as ipc } from 'electron';
import { Registry } from 'vs/platform/registry/common/platform';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
/**
* Uri display registration needs to be shared from renderer to main.
* Since there will be another instance of the uri display service running on main.
*/
class LabelRegistrationContribution implements IWorkbenchContribution {
constructor(@ILabelService labelService: ILabelService) {
labelService.onDidRegisterFormatter(data => {
ipc.send('vscode:labelRegisterFormatter', data);
});
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LabelRegistrationContribution, LifecyclePhase.Starting);

View File

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

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
import { ILogService } from 'vs/platform/log/common/log';
import { IURLService } from 'vs/platform/url/common/url';
@@ -61,10 +60,10 @@ function parseOpenUrl(args: ParsedArgs): URI[] {
export interface ILaunchService {
_serviceBrand: any;
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void>;
getMainProcessId(): TPromise<number>;
getMainProcessInfo(): TPromise<IMainProcessInfo>;
getLogsPath(): TPromise<string>;
start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void>;
getMainProcessId(): Promise<number>;
getMainProcessInfo(): Promise<IMainProcessInfo>;
getLogsPath(): Promise<string>;
}
export class LaunchChannel implements IServerChannel {
@@ -75,7 +74,7 @@ export class LaunchChannel implements IServerChannel {
throw new Error(`Event not found: ${event}`);
}
call(_, command: string, arg: any): TPromise<any> {
call(_, command: string, arg: any): Promise<any> {
switch (command) {
case 'start':
const { args, userEnv } = arg as IStartArguments;
@@ -101,19 +100,19 @@ export class LaunchChannelClient implements ILaunchService {
constructor(private channel: IChannel) { }
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
return this.channel.call('start', { args, userEnv });
}
getMainProcessId(): TPromise<number> {
getMainProcessId(): Promise<number> {
return this.channel.call('get-main-process-id', null);
}
getMainProcessInfo(): TPromise<IMainProcessInfo> {
getMainProcessInfo(): Promise<IMainProcessInfo> {
return this.channel.call('get-main-process-info', null);
}
getLogsPath(): TPromise<string> {
getLogsPath(): Promise<string> {
return this.channel.call('get-logs-path', null);
}
}
@@ -123,22 +122,22 @@ export class LaunchService implements ILaunchService {
_serviceBrand: any;
constructor(
@ILogService private logService: ILogService,
@IWindowsMainService private windowsMainService: IWindowsMainService,
@IURLService private urlService: IURLService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
@ILogService private readonly logService: ILogService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IURLService private readonly urlService: IURLService,
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IConfigurationService private readonly configurationService: IConfigurationService
) { }
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
this.logService.trace('Received data from other instance: ', args, userEnv);
const urlsToOpen = parseOpenUrl(args);
// Check early for open-url which is handled in URL service
if (urlsToOpen.length) {
let whenWindowReady = TPromise.as<any>(null);
let whenWindowReady: Promise<any> = Promise.resolve<any>(null);
// Create a window if there is none
if (this.windowsMainService.getWindowCount() === 0) {
@@ -153,14 +152,14 @@ export class LaunchService implements ILaunchService {
}
});
return TPromise.as(void 0);
return Promise.resolve(undefined);
}
// Otherwise handle in windows service
return this.startOpenWindow(args, userEnv);
}
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
let usedWindows: ICodeWindow[] = [];
@@ -236,19 +235,19 @@ export class LaunchService implements ILaunchService {
return Promise.race([
this.windowsMainService.waitForWindowCloseOrLoad(usedWindows[0].id),
whenDeleted(args.waitMarkerFilePath)
]).then(() => void 0, () => void 0);
]).then(() => undefined, () => undefined);
}
return TPromise.as(void 0);
return Promise.resolve(undefined);
}
getMainProcessId(): TPromise<number> {
getMainProcessId(): Promise<number> {
this.logService.trace('Received request for process ID from other instance.');
return TPromise.as(process.pid);
return Promise.resolve(process.pid);
}
getMainProcessInfo(): TPromise<IMainProcessInfo> {
getMainProcessInfo(): Promise<IMainProcessInfo> {
this.logService.trace('Received request for main process info from other instance.');
const windows: IWindowInfo[] = [];
@@ -261,17 +260,17 @@ export class LaunchService implements ILaunchService {
}
});
return TPromise.wrap({
return Promise.resolve({
mainPID: process.pid,
mainArguments: process.argv.slice(1),
windows
} as IMainProcessInfo);
});
}
getLogsPath(): TPromise<string> {
getLogsPath(): Promise<string> {
this.logService.trace('Received request for logs path from other instance.');
return TPromise.as(this.environmentService.logsPath);
return Promise.resolve(this.environmentService.logsPath);
}
private codeWindowToInfo(window: ICodeWindow): IWindowInfo {

View File

@@ -24,7 +24,7 @@ export interface BeforeShutdownEvent {
* Allows to veto the shutdown. The veto can be a long running operation but it
* will block the application from closing.
*/
veto(value: boolean | Thenable<boolean>): void;
veto(value: boolean | Promise<boolean>): void;
/**
* The reason why the application will be shutting down.
@@ -46,7 +46,7 @@ export interface WillShutdownEvent {
* Allows to join the shutdown. The promise can be a long running operation but it
* will block the application from closing.
*/
join(promise: Thenable<void>): void;
join(promise: Promise<void>): void;
/**
* The reason why the application is shutting down.
@@ -162,7 +162,7 @@ export interface ILifecycleService {
* Returns a promise that resolves when a certain lifecycle phase
* has started.
*/
when(phase: LifecyclePhase): Thenable<void>;
when(phase: LifecyclePhase): Promise<void>;
}
export const NullLifecycleService: ILifecycleService = {
@@ -176,12 +176,12 @@ export const NullLifecycleService: ILifecycleService = {
};
// Shared veto handling across main and renderer
export function handleVetos(vetos: (boolean | Thenable<boolean>)[], onError: (error: Error) => void): Promise<boolean /* veto */> {
export function handleVetos(vetos: (boolean | Promise<boolean>)[], onError: (error: Error) => void): Promise<boolean /* veto */> {
if (vetos.length === 0) {
return Promise.resolve(false);
}
const promises: Thenable<void>[] = [];
const promises: Promise<void>[] = [];
let lazyValue = false;
for (let valueOrPromise of vetos) {

View File

@@ -42,10 +42,10 @@ export class LifecycleService extends Disposable implements ILifecycleService {
private shutdownReason: ShutdownReason;
constructor(
@INotificationService private notificationService: INotificationService,
@IWindowService private windowService: IWindowService,
@IStorageService private storageService: IStorageService,
@ILogService private logService: ILogService
@INotificationService private readonly notificationService: INotificationService,
@IWindowService private readonly windowService: IWindowService,
@IStorageService private readonly storageService: IStorageService,
@ILogService private readonly logService: ILogService
) {
super();
@@ -116,7 +116,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
}
private handleBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
const vetos: (boolean | Thenable<boolean>)[] = [];
const vetos: (boolean | Promise<boolean>)[] = [];
this._onBeforeShutdown.fire({
veto(value) {
@@ -131,8 +131,8 @@ export class LifecycleService extends Disposable implements ILifecycleService {
});
}
private handleWillShutdown(reason: ShutdownReason): Thenable<void> {
const joiners: Thenable<void>[] = [];
private handleWillShutdown(reason: ShutdownReason): Promise<void> {
const joiners: Promise<void>[] = [];
this._onWillShutdown.fire({
join(promise) {
@@ -143,7 +143,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
reason
});
return Promise.all(joiners).then(() => void 0, err => {
return Promise.all(joiners).then(() => undefined, err => {
this.notificationService.error(toErrorMessage(err));
onUnexpectedError(err);
});
@@ -163,13 +163,14 @@ export class LifecycleService extends Disposable implements ILifecycleService {
this._phase = value;
mark(`LifecyclePhase/${LifecyclePhaseToString(value)}`);
if (this.phaseWhen.has(this._phase)) {
this.phaseWhen.get(this._phase).open();
const barrier = this.phaseWhen.get(this._phase);
if (barrier) {
barrier.open();
this.phaseWhen.delete(this._phase);
}
}
when(phase: LifecyclePhase): Thenable<any> {
when(phase: LifecyclePhase): Promise<any> {
if (phase <= this._phase) {
return Promise.resolve();
}

View File

@@ -12,7 +12,6 @@ import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';
import { always } from 'vs/base/common/async';
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
@@ -26,7 +25,7 @@ export const enum UnloadReason {
export interface IWindowUnloadEvent {
window: ICodeWindow;
reason: UnloadReason;
veto(value: boolean | Thenable<boolean>): void;
veto(value: boolean | Promise<boolean>): void;
}
export interface ShutdownEvent {
@@ -35,7 +34,7 @@ export interface ShutdownEvent {
* Allows to join the shutdown. The promise can be a long running operation but it
* will block the application from closing.
*/
join(promise: Thenable<void>): void;
join(promise: Promise<void>): void;
}
export interface ILifecycleService {
@@ -79,7 +78,7 @@ export interface ILifecycleService {
/**
* Unload a window for the provided reason. All lifecycle event handlers are triggered.
*/
unload(window: ICodeWindow, reason: UnloadReason): Thenable<boolean /* veto */>;
unload(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */>;
/**
* Restart the application with optional arguments (CLI). All lifecycle event handlers are triggered.
@@ -89,7 +88,7 @@ export interface ILifecycleService {
/**
* Shutdown the application normally. All lifecycle event handlers are triggered.
*/
quit(fromUpdate?: boolean): Thenable<boolean /* veto */>;
quit(fromUpdate?: boolean): Promise<boolean /* veto */>;
/**
* Forcefully shutdown the application. No livecycle event handlers are triggered.
@@ -107,10 +106,10 @@ export class LifecycleService extends Disposable implements ILifecycleService {
private oneTimeListenerTokenGenerator = 0;
private windowCounter = 0;
private pendingQuitPromise: Thenable<boolean> | null;
private pendingQuitPromise: Promise<boolean> | null;
private pendingQuitPromiseResolve: { (veto: boolean): void } | null;
private pendingWillShutdownPromise: Thenable<void> | null;
private pendingWillShutdownPromise: Promise<void> | null;
private _quitRequested = false;
get quitRequested(): boolean { return this._quitRequested; }
@@ -131,8 +130,8 @@ export class LifecycleService extends Disposable implements ILifecycleService {
readonly onBeforeWindowUnload: Event<IWindowUnloadEvent> = this._onBeforeWindowUnload.event;
constructor(
@ILogService private logService: ILogService,
@IStateService private stateService: IStateService
@ILogService private readonly logService: ILogService,
@IStateService private readonly stateService: IStateService
) {
super();
@@ -202,7 +201,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
const shutdownPromise = this.beginOnWillShutdown();
// Wait until shutdown is signaled to be complete
always(shutdownPromise, () => {
shutdownPromise.finally(() => {
// Resolve pending quit promise now without veto
this.resolvePendingQuitPromise(false /* no veto */);
@@ -217,14 +216,14 @@ export class LifecycleService extends Disposable implements ILifecycleService {
});
}
private beginOnWillShutdown(): Thenable<void> {
private beginOnWillShutdown(): Promise<void> {
if (this.pendingWillShutdownPromise) {
return this.pendingWillShutdownPromise; // shutdown is already running
}
this.logService.trace('Lifecycle#onWillShutdown.fire()');
const joiners: Thenable<void>[] = [];
const joiners: Promise<void>[] = [];
this._onWillShutdown.fire({
join(promise) {
@@ -234,7 +233,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
}
});
this.pendingWillShutdownPromise = Promise.all(joiners).then(null, err => this.logService.error(err));
this.pendingWillShutdownPromise = Promise.all(joiners).then(undefined, err => this.logService.error(err));
return this.pendingWillShutdownPromise;
}
@@ -292,7 +291,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
});
}
unload(window: ICodeWindow, reason: UnloadReason): Thenable<boolean /* veto */> {
unload(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */> {
// Always allow to unload a window that is not yet ready
if (!window.isReady) {
@@ -348,7 +347,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
}
}
private onBeforeUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): Thenable<boolean /* veto */> {
private onBeforeUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */> {
return new Promise<boolean>(c => {
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
const okChannel = `vscode:ok${oneTimeEventToken}`;
@@ -366,8 +365,8 @@ export class LifecycleService extends Disposable implements ILifecycleService {
});
}
private onBeforeUnloadWindowInMain(window: ICodeWindow, reason: UnloadReason): Thenable<boolean /* veto */> {
const vetos: (boolean | Thenable<boolean>)[] = [];
private onBeforeUnloadWindowInMain(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */> {
const vetos: (boolean | Promise<boolean>)[] = [];
this._onBeforeWindowUnload.fire({
reason,
@@ -380,7 +379,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
return handleVetos(vetos, err => this.logService.error(err));
}
private onWillUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): Thenable<void> {
private onWillUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): Promise<void> {
return new Promise<void>(resolve => {
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
const replyChannel = `vscode:reply${oneTimeEventToken}`;
@@ -395,7 +394,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
* A promise that completes to indicate if the quit request has been veto'd
* by the user or not.
*/
quit(fromUpdate?: boolean): Thenable<boolean /* veto */> {
quit(fromUpdate?: boolean): Promise<boolean /* veto */> {
if (this.pendingQuitPromise) {
return this.pendingQuitPromise;
}

View File

@@ -16,7 +16,6 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { combinedDisposable, Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { TPromise } from 'vs/base/common/winjs.base';
import { IFilter, ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
import { ClickBehavior, DefaultController, DefaultTreestyler, IControllerOptions, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
@@ -33,10 +32,12 @@ import { attachInputBoxStyler, attachListStyler, computeStyles, defaultListStyle
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys';
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { AsyncDataTree, IDataSource, IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource } from 'vs/base/browser/ui/tree/tree';
import { AsyncDataTree, IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree';
import { IKeyboardNavigationEventFilter } from 'vs/base/browser/ui/tree/abstractTree';
export type ListWidget = List<any> | PagedList<any> | ITree | ObjectTree<any, any> | AsyncDataTree<any, any>;
export type ListWidget = List<any> | PagedList<any> | ITree | ObjectTree<any, any> | DataTree<any, any, any> | AsyncDataTree<any, any, any>;
export const IListService = createDecorator<IListService>('listService');
@@ -103,6 +104,9 @@ export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListF
export const WorkbenchListHasSelectionOrFocus = new RawContextKey<boolean>('listHasSelectionOrFocus', false);
export const WorkbenchListDoubleSelection = new RawContextKey<boolean>('listDoubleSelection', false);
export const WorkbenchListMultiSelection = new RawContextKey<boolean>('listMultiSelection', false);
export const WorkbenchListSupportsKeyboardNavigation = new RawContextKey<boolean>('listSupportsKeyboardNavigation', true);
export const WorkbenchListAutomaticKeyboardNavigationKey = 'listAutomaticKeyboardNavigation';
export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey<boolean>(WorkbenchListAutomaticKeyboardNavigationKey, true);
function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: ListWidget): IContextKeyService {
const result = contextKeyService.createScoped(widget.getHTMLElement());
@@ -113,6 +117,8 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi
export const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';
export const openModeSettingKey = 'workbench.list.openMode';
export const horizontalScrollingKey = 'workbench.tree.horizontalScrolling';
export const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation';
const treeIndentKey = 'workbench.tree.indent';
function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {
return configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
@@ -145,12 +151,13 @@ class WorkbenchOpenController implements IOpenController {
shouldOpen(event: UIEvent): boolean {
if (event instanceof MouseEvent) {
const isLeftButton = event.button === 0;
const isDoubleClick = event.detail === 2;
if (!useSingleClickToOpen(this.configurationService) && !isDoubleClick) {
if (isLeftButton && !useSingleClickToOpen(this.configurationService) && !isDoubleClick) {
return false;
}
if (event.button === 0 /* left mouse button */ || event.button === 1 /* middle mouse button */) {
if (isLeftButton /* left mouse button */ || event.button === 1 /* middle mouse button */) {
return this.existingOpenController ? this.existingOpenController.shouldOpen(event) : true;
}
@@ -161,14 +168,25 @@ class WorkbenchOpenController implements IOpenController {
}
}
function handleListControllers<T>(options: IListOptions<T>, configurationService: IConfigurationService): IListOptions<T> {
function toWorkbenchListOptions<T>(options: IListOptions<T>, configurationService: IConfigurationService, keybindingService: IKeybindingService): IListOptions<T> {
const result = { ...options };
if (options.multipleSelectionSupport !== false && !options.multipleSelectionController) {
options.multipleSelectionController = new MultipleSelectionController(configurationService);
result.multipleSelectionController = new MultipleSelectionController(configurationService);
}
options.openController = new WorkbenchOpenController(configurationService, options.openController);
result.openController = new WorkbenchOpenController(configurationService, options.openController);
return options;
if (options.keyboardNavigationLabelProvider) {
const tlp = options.keyboardNavigationLabelProvider;
result.keyboardNavigationLabelProvider = {
getKeyboardNavigationLabel(e) { return tlp.getKeyboardNavigationLabel(e); },
mightProducePrintableCharacter(e) { return keybindingService.mightProducePrintableCharacter(e); }
};
}
return result;
}
let sharedListStyleSheet: HTMLStyleElement;
@@ -204,6 +222,7 @@ function handleTreeController(configuration: ITreeConfiguration, instantiationSe
export class WorkbenchList<T> extends List<T> {
readonly contextKeyService: IContextKeyService;
private readonly configurationService: IConfigurationService;
private listHasSelectionOrFocus: IContextKey<boolean>;
private listDoubleSelection: IContextKey<boolean>;
@@ -219,19 +238,23 @@ export class WorkbenchList<T> extends List<T> {
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService
) {
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
super(container, delegate, renderers,
{
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...handleListControllers(options, configurationService)
...toWorkbenchListOptions(options, configurationService, keybindingService),
horizontalScrolling
} as IListOptions<T>
);
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.configurationService = configurationService;
const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
listSupportsMultiSelect.set(!(options.multipleSelectionSupport === false));
@@ -281,8 +304,9 @@ export class WorkbenchList<T> extends List<T> {
export class WorkbenchPagedList<T> extends PagedList<T> {
readonly contextKeyService: IContextKeyService;
private readonly configurationService: IConfigurationService;
private disposables: IDisposable[] = [];
private disposables: IDisposable[];
private _useAltAsMultipleSelectionModifier: boolean;
@@ -294,19 +318,24 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService
) {
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
super(container, delegate, renderers,
{
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...handleListControllers(options, configurationService)
...toWorkbenchListOptions(options, configurationService, keybindingService),
horizontalScrolling
} as IListOptions<T>
);
this.disposables = [];
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.configurationService = configurationService;
const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
listSupportsMultiSelect.set(!(options.multipleSelectionSupport === false));
@@ -453,7 +482,7 @@ export class WorkbenchTreeController extends DefaultController {
constructor(
options: IControllerOptions,
@IConfigurationService private configurationService: IConfigurationService
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(massageControllerOptions(options));
@@ -494,7 +523,7 @@ export interface IResourceResultsNavigationOptions {
export class TreeResourceNavigator extends Disposable {
private readonly _openResource: Emitter<IOpenResourceOptions> = new Emitter<IOpenResourceOptions>();
private readonly _openResource = new Emitter<IOpenResourceOptions>();
readonly openResource: Event<IOpenResourceOptions> = this._openResource.event;
constructor(private tree: WorkbenchTree, private options?: IResourceResultsNavigationOptions) {
@@ -575,14 +604,27 @@ export interface IResourceResultsNavigationOptions {
openOnFocus: boolean;
}
export interface SelectionKeyboardEvent extends KeyboardEvent {
preserveFocus?: boolean;
}
export function getSelectionKeyboardEvent(typeArg: string, preserveFocus?: boolean): SelectionKeyboardEvent {
const e = new KeyboardEvent(typeArg);
(<SelectionKeyboardEvent>e).preserveFocus = preserveFocus;
return e;
}
export class TreeResourceNavigator2<T, TFilterData> extends Disposable {
private readonly _openResource: Emitter<IOpenEvent<T>> = new Emitter<IOpenEvent<T>>();
readonly openResource: Event<IOpenEvent<T>> = this._openResource.event;
private readonly _onDidOpenResource = new Emitter<IOpenEvent<T | null>>();
readonly onDidOpenResource: Event<IOpenEvent<T | null>> = this._onDidOpenResource.event;
constructor(private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchAsyncDataTree<T, TFilterData>, private options?: IResourceResultsNavigationOptions) {
constructor(
private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchDataTree<any, T, TFilterData> | WorkbenchAsyncDataTree<any, T, TFilterData>,
private options?: IResourceResultsNavigationOptions
) {
super();
this.registerListeners();
}
@@ -594,9 +636,9 @@ export class TreeResourceNavigator2<T, TFilterData> extends Disposable {
this._register(this.tree.onDidChangeSelection(e => this.onSelection(e)));
}
private onFocus(e: ITreeEvent<T>): void {
private onFocus(e: ITreeEvent<T | null>): void {
const focus = this.tree.getFocus();
this.tree.setSelection(focus, e.browserEvent);
this.tree.setSelection(focus as T[], e.browserEvent);
if (!e.browserEvent) {
return;
@@ -609,18 +651,24 @@ export class TreeResourceNavigator2<T, TFilterData> extends Disposable {
}
}
private onSelection(e: ITreeEvent<T>): void {
if (!e.browserEvent) {
private onSelection(e: ITreeEvent<T | null>): void {
if (!e.browserEvent || e.browserEvent.type === 'contextmenu') {
return;
}
const isDoubleClick = e.browserEvent.detail === 2;
const sideBySide = e.browserEvent instanceof MouseEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey);
this.open(!isDoubleClick, isDoubleClick, sideBySide);
const preserveFocus = (e.browserEvent instanceof KeyboardEvent && typeof (<SelectionKeyboardEvent>e.browserEvent).preserveFocus === 'boolean') ?
!!(<SelectionKeyboardEvent>e.browserEvent).preserveFocus :
!isDoubleClick;
if (this.tree.openOnSingleClick || isDoubleClick) {
const sideBySide = e.browserEvent instanceof MouseEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey);
this.open(preserveFocus, isDoubleClick, sideBySide);
}
}
private open(preserveFocus: boolean, pinned: boolean, sideBySide: boolean): void {
this._openResource.fire({
this._onDidOpenResource.fire({
editorOptions: {
preserveFocus,
pinned,
@@ -675,8 +723,8 @@ export class HighlightingTreeController extends WorkbenchTreeController {
class HightlightsFilter implements IFilter {
static add(config: ITreeConfiguration, options: IHighlightingTreeOptions): ITreeConfiguration {
let myFilter = new HightlightsFilter();
myFilter.enabled = options.filterOnType;
const myFilter = new HightlightsFilter();
myFilter.enabled = !!options.filterOnType;
if (!config.filter) {
config.filter = myFilter;
} else {
@@ -784,7 +832,7 @@ export class HighlightingWorkbenchTree extends WorkbenchTree {
this.disposables.push(this._onDidStartFilter);
}
setInput(element: any): TPromise<any> {
setInput(element: any): Promise<any> {
this.input.setEnabled(false);
return super.setInput(element).then(value => {
if (!this.input.inputElement) {
@@ -798,7 +846,7 @@ export class HighlightingWorkbenchTree extends WorkbenchTree {
layout(height?: number, width?: number): void {
this.input.layout();
super.layout(isNaN(height) ? height : height - getTotalHeight(this.inputContainer), width);
super.layout(typeof height !== 'number' || isNaN(height) ? height : height - getTotalHeight(this.inputContainer), width);
}
private onTypeInTree(): void {
@@ -824,7 +872,7 @@ export class HighlightingWorkbenchTree extends WorkbenchTree {
let topElement: any;
if (pattern) {
let nav = this.getNavigator(undefined, false);
let topScore: FuzzyScore;
let topScore: FuzzyScore | undefined;
while (nav.next()) {
let element = nav.current();
let score = this.highlighter.getHighlights(this, element, pattern);
@@ -842,7 +890,7 @@ export class HighlightingWorkbenchTree extends WorkbenchTree {
this.refresh().then(() => {
if (topElement) {
this.reveal(topElement, .5).then(_ => {
this.reveal(topElement, 0.5).then(_ => {
this.setSelection([topElement], this);
this.setFocus(topElement, this);
});
@@ -856,7 +904,7 @@ export class HighlightingWorkbenchTree extends WorkbenchTree {
return this.highlights.size > 0;
}
getHighlighterScore(element: any): FuzzyScore {
getHighlighterScore(element: any): FuzzyScore | undefined {
return this.highlights.get(this._getHighlightsStorageKey(element));
}
@@ -867,6 +915,27 @@ export class HighlightingWorkbenchTree extends WorkbenchTree {
}
}
function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingService: IKeybindingService): IKeyboardNavigationEventFilter {
let inChord = false;
return event => {
if (inChord) {
inChord = false;
return false;
}
const result = keybindingService.softDispatch(event, container);
if (result && result.enterChord) {
inChord = true;
return false;
}
inChord = false;
return true;
};
}
export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> {
readonly contextKeyService: IContextKeyService;
@@ -877,6 +946,8 @@ export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void>
private hasDoubleSelection: IContextKey<boolean>;
private hasMultiSelection: IContextKey<boolean>;
private _useAltAsMultipleSelectionModifier: boolean;
constructor(
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
@@ -885,14 +956,29 @@ export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void>
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService
) {
WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService);
WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService);
const automaticKeyboardNavigation = contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationKey);
const keyboardNavigation = configurationService.getValue<string>(keyboardNavigationSettingKey);
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
const openOnSingleClick = useSingleClickToOpen(configurationService);
super(container, delegate, renderers, {
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...handleListControllers(options, configurationService)
...toWorkbenchListOptions(options, configurationService, keybindingService),
indent: configurationService.getValue(treeIndentKey),
automaticKeyboardNavigation,
simpleKeyboardNavigation: keyboardNavigation === 'simple',
filterOnType: keyboardNavigation === 'filter',
horizontalScrolling,
openOnSingleClick,
keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService)
});
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
@@ -904,6 +990,11 @@ export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void>
this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
const interestingContextKeys = new Set();
interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationKey);
this.disposables.push(
this.contextKeyService,
(listService as ListService).register(this),
@@ -921,17 +1012,48 @@ export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void>
const focus = this.getFocus();
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
}),
configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(openModeSettingKey)) {
this.updateOptions({ openOnSingleClick: useSingleClickToOpen(configurationService) });
}
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
}
if (e.affectsConfiguration(treeIndentKey)) {
const indent = configurationService.getValue<number>(treeIndentKey);
this.updateOptions({ indent });
}
if (e.affectsConfiguration(keyboardNavigationSettingKey)) {
const keyboardNavigation = configurationService.getValue<string>(keyboardNavigationSettingKey);
this.updateOptions({
simpleKeyboardNavigation: keyboardNavigation === 'simple',
filterOnType: keyboardNavigation === 'filter'
});
}
}),
this.contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(interestingContextKeys)) {
const automaticKeyboardNavigation = this.contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationKey);
this.updateOptions({
automaticKeyboardNavigation
});
}
})
);
}
get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
}
}
export class WorkbenchAsyncDataTree<T extends NonNullable<any>, TFilterData = void> extends AsyncDataTree<T, TFilterData> {
export class WorkbenchDataTree<TInput, T, TFilterData = void> extends DataTree<TInput, T, TFilterData> {
readonly contextKeyService: IContextKeyService;
@@ -939,23 +1061,40 @@ export class WorkbenchAsyncDataTree<T extends NonNullable<any>, TFilterData = vo
private hasDoubleSelection: IContextKey<boolean>;
private hasMultiSelection: IContextKey<boolean>;
private _useAltAsMultipleSelectionModifier: boolean;
constructor(
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
dataSource: IDataSource<T>,
options: IAsyncDataTreeOptions<T, TFilterData>,
dataSource: IDataSource<TInput, T>,
options: IDataTreeOptions<T, TFilterData>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService
) {
WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService);
WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService);
const automaticKeyboardNavigation = contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationKey);
const keyboardNavigation = configurationService.getValue<string>(keyboardNavigationSettingKey);
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
const openOnSingleClick = useSingleClickToOpen(configurationService);
super(container, delegate, renderers, dataSource, {
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...handleListControllers(options, configurationService)
...toWorkbenchListOptions(options, configurationService, keybindingService),
indent: configurationService.getValue(treeIndentKey),
automaticKeyboardNavigation,
simpleKeyboardNavigation: keyboardNavigation === 'simple',
filterOnType: keyboardNavigation === 'filter',
horizontalScrolling,
openOnSingleClick,
keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService)
});
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
@@ -967,6 +1106,11 @@ export class WorkbenchAsyncDataTree<T extends NonNullable<any>, TFilterData = vo
this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
const interestingContextKeys = new Set();
interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationKey);
this.disposables.push(
this.contextKeyService,
(listService as ListService).register(this),
@@ -984,9 +1128,151 @@ export class WorkbenchAsyncDataTree<T extends NonNullable<any>, TFilterData = vo
const focus = this.getFocus();
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
}),
configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(openModeSettingKey)) {
this.updateOptions({ openOnSingleClick: useSingleClickToOpen(configurationService) });
}
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
}
if (e.affectsConfiguration(treeIndentKey)) {
const indent = configurationService.getValue<number>(treeIndentKey);
this.updateOptions({ indent });
}
if (e.affectsConfiguration(keyboardNavigationSettingKey)) {
const keyboardNavigation = configurationService.getValue<string>(keyboardNavigationSettingKey);
this.updateOptions({
simpleKeyboardNavigation: keyboardNavigation === 'simple',
filterOnType: keyboardNavigation === 'filter'
});
}
}),
this.contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(interestingContextKeys)) {
const automaticKeyboardNavigation = this.contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationKey);
this.updateOptions({
automaticKeyboardNavigation
});
}
})
);
}
get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
}
export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
readonly contextKeyService: IContextKeyService;
private hasSelectionOrFocus: IContextKey<boolean>;
private hasDoubleSelection: IContextKey<boolean>;
private hasMultiSelection: IContextKey<boolean>;
private _useAltAsMultipleSelectionModifier: boolean;
constructor(
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
dataSource: IAsyncDataSource<TInput, T>,
options: IAsyncDataTreeOptions<T, TFilterData>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService
) {
WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService);
WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService);
const automaticKeyboardNavigation = contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationKey);
const keyboardNavigation = configurationService.getValue<string>(keyboardNavigationSettingKey);
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
const openOnSingleClick = useSingleClickToOpen(configurationService);
super(container, delegate, renderers, dataSource, {
keyboardSupport: false,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...toWorkbenchListOptions(options, configurationService, keybindingService),
indent: configurationService.getValue<number>(treeIndentKey),
automaticKeyboardNavigation,
simpleKeyboardNavigation: keyboardNavigation === 'simple',
filterOnType: keyboardNavigation === 'filter',
horizontalScrolling,
openOnSingleClick,
keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService)
});
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
listSupportsMultiSelect.set(!(options.multipleSelectionSupport === false));
this.hasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);
this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
const interestingContextKeys = new Set();
interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationKey);
this.disposables.push(
this.contextKeyService,
(listService as ListService).register(this),
attachListStyler(this, themeService),
this.onDidChangeSelection(() => {
const selection = this.getSelection();
const focus = this.getFocus();
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
this.hasMultiSelection.set(selection.length > 1);
this.hasDoubleSelection.set(selection.length === 2);
}),
this.onDidChangeFocus(() => {
const selection = this.getSelection();
const focus = this.getFocus();
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
}),
configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(openModeSettingKey)) {
this.updateOptions({ openOnSingleClick: useSingleClickToOpen(configurationService) });
}
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
}
if (e.affectsConfiguration(treeIndentKey)) {
const indent = configurationService.getValue<number>(treeIndentKey);
this.updateOptions({ indent });
}
if (e.affectsConfiguration(keyboardNavigationSettingKey)) {
const keyboardNavigation = configurationService.getValue<string>(keyboardNavigationSettingKey);
this.updateOptions({
simpleKeyboardNavigation: keyboardNavigation === 'simple',
filterOnType: keyboardNavigation === 'filter'
});
}
}),
this.contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(interestingContextKeys)) {
const automaticKeyboardNavigation = this.contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationKey);
this.updateOptions({
automaticKeyboardNavigation
});
}
})
);
}
get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
}
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
@@ -1025,7 +1311,25 @@ configurationRegistry.registerConfiguration({
[horizontalScrollingKey]: {
'type': 'boolean',
'default': false,
'description': localize('horizontalScrolling setting', "Controls whether trees support horizontal scrolling in the workbench.")
}
'description': localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench.")
},
[treeIndentKey]: {
'type': 'number',
'default': 8,
minimum: 0,
maximum: 20,
'description': localize('tree indent setting', "Controls tree indentation in pixels.")
},
[keyboardNavigationSettingKey]: {
'type': 'string',
'enum': ['simple', 'highlight', 'filter'],
'enumDescriptions': [
localize('keyboardNavigationSettingKey.simple', "Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes."),
localize('keyboardNavigationSettingKey.highlight', "Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements."),
localize('keyboardNavigationSettingKey.filter', "Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.")
],
'default': 'highlight',
'description': localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter.")
},
}
});

View File

@@ -29,7 +29,7 @@ export interface ILocalizationsService {
_serviceBrand: any;
readonly onDidLanguagesChange: Event<void>;
getLanguageIds(type?: LanguageType): Thenable<string[]>;
getLanguageIds(type?: LanguageType): Promise<string[]>;
}
export function isValidLocalization(localization: ILocalization): boolean {

View File

@@ -9,7 +9,7 @@ import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier } fr
import { Disposable } from 'vs/base/common/lifecycle';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Queue } from 'vs/base/common/async';
import { areSameExtensions, getGalleryExtensionIdFromLocal, getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ILogService } from 'vs/platform/log/common/log';
import { isValidLocalization, ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations';
import product from 'vs/platform/node/product';
@@ -42,9 +42,9 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe
readonly onDidLanguagesChange: Event<void> = this._onDidLanguagesChange.event;
constructor(
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IEnvironmentService environmentService: IEnvironmentService,
@ILogService private logService: ILogService
@ILogService private readonly logService: ILogService
) {
super();
this.cache = this._register(new LanguagePacksCache(environmentService, logService));
@@ -55,7 +55,7 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe
this.extensionManagementService.getInstalled().then(installed => this.cache.update(installed));
}
getLanguageIds(type: LanguageType): Thenable<string[]> {
getLanguageIds(type: LanguageType): Promise<string[]> {
if (type === LanguageType.Core) {
return Promise.resolve([...systemLanguages]);
}
@@ -66,7 +66,7 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe
});
}
private onDidInstallExtension(extension: ILocalExtension): void {
private onDidInstallExtension(extension: ILocalExtension | undefined): void {
if (extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) {
this.logService.debug('Adding language packs from the extension', extension.identifier.id);
this.update();
@@ -76,7 +76,6 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe
private onDidUninstallExtension(identifier: IExtensionIdentifier): void {
this.cache.getLanguagePacks()
.then(languagePacks => {
identifier = { id: getIdFromLocalExtensionId(identifier.id), uuid: identifier.uuid };
if (Object.keys(languagePacks).some(language => languagePacks[language] && languagePacks[language].extensions.some(e => areSameExtensions(e.extensionIdentifier, identifier)))) {
this.logService.debug('Removing language packs from the extension', identifier.id);
this.update();
@@ -103,14 +102,14 @@ class LanguagePacksCache extends Disposable {
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@ILogService private logService: ILogService
@ILogService private readonly logService: ILogService
) {
super();
this.languagePacksFilePath = posix.join(environmentService.userDataPath, 'languagepacks.json');
this.languagePacksFileLimiter = new Queue();
}
getLanguagePacks(): Thenable<{ [language: string]: ILanguagePack }> {
getLanguagePacks(): Promise<{ [language: string]: ILanguagePack }> {
// if queue is not empty, fetch from disk
if (this.languagePacksFileLimiter.size) {
return this.withLanguagePacks()
@@ -119,9 +118,9 @@ class LanguagePacksCache extends Disposable {
return Promise.resolve(this.languagePacks);
}
update(extensions: ILocalExtension[]): Thenable<{ [language: string]: ILanguagePack }> {
update(extensions: ILocalExtension[]): Promise<{ [language: string]: ILanguagePack }> {
return this.withLanguagePacks(languagePacks => {
Object.keys(languagePacks).forEach(language => languagePacks[language] = undefined);
Object.keys(languagePacks).forEach(language => delete languagePacks[language]);
this.createLanguagePacksFromExtensions(languagePacks, ...extensions);
}).then(() => this.languagePacks);
}
@@ -136,8 +135,9 @@ class LanguagePacksCache extends Disposable {
}
private createLanguagePacksFromExtension(languagePacks: { [language: string]: ILanguagePack }, extension: ILocalExtension): void {
const extensionIdentifier = { id: getGalleryExtensionIdFromLocal(extension), uuid: extension.identifier.uuid };
for (const localizationContribution of extension.manifest.contributes.localizations) {
const extensionIdentifier = extension.identifier;
const localizations = extension.manifest.contributes && extension.manifest.contributes.localizations ? extension.manifest.contributes.localizations : [];
for (const localizationContribution of localizations) {
if (extension.location.scheme === Schemas.file && isValidLocalization(localizationContribution)) {
let languagePack = languagePacks[localizationContribution.languageId];
if (!languagePack) {
@@ -167,11 +167,11 @@ class LanguagePacksCache extends Disposable {
}
}
private withLanguagePacks<T>(fn: (languagePacks: { [language: string]: ILanguagePack }) => T = () => null): Thenable<T> {
private withLanguagePacks<T>(fn: (languagePacks: { [language: string]: ILanguagePack }) => T | null = () => null): Promise<T> {
return this.languagePacksFileLimiter.queue(() => {
let result: T | null = null;
return pfs.readFile(this.languagePacksFilePath, 'utf8')
.then(null, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err))
.then(undefined, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err))
.then<{ [language: string]: ILanguagePack }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } })
.then(languagePacks => { result = fn(languagePacks); return languagePacks; })
.then(languagePacks => {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
import { Event, buffer } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations';
export class LocalizationsChannel implements IServerChannel {
@@ -12,7 +12,7 @@ export class LocalizationsChannel implements IServerChannel {
onDidLanguagesChange: Event<void>;
constructor(private service: ILocalizationsService) {
this.onDidLanguagesChange = buffer(service.onDidLanguagesChange, true);
this.onDidLanguagesChange = Event.buffer(service.onDidLanguagesChange, true);
}
listen(_, event: string): Event<any> {
@@ -23,7 +23,7 @@ export class LocalizationsChannel implements IServerChannel {
throw new Error(`Event not found: ${event}`);
}
call(_, command: string, arg?: any): Thenable<any> {
call(_, command: string, arg?: any): Promise<any> {
switch (command) {
case 'getLanguageIds': return this.service.getLanguageIds(arg);
}
@@ -40,7 +40,7 @@ export class LocalizationsChannelClient implements ILocalizationsService {
get onDidLanguagesChange(): Event<void> { return this.channel.listen('onDidLanguagesChange'); }
getLanguageIds(type?: LanguageType): Thenable<string[]> {
getLanguageIds(type?: LanguageType): Promise<string[]> {
return this.channel.call('getLanguageIds', type);
}
}

View File

@@ -11,6 +11,10 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
export const ILogService = createServiceDecorator<ILogService>('logService');
function now(): string {
return new Date().toISOString();
}
export enum LogLevel {
Trace,
Debug,
@@ -69,9 +73,9 @@ export class ConsoleLogMainService extends AbstractLogService implements ILogSer
trace(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Trace) {
if (this.useColors) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.log(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
console.log(`[main ${now()}]`, message, ...args);
}
}
}
@@ -79,9 +83,9 @@ export class ConsoleLogMainService extends AbstractLogService implements ILogSer
debug(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Debug) {
if (this.useColors) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.log(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
console.log(`[main ${now()}]`, message, ...args);
}
}
}
@@ -89,9 +93,9 @@ export class ConsoleLogMainService extends AbstractLogService implements ILogSer
info(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Info) {
if (this.useColors) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.log(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
console.log(`[main ${now()}]`, message, ...args);
}
}
}
@@ -99,9 +103,9 @@ export class ConsoleLogMainService extends AbstractLogService implements ILogSer
warn(message: string | Error, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Warning) {
if (this.useColors) {
console.warn(`\x1b[93m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
console.warn(`\x1b[93m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.warn(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
console.warn(`[main ${now()}]`, message, ...args);
}
}
}
@@ -109,9 +113,9 @@ export class ConsoleLogMainService extends AbstractLogService implements ILogSer
error(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Error) {
if (this.useColors) {
console.error(`\x1b[91m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
console.error(`\x1b[91m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.error(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
console.error(`[main ${now()}]`, message, ...args);
}
}
}
@@ -119,9 +123,9 @@ export class ConsoleLogMainService extends AbstractLogService implements ILogSer
critical(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Critical) {
if (this.useColors) {
console.error(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
console.error(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.error(`[main ${new Date().toLocaleTimeString()}]`, message, ...args);
console.error(`[main ${now()}]`, message, ...args);
}
}
}

View File

@@ -5,14 +5,14 @@
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
import { LogLevel, ILogService, DelegatedLogService } from 'vs/platform/log/common/log';
import { Event, buffer } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
export class LogLevelSetterChannel implements IServerChannel {
onDidChangeLogLevel: Event<LogLevel>;
constructor(private service: ILogService) {
this.onDidChangeLogLevel = buffer(service.onDidChangeLogLevel, true);
this.onDidChangeLogLevel = Event.buffer(service.onDidChangeLogLevel, true);
}
listen(_, event: string): Event<any> {
@@ -23,7 +23,7 @@ export class LogLevelSetterChannel implements IServerChannel {
throw new Error(`Event not found: ${event}`);
}
call(_, command: string, arg?: any): Thenable<any> {
call(_, command: string, arg?: any): Promise<any> {
switch (command) {
case 'setLevel': this.service.setLevel(arg);
}

View File

@@ -8,7 +8,7 @@ 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 { Event, Emitter } from 'vs/base/common/event';
import { IMarkerService, IMarkerData, IResourceMarker, IMarker, MarkerStatistics, MarkerSeverity } from './markers';
interface MapMap<V> {
@@ -124,7 +124,7 @@ export class MarkerService implements IMarkerService {
_serviceBrand: any;
private _onMarkerChanged = new Emitter<URI[]>();
private _onMarkerChangedEvent: Event<URI[]> = debounceEvent(this._onMarkerChanged.event, MarkerService._debouncer, 0);
private _onMarkerChangedEvent: Event<URI[]> = Event.debounce(this._onMarkerChanged.event, MarkerService._debouncer, 0);
private _byResource: MapMap<IMarker[]> = Object.create(null);
private _byOwner: MapMap<IMarker[]> = Object.create(null);
private _stats: MarkerStats;
@@ -336,7 +336,7 @@ export class MarkerService implements IMarkerService {
}
private static _accept(marker: IMarker, severities?: number): boolean {
return severities === void 0 || (severities & marker.severity) === marker.severity;
return severities === undefined || (severities & marker.severity) === marker.severity;
}
// --- event debounce logic
@@ -349,7 +349,7 @@ export class MarkerService implements IMarkerService {
last = [];
}
for (const uri of event) {
if (MarkerService._dedupeMap[uri.toString()] === void 0) {
if (MarkerService._dedupeMap[uri.toString()] === undefined) {
MarkerService._dedupeMap[uri.toString()] = true;
last.push(uri);
}

View File

@@ -130,7 +130,7 @@ export namespace IMarkerData {
} else {
result.push(emptyString);
}
if (markerData.severity !== void 0 && markerData.severity !== null) {
if (markerData.severity !== undefined && markerData.severity !== null) {
result.push(MarkerSeverity.toString(markerData.severity));
} else {
result.push(emptyString);
@@ -140,22 +140,22 @@ export namespace IMarkerData {
} else {
result.push(emptyString);
}
if (markerData.startLineNumber !== void 0 && markerData.startLineNumber !== null) {
if (markerData.startLineNumber !== undefined && markerData.startLineNumber !== null) {
result.push(markerData.startLineNumber.toString());
} else {
result.push(emptyString);
}
if (markerData.startColumn !== void 0 && markerData.startColumn !== null) {
if (markerData.startColumn !== undefined && markerData.startColumn !== null) {
result.push(markerData.startColumn.toString());
} else {
result.push(emptyString);
}
if (markerData.endLineNumber !== void 0 && markerData.endLineNumber !== null) {
if (markerData.endLineNumber !== undefined && markerData.endLineNumber !== null) {
result.push(markerData.endLineNumber.toString());
} else {
result.push(emptyString);
}
if (markerData.endColumn !== void 0 && markerData.endColumn !== null) {
if (markerData.endColumn !== undefined && markerData.endColumn !== null) {
result.push(markerData.endColumn.toString());
} else {
result.push(emptyString);

View File

@@ -160,11 +160,11 @@ suite('Marker Service', () => {
let data = randomMarkerData();
let service = new markerService.MarkerService();
data.message = undefined;
data.message = undefined!;
service.changeOne('far', URI.parse('some:uri/path'), [data]);
assert.equal(service.read({ owner: 'far' }).length, 0);
data.message = null;
data.message = null!;
service.changeOne('far', URI.parse('some:uri/path'), [data]);
assert.equal(service.read({ owner: 'far' }).length, 0);

View File

@@ -3,15 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
export const IMenubarService = createDecorator<IMenubarService>('menubarService');
export interface IMenubarService {
_serviceBrand: any;
updateMenubar(windowId: number, menuData: IMenubarData): TPromise<void>;
updateMenubar(windowId: number, menuData: IMenubarData): Promise<void>;
}
export interface IMenubarData {
@@ -25,6 +25,7 @@ export interface IMenubarMenu {
export interface IMenubarKeybinding {
label: string;
userSettingsLabel: string;
isNative?: boolean; // Assumed true if missing
}
@@ -35,6 +36,13 @@ export interface IMenubarMenuItemAction {
enabled?: boolean; // Assumed true if missing
}
export interface IMenubarMenuUriItemAction {
id: string;
label: string;
uri: URI;
enabled?: boolean;
}
export interface IMenubarMenuItemSubmenu {
id: string;
label: string;
@@ -45,7 +53,7 @@ export interface IMenubarMenuItemSeparator {
id: 'vscode.menubar.separator';
}
export type MenubarMenuItem = IMenubarMenuItemAction | IMenubarMenuItemSubmenu | IMenubarMenuItemSeparator;
export type MenubarMenuItem = IMenubarMenuItemAction | IMenubarMenuItemSubmenu | IMenubarMenuItemSeparator | IMenubarMenuUriItemAction;
export function isMenubarMenuItemSubmenu(menuItem: MenubarMenuItem): menuItem is IMenubarMenuItemSubmenu {
return (<IMenubarMenuItemSubmenu>menuItem).submenu !== undefined;
@@ -55,6 +63,10 @@ export function isMenubarMenuItemSeparator(menuItem: MenubarMenuItem): menuItem
return (<IMenubarMenuItemSeparator>menuItem).id === 'vscode.menubar.separator';
}
export function isMenubarMenuItemUriAction(menuItem: MenubarMenuItem): menuItem is IMenubarMenuUriItemAction {
return (<IMenubarMenuUriItemAction>menuItem).uri !== undefined;
}
export function isMenubarMenuItemAction(menuItem: MenubarMenuItem): menuItem is IMenubarMenuItemAction {
return !isMenubarMenuItemSubmenu(menuItem) && !isMenubarMenuItemSeparator(menuItem);
}
return !isMenubarMenuItemSubmenu(menuItem) && !isMenubarMenuItemSeparator(menuItem) && !isMenubarMenuItemUriAction(menuItem);
}

View File

@@ -7,20 +7,18 @@ import * as nls from 'vs/nls';
import { isMacintosh, language } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { app, shell, Menu, MenuItem, BrowserWindow } from 'electron';
import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindingInWindowRequest } from 'vs/platform/windows/common/windows';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
import product from 'vs/platform/node/product';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel } from 'vs/base/common/labels';
import { mnemonicMenuLabel as baseMnemonicLabel } from 'vs/base/common/labels';
import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction, IMenubarMenu } from 'vs/platform/menubar/common/menubar';
import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction, IMenubarMenu, isMenubarMenuItemUriAction } from 'vs/platform/menubar/common/menubar';
import { URI } from 'vs/base/common/uri';
import { ILabelService } from 'vs/platform/label/common/label';
import { IStateService } from 'vs/platform/state/common/state';
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
@@ -31,9 +29,17 @@ interface IMenuItemClickHandler {
inNoWindow: () => void;
}
type IMenuItemInvocation = (
{ type: 'commandId'; commandId: string; }
| { type: 'keybinding'; userSettingsLabel: string; }
);
interface IMenuItemWithKeybinding {
userSettingsLabel?: string;
}
export class Menubar {
private static readonly MAX_MENU_RECENT_ENTRIES = 10;
private static readonly lastKnownMenubarStorageKey = 'lastKnownMenubarData';
private willShutdown: boolean;
@@ -54,16 +60,15 @@ export class Menubar {
private fallbackMenuHandlers: { [id: string]: (menuItem: MenuItem, browserWindow: BrowserWindow, event: Electron.Event) => void } = {};
constructor(
@IUpdateService private updateService: IUpdateService,
@IUpdateService private readonly updateService: IUpdateService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService private configurationService: IConfigurationService,
@IWindowsMainService private windowsMainService: IWindowsMainService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService,
@IHistoryMainService private historyMainService: IHistoryMainService,
@ILabelService private labelService: ILabelService,
@IStateService private stateService: IStateService,
@ILifecycleService private lifecycleService: ILifecycleService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IHistoryMainService private readonly historyMainService: IHistoryMainService,
@IStateService private readonly stateService: IStateService,
@ILifecycleService private readonly lifecycleService: ILifecycleService
) {
this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0);
@@ -88,10 +93,6 @@ export class Menubar {
}
private restoreCachedMenubarData() {
// TODO@sbatten remove this at some point down the road
const outdatedKeys = ['lastKnownAdditionalKeybindings', 'lastKnownKeybindings', 'lastKnownMenubar'];
outdatedKeys.forEach(key => this.stateService.removeItem(key));
const menubarData = this.stateService.getItem<IMenubarData>(Menubar.lastKnownMenubarStorageKey);
if (menubarData) {
if (menubarData.menus) {
@@ -151,22 +152,11 @@ export class Menubar {
}
private registerListeners(): void {
// Keep flag when app quits
this.lifecycleService.onWillShutdown(() => this.willShutdown = true);
// // Listen to some events from window service to update menu
this.historyMainService.onRecentlyOpenedChange(() => this.scheduleUpdateMenu());
this.windowsMainService.onWindowsCountChanged(e => this.onWindowsCountChanged(e));
// this.windowsMainService.onActiveWindowChanged(() => this.updateWorkspaceMenuItems());
// this.windowsMainService.onWindowReady(() => this.updateWorkspaceMenuItems());
// this.windowsMainService.onWindowClose(() => this.updateWorkspaceMenuItems());
// Update when auto save config changes
// this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e));
// Listen to update service
// this.updateService.onStateChange(() => this.updateMenu());
}
private get currentEnableMenuBarMnemonics(): boolean {
@@ -255,7 +245,8 @@ export class Menubar {
let macApplicationMenuItem: Electron.MenuItem;
if (isMacintosh) {
const applicationMenu = new Menu();
macApplicationMenuItem = new MenuItem({ label: product.nameShort, submenu: applicationMenu });
// {{SQL CARBON EDIT}} - Use long name
macApplicationMenuItem = new MenuItem({ label: product.nameLong, submenu: applicationMenu });
this.setMacApplicationMenu(applicationMenu);
menubar.append(macApplicationMenuItem);
}
@@ -284,12 +275,14 @@ export class Menubar {
this.setMenuById(editMenu, 'Edit');
menubar.append(editMenuItem);
// Selection
const selectionMenu = new Menu();
const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu });
// {{SQL CARBON EDIT}} - Disable unused menus
// // Selection
// const selectionMenu = new Menu();
// const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu });
this.setMenuById(selectionMenu, 'Selection');
menubar.append(selectionMenuItem);
// this.setMenuById(selectionMenu, 'Selection');
// menubar.append(selectionMenuItem);
// {{SQL CARBON EDIT}} - End
// View
const viewMenu = new Menu();
@@ -298,26 +291,32 @@ export class Menubar {
this.setMenuById(viewMenu, 'View');
menubar.append(viewMenuItem);
// Go
const gotoMenu = new Menu();
const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
// {{SQL CARBON EDIT}} - Disable unused menus
// // Go
// const gotoMenu = new Menu();
// const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
this.setMenuById(gotoMenu, 'Go');
menubar.append(gotoMenuItem);
// this.setMenuById(gotoMenu, 'Go');
// menubar.append(gotoMenuItem);
// {{SQL CARBON EDIT}} - End
// Debug
const debugMenu = new Menu();
const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")), submenu: debugMenu });
// {{SQL CARBON EDIT}} - Disable unused menus
// // Debug
// const debugMenu = new Menu();
// const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")), submenu: debugMenu });
this.setMenuById(debugMenu, 'Debug');
menubar.append(debugMenuItem);
// this.setMenuById(debugMenu, 'Debug');
// menubar.append(debugMenuItem);
// {{SQL CARBON EDIT}} - End
// Terminal
const terminalMenu = new Menu();
const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")), submenu: terminalMenu });
// {{SQL CARBON EDIT}} - Disable unused menus
// // Terminal
// const terminalMenu = new Menu();
// const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")), submenu: terminalMenu });
this.setMenuById(terminalMenu, 'Terminal');
menubar.append(terminalMenuItem);
// this.setMenuById(terminalMenu, 'Terminal');
// menubar.append(terminalMenuItem);
// {{SQL CARBON EDIT}} - End
// Mac: Window
let macWindowMenuItem: Electron.MenuItem | undefined;
@@ -433,10 +432,10 @@ export class Menubar {
const submenuItem = new MenuItem({ label: this.mnemonicLabel(item.label), submenu: submenu });
this.setMenu(submenu, item.submenu.items);
menu.append(submenuItem);
} else if (isMenubarMenuItemUriAction(item)) {
menu.append(this.createOpenRecentMenuItem(item.uri, item.label, item.id, item.id === 'openRecentFile'));
} else if (isMenubarMenuItemAction(item)) {
if (item.id === 'workbench.action.openRecent') {
this.insertRecentMenuItems(menu);
} else if (item.id === 'workbench.action.showAboutDialog') {
if (item.id === 'workbench.action.showAboutDialog') {
this.insertCheckForUpdatesItems(menu);
}
@@ -472,41 +471,8 @@ export class Menubar {
}
}
private insertRecentMenuItems(menu: Electron.Menu) {
const { workspaces, files } = this.historyMainService.getRecentlyOpened();
// Workspaces
if (workspaces.length > 0) {
for (let i = 0; i < Menubar.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) {
menu.append(this.createOpenRecentMenuItem(workspaces[i], 'openRecentWorkspace', false));
}
menu.append(__separator__());
}
// Files
if (files.length > 0) {
for (let i = 0; i < Menubar.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) {
menu.append(this.createOpenRecentMenuItem(files[i], 'openRecentFile', true));
}
menu.append(__separator__());
}
}
private createOpenRecentMenuItem(workspaceOrFile: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, commandId: string, isFile: boolean): Electron.MenuItem {
let label: string;
let uri: URI;
if (isSingleFolderWorkspaceIdentifier(workspaceOrFile) && !isFile) {
label = unmnemonicLabel(this.labelService.getWorkspaceLabel(workspaceOrFile, { verbose: true }));
uri = workspaceOrFile;
} else if (isWorkspaceIdentifier(workspaceOrFile)) {
label = this.labelService.getWorkspaceLabel(workspaceOrFile, { verbose: true });
uri = URI.file(workspaceOrFile.configPath);
} else {
label = unmnemonicLabel(this.labelService.getUriLabel(workspaceOrFile));
uri = workspaceOrFile;
}
private createOpenRecentMenuItem(uri: URI, label: string, commandId: string, isFile: boolean): Electron.MenuItem {
const revivedUri = URI.revive(uri);
return new MenuItem(this.likeAction(commandId, {
label,
@@ -515,13 +481,13 @@ export class Menubar {
const success = this.windowsMainService.open({
context: OpenContext.MENU,
cli: this.environmentService.args,
urisToOpen: [uri],
urisToOpen: [revivedUri],
forceNewWindow: openInNewWindow,
forceOpenWorkspaceAsFile: isFile
}).length > 0;
if (!success) {
this.historyMainService.removeFromRecentlyOpened([workspaceOrFile]);
this.historyMainService.removeFromRecentlyOpened([revivedUri]);
}
}
}, false));
@@ -621,17 +587,49 @@ export class Menubar {
}
}
private static _menuItemIsTriggeredViaKeybinding(event: Electron.Event, userSettingsLabel: string): boolean {
// The event coming in from Electron will inform us only about the modifier keys pressed.
// The strategy here is to check if the modifier keys match those of the keybinding,
// since it is highly unlikely to use modifier keys when clicking with the mouse
if (!userSettingsLabel) {
// There is no keybinding
return false;
}
let ctrlRequired = /ctrl/.test(userSettingsLabel);
let shiftRequired = /shift/.test(userSettingsLabel);
let altRequired = /alt/.test(userSettingsLabel);
let metaRequired = /cmd/.test(userSettingsLabel) || /super/.test(userSettingsLabel);
if (!ctrlRequired && !shiftRequired && !altRequired && !metaRequired) {
// This keybinding does not use any modifier keys, so we cannot use this heuristic
return false;
}
return (
ctrlRequired === event.ctrlKey
&& shiftRequired === event.shiftKey
&& altRequired === event.altKey
&& metaRequired === event.metaKey
);
}
private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): Electron.MenuItem;
private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem;
private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem {
const label = this.mnemonicLabel(arg1);
const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem, win: Electron.BrowserWindow, event: Electron.Event) => {
const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem & IMenuItemWithKeybinding, win: Electron.BrowserWindow, event: Electron.Event) => {
const userSettingsLabel = menuItem ? menuItem.userSettingsLabel : null;
let commandId = arg2;
if (Array.isArray(arg2)) {
commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking
}
this.runActionInRenderer(commandId);
if (userSettingsLabel && Menubar._menuItemIsTriggeredViaKeybinding(event, userSettingsLabel)) {
this.runActionInRenderer({ type: 'keybinding', userSettingsLabel });
} else {
this.runActionInRenderer({ type: 'commandId', commandId });
}
};
const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0;
const checked = typeof arg4 === 'boolean' ? arg4 : false;
@@ -704,7 +702,7 @@ export class Menubar {
};
}
private runActionInRenderer(id: string): void {
private runActionInRenderer(invocation: IMenuItemInvocation): void {
// We make sure to not run actions when the window has no focus, this helps
// for https://github.com/Microsoft/vscode/issues/25907 and specifically for
// https://github.com/Microsoft/vscode/issues/11928
@@ -719,18 +717,24 @@ export class Menubar {
}
if (activeWindow) {
if (!activeWindow.isReady && isMacintosh && id === 'workbench.action.toggleDevTools' && !this.environmentService.isBuilt) {
// prevent this action from running twice on macOS (https://github.com/Microsoft/vscode/issues/62719)
// we already register a keybinding in bootstrap-window.js for opening developer tools in case something
// goes wrong and that keybinding is only removed when the application has loaded (= window ready).
return;
if (isMacintosh && !this.environmentService.isBuilt && !activeWindow.isReady) {
if ((invocation.type === 'commandId' && invocation.commandId === 'workbench.action.toggleDevTools') || (invocation.type !== 'commandId' && invocation.userSettingsLabel === 'alt+cmd+i')) {
// prevent this action from running twice on macOS (https://github.com/Microsoft/vscode/issues/62719)
// we already register a keybinding in bootstrap-window.js for opening developer tools in case something
// goes wrong and that keybinding is only removed when the application has loaded (= window ready).
return;
}
}
this.windowsMainService.sendToFocused('vscode:runAction', { id, from: 'menu' } as IRunActionInWindowRequest);
if (invocation.type === 'commandId') {
this.windowsMainService.sendToFocused('vscode:runAction', { id: invocation.commandId, from: 'menu' } as IRunActionInWindowRequest);
} else {
this.windowsMainService.sendToFocused('vscode:runKeybinding', { userSettingsLabel: invocation.userSettingsLabel } as IRunKeybindingInWindowRequest);
}
}
}
private withKeybinding(commandId: string | undefined, options: Electron.MenuItemConstructorOptions): Electron.MenuItemConstructorOptions {
private withKeybinding(commandId: string | undefined, options: Electron.MenuItemConstructorOptions & IMenuItemWithKeybinding): Electron.MenuItemConstructorOptions {
const binding = typeof commandId === 'string' ? this.keybindings[commandId] : undefined;
// Apply binding if there is one
@@ -739,6 +743,7 @@ export class Menubar {
// if the binding is native, we can just apply it
if (binding.isNative !== false) {
options.accelerator = binding.label;
options.userSettingsLabel = binding.userSettingsLabel;
}
// the keybinding is not native so we cannot show it as part of the accelerator of
@@ -755,7 +760,7 @@ export class Menubar {
// Unset bindings if there is none
else {
options.accelerator = void 0;
options.accelerator = undefined;
}
return options;

View File

@@ -6,7 +6,6 @@
import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar';
import { Menubar } from 'vs/platform/menubar/electron-main/menubar';
import { ILogService } from 'vs/platform/log/common/log';
import { TPromise } from 'vs/base/common/winjs.base';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class MenubarService implements IMenubarService {
@@ -15,20 +14,20 @@ export class MenubarService implements IMenubarService {
private _menubar: Menubar;
constructor(
@IInstantiationService private instantiationService: IInstantiationService,
@ILogService private logService: ILogService
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ILogService private readonly logService: ILogService
) {
// Install Menu
this._menubar = this.instantiationService.createInstance(Menubar);
}
updateMenubar(windowId: number, menus: IMenubarData): TPromise<void> {
updateMenubar(windowId: number, menus: IMenubarData): Promise<void> {
this.logService.trace('menubarService#updateMenubar', windowId);
if (this._menubar) {
this._menubar.updateMenu(menus, windowId);
}
return TPromise.as(void 0);
return Promise.resolve(undefined);
}
}

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
import { TPromise } from 'vs/base/common/winjs.base';
import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar';
import { Event } from 'vs/base/common/event';
@@ -15,7 +14,7 @@ export class MenubarChannel implements IServerChannel {
throw new Error(`Event not found: ${event}`);
}
call(_, command: string, arg?: any): TPromise<any> {
call(_, command: string, arg?: any): Promise<any> {
switch (command) {
case 'updateMenubar': return this.service.updateMenubar(arg[0], arg[1]);
}
@@ -30,7 +29,7 @@ export class MenubarChannelClient implements IMenubarService {
constructor(private channel: IChannel) { }
updateMenubar(windowId: number, menuData: IMenubarData): TPromise<void> {
updateMenubar(windowId: number, menuData: IMenubarData): Promise<void> {
return this.channel.call('updateMenubar', [windowId, menuData]);
}
}
}

View File

@@ -86,6 +86,7 @@ export interface IProductConfiguration {
};
logUploaderUrl: string;
portable?: string;
uiExtensions?: string[];
}
export interface ISurveyData {

View File

@@ -13,7 +13,7 @@ import { open as _openZip, Entry, ZipFile } from 'yauzl';
import * as yazl from 'yazl';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { once } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
export interface IExtractOptions {
overwrite?: boolean;
@@ -62,7 +62,7 @@ function toExtractError(err: Error): ExtractError {
return err;
}
let type: ExtractErrorType | undefined = void 0;
let type: ExtractErrorType | undefined = undefined;
if (/end of central directory record signature not found/.test(err.message)) {
type = 'CorruptZip';
@@ -81,13 +81,13 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa
let istream: WriteStream;
once(token.onCancellationRequested)(() => {
Event.once(token.onCancellationRequested)(() => {
if (istream) {
istream.close();
istream.destroy();
}
});
return Promise.resolve(mkdirp(targetDirName, void 0, token)).then(() => new Promise((c, e) => {
return Promise.resolve(mkdirp(targetDirName, undefined, token)).then(() => new Promise((c, e) => {
if (token.isCancellationRequested) {
return;
}
@@ -108,7 +108,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, log
let last = createCancelablePromise<void>(() => Promise.resolve());
let extractedEntriesCount = 0;
once(token.onCancellationRequested)(() => {
Event.once(token.onCancellationRequested)(() => {
logService.debug(targetPath, 'Cancelled.');
last.cancel();
zipfile.close();
@@ -151,7 +151,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, log
// directory file names end with '/'
if (/\/$/.test(fileName)) {
const targetFileName = path.join(targetPath, fileName);
last = createCancelablePromise(token => mkdirp(targetFileName, void 0, token).then(() => readNextEntry(token)).then(null, e));
last = createCancelablePromise(token => mkdirp(targetFileName, undefined, token).then(() => readNextEntry(token)).then(undefined, e));
return;
}
@@ -164,8 +164,8 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, log
}
function openZip(zipFile: string, lazy: boolean = false): Promise<ZipFile> {
return nfcall<ZipFile>(_openZip, zipFile, lazy ? { lazyEntries: true } : void 0)
.then(null, err => Promise.reject(toExtractError(err)));
return nfcall<ZipFile>(_openZip, zipFile, lazy ? { lazyEntries: true } : undefined)
.then(undefined, err => Promise.reject(toExtractError(err)));
}
export interface IFile {

View File

@@ -18,10 +18,10 @@ export interface IOpenerService {
* @param resource A resource
* @return A promise that resolves when the opening is done.
*/
open(resource: URI, options?: { openToSide?: boolean }): Promise<any>;
open(resource: URI, options?: { openToSide?: boolean }): Promise<boolean>;
}
export const NullOpenerService: IOpenerService = Object.freeze({
_serviceBrand: undefined,
open() { return Promise.resolve(undefined); }
open() { return Promise.resolve(false); }
});

View File

@@ -15,14 +15,14 @@ export interface IProgressService {
/**
* Show progress customized with the provided flags.
*/
show(infinite: boolean, delay?: number): IProgressRunner;
show(infinite: true, delay?: number): IProgressRunner;
show(total: number, delay?: number): IProgressRunner;
/**
* Indicate progress for the duration of the provided promise. Progress will stop in
* any case of promise completion, error or cancellation.
*/
showWhile(promise: Thenable<any>, delay?: number): Thenable<void>;
showWhile(promise: Promise<any>, delay?: number): Promise<void>;
}
export const enum ProgressLocation {
@@ -52,7 +52,7 @@ export interface IProgressService2 {
_serviceBrand: any;
withProgress<P extends Thenable<R>, R=any>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => P, onDidCancel?: () => void): P;
withProgress<R=any>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: () => void): Promise<R>;
}
export interface IProgressRunner {
@@ -130,7 +130,7 @@ export class LongRunningOperation {
this.currentOperationDisposables.push(
toDisposable(() => clearTimeout(this.currentProgressTimeout)),
toDisposable(() => newOperationToken.cancel()),
toDisposable(() => this.currentProgressRunner ? this.currentProgressRunner.done() : void 0)
toDisposable(() => this.currentProgressRunner ? this.currentProgressRunner.done() : undefined)
);
return {

View File

@@ -26,7 +26,7 @@ export interface IQuickOpenService {
*
* The returned promise completes when quick open is closing.
*/
show(prefix?: string, options?: IShowOptions): Thenable<void>;
show(prefix?: string, options?: IShowOptions): Promise<void>;
/**
* Allows to navigate from the outside in an opened picker.

View File

@@ -114,7 +114,7 @@ export interface IInputOptions {
/**
* an optional function that is used to validate user input.
*/
validateInput?: (input: string) => Thenable<string>;
validateInput?: (input: string) => Promise<string>;
}
export interface IQuickInput {
@@ -230,9 +230,9 @@ export interface IQuickInputService {
/**
* Opens the quick input box for selecting items and returns a promise with the user selected item(s) if any.
*/
pick<T extends IQuickPickItem>(picks: Thenable<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: true }, token?: CancellationToken): Promise<T[]>;
pick<T extends IQuickPickItem>(picks: Thenable<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: false }, token?: CancellationToken): Promise<T>;
pick<T extends IQuickPickItem>(picks: Thenable<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: Omit<IPickOptions<T>, 'canPickMany'>, token?: CancellationToken): Promise<T>;
pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: true }, token?: CancellationToken): Promise<T[]>;
pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: false }, token?: CancellationToken): Promise<T>;
pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: Omit<IPickOptions<T>, 'canPickMany'>, token?: CancellationToken): Promise<T>;
/**
* Opens the quick input box for text input and returns a promise with the user typed value if any.

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