From 994d0b2855ca0f961712fc9b0301bd8cd9090fd0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Feb 2019 16:47:40 +0100 Subject: [PATCH] Index color and icon themes --- resources/extension.vsixmanifest | 2 + src/manifest.ts | 12 ++++ src/package.ts | 61 +++++++++++++++- src/test/package.test.ts | 118 ++++++++++++++++++++++++++++++- 4 files changed, 188 insertions(+), 5 deletions(-) diff --git a/resources/extension.vsixmanifest b/resources/extension.vsixmanifest index 0fd2ee2..ce34998 100644 --- a/resources/extension.vsixmanifest +++ b/resources/extension.vsixmanifest @@ -13,6 +13,8 @@ + + <% if (links.repository) { %> diff --git a/src/manifest.ts b/src/manifest.ts index 28105cc..fe17142 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -16,8 +16,20 @@ export interface Localization { translations: Translation[]; } +export interface Theme { + id: string; + path: string; + label?: string; +} + +export interface ColorTheme extends Theme { + uiTheme: string; +} + export interface Contributions { 'localizations'?: Localization[]; + 'themes'?: ColorTheme[]; + 'iconThemes'?: Theme[]; [contributionType: string]: any; } diff --git a/src/package.ts b/src/package.ts index 91596b7..b2813fb 100644 --- a/src/package.ts +++ b/src/package.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as cp from 'child_process'; import * as _ from 'lodash'; import * as yazl from 'yazl'; -import { Manifest } from './manifest'; +import { Manifest, Theme } from './manifest'; import { ITranslations, patchNLS } from './nls'; import * as util from './util'; import * as _glob from 'glob'; @@ -220,7 +220,11 @@ class ManifestProcessor extends BaseProcessor { extensionDependencies: _(manifest.extensionDependencies || []).uniq().join(','), extensionPack: _(manifest.extensionPack || []).uniq().join(','), localizedLanguages: (manifest.contributes && manifest.contributes.localizations) ? - manifest.contributes.localizations.map(loc => loc.localizedLanguageName || loc.languageName || loc.languageId).join(',') : '' + manifest.contributes.localizations.map(loc => loc.localizedLanguageName || loc.languageName || loc.languageId).join(',') : '', + colorThemes: (manifest.contributes && manifest.contributes.themes && manifest.contributes.themes.length > 0) ? + manifest.contributes.themes.map(theme => theme.id).join(',') : '', + iconThemes: (manifest.contributes && manifest.contributes.iconThemes && manifest.contributes.iconThemes.length > 0) ? + manifest.contributes.iconThemes.map(theme => theme.id).join(',') : '' }; if (isGitHub) { @@ -579,6 +583,56 @@ export class NLSProcessor extends BaseProcessor { } } +export class ThemeProcessor extends BaseProcessor { + + private colorThemes: { [path: string]: string } = Object.create(null); + private iconThemes: { [path: string]: string } = Object.create(null); + + constructor(manifest: Manifest) { + super(manifest); + + // Color Themes + if (manifest.contributes && manifest.contributes.themes && manifest.contributes.themes.length > 0) { + this.populateThemes(manifest.contributes.themes, this.colorThemes); + } + + // Icon Themes + if (manifest.contributes && manifest.contributes.iconThemes && manifest.contributes.iconThemes.length > 0) { + this.populateThemes(manifest.contributes.iconThemes, this.iconThemes); + } + } + + private populateThemes(themes: Theme[], themesPathsMap: { [path: string]: string }) { + const themesMap: { [languageId: string]: string; } = Object.create(null); + + // take last reference in the manifest for any given theme + for (const theme of themes) { + themesMap[theme.id] = `extension/${theme.path}`; + } + + // invert the map for later easier retrieval + for (const themeId of Object.keys(themesMap)) { + themesPathsMap[themesMap[themeId]] = themeId; + } + } + + onFile(file: IFile): Promise { + const normalizedPath = util.normalize(file.path); + + const colorTheme = this.colorThemes[normalizedPath]; + if (colorTheme) { + this.assets.push({ type: `Microsoft.VisualStudio.Code.ColorTheme.${colorTheme}`, path: normalizedPath }); + } + + const iconTheme = this.iconThemes[normalizedPath]; + if (iconTheme) { + this.assets.push({ type: `Microsoft.VisualStudio.Code.IconTheme.${iconTheme}`, path: normalizedPath }); + } + + return Promise.resolve(file); + } +} + export function validateManifest(manifest: Manifest): Manifest { validatePublisher(manifest.publisher); validateExtensionName(manifest.name); @@ -779,7 +833,8 @@ export function createDefaultProcessors(manifest: Manifest, options: IPackageOpt new ChangelogProcessor(manifest, options), new LicenseProcessor(manifest), new IconProcessor(manifest), - new NLSProcessor(manifest) + new NLSProcessor(manifest), + new ThemeProcessor(manifest) ]; } diff --git a/src/test/package.test.ts b/src/test/package.test.ts index 4a0ddeb..058c702 100644 --- a/src/test/package.test.ts +++ b/src/test/package.test.ts @@ -683,7 +683,7 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - themes: [{ label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme' }] + themes: [{ label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme', id: 'monokai' }] } }; @@ -718,7 +718,7 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - themes: [{ label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme' }] + themes: [{ label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme', id: 'monokai' }] } }; @@ -730,6 +730,66 @@ describe('toVsixManifest', () => { }); }); + it('should expose color theme contributions as properties', () => { + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + contributes: { + themes: [ + { label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme', id: 'monokai' }, + { label: 'monokai-dark', uiTheme: 'vs-dark', path: 'monokai-dark.tmTheme', id: 'monokai-dark' } + ] + } + }; + + const files = [ + { path: 'extension/monokai.tmTheme', contents: new Buffer('') }, + { path: 'extension/monokai-dark.tmTheme', contents: new Buffer('') }, + ]; + + return _toVsixManifest(manifest, files) + .then(parseXmlManifest) + .then(result => { + const properties = result.PackageManifest.Metadata[0].Properties[0].Property; + const colorThemesProp = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.ColorThemes'); + assert.equal(colorThemesProp.length, 1); + + const colorThemes = colorThemesProp[0].$.Value.split(','); + assert.deepEqual(colorThemes, ['monokai', 'monokai-dark']); + }); + }); + + + it('should expose color theme contributions as assets', () => { + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + contributes: { + themes: [ + { label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme', id: 'monokai' }, + { label: 'monokai-dark', uiTheme: 'vs-dark', path: 'monokai-dark.tmTheme', id: 'monokai-dark' } + ] + } + }; + + const files = [ + { path: 'extension/monokai.tmTheme', contents: new Buffer('') }, + { path: 'extension/monokai-dark.tmTheme', contents: new Buffer('') }, + ]; + + return _toVsixManifest(manifest, files) + .then(parseXmlManifest) + .then(result => { + const assets = result.PackageManifest.Assets[0].Asset; + assert(assets.some(asset => asset.$.Type === 'Microsoft.VisualStudio.Code.ColorTheme.monokai' && asset.$.Path === 'extension/monokai.tmTheme')); + assert(assets.some(asset => asset.$.Type === 'Microsoft.VisualStudio.Code.ColorTheme.monokai-dark' && asset.$.Path === 'extension/monokai-dark.tmTheme')); + }); + }); + it('should automatically add theme tag for icon themes', () => { const manifest = { name: 'test', @@ -768,6 +828,60 @@ describe('toVsixManifest', () => { }); }); + it('should expose icon theme contributions as properties', () => { + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + contributes: { + iconThemes: [ + { label: 'fakeicons', path: 'fake.icons', id: 'fakeicons' } + ] + } + }; + + const files = [ + { path: 'extension/fake.icons', contents: new Buffer('') }, + ]; + + return _toVsixManifest(manifest, files) + .then(parseXmlManifest) + .then(result => { + const properties = result.PackageManifest.Metadata[0].Properties[0].Property; + const iconThemesProp = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.IconThemes'); + assert.equal(iconThemesProp.length, 1); + + const iconThemes = iconThemesProp[0].$.Value.split(','); + assert.deepEqual(iconThemes, ['fakeicons']); + }); + }); + + it('should expose icon theme contributions as assets', () => { + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + contributes: { + iconThemes: [ + { label: 'fakeicons', path: 'fake.icons', id: 'fakeicons' } + ] + } + }; + + const files = [ + { path: 'extension/fake.icons', contents: new Buffer('') }, + ]; + + return _toVsixManifest(manifest, files) + .then(parseXmlManifest) + .then(result => { + const assets = result.PackageManifest.Assets[0].Asset; + assert(assets.some(asset => asset.$.Type === 'Microsoft.VisualStudio.Code.IconTheme.fakeicons' && asset.$.Path === 'extension/fake.icons')); + }); + }); + it('should automatically add language tag with activationEvent', () => { const manifest = { name: 'test',