mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-01-13 17:23:11 -05:00
1.0 wip
This commit is contained in:
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
42
.vscode/tasks.json
vendored
42
.vscode/tasks.json
vendored
@@ -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.
|
||||
"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
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
58
package.json
58
package.json
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"publisher": "eamodio",
|
||||
"engines": {
|
||||
"vscode": "^1.5.0"
|
||||
"vscode": "^1.6.0"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"displayName": "GitLens",
|
||||
@@ -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 ./"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/// <reference path="../node_modules/rxjs/Observable.d.ts" />
|
||||
/// <reference path="../../../node_modules/rxjs/Observable.d.ts" />
|
||||
declare module "spawn-rx" {
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use strict'
|
||||
'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) {
|
||||
this.clear();
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.showBlameAnnotation(editor, sha);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use strict'
|
||||
'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';
|
||||
@@ -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,19 +72,20 @@ 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 => {
|
||||
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<string>('renderWhitespace');
|
||||
this._toggleWhitespace = whitespace !== 'false' && whitespace !== 'none';
|
||||
if (this._toggleWhitespace) {
|
||||
this._renderWhitespaceSetting = workspace.getConfiguration('editor').get<string>('renderWhitespace');
|
||||
if (this._renderWhitespaceSetting !== 'none') {
|
||||
commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace);
|
||||
}
|
||||
|
||||
@@ -101,14 +103,13 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
private _applyCommitHighlight(sha: string) {
|
||||
return this._blame.then(blame => {
|
||||
private async _applyCommitHighlight(sha: string) {
|
||||
const blame = await this._blame;
|
||||
if (!blame || !blame.lines.length) return;
|
||||
|
||||
const highlightDecorationRanges = blame.lines
|
||||
@@ -116,12 +117,11 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
.map(l => this.editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)));
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
'use strict'
|
||||
'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<IConfig>('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<void> {
|
||||
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() {
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
'use strict'
|
||||
'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;
|
||||
}
|
||||
@@ -1,44 +1,20 @@
|
||||
'use strict'
|
||||
'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;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
if (sha && !GitProvider.isUncommitted(sha)) {
|
||||
if (!compareWithSha) {
|
||||
return window.showInformationMessage(`Commit ${sha} has no previous commit`);
|
||||
}
|
||||
@@ -48,4 +24,50 @@ export default class DiffWithPreviousCommand extends EditorCommand {
|
||||
.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 (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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,58 @@
|
||||
'use strict'
|
||||
import {commands, TextEditor, TextEditorEdit, Uri, window} from 'vscode';
|
||||
'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 (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;
|
||||
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));
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit, Uri } from 'vscode';
|
||||
import BlameAnnotationController from '../blameAnnotationController';
|
||||
import { EditorCommand } from './commands';
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri } from 'vscode';
|
||||
import { EditorCommand } from './commands';
|
||||
import { BuiltInCommands, Commands } from '../constants';
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/commands/showHistory.ts
Normal file
29
src/commands/showHistory.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit, Uri } from 'vscode';
|
||||
import BlameAnnotationController from '../blameAnnotationController';
|
||||
import { EditorCommand } from './commands';
|
||||
@@ -6,32 +6,26 @@ 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));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleCodeLensCommand extends EditorCommand {
|
||||
constructor(private git: GitProvider) {
|
||||
super(Commands.ToggleCodeLens);
|
||||
}
|
||||
|
||||
execute(editor: TextEditor, edit: TextEditorEdit) {
|
||||
return this.git.toggleCodeLens(editor);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit } from 'vscode';
|
||||
import { EditorCommand } from './commands';
|
||||
import { Commands} from '../constants';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -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 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));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'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<IGitBlame> {
|
||||
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<IGitBlame> {
|
||||
}
|
||||
|
||||
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<IGitBlame> {
|
||||
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<IGitBlame> {
|
||||
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<IGitBlame> {
|
||||
commits.forEach(c => authors.get(c.author).lineCount += c.lines.length);
|
||||
|
||||
const sortedAuthors: Map<string, IGitAuthor> = 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));
|
||||
|
||||
|
||||
143
src/git/enrichers/logParserEnricher.ts
Normal file
143
src/git/enrichers/logParserEnricher.ts
Normal file
@@ -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<IGitLog> {
|
||||
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<string, IGitAuthor> = new Map();
|
||||
const commits: Map<string, IGitCommit> = 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<string, IGitAuthor> = 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<string, IGitCommit> = new Map();
|
||||
// Array.from(commits.values())
|
||||
// .sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||
// .forEach(c => sortedCommits.set(c.sha, c));
|
||||
|
||||
return <IGitLog>{
|
||||
repoPath: repoPath,
|
||||
authors: sortedAuthors,
|
||||
// commits: sortedCommits,
|
||||
commits: commits
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,17 @@ 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 => {
|
||||
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 => {
|
||||
}
|
||||
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, ' '));
|
||||
@@ -23,7 +24,7 @@ function gitCommand(cwd: string, ...args) {
|
||||
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<string>((resolve, reject) => {
|
||||
Git.getVersionedFileText(fileName, repoPath, sha).then(data => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
import {Uri} from 'vscode';
|
||||
import Git from './git';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface IGitEnricher<T> {
|
||||
enrich(data: string, ...args): T;
|
||||
enrich(data: string, ...args: any[]): T;
|
||||
}
|
||||
|
||||
export interface IGitBlame {
|
||||
@@ -90,3 +90,9 @@ export interface IGitCommitLine {
|
||||
originalLine: number;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export interface IGitLog {
|
||||
repoPath: string;
|
||||
authors: Map<string, IGitAuthor>;
|
||||
commits: Map<string, IGitCommit>;
|
||||
}
|
||||
@@ -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<CodeLens> {
|
||||
if (lens instanceof GitDiffWithWorkingTreeCodeLens) return this._resolveDiffWithWorkingTreeCodeLens(lens, token);
|
||||
if (lens instanceof GitDiffWithPreviousCodeLens) return this._resolveGitDiffWithPreviousCodeLens(lens, token);
|
||||
return Promise.reject<CodeLens>(null);
|
||||
}
|
||||
|
||||
_resolveDiffWithWorkingTreeCodeLens(lens: GitDiffWithWorkingTreeCodeLens, token: CancellationToken): Thenable<CodeLens> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 { 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, IGitCommit} from './gitProvider';
|
||||
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<CodeLens> {
|
||||
if (lens instanceof GitRecentChangeCodeLens) return this._resolveGitRecentChangeCodeLens(lens, token);
|
||||
if (lens instanceof GitAuthorsCodeLens) return this._resolveGitAuthorsCodeLens(lens, token);
|
||||
return Promise.reject<CodeLens>(null);
|
||||
}
|
||||
|
||||
_resolveGitRecentChangeCodeLens(lens: GitRecentChangeCodeLens, token: CancellationToken): Thenable<CodeLens> {
|
||||
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<GitRecentChangeCodeLens>(title, lens, blame);
|
||||
@@ -168,7 +169,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
|
||||
_resolveGitAuthorsCodeLens(lens: GitAuthorsCodeLens, token: CancellationToken): Thenable<CodeLens> {
|
||||
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<GitAuthorsCodeLens>(title, lens, blame);
|
||||
case CodeLensCommand.BlameExplorer: return this._applyBlameExplorerCommand<GitAuthorsCodeLens>(title, lens, blame);
|
||||
|
||||
@@ -11,6 +11,7 @@ export default class GitContentProvider implements TextDocumentContentProvider {
|
||||
provideTextDocumentContent(uri: Uri): string | Thenable<string> {
|
||||
const data = GitProvider.fromGitUri(uri);
|
||||
return this.git.getVersionedFileText(data.originalFileName || data.fileName, data.repoPath, data.sha)
|
||||
.then(text => data.decoration ? `${data.decoration}\n${text}` : text)
|
||||
.catch(ex => console.error('[GitLens.GitContentProvider]', 'getVersionedFileText', ex));
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,35 @@
|
||||
'use strict'
|
||||
'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 {
|
||||
//date: Date;
|
||||
blame: Promise<IGitBlame>;
|
||||
errorMessage?: string
|
||||
class CacheEntry {
|
||||
blame?: ICachedBlame;
|
||||
log?: ICachedLog;
|
||||
|
||||
get hasErrors() {
|
||||
return !!((this.blame && this.blame.errorMessage) || (this.log && this.log.errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
interface ICachedItem<T> {
|
||||
//date: Date;
|
||||
item: Promise<T>;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
interface ICachedBlame extends ICachedItem<IGitBlame> { }
|
||||
interface ICachedLog extends ICachedItem<IGitLog> { }
|
||||
|
||||
enum RemoveCacheReason {
|
||||
DocumentClosed,
|
||||
@@ -28,8 +38,8 @@ enum RemoveCacheReason {
|
||||
}
|
||||
|
||||
export default class GitProvider extends Disposable {
|
||||
private _blameCache: Map<string, IBlameCacheEntry>|null;
|
||||
private _blameCacheDisposable: Disposable|null;
|
||||
private _cache: Map<string, CacheEntry> | null;
|
||||
private _cacheDisposable: Disposable | null;
|
||||
|
||||
private _config: IConfig;
|
||||
private _disposable: Disposable;
|
||||
@@ -37,7 +47,7 @@ export default class GitProvider extends Disposable {
|
||||
private _codeLensProviderSelector: DocumentFilter;
|
||||
private _gitignore: Promise<ignore.Ignore>;
|
||||
|
||||
static BlameEmptyPromise: Promise<IGitBlame|null> = Promise.resolve(null);
|
||||
static EmptyPromise: Promise<IGitBlame | IGitLog> = Promise.resolve(null);
|
||||
static BlameFormat = GitBlameFormat.incremental;
|
||||
|
||||
constructor(private context: ExtensionContext) {
|
||||
@@ -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<IConfig>('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,25 +158,30 @@ export default class GitProvider extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
getRepoPath(cwd: string) {
|
||||
getRepoPath(cwd: string): Promise<string> {
|
||||
return Git.repoPath(cwd);
|
||||
}
|
||||
|
||||
getBlameForFile(fileName: string) {
|
||||
async getBlameForFile(fileName: string): Promise<IGitBlame | null> {
|
||||
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;
|
||||
entry = this._cache.get(cacheKey);
|
||||
if (entry !== undefined && entry.blame !== undefined) return entry.blame.item;
|
||||
if (entry === undefined) {
|
||||
entry = new CacheEntry();
|
||||
}
|
||||
}
|
||||
|
||||
return this._gitignore.then(ignore => {
|
||||
const ignore = await this._gitignore;
|
||||
let blame: Promise<IGitBlame>;
|
||||
if (ignore && !ignore.filter([fileName]).length) {
|
||||
console.log('[GitLens]', `Skipping blame; ${fileName} is gitignored`);
|
||||
blame = GitProvider.BlameEmptyPromise;
|
||||
} else {
|
||||
blame = GitProvider.EmptyPromise;
|
||||
}
|
||||
else {
|
||||
blame = Git.blame(GitProvider.BlameFormat, fileName)
|
||||
.then(data => new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName))
|
||||
.catch(ex => {
|
||||
@@ -174,12 +189,15 @@ export default class GitProvider extends Disposable {
|
||||
if (this.UseCaching) {
|
||||
const msg = ex && ex.toString();
|
||||
console.log('[GitLens]', `Replace blame cache: cacheKey=${cacheKey}`);
|
||||
this._blameCache.set(cacheKey, <IBlameCacheEntry>{
|
||||
|
||||
entry.blame = <ICachedBlame>{
|
||||
//date: new Date(),
|
||||
blame: GitProvider.BlameEmptyPromise,
|
||||
item: GitProvider.EmptyPromise,
|
||||
errorMessage: msg
|
||||
});
|
||||
return GitProvider.BlameEmptyPromise;
|
||||
};
|
||||
|
||||
this._cache.set(cacheKey, entry);
|
||||
return GitProvider.EmptyPromise;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
@@ -187,53 +205,56 @@ export default class GitProvider extends Disposable {
|
||||
|
||||
if (this.UseCaching) {
|
||||
console.log('[GitLens]', `Add blame cache: cacheKey=${cacheKey}`);
|
||||
this._blameCache.set(cacheKey, <IBlameCacheEntry> {
|
||||
|
||||
entry.blame = <ICachedBlame>{
|
||||
//date: new Date(),
|
||||
blame: blame
|
||||
});
|
||||
item: blame
|
||||
};
|
||||
|
||||
this._cache.set(cacheKey, entry);
|
||||
}
|
||||
|
||||
return blame;
|
||||
});
|
||||
}
|
||||
|
||||
getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise<IGitBlameLine|null> {
|
||||
async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise<IGitBlameLine | null> {
|
||||
if (this.UseCaching && !sha) {
|
||||
return this.getBlameForFile(fileName).then(blame => {
|
||||
const blame = await this.getBlameForFile(fileName);
|
||||
const blameLine = blame && blame.lines[line];
|
||||
if (!blameLine) return null;
|
||||
|
||||
const commit = blame.commits.get(blameLine.sha);
|
||||
return {
|
||||
return <IGitBlameLine>{
|
||||
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 => {
|
||||
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;
|
||||
const commit = Iterables.first(blame.commits.values());
|
||||
if (repoPath) {
|
||||
commit.repoPath = repoPath;
|
||||
}
|
||||
return <IGitBlameLine>{
|
||||
author: blame.authors.values().next().value,
|
||||
author: Iterables.first(blame.authors.values()),
|
||||
commit: commit,
|
||||
line: blame.lines[line]
|
||||
};
|
||||
})
|
||||
.catch(ex => null);
|
||||
}
|
||||
catch (ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getBlameForRange(fileName: string, range: Range): Promise<IGitBlameLines|null> {
|
||||
return this.getBlameForFile(fileName).then(blame => {
|
||||
async getBlameForRange(fileName: string, range: Range): Promise<IGitBlameLines | null> {
|
||||
const blame = await this.getBlameForFile(fileName);
|
||||
if (!blame) return null;
|
||||
|
||||
if (!blame.lines.length) return Object.assign({ allLines: blame.lines }, blame);
|
||||
@@ -272,17 +293,16 @@ export default class GitProvider extends Disposable {
|
||||
.sort((a, b) => b.lineCount - a.lineCount)
|
||||
.forEach(a => sortedAuthors.set(a.name, a));
|
||||
|
||||
return {
|
||||
return <IGitBlameLines>{
|
||||
authors: sortedAuthors,
|
||||
commits: commits,
|
||||
lines: lines,
|
||||
allLines: blame.lines
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getBlameForShaRange(fileName: string, sha: string, range: Range): Promise<IGitBlameCommitLines|null> {
|
||||
return this.getBlameForFile(fileName).then(blame => {
|
||||
async getBlameForShaRange(fileName: string, sha: string, range: Range): Promise<IGitBlameCommitLines | null> {
|
||||
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);
|
||||
@@ -294,18 +314,16 @@ export default class GitProvider extends Disposable {
|
||||
commit: commit,
|
||||
lines: lines
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getBlameLocations(fileName: string, range: Range): Promise<Location[]|null> {
|
||||
return this.getBlameForRange(fileName, range).then(blame => {
|
||||
async getBlameLocations(fileName: string, range: Range): Promise<Location[] | null> {
|
||||
const blame = await this.getBlameForRange(fileName, range);
|
||||
if (!blame) return null;
|
||||
|
||||
const commitCount = blame.commits.size;
|
||||
|
||||
const locations: Array<Location> = [];
|
||||
Array.from(blame.commits.values())
|
||||
.forEach((c, i) => {
|
||||
Iterables.forEach(blame.commits.values(), (c, i) => {
|
||||
if (c.isUncommitted) return;
|
||||
|
||||
const uri = GitProvider.toBlameUri(c, i + 1, commitCount, range);
|
||||
@@ -316,9 +334,83 @@ export default class GitProvider extends Disposable {
|
||||
});
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
async getLogForFile(fileName: string) {
|
||||
fileName = Git.normalizePath(fileName);
|
||||
|
||||
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 ignore = await this._gitignore;
|
||||
let log: Promise<IGitLog>;
|
||||
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 = <ICachedLog>{
|
||||
//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 = <ICachedLog>{
|
||||
//date: new Date(),
|
||||
item: log
|
||||
};
|
||||
|
||||
this._cache.set(cacheKey, entry);
|
||||
}
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
async getLogLocations(fileName: string): Promise<Location[] | null> {
|
||||
const log = await this.getLogForFile(fileName);
|
||||
if (!log) return null;
|
||||
|
||||
const commitCount = log.commits.size;
|
||||
|
||||
const locations: Array<Location> = [];
|
||||
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) {
|
||||
return Git.getVersionedFile(fileName, repoPath, sha);
|
||||
}
|
||||
@@ -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<IGitBlameUriData>(uri);
|
||||
data.range = new Range(data.range[0].line, data.range[0].character, data.range[1].line, data.range[1].character);
|
||||
const range = <any>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<T extends IGitUriData>(commit: IGitCommit, index: number, originalFileName?: string): T {
|
||||
private static _toGitUriData<T extends IGitUriData>(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 {
|
||||
|
||||
11
src/system.ts
Normal file
11
src/system.ts
Normal file
@@ -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';
|
||||
14
src/system/function.ts
Normal file
14
src/system/function.ts
Normal file
@@ -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<T extends Function>(fn: T, wait?: number, options?: any): T & IDeferred {
|
||||
return _debounce(fn, wait, options);
|
||||
}
|
||||
}
|
||||
55
src/system/iterable.ts
Normal file
55
src/system/iterable.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
export namespace Iterables {
|
||||
export function* filter<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): Iterable<T> {
|
||||
for (const item of source) {
|
||||
if (predicate(item)) yield item;
|
||||
}
|
||||
}
|
||||
|
||||
export function* filterMap<T, TMapped>(source: Iterable<T> | IterableIterator<T>, predicateMapper: (item: T) => TMapped | undefined | null): Iterable<TMapped> {
|
||||
for (const item of source) {
|
||||
const mapped = predicateMapper(item);
|
||||
if (mapped) yield mapped;
|
||||
}
|
||||
}
|
||||
|
||||
export function forEach<T>(source: Iterable<T> | IterableIterator<T>, fn: (item: T, index: number) => void): void {
|
||||
let i = 0;
|
||||
for (const item of source) {
|
||||
fn(item, i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
export function find<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): T {
|
||||
for (const item of source) {
|
||||
if (predicate(item)) return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function first<T>(source: Iterable<T>): T {
|
||||
return source[Symbol.iterator]().next().value;
|
||||
}
|
||||
|
||||
export function* flatMap<T, TMapped>(source: Iterable<T> | IterableIterator<T>, mapper: (item: T) => Iterable<TMapped>): Iterable<TMapped> {
|
||||
for (const item of source) {
|
||||
yield* mapper(item);
|
||||
}
|
||||
}
|
||||
|
||||
export function isIterable(source: Iterable<any>): boolean {
|
||||
return typeof source[Symbol.iterator] === 'function';
|
||||
}
|
||||
|
||||
export function* map<T, TMapped>(source: Iterable<T> | IterableIterator<T>, mapper: (item: T) => TMapped): Iterable<TMapped> {
|
||||
for (const item of source) {
|
||||
yield mapper(item);
|
||||
}
|
||||
}
|
||||
|
||||
export function next<T>(source: IterableIterator<T>): T {
|
||||
return source.next().value;
|
||||
}
|
||||
}
|
||||
15
src/system/object.ts
Normal file
15
src/system/object.ts
Normal file
@@ -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]];
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/system/string.ts
Normal file
9
src/system/string.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
97
tslint.json
Normal file
97
tslint.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user