From a2903ce4a9b012f53a4afbd1bca1d667be7a7479 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 13 Jun 2017 00:12:31 -0400 Subject: [PATCH] Refactors formatters Moves annotation messages from formatter to annotations Moves icons into dark/light folders --- .../{git-icon-dark.svg => dark/git-icon.svg} | 0 .../highlight-gutter.svg} | 0 .../git-icon.svg} | 0 .../highlight-gutter.svg} | 0 package.json | 4 +- src/annotations/annotationController.ts | 8 +- src/annotations/annotations.ts | 47 ++++- .../recentChangesAnnotationProvider.ts | 8 +- src/git/formatters/commit.ts | 160 ++---------------- src/git/formatters/formatter.ts | 119 +++++++++++++ 10 files changed, 181 insertions(+), 165 deletions(-) rename images/{git-icon-dark.svg => dark/git-icon.svg} (100%) rename images/{blame-dark.svg => dark/highlight-gutter.svg} (100%) rename images/{git-icon-light.svg => light/git-icon.svg} (100%) rename images/{blame-light.svg => light/highlight-gutter.svg} (100%) create mode 100644 src/git/formatters/formatter.ts diff --git a/images/git-icon-dark.svg b/images/dark/git-icon.svg similarity index 100% rename from images/git-icon-dark.svg rename to images/dark/git-icon.svg diff --git a/images/blame-dark.svg b/images/dark/highlight-gutter.svg similarity index 100% rename from images/blame-dark.svg rename to images/dark/highlight-gutter.svg diff --git a/images/git-icon-light.svg b/images/light/git-icon.svg similarity index 100% rename from images/git-icon-light.svg rename to images/light/git-icon.svg diff --git a/images/blame-light.svg b/images/light/highlight-gutter.svg similarity index 100% rename from images/blame-light.svg rename to images/light/highlight-gutter.svg diff --git a/package.json b/package.json index 830dfbd..c59d449 100644 --- a/package.json +++ b/package.json @@ -794,8 +794,8 @@ "title": "Toggle File Blame Annotations", "category": "GitLens", "icon": { - "dark": "images/git-icon-dark.svg", - "light": "images/git-icon-light.svg" + "dark": "images/dark/git-icon.svg", + "light": "images/light/git-icon.svg" } }, { diff --git a/src/annotations/annotationController.ts b/src/annotations/annotationController.ts index d218d03..18ebcc2 100644 --- a/src/annotations/annotationController.ts +++ b/src/annotations/annotationController.ts @@ -113,7 +113,7 @@ export class AnnotationController extends Disposable { ? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor : undefined, gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter) - ? this.context.asAbsolutePath('images/blame-dark.svg') + ? this.context.asAbsolutePath('images/dark/highlight-gutter.svg') : undefined, overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler) ? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor @@ -124,7 +124,7 @@ export class AnnotationController extends Disposable { ? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor : undefined, gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter) - ? this.context.asAbsolutePath('images/blame-light.svg') + ? this.context.asAbsolutePath('images/light/highlight-gutter.svg') : undefined, overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler) ? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor @@ -147,7 +147,7 @@ export class AnnotationController extends Disposable { ? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor : undefined, gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter) - ? this.context.asAbsolutePath('images/blame-dark.svg') + ? this.context.asAbsolutePath('images/dark/highlight-gutter.svg') : undefined, overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler) ? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor @@ -158,7 +158,7 @@ export class AnnotationController extends Disposable { ? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor : undefined, gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter) - ? this.context.asAbsolutePath('images/blame-light.svg') + ? this.context.asAbsolutePath('images/light/highlight-gutter.svg') : undefined, overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler) ? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor diff --git a/src/annotations/annotations.ts b/src/annotations/annotations.ts index 5dfd82d..75a4440 100644 --- a/src/annotations/annotations.ts +++ b/src/annotations/annotations.ts @@ -1,6 +1,6 @@ import { DecorationInstanceRenderOptions, DecorationOptions, ThemableDecorationRenderOptions } from 'vscode'; import { IThemeConfig, themeDefaults } from '../configuration'; -import { CommitFormatter, GitCommit, GitService, GitUri, ICommitFormatOptions } from '../gitService'; +import { CommitFormatter, GitCommit, GitDiffLine, GitService, GitUri, ICommitFormatOptions } from '../gitService'; import * as moment from 'moment'; interface IHeatmapConfig { @@ -20,6 +20,8 @@ interface IRenderOptions { } export const endOfLineIndex = 1000000; +const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g; +// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___'; export class Annotations { @@ -43,15 +45,50 @@ export class Annotations { return '#793738'; } + static getHoverMessage(commit: GitCommit, dateFormat: string | null): string | string[] { + if (dateFormat === null) { + dateFormat = 'MMMM Do, YYYY h:MMa'; + } + + let message = ''; + if (!commit.isUncommitted) { + message = commit.message + // Escape markdown + .replace(escapeMarkdownRegEx, '\\$&') + // Escape markdown header (since the above regex won't match it) + .replace(/^===/gm, '\u200b===') + // Keep under the same block-quote + .replace(/\n/g, ' \n'); + message = `\n\n> ${message}`; + } + return `\`${commit.shortSha}\`   __${commit.author}__, ${moment(commit.date).fromNow()}   _(${moment(commit.date).format(dateFormat)})_${message}`; + } + + static getHoverDiffMessage(commit: GitCommit, previous: GitDiffLine | undefined, current: GitDiffLine | undefined): string | undefined { + if (previous === undefined && current === undefined) return undefined; + + const codeDiff = this._getCodeDiff(previous, current); + return commit.isUncommitted + ? `\`Changes\`   \u2014   _uncommitted_\n${codeDiff}` + : `\`Changes\`   \u2014   \`${commit.previousShortSha}\` \u2194 \`${commit.shortSha}\`\n${codeDiff}`; + } + + private static _getCodeDiff(previous: GitDiffLine | undefined, current: GitDiffLine | undefined): string { + return `\`\`\` +- ${previous === undefined ? '' : previous.line.trim()} ++ ${current === undefined ? '' : current.line.trim()} +\`\`\``; + } + static async changesHover(commit: GitCommit, line: number, uri: GitUri, git: GitService): Promise { let message: string | undefined = undefined; if (commit.isUncommitted) { const [previous, current] = await git.getDiffForLine(uri, line + uri.offset); - message = CommitFormatter.toHoverDiff(commit, previous, current); + message = this.getHoverDiffMessage(commit, previous, current); } else if (commit.previousSha !== undefined) { const [previous, current] = await git.getDiffForLine(uri, line + uri.offset, commit.previousSha); - message = CommitFormatter.toHoverDiff(commit, previous, current); + message = this.getHoverDiffMessage(commit, previous, current); } return { @@ -60,7 +97,7 @@ export class Annotations { } static detailsHover(commit: GitCommit, dateFormat: string | null): DecorationOptions { - const message = CommitFormatter.toHoverAnnotation(commit, dateFormat); + const message = this.getHoverMessage(commit, dateFormat); return { hoverMessage: message } as DecorationOptions; @@ -129,7 +166,7 @@ export class Annotations { static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null): DecorationOptions { return { - hoverMessage: CommitFormatter.toHoverAnnotation(commit, dateFormat), + hoverMessage: this.getHoverMessage(commit, dateFormat), renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined } as DecorationOptions; } diff --git a/src/annotations/recentChangesAnnotationProvider.ts b/src/annotations/recentChangesAnnotationProvider.ts index 005ddae..2c24d3a 100644 --- a/src/annotations/recentChangesAnnotationProvider.ts +++ b/src/annotations/recentChangesAnnotationProvider.ts @@ -1,9 +1,9 @@ 'use strict'; import { DecorationOptions, ExtensionContext, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode'; -import { endOfLineIndex } from './annotations'; +import { Annotations, endOfLineIndex } from './annotations'; import { FileAnnotationType } from './annotationController'; import { AnnotationProviderBase } from './annotationProvider'; -import { CommitFormatter, GitService, GitUri } from '../gitService'; +import { GitService, GitUri } from '../gitService'; export class RecentChangesAnnotationProvider extends AnnotationProviderBase { @@ -43,14 +43,14 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase { if (cfg.hover.details) { decorators.push({ - hoverMessage: CommitFormatter.toHoverAnnotation(commit, dateFormat), + hoverMessage: Annotations.getHoverMessage(commit, dateFormat), range: range } as DecorationOptions); } let message: string | undefined = undefined; if (cfg.hover.changes) { - message = CommitFormatter.toHoverDiff(commit, chunk.previous[count], change); + message = Annotations.getHoverDiffMessage(commit, chunk.previous[count], change); } decorators.push({ diff --git a/src/git/formatters/commit.ts b/src/git/formatters/commit.ts index be8d3a5..1463a6c 100644 --- a/src/git/formatters/commit.ts +++ b/src/git/formatters/commit.ts @@ -1,14 +1,10 @@ 'use strict'; import { Strings } from '../../system'; import { GitCommit } from '../models/commit'; -import { GitDiffLine } from '../models/diff'; +import { Formatter, IFormatOptions } from './formatter'; import * as moment from 'moment'; -const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g; -// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___'; - -export interface ICommitFormatOptions { - dateFormat?: string | null; +export interface ICommitFormatOptions extends IFormatOptions { tokenOptions?: { ago?: Strings.ITokenOptions; author?: Strings.ITokenOptions; @@ -18,58 +14,34 @@ export interface ICommitFormatOptions { }; } -export class CommitFormatter { - - private _commit: GitCommit; - private _options: ICommitFormatOptions; - - constructor(commit: GitCommit, options?: ICommitFormatOptions) { - this.reset(commit, options); - } - - reset(commit: GitCommit, options?: ICommitFormatOptions) { - this._commit = commit; - - if (options === undefined && this._options !== undefined) return; - - options = options || {}; - if (options.tokenOptions == null) { - options.tokenOptions = {}; - } - - if (options.dateFormat == null) { - options.dateFormat = 'MMMM Do, YYYY h:MMa'; - } - - this._options = options; - } +export class CommitFormatter extends Formatter { get ago() { - const ago = moment(this._commit.date).fromNow(); + const ago = moment(this._item.date).fromNow(); return this._padOrTruncate(ago, this._options.tokenOptions!.ago); } get author() { - const author = this._commit.author; + const author = this._item.author; return this._padOrTruncate(author, this._options.tokenOptions!.author); } get authorAgo() { - const authorAgo = `${this._commit.author}, ${moment(this._commit.date).fromNow()}`; + const authorAgo = `${this._item.author}, ${moment(this._item.date).fromNow()}`; return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo); } get date() { - const date = moment(this._commit.date).format(this._options.dateFormat!); + const date = moment(this._item.date).format(this._options.dateFormat!); return this._padOrTruncate(date, this._options.tokenOptions!.date); } get id() { - return this._commit.shortSha; + return this._item.shortSha; } get message() { - const message = this._commit.isUncommitted ? 'Uncommitted change' : this._commit.message; + const message = this._item.isUncommitted ? 'Uncommitted change' : this._item.message; return this._padOrTruncate(message, this._options.tokenOptions!.message); } @@ -77,122 +49,10 @@ export class CommitFormatter { return this.id; } - private collapsableWhitespace: number = 0; - - private _padOrTruncate(s: string, options: Strings.ITokenOptions | undefined) { - // NOTE: the collapsable whitespace logic relies on the javascript template evaluation to be left to right - if (options === undefined) { - options = { - truncateTo: undefined, - padDirection: 'left', - collapseWhitespace: false - }; - } - - let max = options.truncateTo; - - if (max === undefined) { - if (this.collapsableWhitespace === 0) return s; - - // If we have left over whitespace make sure it gets re-added - const diff = this.collapsableWhitespace - s.length; - this.collapsableWhitespace = 0; - - if (diff <= 0) return s; - if (options.truncateTo === undefined) return s; - return Strings.padLeft(s, diff); - } - - max += this.collapsableWhitespace; - this.collapsableWhitespace = 0; - - const diff = max - s.length; - if (diff > 0) { - if (options.collapseWhitespace) { - this.collapsableWhitespace = diff; - } - - if (options.padDirection === 'left') return Strings.padLeft(s, max); - - if (options.collapseWhitespace) { - max -= diff; - } - return Strings.padRight(s, max); - } - - if (diff < 0) return Strings.truncate(s, max); - - return s; - } - - private static _formatter: CommitFormatter | undefined = undefined; - - static fromCommit(commit: GitCommit, options?: ICommitFormatOptions): CommitFormatter { - if (CommitFormatter._formatter === undefined) { - CommitFormatter._formatter = new CommitFormatter(commit, options); - } - else { - CommitFormatter._formatter.reset(commit, options); - } - return CommitFormatter._formatter; - } - static fromTemplate(template: string, commit: GitCommit, dateFormat: string | null): string; static fromTemplate(template: string, commit: GitCommit, options?: ICommitFormatOptions): string; static fromTemplate(template: string, commit: GitCommit, dateFormatOrOptions?: string | null | ICommitFormatOptions): string; static fromTemplate(template: string, commit: GitCommit, dateFormatOrOptions?: string | null | ICommitFormatOptions): string { - let options: ICommitFormatOptions | undefined = undefined; - if (dateFormatOrOptions == null || typeof dateFormatOrOptions === 'string') { - const tokenOptions = Strings.getTokensFromTemplate(template) - .reduce((map, token) => { - map[token.key] = token.options; - return map; - }, {} as { [token: string]: ICommitFormatOptions }); - - options = { - dateFormat: dateFormatOrOptions, - tokenOptions: tokenOptions - }; - } - else { - options = dateFormatOrOptions; - } - - return Strings.interpolate(template, new CommitFormatter(commit, options)); - } - - static toHoverAnnotation(commit: GitCommit, dateFormat: string | null): string | string[] { - if (dateFormat === null) { - dateFormat = 'MMMM Do, YYYY h:MMa'; - } - - let message = ''; - if (!commit.isUncommitted) { - message = commit.message - // Escape markdown - .replace(escapeMarkdownRegEx, '\\$&') - // Escape markdown header (since the above regex won't match it) - .replace(/^===/gm, '\u200b===') - // Keep under the same block-quote - .replace(/\n/g, ' \n'); - message = `\n\n> ${message}`; - } - return `\`${commit.shortSha}\`   __${commit.author}__, ${moment(commit.date).fromNow()}   _(${moment(commit.date).format(dateFormat)})_${message}`; - } - - static toHoverDiff(commit: GitCommit, previous: GitDiffLine | undefined, current: GitDiffLine | undefined): string | undefined { - if (previous === undefined && current === undefined) return undefined; - - const codeDiff = this._getCodeDiff(previous, current); - return commit.isUncommitted - ? `\`Changes\`   \u2014   _uncommitted_\n${codeDiff}` - : `\`Changes\`   \u2014   \`${commit.previousShortSha}\` \u2194 \`${commit.shortSha}\`\n${codeDiff}`; - } - - private static _getCodeDiff(previous: GitDiffLine | undefined, current: GitDiffLine | undefined): string { - return `\`\`\` -- ${previous === undefined ? '' : previous.line.trim()} -+ ${current === undefined ? '' : current.line.trim()} -\`\`\``; + return super.fromTemplateCore(this, template, commit, dateFormatOrOptions); } } \ No newline at end of file diff --git a/src/git/formatters/formatter.ts b/src/git/formatters/formatter.ts new file mode 100644 index 0000000..a92907f --- /dev/null +++ b/src/git/formatters/formatter.ts @@ -0,0 +1,119 @@ +'use strict'; +import { Strings } from '../../system'; + +export interface IFormatOptions { + dateFormat?: string | null; + tokenOptions?: { [id: string]: Strings.ITokenOptions | undefined }; +} + +type Constructor = new (...args: any[]) => T; + +export abstract class Formatter { + + protected _item: TItem; + protected _options: TOptions; + + constructor(item: TItem, options?: TOptions) { + this.reset(item, options); + } + + reset(item: TItem, options?: TOptions) { + this._item = item; + + if (options === undefined && this._options !== undefined) return; + + if (options === undefined) { + options = {} as TOptions; + } + + if (options.dateFormat == null) { + options.dateFormat = 'MMMM Do, YYYY h:MMa'; + } + + if (options.tokenOptions == null) { + options.tokenOptions = {}; + } + + this._options = options; + } + + private collapsableWhitespace: number = 0; + + protected _padOrTruncate(s: string, options: Strings.ITokenOptions | undefined) { + // NOTE: the collapsable whitespace logic relies on the javascript template evaluation to be left to right + if (options === undefined) { + options = { + truncateTo: undefined, + padDirection: 'left', + collapseWhitespace: false + }; + } + + let max = options.truncateTo; + + if (max === undefined) { + if (this.collapsableWhitespace === 0) return s; + + // If we have left over whitespace make sure it gets re-added + const diff = this.collapsableWhitespace - s.length; + this.collapsableWhitespace = 0; + + if (diff <= 0) return s; + if (options.truncateTo === undefined) return s; + return Strings.padLeft(s, diff); + } + + max += this.collapsableWhitespace; + this.collapsableWhitespace = 0; + + const diff = max - s.length; + if (diff > 0) { + if (options.collapseWhitespace) { + this.collapsableWhitespace = diff; + } + + if (options.padDirection === 'left') return Strings.padLeft(s, max); + + if (options.collapseWhitespace) { + max -= diff; + } + return Strings.padRight(s, max); + } + + if (diff < 0) return Strings.truncate(s, max); + + return s; + } + + private static _formatter: Formatter | undefined = undefined; + + protected static fromTemplateCore, TItem, TOptions extends IFormatOptions>(formatter: TFormatter | Constructor, template: string, item: TItem, dateFormatOrOptions?: string | null | TOptions): string { + if (formatter instanceof Formatter) return Strings.interpolate(template, formatter); + + let options: TOptions | undefined = undefined; + if (dateFormatOrOptions == null || typeof dateFormatOrOptions === 'string') { + const tokenOptions = Strings.getTokensFromTemplate(template) + .reduce((map, token) => { + map[token.key] = token.options; + return map; + }, {} as { [token: string]: Strings.ITokenOptions | undefined }); + + options = { + dateFormat: dateFormatOrOptions, + tokenOptions: tokenOptions + } as TOptions; + } + else { + options = dateFormatOrOptions; + } + + if (this._formatter === undefined) { + this._formatter = new formatter(item, options); + } + else { + this._formatter.reset(item, options); + } + + return Strings.interpolate(template, this._formatter); + } +} \ No newline at end of file