Просмотр исходного кода

Closes #493 - Adds changes to commits in explorers

Adds new ${changes} and ${changesShort} template tokens
Adds support for prefixes and suffixes around tokens
Fixes missing/mismatched token option issues
main
Eric Amodio 6 лет назад
Родитель
Сommit
2d977262d2
11 измененных файлов: 212 добавлений и 104 удалений
  1. +2
    -2
      package.json
  2. +26
    -3
      src/git/formatters/commitFormatter.ts
  3. +49
    -34
      src/git/formatters/formatter.ts
  4. +6
    -2
      src/git/formatters/statusFormatter.ts
  5. +63
    -18
      src/git/models/logCommit.ts
  6. +42
    -32
      src/git/models/status.ts
  7. +1
    -1
      src/quickpicks/commitQuickPick.ts
  8. +2
    -2
      src/quickpicks/commonQuickPicks.ts
  9. +11
    -8
      src/system/string.ts
  10. +8
    -0
      src/views/nodes/commitNode.ts
  11. +2
    -2
      src/views/nodes/statusNode.ts

+ 2
- 2
package.json Просмотреть файл

@ -451,7 +451,7 @@
},
"gitlens.explorers.commitFormat": {
"type": "string",
"default": "${message} • ${authorAgoOrDate} (${id})",
"default": "${message} • ${authorAgoOrDate}${ (id)}",
"description": "Specifies the format of committed changes in the `GitLens` and `GitLens Results` explorers\nAvailable tokens\n ${id} - commit id\n ${author} - commit author\n ${message} - commit message\n ${ago} - relative commit date (e.g. 1 day ago)\n ${date} - formatted commit date (format specified by `gitlens.defaultDateFormat`)\\n ${agoOrDate} - commit date specified by `gitlens.defaultDateStyle`\n ${authorAgo} - commit author, relative commit date\n ${authorAgoOrDate} - commit author, commit date specified by `gitlens.defaultDateStyle`\nSee https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting",
"scope": "window"
},
@ -469,7 +469,7 @@
},
"gitlens.explorers.statusFileFormat": {
"type": "string",
"default": "${working}${filePath}",
"default": "${working }${filePath}",
"description": "Specifies the format of the status of a working or committed file in the `GitLens` and `GitLens Results` explorers\nAvailable tokens\n ${directory} - directory name\n ${file} - file name\n ${filePath} - formatted file name and path\n ${path} - full file path\n ${working} - optional indicator if the file is uncommitted",
"scope": "window"
},

+ 26
- 3
src/git/formatters/commitFormatter.ts Просмотреть файл

@ -3,7 +3,8 @@ import { DateStyle } from '../../configuration';
import { GlyphChars } from '../../constants';
import { Container } from '../../container';
import { Strings } from '../../system';
import { GitCommit } from '../models/commit';
import { GitCommit, GitCommitType } from '../models/commit';
import { GitLogCommit } from '../models/models';
import { Formatter, IFormatOptions } from './formatter';
const emojiMap: { [key: string]: string } = require('../../../emoji/emojis.json');
@ -19,7 +20,10 @@ export interface ICommitFormatOptions extends IFormatOptions {
author?: Strings.ITokenOptions;
authorAgo?: Strings.ITokenOptions;
authorAgoOrDate?: Strings.ITokenOptions;
changes?: Strings.ITokenOptions;
changesShort?: Strings.ITokenOptions;
date?: Strings.ITokenOptions;
id?: Strings.ITokenOptions;
message?: Strings.ITokenOptions;
};
}
@ -59,7 +63,26 @@ export class CommitFormatter extends Formatter
get authorAgoOrDate() {
const authorAgo = `${this._item.author}, ${this._agoOrDate}`;
return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo);
return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgoOrDate);
}
get changes() {
if (!(this._item instanceof GitLogCommit) || this._item.type === GitCommitType.File) {
return this._padOrTruncate('', this._options.tokenOptions!.changes);
}
return this._padOrTruncate(this._item.getFormattedDiffStatus(), this._options.tokenOptions!.changes);
}
get changesShort() {
if (!(this._item instanceof GitLogCommit) || this._item.type === GitCommitType.File) {
return this._padOrTruncate('', this._options.tokenOptions!.changesShort);
}
return this._padOrTruncate(
this._item.getFormattedDiffStatus({ compact: true, separator: '' }),
this._options.tokenOptions!.changesShort
);
}
get date() {
@ -67,7 +90,7 @@ export class CommitFormatter extends Formatter
}
get id() {
return this._item.shortSha;
return this._padOrTruncate(this._item.shortSha || '', this._options.tokenOptions!.id);
}
get message() {

+ 49
- 34
src/git/formatters/formatter.ts Просмотреть файл

@ -41,50 +41,62 @@ export abstract class Formatter
private collapsableWhitespace: number = 0;
protected _padOrTruncate(s: string, options: Strings.ITokenOptions | undefined) {
if (s === '') return s;
// NOTE: the collapsable whitespace logic relies on the javascript template evaluation to be left to right
if (options === undefined) {
options = {
truncateTo: undefined,
collapseWhitespace: false,
padDirection: 'left',
collapseWhitespace: false
prefix: undefined,
suffix: undefined,
truncateTo: undefined
};
}
let max = options.truncateTo;
if (max === undefined) {
if (this.collapsableWhitespace === 0) return s;
if (this.collapsableWhitespace !== 0) {
const width = Strings.getWidth(s);
const width = Strings.getWidth(s);
// If we have left over whitespace make sure it gets re-added
const diff = this.collapsableWhitespace - width;
this.collapsableWhitespace = 0;
// If we have left over whitespace make sure it gets re-added
const diff = this.collapsableWhitespace - width;
this.collapsableWhitespace = 0;
if (diff <= 0) return s;
if (options.truncateTo === undefined) return s;
return Strings.padLeft(s, diff, undefined, width);
if (diff > 0 && options.truncateTo !== undefined) {
s = Strings.padLeft(s, diff, undefined, width);
}
}
}
else {
max += this.collapsableWhitespace;
this.collapsableWhitespace = 0;
max += this.collapsableWhitespace;
this.collapsableWhitespace = 0;
const width = Strings.getWidth(s);
const diff = max - width;
if (diff > 0) {
if (options.collapseWhitespace) {
this.collapsableWhitespace = diff;
const width = Strings.getWidth(s);
const diff = max - width;
if (diff > 0) {
if (options.collapseWhitespace) {
this.collapsableWhitespace = diff;
}
if (options.padDirection === 'left') {
s = Strings.padLeft(s, max, undefined, width);
}
else {
if (options.collapseWhitespace) {
max -= diff;
}
s = Strings.padRight(s, max, undefined, width);
}
}
if (options.padDirection === 'left') return Strings.padLeft(s, max, undefined, width);
if (options.collapseWhitespace) {
max -= diff;
else if (diff < 0) {
s = Strings.truncate(s, max, undefined, width);
}
return Strings.padRight(s, max, undefined, width);
}
if (diff < 0) return Strings.truncate(s, max, undefined, width);
if (options.prefix || options.suffix) {
s = `${options.prefix || ''}${s}${options.suffix || ''}`;
}
return s;
}
@ -107,6 +119,15 @@ export abstract class Formatter
let options: TOptions | undefined = undefined;
if (dateFormatOrOptions == null || typeof dateFormatOrOptions === 'string') {
options = {
dateFormat: dateFormatOrOptions
} as TOptions;
}
else {
options = dateFormatOrOptions;
}
if (options.tokenOptions == null) {
const tokenOptions = Strings.getTokensFromTemplate(template).reduce(
(map, token) => {
map[token.key] = token.options;
@ -115,13 +136,7 @@ export abstract class Formatter
{} as { [token: string]: Strings.ITokenOptions | undefined }
);
options = {
dateFormat: dateFormatOrOptions,
tokenOptions: tokenOptions
} as TOptions;
}
else {
options = dateFormatOrOptions;
options.tokenOptions = tokenOptions;
}
if (this._formatter === undefined) {

+ 6
- 2
src/git/formatters/statusFormatter.ts Просмотреть файл

@ -14,13 +14,14 @@ export interface IStatusFormatOptions extends IFormatOptions {
filePath?: Strings.ITokenOptions;
path?: Strings.ITokenOptions;
status?: Strings.ITokenOptions;
working?: Strings.ITokenOptions;
};
}
export class StatusFileFormatter extends Formatter<IGitStatusFile, IStatusFormatOptions> {
get directory() {
const directory = GitStatusFile.getFormattedDirectory(this._item, false, this._options.relativePath);
return this._padOrTruncate(directory, this._options.tokenOptions!.file);
return this._padOrTruncate(directory, this._options.tokenOptions!.directory);
}
get file() {
@ -45,7 +46,10 @@ export class StatusFileFormatter extends Formatter
get working() {
const commit = (this._item as IGitStatusFileWithCommit).commit;
return commit !== undefined && commit.isUncommitted ? `${GlyphChars.Pencil} ${GlyphChars.Space}` : '';
return this._padOrTruncate(
commit !== undefined && commit.isUncommitted ? GlyphChars.Pencil : '',
this._options.tokenOptions!.working
);
}
static fromTemplate(template: string, status: IGitStatusFile, dateFormat: string | null): string;

+ 63
- 18
src/git/models/logCommit.ts Просмотреть файл

@ -59,27 +59,72 @@ export class GitLogCommit extends GitCommit {
return this.isFile && this.previousSha ? this.previousSha : `${this.sha}^`;
}
getDiffStatus(): string {
let added = 0;
let deleted = 0;
let changed = 0;
for (const f of this.fileStatuses) {
switch (f.status) {
case 'A':
case '?':
added++;
break;
case 'D':
deleted++;
break;
default:
changed++;
break;
private _diff?: {
added: number;
deleted: number;
changed: number;
};
getDiffStatus() {
if (this._diff === undefined) {
this._diff = {
added: 0,
deleted: 0,
changed: 0
};
if (this.fileStatuses.length !== 0) {
for (const f of this.fileStatuses) {
switch (f.status) {
case 'A':
case '?':
this._diff.added++;
break;
case 'D':
this._diff.deleted++;
break;
default:
this._diff.changed++;
break;
}
}
}
}
return this._diff;
}
getFormattedDiffStatus(
options: {
compact?: boolean;
empty?: string;
expand?: boolean;
prefix?: string;
separator?: string;
suffix?: string;
} = {}
): string {
const { added, changed, deleted } = this.getDiffStatus();
if (added === 0 && changed === 0 && deleted === 0) return options.empty || '';
options = { compact: true, empty: '', prefix: '', separator: ' ', suffix: '', ...options };
if (options.expand) {
let status = '';
if (added) {
status += `${Strings.pluralize('file', added)} added`;
}
if (changed) {
status += `${status === '' ? '' : options.separator}${Strings.pluralize('file', changed)} changed`;
}
if (deleted) {
status += `${status === '' ? '' : options.separator}${Strings.pluralize('file', deleted)} deleted`;
}
return `${options.prefix}${status}${options.suffix}`;
}
return `+${added} ~${changed} -${deleted}`;
return `${options.prefix}${options.compact && added === 0 ? '' : `+${added}${options.separator}`}${
options.compact && changed === 0 ? '' : `~${changed}${options.separator}`
}${options.compact && deleted === 0 ? '' : `-${deleted}`}${options.suffix}`;
}
toFileCommit(fileName: string): GitLogCommit | undefined;

+ 42
- 32
src/git/models/status.ts Просмотреть файл

@ -39,10 +39,7 @@ export class GitStatus {
changed: number;
};
getDiffStatus(options: { empty?: string; expand?: boolean; prefix?: string; separator?: string } = {}): string {
options = { empty: '', prefix: '', separator: ' ', ...options };
if (this.files.length === 0) return options.empty!;
getDiffStatus() {
if (this._diff === undefined) {
this._diff = {
added: 0,
@ -50,45 +47,58 @@ export class GitStatus {
changed: 0
};
for (const f of this.files) {
switch (f.status) {
case 'A':
case '?':
this._diff.added++;
break;
case 'D':
this._diff.deleted++;
break;
default:
this._diff.changed++;
break;
if (this.files.length !== 0) {
for (const f of this.files) {
switch (f.status) {
case 'A':
case '?':
this._diff.added++;
break;
case 'D':
this._diff.deleted++;
break;
default:
this._diff.changed++;
break;
}
}
}
}
return this._diff;
}
getFormattedDiffStatus(
options: {
compact?: boolean;
empty?: string;
expand?: boolean;
prefix?: string;
separator?: string;
suffix?: string;
} = {}
): string {
const { added, changed, deleted } = this.getDiffStatus();
if (added === 0 && changed === 0 && deleted === 0) return options.empty || '';
options = { compact: true, empty: '', prefix: '', separator: ' ', suffix: '', ...options };
if (options.expand) {
let status = '';
if (this._diff.added) {
status += `${Strings.pluralize('file', this._diff.added)} added`;
if (added) {
status += `${Strings.pluralize('file', added)} added`;
}
if (this._diff.changed) {
status += `${status === '' ? '' : options.separator}${this._diff.changed} ${Strings.pluralize(
'file',
this._diff.changed
)} changed`;
if (changed) {
status += `${status === '' ? '' : options.separator}${Strings.pluralize('file', changed)} changed`;
}
if (this._diff.deleted) {
status += `${status === '' ? '' : options.separator}${this._diff.deleted} ${Strings.pluralize(
'file',
this._diff.deleted
)} deleted`;
if (deleted) {
status += `${status === '' ? '' : options.separator}${Strings.pluralize('file', deleted)} deleted`;
}
return `${options.prefix}${status}`;
return `${options.prefix}${status}${options.suffix}`;
}
return `${options.prefix}+${this._diff.added}${options.separator}~${this._diff.changed}${options.separator}-${
this._diff.deleted
}`;
return `${options.prefix}${options.compact && added === 0 ? '' : `+${added}${options.separator}`}${
options.compact && changed === 0 ? '' : `~${changed}${options.separator}`
}${options.compact && deleted === 0 ? '' : `-${deleted}`}${options.suffix}`;
}
getUpstreamStatus(options: { empty?: string; expand?: boolean; prefix?: string; separator?: string }): string {

+ 1
- 1
src/quickpicks/commitQuickPick.ts Просмотреть файл

@ -276,7 +276,7 @@ export class CommitQuickPick {
new CommandQuickPickItem(
{
label: `Changed Files`,
description: commit.getDiffStatus()
description: commit.getFormattedDiffStatus()
},
Commands.ShowQuickCommitDetails,
[

+ 2
- 2
src/quickpicks/commonQuickPicks.ts Просмотреть файл

@ -116,13 +116,13 @@ export class CommitQuickPickItem implements QuickPickItem {
GlyphChars.Dot,
1,
1
)} ${commit.formattedDate} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getDiffStatus()}`;
)} ${commit.formattedDate} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getFormattedDiffStatus()}`;
}
else {
this.label = message;
this.description = `${Strings.pad('$(git-commit)', 1, 1)} ${commit.shortSha}`;
this.detail = `${GlyphChars.Space} ${commit.author}, ${commit.formattedDate}${
commit.isFile ? '' : ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getDiffStatus()}`
commit.isFile ? '' : ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getFormattedDiffStatus()}`
}`;
}
}

+ 11
- 8
src/system/string.ts Просмотреть файл

@ -8,13 +8,15 @@ export namespace Strings {
}
const pathNormalizer = /\\/g;
const TokenRegex = /\$\{([^|]*?)(?:\|(\d+)(\-|\?)?)?\}/g;
const TokenSanitizeRegex = /\$\{(\w*?)(?:\W|\d)*?\}/g;
const TokenRegex = /\$\{(\W*)?([^|]*?)(?:\|(\d+)(\-|\?)?)?(\W*)?\}/g;
const TokenSanitizeRegex = /\$\{(?:\W*)?(\w*?)(?:[\W\d]*)\}/g;
export interface ITokenOptions {
collapseWhitespace: boolean;
padDirection: 'left' | 'right';
prefix: string | undefined;
suffix: string | undefined;
truncateTo: number | undefined;
collapseWhitespace: boolean;
}
export function getTokensFromTemplate(template: string) {
@ -22,14 +24,15 @@ export namespace Strings {
let match = TokenRegex.exec(template);
while (match != null) {
const truncateTo = match[2];
const option = match[3];
const [, prefix, key, truncateTo, option, suffix] = match;
tokens.push({
key: match[1],
key: key,
options: {
truncateTo: truncateTo == null ? undefined : parseInt(truncateTo, 10),
collapseWhitespace: option === '?',
padDirection: option === '-' ? 'left' : 'right',
collapseWhitespace: option === '?'
prefix: prefix,
suffix: suffix,
truncateTo: truncateTo == null ? undefined : parseInt(truncateTo, 10)
}
});
match = TokenRegex.exec(template);

+ 8
- 0
src/views/nodes/commitNode.ts Просмотреть файл

@ -91,6 +91,14 @@ export class CommitNode extends ExplorerRefNode {
} as ICommitFormatOptions
);
if (!this.commit.isUncommitted) {
item.tooltip += this.commit.getFormattedDiffStatus({
expand: true,
prefix: '\n\n',
separator: '\n'
});
}
return item;
}

+ 2
- 2
src/views/nodes/statusNode.ts Просмотреть файл

@ -87,7 +87,7 @@ export class StatusNode extends ExplorerNode {
let hasChildren = false;
const hasWorkingChanges = status.files.length !== 0 && this.includeWorkingTree;
let label = `${status.getUpstreamStatus({ prefix: `${GlyphChars.Space} ` })}${
hasWorkingChanges ? status.getDiffStatus({ prefix: `${GlyphChars.Space} ` }) : ''
hasWorkingChanges ? status.getFormattedDiffStatus({ prefix: `${GlyphChars.Space} ` }) : ''
}`;
let tooltip = `${status.branch} (current)`;
let iconSuffix = '';
@ -112,7 +112,7 @@ ${status.getUpstreamStatus({ empty: 'up-to-date', expand: true, separator: '\n'
}
if (hasWorkingChanges) {
tooltip += `\n\nHas uncommitted changes${status.getDiffStatus({
tooltip += `\n\nHas uncommitted changes${status.getFormattedDiffStatus({
expand: true,
prefix: `\n`,
separator: '\n'

Загрузка…
Отмена
Сохранить