Skip to content

Commit 14f6f36

Browse files
authored
Merge pull request #41 from andrewbranch/bug/xml
Support themes published in plist format
2 parents b558f75 + cf093d1 commit 14f6f36

22 files changed

+944
-505
lines changed

package-lock.json

Lines changed: 36 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"loglevel": "^1.6.3",
5959
"oniguruma": "^7.2.0",
6060
"plist": "^3.0.1",
61+
"rimraf": "^3.0.0",
6162
"unist-util-visit": "^1.4.0",
6263
"vscode-textmate": "^4.1.0"
6364
},

scripts/scrapeBuiltinExtensions.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const util = require('util');
44
const glob = require('glob');
55
const path = require('path');
66
const processExtension = require('../src/processExtension');
7-
const { requireGrammar } = require('../src/utils');
7+
const { requirePlistOrJson } = require('../src/utils');
88

99
const copyFile = util.promisify(fs.copyFile);
1010
const writeFile = util.promisify(fs.writeFile);
@@ -30,13 +30,13 @@ glob(path.resolve(__dirname, '../vscode/extensions/*/package.json'), async (err,
3030
...await newManifestPromise,
3131
...await Object.keys(extension.grammars).reduce(async (newManifestPromise, scopeName) => {
3232
const grammar = extension.grammars[scopeName];
33-
const content = await requireGrammar(grammar.path);
33+
const content = await requirePlistOrJson(grammar.path);
3434
let destPath = grammarPath(path.basename(grammar.path));
3535
const newContent = processTmJson(content);
3636
// Different languages are sometimes named the same thing
3737
languageId++;
3838
if (await exists(destPath)) {
39-
const existingGrammar = await requireGrammar(destPath);
39+
const existingGrammar = await requirePlistOrJson(destPath);
4040
if (existingGrammar.scopeName !== scopeName) {
4141
const [baseName, ...ext] = path.basename(grammar.path).split('.');
4242
const renamed = [baseName, languageId, ...ext].join('.');

src/downloadExtension.js

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ const fs = require('fs');
33
const path = require('path');
44
const zlib = require('zlib');
55
const util = require('util');
6-
const request = require('request');
7-
const decompress = require('decompress');
86
const processExtension = require('./processExtension');
97
const { highestBuiltinLanguageId } = require('./storeUtils');
108
const {
@@ -42,34 +40,23 @@ async function syncExtensionData({ identifier }, cache, extensionDir) {
4240
* @param {import('.').ExtensionDemand} extensionDemand
4341
* @param {*} cache
4442
* @param {string} extensionDir
43+
* @param {import('./host').Host} host
4544
*/
46-
async function downloadExtension(extensionDemand, cache, extensionDir) {
45+
async function downloadExtension(extensionDemand, cache, extensionDir, host) {
4746
const { identifier, version } = extensionDemand;
4847
const { publisher, name } = parseExtensionIdentifier(identifier);
4948
const url = `https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher}/vsextensions/${name}/${version}/vspackage`;
50-
const archive = await new Promise((resolve, reject) => {
51-
request.get(url, { encoding: null }, (error, res, body) => {
52-
if (error) {
53-
return reject(error);
54-
}
55-
if (res.statusCode === 404) {
56-
return reject(
57-
new Error(`Could not find extension with publisher '${publisher}', name '${name}', and verion '${version}'.`)
58-
);
59-
}
60-
if (res.statusCode !== 200) {
61-
return reject(new Error(`Failed to download extension ${identifier} with status code ${res.statusCode}`));
62-
}
63-
if (res.headers['content-encoding'] === 'gzip') {
64-
return gunzip(body).then(resolve, reject);
65-
}
66-
67-
resolve(body);
68-
});
69-
});
49+
const response = await host.fetch(url, { encoding: null });
50+
if (response.statusCode === 404) {
51+
throw new Error(`Could not find extension with publisher '${publisher}', name '${name}', and verion '${version}'.`);
52+
}
53+
if (response.statusCode !== 200) {
54+
const details = response.body ? `. Response body:\n\n${response.body.toString('utf8')}` : '';
55+
throw new Error(`Failed to download extension ${identifier} with status code ${response.statusCode}${details}`);
56+
}
7057

7158
const extensionPath = getExtensionBasePath(identifier, extensionDir);
72-
await decompress(archive, extensionPath);
59+
await host.decompress(response.body, extensionPath);
7360
await syncExtensionData(extensionDemand, cache, extensionDir);
7461
return extensionPath;
7562
}
@@ -79,24 +66,25 @@ async function downloadExtension(extensionDemand, cache, extensionDir) {
7966
* @property {import('.').ExtensionDemand[]} extensions
8067
* @property {*} cache
8168
* @property {string} extensionDir
69+
* @property {import('./host').Host} host
8270
*/
8371

8472
/**
8573
* @param {DownloadExtensionOptions} options
8674
*/
87-
async function downloadExtensionsIfNeeded({ extensions, cache, extensionDir }) {
75+
async function downloadExtensionsIfNeeded({ extensions, cache, extensionDir, host }) {
8876
extensions = extensions.slice();
8977
while (extensions.length) {
9078
const extensionDemand = extensions.shift();
9179
const { identifier, version } = extensionDemand;
9280
const extensionPath = getExtensionBasePath(identifier, extensionDir);
9381
if (!fs.existsSync(extensionPath)) {
94-
await downloadExtension(extensionDemand, cache, extensionDir);
82+
await downloadExtension(extensionDemand, cache, extensionDir, host);
9583
continue;
9684
}
9785
const packageJson = getExtensionPackageJson(identifier, extensionDir);
9886
if (packageJson.version !== version) {
99-
await downloadExtension(extensionDemand, cache, extensionDir);
87+
await downloadExtension(extensionDemand, cache, extensionDir, host);
10088
continue;
10189
}
10290

src/host.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const request = require('request');
2+
const decompress = require('decompress');
3+
const zlib = require('zlib');
4+
const util = require('util');
5+
const gunzip = util.promisify(zlib.gunzip);
6+
7+
/**
8+
* @typedef {object} Response
9+
* @property {Buffer | undefined} body
10+
* @property {number} statusCode
11+
*/
12+
13+
/**
14+
* @typedef {object} Host
15+
* @property {(url: string, options: request.CoreOptions) => Promise<Response>} fetch
16+
* @property {(input: string | Buffer, output: string) => Promise<unknown>} decompress
17+
*/
18+
19+
/** @type {Host} */
20+
const host = {
21+
fetch: (url, options) =>
22+
new Promise((resolve, reject) => {
23+
request.get(url, options, async (error, res, body) => {
24+
if (error) {
25+
return reject(error);
26+
}
27+
if (res.statusCode !== 200) {
28+
return resolve({ body, statusCode: res.statusCode });
29+
}
30+
if (res.headers['content-encoding'] === 'gzip') {
31+
const unzipped = await gunzip(body);
32+
return resolve({ body: unzipped, statusCode: res.statusCode });
33+
}
34+
35+
resolve({ body, statusCode: res.statusCode });
36+
});
37+
}),
38+
39+
decompress
40+
};
41+
42+
module.exports = host;

src/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const fs = require('fs');
33
const path = require('path');
44
const logger = require('loglevel');
5+
const defaultHost = require('./host');
56
const visit = require('unist-util-visit');
67
const escapeHTML = require('lodash.escape');
78
const lineHighlighting = require('./lineHighlighting');
@@ -122,6 +123,7 @@ function getStylesFromSettings(settings) {
122123
* @property {(colorValue: string, theme: string) => string=} replaceColor
123124
* @property {string=} extensionDataDirectory
124125
* @property {'trace' | 'debug' | 'info' | 'warn' | 'error'=} logLevel
126+
* @property {import('./host').Host=} host
125127
*/
126128

127129
function createPlugin() {
@@ -143,7 +145,8 @@ function createPlugin() {
143145
injectStyles = true,
144146
replaceColor = x => x,
145147
extensionDataDirectory = path.resolve(__dirname, '../lib/extensions'),
146-
logLevel = 'error'
148+
logLevel = 'error',
149+
host = defaultHost
147150
} = {}
148151
) {
149152
logger.setLevel(logLevel);
@@ -162,7 +165,8 @@ function createPlugin() {
162165
await downloadExtensionsIfNeeded({
163166
extensions,
164167
cache,
165-
extensionDir: extensionDataDirectory
168+
extensionDir: extensionDataDirectory,
169+
host
166170
});
167171

168172
const grammarCache = await cache.get('grammars');

src/processExtension.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @ts-check
22
const path = require('path');
3-
const { getLanguageNames, requireJson, requireGrammar } = require('./utils');
3+
const { getLanguageNames, requireJson, requirePlistOrJson } = require('./utils');
44

55
async function processExtension(packageJsonPath) {
66
const packageJson = requireJson(packageJsonPath);
@@ -10,7 +10,7 @@ async function processExtension(packageJsonPath) {
1010
const manifest = await Promise.all(
1111
packageJson.contributes.grammars.map(async grammar => {
1212
const sourcePath = path.resolve(path.dirname(packageJsonPath), grammar.path);
13-
const content = await requireGrammar(sourcePath);
13+
const content = await requirePlistOrJson(sourcePath);
1414
const { scopeName } = content;
1515
const languageRegistration = packageJson.contributes.languages.find(l => l.id === grammar.language);
1616

@@ -44,7 +44,7 @@ async function processExtension(packageJsonPath) {
4444
const manifest = await Promise.all(
4545
packageJson.contributes.themes.map(async theme => {
4646
const sourcePath = path.resolve(path.dirname(packageJsonPath), theme.path);
47-
const themeContents = requireJson(sourcePath);
47+
const themeContents = await requirePlistOrJson(sourcePath);
4848
return {
4949
id: theme.id || path.basename(theme.path).split('.')[0],
5050
path: sourcePath,

src/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function sanitizeForClassName(str) {
7272

7373
const readFile = util.promisify(fs.readFile);
7474
const requireJson = /** @param {string} pathName */ pathName => JSON5.parse(fs.readFileSync(pathName, 'utf8'));
75-
const requireGrammar = /** @param {string} pathName */ async pathName =>
75+
const requirePlistOrJson = /** @param {string} pathName */ async pathName =>
7676
path.extname(pathName) === '.json' ? requireJson(pathName) : plist.parse(await readFile(pathName, 'utf8'));
7777

7878
module.exports = {
@@ -83,5 +83,5 @@ module.exports = {
8383
getLanguageNames,
8484
sanitizeForClassName,
8585
requireJson,
86-
requireGrammar
86+
requirePlistOrJson
8787
};

test/custom.tmLanguage.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)