Skip to content

Commit

Permalink
Add Editor Actions Left / Center / Right menus and EditorActionBarFac… (
Browse files Browse the repository at this point in the history
  • Loading branch information
softwarenerd authored Dec 18, 2024
1 parent 4ea5c56 commit 6c454a0
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 87 deletions.
5 changes: 5 additions & 0 deletions src/vs/platform/actions/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ export class MenuId {
static readonly EditorContextCopy = new MenuId('EditorContextCopy');
static readonly EditorContextPeek = new MenuId('EditorContextPeek');
static readonly EditorContextShare = new MenuId('EditorContextShare');
// --- Start Positron ---
static readonly EditorActionsLeft = new MenuId('EditorActionsLeft');
static readonly EditorActionsCenter = new MenuId('EditorActionsCenter');
static readonly EditorActionsRight = new MenuId('EditorActionsRight');
// --- End Positron ---
static readonly EditorTitle = new MenuId('EditorTitle');
static readonly EditorTitleRun = new MenuId('EditorTitleRun');
static readonly EditorTitleContext = new MenuId('EditorTitleContext');
Expand Down
260 changes: 173 additions & 87 deletions src/vs/workbench/browser/parts/editor/editorActionBarFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ export class EditorActionBarFactory extends Disposable {
//#region Private Properties

/**
* Gets the menu disposable store.
* Gets the menu disposable stores.
*/
private readonly _menuDisposableStore = this._register(new DisposableStore());
private readonly _menuDisposableStores = new Map<MenuId, DisposableStore>();

/**
* Gets or sets the editor title menu.
* Gets the menus.
*/
private _editorTitleMenu: IMenu;
private readonly _menus = new Map<MenuId, IMenu>();

/**
* Gets the onDidActionsChange event emitter.
Expand All @@ -93,6 +93,20 @@ export class EditorActionBarFactory extends Disposable {

//#endregion Public Events

//#region Private Properties

/**
* Gets the context key service.
*/
private get contextKeyService() {
// If there is an active editor pane, use its scoped context key service, if possible.
// Otherwise, use the editor group's scoped context key service.
return this._editorGroup.activeEditorPane?.scopedContextKeyService ??
this._editorGroup.scopedContextKeyService;
}

//#endregion Private Properties

//#region Constructor

/**
Expand All @@ -112,48 +126,22 @@ export class EditorActionBarFactory extends Disposable {
super();

/**
* Creates the editor title menu.
* @returns The editor title menu.
* Creates the menus.
*/
const createEditorTitleMenu = () => {
// Clear the menu disposable store.
this._menuDisposableStore.clear();

// If there is an active editor pane, use its scoped context key service, if possible.
// Otherwise, use the editor group's scoped context key service.
const contextKeyService = this._editorGroup.activeEditorPane?.scopedContextKeyService ??
this._editorGroup.scopedContextKeyService;

// Create the menu.
const editorTitleMenu = this._menuDisposableStore.add(this._menuService.createMenu(
MenuId.EditorTitle,
contextKeyService,
{
emitEventsForSubmenuChanges: true,
eventDebounceDelay: 0
}
));

// Add the onDidChange event handler.
this._menuDisposableStore.add(editorTitleMenu.onDidChange(() => {
// Create the menu.
this._editorTitleMenu = createEditorTitleMenu();

// Raise the onDidActionsChange event.
this._onDidActionsChangeEmitter.fire();
}));

// Return the menu.
return editorTitleMenu;
const createMenus = () => {
this.createMenu(MenuId.EditorActionsLeft);
this.createMenu(MenuId.EditorActionsCenter);
this.createMenu(MenuId.EditorActionsRight);
this.createMenu(MenuId.EditorTitle);
};

// Create the menu.
this._editorTitleMenu = createEditorTitleMenu();
// Create the menus.
createMenus();

// Add the onDidActiveEditorChange event handler.
this._register(this._editorGroup.onDidActiveEditorChange(() => {
// Create the menu.
this._editorTitleMenu = createEditorTitleMenu();
this._register(this._editorGroup.onDidActiveEditorChange(e => {
// Recreate the menus.
createMenus();

// Raise the onDidActionsChange event.
this._onDidActionsChangeEmitter.fire();
Expand All @@ -162,36 +150,158 @@ export class EditorActionBarFactory extends Disposable {

//#endregion Constructor

//#region Public Properties
//#region Public Methods

/**
* Gets the menu.
* Creates the action bar.
* @param auxiliaryWindow A value which indicates whether the window is an auxiliary window.
* @returns The action bar.
*/
get menu() {
return this._editorTitleMenu;
create(auxiliaryWindow?: boolean) {
// Create the set of processed actions.
const processedActions = new Set<string>();

// Build the left action bar elements from the editor actions left menu.
const leftActionBarElements = this.buildActionBarElements(
processedActions,
MenuId.EditorActionsLeft,
false
);

// Build the center action bar elements from the editor actions center menu.
const centerActionBarElements = this.buildActionBarElements(
processedActions,
MenuId.EditorActionsCenter,
false
);

// Build the right action bar elements from the editor actions right menu and the editor
// title menu.
let rightActionBarElements = [
// Build the right action bar elements from the editor actions right menu.
...this.buildActionBarElements(
processedActions,
MenuId.EditorActionsRight,
false
),
// Build the right action bar elements from the editor title menu.
...this.buildActionBarElements(
processedActions,
MenuId.EditorTitle,
true
)
];

// Splice the move editor to new window command button into the right action bar elements.
if (auxiliaryWindow !== undefined) {
rightActionBarElements.splice(
rightActionBarElements.length - 1,
0,
<ActionBarCommandButton
disabled={auxiliaryWindow}
iconId='positron-open-in-new-window'
tooltip={positronMoveIntoNewWindowTooltip}
ariaLabel={positronMoveIntoNewWindowAriaLabel}
commandId='workbench.action.moveEditorToNewWindow'
/>
);
}

// Return the action bar.
return (
<PositronActionBar
size='small'
borderTop={false}
borderBottom={true}
paddingLeft={PADDING_LEFT}
paddingRight={PADDING_RIGHT}
>
{leftActionBarElements.length > 0 &&
<ActionBarRegion location='left'>
{leftActionBarElements}
</ActionBarRegion>
}
{centerActionBarElements.length > 0 &&
<ActionBarRegion location='center'>
{centerActionBarElements}
</ActionBarRegion>
}
{rightActionBarElements.length > 0 &&
<ActionBarRegion location='right'>
{rightActionBarElements}
</ActionBarRegion>
}
</PositronActionBar>
);
}

//#endregion Public Properties
//#endregion Public Methods

//#region Public Methods
//#region Private Methods

/**
* Creates the action bar.
* @param auxiliaryWindow A value which indicates whether the window is an auxiliary window.
* @returns The action bar.
* Creates a menu.
* @param menuId The menu ID.
*/
create(auxiliaryWindow?: boolean) {
// Break the actions into primary actions, secondary actions, and submenu descriptors.
private createMenu(menuId: MenuId) {
// Dispose the current menu disposable store.
this._menuDisposableStores.get(menuId)?.dispose();

// Add the menu disposable store.
const disposableStore = new DisposableStore();
this._menuDisposableStores.set(menuId, disposableStore);

// Create the menu.
const menu = disposableStore.add(this._menuService.createMenu(
menuId,
this.contextKeyService,
{
emitEventsForSubmenuChanges: true,
eventDebounceDelay: 0
}
));
this._menus.set(menuId, menu);

// Add the onDidChange event handler to the menu.
disposableStore.add(menu.onDidChange(() => {
// Recreate the menu.
this.createMenu(menuId);

// Raise the onDidActionsChange event.
this._onDidActionsChangeEmitter.fire();
}));
}

/**
* Builds action bar elements for a menu.
* @param processedActions The processed actions.
* @param menuId The menu ID.
* @param buildSecondaryActions A value which indicates whether to build secondary actions.
*/
private buildActionBarElements(
processedActions: Set<string>,
menuId: MenuId,
buildSecondaryActions: boolean
) {
// Get the menu.
const menu = this._menus.get(menuId);
if (!menu) {
return [];
}

// Process the menu actions.
const primaryActions: IAction[] = [];
const secondaryActions: IAction[] = [];
const submenuDescriptors = new Set<SubmenuDescriptor>();
const options = {
arg: this._editorGroup.activeEditor?.resource,
shouldForwardArgs: true
} satisfies IMenuActionOptions;
for (const [group, actions] of this._editorTitleMenu.getActions(options)) {
for (const [group, actions] of menu.getActions(options)) {
// Determine the target actions.
const targetActions = this.isPrimaryGroup(group) ? primaryActions : secondaryActions;
const targetActions = !buildSecondaryActions || this.isPrimaryGroup(group) ?
primaryActions :
secondaryActions;

// Push a separator between groups.
if (targetActions.length > 0) {
Expand All @@ -217,7 +327,9 @@ export class EditorActionBarFactory extends Disposable {
// Inline submenus, where possible.
for (const { group, action, index } of submenuDescriptors) {
// Set the target.
const target = this.isPrimaryGroup(group) ? primaryActions : secondaryActions;
const target = !buildSecondaryActions || this.isPrimaryGroup(group) ?
primaryActions :
secondaryActions;

// Inline the submenu, if possible.
if (this.shouldInlineSubmenuAction(group, action)) {
Expand All @@ -234,7 +346,10 @@ export class EditorActionBarFactory extends Disposable {
elements.push(<ActionBarSeparator />);
} else if (action instanceof MenuItemAction) {
// Menu item action.
elements.push(<ActionBarActionButton action={action} />);
if (!processedActions.has(action.id)) {
processedActions.add(action.id);
elements.push(<ActionBarActionButton action={action} />);
}
} else if (action instanceof SubmenuAction) {
// Submenu action. Get the first action.
const firstAction = action.actions[0];
Expand Down Expand Up @@ -273,19 +388,6 @@ export class EditorActionBarFactory extends Disposable {
}
}

// If we know whether we're in an auxiliary window, add the move into new window button.
if (auxiliaryWindow !== undefined) {
elements.push(
<ActionBarCommandButton
disabled={auxiliaryWindow}
iconId='positron-open-in-new-window'
tooltip={positronMoveIntoNewWindowTooltip}
ariaLabel={positronMoveIntoNewWindowAriaLabel}
commandId='workbench.action.moveEditorToNewWindow'
/>
);
}

// If there are secondary actions, add the more actions button. Note that the normal
// dropdown arrow is hidden on this button because it uses the ··· icon.
if (secondaryActions.length) {
Expand All @@ -301,26 +403,10 @@ export class EditorActionBarFactory extends Disposable {
);
}

// Return the elements.
return (
<PositronActionBar
size='small'
borderTop={false}
borderBottom={true}
paddingLeft={PADDING_LEFT}
paddingRight={PADDING_RIGHT}
>
<ActionBarRegion location='right'>
{elements}
</ActionBarRegion>
</PositronActionBar>
);
// Return the action bar elements.
return elements;
}

//#endregion Public Methods

//#region Private Methods

/**
* Determines whether a group is the primary group.
* @param group The group.
Expand Down
17 changes: 17 additions & 0 deletions src/vs/workbench/services/actions/common/menusExtensionPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,23 @@ const apiMenus: IAPIMenu[] = [
description: localize('menus.touchBar', "The touch bar (macOS only)"),
supportsSubmenus: false
},
// --- Start Positron ---
{
key: 'editor/actions/left',
id: MenuId.EditorActionsLeft,
description: localize('menus.editorActionsLeft', "The editor actions left menu")
},
{
key: 'editor/actions/center',
id: MenuId.EditorActionsCenter,
description: localize('menus.editorActionsCenter', "The editor actions center menu")
},
{
key: 'editor/actions/right',
id: MenuId.EditorActionsRight,
description: localize('menus.editorActionsRight', "The editor actions right menu")
},
// --- End Positron ---
{
key: 'editor/title',
id: MenuId.EditorTitle,
Expand Down

0 comments on commit 6c454a0

Please sign in to comment.