Browse Source

Adds support for git commands on scheme=git

Rewrites blame annotation controller and provider - fixes whitespace issues, reduces overhead, and provides better performance
Rewrites status bar blame support - reduces overhead and provides better performance
Adds showFileHistory command to status bar
Renames showHistory to showFileHistory
Fixes log to use iso 8601 for dates
main
Eric Amodio 8 years ago
parent
commit
638a6dc838
29 changed files with 615 additions and 282 deletions
  1. +7
    -2
      CHANGELOG.md
  2. +3
    -3
      README.md
  3. +19
    -11
      package.json
  4. +154
    -34
      src/blameAnnotationController.ts
  5. +70
    -33
      src/blameAnnotationProvider.ts
  6. +73
    -23
      src/blameStatusBarController.ts
  7. +15
    -10
      src/commands/diffLineWithPrevious.ts
  8. +16
    -11
      src/commands/diffLineWithWorking.ts
  9. +14
    -10
      src/commands/diffWithPrevious.ts
  10. +16
    -11
      src/commands/diffWithWorking.ts
  11. +8
    -15
      src/commands/showBlame.ts
  12. +4
    -2
      src/commands/showBlameHistory.ts
  13. +7
    -5
      src/commands/showFileHistory.ts
  14. +8
    -15
      src/commands/toggleBlame.ts
  15. +42
    -0
      src/comparers.ts
  16. +8
    -4
      src/configuration.ts
  17. +2
    -2
      src/constants.ts
  18. +4
    -4
      src/extension.ts
  19. +4
    -4
      src/git/enrichers/blameParserEnricher.ts
  20. +5
    -5
      src/git/enrichers/logParserEnricher.ts
  21. +3
    -3
      src/git/git.ts
  22. +1
    -0
      src/git/gitLocator.ts
  23. +1
    -1
      src/gitBlameCodeLensProvider.ts
  24. +7
    -7
      src/gitCodeLensProvider.ts
  25. +16
    -5
      src/gitContentProvider.ts
  26. +72
    -34
      src/gitProvider.ts
  27. +1
    -0
      src/system.ts
  28. +7
    -0
      src/system/iterable.ts

+ 7
- 2
CHANGELOG.md View File

@ -4,17 +4,22 @@
### 0.9.0
- Adds support for git history (log)!
- Adds new `gitlens.showHistory` command to open the history explorer
- Adds new `gitlens.showHistory` option to the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings
- Adds support for blame annotations and git commands on file revisions
- Adds ability to show multiple blame annotation at the same time (one per vscode editor)
- Adds new `gitlens.showFileHistory` command to open the history explorer
- Adds new `gitlens.showFileHistory` option to the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings
- Adds per-language CodeLens location customization using the `gitlens.codeLens.languageLocations` setting
- Adds new `gitlens.diffLineWithPrevious` command for line sensitive diffs
- Adds new `gitlens.diffLineWithWorking` command for line sensitive diffs
- Adds `gitlens.diffWithPrevious` command to the explorer context menu
- Adds output channel logging, controlled by the `gitlens.advanced.output.level` setting
- Complete rewrite of the blame annotation provider to reduce overhead and provide better performance
- Improves performance (significantly) when only showing CodeLens at the document level
- Improves performance of status bar blame support
- Changes `gitlens.diffWithPrevious` command to always be file sensitive diffs
- Changes `gitlens.diffWithWorking` command to always be file sensitive diffs
- Removes all debug logging, unless the `gitlens.advanced.debug` settings it on
- Fixes many (most?) issues with whitespace toggling (required because of https://github.com/Microsoft/vscode/issues/11485)
- Fixes issue where blame annotations would not be cleared properly when switching between open files
### 0.5.5

+ 3
- 3
README.md View File

@ -41,13 +41,13 @@ Must be using Git and it must be in your path.
|`gitlens.codeLens.locationCustomSymbols`|Specifies the set of document symbols to render active document CodeLens on. Must be a member of `SymbolKind`
|`gitlens.codeLens.languageLocations`|Specifies where CodeLens will be rendered in the active document for the specified languages
|`gitlens.codeLens.recentChange.enabled`|Specifies whether the recent change CodeLens is shown
|`gitlens.codeLens.recentChange.command`|Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|`gitlens.codeLens.recentChange.command`|Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|`gitlens.codeLens.authors.enabled`|Specifies whether the authors CodeLens is shown
|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|`gitlens.menus.fileDiff.enabled`|Specifies whether file-based diff commands will be added to the context menus
|`gitlens.menus.lineDiff.enabled`|Specifies whether line-based diff commands will be added to the context menus
|`gitlens.statusBar.enabled`|Specifies whether blame information is shown in the status bar
|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
---
## Known Issues

+ 19
- 11
package.json View File

@ -156,15 +156,15 @@
},
"gitlens.codeLens.recentChange.command": {
"type": "string",
"default": "gitlens.showHistory",
"default": "gitlens.showFileHistory",
"enum": [
"gitlens.toggleBlame",
"gitlens.showBlameHistory",
"gitlens.showHistory",
"gitlens.showFileHistory",
"gitlens.diffWithPrevious",
"git.viewFileHistory"
],
"description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
"description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
},
"gitlens.codeLens.authors.enabled": {
"type": "boolean",
@ -177,11 +177,11 @@
"enum": [
"gitlens.toggleBlame",
"gitlens.showBlameHistory",
"gitlens.showHistory",
"gitlens.showFileHistory",
"gitlens.diffWithPrevious",
"git.viewFileHistory"
],
"description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
"description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
},
"gitlens.statusBar.enabled": {
"type": "boolean",
@ -194,12 +194,12 @@
"enum": [
"gitlens.toggleBlame",
"gitlens.showBlameHistory",
"gitlens.showHistory",
"gitlens.showFileHistory",
"gitlens.diffWithPrevious",
"gitlens.toggleCodeLens",
"git.viewFileHistory"
],
"description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
"description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
},
"gitlens.menus.fileDiff.enabled": {
"type": "boolean",
@ -208,7 +208,7 @@
},
"gitlens.menus.lineDiff.enabled": {
"type": "boolean",
"default": true,
"default": false,
"description": "Specifies whether line-based diff commands will be added to the context menus"
},
"gitlens.advanced.caching.enabled": {
@ -216,6 +216,11 @@
"default": true,
"description": "Specifies whether git blame output will be cached"
},
"gitlens.advanced.caching.statusBar.maxLines": {
"type": "number",
"default": 0,
"description": "Specifies whether status bar git blame output will be cached for larger documents"
},
"gitlens.advanced.debug": {
"type": "boolean",
"default": false,
@ -280,8 +285,8 @@
"category": "GitLens"
},
{
"command": "gitlens.showHistory",
"title": "Open Git History",
"command": "gitlens.showFileHistory",
"title": "Open Git File History",
"category": "GitLens"
}
],
@ -289,6 +294,7 @@
"explorer/context": [
{
"command": "gitlens.diffWithPrevious",
"alt": "gitlens.diffWithWorking",
"when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled",
"group": "2_gitlens-file"
}
@ -313,11 +319,13 @@
},
{
"command": "gitlens.diffWithWorking",
"alt": "gitlens.diffLineWithWorking",
"when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled",
"group": "3_gitlens-file@1.0"
},
{
"command": "gitlens.diffWithPrevious",
"alt": "gitlens.diffLineWithPrevious",
"when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled",
"group": "3_gitlens-file@1.1"
},
@ -358,7 +366,7 @@
"devDependencies": {
"mocha": "^3.1.2",
"tslint": "^3.15.1",
"typescript": "^2.0.8",
"typescript": "^2.0.9",
"vscode": "^1.0.3",
"@types/node": "^6.0.46",
"@types/mocha": "^2.2.32",

+ 154
- 34
src/blameAnnotationController.ts View File

@ -1,64 +1,184 @@
'use strict';
import { Disposable, ExtensionContext, TextEditor, workspace } from 'vscode';
import { Functions, IDeferred } from './system';
import { commands, Disposable, ExtensionContext, TextDocument, TextEditor, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
import { BlameAnnotationProvider } from './blameAnnotationProvider';
import { TextDocumentComparer, TextEditorComparer } from './comparers';
import { BuiltInCommands } from './constants';
import GitProvider from './gitProvider';
import { Logger } from './logger';
export default class BlameAnnotationController extends Disposable {
private _disposable: Disposable;
private _annotationProvider: BlameAnnotationProvider | undefined;
private _annotationProviders: Map<number, BlameAnnotationProvider> = new Map();
private _blameAnnotationsDisposable: Disposable;
private _pendingWhitespaceToggleDisposable: Disposable;
private _pendingClearAnnotations: Map<number, (() => void) & IDeferred> = new Map();
private _pendingWhitespaceToggles: Set<number> = new Set();
private _visibleColumns: Set<number>;
constructor(private context: ExtensionContext, private git: GitProvider) {
super(() => this.dispose());
}
const subscriptions: Disposable[] = [];
dispose() {
for (const fn of this._pendingClearAnnotations.values()) {
fn.cancel();
}
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
this._pendingWhitespaceToggleDisposable && this._pendingWhitespaceToggleDisposable.dispose();
}
// subscriptions.push(window.onDidChangeActiveTextEditor(e => {
// if (!e || !this._controller || this._controller.editor === e) return;
// this.clear();
// }));
async clear(column: number, toggleRenderWhitespace: boolean = true) {
const provider = this._annotationProviders.get(column);
if (!provider) return;
subscriptions.push(workspace.onDidCloseTextDocument(d => {
if (!this._annotationProvider || this._annotationProvider.uri.fsPath !== d.uri.fsPath) return;
this.clear();
}));
this._annotationProviders.delete(column);
await provider.dispose(toggleRenderWhitespace);
this._disposable = Disposable.from(...subscriptions);
if (this._annotationProviders.size === 0) {
Logger.log(`Remove listener registrations for blame annotations`);
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
this._blameAnnotationsDisposable = undefined;
}
}
dispose() {
this.clear();
this._disposable && this._disposable.dispose();
async showBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
if (!editor || !editor.document) return false;
if (!this._blameAnnotationsDisposable && this._annotationProviders.size === 0) {
Logger.log(`Add listener registrations for blame annotations`);
const subscriptions: Disposable[] = [];
subscriptions.push(window.onDidChangeVisibleTextEditors(Functions.debounce(this._onVisibleTextEditorsChanged, 100), this));
subscriptions.push(window.onDidChangeTextEditorViewColumn(this._onTextEditorViewColumnChanged, this));
subscriptions.push(workspace.onDidCloseTextDocument(this._onTextDocumentClosed, this));
this._blameAnnotationsDisposable = Disposable.from(...subscriptions);
this._visibleColumns = this._getVisibleColumns(window.visibleTextEditors);
}
let provider = this._annotationProviders.get(editor.viewColumn);
if (provider) {
if (TextEditorComparer.equals(provider.editor, editor)) {
await provider.setSelection(shaOrLine);
return true;
}
await this.clear(provider.editor.viewColumn, false);
}
provider = new BlameAnnotationProvider(this.context, this.git, editor);
this._annotationProviders.set(editor.viewColumn, provider);
return provider.provideBlameAnnotation(shaOrLine);
}
clear() {
this._annotationProvider && this._annotationProvider.dispose();
this._annotationProvider = undefined;
async toggleBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
if (!editor || !editor.document) return false;
let provider = this._annotationProviders.get(editor.viewColumn);
if (!provider) return this.showBlameAnnotation(editor, shaOrLine);
await this.clear(provider.editor.viewColumn);
return false;
}
get annotated() {
return this._annotationProvider !== undefined;
private _getVisibleColumns(editors: TextEditor[]): Set<number> {
const set: Set<number> = new Set();
for (const e of editors) {
if (e.viewColumn === undefined) continue;
set.add(e.viewColumn);
}
return set;
}
showBlameAnnotation(editor: TextEditor, sha?: string): Promise<void> {
if (!editor || !editor.document || editor.document.isUntitled) {
this.clear();
return Promise.resolve();
private _onActiveTextEditorChanged(e: TextEditor) {
if (e.viewColumn === undefined || this._pendingWhitespaceToggles.size === 0) return;
if (this._pendingWhitespaceToggles.has(e.viewColumn)) {
Logger.log('ActiveTextEditorChanged:', `Remove pending whitespace toggle for column ${e.viewColumn}`);
this._pendingWhitespaceToggles.delete(e.viewColumn);
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- toggle whitespace back on
Logger.log('ActiveTextEditorChanged:', `Toggle whitespace rendering on`);
commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
}
if (!this._annotationProvider) {
this._annotationProvider = new BlameAnnotationProvider(this.context, this.git, editor);
return this._annotationProvider.provideBlameAnnotation(sha);
if (this._pendingWhitespaceToggles.size === 0) {
Logger.log('ActiveTextEditorChanged:', `Remove listener registrations for pending whitespace toggles`);
this._pendingWhitespaceToggleDisposable.dispose();
this._pendingWhitespaceToggleDisposable = undefined;
}
}
private _onTextDocumentClosed(e: TextDocument) {
for (const [key, p] of this._annotationProviders) {
if (!TextDocumentComparer.equals(p.document, e)) continue;
Logger.log('TextDocumentClosed:', `Add pending clear of blame annotations for column ${key}`);
// Since we don't know if a whole column is going away -- we don't know if we should reset the whitespace
// So defer until onDidChangeVisibleTextEditors fires
const fn = Functions.debounce(() => {
this._pendingClearAnnotations.delete(key);
this.clear(key);
}, 250);
this._pendingClearAnnotations.set(key, fn);
fn();
}
}
private async _onTextEditorViewColumnChanged(e: TextEditorViewColumnChangeEvent) {
this._visibleColumns = this._getVisibleColumns(window.visibleTextEditors);
return Promise.resolve();
Logger.log('TextEditorViewColumnChanged:', `Clear blame annotations for column ${e.viewColumn}`);
await this.clear(e.viewColumn);
for (const [key, p] of this._annotationProviders) {
if (!TextEditorComparer.equals(p.editor, e.textEditor)) continue;
Logger.log('TextEditorViewColumnChanged:', `Clear blame annotations for column ${key}`);
await this.clear(key, false);
}
}
toggleBlameAnnotation(editor: TextEditor, sha?: string): Promise<void> {
if (!editor || !editor.document || editor.document.isUntitled || this._annotationProvider) {
this.clear();
return Promise.resolve();
private async _onVisibleTextEditorsChanged(e: TextEditor[]) {
if (e.every(_ => _.document.uri.scheme === 'inmemory')) return;
this._visibleColumns = this._getVisibleColumns(e);
for (const [key, fn] of this._pendingClearAnnotations) {
Logger.log('VisibleTextEditorsChanged:', `Remove pending blame annotations for column ${key}`);
fn.cancel();
this._pendingClearAnnotations.delete(key);
// Clear and reset the whitespace depending on if the column went away
Logger.log('VisibleTextEditorsChanged:', `Clear blame annotations for column ${key}`);
await this.clear(key, this._visibleColumns.has(key));
}
return this.showBlameAnnotation(editor, sha);
for (const [key, p] of this._annotationProviders) {
if (e.some(_ => TextEditorComparer.equals(p.editor, _))) continue;
Logger.log('VisibleTextEditorsChanged:', `Clear blame annotations for column ${key}`);
const editor = window.activeTextEditor;
if (p.requiresRenderWhitespaceToggle && (editor && editor.viewColumn !== key)) {
this.clear(key, false);
if (!this._pendingWhitespaceToggleDisposable) {
Logger.log('VisibleTextEditorsChanged:', `Add listener registrations for pending whitespace toggles`);
this._pendingWhitespaceToggleDisposable = window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this);
}
Logger.log('VisibleTextEditorsChanged:', `Add pending whitespace toggle for column ${key}`);
this._pendingWhitespaceToggles.add(key);
}
else {
this.clear(key);
}
}
}
}

+ 70
- 33
src/blameAnnotationProvider.ts View File

@ -1,9 +1,11 @@
'use strict';
import { Iterables } from './system';
import { commands, DecorationOptions, Disposable, ExtensionContext, OverviewRulerLane, Range, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, Uri, window, workspace } from 'vscode';
import { BuiltInCommands } from './constants';
import { commands, DecorationOptions, Disposable, ExtensionContext, OverviewRulerLane, Range, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
import { TextDocumentComparer } from './comparers';
import { BlameAnnotationStyle, IBlameConfig } from './configuration';
import GitProvider, { GitCommit, IGitBlame } from './gitProvider';
import { BuiltInCommands } from './constants';
import GitProvider, { GitCommit, GitUri, IGitBlame } from './gitProvider';
import { Logger } from './logger';
import * as moment from 'moment';
const blameDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
@ -15,13 +17,13 @@ const blameDecoration: TextEditorDecorationType = window.createTextEditorDecorat
let highlightDecoration: TextEditorDecorationType;
export class BlameAnnotationProvider extends Disposable {
public uri: Uri;
public document: TextDocument;
public requiresRenderWhitespaceToggle: boolean = false;
private _blame: Promise<IGitBlame>;
private _config: IBlameConfig;
private _disposable: Disposable;
private _document: TextDocument;
private _renderWhitespaceSetting: string;
private _uri: GitUri;
constructor(context: ExtensionContext, private git: GitProvider, public editor: TextEditor) {
super(() => this.dispose());
@ -44,49 +46,56 @@ export class BlameAnnotationProvider extends Disposable {
});
}
this._document = this.editor.document;
this.uri = this._document.uri;
this._blame = this.git.getBlameForFile(this.uri.fsPath);
this.document = this.editor.document;
this._uri = GitUri.fromUri(this.document.uri);
this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath);
this._config = workspace.getConfiguration('gitlens').get<IBlameConfig>('blame');
const subscriptions: Disposable[] = [];
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this));
this._disposable = Disposable.from(...subscriptions);
this._onConfigurationChanged();
}
dispose() {
async dispose(toggleRenderWhitespace: boolean = true) {
if (this.editor) {
// HACK: This only works when switching to another editor - diffs handle whitespace toggle differently
if (this._renderWhitespaceSetting !== 'none') {
commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
}
this.editor.setDecorations(blameDecoration, []);
this.editor.setDecorations(highlightDecoration, []);
}
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- toggle whitespace back on
if (toggleRenderWhitespace && this.requiresRenderWhitespaceToggle) {
Logger.log('BlameAnnotationProvider.dispose:', `Toggle whitespace rendering on`);
await commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
}
this._disposable && this._disposable.dispose();
}
private _onConfigurationChanged() {
const renderWhitespace = workspace.getConfiguration('editor').get<string>('renderWhitespace');
this.requiresRenderWhitespaceToggle = !(renderWhitespace == null || renderWhitespace === 'none');
}
private async _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent) {
const blame = await this.git.getBlameForLine(e.textEditor.document.fileName, e.selections[0].active.line);
if (blame) {
this._applyCommitHighlight(blame.commit.sha);
}
if (!TextDocumentComparer.equals(this.document, e.textEditor && e.textEditor.document)) return;
return this.setSelection(e.selections[0].active.line);
}
async provideBlameAnnotation(sha?: string) {
async provideBlameAnnotation(shaOrLine?: string | number): Promise<boolean> {
const blame = await this._blame;
if (!blame || !blame.lines.length) return;
if (!blame || !blame.lines.length) return false;
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- toggle whitespace off
this._renderWhitespaceSetting = workspace.getConfiguration('editor').get<string>('renderWhitespace');
if (this._renderWhitespaceSetting !== 'none') {
commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
if (this.requiresRenderWhitespaceToggle) {
Logger.log('BlameAnnotationProvider.provideBlameAnnotation:', `Toggle whitespace rendering off`);
await commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
}
let blameDecorationOptions: DecorationOptions[] | undefined;
@ -103,23 +112,49 @@ export class BlameAnnotationProvider extends Disposable {
this.editor.setDecorations(blameDecoration, blameDecorationOptions);
}
sha = sha || Iterables.first(blame.commits.values()).sha;
return this._applyCommitHighlight(sha);
this._setSelection(blame, shaOrLine);
return true;
}
private async _applyCommitHighlight(sha: string) {
async setSelection(shaOrLine?: string | number) {
const blame = await this._blame;
if (!blame || !blame.lines.length) return;
return this._setSelection(blame, shaOrLine);
}
private _setSelection(blame: IGitBlame, shaOrLine?: string | number) {
const offset = this._uri.offset;
let sha: string;
if (typeof shaOrLine === 'string') {
sha = shaOrLine;
}
else if (typeof shaOrLine === 'number') {
const line = shaOrLine - offset;
if (line >= 0) {
sha = blame.lines[line].sha;
}
}
else {
sha = Iterables.first(blame.commits.values()).sha;
}
if (!sha) {
this.editor.setDecorations(highlightDecoration, []);
return;
}
const highlightDecorationRanges = blame.lines
.filter(l => l.sha === sha)
.map(l => this.editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)));
.map(l => this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)));
this.editor.setDecorations(highlightDecoration, highlightDecorationRanges);
}
private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] {
const offset = this._uri.offset;
let count = 0;
let lastSha: string;
return blame.lines.map(l => {
@ -143,7 +178,7 @@ export class BlameAnnotationProvider extends Disposable {
count = -1;
}
const isEmptyOrWhitespace = this._document.lineAt(l.line).isEmptyOrWhitespace;
const isEmptyOrWhitespace = this.document.lineAt(l.line).isEmptyOrWhitespace;
if (!isEmptyOrWhitespace) {
switch (++count) {
case 0:
@ -164,7 +199,7 @@ export class BlameAnnotationProvider extends Disposable {
lastSha = l.sha;
return <DecorationOptions>{
range: this.editor.document.validateRange(new Range(l.line, 0, l.line, 0)),
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 0)),
hoverMessage: hoverMessage,
renderOptions: { before: { color: color, contentText: gutter, width: '11em' } }
};
@ -172,6 +207,8 @@ export class BlameAnnotationProvider extends Disposable {
}
private _getExpandedGutterDecorations(blame: IGitBlame): DecorationOptions[] {
const offset = this._uri.offset;
let width = 0;
if (this._config.annotation.sha) {
width += 5;
@ -211,7 +248,7 @@ export class BlameAnnotationProvider extends Disposable {
const gutter = this._getGutter(commit);
return <DecorationOptions>{
range: this.editor.document.validateRange(new Range(l.line, 0, l.line, 0)),
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 0)),
hoverMessage: hoverMessage,
renderOptions: { before: { color: color, contentText: gutter, width: `${width}em` } }
};

+ 73
- 23
src/blameStatusBarController.ts View File

@ -1,16 +1,21 @@
'use strict';
import { Objects } from './system';
import { Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextEditor, window, workspace } from 'vscode';
import { IConfig, IStatusBarConfig, StatusBarCommand } from './configuration';
import { Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextDocument, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
import { TextDocumentComparer } from './comparers';
import { IConfig, StatusBarCommand } from './configuration';
import { WorkspaceState } from './constants';
import GitProvider, { IGitBlameLine } from './gitProvider';
import GitProvider, { GitCommit, GitUri, IGitBlame } from './gitProvider';
import * as moment from 'moment';
export default class BlameStatusBarController extends Disposable {
private _config: IStatusBarConfig;
private _blame: Promise<IGitBlame> | undefined;
private _config: IConfig;
private _disposable: Disposable;
private _statusBarItem: StatusBarItem|null;
private _statusBarDisposable: Disposable|null;
private _document: TextDocument | undefined;
private _statusBarItem: StatusBarItem | undefined;
private _statusBarDisposable: Disposable | undefined;
private _uri: GitUri;
private _useCaching: boolean;
constructor(private context: ExtensionContext, private git: GitProvider) {
super(() => this.dispose());
@ -33,7 +38,7 @@ export default class BlameStatusBarController extends Disposable {
private _onConfigure() {
const config = workspace.getConfiguration('').get<IConfig>('gitlens');
if (!Objects.areEquivalent(config.statusBar, this._config)) {
if (!Objects.areEquivalent(config.statusBar, this._config && this._config.statusBar)) {
this._statusBarDisposable && this._statusBarDisposable.dispose();
this._statusBarItem && this._statusBarItem.dispose();
@ -47,7 +52,7 @@ export default class BlameStatusBarController extends Disposable {
break;
case StatusBarCommand.GitViewHistory:
if (!this.context.workspaceState.get(WorkspaceState.HasGitHistoryExtension, false)) {
config.statusBar.command = StatusBarCommand.BlameExplorer;
config.statusBar.command = StatusBarCommand.ShowBlameHistory;
}
break;
}
@ -55,28 +60,70 @@ export default class BlameStatusBarController extends Disposable {
const subscriptions: Disposable[] = [];
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveSelectionChanged, this));
subscriptions.push(window.onDidChangeTextEditorSelection(e => this._onActiveSelectionChanged(e.textEditor)));
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this));
this._statusBarDisposable = Disposable.from(...subscriptions);
} else {
this._statusBarDisposable = null;
this._statusBarItem = null;
this._statusBarDisposable = undefined;
this._statusBarItem = undefined;
}
}
this._config = config.statusBar;
this._config = config;
this._onActiveTextEditorChanged(window.activeTextEditor);
}
private async _onActiveSelectionChanged(editor: TextEditor): Promise<void> {
if (!editor || !editor.document || editor.document.isUntitled) {
private async _onActiveTextEditorChanged(e: TextEditor): Promise<void> {
if (!e || !e.document || e.document.isUntitled || e.viewColumn === undefined) {
this.clear();
return;
}
const blame = await this.git.getBlameForLine(editor.document.uri.fsPath, editor.selection.active.line);
if (blame) {
this.show(blame);
this._document = e.document;
this._uri = GitUri.fromUri(this._document.uri);
const maxLines = this._config.advanced.caching.statusBar.maxLines;
this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || this._document.lineCount <= maxLines);
if (this._useCaching) {
this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath);
}
else {
this._blame = undefined;
}
return this._showBlame(e.selection.active.line);
}
private async _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
if (!TextDocumentComparer.equals(this._document, e.textEditor && e.textEditor.document)) return;
return this._showBlame(e.selections[0].active.line);
}
private async _showBlame(line: number) {
line = line - this._uri.offset;
let commit: GitCommit;
if (line >= 0) {
if (this._useCaching) {
const blame = await this._blame;
if (!blame || !blame.lines.length) {
this.clear();
return;
}
const sha = blame.lines[line].sha;
commit = blame.commits.get(sha);
}
else {
const blameLine = await this.git.getBlameForLine(this._uri.fsPath, line, this._uri.sha, this._uri.repoPath);
commit = blameLine && blameLine.commit;
}
}
if (commit) {
this.show(commit);
}
else {
this.clear();
@ -85,20 +132,23 @@ export default class BlameStatusBarController extends Disposable {
clear() {
this._statusBarItem && this._statusBarItem.hide();
this._document = undefined;
this._blame = undefined;
}
show(blameLine: IGitBlameLine) {
const commit = blameLine.commit;
show(commit: GitCommit) {
this._statusBarItem.text = `$(git-commit) ${commit.author}, ${moment(commit.date).fromNow()}`;
//this._statusBarItem.tooltip = [`Last changed by ${commit.author}`, moment(commit.date).format('MMMM Do, YYYY h:MMa'), '', commit.message].join('\n');
switch (this._config.command) {
switch (this._config.statusBar.command) {
case StatusBarCommand.BlameAnnotate:
this._statusBarItem.tooltip = 'Toggle Blame Annotations';
break;
case StatusBarCommand.BlameExplorer:
case StatusBarCommand.ShowBlameHistory:
this._statusBarItem.tooltip = 'Open Blame History';
break;
case StatusBarCommand.ShowFileHistory:
this._statusBarItem.tooltip = 'Open File History';
break;
case StatusBarCommand.DiffWithPrevious:
this._statusBarItem.tooltip = 'Compare to Previous Commit';
break;

+ 15
- 10
src/commands/diffLineWithPrevious.ts View File

@ -2,7 +2,7 @@
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands';
import { Commands } from '../constants';
import GitProvider, { GitCommit } from '../gitProvider';
import GitProvider, { GitCommit, GitUri } from '../gitProvider';
import { Logger } from '../logger';
export default class DiffLineWithPreviousCommand extends EditorCommand {
@ -10,18 +10,23 @@ export default class DiffLineWithPreviousCommand extends EditorCommand {
super(Commands.DiffLineWithPrevious);
}
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
async execute(editor: TextEditor): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri): Promise<any>;
async execute(editor: TextEditor, edit?: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
line = line || editor.selection.active.line;
if (!commit || GitProvider.isUncommitted(commit.sha)) {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
const gitUri = GitUri.fromUri(uri);
const blameline = line - gitUri.offset;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(uri.fsPath, line);
const blame = await this.git.getBlameForLine(gitUri.fsPath, blameline, gitUri.sha, gitUri.repoPath);
if (!blame) return window.showWarningMessage(`Unable to open diff. File is probably not under source control`);
// If the line is uncommitted, find the previous commit
@ -33,7 +38,7 @@ export default class DiffLineWithPreviousCommand extends EditorCommand {
const prevCommit = prevBlame.commit;
commit = new GitCommit(commit.repoPath, commit.sha, commit.fileName, commit.author, commit.date, commit.message, commit.lines, commit.originalFileName, prevCommit.sha, prevCommit.fileName);
line = blame.line.originalLine + 1;
line = blame.line.originalLine + 1 + gitUri.offset;
}
catch (ex) {
Logger.error('[GitLens.DiffWithPreviousLineCommand]', `getBlameForLine(${blame.line.originalLine}, ${commit.previousSha})`, ex);
@ -42,7 +47,7 @@ export default class DiffLineWithPreviousCommand extends EditorCommand {
}
}
catch (ex) {
Logger.error('[GitLens.DiffWithPreviousLineCommand]', `getBlameForLine(${line})`, ex);
Logger.error('[GitLens.DiffWithPreviousLineCommand]', `getBlameForLine(${blameline})`, ex);
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
}
}

+ 16
- 11
src/commands/diffLineWithWorking.ts View File

@ -2,7 +2,7 @@
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands';
import { Commands } from '../constants';
import GitProvider, { GitCommit } from '../gitProvider';
import GitProvider, { GitCommit, GitUri } from '../gitProvider';
import { Logger } from '../logger';
export default class DiffLineWithWorkingCommand extends EditorCommand {
@ -10,33 +10,38 @@ export default class DiffLineWithWorkingCommand extends EditorCommand {
super(Commands.DiffLineWithWorking);
}
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
async execute(editor: TextEditor): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri): Promise<any>;
async execute(editor: TextEditor, edit?: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
line = line || editor.selection.active.line;
if (!commit || GitProvider.isUncommitted(commit.sha)) {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
const gitUri = GitUri.fromUri(uri);
const blameline = line - gitUri.offset;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(uri.fsPath, line);
const blame = await this.git.getBlameForLine(gitUri.fsPath, blameline, gitUri.sha, gitUri.repoPath);
if (!blame) return window.showWarningMessage(`Unable to open diff. File is probably not under source control`);
commit = blame.commit;
// If the line is uncommitted, find the previous commit
if (commit.isUncommitted) {
commit = new GitCommit(commit.repoPath, commit.previousSha, commit.previousFileName, commit.author, commit.date, commit.message);
line = blame.line.line + 1;
line = blame.line.line + 1 + gitUri.offset;
}
}
catch (ex) {
Logger.error('[GitLens.DiffLineWithWorkingCommand]', `getBlameForLine(${line})`, ex);
Logger.error('[GitLens.DiffLineWithWorkingCommand]', `getBlameForLine(${blameline})`, ex);
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
}
}
return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit, line);
return commands.executeCommand(Commands.DiffWithWorking, uri, commit, line);
}
}

+ 14
- 10
src/commands/diffWithPrevious.ts View File

@ -3,7 +3,7 @@ import { Iterables } from '../system';
import { commands, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants';
import GitProvider, { GitCommit } from '../gitProvider';
import GitProvider, { GitCommit, GitUri } from '../gitProvider';
import { Logger } from '../logger';
import * as moment from 'moment';
import * as path from 'path';
@ -13,29 +13,33 @@ export default class DiffWithPreviousCommand extends EditorCommand {
super(Commands.DiffWithPrevious);
}
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>;
async execute(editor: TextEditor): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri, commit: GitCommit, range?: Range): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri, commit: GitCommit, line?: number): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, rangeOrLine?: Range | number): Promise<any> {
async execute(editor: TextEditor, edit?: TextEditorEdit, uri?: Uri, commit?: GitCommit, rangeOrLine?: Range | number): Promise<any> {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
let line = editor.selection.active.line;
if (typeof rangeOrLine === 'number') {
line = rangeOrLine || line;
}
if (!commit || rangeOrLine instanceof Range) {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
const gitUri = GitUri.fromUri(uri);
try {
const log = await this.git.getLogForFile(uri.fsPath, <Range>rangeOrLine);
const log = await this.git.getLogForFile(gitUri.fsPath, <Range>rangeOrLine);
if (!log) return window.showWarningMessage(`Unable to open diff. File is probably not under source control`);
commit = commit ? Iterables.find(log.commits.values(), _ => _.sha === commit.sha) : Iterables.first(log.commits.values());
const sha = (commit && commit.sha) || gitUri.sha;
commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
}
catch (ex) {
Logger.error('[GitLens.DiffWithPreviousCommand]', `getLogForFile(${uri.fsPath})`, ex);
Logger.error('[GitLens.DiffWithPreviousCommand]', `getLogForFile(${gitUri.fsPath})`, ex);
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
}
}

+ 16
- 11
src/commands/diffWithWorking.ts View File

@ -3,7 +3,7 @@ import { Iterables } from '../system';
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants';
import GitProvider, { GitCommit } from '../gitProvider';
import GitProvider, { GitCommit, GitUri } from '../gitProvider';
import { Logger } from '../logger';
import * as path from 'path';
@ -12,31 +12,36 @@ export default class DiffWithWorkingCommand extends EditorCommand {
super(Commands.DiffWithWorking);
}
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
async execute(editor: TextEditor): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri): Promise<any>;
async execute(editor: TextEditor, edit?: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
line = line || editor.selection.active.line;
if (!commit || GitProvider.isUncommitted(commit.sha)) {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
const gitUri = GitUri.fromUri(uri);
try {
const log = await this.git.getLogForFile(uri.fsPath);
const log = await this.git.getLogForFile(gitUri.fsPath);
if (!log) return window.showWarningMessage(`Unable to open diff. File is probably not under source control`);
commit = Iterables.first(log.commits.values());
commit = (gitUri.sha && log.commits.get(gitUri.sha)) || Iterables.first(log.commits.values());
}
catch (ex) {
Logger.error('[GitLens.DiffWithWorkingCommand]', `getLogForFile(${uri.fsPath})`, ex);
Logger.error('[GitLens.DiffWithWorkingCommand]', `getLogForFile(${gitUri.fsPath})`, ex);
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
}
}
const gitUri = GitUri.fromUri(uri);
try {
const compare = await this.git.getVersionedFile(commit.uri.fsPath, commit.repoPath, commit.sha);
await commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), uri, `${path.basename(commit.uri.fsPath)} (${commit.sha}) ↔ ${path.basename(uri.fsPath)}`);
await commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), gitUri.fileUri(), `${path.basename(commit.uri.fsPath)} (${commit.sha}) ↔ ${path.basename(gitUri.fsPath)}`);
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: line, at: 'center' });
}
catch (ex) {

+ 8
- 15
src/commands/showBlame.ts View File

@ -3,30 +3,23 @@ import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import BlameAnnotationController from '../blameAnnotationController';
import { EditorCommand } from './commands';
import { Commands } from '../constants';
import GitProvider from '../gitProvider';
import { Logger } from '../logger';
export default class ShowBlameCommand extends EditorCommand {
constructor(private git: GitProvider, private annotationController: BlameAnnotationController) {
constructor(private annotationController: BlameAnnotationController) {
super(Commands.ShowBlame);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) {
if (sha) {
return this.annotationController.toggleBlameAnnotation(editor, sha);
}
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string): Promise<any> {
try {
const blame = await this.git.getBlameForLine(uri.fsPath, editor.selection.active.line);
return this.annotationController.showBlameAnnotation(editor, blame && blame.commit.sha);
if (sha) {
return this.annotationController.showBlameAnnotation(editor, sha);
}
return this.annotationController.showBlameAnnotation(editor, editor.selection.active.line);
}
catch (ex) {
Logger.error('[GitLens.ShowBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex);
Logger.error('GitLens.ShowBlameCommand', ex);
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
}
}

+ 4
- 2
src/commands/showBlameHistory.ts View File

@ -2,7 +2,7 @@
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants';
import GitProvider from '../gitProvider';
import GitProvider, { GitUri } from '../gitProvider';
import { Logger } from '../logger';
export default class ShowBlameHistoryCommand extends EditorCommand {
@ -20,8 +20,10 @@ export default class ShowBlameHistoryCommand extends EditorCommand {
position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
}
const gitUri = GitUri.fromUri(uri);
try {
const locations = await this.git.getBlameLocations(uri.fsPath, range);
const locations = await this.git.getBlameLocations(gitUri.fsPath, range);
if (!locations) return window.showWarningMessage(`Unable to show blame history. File is probably not under source control`);
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations);

src/commands/showHistory.ts → src/commands/showFileHistory.ts View File

@ -2,12 +2,12 @@
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants';
import GitProvider from '../gitProvider';
import GitProvider, { GitUri } from '../gitProvider';
import { Logger } from '../logger';
export default class ShowHistoryCommand extends EditorCommand {
export default class ShowFileHistoryCommand extends EditorCommand {
constructor(private git: GitProvider) {
super(Commands.ShowHistory);
super(Commands.ShowFileHistory);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, position?: Position, sha?: string, line?: number) {
@ -19,14 +19,16 @@ export default class ShowHistoryCommand extends EditorCommand {
position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
}
const gitUri = GitUri.fromUri(uri);
try {
const locations = await this.git.getLogLocations(uri.fsPath, sha, line);
const locations = await this.git.getLogLocations(gitUri.fsPath, sha, line);
if (!locations) return window.showWarningMessage(`Unable to show history. File is probably not under source control`);
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations);
}
catch (ex) {
Logger.error('[GitLens.ShowHistoryCommand]', 'getLogLocations', ex);
Logger.error('[GitLens.ShowFileHistoryCommand]', 'getLogLocations', ex);
return window.showErrorMessage(`Unable to show history. See output channel for more details`);
}
}

+ 8
- 15
src/commands/toggleBlame.ts View File

@ -3,30 +3,23 @@ import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import BlameAnnotationController from '../blameAnnotationController';
import { EditorCommand } from './commands';
import { Commands } from '../constants';
import GitProvider from '../gitProvider';
import { Logger } from '../logger';
export default class ToggleBlameCommand extends EditorCommand {
constructor(private git: GitProvider, private annotationController: BlameAnnotationController) {
constructor(private annotationController: BlameAnnotationController) {
super(Commands.ToggleBlame);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) {
if (sha) {
return this.annotationController.toggleBlameAnnotation(editor, sha);
}
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string): Promise<any> {
try {
const blame = await this.git.getBlameForLine(uri.fsPath, editor.selection.active.line);
return this.annotationController.toggleBlameAnnotation(editor, blame && blame.commit.sha);
if (sha) {
return this.annotationController.toggleBlameAnnotation(editor, sha);
}
return this.annotationController.toggleBlameAnnotation(editor, editor.selection.active.line);
}
catch (ex) {
Logger.error('[GitLens.ToggleBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex);
Logger.error('GitLens.ToggleBlameCommand', ex);
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
}
}

+ 42
- 0
src/comparers.ts View File

@ -0,0 +1,42 @@
'use strict';
import { TextDocument, TextEditor, Uri } from 'vscode';
abstract class Comparer<T> {
abstract equals(lhs: T, rhs: T): boolean;
}
class UriComparer extends Comparer<Uri> {
equals(lhs: Uri, rhs: Uri) {
if (!lhs && !rhs) return true;
if ((lhs && !rhs) || (!lhs && rhs)) return false;
return lhs.scheme === rhs.scheme && lhs.fsPath === rhs.fsPath;
}
}
class TextDocumentComparer extends Comparer<TextDocument> {
equals(lhs: TextDocument, rhs: TextDocument) {
if (!lhs && !rhs) return true;
if ((lhs && !rhs) || (!lhs && rhs)) return false;
return uriComparer.equals(lhs.uri, rhs.uri);
}
}
class TextEditorComparer extends Comparer<TextEditor> {
equals(lhs: TextEditor, rhs: TextEditor) {
if (!lhs && !rhs) return true;
if ((lhs && !rhs) || (!lhs && rhs)) return false;
return textDocumentComparer.equals(lhs.document, rhs.document);
}
}
const textDocumentComparer = new TextDocumentComparer();
const textEditorComparer = new TextEditorComparer();
const uriComparer = new UriComparer();
export {
textDocumentComparer as TextDocumentComparer,
textEditorComparer as TextEditorComparer,
uriComparer as UriComparer
};

+ 8
- 4
src/configuration.ts View File

@ -16,11 +16,11 @@ export interface IBlameConfig {
};
}
export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory';
export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory';
export const CodeLensCommand = {
BlameAnnotate: Commands.ToggleBlame as CodeLensCommand,
ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand,
ShowHistory: Commands.ShowHistory as CodeLensCommand,
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand,
GitViewHistory: 'git.viewFileHistory' as CodeLensCommand
};
@ -61,10 +61,11 @@ export interface ICodeLensesConfig {
authors: ICodeLensConfig;
}
export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory';
export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory';
export const StatusBarCommand = {
BlameAnnotate: Commands.ToggleBlame as StatusBarCommand,
BlameExplorer: Commands.ShowBlameHistory as StatusBarCommand,
ShowBlameHistory: Commands.ShowBlameHistory as StatusBarCommand,
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
DiffWithPrevious: Commands.DiffWithPrevious as StatusBarCommand,
ToggleCodeLens: Commands.ToggleCodeLens as StatusBarCommand,
GitViewHistory: 'git.viewFileHistory' as StatusBarCommand
@ -85,6 +86,9 @@ export const OutputLevel = {
export interface IAdvancedConfig {
caching: {
enabled: boolean;
statusBar: {
maxLines: number;
}
};
debug: boolean;
git: string;

+ 2
- 2
src/constants.ts View File

@ -14,7 +14,7 @@ export const BuiltInCommands = {
ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands
};
export type Commands = 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showHistory' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens';
export type Commands = 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens';
export const Commands = {
DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands,
DiffLineWithPrevious: 'gitlens.diffLineWithPrevious' as Commands,
@ -22,7 +22,7 @@ export const Commands = {
DiffLineWithWorking: 'gitlens.diffLineWithWorking' as Commands,
ShowBlame: 'gitlens.showBlame' as Commands,
ShowBlameHistory: 'gitlens.showBlameHistory' as Commands,
ShowHistory: 'gitlens.showHistory' as Commands,
ShowFileHistory: 'gitlens.showFileHistory' as Commands,
ToggleBlame: 'gitlens.toggleBlame' as Commands,
ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands
};

+ 4
- 4
src/extension.ts View File

@ -15,7 +15,7 @@ import DiffWithWorkingCommand from './commands/diffWithWorking';
import DiffLineWithWorkingCommand from './commands/diffLineWithWorking';
import ShowBlameCommand from './commands/showBlame';
import ShowBlameHistoryCommand from './commands/showBlameHistory';
import ShowHistoryCommand from './commands/showHistory';
import ShowFileHistoryCommand from './commands/showFileHistory';
import ToggleBlameCommand from './commands/toggleBlame';
import ToggleCodeLensCommand from './commands/toggleCodeLens';
@ -64,10 +64,10 @@ export async function activate(context: ExtensionContext) {
context.subscriptions.push(new DiffLineWithWorkingCommand(git));
context.subscriptions.push(new DiffWithPreviousCommand(git));
context.subscriptions.push(new DiffLineWithPreviousCommand(git));
context.subscriptions.push(new ShowBlameCommand(git, annotationController));
context.subscriptions.push(new ToggleBlameCommand(git, annotationController));
context.subscriptions.push(new ShowBlameCommand(annotationController));
context.subscriptions.push(new ToggleBlameCommand(annotationController));
context.subscriptions.push(new ShowBlameHistoryCommand(git));
context.subscriptions.push(new ShowHistoryCommand(git));
context.subscriptions.push(new ShowFileHistoryCommand(git));
context.subscriptions.push(new ToggleCodeLensCommand(git));
}

+ 4
- 4
src/git/enrichers/blameParserEnricher.ts View File

@ -35,10 +35,10 @@ export class GitBlameParserEnricher implements IGitEnricher {
}
private _parseEntries(data: string): IBlameEntry[] {
if (!data) return null;
if (!data) return undefined;
const lines = data.split('\n');
if (!lines.length) return null;
if (!lines.length) return undefined;
const entries: IBlameEntry[] = [];
@ -107,7 +107,7 @@ export class GitBlameParserEnricher implements IGitEnricher {
entry.fileName = lineParts.slice(1).join(' ');
entries.push(entry);
entry = null;
entry = undefined;
break;
default:
@ -120,7 +120,7 @@ export class GitBlameParserEnricher implements IGitEnricher {
enrich(data: string, fileName: string): IGitBlame {
const entries = this._parseEntries(data);
if (!entries) return null;
if (!entries) return undefined;
const authors: Map<string, IGitAuthor> = new Map();
const commits: Map<string, GitCommit> = new Map();

+ 5
- 5
src/git/enrichers/logParserEnricher.ts View File

@ -19,10 +19,10 @@ interface ILogEntry {
export class GitLogParserEnricher implements IGitEnricher<IGitLog> {
private _parseEntries(data: string): ILogEntry[] {
if (!data) return null;
if (!data) return undefined;
const lines = data.split('\n');
if (!lines.length) return null;
if (!lines.length) return undefined;
const entries: ILogEntry[] = [];
@ -49,7 +49,7 @@ export class GitLogParserEnricher implements IGitEnricher {
break;
case 'author-date':
entry.authorDate = lineParts.slice(1).join(' ').trim();
entry.authorDate = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`;
break;
// case 'committer':
@ -76,7 +76,7 @@ export class GitLogParserEnricher implements IGitEnricher {
}
entries.push(entry);
entry = null;
entry = undefined;
break;
default:
@ -89,7 +89,7 @@ export class GitLogParserEnricher implements IGitEnricher {
enrich(data: string, fileName: string): IGitLog {
const entries = this._parseEntries(data);
if (!entries) return null;
if (!entries) return undefined;
const authors: Map<string, IGitAuthor> = new Map();
const commits: Map<string, GitCommit> = new Map();

+ 3
- 3
src/git/git.ts View File

@ -83,13 +83,13 @@ export default class Git {
static log(fileName: string, repoPath?: string) {
const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath);
return gitCommand(root, 'log', `--follow`, `--name-only`, `--no-merges`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, file);
return gitCommand(root, 'log', `--follow`, `--name-only`, `--no-merges`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, file);
}
static logRange(fileName: string, start: number, end: number, repoPath?: string) {
const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath);
return gitCommand(root, 'log', `--name-only`, `--no-merges`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, `-L ${start},${end}:${file}`);
return gitCommand(root, 'log', `--name-only`, `--no-merges`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, `-L ${start},${end}:${file}`);
}
static getVersionedFile(fileName: string, repoPath: string, sha: string) {
@ -102,7 +102,7 @@ export default class Git {
return;
}
//Logger.log(`getVersionedFile(${fileName}, ${sha}); destination=${destination}`);
Logger.log(`getVersionedFile(${fileName}, ${repoPath}, ${sha}); destination=${destination}`);
fs.appendFile(destination, data, err => {
if (err) {
reject(err);

+ 1
- 0
src/git/gitLocator.ts View File

@ -1,3 +1,4 @@
'use strict';
import { spawnPromise } from 'spawn-rx';
import * as path from 'path';

+ 1
- 1
src/gitBlameCodeLensProvider.ts View File

@ -61,7 +61,7 @@ export default class GitBlameCodeLensProvider implements CodeLensProvider {
resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable<CodeLens> {
if (lens instanceof GitDiffWithWorkingTreeCodeLens) return this._resolveDiffWithWorkingTreeCodeLens(lens, token);
if (lens instanceof GitDiffWithPreviousCodeLens) return this._resolveGitDiffWithPreviousCodeLens(lens, token);
return Promise.reject<CodeLens>(null);
return Promise.reject<CodeLens>(undefined);
}
_resolveDiffWithWorkingTreeCodeLens(lens: GitDiffWithWorkingTreeCodeLens, token: CancellationToken): Thenable<CodeLens> {

+ 7
- 7
src/gitCodeLensProvider.ts View File

@ -41,7 +41,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
let languageLocations = this._config.codeLens.languageLocations.find(_ => _.language.toLowerCase() === document.languageId);
if (languageLocations == null) {
languageLocations = <ICodeLensLanguageLocation>{
language: null,
language: undefined,
location: this._config.codeLens.location,
customSymbols: this._config.codeLens.locationCustomSymbols
};
@ -171,7 +171,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable<CodeLens> {
if (lens instanceof GitRecentChangeCodeLens) return this._resolveGitRecentChangeCodeLens(lens, token);
if (lens instanceof GitAuthorsCodeLens) return this._resolveGitAuthorsCodeLens(lens, token);
return Promise.reject<CodeLens>(null);
return Promise.reject<CodeLens>(undefined);
}
async _resolveGitRecentChangeCodeLens(lens: GitRecentChangeCodeLens, token: CancellationToken): Promise<CodeLens> {
@ -186,7 +186,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
switch (this._config.codeLens.recentChange.command) {
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand<GitRecentChangeCodeLens>(title, lens, blame);
case CodeLensCommand.ShowBlameHistory: return this._applyShowBlameHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame);
case CodeLensCommand.ShowHistory: return this._applyShowHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame, recentCommit);
case CodeLensCommand.ShowFileHistory: return this._applyShowFileHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame, recentCommit);
case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand<GitRecentChangeCodeLens>(title, lens, blame, recentCommit);
case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame);
default: return lens;
@ -202,7 +202,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
switch (this._config.codeLens.authors.command) {
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.ShowBlameHistory: return this._applyShowBlameHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.ShowHistory: return this._applyShowHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.ShowFileHistory: return this._applyShowFileHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
default: return lens;
@ -227,7 +227,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
return lens;
}
_applyShowHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines, commit?: GitCommit) {
_applyShowFileHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines, commit?: GitCommit) {
let line = lens.range.start.line;
const blameLine = commit.lines.find(_ => _.line === line);
if (blameLine) {
@ -237,7 +237,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
const position = lens.isFullRange ? new Position(1, 0) : lens.range.start;
lens.command = {
title: title,
command: Commands.ShowHistory,
command: Commands.ShowFileHistory,
arguments: [Uri.file(lens.fileName), position, commit.sha, line]
};
return lens;
@ -262,7 +262,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
}
_applyGitHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines) {
if (!this._hasGitHistoryExtension) return this._applyShowHistoryCommand(title, lens, blame);
if (!this._hasGitHistoryExtension) return this._applyShowFileHistoryCommand(title, lens, blame);
lens.command = {
title: title,

+ 16
- 5
src/gitContentProvider.ts View File

@ -1,18 +1,29 @@
'use strict';
import { ExtensionContext, TextDocumentContentProvider, Uri } from 'vscode';
import { ExtensionContext, TextDocumentContentProvider, Uri, window } from 'vscode';
import { DocumentSchemes } from './constants';
import GitProvider from './gitProvider';
import { Logger } from './logger';
import * as path from 'path';
export default class GitContentProvider implements TextDocumentContentProvider {
static scheme = DocumentSchemes.Git;
constructor(context: ExtensionContext, private git: GitProvider) { }
provideTextDocumentContent(uri: Uri): string | Thenable<string> {
async provideTextDocumentContent(uri: Uri): Promise<string> {
const data = GitProvider.fromGitUri(uri);
return this.git.getVersionedFileText(data.originalFileName || data.fileName, data.repoPath, data.sha)
.then(text => data.decoration ? `${data.decoration}\n${text}` : text)
.catch(ex => Logger.error('[GitLens.GitContentProvider]', 'getVersionedFileText', ex));
const fileName = data.originalFileName || data.fileName;
try {
let text = await this.git.getVersionedFileText(fileName, data.repoPath, data.sha);
if (data.decoration) {
text = `${data.decoration}\n${text}`;
}
return text;
}
catch (ex) {
Logger.error('[GitLens.GitContentProvider]', 'getVersionedFileText', ex);
await window.showErrorMessage(`Unable to show Git revision ${data.sha} of '${path.relative(data.repoPath, fileName)}'`);
return undefined;
}
}
}

+ 72
- 34
src/gitProvider.ts View File

@ -39,16 +39,16 @@ enum RemoveCacheReason {
}
export default class GitProvider extends Disposable {
private _cache: Map<string, CacheEntry> | null;
private _cacheDisposable: Disposable | null;
private _cache: Map<string, CacheEntry> | undefined;
private _cacheDisposable: Disposable | undefined;
private _config: IConfig;
private _disposable: Disposable;
private _codeLensProviderDisposable: Disposable | null;
private _codeLensProviderDisposable: Disposable | undefined;
private _codeLensProviderSelector: DocumentFilter;
private _gitignore: Promise<ignore.Ignore>;
static EmptyPromise: Promise<IGitBlame | IGitLog> = Promise.resolve(null);
static EmptyPromise: Promise<IGitBlame | IGitLog> = Promise.resolve(undefined);
static BlameFormat = GitBlameFormat.incremental;
constructor(private context: ExtensionContext) {
@ -58,7 +58,7 @@ export default class GitProvider extends Disposable {
this._onConfigure();
this._gitignore = new Promise<ignore.Ignore | null>((resolve, reject) => {
this._gitignore = new Promise<ignore.Ignore | undefined>((resolve, reject) => {
const gitignorePath = path.join(repoPath, '.gitignore');
fs.exists(gitignorePath, e => {
if (e) {
@ -67,11 +67,11 @@ export default class GitProvider extends Disposable {
resolve(ignore().add(data));
return;
}
resolve(null);
resolve(undefined);
});
return;
}
resolve(null);
resolve(undefined);
});
});
@ -106,7 +106,7 @@ export default class GitProvider extends Disposable {
this._codeLensProviderSelector = GitCodeLensProvider.selector;
this._codeLensProviderDisposable = languages.registerCodeLensProvider(this._codeLensProviderSelector, new GitCodeLensProvider(this.context, this));
} else {
this._codeLensProviderDisposable = null;
this._codeLensProviderDisposable = undefined;
}
}
@ -127,9 +127,9 @@ export default class GitProvider extends Disposable {
this._cacheDisposable = Disposable.from(...disposables);
} else {
this._cacheDisposable && this._cacheDisposable.dispose();
this._cacheDisposable = null;
this._cacheDisposable = undefined;
this._cache && this._cache.clear();
this._cache = null;
this._cache = undefined;
}
}
@ -167,13 +167,15 @@ export default class GitProvider extends Disposable {
return Git.repoPath(cwd);
}
getBlameForFile(fileName: string): Promise<IGitBlame | null> {
Logger.log(`getBlameForFile('${fileName}')`);
getBlameForFile(fileName: string, sha?: string, repoPath?: string): Promise<IGitBlame | undefined> {
Logger.log(`getBlameForFile('${fileName}', ${sha}, ${repoPath})`);
fileName = Git.normalizePath(fileName);
const useCaching = this.UseCaching && !sha;
let cacheKey: string | undefined;
let entry: CacheEntry | undefined;
if (this.UseCaching) {
if (useCaching) {
cacheKey = this._getCacheEntryKey(fileName);
entry = this._cache.get(cacheKey);
@ -189,11 +191,11 @@ export default class GitProvider extends Disposable {
return <Promise<IGitBlame>>GitProvider.EmptyPromise;
}
return Git.blame(GitProvider.BlameFormat, fileName)
return Git.blame(GitProvider.BlameFormat, fileName, sha, repoPath)
.then(data => new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName))
.catch(ex => {
// Trap and cache expected blame errors
if (this.UseCaching) {
if (useCaching) {
const msg = ex && ex.toString();
Logger.log(`Replace blame cache with empty promise for '${cacheKey}'`);
@ -206,11 +208,11 @@ export default class GitProvider extends Disposable {
this._cache.set(cacheKey, entry);
return <Promise<IGitBlame>>GitProvider.EmptyPromise;
}
return null;
return undefined;
});
});
if (this.UseCaching) {
if (useCaching) {
Logger.log(`Add blame cache for '${cacheKey}'`);
entry.blame = <ICachedBlame>{
@ -224,13 +226,13 @@ export default class GitProvider extends Disposable {
return promise;
}
async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise<IGitBlameLine | null> {
async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise<IGitBlameLine | undefined> {
Logger.log(`getBlameForLine('${fileName}', ${line}, ${sha}, ${repoPath})`);
if (this.UseCaching && !sha) {
const blame = await this.getBlameForFile(fileName);
const blameLine = blame && blame.lines[line];
if (!blameLine) return null;
if (!blameLine) return undefined;
const commit = blame.commits.get(blameLine.sha);
return <IGitBlameLine>{
@ -245,7 +247,7 @@ export default class GitProvider extends Disposable {
try {
const data = await Git.blameLines(GitProvider.BlameFormat, fileName, line + 1, line + 1, sha, repoPath);
const blame = new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName);
if (!blame) return null;
if (!blame) return undefined;
const commit = Iterables.first(blame.commits.values());
if (repoPath) {
@ -258,15 +260,15 @@ export default class GitProvider extends Disposable {
};
}
catch (ex) {
return null;
return undefined;
}
}
async getBlameForRange(fileName: string, range: Range): Promise<IGitBlameLines | null> {
async getBlameForRange(fileName: string, range: Range): Promise<IGitBlameLines | undefined> {
Logger.log(`getBlameForRange('${fileName}', ${range})`);
const blame = await this.getBlameForFile(fileName);
if (!blame) return null;
if (!blame) return undefined;
if (!blame.lines.length) return Object.assign({ allLines: blame.lines }, blame);
@ -312,11 +314,11 @@ export default class GitProvider extends Disposable {
};
}
async getBlameForShaRange(fileName: string, sha: string, range: Range): Promise<IGitBlameCommitLines | null> {
async getBlameForShaRange(fileName: string, sha: string, range: Range): Promise<IGitBlameCommitLines | undefined> {
Logger.log(`getBlameForShaRange('${fileName}', ${sha}, ${range})`);
const blame = await this.getBlameForFile(fileName);
if (!blame) return null;
if (!blame) return undefined;
const lines = blame.lines.slice(range.start.line, range.end.line + 1).filter(l => l.sha === sha);
let commit = blame.commits.get(sha);
@ -329,11 +331,11 @@ export default class GitProvider extends Disposable {
};
}
async getBlameLocations(fileName: string, range: Range): Promise<Location[] | null> {
async getBlameLocations(fileName: string, range: Range): Promise<Location[] | undefined> {
Logger.log(`getBlameForShaRange('${fileName}', ${range})`);
const blame = await this.getBlameForRange(fileName, range);
if (!blame) return null;
if (!blame) return undefined;
const commitCount = blame.commits.size;
@ -351,7 +353,7 @@ export default class GitProvider extends Disposable {
return locations;
}
getLogForFile(fileName: string, range?: Range): Promise<IGitLog | null> {
getLogForFile(fileName: string, range?: Range): Promise<IGitLog | undefined> {
Logger.log(`getLogForFile('${fileName}', ${range})`);
fileName = Git.normalizePath(fileName);
@ -392,7 +394,7 @@ export default class GitProvider extends Disposable {
this._cache.set(cacheKey, entry);
return <Promise<IGitLog>>GitProvider.EmptyPromise;
}
return null;
return undefined;
});
});
@ -410,11 +412,11 @@ export default class GitProvider extends Disposable {
return promise;
}
async getLogLocations(fileName: string, sha?: string, line?: number): Promise<Location[] | null> {
async getLogLocations(fileName: string, sha?: string, line?: number): Promise<Location[] | undefined> {
Logger.log(`getLogLocations('${fileName}', ${sha}, ${line})`);
const log = await this.getLogForFile(fileName);
if (!log) return null;
if (!log) return undefined;
const commitCount = log.commits.size;
@ -454,7 +456,7 @@ export default class GitProvider extends Disposable {
this._codeLensProviderDisposable.dispose();
if (editor.document.fileName === (this._codeLensProviderSelector && this._codeLensProviderSelector.pattern)) {
this._codeLensProviderDisposable = null;
this._codeLensProviderDisposable = undefined;
return;
}
}
@ -468,7 +470,7 @@ export default class GitProvider extends Disposable {
disposables.push(window.onDidChangeActiveTextEditor(e => {
if (e.viewColumn && e.document !== editor.document) {
this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose();
this._codeLensProviderDisposable = null;
this._codeLensProviderDisposable = undefined;
}
}));
@ -487,7 +489,7 @@ export default class GitProvider extends Disposable {
return data;
}
static fromGitUri(uri: Uri) {
static fromGitUri(uri: Uri): IGitUriData {
if (uri.scheme !== DocumentSchemes.Git) throw new Error(`fromGitUri(uri=${uri}) invalid scheme`);
return GitProvider._fromGitUri<IGitUriData>(uri);
}
@ -538,6 +540,42 @@ export default class GitProvider extends Disposable {
}
}
export class GitUri extends Uri {
offset: number;
repoPath?: string | undefined;
sha?: string | undefined;
constructor(uri?: Uri) {
super();
if (!uri) return;
const base = <any>this;
base._scheme = uri.scheme;
base._authority = uri.authority;
base._path = uri.path;
base._query = uri.query;
base._fragment = uri.fragment;
this.offset = 0;
if (uri.scheme === DocumentSchemes.Git || uri.scheme === DocumentSchemes.GitBlame) {
const data = GitProvider.fromGitUri(uri);
base._fsPath = data.originalFileName || data.fileName;
this.offset = (data.decoration && data.decoration.split('\n').length) || 0;
this.repoPath = data.repoPath;
this.sha = data.sha;
}
}
fileUri() {
return Uri.file(this.fsPath);
}
static fromUri(uri: Uri) {
return new GitUri(uri);
}
}
export interface IGitUriData {
repoPath: string;
fileName: string;

+ 1
- 0
src/system.ts View File

@ -1,3 +1,4 @@
'use strict';
// export * from './system/array';
// export * from './system/disposable';
// export * from './system/element';

+ 7
- 0
src/system/iterable.ts View File

@ -58,4 +58,11 @@ export namespace Iterables {
export function next<T>(source: IterableIterator<T>): T {
return source.next().value;
}
export function some<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): boolean {
for (const item of source) {
if (predicate(item)) return true;
}
return false;
}
}

Loading…
Cancel
Save