Skip to content

Commit b0b51eb

Browse files
authored
Use markdown in PR tree labels (#8069)
* Use markdown in PR tree labels * Make escape syntax more specific * Fix test
1 parent b10dac6 commit b0b51eb

File tree

4 files changed

+60
-17
lines changed

4 files changed

+60
-17
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"remoteCodingAgents",
3636
"shareProvider",
3737
"tokenInformation",
38+
"treeItemMarkdownLabel",
3839
"treeViewMarkdownMessage"
3940
],
4041
"version": "0.120.0",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
// @kycutler https://github.com/microsoft/vscode/issues/271523
9+
10+
export interface TreeItemLabel2 {
11+
highlights?: [number, number][];
12+
13+
/**
14+
* A human-readable string or MarkdownString describing the {@link TreeItem Tree item}.
15+
*
16+
* When using MarkdownString, only the following Markdown syntax is supported:
17+
* - Icons (e.g., `$(icon-name)`, when the `supportIcons` flag is also set)
18+
* - Bold, italics, and strikethrough formatting, but only when the syntax wraps the entire string
19+
* (e.g., `**bold**`, `_italic_`, `~~strikethrough~~`)
20+
*/
21+
label: string | MarkdownString;
22+
}
23+
}

src/test/view/prsTree.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,14 +256,18 @@ describe('GitHub Pull Requests view', function () {
256256
assert.strictEqual(localChildren.length, 2);
257257
const [localItem0, localItem1] = await Promise.all(localChildren.map(node => node.getTreeItem()));
258258

259-
assert.strictEqual(localItem0.label, 'zero');
259+
const label0 = (localItem0.label as vscode.TreeItemLabel2).label;
260+
assert.ok(label0 instanceof vscode.MarkdownString);
261+
assert.equal(label0.value, 'zero');
260262
assert.strictEqual(localItem0.tooltip, undefined);
261263
assert.strictEqual(localItem0.description, 'by @me');
262264
assert.strictEqual(localItem0.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed);
263265
assert.strictEqual(localItem0.contextValue, 'pullrequest:local:nonactive:hasHeadRef');
264266
assert.deepStrictEqual(localItem0.iconPath!.toString(), 'https://avatars.com/me.jpg');
265267

266-
assert.strictEqual(localItem1.label, '✓ one');
268+
const label1 = (localItem1.label as vscode.TreeItemLabel2).label;
269+
assert.ok(label1 instanceof vscode.MarkdownString);
270+
assert.equal(label1.value, '$(check) one');
267271
assert.strictEqual(localItem1.tooltip, undefined);
268272
assert.strictEqual(localItem1.description, 'by @you');
269273
assert.strictEqual(localItem1.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed);

src/view/treeNodes/pullRequestNode.ts

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -297,30 +297,45 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
297297
}
298298
}
299299

300-
async getTreeItem(): Promise<vscode.TreeItem> {
300+
private _getLabel(): string {
301301
const currentBranchIsForThisPR = this.pullRequestModel.equals(this._folderReposManager.activePullRequest);
302+
const { title, number, author, isDraft } = this.pullRequestModel;
303+
let label = '';
302304

303-
const { title, number, author, isDraft, html_url } = this.pullRequestModel;
304-
let labelTitle = this.pullRequestModel.title.length > 50 ? `${this.pullRequestModel.title.substring(0, 50)}...` : this.pullRequestModel.title;
305-
if (COPILOT_ACCOUNTS[this.pullRequestModel.author.login]) {
306-
labelTitle = labelTitle.replace('[WIP]', '');
305+
if (currentBranchIsForThisPR) {
306+
label += '$(check) ';
307307
}
308-
const login = author.specialDisplayName ?? author.login;
309-
310-
const hasNotification = this._notificationProvider.hasNotification(this.pullRequestModel) || this._prsTreeModel.hasCopilotNotification(this.pullRequestModel.remote.owner, this.pullRequestModel.remote.repositoryName, this.pullRequestModel.number);
311-
312-
const formattedPRNumber = number.toString();
313-
let labelPrefix = currentBranchIsForThisPR ? '✓ ' : '';
314308

315309
if (
316310
vscode.workspace
317311
.getConfiguration(PR_SETTINGS_NAMESPACE)
318312
.get<boolean>(SHOW_PULL_REQUEST_NUMBER_IN_TREE, false)
319313
) {
320-
labelPrefix += `#${formattedPRNumber}: `;
314+
label += `#${number}: `;
315+
}
316+
317+
let labelTitle = title.length > 50 ? `${title.substring(0, 50)}...` : title;
318+
if (COPILOT_ACCOUNTS[author.login]) {
319+
labelTitle = labelTitle.replace('[WIP]', '');
321320
}
321+
// Escape any $(...) syntax to avoid rendering PR titles as icons.
322+
label += labelTitle.replace(/\$\([a-zA-Z0-9~-]+\)/g, '\\$&');
322323

323-
const label = `${labelPrefix}${isDraft ? '\u270E ' : ''}${labelTitle}`;
324+
if (isDraft) {
325+
label = `_${label}_`;
326+
}
327+
328+
return label;
329+
}
330+
331+
async getTreeItem(): Promise<vscode.TreeItem> {
332+
const currentBranchIsForThisPR = this.pullRequestModel.equals(this._folderReposManager.activePullRequest);
333+
const { title, number, author, isDraft, html_url } = this.pullRequestModel;
334+
const login = author.specialDisplayName ?? author.login;
335+
const hasNotification = this._notificationProvider.hasNotification(this.pullRequestModel) || this._prsTreeModel.hasCopilotNotification(this.pullRequestModel.remote.owner, this.pullRequestModel.remote.repositoryName, this.pullRequestModel.number);
336+
const label: vscode.TreeItemLabel2 = {
337+
label: new vscode.MarkdownString(this._getLabel(), true)
338+
};
324339
const description = `by @${login}`;
325340
const command = {
326341
title: vscode.l10n.t('View Pull Request Description'),
@@ -329,7 +344,7 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
329344
};
330345

331346
return {
332-
label,
347+
label: label as vscode.TreeItemLabel,
333348
id: `${this.parent instanceof TreeNode ? (this.parent.id ?? this.parent.label) : ''}${html_url}${this._isLocal ? this.pullRequestModel.localBranchName : ''}`, // unique id stable across checkout status
334349
description,
335350
collapsibleState: 1,
@@ -341,7 +356,7 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
341356
(((this.pullRequestModel.item.isRemoteHeadDeleted && !this._isLocal) || !this._folderReposManager.isPullRequestAssociatedWithOpenRepository(this.pullRequestModel)) ? '' : ':hasHeadRef'),
342357
iconPath: await this._getIcon(),
343358
accessibilityInformation: {
344-
label: `${isDraft ? 'Draft ' : ''}Pull request number ${formattedPRNumber}: ${title} by ${login}`
359+
label: `${isDraft ? 'Draft ' : ''}Pull request number ${number}: ${title} by ${login}`
345360
},
346361
resourceUri: createPRNodeUri(this.pullRequestModel, this.parent instanceof CategoryTreeNode && this.parent.isCopilot ? true : undefined),
347362
command

0 commit comments

Comments
 (0)