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',