From ebd0e02e30b3596cab0ebede54b40a532e2c6d4a Mon Sep 17 00:00:00 2001 From: Brian Lambert Date: Wed, 15 Jan 2025 13:37:39 -0700 Subject: [PATCH 1/7] Move Data Explorer action bar actions to Editor Actions and the Editor Action Bar (#6000) --- src/vs/editor/common/config/editorOptions.ts | 6 +- src/vs/platform/action/common/action.ts | 8 + src/vs/platform/actions/common/actions.ts | 12 + .../components/actionBarActionButton.tsx | 5 + .../browser/components/actionBarButton.css | 4 + .../parts/editor/editorActionBarControl.tsx | 8 +- .../parts/editor/editorActionBarFactory.tsx | 24 +- .../dataExplorerPanel/dataExplorerPanel.css | 1 - .../positronDataExplorer.css | 2 - .../positronDataExplorer.tsx | 6 +- .../classes/dataGridInstance.tsx | 52 ++- src/vs/workbench/browser/workbench.ts | 26 ++ .../browser/codeEditor.contribution.ts | 1 + .../codeEditor/browser/toggleActionBar.ts | 13 +- .../browser/positronDataExplorerActions.ts | 324 +++++++++++++++++- .../positronDataExplorerContextKeys.ts | 28 ++ .../browser/positronDataExplorerEditor.tsx | 45 ++- .../preferences/browser/settingsLayout.ts | 14 +- .../positronDataExplorerInstance.ts | 18 +- .../browser/positronDataExplorerInstance.ts | 32 ++ .../action-bar/editor-action-bar.test.ts | 11 +- .../data-explorer-python-polars.test.ts | 2 +- 22 files changed, 562 insertions(+), 80 deletions(-) create mode 100644 src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerContextKeys.ts diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 8a429df7d93..b8203b1a6ae 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -3149,8 +3149,10 @@ class EditorActionBar extends BaseEditorOption; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index d43218b0dea..009926ec1ec 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -513,6 +513,14 @@ export class MenuItemAction implements IAction { readonly enabled: boolean; readonly checked?: boolean; + // --- Start Positron --- + /** + * Gets a value which indicates whether to display the title for the action when it appears on + * an action bar. + */ + readonly displayTitleOnActionBar?: boolean; + // --- End Positron --- + constructor( item: ICommandAction, alt: ICommandAction | undefined, @@ -528,6 +536,10 @@ export class MenuItemAction implements IAction { this.enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition); this.checked = undefined; + // --- Start Positron --- + this.displayTitleOnActionBar = item.displayTitleOnActionBar; + // --- End Positron --- + let icon: ThemeIcon | undefined; if (item.toggled) { diff --git a/src/vs/platform/positronActionBar/browser/components/actionBarActionButton.tsx b/src/vs/platform/positronActionBar/browser/components/actionBarActionButton.tsx index a8826f0c512..ceb7ebfdaf7 100644 --- a/src/vs/platform/positronActionBar/browser/components/actionBarActionButton.tsx +++ b/src/vs/platform/positronActionBar/browser/components/actionBarActionButton.tsx @@ -143,6 +143,11 @@ export const ActionBarActionButton = (props: ActionBarActionButtonProps) => { action, !useAlternativeAction ), + text: action instanceof MenuItemAction ? + action.displayTitleOnActionBar ? + action.label : + undefined : + undefined, disabled: !action.enabled, onMouseEnter: () => setMouseInside(true), onMouseLeave: () => setMouseInside(false), diff --git a/src/vs/platform/positronActionBar/browser/components/actionBarButton.css b/src/vs/platform/positronActionBar/browser/components/actionBarButton.css index 44a8c5554a3..6a06b316d07 100644 --- a/src/vs/platform/positronActionBar/browser/components/actionBarButton.css +++ b/src/vs/platform/positronActionBar/browser/components/actionBarButton.css @@ -63,6 +63,10 @@ text-overflow: ellipsis; } +.disabled .action-bar-button-text { + color: var(--positronActionBar-disabledForeground); +} + .action-bar-button-icon { padding: 0 6px; align-items: center; diff --git a/src/vs/workbench/browser/parts/editor/editorActionBarControl.tsx b/src/vs/workbench/browser/parts/editor/editorActionBarControl.tsx index 042d1ba2e77..32a552ee3c1 100644 --- a/src/vs/workbench/browser/parts/editor/editorActionBarControl.tsx +++ b/src/vs/workbench/browser/parts/editor/editorActionBarControl.tsx @@ -31,7 +31,7 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common * Constants. */ const EDITOR_ACTION_BAR_HEIGHT = 32; -const CONFIGURATION_SETTING = 'editor.actionBar.enabled'; +export const EDITOR_ACTION_BAR_CONFIGURATION_SETTING = 'editor.actionBar.enabled'; /** * EditorActionBarControl class. @@ -212,7 +212,7 @@ export class EditorActionBarControlFactory { @IInstantiationService private readonly _instantiationService: IInstantiationService ) { // Check if the configuration setting is enabled. If so, create the control. - if (this._configurationService.getValue(CONFIGURATION_SETTING)) { + if (this._configurationService.getValue(EDITOR_ACTION_BAR_CONFIGURATION_SETTING)) { this.createControl(); } @@ -227,9 +227,9 @@ export class EditorActionBarControlFactory { // configuration setting. this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { // Check if the configuration setting has changed. - if (e.affectsConfiguration(CONFIGURATION_SETTING)) { + if (e.affectsConfiguration(EDITOR_ACTION_BAR_CONFIGURATION_SETTING)) { // Process the change. - if (this._configurationService.getValue(CONFIGURATION_SETTING)) { + if (this._configurationService.getValue(EDITOR_ACTION_BAR_CONFIGURATION_SETTING)) { // Create the control, if it doesn't exist. if (!this._control) { this.createControl(); diff --git a/src/vs/workbench/browser/parts/editor/editorActionBarFactory.tsx b/src/vs/workbench/browser/parts/editor/editorActionBarFactory.tsx index 2d9fc90a0fc..f46712b1802 100644 --- a/src/vs/workbench/browser/parts/editor/editorActionBarFactory.tsx +++ b/src/vs/workbench/browser/parts/editor/editorActionBarFactory.tsx @@ -194,19 +194,17 @@ export class EditorActionBarFactory extends Disposable { ]; // Splice the move editor to new window command button into the right action bar elements. - if (auxiliaryWindow !== undefined) { - rightActionBarElements.splice( - rightActionBarElements.length - 1, - 0, - - ); - } + rightActionBarElements.splice( + rightActionBarElements.length - 1, + 0, + + ); // Return the action bar. return ( diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.css b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.css index 472b859df80..a8a74731469 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.css +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.css @@ -9,6 +9,5 @@ min-width: 0; min-height: 0; display: grid; - grid-row: data-explorer-panel / end; grid-template-rows: [filter-bar] min-content [data-explorer] 1fr [status-bar] 24px [end]; } diff --git a/src/vs/workbench/browser/positronDataExplorer/positronDataExplorer.css b/src/vs/workbench/browser/positronDataExplorer/positronDataExplorer.css index e11db33611a..c946bf3b926 100644 --- a/src/vs/workbench/browser/positronDataExplorer/positronDataExplorer.css +++ b/src/vs/workbench/browser/positronDataExplorer/positronDataExplorer.css @@ -8,9 +8,7 @@ height: 100%; min-width: 0; min-height: 0; - display: grid; position: relative; color: var(--vscode-positronDataExplorer-foreground); background-color: var(--vscode-positronDataExplorer-background); - grid-template-rows: [action-bar] 32px [data-explorer-panel] 1fr [end]; } diff --git a/src/vs/workbench/browser/positronDataExplorer/positronDataExplorer.tsx b/src/vs/workbench/browser/positronDataExplorer/positronDataExplorer.tsx index d69e1e01367..73ae578e598 100644 --- a/src/vs/workbench/browser/positronDataExplorer/positronDataExplorer.tsx +++ b/src/vs/workbench/browser/positronDataExplorer/positronDataExplorer.tsx @@ -12,12 +12,11 @@ import React, { PropsWithChildren, useEffect, useState } from 'react'; // Other dependencies. import { DisposableStore } from '../../../base/common/lifecycle.js'; import { ILayoutService } from '../../../platform/layout/browser/layoutService.js'; +import { PositronDataExplorerContextProvider } from './positronDataExplorerContext.js'; +import { DataExplorerPanel } from './components/dataExplorerPanel/dataExplorerPanel.js'; import { IClipboardService } from '../../../platform/clipboard/common/clipboardService.js'; import { IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js'; -import { ActionBar } from './components/actionBar/actionBar.js'; import { PositronActionBarServices } from '../../../platform/positronActionBar/browser/positronActionBarState.js'; -import { PositronDataExplorerContextProvider } from './positronDataExplorerContext.js'; -import { DataExplorerPanel } from './components/dataExplorerPanel/dataExplorerPanel.js'; import { IPositronDataExplorerInstance } from '../../services/positronDataExplorer/browser/interfaces/positronDataExplorerInstance.js'; import { PositronDataExplorerClosed, PositronDataExplorerClosedStatus } from './components/dataExplorerClosed/positronDataExplorerClosed.js'; @@ -89,7 +88,6 @@ export const PositronDataExplorer = (props: PropsWithChildren
- {closed && ( ); + /** + * The onDidChangeColumnSorting event emitter. + */ + protected readonly _onDidChangeColumnSortingEmitter = this._register(new Emitter); + //#endregion Protected Events //#region Constructor & Dispose @@ -1178,6 +1183,13 @@ export abstract class DataGridInstance extends Disposable { return this._cursorRowIndex; } + /** + * Gets a value which indicates whether column sorting is active. + */ + get isColumnSorting() { + return this._columnSortKeys.size > 0; + } + //#endregion Public Properties //#region Public Events @@ -1187,6 +1199,11 @@ export abstract class DataGridInstance extends Disposable { */ readonly onDidUpdate = this._onDidUpdateEmitter.event; + /** + * onDidChangeColumnSorting event. + */ + readonly onDidChangeColumnSorting = this._onDidChangeColumnSortingEmitter.event; + //#endregion Public Events //#region Public Methods @@ -1433,25 +1450,22 @@ export abstract class DataGridInstance extends Disposable { columnIndex, new ColumnSortKeyDescriptor(this._columnSortKeys.size, columnIndex, ascending) ); - - // Fire the onDidUpdate event. - this._onDidUpdateEmitter.fire(); - - // Sort the data. - await this.doSortData(); + } else if (ascending !== columnSortKey.ascending) { + // Update the column sort key. + columnSortKey.ascending = ascending; } else { - // If the sort order has changed, update the column sort key. - if (ascending !== columnSortKey.ascending) { - // Update the sort order. - columnSortKey.ascending = ascending; + // Sorting has not unchanged. Do nothing. + return; + } - // Fire the onDidUpdate event. - this._onDidUpdateEmitter.fire(); + // Fire the onDidChangeColumnSorting event. + this._onDidChangeColumnSortingEmitter.fire(true); - // Sort the data. - await this.doSortData(); - } - } + // Fire the onDidUpdate event. + this._onDidUpdateEmitter.fire(); + + // Sort the data. + await this.doSortData(); } /** @@ -1476,6 +1490,9 @@ export abstract class DataGridInstance extends Disposable { } }); + // Fire the onDidChangeColumnSorting event. + this._onDidChangeColumnSortingEmitter.fire(this._columnSortKeys.size > 0); + // Fire the onDidUpdate event. this._onDidUpdateEmitter.fire(); @@ -1492,6 +1509,9 @@ export abstract class DataGridInstance extends Disposable { // Clear column sort keys. this._columnSortKeys.clear(); + // Fire the onDidChangeColumnSorting event. + this._onDidChangeColumnSortingEmitter.fire(false); + // Fire the onDidUpdate event. this._onDidUpdateEmitter.fire(); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index db5688ccec7..a9c5b70459d 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -49,6 +49,11 @@ import { AccessibilityProgressSignalScheduler } from '../../platform/accessibili import { setProgressAcccessibilitySignalScheduler } from '../../base/browser/ui/progressbar/progressAccessibilitySignal.js'; import { AccessibleViewRegistry } from '../../platform/accessibility/browser/accessibleViewRegistry.js'; import { NotificationAccessibleView } from './parts/notifications/notificationAccessibleView.js'; +// --- Start Positron --- +// eslint-disable-next-line no-duplicate-imports +import { LayoutSettings, EditorActionsLocation } from '../services/layout/browser/layoutService.js'; +import { EDITOR_ACTION_BAR_CONFIGURATION_SETTING } from './parts/editor/editorActionBarControl.js'; +// --- End Positron --- export interface IWorkbenchOptions { @@ -218,6 +223,27 @@ export class Workbench extends Layout { // Configuration changes this._register(configurationService.onDidChangeConfiguration(e => this.updateFontAliasing(e, configurationService))); + // --- Start Positron --- + // Add a onDidChangeConfiguration event listener to listen for changes to the editor action + // bar configuration setting. This is a temporary workaround that hides editor actions when + // the user has configured the editor action bar to be enabled. Eventually, editor actions + // will be replaced by the editor action bar. This approach allows testers to use both the + // editor actions and the editor action bar by adjusting the editor actions location after + // enabling the editor action bar. + this._register(configurationService.onDidChangeConfiguration(async e => { + // Check whether the editor action bar configuration setting has changed. + if (e.affectsConfiguration(EDITOR_ACTION_BAR_CONFIGURATION_SETTING)) { + // Process the editor action bar configuration setting. + await configurationService.updateValue( + LayoutSettings.EDITOR_ACTIONS_LOCATION, + configurationService.getValue(EDITOR_ACTION_BAR_CONFIGURATION_SETTING) ? + EditorActionsLocation.HIDDEN : + EditorActionsLocation.DEFAULT + ); + } + })); + // --- End Positron --- + // Font Info if (isNative) { this._register(storageService.onWillSaveState(e => { diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index ac45ecadd87..24cb06ba6de 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -14,6 +14,7 @@ import './inspectEditorTokens/inspectEditorTokens.js'; import './quickaccess/gotoLineQuickAccess.js'; import './quickaccess/gotoSymbolQuickAccess.js'; import './saveParticipants.js'; +import './toggleActionBar.js'; import './toggleColumnSelection.js'; import './toggleMinimap.js'; import './toggleMultiCursorModifier.js'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleActionBar.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleActionBar.ts index 63a9f49ccc3..558a625f804 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleActionBar.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleActionBar.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize, localize2 } from '../../../../nls.js'; -import { Action2, MenuId } from '../../../../platform/actions/common/actions.js'; +import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; @@ -27,8 +27,8 @@ export class ToggleActionBarAction extends Action2 { super({ id: ToggleActionBarAction.ID, title: { - ...localize2('toggleActionBar', "Toggle Editor Action Bar"), - mnemonicTitle: localize({ key: 'miActionBar', comment: ['&& denotes a mnemonic'] }, "&&Editor Action Bar"), + ...localize2('toggleActionBar', "Toggle Action Bar"), + mnemonicTitle: localize({ key: 'miActionBar', comment: ['&& denotes a mnemonic'] }, "&&Action Bar"), }, category: Categories.View, f1: true, @@ -49,8 +49,8 @@ export class ToggleActionBarAction extends Action2 { // Get the configuration service. const configurationService = accessor.get(IConfigurationService); - // Update the value. - return configurationService.updateValue( + // Update the action bar enabled state. + await configurationService.updateValue( 'editor.actionBar.enabled', !configurationService.getValue('editor.actionBar.enabled') ); @@ -60,5 +60,4 @@ export class ToggleActionBarAction extends Action2 { /** * Registers the action. */ -// At the moment, do not register the ToggleActionBarAction because it is still experimental. -// registerAction2(ToggleActionBarAction); +registerAction2(ToggleActionBarAction); diff --git a/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerActions.ts b/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerActions.ts index d91b908e1bb..e4419175b86 100644 --- a/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerActions.ts +++ b/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerActions.ts @@ -8,7 +8,7 @@ import { IEditorPane } from '../../../common/editor.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { ILocalizedString } from '../../../../platform/action/common/action.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; -import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { PositronDataExplorerFocused } from '../../../common/contextkeys.js'; import { IsDevelopmentContext } from '../../../../platform/contextkey/common/contextkeys.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; @@ -16,8 +16,10 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; import { IPositronDataExplorerEditor } from './positronDataExplorerEditor.js'; -import { IPositronDataExplorerService } from '../../../services/positronDataExplorer/browser/interfaces/positronDataExplorerService.js'; +import { IPositronDataExplorerService, PositronDataExplorerLayout } from '../../../services/positronDataExplorer/browser/interfaces/positronDataExplorerService.js'; import { PositronDataExplorerEditorInput } from './positronDataExplorerEditorInput.js'; +import { POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING, POSITRON_DATA_EXPLORER_LAYOUT } from './positronDataExplorerContextKeys.js'; +import { Codicon } from '../../../../base/common/codicons.js'; /** * Positron data explorer action category. @@ -43,16 +45,11 @@ export const enum PositronDataExplorerCommandId { CopyTableDataAction = 'workbench.action.positronDataExplorer.copyTableData', CollapseSummaryAction = 'workbench.action.positronDataExplorer.collapseSummary', ExpandSummaryAction = 'workbench.action.positronDataExplorer.expandSummary', + SummaryOnLeftAction = 'workbench.action.positronDataExplorer.summaryOnLeft', + SummaryOnRightAction = 'workbench.action.positronDataExplorer.summaryOnRight', + ClearColumnSortingAction = 'workbench.action.positronDataExplorer.clearColumnSorting' } -/** - * A ContextKeyExpression that is true when the active editor is a Positron data explorer editor. - */ -export const POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR = ContextKeyExpr.equals( - 'activeEditor', - PositronDataExplorerEditorInput.EditorID -); - /** * Gets the IPositronDataExplorerEditor for the specified editor pane. * @param editorPane The editor pane. @@ -117,7 +114,10 @@ class PositronDataExplorerCopyAction extends Action2 { // Notify the user. notificationService.notify({ severity: Severity.Error, - message: localize('positron.dataExplorer.copy.noActiveEditor', "Cannot copy. A Positron Data Explorer is not active."), + message: localize( + 'positron.dataExplorer.copy.noActiveEditor', + "Cannot copy. A Positron Data Explorer is not active." + ), sticky: false }); }; @@ -195,7 +195,10 @@ class PositronDataExplorerCopyTableDataAction extends Action2 { // Notify the user. notificationService.notify({ severity: Severity.Error, - message: localize('positron.dataExplorer.copyTableData.noActiveEditor', "Cannot copy table data. A Positron Data Explorer is not active."), + message: localize( + 'positron.dataExplorer.copyTableData.noActiveEditor', + "Cannot copy table data. A Positron Data Explorer is not active." + ), sticky: false }); }; @@ -270,7 +273,10 @@ class PositronDataExplorerCollapseSummaryAction extends Action2 { // Notify the user. notificationService.notify({ severity: Severity.Error, - message: localize('positron.dataExplorer.collapseSummary.noActiveEditor', "Cannot Collapse Summary. A Positron Data Explorer is not active."), + message: localize( + 'positron.dataExplorer.collapseSummary.noActiveEditor', + "Cannot Collapse Summary. A Positron Data Explorer is not active." + ), sticky: false }); }; @@ -345,7 +351,10 @@ class PositronDataExplorerExpandSummaryAction extends Action2 { // Notify the user. notificationService.notify({ severity: Severity.Error, - message: localize('positron.dataExplorer.expandSummary.noActiveEditor', "Cannot Expand Summary. A Positron Data Explorer is not active."), + message: localize( + 'positron.dataExplorer.expandSummary.noActiveEditor', + "Cannot Expand Summary. A Positron Data Explorer is not active." + ), sticky: false }); }; @@ -381,6 +390,290 @@ class PositronDataExplorerExpandSummaryAction extends Action2 { } } +/** + * PositronDataExplorerSummaryOnLeftAction action. + */ +class PositronDataExplorerSummaryOnLeftAction extends Action2 { + /** + * Constructor. + */ + constructor() { + super({ + id: PositronDataExplorerCommandId.SummaryOnLeftAction, + title: { + value: localize('positronDataExplorer.summaryOnLeft', 'Summary on Left'), + original: 'Summary on Left' + }, + category, + f1: true, + precondition: POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, + icon: Codicon.positronDataExplorerSummaryOnLeft, + toggled: ContextKeyExpr.and( + POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, + POSITRON_DATA_EXPLORER_LAYOUT.isEqualTo( + PositronDataExplorerLayout.SummaryOnLeft + ) + ), + menu: [ + { + id: MenuId.EditorTitle, + when: POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, + group: '1_data_explorer', + order: 1 + }, + ] + }); + } + + /** + * Runs the action. + * @param accessor The services accessor. + */ + async run(accessor: ServicesAccessor): Promise { + // Access the services we need. + const editorService = accessor.get(IEditorService); + const notificationService = accessor.get(INotificationService); + const positronDataExplorerService = accessor.get(IPositronDataExplorerService); + + // Get the Positron data explorer editor. + const positronDataExplorerEditor = getPositronDataExplorerEditorFromEditorPane( + editorService.activeEditorPane + ); + + /** + * Notifies the user that layout failed. + */ + const notifyUserThatLayoutFailed = () => { + // Notify the user. + notificationService.notify({ + severity: Severity.Error, + message: localize( + 'positron.dataExplorer.changeLayout.noActiveEditor', + "Cannot Change Layout. A Positron Data Explorer is not active." + ), + sticky: false + }); + }; + + // Make sure that the Positron data explorer editor was returned. + if (!positronDataExplorerEditor) { + notifyUserThatLayoutFailed(); + return; + } + + // Get the identifier. + const identifier = positronDataExplorerEditor.identifier; + + // Make sure the identifier was returned. + if (!identifier) { + notifyUserThatLayoutFailed(); + return; + } + + // Get the Positron data explorer instance. + const positronDataExplorerInstance = positronDataExplorerService.getInstance( + identifier + ); + + // Make sure the Positron data explorer instance was returned. + if (!positronDataExplorerInstance) { + notifyUserThatLayoutFailed(); + return; + } + + // Change layout. + positronDataExplorerInstance.layout = PositronDataExplorerLayout.SummaryOnLeft; + } +} + +/** + * PositronDataExplorerSummaryOnRightAction action. + */ +class PositronDataExplorerSummaryOnRightAction extends Action2 { + /** + * Constructor. + */ + constructor() { + super({ + id: PositronDataExplorerCommandId.SummaryOnRightAction, + title: { + value: localize('positronDataExplorer.summaryOnRight', 'Summary on Right'), + original: 'Summary on Right' + }, + category, + f1: true, + precondition: POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, + icon: Codicon.positronDataExplorerSummaryOnRight, + toggled: ContextKeyExpr.and( + POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, + POSITRON_DATA_EXPLORER_LAYOUT.isEqualTo( + PositronDataExplorerLayout.SummaryOnRight + ) + ), + menu: [ + { + id: MenuId.EditorTitle, + when: POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, + group: '1_data_explorer', + order: 2 + }, + ] + }); + } + + /** + * Runs the action. + * @param accessor The services accessor. + */ + async run(accessor: ServicesAccessor): Promise { + // Access the services we need. + const editorService = accessor.get(IEditorService); + const notificationService = accessor.get(INotificationService); + const positronDataExplorerService = accessor.get(IPositronDataExplorerService); + + // Get the Positron data explorer editor. + const positronDataExplorerEditor = getPositronDataExplorerEditorFromEditorPane( + editorService.activeEditorPane + ); + + /** + * Notifies the user that layout failed. + */ + const notifyUserThatLayoutFailed = () => { + // Notify the user. + notificationService.notify({ + severity: Severity.Error, + message: localize( + 'positron.dataExplorer.changeLayout.noActiveEditor', + "Cannot Change Layout. A Positron Data Explorer is not active." + ), + sticky: false + }); + }; + + // Make sure that the Positron data explorer editor was returned. + if (!positronDataExplorerEditor) { + notifyUserThatLayoutFailed(); + return; + } + + // Get the identifier. + const identifier = positronDataExplorerEditor.identifier; + + // Make sure the identifier was returned. + if (!identifier) { + notifyUserThatLayoutFailed(); + return; + } + + // Get the Positron data explorer instance. + const positronDataExplorerInstance = positronDataExplorerService.getInstance( + identifier + ); + + // Make sure the Positron data explorer instance was returned. + if (!positronDataExplorerInstance) { + notifyUserThatLayoutFailed(); + return; + } + + // Change layout. + positronDataExplorerInstance.layout = PositronDataExplorerLayout.SummaryOnRight; + } +} + +/** + * PositronDataExplorerClearColumnSortingAction action. + */ +class PositronDataExplorerClearColumnSortingAction extends Action2 { + /** + * Constructor. + */ + constructor() { + super({ + id: PositronDataExplorerCommandId.ClearColumnSortingAction, + title: { + value: localize('positronDataExplorer.clearColumnSorting', 'Clear Column Sorting'), + original: 'Clear Column Sorting' + }, + displayTitleOnActionBar: true, + category, + f1: true, + precondition: ContextKeyExpr.and( + POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, + POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING + ), + icon: Codicon.positronClearSorting, + menu: [ + { + id: MenuId.EditorActionsLeft, + when: POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, + }, + ] + }); + } + + /** + * Runs the action. + * @param accessor The services accessor. + */ + async run(accessor: ServicesAccessor): Promise { + // Access the services we need. + const editorService = accessor.get(IEditorService); + const notificationService = accessor.get(INotificationService); + const positronDataExplorerService = accessor.get(IPositronDataExplorerService); + + // Get the Positron data explorer editor. + const positronDataExplorerEditor = getPositronDataExplorerEditorFromEditorPane( + editorService.activeEditorPane + ); + + /** + * Notifies the user that clear sorting failed. + */ + const notifyUserThatClearSortingFailed = () => { + // Notify the user. + notificationService.notify({ + severity: Severity.Error, + message: localize( + 'positron.dataExplorer.clearSorting.noActiveEditor', + "Cannot Clear Sorting. A Positron Data Explorer is not active." + ), + sticky: false + }); + }; + + // Make sure that the Positron data explorer editor was returned. + if (!positronDataExplorerEditor) { + notifyUserThatClearSortingFailed(); + return; + } + + // Get the identifier. + const identifier = positronDataExplorerEditor.identifier; + + // Make sure the identifier was returned. + if (!identifier) { + notifyUserThatClearSortingFailed(); + return; + } + + // Get the Positron data explorer instance. + const positronDataExplorerInstance = positronDataExplorerService.getInstance( + identifier + ); + + // Make sure the Positron data explorer instance was returned. + if (!positronDataExplorerInstance) { + notifyUserThatClearSortingFailed(); + return; + } + + // Clear column sorting. + await positronDataExplorerInstance.clearColumnSorting(); + } +} + /** * Registers Positron data explorer actions. */ @@ -389,4 +682,7 @@ export function registerPositronDataExplorerActions() { registerAction2(PositronDataExplorerCopyTableDataAction); registerAction2(PositronDataExplorerCollapseSummaryAction); registerAction2(PositronDataExplorerExpandSummaryAction); + registerAction2(PositronDataExplorerSummaryOnLeftAction); + registerAction2(PositronDataExplorerSummaryOnRightAction); + registerAction2(PositronDataExplorerClearColumnSortingAction); } diff --git a/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerContextKeys.ts b/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerContextKeys.ts new file mode 100644 index 00000000000..f48648fa95c --- /dev/null +++ b/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerContextKeys.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import { PositronDataExplorerEditorInput } from './positronDataExplorerEditorInput.js'; +import { ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { PositronDataExplorerLayout } from '../../../services/positronDataExplorer/browser/interfaces/positronDataExplorerService.js'; + +/** + * A ContextKeyExpression that is true when the active editor is a Positron data explorer editor. + */ +export const POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR = ContextKeyExpr.equals( + 'activeEditor', + PositronDataExplorerEditorInput.EditorID +); + +/** + * Raw context keys. + */ +export const POSITRON_DATA_EXPLORER_LAYOUT = new RawContextKey( + 'positronDataExplorerLayout', + PositronDataExplorerLayout.SummaryOnLeft +); +export const POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING = new RawContextKey( + 'positronDataExplorerIsColumnSorting', + false +); diff --git a/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerEditor.tsx b/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerEditor.tsx index 6453d22fe12..0694299ac13 100644 --- a/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerEditor.tsx +++ b/src/vs/workbench/contrib/positronDataExplorerEditor/browser/positronDataExplorerEditor.tsx @@ -22,7 +22,7 @@ import { ILayoutService } from '../../../../platform/layout/browser/layoutServic import { EditorPane } from '../../../browser/parts/editor/editorPane.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { EditorActivation, IEditorOptions } from '../../../../platform/editor/common/editor.js'; @@ -32,9 +32,10 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common import { PositronDataExplorer } from '../../../browser/positronDataExplorer/positronDataExplorer.js'; import { IReactComponentContainer, ISize, PositronReactRenderer } from '../../../../base/browser/positronReactRenderer.js'; import { PositronDataExplorerUri } from '../../../services/positronDataExplorer/common/positronDataExplorerUri.js'; -import { IPositronDataExplorerService } from '../../../services/positronDataExplorer/browser/interfaces/positronDataExplorerService.js'; +import { IPositronDataExplorerService, PositronDataExplorerLayout } from '../../../services/positronDataExplorer/browser/interfaces/positronDataExplorerService.js'; import { PositronDataExplorerEditorInput } from './positronDataExplorerEditorInput.js'; import { PositronDataExplorerClosed, PositronDataExplorerClosedStatus } from '../../../browser/positronDataExplorer/components/dataExplorerClosed/positronDataExplorerClosed.js'; +import { POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING, POSITRON_DATA_EXPLORER_LAYOUT } from './positronDataExplorerContextKeys.js'; /** * IPositronDataExplorerEditorOptions interface. @@ -85,6 +86,16 @@ export class PositronDataExplorerEditor extends EditorPane implements IPositronD */ private _identifier?: string; + /** + * Gets the layout context key. + */ + private readonly _layoutContextKey: IContextKey; + + /** + * Gets the is column sorting context key. + */ + private readonly _isColumnSortingContextKey: IContextKey; + /** * The onSizeChanged event emitter. */ @@ -232,6 +243,14 @@ export class PositronDataExplorerEditor extends EditorPane implements IPositronD // Create the Positron data explorer container. this._positronDataExplorerContainer = DOM.$('.positron-data-explorer-container'); + + // Create the context keys. + this._layoutContextKey = POSITRON_DATA_EXPLORER_LAYOUT.bindTo( + this._group.scopedContextKeyService + ); + this._isColumnSortingContextKey = POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING.bindTo( + this._group.scopedContextKeyService + ); } /** @@ -326,6 +345,14 @@ export class PositronDataExplorerEditor extends EditorPane implements IPositronD } }); + // Set the context keys. + this._layoutContextKey.set( + positronDataExplorerInstance.layout + ); + this._isColumnSortingContextKey.set( + positronDataExplorerInstance.tableDataDataGridInstance.isColumnSorting + ); + // Render the PositronDataExplorer. this._positronReactRenderer.render( ); + // Add the onDidChangeLayout event handler. + this._positronReactRenderer.register( + positronDataExplorerInstance.onDidChangeLayout(positronDataExplorerLayout => + this._layoutContextKey.set(positronDataExplorerLayout) + ) + ); + // Add the onDidRequestFocus event handler. this._positronReactRenderer.register( positronDataExplorerInstance.onDidRequestFocus(() => this._group.openEditor(input, { activation: EditorActivation.ACTIVATE }) ) ); + + // Add the onDidChangeColumnSorting event handler. + this._positronReactRenderer.register( + positronDataExplorerInstance.onDidChangeColumnSorting(isColumnSorting => + this._isColumnSortingContextKey.set(isColumnSorting) + ) + ); } else { this._positronReactRenderer.render( = { label: localize('textEditor', "Text Editor"), settings: ['editor.*'], children: [ + // --- Start Positron --- + { + id: 'editor/actionBar', + label: localize('actionBar', "Action Bar"), + settings: ['editor.actionBar.*'] + }, + // --- End Positron --- { id: 'editor/cursor', label: localize('cursor', "Cursor"), @@ -91,13 +98,6 @@ export const tocData: ITOCEntry = { label: localize('files', "Files"), settings: ['files.*'] }, - // --- Start Positron --- - { - id: 'editor/experimental', - label: localize('experimental', "Experimental"), - settings: ['editor.actionBar.*'] - }, - // --- End Positron --- ] }, { diff --git a/src/vs/workbench/services/positronDataExplorer/browser/interfaces/positronDataExplorerInstance.ts b/src/vs/workbench/services/positronDataExplorer/browser/interfaces/positronDataExplorerInstance.ts index b018147f456..c9c482cb0d0 100644 --- a/src/vs/workbench/services/positronDataExplorer/browser/interfaces/positronDataExplorerInstance.ts +++ b/src/vs/workbench/services/positronDataExplorer/browser/interfaces/positronDataExplorerInstance.ts @@ -15,7 +15,7 @@ import { PositronDataExplorerLayout } from './positronDataExplorerService.js'; */ export interface IPositronDataExplorerInstance extends IDisposable { /** - * Gets the data explorer client instance. + * Gets the language name. */ readonly languageName: string; @@ -44,6 +44,11 @@ export interface IPositronDataExplorerInstance extends IDisposable { */ readonly tableDataDataGridInstance: TableDataDataGridInstance; + /** + * Gets a value which indicates whether one or more columns are sorted. + */ + readonly isColumnSorting: boolean; + /** * The onDidClose event. */ @@ -74,6 +79,11 @@ export interface IPositronDataExplorerInstance extends IDisposable { */ readonly onDidExpandSummary: Event; + /** + * The onDidChangeColumnSorting event. + */ + readonly onDidChangeColumnSorting: Event; + /** * Requests focus for the instance. */ @@ -89,6 +99,12 @@ export interface IPositronDataExplorerInstance extends IDisposable { */ expandSummary(): void; + /** + * Clears column sorting. + * @returns A Promise that resolves when column sorting has been cleared. + */ + clearColumnSorting(): Promise; + /** * Copies the selection or cursor cell to the clipboard. */ diff --git a/src/vs/workbench/services/positronDataExplorer/browser/positronDataExplorerInstance.ts b/src/vs/workbench/services/positronDataExplorer/browser/positronDataExplorerInstance.ts index 570ce685c15..8628aff0c7e 100644 --- a/src/vs/workbench/services/positronDataExplorer/browser/positronDataExplorerInstance.ts +++ b/src/vs/workbench/services/positronDataExplorer/browser/positronDataExplorerInstance.ts @@ -102,6 +102,11 @@ export class PositronDataExplorerInstance extends Disposable implements IPositro */ private readonly _onDidExpandSummaryEmitter = this._register(new Emitter()); + /** + * The onDidChangeColumnSorting event emitter. + */ + private readonly _onDidChangeColumnSortingEmitter = this._register(new Emitter()); + //#endregion Private Properties //#region Constructor @@ -175,6 +180,13 @@ export class PositronDataExplorerInstance extends Disposable implements IPositro this._tableDataDataGridInstance.scrollToColumn(columnIndex); })); + // Add the onDidChangeColumnSorting event handler. + this._register( + this._tableDataDataGridInstance.onDidChangeColumnSorting(isColumnSorting => + this._onDidChangeColumnSortingEmitter.fire(isColumnSorting) + ) + ); + // Add the onDidRequestFocus event handler. this._register(this.onDidRequestFocus(() => { const uri = PositronDataExplorerUri.generate(this._dataExplorerClientInstance.identifier); @@ -244,6 +256,13 @@ export class PositronDataExplorerInstance extends Disposable implements IPositro return this._tableDataDataGridInstance; } + /** + * Gets a value which indicates whether one or more columns are sorted. + */ + get isColumnSorting() { + return this._tableDataDataGridInstance.isColumnSorting; + } + /** * Requests focus. */ @@ -265,6 +284,14 @@ export class PositronDataExplorerInstance extends Disposable implements IPositro this._onDidExpandSummaryEmitter.fire(); } + /** + * Clears column sorting. + * @returns A Promise that resolves when column sorting has been cleared. + */ + async clearColumnSorting(): Promise { + await this._tableDataDataGridInstance.clearColumnSortKeys(); + } + /** * Copies the selection or cursor cell to the clipboard. */ @@ -412,5 +439,10 @@ export class PositronDataExplorerInstance extends Disposable implements IPositro */ readonly onDidExpandSummary = this._onDidExpandSummaryEmitter.event; + /** + * The onDidChangeColumnSorting event. + */ + readonly onDidChangeColumnSorting = this._onDidChangeColumnSortingEmitter.event; + //#endregion IPositronDataExplorerInstance Implementation } diff --git a/test/e2e/tests/action-bar/editor-action-bar.test.ts b/test/e2e/tests/action-bar/editor-action-bar.test.ts index 187c85a2fa1..943a61164ae 100644 --- a/test/e2e/tests/action-bar/editor-action-bar.test.ts +++ b/test/e2e/tests/action-bar/editor-action-bar.test.ts @@ -99,7 +99,7 @@ async function verifySplitEditor(page, tabName: string) { // Sometimes in CI the click doesn't register, wrapping these actions to reduce flake await expect(async () => { await page.keyboard.down('Alt'); - await page.getByLabel('Split Editor Down').nth(1).click(); + await page.getByLabel('Split Editor Down').click(); await page.keyboard.up('Alt'); await expect(page.getByRole('tab', { name: tabName })).toHaveCount(2); }).toPass({ timeout: 10000 }); @@ -111,7 +111,7 @@ async function verifyOpenInNewWindow(page, expectedText: string) { await test.step(`verify "open new window" contains: ${expectedText}`, async () => { const [newPage] = await Promise.all([ page.context().waitForEvent('page'), - page.getByLabel('Move into new window').nth(1).click(), + page.getByLabel('Move into new window').first().click(), ]); await newPage.waitForLoadState(); await expect(newPage.getByText(expectedText)).toBeVisible(); @@ -120,7 +120,7 @@ async function verifyOpenInNewWindow(page, expectedText: string) { async function clickCustomizeNotebookMenuItem(page, menuItem: string) { const role = menuItem.includes('Line Numbers') ? 'menuitemcheckbox' : 'menuitem'; - const dropdownButton = page.getByLabel('Customize Notebook...').nth(1); + const dropdownButton = page.getByLabel('Customize Notebook...'); await dropdownButton.evaluate((button) => { (button as HTMLElement).dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true })); }); @@ -141,14 +141,13 @@ async function verifyLineNumbersVisibility(page, isVisible: boolean) { async function verifyOpenChanges(page: Page) { await test.step('verify "open changes" shows diff', async () => { - // make change & save await page.getByText('date', { exact: true }).click(); await page.keyboard.press('X'); await bindPlatformHotkey(page, 'S'); // click open changes & verify - await page.getByLabel('Open Changes').nth(1).click(); + await page.getByLabel('Open Changes').click(); await expect(page.getByLabel('Revert Block')).toBeVisible(); await expect(page.getByLabel('Stage Block')).toBeVisible(); await page.getByRole('tab', { name: 'quarto_basic.qmd (Working' }).getByLabel('Close').click(); @@ -165,7 +164,7 @@ async function bindPlatformHotkey(page: Page, key: string) { async function verifyOpenViewerRendersHtml(app: Application) { await test.step('verify "open in viewer" renders html', async () => { - await app.code.driver.page.getByLabel('Open in Viewer').nth(1).click(); + await app.code.driver.page.getByLabel('Open in Viewer').click(); const viewerFrame = app.code.driver.page.locator('iframe.webview').contentFrame().locator('#active-frame').contentFrame(); const cellLocator = app.web ? viewerFrame.frameLocator('iframe').getByRole('cell', { name: 'Oil, Gas, and Other Regulated' }) diff --git a/test/e2e/tests/data-explorer/data-explorer-python-polars.test.ts b/test/e2e/tests/data-explorer/data-explorer-python-polars.test.ts index d948ac8cf03..794859edd0c 100644 --- a/test/e2e/tests/data-explorer/data-explorer-python-polars.test.ts +++ b/test/e2e/tests/data-explorer/data-explorer-python-polars.test.ts @@ -112,7 +112,7 @@ test.describe('Data Explorer - Python Polars', { expect(tableData.length).toBe(2); }).toPass({ timeout: 60000 }); - await app.workbench.dataExplorer.clearSortingButton.click(); + await app.workbench.dataExplorer.selectColumnMenuItem(1, 'Clear Sorting'); await expect(async () => { tableData = await app.workbench.dataExplorer.getDataExplorerTableData(); From a2cd7cd22039c412ae96015d9f8d6d3581ba9ac8 Mon Sep 17 00:00:00 2001 From: Brian Lambert Date: Wed, 15 Jan 2025 19:27:11 -0700 Subject: [PATCH 2/7] Add 'checked' state to `ActionBarButton` and use that state in `ActionBarActionButton` (#6013) --- .../browser/components/actionBarActionButton.tsx | 1 + .../browser/components/actionBarButton.css | 5 +++++ .../browser/components/actionBarButton.tsx | 7 +++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/positronActionBar/browser/components/actionBarActionButton.tsx b/src/vs/platform/positronActionBar/browser/components/actionBarActionButton.tsx index ceb7ebfdaf7..e6d844e2727 100644 --- a/src/vs/platform/positronActionBar/browser/components/actionBarActionButton.tsx +++ b/src/vs/platform/positronActionBar/browser/components/actionBarActionButton.tsx @@ -148,6 +148,7 @@ export const ActionBarActionButton = (props: ActionBarActionButtonProps) => { action.label : undefined : undefined, + checked: action.checked, disabled: !action.enabled, onMouseEnter: () => setMouseInside(true), onMouseLeave: () => setMouseInside(false), diff --git a/src/vs/platform/positronActionBar/browser/components/actionBarButton.css b/src/vs/platform/positronActionBar/browser/components/actionBarButton.css index 6a06b316d07..19acf3ca452 100644 --- a/src/vs/platform/positronActionBar/browser/components/actionBarButton.css +++ b/src/vs/platform/positronActionBar/browser/components/actionBarButton.css @@ -18,6 +18,7 @@ height: 28px; border: none; display: flex; + margin: 0 1px; cursor: pointer; overflow: visible; border-radius: 6px; @@ -27,6 +28,10 @@ color: var(--positronActionBar-foreground); } +.action-bar-button.checked { + background: var(--positronActionBar-hoverBackground); +} + .action-bar-button:hover { background: var(--positronActionBar-hoverBackground); } diff --git a/src/vs/platform/positronActionBar/browser/components/actionBarButton.tsx b/src/vs/platform/positronActionBar/browser/components/actionBarButton.tsx index 93933d90718..843534ea248 100644 --- a/src/vs/platform/positronActionBar/browser/components/actionBarButton.tsx +++ b/src/vs/platform/positronActionBar/browser/components/actionBarButton.tsx @@ -26,6 +26,7 @@ export interface ActionBarButtonProps { readonly align?: 'left' | 'right'; readonly tooltip?: string | (() => string | undefined); readonly dropdownTooltip?: string | (() => string | undefined); + readonly checked?: boolean; readonly disabled?: boolean; readonly ariaLabel?: string; readonly dropdownAriaLabel?: string; @@ -116,7 +117,8 @@ export const ActionBarButton = forwardRef< hoverManager={context.hoverManager} className={positronClassNames( 'action-bar-button', - { 'fade-in': optionalBoolean(props.fadeIn) } + { 'fade-in': optionalBoolean(props.fadeIn) }, + { 'checked': optionalBoolean(props.checked) } )} ariaLabel={ariaLabel} tooltip={props.tooltip} @@ -134,7 +136,8 @@ export const ActionBarButton = forwardRef< return (