Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

joh/mammoth cat #161465

Merged
merged 2 commits into from
Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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