Refactors formatters
Moves annotation messages from formatter to annotations Moves icons into dark/light folders
|
Before Width: | Height: | Size: 815 B After Width: | Height: | Size: 815 B |
|
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 312 B |
|
Before Width: | Height: | Size: 814 B After Width: | Height: | Size: 814 B |
|
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 313 B |
@@ -794,8 +794,8 @@
|
|||||||
"title": "Toggle File Blame Annotations",
|
"title": "Toggle File Blame Annotations",
|
||||||
"category": "GitLens",
|
"category": "GitLens",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "images/git-icon-dark.svg",
|
"dark": "images/dark/git-icon.svg",
|
||||||
"light": "images/git-icon-light.svg"
|
"light": "images/light/git-icon.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export class AnnotationController extends Disposable {
|
|||||||
? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
|
? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
|
||||||
: undefined,
|
: undefined,
|
||||||
gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter)
|
gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter)
|
||||||
? this.context.asAbsolutePath('images/blame-dark.svg')
|
? this.context.asAbsolutePath('images/dark/highlight-gutter.svg')
|
||||||
: undefined,
|
: undefined,
|
||||||
overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
|
overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
|
||||||
? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
|
? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
|
||||||
@@ -124,7 +124,7 @@ export class AnnotationController extends Disposable {
|
|||||||
? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
|
? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
|
||||||
: undefined,
|
: undefined,
|
||||||
gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter)
|
gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter)
|
||||||
? this.context.asAbsolutePath('images/blame-light.svg')
|
? this.context.asAbsolutePath('images/light/highlight-gutter.svg')
|
||||||
: undefined,
|
: undefined,
|
||||||
overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
|
overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
|
||||||
? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
|
? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
|
||||||
@@ -147,7 +147,7 @@ export class AnnotationController extends Disposable {
|
|||||||
? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
|
? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
|
||||||
: undefined,
|
: undefined,
|
||||||
gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter)
|
gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter)
|
||||||
? this.context.asAbsolutePath('images/blame-dark.svg')
|
? this.context.asAbsolutePath('images/dark/highlight-gutter.svg')
|
||||||
: undefined,
|
: undefined,
|
||||||
overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
|
overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
|
||||||
? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
|
? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
|
||||||
@@ -158,7 +158,7 @@ export class AnnotationController extends Disposable {
|
|||||||
? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
|
? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
|
||||||
: undefined,
|
: undefined,
|
||||||
gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter)
|
gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter)
|
||||||
? this.context.asAbsolutePath('images/blame-light.svg')
|
? this.context.asAbsolutePath('images/light/highlight-gutter.svg')
|
||||||
: undefined,
|
: undefined,
|
||||||
overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
|
overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
|
||||||
? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
|
? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DecorationInstanceRenderOptions, DecorationOptions, ThemableDecorationRenderOptions } from 'vscode';
|
import { DecorationInstanceRenderOptions, DecorationOptions, ThemableDecorationRenderOptions } from 'vscode';
|
||||||
import { IThemeConfig, themeDefaults } from '../configuration';
|
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';
|
import * as moment from 'moment';
|
||||||
|
|
||||||
interface IHeatmapConfig {
|
interface IHeatmapConfig {
|
||||||
@@ -20,6 +20,8 @@ interface IRenderOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const endOfLineIndex = 1000000;
|
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 {
|
export class Annotations {
|
||||||
|
|
||||||
@@ -43,15 +45,50 @@ export class Annotations {
|
|||||||
return '#793738';
|
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<DecorationOptions> {
|
static async changesHover(commit: GitCommit, line: number, uri: GitUri, git: GitService): Promise<DecorationOptions> {
|
||||||
let message: string | undefined = undefined;
|
let message: string | undefined = undefined;
|
||||||
if (commit.isUncommitted) {
|
if (commit.isUncommitted) {
|
||||||
const [previous, current] = await git.getDiffForLine(uri, line + uri.offset);
|
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) {
|
else if (commit.previousSha !== undefined) {
|
||||||
const [previous, current] = await git.getDiffForLine(uri, line + uri.offset, commit.previousSha);
|
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 {
|
return {
|
||||||
@@ -60,7 +97,7 @@ export class Annotations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static detailsHover(commit: GitCommit, dateFormat: string | null): DecorationOptions {
|
static detailsHover(commit: GitCommit, dateFormat: string | null): DecorationOptions {
|
||||||
const message = CommitFormatter.toHoverAnnotation(commit, dateFormat);
|
const message = this.getHoverMessage(commit, dateFormat);
|
||||||
return {
|
return {
|
||||||
hoverMessage: message
|
hoverMessage: message
|
||||||
} as DecorationOptions;
|
} as DecorationOptions;
|
||||||
@@ -129,7 +166,7 @@ export class Annotations {
|
|||||||
|
|
||||||
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null): DecorationOptions {
|
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null): DecorationOptions {
|
||||||
return {
|
return {
|
||||||
hoverMessage: CommitFormatter.toHoverAnnotation(commit, dateFormat),
|
hoverMessage: this.getHoverMessage(commit, dateFormat),
|
||||||
renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined
|
renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined
|
||||||
} as DecorationOptions;
|
} as DecorationOptions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { DecorationOptions, ExtensionContext, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode';
|
import { DecorationOptions, ExtensionContext, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode';
|
||||||
import { endOfLineIndex } from './annotations';
|
import { Annotations, endOfLineIndex } from './annotations';
|
||||||
import { FileAnnotationType } from './annotationController';
|
import { FileAnnotationType } from './annotationController';
|
||||||
import { AnnotationProviderBase } from './annotationProvider';
|
import { AnnotationProviderBase } from './annotationProvider';
|
||||||
import { CommitFormatter, GitService, GitUri } from '../gitService';
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
|
||||||
export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
||||||
|
|
||||||
@@ -43,14 +43,14 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
|||||||
|
|
||||||
if (cfg.hover.details) {
|
if (cfg.hover.details) {
|
||||||
decorators.push({
|
decorators.push({
|
||||||
hoverMessage: CommitFormatter.toHoverAnnotation(commit, dateFormat),
|
hoverMessage: Annotations.getHoverMessage(commit, dateFormat),
|
||||||
range: range
|
range: range
|
||||||
} as DecorationOptions);
|
} as DecorationOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
let message: string | undefined = undefined;
|
let message: string | undefined = undefined;
|
||||||
if (cfg.hover.changes) {
|
if (cfg.hover.changes) {
|
||||||
message = CommitFormatter.toHoverDiff(commit, chunk.previous[count], change);
|
message = Annotations.getHoverDiffMessage(commit, chunk.previous[count], change);
|
||||||
}
|
}
|
||||||
|
|
||||||
decorators.push({
|
decorators.push({
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { Strings } from '../../system';
|
import { Strings } from '../../system';
|
||||||
import { GitCommit } from '../models/commit';
|
import { GitCommit } from '../models/commit';
|
||||||
import { GitDiffLine } from '../models/diff';
|
import { Formatter, IFormatOptions } from './formatter';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
|
||||||
const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g;
|
export interface ICommitFormatOptions extends IFormatOptions {
|
||||||
// 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;
|
|
||||||
tokenOptions?: {
|
tokenOptions?: {
|
||||||
ago?: Strings.ITokenOptions;
|
ago?: Strings.ITokenOptions;
|
||||||
author?: Strings.ITokenOptions;
|
author?: Strings.ITokenOptions;
|
||||||
@@ -18,58 +14,34 @@ export interface ICommitFormatOptions {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommitFormatter {
|
export class CommitFormatter extends Formatter<GitCommit, ICommitFormatOptions> {
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
get ago() {
|
get ago() {
|
||||||
const ago = moment(this._commit.date).fromNow();
|
const ago = moment(this._item.date).fromNow();
|
||||||
return this._padOrTruncate(ago, this._options.tokenOptions!.ago);
|
return this._padOrTruncate(ago, this._options.tokenOptions!.ago);
|
||||||
}
|
}
|
||||||
|
|
||||||
get author() {
|
get author() {
|
||||||
const author = this._commit.author;
|
const author = this._item.author;
|
||||||
return this._padOrTruncate(author, this._options.tokenOptions!.author);
|
return this._padOrTruncate(author, this._options.tokenOptions!.author);
|
||||||
}
|
}
|
||||||
|
|
||||||
get authorAgo() {
|
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);
|
return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo);
|
||||||
}
|
}
|
||||||
|
|
||||||
get date() {
|
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);
|
return this._padOrTruncate(date, this._options.tokenOptions!.date);
|
||||||
}
|
}
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
return this._commit.shortSha;
|
return this._item.shortSha;
|
||||||
}
|
}
|
||||||
|
|
||||||
get message() {
|
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);
|
return this._padOrTruncate(message, this._options.tokenOptions!.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,122 +49,10 @@ export class CommitFormatter {
|
|||||||
return this.id;
|
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, dateFormat: string | null): string;
|
||||||
static fromTemplate(template: string, commit: GitCommit, options?: ICommitFormatOptions): 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;
|
||||||
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;
|
return super.fromTemplateCore(this, template, commit, dateFormatOrOptions);
|
||||||
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()}
|
|
||||||
\`\`\``;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
119
src/git/formatters/formatter.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||