diff --git a/src/vs/workbench/parts/extensions/common/extensions.ts b/src/vs/workbench/parts/extensions/common/extensions.ts index 179e2640bf89f88ed3dfc5e01209e6f7d0b37a4c..6189209eaf0837ab2d99af29cf1501f39b044ed9 100644 --- a/src/vs/workbench/parts/extensions/common/extensions.ts +++ b/src/vs/workbench/parts/extensions/common/extensions.ts @@ -15,6 +15,7 @@ export interface IExtensionManifest { version: string; displayName?: string; description?: string; + installs: number; } export interface IGalleryInformation { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts index 5ea3b45b94c3f2bdb9edeed008276f2896eb2be5..65ae22ea56f7023e52f3c366180882bcd668cb4a 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts @@ -56,7 +56,7 @@ interface ITemplateData { root: HTMLElement; displayName: HighlightedLabel; version: HTMLElement; - since: HTMLElement; + installs: HTMLElement; author: HTMLElement; actionbar: ActionBar; description: HighlightedLabel; @@ -79,10 +79,11 @@ function extensionEquals(one: IExtension, other: IExtension): boolean { return one.publisher === other.publisher && one.name === other.name; } +/** + * Compare by Install count descending. + */ function extensionEntryCompare(one: IExtensionEntry, other: IExtensionEntry): number { - const oneName = one.extension.displayName || one.extension.name; - const otherName = other.extension.displayName || other.extension.name; - return oneName.localeCompare(otherName); + return other.extension.installs - one.extension.installs; } class OpenInGalleryAction extends Action { @@ -171,19 +172,22 @@ class Renderer implements IRenderer { } renderTemplate(templateId: string, container: HTMLElement): ITemplateData { + // Important to preserve order here. const root = dom.append(container, $('.extension')); const firstRow = dom.append(root, $('.row')); const secondRow = dom.append(root, $('.row')); const published = dom.append(firstRow, $('.published')); - const since = dom.append(published, $('span.since')); + const displayName = new HighlightedLabel(dom.append(firstRow, $('span.name'))); + const installs = dom.append(firstRow, $('span.installs')); + const version = dom.append(published, $('span.version')); const author = dom.append(published, $('span.author')); return { root, author, - since, - displayName: new HighlightedLabel(dom.append(firstRow, $('span.name'))), - version: dom.append(firstRow, $('span.version')), + displayName, + version, + installs, actionbar: new ActionBar(dom.append(secondRow, $('.actions'))), description: new HighlightedLabel(dom.append(secondRow, $('span.description'))), disposables: [] @@ -194,6 +198,7 @@ class Renderer implements IRenderer { const extension = entry.extension; const date = extension.galleryInformation ? extension.galleryInformation.date : null; const publisher = extension.galleryInformation ? extension.galleryInformation.publisherDisplayName : extension.publisher; + const installs = extension.installs; const actionOptions = { icon: true, label: false }; const updateActions = () => { @@ -236,7 +241,7 @@ class Renderer implements IRenderer { data.displayName.set(extension.displayName, entry.highlights.displayName); data.displayName.element.title = extension.name; data.version.textContent = extension.version; - data.since.textContent = date ? since(new Date(date)) : ''; + data.installs.textContent = String(installs); data.author.textContent = publisher; data.description.set(extension.description, entry.highlights.description); data.description.element.title = extension.description; diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css index 18330bfd5cad8d75f94e491e6eea80230ec5e611..264f2e08dd00d4765acabac594935067a91d2fc3 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css @@ -28,12 +28,12 @@ opacity: 0.6; } -.quick-open-widget .extension .version { +.quick-open-widget .extension .installs { font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; font-size: smaller; } -.quick-open-widget .extension .version { +.quick-open-widget .extension .installs { padding-left: 6px; opacity: 0.7; } @@ -44,7 +44,7 @@ font-size: smaller; } -.quick-open-widget .extension .published > .since { +.quick-open-widget .extension .published > .version { opacity: 0.6; margin-right: 0.5em; } diff --git a/src/vs/workbench/parts/extensions/node/extensionsService.ts b/src/vs/workbench/parts/extensions/node/extensionsService.ts index c4a65221686fe463180a24c21dbe1d62d04ad271..ec8c49693ba96dc18305605ca22788d282255b98 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsService.ts @@ -64,7 +64,8 @@ function createExtension(manifest: IExtensionManifest, galleryInformation?: IGal displayName: manifest.displayName || manifest.name, publisher: manifest.publisher, version: manifest.version, - description: manifest.description || '' + description: manifest.description || '', + installs: 0 }; if (galleryInformation) { diff --git a/src/vs/workbench/parts/extensions/node/vsoGalleryService.ts b/src/vs/workbench/parts/extensions/node/vsoGalleryService.ts index dd107ccc3f68d0bdcb7ee8e9732569766ac9f729..e341e1841f8cb90cb41d52f6b3fddcf1655d1d8b 100644 --- a/src/vs/workbench/parts/extensions/node/vsoGalleryService.ts +++ b/src/vs/workbench/parts/extensions/node/vsoGalleryService.ts @@ -33,6 +33,12 @@ export interface IGalleryExtension { publisher: { displayName: string, publisherId: string, publisherName: string; }; versions: IGalleryExtensionVersion[]; galleryApiUrl: string; + statistics: IGalleryExtensionStatistic[]; +} + +export interface IGalleryExtensionStatistic { + statisticName: string; + value: number; } export class GalleryService implements IGalleryService { @@ -53,10 +59,32 @@ export class GalleryService implements IGalleryService { return `${ this.extensionsGalleryUrl }${ path }`; } + /** + * Extracts install count statistic. + */ + private extractInstalls(statistics: IGalleryExtensionStatistic[]): number { + // Sometimes there are no statistics. + if (!statistics) { + return 0; + } + var result = 0; + statistics.forEach(stat => { + if (stat.statisticName === 'install') { + result = stat.value; + } + }) + return result; + } + public isEnabled(): boolean { return !!this.extensionsGalleryUrl; } + /** + * Queries VS Code Extension marketplace for extensions. + * + * Sorts by install count. + */ public query(): TPromise { if (!this.extensionsGalleryUrl) { return TPromise.wrapError(new Error('No extension gallery service configured.')); @@ -69,7 +97,7 @@ export class GalleryService implements IGalleryService { value: 'vscode' }] }], - flags: 0x1 | 0x4 | 0x80 + flags: 0x1 | 0x4 | 0x80 | 0x100 }); const request = { @@ -92,13 +120,14 @@ export class GalleryService implements IGalleryService { publisher: extension.publisher.publisherName, version: extension.versions[0].version, description: extension.shortDescription || '', + installs: this.extractInstalls(extension.statistics), galleryInformation: { galleryApiUrl: this.extensionsGalleryUrl, id: extension.extensionId, downloadUrl: `${ extension.versions[0].assetUri }/Microsoft.VisualStudio.Services.VSIXPackage?install=true`, publisherId: extension.publisher.publisherId, publisherDisplayName: extension.publisher.displayName, - date: extension.versions[0].lastUpdated + date: extension.versions[0].lastUpdated, } })); });