Path: blob/main/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4/* eslint-disable local/code-no-dangerous-type-assertions */56import './media/keybindingsEditor.css';7import { localize } from '../../../../nls.js';8import { Delayer } from '../../../../base/common/async.js';9import * as DOM from '../../../../base/browser/dom.js';10import { isIOS, OS } from '../../../../base/common/platform.js';11import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';12import { ToggleActionViewItem } from '../../../../base/browser/ui/toggle/toggle.js';13import { HighlightedLabel } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';14import { KeybindingLabel } from '../../../../base/browser/ui/keybindingLabel/keybindingLabel.js';15import { IAction, Action, Separator } from '../../../../base/common/actions.js';16import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';17import { EditorPane } from '../../../browser/parts/editor/editorPane.js';18import { IEditorOpenContext } from '../../../common/editor.js';19import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';20import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';21import { KeybindingsEditorModel, KEYBINDING_ENTRY_TEMPLATE_ID } from '../../../services/preferences/browser/keybindingsEditorModel.js';22import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';23import { IKeybindingService, IUserFriendlyKeybinding } from '../../../../platform/keybinding/common/keybinding.js';24import { DefineKeybindingWidget, KeybindingsSearchWidget } from './keybindingWidgets.js';25import { CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE, CONTEXT_WHEN_FOCUS } from '../common/preferences.js';26import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';27import { IKeybindingEditingService } from '../../../services/keybinding/common/keybindingEditing.js';28import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js';29import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from '../../../../platform/theme/common/themeService.js';30import { ThemeIcon } from '../../../../base/common/themables.js';31import { IContextKeyService, IContextKey, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';32import { KeyCode } from '../../../../base/common/keyCodes.js';33import { badgeBackground, contrastBorder, badgeForeground, listActiveSelectionForeground, listInactiveSelectionForeground, listHoverForeground, listFocusForeground, editorBackground, foreground, listActiveSelectionBackground, listInactiveSelectionBackground, listFocusBackground, listHoverBackground, registerColor, tableOddRowsBackgroundColor, asCssVariable } from '../../../../platform/theme/common/colorRegistry.js';34import { IEditorService } from '../../../services/editor/common/editorService.js';35import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js';36import { WorkbenchTable } from '../../../../platform/list/browser/listService.js';37import { INotificationService } from '../../../../platform/notification/common/notification.js';38import { CancellationToken } from '../../../../base/common/cancellation.js';39import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';40import { Emitter, Event } from '../../../../base/common/event.js';41import { MenuRegistry, MenuId, isIMenuItem } from '../../../../platform/actions/common/actions.js';42import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';43import { WORKBENCH_BACKGROUND } from '../../../common/theme.js';44import { IKeybindingItemEntry, IKeybindingsEditorPane } from '../../../services/preferences/common/preferences.js';45import { keybindingsRecordKeysIcon, keybindingsSortIcon, keybindingsAddIcon, preferencesClearInputIcon, keybindingsEditIcon } from './preferencesIcons.js';46import { ITableRenderer, ITableVirtualDelegate } from '../../../../base/browser/ui/table/table.js';47import { KeybindingsEditorInput } from '../../../services/preferences/browser/keybindingsEditorInput.js';48import { IEditorOptions } from '../../../../platform/editor/common/editor.js';49import { ToolBar } from '../../../../base/browser/ui/toolbar/toolbar.js';50import { defaultKeybindingLabelStyles, defaultToggleStyles, getInputBoxStyle } from '../../../../platform/theme/browser/defaultStyles.js';51import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js';52import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';53import { isString } from '../../../../base/common/types.js';54import { SuggestEnabledInput } from '../../codeEditor/browser/suggestEnabledInput/suggestEnabledInput.js';55import { CompletionItemKind } from '../../../../editor/common/languages.js';56import { settingsTextInputBorder } from '../common/settingsEditorColorRegistry.js';57import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';58import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js';59import { registerNavigableContainer } from '../../../browser/actions/widgetNavigationCommands.js';60import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';61import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';62import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';63import type { IManagedHover } from '../../../../base/browser/ui/hover/hover.js';64import { IHoverService } from '../../../../platform/hover/browser/hover.js';65import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';6667const $ = DOM.$;6869export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorPane {7071static readonly ID: string = 'workbench.editor.keybindings';7273private _onDefineWhenExpression: Emitter<IKeybindingItemEntry> = this._register(new Emitter<IKeybindingItemEntry>());74readonly onDefineWhenExpression: Event<IKeybindingItemEntry> = this._onDefineWhenExpression.event;7576private _onRejectWhenExpression = this._register(new Emitter<IKeybindingItemEntry>());77readonly onRejectWhenExpression = this._onRejectWhenExpression.event;7879private _onAcceptWhenExpression = this._register(new Emitter<IKeybindingItemEntry>());80readonly onAcceptWhenExpression = this._onAcceptWhenExpression.event;8182private _onLayout: Emitter<void> = this._register(new Emitter<void>());83readonly onLayout: Event<void> = this._onLayout.event;8485private keybindingsEditorModel: KeybindingsEditorModel | null = null;8687private headerContainer!: HTMLElement;88private actionsContainer!: HTMLElement;89private searchWidget!: KeybindingsSearchWidget;90private searchHistoryDelayer: Delayer<void>;9192private overlayContainer!: HTMLElement;93private defineKeybindingWidget!: DefineKeybindingWidget;9495private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry | null = null;96private tableEntries: IKeybindingItemEntry[] = [];97private keybindingsTableContainer!: HTMLElement;98private keybindingsTable!: WorkbenchTable<IKeybindingItemEntry>;99100private dimension: DOM.Dimension | null = null;101private delayedFiltering: Delayer<void>;102private latestEmptyFilters: string[] = [];103private keybindingsEditorContextKey: IContextKey<boolean>;104private keybindingFocusContextKey: IContextKey<boolean>;105private searchFocusContextKey: IContextKey<boolean>;106107private readonly sortByPrecedenceAction: Action;108private readonly recordKeysAction: Action;109110private ariaLabelElement!: HTMLElement;111readonly overflowWidgetsDomNode: HTMLElement;112113constructor(114group: IEditorGroup,115@ITelemetryService telemetryService: ITelemetryService,116@IThemeService themeService: IThemeService,117@IKeybindingService private readonly keybindingsService: IKeybindingService,118@IContextMenuService private readonly contextMenuService: IContextMenuService,119@IKeybindingEditingService private readonly keybindingEditingService: IKeybindingEditingService,120@IContextKeyService private readonly contextKeyService: IContextKeyService,121@INotificationService private readonly notificationService: INotificationService,122@IClipboardService private readonly clipboardService: IClipboardService,123@IInstantiationService private readonly instantiationService: IInstantiationService,124@IEditorService private readonly editorService: IEditorService,125@IStorageService storageService: IStorageService,126@IConfigurationService private readonly configurationService: IConfigurationService,127@IAccessibilityService private readonly accessibilityService: IAccessibilityService128) {129super(KeybindingsEditor.ID, group, telemetryService, themeService, storageService);130this.delayedFiltering = new Delayer<void>(300);131this._register(keybindingsService.onDidUpdateKeybindings(() => this.render(!!this.keybindingFocusContextKey.get())));132133this.keybindingsEditorContextKey = CONTEXT_KEYBINDINGS_EDITOR.bindTo(this.contextKeyService);134this.searchFocusContextKey = CONTEXT_KEYBINDINGS_SEARCH_FOCUS.bindTo(this.contextKeyService);135this.keybindingFocusContextKey = CONTEXT_KEYBINDING_FOCUS.bindTo(this.contextKeyService);136this.searchHistoryDelayer = new Delayer<void>(500);137138this.recordKeysAction = this._register(new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, localize('recordKeysLabel', "Record Keys"), ThemeIcon.asClassName(keybindingsRecordKeysIcon)));139this.recordKeysAction.checked = false;140141this.sortByPrecedenceAction = this._register(new Action(KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, localize('sortByPrecedeneLabel', "Sort by Precedence (Highest first)"), ThemeIcon.asClassName(keybindingsSortIcon)));142this.sortByPrecedenceAction.checked = false;143this.overflowWidgetsDomNode = $('.keybindings-overflow-widgets-container.monaco-editor');144}145146override create(parent: HTMLElement): void {147super.create(parent);148this._register(registerNavigableContainer({149name: 'keybindingsEditor',150focusNotifiers: [this],151focusNextWidget: () => {152if (this.searchWidget.hasFocus()) {153this.focusKeybindings();154}155},156focusPreviousWidget: () => {157if (!this.searchWidget.hasFocus()) {158this.focusSearch();159}160}161}));162}163164protected createEditor(parent: HTMLElement): void {165const keybindingsEditorElement = DOM.append(parent, $('div', { class: 'keybindings-editor' }));166167this.createAriaLabelElement(keybindingsEditorElement);168this.createOverlayContainer(keybindingsEditorElement);169this.createHeader(keybindingsEditorElement);170this.createBody(keybindingsEditorElement);171}172173override setInput(input: KeybindingsEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {174this.keybindingsEditorContextKey.set(true);175return super.setInput(input, options, context, token)176.then(() => this.render(!!(options && options.preserveFocus)));177}178179override clearInput(): void {180super.clearInput();181this.keybindingsEditorContextKey.reset();182this.keybindingFocusContextKey.reset();183}184185layout(dimension: DOM.Dimension): void {186this.dimension = dimension;187this.layoutSearchWidget(dimension);188189this.overlayContainer.style.width = dimension.width + 'px';190this.overlayContainer.style.height = dimension.height + 'px';191this.defineKeybindingWidget.layout(this.dimension);192193this.layoutKeybindingsTable();194this._onLayout.fire();195}196197override focus(): void {198super.focus();199200const activeKeybindingEntry = this.activeKeybindingEntry;201if (activeKeybindingEntry) {202this.selectEntry(activeKeybindingEntry);203} else if (!isIOS) {204this.searchWidget.focus();205}206}207208get activeKeybindingEntry(): IKeybindingItemEntry | null {209const focusedElement = this.keybindingsTable.getFocusedElements()[0];210return focusedElement && focusedElement.templateId === KEYBINDING_ENTRY_TEMPLATE_ID ? <IKeybindingItemEntry>focusedElement : null;211}212213async defineKeybinding(keybindingEntry: IKeybindingItemEntry, add: boolean): Promise<void> {214this.selectEntry(keybindingEntry);215this.showOverlayContainer();216try {217const key = await this.defineKeybindingWidget.define();218if (key) {219await this.updateKeybinding(keybindingEntry, key, keybindingEntry.keybindingItem.when, add);220}221} catch (error) {222this.onKeybindingEditingError(error);223} finally {224this.hideOverlayContainer();225this.selectEntry(keybindingEntry);226}227}228229defineWhenExpression(keybindingEntry: IKeybindingItemEntry): void {230if (keybindingEntry.keybindingItem.keybinding) {231this.selectEntry(keybindingEntry);232this._onDefineWhenExpression.fire(keybindingEntry);233}234}235236rejectWhenExpression(keybindingEntry: IKeybindingItemEntry): void {237this._onRejectWhenExpression.fire(keybindingEntry);238}239240acceptWhenExpression(keybindingEntry: IKeybindingItemEntry): void {241this._onAcceptWhenExpression.fire(keybindingEntry);242}243244async updateKeybinding(keybindingEntry: IKeybindingItemEntry, key: string, when: string | undefined, add?: boolean): Promise<void> {245const currentKey = keybindingEntry.keybindingItem.keybinding ? keybindingEntry.keybindingItem.keybinding.getUserSettingsLabel() : '';246if (currentKey !== key || keybindingEntry.keybindingItem.when !== when) {247if (add) {248await this.keybindingEditingService.addKeybinding(keybindingEntry.keybindingItem.keybindingItem, key, when || undefined);249} else {250await this.keybindingEditingService.editKeybinding(keybindingEntry.keybindingItem.keybindingItem, key, when || undefined);251}252if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering253this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry;254}255}256}257258async removeKeybinding(keybindingEntry: IKeybindingItemEntry): Promise<void> {259this.selectEntry(keybindingEntry);260if (keybindingEntry.keybindingItem.keybinding) { // This should be a pre-condition261try {262await this.keybindingEditingService.removeKeybinding(keybindingEntry.keybindingItem.keybindingItem);263this.focus();264} catch (error) {265this.onKeybindingEditingError(error);266this.selectEntry(keybindingEntry);267}268}269}270271async resetKeybinding(keybindingEntry: IKeybindingItemEntry): Promise<void> {272this.selectEntry(keybindingEntry);273try {274await this.keybindingEditingService.resetKeybinding(keybindingEntry.keybindingItem.keybindingItem);275if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering276this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry;277}278this.selectEntry(keybindingEntry);279} catch (error) {280this.onKeybindingEditingError(error);281this.selectEntry(keybindingEntry);282}283}284285async copyKeybinding(keybinding: IKeybindingItemEntry): Promise<void> {286this.selectEntry(keybinding);287const userFriendlyKeybinding: IUserFriendlyKeybinding = {288key: keybinding.keybindingItem.keybinding ? keybinding.keybindingItem.keybinding.getUserSettingsLabel() || '' : '',289command: keybinding.keybindingItem.command290};291if (keybinding.keybindingItem.when) {292userFriendlyKeybinding.when = keybinding.keybindingItem.when;293}294await this.clipboardService.writeText(JSON.stringify(userFriendlyKeybinding, null, ' '));295}296297async copyKeybindingCommand(keybinding: IKeybindingItemEntry): Promise<void> {298this.selectEntry(keybinding);299await this.clipboardService.writeText(keybinding.keybindingItem.command);300}301302async copyKeybindingCommandTitle(keybinding: IKeybindingItemEntry): Promise<void> {303this.selectEntry(keybinding);304await this.clipboardService.writeText(keybinding.keybindingItem.commandLabel);305}306307focusSearch(): void {308this.searchWidget.focus();309}310311search(filter: string): void {312this.focusSearch();313this.searchWidget.setValue(filter);314this.selectEntry(0);315}316317clearSearchResults(): void {318this.searchWidget.clear();319}320321showSimilarKeybindings(keybindingEntry: IKeybindingItemEntry): void {322const value = `"${keybindingEntry.keybindingItem.keybinding.getAriaLabel()}"`;323if (value !== this.searchWidget.getValue()) {324this.searchWidget.setValue(value);325}326}327328private createAriaLabelElement(parent: HTMLElement): void {329this.ariaLabelElement = DOM.append(parent, DOM.$(''));330this.ariaLabelElement.setAttribute('id', 'keybindings-editor-aria-label-element');331this.ariaLabelElement.setAttribute('aria-live', 'assertive');332}333334private createOverlayContainer(parent: HTMLElement): void {335this.overlayContainer = DOM.append(parent, $('.overlay-container'));336this.overlayContainer.style.position = 'absolute';337this.overlayContainer.style.zIndex = '40'; // has to greater than sash z-index which is 35338this.defineKeybindingWidget = this._register(this.instantiationService.createInstance(DefineKeybindingWidget, this.overlayContainer));339this._register(this.defineKeybindingWidget.onDidChange(keybindingStr => this.defineKeybindingWidget.printExisting(this.keybindingsEditorModel!.fetch(`"${keybindingStr}"`).length)));340this._register(this.defineKeybindingWidget.onShowExistingKeybidings(keybindingStr => this.searchWidget.setValue(`"${keybindingStr}"`)));341this.hideOverlayContainer();342}343344private showOverlayContainer() {345this.overlayContainer.style.display = 'block';346}347348private hideOverlayContainer() {349this.overlayContainer.style.display = 'none';350}351352private createHeader(parent: HTMLElement): void {353this.headerContainer = DOM.append(parent, $('.keybindings-header'));354const fullTextSearchPlaceholder = localize('SearchKeybindings.FullTextSearchPlaceholder', "Type to search in keybindings");355const keybindingsSearchPlaceholder = localize('SearchKeybindings.KeybindingsSearchPlaceholder', "Recording Keys. Press Escape to exit");356357const clearInputAction = this._register(new Action(KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Keybindings Search Input"), ThemeIcon.asClassName(preferencesClearInputIcon), false, async () => this.clearSearchResults()));358359const searchContainer = DOM.append(this.headerContainer, $('.search-container'));360this.searchWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, searchContainer, {361ariaLabel: fullTextSearchPlaceholder,362placeholder: fullTextSearchPlaceholder,363focusKey: this.searchFocusContextKey,364ariaLabelledBy: 'keybindings-editor-aria-label-element',365recordEnter: true,366quoteRecordedKeys: true,367history: new Set<string>(this.getMemento(StorageScope.PROFILE, StorageTarget.USER)['searchHistory'] ?? []),368inputBoxStyles: getInputBoxStyle({369inputBorder: settingsTextInputBorder370})371}));372this._register(this.searchWidget.onDidChange(searchValue => {373clearInputAction.enabled = !!searchValue;374this.delayedFiltering.trigger(() => this.filterKeybindings());375this.updateSearchOptions();376}));377this._register(this.searchWidget.onEscape(() => this.recordKeysAction.checked = false));378379this.actionsContainer = DOM.append(searchContainer, DOM.$('.keybindings-search-actions-container'));380const recordingBadge = this.createRecordingBadge(this.actionsContainer);381382this._register(this.sortByPrecedenceAction.onDidChange(e => {383if (e.checked !== undefined) {384this.renderKeybindingsEntries(false);385}386this.updateSearchOptions();387}));388389this._register(this.recordKeysAction.onDidChange(e => {390if (e.checked !== undefined) {391recordingBadge.classList.toggle('disabled', !e.checked);392if (e.checked) {393this.searchWidget.inputBox.setPlaceHolder(keybindingsSearchPlaceholder);394this.searchWidget.inputBox.setAriaLabel(keybindingsSearchPlaceholder);395this.searchWidget.startRecordingKeys();396this.searchWidget.focus();397} else {398this.searchWidget.inputBox.setPlaceHolder(fullTextSearchPlaceholder);399this.searchWidget.inputBox.setAriaLabel(fullTextSearchPlaceholder);400this.searchWidget.stopRecordingKeys();401this.searchWidget.focus();402}403this.updateSearchOptions();404}405}));406407const actions = [this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction];408const toolBar = this._register(new ToolBar(this.actionsContainer, this.contextMenuService, {409actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {410if (action.id === this.sortByPrecedenceAction.id || action.id === this.recordKeysAction.id) {411return new ToggleActionViewItem(null, action, { ...options, keybinding: this.keybindingsService.lookupKeybinding(action.id)?.getLabel(), toggleStyles: defaultToggleStyles });412}413return undefined;414},415getKeyBinding: action => this.keybindingsService.lookupKeybinding(action.id)416}));417toolBar.setActions(actions);418this._register(this.keybindingsService.onDidUpdateKeybindings(() => toolBar.setActions(actions)));419}420421private updateSearchOptions(): void {422const keybindingsEditorInput = this.input as KeybindingsEditorInput;423if (keybindingsEditorInput) {424keybindingsEditorInput.searchOptions = {425searchValue: this.searchWidget.getValue(),426recordKeybindings: !!this.recordKeysAction.checked,427sortByPrecedence: !!this.sortByPrecedenceAction.checked428};429}430}431432private createRecordingBadge(container: HTMLElement): HTMLElement {433const recordingBadge = DOM.append(container, DOM.$('.recording-badge.monaco-count-badge.long.disabled'));434recordingBadge.textContent = localize('recording', "Recording Keys");435436recordingBadge.style.backgroundColor = asCssVariable(badgeBackground);437recordingBadge.style.color = asCssVariable(badgeForeground);438recordingBadge.style.border = `1px solid ${asCssVariable(contrastBorder)}`;439440return recordingBadge;441}442443private layoutSearchWidget(dimension: DOM.Dimension): void {444this.searchWidget.layout(dimension);445this.headerContainer.classList.toggle('small', dimension.width < 400);446this.searchWidget.inputBox.inputElement.style.paddingRight = `${DOM.getTotalWidth(this.actionsContainer) + 12}px`;447}448449private createBody(parent: HTMLElement): void {450const bodyContainer = DOM.append(parent, $('.keybindings-body'));451this.createTable(bodyContainer);452}453454private createTable(parent: HTMLElement): void {455this.keybindingsTableContainer = DOM.append(parent, $('.keybindings-table-container'));456this.keybindingsTable = this._register(this.instantiationService.createInstance(WorkbenchTable,457'KeybindingsEditor',458this.keybindingsTableContainer,459new Delegate(),460[461{462label: '',463tooltip: '',464weight: 0,465minimumWidth: 40,466maximumWidth: 40,467templateId: ActionsColumnRenderer.TEMPLATE_ID,468project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }469},470{471label: localize('command', "Command"),472tooltip: '',473weight: 0.3,474templateId: CommandColumnRenderer.TEMPLATE_ID,475project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }476},477{478label: localize('keybinding', "Keybinding"),479tooltip: '',480weight: 0.2,481templateId: KeybindingColumnRenderer.TEMPLATE_ID,482project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }483},484{485label: localize('when', "When"),486tooltip: '',487weight: 0.35,488templateId: WhenColumnRenderer.TEMPLATE_ID,489project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }490},491{492label: localize('source', "Source"),493tooltip: '',494weight: 0.15,495templateId: SourceColumnRenderer.TEMPLATE_ID,496project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }497},498],499[500this.instantiationService.createInstance(ActionsColumnRenderer, this),501this.instantiationService.createInstance(CommandColumnRenderer),502this.instantiationService.createInstance(KeybindingColumnRenderer),503this.instantiationService.createInstance(WhenColumnRenderer, this),504this.instantiationService.createInstance(SourceColumnRenderer),505],506{507identityProvider: { getId: (e: IKeybindingItemEntry) => e.id },508horizontalScrolling: false,509accessibilityProvider: new AccessibilityProvider(this.configurationService),510keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IKeybindingItemEntry) => e.keybindingItem.commandLabel || e.keybindingItem.command },511overrideStyles: {512listBackground: editorBackground513},514multipleSelectionSupport: false,515setRowLineHeight: false,516openOnSingleClick: false,517transformOptimization: false // disable transform optimization as it causes the editor overflow widgets to be mispositioned518}519)) as WorkbenchTable<IKeybindingItemEntry>;520521this._register(this.keybindingsTable.onContextMenu(e => this.onContextMenu(e)));522this._register(this.keybindingsTable.onDidChangeFocus(e => this.onFocusChange()));523this._register(this.keybindingsTable.onDidFocus(() => {524this.keybindingsTable.getHTMLElement().classList.add('focused');525this.onFocusChange();526}));527this._register(this.keybindingsTable.onDidBlur(() => {528this.keybindingsTable.getHTMLElement().classList.remove('focused');529this.keybindingFocusContextKey.reset();530}));531this._register(this.keybindingsTable.onDidOpen((e) => {532// stop double click action on the input #148493533if (e.browserEvent?.defaultPrevented) {534return;535}536const activeKeybindingEntry = this.activeKeybindingEntry;537if (activeKeybindingEntry) {538this.defineKeybinding(activeKeybindingEntry, false);539}540}));541542DOM.append(this.keybindingsTableContainer, this.overflowWidgetsDomNode);543}544545private async render(preserveFocus: boolean): Promise<void> {546if (this.input) {547const input: KeybindingsEditorInput = this.input as KeybindingsEditorInput;548this.keybindingsEditorModel = await input.resolve();549await this.keybindingsEditorModel.resolve(this.getActionsLabels());550this.renderKeybindingsEntries(false, preserveFocus);551if (input.searchOptions) {552this.recordKeysAction.checked = input.searchOptions.recordKeybindings;553this.sortByPrecedenceAction.checked = input.searchOptions.sortByPrecedence;554this.searchWidget.setValue(input.searchOptions.searchValue);555} else {556this.updateSearchOptions();557}558}559}560561private getActionsLabels(): Map<string, string> {562const actionsLabels: Map<string, string> = new Map<string, string>();563for (const editorAction of EditorExtensionsRegistry.getEditorActions()) {564actionsLabels.set(editorAction.id, editorAction.label);565}566for (const menuItem of MenuRegistry.getMenuItems(MenuId.CommandPalette)) {567if (isIMenuItem(menuItem)) {568const title = typeof menuItem.command.title === 'string' ? menuItem.command.title : menuItem.command.title.value;569const category = menuItem.command.category ? typeof menuItem.command.category === 'string' ? menuItem.command.category : menuItem.command.category.value : undefined;570actionsLabels.set(menuItem.command.id, category ? `${category}: ${title}` : title);571}572}573return actionsLabels;574}575576private filterKeybindings(): void {577this.renderKeybindingsEntries(this.searchWidget.hasFocus());578this.searchHistoryDelayer.trigger(() => {579this.searchWidget.inputBox.addToHistory();580this.getMemento(StorageScope.PROFILE, StorageTarget.USER)['searchHistory'] = this.searchWidget.inputBox.getHistory();581this.saveState();582});583}584585public clearKeyboardShortcutSearchHistory(): void {586this.searchWidget.inputBox.clearHistory();587this.getMemento(StorageScope.PROFILE, StorageTarget.USER)['searchHistory'] = this.searchWidget.inputBox.getHistory();588this.saveState();589}590591private renderKeybindingsEntries(reset: boolean, preserveFocus?: boolean): void {592if (this.keybindingsEditorModel) {593const filter = this.searchWidget.getValue();594const keybindingsEntries: IKeybindingItemEntry[] = this.keybindingsEditorModel.fetch(filter, this.sortByPrecedenceAction.checked);595this.accessibilityService.alert(localize('foundResults', "{0} results", keybindingsEntries.length));596this.ariaLabelElement.setAttribute('aria-label', this.getAriaLabel(keybindingsEntries));597598if (keybindingsEntries.length === 0) {599this.latestEmptyFilters.push(filter);600}601const currentSelectedIndex = this.keybindingsTable.getSelection()[0];602this.tableEntries = keybindingsEntries;603this.keybindingsTable.splice(0, this.keybindingsTable.length, this.tableEntries);604this.layoutKeybindingsTable();605606if (reset) {607this.keybindingsTable.setSelection([]);608this.keybindingsTable.setFocus([]);609} else {610if (this.unAssignedKeybindingItemToRevealAndFocus) {611const index = this.getNewIndexOfUnassignedKeybinding(this.unAssignedKeybindingItemToRevealAndFocus);612if (index !== -1) {613this.keybindingsTable.reveal(index, 0.2);614this.selectEntry(index);615}616this.unAssignedKeybindingItemToRevealAndFocus = null;617} else if (currentSelectedIndex !== -1 && currentSelectedIndex < this.tableEntries.length) {618this.selectEntry(currentSelectedIndex, preserveFocus);619} else if (this.editorService.activeEditorPane === this && !preserveFocus) {620this.focus();621}622}623}624}625626private getAriaLabel(keybindingsEntries: IKeybindingItemEntry[]): string {627if (this.sortByPrecedenceAction.checked) {628return localize('show sorted keybindings', "Showing {0} Keybindings in precedence order", keybindingsEntries.length);629} else {630return localize('show keybindings', "Showing {0} Keybindings in alphabetical order", keybindingsEntries.length);631}632}633634private layoutKeybindingsTable(): void {635if (!this.dimension) {636return;637}638639const tableHeight = this.dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 12 /*padding*/);640this.keybindingsTableContainer.style.height = `${tableHeight}px`;641this.keybindingsTable.layout(tableHeight);642}643644private getIndexOf(listEntry: IKeybindingItemEntry): number {645const index = this.tableEntries.indexOf(listEntry);646if (index === -1) {647for (let i = 0; i < this.tableEntries.length; i++) {648if (this.tableEntries[i].id === listEntry.id) {649return i;650}651}652}653return index;654}655656private getNewIndexOfUnassignedKeybinding(unassignedKeybinding: IKeybindingItemEntry): number {657for (let index = 0; index < this.tableEntries.length; index++) {658const entry = this.tableEntries[index];659if (entry.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {660const keybindingItemEntry = (<IKeybindingItemEntry>entry);661if (keybindingItemEntry.keybindingItem.command === unassignedKeybinding.keybindingItem.command) {662return index;663}664}665}666return -1;667}668669private selectEntry(keybindingItemEntry: IKeybindingItemEntry | number, focus: boolean = true): void {670const index = typeof keybindingItemEntry === 'number' ? keybindingItemEntry : this.getIndexOf(keybindingItemEntry);671if (index !== -1 && index < this.keybindingsTable.length) {672if (focus) {673this.keybindingsTable.domFocus();674this.keybindingsTable.setFocus([index]);675}676this.keybindingsTable.setSelection([index]);677}678}679680focusKeybindings(): void {681this.keybindingsTable.domFocus();682const currentFocusIndices = this.keybindingsTable.getFocus();683this.keybindingsTable.setFocus([currentFocusIndices.length ? currentFocusIndices[0] : 0]);684}685686selectKeybinding(keybindingItemEntry: IKeybindingItemEntry): void {687this.selectEntry(keybindingItemEntry);688}689690recordSearchKeys(): void {691this.recordKeysAction.checked = true;692}693694toggleSortByPrecedence(): void {695this.sortByPrecedenceAction.checked = !this.sortByPrecedenceAction.checked;696}697698private onContextMenu(e: IListContextMenuEvent<IKeybindingItemEntry>): void {699if (!e.element) {700return;701}702703if (e.element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {704const keybindingItemEntry = <IKeybindingItemEntry>e.element;705this.selectEntry(keybindingItemEntry);706this.contextMenuService.showContextMenu({707getAnchor: () => e.anchor,708getActions: () => [709this.createCopyAction(keybindingItemEntry),710this.createCopyCommandAction(keybindingItemEntry),711this.createCopyCommandTitleAction(keybindingItemEntry),712new Separator(),713...(keybindingItemEntry.keybindingItem.keybinding714? [this.createDefineKeybindingAction(keybindingItemEntry), this.createAddKeybindingAction(keybindingItemEntry)]715: [this.createDefineKeybindingAction(keybindingItemEntry)]),716new Separator(),717this.createRemoveAction(keybindingItemEntry),718this.createResetAction(keybindingItemEntry),719new Separator(),720this.createDefineWhenExpressionAction(keybindingItemEntry),721new Separator(),722this.createShowConflictsAction(keybindingItemEntry)]723});724}725}726727private onFocusChange(): void {728this.keybindingFocusContextKey.reset();729const element = this.keybindingsTable.getFocusedElements()[0];730if (!element) {731return;732}733if (element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {734this.keybindingFocusContextKey.set(true);735}736}737738private createDefineKeybindingAction(keybindingItemEntry: IKeybindingItemEntry): IAction {739return <IAction>{740label: keybindingItemEntry.keybindingItem.keybinding ? localize('changeLabel', "Change Keybinding...") : localize('addLabel', "Add Keybinding..."),741enabled: true,742id: KEYBINDINGS_EDITOR_COMMAND_DEFINE,743run: () => this.defineKeybinding(keybindingItemEntry, false)744};745}746747private createAddKeybindingAction(keybindingItemEntry: IKeybindingItemEntry): IAction {748return <IAction>{749label: localize('addLabel', "Add Keybinding..."),750enabled: true,751id: KEYBINDINGS_EDITOR_COMMAND_ADD,752run: () => this.defineKeybinding(keybindingItemEntry, true)753};754}755756private createDefineWhenExpressionAction(keybindingItemEntry: IKeybindingItemEntry): IAction {757return <IAction>{758label: localize('editWhen', "Change When Expression"),759enabled: !!keybindingItemEntry.keybindingItem.keybinding,760id: KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN,761run: () => this.defineWhenExpression(keybindingItemEntry)762};763}764765private createRemoveAction(keybindingItem: IKeybindingItemEntry): IAction {766return <IAction>{767label: localize('removeLabel', "Remove Keybinding"),768enabled: !!keybindingItem.keybindingItem.keybinding,769id: KEYBINDINGS_EDITOR_COMMAND_REMOVE,770run: () => this.removeKeybinding(keybindingItem)771};772}773774private createResetAction(keybindingItem: IKeybindingItemEntry): IAction {775return <IAction>{776label: localize('resetLabel', "Reset Keybinding"),777enabled: !keybindingItem.keybindingItem.keybindingItem.isDefault,778id: KEYBINDINGS_EDITOR_COMMAND_RESET,779run: () => this.resetKeybinding(keybindingItem)780};781}782783private createShowConflictsAction(keybindingItem: IKeybindingItemEntry): IAction {784return <IAction>{785label: localize('showSameKeybindings', "Show Same Keybindings"),786enabled: !!keybindingItem.keybindingItem.keybinding,787id: KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR,788run: () => this.showSimilarKeybindings(keybindingItem)789};790}791792private createCopyAction(keybindingItem: IKeybindingItemEntry): IAction {793return <IAction>{794label: localize('copyLabel', "Copy"),795enabled: true,796id: KEYBINDINGS_EDITOR_COMMAND_COPY,797run: () => this.copyKeybinding(keybindingItem)798};799}800801private createCopyCommandAction(keybinding: IKeybindingItemEntry): IAction {802return <IAction>{803label: localize('copyCommandLabel', "Copy Command ID"),804enabled: true,805id: KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND,806run: () => this.copyKeybindingCommand(keybinding)807};808}809810private createCopyCommandTitleAction(keybinding: IKeybindingItemEntry): IAction {811return <IAction>{812label: localize('copyCommandTitleLabel', "Copy Command Title"),813enabled: !!keybinding.keybindingItem.commandLabel,814id: KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE,815run: () => this.copyKeybindingCommandTitle(keybinding)816};817}818819private onKeybindingEditingError(error: any): void {820this.notificationService.error(typeof error === 'string' ? error : localize('error', "Error '{0}' while editing the keybinding. Please open 'keybindings.json' file and check for errors.", `${error}`));821}822}823824class Delegate implements ITableVirtualDelegate<IKeybindingItemEntry> {825826readonly headerRowHeight = 30;827828getHeight(element: IKeybindingItemEntry) {829if (element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {830const commandIdMatched = (<IKeybindingItemEntry>element).keybindingItem.commandLabel && (<IKeybindingItemEntry>element).commandIdMatches;831const commandDefaultLabelMatched = !!(<IKeybindingItemEntry>element).commandDefaultLabelMatches;832const extensionIdMatched = !!(<IKeybindingItemEntry>element).extensionIdMatches;833if (commandIdMatched && commandDefaultLabelMatched) {834return 60;835}836if (extensionIdMatched || commandIdMatched || commandDefaultLabelMatched) {837return 40;838}839}840return 24;841}842843}844845interface IActionsColumnTemplateData {846readonly actionBar: ActionBar;847}848849class ActionsColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IActionsColumnTemplateData> {850851static readonly TEMPLATE_ID = 'actions';852853readonly templateId: string = ActionsColumnRenderer.TEMPLATE_ID;854855constructor(856private readonly keybindingsEditor: KeybindingsEditor,857@IKeybindingService private readonly keybindingsService: IKeybindingService858) {859}860861renderTemplate(container: HTMLElement): IActionsColumnTemplateData {862const element = DOM.append(container, $('.actions'));863const actionBar = new ActionBar(element);864return { actionBar };865}866867renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: IActionsColumnTemplateData): void {868templateData.actionBar.clear();869const actions: IAction[] = [];870if (keybindingItemEntry.keybindingItem.keybinding) {871actions.push(this.createEditAction(keybindingItemEntry));872} else {873actions.push(this.createAddAction(keybindingItemEntry));874}875templateData.actionBar.push(actions, { icon: true });876}877878private createEditAction(keybindingItemEntry: IKeybindingItemEntry): IAction {879const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE);880return <IAction>{881class: ThemeIcon.asClassName(keybindingsEditIcon),882enabled: true,883id: 'editKeybinding',884tooltip: keybinding ? localize('editKeybindingLabelWithKey', "Change Keybinding {0}", `(${keybinding.getLabel()})`) : localize('editKeybindingLabel', "Change Keybinding"),885run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry, false)886};887}888889private createAddAction(keybindingItemEntry: IKeybindingItemEntry): IAction {890const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE);891return <IAction>{892class: ThemeIcon.asClassName(keybindingsAddIcon),893enabled: true,894id: 'addKeybinding',895tooltip: keybinding ? localize('addKeybindingLabelWithKey', "Add Keybinding {0}", `(${keybinding.getLabel()})`) : localize('addKeybindingLabel', "Add Keybinding"),896run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry, false)897};898}899900disposeTemplate(templateData: IActionsColumnTemplateData): void {901templateData.actionBar.dispose();902}903904}905906interface ICommandColumnTemplateData {907commandColumn: HTMLElement;908commandColumnHover: IManagedHover;909commandLabelContainer: HTMLElement;910commandLabel: HighlightedLabel;911commandDefaultLabelContainer: HTMLElement;912commandDefaultLabel: HighlightedLabel;913commandIdLabelContainer: HTMLElement;914commandIdLabel: HighlightedLabel;915}916917class CommandColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ICommandColumnTemplateData> {918919static readonly TEMPLATE_ID = 'commands';920921readonly templateId: string = CommandColumnRenderer.TEMPLATE_ID;922923constructor(924@IHoverService private readonly _hoverService: IHoverService925) {926}927928renderTemplate(container: HTMLElement): ICommandColumnTemplateData {929const commandColumn = DOM.append(container, $('.command'));930const commandColumnHover = this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), commandColumn, '');931const commandLabelContainer = DOM.append(commandColumn, $('.command-label'));932const commandLabel = new HighlightedLabel(commandLabelContainer);933const commandDefaultLabelContainer = DOM.append(commandColumn, $('.command-default-label'));934const commandDefaultLabel = new HighlightedLabel(commandDefaultLabelContainer);935const commandIdLabelContainer = DOM.append(commandColumn, $('.command-id.code'));936const commandIdLabel = new HighlightedLabel(commandIdLabelContainer);937return { commandColumn, commandColumnHover, commandLabelContainer, commandLabel, commandDefaultLabelContainer, commandDefaultLabel, commandIdLabelContainer, commandIdLabel };938}939940renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: ICommandColumnTemplateData): void {941const keybindingItem = keybindingItemEntry.keybindingItem;942const commandIdMatched = !!(keybindingItem.commandLabel && keybindingItemEntry.commandIdMatches);943const commandDefaultLabelMatched = !!keybindingItemEntry.commandDefaultLabelMatches;944945templateData.commandColumn.classList.toggle('vertical-align-column', commandIdMatched || commandDefaultLabelMatched);946const title = keybindingItem.commandLabel ? localize('title', "{0} ({1})", keybindingItem.commandLabel, keybindingItem.command) : keybindingItem.command;947templateData.commandColumn.setAttribute('aria-label', title);948templateData.commandColumnHover.update(title);949950if (keybindingItem.commandLabel) {951templateData.commandLabelContainer.classList.remove('hide');952templateData.commandLabel.set(keybindingItem.commandLabel, keybindingItemEntry.commandLabelMatches);953} else {954templateData.commandLabelContainer.classList.add('hide');955templateData.commandLabel.set(undefined);956}957958if (keybindingItemEntry.commandDefaultLabelMatches) {959templateData.commandDefaultLabelContainer.classList.remove('hide');960templateData.commandDefaultLabel.set(keybindingItem.commandDefaultLabel, keybindingItemEntry.commandDefaultLabelMatches);961} else {962templateData.commandDefaultLabelContainer.classList.add('hide');963templateData.commandDefaultLabel.set(undefined);964}965966if (keybindingItemEntry.commandIdMatches || !keybindingItem.commandLabel) {967templateData.commandIdLabelContainer.classList.remove('hide');968templateData.commandIdLabel.set(keybindingItem.command, keybindingItemEntry.commandIdMatches);969} else {970templateData.commandIdLabelContainer.classList.add('hide');971templateData.commandIdLabel.set(undefined);972}973}974975disposeTemplate(templateData: ICommandColumnTemplateData): void {976templateData.commandColumnHover.dispose();977templateData.commandDefaultLabel.dispose();978templateData.commandIdLabel.dispose();979templateData.commandLabel.dispose();980}981}982983interface IKeybindingColumnTemplateData {984keybindingLabel: KeybindingLabel;985}986987class KeybindingColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IKeybindingColumnTemplateData> {988989static readonly TEMPLATE_ID = 'keybindings';990991readonly templateId: string = KeybindingColumnRenderer.TEMPLATE_ID;992993constructor() { }994995renderTemplate(container: HTMLElement): IKeybindingColumnTemplateData {996const element = DOM.append(container, $('.keybinding'));997const keybindingLabel = new KeybindingLabel(DOM.append(element, $('div.keybinding-label')), OS, defaultKeybindingLabelStyles);998return { keybindingLabel };999}10001001renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: IKeybindingColumnTemplateData): void {1002if (keybindingItemEntry.keybindingItem.keybinding) {1003templateData.keybindingLabel.set(keybindingItemEntry.keybindingItem.keybinding, keybindingItemEntry.keybindingMatches);1004} else {1005templateData.keybindingLabel.set(undefined, undefined);1006}1007}10081009disposeTemplate(templateData: IKeybindingColumnTemplateData): void {1010templateData.keybindingLabel.dispose();1011}1012}10131014interface ISourceColumnTemplateData {1015sourceColumn: HTMLElement;1016sourceColumnHover: IManagedHover;1017sourceLabel: HighlightedLabel;1018extensionContainer: HTMLElement;1019extensionLabel: HTMLAnchorElement;1020extensionId: HighlightedLabel;1021disposables: DisposableStore;1022}10231024function onClick(element: HTMLElement, callback: () => void): IDisposable {1025const disposables = new DisposableStore();1026disposables.add(DOM.addDisposableListener(element, DOM.EventType.CLICK, DOM.finalHandler(callback)));1027disposables.add(DOM.addDisposableListener(element, DOM.EventType.KEY_UP, e => {1028const keyboardEvent = new StandardKeyboardEvent(e);1029if (keyboardEvent.equals(KeyCode.Space) || keyboardEvent.equals(KeyCode.Enter)) {1030e.preventDefault();1031e.stopPropagation();1032callback();1033}1034}));1035return disposables;1036}10371038class SourceColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ISourceColumnTemplateData> {10391040static readonly TEMPLATE_ID = 'source';10411042readonly templateId: string = SourceColumnRenderer.TEMPLATE_ID;10431044constructor(1045@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,1046@IHoverService private readonly hoverService: IHoverService,1047) { }10481049renderTemplate(container: HTMLElement): ISourceColumnTemplateData {1050const sourceColumn = DOM.append(container, $('.source'));1051const sourceColumnHover = this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), sourceColumn, '');1052const sourceLabel = new HighlightedLabel(DOM.append(sourceColumn, $('.source-label')));1053const extensionContainer = DOM.append(sourceColumn, $('.extension-container'));1054const extensionLabel = DOM.append<HTMLAnchorElement>(extensionContainer, $('a.extension-label', { tabindex: 0 }));1055const extensionId = new HighlightedLabel(DOM.append(extensionContainer, $('.extension-id-container.code')));1056return { sourceColumn, sourceColumnHover, sourceLabel, extensionLabel, extensionContainer, extensionId, disposables: new DisposableStore() };1057}10581059renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: ISourceColumnTemplateData): void {1060templateData.disposables.clear();1061if (isString(keybindingItemEntry.keybindingItem.source)) {1062templateData.extensionContainer.classList.add('hide');1063templateData.sourceLabel.element.classList.remove('hide');1064templateData.sourceColumnHover.update('');1065templateData.sourceLabel.set(keybindingItemEntry.keybindingItem.source || '-', keybindingItemEntry.sourceMatches);1066} else {1067templateData.extensionContainer.classList.remove('hide');1068templateData.sourceLabel.element.classList.add('hide');1069const extension = keybindingItemEntry.keybindingItem.source;1070const extensionLabel = extension.displayName ?? extension.identifier.value;1071templateData.sourceColumnHover.update(localize('extension label', "Extension ({0})", extensionLabel));1072templateData.extensionLabel.textContent = extensionLabel;1073templateData.disposables.add(onClick(templateData.extensionLabel, () => {1074this.extensionsWorkbenchService.open(extension.identifier.value);1075}));1076if (keybindingItemEntry.extensionIdMatches) {1077templateData.extensionId.element.classList.remove('hide');1078templateData.extensionId.set(extension.identifier.value, keybindingItemEntry.extensionIdMatches);1079} else {1080templateData.extensionId.element.classList.add('hide');1081templateData.extensionId.set(undefined);1082}1083}1084}10851086disposeTemplate(templateData: ISourceColumnTemplateData): void {1087templateData.sourceColumnHover.dispose();1088templateData.disposables.dispose();1089templateData.sourceLabel.dispose();1090templateData.extensionId.dispose();1091}1092}10931094class WhenInputWidget extends Disposable {10951096private readonly input: SuggestEnabledInput;10971098private readonly _onDidAccept = this._register(new Emitter<string>());1099readonly onDidAccept = this._onDidAccept.event;11001101private readonly _onDidReject = this._register(new Emitter<void>());1102readonly onDidReject = this._onDidReject.event;11031104constructor(1105parent: HTMLElement,1106keybindingsEditor: KeybindingsEditor,1107@IInstantiationService instantiationService: IInstantiationService,1108@IContextKeyService contextKeyService: IContextKeyService,1109) {1110super();1111const focusContextKey = CONTEXT_WHEN_FOCUS.bindTo(contextKeyService);1112this.input = this._register(instantiationService.createInstance(SuggestEnabledInput, 'keyboardshortcutseditor#wheninput', parent, {1113provideResults: () => {1114const result = [];1115for (const contextKey of RawContextKey.all()) {1116result.push({ label: contextKey.key, documentation: contextKey.description, detail: contextKey.type, kind: CompletionItemKind.Constant });1117}1118return result;1119},1120triggerCharacters: ['!', ' '],1121wordDefinition: /[a-zA-Z.]+/,1122alwaysShowSuggestions: true,1123}, '', `keyboardshortcutseditor#wheninput`, { focusContextKey, overflowWidgetsDomNode: keybindingsEditor.overflowWidgetsDomNode }));11241125this._register((DOM.addDisposableListener(this.input.element, DOM.EventType.DBLCLICK, e => DOM.EventHelper.stop(e))));1126this._register(toDisposable(() => focusContextKey.reset()));11271128this._register(keybindingsEditor.onAcceptWhenExpression(() => this._onDidAccept.fire(this.input.getValue())));1129this._register(Event.any(keybindingsEditor.onRejectWhenExpression, this.input.onDidBlur)(() => this._onDidReject.fire()));1130}11311132layout(dimension: DOM.Dimension): void {1133this.input.layout(dimension);1134}11351136show(value: string): void {1137this.input.setValue(value);1138this.input.focus(true);1139}11401141}11421143interface IWhenColumnTemplateData {1144readonly element: HTMLElement;1145readonly whenLabelContainer: HTMLElement;1146readonly whenInputContainer: HTMLElement;1147readonly whenLabel: HighlightedLabel;1148readonly disposables: DisposableStore;1149}11501151class WhenColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IWhenColumnTemplateData> {11521153static readonly TEMPLATE_ID = 'when';11541155readonly templateId: string = WhenColumnRenderer.TEMPLATE_ID;11561157constructor(1158private readonly keybindingsEditor: KeybindingsEditor,1159@IHoverService private readonly hoverService: IHoverService,1160@IInstantiationService private readonly instantiationService: IInstantiationService,1161) { }11621163renderTemplate(container: HTMLElement): IWhenColumnTemplateData {1164const element = DOM.append(container, $('.when'));11651166const whenLabelContainer = DOM.append(element, $('div.when-label'));1167const whenLabel = new HighlightedLabel(whenLabelContainer);11681169const whenInputContainer = DOM.append(element, $('div.when-input-container'));11701171return {1172element,1173whenLabelContainer,1174whenLabel,1175whenInputContainer,1176disposables: new DisposableStore(),1177};1178}11791180renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: IWhenColumnTemplateData): void {1181templateData.disposables.clear();1182const whenInputDisposables = templateData.disposables.add(new DisposableStore());1183templateData.disposables.add(this.keybindingsEditor.onDefineWhenExpression(e => {1184if (keybindingItemEntry === e) {1185templateData.element.classList.add('input-mode');11861187const inputWidget = whenInputDisposables.add(this.instantiationService.createInstance(WhenInputWidget, templateData.whenInputContainer, this.keybindingsEditor));1188inputWidget.layout(new DOM.Dimension(templateData.element.parentElement!.clientWidth, 18));1189inputWidget.show(keybindingItemEntry.keybindingItem.when || '');11901191const hideInputWidget = () => {1192whenInputDisposables.clear();1193templateData.element.classList.remove('input-mode');1194templateData.element.parentElement!.style.paddingLeft = '10px';1195DOM.clearNode(templateData.whenInputContainer);1196};11971198whenInputDisposables.add(inputWidget.onDidAccept(value => {1199hideInputWidget();1200this.keybindingsEditor.updateKeybinding(keybindingItemEntry, keybindingItemEntry.keybindingItem.keybinding ? keybindingItemEntry.keybindingItem.keybinding.getUserSettingsLabel() || '' : '', value);1201this.keybindingsEditor.selectKeybinding(keybindingItemEntry);1202}));12031204whenInputDisposables.add(inputWidget.onDidReject(() => {1205hideInputWidget();1206this.keybindingsEditor.selectKeybinding(keybindingItemEntry);1207}));12081209templateData.element.parentElement!.style.paddingLeft = '0px';1210}1211}));12121213templateData.whenLabelContainer.classList.toggle('code', !!keybindingItemEntry.keybindingItem.when);1214templateData.whenLabelContainer.classList.toggle('empty', !keybindingItemEntry.keybindingItem.when);12151216if (keybindingItemEntry.keybindingItem.when) {1217templateData.whenLabel.set(keybindingItemEntry.keybindingItem.when, keybindingItemEntry.whenMatches, keybindingItemEntry.keybindingItem.when);1218templateData.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), templateData.element, keybindingItemEntry.keybindingItem.when));1219} else {1220templateData.whenLabel.set('-');1221}1222}12231224disposeTemplate(templateData: IWhenColumnTemplateData): void {1225templateData.disposables.dispose();1226templateData.whenLabel.dispose();1227}1228}12291230class AccessibilityProvider implements IListAccessibilityProvider<IKeybindingItemEntry> {12311232constructor(private readonly configurationService: IConfigurationService) { }12331234getWidgetAriaLabel(): string {1235return localize('keybindingsLabel', "Keybindings");1236}12371238getAriaLabel({ keybindingItem }: IKeybindingItemEntry): string {1239const ariaLabel = [1240keybindingItem.commandLabel ? keybindingItem.commandLabel : keybindingItem.command,1241keybindingItem.keybinding?.getAriaLabel() || localize('noKeybinding', "No keybinding assigned"),1242keybindingItem.when ? keybindingItem.when : localize('noWhen', "No when context"),1243isString(keybindingItem.source) ? keybindingItem.source : keybindingItem.source.description ?? keybindingItem.source.identifier.value,1244];1245if (this.configurationService.getValue(AccessibilityVerbositySettingId.KeybindingsEditor)) {1246const kbEditorAriaLabel = localize('keyboard shortcuts aria label', "use space or enter to change the keybinding.");1247ariaLabel.push(kbEditorAriaLabel);1248}1249return ariaLabel.join(', ');1250}1251}12521253registerColor('keybindingTable.headerBackground', tableOddRowsBackgroundColor, 'Background color for the keyboard shortcuts table header.');1254registerColor('keybindingTable.rowsBackground', tableOddRowsBackgroundColor, 'Background color for the keyboard shortcuts table alternating rows.');12551256registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {1257const foregroundColor = theme.getColor(foreground);1258if (foregroundColor) {1259const whenForegroundColor = foregroundColor.transparent(.8).makeOpaque(WORKBENCH_BACKGROUND(theme));1260collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table .monaco-table-tr .monaco-table-td .code { color: ${whenForegroundColor}; }`);1261}12621263const listActiveSelectionForegroundColor = theme.getColor(listActiveSelectionForeground);1264const listActiveSelectionBackgroundColor = theme.getColor(listActiveSelectionBackground);1265if (listActiveSelectionForegroundColor && listActiveSelectionBackgroundColor) {1266const whenForegroundColor = listActiveSelectionForegroundColor.transparent(.8).makeOpaque(listActiveSelectionBackgroundColor);1267collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table.focused .monaco-list-row.selected .monaco-table-tr .monaco-table-td .code { color: ${whenForegroundColor}; }`);1268}12691270const listInactiveSelectionForegroundColor = theme.getColor(listInactiveSelectionForeground);1271const listInactiveSelectionBackgroundColor = theme.getColor(listInactiveSelectionBackground);1272if (listInactiveSelectionForegroundColor && listInactiveSelectionBackgroundColor) {1273const whenForegroundColor = listInactiveSelectionForegroundColor.transparent(.8).makeOpaque(listInactiveSelectionBackgroundColor);1274collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table .monaco-list-row.selected .monaco-table-tr .monaco-table-td .code { color: ${whenForegroundColor}; }`);1275}12761277const listFocusForegroundColor = theme.getColor(listFocusForeground);1278const listFocusBackgroundColor = theme.getColor(listFocusBackground);1279if (listFocusForegroundColor && listFocusBackgroundColor) {1280const whenForegroundColor = listFocusForegroundColor.transparent(.8).makeOpaque(listFocusBackgroundColor);1281collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table.focused .monaco-list-row.focused .monaco-table-tr .monaco-table-td .code { color: ${whenForegroundColor}; }`);1282}12831284const listHoverForegroundColor = theme.getColor(listHoverForeground);1285const listHoverBackgroundColor = theme.getColor(listHoverBackground);1286if (listHoverForegroundColor && listHoverBackgroundColor) {1287const whenForegroundColor = listHoverForegroundColor.transparent(.8).makeOpaque(listHoverBackgroundColor);1288collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table.focused .monaco-list-row:hover:not(.focused):not(.selected) .monaco-table-tr .monaco-table-td .code { color: ${whenForegroundColor}; }`);1289}1290});129112921293