diff --git a/README.md b/README.md
index 55e4865..c5d144a 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,14 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- Adds a `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`) to toggle the current line blame annotations on and off
- Also adds a `Show Line Blame Annotations` command (`gitlens.showLineBlame`)
+### Git Recent Changes Annotations
+
+- Adds on-demand, [customizable](#file-recent-changes-annotation-settings) and [themeable](#theme-settings), **recent changes annotations** of the whole file
+ - Highlights all of lines changed in the most recent commit
+ - Also adds a `changes` (diff) hover annotation to the current line annotation which provides **instant** access to the line's previous version ([optional](#file-recent-changes-annotation-settings), on by default)
+
+- Adds `Toggle Recent File Changes Annotations` command (`gitlens.toggleFileRecentChanges`) to toggle the recent changes annotations on and off
+
### Git Code Lens
- Adds **code lens** to the top of the file and on code blocks ([optional](#code-lens-settings), on by default)
@@ -244,6 +252,14 @@ GitLens is highly customizable and provides many configuration settings to allow
|`gitlens.annotations.line.hover.details`|Specifies whether or not to provide a commit details hover annotation for the current line
|`gitlens.annotations.line.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation for the current line
+### File Recent Changes Annotation Settings
+
+|Name | Description
+|-----|------------
+|`gitlens.recentChanges.file.lineHighlight.locations`|Specifies where the highlights of the recently changed lines will be shown
`gutter` - adds a gutter glyph
`line` - adds a full-line highlight background color
`overviewRuler` - adds a decoration to the overviewRuler (scroll bar)
+|`gitlens.annotations.file.recentChanges.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotations
+|`gitlens.annotations.file.recentChanges.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
+
### Code Lens Settings
|Name | Description
diff --git a/package.json b/package.json
index 7f22729..9de26a7 100644
--- a/package.json
+++ b/package.json
@@ -122,6 +122,16 @@
"default": true,
"description": "Specifies whether or not to trigger hover annotations over the whole line"
},
+ "gitlens.annotations.file.recentChanges.hover.changes": {
+ "type": "boolean",
+ "default": true,
+ "description": "Specifies whether or not to provide a changes (diff) hover annotations"
+ },
+ "gitlens.annotations.file.recentChanges.hover.wholeLine": {
+ "type": "boolean",
+ "default": true,
+ "description": "Specifies whether or not to trigger hover annotations over the whole line"
+ },
"gitlens.annotations.line.hover.details": {
"type": "boolean",
"default": true,
@@ -205,6 +215,26 @@
],
"description": "Specifies the type of blame annotations that will be shown for the current line\n `trailing` - adds an annotation to the end of the current line\n `hover` - shows annotations when hovering over the current line"
},
+ "gitlens.recentChanges.file.lineHighlight.locations": {
+ "type": "array",
+ "default": [
+ "gutter",
+ "line",
+ "overviewRuler"
+ ],
+ "items": {
+ "type": "string",
+ "enum": [
+ "gutter",
+ "line",
+ "overviewRuler"
+ ]
+ },
+ "minItems": 1,
+ "maxItems": 3,
+ "uniqueItems": true,
+ "description": "Specifies where the highlights of the recently changed lines will be shown\n `gutter` - adds a gutter glyph\n `line` - adds a full-line highlight background color\n `overviewRuler` - adds a decoration to the overviewRuler (scroll bar)"
+ },
"gitlens.codeLens.enabled": {
"type": "boolean",
"default": true,
@@ -757,6 +787,11 @@
"dark": "images/git-icon-dark.svg",
"light": "images/git-icon-light.svg"
}
+ },
+ {
+ "command": "gitlens.toggleFileRecentChanges",
+ "title": "Toggle Recent File Changes Annotations",
+ "category": "GitLens"
},
{
"command": "gitlens.toggleLineBlame",
@@ -920,6 +955,10 @@
{
"command": "gitlens.toggleFileBlame",
"when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.toggleFileRecentChanges",
+ "when": "gitlens:isTracked"
},
{
"command": "gitlens.toggleLineBlame",
diff --git a/src/annotations/annotationController.ts b/src/annotations/annotationController.ts
index 2e15e7f..d218d03 100644
--- a/src/annotations/annotationController.ts
+++ b/src/annotations/annotationController.ts
@@ -3,19 +3,29 @@ import { Functions, Objects } from '../system';
import { DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, TextDocument, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
import { AnnotationProviderBase } from './annotationProvider';
import { TextDocumentComparer, TextEditorComparer } from '../comparers';
-import { BlameLineHighlightLocations, ExtensionKey, FileAnnotationType, IConfig, themeDefaults } from '../configuration';
+import { ExtensionKey, IConfig, LineHighlightLocations, themeDefaults } from '../configuration';
import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri } from '../gitService';
import { GutterBlameAnnotationProvider } from './gutterBlameAnnotationProvider';
import { HoverBlameAnnotationProvider } from './hoverBlameAnnotationProvider';
import { Logger } from '../logger';
+import { RecentChangesAnnotationProvider } from './recentChangesAnnotationProvider';
import { WhitespaceController } from './whitespaceController';
+export type FileAnnotationType = 'gutter' | 'hover' | 'recentChanges';
+export const FileAnnotationType = {
+ Gutter: 'gutter' as FileAnnotationType,
+ Hover: 'hover' as FileAnnotationType,
+ RecentChanges: 'recentChanges' as FileAnnotationType
+};
+
export const Decorations = {
- annotation: window.createTextEditorDecorationType({
+ blameAnnotation: window.createTextEditorDecorationType({
isWholeLine: true,
textDecoration: 'none'
} as DecorationRenderOptions),
- highlight: undefined as TextEditorDecorationType | undefined
+ blameHighlight: undefined as TextEditorDecorationType | undefined,
+ recentChangesAnnotation: undefined as TextEditorDecorationType | undefined,
+ recentChangesHighlight: undefined as TextEditorDecorationType | undefined
};
export class AnnotationController extends Disposable {
@@ -46,8 +56,8 @@ export class AnnotationController extends Disposable {
dispose() {
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
- Decorations.annotation && Decorations.annotation.dispose();
- Decorations.highlight && Decorations.highlight.dispose();
+ Decorations.blameAnnotation && Decorations.blameAnnotation.dispose();
+ Decorations.blameHighlight && Decorations.blameHighlight.dispose();
this._annotationsDisposable && this._annotationsDisposable.dispose();
this._whitespaceController && this._whitespaceController.dispose();
@@ -82,50 +92,83 @@ export class AnnotationController extends Disposable {
}
const cfg = workspace.getConfiguration().get(ExtensionKey)!;
- const cfgHighlight = cfg.blame.file.lineHighlight;
+ const cfgBlameHighlight = cfg.blame.file.lineHighlight;
+ const cfgChangesHighlight = cfg.recentChanges.file.lineHighlight;
const cfgTheme = cfg.theme.lineHighlight;
- if (!Objects.areEquivalent(cfgHighlight, this._config && this._config.blame.file.lineHighlight) ||
+ if (!Objects.areEquivalent(cfgBlameHighlight, this._config && this._config.blame.file.lineHighlight) ||
+ !Objects.areEquivalent(cfgChangesHighlight, this._config && this._config.recentChanges.file.lineHighlight) ||
!Objects.areEquivalent(cfgTheme, this._config && this._config.theme.lineHighlight)) {
changed = true;
- Decorations.highlight && Decorations.highlight.dispose();
+ Decorations.blameHighlight && Decorations.blameHighlight.dispose();
- if (cfgHighlight.enabled) {
- Decorations.highlight = window.createTextEditorDecorationType({
+ if (cfgBlameHighlight.enabled) {
+ Decorations.blameHighlight = window.createTextEditorDecorationType({
gutterIconSize: 'contain',
isWholeLine: true,
overviewRulerLane: OverviewRulerLane.Right,
dark: {
- backgroundColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.Line)
+ backgroundColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.Line)
? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
: undefined,
- gutterIconPath: cfgHighlight.locations.includes(BlameLineHighlightLocations.Gutter)
+ gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter)
? this.context.asAbsolutePath('images/blame-dark.svg')
: undefined,
- overviewRulerColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.OverviewRuler)
+ overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
: undefined
},
light: {
- backgroundColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.Line)
+ backgroundColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.Line)
? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
: undefined,
- gutterIconPath: cfgHighlight.locations.includes(BlameLineHighlightLocations.Gutter)
+ gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter)
? this.context.asAbsolutePath('images/blame-light.svg')
: undefined,
- overviewRulerColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.OverviewRuler)
+ overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
: undefined
}
});
}
else {
- Decorations.highlight = undefined;
+ Decorations.blameHighlight = undefined;
}
+
+ Decorations.recentChangesHighlight && Decorations.recentChangesHighlight.dispose();
+
+ Decorations.recentChangesHighlight = window.createTextEditorDecorationType({
+ gutterIconSize: 'contain',
+ isWholeLine: true,
+ overviewRulerLane: OverviewRulerLane.Right,
+ dark: {
+ backgroundColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.Line)
+ ? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
+ : undefined,
+ gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter)
+ ? this.context.asAbsolutePath('images/blame-dark.svg')
+ : undefined,
+ overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
+ ? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
+ : undefined
+ },
+ light: {
+ backgroundColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.Line)
+ ? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
+ : undefined,
+ gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter)
+ ? this.context.asAbsolutePath('images/blame-light.svg')
+ : undefined,
+ overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
+ ? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
+ : undefined
+ }
+ });
}
if (!Objects.areEquivalent(cfg.blame.file, this._config && this._config.blame.file) ||
+ !Objects.areEquivalent(cfg.recentChanges.file, this._config && this._config.recentChanges.file) ||
!Objects.areEquivalent(cfg.annotations, this._config && this._config.annotations) ||
!Objects.areEquivalent(cfg.theme.annotations, this._config && this._config.theme.annotations)) {
changed = true;
@@ -138,7 +181,12 @@ export class AnnotationController extends Disposable {
for (const provider of this._annotationProviders.values()) {
if (provider === undefined) continue;
- provider.reset(this._whitespaceController);
+ if (provider.annotationType === FileAnnotationType.RecentChanges) {
+ provider.reset(Decorations.recentChangesAnnotation, Decorations.recentChangesHighlight);
+ }
+ else {
+ provider.reset(Decorations.blameAnnotation, Decorations.blameHighlight, this._whitespaceController);
+ }
}
}
}
@@ -184,10 +232,15 @@ export class AnnotationController extends Disposable {
let provider: AnnotationProviderBase | undefined = undefined;
switch (type) {
case FileAnnotationType.Gutter:
- provider = new GutterBlameAnnotationProvider(this.context, editor, Decorations.annotation, Decorations.highlight, this._whitespaceController, this.git, gitUri);
+ provider = new GutterBlameAnnotationProvider(this.context, editor, Decorations.blameAnnotation, Decorations.blameHighlight, this._whitespaceController, this.git, gitUri);
break;
+
case FileAnnotationType.Hover:
- provider = new HoverBlameAnnotationProvider(this.context, editor, Decorations.annotation, Decorations.highlight, this._whitespaceController, this.git, gitUri);
+ provider = new HoverBlameAnnotationProvider(this.context, editor, Decorations.blameAnnotation, Decorations.blameHighlight, this._whitespaceController, this.git, gitUri);
+ break;
+
+ case FileAnnotationType.RecentChanges:
+ provider = new RecentChangesAnnotationProvider(this.context, editor, undefined, Decorations.recentChangesHighlight!, this.git, gitUri);
break;
}
if (provider === undefined || !(await provider.validate())) return false;
@@ -219,13 +272,17 @@ export class AnnotationController extends Disposable {
}
async toggleAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise {
- if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
+ if (!editor || !editor.document || type === FileAnnotationType.RecentChanges ? !this.git.isTrackable(editor.document.uri) : !this.git.isEditorBlameable(editor)) return false;
const provider = this._annotationProviders.get(editor.viewColumn || -1);
if (provider === undefined) return this.showAnnotations(editor, type, shaOrLine);
+ const reopen = provider.annotationType !== type;
await this.clear(provider.editor.viewColumn || -1);
- return false;
+
+ if (!reopen) return false;
+
+ return this.showAnnotations(editor, type, shaOrLine);
}
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
diff --git a/src/annotations/annotationProvider.ts b/src/annotations/annotationProvider.ts
index a4e013e..0a8be81 100644
--- a/src/annotations/annotationProvider.ts
+++ b/src/annotations/annotationProvider.ts
@@ -1,8 +1,9 @@
'use strict';
import { Functions } from '../system';
import { Disposable, ExtensionContext, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
+import { FileAnnotationType } from '../annotations/annotationController';
import { TextDocumentComparer } from '../comparers';
-import { ExtensionKey, FileAnnotationType, IConfig } from '../configuration';
+import { ExtensionKey, IConfig } from '../configuration';
import { WhitespaceController } from './whitespaceController';
export abstract class AnnotationProviderBase extends Disposable {
@@ -13,7 +14,7 @@ import { WhitespaceController } from './whitespaceController';
protected _config: IConfig;
protected _disposable: Disposable;
- constructor(context: ExtensionContext, public editor: TextEditor, protected decoration: TextEditorDecorationType, protected highlightDecoration: TextEditorDecorationType | undefined, protected whitespaceController: WhitespaceController | undefined) {
+ constructor(context: ExtensionContext, public editor: TextEditor, protected decoration: TextEditorDecorationType | undefined, protected highlightDecoration: TextEditorDecorationType | undefined, protected whitespaceController: WhitespaceController | undefined) {
super(() => this.dispose());
this.document = this.editor.document;
@@ -42,10 +43,14 @@ import { WhitespaceController } from './whitespaceController';
async clear() {
if (this.editor !== undefined) {
try {
- this.editor.setDecorations(this.decoration, []);
- this.highlightDecoration && this.editor.setDecorations(this.highlightDecoration, []);
- // I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
+ if (this.decoration !== undefined) {
+ this.editor.setDecorations(this.decoration, []);
+ }
+
if (this.highlightDecoration !== undefined) {
+ this.editor.setDecorations(this.highlightDecoration, []);
+
+ // I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
await Functions.wait(1);
if (this.highlightDecoration === undefined) return;
@@ -60,10 +65,12 @@ import { WhitespaceController } from './whitespaceController';
this.whitespaceController && await this.whitespaceController.restore();
}
- async reset(whitespaceController: WhitespaceController | undefined) {
+ async reset(decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController?: WhitespaceController) {
await this.clear();
this._config = workspace.getConfiguration().get(ExtensionKey)!;
+ this.decoration = decoration;
+ this.highlightDecoration = highlightDecoration;
this.whitespaceController = whitespaceController;
await this.provideAnnotation(this.editor === undefined ? undefined : this.editor.selection.active.line);
diff --git a/src/annotations/blameAnnotationProvider.ts b/src/annotations/blameAnnotationProvider.ts
index ef77364..d69c906 100644
--- a/src/annotations/blameAnnotationProvider.ts
+++ b/src/annotations/blameAnnotationProvider.ts
@@ -9,7 +9,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
protected _blame: Promise;
- constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, protected git: GitService, protected uri: GitUri) {
+ constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, protected git: GitService, protected uri: GitUri) {
super(context, editor, decoration, highlightDecoration, whitespaceController);
this._blame = this.git.getBlameForFile(this.uri);
@@ -56,7 +56,6 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
const blame = await this._blame;
return blame !== undefined && blame.lines.length !== 0;
}
-
protected async getBlame(requiresWhitespaceHack: boolean): Promise {
let whitespacePromise: Promise | undefined;
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- override whitespace (turn off)
diff --git a/src/annotations/diffAnnotationProvider.ts b/src/annotations/diffAnnotationProvider.ts
deleted file mode 100644
index 4baf345..0000000
--- a/src/annotations/diffAnnotationProvider.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-'use strict';
-import { DecorationOptions, ExtensionContext, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode';
-import { AnnotationProviderBase } from './annotationProvider';
-import { GitService, GitUri } from '../gitService';
-import { WhitespaceController } from './whitespaceController';
-
-export class DiffAnnotationProvider extends AnnotationProviderBase {
-
- constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, private git: GitService, private uri: GitUri) {
- super(context, editor, decoration, highlightDecoration, whitespaceController);
- }
-
- async provideAnnotation(shaOrLine?: string | number): Promise {
- // let sha1: string | undefined = undefined;
- // let sha2: string | undefined = undefined;
- // if (shaOrLine === undefined) {
- // const commit = await this.git.getLogCommit(this.uri.repoPath, this.uri.fsPath, { previous: true });
- // if (commit === undefined) return false;
-
- // sha1 = commit.previousSha;
- // }
- // else if (typeof shaOrLine === 'string') {
- // sha1 = shaOrLine;
- // }
- // else {
- // const blame = await this.git.getBlameForLine(this.uri, shaOrLine);
- // if (blame === undefined) return false;
-
- // sha1 = blame.commit.previousSha;
- // sha2 = blame.commit.sha;
- // }
-
- // if (sha1 === undefined) return false;
-
- const commit = await this.git.getLogCommit(this.uri.repoPath, this.uri.fsPath, { previous: true });
- if (commit === undefined) return false;
-
- const diff = await this.git.getDiffForFile(this.uri, commit.previousSha);
- if (diff === undefined) return false;
-
- const decorators: DecorationOptions[] = [];
-
- for (const chunk of diff.chunks) {
- let count = chunk.currentPosition.start - 2;
- for (const change of chunk.current) {
- if (change === undefined) continue;
-
- count++;
-
- if (change.state === 'unchanged') continue;
-
- decorators.push({
- range: new Range(new Position(count, 0), new Position(count, 0))
- } as DecorationOptions);
- }
- }
-
- this.editor.setDecorations(this.decoration, decorators);
-
- return true;
- }
-
- async selection(shaOrLine?: string | number): Promise {
- }
-
- async validate(): Promise {
- return true;
- }
-}
\ No newline at end of file
diff --git a/src/annotations/gutterBlameAnnotationProvider.ts b/src/annotations/gutterBlameAnnotationProvider.ts
index a125438..cda5744 100644
--- a/src/annotations/gutterBlameAnnotationProvider.ts
+++ b/src/annotations/gutterBlameAnnotationProvider.ts
@@ -1,9 +1,9 @@
'use strict';
import { Strings } from '../system';
import { DecorationOptions, Range } from 'vscode';
+import { FileAnnotationType } from './annotationController';
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
import { Annotations, endOfLineIndex } from './annotations';
-import { FileAnnotationType } from '../configuration';
import { ICommitFormatOptions } from '../gitService';
import * as moment from 'moment';
@@ -67,7 +67,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
}
if (decorations.length) {
- this.editor.setDecorations(this.decoration, decorations);
+ this.editor.setDecorations(this.decoration!, decorations);
}
this.selection(shaOrLine, blame);
diff --git a/src/annotations/hoverBlameAnnotationProvider.ts b/src/annotations/hoverBlameAnnotationProvider.ts
index 64694f0..53166b0 100644
--- a/src/annotations/hoverBlameAnnotationProvider.ts
+++ b/src/annotations/hoverBlameAnnotationProvider.ts
@@ -1,8 +1,8 @@
'use strict';
import { DecorationOptions, Range } from 'vscode';
+import { FileAnnotationType } from './annotationController';
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
import { Annotations, endOfLineIndex } from './annotations';
-import { FileAnnotationType } from '../configuration';
import * as moment from 'moment';
export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
@@ -40,7 +40,7 @@ export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
}
if (decorations.length) {
- this.editor.setDecorations(this.decoration, decorations);
+ this.editor.setDecorations(this.decoration!, decorations);
}
this.selection(shaOrLine, blame);
diff --git a/src/annotations/recentChangesAnnotationProvider.ts b/src/annotations/recentChangesAnnotationProvider.ts
new file mode 100644
index 0000000..5b596fe
--- /dev/null
+++ b/src/annotations/recentChangesAnnotationProvider.ts
@@ -0,0 +1,61 @@
+'use strict';
+import { DecorationOptions, ExtensionContext, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode';
+import { endOfLineIndex } from './annotations';
+import { FileAnnotationType } from './annotationController';
+import { AnnotationProviderBase } from './annotationProvider';
+import { CommitFormatter, GitService, GitUri } from '../gitService';
+
+export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
+
+ constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined, private git: GitService, private uri: GitUri) {
+ super(context, editor, decoration, highlightDecoration, undefined);
+ }
+
+ async provideAnnotation(shaOrLine?: string | number): Promise {
+ this.annotationType = FileAnnotationType.RecentChanges;
+
+ const commit = await this.git.getLogCommit(this.uri.repoPath, this.uri.fsPath, { previous: true });
+ if (commit === undefined) return false;
+
+ const diff = await this.git.getDiffForFile(this.uri, commit.previousSha);
+ if (diff === undefined) return false;
+
+ const cfg = this._config.annotations.file.recentChanges;
+
+ const decorators: DecorationOptions[] = [];
+
+ for (const chunk of diff.chunks) {
+ let count = chunk.currentPosition.start - 2;
+ for (const change of chunk.current) {
+ if (change === undefined) continue;
+
+ count++;
+
+ if (change.state === 'unchanged') continue;
+
+ let endingIndex = 0;
+ let message: string | undefined = undefined;
+ if (cfg.hover.changes) {
+ message = CommitFormatter.toHoverDiff(commit, chunk.previous[count], change);
+ endingIndex = cfg.hover.wholeLine ? endOfLineIndex : this.editor.document.lineAt(count).firstNonWhitespaceCharacterIndex;
+ }
+
+ decorators.push({
+ hoverMessage: message,
+ range: this.editor.document.validateRange(new Range(new Position(count, 0), new Position(count, endingIndex)))
+ } as DecorationOptions);
+ }
+ }
+
+ this.editor.setDecorations(this.highlightDecoration!, decorators);
+
+ return true;
+ }
+
+ async selection(shaOrLine?: string | number): Promise {
+ }
+
+ async validate(): Promise {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/commands.ts b/src/commands.ts
index 88aa41d..7fce9d4 100644
--- a/src/commands.ts
+++ b/src/commands.ts
@@ -38,4 +38,5 @@ export * from './commands/stashDelete';
export * from './commands/stashSave';
export * from './commands/toggleCodeLens';
export * from './commands/toggleFileBlame';
+export * from './commands/toggleFileRecentChanges';
export * from './commands/toggleLineBlame';
\ No newline at end of file
diff --git a/src/commands/common.ts b/src/commands/common.ts
index 7d326bf..85a65f1 100644
--- a/src/commands/common.ts
+++ b/src/commands/common.ts
@@ -39,6 +39,7 @@ export type Commands = 'gitlens.closeUnchangedFiles' |
'gitlens.stashSave' |
'gitlens.toggleCodeLens' |
'gitlens.toggleFileBlame' |
+ 'gitlens.toggleFileRecentChanges' |
'gitlens.toggleLineBlame';
export const Commands = {
CloseUnchangedFiles: 'gitlens.closeUnchangedFiles' as Commands,
@@ -76,6 +77,7 @@ export const Commands = {
StashSave: 'gitlens.stashSave' as Commands,
ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands,
ToggleFileBlame: 'gitlens.toggleFileBlame' as Commands,
+ ToggleFileRecentChanges: 'gitlens.toggleFileRecentChanges' as Commands,
ToggleLineBlame: 'gitlens.toggleLineBlame' as Commands
};
diff --git a/src/commands/showFileBlame.ts b/src/commands/showFileBlame.ts
index d18d24c..1da2dbe 100644
--- a/src/commands/showFileBlame.ts
+++ b/src/commands/showFileBlame.ts
@@ -1,8 +1,8 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
-import { AnnotationController } from '../annotations/annotationController';
+import { AnnotationController, FileAnnotationType } from '../annotations/annotationController';
import { Commands, EditorCommand } from './common';
-import { ExtensionKey, FileAnnotationType, IConfig } from '../configuration';
+import { ExtensionKey, IConfig } from '../configuration';
import { Logger } from '../logger';
export interface ShowFileBlameCommandArgs {
diff --git a/src/commands/showLineBlame.ts b/src/commands/showLineBlame.ts
index 340a559..911c148 100644
--- a/src/commands/showLineBlame.ts
+++ b/src/commands/showLineBlame.ts
@@ -1,8 +1,8 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
-import { CurrentLineController } from '../currentLineController';
+import { CurrentLineController, LineAnnotationType } from '../currentLineController';
import { Commands, EditorCommand } from './common';
-import { ExtensionKey, IConfig, LineAnnotationType } from '../configuration';
+import { ExtensionKey, IConfig } from '../configuration';
import { Logger } from '../logger';
export interface ShowLineBlameCommandArgs {
diff --git a/src/commands/toggleFileBlame.ts b/src/commands/toggleFileBlame.ts
index 901d1d2..e0e1eb8 100644
--- a/src/commands/toggleFileBlame.ts
+++ b/src/commands/toggleFileBlame.ts
@@ -1,8 +1,8 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
-import { AnnotationController } from '../annotations/annotationController';
+import { AnnotationController, FileAnnotationType } from '../annotations/annotationController';
import { Commands, EditorCommand } from './common';
-import { ExtensionKey, FileAnnotationType, IConfig } from '../configuration';
+import { ExtensionKey, IConfig } from '../configuration';
import { Logger } from '../logger';
export interface ToggleFileBlameCommandArgs {
diff --git a/src/commands/toggleFileRecentChanges.ts b/src/commands/toggleFileRecentChanges.ts
new file mode 100644
index 0000000..d744f55
--- /dev/null
+++ b/src/commands/toggleFileRecentChanges.ts
@@ -0,0 +1,24 @@
+'use strict';
+import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
+import { AnnotationController, FileAnnotationType } from '../annotations/annotationController';
+import { Commands, EditorCommand } from './common';
+import { Logger } from '../logger';
+
+export class ToggleFileRecentChangesCommand extends EditorCommand {
+
+ constructor(private annotationController: AnnotationController) {
+ super(Commands.ToggleFileRecentChanges);
+ }
+
+ async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri): Promise {
+ if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
+
+ try {
+ return this.annotationController.toggleAnnotations(editor, FileAnnotationType.RecentChanges);
+ }
+ catch (ex) {
+ Logger.error(ex, 'ToggleFileRecentChangesCommand');
+ return window.showErrorMessage(`Unable to toggle recent file changes annotations. See output channel for more details`);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/commands/toggleLineBlame.ts b/src/commands/toggleLineBlame.ts
index 3742ddd..45456b0 100644
--- a/src/commands/toggleLineBlame.ts
+++ b/src/commands/toggleLineBlame.ts
@@ -1,8 +1,8 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
-import { CurrentLineController } from '../currentLineController';
+import { CurrentLineController, LineAnnotationType } from '../currentLineController';
import { Commands, EditorCommand } from './common';
-import { ExtensionKey, IConfig, LineAnnotationType } from '../configuration';
+import { ExtensionKey, IConfig } from '../configuration';
import { Logger } from '../logger';
export interface ToggleLineBlameCommandArgs {
diff --git a/src/configuration.ts b/src/configuration.ts
index f9b137e..ba3bcdf 100644
--- a/src/configuration.ts
+++ b/src/configuration.ts
@@ -1,16 +1,11 @@
'use strict';
+import { FileAnnotationType } from './annotations/annotationController';
import { Commands } from './commands';
+import { LineAnnotationType } from './currentLineController';
import { OutputLevel } from './logger';
export { ExtensionKey } from './constants';
-export type BlameLineHighlightLocations = 'gutter' | 'line' | 'overviewRuler';
-export const BlameLineHighlightLocations = {
- Gutter: 'gutter' as BlameLineHighlightLocations,
- Line: 'line' as BlameLineHighlightLocations,
- OverviewRuler: 'overviewRuler' as BlameLineHighlightLocations
-};
-
export type CodeLensCommand = 'gitlens.toggleFileBlame' |
'gitlens.showBlameHistory' |
'gitlens.showFileHistory' |
@@ -38,16 +33,11 @@ export const CodeLensLocations = {
Custom: 'custom' as CodeLensLocations
};
-export type FileAnnotationType = 'gutter' | 'hover';
-export const FileAnnotationType = {
- Gutter: 'gutter' as FileAnnotationType,
- Hover: 'hover' as FileAnnotationType
-};
-
-export type LineAnnotationType = 'trailing' | 'hover';
-export const LineAnnotationType = {
- Trailing: 'trailing' as LineAnnotationType,
- Hover: 'hover' as LineAnnotationType
+export type LineHighlightLocations = 'gutter' | 'line' | 'overviewRuler';
+export const LineHighlightLocations = {
+ Gutter: 'gutter' as LineHighlightLocations,
+ Line: 'line' as LineHighlightLocations,
+ OverviewRuler: 'overviewRuler' as LineHighlightLocations
};
export type StatusBarCommand = 'gitlens.toggleFileBlame' |
@@ -244,6 +234,13 @@ export interface IConfig {
};
wholeLine: boolean;
};
+
+ recentChanges: {
+ hover: {
+ changes: boolean;
+ wholeLine: boolean;
+ };
+ };
};
line: {
@@ -269,7 +266,7 @@ export interface IConfig {
annotationType: FileAnnotationType;
lineHighlight: {
enabled: boolean;
- locations: BlameLineHighlightLocations[];
+ locations: LineHighlightLocations[];
};
};
@@ -279,6 +276,14 @@ export interface IConfig {
};
};
+ recentChanges: {
+ file: {
+ lineHighlight: {
+ locations: LineHighlightLocations[];
+ };
+ }
+ };
+
codeLens: {
enabled: boolean;
recentChange: {
diff --git a/src/currentLineController.ts b/src/currentLineController.ts
index 9b4b2a1..d9256a6 100644
--- a/src/currentLineController.ts
+++ b/src/currentLineController.ts
@@ -1,11 +1,11 @@
'use strict';
import { Functions, Objects } from './system';
import { DecorationOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
-import { AnnotationController } from './annotations/annotationController';
+import { AnnotationController, FileAnnotationType } from './annotations/annotationController';
import { Annotations, endOfLineIndex } from './annotations/annotations';
import { Commands } from './commands';
import { TextEditorComparer } from './comparers';
-import { FileAnnotationType, IConfig, LineAnnotationType, StatusBarCommand } from './configuration';
+import { IConfig, StatusBarCommand } from './configuration';
import { DocumentSchemes, ExtensionKey } from './constants';
import { BlameabilityChangeEvent, CommitFormatter, GitCommit, GitCommitLine, GitContextTracker, GitService, GitUri } from './gitService';
@@ -16,6 +16,12 @@ const annotationDecoration: TextEditorDecorationType = window.createTextEditorDe
}
} as DecorationRenderOptions);
+export type LineAnnotationType = 'trailing' | 'hover';
+export const LineAnnotationType = {
+ Trailing: 'trailing' as LineAnnotationType,
+ Hover: 'hover' as LineAnnotationType
+};
+
export class CurrentLineController extends Disposable {
private _activeEditorLineDisposable: Disposable | undefined;
@@ -351,6 +357,20 @@ export class CurrentLineController extends Disposable {
}
}
+ break;
+ }
+ case FileAnnotationType.RecentChanges: {
+ const cfgChanges = this._config.annotations.file.recentChanges.hover;
+ if (cfgChanges.changes) {
+ if (cfgChanges.wholeLine) {
+ // Avoid double annotations if we are showing the whole-file hover blame annotations
+ showChanges = false;
+ }
+ else {
+ showChangesInStartingWhitespace = false;
+ }
+ }
+
break;
}
}
diff --git a/src/extension.ts b/src/extension.ts
index ddb7780..9b2bf06 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -8,7 +8,7 @@ import { OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteC
import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands';
import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands';
import { ResetSuppressedWarningsCommand } from './commands';
-import { ShowFileBlameCommand, ShowLineBlameCommand, ToggleFileBlameCommand, ToggleLineBlameCommand } from './commands';
+import { ShowFileBlameCommand, ShowLineBlameCommand, ToggleFileBlameCommand, ToggleFileRecentChangesCommand, ToggleLineBlameCommand } from './commands';
import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands';
import { ShowLastQuickPickCommand } from './commands';
import { ShowQuickBranchHistoryCommand, ShowQuickCurrentBranchHistoryCommand, ShowQuickFileHistoryCommand } from './commands';
@@ -17,9 +17,9 @@ import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand } from './command
import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands';
import { ToggleCodeLensCommand } from './commands';
import { Keyboard } from './commands';
-import { BlameLineHighlightLocations, CodeLensLocations, IConfig, LineAnnotationType } from './configuration';
+import { CodeLensLocations, IConfig, LineHighlightLocations } from './configuration';
import { ApplicationInsightsKey, ExtensionKey, QualifiedExtensionId, WorkspaceState } from './constants';
-import { CurrentLineController } from './currentLineController';
+import { CurrentLineController, LineAnnotationType } from './currentLineController';
import { GitContentProvider } from './gitContentProvider';
import { GitContextTracker, GitService } from './gitService';
import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider';
@@ -107,6 +107,7 @@ export async function activate(context: ExtensionContext) {
context.subscriptions.push(new ShowFileBlameCommand(annotationController));
context.subscriptions.push(new ShowLineBlameCommand(currentLineController));
context.subscriptions.push(new ToggleFileBlameCommand(annotationController));
+ context.subscriptions.push(new ToggleFileRecentChangesCommand(annotationController));
context.subscriptions.push(new ToggleLineBlameCommand(currentLineController));
context.subscriptions.push(new ResetSuppressedWarningsCommand(context));
context.subscriptions.push(new ShowBlameHistoryCommand(git));
@@ -166,10 +167,10 @@ async function migrateSettings(context: ExtensionContext) {
await cfg.update('blame.file.lineHighlight.enabled', false);
break;
case 'gutter':
- await cfg.update('blame.file.lineHighlight.locations', [BlameLineHighlightLocations.Gutter, BlameLineHighlightLocations.OverviewRuler], true);
+ await cfg.update('blame.file.lineHighlight.locations', [LineHighlightLocations.Gutter, LineHighlightLocations.OverviewRuler], true);
break;
case 'line':
- await cfg.update('blame.file.lineHighlight.locations', [BlameLineHighlightLocations.Line, BlameLineHighlightLocations.OverviewRuler], true);
+ await cfg.update('blame.file.lineHighlight.locations', [LineHighlightLocations.Line, LineHighlightLocations.OverviewRuler], true);
break;
case 'both':
}