Adds error messages for failed operations

Adds showHistory command support to CodeLens
Fixes and improve the showHistory explorer
Refactoring
This commit is contained in:
Eric Amodio
2016-11-10 03:22:43 -05:00
parent 562afeaaad
commit f4410be30a
25 changed files with 584 additions and 305 deletions

View File

@@ -1,14 +1,20 @@
---
## Release Notes
### 1.0.0
### 0.9.0
- Adds support for git history (log)
- Adds `gitlens.diffWithPrevious` command to the explore context menu
- Adds support for git history (log)!
- Adds new `gitlens.showHistory` command to open the history explorer
- Adds new `gitlens.showHistory` option to the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings
- Adds per-language CodeLens location customization using the `gitlens.codeLens.languageLocations` setting
- Adds new `gitlens.diffLineWithPrevious` command for line sensitive diffs
- Adds new `gitlens.diffLineWithWorking` command for line sensitive diffs
- Adds `gitlens.diffWithPrevious` command to the explorer context menu
- Adds output channel logging, controlled by the `gitlens.advanced.output.level` setting
- 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
- Removes all debug logging, unless the `gitlens.advanced.output.debug` settings it on
- Improves performance (significantly) when only showing CodeLens at the document level
- Changes `gitlens.diffWithPrevious` command to always be file sensitive diffs
- Changes `gitlens.diffWithWorking` command to always be file sensitive diffs
- Removes all debug logging, unless the `gitlens.advanced.debug` settings it on
- Fixes issue where blame annotations would not be cleared properly when switching between open files
### 0.5.5

View File

@@ -12,9 +12,10 @@ Provides Git information (most recent commit, # of authors) in CodeLens, on-dema
> Clicking on the CodeLens toggles Git blame annotations on/off
- Provides on-demand **inline blame annotations** with multiple styles
- Provides Git blame information about the selected line in the **status bar**
- Provides a Git **blame history explorer** to visualize the history of a file or block
- Provides a Git **history explorer** to visualize the history of a file or block
- Provides a Git **blame history explorer** to visualize the blame history of a file or block
- Provides ability to **compare diffs** with the working tree as well as with previous versions
- Provides many configuration settings to allow the **customization** of almost all Features
- Provides many configuration settings to allow the **customization** of almost all features
---
## Screenshots
@@ -38,17 +39,20 @@ Must be using Git and it must be in your path.
|`gitlens.codeLens.visibility`|Specifies when CodeLens will be triggered in the active document. `auto` - automatically. `ondemand` - only when requested. `off` - disables all active document CodeLens
|`gitlens.codeLens.location`|Specifies where CodeLens will be rendered in the active document. `all` - render at the top of the document, on container-like (classes, modules, etc), and on member-like (methods, functions, properties, etc) lines. `document+containers` - render at the top of the document and on container-like lines. `document` - only render at the top of the document. `custom` - rendering controlled by `gitlens.codeLens.locationCustomSymbols`
|`gitlens.codeLens.locationCustomSymbols`|Specifies the set of document symbols to render active document CodeLens on. Must be a member of `SymbolKind`
|`gitlens.codeLens.languageLocations`|Specifies where CodeLens will be rendered in the active document for the specified languages
|`gitlens.codeLens.recentChange.enabled`|Specifies whether the recent change CodeLens is shown
|`gitlens.codeLens.recentChange.command`|Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|`gitlens.codeLens.recentChange.command`|Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|`gitlens.codeLens.authors.enabled`|Specifies whether the authors CodeLens is shown
|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension
|`gitlens.menus.fileDiff.enabled`|Specifies whether file-based diff commands will be added to the context menus
|`gitlens.menus.lineDiff.enabled`|Specifies whether line-based diff commands will be added to the context menus
|`gitlens.statusBar.enabled`|Specifies whether blame information is shown in the status bar
|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
---
## Known Issues
- Content in the **Blame history explorer** disappears after a bit: [vscode issue](https://github.com/Microsoft/vscode/issues/11360)
- Content in the **history explorers** disappears after a bit: [vscode issue](https://github.com/Microsoft/vscode/issues/11360)
- Highlighted lines disappear in **Blame explorer** after changing selection and returning to a previous selection: [vscode issue](https://github.com/Microsoft/vscode/issues/11360)
- CodeLens aren't updated properly after a file is saved: [vscode issue](https://github.com/Microsoft/vscode/issues/11546)
- Visible whitespace causes issue with blame overlay (currently fixed with a hack, but fails randomly): [vscode issue](https://github.com/Microsoft/vscode/issues/11485)

View File

@@ -1,6 +1,6 @@
{
"name": "gitlens",
"version": "0.5.5",
"version": "0.9.0",
"author": {
"name": "Eric Amodio",
"email": "eamodio@gmail.com"
@@ -18,7 +18,7 @@
"keywords": [
"git",
"blame",
"gitblame",
"history",
"codelens",
"annotation"
],
@@ -27,7 +27,7 @@
"theme": "dark"
},
"icon": "images/gitlens-icon.png",
"preview": false,
"preview": true,
"homepage": "https://github.com/eamodio/vscode-gitlens/blob/master/README.md",
"bugs": {
"url": "https://github.com/eamodio/vscode-gitlens/issues"
@@ -91,6 +91,64 @@
"type": "array",
"description": "Specifies the set of document symbols to render active document CodeLens on. Must be a member of `SymbolKind`"
},
"gitlens.codeLens.languageLocations": {
"type": "array",
"default": [
{
"language": "json",
"location": "document"
},
{
"language": "css",
"location": "document"
},
{
"language": "scss",
"location": "document"
},
{
"language": "less",
"location": "document"
}
],
"items": {
"type": "object",
"required": [
"language",
"location"
],
"properties": {
"language": {
"type": "string",
"description": "Specifies the language to which this CodeLens override applies"
},
"location": {
"type": "string",
"default": "document+containers",
"enum": [
"all",
"document+containers",
"document",
"custom",
"none"
],
"description": "Specifies where CodeLens will be rendered in the active document for the specified language. `all` - render at the top of the document, on container-like (classes, modules, etc), and on member-like (methods, functions, properties, etc) lines. `document+containers` - render at the top of the document and on container-like lines. `document` - only render at the top of the document. `custom` - rendering controlled by `customSymbols`"
},
"customSymbols": {
"type": "string",
"description": "Specifies the set of document symbols to render active document CodeLens on. Must be a member of `SymbolKind`"
}
}
},
"uniqueItems": true,
"enum": [
"all",
"document+containers",
"document",
"custom"
],
"description": "Specifies where CodeLens will be rendered in the active document for the specified languages"
},
"gitlens.codeLens.recentChange.enabled": {
"type": "boolean",
"default": true,
@@ -98,7 +156,7 @@
},
"gitlens.codeLens.recentChange.command": {
"type": "string",
"default": "gitlens.showBlameHistory",
"default": "gitlens.showHistory",
"enum": [
"gitlens.toggleBlame",
"gitlens.showBlameHistory",
@@ -106,7 +164,7 @@
"gitlens.diffWithPrevious",
"git.viewFileHistory"
],
"description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
"description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
},
"gitlens.codeLens.authors.enabled": {
"type": "boolean",
@@ -123,7 +181,7 @@
"gitlens.diffWithPrevious",
"git.viewFileHistory"
],
"description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
"description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
},
"gitlens.statusBar.enabled": {
"type": "boolean",
@@ -141,17 +199,27 @@
"gitlens.toggleCodeLens",
"git.viewFileHistory"
],
"description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
"description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showHistory` - opens the history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension"
},
"gitlens.menus.fileDiff.enabled": {
"type": "boolean",
"default": true,
"description": "Specifies whether file-based diff commands will be added to the context menus"
},
"gitlens.menus.lineDiff.enabled": {
"type": "boolean",
"default": true,
"description": "Specifies whether line-based diff commands will be added to the context menus"
},
"gitlens.advanced.caching.enabled": {
"type": "boolean",
"default": true,
"description": "Specifies whether git blame output will be cached"
},
"gitlens.advanced.output.debug": {
"gitlens.advanced.debug": {
"type": "boolean",
"default": false,
"description": "Specifies whether all output will be sent to the console"
"description": "Specifies debug mode"
},
"gitlens.advanced.output.level": {
"type": "string",
@@ -168,12 +236,22 @@
"commands": [
{
"command": "gitlens.diffWithPrevious",
"title": "Open Diff with Previous Commit",
"title": "Diff with Previous Commit",
"category": "GitLens"
},
{
"command": "gitlens.diffLineWithPrevious",
"title": "Diff with Previous Commit (line)",
"category": "GitLens"
},
{
"command": "gitlens.diffWithWorking",
"title": "Open Diff with Working Tree",
"title": "Diff with Working Tree",
"category": "GitLens"
},
{
"command": "gitlens.diffLineWithWorking",
"title": "Diff with Working Tree (line)",
"category": "GitLens"
},
{
@@ -206,31 +284,42 @@
"explorer/context": [
{
"command": "gitlens.diffWithPrevious",
"group": "gitlens@1.1"
"when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled",
"group": "2_gitlens-file"
}
],
"editor/title": [
{
"when": "editorTextFocus",
"command": "gitlens.toggleBlame",
"group": "gitlens"
"when": "editorTextFocus && config.git.enabled",
"group": "2_gitlens-blame"
}
],
"editor/context": [
{
"when": "editorTextFocus",
"command": "gitlens.diffLineWithWorking",
"when": "editorTextFocus && config.gitlens.menus.lineDiff.enabled && config.git.enabled",
"group": "3_gitlens-line@1.0"
},
{
"command": "gitlens.diffLineWithPrevious",
"when": "editorTextFocus && config.gitlens.menus.lineDiff.enabled && config.git.enabled",
"group": "3_gitlens-line@1.1"
},
{
"command": "gitlens.diffWithWorking",
"group": "gitlens@1.0"
"when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled",
"group": "3_gitlens-file@1.0"
},
{
"when": "editorTextFocus",
"command": "gitlens.diffWithPrevious",
"group": "gitlens@1.1"
"when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled",
"group": "3_gitlens-file@1.1"
},
{
"when": "editorTextFocus",
"command": "gitlens.toggleBlame",
"group": "gitlens-blame@1.2"
"when": "editorTextFocus && config.git.enabled",
"group": "2_gitlens-blame"
}
]
},

View File

@@ -39,7 +39,7 @@ export default class BlameAnnotationController extends Disposable {
return this._annotationProvider !== undefined;
}
showBlameAnnotation(editor: TextEditor, sha?: string) {
showBlameAnnotation(editor: TextEditor, sha?: string): Promise<void> {
if (!editor || !editor.document || editor.document.isUntitled) {
this.clear();
return Promise.resolve();
@@ -53,7 +53,7 @@ export default class BlameAnnotationController extends Disposable {
return Promise.resolve();
}
toggleBlameAnnotation(editor: TextEditor, sha?: string) {
toggleBlameAnnotation(editor: TextEditor, sha?: string): Promise<void> {
if (!editor || !editor.document || editor.document.isUntitled || this._annotationProvider) {
this.clear();
return Promise.resolve();

View File

@@ -3,7 +3,7 @@ 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 GitProvider, { GitCommit, IGitBlame } from './gitProvider';
import * as moment from 'moment';
const blameDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
@@ -218,7 +218,7 @@ export class BlameAnnotationProvider extends Disposable {
});
}
private _getAuthor(commit: IGitCommit, max: number = 17, force: boolean = false) {
private _getAuthor(commit: GitCommit, max: number = 17, force: boolean = false) {
if (!force && !this._config.annotation.author) return '';
let author = commit.isUncommitted ? 'Uncommitted' : commit.author;
if (author.length > max) {
@@ -227,12 +227,12 @@ export class BlameAnnotationProvider extends Disposable {
return author;
}
private _getDate(commit: IGitCommit, force?: boolean) {
private _getDate(commit: GitCommit, force?: boolean) {
if (!force && !this._config.annotation.date) return '';
return moment(commit.date).format('MM/DD/YYYY');
}
private _getGutter(commit: IGitCommit) {
private _getGutter(commit: GitCommit) {
const author = this._getAuthor(commit);
const date = this._getDate(commit);
if (this._config.annotation.sha) {

View File

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

View File

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

View File

@@ -1,74 +1,60 @@
'use strict';
import { Iterables } from '../system';
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { commands, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants';
import BlameAnnotationController from '../blameAnnotationController';
import GitProvider from '../gitProvider';
import GitProvider, { GitCommit } from '../gitProvider';
import { Logger } from '../logger';
import * as moment from 'moment';
import * as path from 'path';
export default class DiffWithPreviousCommand extends EditorCommand {
constructor(private git: GitProvider, private annotationController: BlameAnnotationController) {
constructor(private git: GitProvider) {
super(Commands.DiffWithPrevious);
}
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 (!compareWithSha) {
return window.showInformationMessage(`Commit ${sha} has no previous commit`);
async execute(editor: TextEditor, edit: TextEditorEdit): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri, commit: GitCommit, range?: Range): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri: Uri, commit: GitCommit, line?: number): Promise<any>;
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, commit?: GitCommit, rangeOrLine?: Range | number): Promise<any> {
let line = editor.selection.active.line;
if (typeof rangeOrLine === 'number') {
line = rangeOrLine || line;
}
if (!commit || rangeOrLine instanceof Range) {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
}
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 => Logger.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;
const log = await this.git.getLogForFile(uri.fsPath, <Range>rangeOrLine);
if (!log) return window.showWarningMessage(`Unable to open diff. File is probably not under source control`);
// 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) {
Logger.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) {
Logger.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);
commit = commit ? Iterables.find(log.commits.values(), _ => _.sha === commit.sha) : Iterables.first(log.commits.values());
}
catch (ex) {
Logger.error('[GitLens.DiffWithPreviousCommand]', `getLogForFile(${uri.fsPath})`, ex);
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
}
}
if (!commit.previousSha) {
return window.showInformationMessage(`Commit ${commit.sha} (${commit.author}, ${moment(commit.date).fromNow()}) has no previous commit`);
}
try {
const values = await Promise.all([
this.git.getVersionedFile(commit.uri.fsPath, commit.repoPath, commit.sha),
this.git.getVersionedFile(commit.previousUri.fsPath, commit.repoPath, commit.previousSha)
]);
await commands.executeCommand(BuiltInCommands.Diff, Uri.file(values[1]), Uri.file(values[0]), `${path.basename(commit.previousUri.fsPath)} (${commit.previousSha}) ↔ ${path.basename(commit.uri.fsPath)} (${commit.sha})`);
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: line, at: 'center' });
}
catch (ex) {
Logger.error('[GitLens.DiffWithPreviousCommand]', 'getVersionedFile', ex);
return window.showErrorMessage(`Unable to open diff. See output channel for more details`);
}
}
}

View File

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

View File

@@ -1,5 +1,5 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri } from 'vscode';
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import BlameAnnotationController from '../blameAnnotationController';
import { EditorCommand } from './commands';
import { Commands } from '../constants';
@@ -23,10 +23,11 @@ export default class ShowBlameCommand extends EditorCommand {
try {
const blame = await this.git.getBlameForLine(uri.fsPath, editor.selection.active.line);
this.annotationController.showBlameAnnotation(editor, blame && blame.commit.sha);
return this.annotationController.showBlameAnnotation(editor, blame && blame.commit.sha);
}
catch (ex) {
Logger.error('[GitLens.ShowBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex);
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
}
}
}

View File

@@ -1,5 +1,5 @@
'use strict';
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri } from 'vscode';
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants';
import GitProvider from '../gitProvider';
@@ -22,10 +22,13 @@ export default class ShowBlameHistoryCommand extends EditorCommand {
try {
const locations = await this.git.getBlameLocations(uri.fsPath, range);
if (!locations) return window.showWarningMessage(`Unable to show blame history. File is probably not under source control`);
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations);
}
catch (ex) {
Logger.error('[GitLens.ShowBlameHistoryCommand]', 'getBlameLocations', ex);
return window.showErrorMessage(`Unable to show blame history. See output channel for more details`);
}
}
}

View File

@@ -1,5 +1,5 @@
'use strict';
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri } from 'vscode';
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { EditorCommand } from './commands';
import { BuiltInCommands, Commands } from '../constants';
import GitProvider from '../gitProvider';
@@ -10,7 +10,7 @@ export default class ShowHistoryCommand extends EditorCommand {
super(Commands.ShowHistory);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, position?: Position) {
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, position?: Position, sha?: string, line?: number) {
if (!(uri instanceof Uri)) {
if (!editor.document) return undefined;
uri = editor.document.uri;
@@ -20,11 +20,14 @@ export default class ShowHistoryCommand extends EditorCommand {
}
try {
const locations = await this.git.getLogLocations(uri.fsPath);
const locations = await this.git.getLogLocations(uri.fsPath, sha, line);
if (!locations) return window.showWarningMessage(`Unable to show history. File is probably not under source control`);
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations);
}
catch (ex) {
Logger.error('[GitLens.ShowHistoryCommand]', 'getLogLocations', ex);
return window.showErrorMessage(`Unable to show history. See output channel for more details`);
}
}
}

View File

@@ -1,5 +1,5 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri } from 'vscode';
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import BlameAnnotationController from '../blameAnnotationController';
import { EditorCommand } from './commands';
import { Commands } from '../constants';
@@ -23,10 +23,11 @@ export default class ToggleBlameCommand extends EditorCommand {
try {
const blame = await this.git.getBlameForLine(uri.fsPath, editor.selection.active.line);
this.annotationController.toggleBlameAnnotation(editor, blame && blame.commit.sha);
return this.annotationController.toggleBlameAnnotation(editor, blame && blame.commit.sha);
}
catch (ex) {
Logger.error('[GitLens.ToggleBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex);
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
}
}
}

View File

@@ -19,17 +19,19 @@ export interface IBlameConfig {
export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory';
export const CodeLensCommand = {
BlameAnnotate: Commands.ToggleBlame as CodeLensCommand,
BlameExplorer: Commands.ShowBlameHistory as CodeLensCommand,
ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand,
ShowHistory: Commands.ShowHistory as CodeLensCommand,
DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand,
GitViewHistory: 'git.viewFileHistory' as CodeLensCommand
};
export type CodeLensLocation = 'all' | 'document+containers' | 'document' | 'custom';
export type CodeLensLocation = 'all' | 'document+containers' | 'document' | 'custom' | 'none';
export const CodeLensLocation = {
All: 'all' as CodeLensLocation,
DocumentAndContainers: 'document+containers' as CodeLensLocation,
Document: 'document' as CodeLensLocation,
Custom: 'custom' as CodeLensLocation
Custom: 'custom' as CodeLensLocation,
None: 'none' as CodeLensLocation
};
export type CodeLensVisibility = 'auto' | 'ondemand' | 'off';
@@ -44,10 +46,17 @@ export interface ICodeLensConfig {
command: CodeLensCommand;
}
export interface ICodeLensLanguageLocation {
language: string;
location: CodeLensLocation;
customSymbols?: string[];
}
export interface ICodeLensesConfig {
visibility: CodeLensVisibility;
location: CodeLensLocation;
locationCustomSymbols: string[];
languageLocations: ICodeLensLanguageLocation[];
recentChange: ICodeLensConfig;
authors: ICodeLensConfig;
}
@@ -77,8 +86,8 @@ export interface IAdvancedConfig {
caching: {
enabled: boolean;
};
debug: boolean;
output: {
debug: boolean;
level: OutputLevel;
};
}

View File

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

View File

@@ -8,7 +8,9 @@ import GitBlameContentProvider from './gitBlameContentProvider';
import GitProvider, { Git } from './gitProvider';
import { WorkspaceState } from './constants';
import DiffWithPreviousCommand from './commands/diffWithPrevious';
import DiffLineWithPreviousCommand from './commands/diffLineWithPrevious';
import DiffWithWorkingCommand from './commands/diffWithWorking';
import DiffLineWithWorkingCommand from './commands/diffLineWithWorking';
import ShowBlameCommand from './commands/showBlame';
import ShowBlameHistoryCommand from './commands/showBlameHistory';
import ShowHistoryCommand from './commands/showHistory';
@@ -46,8 +48,10 @@ export function activate(context: ExtensionContext) {
const statusBarController = new BlameStatusBarController(context, git);
context.subscriptions.push(statusBarController);
context.subscriptions.push(new DiffWithWorkingCommand(git, annotationController));
context.subscriptions.push(new DiffWithPreviousCommand(git, annotationController));
context.subscriptions.push(new DiffWithWorkingCommand(git));
context.subscriptions.push(new DiffLineWithWorkingCommand(git));
context.subscriptions.push(new DiffWithPreviousCommand(git));
context.subscriptions.push(new DiffLineWithPreviousCommand(git));
context.subscriptions.push(new ShowBlameCommand(git, annotationController));
context.subscriptions.push(new ToggleBlameCommand(git, annotationController));
context.subscriptions.push(new ShowBlameHistoryCommand(git));

View File

@@ -1,5 +1,5 @@
'use strict';
import { GitBlameFormat, GitCommit, IGitAuthor, IGitBlame, IGitCommit, IGitCommitLine, IGitEnricher } from './../git';
import { GitBlameFormat, GitCommit, IGitAuthor, IGitBlame, IGitCommitLine, IGitEnricher } from './../git';
import * as moment from 'moment';
import * as path from 'path';
@@ -123,7 +123,7 @@ export class GitBlameParserEnricher implements IGitEnricher<IGitBlame> {
if (!entries) return null;
const authors: Map<string, IGitAuthor> = new Map();
const commits: Map<string, IGitCommit> = new Map();
const commits: Map<string, GitCommit> = new Map();
const lines: Array<IGitCommitLine> = [];
let repoPath: string;

View File

@@ -1,5 +1,5 @@
'use strict';
import { GitCommit, IGitAuthor, IGitCommit, IGitEnricher, IGitLog } from './../git';
import { GitCommit, IGitAuthor, IGitEnricher, IGitLog } from './../git';
import * as moment from 'moment';
import * as path from 'path';
@@ -35,6 +35,7 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> {
}
if (!entry) {
if (!/^[a-f0-9]{40}$/.test(lineParts[0])) continue;
entry = {
sha: lineParts[0].substring(0, 8)
};
@@ -66,7 +67,13 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> {
case 'filename':
position += 2;
lineParts = lines[position].split(' ');
entry.fileName = lineParts.join(' ');
if (lineParts.length === 1) {
entry.fileName = lineParts[0];
}
else {
entry.fileName = lineParts[3].substring(2);
position += 4;
}
entries.push(entry);
entry = null;
@@ -85,10 +92,11 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> {
if (!entries) return null;
const authors: Map<string, IGitAuthor> = new Map();
const commits: Map<string, IGitCommit> = new Map();
const commits: Map<string, GitCommit> = new Map();
let repoPath: string;
let relativeFileName: string;
let recentCommit: GitCommit;
for (let i = 0, len = entries.length; i < len; i++) {
const entry = entries[i];
@@ -118,6 +126,12 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> {
commits.set(entry.sha, commit);
}
if (recentCommit) {
recentCommit.previousSha = commit.sha;
recentCommit.previousFileName = commit.originalFileName || commit.fileName;
}
recentCommit = commit;
}
commits.forEach(c => authors.get(c.author).lineCount += c.lines.length);

View File

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

View File

@@ -1,5 +1,5 @@
'use strict';
import {Uri} from 'vscode';
import { Uri } from 'vscode';
import Git from './git';
import * as path from 'path';
@@ -10,13 +10,13 @@ export interface IGitEnricher<T> {
export interface IGitBlame {
repoPath: string;
authors: Map<string, IGitAuthor>;
commits: Map<string, IGitCommit>;
commits: Map<string, GitCommit>;
lines: IGitCommitLine[];
}
export interface IGitBlameLine {
author: IGitAuthor;
commit: IGitCommit;
commit: GitCommit;
line: IGitCommitLine;
}
@@ -26,7 +26,7 @@ export interface IGitBlameLines extends IGitBlame {
export interface IGitBlameCommitLines {
author: IGitAuthor;
commit: IGitCommit;
commit: GitCommit;
lines: IGitCommitLine[];
}
@@ -35,7 +35,7 @@ export interface IGitAuthor {
lineCount: number;
}
export interface IGitCommit {
interface IGitCommit {
repoPath: string;
sha: string;
fileName: string;
@@ -57,10 +57,20 @@ export class GitCommit implements IGitCommit {
originalFileName?: string;
previousSha?: string;
previousFileName?: string;
private _isUncommitted: boolean|undefined;
private _isUncommitted: boolean | undefined;
constructor(public repoPath: string, public sha: string, public fileName: string, public author: string, public date: Date, public message: string,
lines?: IGitCommitLine[], originalFileName?: string, previousSha?: string, previousFileName?: string) {
constructor(
public repoPath: string,
public sha: string,
public fileName: string,
public author: string,
public date: Date,
public message: string,
lines?: IGitCommitLine[],
originalFileName?: string,
previousSha?: string,
previousFileName?: string
) {
this.lines = lines || [];
this.originalFileName = originalFileName;
this.previousSha = previousSha;
@@ -94,5 +104,5 @@ export interface IGitCommitLine {
export interface IGitLog {
repoPath: string;
authors: Map<string, IGitAuthor>;
commits: Map<string, IGitCommit>;
commits: Map<string, GitCommit>;
}

View File

@@ -1,17 +1,17 @@
'use strict';
import { CancellationToken, CodeLens, CodeLensProvider, DocumentSelector, ExtensionContext, Range, TextDocument, Uri } from 'vscode';
import { Commands, DocumentSchemes } from './constants';
import GitProvider, { IGitCommit } from './gitProvider';
import GitProvider, { GitCommit } from './gitProvider';
import * as path from 'path';
export class GitDiffWithWorkingTreeCodeLens extends CodeLens {
constructor(git: GitProvider, public fileName: string, public commit: IGitCommit, range: Range) {
constructor(git: GitProvider, public fileName: string, public commit: GitCommit, range: Range) {
super(range);
}
}
export class GitDiffWithPreviousCodeLens extends CodeLens {
constructor(git: GitProvider, public fileName: string, public commit: IGitCommit, range: Range) {
constructor(git: GitProvider, public fileName: string, public commit: GitCommit, range: Range) {
super(range);
}
}
@@ -84,11 +84,7 @@ export default class GitBlameCodeLensProvider implements CodeLensProvider {
command: Commands.DiffWithPrevious,
arguments: [
Uri.file(lens.fileName),
lens.commit.repoPath,
lens.commit.sha,
lens.commit.uri,
lens.commit.previousSha,
lens.commit.previousUri,
lens.commit,
lens.range.start.line
]
};

View File

@@ -2,12 +2,12 @@
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 { CodeLensCommand, CodeLensLocation, IConfig, ICodeLensLanguageLocation } from './configuration';
import GitProvider, { GitCommit, IGitBlame, IGitBlameLines, IGitLog } from './gitProvider';
import * as moment from 'moment';
export class GitRecentChangeCodeLens extends CodeLens {
constructor(private git: GitProvider, public fileName: string, public symbolKind: SymbolKind, public blameRange: Range, range: Range) {
constructor(private git: GitProvider, public fileName: string, public symbolKind: SymbolKind, public blameRange: Range, public isFullRange: boolean, range: Range) {
super(range);
}
@@ -17,7 +17,7 @@ export class GitRecentChangeCodeLens extends CodeLens {
}
export class GitAuthorsCodeLens extends CodeLens {
constructor(private git: GitProvider, public fileName: string, public symbolKind: SymbolKind, public blameRange: Range, range: Range) {
constructor(private git: GitProvider, public fileName: string, public symbolKind: SymbolKind, public blameRange: Range, public isFullRange: boolean, range: Range) {
super(range);
}
@@ -29,45 +29,67 @@ export class GitAuthorsCodeLens extends CodeLens {
export default class GitCodeLensProvider implements CodeLensProvider {
static selector: DocumentSelector = { scheme: DocumentSchemes.File };
private _config: ICodeLensesConfig;
private _config: IConfig;
private _hasGitHistoryExtension: boolean;
constructor(context: ExtensionContext, private git: GitProvider) {
this._config = workspace.getConfiguration('gitlens').get<ICodeLensesConfig>('codeLens');
this._config = workspace.getConfiguration('').get<IConfig>('gitlens');
this._hasGitHistoryExtension = context.workspaceState.get(WorkspaceState.HasGitHistoryExtension, false);
}
provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable<CodeLens[]> {
const fileName = document.fileName;
const promise = Promise.all([this.git.getBlameForFile(fileName) as Promise<any>, (commands.executeCommand(BuiltInCommands.ExecuteDocumentSymbolProvider, document.uri) as Promise<any>)]);
return promise.then(values => {
const blame = values[0] as IGitBlame;
if (!blame || !blame.lines.length) return [];
const symbols = values[1] as SymbolInformation[];
const lenses: CodeLens[] = [];
symbols.forEach(sym => this._provideCodeLens(fileName, document, sym, lenses));
if (this._config.location !== CodeLensLocation.Custom || (this._config.locationCustomSymbols || []).find(_ => _.toLowerCase() === 'file')) {
// Check if we have a lens for the whole document -- if not add one
if (!lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0)) {
const blameRange = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
if (this._config.recentChange.enabled) {
lenses.push(new GitRecentChangeCodeLens(this.git, fileName, SymbolKind.File, blameRange, new Range(0, 0, 0, blameRange.start.character)));
}
if (this._config.authors.enabled) {
lenses.push(new GitAuthorsCodeLens(this.git, fileName, SymbolKind.File, blameRange, new Range(0, 1, 0, blameRange.start.character)));
}
}
}
return lenses;
});
}
private _isValidSymbol(kind: SymbolKind) {
switch (this._config.location) {
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
let languageLocations = this._config.codeLens.languageLocations.find(_ => _.language.toLowerCase() === document.languageId);
if (languageLocations == null) {
languageLocations = <ICodeLensLanguageLocation>{
language: null,
location: this._config.codeLens.location,
customSymbols: this._config.codeLens.locationCustomSymbols
};
}
const lenses: CodeLens[] = [];
if (languageLocations.location === CodeLensLocation.None) return lenses;
const fileName = document.fileName;
const blamePromise = this.git.getBlameForFile(fileName);
let blame: IGitBlame;
if (languageLocations.location === CodeLensLocation.Document) {
blame = await blamePromise;
if (!blame || !blame.lines.length) return lenses;
}
else {
const values = await Promise.all([
<Promise<any>>blamePromise,
<Promise<any>>commands.executeCommand(BuiltInCommands.ExecuteDocumentSymbolProvider, document.uri)
]);
blame = values[0] as IGitBlame;
if (!blame || !blame.lines.length) return lenses;
const symbols = values[1] as SymbolInformation[];
symbols.forEach(sym => this._provideCodeLens(fileName, document, sym, languageLocations, lenses));
}
if (languageLocations.location !== CodeLensLocation.Custom || (languageLocations.customSymbols || []).find(_ => _.toLowerCase() === 'file')) {
// Check if we have a lens for the whole document -- if not add one
if (!lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0)) {
const blameRange = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
if (this._config.codeLens.recentChange.enabled) {
lenses.push(new GitRecentChangeCodeLens(this.git, fileName, SymbolKind.File, blameRange, true, new Range(0, 0, 0, blameRange.start.character)));
}
if (this._config.codeLens.authors.enabled) {
lenses.push(new GitAuthorsCodeLens(this.git, fileName, SymbolKind.File, blameRange, true, new Range(0, 1, 0, blameRange.start.character)));
}
}
}
return lenses;
}
private _isValidSymbol(kind: SymbolKind, languageLocation: ICodeLensLanguageLocation) {
switch (languageLocation.location) {
case CodeLensLocation.All:
case CodeLensLocation.DocumentAndContainers:
switch (kind) {
@@ -83,20 +105,20 @@ export default class GitCodeLensProvider implements CodeLensProvider {
case SymbolKind.Function:
case SymbolKind.Property:
case SymbolKind.Enum:
return this._config.location === CodeLensLocation.All;
return languageLocation.location === CodeLensLocation.All;
default:
return false;
}
case CodeLensLocation.Document:
return false;
case CodeLensLocation.Custom:
return !!(this._config.locationCustomSymbols || []).find(_ => _.toLowerCase() === SymbolKind[kind].toLowerCase());
return !!(languageLocation.customSymbols || []).find(_ => _.toLowerCase() === SymbolKind[kind].toLowerCase());
}
return false;
}
private _provideCodeLens(fileName: string, document: TextDocument, symbol: SymbolInformation, lenses: CodeLens[]): void {
if (!this._isValidSymbol(symbol.kind)) return;
private _provideCodeLens(fileName: string, document: TextDocument, symbol: SymbolInformation, languageLocation: ICodeLensLanguageLocation, lenses: CodeLens[]): void {
if (!this._isValidSymbol(symbol.kind, languageLocation)) return;
const line = document.lineAt(symbol.location.range.start);
// Make sure there is only 1 lense per line
@@ -115,15 +137,15 @@ export default class GitCodeLensProvider implements CodeLensProvider {
startChar += Math.floor(symbol.name.length / 2);
}
if (this._config.recentChange.enabled) {
lenses.push(new GitRecentChangeCodeLens(this.git, fileName, symbol.kind, symbol.location.range, line.range.with(new Position(line.range.start.line, startChar))));
if (this._config.codeLens.recentChange.enabled) {
lenses.push(new GitRecentChangeCodeLens(this.git, fileName, symbol.kind, symbol.location.range, false, line.range.with(new Position(line.range.start.line, startChar))));
startChar++;
}
if (this._config.authors.enabled) {
if (this._config.codeLens.authors.enabled) {
// HACK for Omnisharp, since it doesn't return full ranges
let multiline = (symbol.location.range.end.line - symbol.location.range.start.line) > 1;
if (!multiline && fileName.endsWith('.cs')) {
if (!multiline && document.languageId === 'csharp') {
switch (symbol.kind) {
case SymbolKind.File:
case SymbolKind.Package:
@@ -141,7 +163,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
}
if (multiline) {
lenses.push(new GitAuthorsCodeLens(this.git, fileName, symbol.kind, symbol.location.range, line.range.with(new Position(line.range.start.line, startChar))));
lenses.push(new GitAuthorsCodeLens(this.git, fileName, symbol.kind, symbol.location.range, false, line.range.with(new Position(line.range.start.line, startChar))));
}
}
}
@@ -152,44 +174,51 @@ export default class GitCodeLensProvider implements CodeLensProvider {
return Promise.reject<CodeLens>(null);
}
_resolveGitRecentChangeCodeLens(lens: GitRecentChangeCodeLens, token: CancellationToken): Thenable<CodeLens> {
return lens.getBlame().then(blame => {
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);
case CodeLensCommand.BlameExplorer: return this._applyBlameExplorerCommand<GitRecentChangeCodeLens>(title, lens, blame);
case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand<GitRecentChangeCodeLens>(title, lens, blame);
case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame);
default: return lens;
}
});
async _resolveGitRecentChangeCodeLens(lens: GitRecentChangeCodeLens, token: CancellationToken): Promise<CodeLens> {
const blame = await lens.getBlame();
const recentCommit = Iterables.first(blame.commits.values());
let title = `${recentCommit.author}, ${moment(recentCommit.date).fromNow()}`;
if (this._config.advanced.debug) {
title += ` [${recentCommit.sha}, Symbol(${SymbolKind[lens.symbolKind]}), Lines(${lens.blameRange.start.line + 1}-${lens.blameRange.end.line + 1})]`;
}
switch (this._config.codeLens.recentChange.command) {
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand<GitRecentChangeCodeLens>(title, lens, blame);
case CodeLensCommand.ShowBlameHistory: return this._applyShowBlameHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame);
case CodeLensCommand.ShowHistory: return this._applyShowHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame, recentCommit);
case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand<GitRecentChangeCodeLens>(title, lens, blame, recentCommit);
case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand<GitRecentChangeCodeLens>(title, lens, blame);
default: return lens;
}
}
_resolveGitAuthorsCodeLens(lens: GitAuthorsCodeLens, token: CancellationToken): Thenable<CodeLens> {
return lens.getBlame().then(blame => {
const count = blame.authors.size;
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);
case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
default: return lens;
}
});
async _resolveGitAuthorsCodeLens(lens: GitAuthorsCodeLens, token: CancellationToken): Promise<CodeLens> {
const blame = await lens.getBlame();
const count = blame.authors.size;
const title = `${count} ${count > 1 ? 'authors' : 'author'} (${Iterables.first(blame.authors.values()).name}${count > 1 ? ' and others' : ''})`;
switch (this._config.codeLens.authors.command) {
case CodeLensCommand.BlameAnnotate: return this._applyBlameAnnotateCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.ShowBlameHistory: return this._applyShowBlameHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.ShowHistory: return this._applyShowHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand<GitAuthorsCodeLens>(title, lens, blame);
default: return lens;
}
}
_applyBlameAnnotateCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines, sha?: string) {
_applyBlameAnnotateCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines) {
lens.command = {
title: title,
command: Commands.ToggleBlame,
arguments: [Uri.file(lens.fileName), sha]
arguments: [Uri.file(lens.fileName)]
};
return lens;
}
_applyBlameExplorerCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines) {
_applyShowBlameHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines) {
lens.command = {
title: title,
command: Commands.ShowBlameHistory,
@@ -198,28 +227,42 @@ export default class GitCodeLensProvider implements CodeLensProvider {
return lens;
}
_applyDiffWithPreviousCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines) {
const line = blame.allLines[lens.range.start.line];
const commit = blame.commits.get(line.sha);
_applyShowHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines, commit?: GitCommit) {
let line = lens.range.start.line;
const blameLine = commit.lines.find(_ => _.line === line);
if (blameLine) {
line = blameLine.originalLine;
}
const position = lens.isFullRange ? new Position(1, 0) : lens.range.start;
lens.command = {
title: title,
command: Commands.ShowHistory,
arguments: [Uri.file(lens.fileName), position, commit.sha, line]
};
return lens;
}
async _applyDiffWithPreviousCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines, commit?: GitCommit): Promise<T> {
if (!commit) {
const blameLine = blame.allLines[lens.range.start.line];
commit = blame.commits.get(blameLine.sha);
}
lens.command = {
title: title,
command: Commands.DiffWithPrevious,
arguments: [
Uri.file(lens.fileName),
commit.repoPath,
commit.sha,
commit.uri,
commit.previousSha,
commit.previousUri,
line.line
commit,
lens.isFullRange ? undefined : lens.blameRange
]
};
return lens;
}
_applyGitHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines) {
if (!this._hasGitHistoryExtension) return this._applyBlameExplorerCommand(title, lens, blame);
if (!this._hasGitHistoryExtension) return this._applyShowHistoryCommand(title, lens, blame);
lens.command = {
title: title,

View File

@@ -4,7 +4,7 @@ import { Disposable, DocumentFilter, ExtensionContext, languages, Location, Posi
import { DocumentSchemes, WorkspaceState } from './constants';
import { CodeLensVisibility, IConfig } from './configuration';
import GitCodeLensProvider from './gitCodeLensProvider';
import Git, { GitBlameParserEnricher, GitBlameFormat, GitCommit, GitLogParserEnricher, IGitAuthor, IGitBlame, IGitBlameCommitLines, IGitBlameLine, IGitBlameLines, IGitCommit, IGitLog } from './git/git';
import Git, { GitBlameParserEnricher, GitBlameFormat, GitCommit, GitLogParserEnricher, IGitAuthor, IGitBlame, IGitBlameCommitLines, IGitBlameLine, IGitBlameLines, IGitLog } from './git/git';
import { Logger } from './logger';
import * as fs from 'fs';
import * as ignore from 'ignore';
@@ -96,7 +96,11 @@ export default class GitProvider extends Disposable {
private _onConfigure() {
const config = workspace.getConfiguration().get<IConfig>('gitlens');
if (!Objects.areEquivalent(config.codeLens, this._config && this._config.codeLens)) {
const codeLensChanged = !Objects.areEquivalent(config.codeLens, this._config && this._config.codeLens);
const advancedChanged = !Objects.areEquivalent(config.advanced, this._config && this._config.advanced);
if (codeLensChanged || advancedChanged) {
Logger.log('CodeLens config changed; resetting CodeLens provider');
this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose();
if (config.codeLens.visibility === CodeLensVisibility.Auto && (config.codeLens.recentChange.enabled || config.codeLens.authors.enabled)) {
this._codeLensProviderSelector = GitCodeLensProvider.selector;
@@ -106,7 +110,7 @@ export default class GitProvider extends Disposable {
}
}
if (!Objects.areEquivalent(config.advanced, this._config && this._config.advanced)) {
if (advancedChanged) {
if (config.advanced.caching.enabled) {
// TODO: Cache needs to be cleared on file changes -- createFileSystemWatcher or timeout?
this._cache = new Map();
@@ -163,28 +167,29 @@ export default class GitProvider extends Disposable {
return Git.repoPath(cwd);
}
async getBlameForFile(fileName: string): Promise<IGitBlame | null> {
getBlameForFile(fileName: string): Promise<IGitBlame | null> {
Logger.log(`getBlameForFile('${fileName}')`);
fileName = Git.normalizePath(fileName);
const cacheKey = this._getCacheEntryKey(fileName);
let entry: CacheEntry | undefined = undefined;
let cacheKey: string | undefined;
let entry: CacheEntry | undefined;
if (this.UseCaching) {
cacheKey = this._getCacheEntryKey(fileName);
entry = this._cache.get(cacheKey);
if (entry !== undefined && entry.blame !== undefined) return entry.blame.item;
if (entry === undefined) {
entry = new CacheEntry();
}
}
const ignore = await this._gitignore;
let blame: Promise<IGitBlame>;
if (ignore && !ignore.filter([fileName]).length) {
Logger.log(`Skipping blame; '${fileName}' is gitignored`);
blame = GitProvider.EmptyPromise;
}
else {
blame = Git.blame(GitProvider.BlameFormat, fileName)
const promise = this._gitignore.then(ignore => {
if (ignore && !ignore.filter([fileName]).length) {
Logger.log(`Skipping blame; '${fileName}' is gitignored`);
return <Promise<IGitBlame>>GitProvider.EmptyPromise;
}
return Git.blame(GitProvider.BlameFormat, fileName)
.then(data => new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName))
.catch(ex => {
// Trap and cache expected blame errors
@@ -199,24 +204,24 @@ export default class GitProvider extends Disposable {
};
this._cache.set(cacheKey, entry);
return GitProvider.EmptyPromise;
return <Promise<IGitBlame>>GitProvider.EmptyPromise;
}
return null;
});
}
});
if (this.UseCaching) {
Logger.log(`Add ${(blame === GitProvider.EmptyPromise ? 'empty promise to ' : '')}blame cache for '${cacheKey}'`);
Logger.log(`Add blame cache for '${cacheKey}'`);
entry.blame = <ICachedBlame>{
//date: new Date(),
item: blame
item: promise
};
this._cache.set(cacheKey, entry);
}
return blame;
return promise;
}
async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise<IGitBlameLine | null> {
@@ -274,11 +279,11 @@ export default class GitProvider extends Disposable {
lines.forEach(l => shas.add(l.sha));
const authors: Map<string, IGitAuthor> = new Map();
const commits: Map<string, IGitCommit> = new Map();
const commits: Map<string, GitCommit> = 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,
const commit: GitCommit = 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);
@@ -346,32 +351,35 @@ export default class GitProvider extends Disposable {
return locations;
}
async getLogForFile(fileName: string) {
Logger.log(`getLogForFile('${fileName}')`);
getLogForFile(fileName: string, range?: Range): Promise<IGitLog | null> {
Logger.log(`getLogForFile('${fileName}', ${range})`);
fileName = Git.normalizePath(fileName);
const cacheKey = this._getCacheEntryKey(fileName);
let entry: CacheEntry = undefined;
if (this.UseCaching) {
const useCaching = this.UseCaching && !range;
let cacheKey: string;
let entry: CacheEntry;
if (useCaching) {
cacheKey = this._getCacheEntryKey(fileName);
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) {
Logger.log(`Skipping log; '${fileName}' is gitignored`);
log = GitProvider.EmptyPromise;
}
else {
log = Git.log(fileName)
const promise = this._gitignore.then(ignore => {
if (ignore && !ignore.filter([fileName]).length) {
Logger.log(`Skipping log; '${fileName}' is gitignored`);
return <Promise<IGitLog>>GitProvider.EmptyPromise;
}
return (range ? Git.logRange(fileName, range.start.line + 1, range.end.line + 1) : Git.log(fileName))
.then(data => new GitLogParserEnricher().enrich(data, fileName))
.catch(ex => {
// Trap and cache expected blame errors
if (this.UseCaching) {
if (useCaching) {
const msg = ex && ex.toString();
Logger.log(`Replace log cache with empty promise for '${cacheKey}'`);
@@ -382,28 +390,28 @@ export default class GitProvider extends Disposable {
};
this._cache.set(cacheKey, entry);
return GitProvider.EmptyPromise;
return <Promise<IGitLog>>GitProvider.EmptyPromise;
}
return null;
});
}
});
if (this.UseCaching) {
Logger.log(`Add ${(log === GitProvider.EmptyPromise ? 'empty promise to ' : '')}log cache for '${cacheKey}'`);
if (useCaching) {
Logger.log(`Add log cache for '${cacheKey}'`);
entry.log = <ICachedLog>{
//date: new Date(),
item: log
item: promise
};
this._cache.set(cacheKey, entry);
}
return log;
return promise;
}
async getLogLocations(fileName: string): Promise<Location[] | null> {
Logger.log(`getLogLocations('${fileName}')`);
async getLogLocations(fileName: string, sha?: string, line?: number): Promise<Location[] | null> {
Logger.log(`getLogLocations('${fileName}', ${sha}, ${line})`);
const log = await this.getLogForFile(fileName);
if (!log) return null;
@@ -413,12 +421,14 @@ export default class GitProvider extends Disposable {
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
const decoration = `\u2937 ${c.author}, ${moment(c.date).format('MMMM Do, YYYY h:MMa')}`;
const uri = c.originalFileName
? GitProvider.toGitUri(c, i + 1, commitCount, c.originalFileName, decoration)
: GitProvider.toGitUri(c, i + 1, commitCount, undefined, decoration),
new Position(2, 0)));
: GitProvider.toGitUri(c, i + 1, commitCount, undefined, decoration);
locations.push(new Location(uri, new Position(0, 0)));
if (c.sha === sha) {
locations.push(new Location(uri, new Position(line + 1, 0)));
}
});
return locations;
@@ -486,26 +496,30 @@ export default class GitProvider extends Disposable {
return JSON.parse(uri.query) as T;
}
static toBlameUri(commit: IGitCommit, index: number, commitCount: number, range: Range, originalFileName?: string) {
static toBlameUri(commit: GitCommit, index: number, commitCount: number, range: Range, originalFileName?: string) {
return GitProvider._toGitUri(commit, DocumentSchemes.GitBlame, commitCount, GitProvider._toGitBlameUriData(commit, index, range, originalFileName));
}
static toGitUri(commit: IGitCommit, index: number, commitCount: number, originalFileName?: string, decoration?: string) {
static toGitUri(commit: GitCommit, 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) {
private static _toGitUri(commit: GitCommit, scheme: DocumentSchemes, commitCount: number, data: IGitUriData | IGitBlameUriData) {
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}`;
const uriPath = `${path.relative(commit.repoPath, data.fileName.slice(0, -ext.length))}/${commit.sha}${ext}`;
let message = commit.message;
if (message.length > 50) {
message = message.substring(0, 49) + '\u2026';
}
// 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: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)}`);
return Uri.parse(`${scheme}:${pad(data.index)} \u2022 ${message} \u2022 ${moment(commit.date).format('MMM D, YYYY hh:MMa')} \u2022 ${uriPath}?${JSON.stringify(data)}`);
}
private static _toGitUriData<T extends IGitUriData>(commit: IGitCommit, index: number, originalFileName?: string, decoration?: string): T {
private static _toGitUriData<T extends IGitUriData>(commit: GitCommit, 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) {
@@ -517,7 +531,7 @@ export default class GitProvider extends Disposable {
return data;
}
private static _toGitBlameUriData(commit: IGitCommit, index: number, range: Range, originalFileName?: string) {
private static _toGitBlameUriData(commit: GitCommit, index: number, range: Range, originalFileName?: string) {
const data = this._toGitUriData<IGitBlameUriData>(commit, index, originalFileName);
data.range = range;
return data;

View File

@@ -26,7 +26,7 @@ function onConfigurationChange() {
export class Logger {
static log(message?: any, ...params: any[]): void {
if (config.output.debug) {
if (config.debug) {
console.log('[GitLens]', message, ...params);
}
@@ -36,7 +36,7 @@ export class Logger {
}
static error(message?: any, ...params: any[]): void {
if (config.output.debug) {
if (config.debug) {
console.error('[GitLens]', message, ...params);
}
@@ -46,7 +46,7 @@ export class Logger {
}
static warn(message?: any, ...params: any[]): void {
if (config.output.debug) {
if (config.debug) {
console.warn('[GitLens]', message, ...params);
}

View File

@@ -43,6 +43,12 @@ export namespace Iterables {
return typeof source[Symbol.iterator] === 'function';
}
export function last<T>(source: Iterable<T>): T {
let item: T;
for (item of source) { /* noop */ }
return item;
}
export function* map<T, TMapped>(source: Iterable<T> | IterableIterator<T>, mapper: (item: T) => TMapped): Iterable<TMapped> {
for (const item of source) {
yield mapper(item);