From 867001dc60ba3e51fadda86d074174b71d7230fb Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Sun, 5 Aug 2018 15:39:21 -0400 Subject: [PATCH] Adds Strings.pluralize --- src/annotations/annotations.ts | 3 ++- src/git/models/branch.ts | 30 +++++++---------------- src/git/models/status.ts | 40 ++++++++++++++++++------------- src/system/string.ts | 12 ++++++++++ src/views/nodes/comparisonResultsNode.ts | 3 +-- src/views/nodes/statusFileCommitsNode.ts | 3 ++- src/views/nodes/statusFilesNode.ts | 2 +- src/views/nodes/statusFilesResultsNode.ts | 2 +- src/views/nodes/statusUpstreamNode.ts | 17 ++++++------- src/views/resultsExplorer.ts | 9 +++---- 10 files changed, 64 insertions(+), 57 deletions(-) diff --git a/src/annotations/annotations.ts b/src/annotations/annotations.ts index 784948b..d14d845 100644 --- a/src/annotations/annotations.ts +++ b/src/annotations/annotations.ts @@ -51,6 +51,7 @@ const defaultHeatmapHotColor = '#f66a0a'; const defaultHeatmapColdColor = '#0a60f6'; 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___'; +const markdownHeaderReplacement = `${GlyphChars.ZeroWidthSpace}===`; let computedHeatmapColor: { color: string; @@ -154,7 +155,7 @@ export class Annotations { // Escape markdown .replace(escapeMarkdownRegEx, '\\$&') // Escape markdown header (since the above regex won't match it) - .replace(/^===/gm, `${GlyphChars.ZeroWidthSpace}===`) + .replace(/^===/gm, markdownHeaderReplacement) // Keep under the same block-quote .replace(/\n/g, ' \n'); message = `\n\n> ${message}`; diff --git a/src/git/models/branch.ts b/src/git/models/branch.ts index 50974b8..6d19a66 100644 --- a/src/git/models/branch.ts +++ b/src/git/models/branch.ts @@ -1,4 +1,4 @@ -import { GlyphChars } from '../../constants'; +import { GitStatus } from './status'; 'use strict'; @@ -63,26 +63,14 @@ export class GitBranch { return undefined; } - getTrackingStatus(options: { empty?: string; expand?: boolean; prefix?: string; separator?: string } = {}): string { - options = { empty: '', prefix: '', separator: ' ', ...options }; - if (this.tracking === undefined || (this.state.behind === 0 && this.state.ahead === 0)) return options.empty!; - - if (options.expand) { - let status = ''; - if (this.state.behind) { - status += `${this.state.behind} ${this.state.behind === 1 ? 'commit' : 'commits'} behind`; - } - if (this.state.ahead) { - status += `${status === '' ? '' : options.separator}${this.state.ahead} ${ - this.state.ahead === 1 ? 'commit' : 'commits' - } ahead`; - } - return `${options.prefix}${status}`; - } - - return `${options.prefix}${this.state.behind}${GlyphChars.ArrowDown}${options.separator}${this.state.ahead}${ - GlyphChars.ArrowUp - }`; + getTrackingStatus(options?: { + empty?: string; + expand?: boolean; + prefix?: string; + separator?: string; + suffix?: string; + }): string { + return GitStatus.getUpstreamStatus(this.tracking, this.state, options); } isValid(): boolean { diff --git a/src/git/models/status.ts b/src/git/models/status.ts index dbd4172..e403023 100644 --- a/src/git/models/status.ts +++ b/src/git/models/status.ts @@ -57,17 +57,19 @@ export class GitStatus { if (options.expand) { let status = ''; if (this._diff.added) { - status += `${this._diff.added} ${this._diff.added === 1 ? 'file' : 'files'} added`; + status += `${Strings.pluralize('file', this._diff.added)} added`; } if (this._diff.changed) { - status += `${status === '' ? '' : options.separator}${this._diff.changed} ${ - this._diff.changed === 1 ? 'file' : 'files' - } changed`; + status += `${status === '' ? '' : options.separator}${this._diff.changed} ${Strings.pluralize( + 'file', + this._diff.changed + )} changed`; } if (this._diff.deleted) { - status += `${status === '' ? '' : options.separator}${this._diff.deleted} ${ - this._diff.deleted === 1 ? 'file' : 'files' - } deleted`; + status += `${status === '' ? '' : options.separator}${this._diff.deleted} ${Strings.pluralize( + 'file', + this._diff.deleted + )} deleted`; } return `${options.prefix}${status}`; } @@ -77,24 +79,30 @@ export class GitStatus { }`; } - getUpstreamStatus(options: { empty?: string; expand?: boolean; prefix?: string; separator?: string } = {}): string { + getUpstreamStatus(options: { empty?: string; expand?: boolean; prefix?: string; separator?: string }): string { + return GitStatus.getUpstreamStatus(this.upstream, this.state, options); + } + + static getUpstreamStatus( + upstream: string | undefined, + state: { ahead: number; behind: number }, + options: { empty?: string; expand?: boolean; prefix?: string; separator?: string } = {} + ): string { options = { empty: '', prefix: '', separator: ' ', ...options }; - if (this.upstream === undefined || (this.state.behind === 0 && this.state.ahead === 0)) return options.empty!; + if (upstream === undefined || (state.behind === 0 && state.ahead === 0)) return options.empty!; if (options.expand) { let status = ''; - if (this.state.behind) { - status += `${this.state.behind} ${this.state.behind === 1 ? 'commit' : 'commits'} behind`; + if (state.behind) { + status += `${Strings.pluralize('commit', state.behind)} behind`; } - if (this.state.ahead) { - status += `${status === '' ? '' : options.separator}${this.state.ahead} ${ - this.state.ahead === 1 ? 'commit' : 'commits' - } ahead`; + if (state.ahead) { + status += `${status === '' ? '' : options.separator}${Strings.pluralize('commit', state.ahead)} ahead`; } return `${options.prefix}${status}`; } - return `${options.prefix}${this.state.behind}${GlyphChars.ArrowDown}${options.separator}${this.state.ahead}${ + return `${options.prefix}${state.behind}${GlyphChars.ArrowDown}${options.separator}${state.ahead}${ GlyphChars.ArrowUp }`; } diff --git a/src/system/string.ts b/src/system/string.ts index c0e8156..d5c9dbf 100644 --- a/src/system/string.ts +++ b/src/system/string.ts @@ -108,6 +108,18 @@ export namespace Strings { return s; } + export function pluralize( + s: string, + count: number, + options?: { number?: string; plural?: string; suffix?: string; zero?: string } + ) { + if (options === undefined) return `${count} ${s}${count === 1 ? '' : 's'}`; + + return `${count === 0 ? options.zero || count : options.number || count} ${ + count === 1 ? s : options.plural || `${s}${options.suffix}` + }`; + } + // Removes \ / : * ? " < > | and C0 and C1 control codes const illegalCharsForFSRegEx = /[\\/:*?"<>|\x00-\x1f\x80-\x9f]/g; diff --git a/src/views/nodes/comparisonResultsNode.ts b/src/views/nodes/comparisonResultsNode.ts index 70042eb..661a0c3 100644 --- a/src/views/nodes/comparisonResultsNode.ts +++ b/src/views/nodes/comparisonResultsNode.ts @@ -30,8 +30,7 @@ export class ComparisonResultsNode extends ExplorerNode { const count = log !== undefined ? log.count : 0; const truncated = log !== undefined ? log.truncated : false; - if (count === 1) return `1 commit`; - return `${count === 0 ? 'No' : `${count}${truncated ? '+' : ''}`} commits`; + return Strings.pluralize('commit', count, { number: truncated ? `${count}+` : undefined, zero: 'No' }); }; this.children = [ diff --git a/src/views/nodes/statusFileCommitsNode.ts b/src/views/nodes/statusFileCommitsNode.ts index 9a06b52..1485b2b 100644 --- a/src/views/nodes/statusFileCommitsNode.ts +++ b/src/views/nodes/statusFileCommitsNode.ts @@ -12,6 +12,7 @@ import { IStatusFormatOptions, StatusFileFormatter } from '../../gitService'; +import { Strings } from '../../system'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; @@ -145,7 +146,7 @@ export class StatusFileCommitsNode extends ExplorerNode { } if (commits > 0) { - changedIn.push(`${commits} ${commits === 1 ? 'commit' : 'commits'}`); + changedIn.push(Strings.pluralize('commit', commits)); } if (changedIn.length > 2) { diff --git a/src/views/nodes/statusFilesNode.ts b/src/views/nodes/statusFilesNode.ts index 44493c5..348d6b3 100644 --- a/src/views/nodes/statusFilesNode.ts +++ b/src/views/nodes/statusFilesNode.ts @@ -222,7 +222,7 @@ export class StatusFilesNode extends ExplorerNode { } } - const label = `${files} ${files === 1 ? 'file' : 'files'} changed`; + const label = `${Strings.pluralize('file', files)} changed`; const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed); item.id = this.id; item.contextValue = ResourceType.StatusFiles; diff --git a/src/views/nodes/statusFilesResultsNode.ts b/src/views/nodes/statusFilesResultsNode.ts index fb03605..84be151 100644 --- a/src/views/nodes/statusFilesResultsNode.ts +++ b/src/views/nodes/statusFilesResultsNode.ts @@ -69,7 +69,7 @@ export class StatusFilesResultsNode extends ExplorerNode { const diff = await Container.git.getDiffStatus(this.uri.repoPath!, this.ref1, this.ref2); const count = diff !== undefined ? diff.length : 0; - const label = `${count === 0 ? 'No' : count} ${count === 1 ? 'file' : 'files'} changed`; + const label = `${Strings.pluralize('file', count, { zero: 'No' })} changed`; this._cache = { label: label, diff --git a/src/views/nodes/statusUpstreamNode.ts b/src/views/nodes/statusUpstreamNode.ts index fa88201..60909b2 100644 --- a/src/views/nodes/statusUpstreamNode.ts +++ b/src/views/nodes/statusUpstreamNode.ts @@ -2,7 +2,7 @@ import { TreeItem, TreeItemCollapsibleState } from 'vscode'; import { Container } from '../../container'; import { GitStatus, GitUri } from '../../gitService'; -import { Iterables } from '../../system'; +import { Iterables, Strings } from '../../system'; import { CommitNode } from './commitNode'; import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; @@ -49,20 +49,17 @@ export class StatusUpstreamNode extends ExplorerNode { } async getTreeItem(): Promise { - const label = - this.direction === 'ahead' - ? `${this.status.state.ahead} ${this.status.state.ahead === 1 ? 'commit' : 'commits'} (ahead of ${ - this.status.upstream - })` - : `${this.status.state.behind} ${this.status.state.behind === 1 ? 'commit' : 'commits'} (behind ${ - this.status.upstream - })`; + const ahead = this.direction === 'ahead'; + const label = ahead + ? `${Strings.pluralize('commit', this.status.state.ahead)} ahead` + : `${Strings.pluralize('commit', this.status.state.behind)} behind`; const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed); item.id = this.id; item.contextValue = ResourceType.StatusUpstream; + item.tooltip = `${label}${ahead ? ' of ' : ''}${this.status.upstream}`; - const iconSuffix = this.direction === 'ahead' ? 'upload' : 'download'; + const iconSuffix = ahead ? 'upload' : 'download'; item.iconPath = { dark: Container.context.asAbsolutePath(`images/dark/icon-${iconSuffix}.svg`), light: Container.context.asAbsolutePath(`images/light/icon-${iconSuffix}.svg`) diff --git a/src/views/resultsExplorer.ts b/src/views/resultsExplorer.ts index 0b57ce9..9653bea 100644 --- a/src/views/resultsExplorer.ts +++ b/src/views/resultsExplorer.ts @@ -237,10 +237,11 @@ export class ResultsExplorer implements TreeDataProvider, Disposab results.repoPath}`; } - if (count === 1) return `1 ${resultsType.singular} for ${resultsLabel.label}${repository}`; - return `${count === 0 ? 'No' : `${count}${truncated ? '+' : ''}`} ${resultsType.plural} for ${ - resultsLabel.label - }${repository}`; + return `${Strings.pluralize(resultsType.singular, count, { + number: truncated ? `${count}+` : undefined, + plural: resultsType.plural, + zero: 'No' + })} for ${resultsLabel.label}${repository}`; }; this.showResults(