From 5e62d5e40b07cd2ef9ee831bdc68fce0143befe6 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 3 Oct 2017 17:19:31 -0400 Subject: [PATCH] Adds new command bar to hover annotations Adds new open file revision command with annotation support --- src/annotations/annotations.ts | 42 ++++++++++---- src/annotations/blameAnnotationProvider.ts | 7 ++- src/annotations/recentChangesAnnotationProvider.ts | 2 +- src/commands.ts | 1 + src/commands/common.ts | 2 + src/commands/diffWith.ts | 5 +- src/commands/openFileRevision.ts | 66 ++++++++++++++++++++++ src/constants.ts | 13 ++++- src/currentLineController.ts | 7 ++- src/extension.ts | 10 ++-- 10 files changed, 134 insertions(+), 21 deletions(-) create mode 100644 src/commands/openFileRevision.ts diff --git a/src/annotations/annotations.ts b/src/annotations/annotations.ts index 267ce67..2a385e3 100644 --- a/src/annotations/annotations.ts +++ b/src/annotations/annotations.ts @@ -1,6 +1,7 @@ import { Dates, Objects, Strings } from '../system'; -import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode'; -import { DiffWithCommand, OpenCommitInRemoteCommand, ShowQuickCommitDetailsCommand } from '../commands'; +import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions, window } from 'vscode'; +import { FileAnnotationType } from './annotationController'; +import { DiffWithCommand, OpenCommitInRemoteCommand, OpenFileRevisionCommand, ShowQuickCommitDetailsCommand, ShowQuickCommitFileDetailsCommand } from '../commands'; import { IThemeConfig, themeDefaults } from '../configuration'; import { GlyphChars } from '../constants'; import { CommitFormatter, GitCommit, GitDiffChunkLine, GitService, GitUri, ICommitFormatOptions } from '../gitService'; @@ -47,18 +48,39 @@ export class Annotations { return '#793738'; } - static getHoverMessage(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): MarkdownString { + private static getHoverCommandBar(commit: GitCommit, hasRemotes: boolean, annotationType?: FileAnnotationType) { + let commandBar = `[\`${GlyphChars.DoubleArrowLeft}\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") `; + + if (commit.previousSha !== undefined) { + if (annotationType === FileAnnotationType.RecentChanges) { + annotationType = FileAnnotationType.Gutter; + } + + const uri = GitService.toGitContentUri(commit.previousSha, commit.previousUri.fsPath, commit.repoPath); + const line = window.activeTextEditor!.selection.active.line; + + commandBar += `[\`${GlyphChars.SquareWithTopShadow}\`](${OpenFileRevisionCommand.getMarkdownCommandArgs(uri, annotationType || FileAnnotationType.Gutter, line)} "Blame Previous Revision") `; + } + + if (hasRemotes) { + commandBar += `[\`${GlyphChars.ArrowUpRight}\`](${OpenCommitInRemoteCommand.getMarkdownCommandArgs(commit.sha)} "Open in Remote") `; + } + + commandBar += `[\`${GlyphChars.MiddleEllipsis}\`](${ShowQuickCommitFileDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show More Actions")`; + + return commandBar; + } + + static getHoverMessage(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean, annotationType?: FileAnnotationType): MarkdownString { if (dateFormat === null) { dateFormat = 'MMMM Do, YYYY h:MMa'; } let message = ''; - let openInRemoteCommand = ''; + let commandBar = ''; let showCommitDetailsCommand = ''; if (!commit.isUncommitted) { - if (hasRemotes) { - openInRemoteCommand = `${' '.repeat(2)} [\`${GlyphChars.ArrowUpRight}\`](${OpenCommitInRemoteCommand.getMarkdownCommandArgs(commit.sha)} "Open in Remote") `; - } + commandBar = `\n\n${this.getHoverCommandBar(commit, hasRemotes, annotationType)}`; showCommitDetailsCommand = `[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details")`; message = commit.message @@ -74,7 +96,7 @@ export class Annotations { showCommitDetailsCommand = `\`${commit.shortSha}\``; } - const markdown = new MarkdownString(`${showCommitDetailsCommand}   __${commit.author}__, ${commit.fromNow()}   _(${commit.formatDate(dateFormat)})_ ${openInRemoteCommand}${message}`); + const markdown = new MarkdownString(`${showCommitDetailsCommand}   __${commit.author}__, ${commit.fromNow()}   _(${commit.formatDate(dateFormat)})_ ${message}${commandBar}`); markdown.isTrusted = true; return markdown; } @@ -107,8 +129,8 @@ export class Annotations { } as DecorationOptions; } - static detailsHover(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): DecorationOptions { - const message = this.getHoverMessage(commit, dateFormat, hasRemotes); + static detailsHover(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean, annotationType?: FileAnnotationType): DecorationOptions { + const message = this.getHoverMessage(commit, dateFormat, hasRemotes, annotationType); return { hoverMessage: message } as DecorationOptions; diff --git a/src/annotations/blameAnnotationProvider.ts b/src/annotations/blameAnnotationProvider.ts index 434f743..e084eb4 100644 --- a/src/annotations/blameAnnotationProvider.ts +++ b/src/annotations/blameAnnotationProvider.ts @@ -110,9 +110,14 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase let logCommit: GitCommit | undefined = undefined; if (!commit.isUncommitted) { logCommit = await this.git.getLogCommit(commit.repoPath, commit.uri.fsPath, commit.sha); + if (logCommit !== undefined) { + // Preserve the previous commit from the blame commit + logCommit.previousFileName = commit.previousFileName; + logCommit.previousSha = commit.previousSha; + } } - const message = Annotations.getHoverMessage(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes(commit.repoPath)); + const message = Annotations.getHoverMessage(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes(commit.repoPath), this._config.blame.file.annotationType); return new Hover(message, document.validateRange(new Range(position.line, 0, position.line, endOfLineIndex))); } } \ No newline at end of file diff --git a/src/annotations/recentChangesAnnotationProvider.ts b/src/annotations/recentChangesAnnotationProvider.ts index 92b50fb..78a711c 100644 --- a/src/annotations/recentChangesAnnotationProvider.ts +++ b/src/annotations/recentChangesAnnotationProvider.ts @@ -41,7 +41,7 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase { if (cfg.hover.details) { decorators.push({ - hoverMessage: Annotations.getHoverMessage(commit, dateFormat, this.git.hasRemotes(commit.repoPath)), + hoverMessage: Annotations.getHoverMessage(commit, dateFormat, this.git.hasRemotes(commit.repoPath), this._config.blame.file.annotationType), range: range } as DecorationOptions); } diff --git a/src/commands.ts b/src/commands.ts index a5b573a..a311184 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -20,6 +20,7 @@ export * from './commands/openBranchesInRemote'; export * from './commands/openBranchInRemote'; export * from './commands/openCommitInRemote'; export * from './commands/openFileInRemote'; +export * from './commands/openFileRevision'; export * from './commands/openInRemote'; export * from './commands/openRepoInRemote'; export * from './commands/resetSuppressedWarnings'; diff --git a/src/commands/common.ts b/src/commands/common.ts index 2871c06..e7e1f70 100644 --- a/src/commands/common.ts +++ b/src/commands/common.ts @@ -25,6 +25,7 @@ export type Commands = 'gitlens.openBranchInRemote' | 'gitlens.openCommitInRemote' | 'gitlens.openFileInRemote' | + 'gitlens.openFileRevision' | 'gitlens.openInRemote' | 'gitlens.openRepoInRemote' | 'gitlens.resetSuppressedWarnings' | @@ -68,6 +69,7 @@ export const Commands = { OpenBranchInRemote: 'gitlens.openBranchInRemote' as Commands, OpenCommitInRemote: 'gitlens.openCommitInRemote' as Commands, OpenFileInRemote: 'gitlens.openFileInRemote' as Commands, + OpenFileRevision: 'gitlens.openFileRevision' as Commands, OpenInRemote: 'gitlens.openInRemote' as Commands, OpenRepoInRemote: 'gitlens.openRepoInRemote' as Commands, ResetSuppressedWarnings: 'gitlens.resetSuppressedWarnings' as Commands, diff --git a/src/commands/diffWith.ts b/src/commands/diffWith.ts index f57ad5c..4ac1813 100644 --- a/src/commands/diffWith.ts +++ b/src/commands/diffWith.ts @@ -26,7 +26,7 @@ export class DiffWithCommand extends ActiveEditorCommand { static getMarkdownCommandArgs(args: DiffWithCommandArgs): string; static getMarkdownCommandArgs(commit1: GitCommit, commit2: GitCommit): string; static getMarkdownCommandArgs(argsOrCommit1: DiffWithCommandArgs | GitCommit, commit2?: GitCommit): string { - let args = argsOrCommit1; + let args: DiffWithCommandArgs | GitCommit; if (argsOrCommit1 instanceof GitCommit) { const commit1 = argsOrCommit1; @@ -72,6 +72,9 @@ export class DiffWithCommand extends ActiveEditorCommand { }; } } + else { + args = argsOrCommit1; + } return super.getMarkdownCommandArgsCore(Commands.DiffWith, args); } diff --git a/src/commands/openFileRevision.ts b/src/commands/openFileRevision.ts new file mode 100644 index 0000000..706cfcc --- /dev/null +++ b/src/commands/openFileRevision.ts @@ -0,0 +1,66 @@ +'use strict'; +import { Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; +import { AnnotationController, FileAnnotationType } from '../annotations/annotationController'; +import { ActiveEditorCommand, Commands, getCommandUri, openEditor } from './common'; +import { Logger } from '../logger'; + +export interface OpenFileRevisionCommandArgs { + uri?: Uri; + line?: number; + showOptions?: TextDocumentShowOptions; + annotationType?: FileAnnotationType; +} + +export class OpenFileRevisionCommand extends ActiveEditorCommand { + + static getMarkdownCommandArgs(args: OpenFileRevisionCommandArgs): string; + static getMarkdownCommandArgs(uri: Uri, annotationType?: FileAnnotationType, line?: number): string; + static getMarkdownCommandArgs(argsOrUri: OpenFileRevisionCommandArgs | Uri, annotationType?: FileAnnotationType, line?: number): string { + let args: OpenFileRevisionCommandArgs | Uri; + if (argsOrUri instanceof Uri) { + const uri = argsOrUri; + + args = { + uri: uri, + line: line, + annotationType: annotationType + }; + } + else { + args = argsOrUri; + } + + return super.getMarkdownCommandArgsCore(Commands.OpenFileRevision, args); + } + + constructor(private annotationController: AnnotationController) { + super(Commands.OpenFileRevision); + } + + async execute(editor: TextEditor, uri?: Uri, args: OpenFileRevisionCommandArgs = {}) { + uri = getCommandUri(uri, editor); + + args = { ...args }; + if (args.line === undefined) { + args.line = editor === undefined ? 0 : editor.selection.active.line; + } + + try { + if (args.line !== undefined && args.line !== 0) { + if (args.showOptions === undefined) { + args.showOptions = {}; + } + args.showOptions.selection = new Range(args.line, 0, args.line, 0); + } + + const e = await openEditor(args.uri!, args.showOptions); + if (args.annotationType === undefined) return e; + + return this.annotationController.showAnnotations(e!, args.annotationType, args.line); + } + catch (ex) { + Logger.error(ex, 'OpenFileRevisionCommand'); + return window.showErrorMessage(`Unable to open in file revision. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index 014e103..7af4b1b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -72,7 +72,8 @@ export const DocumentSchemes = { GitLensGit: 'gitlens-git' as DocumentSchemes }; -export type GlyphChars = '\u21a9' | +export type GlyphChars = + '\u21a9' | '\u2193' | '\u2937' | '\u2190' | @@ -85,9 +86,14 @@ export type GlyphChars = '\u21a9' | '\u2713' | '\u2014' | '\u2022' | + '\u226A' | + '\u22D8' | '\u2026' | + '\u22EF' | '\u270E' | '\u00a0' | + '\u274F' | + '\u2750' | '\u200b'; export const GlyphChars = { ArrowBack: '\u21a9' as GlyphChars, @@ -103,9 +109,14 @@ export const GlyphChars = { Check: '\u2713' as GlyphChars, Dash: '\u2014' as GlyphChars, Dot: '\u2022' as GlyphChars, + DoubleArrowLeft: '\u226A' as GlyphChars, + DoubleArrowRight: '\u22D8' as GlyphChars, Ellipsis: '\u2026' as GlyphChars, + MiddleEllipsis: '\u22EF' as GlyphChars, Pensil: '\u270E' as GlyphChars, Space: '\u00a0' as GlyphChars, + SquareWithBottomShadow: '\u274F' as GlyphChars, + SquareWithTopShadow: '\u2750' as GlyphChars, ZeroWidthSpace: '\u200b' as GlyphChars }; diff --git a/src/currentLineController.ts b/src/currentLineController.ts index 8c21ecc..c49c98c 100644 --- a/src/currentLineController.ts +++ b/src/currentLineController.ts @@ -376,9 +376,14 @@ export class CurrentLineController extends Disposable { let logCommit: GitCommit | undefined = undefined; if (!commit.isUncommitted) { logCommit = await this.git.getLogCommit(this._uri.repoPath, this._uri.fsPath, commit.sha); + if (logCommit !== undefined) { + // Preserve the previous commit from the blame commit + logCommit.previousFileName = commit.previousFileName; + logCommit.previousSha = commit.previousSha; + } } - const decoration = Annotations.detailsHover(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes((logCommit || commit).repoPath)); + const decoration = Annotations.detailsHover(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes((logCommit || commit).repoPath), this._config.blame.file.annotationType); decoration.range = range; decorationOptions.push(decoration); diff --git a/src/extension.ts b/src/extension.ts index 94be411..47303ac 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,7 +4,7 @@ import { commands, ExtensionContext, extensions, languages, window, workspace } import { AnnotationController } from './annotations/annotationController'; import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands'; import { ExternalDiffCommand } from './commands'; -import { OpenBranchesInRemoteCommand, OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands'; +import { OpenBranchesInRemoteCommand, OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenFileRevisionCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands'; import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands'; import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithRevisionCommand, DiffWithWorkingCommand } from './commands'; import { ResetSuppressedWarningsCommand } from './commands'; @@ -80,10 +80,6 @@ export async function activate(context: ExtensionContext) { const gitContextTracker = new GitContextTracker(git); context.subscriptions.push(gitContextTracker); - context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, git))); - - context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider(context, git))); - const annotationController = new AnnotationController(context, git, gitContextTracker); context.subscriptions.push(annotationController); @@ -95,8 +91,9 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new Keyboard()); + context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, git))); + context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider(context, git))); context.subscriptions.push(window.registerTreeDataProvider('gitlens.gitExplorer', new GitExplorer(context, git))); - context.subscriptions.push(commands.registerTextEditorCommand('gitlens.computingFileAnnotations', () => { })); context.subscriptions.push(new CloseUnchangedFilesCommand(git)); @@ -117,6 +114,7 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new OpenBranchInRemoteCommand(git)); context.subscriptions.push(new OpenCommitInRemoteCommand(git)); context.subscriptions.push(new OpenFileInRemoteCommand(git)); + context.subscriptions.push(new OpenFileRevisionCommand(annotationController)); context.subscriptions.push(new OpenInRemoteCommand()); context.subscriptions.push(new OpenRepoInRemoteCommand(git)); context.subscriptions.push(new ClearFileAnnotationsCommand(annotationController));