Skip to content

Commit

Permalink
perf - use bulk function to add command and menu items, fixes #96693
Browse files Browse the repository at this point in the history
  • Loading branch information
jrieken committed May 4, 2020
1 parent b19b7a7 commit 2bac477
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 34 deletions.
4 changes: 4 additions & 0 deletions src/vs/base/common/iterator.ts
Expand Up @@ -10,6 +10,10 @@ export namespace Iterable {
return _empty;
}

export function* single<T>(element: T): Iterable<T> {
yield element;
}

export function from<T>(iterable: Iterable<T> | undefined | null): Iterable<T> {
return iterable || _empty;
}
Expand Down
74 changes: 56 additions & 18 deletions src/vs/platform/actions/common/actions.ts
Expand Up @@ -14,6 +14,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { UriDto } from 'vs/base/common/types';
import { Iterable } from 'vs/base/common/iterator';

export interface ILocalizedString {
value: string;
Expand Down Expand Up @@ -153,30 +154,50 @@ export interface IMenuService {

export type ICommandsMap = Map<string, ICommandAction>;

export interface IMenuRegistryChangeEvent {
has(id: MenuId): boolean;
}

export interface IMenuRegistry {
readonly onDidChangeMenu: Event<IMenuRegistryChangeEvent>;
addCommands(newCommands: Iterable<ICommandAction>): IDisposable;
addCommand(userCommand: ICommandAction): IDisposable;
getCommand(id: string): ICommandAction | undefined;
getCommands(): ICommandsMap;
appendMenuItems(items: Iterable<{ id: MenuId, item: IMenuItem | ISubmenuItem }>): IDisposable;
appendMenuItem(menu: MenuId, item: IMenuItem | ISubmenuItem): IDisposable;
getMenuItems(loc: MenuId): Array<IMenuItem | ISubmenuItem>;
readonly onDidChangeMenu: Event<MenuId>;
}

export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {

private readonly _commands = new Map<string, ICommandAction>();
private readonly _menuItems = new Map<MenuId, Array<IMenuItem | ISubmenuItem>>();
private readonly _onDidChangeMenu = new Emitter<MenuId>();
private readonly _onDidChangeMenu = new Emitter<IMenuRegistryChangeEvent>();

readonly onDidChangeMenu: Event<MenuId> = this._onDidChangeMenu.event;
readonly onDidChangeMenu: Event<IMenuRegistryChangeEvent> = this._onDidChangeMenu.event;

addCommand(command: ICommandAction): IDisposable {
this._commands.set(command.id, command);
this._onDidChangeMenu.fire(MenuId.CommandPalette);
return this.addCommands(Iterable.single(command));
}

private readonly _commandPaletteChangeEvent: IMenuRegistryChangeEvent = {
has: id => id === MenuId.CommandPalette
};

addCommands(commands: Iterable<ICommandAction>): IDisposable {
for (const command of commands) {
this._commands.set(command.id, command);
}
this._onDidChangeMenu.fire(this._commandPaletteChangeEvent);
return {
dispose: () => {
if (this._commands.delete(command.id)) {
this._onDidChangeMenu.fire(MenuId.CommandPalette);
let didChange = false;
for (const command of commands) {
didChange = this._commands.delete(command.id) || didChange;
}
if (didChange) {
this._onDidChangeMenu.fire(this._commandPaletteChangeEvent);
}
}
};
Expand All @@ -193,20 +214,37 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry {
}

appendMenuItem(id: MenuId, item: IMenuItem | ISubmenuItem): IDisposable {
let array = this._menuItems.get(id);
if (!array) {
array = [item];
this._menuItems.set(id, array);
} else {
array.push(item);
return this.appendMenuItems(Iterable.single({ id, item }));
}

appendMenuItems(items: Iterable<{ id: MenuId, item: IMenuItem | ISubmenuItem }>): IDisposable {

const changedIds = new Set<MenuId>();

for (const { id, item } of items) {
let array = this._menuItems.get(id);
if (!array) {
array = [item];
this._menuItems.set(id, array);
} else {
array.push(item);
}
changedIds.add(id);
}
this._onDidChangeMenu.fire(id);

this._onDidChangeMenu.fire(changedIds);

return {
dispose: () => {
const idx = array!.indexOf(item);
if (idx >= 0) {
array!.splice(idx, 1);
this._onDidChangeMenu.fire(id);
const changedIds = new Set<MenuId>();
for (const { id, item } of items) {
const array = this._menuItems.get(id);
const idx = array?.indexOf(item) ?? -1;
if (idx >= 0) {
array!.splice(idx, 1);
changedIds.add(id);
}
this._onDidChangeMenu.fire(changedIds);
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/vs/platform/actions/common/menuService.ts
Expand Up @@ -45,7 +45,7 @@ class Menu implements IMenu {
// rebuild this menu whenever the menu registry reports an
// event for this MenuId
this._dispoables.add(Event.debounce(
Event.filter(MenuRegistry.onDidChangeMenu, menuId => menuId === this._id),
Event.filter(MenuRegistry.onDidChangeMenu, set => set.has(this._id)),
() => { },
50
)(this._build, this));
Expand Down
37 changes: 22 additions & 15 deletions src/vs/workbench/api/common/menusExtensionPoint.ts
Expand Up @@ -10,7 +10,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { forEach } from 'vs/base/common/collections';
import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { MenuId, MenuRegistry, ILocalizedString, IMenuItem } from 'vs/platform/actions/common/actions';
import { MenuId, MenuRegistry, ILocalizedString, IMenuItem, ICommandAction } from 'vs/platform/actions/common/actions';
import { URI } from 'vs/base/common/uri';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
Expand Down Expand Up @@ -381,7 +381,7 @@ export const commandsExtensionPoint = ExtensionsRegistry.registerExtensionPoint<

commandsExtensionPoint.setHandler(extensions => {

function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser<any>) {
function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser<any>, bucket: ICommandAction[]) {

if (!schema.isValidCommand(userFriendlyCommand, extension.collector)) {
return;
Expand All @@ -405,29 +405,30 @@ commandsExtensionPoint.setHandler(extensions => {
if (MenuRegistry.getCommand(command)) {
extension.collector.info(localize('dup', "Command `{0}` appears multiple times in the `commands` section.", userFriendlyCommand.command));
}
const registration = MenuRegistry.addCommand({
bucket.push({
id: command,
title,
category,
precondition: ContextKeyExpr.deserialize(enablement),
icon: absoluteIcon
});
_commandRegistrations.add(registration);
}

// remove all previous command registrations
_commandRegistrations.clear();

const newCommands: ICommandAction[] = [];
for (const extension of extensions) {
const { value } = extension;
if (Array.isArray(value)) {
for (const command of value) {
handleCommand(command, extension);
handleCommand(command, extension, newCommands);
}
} else {
handleCommand(value, extension);
handleCommand(value, extension, newCommands);
}
}
_commandRegistrations.add(MenuRegistry.addCommands(newCommands));
});

const _menuRegistrations = new DisposableStore();
Expand All @@ -440,6 +441,8 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM
// remove all previous menu registrations
_menuRegistrations.clear();

const items: { id: MenuId, item: IMenuItem }[] = [];

for (let extension of extensions) {
const { value, collector } = extension;

Expand All @@ -461,7 +464,7 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM

for (let item of entry.value) {
let command = MenuRegistry.getCommand(item.command);
let alt = item.alt && MenuRegistry.getCommand(item.alt);
let alt = item.alt && MenuRegistry.getCommand(item.alt) || undefined;

if (!command) {
collector.error(localize('missing.command', "Menu item references a command `{0}` which is not defined in the 'commands' section.", item.command));
Expand All @@ -486,15 +489,19 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM
}
}

const registration = MenuRegistry.appendMenuItem(menu, {
command,
alt,
group,
order,
when: ContextKeyExpr.deserialize(item.when)
} as IMenuItem);
_menuRegistrations.add(registration);
items.push({
id: menu,
item: {
command,
alt,
group,
order,
when: ContextKeyExpr.deserialize(item.when)
}
});
}
});
}

_menuRegistrations.add(MenuRegistry.appendMenuItems(items));
});

0 comments on commit 2bac477

Please sign in to comment.