mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-01-24 17:24:53 -05:00
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:
@@ -4,17 +4,22 @@
|
||||
### 0.9.0
|
||||
|
||||
- Adds support for git history (log)!
|
||||
- Adds new `gitlens.showHistory` command to open the history explorer
|
||||
- Adds new `gitlens.showHistory` option to the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings
|
||||
- Adds support for blame annotations and git commands on file revisions
|
||||
- Adds ability to show multiple blame annotation at the same time (one per vscode editor)
|
||||
- Adds new `gitlens.showFileHistory` command to open the history explorer
|
||||
- Adds new `gitlens.showFileHistory` option to the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings
|
||||
- Adds per-language CodeLens location customization using the `gitlens.codeLens.languageLocations` setting
|
||||
- Adds new `gitlens.diffLineWithPrevious` command for line sensitive diffs
|
||||
- Adds new `gitlens.diffLineWithWorking` command for line sensitive diffs
|
||||
- Adds `gitlens.diffWithPrevious` command to the explorer context menu
|
||||
- Adds output channel logging, controlled by the `gitlens.advanced.output.level` setting
|
||||
- Complete rewrite of the blame annotation provider to reduce overhead and provide better performance
|
||||
- Improves performance (significantly) when only showing CodeLens at the document level
|
||||
- Improves performance of status bar blame support
|
||||
- Changes `gitlens.diffWithPrevious` command to always be file sensitive diffs
|
||||
- Changes `gitlens.diffWithWorking` command to always be file sensitive diffs
|
||||
- Removes all debug logging, unless the `gitlens.advanced.debug` settings it on
|
||||
- Fixes many (most?) issues with whitespace toggling (required because of https://github.com/Microsoft/vscode/issues/11485)
|
||||
- Fixes issue where blame annotations would not be cleared properly when switching between open files
|
||||
|
||||
### 0.5.5
|
||||
|
||||
@@ -41,13 +41,13 @@ Must be using Git and it must be in your path.
|
||||
|`gitlens.codeLens.locationCustomSymbols`|Specifies the set of document symbols to render active document CodeLens on. Must be a member of `SymbolKind`
|
||||
|`gitlens.codeLens.languageLocations`|Specifies where CodeLens will be rendered in the active document for the specified languages
|
||||
|`gitlens.codeLens.recentChange.enabled`|Specifies whether the recent change CodeLens is shown
|
||||
|`gitlens.codeLens.recentChange.command`|Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|
||||
|`gitlens.codeLens.recentChange.command`|Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|
||||
|`gitlens.codeLens.authors.enabled`|Specifies whether the authors CodeLens is shown
|
||||
|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|
||||
|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|
||||
|`gitlens.menus.fileDiff.enabled`|Specifies whether file-based diff commands will be added to the context menus
|
||||
|`gitlens.menus.lineDiff.enabled`|Specifies whether line-based diff commands will be added to the context menus
|
||||
|`gitlens.statusBar.enabled`|Specifies whether blame information is shown in the status bar
|
||||
|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|
||||
|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|
||||
|
||||
---
|
||||
## Known Issues
|
||||
|
||||
30
package.json
30
package.json
@@ -156,15 +156,15 @@
|
||||
},
|
||||
"gitlens.codeLens.recentChange.command": {
|
||||
"type": "string",
|
||||
"default": "gitlens.showHistory",
|
||||
"default": "gitlens.showFileHistory",
|
||||
"enum": [
|
||||
"gitlens.toggleBlame",
|
||||
"gitlens.showBlameHistory",
|
||||
"gitlens.showHistory",
|
||||
"gitlens.showFileHistory",
|
||||
"gitlens.diffWithPrevious",
|
||||
"git.viewFileHistory"
|
||||
],
|
||||
"description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|
||||
"description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|
||||
},
|
||||
"gitlens.codeLens.authors.enabled": {
|
||||
"type": "boolean",
|
||||
@@ -177,11 +177,11 @@
|
||||
"enum": [
|
||||
"gitlens.toggleBlame",
|
||||
"gitlens.showBlameHistory",
|
||||
"gitlens.showHistory",
|
||||
"gitlens.showFileHistory",
|
||||
"gitlens.diffWithPrevious",
|
||||
"git.viewFileHistory"
|
||||
],
|
||||
"description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|
||||
"description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|
||||
},
|
||||
"gitlens.statusBar.enabled": {
|
||||
"type": "boolean",
|
||||
@@ -194,12 +194,12 @@
|
||||
"enum": [
|
||||
"gitlens.toggleBlame",
|
||||
"gitlens.showBlameHistory",
|
||||
"gitlens.showHistory",
|
||||
"gitlens.showFileHistory",
|
||||
"gitlens.diffWithPrevious",
|
||||
"gitlens.toggleCodeLens",
|
||||
"git.viewFileHistory"
|
||||
],
|
||||
"description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|
||||
"description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|
||||
},
|
||||
"gitlens.menus.fileDiff.enabled": {
|
||||
"type": "boolean",
|
||||
@@ -208,7 +208,7 @@
|
||||
},
|
||||
"gitlens.menus.lineDiff.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"default": false,
|
||||
"description": "Specifies whether line-based diff commands will be added to the context menus"
|
||||
},
|
||||
"gitlens.advanced.caching.enabled": {
|
||||
@@ -216,6 +216,11 @@
|
||||
"default": true,
|
||||
"description": "Specifies whether git blame output will be cached"
|
||||
},
|
||||
"gitlens.advanced.caching.statusBar.maxLines": {
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"description": "Specifies whether status bar git blame output will be cached for larger documents"
|
||||
},
|
||||
"gitlens.advanced.debug": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
@@ -280,8 +285,8 @@
|
||||
"category": "GitLens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showHistory",
|
||||
"title": "Open Git History",
|
||||
"command": "gitlens.showFileHistory",
|
||||
"title": "Open Git File History",
|
||||
"category": "GitLens"
|
||||
}
|
||||
],
|
||||
@@ -289,6 +294,7 @@
|
||||
"explorer/context": [
|
||||
{
|
||||
"command": "gitlens.diffWithPrevious",
|
||||
"alt": "gitlens.diffWithWorking",
|
||||
"when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled",
|
||||
"group": "2_gitlens-file"
|
||||
}
|
||||
@@ -313,11 +319,13 @@
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithWorking",
|
||||
"alt": "gitlens.diffLineWithWorking",
|
||||
"when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled",
|
||||
"group": "3_gitlens-file@1.0"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithPrevious",
|
||||
"alt": "gitlens.diffLineWithPrevious",
|
||||
"when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled",
|
||||
"group": "3_gitlens-file@1.1"
|
||||
},
|
||||
@@ -358,7 +366,7 @@
|
||||
"devDependencies": {
|
||||
"mocha": "^3.1.2",
|
||||
"tslint": "^3.15.1",
|
||||
"typescript": "^2.0.8",
|
||||
"typescript": "^2.0.9",
|
||||
"vscode": "^1.0.3",
|
||||
"@types/node": "^6.0.46",
|
||||
"@types/mocha": "^2.2.32",
|
||||
|
||||
@@ -1,64 +1,184 @@
|
||||
'use strict';
|
||||
import { Disposable, ExtensionContext, TextEditor, workspace } from 'vscode';
|
||||
import { Functions, IDeferred } from './system';
|
||||
import { commands, Disposable, ExtensionContext, TextDocument, TextEditor, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
|
||||
import { BlameAnnotationProvider } from './blameAnnotationProvider';
|
||||
import { TextDocumentComparer, TextEditorComparer } from './comparers';
|
||||
import { BuiltInCommands } from './constants';
|
||||
import GitProvider from './gitProvider';
|
||||
import { Logger } from './logger';
|
||||
|
||||
export default class BlameAnnotationController extends Disposable {
|
||||
private _disposable: Disposable;
|
||||
private _annotationProvider: BlameAnnotationProvider | undefined;
|
||||
private _annotationProviders: Map<number, BlameAnnotationProvider> = new Map();
|
||||
private _blameAnnotationsDisposable: Disposable;
|
||||
private _pendingWhitespaceToggleDisposable: Disposable;
|
||||
private _pendingClearAnnotations: Map<number, (() => void) & IDeferred> = new Map();
|
||||
private _pendingWhitespaceToggles: Set<number> = new Set();
|
||||
private _visibleColumns: Set<number>;
|
||||
|
||||
constructor(private context: ExtensionContext, private git: GitProvider) {
|
||||
super(() => this.dispose());
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
// 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() {
|
||||
this.clear();
|
||||
this._disposable && this._disposable.dispose();
|
||||
for (const fn of this._pendingClearAnnotations.values()) {
|
||||
fn.cancel();
|
||||
}
|
||||
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
|
||||
|
||||
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
|
||||
this._pendingWhitespaceToggleDisposable && this._pendingWhitespaceToggleDisposable.dispose();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._annotationProvider && this._annotationProvider.dispose();
|
||||
this._annotationProvider = undefined;
|
||||
async clear(column: number, toggleRenderWhitespace: boolean = true) {
|
||||
const provider = this._annotationProviders.get(column);
|
||||
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() {
|
||||
return this._annotationProvider !== undefined;
|
||||
}
|
||||
async showBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
|
||||
if (!editor || !editor.document) return false;
|
||||
|
||||
showBlameAnnotation(editor: TextEditor, sha?: string): Promise<void> {
|
||||
if (!editor || !editor.document || editor.document.isUntitled) {
|
||||
this.clear();
|
||||
return Promise.resolve();
|
||||
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);
|
||||
}
|
||||
|
||||
if (!this._annotationProvider) {
|
||||
this._annotationProvider = new BlameAnnotationProvider(this.context, this.git, editor);
|
||||
return this._annotationProvider.provideBlameAnnotation(sha);
|
||||
let provider = this._annotationProviders.get(editor.viewColumn);
|
||||
if (provider) {
|
||||
if (TextEditorComparer.equals(provider.editor, editor)) {
|
||||
await provider.setSelection(shaOrLine);
|
||||
return true;
|
||||
}
|
||||
await this.clear(provider.editor.viewColumn, false);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
provider = new BlameAnnotationProvider(this.context, this.git, editor);
|
||||
this._annotationProviders.set(editor.viewColumn, provider);
|
||||
return provider.provideBlameAnnotation(shaOrLine);
|
||||
}
|
||||
|
||||
toggleBlameAnnotation(editor: TextEditor, sha?: string): Promise<void> {
|
||||
if (!editor || !editor.document || editor.document.isUntitled || this._annotationProvider) {
|
||||
this.clear();
|
||||
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;
|
||||
}
|
||||
|
||||
private _getVisibleColumns(editors: TextEditor[]): Set<number> {
|
||||
const set: Set<number> = new Set();
|
||||
for (const e of editors) {
|
||||
if (e.viewColumn === undefined) continue;
|
||||
|
||||
set.add(e.viewColumn);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return this.showBlameAnnotation(editor, sha);
|
||||
if (this._pendingWhitespaceToggles.size === 0) {
|
||||
Logger.log('ActiveTextEditorChanged:', `Remove listener registrations for pending whitespace toggles`);
|
||||
this._pendingWhitespaceToggleDisposable.dispose();
|
||||
this._pendingWhitespaceToggleDisposable = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _onTextDocumentClosed(e: TextDocument) {
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (!TextDocumentComparer.equals(p.document, e)) continue;
|
||||
|
||||
Logger.log('TextDocumentClosed:', `Add pending clear of blame annotations for column ${key}`);
|
||||
|
||||
// Since we don't know if a whole column is going away -- we don't know if we should reset the whitespace
|
||||
// So defer until onDidChangeVisibleTextEditors fires
|
||||
const fn = Functions.debounce(() => {
|
||||
this._pendingClearAnnotations.delete(key);
|
||||
this.clear(key);
|
||||
}, 250);
|
||||
this._pendingClearAnnotations.set(key, fn);
|
||||
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
private async _onTextEditorViewColumnChanged(e: TextEditorViewColumnChangeEvent) {
|
||||
this._visibleColumns = this._getVisibleColumns(window.visibleTextEditors);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
'use strict';
|
||||
import { Iterables } from './system';
|
||||
import { commands, DecorationOptions, Disposable, ExtensionContext, OverviewRulerLane, Range, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, Uri, window, workspace } from 'vscode';
|
||||
import { BuiltInCommands } from './constants';
|
||||
import { commands, DecorationOptions, Disposable, ExtensionContext, OverviewRulerLane, Range, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import { TextDocumentComparer } from './comparers';
|
||||
import { BlameAnnotationStyle, IBlameConfig } from './configuration';
|
||||
import GitProvider, { GitCommit, IGitBlame } from './gitProvider';
|
||||
import { BuiltInCommands } from './constants';
|
||||
import GitProvider, { GitCommit, GitUri, IGitBlame } from './gitProvider';
|
||||
import { Logger } from './logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
const blameDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
|
||||
@@ -15,13 +17,13 @@ const blameDecoration: TextEditorDecorationType = window.createTextEditorDecorat
|
||||
let highlightDecoration: TextEditorDecorationType;
|
||||
|
||||
export class BlameAnnotationProvider extends Disposable {
|
||||
public uri: Uri;
|
||||
public document: TextDocument;
|
||||
public requiresRenderWhitespaceToggle: boolean = false;
|
||||
|
||||
private _blame: Promise<IGitBlame>;
|
||||
private _config: IBlameConfig;
|
||||
private _disposable: Disposable;
|
||||
private _document: TextDocument;
|
||||
private _renderWhitespaceSetting: string;
|
||||
private _uri: GitUri;
|
||||
|
||||
constructor(context: ExtensionContext, private git: GitProvider, public editor: TextEditor) {
|
||||
super(() => this.dispose());
|
||||
@@ -44,49 +46,56 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
this._document = this.editor.document;
|
||||
this.uri = this._document.uri;
|
||||
|
||||
this._blame = this.git.getBlameForFile(this.uri.fsPath);
|
||||
this.document = this.editor.document;
|
||||
this._uri = GitUri.fromUri(this.document.uri);
|
||||
this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath);
|
||||
|
||||
this._config = workspace.getConfiguration('gitlens').get<IBlameConfig>('blame');
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this));
|
||||
|
||||
this._disposable = Disposable.from(...subscriptions);
|
||||
|
||||
this._onConfigurationChanged();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
async dispose(toggleRenderWhitespace: boolean = true) {
|
||||
if (this.editor) {
|
||||
// HACK: This only works when switching to another editor - diffs handle whitespace toggle differently
|
||||
if (this._renderWhitespaceSetting !== 'none') {
|
||||
commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
|
||||
}
|
||||
|
||||
this.editor.setDecorations(blameDecoration, []);
|
||||
this.editor.setDecorations(highlightDecoration, []);
|
||||
}
|
||||
|
||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- toggle whitespace back on
|
||||
if (toggleRenderWhitespace && this.requiresRenderWhitespaceToggle) {
|
||||
Logger.log('BlameAnnotationProvider.dispose:', `Toggle whitespace rendering on`);
|
||||
await commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
|
||||
}
|
||||
|
||||
this._disposable && this._disposable.dispose();
|
||||
}
|
||||
|
||||
private async _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent) {
|
||||
const blame = await this.git.getBlameForLine(e.textEditor.document.fileName, e.selections[0].active.line);
|
||||
if (blame) {
|
||||
this._applyCommitHighlight(blame.commit.sha);
|
||||
}
|
||||
private _onConfigurationChanged() {
|
||||
const renderWhitespace = workspace.getConfiguration('editor').get<string>('renderWhitespace');
|
||||
this.requiresRenderWhitespaceToggle = !(renderWhitespace == null || renderWhitespace === 'none');
|
||||
}
|
||||
|
||||
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;
|
||||
if (!blame || !blame.lines.length) return;
|
||||
if (!blame || !blame.lines.length) return false;
|
||||
|
||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- toggle whitespace off
|
||||
this._renderWhitespaceSetting = workspace.getConfiguration('editor').get<string>('renderWhitespace');
|
||||
if (this._renderWhitespaceSetting !== 'none') {
|
||||
commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
|
||||
if (this.requiresRenderWhitespaceToggle) {
|
||||
Logger.log('BlameAnnotationProvider.provideBlameAnnotation:', `Toggle whitespace rendering off`);
|
||||
await commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
|
||||
}
|
||||
|
||||
let blameDecorationOptions: DecorationOptions[] | undefined;
|
||||
@@ -103,23 +112,49 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
this.editor.setDecorations(blameDecoration, blameDecorationOptions);
|
||||
}
|
||||
|
||||
sha = sha || Iterables.first(blame.commits.values()).sha;
|
||||
|
||||
return this._applyCommitHighlight(sha);
|
||||
this._setSelection(blame, shaOrLine);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _applyCommitHighlight(sha: string) {
|
||||
async setSelection(shaOrLine?: string | number) {
|
||||
const blame = await this._blame;
|
||||
if (!blame || !blame.lines.length) return;
|
||||
|
||||
return this._setSelection(blame, shaOrLine);
|
||||
}
|
||||
|
||||
private _setSelection(blame: IGitBlame, shaOrLine?: string | number) {
|
||||
const offset = this._uri.offset;
|
||||
|
||||
let sha: string;
|
||||
if (typeof shaOrLine === 'string') {
|
||||
sha = shaOrLine;
|
||||
}
|
||||
else if (typeof shaOrLine === 'number') {
|
||||
const line = shaOrLine - offset;
|
||||
if (line >= 0) {
|
||||
sha = blame.lines[line].sha;
|
||||
}
|
||||
}
|
||||
else {
|
||||
sha = Iterables.first(blame.commits.values()).sha;
|
||||
}
|
||||
|
||||
if (!sha) {
|
||||
this.editor.setDecorations(highlightDecoration, []);
|
||||
return;
|
||||
}
|
||||
|
||||
const highlightDecorationRanges = blame.lines
|
||||
.filter(l => l.sha === sha)
|
||||
.map(l => this.editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)));
|
||||
.map(l => this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)));
|
||||
|
||||
this.editor.setDecorations(highlightDecoration, highlightDecorationRanges);
|
||||
}
|
||||
|
||||
private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] {
|
||||
const offset = this._uri.offset;
|
||||
|
||||
let count = 0;
|
||||
let lastSha: string;
|
||||
return blame.lines.map(l => {
|
||||
@@ -143,7 +178,7 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
count = -1;
|
||||
}
|
||||
|
||||
const isEmptyOrWhitespace = this._document.lineAt(l.line).isEmptyOrWhitespace;
|
||||
const isEmptyOrWhitespace = this.document.lineAt(l.line).isEmptyOrWhitespace;
|
||||
if (!isEmptyOrWhitespace) {
|
||||
switch (++count) {
|
||||
case 0:
|
||||
@@ -164,7 +199,7 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
lastSha = l.sha;
|
||||
|
||||
return <DecorationOptions>{
|
||||
range: this.editor.document.validateRange(new Range(l.line, 0, l.line, 0)),
|
||||
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 0)),
|
||||
hoverMessage: hoverMessage,
|
||||
renderOptions: { before: { color: color, contentText: gutter, width: '11em' } }
|
||||
};
|
||||
@@ -172,6 +207,8 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
}
|
||||
|
||||
private _getExpandedGutterDecorations(blame: IGitBlame): DecorationOptions[] {
|
||||
const offset = this._uri.offset;
|
||||
|
||||
let width = 0;
|
||||
if (this._config.annotation.sha) {
|
||||
width += 5;
|
||||
@@ -211,7 +248,7 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
|
||||
const gutter = this._getGutter(commit);
|
||||
return <DecorationOptions>{
|
||||
range: this.editor.document.validateRange(new Range(l.line, 0, l.line, 0)),
|
||||
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 0)),
|
||||
hoverMessage: hoverMessage,
|
||||
renderOptions: { before: { color: color, contentText: gutter, width: `${width}em` } }
|
||||
};
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
'use strict';
|
||||
import { Objects } from './system';
|
||||
import { Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextEditor, window, workspace } from 'vscode';
|
||||
import { IConfig, IStatusBarConfig, StatusBarCommand } from './configuration';
|
||||
import { Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextDocument, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import { TextDocumentComparer } from './comparers';
|
||||
import { IConfig, StatusBarCommand } from './configuration';
|
||||
import { WorkspaceState } from './constants';
|
||||
import GitProvider, { IGitBlameLine } from './gitProvider';
|
||||
import GitProvider, { GitCommit, GitUri, IGitBlame } from './gitProvider';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export default class BlameStatusBarController extends Disposable {
|
||||
private _config: IStatusBarConfig;
|
||||
private _blame: Promise<IGitBlame> | undefined;
|
||||
private _config: IConfig;
|
||||
private _disposable: Disposable;
|
||||
private _statusBarItem: StatusBarItem|null;
|
||||
private _statusBarDisposable: Disposable|null;
|
||||
private _document: TextDocument | undefined;
|
||||
private _statusBarItem: StatusBarItem | undefined;
|
||||
private _statusBarDisposable: Disposable | undefined;
|
||||
private _uri: GitUri;
|
||||
private _useCaching: boolean;
|
||||
|
||||
constructor(private context: ExtensionContext, private git: GitProvider) {
|
||||
super(() => this.dispose());
|
||||
@@ -33,7 +38,7 @@ export default class BlameStatusBarController extends Disposable {
|
||||
private _onConfigure() {
|
||||
const config = workspace.getConfiguration('').get<IConfig>('gitlens');
|
||||
|
||||
if (!Objects.areEquivalent(config.statusBar, this._config)) {
|
||||
if (!Objects.areEquivalent(config.statusBar, this._config && this._config.statusBar)) {
|
||||
this._statusBarDisposable && this._statusBarDisposable.dispose();
|
||||
this._statusBarItem && this._statusBarItem.dispose();
|
||||
|
||||
@@ -47,7 +52,7 @@ export default class BlameStatusBarController extends Disposable {
|
||||
break;
|
||||
case StatusBarCommand.GitViewHistory:
|
||||
if (!this.context.workspaceState.get(WorkspaceState.HasGitHistoryExtension, false)) {
|
||||
config.statusBar.command = StatusBarCommand.BlameExplorer;
|
||||
config.statusBar.command = StatusBarCommand.ShowBlameHistory;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -55,28 +60,70 @@ export default class BlameStatusBarController extends Disposable {
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveSelectionChanged, this));
|
||||
subscriptions.push(window.onDidChangeTextEditorSelection(e => this._onActiveSelectionChanged(e.textEditor)));
|
||||
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this));
|
||||
|
||||
this._statusBarDisposable = Disposable.from(...subscriptions);
|
||||
} else {
|
||||
this._statusBarDisposable = null;
|
||||
this._statusBarItem = null;
|
||||
this._statusBarDisposable = undefined;
|
||||
this._statusBarItem = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
this._config = config.statusBar;
|
||||
this._config = config;
|
||||
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
private async _onActiveSelectionChanged(editor: TextEditor): Promise<void> {
|
||||
if (!editor || !editor.document || editor.document.isUntitled) {
|
||||
private async _onActiveTextEditorChanged(e: TextEditor): Promise<void> {
|
||||
if (!e || !e.document || e.document.isUntitled || e.viewColumn === undefined) {
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const blame = await this.git.getBlameForLine(editor.document.uri.fsPath, editor.selection.active.line);
|
||||
if (blame) {
|
||||
this.show(blame);
|
||||
this._document = e.document;
|
||||
this._uri = GitUri.fromUri(this._document.uri);
|
||||
const maxLines = this._config.advanced.caching.statusBar.maxLines;
|
||||
this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || this._document.lineCount <= maxLines);
|
||||
if (this._useCaching) {
|
||||
this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath);
|
||||
}
|
||||
else {
|
||||
this._blame = undefined;
|
||||
}
|
||||
|
||||
return this._showBlame(e.selection.active.line);
|
||||
}
|
||||
|
||||
private async _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
|
||||
if (!TextDocumentComparer.equals(this._document, e.textEditor && e.textEditor.document)) return;
|
||||
|
||||
return this._showBlame(e.selections[0].active.line);
|
||||
}
|
||||
|
||||
private async _showBlame(line: number) {
|
||||
line = line - this._uri.offset;
|
||||
|
||||
let commit: GitCommit;
|
||||
if (line >= 0) {
|
||||
if (this._useCaching) {
|
||||
const blame = await this._blame;
|
||||
if (!blame || !blame.lines.length) {
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const sha = blame.lines[line].sha;
|
||||
commit = blame.commits.get(sha);
|
||||
}
|
||||
else {
|
||||
const blameLine = await this.git.getBlameForLine(this._uri.fsPath, line, this._uri.sha, this._uri.repoPath);
|
||||
commit = blameLine && blameLine.commit;
|
||||
}
|
||||
}
|
||||
|
||||
if (commit) {
|
||||
this.show(commit);
|
||||
}
|
||||
else {
|
||||
this.clear();
|
||||
@@ -85,20 +132,23 @@ export default class BlameStatusBarController extends Disposable {
|
||||
|
||||
clear() {
|
||||
this._statusBarItem && this._statusBarItem.hide();
|
||||
this._document = undefined;
|
||||
this._blame = undefined;
|
||||
}
|
||||
|
||||
show(blameLine: IGitBlameLine) {
|
||||
const commit = blameLine.commit;
|
||||
show(commit: GitCommit) {
|
||||
this._statusBarItem.text = `$(git-commit) ${commit.author}, ${moment(commit.date).fromNow()}`;
|
||||
//this._statusBarItem.tooltip = [`Last changed by ${commit.author}`, moment(commit.date).format('MMMM Do, YYYY h:MMa'), '', commit.message].join('\n');
|
||||
|
||||
switch (this._config.command) {
|
||||
switch (this._config.statusBar.command) {
|
||||
case StatusBarCommand.BlameAnnotate:
|
||||
this._statusBarItem.tooltip = 'Toggle Blame Annotations';
|
||||
break;
|
||||
case StatusBarCommand.BlameExplorer:
|
||||
case StatusBarCommand.ShowBlameHistory:
|
||||
this._statusBarItem.tooltip = 'Open Blame History';
|
||||
break;
|
||||
case StatusBarCommand.ShowFileHistory:
|
||||
this._statusBarItem.tooltip = 'Open File History';
|
||||
break;
|
||||
case StatusBarCommand.DiffWithPrevious:
|
||||
this._statusBarItem.tooltip = 'Compare to Previous Commit';
|
||||
break;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { EditorCommand } from './commands';
|
||||
import { Commands } from '../constants';
|
||||
import GitProvider, { GitCommit } from '../gitProvider';
|
||||
import GitProvider, { GitCommit, GitUri } from '../gitProvider';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export default class DiffLineWithPreviousCommand extends EditorCommand {
|
||||
@@ -10,18 +10,23 @@ export default class DiffLineWithPreviousCommand extends EditorCommand {
|
||||
super(Commands.DiffLineWithPrevious);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
|
||||
async execute(editor: TextEditor): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri): Promise<any>;
|
||||
async execute(editor: TextEditor, edit?: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
|
||||
line = line || editor.selection.active.line;
|
||||
|
||||
if (!commit || GitProvider.isUncommitted(commit.sha)) {
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
const gitUri = GitUri.fromUri(uri);
|
||||
const blameline = line - gitUri.offset;
|
||||
if (blameline < 0) return undefined;
|
||||
|
||||
try {
|
||||
const blame = await this.git.getBlameForLine(uri.fsPath, line);
|
||||
const blame = await this.git.getBlameForLine(gitUri.fsPath, blameline, gitUri.sha, gitUri.repoPath);
|
||||
if (!blame) return window.showWarningMessage(`Unable to open diff. File is probably not under source control`);
|
||||
|
||||
// If the line is uncommitted, find the previous commit
|
||||
@@ -33,7 +38,7 @@ export default class DiffLineWithPreviousCommand extends EditorCommand {
|
||||
|
||||
const prevCommit = prevBlame.commit;
|
||||
commit = new GitCommit(commit.repoPath, commit.sha, commit.fileName, commit.author, commit.date, commit.message, commit.lines, commit.originalFileName, prevCommit.sha, prevCommit.fileName);
|
||||
line = blame.line.originalLine + 1;
|
||||
line = blame.line.originalLine + 1 + gitUri.offset;
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error('[GitLens.DiffWithPreviousLineCommand]', `getBlameForLine(${blame.line.originalLine}, ${commit.previousSha})`, ex);
|
||||
@@ -42,7 +47,7 @@ export default class DiffLineWithPreviousCommand extends EditorCommand {
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error('[GitLens.DiffWithPreviousLineCommand]', `getBlameForLine(${line})`, ex);
|
||||
Logger.error('[GitLens.DiffWithPreviousLineCommand]', `getBlameForLine(${blameline})`, ex);
|
||||
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { EditorCommand } from './commands';
|
||||
import { Commands } from '../constants';
|
||||
import GitProvider, { GitCommit } from '../gitProvider';
|
||||
import GitProvider, { GitCommit, GitUri } from '../gitProvider';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export default class DiffLineWithWorkingCommand extends EditorCommand {
|
||||
@@ -10,33 +10,38 @@ export default class DiffLineWithWorkingCommand extends EditorCommand {
|
||||
super(Commands.DiffLineWithWorking);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
|
||||
async execute(editor: TextEditor): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri): Promise<any>;
|
||||
async execute(editor: TextEditor, edit?: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
|
||||
line = line || editor.selection.active.line;
|
||||
|
||||
if (!commit || GitProvider.isUncommitted(commit.sha)) {
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
const gitUri = GitUri.fromUri(uri);
|
||||
const blameline = line - gitUri.offset;
|
||||
if (blameline < 0) return undefined;
|
||||
|
||||
try {
|
||||
const blame = await this.git.getBlameForLine(uri.fsPath, line);
|
||||
const blame = await this.git.getBlameForLine(gitUri.fsPath, blameline, gitUri.sha, gitUri.repoPath);
|
||||
if (!blame) return window.showWarningMessage(`Unable to open diff. File is probably not under source control`);
|
||||
|
||||
commit = blame.commit;
|
||||
// If the line is uncommitted, find the previous commit
|
||||
if (commit.isUncommitted) {
|
||||
commit = new GitCommit(commit.repoPath, commit.previousSha, commit.previousFileName, commit.author, commit.date, commit.message);
|
||||
line = blame.line.line + 1;
|
||||
line = blame.line.line + 1 + gitUri.offset;
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error('[GitLens.DiffLineWithWorkingCommand]', `getBlameForLine(${line})`, ex);
|
||||
Logger.error('[GitLens.DiffLineWithWorkingCommand]', `getBlameForLine(${blameline})`, ex);
|
||||
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
|
||||
return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit, line);
|
||||
return commands.executeCommand(Commands.DiffWithWorking, uri, commit, line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Iterables } from '../system';
|
||||
import { commands, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { EditorCommand } from './commands';
|
||||
import { BuiltInCommands, Commands } from '../constants';
|
||||
import GitProvider, { GitCommit } from '../gitProvider';
|
||||
import GitProvider, { GitCommit, GitUri } from '../gitProvider';
|
||||
import { Logger } from '../logger';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
@@ -13,29 +13,33 @@ export default class DiffWithPreviousCommand extends EditorCommand {
|
||||
super(Commands.DiffWithPrevious);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>;
|
||||
async execute(editor: TextEditor): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri, commit: GitCommit, range?: Range): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri, commit: GitCommit, line?: number): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, rangeOrLine?: Range | number): Promise<any> {
|
||||
async execute(editor: TextEditor, edit?: TextEditorEdit, uri?: Uri, commit?: GitCommit, rangeOrLine?: Range | number): Promise<any> {
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
|
||||
let line = editor.selection.active.line;
|
||||
if (typeof rangeOrLine === 'number') {
|
||||
line = rangeOrLine || line;
|
||||
}
|
||||
|
||||
if (!commit || rangeOrLine instanceof Range) {
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
const gitUri = GitUri.fromUri(uri);
|
||||
|
||||
try {
|
||||
const log = await this.git.getLogForFile(uri.fsPath, <Range>rangeOrLine);
|
||||
const log = await this.git.getLogForFile(gitUri.fsPath, <Range>rangeOrLine);
|
||||
if (!log) return window.showWarningMessage(`Unable to open diff. File is probably not under source control`);
|
||||
|
||||
commit = commit ? Iterables.find(log.commits.values(), _ => _.sha === commit.sha) : Iterables.first(log.commits.values());
|
||||
const sha = (commit && commit.sha) || gitUri.sha;
|
||||
commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error('[GitLens.DiffWithPreviousCommand]', `getLogForFile(${uri.fsPath})`, ex);
|
||||
Logger.error('[GitLens.DiffWithPreviousCommand]', `getLogForFile(${gitUri.fsPath})`, ex);
|
||||
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Iterables } from '../system';
|
||||
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { EditorCommand } from './commands';
|
||||
import { BuiltInCommands, Commands } from '../constants';
|
||||
import GitProvider, { GitCommit } from '../gitProvider';
|
||||
import GitProvider, { GitCommit, GitUri } from '../gitProvider';
|
||||
import { Logger } from '../logger';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -12,31 +12,36 @@ export default class DiffWithWorkingCommand extends EditorCommand {
|
||||
super(Commands.DiffWithWorking);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
|
||||
async execute(editor: TextEditor): Promise<any>;
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri): Promise<any>;
|
||||
async execute(editor: TextEditor, edit?: TextEditorEdit, uri?: Uri, commit?: GitCommit, line?: number): Promise<any> {
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
|
||||
line = line || editor.selection.active.line;
|
||||
|
||||
if (!commit || GitProvider.isUncommitted(commit.sha)) {
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
const gitUri = GitUri.fromUri(uri);
|
||||
|
||||
try {
|
||||
const log = await this.git.getLogForFile(uri.fsPath);
|
||||
const log = await this.git.getLogForFile(gitUri.fsPath);
|
||||
if (!log) return window.showWarningMessage(`Unable to open diff. File is probably not under source control`);
|
||||
|
||||
commit = Iterables.first(log.commits.values());
|
||||
commit = (gitUri.sha && log.commits.get(gitUri.sha)) || Iterables.first(log.commits.values());
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error('[GitLens.DiffWithWorkingCommand]', `getLogForFile(${uri.fsPath})`, ex);
|
||||
Logger.error('[GitLens.DiffWithWorkingCommand]', `getLogForFile(${gitUri.fsPath})`, ex);
|
||||
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
|
||||
const gitUri = GitUri.fromUri(uri);
|
||||
|
||||
try {
|
||||
const compare = await this.git.getVersionedFile(commit.uri.fsPath, commit.repoPath, commit.sha);
|
||||
await commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), uri, `${path.basename(commit.uri.fsPath)} (${commit.sha}) ↔ ${path.basename(uri.fsPath)}`);
|
||||
await commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), gitUri.fileUri(), `${path.basename(commit.uri.fsPath)} (${commit.sha}) ↔ ${path.basename(gitUri.fsPath)}`);
|
||||
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: line, at: 'center' });
|
||||
}
|
||||
catch (ex) {
|
||||
|
||||
@@ -3,30 +3,23 @@ import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import BlameAnnotationController from '../blameAnnotationController';
|
||||
import { EditorCommand } from './commands';
|
||||
import { Commands } from '../constants';
|
||||
import GitProvider from '../gitProvider';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export default class ShowBlameCommand extends EditorCommand {
|
||||
constructor(private git: GitProvider, private annotationController: BlameAnnotationController) {
|
||||
constructor(private annotationController: BlameAnnotationController) {
|
||||
super(Commands.ShowBlame);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) {
|
||||
if (sha) {
|
||||
return this.annotationController.toggleBlameAnnotation(editor, sha);
|
||||
}
|
||||
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string): Promise<any> {
|
||||
try {
|
||||
const blame = await this.git.getBlameForLine(uri.fsPath, editor.selection.active.line);
|
||||
return this.annotationController.showBlameAnnotation(editor, blame && blame.commit.sha);
|
||||
if (sha) {
|
||||
return this.annotationController.showBlameAnnotation(editor, sha);
|
||||
}
|
||||
|
||||
return this.annotationController.showBlameAnnotation(editor, editor.selection.active.line);
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error('[GitLens.ShowBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex);
|
||||
Logger.error('GitLens.ShowBlameCommand', ex);
|
||||
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { EditorCommand } from './commands';
|
||||
import { BuiltInCommands, Commands } from '../constants';
|
||||
import GitProvider from '../gitProvider';
|
||||
import GitProvider, { GitUri } from '../gitProvider';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export default class ShowBlameHistoryCommand extends EditorCommand {
|
||||
@@ -20,8 +20,10 @@ export default class ShowBlameHistoryCommand extends EditorCommand {
|
||||
position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
|
||||
}
|
||||
|
||||
const gitUri = GitUri.fromUri(uri);
|
||||
|
||||
try {
|
||||
const locations = await this.git.getBlameLocations(uri.fsPath, range);
|
||||
const locations = await this.git.getBlameLocations(gitUri.fsPath, range);
|
||||
if (!locations) return window.showWarningMessage(`Unable to show blame history. File is probably not under source control`);
|
||||
|
||||
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations);
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { EditorCommand } from './commands';
|
||||
import { BuiltInCommands, Commands } from '../constants';
|
||||
import GitProvider from '../gitProvider';
|
||||
import GitProvider, { GitUri } from '../gitProvider';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export default class ShowHistoryCommand extends EditorCommand {
|
||||
export default class ShowFileHistoryCommand extends EditorCommand {
|
||||
constructor(private git: GitProvider) {
|
||||
super(Commands.ShowHistory);
|
||||
super(Commands.ShowFileHistory);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, position?: Position, sha?: string, line?: number) {
|
||||
@@ -19,14 +19,16 @@ export default class ShowHistoryCommand extends EditorCommand {
|
||||
position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
|
||||
}
|
||||
|
||||
const gitUri = GitUri.fromUri(uri);
|
||||
|
||||
try {
|
||||
const locations = await this.git.getLogLocations(uri.fsPath, sha, line);
|
||||
const locations = await this.git.getLogLocations(gitUri.fsPath, sha, line);
|
||||
if (!locations) return window.showWarningMessage(`Unable to show history. File is probably not under source control`);
|
||||
|
||||
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations);
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error('[GitLens.ShowHistoryCommand]', 'getLogLocations', ex);
|
||||
Logger.error('[GitLens.ShowFileHistoryCommand]', 'getLogLocations', ex);
|
||||
return window.showErrorMessage(`Unable to show history. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
@@ -3,30 +3,23 @@ import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import BlameAnnotationController from '../blameAnnotationController';
|
||||
import { EditorCommand } from './commands';
|
||||
import { Commands } from '../constants';
|
||||
import GitProvider from '../gitProvider';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export default class ToggleBlameCommand extends EditorCommand {
|
||||
constructor(private git: GitProvider, private annotationController: BlameAnnotationController) {
|
||||
constructor(private annotationController: BlameAnnotationController) {
|
||||
super(Commands.ToggleBlame);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) {
|
||||
if (sha) {
|
||||
return this.annotationController.toggleBlameAnnotation(editor, sha);
|
||||
}
|
||||
|
||||
if (!(uri instanceof Uri)) {
|
||||
if (!editor.document) return undefined;
|
||||
uri = editor.document.uri;
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string): Promise<any> {
|
||||
try {
|
||||
const blame = await this.git.getBlameForLine(uri.fsPath, editor.selection.active.line);
|
||||
return this.annotationController.toggleBlameAnnotation(editor, blame && blame.commit.sha);
|
||||
if (sha) {
|
||||
return this.annotationController.toggleBlameAnnotation(editor, sha);
|
||||
}
|
||||
|
||||
return this.annotationController.toggleBlameAnnotation(editor, editor.selection.active.line);
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error('[GitLens.ToggleBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex);
|
||||
Logger.error('GitLens.ToggleBlameCommand', ex);
|
||||
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
|
||||
42
src/comparers.ts
Normal file
42
src/comparers.ts
Normal 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
|
||||
};
|
||||
@@ -16,11 +16,11 @@ export interface IBlameConfig {
|
||||
};
|
||||
}
|
||||
|
||||
export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory';
|
||||
export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory';
|
||||
export const CodeLensCommand = {
|
||||
BlameAnnotate: Commands.ToggleBlame as CodeLensCommand,
|
||||
ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand,
|
||||
ShowHistory: Commands.ShowHistory as CodeLensCommand,
|
||||
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
|
||||
DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand,
|
||||
GitViewHistory: 'git.viewFileHistory' as CodeLensCommand
|
||||
};
|
||||
@@ -61,10 +61,11 @@ export interface ICodeLensesConfig {
|
||||
authors: ICodeLensConfig;
|
||||
}
|
||||
|
||||
export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory';
|
||||
export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory';
|
||||
export const StatusBarCommand = {
|
||||
BlameAnnotate: Commands.ToggleBlame as StatusBarCommand,
|
||||
BlameExplorer: Commands.ShowBlameHistory as StatusBarCommand,
|
||||
ShowBlameHistory: Commands.ShowBlameHistory as StatusBarCommand,
|
||||
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
|
||||
DiffWithPrevious: Commands.DiffWithPrevious as StatusBarCommand,
|
||||
ToggleCodeLens: Commands.ToggleCodeLens as StatusBarCommand,
|
||||
GitViewHistory: 'git.viewFileHistory' as StatusBarCommand
|
||||
@@ -85,6 +86,9 @@ export const OutputLevel = {
|
||||
export interface IAdvancedConfig {
|
||||
caching: {
|
||||
enabled: boolean;
|
||||
statusBar: {
|
||||
maxLines: number;
|
||||
}
|
||||
};
|
||||
debug: boolean;
|
||||
git: string;
|
||||
|
||||
@@ -14,7 +14,7 @@ export const BuiltInCommands = {
|
||||
ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands
|
||||
};
|
||||
|
||||
export type Commands = 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showHistory' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens';
|
||||
export type Commands = 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens';
|
||||
export const Commands = {
|
||||
DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands,
|
||||
DiffLineWithPrevious: 'gitlens.diffLineWithPrevious' as Commands,
|
||||
@@ -22,7 +22,7 @@ export const Commands = {
|
||||
DiffLineWithWorking: 'gitlens.diffLineWithWorking' as Commands,
|
||||
ShowBlame: 'gitlens.showBlame' as Commands,
|
||||
ShowBlameHistory: 'gitlens.showBlameHistory' as Commands,
|
||||
ShowHistory: 'gitlens.showHistory' as Commands,
|
||||
ShowFileHistory: 'gitlens.showFileHistory' as Commands,
|
||||
ToggleBlame: 'gitlens.toggleBlame' as Commands,
|
||||
ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ import DiffWithWorkingCommand from './commands/diffWithWorking';
|
||||
import DiffLineWithWorkingCommand from './commands/diffLineWithWorking';
|
||||
import ShowBlameCommand from './commands/showBlame';
|
||||
import ShowBlameHistoryCommand from './commands/showBlameHistory';
|
||||
import ShowHistoryCommand from './commands/showHistory';
|
||||
import ShowFileHistoryCommand from './commands/showFileHistory';
|
||||
import ToggleBlameCommand from './commands/toggleBlame';
|
||||
import ToggleCodeLensCommand from './commands/toggleCodeLens';
|
||||
|
||||
@@ -64,10 +64,10 @@ export async function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(new DiffLineWithWorkingCommand(git));
|
||||
context.subscriptions.push(new DiffWithPreviousCommand(git));
|
||||
context.subscriptions.push(new DiffLineWithPreviousCommand(git));
|
||||
context.subscriptions.push(new ShowBlameCommand(git, annotationController));
|
||||
context.subscriptions.push(new ToggleBlameCommand(git, annotationController));
|
||||
context.subscriptions.push(new ShowBlameCommand(annotationController));
|
||||
context.subscriptions.push(new ToggleBlameCommand(annotationController));
|
||||
context.subscriptions.push(new ShowBlameHistoryCommand(git));
|
||||
context.subscriptions.push(new ShowHistoryCommand(git));
|
||||
context.subscriptions.push(new ShowFileHistoryCommand(git));
|
||||
context.subscriptions.push(new ToggleCodeLensCommand(git));
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ export class GitBlameParserEnricher implements IGitEnricher<IGitBlame> {
|
||||
}
|
||||
|
||||
private _parseEntries(data: string): IBlameEntry[] {
|
||||
if (!data) return null;
|
||||
if (!data) return undefined;
|
||||
|
||||
const lines = data.split('\n');
|
||||
if (!lines.length) return null;
|
||||
if (!lines.length) return undefined;
|
||||
|
||||
const entries: IBlameEntry[] = [];
|
||||
|
||||
@@ -107,7 +107,7 @@ export class GitBlameParserEnricher implements IGitEnricher<IGitBlame> {
|
||||
entry.fileName = lineParts.slice(1).join(' ');
|
||||
|
||||
entries.push(entry);
|
||||
entry = null;
|
||||
entry = undefined;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -120,7 +120,7 @@ export class GitBlameParserEnricher implements IGitEnricher<IGitBlame> {
|
||||
|
||||
enrich(data: string, fileName: string): IGitBlame {
|
||||
const entries = this._parseEntries(data);
|
||||
if (!entries) return null;
|
||||
if (!entries) return undefined;
|
||||
|
||||
const authors: Map<string, IGitAuthor> = new Map();
|
||||
const commits: Map<string, GitCommit> = new Map();
|
||||
|
||||
@@ -19,10 +19,10 @@ interface ILogEntry {
|
||||
|
||||
export class GitLogParserEnricher implements IGitEnricher<IGitLog> {
|
||||
private _parseEntries(data: string): ILogEntry[] {
|
||||
if (!data) return null;
|
||||
if (!data) return undefined;
|
||||
|
||||
const lines = data.split('\n');
|
||||
if (!lines.length) return null;
|
||||
if (!lines.length) return undefined;
|
||||
|
||||
const entries: ILogEntry[] = [];
|
||||
|
||||
@@ -49,7 +49,7 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> {
|
||||
break;
|
||||
|
||||
case 'author-date':
|
||||
entry.authorDate = lineParts.slice(1).join(' ').trim();
|
||||
entry.authorDate = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`;
|
||||
break;
|
||||
|
||||
// case 'committer':
|
||||
@@ -76,7 +76,7 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> {
|
||||
}
|
||||
|
||||
entries.push(entry);
|
||||
entry = null;
|
||||
entry = undefined;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -89,7 +89,7 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> {
|
||||
|
||||
enrich(data: string, fileName: string): IGitLog {
|
||||
const entries = this._parseEntries(data);
|
||||
if (!entries) return null;
|
||||
if (!entries) return undefined;
|
||||
|
||||
const authors: Map<string, IGitAuthor> = new Map();
|
||||
const commits: Map<string, GitCommit> = new Map();
|
||||
|
||||
@@ -83,13 +83,13 @@ export default class Git {
|
||||
static log(fileName: string, repoPath?: string) {
|
||||
const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath);
|
||||
|
||||
return gitCommand(root, 'log', `--follow`, `--name-only`, `--no-merges`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, file);
|
||||
return gitCommand(root, 'log', `--follow`, `--name-only`, `--no-merges`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, file);
|
||||
}
|
||||
|
||||
static logRange(fileName: string, start: number, end: number, repoPath?: string) {
|
||||
const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath);
|
||||
|
||||
return gitCommand(root, 'log', `--name-only`, `--no-merges`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, `-L ${start},${end}:${file}`);
|
||||
return gitCommand(root, 'log', `--name-only`, `--no-merges`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, `-L ${start},${end}:${file}`);
|
||||
}
|
||||
|
||||
static getVersionedFile(fileName: string, repoPath: string, sha: string) {
|
||||
@@ -102,7 +102,7 @@ export default class Git {
|
||||
return;
|
||||
}
|
||||
|
||||
//Logger.log(`getVersionedFile(${fileName}, ${sha}); destination=${destination}`);
|
||||
Logger.log(`getVersionedFile(${fileName}, ${repoPath}, ${sha}); destination=${destination}`);
|
||||
fs.appendFile(destination, data, err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
'use strict';
|
||||
import { spawnPromise } from 'spawn-rx';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export default class GitBlameCodeLensProvider implements CodeLensProvider {
|
||||
resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable<CodeLens> {
|
||||
if (lens instanceof GitDiffWithWorkingTreeCodeLens) return this._resolveDiffWithWorkingTreeCodeLens(lens, token);
|
||||
if (lens instanceof GitDiffWithPreviousCodeLens) return this._resolveGitDiffWithPreviousCodeLens(lens, token);
|
||||
return Promise.reject<CodeLens>(null);
|
||||
return Promise.reject<CodeLens>(undefined);
|
||||
}
|
||||
|
||||
_resolveDiffWithWorkingTreeCodeLens(lens: GitDiffWithWorkingTreeCodeLens, token: CancellationToken): Thenable<CodeLens> {
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
||||
let languageLocations = this._config.codeLens.languageLocations.find(_ => _.language.toLowerCase() === document.languageId);
|
||||
if (languageLocations == null) {
|
||||
languageLocations = <ICodeLensLanguageLocation>{
|
||||
language: null,
|
||||
language: undefined,
|
||||
location: this._config.codeLens.location,
|
||||
customSymbols: this._config.codeLens.locationCustomSymbols
|
||||
};
|
||||
@@ -171,7 +171,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
||||
resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable<CodeLens> {
|
||||
if (lens instanceof GitRecentChangeCodeLens) return this._resolveGitRecentChangeCodeLens(lens, token);
|
||||
if (lens instanceof GitAuthorsCodeLens) return this._resolveGitAuthorsCodeLens(lens, token);
|
||||
return Promise.reject<CodeLens>(null);
|
||||
return Promise.reject<CodeLens>(undefined);
|
||||
}
|
||||
|
||||
async _resolveGitRecentChangeCodeLens(lens: GitRecentChangeCodeLens, token: CancellationToken): Promise<CodeLens> {
|
||||
@@ -186,7 +186,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
||||
switch (this._config.codeLens.recentChange.command) {
|
||||
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand<GitRecentChangeCodeLens>(title, lens, blame);
|
||||
case CodeLensCommand.ShowBlameHistory: return this._applyShowBlameHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame);
|
||||
case CodeLensCommand.ShowHistory: return this._applyShowHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame, recentCommit);
|
||||
case CodeLensCommand.ShowFileHistory: return this._applyShowFileHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame, recentCommit);
|
||||
case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand<GitRecentChangeCodeLens>(title, lens, blame, recentCommit);
|
||||
case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame);
|
||||
default: return lens;
|
||||
@@ -202,7 +202,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
||||
switch (this._config.codeLens.authors.command) {
|
||||
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand<GitAuthorsCodeLens>(title, lens, blame);
|
||||
case CodeLensCommand.ShowBlameHistory: return this._applyShowBlameHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
|
||||
case CodeLensCommand.ShowHistory: return this._applyShowHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
|
||||
case CodeLensCommand.ShowFileHistory: return this._applyShowFileHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
|
||||
case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand<GitAuthorsCodeLens>(title, lens, blame);
|
||||
case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
|
||||
default: return lens;
|
||||
@@ -227,7 +227,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
||||
return lens;
|
||||
}
|
||||
|
||||
_applyShowHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines, commit?: GitCommit) {
|
||||
_applyShowFileHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines, commit?: GitCommit) {
|
||||
let line = lens.range.start.line;
|
||||
const blameLine = commit.lines.find(_ => _.line === line);
|
||||
if (blameLine) {
|
||||
@@ -237,7 +237,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
||||
const position = lens.isFullRange ? new Position(1, 0) : lens.range.start;
|
||||
lens.command = {
|
||||
title: title,
|
||||
command: Commands.ShowHistory,
|
||||
command: Commands.ShowFileHistory,
|
||||
arguments: [Uri.file(lens.fileName), position, commit.sha, line]
|
||||
};
|
||||
return lens;
|
||||
@@ -262,7 +262,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
||||
}
|
||||
|
||||
_applyGitHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines) {
|
||||
if (!this._hasGitHistoryExtension) return this._applyShowHistoryCommand(title, lens, blame);
|
||||
if (!this._hasGitHistoryExtension) return this._applyShowFileHistoryCommand(title, lens, blame);
|
||||
|
||||
lens.command = {
|
||||
title: title,
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
'use strict';
|
||||
import { ExtensionContext, TextDocumentContentProvider, Uri } from 'vscode';
|
||||
import { ExtensionContext, TextDocumentContentProvider, Uri, window } from 'vscode';
|
||||
import { DocumentSchemes } from './constants';
|
||||
import GitProvider from './gitProvider';
|
||||
import { Logger } from './logger';
|
||||
import * as path from 'path';
|
||||
|
||||
export default class GitContentProvider implements TextDocumentContentProvider {
|
||||
static scheme = DocumentSchemes.Git;
|
||||
|
||||
constructor(context: ExtensionContext, private git: GitProvider) { }
|
||||
|
||||
provideTextDocumentContent(uri: Uri): string | Thenable<string> {
|
||||
async provideTextDocumentContent(uri: Uri): Promise<string> {
|
||||
const data = GitProvider.fromGitUri(uri);
|
||||
return this.git.getVersionedFileText(data.originalFileName || data.fileName, data.repoPath, data.sha)
|
||||
.then(text => data.decoration ? `${data.decoration}\n${text}` : text)
|
||||
.catch(ex => Logger.error('[GitLens.GitContentProvider]', 'getVersionedFileText', ex));
|
||||
const fileName = data.originalFileName || data.fileName;
|
||||
try {
|
||||
let text = await this.git.getVersionedFileText(fileName, data.repoPath, data.sha);
|
||||
if (data.decoration) {
|
||||
text = `${data.decoration}\n${text}`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error('[GitLens.GitContentProvider]', 'getVersionedFileText', ex);
|
||||
await window.showErrorMessage(`Unable to show Git revision ${data.sha} of '${path.relative(data.repoPath, fileName)}'`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,16 +39,16 @@ enum RemoveCacheReason {
|
||||
}
|
||||
|
||||
export default class GitProvider extends Disposable {
|
||||
private _cache: Map<string, CacheEntry> | null;
|
||||
private _cacheDisposable: Disposable | null;
|
||||
private _cache: Map<string, CacheEntry> | undefined;
|
||||
private _cacheDisposable: Disposable | undefined;
|
||||
|
||||
private _config: IConfig;
|
||||
private _disposable: Disposable;
|
||||
private _codeLensProviderDisposable: Disposable | null;
|
||||
private _codeLensProviderDisposable: Disposable | undefined;
|
||||
private _codeLensProviderSelector: DocumentFilter;
|
||||
private _gitignore: Promise<ignore.Ignore>;
|
||||
|
||||
static EmptyPromise: Promise<IGitBlame | IGitLog> = Promise.resolve(null);
|
||||
static EmptyPromise: Promise<IGitBlame | IGitLog> = Promise.resolve(undefined);
|
||||
static BlameFormat = GitBlameFormat.incremental;
|
||||
|
||||
constructor(private context: ExtensionContext) {
|
||||
@@ -58,7 +58,7 @@ export default class GitProvider extends Disposable {
|
||||
|
||||
this._onConfigure();
|
||||
|
||||
this._gitignore = new Promise<ignore.Ignore | null>((resolve, reject) => {
|
||||
this._gitignore = new Promise<ignore.Ignore | undefined>((resolve, reject) => {
|
||||
const gitignorePath = path.join(repoPath, '.gitignore');
|
||||
fs.exists(gitignorePath, e => {
|
||||
if (e) {
|
||||
@@ -67,11 +67,11 @@ export default class GitProvider extends Disposable {
|
||||
resolve(ignore().add(data));
|
||||
return;
|
||||
}
|
||||
resolve(null);
|
||||
resolve(undefined);
|
||||
});
|
||||
return;
|
||||
}
|
||||
resolve(null);
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,7 +106,7 @@ export default class GitProvider extends Disposable {
|
||||
this._codeLensProviderSelector = GitCodeLensProvider.selector;
|
||||
this._codeLensProviderDisposable = languages.registerCodeLensProvider(this._codeLensProviderSelector, new GitCodeLensProvider(this.context, this));
|
||||
} else {
|
||||
this._codeLensProviderDisposable = null;
|
||||
this._codeLensProviderDisposable = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,9 +127,9 @@ export default class GitProvider extends Disposable {
|
||||
this._cacheDisposable = Disposable.from(...disposables);
|
||||
} else {
|
||||
this._cacheDisposable && this._cacheDisposable.dispose();
|
||||
this._cacheDisposable = null;
|
||||
this._cacheDisposable = undefined;
|
||||
this._cache && this._cache.clear();
|
||||
this._cache = null;
|
||||
this._cache = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,13 +167,15 @@ export default class GitProvider extends Disposable {
|
||||
return Git.repoPath(cwd);
|
||||
}
|
||||
|
||||
getBlameForFile(fileName: string): Promise<IGitBlame | null> {
|
||||
Logger.log(`getBlameForFile('${fileName}')`);
|
||||
getBlameForFile(fileName: string, sha?: string, repoPath?: string): Promise<IGitBlame | undefined> {
|
||||
Logger.log(`getBlameForFile('${fileName}', ${sha}, ${repoPath})`);
|
||||
fileName = Git.normalizePath(fileName);
|
||||
|
||||
const useCaching = this.UseCaching && !sha;
|
||||
|
||||
let cacheKey: string | undefined;
|
||||
let entry: CacheEntry | undefined;
|
||||
if (this.UseCaching) {
|
||||
if (useCaching) {
|
||||
cacheKey = this._getCacheEntryKey(fileName);
|
||||
entry = this._cache.get(cacheKey);
|
||||
|
||||
@@ -189,11 +191,11 @@ export default class GitProvider extends Disposable {
|
||||
return <Promise<IGitBlame>>GitProvider.EmptyPromise;
|
||||
}
|
||||
|
||||
return Git.blame(GitProvider.BlameFormat, fileName)
|
||||
return Git.blame(GitProvider.BlameFormat, fileName, sha, repoPath)
|
||||
.then(data => new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName))
|
||||
.catch(ex => {
|
||||
// Trap and cache expected blame errors
|
||||
if (this.UseCaching) {
|
||||
if (useCaching) {
|
||||
const msg = ex && ex.toString();
|
||||
Logger.log(`Replace blame cache with empty promise for '${cacheKey}'`);
|
||||
|
||||
@@ -206,11 +208,11 @@ export default class GitProvider extends Disposable {
|
||||
this._cache.set(cacheKey, entry);
|
||||
return <Promise<IGitBlame>>GitProvider.EmptyPromise;
|
||||
}
|
||||
return null;
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
if (this.UseCaching) {
|
||||
if (useCaching) {
|
||||
Logger.log(`Add blame cache for '${cacheKey}'`);
|
||||
|
||||
entry.blame = <ICachedBlame>{
|
||||
@@ -224,13 +226,13 @@ export default class GitProvider extends Disposable {
|
||||
return promise;
|
||||
}
|
||||
|
||||
async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise<IGitBlameLine | null> {
|
||||
async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise<IGitBlameLine | undefined> {
|
||||
Logger.log(`getBlameForLine('${fileName}', ${line}, ${sha}, ${repoPath})`);
|
||||
|
||||
if (this.UseCaching && !sha) {
|
||||
const blame = await this.getBlameForFile(fileName);
|
||||
const blameLine = blame && blame.lines[line];
|
||||
if (!blameLine) return null;
|
||||
if (!blameLine) return undefined;
|
||||
|
||||
const commit = blame.commits.get(blameLine.sha);
|
||||
return <IGitBlameLine>{
|
||||
@@ -245,7 +247,7 @@ export default class GitProvider extends Disposable {
|
||||
try {
|
||||
const data = await Git.blameLines(GitProvider.BlameFormat, fileName, line + 1, line + 1, sha, repoPath);
|
||||
const blame = new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName);
|
||||
if (!blame) return null;
|
||||
if (!blame) return undefined;
|
||||
|
||||
const commit = Iterables.first(blame.commits.values());
|
||||
if (repoPath) {
|
||||
@@ -258,15 +260,15 @@ export default class GitProvider extends Disposable {
|
||||
};
|
||||
}
|
||||
catch (ex) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async getBlameForRange(fileName: string, range: Range): Promise<IGitBlameLines | null> {
|
||||
async getBlameForRange(fileName: string, range: Range): Promise<IGitBlameLines | undefined> {
|
||||
Logger.log(`getBlameForRange('${fileName}', ${range})`);
|
||||
|
||||
const blame = await this.getBlameForFile(fileName);
|
||||
if (!blame) return null;
|
||||
if (!blame) return undefined;
|
||||
|
||||
if (!blame.lines.length) return Object.assign({ allLines: blame.lines }, blame);
|
||||
|
||||
@@ -312,11 +314,11 @@ export default class GitProvider extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
async getBlameForShaRange(fileName: string, sha: string, range: Range): Promise<IGitBlameCommitLines | null> {
|
||||
async getBlameForShaRange(fileName: string, sha: string, range: Range): Promise<IGitBlameCommitLines | undefined> {
|
||||
Logger.log(`getBlameForShaRange('${fileName}', ${sha}, ${range})`);
|
||||
|
||||
const blame = await this.getBlameForFile(fileName);
|
||||
if (!blame) return null;
|
||||
if (!blame) return undefined;
|
||||
|
||||
const lines = blame.lines.slice(range.start.line, range.end.line + 1).filter(l => l.sha === sha);
|
||||
let commit = blame.commits.get(sha);
|
||||
@@ -329,11 +331,11 @@ export default class GitProvider extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
async getBlameLocations(fileName: string, range: Range): Promise<Location[] | null> {
|
||||
async getBlameLocations(fileName: string, range: Range): Promise<Location[] | undefined> {
|
||||
Logger.log(`getBlameForShaRange('${fileName}', ${range})`);
|
||||
|
||||
const blame = await this.getBlameForRange(fileName, range);
|
||||
if (!blame) return null;
|
||||
if (!blame) return undefined;
|
||||
|
||||
const commitCount = blame.commits.size;
|
||||
|
||||
@@ -351,7 +353,7 @@ export default class GitProvider extends Disposable {
|
||||
return locations;
|
||||
}
|
||||
|
||||
getLogForFile(fileName: string, range?: Range): Promise<IGitLog | null> {
|
||||
getLogForFile(fileName: string, range?: Range): Promise<IGitLog | undefined> {
|
||||
Logger.log(`getLogForFile('${fileName}', ${range})`);
|
||||
fileName = Git.normalizePath(fileName);
|
||||
|
||||
@@ -392,7 +394,7 @@ export default class GitProvider extends Disposable {
|
||||
this._cache.set(cacheKey, entry);
|
||||
return <Promise<IGitLog>>GitProvider.EmptyPromise;
|
||||
}
|
||||
return null;
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -410,11 +412,11 @@ export default class GitProvider extends Disposable {
|
||||
return promise;
|
||||
}
|
||||
|
||||
async getLogLocations(fileName: string, sha?: string, line?: number): Promise<Location[] | null> {
|
||||
async getLogLocations(fileName: string, sha?: string, line?: number): Promise<Location[] | undefined> {
|
||||
Logger.log(`getLogLocations('${fileName}', ${sha}, ${line})`);
|
||||
|
||||
const log = await this.getLogForFile(fileName);
|
||||
if (!log) return null;
|
||||
if (!log) return undefined;
|
||||
|
||||
const commitCount = log.commits.size;
|
||||
|
||||
@@ -454,7 +456,7 @@ export default class GitProvider extends Disposable {
|
||||
this._codeLensProviderDisposable.dispose();
|
||||
|
||||
if (editor.document.fileName === (this._codeLensProviderSelector && this._codeLensProviderSelector.pattern)) {
|
||||
this._codeLensProviderDisposable = null;
|
||||
this._codeLensProviderDisposable = undefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -468,7 +470,7 @@ export default class GitProvider extends Disposable {
|
||||
disposables.push(window.onDidChangeActiveTextEditor(e => {
|
||||
if (e.viewColumn && e.document !== editor.document) {
|
||||
this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose();
|
||||
this._codeLensProviderDisposable = null;
|
||||
this._codeLensProviderDisposable = undefined;
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -487,7 +489,7 @@ export default class GitProvider extends Disposable {
|
||||
return data;
|
||||
}
|
||||
|
||||
static fromGitUri(uri: Uri) {
|
||||
static fromGitUri(uri: Uri): IGitUriData {
|
||||
if (uri.scheme !== DocumentSchemes.Git) throw new Error(`fromGitUri(uri=${uri}) invalid scheme`);
|
||||
return GitProvider._fromGitUri<IGitUriData>(uri);
|
||||
}
|
||||
@@ -538,6 +540,42 @@ export default class GitProvider extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export class GitUri extends Uri {
|
||||
offset: number;
|
||||
repoPath?: string | undefined;
|
||||
sha?: string | undefined;
|
||||
|
||||
constructor(uri?: Uri) {
|
||||
super();
|
||||
if (!uri) return;
|
||||
|
||||
const base = <any>this;
|
||||
base._scheme = uri.scheme;
|
||||
base._authority = uri.authority;
|
||||
base._path = uri.path;
|
||||
base._query = uri.query;
|
||||
base._fragment = uri.fragment;
|
||||
|
||||
this.offset = 0;
|
||||
if (uri.scheme === DocumentSchemes.Git || uri.scheme === DocumentSchemes.GitBlame) {
|
||||
const data = GitProvider.fromGitUri(uri);
|
||||
base._fsPath = data.originalFileName || data.fileName;
|
||||
|
||||
this.offset = (data.decoration && data.decoration.split('\n').length) || 0;
|
||||
this.repoPath = data.repoPath;
|
||||
this.sha = data.sha;
|
||||
}
|
||||
}
|
||||
|
||||
fileUri() {
|
||||
return Uri.file(this.fsPath);
|
||||
}
|
||||
|
||||
static fromUri(uri: Uri) {
|
||||
return new GitUri(uri);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IGitUriData {
|
||||
repoPath: string;
|
||||
fileName: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
'use strict';
|
||||
// export * from './system/array';
|
||||
// export * from './system/disposable';
|
||||
// export * from './system/element';
|
||||
|
||||
@@ -58,4 +58,11 @@ export namespace Iterables {
|
||||
export function next<T>(source: IterableIterator<T>): T {
|
||||
return source.next().value;
|
||||
}
|
||||
|
||||
export function some<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): boolean {
|
||||
for (const item of source) {
|
||||
if (predicate(item)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user