Archived
1
0

Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@ -0,0 +1,280 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.quick-input-widget {
position: absolute;
width: 600px;
z-index: 2000;
padding-bottom: 6px;
left: 50%;
margin-left: -300px;
}
.quick-input-titlebar {
display: flex;
}
.quick-input-left-action-bar {
display: flex;
margin-left: 4px;
flex: 1;
}
.quick-input-left-action-bar.monaco-action-bar .actions-container {
justify-content: flex-start;
}
.quick-input-title {
padding: 3px 0px;
text-align: center;
}
.quick-input-right-action-bar {
display: flex;
margin-right: 4px;
flex: 1;
}
.quick-input-titlebar .monaco-action-bar .action-label.codicon {
margin: 0;
width: 19px;
height: 100%;
background-position: center;
background-repeat: no-repeat;
}
.quick-input-description {
margin: 6px;
}
.quick-input-header .quick-input-description {
margin: 4px 2px;
}
.quick-input-header {
display: flex;
padding: 6px 6px 0px 6px;
margin-bottom: -2px;
}
.quick-input-widget.hidden-input .quick-input-header {
/* reduce margins and paddings when input box hidden */
padding: 0;
margin-bottom: 0;
}
.quick-input-and-message {
display: flex;
flex-direction: column;
flex-grow: 1;
position: relative;
}
.quick-input-check-all {
align-self: center;
margin: 0;
}
.quick-input-filter {
flex-grow: 1;
display: flex;
position: relative;
}
.quick-input-box {
flex-grow: 1;
}
.quick-input-widget.show-checkboxes .quick-input-box,
.quick-input-widget.show-checkboxes .quick-input-message {
margin-left: 5px;
}
.quick-input-visible-count {
position: absolute;
left: -10000px;
}
.quick-input-count {
align-self: center;
position: absolute;
right: 4px;
display: flex;
align-items: center;
}
.quick-input-count .monaco-count-badge {
vertical-align: middle;
padding: 2px 4px;
border-radius: 2px;
min-height: auto;
line-height: normal;
}
.quick-input-action {
margin-left: 6px;
}
.quick-input-action .monaco-text-button {
font-size: 11px;
padding: 0 6px;
display: flex;
height: 100%;
align-items: center;
}
.quick-input-message {
margin-top: -1px;
padding: 5px 5px 2px 5px;
}
.quick-input-progress.monaco-progress-container {
position: relative;
}
.quick-input-progress.monaco-progress-container,
.quick-input-progress.monaco-progress-container .progress-bit {
height: 2px;
}
.quick-input-list {
line-height: 22px;
margin-top: 6px;
}
.quick-input-widget.hidden-input .quick-input-list {
margin-top: 0; /* reduce margins when input box hidden */
}
.quick-input-list .monaco-list {
overflow: hidden;
max-height: calc(20 * 22px);
}
.quick-input-list .quick-input-list-entry {
box-sizing: border-box;
overflow: hidden;
display: flex;
height: 100%;
padding: 0 6px;
}
.quick-input-list .quick-input-list-entry.quick-input-list-separator-border {
border-top-width: 1px;
border-top-style: solid;
}
.quick-input-list .monaco-list-row:first-child .quick-input-list-entry.quick-input-list-separator-border {
border-top-style: none;
}
.quick-input-list .quick-input-list-label {
overflow: hidden;
display: flex;
height: 100%;
flex: 1;
}
.quick-input-list .quick-input-list-checkbox {
align-self: center;
margin: 0;
}
.quick-input-list .quick-input-list-rows {
overflow: hidden;
text-overflow: ellipsis;
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
margin-left: 5px;
}
.quick-input-widget.show-checkboxes .quick-input-list .quick-input-list-rows {
margin-left: 10px;
}
.quick-input-widget .quick-input-list .quick-input-list-checkbox {
display: none;
}
.quick-input-widget.show-checkboxes .quick-input-list .quick-input-list-checkbox {
display: inline;
}
.quick-input-list .quick-input-list-rows > .quick-input-list-row {
display: flex;
align-items: center;
}
.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label,
.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label .monaco-icon-label-container > .monaco-icon-name-container {
flex: 1; /* make sure the icon label grows within the row */
}
.quick-input-list .quick-input-list-rows > .quick-input-list-row .codicon[class*='codicon-'] {
vertical-align: sub;
}
.quick-input-list .quick-input-list-rows .monaco-highlighted-label span {
opacity: 1;
}
.quick-input-list .quick-input-list-entry .quick-input-list-entry-keybinding {
margin-right: 8px; /* separate from the separator label or scrollbar if any */
}
.quick-input-list .quick-input-list-label-meta {
opacity: 0.7;
line-height: normal;
text-overflow: ellipsis;
overflow: hidden;
}
.quick-input-list .monaco-highlighted-label .highlight {
font-weight: bold;
}
.quick-input-list .quick-input-list-entry .quick-input-list-separator {
margin-right: 8px; /* separate from keybindings or actions */
}
.quick-input-list .quick-input-list-entry-action-bar {
display: flex;
flex: 0;
overflow: visible;
}
.quick-input-list .quick-input-list-entry-action-bar .action-label {
/*
* By default, actions in the quick input action bar are hidden
* until hovered over them or selected.
*/
display: none;
}
.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon {
margin: 0;
height: 100%;
padding: 0 2px;
vertical-align: middle;
}
.quick-input-list .quick-input-list-entry-action-bar {
margin-top: 1px;
}
.quick-input-list .quick-input-list-entry-action-bar {
margin-right: 4px; /* separate from scrollbar */
}
.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon {
margin-right: 4px; /* separate actions */
}
.quick-input-list .quick-input-list-entry .quick-input-list-entry-action-bar .action-label.always-visible,
.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar .action-label,
.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar .action-label {
display: flex;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,128 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/quickInput';
import * as dom from 'vs/base/browser/dom';
import { InputBox, IRange, MessageType, IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import Severity from 'vs/base/common/severity';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
const $ = dom.$;
export class QuickInputBox extends Disposable {
private container: HTMLElement;
private inputBox: InputBox;
constructor(
private parent: HTMLElement
) {
super();
this.container = dom.append(this.parent, $('.quick-input-box'));
this.inputBox = this._register(new InputBox(this.container, undefined));
}
onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => {
return dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
handler(new StandardKeyboardEvent(e));
});
};
onMouseDown = (handler: (event: StandardMouseEvent) => void): IDisposable => {
return dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => {
handler(new StandardMouseEvent(e));
});
};
onDidChange = (handler: (event: string) => void): IDisposable => {
return this.inputBox.onDidChange(handler);
};
get value() {
return this.inputBox.value;
}
set value(value: string) {
this.inputBox.value = value;
}
select(range: IRange | null = null): void {
this.inputBox.select(range);
}
isSelectionAtEnd(): boolean {
return this.inputBox.isSelectionAtEnd();
}
setPlaceholder(placeholder: string): void {
this.inputBox.setPlaceHolder(placeholder);
}
get placeholder() {
return this.inputBox.inputElement.getAttribute('placeholder') || '';
}
set placeholder(placeholder: string) {
this.inputBox.setPlaceHolder(placeholder);
}
get ariaLabel() {
return this.inputBox.getAriaLabel();
}
set ariaLabel(ariaLabel: string) {
this.inputBox.setAriaLabel(ariaLabel);
}
get password() {
return this.inputBox.inputElement.type === 'password';
}
set password(password: boolean) {
this.inputBox.inputElement.type = password ? 'password' : 'text';
}
set enabled(enabled: boolean) {
this.inputBox.setEnabled(enabled);
}
hasFocus(): boolean {
return this.inputBox.hasFocus();
}
setAttribute(name: string, value: string): void {
this.inputBox.inputElement.setAttribute(name, value);
}
removeAttribute(name: string): void {
this.inputBox.inputElement.removeAttribute(name);
}
showDecoration(decoration: Severity): void {
if (decoration === Severity.Ignore) {
this.inputBox.hideMessage();
} else {
this.inputBox.showMessage({ type: decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR, content: '' });
}
}
stylesForType(decoration: Severity) {
return this.inputBox.stylesForType(decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR);
}
setFocus(): void {
this.inputBox.focus();
}
layout(): void {
this.inputBox.layout();
}
style(styles: IInputBoxStyles): void {
this.inputBox.style(styles);
}
}

View File

@ -0,0 +1,726 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/quickInput';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
import * as dom from 'vs/base/browser/dom';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput';
import { IMatch } from 'vs/base/common/filters';
import { matchesFuzzyCodiconAware, parseCodicons } from 'vs/base/common/codicon';
import { compareAnything } from 'vs/base/common/comparers';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { memoize } from 'vs/base/common/decorators';
import { range } from 'vs/base/common/arrays';
import * as platform from 'vs/base/common/platform';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput';
import { IListOptions, List, IListStyles, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
import { localize } from 'vs/nls';
const $ = dom.$;
interface IListElement {
readonly index: number;
readonly item: IQuickPickItem;
readonly saneLabel: string;
readonly saneAriaLabel: string;
readonly saneDescription?: string;
readonly saneDetail?: string;
readonly labelHighlights?: IMatch[];
readonly descriptionHighlights?: IMatch[];
readonly detailHighlights?: IMatch[];
readonly checked: boolean;
readonly separator?: IQuickPickSeparator;
readonly fireButtonTriggered: (event: IQuickPickItemButtonEvent<IQuickPickItem>) => void;
}
class ListElement implements IListElement, IDisposable {
index!: number;
item!: IQuickPickItem;
saneLabel!: string;
saneAriaLabel!: string;
saneDescription?: string;
saneDetail?: string;
hidden = false;
private readonly _onChecked = new Emitter<boolean>();
onChecked = this._onChecked.event;
_checked?: boolean;
get checked() {
return !!this._checked;
}
set checked(value: boolean) {
if (value !== this._checked) {
this._checked = value;
this._onChecked.fire(value);
}
}
separator?: IQuickPickSeparator;
labelHighlights?: IMatch[];
descriptionHighlights?: IMatch[];
detailHighlights?: IMatch[];
fireButtonTriggered!: (event: IQuickPickItemButtonEvent<IQuickPickItem>) => void;
constructor(init: IListElement) {
Object.assign(this, init);
}
dispose() {
this._onChecked.dispose();
}
}
interface IListElementTemplateData {
entry: HTMLDivElement;
checkbox: HTMLInputElement;
label: IconLabel;
keybinding: KeybindingLabel;
detail: HighlightedLabel;
separator: HTMLDivElement;
actionBar: ActionBar;
element: ListElement;
toDisposeElement: IDisposable[];
toDisposeTemplate: IDisposable[];
}
class ListElementRenderer implements IListRenderer<ListElement, IListElementTemplateData> {
static readonly ID = 'listelement';
get templateId() {
return ListElementRenderer.ID;
}
renderTemplate(container: HTMLElement): IListElementTemplateData {
const data: IListElementTemplateData = Object.create(null);
data.toDisposeElement = [];
data.toDisposeTemplate = [];
data.entry = dom.append(container, $('.quick-input-list-entry'));
// Checkbox
const label = dom.append(data.entry, $('label.quick-input-list-label'));
data.toDisposeTemplate.push(dom.addStandardDisposableListener(label, dom.EventType.CLICK, e => {
if (!data.checkbox.offsetParent) { // If checkbox not visible:
e.preventDefault(); // Prevent toggle of checkbox when it is immediately shown afterwards. #91740
}
}));
data.checkbox = <HTMLInputElement>dom.append(label, $('input.quick-input-list-checkbox'));
data.checkbox.type = 'checkbox';
data.toDisposeTemplate.push(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => {
data.element.checked = data.checkbox.checked;
}));
// Rows
const rows = dom.append(label, $('.quick-input-list-rows'));
const row1 = dom.append(rows, $('.quick-input-list-row'));
const row2 = dom.append(rows, $('.quick-input-list-row'));
// Label
data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportCodicons: true });
// Keybinding
const keybindingContainer = dom.append(row1, $('.quick-input-list-entry-keybinding'));
data.keybinding = new KeybindingLabel(keybindingContainer, platform.OS);
// Detail
const detailContainer = dom.append(row2, $('.quick-input-list-label-meta'));
data.detail = new HighlightedLabel(detailContainer, true);
// Separator
data.separator = dom.append(data.entry, $('.quick-input-list-separator'));
// Actions
data.actionBar = new ActionBar(data.entry);
data.actionBar.domNode.classList.add('quick-input-list-entry-action-bar');
data.toDisposeTemplate.push(data.actionBar);
return data;
}
renderElement(element: ListElement, index: number, data: IListElementTemplateData): void {
data.toDisposeElement = dispose(data.toDisposeElement);
data.element = element;
data.checkbox.checked = element.checked;
data.toDisposeElement.push(element.onChecked(checked => data.checkbox.checked = checked));
const { labelHighlights, descriptionHighlights, detailHighlights } = element;
// Label
const options: IIconLabelValueOptions = Object.create(null);
options.matches = labelHighlights || [];
options.descriptionTitle = element.saneDescription;
options.descriptionMatches = descriptionHighlights || [];
options.extraClasses = element.item.iconClasses;
options.italic = element.item.italic;
options.strikethrough = element.item.strikethrough;
data.label.setLabel(element.saneLabel, element.saneDescription, options);
// Keybinding
data.keybinding.set(element.item.keybinding);
// Meta
data.detail.set(element.saneDetail, detailHighlights);
// Separator
if (element.separator && element.separator.label) {
data.separator.textContent = element.separator.label;
data.separator.style.display = '';
} else {
data.separator.style.display = 'none';
}
data.entry.classList.toggle('quick-input-list-separator-border', !!element.separator);
// Actions
data.actionBar.clear();
const buttons = element.item.buttons;
if (buttons && buttons.length) {
data.actionBar.push(buttons.map((button, index) => {
let cssClasses = button.iconClass || (button.iconPath ? getIconClass(button.iconPath) : undefined);
if (button.alwaysVisible) {
cssClasses = cssClasses ? `${cssClasses} always-visible` : 'always-visible';
}
const action = new Action(`id-${index}`, '', cssClasses, true, () => {
element.fireButtonTriggered({
button,
item: element.item
});
return Promise.resolve();
});
action.tooltip = button.tooltip || '';
return action;
}), { icon: true, label: false });
data.entry.classList.add('has-actions');
} else {
data.entry.classList.remove('has-actions');
}
}
disposeElement(element: ListElement, index: number, data: IListElementTemplateData): void {
data.toDisposeElement = dispose(data.toDisposeElement);
}
disposeTemplate(data: IListElementTemplateData): void {
data.toDisposeElement = dispose(data.toDisposeElement);
data.toDisposeTemplate = dispose(data.toDisposeTemplate);
}
}
class ListElementDelegate implements IListVirtualDelegate<ListElement> {
getHeight(element: ListElement): number {
return element.saneDetail ? 44 : 22;
}
getTemplateId(element: ListElement): string {
return ListElementRenderer.ID;
}
}
export enum QuickInputListFocus {
First = 1,
Second,
Last,
Next,
Previous,
NextPage,
PreviousPage
}
export class QuickInputList {
readonly id: string;
private container: HTMLElement;
private list: List<ListElement>;
private inputElements: Array<IQuickPickItem | IQuickPickSeparator> = [];
private elements: ListElement[] = [];
private elementsToIndexes = new Map<IQuickPickItem, number>();
matchOnDescription = false;
matchOnDetail = false;
matchOnLabel = true;
sortByLabel = true;
private readonly _onChangedAllVisibleChecked = new Emitter<boolean>();
onChangedAllVisibleChecked: Event<boolean> = this._onChangedAllVisibleChecked.event;
private readonly _onChangedCheckedCount = new Emitter<number>();
onChangedCheckedCount: Event<number> = this._onChangedCheckedCount.event;
private readonly _onChangedVisibleCount = new Emitter<number>();
onChangedVisibleCount: Event<number> = this._onChangedVisibleCount.event;
private readonly _onChangedCheckedElements = new Emitter<IQuickPickItem[]>();
onChangedCheckedElements: Event<IQuickPickItem[]> = this._onChangedCheckedElements.event;
private readonly _onButtonTriggered = new Emitter<IQuickPickItemButtonEvent<IQuickPickItem>>();
onButtonTriggered = this._onButtonTriggered.event;
private readonly _onKeyDown = new Emitter<StandardKeyboardEvent>();
onKeyDown: Event<StandardKeyboardEvent> = this._onKeyDown.event;
private readonly _onLeave = new Emitter<void>();
onLeave: Event<void> = this._onLeave.event;
private _fireCheckedEvents = true;
private elementDisposables: IDisposable[] = [];
private disposables: IDisposable[] = [];
constructor(
private parent: HTMLElement,
id: string,
options: IQuickInputOptions,
) {
this.id = id;
this.container = dom.append(this.parent, $('.quick-input-list'));
const delegate = new ListElementDelegate();
const accessibilityProvider = new QuickInputAccessibilityProvider();
this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], {
identityProvider: { getId: element => element.saneLabel },
setRowLineHeight: false,
multipleSelectionSupport: false,
horizontalScrolling: false,
accessibilityProvider
} as IListOptions<ListElement>);
this.list.getHTMLElement().id = id;
this.disposables.push(this.list);
this.disposables.push(this.list.onKeyDown(e => {
const event = new StandardKeyboardEvent(e);
switch (event.keyCode) {
case KeyCode.Space:
this.toggleCheckbox();
break;
case KeyCode.KEY_A:
if (platform.isMacintosh ? e.metaKey : e.ctrlKey) {
this.list.setFocus(range(this.list.length));
}
break;
case KeyCode.UpArrow:
const focus1 = this.list.getFocus();
if (focus1.length === 1 && focus1[0] === 0) {
this._onLeave.fire();
}
break;
case KeyCode.DownArrow:
const focus2 = this.list.getFocus();
if (focus2.length === 1 && focus2[0] === this.list.length - 1) {
this._onLeave.fire();
}
break;
}
this._onKeyDown.fire(event);
}));
this.disposables.push(this.list.onMouseDown(e => {
if (e.browserEvent.button !== 2) {
// Works around / fixes #64350.
e.browserEvent.preventDefault();
}
}));
this.disposables.push(dom.addDisposableListener(this.container, dom.EventType.CLICK, e => {
if (e.x || e.y) { // Avoid 'click' triggered by 'space' on checkbox.
this._onLeave.fire();
}
}));
this.disposables.push(this.list.onMouseMiddleClick(e => {
this._onLeave.fire();
}));
this.disposables.push(this.list.onContextMenu(e => {
if (typeof e.index === 'number') {
e.browserEvent.preventDefault();
// we want to treat a context menu event as
// a gesture to open the item at the index
// since we do not have any context menu
// this enables for example macOS to Ctrl-
// click on an item to open it.
this.list.setSelection([e.index]);
}
}));
this.disposables.push(
this._onChangedAllVisibleChecked,
this._onChangedCheckedCount,
this._onChangedVisibleCount,
this._onChangedCheckedElements,
this._onButtonTriggered,
this._onLeave,
this._onKeyDown
);
}
@memoize
get onDidChangeFocus() {
return Event.map(this.list.onDidChangeFocus, e => e.elements.map(e => e.item));
}
@memoize
get onDidChangeSelection() {
return Event.map(this.list.onDidChangeSelection, e => ({ items: e.elements.map(e => e.item), event: e.browserEvent }));
}
getAllVisibleChecked() {
return this.allVisibleChecked(this.elements, false);
}
private allVisibleChecked(elements: ListElement[], whenNoneVisible = true) {
for (let i = 0, n = elements.length; i < n; i++) {
const element = elements[i];
if (!element.hidden) {
if (!element.checked) {
return false;
} else {
whenNoneVisible = true;
}
}
}
return whenNoneVisible;
}
getCheckedCount() {
let count = 0;
const elements = this.elements;
for (let i = 0, n = elements.length; i < n; i++) {
if (elements[i].checked) {
count++;
}
}
return count;
}
getVisibleCount() {
let count = 0;
const elements = this.elements;
for (let i = 0, n = elements.length; i < n; i++) {
if (!elements[i].hidden) {
count++;
}
}
return count;
}
setAllVisibleChecked(checked: boolean) {
try {
this._fireCheckedEvents = false;
this.elements.forEach(element => {
if (!element.hidden) {
element.checked = checked;
}
});
} finally {
this._fireCheckedEvents = true;
this.fireCheckedEvents();
}
}
setElements(inputElements: Array<IQuickPickItem | IQuickPickSeparator>): void {
this.elementDisposables = dispose(this.elementDisposables);
const fireButtonTriggered = (event: IQuickPickItemButtonEvent<IQuickPickItem>) => this.fireButtonTriggered(event);
this.inputElements = inputElements;
this.elements = inputElements.reduce((result, item, index) => {
if (item.type !== 'separator') {
const previous = index && inputElements[index - 1];
const saneLabel = item.label && item.label.replace(/\r?\n/g, ' ');
const saneDescription = item.description && item.description.replace(/\r?\n/g, ' ');
const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' ');
const saneAriaLabel = item.ariaLabel || [saneLabel, saneDescription, saneDetail]
.map(s => s && parseCodicons(s).text)
.filter(s => !!s)
.join(', ');
result.push(new ListElement({
index,
item,
saneLabel,
saneAriaLabel,
saneDescription,
saneDetail,
labelHighlights: item.highlights?.label,
descriptionHighlights: item.highlights?.description,
detailHighlights: item.highlights?.detail,
checked: false,
separator: previous && previous.type === 'separator' ? previous : undefined,
fireButtonTriggered
}));
}
return result;
}, [] as ListElement[]);
this.elementDisposables.push(...this.elements);
this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents())));
this.elementsToIndexes = this.elements.reduce((map, element, index) => {
map.set(element.item, index);
return map;
}, new Map<IQuickPickItem, number>());
this.list.splice(0, this.list.length); // Clear focus and selection first, sending the events when the list is empty.
this.list.splice(0, this.list.length, this.elements);
this._onChangedVisibleCount.fire(this.elements.length);
}
getElementsCount(): number {
return this.inputElements.length;
}
getFocusedElements() {
return this.list.getFocusedElements()
.map(e => e.item);
}
setFocusedElements(items: IQuickPickItem[]) {
this.list.setFocus(items
.filter(item => this.elementsToIndexes.has(item))
.map(item => this.elementsToIndexes.get(item)!));
if (items.length > 0) {
const focused = this.list.getFocus()[0];
if (typeof focused === 'number') {
this.list.reveal(focused);
}
}
}
getActiveDescendant() {
return this.list.getHTMLElement().getAttribute('aria-activedescendant');
}
getSelectedElements() {
return this.list.getSelectedElements()
.map(e => e.item);
}
setSelectedElements(items: IQuickPickItem[]) {
this.list.setSelection(items
.filter(item => this.elementsToIndexes.has(item))
.map(item => this.elementsToIndexes.get(item)!));
}
getCheckedElements() {
return this.elements.filter(e => e.checked)
.map(e => e.item);
}
setCheckedElements(items: IQuickPickItem[]) {
try {
this._fireCheckedEvents = false;
const checked = new Set();
for (const item of items) {
checked.add(item);
}
for (const element of this.elements) {
element.checked = checked.has(element.item);
}
} finally {
this._fireCheckedEvents = true;
this.fireCheckedEvents();
}
}
set enabled(value: boolean) {
this.list.getHTMLElement().style.pointerEvents = value ? '' : 'none';
}
focus(what: QuickInputListFocus): void {
if (!this.list.length) {
return;
}
if (what === QuickInputListFocus.Next && this.list.getFocus()[0] === this.list.length - 1) {
what = QuickInputListFocus.First;
}
if (what === QuickInputListFocus.Previous && this.list.getFocus()[0] === 0) {
what = QuickInputListFocus.Last;
}
if (what === QuickInputListFocus.Second && this.list.length < 2) {
what = QuickInputListFocus.First;
}
switch (what) {
case QuickInputListFocus.First:
this.list.focusFirst();
break;
case QuickInputListFocus.Second:
this.list.focusNth(1);
break;
case QuickInputListFocus.Last:
this.list.focusLast();
break;
case QuickInputListFocus.Next:
this.list.focusNext();
break;
case QuickInputListFocus.Previous:
this.list.focusPrevious();
break;
case QuickInputListFocus.NextPage:
this.list.focusNextPage();
break;
case QuickInputListFocus.PreviousPage:
this.list.focusPreviousPage();
break;
}
const focused = this.list.getFocus()[0];
if (typeof focused === 'number') {
this.list.reveal(focused);
}
}
clearFocus() {
this.list.setFocus([]);
}
domFocus() {
this.list.domFocus();
}
layout(maxHeight?: number): void {
this.list.getHTMLElement().style.maxHeight = maxHeight ? `calc(${Math.floor(maxHeight / 44) * 44}px)` : '';
this.list.layout();
}
filter(query: string): boolean {
if (!(this.sortByLabel || this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) {
this.list.layout();
return false;
}
query = query.trim();
// Reset filtering
if (!query || !(this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) {
this.elements.forEach(element => {
element.labelHighlights = undefined;
element.descriptionHighlights = undefined;
element.detailHighlights = undefined;
element.hidden = false;
const previous = element.index && this.inputElements[element.index - 1];
element.separator = previous && previous.type === 'separator' ? previous : undefined;
});
}
// Filter by value (since we support codicons, use codicon aware fuzzy matching)
else {
this.elements.forEach(element => {
const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneLabel))) : undefined;
const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneDescription || ''))) : undefined;
const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneDetail || ''))) : undefined;
if (labelHighlights || descriptionHighlights || detailHighlights) {
element.labelHighlights = labelHighlights;
element.descriptionHighlights = descriptionHighlights;
element.detailHighlights = detailHighlights;
element.hidden = false;
} else {
element.labelHighlights = undefined;
element.descriptionHighlights = undefined;
element.detailHighlights = undefined;
element.hidden = !element.item.alwaysShow;
}
element.separator = undefined;
});
}
const shownElements = this.elements.filter(element => !element.hidden);
// Sort by value
if (this.sortByLabel && query) {
const normalizedSearchValue = query.toLowerCase();
shownElements.sort((a, b) => {
return compareEntries(a, b, normalizedSearchValue);
});
}
this.elementsToIndexes = shownElements.reduce((map, element, index) => {
map.set(element.item, index);
return map;
}, new Map<IQuickPickItem, number>());
this.list.splice(0, this.list.length, shownElements);
this.list.setFocus([]);
this.list.layout();
this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked());
this._onChangedVisibleCount.fire(shownElements.length);
return true;
}
toggleCheckbox() {
try {
this._fireCheckedEvents = false;
const elements = this.list.getFocusedElements();
const allChecked = this.allVisibleChecked(elements);
for (const element of elements) {
element.checked = !allChecked;
}
} finally {
this._fireCheckedEvents = true;
this.fireCheckedEvents();
}
}
display(display: boolean) {
this.container.style.display = display ? '' : 'none';
}
isDisplayed() {
return this.container.style.display !== 'none';
}
dispose() {
this.elementDisposables = dispose(this.elementDisposables);
this.disposables = dispose(this.disposables);
}
private fireCheckedEvents() {
if (this._fireCheckedEvents) {
this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked());
this._onChangedCheckedCount.fire(this.getCheckedCount());
this._onChangedCheckedElements.fire(this.getCheckedElements());
}
}
private fireButtonTriggered(event: IQuickPickItemButtonEvent<IQuickPickItem>) {
this._onButtonTriggered.fire(event);
}
style(styles: IListStyles) {
this.list.style(styles);
}
}
function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number {
const labelHighlightsA = elementA.labelHighlights || [];
const labelHighlightsB = elementB.labelHighlights || [];
if (labelHighlightsA.length && !labelHighlightsB.length) {
return -1;
}
if (!labelHighlightsA.length && labelHighlightsB.length) {
return 1;
}
if (labelHighlightsA.length === 0 && labelHighlightsB.length === 0) {
return 0;
}
return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor);
}
class QuickInputAccessibilityProvider implements IListAccessibilityProvider<ListElement> {
getWidgetAriaLabel(): string {
return localize('quickInput', "Quick Input");
}
getAriaLabel(element: ListElement): string | null {
return element.saneAriaLabel;
}
getWidgetRole() {
return 'listbox';
}
getRole() {
return 'option';
}
}

View File

@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/quickInput';
import * as dom from 'vs/base/browser/dom';
import { URI } from 'vs/base/common/uri';
import { IdGenerator } from 'vs/base/common/idGenerator';
const iconPathToClass: Record<string, string> = {};
const iconClassGenerator = new IdGenerator('quick-input-button-icon-');
export function getIconClass(iconPath: { dark: URI; light?: URI; } | undefined): string | undefined {
if (!iconPath) {
return undefined;
}
let iconClass: string;
const key = iconPath.dark.toString();
if (iconPathToClass[key]) {
iconClass = iconPathToClass[key];
} else {
iconClass = iconClassGenerator.nextId();
dom.createCSSRule(`.${iconClass}`, `background-image: ${dom.asCSSUrl(iconPath.light || iconPath.dark)}`);
dom.createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: ${dom.asCSSUrl(iconPath.dark)}`);
iconPathToClass[key] = iconClass;
}
return iconClass;
}

View File

@ -0,0 +1,365 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IMatch } from 'vs/base/common/filters';
import { IItemAccessor } from 'vs/base/common/fuzzyScorer';
import { Schemas } from 'vs/base/common/network';
export interface IQuickPickItemHighlights {
label?: IMatch[];
description?: IMatch[];
detail?: IMatch[];
}
export interface IQuickPickItem {
type?: 'item';
id?: string;
label: string;
ariaLabel?: string;
description?: string;
detail?: string;
/**
* Allows to show a keybinding next to the item to indicate
* how the item can be triggered outside of the picker using
* keyboard shortcut.
*/
keybinding?: ResolvedKeybinding;
iconClasses?: string[];
italic?: boolean;
strikethrough?: boolean;
highlights?: IQuickPickItemHighlights;
buttons?: IQuickInputButton[];
picked?: boolean;
alwaysShow?: boolean;
}
export interface IQuickPickSeparator {
type: 'separator';
label?: string;
}
export interface IKeyMods {
readonly ctrlCmd: boolean;
readonly alt: boolean;
}
export const NO_KEY_MODS: IKeyMods = { ctrlCmd: false, alt: false };
export interface IQuickNavigateConfiguration {
keybindings: ResolvedKeybinding[];
}
export interface IPickOptions<T extends IQuickPickItem> {
/**
* an optional string to show as placeholder in the input box to guide the user what she picks on
*/
placeHolder?: string;
/**
* an optional flag to include the description when filtering the picks
*/
matchOnDescription?: boolean;
/**
* an optional flag to include the detail when filtering the picks
*/
matchOnDetail?: boolean;
/**
* an optional flag to filter the picks based on label. Defaults to true.
*/
matchOnLabel?: boolean;
/**
* an option flag to control whether focus is always automatically brought to a list item. Defaults to true.
*/
autoFocusOnList?: boolean;
/**
* an optional flag to not close the picker on focus lost
*/
ignoreFocusLost?: boolean;
/**
* an optional flag to make this picker multi-select
*/
canPickMany?: boolean;
/**
* enables quick navigate in the picker to open an element without typing
*/
quickNavigate?: IQuickNavigateConfiguration;
/**
* a context key to set when this picker is active
*/
contextKey?: string;
/**
* an optional property for the item to focus initially.
*/
activeItem?: Promise<T> | T;
onKeyMods?: (keyMods: IKeyMods) => void;
onDidFocus?: (entry: T) => void;
onDidTriggerItemButton?: (context: IQuickPickItemButtonContext<T>) => void;
}
export interface IInputOptions {
/**
* the value to prefill in the input box
*/
value?: string;
/**
* the selection of value, default to the whole word
*/
valueSelection?: [number, number];
/**
* the text to display underneath the input box
*/
prompt?: string;
/**
* an optional string to show as placeholder in the input box to guide the user what to type
*/
placeHolder?: string;
/**
* Controls if a password input is shown. Password input hides the typed text.
*/
password?: boolean;
ignoreFocusLost?: boolean;
/**
* an optional function that is used to validate user input.
*/
validateInput?: (input: string) => Promise<string | null | undefined>;
}
export interface IQuickInput extends IDisposable {
readonly onDidHide: Event<void>;
readonly onDispose: Event<void>;
title: string | undefined;
description: string | undefined;
step: number | undefined;
totalSteps: number | undefined;
enabled: boolean;
contextKey: string | undefined;
busy: boolean;
ignoreFocusOut: boolean;
show(): void;
hide(): void;
}
export interface IQuickPickAcceptEvent {
/**
* Signals if the picker item is to be accepted
* in the background while keeping the picker open.
*/
inBackground: boolean;
}
export enum ItemActivation {
NONE,
FIRST,
SECOND,
LAST
}
export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
value: string;
/**
* A method that allows to massage the value used
* for filtering, e.g, to remove certain parts.
*/
filterValue: (value: string) => string;
ariaLabel: string | undefined;
placeholder: string | undefined;
readonly onDidChangeValue: Event<string>;
readonly onDidAccept: Event<IQuickPickAcceptEvent>;
/**
* If enabled, will fire the `onDidAccept` event when
* pressing the arrow-right key with the idea of accepting
* the selected item without closing the picker.
*/
canAcceptInBackground: boolean;
ok: boolean | 'default';
readonly onDidCustom: Event<void>;
customButton: boolean;
customLabel: string | undefined;
customHover: string | undefined;
buttons: ReadonlyArray<IQuickInputButton>;
readonly onDidTriggerButton: Event<IQuickInputButton>;
readonly onDidTriggerItemButton: Event<IQuickPickItemButtonEvent<T>>;
items: ReadonlyArray<T | IQuickPickSeparator>;
canSelectMany: boolean;
matchOnDescription: boolean;
matchOnDetail: boolean;
matchOnLabel: boolean;
sortByLabel: boolean;
autoFocusOnList: boolean;
quickNavigate: IQuickNavigateConfiguration | undefined;
activeItems: ReadonlyArray<T>;
readonly onDidChangeActive: Event<T[]>;
/**
* Allows to control which entry should be activated by default.
*/
itemActivation: ItemActivation;
selectedItems: ReadonlyArray<T>;
readonly onDidChangeSelection: Event<T[]>;
readonly keyMods: IKeyMods;
valueSelection: Readonly<[number, number]> | undefined;
validationMessage: string | undefined;
inputHasFocus(): boolean;
focusOnInput(): void;
/**
* Hides the input box from the picker UI. This is typically used
* in combination with quick-navigation where no search UI should
* be presented.
*/
hideInput: boolean;
hideCheckAll: boolean;
}
export interface IInputBox extends IQuickInput {
value: string;
valueSelection: Readonly<[number, number]> | undefined;
placeholder: string | undefined;
password: boolean;
readonly onDidChangeValue: Event<string>;
readonly onDidAccept: Event<void>;
buttons: ReadonlyArray<IQuickInputButton>;
readonly onDidTriggerButton: Event<IQuickInputButton>;
prompt: string | undefined;
validationMessage: string | undefined;
}
export interface IQuickInputButton {
/** iconPath or iconClass required */
iconPath?: { dark: URI; light?: URI; };
/** iconPath or iconClass required */
iconClass?: string;
tooltip?: string;
/**
* Whether to always show the button. By default buttons
* are only visible when hovering over them with the mouse
*/
alwaysVisible?: boolean;
}
export interface IQuickPickItemButtonEvent<T extends IQuickPickItem> {
button: IQuickInputButton;
item: T;
}
export interface IQuickPickItemButtonContext<T extends IQuickPickItem> extends IQuickPickItemButtonEvent<T> {
removeItem(): void;
}
export type QuickPickInput<T = IQuickPickItem> = T | IQuickPickSeparator;
//region Fuzzy Scorer Support
export type IQuickPickItemWithResource = IQuickPickItem & { resource?: URI };
export class QuickPickItemScorerAccessor implements IItemAccessor<IQuickPickItemWithResource> {
constructor(private options?: { skipDescription?: boolean, skipPath?: boolean }) { }
getItemLabel(entry: IQuickPickItemWithResource): string {
return entry.label;
}
getItemDescription(entry: IQuickPickItemWithResource): string | undefined {
if (this.options?.skipDescription) {
return undefined;
}
return entry.description;
}
getItemPath(entry: IQuickPickItemWithResource): string | undefined {
if (this.options?.skipPath) {
return undefined;
}
if (entry.resource?.scheme === Schemas.file) {
return entry.resource.fsPath;
}
return entry.resource?.path;
}
}
export const quickPickItemScorerAccessor = new QuickPickItemScorerAccessor();
//#endregion