Skip to content

Commit

Permalink
refactor: move input as a widget (#170231)
Browse files Browse the repository at this point in the history
* #116855 refactor: move input as a widget

* add dbl click listener

* clear disposables on render
  • Loading branch information
sandy081 authored Dec 29, 2022
1 parent e9f533a commit ab08a1b
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 88 deletions.
176 changes: 99 additions & 77 deletions src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { localize } from 'vs/nls';
import { Delayer } from 'vs/base/common/async';
import * as DOM from 'vs/base/browser/dom';
import { isIOS, OS } from 'vs/base/common/platform';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { ToggleActionViewItem } from 'vs/base/browser/ui/toggle/toggle';
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
Expand Down Expand Up @@ -37,7 +37,6 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { CancellationToken } from 'vs/base/common/cancellation';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { Emitter, Event } from 'vs/base/common/event';
import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
Expand All @@ -52,6 +51,7 @@ import { defaultInputBoxStyles, defaultKeybindingLabelStyles, defaultToggleStyle
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { isString } from 'vs/base/common/types';
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';

const $ = DOM.$;

Expand Down Expand Up @@ -1021,38 +1021,24 @@ class SourceColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ISour
}
}

interface IWhenColumnTemplateData {
readonly element: HTMLElement;
readonly whenContainer: HTMLElement;
readonly whenLabel: HighlightedLabel;
readonly whenInput: InputBox;
readonly renderDisposables: DisposableStore;
readonly onDidAccept: Event<void>;
readonly onDidReject: Event<void>;
readonly disposables: DisposableStore;
}
class WhenInputWidget extends Disposable {

class WhenColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IWhenColumnTemplateData> {
private readonly input: InputBox;
readonly el: HTMLElement;

static readonly TEMPLATE_ID = 'when';
private readonly _onDidAccept = this._register(new Emitter<string>());
readonly onDidAccept = this._onDidAccept.event;

readonly templateId: string = WhenColumnRenderer.TEMPLATE_ID;
private whenFocusContextKey: IContextKey<boolean>;
private readonly _onDidReject = this._register(new Emitter<void>());
readonly onDidReject = this._onDidReject.event;

constructor(
private readonly keybindingsEditor: KeybindingsEditor,
@IContextViewService private readonly contextViewService: IContextViewService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextViewService contextViewService: IContextViewService,
) {
this.whenFocusContextKey = CONTEXT_WHEN_FOCUS.bindTo(contextKeyService);
}

renderTemplate(container: HTMLElement): IWhenColumnTemplateData {
const element = DOM.append(container, $('.when'));
super();

const whenContainer = DOM.append(element, $('div.when-label'));
const whenLabel = new HighlightedLabel(whenContainer);
const whenInput = new InputBox(element, this.contextViewService, {
this.input = new InputBox(this.el = DOM.$('.when-input'), contextViewService, {
validationOptions: {
validation: (value) => {
try {
Expand All @@ -1071,76 +1057,122 @@ class WhenColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IWhenCo
inputBoxStyles: defaultInputBoxStyles
});

const disposables = new DisposableStore();

const _onDidAccept: Emitter<void> = disposables.add(new Emitter<void>());
const onDidAccept: Event<void> = _onDidAccept.event;

const _onDidReject: Emitter<void> = disposables.add(new Emitter<void>());
const onDidReject: Event<void> = _onDidReject.event;

const hideInputBox = () => {
element.classList.remove('input-mode');
container.style.paddingLeft = '10px';
};

disposables.add(DOM.addStandardDisposableListener(whenInput.inputElement, DOM.EventType.KEY_DOWN, e => {
this._register(DOM.addStandardDisposableListener(this.input.inputElement, DOM.EventType.KEY_DOWN, e => {
let handled = false;
if (e.equals(KeyCode.Enter)) {
hideInputBox();
_onDidAccept.fire();
this._onDidAccept.fire(this.input.value);
handled = true;
} else if (e.equals(KeyCode.Escape)) {
hideInputBox();
_onDidReject.fire();
this._onDidReject.fire();
handled = true;
}
if (handled) {
e.preventDefault();
e.stopPropagation();
}
}));
disposables.add((DOM.addDisposableListener(whenInput.inputElement, DOM.EventType.FOCUS, () => {
this.whenFocusContextKey.set(true);
})));
disposables.add((DOM.addDisposableListener(whenInput.inputElement, DOM.EventType.BLUR, () => {
this.whenFocusContextKey.set(false);
hideInputBox();
_onDidReject.fire();

const whenFocusContextKey = CONTEXT_WHEN_FOCUS.bindTo(contextKeyService);
this._register((DOM.addDisposableListener(this.input.inputElement, DOM.EventType.FOCUS, () => whenFocusContextKey.set(true))));
this._register((DOM.addDisposableListener(this.input.inputElement, DOM.EventType.BLUR, () => {
whenFocusContextKey.set(false);
this._onDidReject.fire();
})));

// stop double click action on the input #148493
disposables.add((DOM.addDisposableListener(whenInput.inputElement, DOM.EventType.DBLCLICK, e => DOM.EventHelper.stop(e))));
this._register((DOM.addDisposableListener(this.input.inputElement, DOM.EventType.DBLCLICK, e => DOM.EventHelper.stop(e))));
}

const renderDisposables = disposables.add(new DisposableStore());
layout(dimension: DOM.Dimension): void {
this.input.element.style.height = `${dimension.height}px`;
}

show(value: string): void {
DOM.show(this.el);
this.input.value = value;
this.input.focus();
this.input.select();
}

hide(): void {
DOM.hide(this.el);
}
}

interface IWhenColumnTemplateData {
readonly element: HTMLElement;
readonly whenLabelContainer: HTMLElement;
readonly whenInputContainer: HTMLElement;
readonly whenLabel: HighlightedLabel;
readonly disposables: DisposableStore;
}

class WhenColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IWhenColumnTemplateData> {

static readonly TEMPLATE_ID = 'when';

readonly templateId: string = WhenColumnRenderer.TEMPLATE_ID;
private readonly whenInputWidget: WhenInputWidget;

constructor(
private readonly keybindingsEditor: KeybindingsEditor,
@IInstantiationService instantiationService: IInstantiationService,
) {
this.whenInputWidget = instantiationService.createInstance(WhenInputWidget);
}

renderTemplate(container: HTMLElement): IWhenColumnTemplateData {
const element = DOM.append(container, $('.when'));

const whenLabelContainer = DOM.append(element, $('div.when-label'));
const whenLabel = new HighlightedLabel(whenLabelContainer);

const whenInputContainer = DOM.append(element, $('div.when-input-container'));

return {
element,
whenContainer,
whenLabelContainer,
whenLabel,
whenInput,
onDidAccept,
onDidReject,
renderDisposables,
disposables,
whenInputContainer,
disposables: new DisposableStore(),
};
}

renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: IWhenColumnTemplateData, height: number | undefined): void {
templateData.renderDisposables.clear();

templateData.renderDisposables.add(this.keybindingsEditor.onDefineWhenExpression(e => {
templateData.disposables.clear();
const whenInputDisposables = templateData.disposables.add(new DisposableStore());
templateData.disposables.add(this.keybindingsEditor.onDefineWhenExpression(e => {
if (keybindingItemEntry === e) {
templateData.element.classList.add('input-mode');
templateData.whenInput.focus();
templateData.whenInput.select();
DOM.append(templateData.whenInputContainer, this.whenInputWidget.el);
this.whenInputWidget.show(keybindingItemEntry.keybindingItem.when || '');
this.whenInputWidget.layout(new DOM.Dimension(templateData.element.parentElement!.clientWidth, 24));

const hideInputWidget = () => {
whenInputDisposables.clear();
templateData.element.classList.remove('input-mode');
templateData.element.parentElement!.style.paddingLeft = '10px';
DOM.clearNode(templateData.whenInputContainer);
this.whenInputWidget.hide();
};

whenInputDisposables.add(this.whenInputWidget.onDidAccept(value => {
hideInputWidget();
this.keybindingsEditor.updateKeybinding(keybindingItemEntry, keybindingItemEntry.keybindingItem.keybinding ? keybindingItemEntry.keybindingItem.keybinding.getUserSettingsLabel() || '' : '', value);
this.keybindingsEditor.selectKeybinding(keybindingItemEntry);
}));

whenInputDisposables.add(this.whenInputWidget.onDidReject(value => {
hideInputWidget();
this.keybindingsEditor.selectKeybinding(keybindingItemEntry);
}));

templateData.element.parentElement!.style.paddingLeft = '0px';
}
}));

templateData.whenInput.value = keybindingItemEntry.keybindingItem.when || '';
templateData.whenContainer.classList.toggle('code', !!keybindingItemEntry.keybindingItem.when);
templateData.whenContainer.classList.toggle('empty', !keybindingItemEntry.keybindingItem.when);
templateData.whenLabelContainer.classList.toggle('code', !!keybindingItemEntry.keybindingItem.when);
templateData.whenLabelContainer.classList.toggle('empty', !keybindingItemEntry.keybindingItem.when);

if (keybindingItemEntry.keybindingItem.when) {
templateData.whenLabel.set(keybindingItemEntry.keybindingItem.when, keybindingItemEntry.whenMatches);
Expand All @@ -1152,20 +1184,10 @@ class WhenColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IWhenCo
templateData.element.title = '';
}

templateData.renderDisposables.add(templateData.onDidAccept(() => {
this.keybindingsEditor.updateKeybinding(keybindingItemEntry, keybindingItemEntry.keybindingItem.keybinding ? keybindingItemEntry.keybindingItem.keybinding.getUserSettingsLabel() || '' : '', templateData.whenInput.value);
this.keybindingsEditor.selectKeybinding(keybindingItemEntry);
}));

templateData.renderDisposables.add(templateData.onDidReject(() => {
templateData.whenInput.value = keybindingItemEntry.keybindingItem.when || '';
this.keybindingsEditor.selectKeybinding(keybindingItemEntry);
}));
}

disposeTemplate(templateData: IWhenColumnTemplateData): void {
templateData.disposables.dispose();
templateData.renderDisposables.dispose();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,24 +142,14 @@
padding-left: 4px;
}

.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table-tr .monaco-table-td .when:not(.input-mode) .monaco-inputbox,
.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table-tr .monaco-table-td .when.input-mode .when-label {
display: none;
}

.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table-tr .monaco-table-td .when .monaco-inputbox {
line-height: normal;
}

.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table-tr .monaco-table-td .when .monaco-inputbox input {
.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table-tr .monaco-table-td .when .suggest-input-container {
padding-left: 10px;
}

.monaco-workbench.mac .keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table-tr .monaco-table-td .when .monaco-inputbox,
.monaco-workbench.mac .keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table-tr .monaco-table-td .when .monaco-inputbox {
height: 24px;
}

/** Source column styling **/
.keybindings-editor > .keybindings-body > .keybindings-table-container .monaco-table-tr .monaco-table-td .source a {
color: var(--vscode-textLink-foreground);
Expand Down

0 comments on commit ab08a1b

Please sign in to comment.