From 4da21c3cc1198051d1e8b43216f47dd41ed7d8f7 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 3 Mar 2017 12:43:50 -0500 Subject: [PATCH] Adds blame and active line annotation support to git diff split view Adds command (compare, copy sha/message, etc) support to git diff split view Fixes #41 - Toggle Blame annotations on compare files page --- src/blameActiveLineController.ts | 19 ++++++++++++++----- src/blameAnnotationController.ts | 14 ++++++-------- src/blameAnnotationProvider.ts | 12 +++++------- src/commands/copyMessageToClipboard.ts | 2 +- src/commands/copyShaToClipboard.ts | 2 +- src/commands/diffLineWithPrevious.ts | 2 +- src/commands/diffLineWithWorking.ts | 2 +- src/commands/diffWithPrevious.ts | 2 +- src/commands/diffWithWorking.ts | 4 ++-- src/commands/showBlameHistory.ts | 2 +- src/commands/showFileHistory.ts | 2 +- src/commands/showQuickCommitDetails.ts | 2 +- src/commands/showQuickFileHistory.ts | 2 +- src/commands/showQuickRepoHistory.ts | 2 +- src/commands/showQuickRepoStatus.ts | 2 +- src/constants.ts | 5 +++-- src/git/gitUri.ts | 16 +++++++++++----- src/gitCodeLensProvider.ts | 2 +- src/gitContentProvider.ts | 2 +- src/gitProvider.ts | 12 +++++++++--- src/gitRevisionCodeLensProvider.ts | 2 +- src/quickPicks/gitQuickPicks.ts | 2 +- 22 files changed, 65 insertions(+), 47 deletions(-) diff --git a/src/blameActiveLineController.ts b/src/blameActiveLineController.ts index adf681b..b81352e 100644 --- a/src/blameActiveLineController.ts +++ b/src/blameActiveLineController.ts @@ -107,15 +107,24 @@ export class BlameActiveLineController extends Disposable { this._onActiveTextEditorChanged(window.activeTextEditor); } - private _onActiveTextEditorChanged(editor: TextEditor) { + private isEditorBlameable(editor: TextEditor): boolean { + if (!editor || !editor.document) return false; + + const scheme = editor.document.uri.scheme; + if (scheme !== DocumentSchemes.File && scheme !== DocumentSchemes.Git && scheme !== DocumentSchemes.GitLensGit) return false; + + if (editor.document.isUntitled && scheme !== DocumentSchemes.Git && scheme !== DocumentSchemes.GitLensGit) return false; + + return this.git.isEditorBlameable(editor); + } + + private async _onActiveTextEditorChanged(editor: TextEditor) { this._currentLine = -1; const previousEditor = this._editor; previousEditor && previousEditor.setDecorations(activeLineDecoration, []); - if (!editor || !editor.document || (editor.document.isUntitled && editor.document.uri.scheme !== DocumentSchemes.Git) || - (editor.document.uri.scheme !== DocumentSchemes.File && editor.document.uri.scheme !== DocumentSchemes.Git) || - (editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor))) { + if (!this.isEditorBlameable(editor)) { this.clear(editor); this._editor = undefined; @@ -125,7 +134,7 @@ export class BlameActiveLineController extends Disposable { this._blameable = editor && editor.document && !editor.document.isDirty; this._editor = editor; - this._uri = GitUri.fromUri(editor.document.uri, this.git); + this._uri = await GitUri.fromUri(editor.document.uri, this.git); const maxLines = this._config.advanced.caching.statusBar.maxLines; this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines); if (this._useCaching) { diff --git a/src/blameAnnotationController.ts b/src/blameAnnotationController.ts index 6e4d5ba..d12696c 100644 --- a/src/blameAnnotationController.ts +++ b/src/blameAnnotationController.ts @@ -5,7 +5,7 @@ import { BlameabilityChangeEvent, BlameabilityTracker } from './blameabilityTrac import { BlameAnnotationProvider } from './blameAnnotationProvider'; import { TextDocumentComparer, TextEditorComparer } from './comparers'; import { IBlameConfig } from './configuration'; -import { GitProvider } from './gitProvider'; +import { GitProvider, GitUri } from './gitProvider'; import { Logger } from './logger'; import { WhitespaceController } from './whitespaceController'; @@ -153,8 +153,7 @@ export class BlameAnnotationController extends Disposable { } async showBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise { - if (!editor || !editor.document) return false; - if (editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor)) return false; + if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false; const currentProvider = this._annotationProviders.get(editor.viewColumn || -1); if (currentProvider && TextEditorComparer.equals(currentProvider.editor, editor)) { @@ -162,7 +161,8 @@ export class BlameAnnotationController extends Disposable { return true; } - const provider = new BlameAnnotationProvider(this.context, this.git, this._whitespaceController, editor); + const gitUri = await GitUri.fromUri(editor.document.uri, this.git); + const provider = new BlameAnnotationProvider(this.context, this.git, this._whitespaceController, editor, gitUri); if (!await provider.supportsBlame()) return false; if (currentProvider) { @@ -191,15 +191,13 @@ export class BlameAnnotationController extends Disposable { } isAnnotating(editor: TextEditor): boolean { - if (!editor || !editor.document) return false; - if (editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor)) return false; + if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false; return !!this._annotationProviders.get(editor.viewColumn || -1); } async toggleBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise { - if (!editor || !editor.document) return false; - if (editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor)) return false; + if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false; let provider = this._annotationProviders.get(editor.viewColumn || -1); if (!provider) return this.showBlameAnnotation(editor, shaOrLine); diff --git a/src/blameAnnotationProvider.ts b/src/blameAnnotationProvider.ts index 46efa45..a84373b 100644 --- a/src/blameAnnotationProvider.ts +++ b/src/blameAnnotationProvider.ts @@ -15,15 +15,13 @@ export class BlameAnnotationProvider extends Disposable { private _blame: Promise; private _config: IBlameConfig; private _disposable: Disposable; - private _uri: GitUri; - constructor(context: ExtensionContext, private git: GitProvider, private whitespaceController: WhitespaceController | undefined, public editor: TextEditor) { + constructor(context: ExtensionContext, private git: GitProvider, private whitespaceController: WhitespaceController | undefined, public editor: TextEditor, private uri: GitUri) { super(() => this.dispose()); this.document = this.editor.document; - this._uri = GitUri.fromUri(this.document.uri, this.git); - this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath); + this._blame = this.git.getBlameForFile(this.uri.fsPath, this.uri.sha, this.uri.repoPath); this._config = workspace.getConfiguration('gitlens').get('blame'); @@ -104,7 +102,7 @@ export class BlameAnnotationProvider extends Disposable { private _setSelection(blame: IGitBlame, shaOrLine?: string | number) { if (!BlameDecorations.highlight) return; - const offset = this._uri.offset; + const offset = this.uri.offset; let sha: string; if (typeof shaOrLine === 'string') { @@ -134,7 +132,7 @@ export class BlameAnnotationProvider extends Disposable { } private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] { - const offset = this._uri.offset; + const offset = this.uri.offset; let count = 0; let lastSha: string; @@ -195,7 +193,7 @@ export class BlameAnnotationProvider extends Disposable { } private _getExpandedGutterDecorations(blame: IGitBlame, trailing: boolean = false): DecorationOptions[] { - const offset = this._uri.offset; + const offset = this.uri.offset; let width = 0; if (!trailing) { diff --git a/src/commands/copyMessageToClipboard.ts b/src/commands/copyMessageToClipboard.ts index 72922ff..c4eff97 100644 --- a/src/commands/copyMessageToClipboard.ts +++ b/src/commands/copyMessageToClipboard.ts @@ -28,7 +28,7 @@ export class CopyMessageToClipboardCommand extends ActiveEditorCommand { return undefined; } - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); if (!message) { if (!sha) { diff --git a/src/commands/copyShaToClipboard.ts b/src/commands/copyShaToClipboard.ts index 84adbbb..5d6dd03 100644 --- a/src/commands/copyShaToClipboard.ts +++ b/src/commands/copyShaToClipboard.ts @@ -28,7 +28,7 @@ export class CopyShaToClipboardCommand extends ActiveEditorCommand { return undefined; } - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); if (!sha) { if (editor && editor.document && editor.document.isDirty) return undefined; diff --git a/src/commands/diffLineWithPrevious.ts b/src/commands/diffLineWithPrevious.ts index af98889..b10a421 100644 --- a/src/commands/diffLineWithPrevious.ts +++ b/src/commands/diffLineWithPrevious.ts @@ -20,7 +20,7 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand { uri = editor.document.uri; } - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); line = line || (editor && editor.selection.active.line) || gitUri.offset; if (!commit || GitProvider.isUncommitted(commit.sha)) { diff --git a/src/commands/diffLineWithWorking.ts b/src/commands/diffLineWithWorking.ts index bbae66d..4b522b8 100644 --- a/src/commands/diffLineWithWorking.ts +++ b/src/commands/diffLineWithWorking.ts @@ -18,7 +18,7 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand { uri = editor.document.uri; } - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); line = line || (editor && editor.selection.active.line) || gitUri.offset; if (!commit || GitProvider.isUncommitted(commit.sha)) { diff --git a/src/commands/diffWithPrevious.ts b/src/commands/diffWithPrevious.ts index 7a206d9..6b2fd28 100644 --- a/src/commands/diffWithPrevious.ts +++ b/src/commands/diffWithPrevious.ts @@ -31,7 +31,7 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand { } if (!commit || rangeOrLine instanceof Range) { - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); try { if (!gitUri.sha) { diff --git a/src/commands/diffWithWorking.ts b/src/commands/diffWithWorking.ts index 92f3d79..cd22f3f 100644 --- a/src/commands/diffWithWorking.ts +++ b/src/commands/diffWithWorking.ts @@ -24,7 +24,7 @@ export class DiffWithWorkingCommand extends ActiveEditorCommand { line = line || (editor && editor.selection.active.line) || 0; if (!commit || GitProvider.isUncommitted(commit.sha)) { - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); try { const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath, undefined, gitUri.sha ? undefined : 1); @@ -38,7 +38,7 @@ export class DiffWithWorkingCommand extends ActiveEditorCommand { } } - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); try { const compare = await this.git.getVersionedFile(commit.uri.fsPath, commit.repoPath, commit.sha); diff --git a/src/commands/showBlameHistory.ts b/src/commands/showBlameHistory.ts index 2f5aff9..d9efbac 100644 --- a/src/commands/showBlameHistory.ts +++ b/src/commands/showBlameHistory.ts @@ -23,7 +23,7 @@ export class ShowBlameHistoryCommand extends EditorCommand { position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start; } - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); try { const locations = await this.git.getBlameLocations(gitUri.fsPath, range, gitUri.sha, gitUri.repoPath, sha, line); diff --git a/src/commands/showFileHistory.ts b/src/commands/showFileHistory.ts index b0857e5..585ea00 100644 --- a/src/commands/showFileHistory.ts +++ b/src/commands/showFileHistory.ts @@ -22,7 +22,7 @@ export class ShowFileHistoryCommand extends EditorCommand { position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start; } - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); try { const locations = await this.git.getLogLocations(gitUri.fsPath, gitUri.sha, gitUri.repoPath, sha, line); diff --git a/src/commands/showQuickCommitDetails.ts b/src/commands/showQuickCommitDetails.ts index d719e7c..99c10b4 100644 --- a/src/commands/showQuickCommitDetails.ts +++ b/src/commands/showQuickCommitDetails.ts @@ -18,7 +18,7 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { uri = editor.document.uri; } - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); let repoPath = gitUri.repoPath; diff --git a/src/commands/showQuickFileHistory.ts b/src/commands/showQuickFileHistory.ts index 7dedc1a..351f83a 100644 --- a/src/commands/showQuickFileHistory.ts +++ b/src/commands/showQuickFileHistory.ts @@ -20,7 +20,7 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCommand { return commands.executeCommand(Commands.ShowQuickRepoHistory); } - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); if (maxCount == null) { maxCount = this.git.config.advanced.maxQuickHistory; diff --git a/src/commands/showQuickRepoHistory.ts b/src/commands/showQuickRepoHistory.ts index ea739b1..5314b38 100644 --- a/src/commands/showQuickRepoHistory.ts +++ b/src/commands/showQuickRepoHistory.ts @@ -23,7 +23,7 @@ export class ShowQuickRepoHistoryCommand extends ActiveEditorCommand { try { let repoPath: string; if (uri instanceof Uri) { - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); repoPath = gitUri.repoPath; if (!repoPath) { diff --git a/src/commands/showQuickRepoStatus.ts b/src/commands/showQuickRepoStatus.ts index 5ec3d45..a96f23a 100644 --- a/src/commands/showQuickRepoStatus.ts +++ b/src/commands/showQuickRepoStatus.ts @@ -19,7 +19,7 @@ export class ShowQuickRepoStatusCommand extends ActiveEditorCommand { try { let repoPath: string; if (uri instanceof Uri) { - const gitUri = GitUri.fromUri(uri, this.git); + const gitUri = await GitUri.fromUri(uri, this.git); repoPath = gitUri.repoPath; if (!repoPath) { diff --git a/src/constants.ts b/src/constants.ts index 1f1fce3..7673173 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,10 +16,11 @@ export const BuiltInCommands = { ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands }; -export type DocumentSchemes = 'file' | 'gitlens-git'; +export type DocumentSchemes = 'file' | 'git' | 'gitlens-git'; export const DocumentSchemes = { File: 'file' as DocumentSchemes, - Git: 'gitlens-git' as DocumentSchemes + Git: 'git' as DocumentSchemes, + GitLensGit: 'gitlens-git' as DocumentSchemes }; export type WorkspaceState = 'repoPath'; diff --git a/src/git/gitUri.ts b/src/git/gitUri.ts index 7084ac2..d13008f 100644 --- a/src/git/gitUri.ts +++ b/src/git/gitUri.ts @@ -1,4 +1,5 @@ 'use strict'; +import { Iterables } from '../system'; import { Uri } from 'vscode'; import { DocumentSchemes } from '../constants'; import { Git, GitProvider } from '../gitProvider'; @@ -22,7 +23,7 @@ export class GitUri extends Uri { base._fragment = uri.fragment; this.offset = 0; - if (uri.scheme === DocumentSchemes.Git) { + if (uri.scheme === DocumentSchemes.GitLensGit) { const data = GitProvider.fromGitContentUri(uri); base._fsPath = data.originalFileName || data.fileName; @@ -52,12 +53,17 @@ export class GitUri extends Uri { return Uri.file(this.sha ? this.path : this.fsPath); } - static fromUri(uri: Uri, git?: GitProvider) { + static async fromUri(uri: Uri, git: GitProvider) { if (uri instanceof GitUri) return uri; - if (git) { - const gitUri = git.getGitUriForFile(uri.fsPath); - if (gitUri) return gitUri; + const gitUri = git.getGitUriForFile(uri.fsPath); + if (gitUri) return gitUri; + + // If this is a git uri, assume it is showing the most recent commit + if (uri.scheme === 'git' && uri.query === '~') { + const log = await git.getLogForFile(uri.fsPath, undefined, undefined, undefined, 1); + const commit = log && Iterables.first(log.commits.values()); + if (commit) return new GitUri(uri, commit); } return new GitUri(uri); diff --git a/src/gitCodeLensProvider.ts b/src/gitCodeLensProvider.ts index 8205d8d..eb4ac14 100644 --- a/src/gitCodeLensProvider.ts +++ b/src/gitCodeLensProvider.ts @@ -69,7 +69,7 @@ export default class GitCodeLensProvider implements CodeLensProvider { if (languageLocations.location === CodeLensLocation.None) return lenses; - const gitUri = GitUri.fromUri(document.uri, this.git); + const gitUri = await GitUri.fromUri(document.uri, this.git); const blamePromise = this.git.getBlameForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath); let blame: IGitBlame; diff --git a/src/gitContentProvider.ts b/src/gitContentProvider.ts index 7bf8eba..fe894b6 100644 --- a/src/gitContentProvider.ts +++ b/src/gitContentProvider.ts @@ -7,7 +7,7 @@ import * as path from 'path'; export class GitContentProvider implements TextDocumentContentProvider { - static scheme = DocumentSchemes.Git; + static scheme = DocumentSchemes.GitLensGit; constructor(context: ExtensionContext, private git: GitProvider) { } diff --git a/src/gitProvider.ts b/src/gitProvider.ts index 7418908..d77c097 100644 --- a/src/gitProvider.ts +++ b/src/gitProvider.ts @@ -612,6 +612,12 @@ export class GitProvider extends Disposable { return Git.getVersionedFileText(fileName, repoPath, sha); } + isEditorBlameable(editor: TextEditor): boolean { + return (editor.viewColumn !== undefined || + editor.document.uri.scheme === DocumentSchemes.Git || + this.hasGitUriForFile(editor)); + } + toggleCodeLens(editor: TextEditor) { if (this.config.codeLens.visibility !== CodeLensVisibility.OnDemand || (!this.config.codeLens.recentChange.enabled && !this.config.codeLens.authors.enabled)) return; @@ -632,7 +638,7 @@ export class GitProvider extends Disposable { } static fromGitContentUri(uri: Uri): IGitUriData { - if (uri.scheme !== DocumentSchemes.Git) throw new Error(`fromGitUri(uri=${uri}) invalid scheme`); + if (uri.scheme !== DocumentSchemes.GitLensGit) throw new Error(`fromGitUri(uri=${uri}) invalid scheme`); return GitProvider._fromGitContentUri(uri); } @@ -658,11 +664,11 @@ export class GitProvider extends Disposable { } const extension = path.extname(fileName); - return Uri.parse(`${DocumentSchemes.Git}:${path.basename(fileName, extension)}:${data.sha}${extension}?${JSON.stringify(data)}`); + return Uri.parse(`${DocumentSchemes.GitLensGit}:${path.basename(fileName, extension)}:${data.sha}${extension}?${JSON.stringify(data)}`); } static toReferenceGitContentUri(commit: GitCommit, index: number, commitCount: number, originalFileName?: string, decoration?: string): Uri { - return GitProvider._toReferenceGitContentUri(commit, DocumentSchemes.Git, commitCount, GitProvider._toGitUriData(commit, index, originalFileName, decoration)); + return GitProvider._toReferenceGitContentUri(commit, DocumentSchemes.GitLensGit, commitCount, GitProvider._toGitUriData(commit, index, originalFileName, decoration)); } private static _toReferenceGitContentUri(commit: GitCommit, scheme: DocumentSchemes, commitCount: number, data: IGitUriData) { diff --git a/src/gitRevisionCodeLensProvider.ts b/src/gitRevisionCodeLensProvider.ts index 23bcb5e..8e577bd 100644 --- a/src/gitRevisionCodeLensProvider.ts +++ b/src/gitRevisionCodeLensProvider.ts @@ -21,7 +21,7 @@ export class GitDiffWithPreviousCodeLens extends CodeLens { export class GitRevisionCodeLensProvider implements CodeLensProvider { - static selector: DocumentSelector = { scheme: DocumentSchemes.Git }; + static selector: DocumentSelector = { scheme: DocumentSchemes.GitLensGit }; constructor(context: ExtensionContext, private git: GitProvider) { } diff --git a/src/quickPicks/gitQuickPicks.ts b/src/quickPicks/gitQuickPicks.ts index 2a23d60..78c71c9 100644 --- a/src/quickPicks/gitQuickPicks.ts +++ b/src/quickPicks/gitQuickPicks.ts @@ -39,7 +39,7 @@ export class CommitWithFileStatusQuickPickItem extends OpenFileCommandQuickPickI }); this.fileName = fileName; - this.gitUri = GitUri.fromUri(Uri.file(path.resolve(commit.repoPath, fileName))); + this.gitUri = new GitUri(Uri.file(path.resolve(commit.repoPath, fileName))); this.sha = commit.sha; this.status = status; }