mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-13 23:44:09 +01:00
Enable a tooltip in quick pick (#174417)
* hover in quickpick * refactoring * fix tests * remove HTMLElement from API for now * API proposal
This commit is contained in:
committed by
GitHub
parent
89907df37d
commit
f861acbee1
@@ -14,11 +14,40 @@ export interface IHoverDelegateTarget extends IDisposable {
|
||||
}
|
||||
|
||||
export interface IHoverDelegateOptions extends IUpdatableHoverOptions {
|
||||
/**
|
||||
* The content to display in the primary section of the hover. The type of text determines the
|
||||
* default `hideOnHover` behavior.
|
||||
*/
|
||||
content: IMarkdownString | string | HTMLElement;
|
||||
/**
|
||||
* The target for the hover. This determines the position of the hover and it will only be
|
||||
* hidden when the mouse leaves both the hover and the target. A HTMLElement can be used for
|
||||
* simple cases and a IHoverDelegateTarget for more complex cases where multiple elements and/or a
|
||||
* dispose method is required.
|
||||
*/
|
||||
target: IHoverDelegateTarget | HTMLElement;
|
||||
/**
|
||||
* Position of the hover. The default is to show above the target. This option will be ignored
|
||||
* if there is not enough room to layout the hover in the specified position, unless the
|
||||
* forcePosition option is set.
|
||||
*/
|
||||
hoverPosition?: HoverPosition;
|
||||
/**
|
||||
* Whether to show the hover pointer
|
||||
*/
|
||||
showPointer?: boolean;
|
||||
/**
|
||||
* Whether to skip the fade in animation, this should be used when hovering from one hover to
|
||||
* another in the same group so it looks like the hover is moving from one element to the other.
|
||||
*/
|
||||
skipFadeInAnimation?: boolean;
|
||||
/**
|
||||
* The container to pass to {@link IContextViewProvider.showContextView} which renders the hover
|
||||
* in. This is particularly useful for more natural tab focusing behavior, where the hover is
|
||||
* created as the next tab index after the element being hovered and/or to workaround the
|
||||
* element's container hiding on `focusout`.
|
||||
*/
|
||||
container?: HTMLElement;
|
||||
}
|
||||
|
||||
export interface IHoverDelegate {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button';
|
||||
import { CountBadge, ICountBadgeStyles } from 'vs/base/browser/ui/countBadge/countBadge';
|
||||
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
|
||||
import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
|
||||
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
@@ -51,6 +52,7 @@ export interface IQuickInputOptions {
|
||||
renderers: IListRenderer<T, any>[],
|
||||
options: IListOptions<T>,
|
||||
): List<T>;
|
||||
hoverDelegate: IHoverDelegate;
|
||||
styles: IQuickInputStyles;
|
||||
}
|
||||
|
||||
@@ -1416,6 +1418,12 @@ export class QuickInputController extends Disposable {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyCode.Space:
|
||||
if (event.ctrlKey) {
|
||||
dom.EventHelper.stop(e, true);
|
||||
this.getUI().list.toggleHover();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -7,19 +7,23 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { AriaRole } from 'vs/base/browser/ui/aria/aria';
|
||||
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
|
||||
import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
|
||||
import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
|
||||
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { IListAccessibilityProvider, IListOptions, IListStyles, List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
import { ThrottledDelayer } from 'vs/base/common/async';
|
||||
import { compareAnything } from 'vs/base/common/comparers';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { getCodiconAriaLabel, IParsedLabelWithIcons, matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { ltrim } from 'vs/base/common/strings';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
@@ -41,6 +45,7 @@ interface IListElement {
|
||||
readonly saneAriaLabel: string;
|
||||
readonly saneDescription?: string;
|
||||
readonly saneDetail?: string;
|
||||
readonly saneTooltip?: string | IMarkdownString | HTMLElement;
|
||||
readonly labelHighlights?: IMatch[];
|
||||
readonly descriptionHighlights?: IMatch[];
|
||||
readonly detailHighlights?: IMatch[];
|
||||
@@ -60,6 +65,8 @@ class ListElement implements IListElement, IDisposable {
|
||||
saneAriaLabel!: string;
|
||||
saneDescription?: string;
|
||||
saneDetail?: string;
|
||||
saneTooltip?: string | IMarkdownString | HTMLElement;
|
||||
element?: HTMLElement;
|
||||
hidden = false;
|
||||
private readonly _onChecked = new Emitter<boolean>();
|
||||
onChecked = this._onChecked.event;
|
||||
@@ -159,6 +166,7 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
|
||||
|
||||
renderElement(element: ListElement, index: number, data: IListElementTemplateData): void {
|
||||
data.element = element;
|
||||
element.element = withNullAsUndefined(data.entry);
|
||||
const mainItem: QuickPickItem = element.item ? element.item : element.separator!;
|
||||
|
||||
data.checkbox.checked = element.checked;
|
||||
@@ -306,11 +314,13 @@ export class QuickInputList {
|
||||
private _fireCheckedEvents = true;
|
||||
private elementDisposables: IDisposable[] = [];
|
||||
private disposables: IDisposable[] = [];
|
||||
private _lastHover: IHoverWidget | undefined;
|
||||
private _toggleHover: IDisposable | undefined;
|
||||
|
||||
constructor(
|
||||
private parent: HTMLElement,
|
||||
id: string,
|
||||
options: IQuickInputOptions,
|
||||
private options: IQuickInputOptions,
|
||||
) {
|
||||
this.id = id;
|
||||
this.container = dom.append(this.parent, $('.quick-input-list'));
|
||||
@@ -381,6 +391,40 @@ export class QuickInputList {
|
||||
this.list.setSelection([e.index]);
|
||||
}
|
||||
}));
|
||||
|
||||
const delayer = new ThrottledDelayer(options.hoverDelegate.delay);
|
||||
// onMouseOver triggers every time a new element has been moused over
|
||||
// even if it's on the same list item.
|
||||
this.disposables.push(this.list.onMouseOver(async e => {
|
||||
// If we hover over an anchor element, we don't want to show the hover because
|
||||
// the anchor may have a tooltip that we want to show instead.
|
||||
if (e.browserEvent.target instanceof HTMLAnchorElement) {
|
||||
delayer.cancel();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
// anchors are an exception as called out above so we skip them here
|
||||
!(e.browserEvent.relatedTarget instanceof HTMLAnchorElement) &&
|
||||
// check if the mouse is still over the same element
|
||||
dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await delayer.trigger(async () => {
|
||||
if (e.element) {
|
||||
this.showHover(e.element);
|
||||
}
|
||||
});
|
||||
}));
|
||||
this.disposables.push(this.list.onMouseOut(e => {
|
||||
// onMouseOut triggers every time a new element has been moused over
|
||||
// even if it's on the same list item. We only want one event, so we
|
||||
// check if the mouse is still over the same element.
|
||||
if (dom.isAncestor(e.browserEvent.relatedTarget as Node, e.element?.element as Node)) {
|
||||
return;
|
||||
}
|
||||
delayer.cancel();
|
||||
}));
|
||||
this.disposables.push(
|
||||
this._onChangedAllVisibleChecked,
|
||||
this._onChangedCheckedCount,
|
||||
@@ -389,7 +433,8 @@ export class QuickInputList {
|
||||
this._onButtonTriggered,
|
||||
this._onSeparatorButtonTriggered,
|
||||
this._onLeave,
|
||||
this._onKeyDown
|
||||
this._onKeyDown,
|
||||
delayer
|
||||
);
|
||||
}
|
||||
|
||||
@@ -475,7 +520,7 @@ export class QuickInputList {
|
||||
const saneLabel = item.label ? item.label.replace(/\r?\n/g, ' ') : '';
|
||||
const saneSortLabel = parseLabelWithIcons(saneLabel).text.trim();
|
||||
|
||||
let saneMeta, saneDescription, saneDetail, labelHighlights, descriptionHighlights, detailHighlights;
|
||||
let saneMeta, saneDescription, saneDetail, labelHighlights, descriptionHighlights, detailHighlights, saneTooltip;
|
||||
if (item.type !== 'separator') {
|
||||
saneMeta = item.meta && item.meta.replace(/\r?\n/g, ' ');
|
||||
saneDescription = item.description && item.description.replace(/\r?\n/g, ' ');
|
||||
@@ -483,6 +528,7 @@ export class QuickInputList {
|
||||
labelHighlights = item.highlights?.label;
|
||||
descriptionHighlights = item.highlights?.description;
|
||||
detailHighlights = item.highlights?.detail;
|
||||
saneTooltip = item.tooltip;
|
||||
}
|
||||
const saneAriaLabel = item.ariaLabel || [saneLabel, saneDescription, saneDetail]
|
||||
.map(s => getCodiconAriaLabel(s))
|
||||
@@ -512,6 +558,7 @@ export class QuickInputList {
|
||||
saneAriaLabel,
|
||||
saneDescription,
|
||||
saneDetail,
|
||||
saneTooltip,
|
||||
labelHighlights,
|
||||
descriptionHighlights,
|
||||
detailHighlights,
|
||||
@@ -659,6 +706,30 @@ export class QuickInputList {
|
||||
this.list.domFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of the hover and shows a new one for the given index if it has a tooltip.
|
||||
* @param element The element to show the hover for
|
||||
*/
|
||||
private showHover(element: ListElement): void {
|
||||
if (this._lastHover && !this._lastHover.isDisposed) {
|
||||
this.options.hoverDelegate.onDidHideHover?.();
|
||||
this._lastHover?.dispose();
|
||||
}
|
||||
if (!element.element || !element.saneTooltip) {
|
||||
return;
|
||||
}
|
||||
this._lastHover = this.options.hoverDelegate.showHover({
|
||||
content: element.saneTooltip!,
|
||||
target: element.element!,
|
||||
linkHandler: (url) => {
|
||||
this.options.linkOpenerDelegate(url);
|
||||
},
|
||||
showPointer: true,
|
||||
container: this.container,
|
||||
hoverPosition: HoverPosition.RIGHT
|
||||
}, false);
|
||||
}
|
||||
|
||||
layout(maxHeight?: number): void {
|
||||
this.list.getHTMLElement().style.maxHeight = maxHeight ? `calc(${Math.floor(maxHeight / 44) * 44}px)` : '';
|
||||
this.list.layout();
|
||||
@@ -795,6 +866,37 @@ export class QuickInputList {
|
||||
style(styles: IListStyles) {
|
||||
this.list.style(styles);
|
||||
}
|
||||
|
||||
toggleHover() {
|
||||
const element = this.list.getFocusedElements()[0];
|
||||
if (!element.saneTooltip) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there's a hover already, hide it (toggle off)
|
||||
if (this._lastHover && !this._lastHover.isDisposed) {
|
||||
this._lastHover.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is no hover, show it (toggle on)
|
||||
const focused = this.list.getFocusedElements()[0];
|
||||
if (!focused) {
|
||||
return;
|
||||
}
|
||||
this.showHover(focused);
|
||||
const store = new DisposableStore();
|
||||
store.add(this.list.onDidChangeFocus(e => {
|
||||
if (e.indexes.length) {
|
||||
this.showHover(e.elements[0]);
|
||||
}
|
||||
}));
|
||||
if (this._lastHover) {
|
||||
store.add(this._lastHover);
|
||||
}
|
||||
this._toggleHover = store;
|
||||
this.elementDisposables.push(this._toggleHover);
|
||||
}
|
||||
}
|
||||
|
||||
function matchesContiguousIconAware(query: string, target: IParsedLabelWithIcons): IMatch[] | null {
|
||||
|
||||
@@ -89,6 +89,12 @@ export class QuickInputService extends Themable implements IQuickInputService {
|
||||
renderers: IListRenderer<T, any>[],
|
||||
options: IWorkbenchListOptions<T>
|
||||
) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List<T>,
|
||||
hoverDelegate: {
|
||||
showHover(options, focus) {
|
||||
return undefined;
|
||||
},
|
||||
delay: 200
|
||||
},
|
||||
styles: this.computeStyles()
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
export interface IQuickPickItemHighlights {
|
||||
label?: IMatch[];
|
||||
@@ -31,6 +32,7 @@ export interface IQuickPickItem {
|
||||
ariaLabel?: string;
|
||||
description?: string;
|
||||
detail?: string;
|
||||
tooltip?: string | IMarkdownString;
|
||||
/**
|
||||
* Allows to show a keybinding next to the item to indicate
|
||||
* how the item can be triggered outside of the picker using
|
||||
@@ -52,6 +54,7 @@ export interface IQuickPickSeparator {
|
||||
label?: string;
|
||||
ariaLabel?: string;
|
||||
buttons?: readonly IQuickInputButton[];
|
||||
tooltip?: string | IMarkdownString;
|
||||
}
|
||||
|
||||
export interface IKeyMods {
|
||||
|
||||
@@ -58,6 +58,12 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543
|
||||
renderers: IListRenderer<T, any>[],
|
||||
options: IListOptions<T>,
|
||||
) => new List<T>(user, container, delegate, renderers, options),
|
||||
hoverDelegate: {
|
||||
showHover(options, focus) {
|
||||
return undefined;
|
||||
},
|
||||
delay: 200
|
||||
},
|
||||
styles: {
|
||||
button: unthemedButtonStyles,
|
||||
countBadge: unthemedCountStyles,
|
||||
|
||||
@@ -670,7 +670,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
return <Thenable<any>>extHostMessageService.showMessage(extension, Severity.Error, message, rest[0], <Array<string | vscode.MessageItem>>rest.slice(1));
|
||||
},
|
||||
showQuickPick(items: any, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): any {
|
||||
return extHostQuickOpen.showQuickPick(items, options, token);
|
||||
return extHostQuickOpen.showQuickPick(extension, items, options, token);
|
||||
},
|
||||
showWorkspaceFolderPick(options?: vscode.WorkspaceFolderPickOptions) {
|
||||
return extHostQuickOpen.showWorkspaceFolderPick(options);
|
||||
|
||||
@@ -17,14 +17,16 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { ThemeIcon as ThemeIconUtils } from 'vs/base/common/themables';
|
||||
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { MarkdownString } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
|
||||
export type Item = string | QuickPickItem;
|
||||
|
||||
export interface ExtHostQuickOpen {
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Promise<QuickPickItem[] | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: string[] | Promise<string[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<string | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<QuickPickItem | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: Item[] | Promise<Item[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<Item | Item[] | undefined>;
|
||||
showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Promise<QuickPickItem[] | undefined>;
|
||||
showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: string[] | Promise<string[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<string | undefined>;
|
||||
showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<QuickPickItem | undefined>;
|
||||
showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: Item[] | Promise<Item[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<Item | Item[] | undefined>;
|
||||
|
||||
showInput(options?: InputBoxOptions, token?: CancellationToken): Promise<string | undefined>;
|
||||
|
||||
@@ -55,11 +57,10 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
|
||||
this._commands = commands;
|
||||
}
|
||||
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Promise<QuickPickItem[] | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: string[] | Promise<string[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<string | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<QuickPickItem | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: Item[] | Promise<Item[]>, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise<Item | Item[] | undefined> {
|
||||
|
||||
showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Promise<QuickPickItem[] | undefined>;
|
||||
showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: string[] | Promise<string[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<string | undefined>;
|
||||
showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: QuickPickItem[] | Promise<QuickPickItem[]>, options?: QuickPickOptions, token?: CancellationToken): Promise<QuickPickItem | undefined>;
|
||||
showQuickPick(extension: IExtensionDescription, itemsOrItemsPromise: Item[] | Promise<Item[]>, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise<Item | Item[] | undefined> {
|
||||
// clear state from last invocation
|
||||
this._onDidSelectItem = undefined;
|
||||
|
||||
@@ -84,6 +85,8 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const allowedTooltips = isProposedApiEnabled(extension, 'quickPickItemTooltip');
|
||||
|
||||
return itemsPromise.then(items => {
|
||||
|
||||
const pickItems: TransferQuickPickItemOrSeparator[] = [];
|
||||
@@ -94,12 +97,16 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
|
||||
} else if (item.kind === QuickPickItemKind.Separator) {
|
||||
pickItems.push({ type: 'separator', label: item.label });
|
||||
} else {
|
||||
if (item.tooltip && !allowedTooltips) {
|
||||
console.warn(`Extension '${extension.identifier.value}' uses a tooltip which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.identifier.value}`);
|
||||
}
|
||||
pickItems.push({
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
detail: item.detail,
|
||||
picked: item.picked,
|
||||
alwaysShow: item.alwaysShow,
|
||||
tooltip: allowedTooltips ? MarkdownString.fromStrict(item.tooltip) : undefined,
|
||||
handle
|
||||
});
|
||||
}
|
||||
@@ -535,7 +542,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
|
||||
private readonly _onDidChangeSelectionEmitter = new Emitter<T[]>();
|
||||
private readonly _onDidTriggerItemButtonEmitter = new Emitter<QuickPickItemButtonEvent<T>>();
|
||||
|
||||
constructor(extension: IExtensionDescription, onDispose: () => void) {
|
||||
constructor(private extension: IExtensionDescription, onDispose: () => void) {
|
||||
super(extension.identifier, onDispose);
|
||||
this._disposables.push(
|
||||
this._onDidChangeActiveEmitter,
|
||||
@@ -558,12 +565,16 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
|
||||
this._itemsToHandles.set(item, i);
|
||||
});
|
||||
|
||||
const allowedTooltips = isProposedApiEnabled(this.extension, 'quickPickItemTooltip');
|
||||
const pickItems: TransferQuickPickItemOrSeparator[] = [];
|
||||
for (let handle = 0; handle < items.length; handle++) {
|
||||
const item = items[handle];
|
||||
if (item.kind === QuickPickItemKind.Separator) {
|
||||
pickItems.push({ type: 'separator', label: item.label });
|
||||
} else {
|
||||
if (item.tooltip && !allowedTooltips) {
|
||||
console.warn(`Extension '${this.extension.identifier.value}' uses a tooltip which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this.extension.identifier.value}`);
|
||||
}
|
||||
pickItems.push({
|
||||
handle,
|
||||
label: item.label,
|
||||
@@ -571,6 +582,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
|
||||
detail: item.detail,
|
||||
picked: item.picked,
|
||||
alwaysShow: item.alwaysShow,
|
||||
tooltip: allowedTooltips ? MarkdownString.fromStrict(item.tooltip) : undefined,
|
||||
buttons: item.buttons?.map<TransferQuickInputButton>((button, i) => {
|
||||
return {
|
||||
...getIconPathOrClass(button),
|
||||
|
||||
@@ -51,6 +51,7 @@ export const allApiProposals = Object.freeze({
|
||||
portsAttributes: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts',
|
||||
profileContentHandlers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts',
|
||||
quickDiffProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts',
|
||||
quickPickItemTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts',
|
||||
quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts',
|
||||
resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts',
|
||||
scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts',
|
||||
|
||||
@@ -15,9 +15,12 @@ import { QuickInputService as BaseQuickInputService } from 'vs/platform/quickinp
|
||||
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { InQuickPickContextKey } from 'vs/workbench/browser/quickaccess';
|
||||
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
|
||||
import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
|
||||
|
||||
export class QuickInputService extends BaseQuickInputService {
|
||||
|
||||
private readonly hoverDelegate = new QuickInputHoverDelegate(this.configurationService, this.hoverService);
|
||||
private readonly inQuickInputContext = InQuickPickContextKey.bindTo(this.contextKeyService);
|
||||
|
||||
constructor(
|
||||
@@ -27,7 +30,8 @@ export class QuickInputService extends BaseQuickInputService {
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IAccessibilityService accessibilityService: IAccessibilityService,
|
||||
@ILayoutService layoutService: ILayoutService
|
||||
@ILayoutService layoutService: ILayoutService,
|
||||
@IHoverService private readonly hoverService: IHoverService
|
||||
) {
|
||||
super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService);
|
||||
|
||||
@@ -42,9 +46,41 @@ export class QuickInputService extends BaseQuickInputService {
|
||||
protected override createController(): QuickInputController {
|
||||
return super.createController(this.layoutService, {
|
||||
ignoreFocusOut: () => !this.configurationService.getValue('workbench.quickOpen.closeOnFocusLost'),
|
||||
backKeybindingLabel: () => this.keybindingService.lookupKeybinding('workbench.action.quickInputBack')?.getLabel() || undefined
|
||||
backKeybindingLabel: () => this.keybindingService.lookupKeybinding('workbench.action.quickInputBack')?.getLabel() || undefined,
|
||||
hoverDelegate: this.hoverDelegate
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class QuickInputHoverDelegate implements IHoverDelegate {
|
||||
private lastHoverHideTime = 0;
|
||||
readonly placement = 'element';
|
||||
|
||||
get delay() {
|
||||
if (Date.now() - this.lastHoverHideTime < 200) {
|
||||
return 0; // show instantly when a hover was recently shown
|
||||
}
|
||||
|
||||
return this.configurationService.getValue<number>('workbench.hover.delay');
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly configurationService: IConfigurationService,
|
||||
private readonly hoverService: IHoverService
|
||||
) { }
|
||||
|
||||
showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined {
|
||||
return this.hoverService.showHover({
|
||||
...options,
|
||||
hideOnHover: false,
|
||||
hideOnKeyDown: false,
|
||||
skipFadeInAnimation: true,
|
||||
}, focus);
|
||||
}
|
||||
|
||||
onDidHideHover(): void {
|
||||
this.lastHoverHideTime = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IQuickInputService, QuickInputService, InstantiationType.Delayed);
|
||||
|
||||
@@ -165,6 +165,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c
|
||||
import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover';
|
||||
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
|
||||
|
||||
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
||||
@@ -317,7 +318,8 @@ export function workbenchInstantiationService(
|
||||
instantiationService.stub(ICodeEditorService, disposables.add(new CodeEditorService(editorService, themeService, configService)));
|
||||
instantiationService.stub(IPaneCompositePartService, new TestPaneCompositeService());
|
||||
instantiationService.stub(IListService, new TestListService());
|
||||
instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService)));
|
||||
const hoverService = instantiationService.stub(IHoverService, instantiationService.createInstance(TestHoverService));
|
||||
instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService, hoverService)));
|
||||
instantiationService.stub(IWorkspacesService, new TestWorkspacesService());
|
||||
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
|
||||
instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService());
|
||||
@@ -730,6 +732,24 @@ export class TestSideBarPart implements IPaneCompositePart {
|
||||
layout(width: number, height: number, top: number, left: number): void { }
|
||||
}
|
||||
|
||||
class TestHoverService implements IHoverService {
|
||||
private currentHover: IHoverWidget | undefined;
|
||||
_serviceBrand: undefined;
|
||||
showHover(options: IHoverOptions, focus?: boolean | undefined): IHoverWidget | undefined {
|
||||
this.currentHover = new class implements IHoverWidget {
|
||||
private _isDisposed = false;
|
||||
get isDisposed(): boolean { return this._isDisposed; }
|
||||
dispose(): void {
|
||||
this._isDisposed = true;
|
||||
}
|
||||
};
|
||||
return this.currentHover;
|
||||
}
|
||||
hideHover(): void {
|
||||
this.currentHover?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class TestPanelPart implements IPaneCompositePart, IPaneCompositeSelectorPart {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/73904
|
||||
|
||||
export interface QuickPickItem {
|
||||
/**
|
||||
* An optional flag to sort the final results by index of first query match in label. Defaults to true.
|
||||
*/
|
||||
tooltip?: string | MarkdownString;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user