diff --git a/package.json b/package.json index 72b6914584..a2f5efda05 100644 --- a/package.json +++ b/package.json @@ -797,11 +797,15 @@ "type": "string", "enum": [ "author", - "assignee" + "assignee", + "state", + "generic" ], "enumDescriptions": [ "%githubIssues.issueAvatarDisplay.author%", - "%githubIssues.issueAvatarDisplay.assignee%" + "%githubIssues.issueAvatarDisplay.assignee%", + "%githubIssues.issueAvatarDisplay.state%", + "%githubIssues.issueAvatarDisplay.generic%" ], "default": "author", "description": "%githubIssues.issueAvatarDisplay.description%" @@ -837,6 +841,21 @@ "default": false, "description": "%githubPullRequests.showPullRequestNumberInTree.description%" }, + "githubPullRequests.pullRequestAvatarDisplay": { + "type": "string", + "enum": [ + "author", + "state", + "generic" + ], + "enumDescriptions": [ + "%githubPullRequests.pullRequestAvatarDisplay.author%", + "%githubPullRequests.pullRequestAvatarDisplay.state%", + "%githubPullRequests.pullRequestAvatarDisplay.generic%" + ], + "default": "author", + "description": "%githubPullRequests.pullRequestAvatarDisplay.description%" + }, "githubIssues.alwaysPromptForNewIssueRepo": { "type": "boolean", "default": false, diff --git a/package.nls.json b/package.nls.json index 0808791b64..823a526a36 100644 --- a/package.nls.json +++ b/package.nls.json @@ -182,6 +182,12 @@ "githubPullRequests.showPullRequestNumberInTree.description": "Shows the pull request number in the tree view.", "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.", "githubPullRequests.labelCreated.label.description": "Each string element is the value of label that you want to add.", + "githubPullRequests.pullRequestAvatarDisplay.description": "Which icon to use in the pull request tree view", + "githubPullRequests.pullRequestAvatarDisplay.author": "Show the pull request author avatar", + "githubPullRequests.pullRequestAvatarDisplay.state": "Show the pull request type (draft or not) and state (open/closed/merged) as a colored icon", + "githubPullRequests.pullRequestAvatarDisplay.generic": "Show a GitHub icon", + "githubIssues.issueAvatarDisplay.state": "Show the issue state (open/closed) as a colored icon", + "githubIssues.issueAvatarDisplay.generic": "Show an issues icon regardless of state", "githubIssues.alwaysPromptForNewIssueRepo.description": "Enabling will always prompt which repository to create an issue in instead of basing off the current open file.", "view.github.pull.requests.name": "GitHub", "view.github.pull.request.name": "GitHub Pull Request", diff --git a/src/common/settingKeys.ts b/src/common/settingKeys.ts index 0b35cd539f..96ad623b0b 100644 --- a/src/common/settingKeys.ts +++ b/src/common/settingKeys.ts @@ -72,6 +72,10 @@ export const EXPERIMENTAL_NOTIFICATIONS_SCORE = 'experimental.notificationsScore export const WEBVIEW_REFRESH_INTERVAL = 'webviewRefreshInterval'; export const DEV_MODE = 'devMode'; +export const PULL_REQUEST_AVATAR_DISPLAY = 'pullRequestAvatarDisplay'; +export type IssueAvatarDisplay = 'author' | 'assignee' | 'state' | 'generic'; +export type PullRequestAvatarDisplay = 'author' | 'state' | 'generic'; + // git export const GIT = 'git'; export const PULL_BEFORE_CHECKOUT = 'pullBeforeCheckout'; diff --git a/src/issues/issuesView.ts b/src/issues/issuesView.ts index 3774b9a530..f43a04f15a 100644 --- a/src/issues/issuesView.ts +++ b/src/issues/issuesView.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode'; import { issueBodyHasLink } from './issueLinkLookup'; import { IssueItem, QueryGroup, StateManager } from './stateManager'; import { commands, contexts } from '../common/executeCommands'; -import { ISSUE_AVATAR_DISPLAY, ISSUES_SETTINGS_NAMESPACE } from '../common/settingKeys'; +import { ISSUE_AVATAR_DISPLAY, IssueAvatarDisplay, ISSUES_SETTINGS_NAMESPACE } from '../common/settingKeys'; import { DataUri } from '../common/uri'; import { groupBy } from '../common/utils'; import { FolderRepositoryManager, ReposManagerState } from '../github/folderRepositoryManager'; @@ -91,28 +91,36 @@ export class IssuesTreeData const avatarDisplaySetting = vscode.workspace .getConfiguration(ISSUES_SETTINGS_NAMESPACE, null) - .get<'author' | 'assignee'>(ISSUE_AVATAR_DISPLAY, 'author'); - - let avatarUser: IAccount | undefined; - if ((avatarDisplaySetting === 'assignee') && element.assignees && (element.assignees.length > 0)) { - avatarUser = element.assignees[0]; - } else if (avatarDisplaySetting === 'author') { - avatarUser = element.author; - } + .get(ISSUE_AVATAR_DISPLAY, 'author'); + + if (avatarDisplaySetting === 'state') { + treeItem.iconPath = element.isOpen + ? new vscode.ThemeIcon('issues', new vscode.ThemeColor('issues.open')) + : new vscode.ThemeIcon('issue-closed', new vscode.ThemeColor('issues.closed')); + } else if (avatarDisplaySetting === 'generic') { + treeItem.iconPath = new vscode.ThemeIcon('issues'); + } else { + let avatarUser: IAccount | undefined; + if ((avatarDisplaySetting === 'assignee') && element.assignees && (element.assignees.length > 0)) { + avatarUser = element.assignees[0]; + } else { + avatarUser = element.author; + } - if (avatarUser) { - // For enterprise, use placeholder icon instead of trying to fetch avatar - if (!DataUri.isGitHubDotComAvatar(avatarUser.avatarUrl)) { - treeItem.iconPath = new vscode.ThemeIcon('github'); + if (avatarUser) { + // For enterprise, use placeholder icon instead of trying to fetch avatar + if (!DataUri.isGitHubDotComAvatar(avatarUser.avatarUrl)) { + treeItem.iconPath = new vscode.ThemeIcon('github'); + } else { + treeItem.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this.context, [avatarUser], 16, 16))[0] ?? + (element.isOpen + ? new vscode.ThemeIcon('issues', new vscode.ThemeColor('issues.open')) + : new vscode.ThemeIcon('issue-closed', new vscode.ThemeColor('github.issues.closed'))); + } } else { - treeItem.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this.context, [avatarUser], 16, 16))[0] ?? - (element.isOpen - ? new vscode.ThemeIcon('issues', new vscode.ThemeColor('issues.open')) - : new vscode.ThemeIcon('issue-closed', new vscode.ThemeColor('github.issues.closed'))); + // Use GitHub codicon when assignee setting is selected but no assignees exist + treeItem.iconPath = new vscode.ThemeIcon('github'); } - } else { - // Use GitHub codicon when assignee setting is selected but no assignees exist - treeItem.iconPath = new vscode.ThemeIcon('github'); } treeItem.command = { diff --git a/src/view/treeNodes/pullRequestNode.ts b/src/view/treeNodes/pullRequestNode.ts index 23a935ca4d..7bedc0cc03 100644 --- a/src/view/treeNodes/pullRequestNode.ts +++ b/src/view/treeNodes/pullRequestNode.ts @@ -10,10 +10,11 @@ import { COPILOT_ACCOUNTS } from '../../common/comment'; import { getCommentingRanges } from '../../common/commentingRanges'; import { InMemFileChange, SlimFileChange } from '../../common/file'; import Logger from '../../common/logger'; -import { FILE_LIST_LAYOUT, LIST_HORIZONTAL_SCROLLING, PR_SETTINGS_NAMESPACE, SHOW_PULL_REQUEST_NUMBER_IN_TREE, WORKBENCH } from '../../common/settingKeys'; +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'; import { createPRNodeUri, DataUri, fromPRUri, Schemes } from '../../common/uri'; import { FolderRepositoryManager } from '../../github/folderRepositoryManager'; import { CopilotWorkingStatus } from '../../github/githubRepository'; +import { GithubItemStateEnum } from '../../github/interface'; import { IResolvedPullRequestModel, PullRequestModel } from '../../github/pullRequestModel'; import { InMemFileChangeModel, RemoteFileChangeModel } from '../fileChangeModel'; import { getInMemPRFileSystemProvider, provideDocumentContentForChangeModel } from '../inMemPRContentProvider'; @@ -140,7 +141,7 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2 protected registerConfigurationChange() { this._register(vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(`${PR_SETTINGS_NAMESPACE}.${SHOW_PULL_REQUEST_NUMBER_IN_TREE}`)) { + if (e.affectsConfiguration(`${PR_SETTINGS_NAMESPACE}.${SHOW_PULL_REQUEST_NUMBER_IN_TREE}`) || e.affectsConfiguration(`${PR_SETTINGS_NAMESPACE}.${PULL_REQUEST_AVATAR_DISPLAY}`)) { this.refresh(); } })); @@ -279,11 +280,33 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2 ?? new vscode.ThemeIcon('github'); } + private async _getBaseIcon(): Promise { + const { state, isDraft } = this.pullRequestModel; + const iconMode = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get(PULL_REQUEST_AVATAR_DISPLAY, 'author'); + if (iconMode === 'state') { + return new vscode.ThemeIcon( + state === GithubItemStateEnum.Closed ? 'git-pull-request-closed' + : state === GithubItemStateEnum.Merged ? 'git-merge' + : isDraft ? 'git-pull-request-draft' + : 'git-pull-request', + new vscode.ThemeColor( + state === GithubItemStateEnum.Closed ? 'pullRequests.closed' + : state === GithubItemStateEnum.Merged ? 'pullRequests.merged' + : isDraft ? 'pullRequests.draft' + : 'pullRequests.open' + ) + ); + } else if (iconMode === 'generic') { + return new vscode.ThemeIcon('github'); + } + return this._getAuthorIcon(); + } + private async _getIcon(): Promise { const copilotWorkingStatus = await this.pullRequestModel.copilotWorkingStatus(); const theme = this._folderReposManager.themeWatcher.themeData; if (copilotWorkingStatus === CopilotWorkingStatus.NotCopilotIssue) { - return this._getAuthorIcon(); + return this._getBaseIcon(); } switch (copilotWorkingStatus) { case CopilotWorkingStatus.InProgress: @@ -302,7 +325,7 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2 dark: DataUri.copilotErrorAsImageDataURI(getIconForeground(theme, 'dark'), getListErrorForeground(theme, 'dark')) }; default: - return this._getAuthorIcon(); + return this._getBaseIcon(); } } @@ -331,7 +354,8 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2 // Escape any $(...) syntax to avoid rendering PR titles as icons. label += labelTitle.replace(/\$\([a-zA-Z0-9~-]+\)/g, '\\$&'); - if (isDraft) { + const iconMode = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get(PULL_REQUEST_AVATAR_DISPLAY, 'author'); + if (isDraft && iconMode !== 'state') { label = `_${label}_`; }