mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-02-17 02:51:47 -05:00
Changes behaviors when file has unsaved changes:
- Status bar blame information will hide - CodeLens change to a `Cannot determine...` message and become unclickable - Many menu choices and commands will hide Fixes #36 - Blame information is invalid when a file has unsaved changes Fixed #38 - Toggle Blame Annotation button shows even when it isn't valid Preps v2.9.0
This commit is contained in:
3
.vscode/tasks.json
vendored
3
.vscode/tasks.json
vendored
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
// A task runner that calls a custom npm script that compiles the extension.
|
// A task runner that calls a custom npm script that compiles the extension.
|
||||||
{
|
{
|
||||||
"version": "0.1.0",
|
"version": "2.0.0",
|
||||||
"_runner": "terminal",
|
|
||||||
"command": "npm",
|
"command": "npm",
|
||||||
"args": ["run"],
|
"args": ["run"],
|
||||||
"isShellCommand": true,
|
"isShellCommand": true,
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
## Release Notes
|
## Release Notes
|
||||||
|
|
||||||
|
### 2.9.0
|
||||||
|
- To accomodate the realization that blame information is invalid when a file has unsaved changes, the following behavior changes have been made
|
||||||
|
- Status bar blame information will hide
|
||||||
|
- CodeLens change to a `Cannot determine...` message and become unclickable
|
||||||
|
- Many menu choices and commands will hide
|
||||||
|
- Fixes [#38](https://github.com/eamodio/vscode-gitlens/issues/38) - Toggle Blame Annotation button shows even when it isn't valid
|
||||||
|
- Fixes [#36](https://github.com/eamodio/vscode-gitlens/issues/36) - Blame information is invalid when a file has unsaved changes
|
||||||
|
|
||||||
### 2.8.2
|
### 2.8.2
|
||||||
- Adds `gitlens.blame.annotation.dateFormat` to specify how absolute commit dates will be shown in the blame annotations
|
- Adds `gitlens.blame.annotation.dateFormat` to specify how absolute commit dates will be shown in the blame annotations
|
||||||
- Adds `gitlens.statusBar.date` to specify whether and how the commit date will be shown in the blame status bar
|
- Adds `gitlens.statusBar.date` to specify whether and how the commit date will be shown in the blame status bar
|
||||||
|
|||||||
61
package.json
61
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitlens",
|
"name": "gitlens",
|
||||||
"version": "2.8.2",
|
"version": "2.9.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Eric Amodio",
|
"name": "Eric Amodio",
|
||||||
"email": "eamodio@gmail.com"
|
"email": "eamodio@gmail.com"
|
||||||
@@ -444,7 +444,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffLineWithPrevious",
|
"command": "gitlens.diffLineWithPrevious",
|
||||||
"when": "gitlens:enabled"
|
"when": "gitlens:enabled && gitlens:isBlameable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffWithWorking",
|
"command": "gitlens.diffWithWorking",
|
||||||
@@ -452,15 +452,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffLineWithWorking",
|
"command": "gitlens.diffLineWithWorking",
|
||||||
"when": "gitlens:enabled"
|
"when": "gitlens:enabled && gitlens:isBlameable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.showBlame",
|
"command": "gitlens.showBlame",
|
||||||
"when": "gitlens:enabled"
|
"when": "gitlens:enabled && gitlens:isBlameable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.toggleBlame",
|
"command": "gitlens.toggleBlame",
|
||||||
"when": "gitlens:enabled"
|
"when": "gitlens:enabled && gitlens:isBlameable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.toggleCodeLens",
|
"command": "gitlens.toggleCodeLens",
|
||||||
@@ -468,7 +468,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.showBlameHistory",
|
"command": "gitlens.showBlameHistory",
|
||||||
"when": "gitlens:enabled"
|
"when": "gitlens:enabled && gitlens:isBlameable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.showFileHistory",
|
"command": "gitlens.showFileHistory",
|
||||||
@@ -476,7 +476,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.showQuickCommitDetails",
|
"command": "gitlens.showQuickCommitDetails",
|
||||||
"when": "gitlens:enabled"
|
"when": "gitlens:enabled && gitlens:isBlameable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.showQuickFileHistory",
|
"command": "gitlens.showQuickFileHistory",
|
||||||
@@ -492,11 +492,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.copyShaToClipboard",
|
"command": "gitlens.copyShaToClipboard",
|
||||||
"when": "gitlens:enabled"
|
"when": "gitlens:enabled && gitlens:isBlameable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.copyMessageToClipboard",
|
"command": "gitlens.copyMessageToClipboard",
|
||||||
"when": "gitlens:enabled"
|
"when": "gitlens:enabled && gitlens:isBlameable"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"explorer/context": [
|
"explorer/context": [
|
||||||
@@ -507,46 +507,51 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffWithPrevious",
|
"command": "gitlens.diffWithPrevious",
|
||||||
"when": "config.gitlens.menus.diff.enabled && gitlens:enabled",
|
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
"group": "gitlens_diff"
|
"group": "gitlens_diff"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffWithWorking",
|
"command": "gitlens.diffWithWorking",
|
||||||
"when": "config.gitlens.menus.diff.enabled && gitlens:enabled",
|
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
"group": "gitlens_diff"
|
"group": "gitlens_diff"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"editor/title": [
|
"editor/title": [
|
||||||
{
|
{
|
||||||
"command": "gitlens.toggleBlame",
|
"command": "gitlens.toggleBlame",
|
||||||
"when": "gitlens:enabled",
|
"when": "gitlens:enabled && gitlens:isBlameable",
|
||||||
"group": "navigation@100"
|
"group": "navigation@100"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.showQuickFileHistory",
|
"command": "gitlens.showQuickFileHistory",
|
||||||
"when": "gitlens:enabled",
|
"when": "editorFocus && gitlens:enabled",
|
||||||
"group": "1_gitlens@1"
|
"group": "gitlens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickRepoHistory",
|
||||||
|
"when": "!editorFocus && gitlens:enabled",
|
||||||
|
"group": "gitlens"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.showQuickRepoStatus",
|
"command": "gitlens.showQuickRepoStatus",
|
||||||
"when": "gitlens:enabled",
|
"when": "gitlens:enabled",
|
||||||
"group": "1_gitlens@2"
|
"group": "gitlens"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffWithPrevious",
|
"command": "gitlens.diffWithPrevious",
|
||||||
"when": "config.gitlens.menus.diff.enabled && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
"group": "1_gitlens_diff"
|
"group": "gitlens_diff"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffWithWorking",
|
"command": "gitlens.diffWithWorking",
|
||||||
"when": "config.gitlens.menus.diff.enabled && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
"group": "1_gitlens_diff"
|
"group": "gitlens_diff"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"editor/title/context": [
|
"editor/title/context": [
|
||||||
{
|
{
|
||||||
"command": "gitlens.toggleBlame",
|
"command": "gitlens.toggleBlame",
|
||||||
"when": "gitlens:enabled",
|
"when": "gitlens:enabled && gitlens:isBlameable",
|
||||||
"group": "gitlens@1"
|
"group": "gitlens@1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -558,32 +563,32 @@
|
|||||||
"editor/context": [
|
"editor/context": [
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffLineWithPrevious",
|
"command": "gitlens.diffLineWithPrevious",
|
||||||
"when": "editorTextFocus && config.gitlens.menus.diff.enabled && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && gitlens:isBlameable && && config.gitlens.menus.diff.enabled",
|
||||||
"group": "1_gitlens@1"
|
"group": "1_gitlens@1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffLineWithWorking",
|
"command": "gitlens.diffLineWithWorking",
|
||||||
"when": "editorTextFocus && config.gitlens.menus.diff.enabled && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && gitlens:isBlameable && config.gitlens.menus.diff.enabled",
|
||||||
"group": "1_gitlens@2"
|
"group": "1_gitlens@2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.showQuickCommitDetails",
|
"command": "gitlens.showQuickCommitDetails",
|
||||||
"when": "editorTextFocus && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && gitlens:isBlameable",
|
||||||
"group": "1_gitlens@3"
|
"group": "1_gitlens@3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffWithPrevious",
|
"command": "gitlens.diffWithPrevious",
|
||||||
"when": "editorTextFocus && config.gitlens.menus.diff.enabled && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
"group": "1_gitlens-file@1"
|
"group": "1_gitlens-file@1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.diffWithWorking",
|
"command": "gitlens.diffWithWorking",
|
||||||
"when": "editorTextFocus && config.gitlens.menus.diff.enabled && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
"group": "1_gitlens-file@2"
|
"group": "1_gitlens-file@2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.toggleBlame",
|
"command": "gitlens.toggleBlame",
|
||||||
"when": "editorTextFocus && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && gitlens:isBlameable",
|
||||||
"group": "2_gitlens@1"
|
"group": "2_gitlens@1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -594,12 +599,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.copyShaToClipboard",
|
"command": "gitlens.copyShaToClipboard",
|
||||||
"when": "editorTextFocus && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && gitlens:isBlameable",
|
||||||
"group": "9_gitlens@1"
|
"group": "9_gitlens@1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "gitlens.copyMessageToClipboard",
|
"command": "gitlens.copyMessageToClipboard",
|
||||||
"when": "editorTextFocus && gitlens:enabled",
|
"when": "editorTextFocus && gitlens:enabled && gitlens:isBlameable",
|
||||||
"group": "9_gitlens@2"
|
"group": "9_gitlens@2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { Functions, Objects } from './system';
|
import { Functions, Objects } from './system';
|
||||||
import { DecorationOptions, DecorationInstanceRenderOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
import { DecorationOptions, DecorationInstanceRenderOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||||
|
import { BlameabilityChangeEvent, BlameabilityTracker } from './blameabilityTracker';
|
||||||
import { BlameAnnotationController } from './blameAnnotationController';
|
import { BlameAnnotationController } from './blameAnnotationController';
|
||||||
import { BlameAnnotationFormat, BlameAnnotationFormatter } from './blameAnnotationFormatter';
|
import { BlameAnnotationFormat, BlameAnnotationFormatter } from './blameAnnotationFormatter';
|
||||||
import { TextEditorComparer } from './comparers';
|
import { TextEditorComparer } from './comparers';
|
||||||
@@ -19,17 +20,17 @@ export class BlameActiveLineController extends Disposable {
|
|||||||
|
|
||||||
private _activeEditorLineDisposable: Disposable | undefined;
|
private _activeEditorLineDisposable: Disposable | undefined;
|
||||||
private _blame: Promise<IGitBlame> | undefined;
|
private _blame: Promise<IGitBlame> | undefined;
|
||||||
|
private _blameable: boolean;
|
||||||
private _config: IConfig;
|
private _config: IConfig;
|
||||||
private _currentLine: number = -1;
|
private _currentLine: number = -1;
|
||||||
private _disposable: Disposable;
|
private _disposable: Disposable;
|
||||||
private _editor: TextEditor | undefined;
|
private _editor: TextEditor | undefined;
|
||||||
private _editorIsDirty: boolean;
|
|
||||||
private _statusBarItem: StatusBarItem | undefined;
|
private _statusBarItem: StatusBarItem | undefined;
|
||||||
private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
|
private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
|
||||||
private _uri: GitUri;
|
private _uri: GitUri;
|
||||||
private _useCaching: boolean;
|
private _useCaching: boolean;
|
||||||
|
|
||||||
constructor(context: ExtensionContext, private git: GitProvider, private annotationController: BlameAnnotationController) {
|
constructor(context: ExtensionContext, private git: GitProvider, private blameabilityTracker: BlameabilityTracker, private annotationController: BlameAnnotationController) {
|
||||||
super(() => this.dispose());
|
super(() => this.dispose());
|
||||||
|
|
||||||
this._updateBlameDebounced = Functions.debounce(this._updateBlame, 50);
|
this._updateBlameDebounced = Functions.debounce(this._updateBlame, 50);
|
||||||
@@ -93,8 +94,8 @@ export class BlameActiveLineController extends Disposable {
|
|||||||
const subscriptions: Disposable[] = [];
|
const subscriptions: Disposable[] = [];
|
||||||
|
|
||||||
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
||||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onEditorSelectionChanged, this));
|
subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this));
|
||||||
subscriptions.push(workspace.onDidChangeTextDocument(this._onDocumentChanged, this));
|
subscriptions.push(this.blameabilityTracker.onDidChange(this._onBlameabilityChanged, this));
|
||||||
|
|
||||||
this._activeEditorLineDisposable = Disposable.from(...subscriptions);
|
this._activeEditorLineDisposable = Disposable.from(...subscriptions);
|
||||||
}
|
}
|
||||||
@@ -106,6 +107,54 @@ export class BlameActiveLineController extends Disposable {
|
|||||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _onActiveTextEditorChanged(editor: TextEditor) {
|
||||||
|
this._currentLine = -1;
|
||||||
|
|
||||||
|
const previousEditor = this._editor;
|
||||||
|
previousEditor && previousEditor.setDecorations(activeLineDecoration, []);
|
||||||
|
|
||||||
|
if (!editor || !editor.document || (editor.document.isUntitled && editor.document.uri.scheme !== DocumentSchemes.Git) ||
|
||||||
|
(editor.document.uri.scheme !== DocumentSchemes.File && editor.document.uri.scheme !== DocumentSchemes.Git) ||
|
||||||
|
(editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor))) {
|
||||||
|
this.clear(editor);
|
||||||
|
|
||||||
|
this._editor = undefined;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._blameable = editor && editor.document && !editor.document.isDirty;
|
||||||
|
this._editor = editor;
|
||||||
|
this._uri = GitUri.fromUri(editor.document.uri, this.git);
|
||||||
|
const maxLines = this._config.advanced.caching.statusBar.maxLines;
|
||||||
|
this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines);
|
||||||
|
if (this._useCaching) {
|
||||||
|
this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._blame = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateBlame(editor.selection.active.line, editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
|
||||||
|
this._blameable = e.blameable;
|
||||||
|
if (!e.blameable || !this._editor) {
|
||||||
|
this.clear(e.editor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure this is for the editor we are tracking
|
||||||
|
if (!TextEditorComparer.equals(this._editor, e.editor)) return;
|
||||||
|
|
||||||
|
const line = this._editor.selection.active.line;
|
||||||
|
if (line === this._currentLine) return;
|
||||||
|
this._currentLine = line;
|
||||||
|
|
||||||
|
this._updateBlame(this._editor.selection.active.line, this._editor);
|
||||||
|
}
|
||||||
|
|
||||||
private _onBlameAnnotationToggled() {
|
private _onBlameAnnotationToggled() {
|
||||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
}
|
}
|
||||||
@@ -115,39 +164,9 @@ export class BlameActiveLineController extends Disposable {
|
|||||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onActiveTextEditorChanged(e: TextEditor) {
|
private _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): void {
|
||||||
this._currentLine = -1;
|
|
||||||
|
|
||||||
const previousEditor = this._editor;
|
|
||||||
previousEditor && previousEditor.setDecorations(activeLineDecoration, []);
|
|
||||||
|
|
||||||
if (!e || !e.document || (e.document.isUntitled && e.document.uri.scheme !== DocumentSchemes.Git) ||
|
|
||||||
(e.document.uri.scheme !== DocumentSchemes.File && e.document.uri.scheme !== DocumentSchemes.Git) ||
|
|
||||||
(e.viewColumn === undefined && !this.git.hasGitUriForFile(e))) {
|
|
||||||
this.clear(e);
|
|
||||||
|
|
||||||
this._editor = undefined;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._editor = e;
|
|
||||||
this._uri = GitUri.fromUri(e.document.uri, this.git);
|
|
||||||
const maxLines = this._config.advanced.caching.statusBar.maxLines;
|
|
||||||
this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || e.document.lineCount <= maxLines);
|
|
||||||
if (this._useCaching) {
|
|
||||||
this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._blame = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._updateBlame(e.selection.active.line, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onEditorSelectionChanged(e: TextEditorSelectionChangeEvent): void {
|
|
||||||
// Make sure this is for the editor we are tracking
|
// Make sure this is for the editor we are tracking
|
||||||
if (!TextEditorComparer.equals(e.textEditor, this._editor)) return;
|
if (!this._blameable || !TextEditorComparer.equals(this._editor, e.textEditor)) return;
|
||||||
|
|
||||||
const line = e.selections[0].active.line;
|
const line = e.selections[0].active.line;
|
||||||
if (line === this._currentLine) return;
|
if (line === this._currentLine) return;
|
||||||
@@ -156,24 +175,13 @@ export class BlameActiveLineController extends Disposable {
|
|||||||
this._updateBlameDebounced(line, e.textEditor);
|
this._updateBlameDebounced(line, e.textEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onDocumentChanged(e: TextDocumentChangeEvent) {
|
|
||||||
// Make sure this is for the editor we are tracking
|
|
||||||
if (!this._editor || !TextDocumentComparer.equals(e.document, this._editor.document)) return;
|
|
||||||
|
|
||||||
const line = this._editor.selections[0].active.line;
|
|
||||||
if (line === this._currentLine && this._editorIsDirty === this._editor.document.isDirty) return;
|
|
||||||
this._currentLine = line;
|
|
||||||
this._editorIsDirty = this._editor.document.isDirty;
|
|
||||||
|
|
||||||
this._updateBlame(this._editor.selections[0].active.line, this._editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _updateBlame(line: number, editor: TextEditor) {
|
private async _updateBlame(line: number, editor: TextEditor) {
|
||||||
line = line - this._uri.offset;
|
line = line - this._uri.offset;
|
||||||
|
|
||||||
let commitLine: IGitCommitLine;
|
|
||||||
let commit: GitCommit;
|
let commit: GitCommit;
|
||||||
if (line >= 0) {
|
let commitLine: IGitCommitLine;
|
||||||
|
// Since blame information isn't valid when there are unsaved changes -- don't show any status
|
||||||
|
if (this._blameable && line >= 0) {
|
||||||
if (this._useCaching) {
|
if (this._useCaching) {
|
||||||
const blame = this._blame && await this._blame;
|
const blame = this._blame && await this._blame;
|
||||||
if (!blame || !blame.lines.length) {
|
if (!blame || !blame.lines.length) {
|
||||||
@@ -202,6 +210,10 @@ export class BlameActiveLineController extends Disposable {
|
|||||||
|
|
||||||
clear(editor: TextEditor, previousEditor?: TextEditor) {
|
clear(editor: TextEditor, previousEditor?: TextEditor) {
|
||||||
editor && editor.setDecorations(activeLineDecoration, []);
|
editor && editor.setDecorations(activeLineDecoration, []);
|
||||||
|
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
|
||||||
|
if (editor) {
|
||||||
|
setTimeout(() => editor.setDecorations(activeLineDecoration, []), 1);
|
||||||
|
}
|
||||||
|
|
||||||
this._statusBarItem && this._statusBarItem.hide();
|
this._statusBarItem && this._statusBarItem.hide();
|
||||||
}
|
}
|
||||||
@@ -258,20 +270,7 @@ export class BlameActiveLineController extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._config.blame.annotation.activeLine !== 'off') {
|
if (this._config.blame.annotation.activeLine !== 'off') {
|
||||||
let activeLine = this._config.blame.annotation.activeLine;
|
const activeLine = this._config.blame.annotation.activeLine;
|
||||||
|
|
||||||
// Because the inline annotations can be noisy -- only show them if the document isn't dirty
|
|
||||||
if (editor && editor.document && editor.document.isDirty) {
|
|
||||||
editor.setDecorations(activeLineDecoration, []);
|
|
||||||
switch (activeLine) {
|
|
||||||
case 'both':
|
|
||||||
activeLine = 'hover';
|
|
||||||
break;
|
|
||||||
case 'inline':
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset = this._uri.offset;
|
const offset = this._uri.offset;
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { Functions } from './system';
|
import { Functions } from './system';
|
||||||
import { DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, TextDocument, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
|
import { DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, TextDocument, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
|
||||||
|
import { BlameabilityChangeEvent, BlameabilityTracker } from './blameabilityTracker';
|
||||||
import { BlameAnnotationProvider } from './blameAnnotationProvider';
|
import { BlameAnnotationProvider } from './blameAnnotationProvider';
|
||||||
import { TextDocumentComparer, TextEditorComparer } from './comparers';
|
import { TextDocumentComparer, TextEditorComparer } from './comparers';
|
||||||
import { IBlameConfig } from './configuration';
|
import { IBlameConfig } from './configuration';
|
||||||
@@ -8,16 +9,17 @@ import { GitProvider } from './gitProvider';
|
|||||||
import { Logger } from './logger';
|
import { Logger } from './logger';
|
||||||
import { WhitespaceController } from './whitespaceController';
|
import { WhitespaceController } from './whitespaceController';
|
||||||
|
|
||||||
export const blameDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
|
export const BlameDecorations = {
|
||||||
|
annotation: window.createTextEditorDecorationType({
|
||||||
before: {
|
before: {
|
||||||
margin: '0 1.75em 0 0'
|
margin: '0 1.75em 0 0'
|
||||||
},
|
},
|
||||||
after: {
|
after: {
|
||||||
margin: '0 0 0 4em'
|
margin: '0 0 0 4em'
|
||||||
}
|
}
|
||||||
} as DecorationRenderOptions);
|
} as DecorationRenderOptions),
|
||||||
|
highlight: undefined as TextEditorDecorationType
|
||||||
export let highlightDecoration: TextEditorDecorationType;
|
};
|
||||||
|
|
||||||
export class BlameAnnotationController extends Disposable {
|
export class BlameAnnotationController extends Disposable {
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ export class BlameAnnotationController extends Disposable {
|
|||||||
private _disposable: Disposable;
|
private _disposable: Disposable;
|
||||||
private _whitespaceController: WhitespaceController | undefined;
|
private _whitespaceController: WhitespaceController | undefined;
|
||||||
|
|
||||||
constructor(private context: ExtensionContext, private git: GitProvider) {
|
constructor(private context: ExtensionContext, private git: GitProvider, private blameabilityTracker: BlameabilityTracker) {
|
||||||
super(() => this.dispose());
|
super(() => this.dispose());
|
||||||
|
|
||||||
this._onConfigurationChanged();
|
this._onConfigurationChanged();
|
||||||
@@ -47,6 +49,9 @@ export class BlameAnnotationController extends Disposable {
|
|||||||
dispose() {
|
dispose() {
|
||||||
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
|
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
|
||||||
|
|
||||||
|
BlameDecorations.annotation && BlameDecorations.annotation.dispose();
|
||||||
|
BlameDecorations.highlight && BlameDecorations.highlight.dispose();
|
||||||
|
|
||||||
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
|
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
|
||||||
this._whitespaceController && this._whitespaceController.dispose();
|
this._whitespaceController && this._whitespaceController.dispose();
|
||||||
this._disposable && this._disposable.dispose();
|
this._disposable && this._disposable.dispose();
|
||||||
@@ -71,15 +76,11 @@ export class BlameAnnotationController extends Disposable {
|
|||||||
const config = workspace.getConfiguration('gitlens').get<IBlameConfig>('blame');
|
const config = workspace.getConfiguration('gitlens').get<IBlameConfig>('blame');
|
||||||
|
|
||||||
if (config.annotation.highlight !== (this._config && this._config.annotation.highlight)) {
|
if (config.annotation.highlight !== (this._config && this._config.annotation.highlight)) {
|
||||||
highlightDecoration && highlightDecoration.dispose();
|
BlameDecorations.highlight && BlameDecorations.highlight.dispose();
|
||||||
|
|
||||||
switch (config.annotation.highlight) {
|
switch (config.annotation.highlight) {
|
||||||
case 'none':
|
|
||||||
highlightDecoration = undefined;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'gutter':
|
case 'gutter':
|
||||||
highlightDecoration = window.createTextEditorDecorationType({
|
BlameDecorations.highlight = window.createTextEditorDecorationType({
|
||||||
dark: {
|
dark: {
|
||||||
gutterIconPath: this.context.asAbsolutePath('images/blame-dark.svg'),
|
gutterIconPath: this.context.asAbsolutePath('images/blame-dark.svg'),
|
||||||
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
||||||
@@ -94,7 +95,7 @@ export class BlameAnnotationController extends Disposable {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'line':
|
case 'line':
|
||||||
highlightDecoration = window.createTextEditorDecorationType({
|
BlameDecorations.highlight = window.createTextEditorDecorationType({
|
||||||
dark: {
|
dark: {
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||||
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
||||||
@@ -109,7 +110,7 @@ export class BlameAnnotationController extends Disposable {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'both':
|
case 'both':
|
||||||
highlightDecoration = window.createTextEditorDecorationType({
|
BlameDecorations.highlight = window.createTextEditorDecorationType({
|
||||||
dark: {
|
dark: {
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||||
gutterIconPath: this.context.asAbsolutePath('images/blame-dark.svg'),
|
gutterIconPath: this.context.asAbsolutePath('images/blame-dark.svg'),
|
||||||
@@ -125,6 +126,10 @@ export class BlameAnnotationController extends Disposable {
|
|||||||
isWholeLine: true
|
isWholeLine: true
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
BlameDecorations.highlight = undefined;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +177,7 @@ export class BlameAnnotationController extends Disposable {
|
|||||||
subscriptions.push(window.onDidChangeVisibleTextEditors(Functions.debounce(this._onVisibleTextEditorsChanged, 100), this));
|
subscriptions.push(window.onDidChangeVisibleTextEditors(Functions.debounce(this._onVisibleTextEditorsChanged, 100), this));
|
||||||
subscriptions.push(window.onDidChangeTextEditorViewColumn(this._onTextEditorViewColumnChanged, this));
|
subscriptions.push(window.onDidChangeTextEditorViewColumn(this._onTextEditorViewColumnChanged, this));
|
||||||
subscriptions.push(workspace.onDidCloseTextDocument(this._onTextDocumentClosed, this));
|
subscriptions.push(workspace.onDidCloseTextDocument(this._onTextDocumentClosed, this));
|
||||||
|
subscriptions.push(this.blameabilityTracker.onDidChange(this._onBlameabilityChanged, this));
|
||||||
|
|
||||||
this._blameAnnotationsDisposable = Disposable.from(...subscriptions);
|
this._blameAnnotationsDisposable = Disposable.from(...subscriptions);
|
||||||
}
|
}
|
||||||
@@ -202,6 +208,17 @@ export class BlameAnnotationController extends Disposable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
|
||||||
|
if (e.blameable || !e.editor) return;
|
||||||
|
|
||||||
|
for (const [key, p] of this._annotationProviders) {
|
||||||
|
if (!TextDocumentComparer.equals(p.document, e.editor.document)) continue;
|
||||||
|
|
||||||
|
Logger.log('BlameabilityChanged:', `Clear blame annotations for column ${key}`);
|
||||||
|
this.clear(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _onTextDocumentClosed(e: TextDocument) {
|
private _onTextDocumentClosed(e: TextDocument) {
|
||||||
for (const [key, p] of this._annotationProviders) {
|
for (const [key, p] of this._annotationProviders) {
|
||||||
if (!TextDocumentComparer.equals(p.document, e)) continue;
|
if (!TextDocumentComparer.equals(p.document, e)) continue;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { Iterables } from './system';
|
import { Iterables } from './system';
|
||||||
import { DecorationInstanceRenderOptions, DecorationOptions, Disposable, ExtensionContext, Range, TextDocument, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
import { DecorationInstanceRenderOptions, DecorationOptions, Disposable, ExtensionContext, Range, TextDocument, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||||
import { BlameAnnotationFormat, BlameAnnotationFormatter, cssIndent, defaultShaLength, defaultAuthorLength } from './blameAnnotationFormatter';
|
import { BlameAnnotationFormat, BlameAnnotationFormatter, cssIndent, defaultShaLength, defaultAuthorLength } from './blameAnnotationFormatter';
|
||||||
import { blameDecoration, highlightDecoration } from './blameAnnotationController';
|
import { BlameDecorations } from './blameAnnotationController';
|
||||||
import { TextDocumentComparer } from './comparers';
|
import { TextDocumentComparer } from './comparers';
|
||||||
import { BlameAnnotationStyle, IBlameConfig } from './configuration';
|
import { BlameAnnotationStyle, IBlameConfig } from './configuration';
|
||||||
import { GitProvider, GitUri, IGitBlame } from './gitProvider';
|
import { GitProvider, GitUri, IGitBlame } from './gitProvider';
|
||||||
@@ -36,8 +36,15 @@ export class BlameAnnotationProvider extends Disposable {
|
|||||||
|
|
||||||
async dispose() {
|
async dispose() {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
this.editor.setDecorations(blameDecoration, []);
|
try {
|
||||||
highlightDecoration && this.editor.setDecorations(highlightDecoration, []);
|
this.editor.setDecorations(BlameDecorations.annotation, []);
|
||||||
|
BlameDecorations.highlight && this.editor.setDecorations(BlameDecorations.highlight, []);
|
||||||
|
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
|
||||||
|
if (BlameDecorations.highlight) {
|
||||||
|
setTimeout(() => this.editor.setDecorations(BlameDecorations.highlight, []), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- restore whitespace
|
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- restore whitespace
|
||||||
@@ -80,7 +87,7 @@ export class BlameAnnotationProvider extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (blameDecorationOptions) {
|
if (blameDecorationOptions) {
|
||||||
this.editor.setDecorations(blameDecoration, blameDecorationOptions);
|
this.editor.setDecorations(BlameDecorations.annotation, blameDecorationOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._setSelection(blame, shaOrLine);
|
this._setSelection(blame, shaOrLine);
|
||||||
@@ -95,7 +102,7 @@ export class BlameAnnotationProvider extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _setSelection(blame: IGitBlame, shaOrLine?: string | number) {
|
private _setSelection(blame: IGitBlame, shaOrLine?: string | number) {
|
||||||
if (!highlightDecoration) return;
|
if (!BlameDecorations.highlight) return;
|
||||||
|
|
||||||
const offset = this._uri.offset;
|
const offset = this._uri.offset;
|
||||||
|
|
||||||
@@ -115,7 +122,7 @@ export class BlameAnnotationProvider extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sha) {
|
if (!sha) {
|
||||||
this.editor.setDecorations(highlightDecoration, []);
|
this.editor.setDecorations(BlameDecorations.highlight, []);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +130,7 @@ export class BlameAnnotationProvider extends Disposable {
|
|||||||
.filter(l => l.sha === sha)
|
.filter(l => l.sha === sha)
|
||||||
.map(l => this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)));
|
.map(l => this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)));
|
||||||
|
|
||||||
this.editor.setDecorations(highlightDecoration, highlightDecorationRanges);
|
this.editor.setDecorations(BlameDecorations.highlight, highlightDecorationRanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] {
|
private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] {
|
||||||
|
|||||||
95
src/blameabilityTracker.ts
Normal file
95
src/blameabilityTracker.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, Disposable, Event, EventEmitter, TextDocument, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode';
|
||||||
|
import { TextDocumentComparer } from './comparers';
|
||||||
|
import { BuiltInCommands } from './constants';
|
||||||
|
import { GitProvider } from './gitProvider';
|
||||||
|
|
||||||
|
export interface BlameabilityChangeEvent {
|
||||||
|
blameable: boolean;
|
||||||
|
editor: TextEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlameabilityTracker extends Disposable {
|
||||||
|
|
||||||
|
private _onDidChange = new EventEmitter<BlameabilityChangeEvent>();
|
||||||
|
get onDidChange(): Event<BlameabilityChangeEvent> {
|
||||||
|
return this._onDidChange.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _disposable: Disposable;
|
||||||
|
private _documentChangeDisposable: Disposable;
|
||||||
|
private _editor: TextEditor;
|
||||||
|
private _isBlameable: boolean;
|
||||||
|
|
||||||
|
constructor(private git: GitProvider) {
|
||||||
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
const subscriptions: Disposable[] = [];
|
||||||
|
|
||||||
|
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
||||||
|
subscriptions.push(workspace.onDidSaveTextDocument(this._onTextDocumentSaved, this));
|
||||||
|
subscriptions.push(this.git.onDidBlameFail(this._onBlameFailed, this));
|
||||||
|
|
||||||
|
this._disposable = Disposable.from(...subscriptions);
|
||||||
|
|
||||||
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._disposable && this._disposable.dispose();
|
||||||
|
this._documentChangeDisposable && this._documentChangeDisposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onActiveTextEditorChanged(editor: TextEditor) {
|
||||||
|
this._editor = editor;
|
||||||
|
let blameable = editor && editor.document && !editor.document.isDirty;
|
||||||
|
|
||||||
|
if (blameable) {
|
||||||
|
blameable = this.git.getBlameability(editor.document.fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._subscribeToDocumentChanges();
|
||||||
|
this.updateBlameability(blameable, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onBlameFailed(key: string) {
|
||||||
|
const fileName = this._editor && this._editor.document && this._editor.document.fileName;
|
||||||
|
if (!fileName || key !== this.git.getCacheEntryKey(fileName)) return;
|
||||||
|
|
||||||
|
this.updateBlameability(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onTextDocumentChanged(e: TextDocumentChangeEvent) {
|
||||||
|
if (!TextDocumentComparer.equals(this._editor && this._editor.document, e && e.document)) return;
|
||||||
|
|
||||||
|
this._unsubscribeToDocumentChanges();
|
||||||
|
this.updateBlameability(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onTextDocumentSaved(e: TextDocument) {
|
||||||
|
if (!TextDocumentComparer.equals(this._editor && this._editor.document, e)) return;
|
||||||
|
|
||||||
|
this._subscribeToDocumentChanges();
|
||||||
|
this.updateBlameability(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _subscribeToDocumentChanges() {
|
||||||
|
this._unsubscribeToDocumentChanges();
|
||||||
|
this._documentChangeDisposable = workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _unsubscribeToDocumentChanges() {
|
||||||
|
this._documentChangeDisposable && this._documentChangeDisposable.dispose();
|
||||||
|
this._documentChangeDisposable = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateBlameability(blameable: boolean, force: boolean = false) {
|
||||||
|
if (!force && this._isBlameable === blameable) return;
|
||||||
|
|
||||||
|
commands.executeCommand(BuiltInCommands.SetContext, 'gitlens:isBlameable', blameable);
|
||||||
|
this._onDidChange.fire({
|
||||||
|
blameable: blameable,
|
||||||
|
editor: this._editor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,9 @@ export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
|
|||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
if (!sha) {
|
if (!sha) {
|
||||||
const line = editor.selection.active.line;
|
if (editor && editor.document && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
|
const line = (editor && editor.selection.active.line) || gitUri.offset;
|
||||||
const blameline = line - gitUri.offset;
|
const blameline = line - gitUri.offset;
|
||||||
if (blameline < 0) return undefined;
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ export class CopyShaToClipboardCommand extends ActiveEditorCommand {
|
|||||||
const gitUri = GitUri.fromUri(uri, this.git);
|
const gitUri = GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
if (!sha) {
|
if (!sha) {
|
||||||
const line = editor.selection.active.line;
|
if (editor && editor.document && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
|
const line = (editor && editor.selection.active.line) || gitUri.offset;
|
||||||
const blameline = line - gitUri.offset;
|
const blameline = line - gitUri.offset;
|
||||||
if (blameline < 0) return undefined;
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
|
|||||||
uri = editor.document.uri;
|
uri = editor.document.uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
line = line ||(editor && editor.selection.active.line) || 0;
|
const gitUri = GitUri.fromUri(uri, this.git);
|
||||||
let gitUri = GitUri.fromUri(uri, this.git);
|
line = line || (editor && editor.selection.active.line) || gitUri.offset;
|
||||||
|
|
||||||
if (!commit || GitProvider.isUncommitted(commit.sha)) {
|
if (!commit || GitProvider.isUncommitted(commit.sha)) {
|
||||||
|
if (editor && editor.document && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
const blameline = line - gitUri.offset;
|
const blameline = line - gitUri.offset;
|
||||||
if (blameline < 0) return undefined;
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
|
|||||||
uri = editor.document.uri;
|
uri = editor.document.uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
line = line || (editor && editor.selection.active.line) || 0;
|
const gitUri = GitUri.fromUri(uri, this.git);
|
||||||
|
line = line || (editor && editor.selection.active.line) || gitUri.offset;
|
||||||
|
|
||||||
if (!commit || GitProvider.isUncommitted(commit.sha)) {
|
if (!commit || GitProvider.isUncommitted(commit.sha)) {
|
||||||
const gitUri = GitUri.fromUri(uri, this.git);
|
if (editor && editor.document && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
const blameline = line - gitUri.offset;
|
const blameline = line - gitUri.offset;
|
||||||
if (blameline < 0) return undefined;
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { commands, ExtensionContext, languages, window, workspace } from 'vscode';
|
import { commands, ExtensionContext, languages, window, workspace } from 'vscode';
|
||||||
|
import { BlameabilityTracker } from './blameabilityTracker';
|
||||||
import { BlameActiveLineController } from './blameActiveLineController';
|
import { BlameActiveLineController } from './blameActiveLineController';
|
||||||
import { BlameAnnotationController } from './blameAnnotationController';
|
import { BlameAnnotationController } from './blameAnnotationController';
|
||||||
import { configureCssCharacters } from './blameAnnotationFormatter';
|
import { configureCssCharacters } from './blameAnnotationFormatter';
|
||||||
@@ -63,14 +64,17 @@ export async function activate(context: ExtensionContext) {
|
|||||||
const git = new GitProvider(context);
|
const git = new GitProvider(context);
|
||||||
context.subscriptions.push(git);
|
context.subscriptions.push(git);
|
||||||
|
|
||||||
|
const blameabilityTracker = new BlameabilityTracker(git);
|
||||||
|
context.subscriptions.push(blameabilityTracker);
|
||||||
|
|
||||||
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, git)));
|
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, git)));
|
||||||
|
|
||||||
context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider(context, git)));
|
context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider(context, git)));
|
||||||
|
|
||||||
const annotationController = new BlameAnnotationController(context, git);
|
const annotationController = new BlameAnnotationController(context, git, blameabilityTracker);
|
||||||
context.subscriptions.push(annotationController);
|
context.subscriptions.push(annotationController);
|
||||||
|
|
||||||
const activeLineController = new BlameActiveLineController(context, git, annotationController);
|
const activeLineController = new BlameActiveLineController(context, git, blameabilityTracker, annotationController);
|
||||||
context.subscriptions.push(activeLineController);
|
context.subscriptions.push(activeLineController);
|
||||||
|
|
||||||
context.subscriptions.push(new Keyboard(context));
|
context.subscriptions.push(new Keyboard(context));
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
|||||||
static selector: DocumentSelector = { scheme: DocumentSchemes.File };
|
static selector: DocumentSelector = { scheme: DocumentSchemes.File };
|
||||||
|
|
||||||
private _config: IConfig;
|
private _config: IConfig;
|
||||||
|
private _documentIsDirty: boolean;
|
||||||
|
|
||||||
constructor(context: ExtensionContext, private git: GitProvider) {
|
constructor(context: ExtensionContext, private git: GitProvider) {
|
||||||
this._config = workspace.getConfiguration('').get<IConfig>('gitlens');
|
this._config = workspace.getConfiguration('').get<IConfig>('gitlens');
|
||||||
@@ -53,6 +54,8 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
|
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
|
||||||
|
this._documentIsDirty = document.isDirty;
|
||||||
|
|
||||||
let languageLocations = this._config.codeLens.languageLocations.find(_ => _.language.toLowerCase() === document.languageId);
|
let languageLocations = this._config.codeLens.languageLocations.find(_ => _.language.toLowerCase() === document.languageId);
|
||||||
if (languageLocations == null) {
|
if (languageLocations == null) {
|
||||||
languageLocations = {
|
languageLocations = {
|
||||||
@@ -93,7 +96,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
|||||||
if (!lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0)) {
|
if (!lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0)) {
|
||||||
const blameRange = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
|
const blameRange = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
|
||||||
let blameForRangeFn: () => IGitBlameLines;
|
let blameForRangeFn: () => IGitBlameLines;
|
||||||
if (this._config.codeLens.recentChange.enabled) {
|
if (this._documentIsDirty || this._config.codeLens.recentChange.enabled) {
|
||||||
blameForRangeFn = Functions.once(() => this.git.getBlameForRangeSync(blame, gitUri.fsPath, blameRange, gitUri.sha, gitUri.repoPath));
|
blameForRangeFn = Functions.once(() => this.git.getBlameForRangeSync(blame, gitUri.fsPath, blameRange, gitUri.sha, gitUri.repoPath));
|
||||||
lenses.push(new GitRecentChangeCodeLens(blameForRangeFn, gitUri, SymbolKind.File, blameRange, true, new Range(0, 0, 0, blameRange.start.character)));
|
lenses.push(new GitRecentChangeCodeLens(blameForRangeFn, gitUri, SymbolKind.File, blameRange, true, new Range(0, 0, 0, blameRange.start.character)));
|
||||||
}
|
}
|
||||||
@@ -101,10 +104,12 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
|||||||
if (!blameForRangeFn) {
|
if (!blameForRangeFn) {
|
||||||
blameForRangeFn = Functions.once(() => this.git.getBlameForRangeSync(blame, gitUri.fsPath, blameRange, gitUri.sha, gitUri.repoPath));
|
blameForRangeFn = Functions.once(() => this.git.getBlameForRangeSync(blame, gitUri.fsPath, blameRange, gitUri.sha, gitUri.repoPath));
|
||||||
}
|
}
|
||||||
|
if (!this._documentIsDirty) {
|
||||||
lenses.push(new GitAuthorsCodeLens(blameForRangeFn, gitUri, SymbolKind.File, blameRange, true, new Range(0, 1, 0, blameRange.start.character)));
|
lenses.push(new GitAuthorsCodeLens(blameForRangeFn, gitUri, SymbolKind.File, blameRange, true, new Range(0, 1, 0, blameRange.start.character)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return lenses;
|
return lenses;
|
||||||
}
|
}
|
||||||
@@ -158,7 +163,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let blameForRangeFn: () => IGitBlameLines;
|
let blameForRangeFn: () => IGitBlameLines;
|
||||||
if (this._config.codeLens.recentChange.enabled) {
|
if (this._documentIsDirty || this._config.codeLens.recentChange.enabled) {
|
||||||
blameForRangeFn = Functions.once(() => this.git.getBlameForRangeSync(blame, gitUri.fsPath, symbol.location.range, gitUri.sha, gitUri.repoPath));
|
blameForRangeFn = Functions.once(() => this.git.getBlameForRangeSync(blame, gitUri.fsPath, symbol.location.range, gitUri.sha, gitUri.repoPath));
|
||||||
lenses.push(new GitRecentChangeCodeLens(blameForRangeFn, gitUri, symbol.kind, symbol.location.range, false, line.range.with(new Position(line.range.start.line, startChar))));
|
lenses.push(new GitRecentChangeCodeLens(blameForRangeFn, gitUri, symbol.kind, symbol.location.range, false, line.range.with(new Position(line.range.start.line, startChar))));
|
||||||
startChar++;
|
startChar++;
|
||||||
@@ -188,10 +193,12 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
|||||||
if (!blameForRangeFn) {
|
if (!blameForRangeFn) {
|
||||||
blameForRangeFn = Functions.once(() => this.git.getBlameForRangeSync(blame, gitUri.fsPath, symbol.location.range, gitUri.sha, gitUri.repoPath));
|
blameForRangeFn = Functions.once(() => this.git.getBlameForRangeSync(blame, gitUri.fsPath, symbol.location.range, gitUri.sha, gitUri.repoPath));
|
||||||
}
|
}
|
||||||
|
if (!this._documentIsDirty) {
|
||||||
lenses.push(new GitAuthorsCodeLens(blameForRangeFn, gitUri, symbol.kind, symbol.location.range, false, line.range.with(new Position(line.range.start.line, startChar))));
|
lenses.push(new GitAuthorsCodeLens(blameForRangeFn, gitUri, symbol.kind, symbol.location.range, false, line.range.with(new Position(line.range.start.line, startChar))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolveCodeLens(lens: CodeLens, token: CancellationToken): CodeLens | Thenable<CodeLens> {
|
resolveCodeLens(lens: CodeLens, token: CancellationToken): CodeLens | Thenable<CodeLens> {
|
||||||
if (lens instanceof GitRecentChangeCodeLens) return this._resolveGitRecentChangeCodeLens(lens, token);
|
if (lens instanceof GitRecentChangeCodeLens) return this._resolveGitRecentChangeCodeLens(lens, token);
|
||||||
@@ -200,10 +207,30 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_resolveGitRecentChangeCodeLens(lens: GitRecentChangeCodeLens, token: CancellationToken): CodeLens {
|
_resolveGitRecentChangeCodeLens(lens: GitRecentChangeCodeLens, token: CancellationToken): CodeLens {
|
||||||
|
// Since blame information isn't valid when there are unsaved changes -- update the lenses appropriately
|
||||||
|
let title: string;
|
||||||
|
if (this._documentIsDirty) {
|
||||||
|
if (this._config.codeLens.recentChange.enabled && this._config.codeLens.authors.enabled) {
|
||||||
|
title = 'Cannot determine recent change or authors (unsaved changes)';
|
||||||
|
}
|
||||||
|
else if (this._config.codeLens.recentChange.enabled) {
|
||||||
|
title = 'Cannot determine recent change (unsaved changes)';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
title = 'Cannot determine authors (unsaved changes)';
|
||||||
|
}
|
||||||
|
|
||||||
|
lens.command = {
|
||||||
|
title: title,
|
||||||
|
command: undefined
|
||||||
|
};
|
||||||
|
return lens;
|
||||||
|
}
|
||||||
|
|
||||||
const blame = lens.getBlame();
|
const blame = lens.getBlame();
|
||||||
|
|
||||||
const recentCommit = Iterables.first(blame.commits.values());
|
const recentCommit = Iterables.first(blame.commits.values());
|
||||||
let title = `${recentCommit.author}, ${moment(recentCommit.date).fromNow()}`;
|
title = `${recentCommit.author}, ${moment(recentCommit.date).fromNow()}`;
|
||||||
if (this._config.advanced.debug && this._config.advanced.output.level === OutputLevel.Verbose) {
|
if (this._config.advanced.debug && this._config.advanced.output.level === OutputLevel.Verbose) {
|
||||||
title += ` [${recentCommit.sha}, Symbol(${SymbolKind[lens.symbolKind]}), Lines(${lens.blameRange.start.line + 1}-${lens.blameRange.end.line + 1})]`;
|
title += ` [${recentCommit.sha}, Symbol(${SymbolKind[lens.symbolKind]}), Lines(${lens.blameRange.start.line + 1}-${lens.blameRange.end.line + 1})]`;
|
||||||
}
|
}
|
||||||
@@ -223,7 +250,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
|||||||
_resolveGitAuthorsCodeLens(lens: GitAuthorsCodeLens, token: CancellationToken): CodeLens {
|
_resolveGitAuthorsCodeLens(lens: GitAuthorsCodeLens, token: CancellationToken): CodeLens {
|
||||||
const blame = lens.getBlame();
|
const blame = lens.getBlame();
|
||||||
const count = blame.authors.size;
|
const count = blame.authors.size;
|
||||||
const title = `${count} ${count > 1 ? 'authors' : 'author'} (${Iterables.first(blame.authors.values()).name}${count > 1 ? ' and others' : ''})`;
|
let title = `${count} ${count > 1 ? 'authors' : 'author'} (${Iterables.first(blame.authors.values()).name}${count > 1 ? ' and others' : ''})`;
|
||||||
|
|
||||||
switch (this._config.codeLens.authors.command) {
|
switch (this._config.codeLens.authors.command) {
|
||||||
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand<GitAuthorsCodeLens>(title, lens, blame);
|
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand<GitAuthorsCodeLens>(title, lens, blame);
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ export class GitProvider extends Disposable {
|
|||||||
return this._onDidChangeGitCacheEmitter.event;
|
return this._onDidChangeGitCacheEmitter.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _onDidBlameFailEmitter = new EventEmitter<string>();
|
||||||
|
get onDidBlameFail(): Event<string> {
|
||||||
|
return this._onDidBlameFailEmitter.event;
|
||||||
|
}
|
||||||
|
|
||||||
private _gitCache: Map<string, GitCacheEntry> | undefined;
|
private _gitCache: Map<string, GitCacheEntry> | undefined;
|
||||||
private _cacheDisposable: Disposable | undefined;
|
private _cacheDisposable: Disposable | undefined;
|
||||||
private _repoPath: string;
|
private _repoPath: string;
|
||||||
@@ -100,6 +105,12 @@ export class GitProvider extends Disposable {
|
|||||||
this._uriCache = undefined;
|
this._uriCache = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getBlameability(fileName: string): boolean {
|
||||||
|
const cacheKey = this.getCacheEntryKey(Git.normalizePath(fileName));
|
||||||
|
const entry = this._gitCache.get(cacheKey);
|
||||||
|
return !(entry && entry.hasErrors);
|
||||||
|
}
|
||||||
|
|
||||||
public get UseUriCaching() {
|
public get UseUriCaching() {
|
||||||
return !!this._uriCache;
|
return !!this._uriCache;
|
||||||
}
|
}
|
||||||
@@ -189,7 +200,7 @@ export class GitProvider extends Disposable {
|
|||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getCacheEntryKey(fileName: string) {
|
getCacheEntryKey(fileName: string) {
|
||||||
return fileName.toLowerCase();
|
return fileName.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +217,7 @@ export class GitProvider extends Disposable {
|
|||||||
|
|
||||||
const fileName = Git.normalizePath(document.fileName);
|
const fileName = Git.normalizePath(document.fileName);
|
||||||
|
|
||||||
const cacheKey = this._getCacheEntryKey(fileName);
|
const cacheKey = this.getCacheEntryKey(fileName);
|
||||||
|
|
||||||
if (reason === RemoveCacheReason.DocumentSaved) {
|
if (reason === RemoveCacheReason.DocumentSaved) {
|
||||||
// Don't remove broken blame on save (since otherwise we'll have to run the broken blame again)
|
// Don't remove broken blame on save (since otherwise we'll have to run the broken blame again)
|
||||||
@@ -240,7 +251,7 @@ export class GitProvider extends Disposable {
|
|||||||
fileName = fileNameOrEditor.document.uri.fsPath;
|
fileName = fileNameOrEditor.document.uri.fsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheKey = this._getCacheEntryKey(fileName);
|
const cacheKey = this.getCacheEntryKey(fileName);
|
||||||
return this._uriCache.has(cacheKey);
|
return this._uriCache.has(cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +282,7 @@ export class GitProvider extends Disposable {
|
|||||||
getGitUriForFile(fileName: string) {
|
getGitUriForFile(fileName: string) {
|
||||||
if (!this.UseUriCaching) return undefined;
|
if (!this.UseUriCaching) return undefined;
|
||||||
|
|
||||||
const cacheKey = this._getCacheEntryKey(fileName);
|
const cacheKey = this.getCacheEntryKey(fileName);
|
||||||
const entry = this._uriCache.get(cacheKey);
|
const entry = this._uriCache.get(cacheKey);
|
||||||
return entry && entry.uri;
|
return entry && entry.uri;
|
||||||
}
|
}
|
||||||
@@ -294,7 +305,7 @@ export class GitProvider extends Disposable {
|
|||||||
let cacheKey: string | undefined;
|
let cacheKey: string | undefined;
|
||||||
let entry: GitCacheEntry | undefined;
|
let entry: GitCacheEntry | undefined;
|
||||||
if (useCaching) {
|
if (useCaching) {
|
||||||
cacheKey = this._getCacheEntryKey(fileName);
|
cacheKey = this.getCacheEntryKey(fileName);
|
||||||
entry = this._gitCache.get(cacheKey);
|
entry = this._gitCache.get(cacheKey);
|
||||||
|
|
||||||
if (entry !== undefined && entry.blame !== undefined) return entry.blame.item;
|
if (entry !== undefined && entry.blame !== undefined) return entry.blame.item;
|
||||||
@@ -306,6 +317,9 @@ export class GitProvider extends Disposable {
|
|||||||
const promise = this._gitignore.then(ignore => {
|
const promise = this._gitignore.then(ignore => {
|
||||||
if (ignore && !ignore.filter([fileName]).length) {
|
if (ignore && !ignore.filter([fileName]).length) {
|
||||||
Logger.log(`Skipping blame; '${fileName}' is gitignored`);
|
Logger.log(`Skipping blame; '${fileName}' is gitignored`);
|
||||||
|
if (cacheKey) {
|
||||||
|
this._onDidBlameFailEmitter.fire(cacheKey);
|
||||||
|
}
|
||||||
return GitProvider.EmptyPromise as Promise<IGitBlame>;
|
return GitProvider.EmptyPromise as Promise<IGitBlame>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,6 +337,7 @@ export class GitProvider extends Disposable {
|
|||||||
errorMessage: msg
|
errorMessage: msg
|
||||||
} as ICachedBlame;
|
} as ICachedBlame;
|
||||||
|
|
||||||
|
this._onDidBlameFailEmitter.fire(cacheKey);
|
||||||
this._gitCache.set(cacheKey, entry);
|
this._gitCache.set(cacheKey, entry);
|
||||||
return GitProvider.EmptyPromise as Promise<IGitBlame>;
|
return GitProvider.EmptyPromise as Promise<IGitBlame>;
|
||||||
}
|
}
|
||||||
@@ -485,7 +500,7 @@ export class GitProvider extends Disposable {
|
|||||||
let cacheKey: string;
|
let cacheKey: string;
|
||||||
let entry: GitCacheEntry;
|
let entry: GitCacheEntry;
|
||||||
if (useCaching) {
|
if (useCaching) {
|
||||||
cacheKey = this._getCacheEntryKey(fileName);
|
cacheKey = this.getCacheEntryKey(fileName);
|
||||||
entry = this._gitCache.get(cacheKey);
|
entry = this._gitCache.get(cacheKey);
|
||||||
|
|
||||||
if (entry !== undefined && entry.log !== undefined) return entry.log.item;
|
if (entry !== undefined && entry.log !== undefined) return entry.log.item;
|
||||||
@@ -583,7 +598,7 @@ export class GitProvider extends Disposable {
|
|||||||
|
|
||||||
const file = await Git.getVersionedFile(fileName, repoPath, sha);
|
const file = await Git.getVersionedFile(fileName, repoPath, sha);
|
||||||
if (this.UseUriCaching) {
|
if (this.UseUriCaching) {
|
||||||
const cacheKey = this._getCacheEntryKey(file);
|
const cacheKey = this.getCacheEntryKey(file);
|
||||||
const entry = new UriCacheEntry(new GitUri(Uri.file(fileName), { sha, repoPath, fileName }));
|
const entry = new UriCacheEntry(new GitUri(Uri.file(fileName), { sha, repoPath, fileName }));
|
||||||
this._uriCache.set(cacheKey, entry);
|
this._uriCache.set(cacheKey, entry);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user