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",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<DecorationOptions> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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}\` __${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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||