Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../dom.js';
7
import type { IManagedHover } from '../hover/hover.js';
8
import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js';
9
import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js';
10
import { UILabelProvider } from '../../../common/keybindingLabels.js';
11
import { ResolvedKeybinding, ResolvedChord } from '../../../common/keybindings.js';
12
import { Disposable } from '../../../common/lifecycle.js';
13
import { equals } from '../../../common/objects.js';
14
import { OperatingSystem } from '../../../common/platform.js';
15
import './keybindingLabel.css';
16
import { localize } from '../../../../nls.js';
17
18
const $ = dom.$;
19
20
export interface ChordMatches {
21
ctrlKey?: boolean;
22
shiftKey?: boolean;
23
altKey?: boolean;
24
metaKey?: boolean;
25
keyCode?: boolean;
26
}
27
28
export interface Matches {
29
firstPart: ChordMatches;
30
chordPart: ChordMatches;
31
}
32
33
export interface KeybindingLabelOptions extends IKeybindingLabelStyles {
34
renderUnboundKeybindings?: boolean;
35
/**
36
* Default false.
37
*/
38
disableTitle?: boolean;
39
}
40
41
export interface IKeybindingLabelStyles {
42
keybindingLabelBackground: string | undefined;
43
keybindingLabelForeground: string | undefined;
44
keybindingLabelBorder: string | undefined;
45
keybindingLabelBottomBorder: string | undefined;
46
keybindingLabelShadow: string | undefined;
47
}
48
49
export const unthemedKeybindingLabelOptions: KeybindingLabelOptions = {
50
keybindingLabelBackground: undefined,
51
keybindingLabelForeground: undefined,
52
keybindingLabelBorder: undefined,
53
keybindingLabelBottomBorder: undefined,
54
keybindingLabelShadow: undefined
55
};
56
57
export class KeybindingLabel extends Disposable {
58
59
private domNode: HTMLElement;
60
private options: KeybindingLabelOptions;
61
62
private readonly keyElements = new Set<HTMLSpanElement>();
63
64
private hover: IManagedHover;
65
private keybinding: ResolvedKeybinding | undefined;
66
private matches: Matches | undefined;
67
private didEverRender: boolean;
68
69
constructor(container: HTMLElement, private os: OperatingSystem, options?: KeybindingLabelOptions) {
70
super();
71
72
this.options = options || Object.create(null);
73
74
const labelForeground = this.options.keybindingLabelForeground;
75
76
this.domNode = dom.append(container, $('.monaco-keybinding'));
77
if (labelForeground) {
78
this.domNode.style.color = labelForeground;
79
}
80
81
this.hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.domNode, ''));
82
83
this.didEverRender = false;
84
container.appendChild(this.domNode);
85
}
86
87
get element(): HTMLElement {
88
return this.domNode;
89
}
90
91
set(keybinding: ResolvedKeybinding | undefined, matches?: Matches) {
92
if (this.didEverRender && this.keybinding === keybinding && KeybindingLabel.areSame(this.matches, matches)) {
93
return;
94
}
95
96
this.keybinding = keybinding;
97
this.matches = matches;
98
this.render();
99
}
100
101
private render() {
102
this.clear();
103
104
if (this.keybinding) {
105
const chords = this.keybinding.getChords();
106
if (chords[0]) {
107
this.renderChord(this.domNode, chords[0], this.matches ? this.matches.firstPart : null);
108
}
109
for (let i = 1; i < chords.length; i++) {
110
dom.append(this.domNode, $('span.monaco-keybinding-key-chord-separator', undefined, ' '));
111
this.renderChord(this.domNode, chords[i], this.matches ? this.matches.chordPart : null);
112
}
113
const title = (this.options.disableTitle ?? false) ? undefined : this.keybinding.getAriaLabel() || undefined;
114
this.hover.update(title);
115
this.domNode.setAttribute('aria-label', title || '');
116
} else if (this.options && this.options.renderUnboundKeybindings) {
117
this.renderUnbound(this.domNode);
118
}
119
120
this.didEverRender = true;
121
}
122
123
private clear(): void {
124
dom.clearNode(this.domNode);
125
this.keyElements.clear();
126
}
127
128
private renderChord(parent: HTMLElement, chord: ResolvedChord, match: ChordMatches | null) {
129
const modifierLabels = UILabelProvider.modifierLabels[this.os];
130
if (chord.ctrlKey) {
131
this.renderKey(parent, modifierLabels.ctrlKey, Boolean(match?.ctrlKey), modifierLabels.separator);
132
}
133
if (chord.shiftKey) {
134
this.renderKey(parent, modifierLabels.shiftKey, Boolean(match?.shiftKey), modifierLabels.separator);
135
}
136
if (chord.altKey) {
137
this.renderKey(parent, modifierLabels.altKey, Boolean(match?.altKey), modifierLabels.separator);
138
}
139
if (chord.metaKey) {
140
this.renderKey(parent, modifierLabels.metaKey, Boolean(match?.metaKey), modifierLabels.separator);
141
}
142
const keyLabel = chord.keyLabel;
143
if (keyLabel) {
144
this.renderKey(parent, keyLabel, Boolean(match?.keyCode), '');
145
}
146
}
147
148
private renderKey(parent: HTMLElement, label: string, highlight: boolean, separator: string): void {
149
dom.append(parent, this.createKeyElement(label, highlight ? '.highlight' : ''));
150
if (separator) {
151
dom.append(parent, $('span.monaco-keybinding-key-separator', undefined, separator));
152
}
153
}
154
155
private renderUnbound(parent: HTMLElement): void {
156
dom.append(parent, this.createKeyElement(localize('unbound', "Unbound")));
157
}
158
159
private createKeyElement(label: string, extraClass = ''): HTMLElement {
160
const keyElement = $('span.monaco-keybinding-key' + extraClass, undefined, label);
161
this.keyElements.add(keyElement);
162
163
if (this.options.keybindingLabelBackground) {
164
keyElement.style.backgroundColor = this.options.keybindingLabelBackground;
165
}
166
if (this.options.keybindingLabelBorder) {
167
keyElement.style.borderColor = this.options.keybindingLabelBorder;
168
}
169
if (this.options.keybindingLabelBottomBorder) {
170
keyElement.style.borderBottomColor = this.options.keybindingLabelBottomBorder;
171
}
172
if (this.options.keybindingLabelShadow) {
173
keyElement.style.boxShadow = `inset 0 -1px 0 ${this.options.keybindingLabelShadow}`;
174
}
175
176
return keyElement;
177
}
178
179
private static areSame(a: Matches | undefined, b: Matches | undefined): boolean {
180
if (a === b || (!a && !b)) {
181
return true;
182
}
183
return !!a && !!b && equals(a.firstPart, b.firstPart) && equals(a.chordPart, b.chordPart);
184
}
185
}
186
187