Skip to content

Commit 70c31a4

Browse files
authored
Support basic MarkdownStrings in tree view labels (microsoft#272435)
* Support rendering icons in tree view labels * Use MarkdownStrings instead of a flag * Add TreeItem2 to ext API * Remove API in TreeItem, better highlight support * Support bold, improve docstring * PR feedback * Fix default, PR feedback
1 parent cfba06b commit 70c31a4

File tree

13 files changed

+177
-48
lines changed

13 files changed

+177
-48
lines changed

src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,6 @@ export interface IHighlight {
2222
}
2323

2424
export interface IHighlightedLabelOptions {
25-
26-
/**
27-
* Whether the label supports rendering icons.
28-
*/
29-
readonly supportIcons?: boolean;
30-
3125
readonly hoverDelegate?: IHoverDelegate;
3226
}
3327

@@ -41,7 +35,6 @@ export class HighlightedLabel extends Disposable {
4135
private text: string = '';
4236
private title: string = '';
4337
private highlights: readonly IHighlight[] = [];
44-
private supportIcons: boolean;
4538
private didEverRender: boolean = false;
4639
private customHover: IManagedHover | undefined;
4740

@@ -53,7 +46,6 @@ export class HighlightedLabel extends Disposable {
5346
constructor(container: HTMLElement, private readonly options?: IHighlightedLabelOptions) {
5447
super();
5548

56-
this.supportIcons = options?.supportIcons ?? false;
5749
this.domNode = dom.append(container, dom.$('span.monaco-highlighted-label'));
5850
}
5951

@@ -73,7 +65,7 @@ export class HighlightedLabel extends Disposable {
7365
* @param escapeNewLines Whether to escape new lines.
7466
* @returns
7567
*/
76-
set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean) {
68+
set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean, supportIcons?: boolean) {
7769
if (!text) {
7870
text = '';
7971
}
@@ -90,10 +82,10 @@ export class HighlightedLabel extends Disposable {
9082
this.text = text;
9183
this.title = title;
9284
this.highlights = highlights;
93-
this.render();
85+
this.render(supportIcons);
9486
}
9587

96-
private render(): void {
88+
private render(supportIcons?: boolean): void {
9789

9890
const children: Array<HTMLSpanElement | string> = [];
9991
let pos = 0;
@@ -105,7 +97,7 @@ export class HighlightedLabel extends Disposable {
10597

10698
if (pos < highlight.start) {
10799
const substring = this.text.substring(pos, highlight.start);
108-
if (this.supportIcons) {
100+
if (supportIcons) {
109101
children.push(...renderLabelWithIcons(substring));
110102
} else {
111103
children.push(substring);
@@ -114,7 +106,7 @@ export class HighlightedLabel extends Disposable {
114106
}
115107

116108
const substring = this.text.substring(pos, highlight.end);
117-
const element = dom.$('span.highlight', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]);
109+
const element = dom.$('span.highlight', undefined, ...supportIcons ? renderLabelWithIcons(substring) : [substring]);
118110

119111
if (highlight.extraClasses) {
120112
element.classList.add(...highlight.extraClasses);
@@ -126,7 +118,7 @@ export class HighlightedLabel extends Disposable {
126118

127119
if (pos < this.text.length) {
128120
const substring = this.text.substring(pos,);
129-
if (this.supportIcons) {
121+
if (supportIcons) {
130122
children.push(...renderLabelWithIcons(substring));
131123
} else {
132124
children.push(substring);

src/vs/base/browser/ui/iconLabel/iconLabel.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface IIconLabelValueOptions {
3131
suffix?: string;
3232
hideIcon?: boolean;
3333
extraClasses?: readonly string[];
34+
bold?: boolean;
3435
italic?: boolean;
3536
strikethrough?: boolean;
3637
matches?: readonly IMatch[];
@@ -40,6 +41,7 @@ export interface IIconLabelValueOptions {
4041
readonly separator?: string;
4142
readonly domId?: string;
4243
iconPath?: URI;
44+
supportIcons?: boolean;
4345
}
4446

4547
class FastLabelNode {
@@ -136,6 +138,10 @@ export class IconLabel extends Disposable {
136138
labelClasses.push(...options.extraClasses);
137139
}
138140

141+
if (options.bold) {
142+
labelClasses.push('bold');
143+
}
144+
139145
if (options.italic) {
140146
labelClasses.push('italic');
141147
}
@@ -185,7 +191,7 @@ export class IconLabel extends Disposable {
185191
if (description || this.descriptionNode) {
186192
const descriptionNode = this.getOrCreateDescriptionNode();
187193
if (descriptionNode instanceof HighlightedLabel) {
188-
descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines);
194+
descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines, options?.supportIcons);
189195
this.setupHover(descriptionNode.element, options?.descriptionTitle);
190196
} else {
191197
descriptionNode.textContent = description && options?.labelEscapeNewLines ? HighlightedLabel.escapeNewLines(description, []) : (description || '');
@@ -247,7 +253,7 @@ export class IconLabel extends Disposable {
247253
if (!this.descriptionNode) {
248254
const descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container'))));
249255
if (this.creationOptions?.supportDescriptionHighlights) {
250-
this.descriptionNode = this._register(new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')), { supportIcons: !!this.creationOptions.supportIcons }));
256+
this.descriptionNode = this._register(new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description'))));
251257
} else {
252258
this.descriptionNode = this._register(new FastLabelNode(dom.append(descriptionContainer.element, dom.$('span.label-description'))));
253259
}
@@ -338,14 +344,17 @@ class LabelWithHighlights extends Disposable {
338344
this.label = label;
339345
this.options = options;
340346

347+
// Determine supportIcons: use option if provided, otherwise use constructor value
348+
const supportIcons = options?.supportIcons ?? this.supportIcons;
349+
341350
if (typeof label === 'string') {
342351
if (!this.singleLabel) {
343352
this.container.textContent = '';
344353
this.container.classList.remove('multiple');
345-
this.singleLabel = this._register(new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), { supportIcons: this.supportIcons }));
354+
this.singleLabel = this._register(new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId }))));
346355
}
347356

348-
this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines);
357+
this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines, supportIcons);
349358
} else {
350359
this.container.textContent = '';
351360
this.container.classList.add('multiple');
@@ -360,8 +369,8 @@ class LabelWithHighlights extends Disposable {
360369
const id = options?.domId && `${options?.domId}_${i}`;
361370

362371
const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' });
363-
const highlightedLabel = this._register(new HighlightedLabel(dom.append(this.container, name), { supportIcons: this.supportIcons }));
364-
highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines);
372+
const highlightedLabel = this._register(new HighlightedLabel(dom.append(this.container, name)));
373+
highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines, supportIcons);
365374

366375
if (i < label.length - 1) {
367376
dom.append(name, dom.$('span.label-separator', undefined, separator));

src/vs/base/browser/ui/iconLabel/iconlabel.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@
7878
opacity: .95;
7979
}
8080

81+
.monaco-icon-label.bold > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
82+
.monaco-icon-label.bold > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
83+
font-weight: bold;
84+
}
85+
8186
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
8287
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
8388
font-style: italic;

src/vs/base/test/browser/highlightedLabel.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ suite('HighlightedLabel', () => {
1111
let label: HighlightedLabel;
1212

1313
setup(() => {
14-
label = new HighlightedLabel(document.createElement('div'), { supportIcons: true });
14+
label = new HighlightedLabel(document.createElement('div'));
1515
});
1616

1717
test('empty label', function () {

src/vs/platform/extensions/common/extensionsApiProposals.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,9 @@ const _allApiProposals = {
442442
toolProgress: {
443443
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.toolProgress.d.ts',
444444
},
445+
treeItemMarkdownLabel: {
446+
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeItemMarkdownLabel.d.ts',
447+
},
445448
treeViewActiveItem: {
446449
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts',
447450
},

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,18 @@ function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeIte
3232
return { label };
3333
}
3434

35-
if (label
36-
&& typeof label === 'object'
37-
&& typeof label.label === 'string') {
35+
if (label && typeof label === 'object' && label.label) {
3836
let highlights: [number, number][] | undefined = undefined;
3937
if (Array.isArray(label.highlights)) {
4038
highlights = (<[number, number][]>label.highlights).filter((highlight => highlight.length === 2 && typeof highlight[0] === 'number' && typeof highlight[1] === 'number'));
4139
highlights = highlights.length ? highlights : undefined;
4240
}
43-
return { label: label.label, highlights };
41+
if (isString(label.label)) {
42+
return { label: label.label, highlights };
43+
} else if (extHostTypes.MarkdownString.isMarkdownString(label.label)) {
44+
checkProposedApiEnabled(extension, 'treeItemMarkdownLabel');
45+
return { label: MarkdownString.from(label.label), highlights };
46+
}
4447
}
4548

4649
return undefined;
@@ -925,7 +928,15 @@ class ExtHostTreeView<T> extends Disposable {
925928

926929
const treeItemLabel = toTreeItemLabel(label, this._extension);
927930
const prefix: string = parent ? parent.item.handle : ExtHostTreeView.LABEL_HANDLE_PREFIX;
928-
let elementId = treeItemLabel ? treeItemLabel.label : resourceUri ? basename(resourceUri) : '';
931+
let labelValue = '';
932+
if (treeItemLabel) {
933+
if (isMarkdownString(treeItemLabel.label)) {
934+
labelValue = treeItemLabel.label.value;
935+
} else {
936+
labelValue = treeItemLabel.label;
937+
}
938+
}
939+
let elementId = labelValue || (resourceUri ? basename(resourceUri) : '');
929940
elementId = elementId.indexOf('/') !== -1 ? elementId.replace('/', '//') : elementId;
930941
const existingHandle = this._nodes.has(element) ? this._nodes.get(element)!.item.handle : undefined;
931942
const childrenNodes = (this._getChildrenNodes(parent) || []);

src/vs/workbench/browser/labels.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ class ResourceLabelWidget extends IconLabel {
631631

632632
const iconLabelOptions: IIconLabelValueOptions & { extraClasses: string[] } = {
633633
title: '',
634+
bold: this.options?.bold,
634635
italic: this.options?.italic,
635636
strikethrough: this.options?.strikethrough,
636637
matches: this.options?.matches,
@@ -641,6 +642,7 @@ class ResourceLabelWidget extends IconLabel {
641642
disabledCommand: this.options?.disabledCommand,
642643
labelEscapeNewLines: this.options?.labelEscapeNewLines,
643644
descriptionTitle: this.options?.descriptionTitle,
645+
supportIcons: this.options?.supportIcons,
644646
};
645647

646648
const resource = toResource(this.label);

src/vs/workbench/browser/parts/views/media/views.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@
184184
overflow: hidden;
185185
}
186186

187+
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel .monaco-highlighted-label .codicon {
188+
position: relative;
189+
top: 2px;
190+
}
191+
187192
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label-container::after {
188193
content: '';
189194
display: block;

0 commit comments

Comments
 (0)