Custom Menu

Register Custom Menus

There are two common modes for registering custom menus:

  • Register a new menu item
  • Registers a submenu with existing menu items

OpenSumi provides the ability to customize menus based on OpenSumi Contributionmechanism, to implement MenuContribution and call the methods provided by menuRegistry.

interface MenuContribution {
  registerMenus?(menus: IMenuRegistry): void;
}

interface IMenuRegistry {
  // Register a new menu item
  registerMenubarItem(
    menuId: string,
    item: PartialBy<IMenubarItem, 'id'>
  ): IDisposable;
  // Register a submenu with existing menu items
  registerMenuItem(
    menuId: MenuId | string,
    item: IMenuItem | ISubmenuItem | IInternalComponentMenuItem
  ): IDisposable;
}

Register a New Menu Item

For example, if we want to register a new terminal menu item and hope it to be displayed in the first item, we call registry.registerMenuBarItem and pass order: 0 to indicate that it is positioned in the first item.

import {
  MenuContribution,
  IMenuRegistry,
  MenuId
} from '@opensumi/ide-core-browser/lib/menu/next';

const terminalMenuBarId = 'menubar/terminal';

@Domain(MenuContribution)
class MyMenusContribution implements MenuContribution {
  registerMenus(registry: IMenuRegistry) {
    registry.registerMenubarItem(terminalMenuBarId, {
      label: 'terminal',
      order: 0
    });
  }
}

Menu

Register Submenus Under Existing Menu Items

We register the terminal menu item as the first item in the menubar, but it doesn't have a submenu, and will not respond when you click it. We need to register a set of submenu for it. Call registerMenuItem of registry to register a single menu item, or you can use the registerMenuItems method to register multiple submenu items in bulk. The menu needs to perform some action after click. In this case we want to split the terminal after click. We need to bind a Command for it. Command can also be customized by implementing CommandContribution, where we use the built-in terminal.split command.

Note that by default the label of registered menubar items will not take effect, if the bound Command also specifies label property during registration.

registerMenus(registry: IMenuRegistry) {
  registry.registerMenubarItem(terminalMenuBarId, { label: '终端', order: 0 });

  registry.registerMenuItem(terminalMenuBarId, {
    command: 'terminal.split',
    group: '1_split',
  });
}

Split Terminal

When there are plenty of registered menus, we may hope to space out some submenus with similar actions from other menus. We can use the group property to group the submenus. Specifically, you can use the same group value for these similar actions menus. Here we use registry.registerMenuItems to register more submenus.

registerMenus(registry: IMenuRegistry) {
  registry.registerMenubarItem(terminalMenuBarId, { label: 'terminal', order: 0 });

  registry.registerMenuItems(terminalMenuBarId, [
    {
      command: 'terminal.split',
      group: '1_split',
    },
    {
      command: 'terminal.remove',
      group: '2_clear',
    },
    {
      command: 'terminal.clear',
      group: '2_clear',
    },
    {
      command: 'terminal.search',
      group: '3_search',
    },
    {
      command: 'terminal.search.next',
      group: '3_search',
    },
  ]);
}

More MenuItems

Register the Secondary Submenu

For the same type of menu, apart from using group to group them, you can also register them as secondary submenu. When there are a good deal of submenus, you can use secondary menu can effectively improve the user experience. For example, we want to register both search and search next match as a secondary menu of search.

const searchSubMenuId = 'terminal/search';

registerMenus(registry: IMenuRegistry) {
  registry.registerMenubarItem(terminalMenuBarId, { label: 'terminal', order: 0 });

  registry.registerMenuItems(terminalMenuBarId, [
    {
      command: 'terminal.split',
      group: '1_split',
    },
    {
      command: 'terminal.remove',
      group: '2_clear',
    },
    {
      command: 'terminal.clear',
      group: '2_clear',
    },
    {
      label: 'search',
      group: '3_search',
      submenu: searchSubMenuId,
    },
  ]);

  registry.registerMenuItems(searchSubMenuId, [
    {
      command: 'terminal.search',
    },
    {
      command: 'terminal.search.next',
    },
  ]);

}

submenu

Unregistering Menus or Menu Items

OpenSumi also provides the functionality of unregistering menus or menu items. If you do not need certain buttons on the interface, you can unregister them.

core internally registers some menus in advance, such as Help, and also preconfigures menu items for these menus, such as Help > Toggle Developer Tools.

We provide two methods in IMenuRegistry: unregisterMenuId and unregisterMenuItem. The former is used to delete a menu, while the latter is used to delete a menu item of a menu.

export abstract class IMenuRegistry {
  ...
  abstract unregisterMenuItem(menuId: MenuId | string, menuItemId: string): void;
  abstract unregisterMenuId(menuId: string): IDisposable;
}

For example, if we want to delete the Help menu item, we can use:

import {
  MenuContribution,
  IMenuRegistry,
  MenuId
} from '@opensumi/ide-core-browser/lib/menu/next';

const terminalMenuBarId = 'menubar/terminal';

@Domain(MenuContribution)
class MyMenusContribution implements MenuContribution {
  registerMenus(registry: IMenuRegistry) {
    registry.unregisterMenuId(MenuId.MenubarHelpMenu);
  }
}

If we want to delete the Toggle Developer Tools functionality from the Help menu, we need to first find the ID of this menu item, which is usually the ID of the command that the menu is supposed to execute:

import {
  MenuContribution,
  IMenuRegistry,
  MenuId
} from '@opensumi/ide-core-browser/lib/menu/next';

const terminalMenuBarId = 'menubar/terminal';

@Domain(MenuContribution)
class MyMenusContribution implements MenuContribution {
  registerMenus(registry: IMenuRegistry) {
    registry.unregisterMenuItem(
      MenuId.MenubarHelpMenu,
      'electron.toggleDevTools'
    );
  }
}

You can search for the menus that are registered internally in core by using the keyword registerMenuItem. For example, the menu items registered in Electron Basic can be found here: packages/electron-basic/src/browser/index.ts#L159

Using Icon Menus

In addition to custom menus, you can also choose to use icon menus, which display menu items in the form of icon icons.

submenu

Usage

To customize the rendering of the toolbar, you first need to use custom view,see Custom Slot

Then import the component.
import { IconMenuBar } from '@opensumi/ide-menu-bar/lib/browser/menu-bar.view';

/**
 * Custom menu bar component.
 * Add a logo in here, and keep
 * opensumi's original menubar.
 */
export const MenuBarView = () => (
  <div>
    <IconMenuBar />
  </div>
);

Then call the registerMenuItems method provided by menuRegistry in MenuContribution.

Register menu items on the MenuId.IconMenubarContext context.

registerMenus(registry: IMenuRegistry) {
  menus.registerMenuItems(MenuId.IconMenubarContext, [
    {
      command: EDITOR_COMMANDS.REDO.id,
      iconClass: getIcon('up'),
      group: '1_icon_menubar',
    },
    {
      command: EDITOR_COMMANDS.UNDO.id,
      iconClass: getIcon('down'),
      group: '2_icon_menubar',
    },
  ])
}

Note: iconClass is required, otherwise icons cannot be displayed.

The group field will be automatically grouped for you, and separated by a separator | between different groups.