Refactors formatters

Moves annotation messages from formatter to annotations
Moves icons into dark/light folders
This commit is contained in:
Eric Amodio
2017-06-13 00:12:31 -04:00
parent 12debe583c
commit a2903ce4a9
10 changed files with 181 additions and 165 deletions

View File

@@ -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<GitCommit, ICommitFormatOptions> {
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}\` &nbsp; __${commit.author}__, ${moment(commit.date).fromNow()} &nbsp; _(${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\` &nbsp; \u2014 &nbsp; _uncommitted_\n${codeDiff}`
: `\`Changes\` &nbsp; \u2014 &nbsp; \`${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);
}
}

View File

@@ -0,0 +1,119 @@
'use strict';
import { Strings } from '../../system';
export interface IFormatOptions {
dateFormat?: string | null;
tokenOptions?: { [id: string]: Strings.ITokenOptions | undefined };
}
type Constructor<T = {}> = new (...args: any[]) => T;
export abstract class Formatter<TItem = any, TOptions extends IFormatOptions = IFormatOptions> {
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<TFormatter extends Formatter<TItem, TOptions>, TItem, TOptions extends IFormatOptions>(formatter: TFormatter | Constructor<TFormatter>, 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);
}
}