mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-01-14 01:25:43 -05:00
Adds error messages for failed operations
Adds showHistory command support to CodeLens Fixes and improve the showHistory explorer Refactoring
This commit is contained in:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -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
|
||||
|
||||
16
README.md
16
README.md
@@ -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)
|
||||
129
package.json
129
package.json
@@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
52
src/commands/diffLineWithPrevious.ts
Normal file
52
src/commands/diffLineWithPrevious.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
42
src/commands/diffLineWithWorking.ts
Normal file
42
src/commands/diffLineWithWorking.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user