mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-14 03:58:33 -05:00
Merge VS Code 1.31.1 (#4283)
This commit is contained in:
@@ -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()}")`);
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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()]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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, []);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 }');
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ suite('Keytar', () => {
|
||||
try {
|
||||
await keytar.deletePassword(name, 'foo');
|
||||
} finally {
|
||||
// tslint:disable-next-line: no-unsafe-finally
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
60
src/vs/platform/environment/node/argvHelper.ts
Normal file
60
src/vs/platform/environment/node/argvHelper.ts
Normal 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));
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
40
src/vs/platform/extensions/node/extensionsUtil.ts
Normal file
40
src/vs/platform/extensions/node/extensionsUtil.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -23,5 +23,5 @@ export interface IntegrityTestResult {
|
||||
export interface IIntegrityService {
|
||||
_serviceBrand: any;
|
||||
|
||||
isPure(): Thenable<IntegrityTestResult>;
|
||||
isPure(): Promise<IntegrityTestResult>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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))}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ export interface IProductConfiguration {
|
||||
};
|
||||
logUploaderUrl: string;
|
||||
portable?: string;
|
||||
uiExtensions?: string[];
|
||||
}
|
||||
|
||||
export interface ISurveyData {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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); }
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user