Skip to content

Commit 9b507bc

Browse files
authored
Merge pull request microsoft#158616 from microsoft/tyriar/156179-quickinput-find
Support toggles in the quick pick, adopt in terminal run recent command
2 parents 91146b5 + 2ba72b5 commit 9b507bc

File tree

6 files changed

+111
-73
lines changed

6 files changed

+111
-73
lines changed

src/vs/base/browser/ui/findinput/findInput.ts

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Emitter, Event } from 'vs/base/common/event';
1616
import { KeyCode } from 'vs/base/common/keyCodes';
1717
import 'vs/css!./findInput';
1818
import * as nls from 'vs/nls';
19+
import { DisposableStore } from 'vs/base/common/lifecycle';
1920

2021

2122
export interface IFindInputOptions extends IFindInputStyles {
@@ -47,12 +48,12 @@ export class FindInput extends Widget {
4748

4849
static readonly OPTION_CHANGE: string = 'optionChange';
4950

50-
private contextViewProvider: IContextViewProvider;
5151
private placeholder: string;
5252
private validation?: IInputValidator;
5353
private label: string;
5454
private fixFocusOnOptionClickEnabled = true;
5555
private imeSessionInProgress = false;
56+
private additionalTogglesDisposables: DisposableStore = new DisposableStore();
5657

5758
protected inputActiveOptionBorder?: Color;
5859
protected inputActiveOptionForeground?: Color;
@@ -100,9 +101,8 @@ export class FindInput extends Widget {
100101
private _onRegexKeyDown = this._register(new Emitter<IKeyboardEvent>());
101102
public readonly onRegexKeyDown: Event<IKeyboardEvent> = this._onRegexKeyDown.event;
102103

103-
constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider, private readonly _showOptionButtons: boolean, options: IFindInputOptions) {
104+
constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, private readonly _showOptionButtons: boolean, options: IFindInputOptions) {
104105
super();
105-
this.contextViewProvider = contextViewProvider;
106106
this.placeholder = options.placeholder || '';
107107
this.validation = options.validation;
108108
this.label = options.label || NLS_DEFAULT_LABEL;
@@ -135,7 +135,7 @@ export class FindInput extends Widget {
135135
this.domNode = document.createElement('div');
136136
this.domNode.classList.add('monaco-findInput');
137137

138-
this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, {
138+
this.inputBox = this._register(new HistoryInputBox(this.domNode, contextViewProvider, {
139139
placeholder: this.placeholder || '',
140140
ariaLabel: this.label || '',
141141
validationOptions: {
@@ -254,27 +254,7 @@ export class FindInput extends Widget {
254254
this.regex.domNode.style.display = 'none';
255255
}
256256

257-
for (const toggle of options?.additionalToggles ?? []) {
258-
this._register(toggle);
259-
this.controls.appendChild(toggle.domNode);
260-
261-
this._register(toggle.onChange(viaKeyboard => {
262-
this._onDidOptionChange.fire(viaKeyboard);
263-
if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {
264-
this.inputBox.focus();
265-
}
266-
}));
267-
268-
this.additionalToggles.push(toggle);
269-
}
270-
271-
if (this.additionalToggles.length > 0) {
272-
this.controls.style.display = 'block';
273-
}
274-
275-
this.inputBox.paddingRight =
276-
(this._showOptionButtons ? this.caseSensitive.width() + this.wholeWords.width() + this.regex.width() : 0)
277-
+ this.additionalToggles.reduce((r, t) => r + t.width(), 0);
257+
this.setAdditionalToggles(options?.additionalToggles);
278258

279259
this.domNode.appendChild(this.controls);
280260

@@ -338,6 +318,37 @@ export class FindInput extends Widget {
338318
}
339319
}
340320

321+
public setAdditionalToggles(toggles: Toggle[] | undefined): void {
322+
for (const currentToggle of this.additionalToggles) {
323+
currentToggle.domNode.remove();
324+
}
325+
this.additionalToggles = [];
326+
this.additionalTogglesDisposables.dispose();
327+
this.additionalTogglesDisposables = new DisposableStore();
328+
329+
for (const toggle of toggles ?? []) {
330+
this.additionalTogglesDisposables.add(toggle);
331+
this.controls.appendChild(toggle.domNode);
332+
333+
this.additionalTogglesDisposables.add(toggle.onChange(viaKeyboard => {
334+
this._onDidOptionChange.fire(viaKeyboard);
335+
if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {
336+
this.inputBox.focus();
337+
}
338+
}));
339+
340+
this.additionalToggles.push(toggle);
341+
}
342+
343+
if (this.additionalToggles.length > 0) {
344+
this.controls.style.display = 'block';
345+
}
346+
347+
this.inputBox.paddingRight =
348+
(this._showOptionButtons ? this.caseSensitive.width() + this.wholeWords.width() + this.regex.width() : 0)
349+
+ this.additionalToggles.reduce((r, t) => r + t.width(), 0);
350+
}
351+
341352
public clear(): void {
342353
this.clearValidation();
343354
this.setValue('');

src/vs/base/parts/quickinput/browser/quickInput.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybi
1515
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
1616
import { IListOptions, IListStyles, List } from 'vs/base/browser/ui/list/listWidget';
1717
import { IProgressBarStyles, ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
18+
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
1819
import { Action } from 'vs/base/common/actions';
1920
import { equals } from 'vs/base/common/arrays';
2021
import { TimeoutTimer } from 'vs/base/common/async';
@@ -28,7 +29,7 @@ import { isIOS } from 'vs/base/common/platform';
2829
import Severity from 'vs/base/common/severity';
2930
import { isString, withNullAsUndefined } from 'vs/base/common/types';
3031
import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils';
31-
import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInput, IQuickInputButton, IQuickInputHideEvent, IQuickNavigateConfiguration, IQuickPick, IQuickPickDidAcceptEvent, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickWillAcceptEvent, ItemActivation, NO_KEY_MODS, QuickInputHideReason, QuickPickInput } from 'vs/base/parts/quickinput/common/quickInput';
32+
import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInput, IQuickInputButton, IQuickInputHideEvent, IQuickInputToggle, IQuickNavigateConfiguration, IQuickPick, IQuickPickDidAcceptEvent, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickWillAcceptEvent, ItemActivation, NO_KEY_MODS, QuickInputHideReason, QuickPickInput } from 'vs/base/parts/quickinput/common/quickInput';
3233
import 'vs/css!./media/quickInput';
3334
import { localize } from 'vs/nls';
3435
import { QuickInputBox } from './quickInputBox';
@@ -740,6 +741,14 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
740741
this.update();
741742
}
742743

744+
set toggles(toggles: IQuickInputToggle[] | undefined) {
745+
// HACK: Filter out toggles here that are not concrete Toggle objects. This is to workaround
746+
// a layering issue as quick input's interface is in common but Toggle is in browser and
747+
// it requires a HTMLElement on its interface
748+
const concreteToggles = toggles?.filter(opts => opts instanceof Toggle) as Toggle[];
749+
this.ui.inputBox.toggles = concreteToggles;
750+
}
751+
743752
onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
744753

745754
onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;

src/vs/base/parts/quickinput/browser/quickInputBox.ts

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import * as dom from 'vs/base/browser/dom';
77
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
88
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
9-
import { IInputBoxStyles, InputBox, IRange, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
9+
import { FindInput } from 'vs/base/browser/ui/findinput/findInput';
10+
import { IInputBoxStyles, IRange, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
11+
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
1012
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
1113
import Severity from 'vs/base/common/severity';
1214
import 'vs/css!./media/quickInput';
@@ -16,113 +18,117 @@ const $ = dom.$;
1618
export class QuickInputBox extends Disposable {
1719

1820
private container: HTMLElement;
19-
private inputBox: InputBox;
21+
private findInput: FindInput;
2022

2123
constructor(
2224
private parent: HTMLElement
2325
) {
2426
super();
2527
this.container = dom.append(this.parent, $('.quick-input-box'));
26-
this.inputBox = this._register(new InputBox(this.container, undefined));
28+
this.findInput = this._register(new FindInput(this.container, undefined, false, { label: '' }));
2729
}
2830

2931
onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => {
30-
return dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
32+
return dom.addDisposableListener(this.findInput.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
3133
handler(new StandardKeyboardEvent(e));
3234
});
3335
};
3436

3537
onMouseDown = (handler: (event: StandardMouseEvent) => void): IDisposable => {
36-
return dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => {
38+
return dom.addDisposableListener(this.findInput.inputBox.inputElement, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => {
3739
handler(new StandardMouseEvent(e));
3840
});
3941
};
4042

4143
onDidChange = (handler: (event: string) => void): IDisposable => {
42-
return this.inputBox.onDidChange(handler);
44+
return this.findInput.onDidChange(handler);
4345
};
4446

4547
get value() {
46-
return this.inputBox.value;
48+
return this.findInput.getValue();
4749
}
4850

4951
set value(value: string) {
50-
this.inputBox.value = value;
52+
this.findInput.setValue(value);
5153
}
5254

5355
select(range: IRange | null = null): void {
54-
this.inputBox.select(range);
56+
this.findInput.inputBox.select(range);
5557
}
5658

5759
isSelectionAtEnd(): boolean {
58-
return this.inputBox.isSelectionAtEnd();
60+
return this.findInput.inputBox.isSelectionAtEnd();
5961
}
6062

6163
setPlaceholder(placeholder: string): void {
62-
this.inputBox.setPlaceHolder(placeholder);
64+
this.findInput.inputBox.setPlaceHolder(placeholder);
6365
}
6466

6567
get placeholder() {
66-
return this.inputBox.inputElement.getAttribute('placeholder') || '';
68+
return this.findInput.inputBox.inputElement.getAttribute('placeholder') || '';
6769
}
6870

6971
set placeholder(placeholder: string) {
70-
this.inputBox.setPlaceHolder(placeholder);
72+
this.findInput.inputBox.setPlaceHolder(placeholder);
7173
}
7274

7375
get ariaLabel() {
74-
return this.inputBox.getAriaLabel();
76+
return this.findInput.inputBox.getAriaLabel();
7577
}
7678

7779
set ariaLabel(ariaLabel: string) {
78-
this.inputBox.setAriaLabel(ariaLabel);
80+
this.findInput.inputBox.setAriaLabel(ariaLabel);
7981
}
8082

8183
get password() {
82-
return this.inputBox.inputElement.type === 'password';
84+
return this.findInput.inputBox.inputElement.type === 'password';
8385
}
8486

8587
set password(password: boolean) {
86-
this.inputBox.inputElement.type = password ? 'password' : 'text';
88+
this.findInput.inputBox.inputElement.type = password ? 'password' : 'text';
8789
}
8890

8991
set enabled(enabled: boolean) {
90-
this.inputBox.setEnabled(enabled);
92+
this.findInput.setEnabled(enabled);
93+
}
94+
95+
set toggles(toggles: Toggle[] | undefined) {
96+
this.findInput.setAdditionalToggles(toggles);
9197
}
9298

9399
hasFocus(): boolean {
94-
return this.inputBox.hasFocus();
100+
return this.findInput.inputBox.hasFocus();
95101
}
96102

97103
setAttribute(name: string, value: string): void {
98-
this.inputBox.inputElement.setAttribute(name, value);
104+
this.findInput.inputBox.inputElement.setAttribute(name, value);
99105
}
100106

101107
removeAttribute(name: string): void {
102-
this.inputBox.inputElement.removeAttribute(name);
108+
this.findInput.inputBox.inputElement.removeAttribute(name);
103109
}
104110

105111
showDecoration(decoration: Severity): void {
106112
if (decoration === Severity.Ignore) {
107-
this.inputBox.hideMessage();
113+
this.findInput.clearMessage();
108114
} else {
109-
this.inputBox.showMessage({ type: decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR, content: '' });
115+
this.findInput.showMessage({ type: decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR, content: '' });
110116
}
111117
}
112118

113119
stylesForType(decoration: Severity) {
114-
return this.inputBox.stylesForType(decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR);
120+
return this.findInput.inputBox.stylesForType(decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR);
115121
}
116122

117123
setFocus(): void {
118-
this.inputBox.focus();
124+
this.findInput.focus();
119125
}
120126

121127
layout(): void {
122-
this.inputBox.layout();
128+
this.findInput.inputBox.layout();
123129
}
124130

125131
style(styles: IInputBoxStyles): void {
126-
this.inputBox.style(styles);
132+
this.findInput.style(styles);
127133
}
128134
}

src/vs/base/parts/quickinput/common/quickInput.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,15 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
340340
hideInput: boolean;
341341

342342
hideCheckAll: boolean;
343+
344+
/**
345+
* A set of `Toggle` objects to add to the input box.
346+
*/
347+
toggles: IQuickInputToggle[] | undefined;
348+
}
349+
350+
export interface IQuickInputToggle {
351+
onChange: Event<boolean /* via keyboard */>;
343352
}
344353

345354
export interface IInputBox extends IQuickInput {

src/vs/platform/quickinput/browser/quickPickPin.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,22 @@ const buttonClasses = [pinButtonClass, pinnedButtonClass];
1919
* Shows the quickpick once formatted.
2020
*/
2121
export async function showWithPinnedItems(storageService: IStorageService, storageKey: string, quickPick: IQuickPick<IQuickPickItem>, filterDuplicates?: boolean): Promise<void> {
22+
const itemsWithoutPinned = quickPick.items;
23+
let itemsWithPinned = _formatPinnedItems(storageKey, quickPick, storageService, undefined, filterDuplicates);
2224
quickPick.onDidTriggerItemButton(async buttonEvent => {
2325
const expectedButton = buttonEvent.button.iconClass && buttonClasses.includes(buttonEvent.button.iconClass);
2426
if (expectedButton) {
25-
quickPick.items = await _formatPinnedItems(storageKey, quickPick, storageService, buttonEvent.item, filterDuplicates);
27+
quickPick.items = itemsWithoutPinned;
28+
itemsWithPinned = _formatPinnedItems(storageKey, quickPick, storageService, buttonEvent.item, filterDuplicates);
29+
quickPick.items = quickPick.value ? itemsWithoutPinned : itemsWithPinned;
2630
}
2731
});
2832
quickPick.onDidChangeValue(async value => {
29-
// don't show pinned items in the search results
30-
quickPick.items = value ? quickPick.items.filter(i => i.type !== 'separator' && !i.buttons?.find(b => b.iconClass === pinnedButtonClass)) : quickPick.items;
33+
// Return pinned items if there is no search value
34+
quickPick.items = value ? itemsWithoutPinned : itemsWithPinned;
3135
});
32-
quickPick.items = await _formatPinnedItems(storageKey, quickPick, storageService, undefined, filterDuplicates);
33-
await quickPick.show();
36+
quickPick.items = quickPick.value ? itemsWithoutPinned : itemsWithPinned;
37+
quickPick.show();
3438
}
3539

3640
function _formatPinnedItems(storageKey: string, quickPick: IQuickPick<IQuickPickItem>, storageService: IStorageService, changedItem?: IQuickPickItem, filterDuplicates?: boolean): QuickPickItem[] {

src/vs/workbench/contrib/terminal/browser/terminalInstance.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/commo
4848
import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore';
4949
import { IProcessDataEvent, IProcessPropertyMap, IReconnectionProperties, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, ShellIntegrationStatus, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal';
5050
import { escapeNonWindowsPath, collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment';
51-
import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
51+
import { activeContrastBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
5252
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
5353
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
5454
import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
@@ -90,6 +90,7 @@ import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
9090
import { TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks';
9191
import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys';
9292
import { showWithPinnedItems } from 'vs/platform/quickinput/browser/quickPickPin';
93+
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
9394

9495
const enum Constants {
9596
/**
@@ -1010,27 +1011,25 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
10101011
if (items.length === 0) {
10111012
return;
10121013
}
1014+
const fuzzySearchToggle = new Toggle({
1015+
title: 'Fuzzy search',
1016+
icon: Codicon.searchFuzzy,
1017+
isChecked: filterMode === 'fuzzy',
1018+
inputActiveOptionBorder: this._themeService.getColorTheme().getColor(inputActiveOptionBorder),
1019+
inputActiveOptionForeground: this._themeService.getColorTheme().getColor(inputActiveOptionForeground),
1020+
inputActiveOptionBackground: this._themeService.getColorTheme().getColor(inputActiveOptionBackground)
1021+
});
1022+
fuzzySearchToggle.onChange(() => {
1023+
this.runRecent(type, fuzzySearchToggle.checked ? 'fuzzy' : 'contiguous', quickPick.value);
1024+
});
10131025
const outputProvider = this._instantiationService.createInstance(TerminalOutputProvider);
10141026
const quickPick = this._quickInputService.createQuickPick<IQuickPickItem & { rawLabel: string }>();
10151027
const originalItems = items;
10161028
quickPick.items = [...originalItems];
10171029
quickPick.sortByLabel = false;
10181030
quickPick.placeholder = placeholder;
1019-
quickPick.customButton = true;
10201031
quickPick.matchOnLabelMode = filterMode || 'contiguous';
1021-
if (filterMode === 'fuzzy') {
1022-
quickPick.customLabel = nls.localize('terminal.contiguousSearch', 'Use Contiguous Search');
1023-
quickPick.onDidCustom(() => {
1024-
quickPick.hide();
1025-
this.runRecent(type, 'contiguous', quickPick.value);
1026-
});
1027-
} else {
1028-
quickPick.customLabel = nls.localize('terminal.fuzzySearch', 'Use Fuzzy Search');
1029-
quickPick.onDidCustom(() => {
1030-
quickPick.hide();
1031-
this.runRecent(type, 'fuzzy', quickPick.value);
1032-
});
1033-
}
1032+
quickPick.toggles = [fuzzySearchToggle];
10341033
quickPick.onDidTriggerItemButton(async e => {
10351034
if (e.button === removeFromCommandHistoryButton) {
10361035
if (type === 'command') {

0 commit comments

Comments
 (0)