Path: blob/main/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.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*--------------------------------------------------------------------------------------------*/45import * as dom from '../../dom.js';6import type { IManagedHover } from '../hover/hover.js';7import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js';8import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js';9import { UILabelProvider } from '../../../common/keybindingLabels.js';10import { ResolvedKeybinding, ResolvedChord } from '../../../common/keybindings.js';11import { Disposable } from '../../../common/lifecycle.js';12import { equals } from '../../../common/objects.js';13import { OperatingSystem } from '../../../common/platform.js';14import './keybindingLabel.css';15import { localize } from '../../../../nls.js';1617const $ = dom.$;1819export interface ChordMatches {20ctrlKey?: boolean;21shiftKey?: boolean;22altKey?: boolean;23metaKey?: boolean;24keyCode?: boolean;25}2627export interface Matches {28firstPart: ChordMatches;29chordPart: ChordMatches;30}3132export interface KeybindingLabelOptions extends IKeybindingLabelStyles {33renderUnboundKeybindings?: boolean;34/**35* Default false.36*/37disableTitle?: boolean;38}3940export interface IKeybindingLabelStyles {41keybindingLabelBackground: string | undefined;42keybindingLabelForeground: string | undefined;43keybindingLabelBorder: string | undefined;44keybindingLabelBottomBorder: string | undefined;45keybindingLabelShadow: string | undefined;46}4748export const unthemedKeybindingLabelOptions: KeybindingLabelOptions = {49keybindingLabelBackground: undefined,50keybindingLabelForeground: undefined,51keybindingLabelBorder: undefined,52keybindingLabelBottomBorder: undefined,53keybindingLabelShadow: undefined54};5556export class KeybindingLabel extends Disposable {5758private domNode: HTMLElement;59private options: KeybindingLabelOptions;6061private readonly keyElements = new Set<HTMLSpanElement>();6263private hover: IManagedHover;64private keybinding: ResolvedKeybinding | undefined;65private matches: Matches | undefined;66private didEverRender: boolean;6768constructor(container: HTMLElement, private os: OperatingSystem, options?: KeybindingLabelOptions) {69super();7071this.options = options || Object.create(null);7273const labelForeground = this.options.keybindingLabelForeground;7475this.domNode = dom.append(container, $('.monaco-keybinding'));76if (labelForeground) {77this.domNode.style.color = labelForeground;78}7980this.hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.domNode, ''));8182this.didEverRender = false;83container.appendChild(this.domNode);84}8586get element(): HTMLElement {87return this.domNode;88}8990set(keybinding: ResolvedKeybinding | undefined, matches?: Matches) {91if (this.didEverRender && this.keybinding === keybinding && KeybindingLabel.areSame(this.matches, matches)) {92return;93}9495this.keybinding = keybinding;96this.matches = matches;97this.render();98}99100private render() {101this.clear();102103if (this.keybinding) {104const chords = this.keybinding.getChords();105if (chords[0]) {106this.renderChord(this.domNode, chords[0], this.matches ? this.matches.firstPart : null);107}108for (let i = 1; i < chords.length; i++) {109dom.append(this.domNode, $('span.monaco-keybinding-key-chord-separator', undefined, ' '));110this.renderChord(this.domNode, chords[i], this.matches ? this.matches.chordPart : null);111}112const title = (this.options.disableTitle ?? false) ? undefined : this.keybinding.getAriaLabel() || undefined;113this.hover.update(title);114this.domNode.setAttribute('aria-label', title || '');115} else if (this.options && this.options.renderUnboundKeybindings) {116this.renderUnbound(this.domNode);117}118119this.didEverRender = true;120}121122private clear(): void {123dom.clearNode(this.domNode);124this.keyElements.clear();125}126127private renderChord(parent: HTMLElement, chord: ResolvedChord, match: ChordMatches | null) {128const modifierLabels = UILabelProvider.modifierLabels[this.os];129if (chord.ctrlKey) {130this.renderKey(parent, modifierLabels.ctrlKey, Boolean(match?.ctrlKey), modifierLabels.separator);131}132if (chord.shiftKey) {133this.renderKey(parent, modifierLabels.shiftKey, Boolean(match?.shiftKey), modifierLabels.separator);134}135if (chord.altKey) {136this.renderKey(parent, modifierLabels.altKey, Boolean(match?.altKey), modifierLabels.separator);137}138if (chord.metaKey) {139this.renderKey(parent, modifierLabels.metaKey, Boolean(match?.metaKey), modifierLabels.separator);140}141const keyLabel = chord.keyLabel;142if (keyLabel) {143this.renderKey(parent, keyLabel, Boolean(match?.keyCode), '');144}145}146147private renderKey(parent: HTMLElement, label: string, highlight: boolean, separator: string): void {148dom.append(parent, this.createKeyElement(label, highlight ? '.highlight' : ''));149if (separator) {150dom.append(parent, $('span.monaco-keybinding-key-separator', undefined, separator));151}152}153154private renderUnbound(parent: HTMLElement): void {155dom.append(parent, this.createKeyElement(localize('unbound', "Unbound")));156}157158private createKeyElement(label: string, extraClass = ''): HTMLElement {159const keyElement = $('span.monaco-keybinding-key' + extraClass, undefined, label);160this.keyElements.add(keyElement);161162if (this.options.keybindingLabelBackground) {163keyElement.style.backgroundColor = this.options.keybindingLabelBackground;164}165if (this.options.keybindingLabelBorder) {166keyElement.style.borderColor = this.options.keybindingLabelBorder;167}168if (this.options.keybindingLabelBottomBorder) {169keyElement.style.borderBottomColor = this.options.keybindingLabelBottomBorder;170}171if (this.options.keybindingLabelShadow) {172keyElement.style.boxShadow = `inset 0 -1px 0 ${this.options.keybindingLabelShadow}`;173}174175return keyElement;176}177178private static areSame(a: Matches | undefined, b: Matches | undefined): boolean {179if (a === b || (!a && !b)) {180return true;181}182return !!a && !!b && equals(a.firstPart, b.firstPart) && equals(a.chordPart, b.chordPart);183}184}185186187