Skip to content

Commit ddae734

Browse files
authored
API proposal for icons in quick pick (microsoft#184729)
Part of microsoft#184726
1 parent 58f4c35 commit ddae734

File tree

10 files changed

+76
-6
lines changed

10 files changed

+76
-6
lines changed

src/vs/platform/quickinput/browser/media/quickInput.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,18 @@
206206
margin: 0;
207207
}
208208

209+
.quick-input-list .quick-input-list-icon {
210+
background-size: 16px;
211+
background-position: left center;
212+
background-repeat: no-repeat;
213+
padding-right: 6px;
214+
width: 16px;
215+
height: 22px;
216+
display: flex;
217+
align-items: center;
218+
justify-content: center;
219+
}
220+
209221
.quick-input-list .quick-input-list-rows {
210222
overflow: hidden;
211223
text-overflow: ellipsis;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInput, IQuickIn
3535
import { QuickInputBox } from './quickInputBox';
3636
import { QuickInputList, QuickInputListFocus } from './quickInputList';
3737
import { getIconClass, renderQuickInputDescription } from './quickInputUtils';
38+
import { ColorScheme } from 'vs/platform/theme/common/theme';
3839

3940
export interface IQuickInputOptions {
4041
idPrefix: string;
@@ -65,6 +66,7 @@ export interface IQuickInputStyles {
6566
readonly keybindingLabel: IKeybindingLabelStyles;
6667
readonly list: IListStyles;
6768
readonly pickerGroup: { pickerGroupBorder: string | undefined; pickerGroupForeground: string | undefined };
69+
readonly colorScheme: ColorScheme;
6870
}
6971

7072
export interface IQuickInputWidgetStyles {

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import { IQuickInputOptions } from 'vs/platform/quickinput/browser/quickInput';
3434
import { getIconClass } from 'vs/platform/quickinput/browser/quickInputUtils';
3535
import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickSeparatorButtonEvent, QuickPickItem } from 'vs/platform/quickinput/common/quickInput';
3636
import { Lazy } from 'vs/base/common/lazy';
37+
import { URI } from 'vs/base/common/uri';
38+
import { ColorScheme, isDark } from 'vs/platform/theme/common/theme';
3739

3840
const $ = dom.$;
3941

@@ -218,6 +220,7 @@ class ListElement implements IListElement {
218220
interface IListElementTemplateData {
219221
entry: HTMLDivElement;
220222
checkbox: HTMLInputElement;
223+
icon: HTMLDivElement;
221224
label: IconLabel;
222225
keybinding: KeybindingLabel;
223226
detail: IconLabel;
@@ -232,6 +235,8 @@ class ListElementRenderer implements IListRenderer<IListElement, IListElementTem
232235

233236
static readonly ID = 'listelement';
234237

238+
constructor(private readonly colorScheme: ColorScheme) { }
239+
235240
get templateId() {
236241
return ListElementRenderer.ID;
237242
}
@@ -263,6 +268,7 @@ class ListElementRenderer implements IListRenderer<IListElement, IListElementTem
263268

264269
// Label
265270
data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportIcons: true });
271+
data.icon = <HTMLInputElement>dom.prepend(data.label.element, $('.quick-input-list-icon'));
266272

267273
// Keybinding
268274
const keybindingContainer = dom.append(row1, $('.quick-input-list-entry-keybinding'));
@@ -293,6 +299,16 @@ class ListElementRenderer implements IListRenderer<IListElement, IListElementTem
293299

294300
const { labelHighlights, descriptionHighlights, detailHighlights } = element;
295301

302+
if (element.item?.iconPath) {
303+
const icon = isDark(this.colorScheme) ? element.item.iconPath.dark : (element.item.iconPath.light ?? element.item.iconPath.dark);
304+
const iconUrl = URI.revive(icon);
305+
data.icon.className = 'quick-input-list-icon';
306+
data.icon.style.backgroundImage = dom.asCSSUrl(iconUrl);
307+
} else {
308+
data.icon.style.backgroundImage = '';
309+
data.icon.className = element.item?.iconClass ? `quick-input-list-icon ${element.item.iconClass}` : '';
310+
}
311+
296312
// Label
297313
const options: IIconLabelValueOptions = {
298314
matches: labelHighlights || [],
@@ -449,7 +465,7 @@ export class QuickInputList {
449465
this.container = dom.append(this.parent, $('.quick-input-list'));
450466
const delegate = new ListElementDelegate();
451467
const accessibilityProvider = new QuickInputAccessibilityProvider();
452-
this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], {
468+
this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer(this.options.styles.colorScheme)], {
453469
identityProvider: { getId: element => element.saneLabel },
454470
setRowLineHeight: false,
455471
multipleSelectionSupport: false,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ export class QuickInputService extends Themable implements IQuickInputService {
221221
pickerGroup: {
222222
pickerGroupBorder: asCssVariable(pickerGroupBorder),
223223
pickerGroupForeground: asCssVariable(pickerGroupForeground),
224-
}
224+
},
225+
colorScheme: this.themeService.getColorTheme().type
225226
};
226227
}
227228
}

src/vs/platform/quickinput/common/quickInput.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export interface IQuickPickItem {
3939
*/
4040
keybinding?: ResolvedKeybinding;
4141
iconClasses?: readonly string[];
42+
iconPath?: { dark: URI; light?: URI };
43+
iconClass?: string;
4244
italic?: boolean;
4345
strikethrough?: boolean;
4446
highlights?: IQuickPickItemHighlights;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { unthemedKeybindingLabelOptions } from 'vs/base/browser/ui/keybindingLab
1515
import { unthemedProgressBarOptions } from 'vs/base/browser/ui/progressbar/progressbar';
1616
import { QuickInputController } from 'vs/platform/quickinput/browser/quickInput';
1717
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
18+
import { ColorScheme } from 'vs/platform/theme/common/theme';
1819

1920
// Sets up an `onShow` listener to allow us to wait until the quick pick is shown (useful when triggering an `accept()` right after launching a quick pick)
2021
// kick this off before you launch the picker and then await the promise returned after you launch the picker.
@@ -82,6 +83,7 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543
8283
pickerGroupBorder: undefined,
8384
pickerGroupForeground: undefined,
8485
},
86+
colorScheme: ColorScheme.DARK
8587
}
8688
});
8789

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,8 @@ export interface TransferQuickPickItem {
518518
// shared properties from IQuickPickItem
519519
type?: 'item';
520520
label: string;
521+
iconPath?: { light?: URI; dark: URI };
522+
iconClass?: string;
521523
description?: string;
522524
detail?: string;
523525
picked?: boolean;

src/vs/workbench/api/common/extHostQuickOpen.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
8686
}
8787

8888
const allowedTooltips = isProposedApiEnabled(extension, 'quickPickItemTooltip');
89+
const allowedIcons = isProposedApiEnabled(extension, 'quickPickItemIcon');
8990

9091
return itemsPromise.then(items => {
9192

@@ -100,8 +101,14 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
100101
if (item.tooltip && !allowedTooltips) {
101102
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}`);
102103
}
104+
if (item.iconPath && !allowedIcons) {
105+
console.warn(`Extension '${extension.identifier.value}' uses an icon 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}`);
106+
}
107+
const icon = (item.iconPath && allowedIcons) ? getIconPathOrClass(item.iconPath) : undefined;
103108
pickItems.push({
104109
label: item.label,
110+
iconPath: icon?.iconPath,
111+
iconClass: icon?.iconClass,
105112
description: item.description,
106113
detail: item.detail,
107114
picked: item.picked,
@@ -390,7 +397,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
390397
this.update({
391398
buttons: buttons.map<TransferQuickInputButton>((button, i) => {
392399
return {
393-
...getIconPathOrClass(button),
400+
...getIconPathOrClass(button.iconPath),
394401
tooltip: button.tooltip,
395402
handle: button === QuickInputButtons.Back ? -1 : i,
396403
};
@@ -510,8 +517,8 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
510517
return typeof iconPath === 'object' && 'dark' in iconPath ? iconPath.dark : iconPath;
511518
}
512519

513-
function getIconPathOrClass(button: QuickInputButton) {
514-
const iconPathOrIconClass = getIconUris(button.iconPath);
520+
function getIconPathOrClass(icon: QuickInputButton['iconPath']) {
521+
const iconPathOrIconClass = getIconUris(icon);
515522
let iconPath: { dark: URI; light?: URI | undefined } | undefined;
516523
let iconClass: string | undefined;
517524
if ('id' in iconPathOrIconClass) {
@@ -566,6 +573,8 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
566573
});
567574

568575
const allowedTooltips = isProposedApiEnabled(this.extension, 'quickPickItemTooltip');
576+
const allowedIcons = isProposedApiEnabled(this.extension, 'quickPickItemIcon');
577+
569578
const pickItems: TransferQuickPickItemOrSeparator[] = [];
570579
for (let handle = 0; handle < items.length; handle++) {
571580
const item = items[handle];
@@ -575,17 +584,23 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx
575584
if (item.tooltip && !allowedTooltips) {
576585
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}`);
577586
}
587+
if (item.iconPath && !allowedIcons) {
588+
console.warn(`Extension '${this.extension.identifier.value}' uses an icon 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}`);
589+
}
590+
const icon = (item.iconPath && allowedIcons) ? getIconPathOrClass(item.iconPath) : undefined;
578591
pickItems.push({
579592
handle,
580593
label: item.label,
594+
iconPath: icon?.iconPath,
595+
iconClass: icon?.iconClass,
581596
description: item.description,
582597
detail: item.detail,
583598
picked: item.picked,
584599
alwaysShow: item.alwaysShow,
585600
tooltip: allowedTooltips ? MarkdownString.fromStrict(item.tooltip) : undefined,
586601
buttons: item.buttons?.map<TransferQuickInputButton>((button, i) => {
587602
return {
588-
...getIconPathOrClass(button),
603+
...getIconPathOrClass(button.iconPath),
589604
tooltip: button.tooltip,
590605
handle: i
591606
};

src/vs/workbench/services/extensions/common/extensionsApiProposals.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export const allApiProposals = Object.freeze({
6666
portsAttributes: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts',
6767
profileContentHandlers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts',
6868
quickDiffProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts',
69+
quickPickItemIcon: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemIcon.d.ts',
6970
quickPickItemTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts',
7071
quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts',
7172
resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts',
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
declare module 'vscode' {
7+
/**
8+
* Represents an item that can be selected from
9+
* a list of items.
10+
*/
11+
export interface QuickPickItem {
12+
/**
13+
* The icon path or {@link ThemeIcon} for the QuickPickItem.
14+
*/
15+
iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon;
16+
}
17+
}

0 commit comments

Comments
 (0)