diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f974de..ae4df5e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,9 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [5.0.1] - 2017-09-14
### Added
+- Adds an external link icon to the `details` hover annotation to run the `Open Commit in Remote` command (`gitlens.openCommitInRemote`)
### Changed
-- Optimizes date handling (parsing and formatting) to increase blame annotation performance and reduce memory consumption
+- Optimizes performance of the providing blame annotations, especially for large files (saw a ~61% improvement on some files)
+- Optimizes date handling (parsing and formatting) for better performance and reduced memory consumption
### Fixed
diff --git a/README.md b/README.md
index ac9b5e3..d727828 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- 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)
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
+ - Clicking on external link icon will run the the `Open Commit in Remote` command (`gitlens.openCommitInRemote`)
![Line Blame Annotations](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-line-blame-annotations.png)
diff --git a/package-lock.json b/package-lock.json
index 3bfdc91..6770591 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -384,6 +384,16 @@
}
}
},
+ "datauri": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/datauri/-/datauri-1.0.5.tgz",
+ "integrity": "sha1-0JddGrbI8uDOPKQ7qkU5vhLSiaA=",
+ "requires": {
+ "image-size": "0.3.5",
+ "mimer": "0.2.1",
+ "semver": "5.4.1"
+ }
+ },
"dateformat": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.0.0.tgz",
@@ -1281,6 +1291,11 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.5.tgz",
"integrity": "sha512-JLH93mL8amZQhh/p6mfQgVBH3M6epNq3DfsXsTSuSrInVjwyYlFE1nv2AgfRCC8PoOhM0jwQ5v8s9LgbK7yGDw=="
},
+ "image-size": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.3.5.tgz",
+ "integrity": "sha1-gyQOqy+1sAsEqrjHSwRx6cunrYw="
+ },
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -1769,6 +1784,11 @@
"mime-db": "1.30.0"
}
},
+ "mimer": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/mimer/-/mimer-0.2.1.tgz",
+ "integrity": "sha1-xjxaF/6GQj9RYahdVcPtUYm6r/w="
+ },
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
diff --git a/package.json b/package.json
index f5b7150..2fc0d7a 100644
--- a/package.json
+++ b/package.json
@@ -1868,6 +1868,7 @@
"dependencies": {
"applicationinsights": "0.21.0",
"copy-paste": "1.3.0",
+ "datauri": "^1.0.5",
"iconv-lite": "0.4.19",
"ignore": "3.3.5",
"lodash.debounce": "4.0.8",
diff --git a/src/annotations/annotations.ts b/src/annotations/annotations.ts
index 116cfc8..68a59ac 100644
--- a/src/annotations/annotations.ts
+++ b/src/annotations/annotations.ts
@@ -1,9 +1,10 @@
import { Dates, Strings } from '../system';
import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode';
-import { DiffWithCommand, ShowQuickCommitDetailsCommand } from '../commands';
+import { DiffWithCommand, OpenCommitInRemoteCommand, ShowQuickCommitDetailsCommand } from '../commands';
import { IThemeConfig, themeDefaults } from '../configuration';
import { GlyphChars } from '../constants';
import { CommitFormatter, GitCommit, GitDiffChunkLine, GitService, GitUri, ICommitFormatOptions } from '../gitService';
+const Datauri = require('datauri');
interface IHeatmapConfig {
enabled: boolean;
@@ -25,6 +26,25 @@ export const endOfLineIndex = 1000000;
const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g;
// 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___';
+const linkIconSvg = `
+`;
+
+const themeForegroundColor = '#a0a0a0';
+let linkIconDataUri: string | undefined;
+
+function getLinkIconDataUri(foregroundColor: string): string {
+ if (linkIconDataUri === undefined || foregroundColor !== themeForegroundColor) {
+ const datauri = new Datauri();
+ datauri.format('.svg', Strings.interpolate(linkIconSvg, { color: foregroundColor }));
+ linkIconDataUri = datauri.content;
+ foregroundColor = themeForegroundColor;
+ }
+
+ return linkIconDataUri!;
+}
+
export class Annotations {
static applyHeatmap(decoration: DecorationOptions, date: Date, now: number) {
@@ -47,7 +67,7 @@ export class Annotations {
return '#793738';
}
- static getHoverMessage(commit: GitCommit, dateFormat: string | null): MarkdownString {
+ static getHoverMessage(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): MarkdownString {
if (dateFormat === null) {
dateFormat = 'MMMM Do, YYYY h:MMa';
}
@@ -64,7 +84,11 @@ export class Annotations {
message = `\n\n> ${message}`;
}
- const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)}) __${commit.author}__, ${commit.fromNow()} _(${commit.formatDate(dateFormat)})_${message}`);
+ const openInRemoteCommand = hasRemotes
+ ? `${' '.repeat(3)} [![](${getLinkIconDataUri(themeForegroundColor)})](${OpenCommitInRemoteCommand.getMarkdownCommandArgs(commit.sha)} "Open in Remote")`
+ : '';
+
+ const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details") __${commit.author}__, ${commit.fromNow()} _(${commit.formatDate(dateFormat)})_ ${openInRemoteCommand} ${message}`);
markdown.isTrusted = true;
return markdown;
}
@@ -74,8 +98,8 @@ export class Annotations {
const codeDiff = this._getCodeDiff(chunkLine);
const markdown = new MarkdownString(commit.isUncommitted
- ? `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)}) ${GlyphChars.Dash} _uncommitted_\n${codeDiff}`
- : `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)}) ${GlyphChars.Dash} [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)}) ${GlyphChars.ArrowLeftRight} [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)})\n${codeDiff}`);
+ ? `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") ${GlyphChars.Dash} _uncommitted_\n${codeDiff}`
+ : `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") ${GlyphChars.Dash} [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)} "Show Commit Details") ${GlyphChars.ArrowLeftRight} [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details")\n${codeDiff}`);
markdown.isTrusted = true;
return markdown;
}
@@ -97,8 +121,8 @@ export class Annotations {
} as DecorationOptions;
}
- static detailsHover(commit: GitCommit, dateFormat: string | null): DecorationOptions {
- const message = this.getHoverMessage(commit, dateFormat);
+ static detailsHover(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): DecorationOptions {
+ const message = this.getHoverMessage(commit, dateFormat, hasRemotes);
return {
hoverMessage: message
} as DecorationOptions;
@@ -163,9 +187,9 @@ export class Annotations {
} as IRenderOptions;
}
- static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null): DecorationOptions {
+ static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null, hasRemotes: boolean): DecorationOptions {
return {
- hoverMessage: this.getHoverMessage(commit, dateFormat),
+ hoverMessage: this.getHoverMessage(commit, dateFormat, hasRemotes),
renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined
} as DecorationOptions;
}
diff --git a/src/annotations/gutterBlameAnnotationProvider.ts b/src/annotations/gutterBlameAnnotationProvider.ts
index f177de4..f0b9e7a 100644
--- a/src/annotations/gutterBlameAnnotationProvider.ts
+++ b/src/annotations/gutterBlameAnnotationProvider.ts
@@ -39,18 +39,17 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const separateLines = this._config.theme.annotations.file.gutter.separateLines;
const decorations: DecorationOptions[] = [];
+ const decorationsMap: { [sha: string]: DecorationOptions } = Object.create(null);
const document = this.document;
let commit: GitBlameCommit | undefined;
let compacted = false;
let details: DecorationOptions | undefined;
let gutter: DecorationOptions | undefined;
+ let hasRemotes: boolean | undefined;
let previousSha: string | undefined;
for (const l of blame.lines) {
- commit = blame.commits.get(l.sha);
- if (commit === undefined) continue;
-
const line = l.line + offset;
if (previousSha === l.sha) {
@@ -76,6 +75,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
gutter.range = new Range(line, 0, line, endIndex);
+
decorations.push(gutter);
if (details !== undefined) {
@@ -83,6 +83,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
details.range = cfg.hover.wholeLine
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
: gutter.range;
+
decorations.push(details);
}
@@ -92,6 +93,31 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
compacted = false;
previousSha = l.sha;
+ gutter = decorationsMap[l.sha];
+
+ if (gutter !== undefined) {
+ gutter = { ...gutter } as DecorationOptions;
+
+ const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
+ gutter.range = new Range(line, 0, line, endIndex);
+
+ decorations.push(gutter);
+
+ if (details !== undefined) {
+ details = { ...details } as DecorationOptions;
+ details.range = cfg.hover.wholeLine
+ ? document.validateRange(new Range(line, 0, line, endOfLineIndex))
+ : gutter.range;
+
+ decorations.push(details);
+ }
+
+ continue;
+ }
+
+ commit = blame.commits.get(l.sha);
+ if (commit === undefined) continue;
+
gutter = Annotations.gutter(commit, cfg.format, options, renderOptions);
if (cfg.heatmap.enabled) {
@@ -100,13 +126,20 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
gutter.range = new Range(line, 0, line, endIndex);
+
decorations.push(gutter);
+ decorationsMap[l.sha] = gutter;
if (cfg.hover.details) {
- details = Annotations.detailsHover(commit, dateFormat);
+ if (hasRemotes === undefined) {
+ hasRemotes = this.git.hasRemotes(commit.repoPath);
+ }
+
+ details = Annotations.detailsHover(commit, dateFormat, hasRemotes);
details.range = cfg.hover.wholeLine
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
: gutter.range;
+
decorations.push(details);
}
}
diff --git a/src/annotations/hoverBlameAnnotationProvider.ts b/src/annotations/hoverBlameAnnotationProvider.ts
index e16c770..3befb97 100644
--- a/src/annotations/hoverBlameAnnotationProvider.ts
+++ b/src/annotations/hoverBlameAnnotationProvider.ts
@@ -24,18 +24,42 @@ export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
const dateFormat = this._config.defaultDateFormat;
const decorations: DecorationOptions[] = [];
+ const decorationsMap: { [sha: string]: DecorationOptions } = Object.create(null);
const document = this.document;
let commit: GitBlameCommit | undefined;
+ let hasRemotes: boolean | undefined;
let hover: DecorationOptions | undefined;
for (const l of blame.lines) {
+ const line = l.line + offset;
+
+ hover = decorationsMap[l.sha];
+
+ if (hover !== undefined) {
+ hover = { ...hover } as DecorationOptions;
+
+ if (cfg.wholeLine) {
+ hover.range = document.validateRange(new Range(line, 0, line, endOfLineIndex));
+ }
+ else {
+ const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
+ hover.range = new Range(line, 0, line, endIndex);
+ }
+
+ decorations.push(hover);
+
+ continue;
+ }
+
commit = blame.commits.get(l.sha);
if (commit === undefined) continue;
- const line = l.line + offset;
+ if (hasRemotes === undefined) {
+ hasRemotes = this.git.hasRemotes(commit.repoPath);
+ }
- hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled, dateFormat);
+ hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled, dateFormat, hasRemotes);
if (cfg.wholeLine) {
hover.range = document.validateRange(new Range(line, 0, line, endOfLineIndex));
@@ -50,6 +74,8 @@ export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
}
decorations.push(hover);
+ decorationsMap[l.sha] = hover;
+
}
if (decorations.length) {
diff --git a/src/annotations/recentChangesAnnotationProvider.ts b/src/annotations/recentChangesAnnotationProvider.ts
index e6e1294..e73cd4c 100644
--- a/src/annotations/recentChangesAnnotationProvider.ts
+++ b/src/annotations/recentChangesAnnotationProvider.ts
@@ -46,7 +46,7 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
if (cfg.hover.details) {
decorators.push({
- hoverMessage: Annotations.getHoverMessage(commit, dateFormat),
+ hoverMessage: Annotations.getHoverMessage(commit, dateFormat, this.git.hasRemotes(commit.repoPath)),
range: range
} as DecorationOptions);
}
diff --git a/src/commands/openCommitInRemote.ts b/src/commands/openCommitInRemote.ts
index d52ad17..38f8d7d 100644
--- a/src/commands/openCommitInRemote.ts
+++ b/src/commands/openCommitInRemote.ts
@@ -12,6 +12,15 @@ export interface OpenCommitInRemoteCommandArgs {
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
+ static getMarkdownCommandArgs(sha: string): string;
+ static getMarkdownCommandArgs(args: OpenCommitInRemoteCommandArgs): string;
+ static getMarkdownCommandArgs(argsOrSha: OpenCommitInRemoteCommandArgs | string): string {
+ const args = typeof argsOrSha === 'string'
+ ? { sha: argsOrSha }
+ : argsOrSha;
+ return super.getMarkdownCommandArgsCore(Commands.OpenCommitInRemote, args);
+ }
+
constructor(private git: GitService) {
super(Commands.OpenCommitInRemote);
}
diff --git a/src/currentLineController.ts b/src/currentLineController.ts
index 94b3f63..9b5db7a 100644
--- a/src/currentLineController.ts
+++ b/src/currentLineController.ts
@@ -428,7 +428,7 @@ export class CurrentLineController extends Disposable {
// I have no idea why I need this protection -- but it happens
if (editor.document === undefined) return;
- const decoration = Annotations.detailsHover(logCommit || commit, this._config.defaultDateFormat);
+ const decoration = Annotations.detailsHover(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes((logCommit || commit).repoPath));
decoration.range = editor.document.validateRange(new Range(line, showDetailsStartIndex, line, endOfLineIndex));
decorationOptions.push(decoration);