Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Append hint text to placeholder if input supports history #129324

Merged
merged 13 commits into from
Sep 17, 2021
Merged
2 changes: 2 additions & 0 deletions src/vs/base/browser/ui/findinput/findInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface IFindInputOptions extends IFindInputStyles {
readonly appendWholeWordsLabel?: string;
readonly appendRegexLabel?: string;
readonly history?: string[];
readonly showHistoryHint?: () => boolean;
}

export interface IFindInputStyles extends IInputBoxStyles {
Expand Down Expand Up @@ -150,6 +151,7 @@ export class FindInput extends Widget {
inputValidationErrorForeground: this.inputValidationErrorForeground,
inputValidationErrorBorder: this.inputValidationErrorBorder,
history,
showHistoryHint: options.showHistoryHint,
flexibleHeight,
flexibleWidth,
flexibleMaxHeight
Expand Down
2 changes: 2 additions & 0 deletions src/vs/base/browser/ui/findinput/replaceInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface IReplaceInputOptions extends IReplaceInputStyles {

readonly appendPreserveCaseLabel?: string;
readonly history?: string[];
readonly showHistoryHint?: () => boolean;
}

export interface IReplaceInputStyles extends IInputBoxStyles {
Expand Down Expand Up @@ -157,6 +158,7 @@ export class ReplaceInput extends Widget {
inputValidationErrorForeground: this.inputValidationErrorForeground,
inputValidationErrorBorder: this.inputValidationErrorBorder,
history,
showHistoryHint: options.showHistoryHint,
flexibleHeight,
flexibleWidth,
flexibleMaxHeight
Expand Down
64 changes: 62 additions & 2 deletions src/vs/base/browser/ui/inputbox/inputBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ const defaultOpts = {
export class InputBox extends Widget {
private contextViewProvider?: IContextViewProvider;
element: HTMLElement;
private input: HTMLInputElement;
protected input: HTMLInputElement;
private actionbar?: ActionBar;
private options: IInputOptions;
private message: IMessage | null;
private placeholder: string;
protected placeholder: string;
private tooltip: string;
private ariaLabel: string;
private validation?: IInputValidator;
Expand Down Expand Up @@ -634,15 +634,75 @@ export class InputBox extends Widget {

export interface IHistoryInputOptions extends IInputOptions {
history: string[];
readonly showHistoryHint?: () => boolean;
}

export class HistoryInputBox extends InputBox implements IHistoryNavigationWidget {

private readonly history: HistoryNavigator<string>;
private observer: MutationObserver | undefined;

constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IHistoryInputOptions) {
const NLS_PLACEHOLDER_HISTORY_HINT = nls.localize({ key: 'history.inputbox.hint', comment: ['Text will be prefixed with \u21C5 plus a single space, then used as a hint where input field keeps history'] }, "for history");
const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX = ` or \u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT}`;
const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS = ` (\u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT})`;
super(container, contextViewProvider, options);
this.history = new HistoryNavigator<string>(options.history, 100);

// Function to append the history suffix to the placeholder if necessary
const addSuffix = () => {
if (options.showHistoryHint && options.showHistoryHint() && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX) && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS) && this.history.getHistory().length) {
const suffix = this.placeholder.endsWith(')') ? NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX : NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS;
const suffixedPlaceholder = this.placeholder + suffix;
if (options.showPlaceholderOnFocus && document.activeElement !== this.input) {
this.placeholder = suffixedPlaceholder;
}
else {
this.setPlaceHolder(suffixedPlaceholder);
}
}
};

// Spot the change to the textarea class attribute which occurs when it changes between non-empty and empty,
// and add the history suffix to the placeholder if not yet present
this.observer = new MutationObserver((mutationList: MutationRecord[], observer: MutationObserver) => {
mutationList.forEach((mutation: MutationRecord) => {
if (!mutation.target.textContent) {
addSuffix();
}
});
});
this.observer.observe(this.input, { attributeFilter: ['class'] });

this.onfocus(this.input, () => addSuffix());
this.onblur(this.input, () => {
const resetPlaceholder = (historyHint: string) => {
if (!this.placeholder.endsWith(historyHint)) {
return false;
}
else {
const revertedPlaceholder = this.placeholder.slice(0, this.placeholder.length - historyHint.length);
if (options.showPlaceholderOnFocus) {
this.placeholder = revertedPlaceholder;
}
else {
this.setPlaceHolder(revertedPlaceholder);
}
return true;
}
};
if (!resetPlaceholder(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS)) {
resetPlaceholder(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX);
}
});
}

override dispose() {
super.dispose();
if (this.observer) {
this.observer.disconnect();
this.observer = undefined;
}
}

public addToHistory(): void {
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/contrib/debug/browser/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,8 +492,9 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
return this.instantiationService.createInstance(SelectReplActionViewItem, action, session);
} else if (action.id === FILTER_ACTION_ID) {
const filterHistory = JSON.parse(this.storageService.get(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')) as string[];
const showHistoryHint = () => { return this.keybindingService.lookupKeybinding('history.showPrevious')?.getElectronAccelerator() === 'Up' && this.keybindingService.lookupKeybinding('history.showNext')?.getElectronAccelerator() === 'Down'; };
this.filterActionViewItem = this.instantiationService.createInstance(ReplFilterActionViewItem, action,
localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude)"), this.filterState, filterHistory);
localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude)"), this.filterState, filterHistory, showHistoryHint);
return this.filterActionViewItem;
}

Expand Down
4 changes: 3 additions & 1 deletion src/vs/workbench/contrib/debug/browser/replFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
private placeholder: string,
private filters: ReplFilterState,
private history: string[],
private showHistoryHint: () => boolean,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IContextViewService private readonly contextViewService: IContextViewService) {
Expand Down Expand Up @@ -188,7 +189,8 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
private createInput(container: HTMLElement): void {
this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, {
placeholder: this.placeholder,
history: this.history
history: this.history,
showHistoryHint: this.showHistoryHint
}));
this._register(attachInputBoxStyler(this.filterInputBox, this.themeService));
this.filterInputBox.value = this.filters.filterText;
Expand Down
10 changes: 8 additions & 2 deletions src/vs/workbench/contrib/markers/browser/markersViewActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { BaseActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { IMarkersView } from 'vs/workbench/contrib/markers/browser/markers';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';

export interface IMarkersFiltersChangeEvent {
filterText?: boolean;
Expand Down Expand Up @@ -245,16 +246,19 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem {
private focusContextKey: IContextKey<boolean>;
private readonly filtersAction: IAction;
private actionbar: ActionBar | null = null;
private keybindingService;

constructor(
action: IAction,
private markersView: IMarkersView,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IThemeService private readonly themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService keybindingService: IKeybindingService
) {
super(null, action);
this.keybindingService = keybindingService;
this.focusContextKey = Constants.MarkerViewFilterFocusContextKey.bindTo(contextKeyService);
this.delayedFilterUpdate = new Delayer<void>(400);
this._register(toDisposable(() => this.delayedFilterUpdate.cancel()));
Expand Down Expand Up @@ -316,10 +320,12 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem {
}

private createInput(container: HTMLElement): void {
const showHistoryHint = () => { return this.keybindingService.lookupKeybinding('history.showPrevious')?.getElectronAccelerator() === 'Up' && this.keybindingService.lookupKeybinding('history.showNext')?.getElectronAccelerator() === 'Down'; };
this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, {
placeholder: Messages.MARKERS_PANEL_FILTER_PLACEHOLDER,
ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL,
history: this.markersView.filters.filterHistory
history: this.markersView.filters.filterHistory,
showHistoryHint
}));
this._register(attachInputBoxStyler(this.filterInputBox, this.themeService));
this.filterInputBox.value = this.markersView.filters.filterText;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ export class KeybindingsSearchWidget extends SearchWidget {

constructor(parent: HTMLElement, options: KeybindingsSearchOptions,
@IContextViewService contextViewService: IContextViewService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(parent, options, contextViewService, instantiationService, themeService, contextKeyService);
super(parent, options, contextViewService, instantiationService, themeService, contextKeyService, keybindingService);
this._register(attachInputBoxStyler(this.inputBox, themeService));
this._register(toDisposable(() => this.stopRecordingKeys()));
this._firstPart = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ConfigurationTarget } from 'vs/platform/configuration/common/configurat
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ILabelService } from 'vs/platform/label/common/label';
import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
Expand Down Expand Up @@ -370,7 +371,8 @@ export class SearchWidget extends Widget {
@IContextViewService private readonly contextViewService: IContextViewService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IContextKeyService private readonly contextKeyService: IContextKeyService
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IKeybindingService protected readonly keybindingService: IKeybindingService
) {
super();
this.create(parent);
Expand Down Expand Up @@ -420,7 +422,8 @@ export class SearchWidget extends Widget {
}

protected createInputBox(parent: HTMLElement): HistoryInputBox {
const box = this._register(new ContextScopedHistoryInputBox(parent, this.contextViewService, this.options, this.contextKeyService));
const showHistoryHint = () => { return this.keybindingService.lookupKeybinding('history.showPrevious')?.getElectronAccelerator() === 'Up' && this.keybindingService.lookupKeybinding('history.showNext')?.getElectronAccelerator() === 'Down'; };
const box = this._register(new ContextScopedHistoryInputBox(parent, this.contextViewService, { ...this.options, showHistoryHint }, this.contextKeyService));
this._register(attachInputBoxStyler(box, this.themeService));

return box;
Expand Down
14 changes: 10 additions & 4 deletions src/vs/workbench/contrib/search/browser/patternInputWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as nls from 'vs/nls';
import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { attachCheckboxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';

Expand Down Expand Up @@ -49,7 +50,8 @@ export class PatternInputWidget extends Widget implements IThemable {
constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null),
@IThemeService protected themeService: IThemeService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IConfigurationService protected readonly configurationService: IConfigurationService
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
) {
super();
options = {
Expand Down Expand Up @@ -143,6 +145,7 @@ export class PatternInputWidget extends Widget implements IThemable {
this.domNode.style.width = this.width + 'px';
this.domNode.classList.add('monaco-findInput');

const showHistoryHint = () => { return this.keybindingService.lookupKeybinding('history.showPrevious')?.getElectronAccelerator() === 'Up' && this.keybindingService.lookupKeybinding('history.showNext')?.getElectronAccelerator() === 'Down'; };
this.inputBox = new ContextScopedHistoryInputBox(this.domNode, this.contextViewProvider, {
placeholder: options.placeholder,
showPlaceholderOnFocus: options.showPlaceholderOnFocus,
Expand All @@ -151,7 +154,8 @@ export class PatternInputWidget extends Widget implements IThemable {
validationOptions: {
validation: undefined
},
history: options.history || []
history: options.history || [],
showHistoryHint
}, this.contextKeyService);
this._register(attachInputBoxStyler(this.inputBox, this.themeService));
this._register(this.inputBox.onDidChange(() => this._onSubmit.fire(true)));
Expand Down Expand Up @@ -192,8 +196,9 @@ export class IncludePatternInputWidget extends PatternInputWidget {
@IThemeService themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService);
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService, keybindingService);
}

private useSearchInEditorsBox!: Checkbox;
Expand Down Expand Up @@ -243,8 +248,9 @@ export class ExcludePatternInputWidget extends PatternInputWidget {
@IThemeService themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService);
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService, keybindingService);
}

private useExcludesAndIgnoreFilesBox!: Checkbox;
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/contrib/search/browser/searchView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export class SearchView extends ViewPane {

this.inputPatternIncludes = this._register(this.instantiationService.createInstance(IncludePatternInputWidget, folderIncludesList, this.contextViewService, {
ariaLabel: filesToIncludeTitle,
placeholder: nls.localize('placeholder.includes', "(e.g. *.ts, src/**/include)"),
placeholder: nls.localize('placeholder.includes', "e.g. *.ts, src/**/include"),
showPlaceholderOnFocus: true,
history: patternIncludesHistory,
}));
Expand All @@ -339,7 +339,7 @@ export class SearchView extends ViewPane {
dom.append(excludesList, $('h4', undefined, excludesTitle));
this.inputPatternExcludes = this._register(this.instantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, {
ariaLabel: excludesTitle,
placeholder: nls.localize('placeholder.excludes', "(e.g. *.ts, src/**/exclude)"),
placeholder: nls.localize('placeholder.excludes', "e.g. *.ts, src/**/exclude"),
showPlaceholderOnFocus: true,
history: patternExclusionsHistory,
}));
Expand Down
5 changes: 5 additions & 0 deletions src/vs/workbench/contrib/search/browser/searchWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export class SearchWidget extends Widget {

clearHistory(): void {
this.searchInput.inputBox.clearHistory();
this.replaceInput.inputBox.clearHistory();
}

showNextSearchTerm() {
Expand Down Expand Up @@ -302,6 +303,7 @@ export class SearchWidget extends Widget {
}

private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
const showHistoryHint = () => { return this.keyBindingService.lookupKeybinding('history.showPrevious')?.getElectronAccelerator() === 'Up' && this.keyBindingService.lookupKeybinding('history.showNext')?.getElectronAccelerator() === 'Down'; };
const inputOptions: IFindInputOptions = {
label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search'),
validation: (value: string) => this.validateSearchInput(value),
Expand All @@ -310,6 +312,7 @@ export class SearchWidget extends Widget {
appendWholeWordsLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleWholeWordCommandId), this.keyBindingService),
appendRegexLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleRegexCommandId), this.keyBindingService),
history: options.searchHistory,
showHistoryHint,
flexibleHeight: true,
flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT
};
Expand Down Expand Up @@ -392,12 +395,14 @@ export class SearchWidget extends Widget {
private renderReplaceInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
this.replaceContainer = dom.append(parent, dom.$('.replace-container.disabled'));
const replaceBox = dom.append(this.replaceContainer, dom.$('.replace-input'));
const showHistoryHint = () => { return this.keyBindingService.lookupKeybinding('history.showPrevious')?.getElectronAccelerator() === 'Up' && this.keyBindingService.lookupKeybinding('history.showNext')?.getElectronAccelerator() === 'Down'; };

this.replaceInput = this._register(new ContextScopedReplaceInput(replaceBox, this.contextViewService, {
label: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview'),
placeholder: nls.localize('search.replace.placeHolder', "Replace"),
appendPreserveCaseLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.TogglePreserveCaseId), this.keyBindingService),
history: options.replaceHistory,
showHistoryHint,
flexibleHeight: true,
flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT
}, this.contextKeyService, true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
value: this.state.text.value,
placeholderText: localize('testExplorerFilter', "Filter (e.g. text, !exclude, @tag)"),
},
history: this.history.get([]),
history: this.history.get([])
}));
this._register(attachSuggestEnabledInputBoxStyler(input, this.themeService));

Expand Down