Skip to content

Commit

Permalink
Adds autolinked issues to comparisons
Browse files Browse the repository at this point in the history
  • Loading branch information
eamodio committed Dec 16, 2021
1 parent a65b894 commit 7f62292
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 28 deletions.
54 changes: 53 additions & 1 deletion package.json
Expand Up @@ -164,6 +164,8 @@
"onCommand:gitlens.openBlamePriorToChange",
"onCommand:gitlens.openFileRevision",
"onCommand:gitlens.openFileRevisionFrom",
"onCommand:gitlens.openIssueOnRemote",
"onCommand:gitlens.copyRemoteIssueUrl",
"onCommand:gitlens.openPullRequestOnRemote",
"onCommand:gitlens.copyRemotePullRequestUrl",
"onCommand:gitlens.openAssociatedPullRequestOnRemote",
Expand Down Expand Up @@ -3397,6 +3399,24 @@
"dark": "#c74e39",
"highContrast": "#c74e39"
}
},
{
"id": "gitlens.autolinkedIssueOpenIconColor",
"defaults": {
"dark": "#22863a",
"light": "#22863a",
"highContrast": "editor.foreground"
},
"description": "Specifies the icon color indicating that an issue is open"
},
{
"id": "gitlens.autolinkedIssueClosedIconColor",
"defaults": {
"dark": "#cb2431",
"light": "#cb2431",
"highContrast": "editor.foreground"
},
"description": "Specifies the icon color indicating that an issue is closed"
}
],
"commands": [
Expand Down Expand Up @@ -4116,6 +4136,18 @@
},
"category": "GitLens"
},
{
"command": "gitlens.openIssueOnRemote",
"title": "Open Issue on Remote",
"category": "GitLens",
"icon": "$(globe)"
},
{
"command": "gitlens.copyRemoteIssueUrl",
"title": "Copy Issue Url",
"category": "GitLens",
"icon": "$(copy)"
},
{
"command": "gitlens.openPullRequestOnRemote",
"title": "Open Pull Request on Remote",
Expand Down Expand Up @@ -5949,6 +5981,14 @@
"command": "gitlens.copyRemoteComparisonUrl",
"when": "false"
},
{
"command": "gitlens.openIssueOnRemote",
"when": "false"
},
{
"command": "gitlens.copyRemoteIssueUrl",
"when": "false"
},
{
"command": "gitlens.openPullRequestOnRemote",
"when": "false"
Expand Down Expand Up @@ -8469,6 +8509,18 @@
"group": "5_gitlens_open@2",
"alt": "gitlens.copyRemoteFileUrlWithoutRange"
},
{
"command": "gitlens.openIssueOnRemote",
"when": "viewItem =~ /gitlens:autolinked:issue\\b/",
"group": "inline@99",
"alt": "gitlens.copyRemoteIssueUrl"
},
{
"command": "gitlens.openIssueOnRemote",
"when": "viewItem =~ /gitlens:autolinked:issue\\b/",
"group": "1_gitlens_actions@99",
"alt": "gitlens.copyRemoteIssueUrl"
},
{
"command": "gitlens.views.openPullRequest",
"when": "gitlens:action:openPullRequest > 1 && viewItem =~ /gitlens:pullrequest\\b/",
Expand Down Expand Up @@ -9070,7 +9122,7 @@
},
{
"command": "gitlens.views.copy",
"when": "viewItem =~ /gitlens:(?=(branch|commit|contributor|folder|history:line|pullrequest|remote|repository|repo-folder|stash|tag)\\b)/",
"when": "viewItem =~ /gitlens:(?=(autolinked:issue|branch|commit|contributor|folder|history:line|pullrequest|remote|repository|repo-folder|stash|tag)\\b)/",
"group": "7_gitlens_cutcopypaste@1"
},
{
Expand Down
6 changes: 3 additions & 3 deletions src/annotations/autolinks.ts
Expand Up @@ -191,9 +191,9 @@ export class Autolinks implements Disposable {
index = footnotes.size + 1;
footnotes.set(
index,
`[**${
issue.type === 'PullRequest' ? '$(git-pull-request)' : '$(info)'
} ${issueTitle}**](${issueUrl}${title}")\\\n${GlyphChars.Space.repeat(
`${IssueOrPullRequest.getMarkdownIcon(
issue,
)} [**${issueTitle}**](${issueUrl}${title}")\\\n${GlyphChars.Space.repeat(
5,
)}${linkText} ${issue.closed ? 'closed' : 'opened'} ${Dates.getFormatter(
issue.closedDate ?? issue.date,
Expand Down
1 change: 1 addition & 0 deletions src/commands.ts
Expand Up @@ -39,6 +39,7 @@ export * from './commands/openFileOnRemote';
export * from './commands/openFileAtRevision';
export * from './commands/openFileAtRevisionFrom';
export * from './commands/openOnRemote';
export * from './commands/openIssueOnRemote';
export * from './commands/openPullRequestOnRemote';
export * from './commands/openRepoOnRemote';
export * from './commands/openRevisionFile';
Expand Down
2 changes: 2 additions & 0 deletions src/commands/common.ts
Expand Up @@ -49,6 +49,7 @@ export const enum Commands {
CopyRemoteFileUrl = 'gitlens.copyRemoteFileUrlToClipboard',
CopyRemoteFileUrlWithoutRange = 'gitlens.copyRemoteFileUrlWithoutRange',
CopyRemoteFileUrlFrom = 'gitlens.copyRemoteFileUrlFrom',
CopyRemoteIssueUrl = 'gitlens.copyRemoteIssueUrl',
CopyRemotePullRequestUrl = 'gitlens.copyRemotePullRequestUrl',
CopyRemoteRepositoryUrl = 'gitlens.copyRemoteRepositoryUrl',
CopyShaToClipboard = 'gitlens.copyShaToClipboard',
Expand Down Expand Up @@ -92,6 +93,7 @@ export const enum Commands {
OpenFileAtRevisionFrom = 'gitlens.openFileRevisionFrom',
OpenFolderHistory = 'gitlens.openFolderHistory',
OpenOnRemote = 'gitlens.openOnRemote',
OpenIssueOnRemote = 'gitlens.openIssueOnRemote',
OpenPullRequestOnRemote = 'gitlens.openPullRequestOnRemote',
OpenAssociatedPullRequestOnRemote = 'gitlens.openAssociatedPullRequestOnRemote',
OpenRepoOnRemote = 'gitlens.openRepoOnRemote',
Expand Down
36 changes: 36 additions & 0 deletions src/commands/openIssueOnRemote.ts
@@ -0,0 +1,36 @@
'use strict';
import { env, Uri } from 'vscode';
import { AutolinkedItemNode } from '../views/nodes/autolinkedItemNode';
import { Command, command, CommandContext, Commands } from './common';

export interface OpenIssueOnRemoteCommandArgs {
clipboard?: boolean;
issue: { url: string };
}

@command()
export class OpenIssueOnRemoteCommand extends Command {
constructor() {
super([Commands.OpenIssueOnRemote, Commands.CopyRemoteIssueUrl]);
}

protected override preExecute(context: CommandContext, args: OpenIssueOnRemoteCommandArgs) {
if (context.type === 'viewItem' && context.node instanceof AutolinkedItemNode) {
args = {
...args,
issue: { url: context.node.issue.url },
clipboard: context.command === Commands.CopyRemotePullRequestUrl,
};
}

return this.execute(args);
}

async execute(args: OpenIssueOnRemoteCommandArgs) {
if (args.clipboard) {
void (await env.clipboard.writeText(args.issue.url));
} else {
void env.openExternal(Uri.parse(args.issue.url));
}
}
}
50 changes: 48 additions & 2 deletions src/git/models/issue.ts
@@ -1,12 +1,58 @@
'use strict';
import { ColorThemeKind, ThemeColor, ThemeIcon, window } from 'vscode';
import { Colors } from '../../constants';
import { RemoteProviderReference } from './remoteProvider';

export const enum IssueOrPullRequestType {
Issue = 'Issue',
PullRequest = 'PullRequest',
}

export interface IssueOrPullRequest {
type: 'Issue' | 'PullRequest';
type: IssueOrPullRequestType;
provider: RemoteProviderReference;
id: number;
id: string;
date: Date;
title: string;
closed: boolean;
closedDate?: Date;
url: string;
}

export namespace IssueOrPullRequest {
export function getMarkdownIcon(issue: IssueOrPullRequest): string {
if (issue.type === IssueOrPullRequestType.PullRequest) {
if (issue.closed) {
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#a371f7' : '#8250df'
};">$(git-pull-request)</span>`;
}
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#3fb950' : '#1a7f37'
};">$(git-pull-request)</span>`;
}

if (issue.closed) {
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#a371f7' : '#8250df'
};">$(pass)</span>`;
}
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#3fb950' : '#1a7f37'
};">$(issue)</span>`;
}

export function getThemeIcon(issue: IssueOrPullRequest): ThemeIcon {
if (issue.type === IssueOrPullRequestType.PullRequest) {
if (issue.closed) {
return new ThemeIcon('git-pull-request', new ThemeColor(Colors.MergedPullRequestIconColor));
}
return new ThemeIcon('git-pull-request', new ThemeColor(Colors.OpenPullRequestIconColor));
}

if (issue.closed) {
return new ThemeIcon('pass', new ThemeColor(Colors.MergedPullRequestIconColor));
}
return new ThemeIcon('issues', new ThemeColor(Colors.OpenPullRequestIconColor));
}
}
49 changes: 27 additions & 22 deletions src/github/github.ts
Expand Up @@ -5,6 +5,7 @@ import {
ClientError,
DefaultBranch,
IssueOrPullRequest,
IssueOrPullRequestType,
PullRequest,
PullRequestState,
RichRemoteProvider,
Expand Down Expand Up @@ -244,28 +245,30 @@ export class GitHubApi {

try {
const query = `query getIssueOrPullRequest(
$owner: String!
$repo: String!
$number: Int!
) {
repository(name: $repo, owner: $owner) {
issueOrPullRequest(number: $number) {
__typename
... on Issue {
createdAt
closed
closedAt
title
}
... on PullRequest {
createdAt
closed
closedAt
title
$owner: String!
$repo: String!
$number: Int!
) {
repository(name: $repo, owner: $owner) {
issueOrPullRequest(number: $number) {
__typename
... on Issue {
createdAt
closed
closedAt
title
url
}
... on PullRequest {
createdAt
closed
closedAt
title
url
}
}
}
}
}`;
}`;

const rsp = await graphql<QueryResult>(query, {
...options,
Expand All @@ -281,11 +284,12 @@ export class GitHubApi {
return {
provider: provider,
type: issue.type,
id: number,
id: String(number),
date: new Date(issue.createdAt),
title: issue.title,
closed: issue.closed,
closedDate: issue.closedAt == null ? undefined : new Date(issue.closedAt),
url: issue.url,
};
} catch (ex) {
Logger.error(ex, cc);
Expand Down Expand Up @@ -505,12 +509,13 @@ export class GitHubApi {
}

interface GitHubIssueOrPullRequest {
type: 'Issue' | 'PullRequest';
type: IssueOrPullRequestType;
number: number;
createdAt: string;
closed: boolean;
closedAt: string | null;
title: string;
url: string;
}

type GitHubPullRequestState = 'OPEN' | 'CLOSED' | 'MERGED';
Expand Down
69 changes: 69 additions & 0 deletions src/views/nodes/autolinkedItemNode.ts
@@ -0,0 +1,69 @@
'use strict';
import { MarkdownString, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { GitFile, IssueOrPullRequest, IssueOrPullRequestType } from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { Dates } from '../../system';
import { ViewsWithCommits } from '../viewBase';
import { ContextValues, ViewNode } from './viewNode';

export interface FilesQueryResults {
label: string;
files: GitFile[] | undefined;
filtered?: {
filter: 'left' | 'right';
files: GitFile[];
};
}

export class AutolinkedItemNode extends ViewNode<ViewsWithCommits> {
constructor(
view: ViewsWithCommits,
parent: ViewNode,
public readonly repoPath: string,
public readonly issue: IssueOrPullRequest,
) {
super(GitUri.fromRepoPath(repoPath), view, parent);
}

override toClipboard(): string {
return this.issue.url;
}

override get id(): string {
return `${this.parent!.id!}:item(${this.issue.id})`;
}

getChildren(): ViewNode[] {
return [];
}

getTreeItem(): TreeItem {
const formatter = Dates.getFormatter(this.issue.closedDate ?? this.issue.date);

const item = new TreeItem(`${this.issue.id}: ${this.issue.title}`, TreeItemCollapsibleState.None);
item.description = formatter.fromNow();
item.iconPath = IssueOrPullRequest.getThemeIcon(this.issue);
item.contextValue =
this.issue.type === IssueOrPullRequestType.PullRequest
? ContextValues.PullRequest
: ContextValues.AutolinkedIssue;

const linkTitle = ` "Open ${
this.issue.type === IssueOrPullRequestType.PullRequest ? 'Pull Request' : 'Issue'
} \\#${this.issue.id} on ${this.issue.provider.name}"`;
const tooltip = new MarkdownString(
`${IssueOrPullRequest.getMarkdownIcon(this.issue)} [**${this.issue.title}**](${
this.issue.url
}${linkTitle}) \\\n[#${this.issue.id}](${this.issue.url}${linkTitle}) was ${
this.issue.closed ? 'closed' : 'opened'
} ${formatter.fromNow()}`,
true,
);
tooltip.supportHtml = true;
tooltip.isTrusted = true;

item.tooltip = tooltip;

return item;
}
}

0 comments on commit 7f62292

Please sign in to comment.