Skip to content

Commit 0cb65db

Browse files
kabelalexr00
andauthored
Add settings to control the icons used in Issues and PR tree views (#6798)
Implements the feature request #6641. Both tree views support an icon mode of `author` (default), `state`, or `generic`. `author` follows the existing logic prior to this setting, where we attempt to fetch a rounded avatar. PRs fall back to a GitHub icon and Issues fall back to state-specific icon. `state` will always use a state-specific icon that matches the Issue/PR type and state. `generic` will always use the GitHub icon for a PR and an uncolored issues theme icon for Issues. Co-authored-by: Alex Ross <38270282+alexr00@users.noreply.github.com>
1 parent d39d22b commit 0cb65db

File tree

5 files changed

+88
-27
lines changed

5 files changed

+88
-27
lines changed

package.json

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -802,11 +802,15 @@
802802
"type": "string",
803803
"enum": [
804804
"author",
805-
"assignee"
805+
"assignee",
806+
"state",
807+
"generic"
806808
],
807809
"enumDescriptions": [
808810
"%githubIssues.issueAvatarDisplay.author%",
809-
"%githubIssues.issueAvatarDisplay.assignee%"
811+
"%githubIssues.issueAvatarDisplay.assignee%",
812+
"%githubIssues.issueAvatarDisplay.state%",
813+
"%githubIssues.issueAvatarDisplay.generic%"
810814
],
811815
"default": "author",
812816
"description": "%githubIssues.issueAvatarDisplay.description%"
@@ -842,6 +846,21 @@
842846
"default": false,
843847
"description": "%githubPullRequests.showPullRequestNumberInTree.description%"
844848
},
849+
"githubPullRequests.pullRequestAvatarDisplay": {
850+
"type": "string",
851+
"enum": [
852+
"author",
853+
"state",
854+
"generic"
855+
],
856+
"enumDescriptions": [
857+
"%githubPullRequests.pullRequestAvatarDisplay.author%",
858+
"%githubPullRequests.pullRequestAvatarDisplay.state%",
859+
"%githubPullRequests.pullRequestAvatarDisplay.generic%"
860+
],
861+
"default": "author",
862+
"description": "%githubPullRequests.pullRequestAvatarDisplay.description%"
863+
},
845864
"githubIssues.alwaysPromptForNewIssueRepo": {
846865
"type": "boolean",
847866
"default": false,

package.nls.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@
183183
"githubPullRequests.showPullRequestNumberInTree.description": "Shows the pull request number in the tree view.",
184184
"githubPullRequests.labelCreated.description": "Group of labels that you want to add to the pull request automatically. Labels that don't exist in the repository won't be added.",
185185
"githubPullRequests.labelCreated.label.description": "Each string element is the value of label that you want to add.",
186+
"githubPullRequests.pullRequestAvatarDisplay.description": "Which icon to use in the pull request tree view",
187+
"githubPullRequests.pullRequestAvatarDisplay.author": "Show the pull request author avatar",
188+
"githubPullRequests.pullRequestAvatarDisplay.state": "Show the pull request type (draft or not) and state (open/closed/merged) as a colored icon",
189+
"githubPullRequests.pullRequestAvatarDisplay.generic": "Show a GitHub icon",
190+
"githubIssues.issueAvatarDisplay.state": "Show the issue state (open/closed) as a colored icon",
191+
"githubIssues.issueAvatarDisplay.generic": "Show an issues icon regardless of state",
186192
"githubIssues.alwaysPromptForNewIssueRepo.description": "Enabling will always prompt which repository to create an issue in instead of basing off the current open file.",
187193
"view.github.pull.requests.name": "GitHub",
188194
"view.github.pull.request.name": "GitHub Pull Request",

src/common/settingKeys.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ export const EXPERIMENTAL_NOTIFICATIONS_SCORE = 'experimental.notificationsScore
7373
export const WEBVIEW_REFRESH_INTERVAL = 'webviewRefreshInterval';
7474
export const DEV_MODE = 'devMode';
7575

76+
export const PULL_REQUEST_AVATAR_DISPLAY = 'pullRequestAvatarDisplay';
77+
export type IssueAvatarDisplay = 'author' | 'assignee' | 'state' | 'generic';
78+
export type PullRequestAvatarDisplay = 'author' | 'state' | 'generic';
79+
7680
// git
7781
export const GIT = 'git';
7882
export const PULL_BEFORE_CHECKOUT = 'pullBeforeCheckout';

src/issues/issuesView.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
88
import { issueBodyHasLink } from './issueLinkLookup';
99
import { IssueItem, QueryGroup, StateManager } from './stateManager';
1010
import { commands, contexts } from '../common/executeCommands';
11-
import { ISSUE_AVATAR_DISPLAY, ISSUES_SETTINGS_NAMESPACE } from '../common/settingKeys';
11+
import { ISSUE_AVATAR_DISPLAY, IssueAvatarDisplay, ISSUES_SETTINGS_NAMESPACE } from '../common/settingKeys';
1212
import { DataUri } from '../common/uri';
1313
import { groupBy } from '../common/utils';
1414
import { FolderRepositoryManager, ReposManagerState } from '../github/folderRepositoryManager';
@@ -91,28 +91,36 @@ export class IssuesTreeData
9191

9292
const avatarDisplaySetting = vscode.workspace
9393
.getConfiguration(ISSUES_SETTINGS_NAMESPACE, null)
94-
.get<'author' | 'assignee'>(ISSUE_AVATAR_DISPLAY, 'author');
95-
96-
let avatarUser: IAccount | undefined;
97-
if ((avatarDisplaySetting === 'assignee') && element.assignees && (element.assignees.length > 0)) {
98-
avatarUser = element.assignees[0];
99-
} else if (avatarDisplaySetting === 'author') {
100-
avatarUser = element.author;
101-
}
94+
.get<IssueAvatarDisplay>(ISSUE_AVATAR_DISPLAY, 'author');
95+
96+
if (avatarDisplaySetting === 'state') {
97+
treeItem.iconPath = element.isOpen
98+
? new vscode.ThemeIcon('issues', new vscode.ThemeColor('issues.open'))
99+
: new vscode.ThemeIcon('issue-closed', new vscode.ThemeColor('issues.closed'));
100+
} else if (avatarDisplaySetting === 'generic') {
101+
treeItem.iconPath = new vscode.ThemeIcon('issues');
102+
} else {
103+
let avatarUser: IAccount | undefined;
104+
if ((avatarDisplaySetting === 'assignee') && element.assignees && (element.assignees.length > 0)) {
105+
avatarUser = element.assignees[0];
106+
} else {
107+
avatarUser = element.author;
108+
}
102109

103-
if (avatarUser) {
104-
// For enterprise, use placeholder icon instead of trying to fetch avatar
105-
if (!DataUri.isGitHubDotComAvatar(avatarUser.avatarUrl)) {
106-
treeItem.iconPath = new vscode.ThemeIcon('github');
110+
if (avatarUser) {
111+
// For enterprise, use placeholder icon instead of trying to fetch avatar
112+
if (!DataUri.isGitHubDotComAvatar(avatarUser.avatarUrl)) {
113+
treeItem.iconPath = new vscode.ThemeIcon('github');
114+
} else {
115+
treeItem.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this.context, [avatarUser], 16, 16))[0] ??
116+
(element.isOpen
117+
? new vscode.ThemeIcon('issues', new vscode.ThemeColor('issues.open'))
118+
: new vscode.ThemeIcon('issue-closed', new vscode.ThemeColor('github.issues.closed')));
119+
}
107120
} else {
108-
treeItem.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this.context, [avatarUser], 16, 16))[0] ??
109-
(element.isOpen
110-
? new vscode.ThemeIcon('issues', new vscode.ThemeColor('issues.open'))
111-
: new vscode.ThemeIcon('issue-closed', new vscode.ThemeColor('github.issues.closed')));
121+
// Use GitHub codicon when assignee setting is selected but no assignees exist
122+
treeItem.iconPath = new vscode.ThemeIcon('github');
112123
}
113-
} else {
114-
// Use GitHub codicon when assignee setting is selected but no assignees exist
115-
treeItem.iconPath = new vscode.ThemeIcon('github');
116124
}
117125

118126
treeItem.command = {

src/view/treeNodes/pullRequestNode.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import { COPILOT_ACCOUNTS } from '../../common/comment';
1010
import { getCommentingRanges } from '../../common/commentingRanges';
1111
import { InMemFileChange, SlimFileChange } from '../../common/file';
1212
import Logger from '../../common/logger';
13-
import { FILE_LIST_LAYOUT, LIST_HORIZONTAL_SCROLLING, PR_SETTINGS_NAMESPACE, SHOW_PULL_REQUEST_NUMBER_IN_TREE, WORKBENCH } from '../../common/settingKeys';
13+
import { FILE_LIST_LAYOUT, LIST_HORIZONTAL_SCROLLING, PR_SETTINGS_NAMESPACE, PULL_REQUEST_AVATAR_DISPLAY, PullRequestAvatarDisplay, SHOW_PULL_REQUEST_NUMBER_IN_TREE, WORKBENCH } from '../../common/settingKeys';
1414
import { createPRNodeUri, DataUri, fromPRUri, Schemes } from '../../common/uri';
1515
import { FolderRepositoryManager } from '../../github/folderRepositoryManager';
1616
import { CopilotWorkingStatus } from '../../github/githubRepository';
17+
import { GithubItemStateEnum } from '../../github/interface';
1718
import { IResolvedPullRequestModel, PullRequestModel } from '../../github/pullRequestModel';
1819
import { InMemFileChangeModel, RemoteFileChangeModel } from '../fileChangeModel';
1920
import { getInMemPRFileSystemProvider, provideDocumentContentForChangeModel } from '../inMemPRContentProvider';
@@ -140,7 +141,7 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
140141

141142
protected registerConfigurationChange() {
142143
this._register(vscode.workspace.onDidChangeConfiguration(e => {
143-
if (e.affectsConfiguration(`${PR_SETTINGS_NAMESPACE}.${SHOW_PULL_REQUEST_NUMBER_IN_TREE}`)) {
144+
if (e.affectsConfiguration(`${PR_SETTINGS_NAMESPACE}.${SHOW_PULL_REQUEST_NUMBER_IN_TREE}`) || e.affectsConfiguration(`${PR_SETTINGS_NAMESPACE}.${PULL_REQUEST_AVATAR_DISPLAY}`)) {
144145
this.refresh();
145146
}
146147
}));
@@ -279,11 +280,33 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
279280
?? new vscode.ThemeIcon('github');
280281
}
281282

283+
private async _getBaseIcon(): Promise<vscode.Uri | vscode.ThemeIcon> {
284+
const { state, isDraft } = this.pullRequestModel;
285+
const iconMode = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<PullRequestAvatarDisplay>(PULL_REQUEST_AVATAR_DISPLAY, 'author');
286+
if (iconMode === 'state') {
287+
return new vscode.ThemeIcon(
288+
state === GithubItemStateEnum.Closed ? 'git-pull-request-closed'
289+
: state === GithubItemStateEnum.Merged ? 'git-merge'
290+
: isDraft ? 'git-pull-request-draft'
291+
: 'git-pull-request',
292+
new vscode.ThemeColor(
293+
state === GithubItemStateEnum.Closed ? 'pullRequests.closed'
294+
: state === GithubItemStateEnum.Merged ? 'pullRequests.merged'
295+
: isDraft ? 'pullRequests.draft'
296+
: 'pullRequests.open'
297+
)
298+
);
299+
} else if (iconMode === 'generic') {
300+
return new vscode.ThemeIcon('github');
301+
}
302+
return this._getAuthorIcon();
303+
}
304+
282305
private async _getIcon(): Promise<vscode.Uri | vscode.ThemeIcon | { light: vscode.Uri; dark: vscode.Uri }> {
283306
const copilotWorkingStatus = await this.pullRequestModel.copilotWorkingStatus();
284307
const theme = this._folderReposManager.themeWatcher.themeData;
285308
if (copilotWorkingStatus === CopilotWorkingStatus.NotCopilotIssue) {
286-
return this._getAuthorIcon();
309+
return this._getBaseIcon();
287310
}
288311
switch (copilotWorkingStatus) {
289312
case CopilotWorkingStatus.InProgress:
@@ -302,7 +325,7 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
302325
dark: DataUri.copilotErrorAsImageDataURI(getIconForeground(theme, 'dark'), getListErrorForeground(theme, 'dark'))
303326
};
304327
default:
305-
return this._getAuthorIcon();
328+
return this._getBaseIcon();
306329
}
307330
}
308331

@@ -331,7 +354,8 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
331354
// Escape any $(...) syntax to avoid rendering PR titles as icons.
332355
label += labelTitle.replace(/\$\([a-zA-Z0-9~-]+\)/g, '\\$&');
333356

334-
if (isDraft) {
357+
const iconMode = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<PullRequestAvatarDisplay>(PULL_REQUEST_AVATAR_DISPLAY, 'author');
358+
if (isDraft && iconMode !== 'state') {
335359
label = `_${label}_`;
336360
}
337361

0 commit comments

Comments
 (0)