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
This commit is contained in:
Eric Amodio
2016-11-12 01:25:42 -05:00
parent 7ace9cb152
commit 638a6dc838
28 changed files with 592 additions and 259 deletions

View File

@@ -4,17 +4,22 @@
### 0.9.0 ### 0.9.0
- Adds support for git history (log)! - Adds support for git history (log)!
- Adds new `gitlens.showHistory` command to open the history explorer - Adds support for blame annotations and git commands on file revisions
- Adds new `gitlens.showHistory` option to the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings - 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 per-language CodeLens location customization using the `gitlens.codeLens.languageLocations` setting
- Adds new `gitlens.diffLineWithPrevious` command for line sensitive diffs - Adds new `gitlens.diffLineWithPrevious` command for line sensitive diffs
- Adds new `gitlens.diffLineWithWorking` command for line sensitive diffs - Adds new `gitlens.diffLineWithWorking` command for line sensitive diffs
- Adds `gitlens.diffWithPrevious` command to the explorer context menu - Adds `gitlens.diffWithPrevious` command to the explorer context menu
- Adds output channel logging, controlled by the `gitlens.advanced.output.level` setting - 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 (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.diffWithPrevious` command to always be file sensitive diffs
- Changes `gitlens.diffWithWorking` 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 - 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 - Fixes issue where blame annotations would not be cleared properly when switching between open files
### 0.5.5 ### 0.5.5

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.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.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.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.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.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.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.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 ## Known Issues

View File

@@ -156,15 +156,15 @@
}, },
"gitlens.codeLens.recentChange.command": { "gitlens.codeLens.recentChange.command": {
"type": "string", "type": "string",
"default": "gitlens.showHistory", "default": "gitlens.showFileHistory",
"enum": [ "enum": [
"gitlens.toggleBlame", "gitlens.toggleBlame",
"gitlens.showBlameHistory", "gitlens.showBlameHistory",
"gitlens.showHistory", "gitlens.showFileHistory",
"gitlens.diffWithPrevious", "gitlens.diffWithPrevious",
"git.viewFileHistory" "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": { "gitlens.codeLens.authors.enabled": {
"type": "boolean", "type": "boolean",
@@ -177,11 +177,11 @@
"enum": [ "enum": [
"gitlens.toggleBlame", "gitlens.toggleBlame",
"gitlens.showBlameHistory", "gitlens.showBlameHistory",
"gitlens.showHistory", "gitlens.showFileHistory",
"gitlens.diffWithPrevious", "gitlens.diffWithPrevious",
"git.viewFileHistory" "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": { "gitlens.statusBar.enabled": {
"type": "boolean", "type": "boolean",
@@ -194,12 +194,12 @@
"enum": [ "enum": [
"gitlens.toggleBlame", "gitlens.toggleBlame",
"gitlens.showBlameHistory", "gitlens.showBlameHistory",
"gitlens.showHistory", "gitlens.showFileHistory",
"gitlens.diffWithPrevious", "gitlens.diffWithPrevious",
"gitlens.toggleCodeLens", "gitlens.toggleCodeLens",
"git.viewFileHistory" "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": { "gitlens.menus.fileDiff.enabled": {
"type": "boolean", "type": "boolean",
@@ -208,7 +208,7 @@
}, },
"gitlens.menus.lineDiff.enabled": { "gitlens.menus.lineDiff.enabled": {
"type": "boolean", "type": "boolean",
"default": true, "default": false,
"description": "Specifies whether line-based diff commands will be added to the context menus" "description": "Specifies whether line-based diff commands will be added to the context menus"
}, },
"gitlens.advanced.caching.enabled": { "gitlens.advanced.caching.enabled": {
@@ -216,6 +216,11 @@
"default": true, "default": true,
"description": "Specifies whether git blame output will be cached" "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": { "gitlens.advanced.debug": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
@@ -280,8 +285,8 @@
"category": "GitLens" "category": "GitLens"
}, },
{ {
"command": "gitlens.showHistory", "command": "gitlens.showFileHistory",
"title": "Open Git History", "title": "Open Git File History",
"category": "GitLens" "category": "GitLens"
} }
], ],
@@ -289,6 +294,7 @@
"explorer/context": [ "explorer/context": [
{ {
"command": "gitlens.diffWithPrevious", "command": "gitlens.diffWithPrevious",
"alt": "gitlens.diffWithWorking",
"when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled", "when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled",
"group": "2_gitlens-file" "group": "2_gitlens-file"
} }
@@ -313,11 +319,13 @@
}, },
{ {
"command": "gitlens.diffWithWorking", "command": "gitlens.diffWithWorking",
"alt": "gitlens.diffLineWithWorking",
"when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled", "when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled",
"group": "3_gitlens-file@1.0" "group": "3_gitlens-file@1.0"
}, },
{ {
"command": "gitlens.diffWithPrevious", "command": "gitlens.diffWithPrevious",
"alt": "gitlens.diffLineWithPrevious",
"when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled", "when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled",
"group": "3_gitlens-file@1.1" "group": "3_gitlens-file@1.1"
}, },
@@ -358,7 +366,7 @@
"devDependencies": { "devDependencies": {
"mocha": "^3.1.2", "mocha": "^3.1.2",
"tslint": "^3.15.1", "tslint": "^3.15.1",
"typescript": "^2.0.8", "typescript": "^2.0.9",
"vscode": "^1.0.3", "vscode": "^1.0.3",
"@types/node": "^6.0.46", "@types/node": "^6.0.46",
"@types/mocha": "^2.2.32", "@types/mocha": "^2.2.32",

View File

@@ -1,64 +1,184 @@
'use strict'; '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 { BlameAnnotationProvider } from './blameAnnotationProvider';
import { TextDocumentComparer, TextEditorComparer } from './comparers';
import { BuiltInCommands } from './constants';
import GitProvider from './gitProvider'; import GitProvider from './gitProvider';
import { Logger } from './logger';
export default class BlameAnnotationController extends Disposable { export default class BlameAnnotationController extends Disposable {
private _disposable: Disposable; private _annotationProviders: Map<number, BlameAnnotationProvider> = new Map();
private _annotationProvider: BlameAnnotationProvider | undefined; 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) { constructor(private context: ExtensionContext, private git: GitProvider) {
super(() => this.dispose()); super(() => this.dispose());
const subscriptions: Disposable[] = [];
// subscriptions.push(window.onDidChangeActiveTextEditor(e => {
// if (!e || !this._controller || this._controller.editor === e) return;
// this.clear();
// }));
subscriptions.push(workspace.onDidCloseTextDocument(d => {
if (!this._annotationProvider || this._annotationProvider.uri.fsPath !== d.uri.fsPath) return;
this.clear();
}));
this._disposable = Disposable.from(...subscriptions);
} }
dispose() { dispose() {
this.clear(); for (const fn of this._pendingClearAnnotations.values()) {
this._disposable && this._disposable.dispose(); fn.cancel();
}
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
this._pendingWhitespaceToggleDisposable && this._pendingWhitespaceToggleDisposable.dispose();
} }
clear() { async clear(column: number, toggleRenderWhitespace: boolean = true) {
this._annotationProvider && this._annotationProvider.dispose(); const provider = this._annotationProviders.get(column);
this._annotationProvider = undefined; if (!provider) return;
this._annotationProviders.delete(column);
await provider.dispose(toggleRenderWhitespace);
if (this._annotationProviders.size === 0) {
Logger.log(`Remove listener registrations for blame annotations`);
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
this._blameAnnotationsDisposable = undefined;
}
} }
get annotated() { async showBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
return this._annotationProvider !== undefined; 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);
} }
showBlameAnnotation(editor: TextEditor, sha?: string): Promise<void> { let provider = this._annotationProviders.get(editor.viewColumn);
if (!editor || !editor.document || editor.document.isUntitled) { if (provider) {
this.clear(); if (TextEditorComparer.equals(provider.editor, editor)) {
return Promise.resolve(); await provider.setSelection(shaOrLine);
return true;
}
await this.clear(provider.editor.viewColumn, false);
} }
if (!this._annotationProvider) { provider = new BlameAnnotationProvider(this.context, this.git, editor);
this._annotationProvider = new BlameAnnotationProvider(this.context, this.git, editor); this._annotationProviders.set(editor.viewColumn, provider);
return this._annotationProvider.provideBlameAnnotation(sha); return provider.provideBlameAnnotation(shaOrLine);
} }
return Promise.resolve(); 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;
} }
toggleBlameAnnotation(editor: TextEditor, sha?: string): Promise<void> { private _getVisibleColumns(editors: TextEditor[]): Set<number> {
if (!editor || !editor.document || editor.document.isUntitled || this._annotationProvider) { const set: Set<number> = new Set();
this.clear(); for (const e of editors) {
return Promise.resolve(); if (e.viewColumn === undefined) continue;
set.add(e.viewColumn);
}
return set;
} }
return this.showBlameAnnotation(editor, sha); 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._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);
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);
}
}
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));
}
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);
}
}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ import { Iterables } from '../system';
import { commands, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { commands, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands'; import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants'; import { BuiltInCommands, Commands } from '../constants';
import GitProvider, { GitCommit } from '../gitProvider'; import GitProvider, { GitCommit, GitUri } from '../gitProvider';
import { Logger } from '../logger'; import { Logger } from '../logger';
import * as moment from 'moment'; import * as moment from 'moment';
import * as path from 'path'; import * as path from 'path';
@@ -13,29 +13,33 @@ export default class DiffWithPreviousCommand extends EditorCommand {
super(Commands.DiffWithPrevious); 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, 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, 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; let line = editor.selection.active.line;
if (typeof rangeOrLine === 'number') { if (typeof rangeOrLine === 'number') {
line = rangeOrLine || line; line = rangeOrLine || line;
} }
if (!commit || rangeOrLine instanceof Range) { if (!commit || rangeOrLine instanceof Range) {
if (!(uri instanceof Uri)) { const gitUri = GitUri.fromUri(uri);
if (!editor.document) return undefined;
uri = editor.document.uri;
}
try { 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`); 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) { 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`); return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
} }
} }

View File

@@ -3,7 +3,7 @@ import { Iterables } from '../system';
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands'; import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants'; import { BuiltInCommands, Commands } from '../constants';
import GitProvider, { GitCommit } from '../gitProvider'; import GitProvider, { GitCommit, GitUri } from '../gitProvider';
import { Logger } from '../logger'; import { Logger } from '../logger';
import * as path from 'path'; import * as path from 'path';
@@ -12,31 +12,36 @@ export default class DiffWithWorkingCommand extends EditorCommand {
super(Commands.DiffWithWorking); super(Commands.DiffWithWorking);
} }
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>; async execute(editor: TextEditor): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> { async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri): Promise<any>;
line = line || editor.selection.active.line; async execute(editor: TextEditor, edit?: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
if (!commit || GitProvider.isUncommitted(commit.sha)) {
if (!(uri instanceof Uri)) { if (!(uri instanceof Uri)) {
if (!editor.document) return undefined; if (!editor.document) return undefined;
uri = editor.document.uri; uri = editor.document.uri;
} }
line = line || editor.selection.active.line;
if (!commit || GitProvider.isUncommitted(commit.sha)) {
const gitUri = GitUri.fromUri(uri);
try { 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`); 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) { 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`); return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
} }
} }
const gitUri = GitUri.fromUri(uri);
try { try {
const compare = await this.git.getVersionedFile(commit.uri.fsPath, commit.repoPath, commit.sha); 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' }); return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: line, at: 'center' });
} }
catch (ex) { catch (ex) {

View File

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

View File

@@ -2,7 +2,7 @@
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands'; import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants'; import { BuiltInCommands, Commands } from '../constants';
import GitProvider from '../gitProvider'; import GitProvider, { GitUri } from '../gitProvider';
import { Logger } from '../logger'; import { Logger } from '../logger';
export default class ShowBlameHistoryCommand extends EditorCommand { 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; position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
} }
const gitUri = GitUri.fromUri(uri);
try { 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`); 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); return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations);

View File

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

View File

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

42
src/comparers.ts Normal file
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
};

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 = { export const CodeLensCommand = {
BlameAnnotate: Commands.ToggleBlame as CodeLensCommand, BlameAnnotate: Commands.ToggleBlame as CodeLensCommand,
ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand, ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand,
ShowHistory: Commands.ShowHistory as CodeLensCommand, ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand, DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand,
GitViewHistory: 'git.viewFileHistory' as CodeLensCommand GitViewHistory: 'git.viewFileHistory' as CodeLensCommand
}; };
@@ -61,10 +61,11 @@ export interface ICodeLensesConfig {
authors: ICodeLensConfig; 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 = { export const StatusBarCommand = {
BlameAnnotate: Commands.ToggleBlame as 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, DiffWithPrevious: Commands.DiffWithPrevious as StatusBarCommand,
ToggleCodeLens: Commands.ToggleCodeLens as StatusBarCommand, ToggleCodeLens: Commands.ToggleCodeLens as StatusBarCommand,
GitViewHistory: 'git.viewFileHistory' as StatusBarCommand GitViewHistory: 'git.viewFileHistory' as StatusBarCommand
@@ -85,6 +86,9 @@ export const OutputLevel = {
export interface IAdvancedConfig { export interface IAdvancedConfig {
caching: { caching: {
enabled: boolean; enabled: boolean;
statusBar: {
maxLines: number;
}
}; };
debug: boolean; debug: boolean;
git: string; git: string;

View File

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

View File

@@ -15,7 +15,7 @@ import DiffWithWorkingCommand from './commands/diffWithWorking';
import DiffLineWithWorkingCommand from './commands/diffLineWithWorking'; import DiffLineWithWorkingCommand from './commands/diffLineWithWorking';
import ShowBlameCommand from './commands/showBlame'; import ShowBlameCommand from './commands/showBlame';
import ShowBlameHistoryCommand from './commands/showBlameHistory'; import ShowBlameHistoryCommand from './commands/showBlameHistory';
import ShowHistoryCommand from './commands/showHistory'; import ShowFileHistoryCommand from './commands/showFileHistory';
import ToggleBlameCommand from './commands/toggleBlame'; import ToggleBlameCommand from './commands/toggleBlame';
import ToggleCodeLensCommand from './commands/toggleCodeLens'; 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 DiffLineWithWorkingCommand(git));
context.subscriptions.push(new DiffWithPreviousCommand(git)); context.subscriptions.push(new DiffWithPreviousCommand(git));
context.subscriptions.push(new DiffLineWithPreviousCommand(git)); context.subscriptions.push(new DiffLineWithPreviousCommand(git));
context.subscriptions.push(new ShowBlameCommand(git, annotationController)); context.subscriptions.push(new ShowBlameCommand(annotationController));
context.subscriptions.push(new ToggleBlameCommand(git, annotationController)); context.subscriptions.push(new ToggleBlameCommand(annotationController));
context.subscriptions.push(new ShowBlameHistoryCommand(git)); context.subscriptions.push(new ShowBlameHistoryCommand(git));
context.subscriptions.push(new ShowHistoryCommand(git)); context.subscriptions.push(new ShowFileHistoryCommand(git));
context.subscriptions.push(new ToggleCodeLensCommand(git)); context.subscriptions.push(new ToggleCodeLensCommand(git));
} }

View File

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

View File

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

View File

@@ -83,13 +83,13 @@ export default class Git {
static log(fileName: string, repoPath?: string) { static log(fileName: string, repoPath?: string) {
const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath); 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) { static logRange(fileName: string, start: number, end: number, repoPath?: string) {
const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath); 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) { static getVersionedFile(fileName: string, repoPath: string, sha: string) {
@@ -102,7 +102,7 @@ export default class Git {
return; return;
} }
//Logger.log(`getVersionedFile(${fileName}, ${sha}); destination=${destination}`); Logger.log(`getVersionedFile(${fileName}, ${repoPath}, ${sha}); destination=${destination}`);
fs.appendFile(destination, data, err => { fs.appendFile(destination, data, err => {
if (err) { if (err) {
reject(err); reject(err);

View File

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

View File

@@ -61,7 +61,7 @@ export default class GitBlameCodeLensProvider implements CodeLensProvider {
resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable<CodeLens> { resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable<CodeLens> {
if (lens instanceof GitDiffWithWorkingTreeCodeLens) return this._resolveDiffWithWorkingTreeCodeLens(lens, token); if (lens instanceof GitDiffWithWorkingTreeCodeLens) return this._resolveDiffWithWorkingTreeCodeLens(lens, token);
if (lens instanceof GitDiffWithPreviousCodeLens) return this._resolveGitDiffWithPreviousCodeLens(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> { _resolveDiffWithWorkingTreeCodeLens(lens: GitDiffWithWorkingTreeCodeLens, token: CancellationToken): Thenable<CodeLens> {

View File

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

View File

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

View File

@@ -39,16 +39,16 @@ enum RemoveCacheReason {
} }
export default class GitProvider extends Disposable { export default class GitProvider extends Disposable {
private _cache: Map<string, CacheEntry> | null; private _cache: Map<string, CacheEntry> | undefined;
private _cacheDisposable: Disposable | null; private _cacheDisposable: Disposable | undefined;
private _config: IConfig; private _config: IConfig;
private _disposable: Disposable; private _disposable: Disposable;
private _codeLensProviderDisposable: Disposable | null; private _codeLensProviderDisposable: Disposable | undefined;
private _codeLensProviderSelector: DocumentFilter; private _codeLensProviderSelector: DocumentFilter;
private _gitignore: Promise<ignore.Ignore>; 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; static BlameFormat = GitBlameFormat.incremental;
constructor(private context: ExtensionContext) { constructor(private context: ExtensionContext) {
@@ -58,7 +58,7 @@ export default class GitProvider extends Disposable {
this._onConfigure(); 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'); const gitignorePath = path.join(repoPath, '.gitignore');
fs.exists(gitignorePath, e => { fs.exists(gitignorePath, e => {
if (e) { if (e) {
@@ -67,11 +67,11 @@ export default class GitProvider extends Disposable {
resolve(ignore().add(data)); resolve(ignore().add(data));
return; return;
} }
resolve(null); resolve(undefined);
}); });
return; return;
} }
resolve(null); resolve(undefined);
}); });
}); });
@@ -106,7 +106,7 @@ export default class GitProvider extends Disposable {
this._codeLensProviderSelector = GitCodeLensProvider.selector; this._codeLensProviderSelector = GitCodeLensProvider.selector;
this._codeLensProviderDisposable = languages.registerCodeLensProvider(this._codeLensProviderSelector, new GitCodeLensProvider(this.context, this)); this._codeLensProviderDisposable = languages.registerCodeLensProvider(this._codeLensProviderSelector, new GitCodeLensProvider(this.context, this));
} else { } else {
this._codeLensProviderDisposable = null; this._codeLensProviderDisposable = undefined;
} }
} }
@@ -127,9 +127,9 @@ export default class GitProvider extends Disposable {
this._cacheDisposable = Disposable.from(...disposables); this._cacheDisposable = Disposable.from(...disposables);
} else { } else {
this._cacheDisposable && this._cacheDisposable.dispose(); this._cacheDisposable && this._cacheDisposable.dispose();
this._cacheDisposable = null; this._cacheDisposable = undefined;
this._cache && this._cache.clear(); 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); return Git.repoPath(cwd);
} }
getBlameForFile(fileName: string): Promise<IGitBlame | null> { getBlameForFile(fileName: string, sha?: string, repoPath?: string): Promise<IGitBlame | undefined> {
Logger.log(`getBlameForFile('${fileName}')`); Logger.log(`getBlameForFile('${fileName}', ${sha}, ${repoPath})`);
fileName = Git.normalizePath(fileName); fileName = Git.normalizePath(fileName);
const useCaching = this.UseCaching && !sha;
let cacheKey: string | undefined; let cacheKey: string | undefined;
let entry: CacheEntry | undefined; let entry: CacheEntry | undefined;
if (this.UseCaching) { if (useCaching) {
cacheKey = this._getCacheEntryKey(fileName); cacheKey = this._getCacheEntryKey(fileName);
entry = this._cache.get(cacheKey); entry = this._cache.get(cacheKey);
@@ -189,11 +191,11 @@ export default class GitProvider extends Disposable {
return <Promise<IGitBlame>>GitProvider.EmptyPromise; 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)) .then(data => new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName))
.catch(ex => { .catch(ex => {
// Trap and cache expected blame errors // Trap and cache expected blame errors
if (this.UseCaching) { if (useCaching) {
const msg = ex && ex.toString(); const msg = ex && ex.toString();
Logger.log(`Replace blame cache with empty promise for '${cacheKey}'`); 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); this._cache.set(cacheKey, entry);
return <Promise<IGitBlame>>GitProvider.EmptyPromise; return <Promise<IGitBlame>>GitProvider.EmptyPromise;
} }
return null; return undefined;
}); });
}); });
if (this.UseCaching) { if (useCaching) {
Logger.log(`Add blame cache for '${cacheKey}'`); Logger.log(`Add blame cache for '${cacheKey}'`);
entry.blame = <ICachedBlame>{ entry.blame = <ICachedBlame>{
@@ -224,13 +226,13 @@ export default class GitProvider extends Disposable {
return promise; 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})`); Logger.log(`getBlameForLine('${fileName}', ${line}, ${sha}, ${repoPath})`);
if (this.UseCaching && !sha) { if (this.UseCaching && !sha) {
const blame = await this.getBlameForFile(fileName); const blame = await this.getBlameForFile(fileName);
const blameLine = blame && blame.lines[line]; const blameLine = blame && blame.lines[line];
if (!blameLine) return null; if (!blameLine) return undefined;
const commit = blame.commits.get(blameLine.sha); const commit = blame.commits.get(blameLine.sha);
return <IGitBlameLine>{ return <IGitBlameLine>{
@@ -245,7 +247,7 @@ export default class GitProvider extends Disposable {
try { try {
const data = await Git.blameLines(GitProvider.BlameFormat, fileName, line + 1, line + 1, sha, repoPath); const data = await Git.blameLines(GitProvider.BlameFormat, fileName, line + 1, line + 1, sha, repoPath);
const blame = new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName); const blame = new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName);
if (!blame) return null; if (!blame) return undefined;
const commit = Iterables.first(blame.commits.values()); const commit = Iterables.first(blame.commits.values());
if (repoPath) { if (repoPath) {
@@ -258,15 +260,15 @@ export default class GitProvider extends Disposable {
}; };
} }
catch (ex) { 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})`); Logger.log(`getBlameForRange('${fileName}', ${range})`);
const blame = await this.getBlameForFile(fileName); 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); 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})`); Logger.log(`getBlameForShaRange('${fileName}', ${sha}, ${range})`);
const blame = await this.getBlameForFile(fileName); 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); const lines = blame.lines.slice(range.start.line, range.end.line + 1).filter(l => l.sha === sha);
let commit = blame.commits.get(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})`); Logger.log(`getBlameForShaRange('${fileName}', ${range})`);
const blame = await this.getBlameForRange(fileName, range); const blame = await this.getBlameForRange(fileName, range);
if (!blame) return null; if (!blame) return undefined;
const commitCount = blame.commits.size; const commitCount = blame.commits.size;
@@ -351,7 +353,7 @@ export default class GitProvider extends Disposable {
return locations; return locations;
} }
getLogForFile(fileName: string, range?: Range): Promise<IGitLog | null> { getLogForFile(fileName: string, range?: Range): Promise<IGitLog | undefined> {
Logger.log(`getLogForFile('${fileName}', ${range})`); Logger.log(`getLogForFile('${fileName}', ${range})`);
fileName = Git.normalizePath(fileName); fileName = Git.normalizePath(fileName);
@@ -392,7 +394,7 @@ export default class GitProvider extends Disposable {
this._cache.set(cacheKey, entry); this._cache.set(cacheKey, entry);
return <Promise<IGitLog>>GitProvider.EmptyPromise; return <Promise<IGitLog>>GitProvider.EmptyPromise;
} }
return null; return undefined;
}); });
}); });
@@ -410,11 +412,11 @@ export default class GitProvider extends Disposable {
return promise; 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})`); Logger.log(`getLogLocations('${fileName}', ${sha}, ${line})`);
const log = await this.getLogForFile(fileName); const log = await this.getLogForFile(fileName);
if (!log) return null; if (!log) return undefined;
const commitCount = log.commits.size; const commitCount = log.commits.size;
@@ -454,7 +456,7 @@ export default class GitProvider extends Disposable {
this._codeLensProviderDisposable.dispose(); this._codeLensProviderDisposable.dispose();
if (editor.document.fileName === (this._codeLensProviderSelector && this._codeLensProviderSelector.pattern)) { if (editor.document.fileName === (this._codeLensProviderSelector && this._codeLensProviderSelector.pattern)) {
this._codeLensProviderDisposable = null; this._codeLensProviderDisposable = undefined;
return; return;
} }
} }
@@ -468,7 +470,7 @@ export default class GitProvider extends Disposable {
disposables.push(window.onDidChangeActiveTextEditor(e => { disposables.push(window.onDidChangeActiveTextEditor(e => {
if (e.viewColumn && e.document !== editor.document) { if (e.viewColumn && e.document !== editor.document) {
this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose(); this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose();
this._codeLensProviderDisposable = null; this._codeLensProviderDisposable = undefined;
} }
})); }));
@@ -487,7 +489,7 @@ export default class GitProvider extends Disposable {
return data; return data;
} }
static fromGitUri(uri: Uri) { static fromGitUri(uri: Uri): IGitUriData {
if (uri.scheme !== DocumentSchemes.Git) throw new Error(`fromGitUri(uri=${uri}) invalid scheme`); if (uri.scheme !== DocumentSchemes.Git) throw new Error(`fromGitUri(uri=${uri}) invalid scheme`);
return GitProvider._fromGitUri<IGitUriData>(uri); 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 { export interface IGitUriData {
repoPath: string; repoPath: string;
fileName: string; fileName: string;

View File

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

View File

@@ -58,4 +58,11 @@ export namespace Iterables {
export function next<T>(source: IterableIterator<T>): T { export function next<T>(source: IterableIterator<T>): T {
return source.next().value; 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;
}
} }