diff --git a/.vscode/launch.json b/.vscode/launch.json
index c77b2ad..ce5c57d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,7 +11,7 @@
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "${workspaceRoot}/out/src",
- "preLaunchTask": "npm"
+ "preLaunchTask": "compile"
},
{
"name": "Launch Tests",
@@ -22,7 +22,7 @@
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "${workspaceRoot}/out/test",
- "preLaunchTask": "npm"
+ "preLaunchTask": "compile"
}
]
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index fb7f662..1a6b90c 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -9,22 +9,36 @@
// A task runner that calls a custom npm script that compiles the extension.
{
"version": "0.1.0",
-
- // we want to run npm
"command": "npm",
-
- // the command is a shell script
+ "args": ["run"],
"isShellCommand": true,
-
- // show the output window only if unrecognized errors occur.
- "showOutput": "silent",
-
- // we run the custom script "compile" as defined in package.json
- "args": ["run", "compile", "--loglevel", "silent"],
-
- // The tsc compiler is started in watching mode
- "isWatching": true,
-
+ "showOutput": "always",
+ "suppressTaskName": true,
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
- "problemMatcher": "$tsc-watch"
+ "tasks": [{
+ "taskName": "compile",
+ "args": ["compile", "--loglevel", "silent"],
+ "isBuildCommand": true,
+ "isWatching": true,
+ "problemMatcher": "$tsc-watch"
+ }, {
+ "taskName": "tslint",
+ "args": ["lint", "--loglevel", "silent"],
+ "isWatching": true,
+ "problemMatcher": {
+ "owner": "tslint",
+ "fileLocation": [
+ "relative",
+ "${workspaceRoot}"
+ ],
+ "severity": "warning",
+ "pattern": {
+ "regexp": "^(\\S.*)\\[(\\d+), (\\d+)\\]:\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "message": 4
+ }
+ }
+ }]
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 9bdb79c..371615c 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,13 @@ Must be using Git and it must be in your path.
---
## Release Notes
+### 1.0.0
+
+ - Adds support for git history (log)
+ - Changes `gitlens.diffWithPrevious` command to only be line sensitive if blame annotations are visible, otherwise it uses file history
+ - Changes `gitlens.diffWithWorking` command to only be line sensitive if blame annotations are visible, otherwise it uses file history
+ - Fixes issue where blame annotations would not be cleared properly when switching between open files
+
### 0.5.5
- Fixes another off-by-one issue when diffing with caching
diff --git a/package.json b/package.json
index fe5b567..1b41f66 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
},
"publisher": "eamodio",
"engines": {
- "vscode": "^1.5.0"
+ "vscode": "^1.6.0"
},
"license": "SEE LICENSE IN LICENSE",
"displayName": "GitLens",
@@ -25,7 +25,7 @@
"icon": "images/gitlens-icon.png",
"preview": false,
"homepage": "https://github.com/eamodio/vscode-gitlens/blob/master/README.md",
- "bugs": {
+ "bugs": {
"url": "https://github.com/eamodio/vscode-gitlens/issues"
},
"repository": {
@@ -98,6 +98,7 @@
"enum": [
"gitlens.toggleBlame",
"gitlens.showBlameHistory",
+ "gitlens.showHistory",
"gitlens.diffWithPrevious",
"git.viewFileHistory"
],
@@ -114,6 +115,7 @@
"enum": [
"gitlens.toggleBlame",
"gitlens.showBlameHistory",
+ "gitlens.showHistory",
"gitlens.diffWithPrevious",
"git.viewFileHistory"
],
@@ -130,6 +132,7 @@
"enum": [
"gitlens.toggleBlame",
"gitlens.showBlameHistory",
+ "gitlens.showHistory",
"gitlens.diffWithPrevious",
"gitlens.toggleCodeLens",
"git.viewFileHistory"
@@ -147,31 +150,30 @@
"command": "gitlens.diffWithPrevious",
"title": "Open Diff with Previous Commit",
"category": "GitLens"
- },
- {
+ }, {
"command": "gitlens.diffWithWorking",
"title": "Open Diff with Working Tree",
"category": "GitLens"
- },
- {
+ }, {
"command": "gitlens.showBlame",
"title": "Show Git Blame Annotations",
"category": "GitLens"
- },
- {
+ }, {
"command": "gitlens.toggleBlame",
"title": "Toggle Git Blame Annotations",
"category": "GitLens"
- },
- {
+ }, {
"command": "gitlens.toggleCodeLens",
"title": "Toggle Git CodeLens",
"category": "GitLens"
- },
- {
+ }, {
"command": "gitlens.showBlameHistory",
"title": "Open Git Blame History",
"category": "GitLens"
+ }, {
+ "command": "gitlens.showHistory",
+ "title": "Open Git History",
+ "category": "GitLens"
}],
"menus": {
"editor/title": [{
@@ -179,18 +181,15 @@
"command": "gitlens.toggleBlame",
"group": "gitlens"
}],
- "editor/context": [
- {
+ "editor/context": [{
"when": "editorTextFocus",
"command": "gitlens.diffWithWorking",
"group": "gitlens@1.0"
- },
- {
+ }, {
"when": "editorTextFocus",
"command": "gitlens.diffWithPrevious",
"group": "gitlens@1.1"
- },
- {
+ }, {
"when": "editorTextFocus",
"command": "gitlens.toggleBlame",
"group": "gitlens-blame@1.2"
@@ -201,8 +200,7 @@
"key": "alt+b",
"mac": "alt+b",
"when": "editorTextFocus"
- },
- {
+ }, {
"command": "gitlens.toggleCodeLens",
"key": "alt+shift+b",
"mac": "alt+shift+b",
@@ -213,27 +211,29 @@
"*"
],
"dependencies": {
- "ignore": "^3.1.5",
+ "ignore": "^3.2.0",
"lodash.debounce": "^4.0.8",
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.4.0",
- "moment": "^2.15.1",
+ "moment": "^2.15.2",
"spawn-rx": "^2.0.3",
- "tmp": "^0.0.29"
+ "tmp": "^0.0.30"
},
"devDependencies": {
- "typescript": "^2.0.3",
- "vscode": "^1.0.0",
- "mocha": "^3.1.0",
- "@types/node": "^6.0.41",
+ "mocha": "^3.1.2",
+ "tslint": "^3.15.1",
+ "typescript": "^2.0.6",
+ "vscode": "^1.0.3",
+ "@types/node": "^6.0.46",
"@types/mocha": "^2.2.32",
"@types/tmp": "^0.0.31"
},
"scripts": {
- "vscode:prepublish": "tsc -p ./",
"compile": "tsc -watch -p ./",
- "postinstall": "node ./node_modules/vscode/bin/install",
+ "lint": "tslint --project tslint.json",
"pack": "git clean -xdf && npm install && vsce package",
- "pub": "git clean -xdf --exclude=node_modules/ && npm install && vsce publish"
+ "postinstall": "node ./node_modules/vscode/bin/install",
+ "pub": "git clean -xdf --exclude=node_modules/ && npm install && vsce publish",
+ "vscode:prepublish": "tsc -p ./"
}
}
\ No newline at end of file
diff --git a/typings/ignore.d.ts b/src/@types/ignore/index.d.ts
similarity index 100%
rename from typings/ignore.d.ts
rename to src/@types/ignore/index.d.ts
diff --git a/typings/spawn-rx.d.ts b/src/@types/spawn-rx/index.d.ts
similarity index 90%
rename from typings/spawn-rx.d.ts
rename to src/@types/spawn-rx/index.d.ts
index 9133f15..c5b8179 100644
--- a/typings/spawn-rx.d.ts
+++ b/src/@types/spawn-rx/index.d.ts
@@ -1,4 +1,4 @@
-///
+///
declare module "spawn-rx" {
import { Observable } from 'rxjs/Observable';
diff --git a/src/blameAnnotationController.ts b/src/blameAnnotationController.ts
index 50207cb..146730c 100644
--- a/src/blameAnnotationController.ts
+++ b/src/blameAnnotationController.ts
@@ -1,11 +1,11 @@
-'use strict'
-import {Disposable, ExtensionContext, TextEditor, workspace} from 'vscode';
-import {BlameAnnotationProvider} from './blameAnnotationProvider';
+'use strict';
+import { Disposable, ExtensionContext, TextEditor, workspace } from 'vscode';
+import { BlameAnnotationProvider } from './blameAnnotationProvider';
import GitProvider from './gitProvider';
export default class BlameAnnotationController extends Disposable {
private _disposable: Disposable;
- private _annotationProvider: BlameAnnotationProvider|null;
+ private _annotationProvider: BlameAnnotationProvider | undefined;
constructor(private context: ExtensionContext, private git: GitProvider) {
super(() => this.dispose());
@@ -18,7 +18,7 @@ export default class BlameAnnotationController extends Disposable {
// }));
subscriptions.push(workspace.onDidCloseTextDocument(d => {
- if (!this._annotationProvider || this._annotationProvider.uri.toString() !== d.uri.toString()) return;
+ if (!this._annotationProvider || this._annotationProvider.uri.fsPath !== d.uri.fsPath) return;
this.clear();
}));
@@ -32,25 +32,31 @@ export default class BlameAnnotationController extends Disposable {
clear() {
this._annotationProvider && this._annotationProvider.dispose();
- this._annotationProvider = null;
+ this._annotationProvider = undefined;
+ }
+
+ get annotated() {
+ return this._annotationProvider !== undefined;
}
showBlameAnnotation(editor: TextEditor, sha?: string) {
if (!editor || !editor.document || editor.document.isUntitled) {
this.clear();
- return;
+ return Promise.resolve();
}
if (!this._annotationProvider) {
this._annotationProvider = new BlameAnnotationProvider(this.context, this.git, editor);
return this._annotationProvider.provideBlameAnnotation(sha);
}
+
+ return Promise.resolve();
}
toggleBlameAnnotation(editor: TextEditor, sha?: string) {
- if (!editor ||!editor.document || editor.document.isUntitled || this._annotationProvider) {
+ if (!editor || !editor.document || editor.document.isUntitled || this._annotationProvider) {
this.clear();
- return;
+ return Promise.resolve();
}
return this.showBlameAnnotation(editor, sha);
diff --git a/src/blameAnnotationProvider.ts b/src/blameAnnotationProvider.ts
index f72b67c..5860b60 100644
--- a/src/blameAnnotationProvider.ts
+++ b/src/blameAnnotationProvider.ts
@@ -1,8 +1,9 @@
-'use strict'
-import {commands, DecorationOptions, Disposable, ExtensionContext, OverviewRulerLane, Range, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, Uri, window, workspace} from 'vscode';
-import {BuiltInCommands} from './constants';
-import {BlameAnnotationStyle, IBlameConfig} from './configuration';
-import GitProvider, {IGitBlame, IGitCommit} from './gitProvider';
+'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 { BlameAnnotationStyle, IBlameConfig } from './configuration';
+import GitProvider, { IGitBlame, IGitCommit } from './gitProvider';
import * as moment from 'moment';
const blameDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
@@ -20,9 +21,9 @@ export class BlameAnnotationProvider extends Disposable {
private _config: IBlameConfig;
private _disposable: Disposable;
private _document: TextDocument;
- private _toggleWhitespace: boolean;
+ private _renderWhitespaceSetting: string;
- constructor(private context: ExtensionContext, private git: GitProvider, public editor: TextEditor) {
+ constructor(context: ExtensionContext, private git: GitProvider, public editor: TextEditor) {
super(() => this.dispose());
if (!highlightDecoration) {
@@ -30,12 +31,12 @@ export class BlameAnnotationProvider extends Disposable {
dark: {
backgroundColor: 'rgba(255, 255, 255, 0.15)',
gutterIconPath: context.asAbsolutePath('images/blame-dark.png'),
- overviewRulerColor: 'rgba(255, 255, 255, 0.75)',
+ overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
},
light: {
backgroundColor: 'rgba(0, 0, 0, 0.15)',
gutterIconPath: context.asAbsolutePath('images/blame-light.png'),
- overviewRulerColor: 'rgba(0, 0, 0, 0.75)',
+ overviewRulerColor: 'rgba(0, 0, 0, 0.75)'
},
gutterIconSize: 'contain',
overviewRulerLane: OverviewRulerLane.Right,
@@ -60,7 +61,7 @@ export class BlameAnnotationProvider extends Disposable {
dispose() {
if (this.editor) {
// HACK: This only works when switching to another editor - diffs handle whitespace toggle differently
- if (this._toggleWhitespace) {
+ if (this._renderWhitespaceSetting !== 'none') {
commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
}
@@ -71,68 +72,67 @@ export class BlameAnnotationProvider extends Disposable {
this._disposable && this._disposable.dispose();
}
- private _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent) {
- this.git.getBlameForLine(e.textEditor.document.fileName, e.selections[0].active.line)
- .then(blame => blame && this._applyCommitHighlight(blame.commit.sha));
+ 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);
+ }
}
- provideBlameAnnotation(sha?: string) {
- return this._blame.then(blame => {
- if (!blame || !blame.lines.length) return;
+ async provideBlameAnnotation(sha?: string) {
+ const blame = await this._blame;
+ if (!blame || !blame.lines.length) return;
- // HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- toggle whitespace off
- const whitespace = workspace.getConfiguration('editor').get('renderWhitespace');
- this._toggleWhitespace = whitespace !== 'false' && whitespace !== 'none';
- if (this._toggleWhitespace) {
- commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
- }
+ // HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- toggle whitespace off
+ this._renderWhitespaceSetting = workspace.getConfiguration('editor').get('renderWhitespace');
+ if (this._renderWhitespaceSetting !== 'none') {
+ commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
+ }
- let blameDecorationOptions: DecorationOptions[] | undefined;
- switch (this._config.annotation.style) {
- case BlameAnnotationStyle.Compact:
- blameDecorationOptions = this._getCompactGutterDecorations(blame);
- break;
- case BlameAnnotationStyle.Expanded:
- blameDecorationOptions = this._getExpandedGutterDecorations(blame);
- break;
- }
+ let blameDecorationOptions: DecorationOptions[] | undefined;
+ switch (this._config.annotation.style) {
+ case BlameAnnotationStyle.Compact:
+ blameDecorationOptions = this._getCompactGutterDecorations(blame);
+ break;
+ case BlameAnnotationStyle.Expanded:
+ blameDecorationOptions = this._getExpandedGutterDecorations(blame);
+ break;
+ }
- if (blameDecorationOptions) {
- this.editor.setDecorations(blameDecoration, blameDecorationOptions);
- }
+ if (blameDecorationOptions) {
+ this.editor.setDecorations(blameDecoration, blameDecorationOptions);
+ }
- sha = sha || blame.commits.values().next().value.sha;
+ sha = sha || Iterables.first(blame.commits.values()).sha;
- return this._applyCommitHighlight(sha);
- });
+ return this._applyCommitHighlight(sha);
}
- private _applyCommitHighlight(sha: string) {
- return this._blame.then(blame => {
- if (!blame || !blame.lines.length) return;
+ private async _applyCommitHighlight(sha: string) {
+ const blame = await this._blame;
+ if (!blame || !blame.lines.length) return;
- const highlightDecorationRanges = blame.lines
- .filter(l => l.sha === sha)
- .map(l => this.editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)));
+ const highlightDecorationRanges = blame.lines
+ .filter(l => l.sha === sha)
+ .map(l => this.editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)));
- this.editor.setDecorations(highlightDecoration, highlightDecorationRanges);
- });
+ this.editor.setDecorations(highlightDecoration, highlightDecorationRanges);
}
private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] {
let count = 0;
- let lastSha;
+ let lastSha: string;
return blame.lines.map(l => {
let color = l.previousSha ? '#999999' : '#6b6b6b';
let commit = blame.commits.get(l.sha);
- let hoverMessage: string | Array = [`_${l.sha}_ - ${commit.message}`, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY h:MM a')}`];
+ let hoverMessage: string | Array = [`_${l.sha}_ - ${commit.message}`, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY h:MMa')}`];
if (commit.isUncommitted) {
color = 'rgba(0, 188, 242, 0.6)';
let previous = blame.commits.get(commit.previousSha);
if (previous) {
- hoverMessage = ['Uncommitted changes', `_${previous.sha}_ - ${previous.message}`, `${previous.author}, ${moment(previous.date).format('MMMM Do, YYYY h:MM a')}`];
+ hoverMessage = ['Uncommitted changes', `_${previous.sha}_ - ${previous.message}`, `${previous.author}, ${moment(previous.date).format('MMMM Do, YYYY h:MMa')}`];
} else {
hoverMessage = ['Uncommitted changes', `_${l.previousSha}_`];
}
@@ -196,14 +196,14 @@ export class BlameAnnotationProvider extends Disposable {
return blame.lines.map(l => {
let color = l.previousSha ? '#999999' : '#6b6b6b';
let commit = blame.commits.get(l.sha);
- let hoverMessage: string | Array = [`_${l.sha}_ - ${commit.message}`, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY h:MM a')}`];
+ let hoverMessage: string | Array = [`_${l.sha}_ - ${commit.message}`, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY h:MMa')}`];
if (commit.isUncommitted) {
color = 'rgba(0, 188, 242, 0.6)';
let previous = blame.commits.get(commit.previousSha);
if (previous) {
- hoverMessage = ['Uncommitted changes', `_${previous.sha}_ - ${previous.message}`, `${previous.author}, ${moment(previous.date).format('MMMM Do, YYYY h:MM a')}`];
+ hoverMessage = ['Uncommitted changes', `_${previous.sha}_ - ${previous.message}`, `${previous.author}, ${moment(previous.date).format('MMMM Do, YYYY h:MMa')}`];
} else {
hoverMessage = ['Uncommitted changes', `_${l.previousSha}_`];
}
@@ -220,7 +220,7 @@ export class BlameAnnotationProvider extends Disposable {
private _getAuthor(commit: IGitCommit, max: number = 17, force: boolean = false) {
if (!force && !this._config.annotation.author) return '';
- let author = commit.isUncommitted ? 'Uncommitted': commit.author;
+ let author = commit.isUncommitted ? 'Uncommitted' : commit.author;
if (author.length > max) {
return `${author.substring(0, max - 1)}\\2026`;
}
diff --git a/src/blameStatusBarController.ts b/src/blameStatusBarController.ts
index 020a4cc..346da24 100644
--- a/src/blameStatusBarController.ts
+++ b/src/blameStatusBarController.ts
@@ -1,12 +1,11 @@
-'use strict'
-import {Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextEditor, window, workspace} from 'vscode';
-import {IConfig, IStatusBarConfig, StatusBarCommand} from './configuration';
-import {WorkspaceState} from './constants';
-import GitProvider, {IGitBlameLine} from './gitProvider';
+'use strict';
+import { Objects } from './system';
+import { Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextEditor, window, workspace } from 'vscode';
+import { IConfig, IStatusBarConfig, StatusBarCommand } from './configuration';
+import { WorkspaceState } from './constants';
+import GitProvider, { IGitBlameLine } from './gitProvider';
import * as moment from 'moment';
-const isEqual = require('lodash.isequal');
-
export default class BlameStatusBarController extends Disposable {
private _config: IStatusBarConfig;
private _disposable: Disposable;
@@ -34,7 +33,7 @@ export default class BlameStatusBarController extends Disposable {
private _onConfigure() {
const config = workspace.getConfiguration('').get('gitlens');
- if (!isEqual(config.statusBar, this._config)) {
+ if (!Objects.areEquivalent(config.statusBar, this._config)) {
this._statusBarDisposable && this._statusBarDisposable.dispose();
this._statusBarItem && this._statusBarItem.dispose();
@@ -69,14 +68,19 @@ export default class BlameStatusBarController extends Disposable {
this._config = config.statusBar;
}
- private _onActiveSelectionChanged(editor: TextEditor) : void {
+ private async _onActiveSelectionChanged(editor: TextEditor): Promise {
if (!editor || !editor.document || editor.document.isUntitled) {
this.clear();
return;
}
- this.git.getBlameForLine(editor.document.uri.fsPath, editor.selection.active.line)
- .then(blame => blame ? this.show(blame) : this.clear());
+ const blame = await this.git.getBlameForLine(editor.document.uri.fsPath, editor.selection.active.line);
+ if (blame) {
+ this.show(blame);
+ }
+ else {
+ this.clear();
+ }
}
clear() {
@@ -86,7 +90,7 @@ export default class BlameStatusBarController extends Disposable {
show(blameLine: IGitBlameLine) {
const commit = blameLine.commit;
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:MM a'), '', commit.message].join('\n');
+ //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) {
case StatusBarCommand.BlameAnnotate:
diff --git a/src/commands/commands.ts b/src/commands/commands.ts
index d851f65..fe675bc 100644
--- a/src/commands/commands.ts
+++ b/src/commands/commands.ts
@@ -1,33 +1,33 @@
-'use strict'
-import {commands, Disposable, TextEditor, TextEditorEdit} from 'vscode';
-import {Commands} from '../constants';
+'use strict';
+import { commands, Disposable, TextEditor, TextEditorEdit } from 'vscode';
+import { Commands } from '../constants';
export abstract class Command extends Disposable {
- private _subscriptions: Disposable;
+ private _disposable: Disposable;
constructor(command: Commands) {
super(() => this.dispose());
- this._subscriptions = commands.registerCommand(command, this.execute, this);
+ this._disposable = commands.registerCommand(command, this.execute, this);
}
dispose() {
- this._subscriptions && this._subscriptions.dispose();
+ this._disposable && this._disposable.dispose();
}
- abstract execute(...args): any;
+ abstract execute(...args: any[]): any;
}
export abstract class EditorCommand extends Disposable {
- private _subscriptions: Disposable;
+ private _disposable: Disposable;
constructor(command: Commands) {
super(() => this.dispose());
- this._subscriptions = commands.registerTextEditorCommand(command, this.execute, this);
+ this._disposable = commands.registerTextEditorCommand(command, this.execute, this);
}
dispose() {
- this._subscriptions && this._subscriptions.dispose();
+ this._disposable && this._disposable.dispose();
}
- abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args): any;
+ abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any;
}
\ No newline at end of file
diff --git a/src/commands/diffWithPrevious.ts b/src/commands/diffWithPrevious.ts
index 0ad6ebe..57fc719 100644
--- a/src/commands/diffWithPrevious.ts
+++ b/src/commands/diffWithPrevious.ts
@@ -1,51 +1,73 @@
-'use strict'
-import {commands, TextEditor, TextEditorEdit, Uri, window} from 'vscode';
-import {EditorCommand} from './commands';
-import {BuiltInCommands, Commands} from '../constants';
+'use strict';
+import { Iterables } from '../system';
+import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
+import { EditorCommand } from './commands';
+import { BuiltInCommands, Commands } from '../constants';
+import BlameAnnotationController from '../blameAnnotationController';
import GitProvider from '../gitProvider';
import * as path from 'path';
export default class DiffWithPreviousCommand extends EditorCommand {
- constructor(private git: GitProvider) {
+ constructor(private git: GitProvider, private annotationController: BlameAnnotationController) {
super(Commands.DiffWithPrevious);
}
- execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, repoPath?: string, sha?: string, shaUri?: Uri, compareWithSha?: string, compareWithUri?: Uri, line?: number) {
+ async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, repoPath?: string, sha?: string, shaUri?: Uri, compareWithSha?: string, compareWithUri?: Uri, line?: number) {
line = line || editor.selection.active.line;
- if (!sha || GitProvider.isUncommitted(sha)) {
- if (!(uri instanceof Uri)) {
- if (!editor.document) return;
- uri = editor.document.uri;
+ if (sha && !GitProvider.isUncommitted(sha)) {
+ if (!compareWithSha) {
+ return window.showInformationMessage(`Commit ${sha} has no previous commit`);
}
- return this.git.getBlameForLine(uri.fsPath, line)
- .then(blame => {
- if (!blame) return;
-
- // If the line is uncommitted, find the previous commit
- const commit = blame.commit;
- if (commit.isUncommitted) {
- return this.git.getBlameForLine(commit.previousUri.fsPath, blame.line.originalLine + 1, commit.previousSha, commit.repoPath)
- .then(prevBlame => {
- if (!prevBlame) return;
-
- const prevCommit = prevBlame.commit;
- return commands.executeCommand(Commands.DiffWithPrevious, commit.previousUri, commit.repoPath, commit.previousSha, commit.previousUri, prevCommit.sha, prevCommit.uri, blame.line.originalLine);
- })
- .catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', `getBlameForLine(${blame.line.originalLine}, ${commit.previousSha})`, ex));
- }
- return commands.executeCommand(Commands.DiffWithPrevious, commit.uri, commit.repoPath, commit.sha, commit.uri, commit.previousSha, commit.previousUri, line);
- })
- .catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', `getBlameForLine(${line})`, ex));
+ return Promise.all([this.git.getVersionedFile(shaUri.fsPath, repoPath, sha), this.git.getVersionedFile(compareWithUri.fsPath, repoPath, compareWithSha)])
+ .then(values => commands.executeCommand(BuiltInCommands.Diff, Uri.file(values[1]), Uri.file(values[0]), `${path.basename(compareWithUri.fsPath)} (${compareWithSha}) ↔ ${path.basename(shaUri.fsPath)} (${sha})`))
+ .then(() => commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: line, at: 'center' }))
+ .catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', 'getVersionedFile', ex));
+ }
+
+ if (!(uri instanceof Uri)) {
+ if (!editor.document) return undefined;
+ uri = editor.document.uri;
}
- if (!compareWithSha) {
- return window.showInformationMessage(`Commit ${sha} has no previous commit`);
+ if (this.annotationController.annotated) {
+ try {
+ const blame = await this.git.getBlameForLine(uri.fsPath, line);
+ if (!blame) return undefined;
+
+ // If the line is uncommitted, find the previous commit
+ const commit = blame.commit;
+ if (commit.isUncommitted) {
+ try {
+ const prevBlame = await this.git.getBlameForLine(commit.previousUri.fsPath, blame.line.originalLine + 1, commit.previousSha, commit.repoPath);
+ if (!prevBlame) return undefined;
+
+ const prevCommit = prevBlame.commit;
+ return commands.executeCommand(Commands.DiffWithPrevious, commit.previousUri, commit.repoPath, commit.previousSha, commit.previousUri, prevCommit.sha, prevCommit.uri, blame.line.originalLine);
+ }
+ catch (ex) {
+ console.error('[GitLens.DiffWithPreviousCommand]', `getBlameForLine(${blame.line.originalLine}, ${commit.previousSha})`, ex);
+ }
+ }
+ return commands.executeCommand(Commands.DiffWithPrevious, commit.uri, commit.repoPath, commit.sha, commit.uri, commit.previousSha, commit.previousUri, line);
+ }
+ catch (ex) {
+ console.error('[GitLens.DiffWithPreviousCommand]', `getBlameForLine(${line})`, ex);
+ }
}
+ else {
+ try {
+ const log = await this.git.getLogForFile(uri.fsPath);
+ if (!log) return undefined;
- return Promise.all([this.git.getVersionedFile(shaUri.fsPath, repoPath, sha), this.git.getVersionedFile(compareWithUri.fsPath, repoPath, compareWithSha)])
- .then(values => commands.executeCommand(BuiltInCommands.Diff, Uri.file(values[1]), Uri.file(values[0]), `${path.basename(compareWithUri.fsPath)} (${compareWithSha}) ↔ ${path.basename(shaUri.fsPath)} (${sha})`))
- .then(() => commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: line, at: 'center' }))
- .catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', 'getVersionedFile', ex));
+ const commits = log.commits.values();
+ const commit = Iterables.next(commits);
+ const prevCommit = Iterables.next(commits);
+ return commands.executeCommand(Commands.DiffWithPrevious, commit.uri, commit.repoPath, commit.sha, commit.uri, prevCommit.sha, prevCommit.uri, line);
+ }
+ catch (ex) {
+ console.error('[GitLens.DiffWithPreviousCommand]', `getLogForFile(${uri.fsPath})`, ex);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/commands/diffWithWorking.ts b/src/commands/diffWithWorking.ts
index 8124b93..9908b37 100644
--- a/src/commands/diffWithWorking.ts
+++ b/src/commands/diffWithWorking.ts
@@ -1,40 +1,58 @@
-'use strict'
-import {commands, TextEditor, TextEditorEdit, Uri, window} from 'vscode';
-import {EditorCommand} from './commands';
-import {BuiltInCommands, Commands} from '../constants';
+'use strict';
+import { Iterables } from '../system';
+import { commands, TextEditor, TextEditorEdit, Uri } from 'vscode';
+import { EditorCommand } from './commands';
+import { BuiltInCommands, Commands } from '../constants';
+import BlameAnnotationController from '../blameAnnotationController';
import GitProvider from '../gitProvider';
import * as path from 'path';
export default class DiffWithWorkingCommand extends EditorCommand {
- constructor(private git: GitProvider) {
+ constructor(private git: GitProvider, private annotationController: BlameAnnotationController) {
super(Commands.DiffWithWorking);
}
- execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, repoPath?: string, sha?: string, shaUri?: Uri, line?: number) {
+ async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, repoPath?: string, sha?: string, shaUri?: Uri, line?: number) {
line = line || editor.selection.active.line;
- if (!sha || GitProvider.isUncommitted(sha)) {
- if (!(uri instanceof Uri)) {
- if (!editor.document) return;
- uri = editor.document.uri;
- }
+ if (sha && !GitProvider.isUncommitted(sha)) {
+ return this.git.getVersionedFile(shaUri.fsPath, repoPath, sha)
+ .then(compare => commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), uri, `${path.basename(shaUri.fsPath)} (${sha}) ↔ ${path.basename(uri.fsPath)}`))
+ .then(() => commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: line, at: 'center' }))
+ .catch(ex => console.error('[GitLens.DiffWithWorkingCommand]', 'getVersionedFile', ex));
+ }
+
+ if (!(uri instanceof Uri)) {
+ if (!editor.document) return undefined;
+ uri = editor.document.uri;
+ }
- return this.git.getBlameForLine(uri.fsPath, line)
- .then(blame => {
- if (!blame) return;
+ if (this.annotationController.annotated) {
+ try {
+ const blame = await this.git.getBlameForLine(uri.fsPath, line);
+ if (!blame) return undefined;
- const commit = blame.commit;
- // If the line is uncommitted, find the previous commit
- if (commit.isUncommitted) {
- return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit.repoPath, commit.previousSha, commit.previousUri, blame.line.line + 1);
- }
- return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit.repoPath, commit.sha, commit.uri, line)
- })
- .catch(ex => console.error('[GitLens.DiffWithWorkingCommand]', `getBlameForLine(${line})`, ex));
- };
+ const commit = blame.commit;
+ // If the line is uncommitted, find the previous commit
+ if (commit.isUncommitted) {
+ return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit.repoPath, commit.previousSha, commit.previousUri, blame.line.line + 1);
+ }
+ return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit.repoPath, commit.sha, commit.uri, line);
+ }
+ catch (ex) {
+ console.error('[GitLens.DiffWithWorkingCommand]', `getBlameForLine(${line})`, ex);
+ }
+ }
+ else {
+ try {
+ const log = await this.git.getLogForFile(uri.fsPath);
+ if (!log) return undefined;
- return this.git.getVersionedFile(shaUri.fsPath, repoPath, sha)
- .then(compare => commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), uri, `${path.basename(shaUri.fsPath)} (${sha}) ↔ ${path.basename(uri.fsPath)}`))
- .then(() => commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: line, at: 'center' }))
- .catch(ex => console.error('[GitLens.DiffWithWorkingCommand]', 'getVersionedFile', ex));
+ const commit = Iterables.first(log.commits.values());
+ return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit.repoPath, commit.sha, commit.uri, line);
+ }
+ catch (ex) {
+ console.error('[GitLens.DiffWithPreviousCommand]', `getLogForFile(${uri.fsPath})`, ex);
+ }
+ }
}
}
diff --git a/src/commands/showBlame.ts b/src/commands/showBlame.ts
index 7e1dcfd..bb4e46a 100644
--- a/src/commands/showBlame.ts
+++ b/src/commands/showBlame.ts
@@ -1,8 +1,8 @@
-'use strict'
-import {TextEditor, TextEditorEdit, Uri} from 'vscode';
+'use strict';
+import { TextEditor, TextEditorEdit, Uri } from 'vscode';
import BlameAnnotationController from '../blameAnnotationController';
-import {EditorCommand} from './commands';
-import {Commands} from '../constants';
+import { EditorCommand } from './commands';
+import { Commands } from '../constants';
import GitProvider from '../gitProvider';
export default class ShowBlameCommand extends EditorCommand {
@@ -10,18 +10,22 @@ export default class ShowBlameCommand extends EditorCommand {
super(Commands.ShowBlame);
}
- execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) {
+ 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;
+ if (!editor.document) return undefined;
uri = editor.document.uri;
}
- return this.git.getBlameForLine(uri.fsPath, editor.selection.active.line)
- .then(blame => this.annotationController.showBlameAnnotation(editor, blame && blame.commit.sha))
- .catch(ex => console.error('[GitLens.ShowBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex));
+ try {
+ const blame = await this.git.getBlameForLine(uri.fsPath, editor.selection.active.line);
+ this.annotationController.showBlameAnnotation(editor, blame && blame.commit.sha);
+ }
+ catch (ex) {
+ console.error('[GitLens.ShowBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex);
+ }
}
}
\ No newline at end of file
diff --git a/src/commands/showBlameHistory.ts b/src/commands/showBlameHistory.ts
index bda732c..e86b7b6 100644
--- a/src/commands/showBlameHistory.ts
+++ b/src/commands/showBlameHistory.ts
@@ -1,7 +1,7 @@
-'use strict'
-import {commands, Position, Range, TextEditor, TextEditorEdit, Uri} from 'vscode';
-import {EditorCommand} from './commands';
-import {BuiltInCommands, Commands} from '../constants';
+'use strict';
+import { commands, Position, Range, TextEditor, TextEditorEdit, Uri } from 'vscode';
+import { EditorCommand } from './commands';
+import { BuiltInCommands, Commands } from '../constants';
import GitProvider from '../gitProvider';
export default class ShowBlameHistoryCommand extends EditorCommand {
@@ -9,9 +9,9 @@ export default class ShowBlameHistoryCommand extends EditorCommand {
super(Commands.ShowBlameHistory);
}
- execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, range?: Range, position?: Position) {
+ async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, range?: Range, position?: Position) {
if (!(uri instanceof Uri)) {
- if (!editor.document) return;
+ if (!editor.document) return undefined;
uri = editor.document.uri;
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
@@ -19,8 +19,12 @@ export default class ShowBlameHistoryCommand extends EditorCommand {
position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
}
- return this.git.getBlameLocations(uri.fsPath, range)
- .then(locations => commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations))
- .catch(ex => console.error('[GitLens.ShowBlameHistoryCommand]', 'getBlameLocations', ex));
+ try {
+ const locations = await this.git.getBlameLocations(uri.fsPath, range);
+ return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations);
+ }
+ catch (ex) {
+ console.error('[GitLens.ShowBlameHistoryCommand]', 'getBlameLocations', ex);
+ }
}
}
\ No newline at end of file
diff --git a/src/commands/showHistory.ts b/src/commands/showHistory.ts
new file mode 100644
index 0000000..6b72bcf
--- /dev/null
+++ b/src/commands/showHistory.ts
@@ -0,0 +1,29 @@
+'use strict';
+import { commands, Position, Range, TextEditor, TextEditorEdit, Uri } from 'vscode';
+import { EditorCommand} from './commands';
+import { BuiltInCommands, Commands } from '../constants';
+import GitProvider from '../gitProvider';
+
+export default class ShowHistoryCommand extends EditorCommand {
+ constructor(private git: GitProvider) {
+ super(Commands.ShowHistory);
+ }
+
+ async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, position?: Position) {
+ if (!(uri instanceof Uri)) {
+ if (!editor.document) return undefined;
+ uri = editor.document.uri;
+
+ // If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
+ position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
+ }
+
+ try {
+ const locations = await this.git.getLogLocations(uri.fsPath);
+ return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations);
+ }
+ catch (ex) {
+ console.error('[GitLens.ShowHistoryCommand]', 'getLogLocations', ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/commands/toggleBlame.ts b/src/commands/toggleBlame.ts
index aea0774..4c622e8 100644
--- a/src/commands/toggleBlame.ts
+++ b/src/commands/toggleBlame.ts
@@ -1,37 +1,31 @@
-'use strict'
-import {TextEditor, TextEditorEdit, Uri} from 'vscode';
+'use strict';
+import { TextEditor, TextEditorEdit, Uri } from 'vscode';
import BlameAnnotationController from '../blameAnnotationController';
-import {EditorCommand} from './commands';
-import {Commands} from '../constants';
+import { EditorCommand } from './commands';
+import { Commands } from '../constants';
import GitProvider from '../gitProvider';
export default class ToggleBlameCommand extends EditorCommand {
- constructor(private git: GitProvider, private blameController: BlameAnnotationController) {
+ constructor(private git: GitProvider, private annotationController: BlameAnnotationController) {
super(Commands.ToggleBlame);
}
- execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) {
+ async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) {
if (sha) {
- return this.blameController.toggleBlameAnnotation(editor, sha);
+ return this.annotationController.toggleBlameAnnotation(editor, sha);
}
if (!(uri instanceof Uri)) {
- if (!editor.document) return;
+ if (!editor.document) return undefined;
uri = editor.document.uri;
}
- return this.git.getBlameForLine(uri.fsPath, editor.selection.active.line)
- .then(blame => this.blameController.toggleBlameAnnotation(editor, blame && blame.commit.sha))
- .catch(ex => console.error('[GitLens.ToggleBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex));
- }
-}
-
-export class ToggleCodeLensCommand extends EditorCommand {
- constructor(private git: GitProvider) {
- super(Commands.ToggleCodeLens);
- }
-
- execute(editor: TextEditor, edit: TextEditorEdit) {
- return this.git.toggleCodeLens(editor);
+ try {
+ const blame = await this.git.getBlameForLine(uri.fsPath, editor.selection.active.line);
+ this.annotationController.toggleBlameAnnotation(editor, blame && blame.commit.sha);
+ }
+ catch (ex) {
+ console.error('[GitLens.ToggleBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex);
+ }
}
}
\ No newline at end of file
diff --git a/src/commands/toggleCodeLens.ts b/src/commands/toggleCodeLens.ts
index 171c82c..dd02a1c 100644
--- a/src/commands/toggleCodeLens.ts
+++ b/src/commands/toggleCodeLens.ts
@@ -1,7 +1,7 @@
-'use strict'
-import {TextEditor, TextEditorEdit} from 'vscode';
-import {EditorCommand} from './commands';
-import {Commands} from '../constants';
+'use strict';
+import { TextEditor, TextEditorEdit } from 'vscode';
+import { EditorCommand } from './commands';
+import { Commands} from '../constants';
import GitProvider from '../gitProvider';
export default class ToggleCodeLensCommand extends EditorCommand {
diff --git a/src/configuration.ts b/src/configuration.ts
index 0bc5b79..5918c49 100644
--- a/src/configuration.ts
+++ b/src/configuration.ts
@@ -1,11 +1,11 @@
-'use strict'
+'use strict';
import {Commands} from './constants';
export type BlameAnnotationStyle = 'compact' | 'expanded';
export const BlameAnnotationStyle = {
Compact: 'compact' as BlameAnnotationStyle,
Expanded: 'expanded' as BlameAnnotationStyle
-}
+};
export interface IBlameConfig {
annotation: {
@@ -22,22 +22,22 @@ export const CodeLensCommand = {
BlameExplorer: Commands.ShowBlameHistory as CodeLensCommand,
DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand,
GitViewHistory: 'git.viewFileHistory' as CodeLensCommand
-}
+};
export type CodeLensLocation = 'all' | 'document+containers' | 'document' | 'custom';
export const CodeLensLocation = {
All: 'all' as CodeLensLocation,
DocumentAndContainers: 'document+containers' as CodeLensLocation,
Document: 'document' as CodeLensLocation,
- Custom: 'custom' as CodeLensLocation,
-}
+ Custom: 'custom' as CodeLensLocation
+};
export type CodeLensVisibility = 'auto' | 'ondemand' | 'off';
export const CodeLensVisibility = {
Auto: 'auto' as CodeLensVisibility,
OnDemand: 'ondemand' as CodeLensVisibility,
Off: 'off' as CodeLensVisibility
-}
+};
export interface ICodeLensConfig {
enabled: boolean;
@@ -59,7 +59,7 @@ export const StatusBarCommand = {
DiffWithPrevious: Commands.DiffWithPrevious as StatusBarCommand,
ToggleCodeLens: Commands.ToggleCodeLens as StatusBarCommand,
GitViewHistory: 'git.viewFileHistory' as StatusBarCommand
-}
+};
export interface IStatusBarConfig {
enabled: boolean;
@@ -69,12 +69,12 @@ export interface IStatusBarConfig {
export interface IAdvancedConfig {
caching: {
enabled: boolean
- }
+ };
}
export interface IConfig {
- blame: IBlameConfig,
- codeLens: ICodeLensesConfig,
- statusBar: IStatusBarConfig,
- advanced: IAdvancedConfig
+ blame: IBlameConfig;
+ codeLens: ICodeLensesConfig;
+ statusBar: IStatusBarConfig;
+ advanced: IAdvancedConfig;
}
\ No newline at end of file
diff --git a/src/constants.ts b/src/constants.ts
index 2045566..3bd482b 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,4 +1,4 @@
-'use strict'
+'use strict';
export const RepoPath = 'repoPath';
@@ -12,27 +12,28 @@ export const BuiltInCommands = {
RevealLine: 'revealLine' as BuiltInCommands,
ShowReferences: 'editor.action.showReferences' as BuiltInCommands,
ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands
-}
+};
-export type Commands = 'gitlens.diffWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens';
+export type Commands = 'gitlens.diffWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showHistory' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens';
export const Commands = {
DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands,
DiffWithWorking: 'gitlens.diffWithWorking' as Commands,
ShowBlame: 'gitlens.showBlame' as Commands,
ShowBlameHistory: 'gitlens.showBlameHistory' as Commands,
+ ShowHistory: 'gitlens.showHistory' as Commands,
ToggleBlame: 'gitlens.toggleBlame' as Commands,
- ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands,
-}
+ ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands
+};
export type DocumentSchemes = 'file' | 'git' | 'git-blame';
export const DocumentSchemes = {
File: 'file' as DocumentSchemes,
Git: 'git' as DocumentSchemes,
GitBlame: 'git-blame' as DocumentSchemes
-}
+};
export type WorkspaceState = 'hasGitHistoryExtension' | 'repoPath';
export const WorkspaceState = {
HasGitHistoryExtension: 'hasGitHistoryExtension' as WorkspaceState,
RepoPath: 'repoPath' as WorkspaceState
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/src/extension.ts b/src/extension.ts
index 1d2fc81..ac4b4f6 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -1,17 +1,17 @@
'use strict';
-import {CodeLens, DocumentSelector, ExtensionContext, extensions, languages, OverviewRulerLane, StatusBarAlignment, window, workspace} from 'vscode';
+import { ExtensionContext, extensions, languages, workspace } from 'vscode';
import BlameAnnotationController from './blameAnnotationController';
import BlameStatusBarController from './blameStatusBarController';
import GitContentProvider from './gitContentProvider';
import GitBlameCodeLensProvider from './gitBlameCodeLensProvider';
import GitBlameContentProvider from './gitBlameContentProvider';
-import GitProvider, {Git} from './gitProvider';
-import {IStatusBarConfig} from './configuration';
-import {WorkspaceState} from './constants';
+import GitProvider, { Git } from './gitProvider';
+import { WorkspaceState } from './constants';
import DiffWithPreviousCommand from './commands/diffWithPrevious';
import DiffWithWorkingCommand from './commands/diffWithWorking';
import ShowBlameCommand from './commands/showBlame';
import ShowBlameHistoryCommand from './commands/showBlameHistory';
+import ShowHistoryCommand from './commands/showHistory';
import ToggleBlameCommand from './commands/toggleBlame';
import ToggleCodeLensCommand from './commands/toggleCodeLens';
@@ -45,11 +45,12 @@ export function activate(context: ExtensionContext) {
const statusBarController = new BlameStatusBarController(context, git);
context.subscriptions.push(statusBarController);
- context.subscriptions.push(new DiffWithWorkingCommand(git));
- context.subscriptions.push(new DiffWithPreviousCommand(git));
+ context.subscriptions.push(new DiffWithWorkingCommand(git, annotationController));
+ context.subscriptions.push(new DiffWithPreviousCommand(git, annotationController));
context.subscriptions.push(new ShowBlameCommand(git, annotationController));
context.subscriptions.push(new ToggleBlameCommand(git, annotationController));
context.subscriptions.push(new ShowBlameHistoryCommand(git));
+ context.subscriptions.push(new ShowHistoryCommand(git));
context.subscriptions.push(new ToggleCodeLensCommand(git));
}).catch(reason => console.warn('[GitLens]', reason));
}
diff --git a/src/git/enrichers/blameParserEnricher.ts b/src/git/enrichers/blameParserEnricher.ts
index f823376..856db5b 100644
--- a/src/git/enrichers/blameParserEnricher.ts
+++ b/src/git/enrichers/blameParserEnricher.ts
@@ -1,5 +1,5 @@
-'use strict'
-import {GitBlameFormat, GitCommit, IGitAuthor, IGitBlame, IGitCommit, IGitCommitLine, IGitEnricher} from './../git';
+'use strict';
+import { GitBlameFormat, GitCommit, IGitAuthor, IGitBlame, IGitCommit, IGitCommitLine, IGitEnricher } from './../git';
import * as moment from 'moment';
import * as path from 'path';
@@ -45,7 +45,7 @@ export class GitBlameParserEnricher implements IGitEnricher {
let entry: IBlameEntry;
let position = -1;
while (++position < lines.length) {
- let lineParts = lines[position].split(" ");
+ let lineParts = lines[position].split(' ');
if (lineParts.length < 2) {
continue;
}
@@ -62,49 +62,49 @@ export class GitBlameParserEnricher implements IGitEnricher {
}
switch (lineParts[0]) {
- case "author":
- entry.author = lineParts.slice(1).join(" ").trim();
+ case 'author':
+ entry.author = lineParts.slice(1).join(' ').trim();
break;
- // case "author-mail":
+ // case 'author-mail':
// entry.authorEmail = lineParts[1].trim();
// break;
- case "author-time":
+ case 'author-time':
entry.authorDate = lineParts[1];
break;
- case "author-tz":
+ case 'author-tz':
entry.authorTimeZone = lineParts[1];
break;
- // case "committer":
- // entry.committer = lineParts.slice(1).join(" ").trim();
+ // case 'committer':
+ // entry.committer = lineParts.slice(1).join(' ').trim();
// break;
- // case "committer-mail":
+ // case 'committer-mail':
// entry.committerEmail = lineParts[1].trim();
// break;
- // case "committer-time":
+ // case 'committer-time':
// entry.committerDate = lineParts[1];
// break;
- // case "committer-tz":
+ // case 'committer-tz':
// entry.committerTimeZone = lineParts[1];
// break;
- case "summary":
- entry.summary = lineParts.slice(1).join(" ").trim();
+ case 'summary':
+ entry.summary = lineParts.slice(1).join(' ').trim();
break;
- case "previous":
+ case 'previous':
entry.previousSha = lineParts[1].substring(0, 8);
- entry.previousFileName = lineParts.slice(2).join(" ");
+ entry.previousFileName = lineParts.slice(2).join(' ');
break;
- case "filename":
- entry.fileName = lineParts.slice(1).join(" ");
+ case 'filename':
+ entry.fileName = lineParts.slice(1).join(' ');
entries.push(entry);
entry = null;
@@ -149,7 +149,7 @@ export class GitBlameParserEnricher implements IGitEnricher {
authors.set(entry.author, author);
}
- commit = new GitCommit(repoPath, entry.sha, relativeFileName, entry.author, moment(`${entry.authorDate} ${entry.authorTimeZone}`, 'X Z').toDate(), entry.summary);
+ commit = new GitCommit(repoPath, entry.sha, relativeFileName, entry.author, moment(`${entry.authorDate} ${entry.authorTimeZone}`, 'X +-HHmm').toDate(), entry.summary);
if (relativeFileName !== entry.fileName) {
commit.originalFileName = entry.fileName;
@@ -168,7 +168,7 @@ export class GitBlameParserEnricher implements IGitEnricher {
sha: entry.sha,
line: entry.line + j,
originalLine: entry.originalLine + j
- }
+ };
if (commit.previousSha) {
line.previousSha = commit.previousSha;
@@ -182,7 +182,8 @@ export class GitBlameParserEnricher implements IGitEnricher {
commits.forEach(c => authors.get(c.author).lineCount += c.lines.length);
const sortedAuthors: Map = new Map();
- const values = Array.from(authors.values())
+ // const values =
+ Array.from(authors.values())
.sort((a, b) => b.lineCount - a.lineCount)
.forEach(a => sortedAuthors.set(a.name, a));
diff --git a/src/git/enrichers/logParserEnricher.ts b/src/git/enrichers/logParserEnricher.ts
new file mode 100644
index 0000000..9b504e3
--- /dev/null
+++ b/src/git/enrichers/logParserEnricher.ts
@@ -0,0 +1,143 @@
+'use strict';
+import { GitCommit, IGitAuthor, IGitCommit, IGitEnricher, IGitLog } from './../git';
+import * as moment from 'moment';
+import * as path from 'path';
+
+interface ILogEntry {
+ sha: string;
+
+ author?: string;
+ authorDate?: string;
+
+ committer?: string;
+ committerDate?: string;
+
+ fileName?: string;
+
+ summary?: string;
+}
+
+export class GitLogParserEnricher implements IGitEnricher {
+ private _parseEntries(data: string): ILogEntry[] {
+ if (!data) return null;
+
+ const lines = data.split('\n');
+ if (!lines.length) return null;
+
+ const entries: ILogEntry[] = [];
+
+ let entry: ILogEntry;
+ let position = -1;
+ while (++position < lines.length) {
+ let lineParts = lines[position].split(' ');
+ if (lineParts.length < 2) {
+ continue;
+ }
+
+ if (!entry) {
+ entry = {
+ sha: lineParts[0].substring(0, 8)
+ };
+
+ continue;
+ }
+
+ switch (lineParts[0]) {
+ case 'author':
+ entry.author = lineParts.slice(1).join(' ').trim();
+ break;
+
+ case 'author-date':
+ entry.authorDate = lineParts.slice(1).join(' ').trim();
+ break;
+
+ // case 'committer':
+ // entry.committer = lineParts.slice(1).join(' ').trim();
+ // break;
+
+ // case 'committer-date':
+ // entry.committerDate = lineParts.slice(1).join(' ').trim();
+ // break;
+
+ case 'summary':
+ entry.summary = lineParts.slice(1).join(' ').trim();
+ break;
+
+ case 'filename':
+ position += 2;
+ lineParts = lines[position].split(' ');
+ entry.fileName = lineParts.join(' ');
+
+ entries.push(entry);
+ entry = null;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return entries;
+ }
+
+ enrich(data: string, fileName: string): IGitLog {
+ const entries = this._parseEntries(data);
+ if (!entries) return null;
+
+ const authors: Map = new Map();
+ const commits: Map = new Map();
+
+ let repoPath: string;
+ let relativeFileName: string;
+
+ for (let i = 0, len = entries.length; i < len; i++) {
+ const entry = entries[i];
+
+ if (i === 0) {
+ // Try to get the repoPath from the most recent commit
+ repoPath = fileName.replace(`/${entry.fileName}`, '');
+ relativeFileName = path.relative(repoPath, fileName).replace(/\\/g, '/');
+ }
+
+ let commit = commits.get(entry.sha);
+ if (!commit) {
+ let author = authors.get(entry.author);
+ if (!author) {
+ author = {
+ name: entry.author,
+ lineCount: 0
+ };
+ authors.set(entry.author, author);
+ }
+
+ commit = new GitCommit(repoPath, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary);
+
+ if (relativeFileName !== entry.fileName) {
+ commit.originalFileName = entry.fileName;
+ }
+
+ commits.set(entry.sha, commit);
+ }
+ }
+
+ commits.forEach(c => authors.get(c.author).lineCount += c.lines.length);
+
+ const sortedAuthors: Map = new Map();
+ // const values =
+ Array.from(authors.values())
+ .sort((a, b) => b.lineCount - a.lineCount)
+ .forEach(a => sortedAuthors.set(a.name, a));
+
+ // const sortedCommits: Map = new Map();
+ // Array.from(commits.values())
+ // .sort((a, b) => b.date.getTime() - a.date.getTime())
+ // .forEach(c => sortedCommits.set(c.sha, c));
+
+ return {
+ repoPath: repoPath,
+ authors: sortedAuthors,
+ // commits: sortedCommits,
+ commits: commits
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/git/git.ts b/src/git/git.ts
index 29edde2..7f379ad 100644
--- a/src/git/git.ts
+++ b/src/git/git.ts
@@ -2,28 +2,29 @@
import * as fs from 'fs';
import * as path from 'path';
import * as tmp from 'tmp';
-import {spawnPromise} from 'spawn-rx';
+import { spawnPromise } from 'spawn-rx';
export * from './gitEnrichment';
export * from './enrichers/blameParserEnricher';
+export * from './enrichers/logParserEnricher';
const UncommittedRegex = /^[0]+$/;
-function gitCommand(cwd: string, ...args) {
- return spawnPromise('git', args, { cwd: cwd })
- .then(s => {
- console.log('[GitLens]', 'git', ...args, cwd);
- return s;
- })
- .catch(ex => {
- const msg = ex && ex.toString();
- if (msg && (msg.includes('is outside repository') || msg.includes('no such path'))) {
- console.warn('[GitLens]', 'git', ...args, cwd, msg && msg.replace(/\r?\n|\r/g, ' '));
- } else {
- console.error('[GitLens]', 'git', ...args, cwd, msg && msg.replace(/\r?\n|\r/g, ' '));
- }
- throw ex;
- });
+async function gitCommand(cwd: string, ...args: any[]) {
+ try {
+ const s = await spawnPromise('git', args, { cwd: cwd });
+ console.log('[GitLens]', 'git', ...args, cwd);
+ return s;
+ }
+ catch (ex) {
+ const msg = ex && ex.toString();
+ if (msg && (msg.includes('is outside repository') || msg.includes('no such path'))) {
+ console.warn('[GitLens]', 'git', ...args, cwd, msg && msg.replace(/\r?\n|\r/g, ' '));
+ } else {
+ console.error('[GitLens]', 'git', ...args, cwd, msg && msg.replace(/\r?\n|\r/g, ' '));
+ }
+ throw ex;
+ }
}
export type GitBlameFormat = '--incremental' | '--line-porcelain' | '--porcelain';
@@ -31,7 +32,7 @@ export const GitBlameFormat = {
incremental: '--incremental' as GitBlameFormat,
linePorcelain: '--line-porcelain' as GitBlameFormat,
porcelain: '--porcelain' as GitBlameFormat
-}
+};
export default class Git {
static normalizePath(fileName: string, repoPath?: string) {
@@ -72,6 +73,12 @@ export default class Git {
return gitCommand(root, 'blame', `-L ${startLine},${endLine}`, format, '--root', '--', file);
}
+ 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);
+ }
+
static getVersionedFile(fileName: string, repoPath: string, sha: string) {
return new Promise((resolve, reject) => {
Git.getVersionedFileText(fileName, repoPath, sha).then(data => {
diff --git a/src/git/gitEnrichment.ts b/src/git/gitEnrichment.ts
index 7c84037..8eef91d 100644
--- a/src/git/gitEnrichment.ts
+++ b/src/git/gitEnrichment.ts
@@ -1,10 +1,10 @@
-'use strict'
+'use strict';
import {Uri} from 'vscode';
import Git from './git';
import * as path from 'path';
export interface IGitEnricher {
- enrich(data: string, ...args): T;
+ enrich(data: string, ...args: any[]): T;
}
export interface IGitBlame {
@@ -89,4 +89,10 @@ export interface IGitCommitLine {
line: number;
originalLine: number;
code?: string;
+}
+
+export interface IGitLog {
+ repoPath: string;
+ authors: Map;
+ commits: Map;
}
\ No newline at end of file
diff --git a/src/gitBlameCodeLensProvider.ts b/src/gitBlameCodeLensProvider.ts
index 522c9fa..cf770c9 100644
--- a/src/gitBlameCodeLensProvider.ts
+++ b/src/gitBlameCodeLensProvider.ts
@@ -1,18 +1,17 @@
'use strict';
-import {CancellationToken, CodeLens, CodeLensProvider, commands, DocumentSelector, ExtensionContext, Location, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri} from 'vscode';
-import {BuiltInCommands, Commands, DocumentSchemes, WorkspaceState} from './constants';
-import GitProvider, {IGitBlame, IGitCommit} from './gitProvider';
-import * as moment from 'moment';
+import { CancellationToken, CodeLens, CodeLensProvider, DocumentSelector, ExtensionContext, Range, TextDocument, Uri } from 'vscode';
+import { Commands, DocumentSchemes } from './constants';
+import GitProvider, { IGitCommit } from './gitProvider';
import * as path from 'path';
export class GitDiffWithWorkingTreeCodeLens extends CodeLens {
- constructor(private git: GitProvider, public fileName: string, public commit: IGitCommit, range: Range) {
+ constructor(git: GitProvider, public fileName: string, public commit: IGitCommit, range: Range) {
super(range);
}
}
export class GitDiffWithPreviousCodeLens extends CodeLens {
- constructor(private git: GitProvider, public fileName: string, public commit: IGitCommit, range: Range) {
+ constructor(git: GitProvider, public fileName: string, public commit: IGitCommit, range: Range) {
super(range);
}
}
@@ -62,6 +61,7 @@ export default class GitBlameCodeLensProvider implements CodeLensProvider {
resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable {
if (lens instanceof GitDiffWithWorkingTreeCodeLens) return this._resolveDiffWithWorkingTreeCodeLens(lens, token);
if (lens instanceof GitDiffWithPreviousCodeLens) return this._resolveGitDiffWithPreviousCodeLens(lens, token);
+ return Promise.reject(null);
}
_resolveDiffWithWorkingTreeCodeLens(lens: GitDiffWithWorkingTreeCodeLens, token: CancellationToken): Thenable {
diff --git a/src/gitBlameContentProvider.ts b/src/gitBlameContentProvider.ts
index 9c9e84d..c3f21bb 100644
--- a/src/gitBlameContentProvider.ts
+++ b/src/gitBlameContentProvider.ts
@@ -1,6 +1,6 @@
'use strict';
-import {Disposable, EventEmitter, ExtensionContext, OverviewRulerLane, Range, TextEditor, TextEditorDecorationType, TextDocumentContentProvider, Uri, window, workspace} from 'vscode';
-import {DocumentSchemes, WorkspaceState} from './constants';
+import { EventEmitter, ExtensionContext, OverviewRulerLane, Range, TextEditor, TextEditorDecorationType, TextDocumentContentProvider, Uri, window } from 'vscode';
+import { DocumentSchemes } from './constants';
import GitProvider, {IGitBlameUriData} from './gitProvider';
import * as moment from 'moment';
@@ -16,12 +16,12 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi
dark: {
backgroundColor: 'rgba(255, 255, 255, 0.15)',
gutterIconPath: context.asAbsolutePath('images/blame-dark.png'),
- overviewRulerColor: 'rgba(255, 255, 255, 0.75)',
+ overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
},
light: {
backgroundColor: 'rgba(0, 0, 0, 0.15)',
gutterIconPath: context.asAbsolutePath('images/blame-light.png'),
- overviewRulerColor: 'rgba(0, 0, 0, 0.75)',
+ overviewRulerColor: 'rgba(0, 0, 0, 0.75)'
},
gutterIconSize: 'contain',
overviewRulerLane: OverviewRulerLane.Right,
@@ -96,7 +96,7 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi
editor.setDecorations(this._blameDecoration, blame.lines.map(l => {
return {
range: editor.document.validateRange(new Range(l.originalLine, 0, l.originalLine, 1000000)),
- hoverMessage: `${blame.commit.message}\n${blame.commit.author}\n${moment(blame.commit.date).format('MMMM Do, YYYY hh:MM a')}\n${l.sha}`
+ hoverMessage: `${blame.commit.message}\n${blame.commit.author}\n${moment(blame.commit.date).format('MMMM Do, YYYY hh:MMa')}\n${l.sha}`
};
}));
});
diff --git a/src/gitCodeLensProvider.ts b/src/gitCodeLensProvider.ts
index 5bf4f4e..641c2dc 100644
--- a/src/gitCodeLensProvider.ts
+++ b/src/gitCodeLensProvider.ts
@@ -1,12 +1,11 @@
'use strict';
-import {CancellationToken, CodeLens, CodeLensProvider, commands, DocumentSelector, ExtensionContext, Location, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri, window, workspace} from 'vscode';
-import {BuiltInCommands, Commands, DocumentSchemes, WorkspaceState} from './constants';
-import {CodeLensCommand, CodeLensLocation, ICodeLensesConfig} from './configuration';
-import GitProvider, {IGitBlame, IGitBlameLines, IGitCommit} from './gitProvider';
+import { Iterables, Strings } from './system';
+import { CancellationToken, CodeLens, CodeLensProvider, commands, DocumentSelector, ExtensionContext, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri, workspace } from 'vscode';
+import { BuiltInCommands, Commands, DocumentSchemes, WorkspaceState } from './constants';
+import { CodeLensCommand, CodeLensLocation, ICodeLensesConfig } from './configuration';
+import GitProvider, {IGitBlame, IGitBlameLines} from './gitProvider';
import * as moment from 'moment';
-const escapeRegExp = require('lodash.escaperegexp');
-
export class GitRecentChangeCodeLens extends CodeLens {
constructor(private git: GitProvider, public fileName: string, public symbolKind: SymbolKind, public blameRange: Range, range: Range) {
super(range);
@@ -93,6 +92,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
case CodeLensLocation.Custom:
return !!(this._config.locationCustomSymbols || []).find(_ => _.toLowerCase() === SymbolKind[kind].toLowerCase());
}
+ return false;
}
private _provideCodeLens(fileName: string, document: TextDocument, symbol: SymbolInformation, lenses: CodeLens[]): void {
@@ -106,7 +106,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
let startChar = -1;
try {
- startChar = line.text.search(`\\b${escapeRegExp(symbol.name)}\\b`);
+ startChar = line.text.search(`\\b${Strings.escapeRegExp(symbol.name)}\\b`);
}
catch (ex) { }
if (startChar === -1) {
@@ -149,11 +149,12 @@ export default class GitCodeLensProvider implements CodeLensProvider {
resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable {
if (lens instanceof GitRecentChangeCodeLens) return this._resolveGitRecentChangeCodeLens(lens, token);
if (lens instanceof GitAuthorsCodeLens) return this._resolveGitAuthorsCodeLens(lens, token);
+ return Promise.reject(null);
}
_resolveGitRecentChangeCodeLens(lens: GitRecentChangeCodeLens, token: CancellationToken): Thenable {
return lens.getBlame().then(blame => {
- const recentCommit = blame.commits.values().next().value;
+ const recentCommit = Iterables.first(blame.commits.values());
const title = `${recentCommit.author}, ${moment(recentCommit.date).fromNow()}`; // - ${SymbolKind[lens.symbolKind]}(${lens.blameRange.start.line + 1}-${lens.blameRange.end.line + 1})`;
switch (this._config.recentChange.command) {
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand(title, lens, blame);
@@ -168,7 +169,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
_resolveGitAuthorsCodeLens(lens: GitAuthorsCodeLens, token: CancellationToken): Thenable {
return lens.getBlame().then(blame => {
const count = blame.authors.size;
- const title = `${count} ${count > 1 ? 'authors' : 'author'} (${blame.authors.values().next().value.name}${count > 1 ? ' and others' : ''})`;
+ const title = `${count} ${count > 1 ? 'authors' : 'author'} (${Iterables.first(blame.authors.values()).name}${count > 1 ? ' and others' : ''})`;
switch (this._config.authors.command) {
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand(title, lens, blame);
case CodeLensCommand.BlameExplorer: return this._applyBlameExplorerCommand(title, lens, blame);
diff --git a/src/gitContentProvider.ts b/src/gitContentProvider.ts
index bf93ac4..c37982a 100644
--- a/src/gitContentProvider.ts
+++ b/src/gitContentProvider.ts
@@ -11,6 +11,7 @@ export default class GitContentProvider implements TextDocumentContentProvider {
provideTextDocumentContent(uri: Uri): string | Thenable {
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 => console.error('[GitLens.GitContentProvider]', 'getVersionedFileText', ex));
}
}
\ No newline at end of file
diff --git a/src/gitProvider.ts b/src/gitProvider.ts
index 9d533d2..c8b4dd2 100644
--- a/src/gitProvider.ts
+++ b/src/gitProvider.ts
@@ -1,26 +1,36 @@
-'use strict'
-import {Disposable, DocumentFilter, ExtensionContext, languages, Location, Position, Range, TextDocument, TextEditor, Uri, window, workspace} from 'vscode';
-import {DocumentSchemes, WorkspaceState} from './constants';
-import {CodeLensVisibility, IConfig} from './configuration';
+'use strict';
+import { Functions, Iterables, Objects } from './system';
+import { Disposable, DocumentFilter, ExtensionContext, languages, Location, Position, Range, TextDocument, TextEditor, Uri, window, workspace } from 'vscode';
+import { DocumentSchemes, WorkspaceState } from './constants';
+import { CodeLensVisibility, IConfig } from './configuration';
import GitCodeLensProvider from './gitCodeLensProvider';
-import Git, {GitBlameParserEnricher, GitBlameFormat, GitCommit, IGitAuthor, IGitBlame, IGitBlameCommitLines, IGitBlameLine, IGitBlameLines, IGitCommit} from './git/git';
-import * as fs from 'fs'
+import Git, { GitBlameParserEnricher, GitBlameFormat, GitCommit, GitLogParserEnricher, IGitAuthor, IGitBlame, IGitBlameCommitLines, IGitBlameLine, IGitBlameLines, IGitCommit, IGitLog } from './git/git';
+import * as fs from 'fs';
import * as ignore from 'ignore';
import * as moment from 'moment';
import * as path from 'path';
-const debounce = require('lodash.debounce');
-const isEqual = require('lodash.isequal');
-
export { Git };
export * from './git/git';
-interface IBlameCacheEntry {
+class CacheEntry {
+ blame?: ICachedBlame;
+ log?: ICachedLog;
+
+ get hasErrors() {
+ return !!((this.blame && this.blame.errorMessage) || (this.log && this.log.errorMessage));
+ }
+}
+
+interface ICachedItem {
//date: Date;
- blame: Promise;
- errorMessage?: string
+ item: Promise;
+ errorMessage?: string;
}
+interface ICachedBlame extends ICachedItem { }
+interface ICachedLog extends ICachedItem { }
+
enum RemoveCacheReason {
DocumentClosed,
DocumentSaved,
@@ -28,16 +38,16 @@ enum RemoveCacheReason {
}
export default class GitProvider extends Disposable {
- private _blameCache: Map|null;
- private _blameCacheDisposable: Disposable|null;
+ private _cache: Map | null;
+ private _cacheDisposable: Disposable | null;
private _config: IConfig;
private _disposable: Disposable;
- private _codeLensProviderDisposable: Disposable|null;
+ private _codeLensProviderDisposable: Disposable | null;
private _codeLensProviderSelector: DocumentFilter;
private _gitignore: Promise;
- static BlameEmptyPromise: Promise = Promise.resolve(null);
+ static EmptyPromise: Promise = Promise.resolve(null);
static BlameFormat = GitBlameFormat.incremental;
constructor(private context: ExtensionContext) {
@@ -47,7 +57,7 @@ export default class GitProvider extends Disposable {
this._onConfigure();
- this._gitignore = new Promise((resolve, reject) => {
+ this._gitignore = new Promise((resolve, reject) => {
const gitignorePath = path.join(repoPath, '.gitignore');
fs.exists(gitignorePath, e => {
if (e) {
@@ -74,18 +84,18 @@ export default class GitProvider extends Disposable {
dispose() {
this._disposable && this._disposable.dispose();
this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose();
- this._blameCacheDisposable && this._blameCacheDisposable.dispose();
- this._blameCache && this._blameCache.clear();
+ this._cacheDisposable && this._cacheDisposable.dispose();
+ this._cache && this._cache.clear();
}
public get UseCaching() {
- return !!this._blameCache;
+ return !!this._cache;
}
private _onConfigure() {
const config = workspace.getConfiguration().get('gitlens');
- if (!isEqual(config.codeLens, this._config && this._config.codeLens)) {
+ if (!Objects.areEquivalent(config.codeLens, this._config && this._config.codeLens)) {
this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose();
if (config.codeLens.visibility === CodeLensVisibility.Auto && (config.codeLens.recentChange.enabled || config.codeLens.authors.enabled)) {
this._codeLensProviderSelector = GitCodeLensProvider.selector;
@@ -95,51 +105,51 @@ export default class GitProvider extends Disposable {
}
}
- if (!isEqual(config.advanced, this._config && this._config.advanced)) {
+ if (!Objects.areEquivalent(config.advanced, this._config && this._config.advanced)) {
if (config.advanced.caching.enabled) {
// TODO: Cache needs to be cleared on file changes -- createFileSystemWatcher or timeout?
- this._blameCache = new Map();
+ this._cache = new Map();
const disposables: Disposable[] = [];
// TODO: Maybe stop clearing on close and instead limit to a certain number of recent blames
- disposables.push(workspace.onDidCloseTextDocument(d => this._removeCachedBlame(d, RemoveCacheReason.DocumentClosed)));
+ disposables.push(workspace.onDidCloseTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentClosed)));
- const removeCachedBlameFn = debounce(this._removeCachedBlame.bind(this), 2500);
- disposables.push(workspace.onDidSaveTextDocument(d => removeCachedBlameFn(d, RemoveCacheReason.DocumentSaved)));
- disposables.push(workspace.onDidChangeTextDocument(e => removeCachedBlameFn(e.document, RemoveCacheReason.DocumentChanged)));
+ const removeCachedEntryFn = Functions.debounce(this._removeCachedEntry.bind(this), 2500);
+ disposables.push(workspace.onDidSaveTextDocument(d => removeCachedEntryFn(d, RemoveCacheReason.DocumentSaved)));
+ disposables.push(workspace.onDidChangeTextDocument(e => removeCachedEntryFn(e.document, RemoveCacheReason.DocumentChanged)));
- this._blameCacheDisposable = Disposable.from(...disposables);
+ this._cacheDisposable = Disposable.from(...disposables);
} else {
- this._blameCacheDisposable && this._blameCacheDisposable.dispose();
- this._blameCacheDisposable = null;
- this._blameCache && this._blameCache.clear();
- this._blameCache = null;
+ this._cacheDisposable && this._cacheDisposable.dispose();
+ this._cacheDisposable = null;
+ this._cache && this._cache.clear();
+ this._cache = null;
}
}
this._config = config;
}
- private _getBlameCacheKey(fileName: string) {
+ private _getCacheEntryKey(fileName: string) {
return fileName.toLowerCase();
}
- private _removeCachedBlame(document: TextDocument, reason: RemoveCacheReason) {
+ private _removeCachedEntry(document: TextDocument, reason: RemoveCacheReason) {
if (!this.UseCaching) return;
- if (document.uri.scheme != DocumentSchemes.File) return;
+ if (document.uri.scheme !== DocumentSchemes.File) return;
const fileName = Git.normalizePath(document.fileName);
- const cacheKey = this._getBlameCacheKey(fileName);
+ const cacheKey = this._getCacheEntryKey(fileName);
if (reason === RemoveCacheReason.DocumentClosed) {
// Don't remove broken blame on close (since otherwise we'll have to run the broken blame again)
- const entry = this._blameCache.get(cacheKey);
- if (entry && entry.errorMessage) return;
+ const entry = this._cache.get(cacheKey);
+ if (entry && entry.hasErrors) return;
}
- if (this._blameCache.delete(cacheKey)) {
- console.log('[GitLens]', `Clear blame cache: cacheKey=${cacheKey}, reason=${RemoveCacheReason[reason]}`);
+ if (this._cache.delete(cacheKey)) {
+ console.log('[GitLens]', `Clear cache entry: cacheKey=${cacheKey}, reason=${RemoveCacheReason[reason]}`);
// if (reason === RemoveCacheReason.DocumentSaved) {
// // TODO: Killing the code lens provider is too drastic -- makes the editor jump around, need to figure out how to trigger a refresh
@@ -148,175 +158,257 @@ export default class GitProvider extends Disposable {
}
}
- getRepoPath(cwd: string) {
+ getRepoPath(cwd: string): Promise {
return Git.repoPath(cwd);
}
- getBlameForFile(fileName: string) {
+ async getBlameForFile(fileName: string): Promise {
fileName = Git.normalizePath(fileName);
- const cacheKey = this._getBlameCacheKey(fileName);
+ const cacheKey = this._getCacheEntryKey(fileName);
+ let entry: CacheEntry | undefined = undefined;
if (this.UseCaching) {
- let entry = this._blameCache.get(cacheKey);
- if (entry !== undefined) return entry.blame;
- }
-
- return this._gitignore.then(ignore => {
- let blame: Promise;
- if (ignore && !ignore.filter([fileName]).length) {
- console.log('[GitLens]', `Skipping blame; ${fileName} is gitignored`);
- blame = GitProvider.BlameEmptyPromise;
- } else {
- blame = Git.blame(GitProvider.BlameFormat, fileName)
- .then(data => new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName))
- .catch(ex => {
- // Trap and cache expected blame errors
- if (this.UseCaching) {
- const msg = ex && ex.toString();
- console.log('[GitLens]', `Replace blame cache: cacheKey=${cacheKey}`);
- this._blameCache.set(cacheKey, {
- //date: new Date(),
- blame: GitProvider.BlameEmptyPromise,
- errorMessage: msg
- });
- return GitProvider.BlameEmptyPromise;
- }
- return null;
- });
+ entry = this._cache.get(cacheKey);
+ if (entry !== undefined && entry.blame !== undefined) return entry.blame.item;
+ if (entry === undefined) {
+ entry = new CacheEntry();
}
+ }
- if (this.UseCaching) {
- console.log('[GitLens]', `Add blame cache: cacheKey=${cacheKey}`);
- this._blameCache.set(cacheKey, {
- //date: new Date(),
- blame: blame
+ const ignore = await this._gitignore;
+ let blame: Promise;
+ if (ignore && !ignore.filter([fileName]).length) {
+ console.log('[GitLens]', `Skipping blame; ${fileName} is gitignored`);
+ blame = GitProvider.EmptyPromise;
+ }
+ else {
+ blame = Git.blame(GitProvider.BlameFormat, fileName)
+ .then(data => new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName))
+ .catch(ex => {
+ // Trap and cache expected blame errors
+ if (this.UseCaching) {
+ const msg = ex && ex.toString();
+ console.log('[GitLens]', `Replace blame cache: cacheKey=${cacheKey}`);
+
+ entry.blame = {
+ //date: new Date(),
+ item: GitProvider.EmptyPromise,
+ errorMessage: msg
+ };
+
+ this._cache.set(cacheKey, entry);
+ return GitProvider.EmptyPromise;
+ }
+ return null;
});
- }
+ }
- return blame;
- });
+ if (this.UseCaching) {
+ console.log('[GitLens]', `Add blame cache: cacheKey=${cacheKey}`);
+
+ entry.blame = {
+ //date: new Date(),
+ item: blame
+ };
+
+ this._cache.set(cacheKey, entry);
+ }
+
+ return blame;
}
- getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise {
+ async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise {
if (this.UseCaching && !sha) {
- return this.getBlameForFile(fileName).then(blame => {
- const blameLine = blame && blame.lines[line];
- if (!blameLine) return null;
-
- const commit = blame.commits.get(blameLine.sha);
- return {
- author: Object.assign({}, blame.authors.get(commit.author), { lineCount: commit.lines.length }),
- commit: commit,
- line: blameLine
- };
- });
+ const blame = await this.getBlameForFile(fileName);
+ const blameLine = blame && blame.lines[line];
+ if (!blameLine) return null;
+
+ const commit = blame.commits.get(blameLine.sha);
+ return {
+ author: Object.assign({}, blame.authors.get(commit.author), { lineCount: commit.lines.length }),
+ commit: commit,
+ line: blameLine
+ };
}
fileName = Git.normalizePath(fileName);
- return Git.blameLines(GitProvider.BlameFormat, fileName, line + 1, line + 1, sha, repoPath)
- .then(data => new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName))
- .then(blame => {
- if (!blame) return null;
+ 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;
- const commit = blame.commits.values().next().value;
- if (repoPath) {
- commit.repoPath = repoPath;
- }
- return {
- author: blame.authors.values().next().value,
- commit: commit,
- line: blame.lines[line]
- };
- })
- .catch(ex => null);
+ const commit = Iterables.first(blame.commits.values());
+ if (repoPath) {
+ commit.repoPath = repoPath;
+ }
+ return {
+ author: Iterables.first(blame.authors.values()),
+ commit: commit,
+ line: blame.lines[line]
+ };
+ }
+ catch (ex) {
+ return null;
+ }
}
- getBlameForRange(fileName: string, range: Range): Promise {
- return this.getBlameForFile(fileName).then(blame => {
- if (!blame) return null;
+ async getBlameForRange(fileName: string, range: Range): Promise {
+ const blame = await this.getBlameForFile(fileName);
+ if (!blame) return null;
- if (!blame.lines.length) return Object.assign({ allLines: blame.lines }, blame);
+ if (!blame.lines.length) return Object.assign({ allLines: blame.lines }, blame);
- if (range.start.line === 0 && range.end.line === blame.lines.length - 1) {
- return Object.assign({ allLines: blame.lines }, blame);
- }
+ if (range.start.line === 0 && range.end.line === blame.lines.length - 1) {
+ return Object.assign({ allLines: blame.lines }, blame);
+ }
- const lines = blame.lines.slice(range.start.line, range.end.line + 1);
- const shas: Set = new Set();
- lines.forEach(l => shas.add(l.sha));
-
- const authors: Map = new Map();
- const commits: Map = new Map();
- blame.commits.forEach(c => {
- if (!shas.has(c.sha)) return;
-
- const commit: IGitCommit = new GitCommit(c.repoPath, c.sha, c.fileName, c.author, c.date, c.message,
- c.lines.filter(l => l.line >= range.start.line && l.line <= range.end.line), c.originalFileName, c.previousSha, c.previousFileName);
- commits.set(c.sha, commit);
-
- let author = authors.get(commit.author);
- if (!author) {
- author = {
- name: commit.author,
- lineCount: 0
- };
- authors.set(author.name, author);
- }
+ const lines = blame.lines.slice(range.start.line, range.end.line + 1);
+ const shas: Set = new Set();
+ lines.forEach(l => shas.add(l.sha));
- author.lineCount += commit.lines.length;
- });
+ const authors: Map = new Map();
+ const commits: Map = new Map();
+ blame.commits.forEach(c => {
+ if (!shas.has(c.sha)) return;
- const sortedAuthors: Map = new Map();
- Array.from(authors.values())
- .sort((a, b) => b.lineCount - a.lineCount)
- .forEach(a => sortedAuthors.set(a.name, a));
+ const commit: IGitCommit = new GitCommit(c.repoPath, c.sha, c.fileName, c.author, c.date, c.message,
+ c.lines.filter(l => l.line >= range.start.line && l.line <= range.end.line), c.originalFileName, c.previousSha, c.previousFileName);
+ commits.set(c.sha, commit);
- return {
- authors: sortedAuthors,
- commits: commits,
- lines: lines,
- allLines: blame.lines
- };
+ let author = authors.get(commit.author);
+ if (!author) {
+ author = {
+ name: commit.author,
+ lineCount: 0
+ };
+ authors.set(author.name, author);
+ }
+
+ author.lineCount += commit.lines.length;
});
+
+ const sortedAuthors: Map = new Map();
+ Array.from(authors.values())
+ .sort((a, b) => b.lineCount - a.lineCount)
+ .forEach(a => sortedAuthors.set(a.name, a));
+
+ return {
+ authors: sortedAuthors,
+ commits: commits,
+ lines: lines,
+ allLines: blame.lines
+ };
}
- getBlameForShaRange(fileName: string, sha: string, range: Range): Promise {
- return this.getBlameForFile(fileName).then(blame => {
- if (!blame) return null;
+ async getBlameForShaRange(fileName: string, sha: string, range: Range): Promise {
+ const blame = await this.getBlameForFile(fileName);
+ if (!blame) return null;
+
+ const lines = blame.lines.slice(range.start.line, range.end.line + 1).filter(l => l.sha === sha);
+ let commit = blame.commits.get(sha);
+ commit = new GitCommit(commit.repoPath, commit.sha, commit.fileName, commit.author, commit.date, commit.message,
+ lines, commit.originalFileName, commit.previousSha, commit.previousFileName);
+ return {
+ author: Object.assign({}, blame.authors.get(commit.author), { lineCount: commit.lines.length }),
+ commit: commit,
+ lines: lines
+ };
+ }
- const lines = blame.lines.slice(range.start.line, range.end.line + 1).filter(l => l.sha === sha);
- let commit = blame.commits.get(sha);
- commit = new GitCommit(commit.repoPath, commit.sha, commit.fileName, commit.author, commit.date, commit.message,
- lines, commit.originalFileName, commit.previousSha, commit.previousFileName);
- return {
- author: Object.assign({}, blame.authors.get(commit.author), { lineCount: commit.lines.length }),
- commit: commit,
- lines: lines
- };
+ async getBlameLocations(fileName: string, range: Range): Promise {
+ const blame = await this.getBlameForRange(fileName, range);
+ if (!blame) return null;
+
+ const commitCount = blame.commits.size;
+
+ const locations: Array = [];
+ Iterables.forEach(blame.commits.values(), (c, i) => {
+ if (c.isUncommitted) return;
+
+ const uri = GitProvider.toBlameUri(c, i + 1, commitCount, range);
+ c.lines.forEach(l => locations.push(new Location(c.originalFileName
+ ? GitProvider.toBlameUri(c, i + 1, commitCount, range, c.originalFileName)
+ : uri,
+ new Position(l.originalLine, 0))));
});
- }
- getBlameLocations(fileName: string, range: Range): Promise {
- return this.getBlameForRange(fileName, range).then(blame => {
- if (!blame) return null;
+ return locations;
+ }
- const commitCount = blame.commits.size;
+ async getLogForFile(fileName: string) {
+ fileName = Git.normalizePath(fileName);
- const locations: Array = [];
- Array.from(blame.commits.values())
- .forEach((c, i) => {
- if (c.isUncommitted) return;
+ const cacheKey = this._getCacheEntryKey(fileName);
+ let entry: CacheEntry = undefined;
+ if (this.UseCaching) {
+ entry = this._cache.get(cacheKey);
+ if (entry !== undefined && entry.log !== undefined) return entry.log.item;
+ if (entry === undefined) {
+ entry = new CacheEntry();
+ }
+ }
- const uri = GitProvider.toBlameUri(c, i + 1, commitCount, range);
- c.lines.forEach(l => locations.push(new Location(c.originalFileName
- ? GitProvider.toBlameUri(c, i + 1, commitCount, range, c.originalFileName)
- : uri,
- new Position(l.originalLine, 0))));
+ const ignore = await this._gitignore;
+ let log: Promise;
+ if (ignore && !ignore.filter([fileName]).length) {
+ console.log('[GitLens]', `Skipping log; ${fileName} is gitignored`);
+ log = GitProvider.EmptyPromise;
+ }
+ else {
+ log = Git.log(fileName)
+ .then(data => new GitLogParserEnricher().enrich(data, fileName))
+ .catch(ex => {
+ // Trap and cache expected blame errors
+ if (this.UseCaching) {
+ const msg = ex && ex.toString();
+ console.log('[GitLens]', `Replace log cache: cacheKey=${cacheKey}`);
+
+ entry.log = {
+ //date: new Date(),
+ item: GitProvider.EmptyPromise,
+ errorMessage: msg
+ };
+
+ this._cache.set(cacheKey, entry);
+ return GitProvider.EmptyPromise;
+ }
+ return null;
});
+ }
+
+ if (this.UseCaching) {
+ console.log('[GitLens]', `Add log cache: cacheKey=${cacheKey}`);
+
+ entry.log = {
+ //date: new Date(),
+ item: log
+ };
- return locations;
+ this._cache.set(cacheKey, entry);
+ }
+
+ return log;
+ }
+
+ async getLogLocations(fileName: string): Promise {
+ const log = await this.getLogForFile(fileName);
+ if (!log) return null;
+
+ const commitCount = log.commits.size;
+
+ const locations: Array = [];
+ Iterables.forEach(log.commits.values(), (c, i) => {
+ if (c.isUncommitted) return;
+
+ const decoration = `/*\n ${c.sha} - ${c.message}\n ${c.author}, ${moment(c.date).format('MMMM Do, YYYY h:MMa')}\n */`;
+ locations.push(new Location(c.originalFileName
+ ? GitProvider.toGitUri(c, i + 1, commitCount, c.originalFileName, decoration)
+ : GitProvider.toGitUri(c, i + 1, commitCount, undefined, decoration),
+ new Position(2, 0)));
});
+
+ return locations;
}
getVersionedFile(fileName: string, repoPath: string, sha: string) {
@@ -363,7 +455,8 @@ export default class GitProvider extends Disposable {
static fromBlameUri(uri: Uri): IGitBlameUriData {
if (uri.scheme !== DocumentSchemes.GitBlame) throw new Error(`fromGitUri(uri=${uri}) invalid scheme`);
const data = GitProvider._fromGitUri(uri);
- data.range = new Range(data.range[0].line, data.range[0].character, data.range[1].line, data.range[1].character);
+ const range = data.range as Position[];
+ data.range = new Range(range[0].line, range[0].character, range[1].line, range[1].character);
return data;
}
@@ -380,26 +473,30 @@ export default class GitProvider extends Disposable {
return GitProvider._toGitUri(commit, DocumentSchemes.GitBlame, commitCount, GitProvider._toGitBlameUriData(commit, index, range, originalFileName));
}
- static toGitUri(commit: IGitCommit, index: number, commitCount: number, originalFileName?: string) {
- return GitProvider._toGitUri(commit, DocumentSchemes.Git, commitCount, GitProvider._toGitUriData(commit, index, originalFileName));
+ static toGitUri(commit: IGitCommit, index: number, commitCount: number, originalFileName?: string, decoration?: string) {
+ return GitProvider._toGitUri(commit, DocumentSchemes.Git, commitCount, GitProvider._toGitUriData(commit, index, originalFileName, decoration));
}
private static _toGitUri(commit: IGitCommit, scheme: DocumentSchemes, commitCount: number, data: IGitUriData | IGitBlameUriData) {
- const pad = n => ("0000000" + n).slice(-("" + commitCount).length);
+ const pad = (n: number) => ('0000000' + n).slice(-('' + commitCount).length);
const ext = path.extname(data.fileName);
// const uriPath = `${dirname(data.fileName)}/${commit.sha}: ${basename(data.fileName, ext)}${ext}`;
const uriPath = `${path.dirname(data.fileName)}/${commit.sha}${ext}`;
// NOTE: Need to specify an index here, since I can't control the sort order -- just alphabetic or by file location
- return Uri.parse(`${scheme}:${pad(data.index)}. ${commit.author}, ${moment(commit.date).format('MMM D, YYYY hh:MM a')} - ${uriPath}?${JSON.stringify(data)}`);
+ //return Uri.parse(`${scheme}:${pad(data.index)}. ${commit.author}, ${moment(commit.date).format('MMM D, YYYY hh:MMa')} - ${uriPath}?${JSON.stringify(data)}`);
+ return Uri.parse(`${scheme}:${pad(data.index)}. ${moment(commit.date).format('MMM D, YYYY hh:MMa')} - ${uriPath}?${JSON.stringify(data)}`);
}
- private static _toGitUriData(commit: IGitCommit, index: number, originalFileName?: string): T {
+ private static _toGitUriData(commit: IGitCommit, index: number, originalFileName?: string, decoration?: string): T {
const fileName = Git.normalizePath(path.join(commit.repoPath, commit.fileName));
const data = { repoPath: commit.repoPath, fileName: fileName, sha: commit.sha, index: index } as T;
if (originalFileName) {
data.originalFileName = Git.normalizePath(path.join(commit.repoPath, originalFileName));
}
+ if (decoration) {
+ data.decoration = decoration;
+ }
return data;
}
@@ -416,6 +513,7 @@ export interface IGitUriData {
originalFileName?: string;
sha: string;
index: number;
+ decoration?: string;
}
export interface IGitBlameUriData extends IGitUriData {
diff --git a/src/system.ts b/src/system.ts
new file mode 100644
index 0000000..f4c94f2
--- /dev/null
+++ b/src/system.ts
@@ -0,0 +1,11 @@
+// export * from './system/array';
+// export * from './system/disposable';
+// export * from './system/element';
+// export * from './system/event';
+// import Event from './system/event';
+// export { Event };
+export * from './system/function';
+export * from './system/iterable';
+// export * from './system/map';
+export * from './system/object';
+export * from './system/string';
\ No newline at end of file
diff --git a/src/system/function.ts b/src/system/function.ts
new file mode 100644
index 0000000..8ccfae7
--- /dev/null
+++ b/src/system/function.ts
@@ -0,0 +1,14 @@
+'use strict';
+// import { debounce as _debounce } from 'lodash';
+const _debounce = require('lodash.debounce');
+
+export interface IDeferred {
+ cancel(): void;
+ flush(): void;
+}
+
+export namespace Functions {
+ export function debounce(fn: T, wait?: number, options?: any): T & IDeferred {
+ return _debounce(fn, wait, options);
+ }
+}
\ No newline at end of file
diff --git a/src/system/iterable.ts b/src/system/iterable.ts
new file mode 100644
index 0000000..537b6c7
--- /dev/null
+++ b/src/system/iterable.ts
@@ -0,0 +1,55 @@
+'use strict';
+
+export namespace Iterables {
+ export function* filter(source: Iterable | IterableIterator, predicate: (item: T) => boolean): Iterable {
+ for (const item of source) {
+ if (predicate(item)) yield item;
+ }
+ }
+
+ export function* filterMap(source: Iterable | IterableIterator, predicateMapper: (item: T) => TMapped | undefined | null): Iterable {
+ for (const item of source) {
+ const mapped = predicateMapper(item);
+ if (mapped) yield mapped;
+ }
+ }
+
+ export function forEach(source: Iterable | IterableIterator, fn: (item: T, index: number) => void): void {
+ let i = 0;
+ for (const item of source) {
+ fn(item, i);
+ i++;
+ }
+ }
+
+ export function find(source: Iterable | IterableIterator, predicate: (item: T) => boolean): T {
+ for (const item of source) {
+ if (predicate(item)) return item;
+ }
+ return null;
+ }
+
+ export function first(source: Iterable): T {
+ return source[Symbol.iterator]().next().value;
+ }
+
+ export function* flatMap(source: Iterable | IterableIterator, mapper: (item: T) => Iterable): Iterable {
+ for (const item of source) {
+ yield* mapper(item);
+ }
+ }
+
+ export function isIterable(source: Iterable): boolean {
+ return typeof source[Symbol.iterator] === 'function';
+ }
+
+ export function* map(source: Iterable | IterableIterator, mapper: (item: T) => TMapped): Iterable {
+ for (const item of source) {
+ yield mapper(item);
+ }
+ }
+
+ export function next(source: IterableIterator): T {
+ return source.next().value;
+ }
+}
\ No newline at end of file
diff --git a/src/system/object.ts b/src/system/object.ts
new file mode 100644
index 0000000..3c53026
--- /dev/null
+++ b/src/system/object.ts
@@ -0,0 +1,15 @@
+'use strict';
+//import { isEqual as _isEqual } from 'lodash';
+const _isEqual = require('lodash.isequal');
+
+export namespace Objects {
+ export function areEquivalent(first: any, second: any): boolean {
+ return _isEqual(first, second);
+ }
+
+ export function* entries(o: any): IterableIterator<[string, any]> {
+ for (let key in o) {
+ yield [key, o[key]];
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/system/string.ts b/src/system/string.ts
new file mode 100644
index 0000000..1b4f630
--- /dev/null
+++ b/src/system/string.ts
@@ -0,0 +1,9 @@
+'use strict';
+//import { escapeRegExp as _escapeRegExp } from 'lodash';
+const _escapeRegExp = require('lodash.escaperegexp');
+
+export namespace Strings {
+ export function escapeRegExp(s: string): string {
+ return _escapeRegExp(s);
+ }
+}
\ No newline at end of file
diff --git a/test/extension.test.ts b/test/extension.test.ts
index 5c4a4da..563d626 100644
--- a/test/extension.test.ts
+++ b/test/extension.test.ts
@@ -8,14 +8,14 @@ import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
-import * as vscode from 'vscode';
-import * as myExtension from '../src/extension';
+// import * as vscode from 'vscode';
+// import * as myExtension from '../src/extension';
// Defines a Mocha test suite to group tests of similar kind together
-suite("Extension Tests", () => {
+suite('Extension Tests', () => {
// Defines a Mocha unit test
- test("Something 1", () => {
+ test('Something 1', () => {
assert.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});
diff --git a/test/index.ts b/test/index.ts
index 50bae45..31de27a 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -10,7 +10,7 @@
// to report the results back to the caller. When the tests are finished, return
// a possible error to the callback or null if none.
-var testRunner = require('vscode/lib/testrunner');
+let testRunner = require('vscode/lib/testrunner');
// You can directly control Mocha options by uncommenting the following lines
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
diff --git a/tsconfig.json b/tsconfig.json
index 11282c9..be0af14 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,13 +1,25 @@
{
"compilerOptions": {
- "module": "commonjs",
- "target": "es6",
- "outDir": "out",
"lib": [
- "es6"
+ "es6",
+ "es2015"
],
+ "module": "commonjs",
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": false,
+ "noUnusedLocals": true,
+ "outDir": "out",
+ "removeComments": true,
+ "rootDir": ".",
"sourceMap": true,
- "rootDir": "."
+ "strictNullChecks": false,
+ "target": "es6",
+ "typeRoots": [
+ "./node_modules/@types",
+ "./@types"
+ ]
},
"exclude": [
"node_modules",
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 0000000..5871484
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,97 @@
+{
+ "rules": {
+ "arrow-parens": false,
+ "class-name": true,
+ "comment-format": [
+ false
+ ],
+ "curly": false,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "new-parens": true,
+ "no-duplicate-variable": true,
+ "no-eval": true,
+ "no-for-in-array": false,
+ "no-internal-module": true,
+ "no-reference": true,
+ "no-trailing-whitespace": true,
+ "no-unreachable": true,
+ "no-unsafe-finally": true,
+ "no-unused-expression": false,
+ "no-unused-new": true,
+ "no-unused-variable": [
+ true
+ ],
+ "no-var-keyword": true,
+ "no-var-requires": false,
+ "object-literal-key-quotes": [
+ true,
+ "as-needed"
+ ],
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-whitespace"
+ ],
+ "one-variable-per-declaration": [
+ true,
+ "ignore-for-loop"
+ ],
+ "ordered-imports": [
+ false,
+ {
+ "named-imports-order": "case-insensitive"
+ }
+ ],
+ "quotemark": [
+ true,
+ "single",
+ "avoid-escape"
+ ],
+ "radix": true,
+ "semicolon": [
+ true,
+ "always"
+ ],
+ "trailing-comma": [
+ true,
+ {
+ "multiline": "never",
+ "singleline": "never"
+ }
+ ],
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ }
+ ],
+ "use-isnan": true,
+ "variable-name": [
+ true,
+ "allow-leading-underscore",
+ "allow-pascal-case",
+ "ban-keywords",
+ "check-format"
+ ],
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-module",
+ "check-separator",
+ "check-type"
+ ]
+ }
+}
\ No newline at end of file