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:
Tyler James Leonhardt
2023-02-15 08:35:09 -08:00
committed by GitHub
parent 89907df37d
commit f861acbee1
12 changed files with 257 additions and 18 deletions
@@ -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);
+22 -10
View File
@@ -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;
}
}