Skip to content

Commit f601945

Browse files
committed
Adds open in remote to hover annotations
Optimizes annotation computation (cache by commit)
1 parent f0bdf3e commit f601945

File tree

10 files changed

+134
-18
lines changed

10 files changed

+134
-18
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
66

77
## [5.0.1] - 2017-09-14
88
### Added
9+
- Adds an external link icon to the `details` hover annotation to run the `Open Commit in Remote` command (`gitlens.openCommitInRemote`)
910

1011
### Changed
11-
- Optimizes date handling (parsing and formatting) to increase blame annotation performance and reduce memory consumption
12+
- Optimizes performance of the providing blame annotations, especially for large files (saw a ~61% improvement on some files)
13+
- Optimizes date handling (parsing and formatting) for better performance and reduced memory consumption
1214

1315
### Fixed
1416

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
2525
- Adds a `changes` (diff) hover annotation to the current line annotation, which provides **instant** access to the line's previous version ([optional](#line-blame-annotation-settings), on by default)
2626
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
2727
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
28+
- Clicking on external link icon will run the the `Open Commit in Remote` command (`gitlens.openCommitInRemote`)
2829

2930
![Line Blame Annotations](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-line-blame-annotations.png)
3031

package-lock.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,7 @@
18681868
"dependencies": {
18691869
"applicationinsights": "0.21.0",
18701870
"copy-paste": "1.3.0",
1871+
"datauri": "^1.0.5",
18711872
"iconv-lite": "0.4.19",
18721873
"ignore": "3.3.5",
18731874
"lodash.debounce": "4.0.8",

src/annotations/annotations.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Dates, Strings } from '../system';
22
import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode';
3-
import { DiffWithCommand, ShowQuickCommitDetailsCommand } from '../commands';
3+
import { DiffWithCommand, OpenCommitInRemoteCommand, ShowQuickCommitDetailsCommand } from '../commands';
44
import { IThemeConfig, themeDefaults } from '../configuration';
55
import { GlyphChars } from '../constants';
66
import { CommitFormatter, GitCommit, GitDiffChunkLine, GitService, GitUri, ICommitFormatOptions } from '../gitService';
7+
const Datauri = require('datauri');
78

89
interface IHeatmapConfig {
910
enabled: boolean;
@@ -25,6 +26,25 @@ export const endOfLineIndex = 1000000;
2526
const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g;
2627
// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___';
2728

29+
const linkIconSvg = `<?xml version="1.0" encoding="utf-8"?>
30+
<svg width="12" height="18" version="1.1" xmlns="http://www.w3.org/2000/svg">
31+
<path fill="\${color}" d="m11,14l1,0l0,3c0,0.55 -0.45,1 -1,1l-10,0c-0.55,0 -1,-0.45 -1,-1l0,-10c0,-0.55 0.45,-1 1,-1l3,0l0,1l-3,0l0,10l10,0l0,-3l0,0zm-5,-8l2.25,2.25l-3.25,3.25l1.5,1.5l3.25,-3.25l2.25,2.25l0,-6l-6,0l0,0z" />
32+
</svg>`;
33+
34+
const themeForegroundColor = '#a0a0a0';
35+
let linkIconDataUri: string | undefined;
36+
37+
function getLinkIconDataUri(foregroundColor: string): string {
38+
if (linkIconDataUri === undefined || foregroundColor !== themeForegroundColor) {
39+
const datauri = new Datauri();
40+
datauri.format('.svg', Strings.interpolate(linkIconSvg, { color: foregroundColor }));
41+
linkIconDataUri = datauri.content;
42+
foregroundColor = themeForegroundColor;
43+
}
44+
45+
return linkIconDataUri!;
46+
}
47+
2848
export class Annotations {
2949

3050
static applyHeatmap(decoration: DecorationOptions, date: Date, now: number) {
@@ -47,7 +67,7 @@ export class Annotations {
4767
return '#793738';
4868
}
4969

50-
static getHoverMessage(commit: GitCommit, dateFormat: string | null): MarkdownString {
70+
static getHoverMessage(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): MarkdownString {
5171
if (dateFormat === null) {
5272
dateFormat = 'MMMM Do, YYYY h:MMa';
5373
}
@@ -64,7 +84,11 @@ export class Annotations {
6484
message = `\n\n> ${message}`;
6585
}
6686

67-
const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)}) &nbsp; __${commit.author}__, ${commit.fromNow()} &nbsp; _(${commit.formatDate(dateFormat)})_${message}`);
87+
const openInRemoteCommand = hasRemotes
88+
? `${'&nbsp;'.repeat(3)} [![](${getLinkIconDataUri(themeForegroundColor)})](${OpenCommitInRemoteCommand.getMarkdownCommandArgs(commit.sha)} "Open in Remote")`
89+
: '';
90+
91+
const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details") &nbsp; __${commit.author}__, ${commit.fromNow()} &nbsp; _(${commit.formatDate(dateFormat)})_ ${openInRemoteCommand} &nbsp; ${message}`);
6892
markdown.isTrusted = true;
6993
return markdown;
7094
}
@@ -74,8 +98,8 @@ export class Annotations {
7498

7599
const codeDiff = this._getCodeDiff(chunkLine);
76100
const markdown = new MarkdownString(commit.isUncommitted
77-
? `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)}) &nbsp; ${GlyphChars.Dash} &nbsp; _uncommitted_\n${codeDiff}`
78-
: `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)}) &nbsp; ${GlyphChars.Dash} &nbsp; [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)}) ${GlyphChars.ArrowLeftRight} [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)})\n${codeDiff}`);
101+
? `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${GlyphChars.Dash} &nbsp; _uncommitted_\n${codeDiff}`
102+
: `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${GlyphChars.Dash} &nbsp; [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)} "Show Commit Details") ${GlyphChars.ArrowLeftRight} [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details")\n${codeDiff}`);
79103
markdown.isTrusted = true;
80104
return markdown;
81105
}
@@ -97,8 +121,8 @@ export class Annotations {
97121
} as DecorationOptions;
98122
}
99123

100-
static detailsHover(commit: GitCommit, dateFormat: string | null): DecorationOptions {
101-
const message = this.getHoverMessage(commit, dateFormat);
124+
static detailsHover(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): DecorationOptions {
125+
const message = this.getHoverMessage(commit, dateFormat, hasRemotes);
102126
return {
103127
hoverMessage: message
104128
} as DecorationOptions;
@@ -163,9 +187,9 @@ export class Annotations {
163187
} as IRenderOptions;
164188
}
165189

166-
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null): DecorationOptions {
190+
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null, hasRemotes: boolean): DecorationOptions {
167191
return {
168-
hoverMessage: this.getHoverMessage(commit, dateFormat),
192+
hoverMessage: this.getHoverMessage(commit, dateFormat, hasRemotes),
169193
renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined
170194
} as DecorationOptions;
171195
}

src/annotations/gutterBlameAnnotationProvider.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,17 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
3939
const separateLines = this._config.theme.annotations.file.gutter.separateLines;
4040

4141
const decorations: DecorationOptions[] = [];
42+
const decorationsMap: { [sha: string]: DecorationOptions } = Object.create(null);
4243
const document = this.document;
4344

4445
let commit: GitBlameCommit | undefined;
4546
let compacted = false;
4647
let details: DecorationOptions | undefined;
4748
let gutter: DecorationOptions | undefined;
49+
let hasRemotes: boolean | undefined;
4850
let previousSha: string | undefined;
4951

5052
for (const l of blame.lines) {
51-
commit = blame.commits.get(l.sha);
52-
if (commit === undefined) continue;
53-
5453
const line = l.line + offset;
5554

5655
if (previousSha === l.sha) {
@@ -76,13 +75,15 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
7675

7776
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
7877
gutter.range = new Range(line, 0, line, endIndex);
78+
7979
decorations.push(gutter);
8080

8181
if (details !== undefined) {
8282
details = { ...details } as DecorationOptions;
8383
details.range = cfg.hover.wholeLine
8484
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
8585
: gutter.range;
86+
8687
decorations.push(details);
8788
}
8889

@@ -92,6 +93,31 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
9293
compacted = false;
9394
previousSha = l.sha;
9495

96+
gutter = decorationsMap[l.sha];
97+
98+
if (gutter !== undefined) {
99+
gutter = { ...gutter } as DecorationOptions;
100+
101+
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
102+
gutter.range = new Range(line, 0, line, endIndex);
103+
104+
decorations.push(gutter);
105+
106+
if (details !== undefined) {
107+
details = { ...details } as DecorationOptions;
108+
details.range = cfg.hover.wholeLine
109+
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
110+
: gutter.range;
111+
112+
decorations.push(details);
113+
}
114+
115+
continue;
116+
}
117+
118+
commit = blame.commits.get(l.sha);
119+
if (commit === undefined) continue;
120+
95121
gutter = Annotations.gutter(commit, cfg.format, options, renderOptions);
96122

97123
if (cfg.heatmap.enabled) {
@@ -100,13 +126,20 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
100126

101127
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
102128
gutter.range = new Range(line, 0, line, endIndex);
129+
103130
decorations.push(gutter);
131+
decorationsMap[l.sha] = gutter;
104132

105133
if (cfg.hover.details) {
106-
details = Annotations.detailsHover(commit, dateFormat);
134+
if (hasRemotes === undefined) {
135+
hasRemotes = this.git.hasRemotes(commit.repoPath);
136+
}
137+
138+
details = Annotations.detailsHover(commit, dateFormat, hasRemotes);
107139
details.range = cfg.hover.wholeLine
108140
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
109141
: gutter.range;
142+
110143
decorations.push(details);
111144
}
112145
}

src/annotations/hoverBlameAnnotationProvider.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,42 @@ export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
2424
const dateFormat = this._config.defaultDateFormat;
2525

2626
const decorations: DecorationOptions[] = [];
27+
const decorationsMap: { [sha: string]: DecorationOptions } = Object.create(null);
2728
const document = this.document;
2829

2930
let commit: GitBlameCommit | undefined;
31+
let hasRemotes: boolean | undefined;
3032
let hover: DecorationOptions | undefined;
3133

3234
for (const l of blame.lines) {
35+
const line = l.line + offset;
36+
37+
hover = decorationsMap[l.sha];
38+
39+
if (hover !== undefined) {
40+
hover = { ...hover } as DecorationOptions;
41+
42+
if (cfg.wholeLine) {
43+
hover.range = document.validateRange(new Range(line, 0, line, endOfLineIndex));
44+
}
45+
else {
46+
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
47+
hover.range = new Range(line, 0, line, endIndex);
48+
}
49+
50+
decorations.push(hover);
51+
52+
continue;
53+
}
54+
3355
commit = blame.commits.get(l.sha);
3456
if (commit === undefined) continue;
3557

36-
const line = l.line + offset;
58+
if (hasRemotes === undefined) {
59+
hasRemotes = this.git.hasRemotes(commit.repoPath);
60+
}
3761

38-
hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled, dateFormat);
62+
hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled, dateFormat, hasRemotes);
3963

4064
if (cfg.wholeLine) {
4165
hover.range = document.validateRange(new Range(line, 0, line, endOfLineIndex));
@@ -50,6 +74,8 @@ export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
5074
}
5175

5276
decorations.push(hover);
77+
decorationsMap[l.sha] = hover;
78+
5379
}
5480

5581
if (decorations.length) {

src/annotations/recentChangesAnnotationProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
4646

4747
if (cfg.hover.details) {
4848
decorators.push({
49-
hoverMessage: Annotations.getHoverMessage(commit, dateFormat),
49+
hoverMessage: Annotations.getHoverMessage(commit, dateFormat, this.git.hasRemotes(commit.repoPath)),
5050
range: range
5151
} as DecorationOptions);
5252
}

src/commands/openCommitInRemote.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ export interface OpenCommitInRemoteCommandArgs {
1212

1313
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
1414

15+
static getMarkdownCommandArgs(sha: string): string;
16+
static getMarkdownCommandArgs(args: OpenCommitInRemoteCommandArgs): string;
17+
static getMarkdownCommandArgs(argsOrSha: OpenCommitInRemoteCommandArgs | string): string {
18+
const args = typeof argsOrSha === 'string'
19+
? { sha: argsOrSha }
20+
: argsOrSha;
21+
return super.getMarkdownCommandArgsCore<OpenCommitInRemoteCommandArgs>(Commands.OpenCommitInRemote, args);
22+
}
23+
1524
constructor(private git: GitService) {
1625
super(Commands.OpenCommitInRemote);
1726
}

src/currentLineController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ export class CurrentLineController extends Disposable {
428428
// I have no idea why I need this protection -- but it happens
429429
if (editor.document === undefined) return;
430430

431-
const decoration = Annotations.detailsHover(logCommit || commit, this._config.defaultDateFormat);
431+
const decoration = Annotations.detailsHover(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes((logCommit || commit).repoPath));
432432
decoration.range = editor.document.validateRange(new Range(line, showDetailsStartIndex, line, endOfLineIndex));
433433
decorationOptions.push(decoration);
434434

0 commit comments

Comments
 (0)