Skip to content

Commit 86ae8ff

Browse files
committed
1 parent c346475 commit 86ae8ff

7 files changed

Lines changed: 228 additions & 65 deletions

File tree

src/vs/base/browser/ui/checkbox/checkbox.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { Color } from 'vs/base/common/color';
1313
import { Emitter, Event } from 'vs/base/common/event';
1414
import { KeyCode } from 'vs/base/common/keyCodes';
1515
import * as objects from 'vs/base/common/objects';
16+
import { BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
17+
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
1618

1719
export interface ICheckboxOpts extends ICheckboxStyles {
1820
readonly actionClassName: string;
@@ -28,6 +30,48 @@ const defaultOpts = {
2830
inputActiveOptionBorder: Color.fromHex('#007ACC')
2931
};
3032

33+
export class CheckboxActionItem extends BaseActionItem {
34+
35+
private checkbox: Checkbox;
36+
private disposables: IDisposable[] = [];
37+
38+
render(container: HTMLElement): void {
39+
this.element = container;
40+
41+
this.disposables = dispose(this.disposables);
42+
this.checkbox = new Checkbox({
43+
actionClassName: this._action.class,
44+
isChecked: this._action.checked,
45+
title: this._action.label
46+
});
47+
this.disposables.push(this.checkbox);
48+
this.checkbox.onChange(() => this._action.checked = this.checkbox.checked, this, this.disposables);
49+
this.element.appendChild(this.checkbox.domNode);
50+
}
51+
52+
updateEnabled(): void {
53+
if (this.checkbox) {
54+
if (this.isEnabled()) {
55+
this.checkbox.enable();
56+
} else {
57+
this.checkbox.disable();
58+
}
59+
}
60+
}
61+
62+
updateChecked(): void {
63+
if (this.checkbox) {
64+
this.checkbox.checked = this._action.checked;
65+
}
66+
}
67+
68+
dipsose(): void {
69+
this.disposables = dispose(this.disposables);
70+
super.dispose();
71+
}
72+
73+
}
74+
3175
export class Checkbox extends Widget {
3276

3377
private readonly _onChange = this._register(new Emitter<boolean>());

src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@ import 'vs/css!./media/keybindings';
77
import * as nls from 'vs/nls';
88
import { OS } from 'vs/base/common/platform';
99
import { TPromise } from 'vs/base/common/winjs.base';
10-
import { Disposable } from 'vs/base/common/lifecycle';
10+
import { Disposable, dispose, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
1111
import { Event, Emitter } from 'vs/base/common/event';
1212
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
1313
import { Widget } from 'vs/base/browser/ui/widget';
1414
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
1515
import * as dom from 'vs/base/browser/dom';
16-
import { InputBox, IInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
17-
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
16+
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
1817
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
1918
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
2019
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
@@ -24,16 +23,20 @@ import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/co
2423
import { IThemeService } from 'vs/platform/theme/common/themeService';
2524
import { editorWidgetBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
2625
import { ScrollType } from 'vs/editor/common/editorCommon';
26+
import { SearchWidget, SearchOptions } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
2727

28-
class KeybindingInputWidget extends Widget {
28+
export interface KeybindingsSearchOptions extends SearchOptions {
29+
recordEnter?: boolean;
30+
}
2931

30-
private readonly inputBox: InputBox;
32+
export class KeybindingsSearchWidget extends SearchWidget {
3133

32-
private _acceptChords: boolean;
3334
private _firstPart: ResolvedKeybinding;
3435
private _chordPart: ResolvedKeybinding;
3536
private _inputValue: string;
3637

38+
private recordDisposables: IDisposable[] = [];
39+
3740
private _onKeybinding = this._register(new Emitter<[ResolvedKeybinding, ResolvedKeybinding]>());
3841
public readonly onKeybinding: Event<[ResolvedKeybinding, ResolvedKeybinding]> = this._onKeybinding.event;
3942

@@ -46,25 +49,36 @@ class KeybindingInputWidget extends Widget {
4649
private _onBlur = this._register(new Emitter<void>());
4750
public readonly onBlur: Event<void> = this._onBlur.event;
4851

49-
constructor(parent: HTMLElement, private options: IInputOptions,
50-
@IContextViewService private contextViewService: IContextViewService,
52+
constructor(parent: HTMLElement, options: SearchOptions,
53+
@IContextViewService contextViewService: IContextViewService,
5154
@IKeybindingService private keybindingService: IKeybindingService,
55+
@IInstantiationService instantiationService: IInstantiationService,
5256
@IThemeService themeService: IThemeService
5357
) {
54-
super();
55-
this.inputBox = this._register(new InputBox(parent, this.contextViewService, this.options));
58+
super(parent, options, contextViewService, instantiationService, themeService);
5659
this._register(attachInputBoxStyler(this.inputBox, themeService));
57-
this.onkeydown(this.inputBox.inputElement, e => this._onKeyDown(e));
58-
this.onblur(this.inputBox.inputElement, (e) => this._onBlur.fire());
60+
this._register(toDisposable(() => this.stopRecordingKeys()));
61+
62+
this._reset();
63+
}
5964

60-
this.oninput(this.inputBox.inputElement, (e) => {
65+
clear(): void {
66+
this._reset();
67+
super.clear();
68+
}
69+
70+
startRecordingKeys(): void {
71+
this.recordDisposables.push(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => this._onKeyDown(new StandardKeyboardEvent(e))));
72+
this.recordDisposables.push(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.BLUR, () => this._onBlur.fire()));
73+
this.recordDisposables.push(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.INPUT, () => {
6174
// Prevent other characters from showing up
6275
this.setInputValue(this._inputValue);
63-
});
76+
}));
77+
}
6478

65-
this._acceptChords = true;
66-
this._firstPart = null;
67-
this._chordPart = null;
79+
stopRecordingKeys(): void {
80+
this._reset();
81+
dispose(this.recordDisposables);
6882
}
6983

7084
public setInputValue(value: string): void {
@@ -76,15 +90,16 @@ class KeybindingInputWidget extends Widget {
7690
this.inputBox.focus();
7791
}
7892

79-
public reset() {
93+
private _reset() {
8094
this._firstPart = null;
8195
this._chordPart = null;
8296
}
8397

8498
private _onKeyDown(keyboardEvent: IKeyboardEvent): void {
8599
keyboardEvent.preventDefault();
86100
keyboardEvent.stopPropagation();
87-
if (keyboardEvent.equals(KeyCode.Enter)) {
101+
const options = this.options as KeybindingsSearchOptions;
102+
if (!options.recordEnter && keyboardEvent.equals(KeyCode.Enter)) {
88103
this._onEnter.fire();
89104
return;
90105
}
@@ -99,20 +114,16 @@ class KeybindingInputWidget extends Widget {
99114
const keybinding = this.keybindingService.resolveKeyboardEvent(keyboardEvent);
100115
const info = `code: ${keyboardEvent.browserEvent.code}, keyCode: ${keyboardEvent.browserEvent.keyCode}, key: ${keyboardEvent.browserEvent.key} => UI: ${keybinding.getAriaLabel()}, user settings: ${keybinding.getUserSettingsLabel()}, dispatch: ${keybinding.getDispatchParts()[0]}`;
101116

102-
if (this._acceptChords) {
103-
const hasFirstPart = (this._firstPart && this._firstPart.getDispatchParts()[0] !== null);
104-
const hasChordPart = (this._chordPart && this._chordPart.getDispatchParts()[0] !== null);
105-
if (hasFirstPart && hasChordPart) {
106-
// Reset
107-
this._firstPart = keybinding;
108-
this._chordPart = null;
109-
} else if (!hasFirstPart) {
110-
this._firstPart = keybinding;
111-
} else {
112-
this._chordPart = keybinding;
113-
}
114-
} else {
117+
const hasFirstPart = (this._firstPart && this._firstPart.getDispatchParts()[0] !== null);
118+
const hasChordPart = (this._chordPart && this._chordPart.getDispatchParts()[0] !== null);
119+
if (hasFirstPart && hasChordPart) {
120+
// Reset
115121
this._firstPart = keybinding;
122+
this._chordPart = null;
123+
} else if (!hasFirstPart) {
124+
this._firstPart = keybinding;
125+
} else {
126+
this._chordPart = keybinding;
116127
}
117128

118129
let value = '';
@@ -135,7 +146,7 @@ export class DefineKeybindingWidget extends Widget {
135146
private static readonly HEIGHT = 110;
136147

137148
private _domNode: FastDomNode<HTMLElement>;
138-
private _keybindingInputWidget: KeybindingInputWidget;
149+
private _keybindingInputWidget: KeybindingsSearchWidget;
139150
private _outputNode: HTMLElement;
140151
private _showExistingKeybindingsNode: HTMLElement;
141152

@@ -168,7 +179,7 @@ export class DefineKeybindingWidget extends Widget {
168179
}
169180

170181
define(): TPromise<string> {
171-
this._keybindingInputWidget.reset();
182+
this._keybindingInputWidget.clear();
172183
return new TPromise<string>((c, e) => {
173184
if (!this._isVisible) {
174185
this._isVisible = true;
@@ -232,7 +243,8 @@ export class DefineKeybindingWidget extends Widget {
232243
}
233244
}));
234245

235-
this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingInputWidget, this._domNode.domNode, { ariaLabel: message }));
246+
this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, this._domNode.domNode, { ariaLabel: message }));
247+
this._keybindingInputWidget.startRecordingKeys();
236248
this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding)));
237249
this._register(this._keybindingInputWidget.onEnter(() => this.hide()));
238250
this._register(this._keybindingInputWidget.onEscape(() => this.onCancel()));

src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import { Delayer } from 'vs/base/common/async';
1010
import * as DOM from 'vs/base/browser/dom';
1111
import { OS } from 'vs/base/common/platform';
1212
import { dispose } from 'vs/base/common/lifecycle';
13-
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
13+
import { CheckboxActionItem } from 'vs/base/browser/ui/checkbox/checkbox';
1414
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
1515
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
16-
import { IAction } from 'vs/base/common/actions';
16+
import { IAction, Action } from 'vs/base/common/actions';
1717
import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
1818
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
1919
import { EditorOptions } from 'vs/workbench/common/editor';
@@ -22,8 +22,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
2222
import { KeybindingsEditorModel, IKeybindingItemEntry, IListEntry, KEYBINDING_ENTRY_TEMPLATE_ID, KEYBINDING_HEADER_TEMPLATE_ID } from 'vs/workbench/services/preferences/common/keybindingsEditorModel';
2323
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
2424
import { IKeybindingService, IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
25-
import { SearchWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
26-
import { DefineKeybindingWidget } from 'vs/workbench/parts/preferences/browser/keybindingWidgets';
25+
import { DefineKeybindingWidget, KeybindingsSearchWidget, KeybindingsSearchOptions } from 'vs/workbench/parts/preferences/browser/keybindingWidgets';
2726
import {
2827
IKeybindingsEditor, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_COPY,
2928
KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS,
@@ -55,7 +54,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor
5554
private keybindingsEditorModel: KeybindingsEditorModel;
5655

5756
private headerContainer: HTMLElement;
58-
private searchWidget: SearchWidget;
57+
private searchWidget: KeybindingsSearchWidget;
5958

6059
private overlayContainer: HTMLElement;
6160
private defineKeybindingWidget: DefineKeybindingWidget;
@@ -72,7 +71,10 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor
7271
private keybindingsEditorContextKey: IContextKey<boolean>;
7372
private keybindingFocusContextKey: IContextKey<boolean>;
7473
private searchFocusContextKey: IContextKey<boolean>;
75-
private sortByPrecedence: Checkbox;
74+
75+
private actionBar: ActionBar;
76+
private sortByPrecedenceAction: Action;
77+
private recordKeysAction: Action;
7678

7779
private ariaLabelElement: HTMLElement;
7880

@@ -299,21 +301,58 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor
299301
this.headerContainer = DOM.append(parent, $('.keybindings-header'));
300302

301303
const searchContainer = DOM.append(this.headerContainer, $('.search-container'));
302-
this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, searchContainer, {
304+
this.searchWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, searchContainer, <KeybindingsSearchOptions>{
303305
ariaLabel: localize('SearchKeybindings.AriaLabel', "Search keybindings"),
304306
placeholder: localize('SearchKeybindings.Placeholder', "Search keybindings"),
305307
focusKey: this.searchFocusContextKey,
306-
ariaLabelledBy: 'keybindings-editor-aria-label-element'
308+
ariaLabelledBy: 'keybindings-editor-aria-label-element',
309+
recordEnter: true
307310
}));
308311
this._register(this.searchWidget.onDidChange(searchValue => this.delayedFiltering.trigger(() => this.filterKeybindings())));
312+
this._register(this.searchWidget.onEscape(() => {
313+
if (this.searchWidget.getValue()) {
314+
this.searchWidget.clear();
315+
} else {
316+
this.recordKeysAction.checked = false;
317+
}
318+
}));
319+
320+
this.sortByPrecedenceAction = new Action('keybindings.editor.sortByPrecedence', localize('sortByPrecedene', "Sort by Precedence"), 'sort-by-precedence');
321+
this.sortByPrecedenceAction.checked = false;
322+
this._register(this.sortByPrecedenceAction.onDidChange(e => {
323+
if (e.checked !== void 0) {
324+
this.renderKeybindingsEntries(false);
325+
}
326+
}));
327+
328+
this.recordKeysAction = new Action('keybindings.editor.recordKeys', localize('recordKeys', "Record Keys"), 'octicon octicon-keyboard');
329+
this.recordKeysAction.checked = false;
330+
this._register(this.recordKeysAction.onDidChange(e => {
331+
if (e.checked !== void 0) {
332+
if (e.checked) {
333+
this.searchWidget.startRecordingKeys();
334+
this.searchWidget.focus();
335+
} else {
336+
this.searchWidget.stopRecordingKeys();
337+
this.searchWidget.focus();
338+
}
339+
}
340+
}));
309341

310-
this.sortByPrecedence = this._register(new Checkbox({
311-
actionClassName: 'sort-by-precedence',
312-
isChecked: false,
313-
title: localize('sortByPrecedene', "Sort by Precedence")
342+
this.actionBar = this._register(new ActionBar(searchContainer, {
343+
animated: false,
344+
actionItemProvider: (action: Action) => {
345+
if (action.id === this.sortByPrecedenceAction.id) {
346+
return new CheckboxActionItem(null, action);
347+
}
348+
if (action.id === this.recordKeysAction.id) {
349+
return new CheckboxActionItem(null, action);
350+
}
351+
return null;
352+
}
314353
}));
315-
this._register(this.sortByPrecedence.onChange(() => this.renderKeybindingsEntries(false)));
316-
searchContainer.appendChild(this.sortByPrecedence.domNode);
354+
this.actionBar.push([this.recordKeysAction, this.sortByPrecedenceAction]);
355+
317356

318357
this.createOpenKeybindingsElement(this.headerContainer);
319358
}
@@ -405,7 +444,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor
405444
private renderKeybindingsEntries(reset: boolean, preserveFocus?: boolean): void {
406445
if (this.keybindingsEditorModel) {
407446
const filter = this.searchWidget.getValue();
408-
const keybindingsEntries: IKeybindingItemEntry[] = this.keybindingsEditorModel.fetch(filter, this.sortByPrecedence.checked);
447+
const keybindingsEntries: IKeybindingItemEntry[] = this.keybindingsEditorModel.fetch(filter, this.sortByPrecedenceAction.checked);
409448

410449
this.ariaLabelElement.setAttribute('aria-label', this.getAriaLabel(keybindingsEntries));
411450

@@ -438,7 +477,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor
438477
}
439478

440479
private getAriaLabel(keybindingsEntries: IKeybindingItemEntry[]): string {
441-
if (this.sortByPrecedence.checked) {
480+
if (this.sortByPrecedenceAction.checked) {
442481
return localize('show sorted keybindings', "Showing {0} Keybindings in precedence order", keybindingsEntries.length);
443482
} else {
444483
return localize('show keybindings', "Showing {0} Keybindings in alphabetical order", keybindingsEntries.length);
@@ -491,6 +530,14 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor
491530
this.keybindingsList.setFocus([currentFocusIndices.length ? currentFocusIndices[0] : 0]);
492531
}
493532

533+
recordSearchKeys(): void {
534+
this.recordKeysAction.checked = true;
535+
}
536+
537+
toggleSortByPrecedence(): void {
538+
this.sortByPrecedenceAction.checked = !this.sortByPrecedenceAction.checked;
539+
}
540+
494541
private onContextMenu(e: IListContextMenuEvent<IListEntry>): void {
495542
if (e.element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {
496543
this.selectEntry(<IKeybindingItemEntry>e.element);

src/vs/workbench/parts/preferences/browser/media/keybindingsEditor.css

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,23 @@
1717
position: relative;
1818
}
1919

20-
.keybindings-editor > .keybindings-header > .search-container > .sort-by-precedence {
20+
.keybindings-editor > .keybindings-header > .search-container > .monaco-action-bar {
2121
position: absolute;
2222
top: 0;
2323
right: 10px;
2424
margin-top: 5px;
25+
}
26+
27+
.keybindings-editor > .keybindings-header > .search-container > .monaco-action-bar .action-item {
28+
width: 28px;
29+
}
30+
31+
.keybindings-editor > .keybindings-header > .search-container > .monaco-action-bar .action-item > .sort-by-precedence {
2532
background: url('sort_precedence.svg') center center no-repeat;
2633
}
2734

28-
.hc-black .keybindings-editor > .keybindings-header > .search-container > .sort-by-precedence,
29-
.vs-dark .keybindings-editor > .keybindings-header > .search-container > .sort-by-precedence {
35+
.hc-black .keybindings-editor > .keybindings-header > .search-container > .monaco-action-bar .action-item > .sort-by-precedence,
36+
.vs-dark .keybindings-editor > .keybindings-header > .search-container > .monaco-action-bar .action-item > .sort-by-precedence {
3037
background: url('sort_precedence_inverse.svg') center center no-repeat;
3138
}
3239

0 commit comments

Comments
 (0)