Path: blob/main/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts
5267 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, CONTEXT_KEYBINDINGS_SEARCH_HAS_VALUE, 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.$;6869interface IKeybindingsEditorMemento {70searchHistory?: string[];71}7273export class KeybindingsEditor extends EditorPane<IKeybindingsEditorMemento> implements IKeybindingsEditorPane {7475static readonly ID: string = 'workbench.editor.keybindings';7677private _onDefineWhenExpression: Emitter<IKeybindingItemEntry> = this._register(new Emitter<IKeybindingItemEntry>());78readonly onDefineWhenExpression: Event<IKeybindingItemEntry> = this._onDefineWhenExpression.event;7980private _onRejectWhenExpression = this._register(new Emitter<IKeybindingItemEntry>());81readonly onRejectWhenExpression = this._onRejectWhenExpression.event;8283private _onAcceptWhenExpression = this._register(new Emitter<IKeybindingItemEntry>());84readonly onAcceptWhenExpression = this._onAcceptWhenExpression.event;8586private _onLayout: Emitter<void> = this._register(new Emitter<void>());87readonly onLayout: Event<void> = this._onLayout.event;8889private keybindingsEditorModel: KeybindingsEditorModel | null = null;9091private headerContainer!: HTMLElement;92private actionsContainer!: HTMLElement;93private searchWidget!: KeybindingsSearchWidget;94private searchHistoryDelayer: Delayer<void>;9596private overlayContainer!: HTMLElement;97private defineKeybindingWidget!: DefineKeybindingWidget;9899private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry | null = null;100private tableEntries: IKeybindingItemEntry[] = [];101private keybindingsTableContainer!: HTMLElement;102private keybindingsTable!: WorkbenchTable<IKeybindingItemEntry>;103104private dimension: DOM.Dimension | null = null;105private delayedFiltering: Delayer<void>;106private latestEmptyFilters: string[] = [];107private keybindingsEditorContextKey: IContextKey<boolean>;108private keybindingFocusContextKey: IContextKey<boolean>;109private searchFocusContextKey: IContextKey<boolean>;110private searchHasValueContextKey: IContextKey<boolean>;111112private readonly sortByPrecedenceAction: Action;113private readonly recordKeysAction: Action;114115private ariaLabelElement!: HTMLElement;116readonly overflowWidgetsDomNode: HTMLElement;117118constructor(119group: IEditorGroup,120@ITelemetryService telemetryService: ITelemetryService,121@IThemeService themeService: IThemeService,122@IKeybindingService private readonly keybindingsService: IKeybindingService,123@IContextMenuService private readonly contextMenuService: IContextMenuService,124@IKeybindingEditingService private readonly keybindingEditingService: IKeybindingEditingService,125@IContextKeyService private readonly contextKeyService: IContextKeyService,126@INotificationService private readonly notificationService: INotificationService,127@IClipboardService private readonly clipboardService: IClipboardService,128@IInstantiationService private readonly instantiationService: IInstantiationService,129@IEditorService private readonly editorService: IEditorService,130@IStorageService storageService: IStorageService,131@IConfigurationService private readonly configurationService: IConfigurationService,132@IAccessibilityService private readonly accessibilityService: IAccessibilityService133) {134super(KeybindingsEditor.ID, group, telemetryService, themeService, storageService);135this.delayedFiltering = this._register(new Delayer<void>(300));136this._register(keybindingsService.onDidUpdateKeybindings(() => this.render(!!this.keybindingFocusContextKey.get())));137138this.keybindingsEditorContextKey = CONTEXT_KEYBINDINGS_EDITOR.bindTo(this.contextKeyService);139this.searchFocusContextKey = CONTEXT_KEYBINDINGS_SEARCH_FOCUS.bindTo(this.contextKeyService);140this.keybindingFocusContextKey = CONTEXT_KEYBINDING_FOCUS.bindTo(this.contextKeyService);141this.searchHasValueContextKey = CONTEXT_KEYBINDINGS_SEARCH_HAS_VALUE.bindTo(this.contextKeyService);142this.searchHistoryDelayer = this._register(new Delayer<void>(500));143144this.recordKeysAction = this._register(new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, localize('recordKeysLabel', "Record Keys"), ThemeIcon.asClassName(keybindingsRecordKeysIcon)));145this.recordKeysAction.checked = false;146147this.sortByPrecedenceAction = this._register(new Action(KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, localize('sortByPrecedeneLabel', "Sort by Precedence (Highest first)"), ThemeIcon.asClassName(keybindingsSortIcon)));148this.sortByPrecedenceAction.checked = false;149this.overflowWidgetsDomNode = $('.keybindings-overflow-widgets-container.monaco-editor');150}151152override create(parent: HTMLElement): void {153super.create(parent);154this._register(registerNavigableContainer({155name: 'keybindingsEditor',156focusNotifiers: [this],157focusNextWidget: () => {158if (this.searchWidget.hasFocus()) {159this.focusKeybindings();160}161},162focusPreviousWidget: () => {163if (!this.searchWidget.hasFocus()) {164this.focusSearch();165}166}167}));168}169170protected createEditor(parent: HTMLElement): void {171const keybindingsEditorElement = DOM.append(parent, $('div', { class: 'keybindings-editor' }));172173this.createAriaLabelElement(keybindingsEditorElement);174this.createOverlayContainer(keybindingsEditorElement);175this.createHeader(keybindingsEditorElement);176this.createBody(keybindingsEditorElement);177}178179override setInput(input: KeybindingsEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {180this.keybindingsEditorContextKey.set(true);181return super.setInput(input, options, context, token)182.then(() => this.render(!!(options && options.preserveFocus)));183}184185override clearInput(): void {186super.clearInput();187this.keybindingsEditorContextKey.reset();188this.keybindingFocusContextKey.reset();189}190191layout(dimension: DOM.Dimension): void {192this.dimension = dimension;193this.layoutSearchWidget(dimension);194195this.overlayContainer.style.width = dimension.width + 'px';196this.overlayContainer.style.height = dimension.height + 'px';197this.defineKeybindingWidget.layout(this.dimension);198199this.layoutKeybindingsTable();200this._onLayout.fire();201}202203override focus(): void {204super.focus();205206const activeKeybindingEntry = this.activeKeybindingEntry;207if (activeKeybindingEntry) {208this.selectEntry(activeKeybindingEntry);209} else if (!isIOS) {210this.searchWidget.focus();211}212}213214get activeKeybindingEntry(): IKeybindingItemEntry | null {215const focusedElement = this.keybindingsTable.getFocusedElements()[0];216return focusedElement && focusedElement.templateId === KEYBINDING_ENTRY_TEMPLATE_ID ? <IKeybindingItemEntry>focusedElement : null;217}218219async defineKeybinding(keybindingEntry: IKeybindingItemEntry, add: boolean): Promise<void> {220this.selectEntry(keybindingEntry);221this.showOverlayContainer();222try {223const key = await this.defineKeybindingWidget.define();224if (key) {225await this.updateKeybinding(keybindingEntry, key, keybindingEntry.keybindingItem.when, add);226}227} catch (error) {228this.onKeybindingEditingError(error);229} finally {230this.hideOverlayContainer();231this.selectEntry(keybindingEntry);232}233}234235defineWhenExpression(keybindingEntry: IKeybindingItemEntry): void {236if (keybindingEntry.keybindingItem.keybinding) {237this.selectEntry(keybindingEntry);238this._onDefineWhenExpression.fire(keybindingEntry);239}240}241242rejectWhenExpression(keybindingEntry: IKeybindingItemEntry): void {243this._onRejectWhenExpression.fire(keybindingEntry);244}245246acceptWhenExpression(keybindingEntry: IKeybindingItemEntry): void {247this._onAcceptWhenExpression.fire(keybindingEntry);248}249250async updateKeybinding(keybindingEntry: IKeybindingItemEntry, key: string, when: string | undefined, add?: boolean): Promise<void> {251const currentKey = keybindingEntry.keybindingItem.keybinding ? keybindingEntry.keybindingItem.keybinding.getUserSettingsLabel() : '';252if (currentKey !== key || keybindingEntry.keybindingItem.when !== when) {253if (add) {254await this.keybindingEditingService.addKeybinding(keybindingEntry.keybindingItem.keybindingItem, key, when || undefined);255} else {256await this.keybindingEditingService.editKeybinding(keybindingEntry.keybindingItem.keybindingItem, key, when || undefined);257}258if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering259this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry;260}261}262}263264async removeKeybinding(keybindingEntry: IKeybindingItemEntry): Promise<void> {265this.selectEntry(keybindingEntry);266if (keybindingEntry.keybindingItem.keybinding) { // This should be a pre-condition267try {268await this.keybindingEditingService.removeKeybinding(keybindingEntry.keybindingItem.keybindingItem);269this.focus();270} catch (error) {271this.onKeybindingEditingError(error);272this.selectEntry(keybindingEntry);273}274}275}276277async resetKeybinding(keybindingEntry: IKeybindingItemEntry): Promise<void> {278this.selectEntry(keybindingEntry);279try {280await this.keybindingEditingService.resetKeybinding(keybindingEntry.keybindingItem.keybindingItem);281if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering282this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry;283}284this.selectEntry(keybindingEntry);285} catch (error) {286this.onKeybindingEditingError(error);287this.selectEntry(keybindingEntry);288}289}290291async copyKeybinding(keybinding: IKeybindingItemEntry): Promise<void> {292this.selectEntry(keybinding);293const userFriendlyKeybinding: IUserFriendlyKeybinding = {294key: keybinding.keybindingItem.keybinding ? keybinding.keybindingItem.keybinding.getUserSettingsLabel() || '' : '',295command: keybinding.keybindingItem.command296};297if (keybinding.keybindingItem.when) {298userFriendlyKeybinding.when = keybinding.keybindingItem.when;299}300await this.clipboardService.writeText(JSON.stringify(userFriendlyKeybinding, null, ' '));301}302303async copyKeybindingCommand(keybinding: IKeybindingItemEntry): Promise<void> {304this.selectEntry(keybinding);305await this.clipboardService.writeText(keybinding.keybindingItem.command);306}307308async copyKeybindingCommandTitle(keybinding: IKeybindingItemEntry): Promise<void> {309this.selectEntry(keybinding);310await this.clipboardService.writeText(keybinding.keybindingItem.commandLabel);311}312313focusSearch(): void {314this.searchWidget.focus();315}316317search(filter: string): void {318this.focusSearch();319this.searchWidget.setValue(filter);320this.selectEntry(0);321}322323clearSearchResults(): void {324this.searchWidget.clear();325this.searchHasValueContextKey.set(false);326}327328showSimilarKeybindings(keybindingEntry: IKeybindingItemEntry): void {329const value = `"${keybindingEntry.keybindingItem.keybinding.getAriaLabel()}"`;330if (value !== this.searchWidget.getValue()) {331this.searchWidget.setValue(value);332}333}334335private createAriaLabelElement(parent: HTMLElement): void {336this.ariaLabelElement = DOM.append(parent, DOM.$(''));337this.ariaLabelElement.setAttribute('id', 'keybindings-editor-aria-label-element');338this.ariaLabelElement.setAttribute('aria-live', 'assertive');339}340341private createOverlayContainer(parent: HTMLElement): void {342this.overlayContainer = DOM.append(parent, $('.overlay-container'));343this.overlayContainer.style.position = 'absolute';344this.overlayContainer.style.zIndex = '40'; // has to greater than sash z-index which is 35345this.defineKeybindingWidget = this._register(this.instantiationService.createInstance(DefineKeybindingWidget, this.overlayContainer));346this._register(this.defineKeybindingWidget.onDidChange(keybindingStr => this.defineKeybindingWidget.printExisting(this.keybindingsEditorModel!.fetch(`"${keybindingStr}"`).length)));347this._register(this.defineKeybindingWidget.onShowExistingKeybidings(keybindingStr => this.searchWidget.setValue(`"${keybindingStr}"`)));348this.hideOverlayContainer();349}350351private showOverlayContainer() {352this.overlayContainer.style.display = 'block';353}354355private hideOverlayContainer() {356this.overlayContainer.style.display = 'none';357}358359private createHeader(parent: HTMLElement): void {360this.headerContainer = DOM.append(parent, $('.keybindings-header'));361const fullTextSearchPlaceholder = localize('SearchKeybindings.FullTextSearchPlaceholder', "Type to search in keybindings");362const keybindingsSearchPlaceholder = localize('SearchKeybindings.KeybindingsSearchPlaceholder', "Recording Keys. Press Escape to exit");363364const clearInputAction = this._register(new Action(KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Keybindings Search Input"), ThemeIcon.asClassName(preferencesClearInputIcon), false, async () => this.clearSearchResults()));365366const searchContainer = DOM.append(this.headerContainer, $('.search-container'));367this.searchWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, searchContainer, {368ariaLabel: fullTextSearchPlaceholder,369placeholder: fullTextSearchPlaceholder,370focusKey: this.searchFocusContextKey,371ariaLabelledBy: 'keybindings-editor-aria-label-element',372recordEnter: true,373quoteRecordedKeys: true,374history: new Set<string>((this.getMemento(StorageScope.PROFILE, StorageTarget.USER)).searchHistory ?? []),375inputBoxStyles: getInputBoxStyle({376inputBorder: settingsTextInputBorder377})378}));379this._register(this.searchWidget.onDidChange(searchValue => {380const hasValue = !!searchValue;381clearInputAction.enabled = hasValue;382this.searchHasValueContextKey.set(hasValue);383this.delayedFiltering.trigger(() => this.filterKeybindings());384this.updateSearchOptions();385}));386this._register(this.searchWidget.onEscape(() => this.recordKeysAction.checked = false));387388this.actionsContainer = DOM.append(searchContainer, DOM.$('.keybindings-search-actions-container'));389const recordingBadge = this.createRecordingBadge(this.actionsContainer);390391this._register(this.sortByPrecedenceAction.onDidChange(e => {392if (e.checked !== undefined) {393this.renderKeybindingsEntries(false);394}395this.updateSearchOptions();396}));397398this._register(this.recordKeysAction.onDidChange(e => {399if (e.checked !== undefined) {400recordingBadge.classList.toggle('disabled', !e.checked);401if (e.checked) {402this.searchWidget.inputBox.setPlaceHolder(keybindingsSearchPlaceholder);403this.searchWidget.inputBox.setAriaLabel(keybindingsSearchPlaceholder);404this.searchWidget.startRecordingKeys();405this.searchWidget.focus();406} else {407this.searchWidget.inputBox.setPlaceHolder(fullTextSearchPlaceholder);408this.searchWidget.inputBox.setAriaLabel(fullTextSearchPlaceholder);409this.searchWidget.stopRecordingKeys();410this.searchWidget.focus();411}412this.updateSearchOptions();413}414}));415416const actions = [this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction];417const toolBar = this._register(new ToolBar(this.actionsContainer, this.contextMenuService, {418actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {419if (action.id === this.sortByPrecedenceAction.id || action.id === this.recordKeysAction.id) {420return new ToggleActionViewItem(null, action, { ...options, keybinding: this.keybindingsService.lookupKeybinding(action.id)?.getLabel(), toggleStyles: defaultToggleStyles });421}422return undefined;423},424getKeyBinding: action => this.keybindingsService.lookupKeybinding(action.id)425}));426toolBar.setActions(actions);427this._register(this.keybindingsService.onDidUpdateKeybindings(() => toolBar.setActions(actions)));428}429430private updateSearchOptions(): void {431const keybindingsEditorInput = this.input as KeybindingsEditorInput;432if (keybindingsEditorInput) {433keybindingsEditorInput.searchOptions = {434searchValue: this.searchWidget.getValue(),435recordKeybindings: !!this.recordKeysAction.checked,436sortByPrecedence: !!this.sortByPrecedenceAction.checked437};438}439}440441private createRecordingBadge(container: HTMLElement): HTMLElement {442const recordingBadge = DOM.append(container, DOM.$('.recording-badge.monaco-count-badge.long.disabled'));443recordingBadge.textContent = localize('recording', "Recording Keys");444445recordingBadge.style.backgroundColor = asCssVariable(badgeBackground);446recordingBadge.style.color = asCssVariable(badgeForeground);447recordingBadge.style.border = `1px solid ${asCssVariable(contrastBorder)}`;448449return recordingBadge;450}451452private layoutSearchWidget(dimension: DOM.Dimension): void {453this.searchWidget.layout(dimension);454this.headerContainer.classList.toggle('small', dimension.width < 400);455this.searchWidget.inputBox.inputElement.style.paddingRight = `${DOM.getTotalWidth(this.actionsContainer) + 12}px`;456}457458private createBody(parent: HTMLElement): void {459const bodyContainer = DOM.append(parent, $('.keybindings-body'));460this.createTable(bodyContainer);461}462463private createTable(parent: HTMLElement): void {464this.keybindingsTableContainer = DOM.append(parent, $('.keybindings-table-container'));465this.keybindingsTable = this._register(this.instantiationService.createInstance(WorkbenchTable,466'KeybindingsEditor',467this.keybindingsTableContainer,468new Delegate(),469[470{471label: '',472tooltip: '',473weight: 0,474minimumWidth: 40,475maximumWidth: 40,476templateId: ActionsColumnRenderer.TEMPLATE_ID,477project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }478},479{480label: localize('command', "Command"),481tooltip: '',482weight: 0.3,483templateId: CommandColumnRenderer.TEMPLATE_ID,484project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }485},486{487label: localize('keybinding', "Keybinding"),488tooltip: '',489weight: 0.2,490templateId: KeybindingColumnRenderer.TEMPLATE_ID,491project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }492},493{494label: localize('when', "When"),495tooltip: '',496weight: 0.35,497templateId: WhenColumnRenderer.TEMPLATE_ID,498project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }499},500{501label: localize('source', "Source"),502tooltip: '',503weight: 0.15,504templateId: SourceColumnRenderer.TEMPLATE_ID,505project(row: IKeybindingItemEntry): IKeybindingItemEntry { return row; }506},507],508[509this.instantiationService.createInstance(ActionsColumnRenderer, this),510this.instantiationService.createInstance(CommandColumnRenderer),511this.instantiationService.createInstance(KeybindingColumnRenderer),512this.instantiationService.createInstance(WhenColumnRenderer, this),513this.instantiationService.createInstance(SourceColumnRenderer),514],515{516identityProvider: { getId: (e: IKeybindingItemEntry) => e.id },517horizontalScrolling: false,518accessibilityProvider: new AccessibilityProvider(this.configurationService),519keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IKeybindingItemEntry) => e.keybindingItem.commandLabel || e.keybindingItem.command },520overrideStyles: {521listBackground: editorBackground522},523multipleSelectionSupport: false,524setRowLineHeight: false,525openOnSingleClick: false,526transformOptimization: false // disable transform optimization as it causes the editor overflow widgets to be mispositioned527}528)) as WorkbenchTable<IKeybindingItemEntry>;529530this._register(this.keybindingsTable.onContextMenu(e => this.onContextMenu(e)));531this._register(this.keybindingsTable.onDidChangeFocus(e => this.onFocusChange()));532this._register(this.keybindingsTable.onDidFocus(() => {533this.keybindingsTable.getHTMLElement().classList.add('focused');534this.onFocusChange();535}));536this._register(this.keybindingsTable.onDidBlur(() => {537this.keybindingsTable.getHTMLElement().classList.remove('focused');538this.keybindingFocusContextKey.reset();539}));540this._register(this.keybindingsTable.onDidOpen((e) => {541// stop double click action on the input #148493542if (e.browserEvent?.defaultPrevented) {543return;544}545const activeKeybindingEntry = this.activeKeybindingEntry;546if (activeKeybindingEntry) {547this.defineKeybinding(activeKeybindingEntry, false);548}549}));550551DOM.append(this.keybindingsTableContainer, this.overflowWidgetsDomNode);552}553554private async render(preserveFocus: boolean): Promise<void> {555if (this.input) {556const input: KeybindingsEditorInput = this.input as KeybindingsEditorInput;557this.keybindingsEditorModel = await input.resolve();558await this.keybindingsEditorModel.resolve(this.getActionsLabels());559this.renderKeybindingsEntries(false, preserveFocus);560if (input.searchOptions) {561this.recordKeysAction.checked = input.searchOptions.recordKeybindings;562this.sortByPrecedenceAction.checked = input.searchOptions.sortByPrecedence;563this.searchWidget.setValue(input.searchOptions.searchValue);564} else {565this.updateSearchOptions();566}567}568}569570private getActionsLabels(): Map<string, string> {571const actionsLabels: Map<string, string> = new Map<string, string>();572for (const editorAction of EditorExtensionsRegistry.getEditorActions()) {573actionsLabels.set(editorAction.id, editorAction.label);574}575for (const menuItem of MenuRegistry.getMenuItems(MenuId.CommandPalette)) {576if (isIMenuItem(menuItem)) {577const title = typeof menuItem.command.title === 'string' ? menuItem.command.title : menuItem.command.title.value;578const category = menuItem.command.category ? typeof menuItem.command.category === 'string' ? menuItem.command.category : menuItem.command.category.value : undefined;579actionsLabels.set(menuItem.command.id, category ? `${category}: ${title}` : title);580}581}582return actionsLabels;583}584585private filterKeybindings(): void {586this.renderKeybindingsEntries(this.searchWidget.hasFocus());587this.searchHistoryDelayer.trigger(() => {588this.searchWidget.inputBox.addToHistory();589(this.getMemento(StorageScope.PROFILE, StorageTarget.USER)).searchHistory = this.searchWidget.inputBox.getHistory();590this.saveState();591});592}593594public clearKeyboardShortcutSearchHistory(): void {595this.searchWidget.inputBox.clearHistory();596(this.getMemento(StorageScope.PROFILE, StorageTarget.USER)).searchHistory = this.searchWidget.inputBox.getHistory();597this.saveState();598}599600private renderKeybindingsEntries(reset: boolean, preserveFocus?: boolean): void {601if (this.keybindingsEditorModel) {602const filter = this.searchWidget.getValue();603const keybindingsEntries: IKeybindingItemEntry[] = this.keybindingsEditorModel.fetch(filter, this.sortByPrecedenceAction.checked);604this.accessibilityService.alert(localize('foundResults', "{0} results", keybindingsEntries.length));605this.ariaLabelElement.setAttribute('aria-label', this.getAriaLabel(keybindingsEntries));606607if (keybindingsEntries.length === 0) {608this.latestEmptyFilters.push(filter);609}610const currentSelectedIndex = this.keybindingsTable.getSelection()[0];611this.tableEntries = keybindingsEntries;612this.keybindingsTable.splice(0, this.keybindingsTable.length, this.tableEntries);613this.layoutKeybindingsTable();614615if (reset) {616this.keybindingsTable.setSelection([]);617this.keybindingsTable.setFocus([]);618} else {619if (this.unAssignedKeybindingItemToRevealAndFocus) {620const index = this.getNewIndexOfUnassignedKeybinding(this.unAssignedKeybindingItemToRevealAndFocus);621if (index !== -1) {622this.keybindingsTable.reveal(index, 0.2);623this.selectEntry(index);624}625this.unAssignedKeybindingItemToRevealAndFocus = null;626} else if (currentSelectedIndex !== -1 && currentSelectedIndex < this.tableEntries.length) {627this.selectEntry(currentSelectedIndex, preserveFocus);628} else if (this.editorService.activeEditorPane === this && !preserveFocus) {629this.focus();630}631}632}633}634635private getAriaLabel(keybindingsEntries: IKeybindingItemEntry[]): string {636if (this.sortByPrecedenceAction.checked) {637return localize('show sorted keybindings', "Showing {0} Keybindings in precedence order", keybindingsEntries.length);638} else {639return localize('show keybindings', "Showing {0} Keybindings in alphabetical order", keybindingsEntries.length);640}641}642643private layoutKeybindingsTable(): void {644if (!this.dimension) {645return;646}647648const tableHeight = this.dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 12 /*padding*/);649this.keybindingsTableContainer.style.height = `${tableHeight}px`;650this.keybindingsTable.layout(tableHeight);651}652653private getIndexOf(listEntry: IKeybindingItemEntry): number {654const index = this.tableEntries.indexOf(listEntry);655if (index === -1) {656for (let i = 0; i < this.tableEntries.length; i++) {657if (this.tableEntries[i].id === listEntry.id) {658return i;659}660}661}662return index;663}664665private getNewIndexOfUnassignedKeybinding(unassignedKeybinding: IKeybindingItemEntry): number {666for (let index = 0; index < this.tableEntries.length; index++) {667const entry = this.tableEntries[index];668if (entry.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {669const keybindingItemEntry = (<IKeybindingItemEntry>entry);670if (keybindingItemEntry.keybindingItem.command === unassignedKeybinding.keybindingItem.command) {671return index;672}673}674}675return -1;676}677678private selectEntry(keybindingItemEntry: IKeybindingItemEntry | number, focus: boolean = true): void {679const index = typeof keybindingItemEntry === 'number' ? keybindingItemEntry : this.getIndexOf(keybindingItemEntry);680if (index !== -1 && index < this.keybindingsTable.length) {681if (focus) {682this.keybindingsTable.domFocus();683this.keybindingsTable.setFocus([index]);684}685this.keybindingsTable.setSelection([index]);686}687}688689focusKeybindings(): void {690this.keybindingsTable.domFocus();691const currentFocusIndices = this.keybindingsTable.getFocus();692this.keybindingsTable.setFocus([currentFocusIndices.length ? currentFocusIndices[0] : 0]);693}694695selectKeybinding(keybindingItemEntry: IKeybindingItemEntry): void {696this.selectEntry(keybindingItemEntry);697}698699recordSearchKeys(): void {700this.recordKeysAction.checked = true;701}702703toggleSortByPrecedence(): void {704this.sortByPrecedenceAction.checked = !this.sortByPrecedenceAction.checked;705}706707private onContextMenu(e: IListContextMenuEvent<IKeybindingItemEntry>): void {708if (!e.element) {709return;710}711712if (e.element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {713const keybindingItemEntry = <IKeybindingItemEntry>e.element;714this.selectEntry(keybindingItemEntry);715this.contextMenuService.showContextMenu({716getAnchor: () => e.anchor,717getActions: () => [718this.createCopyAction(keybindingItemEntry),719this.createCopyCommandAction(keybindingItemEntry),720this.createCopyCommandTitleAction(keybindingItemEntry),721new Separator(),722...(keybindingItemEntry.keybindingItem.keybinding723? [this.createDefineKeybindingAction(keybindingItemEntry), this.createAddKeybindingAction(keybindingItemEntry)]724: [this.createDefineKeybindingAction(keybindingItemEntry)]),725new Separator(),726this.createRemoveAction(keybindingItemEntry),727this.createResetAction(keybindingItemEntry),728new Separator(),729this.createDefineWhenExpressionAction(keybindingItemEntry),730new Separator(),731this.createShowConflictsAction(keybindingItemEntry)]732});733}734}735736private onFocusChange(): void {737this.keybindingFocusContextKey.reset();738const element = this.keybindingsTable.getFocusedElements()[0];739if (!element) {740return;741}742if (element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {743this.keybindingFocusContextKey.set(true);744}745}746747private createDefineKeybindingAction(keybindingItemEntry: IKeybindingItemEntry): IAction {748return <IAction>{749label: keybindingItemEntry.keybindingItem.keybinding ? localize('changeLabel', "Change Keybinding...") : localize('addLabel', "Add Keybinding..."),750enabled: true,751id: KEYBINDINGS_EDITOR_COMMAND_DEFINE,752run: () => this.defineKeybinding(keybindingItemEntry, false)753};754}755756private createAddKeybindingAction(keybindingItemEntry: IKeybindingItemEntry): IAction {757return <IAction>{758label: localize('addLabel', "Add Keybinding..."),759enabled: true,760id: KEYBINDINGS_EDITOR_COMMAND_ADD,761run: () => this.defineKeybinding(keybindingItemEntry, true)762};763}764765private createDefineWhenExpressionAction(keybindingItemEntry: IKeybindingItemEntry): IAction {766return <IAction>{767label: localize('editWhen', "Change When Expression"),768enabled: !!keybindingItemEntry.keybindingItem.keybinding,769id: KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN,770run: () => this.defineWhenExpression(keybindingItemEntry)771};772}773774private createRemoveAction(keybindingItem: IKeybindingItemEntry): IAction {775return <IAction>{776label: localize('removeLabel', "Remove Keybinding"),777enabled: !!keybindingItem.keybindingItem.keybinding,778id: KEYBINDINGS_EDITOR_COMMAND_REMOVE,779run: () => this.removeKeybinding(keybindingItem)780};781}782783private createResetAction(keybindingItem: IKeybindingItemEntry): IAction {784return <IAction>{785label: localize('resetLabel', "Reset Keybinding"),786enabled: !keybindingItem.keybindingItem.keybindingItem.isDefault,787id: KEYBINDINGS_EDITOR_COMMAND_RESET,788run: () => this.resetKeybinding(keybindingItem)789};790}791792private createShowConflictsAction(keybindingItem: IKeybindingItemEntry): IAction {793return <IAction>{794label: localize('showSameKeybindings', "Show Same Keybindings"),795enabled: !!keybindingItem.keybindingItem.keybinding,796id: KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR,797run: () => this.showSimilarKeybindings(keybindingItem)798};799}800801private createCopyAction(keybindingItem: IKeybindingItemEntry): IAction {802return <IAction>{803label: localize('copyLabel', "Copy"),804enabled: true,805id: KEYBINDINGS_EDITOR_COMMAND_COPY,806run: () => this.copyKeybinding(keybindingItem)807};808}809810private createCopyCommandAction(keybinding: IKeybindingItemEntry): IAction {811return <IAction>{812label: localize('copyCommandLabel', "Copy Command ID"),813enabled: true,814id: KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND,815run: () => this.copyKeybindingCommand(keybinding)816};817}818819private createCopyCommandTitleAction(keybinding: IKeybindingItemEntry): IAction {820return <IAction>{821label: localize('copyCommandTitleLabel', "Copy Command Title"),822enabled: !!keybinding.keybindingItem.commandLabel,823id: KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE,824run: () => this.copyKeybindingCommandTitle(keybinding)825};826}827828private onKeybindingEditingError(error: unknown): void {829this.notificationService.error(typeof error === 'string' ? error : localize('error', "Error '{0}' while editing the keybinding. Please open 'keybindings.json' file and check for errors.", `${error}`));830}831}832833class Delegate implements ITableVirtualDelegate<IKeybindingItemEntry> {834835readonly headerRowHeight = 30;836837getHeight(element: IKeybindingItemEntry) {838if (element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {839const commandIdMatched = (<IKeybindingItemEntry>element).keybindingItem.commandLabel && (<IKeybindingItemEntry>element).commandIdMatches;840const commandDefaultLabelMatched = !!(<IKeybindingItemEntry>element).commandDefaultLabelMatches;841const extensionIdMatched = !!(<IKeybindingItemEntry>element).extensionIdMatches;842if (commandIdMatched && commandDefaultLabelMatched) {843return 60;844}845if (extensionIdMatched || commandIdMatched || commandDefaultLabelMatched) {846return 40;847}848}849return 24;850}851852}853854interface IActionsColumnTemplateData {855readonly actionBar: ActionBar;856}857858class ActionsColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IActionsColumnTemplateData> {859860static readonly TEMPLATE_ID = 'actions';861862readonly templateId: string = ActionsColumnRenderer.TEMPLATE_ID;863864constructor(865private readonly keybindingsEditor: KeybindingsEditor,866@IKeybindingService private readonly keybindingsService: IKeybindingService867) {868}869870renderTemplate(container: HTMLElement): IActionsColumnTemplateData {871const element = DOM.append(container, $('.actions'));872const actionBar = new ActionBar(element);873return { actionBar };874}875876renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: IActionsColumnTemplateData): void {877templateData.actionBar.clear();878const actions: IAction[] = [];879if (keybindingItemEntry.keybindingItem.keybinding) {880actions.push(this.createEditAction(keybindingItemEntry));881} else {882actions.push(this.createAddAction(keybindingItemEntry));883}884templateData.actionBar.push(actions, { icon: true });885}886887private createEditAction(keybindingItemEntry: IKeybindingItemEntry): IAction {888return <IAction>{889class: ThemeIcon.asClassName(keybindingsEditIcon),890enabled: true,891id: 'editKeybinding',892tooltip: this.keybindingsService.appendKeybinding(localize('editKeybindingLabel', "Change Keybinding"), KEYBINDINGS_EDITOR_COMMAND_DEFINE),893run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry, false)894};895}896897private createAddAction(keybindingItemEntry: IKeybindingItemEntry): IAction {898return <IAction>{899class: ThemeIcon.asClassName(keybindingsAddIcon),900enabled: true,901id: 'addKeybinding',902tooltip: this.keybindingsService.appendKeybinding(localize('addKeybindingLabel', "Add Keybinding"), KEYBINDINGS_EDITOR_COMMAND_DEFINE),903run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry, false)904};905}906907disposeTemplate(templateData: IActionsColumnTemplateData): void {908templateData.actionBar.dispose();909}910911}912913interface ICommandColumnTemplateData {914commandColumn: HTMLElement;915commandColumnHover: IManagedHover;916commandLabelContainer: HTMLElement;917commandLabel: HighlightedLabel;918commandDefaultLabelContainer: HTMLElement;919commandDefaultLabel: HighlightedLabel;920commandIdLabelContainer: HTMLElement;921commandIdLabel: HighlightedLabel;922}923924class CommandColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ICommandColumnTemplateData> {925926static readonly TEMPLATE_ID = 'commands';927928readonly templateId: string = CommandColumnRenderer.TEMPLATE_ID;929930constructor(931@IHoverService private readonly _hoverService: IHoverService932) {933}934935renderTemplate(container: HTMLElement): ICommandColumnTemplateData {936const commandColumn = DOM.append(container, $('.command'));937const commandColumnHover = this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), commandColumn, '');938const commandLabelContainer = DOM.append(commandColumn, $('.command-label'));939const commandLabel = new HighlightedLabel(commandLabelContainer);940const commandDefaultLabelContainer = DOM.append(commandColumn, $('.command-default-label'));941const commandDefaultLabel = new HighlightedLabel(commandDefaultLabelContainer);942const commandIdLabelContainer = DOM.append(commandColumn, $('.command-id.code'));943const commandIdLabel = new HighlightedLabel(commandIdLabelContainer);944return { commandColumn, commandColumnHover, commandLabelContainer, commandLabel, commandDefaultLabelContainer, commandDefaultLabel, commandIdLabelContainer, commandIdLabel };945}946947renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: ICommandColumnTemplateData): void {948const keybindingItem = keybindingItemEntry.keybindingItem;949const commandIdMatched = !!(keybindingItem.commandLabel && keybindingItemEntry.commandIdMatches);950const commandDefaultLabelMatched = !!keybindingItemEntry.commandDefaultLabelMatches;951952templateData.commandColumn.classList.toggle('vertical-align-column', commandIdMatched || commandDefaultLabelMatched);953const title = keybindingItem.commandLabel ? localize('title', "{0} ({1})", keybindingItem.commandLabel, keybindingItem.command) : keybindingItem.command;954templateData.commandColumn.setAttribute('aria-label', title);955templateData.commandColumnHover.update(title);956957if (keybindingItem.commandLabel) {958templateData.commandLabelContainer.classList.remove('hide');959templateData.commandLabel.set(keybindingItem.commandLabel, keybindingItemEntry.commandLabelMatches);960} else {961templateData.commandLabelContainer.classList.add('hide');962templateData.commandLabel.set(undefined);963}964965if (keybindingItemEntry.commandDefaultLabelMatches) {966templateData.commandDefaultLabelContainer.classList.remove('hide');967templateData.commandDefaultLabel.set(keybindingItem.commandDefaultLabel, keybindingItemEntry.commandDefaultLabelMatches);968} else {969templateData.commandDefaultLabelContainer.classList.add('hide');970templateData.commandDefaultLabel.set(undefined);971}972973if (keybindingItemEntry.commandIdMatches || !keybindingItem.commandLabel) {974templateData.commandIdLabelContainer.classList.remove('hide');975templateData.commandIdLabel.set(keybindingItem.command, keybindingItemEntry.commandIdMatches);976} else {977templateData.commandIdLabelContainer.classList.add('hide');978templateData.commandIdLabel.set(undefined);979}980}981982disposeTemplate(templateData: ICommandColumnTemplateData): void {983templateData.commandColumnHover.dispose();984templateData.commandDefaultLabel.dispose();985templateData.commandIdLabel.dispose();986templateData.commandLabel.dispose();987}988}989990interface IKeybindingColumnTemplateData {991keybindingLabel: KeybindingLabel;992}993994class KeybindingColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IKeybindingColumnTemplateData> {995996static readonly TEMPLATE_ID = 'keybindings';997998readonly templateId: string = KeybindingColumnRenderer.TEMPLATE_ID;9991000constructor() { }10011002renderTemplate(container: HTMLElement): IKeybindingColumnTemplateData {1003const element = DOM.append(container, $('.keybinding'));1004const keybindingLabel = new KeybindingLabel(DOM.append(element, $('div.keybinding-label')), OS, defaultKeybindingLabelStyles);1005return { keybindingLabel };1006}10071008renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: IKeybindingColumnTemplateData): void {1009if (keybindingItemEntry.keybindingItem.keybinding) {1010templateData.keybindingLabel.set(keybindingItemEntry.keybindingItem.keybinding, keybindingItemEntry.keybindingMatches);1011} else {1012templateData.keybindingLabel.set(undefined, undefined);1013}1014}10151016disposeTemplate(templateData: IKeybindingColumnTemplateData): void {1017templateData.keybindingLabel.dispose();1018}1019}10201021interface ISourceColumnTemplateData {1022sourceColumn: HTMLElement;1023sourceColumnHover: IManagedHover;1024sourceLabel: HighlightedLabel;1025extensionContainer: HTMLElement;1026extensionLabel: HTMLAnchorElement;1027extensionId: HighlightedLabel;1028disposables: DisposableStore;1029}10301031function onClick(element: HTMLElement, callback: () => void): IDisposable {1032const disposables = new DisposableStore();1033disposables.add(DOM.addDisposableListener(element, DOM.EventType.CLICK, DOM.finalHandler(callback)));1034disposables.add(DOM.addDisposableListener(element, DOM.EventType.KEY_UP, e => {1035const keyboardEvent = new StandardKeyboardEvent(e);1036if (keyboardEvent.equals(KeyCode.Space) || keyboardEvent.equals(KeyCode.Enter)) {1037e.preventDefault();1038e.stopPropagation();1039callback();1040}1041}));1042return disposables;1043}10441045class SourceColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ISourceColumnTemplateData> {10461047static readonly TEMPLATE_ID = 'source';10481049readonly templateId: string = SourceColumnRenderer.TEMPLATE_ID;10501051constructor(1052@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,1053@IHoverService private readonly hoverService: IHoverService,1054) { }10551056renderTemplate(container: HTMLElement): ISourceColumnTemplateData {1057const sourceColumn = DOM.append(container, $('.source'));1058const sourceColumnHover = this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), sourceColumn, '');1059const sourceLabel = new HighlightedLabel(DOM.append(sourceColumn, $('.source-label')));1060const extensionContainer = DOM.append(sourceColumn, $('.extension-container'));1061const extensionLabel = DOM.append<HTMLAnchorElement>(extensionContainer, $('a.extension-label', { tabindex: 0 }));1062const extensionId = new HighlightedLabel(DOM.append(extensionContainer, $('.extension-id-container.code')));1063return { sourceColumn, sourceColumnHover, sourceLabel, extensionLabel, extensionContainer, extensionId, disposables: new DisposableStore() };1064}10651066renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: ISourceColumnTemplateData): void {1067templateData.disposables.clear();1068if (isString(keybindingItemEntry.keybindingItem.source)) {1069templateData.extensionContainer.classList.add('hide');1070templateData.sourceLabel.element.classList.remove('hide');1071templateData.sourceColumnHover.update('');1072templateData.sourceLabel.set(keybindingItemEntry.keybindingItem.source || '-', keybindingItemEntry.sourceMatches);1073} else {1074templateData.extensionContainer.classList.remove('hide');1075templateData.sourceLabel.element.classList.add('hide');1076const extension = keybindingItemEntry.keybindingItem.source;1077const extensionLabel = extension.displayName ?? extension.identifier.value;1078templateData.sourceColumnHover.update(localize('extension label', "Extension ({0})", extensionLabel));1079templateData.extensionLabel.textContent = extensionLabel;1080templateData.disposables.add(onClick(templateData.extensionLabel, () => {1081this.extensionsWorkbenchService.open(extension.identifier.value);1082}));1083if (keybindingItemEntry.extensionIdMatches) {1084templateData.extensionId.element.classList.remove('hide');1085templateData.extensionId.set(extension.identifier.value, keybindingItemEntry.extensionIdMatches);1086} else {1087templateData.extensionId.element.classList.add('hide');1088templateData.extensionId.set(undefined);1089}1090}1091}10921093disposeTemplate(templateData: ISourceColumnTemplateData): void {1094templateData.sourceColumnHover.dispose();1095templateData.disposables.dispose();1096templateData.sourceLabel.dispose();1097templateData.extensionId.dispose();1098}1099}11001101class WhenInputWidget extends Disposable {11021103private readonly input: SuggestEnabledInput;11041105private readonly _onDidAccept = this._register(new Emitter<string>());1106readonly onDidAccept = this._onDidAccept.event;11071108private readonly _onDidReject = this._register(new Emitter<void>());1109readonly onDidReject = this._onDidReject.event;11101111constructor(1112parent: HTMLElement,1113keybindingsEditor: KeybindingsEditor,1114@IInstantiationService instantiationService: IInstantiationService,1115@IContextKeyService contextKeyService: IContextKeyService,1116) {1117super();1118const focusContextKey = CONTEXT_WHEN_FOCUS.bindTo(contextKeyService);1119this.input = this._register(instantiationService.createInstance(SuggestEnabledInput, 'keyboardshortcutseditor#wheninput', parent, {1120provideResults: () => {1121const result = [];1122for (const contextKey of RawContextKey.all()) {1123result.push({ label: contextKey.key, documentation: contextKey.description, detail: contextKey.type, kind: CompletionItemKind.Constant });1124}1125return result;1126},1127triggerCharacters: ['!', ' '],1128wordDefinition: /[a-zA-Z.]+/,1129alwaysShowSuggestions: true,1130}, '', `keyboardshortcutseditor#wheninput`, { focusContextKey, overflowWidgetsDomNode: keybindingsEditor.overflowWidgetsDomNode }));11311132this._register((DOM.addDisposableListener(this.input.element, DOM.EventType.DBLCLICK, e => DOM.EventHelper.stop(e))));1133this._register(toDisposable(() => focusContextKey.reset()));11341135this._register(keybindingsEditor.onAcceptWhenExpression(() => this._onDidAccept.fire(this.input.getValue())));1136this._register(Event.any(keybindingsEditor.onRejectWhenExpression, this.input.onDidBlur)(() => this._onDidReject.fire()));1137}11381139layout(dimension: DOM.Dimension): void {1140this.input.layout(dimension);1141}11421143show(value: string): void {1144this.input.setValue(value);1145this.input.focus(true);1146}11471148}11491150interface IWhenColumnTemplateData {1151readonly element: HTMLElement;1152readonly whenLabelContainer: HTMLElement;1153readonly whenInputContainer: HTMLElement;1154readonly whenLabel: HighlightedLabel;1155readonly disposables: DisposableStore;1156}11571158class WhenColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IWhenColumnTemplateData> {11591160static readonly TEMPLATE_ID = 'when';11611162readonly templateId: string = WhenColumnRenderer.TEMPLATE_ID;11631164constructor(1165private readonly keybindingsEditor: KeybindingsEditor,1166@IHoverService private readonly hoverService: IHoverService,1167@IInstantiationService private readonly instantiationService: IInstantiationService,1168) { }11691170renderTemplate(container: HTMLElement): IWhenColumnTemplateData {1171const element = DOM.append(container, $('.when'));11721173const whenLabelContainer = DOM.append(element, $('div.when-label'));1174const whenLabel = new HighlightedLabel(whenLabelContainer);11751176const whenInputContainer = DOM.append(element, $('div.when-input-container'));11771178return {1179element,1180whenLabelContainer,1181whenLabel,1182whenInputContainer,1183disposables: new DisposableStore(),1184};1185}11861187renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: IWhenColumnTemplateData): void {1188templateData.disposables.clear();1189const whenInputDisposables = templateData.disposables.add(new DisposableStore());1190templateData.disposables.add(this.keybindingsEditor.onDefineWhenExpression(e => {1191if (keybindingItemEntry === e) {1192templateData.element.classList.add('input-mode');11931194const inputWidget = whenInputDisposables.add(this.instantiationService.createInstance(WhenInputWidget, templateData.whenInputContainer, this.keybindingsEditor));1195inputWidget.layout(new DOM.Dimension(templateData.element.parentElement!.clientWidth, 18));1196inputWidget.show(keybindingItemEntry.keybindingItem.when || '');11971198const hideInputWidget = () => {1199whenInputDisposables.clear();1200templateData.element.classList.remove('input-mode');1201templateData.element.parentElement!.style.paddingLeft = '10px';1202DOM.clearNode(templateData.whenInputContainer);1203};12041205whenInputDisposables.add(inputWidget.onDidAccept(value => {1206hideInputWidget();1207this.keybindingsEditor.updateKeybinding(keybindingItemEntry, keybindingItemEntry.keybindingItem.keybinding ? keybindingItemEntry.keybindingItem.keybinding.getUserSettingsLabel() || '' : '', value);1208this.keybindingsEditor.selectKeybinding(keybindingItemEntry);1209}));12101211whenInputDisposables.add(inputWidget.onDidReject(() => {1212hideInputWidget();1213this.keybindingsEditor.selectKeybinding(keybindingItemEntry);1214}));12151216templateData.element.parentElement!.style.paddingLeft = '0px';1217}1218}));12191220templateData.whenLabelContainer.classList.toggle('code', !!keybindingItemEntry.keybindingItem.when);1221templateData.whenLabelContainer.classList.toggle('empty', !keybindingItemEntry.keybindingItem.when);12221223if (keybindingItemEntry.keybindingItem.when) {1224templateData.whenLabel.set(keybindingItemEntry.keybindingItem.when, keybindingItemEntry.whenMatches, keybindingItemEntry.keybindingItem.when);1225templateData.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), templateData.element, keybindingItemEntry.keybindingItem.when));1226} else {1227templateData.whenLabel.set('-');1228}1229}12301231disposeTemplate(templateData: IWhenColumnTemplateData): void {1232templateData.disposables.dispose();1233templateData.whenLabel.dispose();1234}1235}12361237class AccessibilityProvider implements IListAccessibilityProvider<IKeybindingItemEntry> {12381239constructor(private readonly configurationService: IConfigurationService) { }12401241getWidgetAriaLabel(): string {1242return localize('keybindingsLabel', "Keybindings");1243}12441245getAriaLabel({ keybindingItem }: IKeybindingItemEntry): string {1246const ariaLabel = [1247keybindingItem.commandLabel ? keybindingItem.commandLabel : keybindingItem.command,1248keybindingItem.keybinding?.getAriaLabel() || localize('noKeybinding', "No keybinding assigned"),1249keybindingItem.when ? keybindingItem.when : localize('noWhen', "No when context"),1250isString(keybindingItem.source) ? keybindingItem.source : keybindingItem.source.description ?? keybindingItem.source.identifier.value,1251];1252if (this.configurationService.getValue(AccessibilityVerbositySettingId.KeybindingsEditor)) {1253const kbEditorAriaLabel = localize('keyboard shortcuts aria label', "use space or enter to change the keybinding.");1254ariaLabel.push(kbEditorAriaLabel);1255}1256return ariaLabel.join(', ');1257}1258}12591260registerColor('keybindingTable.headerBackground', tableOddRowsBackgroundColor, 'Background color for the keyboard shortcuts table header.');1261registerColor('keybindingTable.rowsBackground', tableOddRowsBackgroundColor, 'Background color for the keyboard shortcuts table alternating rows.');12621263registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {1264const foregroundColor = theme.getColor(foreground);1265if (foregroundColor) {1266const whenForegroundColor = foregroundColor.transparent(.8).makeOpaque(WORKBENCH_BACKGROUND(theme));1267collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table .monaco-table-tr .monaco-table-td .code { color: ${whenForegroundColor}; }`);1268}12691270const listActiveSelectionForegroundColor = theme.getColor(listActiveSelectionForeground);1271const listActiveSelectionBackgroundColor = theme.getColor(listActiveSelectionBackground);1272if (listActiveSelectionForegroundColor && listActiveSelectionBackgroundColor) {1273const whenForegroundColor = listActiveSelectionForegroundColor.transparent(.8).makeOpaque(listActiveSelectionBackgroundColor);1274collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table.focused .monaco-list-row.selected .monaco-table-tr .monaco-table-td .code { color: ${whenForegroundColor}; }`);1275}12761277const listInactiveSelectionForegroundColor = theme.getColor(listInactiveSelectionForeground);1278const listInactiveSelectionBackgroundColor = theme.getColor(listInactiveSelectionBackground);1279if (listInactiveSelectionForegroundColor && listInactiveSelectionBackgroundColor) {1280const whenForegroundColor = listInactiveSelectionForegroundColor.transparent(.8).makeOpaque(listInactiveSelectionBackgroundColor);1281collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table .monaco-list-row.selected .monaco-table-tr .monaco-table-td .code { color: ${whenForegroundColor}; }`);1282}12831284const listFocusForegroundColor = theme.getColor(listFocusForeground);1285const listFocusBackgroundColor = theme.getColor(listFocusBackground);1286if (listFocusForegroundColor && listFocusBackgroundColor) {1287const whenForegroundColor = listFocusForegroundColor.transparent(.8).makeOpaque(listFocusBackgroundColor);1288collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table.focused .monaco-list-row.focused .monaco-table-tr .monaco-table-td .code { color: ${whenForegroundColor}; }`);1289}12901291const listHoverForegroundColor = theme.getColor(listHoverForeground);1292const listHoverBackgroundColor = theme.getColor(listHoverBackground);1293if (listHoverForegroundColor && listHoverBackgroundColor) {1294const whenForegroundColor = listHoverForegroundColor.transparent(.8).makeOpaque(listHoverBackgroundColor);1295collector.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}; }`);1296}1297});129812991300