Skip to content

Commit

Permalink
Merge pull request #161465 from microsoft/joh/mammoth-cat
Browse files Browse the repository at this point in the history
joh/mammoth cat
  • Loading branch information
jrieken committed Sep 22, 2022
2 parents 8d88cee + ba19120 commit 0515462
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 19 deletions.
9 changes: 8 additions & 1 deletion src/vs/platform/actions/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,15 @@ export interface IMenuActionOptions {
renderShortTitle?: boolean;
}

export interface IMenuChangeEvent {
readonly menu: IMenu;
readonly isStructuralChange: boolean;
readonly isToggleChange: boolean;
readonly isEnablementChange: boolean;
}

export interface IMenu extends IDisposable {
readonly onDidChange: Event<IMenu>;
readonly onDidChange: Event<IMenuChangeEvent>;
getActions(options?: IMenuActionOptions): [string, Array<MenuItemAction | SubmenuItemAction>][];
}

Expand Down
64 changes: 46 additions & 18 deletions src/vs/platform/actions/common/menuService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/

import { RunOnceScheduler } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { DebounceEmitter, Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IMenu, IMenuActionOptions, IMenuCreateOptions, IMenuItem, IMenuItemHide, IMenuService, isIMenuItem, isISubmenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { IMenu, IMenuActionOptions, IMenuChangeEvent, IMenuCreateOptions, IMenuItem, IMenuItemHide, IMenuService, isIMenuItem, isISubmenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { ICommandAction, ILocalizedString } from 'vs/platform/action/common/action';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
Expand Down Expand Up @@ -139,11 +139,13 @@ class Menu implements IMenu {

private readonly _disposables = new DisposableStore();

private readonly _onDidChange: Emitter<IMenu>;
readonly onDidChange: Event<IMenu>;
private readonly _onDidChange: Emitter<IMenuChangeEvent>;
readonly onDidChange: Event<IMenuChangeEvent>;

private _menuGroups: MenuItemGroup[] = [];
private _contextKeys: Set<string> = new Set();
private _structureContextKeys: Set<string> = new Set();
private _preconditionContextKeys: Set<string> = new Set();
private _toggledContextKeys: Set<string> = new Set();

constructor(
private readonly _id: MenuId,
Expand All @@ -160,7 +162,7 @@ class Menu implements IMenu {
// structure of the menu
const rebuildMenuSoon = new RunOnceScheduler(() => {
this._build();
this._onDidChange.fire(this);
this._onDidChange.fire({ menu: this, isStructuralChange: true, isEnablementChange: true, isToggleChange: true });
}, _options.eventDebounceDelay);
this._disposables.add(rebuildMenuSoon);
this._disposables.add(MenuRegistry.onDidChangeMenu(e => {
Expand All @@ -173,23 +175,47 @@ class Menu implements IMenu {
// we only do that when someone listens on this menu because (1) these events are
// firing often and (2) menu are often leaked
const lazyListener = this._disposables.add(new DisposableStore());

const merge = (events: IMenuChangeEvent[]): IMenuChangeEvent => {

let isStructuralChange = false;
let isEnablementChange = false;
let isToggleChange = false;

for (const item of events) {
isStructuralChange = isStructuralChange || item.isStructuralChange;
isEnablementChange = isEnablementChange || item.isEnablementChange;
isToggleChange = isToggleChange || item.isToggleChange;
if (isStructuralChange && isEnablementChange && isToggleChange) {
// everything is TRUE, no need to continue iterating
break;
}
}

return { menu: this, isStructuralChange, isEnablementChange, isToggleChange };
};

const startLazyListener = () => {
const fireChangeSoon = new RunOnceScheduler(() => this._onDidChange.fire(this), _options.eventDebounceDelay);
lazyListener.add(fireChangeSoon);

lazyListener.add(_contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(this._contextKeys)) {
fireChangeSoon.schedule();
const isStructuralChange = e.affectsSome(this._structureContextKeys);
const isEnablementChange = e.affectsSome(this._preconditionContextKeys);
const isToggleChange = e.affectsSome(this._toggledContextKeys);
if (isStructuralChange || isEnablementChange || isToggleChange) {
this._onDidChange.fire({ menu: this, isStructuralChange, isEnablementChange, isToggleChange });
}
}));
lazyListener.add(_hiddenStates.onDidChange(() => {
fireChangeSoon.schedule();
lazyListener.add(_hiddenStates.onDidChange(e => {
this._onDidChange.fire({ menu: this, isStructuralChange: true, isEnablementChange: false, isToggleChange: false });
}));
};

this._onDidChange = new Emitter({
this._onDidChange = new DebounceEmitter({
// start/stop context key listener
onFirstListenerAdd: startLazyListener,
onLastListenerRemove: lazyListener.clear.bind(lazyListener)
onLastListenerRemove: lazyListener.clear.bind(lazyListener),
delay: _options.eventDebounceDelay,
merge
});
this.onDidChange = this._onDidChange.event;

Expand All @@ -204,7 +230,9 @@ class Menu implements IMenu {

// reset
this._menuGroups.length = 0;
this._contextKeys.clear();
this._structureContextKeys.clear();
this._preconditionContextKeys.clear();
this._toggledContextKeys.clear();

const menuItems = MenuRegistry.getMenuItems(this._id);

Expand All @@ -227,17 +255,17 @@ class Menu implements IMenu {

private _collectContextKeys(item: IMenuItem | ISubmenuItem): void {

Menu._fillInKbExprKeys(item.when, this._contextKeys);
Menu._fillInKbExprKeys(item.when, this._structureContextKeys);

if (isIMenuItem(item)) {
// keep precondition keys for event if applicable
if (item.command.precondition) {
Menu._fillInKbExprKeys(item.command.precondition, this._contextKeys);
Menu._fillInKbExprKeys(item.command.precondition, this._preconditionContextKeys);
}
// keep toggled keys for event if applicable
if (item.command.toggled) {
const toggledExpression: ContextKeyExpression = (item.command.toggled as { condition: ContextKeyExpression }).condition || item.command.toggled;
Menu._fillInKbExprKeys(toggledExpression, this._contextKeys);
Menu._fillInKbExprKeys(toggledExpression, this._toggledContextKeys);
}

} else if (this._options.emitEventsForSubmenuChanges) {
Expand Down

0 comments on commit 0515462

Please sign in to comment.