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

Refactor menu nodes #14676

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

tsmaeder
Copy link
Contributor

@tsmaeder tsmaeder commented Dec 27, 2024

What it does

Fixes #14217

Makes menu nodes active object that can decide on visibility, enablement, etc. themselves. This should simplify handling of menus and toolbar items because a lot of special-case code can be moved to polymorphous implementations instead of being handled at each client site. This also allows the special handling of parameter transformations for plugin-contributed actions (toolbar/menu) to be confined to the plugin engine implementation.

How to test

Test that menu functionality still works, in particular actions contributed from plugins. That includes menus and toolbars everywhere for browser and electron cases.

Follow-ups

One strange thing is with the menu/toolbar support is that the MenuRegistry and associated classes are located in the "common" part of the core packages. If we decide that this is no longer that case, we could move the code to "browser". However, this would introduce another huge batch of changes and therefore has been left out of this PR.
There are also a couple of renamings that would make the naming more consistent and logical that have been left out of the PR to keep the size of the PR manageable.

Breaking changes

This PR introduces breaking changes to be documented after a first round of reviews.

  • This PR introduces breaking changes and requires careful review. If yes, the breaking changes section in the changelog has been updated.

Attribution

Contributed on behalf of STMicroelectronics

Review checklist

Reminder for reviewers

Fixes eclipse-theia#14217

Makes menu nodes active object that can decide on visibility,
enablement, etc. themselves.

Contributed on behalf of STMicroelectronics

Signed-off-by: Thomas Mäder <[email protected]>
@tsmaeder tsmaeder requested review from dhuebner and msujew December 27, 2024 15:42
@dhuebner
Copy link
Member

dhuebner commented Jan 3, 2025

@tsmaeder

  • Linting and Playwright-tests are failing. I will fix the tests and add a commit.
  • Notebook cell actions are broken (I can have a look)
  • Also CompoundMenuNodeRole.flat menu functionality is not working (see screenshot below). What is the alternative?
  • It would be nice to update the in-code documentation and also comments because of changed signature or behavior. Especially public API like changes in menu-model-registry.ts should be updated.
Bildschirmfoto 2025-01-03 um 11 57 25

@tsmaeder
Copy link
Contributor Author

tsmaeder commented Jan 3, 2025

@dhuebner What are you trying to achieve through CompoundMenuNodeRole.flat?

if (!ReactTabBarToolbarItem.is(item)) {
toolbarItemClassNames = TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM;
if (this.evaluateWhenClause(item.when)) {
toolbarItemClassNames += ' enabled';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without adding 'enabled', toolbar items are rendered as disabled

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the responsibility of the toolbar item.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we are missing some implementation, cause the test is failing because workbench.action.showCommands toolbar item is not enabled.

@tsmaeder
Copy link
Contributor Author

tsmaeder commented Jan 3, 2025

@dhuebner rigth now, I'm mostly looking for functional and architectural feedback. I can fix the tests once the dust settles.

@dhuebner
Copy link
Member

dhuebner commented Jan 3, 2025

@tsmaeder

What are you trying to achieve through CompoundMenuNodeRole.flat?

The previous effect was that if you click on three dots toolbar item in notebook cell the children of the more sub-menu were rendered directly (see code before). Now an empty composite menu is rendered and the children only if you click on it.

@dhuebner
Copy link
Member

dhuebner commented Jan 3, 2025

@tsmaeder
I've pushed fixes for Notebook and Preferences tests. The toolbar test is still failing because of missing enabled css class, see comments above.
Overall it seems to be a huge impact change that might break adopters.

Copy link
Member

@msujew msujew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks generally really good. I believe we can improve on the API. Feel free to request my review again once the tests are fixed and I'll do a more in-depth code review.


export type CompoundMenuNode = MenuNode & {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is there a specific reason to prefer type intersections to interfaces? I find interfaces a bit better to read, so I'd prefer them here.

Comment on lines +107 to +113
protected override doRender(menuPath: MenuPath, menu: CompoundMenuNode,
anchor: Anchor,
contextMatcher: ContextMatcher,
args?: any,
context?: HTMLElement,
onHide?: () => void
): ContextMenuAccess {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why not continue using a Options object in here? Using a list of arguments make this more difficult to override and more prone to breakage.

if (!groupNode) {
groupNode = new CompositeMenuNode(menuId, label, options, parent);
disposable = this.changeEventOnDispose(parent.addNode(groupNode));
registerSubmenu(menuPath: MenuPath, label: string, sortString?: string, icon?: string, when?: string, contextKeyOverlay?: Record<string, string>): Disposable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: When refactoring the arguments of the method anyway, why not make it into one single object?

  1. I feel like most JS/TS APIs are moving/have moved to objects, especially if there are optional parameters to a function.
  2. It feels more readable to me, since you no longer need to rely on explicit ordering of arguments.
  3. It preserves better backwards compatibility, as adding a new field to an object is easier than adding a new parameter to a function. At least in my opinion, JS/TS itself doesn't care too much about that during the runtime/compiletime.

registerIndependentSubmenu(id: string, label: string, options?: SubMenuOptions): Disposable {
if (this.independentSubmenus.has(id)) {
console.debug(`Independent submenu with path ${id} registered, but given ID already exists.`);
linkCompoundMenuNode(newParentPath: MenuPath, submenuPath: MenuPath, order?: string, when?: string): Disposable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: We should use objects, see reasoning above.

let nonEmptyNode = undefined;
static removeSingleRootNode(fullMenuModel: CompoundMenuNode): CompoundMenuNode {

let singleChild = undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Implicitly typed as any

Suggested change
let singleChild = undefined;
let singleChild: CompoundMenuNode | undefined = undefined;

*/
iconClass?: string;
}
export interface CommandMenu extends MenuNode, RenderedMenuNode, Action {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: On the other hand, if an interface is completely empty, I'd prefer an intersection type, but that's really only personal preference.

@@ -86,7 +92,7 @@ export class ElectronContextMenuRenderer extends BrowserContextMenuRenderer {
protected useNativeStyle: boolean = true;

constructor(@inject(ElectronMainMenuFactory) private electronMenuFactory: ElectronMainMenuFactory) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: If we no longer need the factory in the constructor, I'd rather have it as a protected field injector. That makes it much easier to override from an adopter perspective.

@@ -71,11 +66,28 @@ export type ElectronMenuItemRole = ('undo' | 'redo' | 'cut' | 'copy' | 'paste' |
'selectNextTab' | 'selectPreviousTab' | 'mergeAllWindows' | 'clearRecentDocuments' |
'moveTabToNewWindow' | 'windowMenu');

function traverseMenuDto(items: MenuDto[], callback: (item: MenuDto) => void): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: WDYT about making these protected methods of the class that uses these functions? Having those as local functions, makes overriding for adopters difficult.

Comment on lines +278 to +284
menus.registerSubmenu([...PLUGIN_TEST_VIEW_TITLE_MENU, TestViewCommands.RUN_ALL_TESTS.id], '', undefined, undefined, undefined, {
'testing.profile.context.group': 'run'
});

menus.registerSubmenu([...PLUGIN_TEST_VIEW_TITLE_MENU, TestViewCommands.DEBUG_ALL_TESTS.id], '', undefined, undefined, undefined, {
'testing.profile.context.group': 'debug'
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having undefined, undefined, undefined here is a good point to support my argument in favor of objects :)

Signed-off-by: Thomas Mäder <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Waiting on reviewers
Development

Successfully merging this pull request may close these issues.

Refactor MenuNode to get rid of MenuExecutor, MenuCommandAdapterRegistry and MenuCommandExecutor
3 participants