Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Package hover: Show when last published #177634

Merged
merged 1 commit into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions extensions/npm/src/features/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { l10n } from 'vscode';


const minute = 60;
const hour = minute * 60;
const day = hour * 24;
const week = day * 7;
const month = day * 30;
const year = day * 365;

/**
* Create a localized of the time between now and the specified date.
* @param date The date to generate the difference from.
* @param appendAgoLabel Whether to append the " ago" to the end.
* @param useFullTimeWords Whether to use full words (eg. seconds) instead of
* shortened (eg. secs).
* @param disallowNow Whether to disallow the string "now" when the difference
* is less than 30 seconds.
*/
export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTimeWords?: boolean, disallowNow?: boolean): string {
if (typeof date !== 'number') {
date = date.getTime();
}

const seconds = Math.round((new Date().getTime() - date) / 1000);
if (seconds < -30) {
return l10n.t('in {0}', fromNow(new Date().getTime() + seconds * 1000, false));
}

if (!disallowNow && seconds < 30) {
return l10n.t('now');
}

let value: number;
if (seconds < minute) {
value = seconds;

if (appendAgoLabel) {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} second ago', value)
: l10n.t('{0} sec ago', value);
} else {
return useFullTimeWords
? l10n.t('{0} seconds ago', value)
: l10n.t('{0} secs ago', value);
}
} else {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} second', value)
: l10n.t('{0} sec', value);
} else {
return useFullTimeWords
? l10n.t('{0} seconds', value)
: l10n.t('{0} secs', value);
}
}
}

if (seconds < hour) {
value = Math.floor(seconds / minute);
if (appendAgoLabel) {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} minute ago', value)
: l10n.t('{0} min ago', value);
} else {
return useFullTimeWords
? l10n.t('{0} minutes ago', value)
: l10n.t('{0} mins ago', value);
}
} else {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} minute', value)
: l10n.t('{0} min', value);
} else {
return useFullTimeWords
? l10n.t('{0} minutes', value)
: l10n.t('{0} mins', value);
}
}
}

if (seconds < day) {
value = Math.floor(seconds / hour);
if (appendAgoLabel) {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} hour ago', value)
: l10n.t('{0} hr ago', value);
} else {
return useFullTimeWords
? l10n.t('{0} hours ago', value)
: l10n.t('{0} hrs ago', value);
}
} else {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} hour', value)
: l10n.t('{0} hr', value);
} else {
return useFullTimeWords
? l10n.t('{0} hours', value)
: l10n.t('{0} hrs', value);
}
}
}

if (seconds < week) {
value = Math.floor(seconds / day);
if (appendAgoLabel) {
return value === 1
? l10n.t('{0} day ago', value)
: l10n.t('{0} days ago', value);
} else {
return value === 1
? l10n.t('{0} day', value)
: l10n.t('{0} days', value);
}
}

if (seconds < month) {
value = Math.floor(seconds / week);
if (appendAgoLabel) {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} week ago', value)
: l10n.t('{0} wk ago', value);
} else {
return useFullTimeWords
? l10n.t('{0} weeks ago', value)
: l10n.t('{0} wks ago', value);
}
} else {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} week', value)
: l10n.t('{0} wk', value);
} else {
return useFullTimeWords
? l10n.t('{0} weeks', value)
: l10n.t('{0} wks', value);
}
}
}

if (seconds < year) {
value = Math.floor(seconds / month);
if (appendAgoLabel) {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} month ago', value)
: l10n.t('{0} mo ago', value);
} else {
return useFullTimeWords
? l10n.t('{0} months ago', value)
: l10n.t('{0} mos ago', value);
}
} else {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} month', value)
: l10n.t('{0} mo', value);
} else {
return useFullTimeWords
? l10n.t('{0} months', value)
: l10n.t('{0} mos', value);
}
}
}

value = Math.floor(seconds / year);
if (appendAgoLabel) {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} year ago', value)
: l10n.t('{0} yr ago', value);
} else {
return useFullTimeWords
? l10n.t('{0} years ago', value)
: l10n.t('{0} yrs ago', value);
}
} else {
if (value === 1) {
return useFullTimeWords
? l10n.t('{0} year', value)
: l10n.t('{0} yr', value);
} else {
return useFullTimeWords
? l10n.t('{0} years', value)
: l10n.t('{0} yrs', value);
}
}
}
19 changes: 12 additions & 7 deletions extensions/npm/src/features/packageJSONContribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Location } from 'jsonc-parser';

import * as cp from 'child_process';
import { dirname } from 'path';
import { fromNow } from './date';

const LIMIT = 40;

Expand Down Expand Up @@ -215,14 +216,14 @@ export class PackageJSONContribution implements IJSONContribution {
return null;
}

private getDocumentation(description: string | undefined, version: string | undefined, homepage: string | undefined): MarkdownString {
private getDocumentation(description: string | undefined, version: string | undefined, time: string | undefined, homepage: string | undefined): MarkdownString {
const str = new MarkdownString();
if (description) {
str.appendText(description);
}
if (version) {
str.appendText('\n\n');
str.appendText(l10n.t("Latest version: {0}", version));
str.appendText(time ? l10n.t("Latest version: {0} published {1}", version, fromNow(Date.parse(time), true, true)) : l10n.t("Latest version: {0}", version));
}
if (homepage) {
str.appendText('\n\n');
Expand All @@ -241,7 +242,7 @@ export class PackageJSONContribution implements IJSONContribution {

return this.fetchPackageInfo(name, resource).then(info => {
if (info) {
item.documentation = this.getDocumentation(info.description, info.version, info.homepage);
item.documentation = this.getDocumentation(info.description, info.version, info.time, info.homepage);
return item;
}
return null;
Expand Down Expand Up @@ -283,15 +284,17 @@ export class PackageJSONContribution implements IJSONContribution {

private npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise<ViewPackageInfo | undefined> {
return new Promise((resolve, _reject) => {
const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version'];
const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version', 'time'];
const cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined;
cp.execFile(npmCommandPath, args, { cwd }, (error, stdout) => {
if (!error) {
try {
const content = JSON.parse(stdout);
const version = content['dist-tags.latest'] || content['version'];
resolve({
description: content['description'],
version: content['dist-tags.latest'] || content['version'],
version,
time: content.time?.[version],
homepage: content['homepage']
});
return;
Expand All @@ -316,6 +319,7 @@ export class PackageJSONContribution implements IJSONContribution {
return {
description: obj.description || '',
version,
time: obj.time?.[version],
homepage: obj.homepage || ''
};
}
Expand All @@ -334,7 +338,7 @@ export class PackageJSONContribution implements IJSONContribution {
if (typeof pack === 'string') {
return this.fetchPackageInfo(pack, resource).then(info => {
if (info) {
return [this.getDocumentation(info.description, info.version, info.homepage)];
return [this.getDocumentation(info.description, info.version, info.time, info.homepage)];
}
return null;
});
Expand Down Expand Up @@ -363,7 +367,7 @@ export class PackageJSONContribution implements IJSONContribution {
proposal.kind = CompletionItemKind.Property;
proposal.insertText = insertText;
proposal.filterText = JSON.stringify(name);
proposal.documentation = this.getDocumentation(pack.description, pack.version, pack?.links?.homepage);
proposal.documentation = this.getDocumentation(pack.description, pack.version, undefined, pack?.links?.homepage);
collector.add(proposal);
}
}
Expand All @@ -379,5 +383,6 @@ interface SearchPackageInfo {
interface ViewPackageInfo {
description: string;
version?: string;
time?: string;
homepage?: string;
}