Skip to content

Commit b01d7ab

Browse files
committed
Closes #182 - adds heatmap annotations
1 parent eb3f570 commit b01d7ab

File tree

9 files changed

+156
-0
lines changed

9 files changed

+156
-0
lines changed

CHANGELOG.md

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

77
## [Unreleased]
88
### Added
9+
- Adds on-demand **heatmap annotations** of the whole file -- closes [#182](https://github.com/eamodio/vscode-gitlens/issues/182)
10+
- Displays a `heatmap` (age) indicator near the gutter, which provides an easy, at-a-glance way to tell the age of a line
11+
- Indicator ranges from bright yellow (newer) to dark brown (older)
12+
- Press `Escape` to quickly toggle the annotations off
13+
- Adds `Toggle File Heatmap Annotations` command (`gitlens.toggleFileHeatmap`) to toggle the heatmap annotations on and off
914
- Adds semi-persistent results for commit operations, via the `Show Commit Details` command (`gitlens.showQuickCommitDetails`) in the `GitLens Results` view -- closes [#237](https://github.com/eamodio/vscode-gitlens/issues/237)
1015
- Adds `Show in Results` option to the commit details quick pick menu to show the commit in the `GitLens Results` view
1116
- Adds `Compare with Index (HEAD)` command (`gitlens.explorers.compareWithHead`) to branch, remote branch, tag, and revision (commit) nodes in the `GitLens` view to compare the current selection with the current index (HEAD) in the `GitLens Results` view

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ While GitLens is highly customizable and provides many [configuration settings](
9393
- Adds a `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`) to toggle the current line blame annotations on and off
9494
- Also adds a `Show Line Blame Annotations` command (`gitlens.showLineBlame`)
9595

96+
### Git Heatmap Annotations
97+
98+
- Adds on-demand **heatmap annotations** of the whole file
99+
- Displays a `heatmap` (age) indicator near the gutter, which provides an easy, at-a-glance way to tell the age of a line
100+
- Indicator ranges from bright yellow (newer) to dark brown (older)
101+
- Press `Escape` to quickly toggle the annotations off
102+
103+
- Adds `Toggle File Heatmap Annotations` command (`gitlens.toggleFileHeatmap`) to toggle the heatmap annotations on and off
104+
96105
### Git Recent Changes Annotations
97106

98107
- Adds on-demand, [customizable](#file-recent-changes-annotation-settings) and [themable](#themable-colors), **recent changes annotations** of the whole file

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,11 @@
12141214
"light": "images/light/git-icon-progress.svg"
12151215
}
12161216
},
1217+
{
1218+
"command": "gitlens.toggleFileHeatmap",
1219+
"title": "Toggle File Heatmap Annotations",
1220+
"category": "GitLens"
1221+
},
12171222
{
12181223
"command": "gitlens.toggleFileRecentChanges",
12191224
"title": "Toggle Recent File Changes Annotations",
@@ -1676,6 +1681,10 @@
16761681
"command": "gitlens.computingFileAnnotations",
16771682
"when": "false"
16781683
},
1684+
{
1685+
"command": "gitlens.toggleFileHeatmap",
1686+
"when": "gitlens:activeIsBlameable"
1687+
},
16791688
{
16801689
"command": "gitlens.toggleFileRecentChanges",
16811690
"when": "gitlens:activeIsTracked"

src/annotations/annotationController.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { configuration, IConfig, LineHighlightLocations } from '../configuration
77
import { CommandContext, isTextEditor, setCommandContext } from '../constants';
88
import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri } from '../gitService';
99
import { GutterBlameAnnotationProvider } from './gutterBlameAnnotationProvider';
10+
import { HeatmapBlameAnnotationProvider } from './heatmapBlameAnnotationProvider';
1011
import { HoverBlameAnnotationProvider } from './hoverBlameAnnotationProvider';
1112
import { Keyboard, KeyboardScope, KeyCommand, Keys } from '../keyboard';
1213
import { Logger } from '../logger';
@@ -15,6 +16,7 @@ import * as path from 'path';
1516

1617
export enum FileAnnotationType {
1718
Gutter = 'gutter',
19+
Heatmap = 'heatmap',
1820
Hover = 'hover',
1921
RecentChanges = 'recentChanges'
2022
}
@@ -343,6 +345,10 @@ export class AnnotationController extends Disposable {
343345
annotationsLabel = 'blame annotations';
344346
break;
345347

348+
case FileAnnotationType.Heatmap:
349+
annotationsLabel = 'heatmap annotations';
350+
break;
351+
346352
case FileAnnotationType.RecentChanges:
347353
annotationsLabel = 'recent changes annotations';
348354
break;
@@ -362,6 +368,10 @@ export class AnnotationController extends Disposable {
362368
provider = new GutterBlameAnnotationProvider(this.context, editor, Decorations.blameAnnotation, Decorations.blameHighlight, this.git, gitUri);
363369
break;
364370

371+
case FileAnnotationType.Heatmap:
372+
provider = new HeatmapBlameAnnotationProvider(this.context, editor, Decorations.blameAnnotation, undefined, this.git, gitUri);
373+
break;
374+
365375
case FileAnnotationType.Hover:
366376
provider = new HoverBlameAnnotationProvider(this.context, editor, Decorations.blameAnnotation, Decorations.blameHighlight, this.git, gitUri);
367377
break;

src/annotations/annotations.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,28 @@ export class Annotations {
207207
} as IRenderOptions;
208208
}
209209

210+
static heatmap(commit: GitCommit, now: number, renderOptions: IRenderOptions): DecorationOptions {
211+
const decoration = {
212+
renderOptions: {
213+
before: { ...renderOptions }
214+
} as DecorationInstanceRenderOptions
215+
} as DecorationOptions;
216+
217+
Annotations.applyHeatmap(decoration, commit.date, now);
218+
219+
return decoration;
220+
}
221+
222+
static heatmapRenderOptions(): IRenderOptions {
223+
return {
224+
borderStyle: 'solid',
225+
borderWidth: '0 0 0 2px',
226+
contentText: GlyphChars.ZeroWidthSpace,
227+
height: '100%',
228+
margin: '0 26px -1px 0'
229+
} as IRenderOptions;
230+
}
231+
210232
static hover(commit: GitCommit, renderOptions: IRenderOptions, now: number): DecorationOptions {
211233
const decoration = {
212234
renderOptions: { before: { ...renderOptions } }
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
import { DecorationOptions, Range } from 'vscode';
3+
import { FileAnnotationType } from './annotationController';
4+
import { Annotations } from './annotations';
5+
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
6+
import { GitBlameCommit } from '../gitService';
7+
import { Logger } from '../logger';
8+
9+
export class HeatmapBlameAnnotationProvider extends BlameAnnotationProviderBase {
10+
11+
async provideAnnotation(shaOrLine?: string | number, type?: FileAnnotationType): Promise<boolean> {
12+
this.annotationType = FileAnnotationType.Heatmap;
13+
14+
const blame = await this.getBlame();
15+
if (blame === undefined) return false;
16+
17+
const start = process.hrtime();
18+
19+
const now = Date.now();
20+
const renderOptions = Annotations.heatmapRenderOptions();
21+
22+
this._decorations = [];
23+
const decorationsMap: { [sha: string]: DecorationOptions | undefined } = Object.create(null);
24+
25+
let commit: GitBlameCommit | undefined;
26+
let heatmap: DecorationOptions | undefined;
27+
28+
for (const l of blame.lines) {
29+
const line = l.line;
30+
31+
heatmap = decorationsMap[l.sha];
32+
if (heatmap !== undefined) {
33+
heatmap = {
34+
...heatmap,
35+
range: new Range(line, 0, line, 0)
36+
} as DecorationOptions;
37+
38+
this._decorations.push(heatmap);
39+
40+
continue;
41+
}
42+
43+
commit = blame.commits.get(l.sha);
44+
if (commit === undefined) continue;
45+
46+
heatmap = Annotations.heatmap(commit, now, renderOptions);
47+
heatmap.range = new Range(line, 0, line, 0);
48+
49+
this._decorations.push(heatmap);
50+
decorationsMap[l.sha] = heatmap;
51+
}
52+
53+
if (this._decorations.length) {
54+
this.editor.setDecorations(this.decoration!, this._decorations);
55+
}
56+
57+
const duration = process.hrtime(start);
58+
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute heatmap annotations`);
59+
60+
this.selection(shaOrLine, blame);
61+
return true;
62+
}
63+
}

src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export * from './commands/stashDelete';
4747
export * from './commands/stashSave';
4848
export * from './commands/toggleCodeLens';
4949
export * from './commands/toggleFileBlame';
50+
export * from './commands/toggleFileHeatmap';
5051
export * from './commands/toggleFileRecentChanges';
5152
export * from './commands/toggleLineBlame';
5253

@@ -87,6 +88,7 @@ export function configureCommands(
8788
context.subscriptions.push(new Commands.ShowFileBlameCommand(annotationController));
8889
context.subscriptions.push(new Commands.ShowLineBlameCommand(currentLineController));
8990
context.subscriptions.push(new Commands.ToggleFileBlameCommand(annotationController));
91+
context.subscriptions.push(new Commands.ToggleFileHeatmapCommand(annotationController));
9092
context.subscriptions.push(new Commands.ToggleFileRecentChangesCommand(annotationController));
9193
context.subscriptions.push(new Commands.ToggleLineBlameCommand(currentLineController));
9294
context.subscriptions.push(new Commands.ResetSuppressedWarningsCommand());

src/commands/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export enum Commands {
4949
StashSave = 'gitlens.stashSave',
5050
ToggleCodeLens = 'gitlens.toggleCodeLens',
5151
ToggleFileBlame = 'gitlens.toggleFileBlame',
52+
ToggleFileHeatmap = 'gitlens.toggleFileHeatmap',
5253
ToggleFileRecentChanges = 'gitlens.toggleFileRecentChanges',
5354
ToggleLineBlame = 'gitlens.toggleLineBlame'
5455
}

src/commands/toggleFileHeatmap.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
3+
import { AnnotationController, FileAnnotationType } from '../annotations/annotationController';
4+
import { Commands, EditorCommand } from './common';
5+
import { UriComparer } from '../comparers';
6+
import { Logger } from '../logger';
7+
8+
export class ToggleFileHeatmapCommand extends EditorCommand {
9+
10+
constructor(
11+
private readonly annotationController: AnnotationController
12+
) {
13+
super(Commands.ToggleFileHeatmap);
14+
}
15+
16+
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri): Promise<any> {
17+
if (editor === undefined || editor.document.isDirty) return undefined;
18+
19+
// Handle the case where we are focused on a non-editor editor (output, debug console)
20+
if (uri !== undefined && !UriComparer.equals(uri, editor.document.uri)) {
21+
const e = window.visibleTextEditors.find(e => UriComparer.equals(uri, e.document.uri));
22+
if (e !== undefined && !e.document.isDirty) {
23+
editor = e;
24+
}
25+
}
26+
27+
try {
28+
return this.annotationController.toggleAnnotations(editor, FileAnnotationType.Heatmap);
29+
}
30+
catch (ex) {
31+
Logger.error(ex, 'ToggleFileHeatmapCommand');
32+
return window.showErrorMessage(`Unable to toggle heatmap annotations. See output channel for more details`);
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)