Skip to content

Commit 17d0f52

Browse files
committed
feat: Improve file conversions
1 parent 1d4bc12 commit 17d0f52

4 files changed

Lines changed: 100 additions & 80 deletions

File tree

package-lock.json

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

package.json

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"capabilities": {
3737
"virtualWorkspaces": {
3838
"supported": true,
39-
"description": "DropEdit and CSON conversion does not work in VSCode Web atm"
39+
"description": "DropEdit and file conversion to CSON/XML do not work in VSCode Web"
4040
},
4141
"untrustedWorkspaces": {
4242
"supported": true,
@@ -642,31 +642,32 @@
642642
"icon": "$(inspect)"
643643
},
644644
{
645-
"command": "extension.convertFileToJSON",
645+
"command": "textmate.convertFileToJSON",
646646
"title": "Convert file to JSON",
647647
"category": "TextMate",
648648
"icon": "assets/TextMate-file-icon.svg"
649649
},
650650
{
651-
"command": "extension.convertFileToYAML",
651+
"command": "textmate.convertFileToYAML",
652652
"title": "Convert file to YAML",
653653
"category": "TextMate",
654654
"icon": "assets/TextMate-file-icon.svg"
655655
},
656656
{
657-
"command": "extension.convertFileToXML",
657+
"command": "textmate.convertFileToXML",
658658
"title": "Convert file to XML",
659659
"category": "TextMate",
660-
"icon": "assets/TextMate-file-icon.svg"
660+
"icon": "assets/TextMate-file-icon.svg",
661+
"enablement": "!isWeb"
661662
},
662663
{
663-
"command": "extension.convertFileToPLIST",
664-
"title": "Convert file to PLIST (ascii)",
664+
"command": "textmate.convertFileToASCII",
665+
"title": "Convert file to ASCII (plist)",
665666
"category": "TextMate",
666667
"icon": "assets/TextMate-file-icon.svg"
667668
},
668669
{
669-
"command": "extension.convertFileToCSON",
670+
"command": "textmate.convertFileToCSON",
670671
"title": "Convert file to CSON",
671672
"category": "TextMate",
672673
"icon": "assets/TextMate-file-icon.svg",
@@ -677,6 +678,7 @@
677678
"dependencies": {
678679
"@syntropiq/libpcre-ts": "file:node_modules_linked/@syntropiq/libpcre-ts",
679680
"cson-parser": "^4.0.9",
681+
"cson2json": "1.0.0",
680682
"date-and-time": "3.6.0",
681683
"oniguruma-parser-cjs": "^0.0.1",
682684
"oniguruma-to-es": "^4.3.3",

src/fileConverter.ts

Lines changed: 69 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@ import * as YAML from 'yaml';
33
import * as XML from 'plist';
44
import * as CSON from 'cson-parser';
55
import * as DATE from 'date-and-time';
6+
import CSON2JSON from 'cson2json';
67

78

89
export function initFileConverter(context: vscode.ExtensionContext) {
910
context.subscriptions.push(
1011
// TODO: use registerTextEditorCommand instead. https://github.com/microsoft/vscode/issues/153164
11-
vscode.commands.registerCommand('extension.convertFileToJSON', async (document: vscode.TextDocument) => await convertFileTo('JSON', document)),
12-
vscode.commands.registerCommand('extension.convertFileToYAML', async (document: vscode.TextDocument) => await convertFileTo('YAML', document)),
13-
vscode.commands.registerCommand('extension.convertFileToXML', async (document: vscode.TextDocument) => await convertFileTo('XML', document)),
14-
vscode.commands.registerCommand('extension.convertFileToPLIST', async (document: vscode.TextDocument) => await convertFileTo('ASCII', document)),
15-
vscode.commands.registerCommand('extension.convertFileToCSON', async (document: vscode.TextDocument) => await convertFileTo('CSON', document)),
12+
vscode.commands.registerCommand('textmate.convertFileToJSON', async (document?: vscode.TextDocument) => await convertFileTo('JSON', document)),
13+
vscode.commands.registerCommand('textmate.convertFileToYAML', async (document?: vscode.TextDocument) => await convertFileTo('YAML', document)),
14+
vscode.commands.registerCommand('textmate.convertFileToXML', async (document?: vscode.TextDocument) => await convertFileTo('XML', document)),
15+
vscode.commands.registerCommand('textmate.convertFileToASCII', async (document?: vscode.TextDocument) => await convertFileTo('ASCII', document)),
16+
vscode.commands.registerCommand('textmate.convertFileToCSON', async (document?: vscode.TextDocument) => await convertFileTo('CSON', document)),
1617
);
1718
}
1819

1920
type Language = 'JSON' | 'YAML' | 'XML' | 'ASCII' | 'CSON';
21+
2022
async function convertFileTo(newLanguage: Language, document?: vscode.TextDocument) {
2123
if (!document) {
2224
const activeTextEditor = vscode.window.activeTextEditor;
@@ -45,15 +47,15 @@ async function convertFileTo(newLanguage: Language, document?: vscode.TextDocume
4547
parsedDocument = parseAsciiPLIST(text);
4648
break;
4749
case 'CSON':
48-
if (!CSON.parse) {
49-
vscode.window.showWarningMessage("TextMate: CSON conversion not available in VSCode Web atm");
50+
if (CSON.parse) {
51+
parsedDocument = CSON.parse(text);
5052
break;
5153
}
52-
parsedDocument = CSON.parse(text);
54+
parsedDocument = CSON2JSON.default(text);
5355
break;
5456
}
5557
} catch (error: any) {
56-
vscode.window.showWarningMessage(`TextMate: Error converting file from ${language}:\n${error.toString()}`);
58+
throw new Error(`TextMate: Error converting file from ${language}:\n${error.toString()}`);
5759
}
5860
// console.log(`parsedDocument: ${language}\n`, parsedDocument);
5961
if (parsedDocument != null) {
@@ -90,24 +92,25 @@ async function convertFileTo(newLanguage: Language, document?: vscode.TextDocume
9092
});
9193
break;
9294
case 'XML':
95+
if (typeof navigator !== 'undefined') {
96+
throw new Error("TextMate: Conversion to XML is not available in VSCode Web atm");
97+
}
9398
newText = XML.build(parsedDocument, { indent: indent });
9499
break;
95100
case 'ASCII':
96101
newText = stringifyAsciiPLIST(parsedDocument, indent);
97102
break;
98103
case 'CSON':
99104
if (!CSON.stringify) {
100-
vscode.window.showWarningMessage("TextMate: CSON conversion not available in VSCode Web atm");
101-
return;
105+
throw new Error("TextMate: Conversion to CSON is not available in VSCode Web atm");
102106
}
103107
newText = CSON.stringify(parsedDocument, undefined, indent);
104108
break;
105109
default:
106110
return;
107111
}
108112
} catch (error: any) {
109-
vscode.window.showWarningMessage(`TextMate: Error converting file to ${newLanguage}:\n${error.toString()}`);
110-
return;
113+
throw new Error(`TextMate: Error converting file to ${newLanguage}:\n${error?.message || error.toString()}`);
111114
}
112115

113116
const newDocument = await vscode.workspace.openTextDocument({ content: newText, language: documentLanguage });
@@ -127,33 +130,36 @@ function rankLanguages(document: vscode.TextDocument): Language[] {
127130
CSON: 0,
128131
};
129132

130-
const documentLanguage = document.languageId;
131-
if (/JSON/i.test(documentLanguage)) {
133+
const languageId = document.languageId;
134+
if (/JSON/i.test(languageId)) {
132135
languageScores.JSON += 10;
133136
}
134-
if (/YA?ML/i.test(documentLanguage)) {
137+
if (/YA?ML/i.test(languageId)) {
135138
languageScores.YAML += 10;
136139
}
137-
if (/XML/i.test(documentLanguage)) {
140+
if (/XML/i.test(languageId)) {
138141
languageScores.XML += 10;
139142
}
140-
if (/ASCII-TEXTMATE/i.test(documentLanguage)) {
143+
if (/ASCII-TEXTMATE/i.test(languageId)) {
141144
languageScores.ASCII += 10;
142145
}
143-
if (/cson|coffeescript/i.test(documentLanguage)) {
146+
if (/cson|coffeescript/i.test(languageId)) {
144147
languageScores.CSON += 10;
145148
}
146149

147150
const fileName = document.fileName;
148151
if (/JSON[CL]?$/i.test(fileName)) {
149152
languageScores.JSON += 6;
150153
}
151-
if (/YA?ML/i.test(fileName)) {
152-
languageScores.YAML += 5;
154+
if (/YA?ML(?:-tmLanguage)?$/i.test(fileName)) {
155+
languageScores.YAML += 6;
153156
}
154157
if (/XML$/i.test(fileName)) {
155158
languageScores.XML += 6;
156159
}
160+
if (/(?:cson|coffeescript)(?:-tmLanguage)?$/i.test(fileName)) {
161+
languageScores.CSON += 6;
162+
}
157163
if (/PLIST$/i.test(fileName)) {
158164
languageScores.ASCII += 5;
159165
}
@@ -164,9 +170,6 @@ function rankLanguages(document: vscode.TextDocument): Language[] {
164170
if (/textmate$/i.test(fileName)) {
165171
languageScores.ASCII += 3;
166172
}
167-
if (/cson$|coffeescript$/i.test(fileName)) {
168-
languageScores.CSON += 6;
169-
}
170173

171174
const text = document.getText();
172175
if (/^\s*{\s*$|^\s*{\s*"/i.test(text)) {
@@ -185,9 +188,12 @@ function rankLanguages(document: vscode.TextDocument): Language[] {
185188
if (/^\s*{\s*['"]?\w+['"]?\s*=/i.test(text)) {
186189
languageScores.ASCII += 3;
187190
}
188-
if (/^\s*#|^\s*\w+:\s/i.test(text)) {
191+
if (/^\s*#|^\s*['"]?\w+['"]?:\s/i.test(text)) {
189192
languageScores.CSON += 3;
190193
}
194+
if (/^\s*\w+:\s+\[/i.test(text)) {
195+
languageScores.CSON += 2;
196+
}
191197

192198
for (const language in languageScores) {
193199
if (languageScores[language as Language] === 0) {
@@ -196,16 +202,16 @@ function rankLanguages(document: vscode.TextDocument): Language[] {
196202
}
197203

198204
const rankedLanguages = (Object.keys(languageScores) as Language[]).sort((a, b) => languageScores[b] - languageScores[a]);
199-
console.log('TextMate: rankedLanguages: ', rankedLanguages, ' ', languageScores);
200-
if (rankLanguages.length === 0) {
201-
vscode.window.showWarningMessage("TextMate: FileConverter:\nCannot determine document's language");
205+
// console.log('TextMate: FileConverter: Ranked Languages: Order: ', rankedLanguages, 'Scores: ', languageScores);
206+
if (rankedLanguages.length === 0) {
207+
throw new Error("TextMate: FileConverter:\nCannot determine document's language");
202208
}
203209
return rankedLanguages;
204210
}
205211

206212

207213

208-
/* == ASCI PLIST == */
214+
/* == ASCII PLIST == */
209215

210216
function stringifyAsciiPLIST(value: Value, space: string = '\t', indent: number = 0, parent?: Value): string {
211217
// https://github.com/textmate/textmate/blob/master/Frameworks/plist/src/to_s.cc
@@ -243,7 +249,7 @@ function stringifyAsciiPLIST(value: Value, space: string = '\t', indent: number
243249
return value.toString();
244250

245251
case 'boolean':
246-
// Apple's custom plist (ascii) boolean
252+
// TextMate2.0's custom boolean
247253
return value ? ':true' : ':false';
248254

249255
case 'object':
@@ -259,36 +265,37 @@ function stringifyAsciiPLIST(value: Value, space: string = '\t', indent: number
259265
}
260266

261267
if (value.length === 1) {
262-
if (value[0] != null) {
263-
const firstValue = value[0];
264-
switch (typeof firstValue) {
265-
case 'object':
266-
if (!(firstValue instanceof Date)) {
267-
break;
268-
}
269-
case 'string':
270-
case 'bigint':
271-
case 'number':
272-
case 'boolean':
273-
return `( ${stringifyAsciiPLIST(firstValue)} )`;
274-
}
268+
const firstValue = value[0];
269+
switch (typeof firstValue) {
270+
case 'object':
271+
if (!(firstValue instanceof Date)) {
272+
break;
273+
}
274+
case 'string':
275+
case 'bigint':
276+
case 'number':
277+
case 'boolean':
278+
if (firstValue == null) {
279+
break;
280+
}
281+
return `( ${stringifyAsciiPLIST(firstValue)} )`;
275282
}
276-
277283
}
278-
return `(${value
279-
.map(element =>
280-
`\n${space.repeat(indent + 1)}${stringifyAsciiPLIST(element, space, indent + 1, value)},`
281-
).join('')
282-
}\n${space.repeat(indent)})`;
284+
const elements = value.map(element => `\n${space.repeat(indent + 1)}${stringifyAsciiPLIST(element, space, indent + 1, value)},`);
285+
return `(${elements.join('')}\n${space.repeat(indent)})`;
283286
}
284287

285-
if (Object.keys(value).length === 0) {
288+
const keys = Object.keys(value);
289+
if (keys.length === 0) {
286290
return '{ }';
287291
}
288-
if (Object.keys(value).length === 1 && typeof Object.values(value)[0] !== 'object') {
289-
return `{ ${stringifyAsciiPLIST(Object.keys(value)[0])} = ${stringifyAsciiPLIST(Object.values(value)[0], space, indent + 1, value)}; }`;
292+
if (keys.length === 1) {
293+
const values = Object.values(value);
294+
if (typeof values[0] !== 'object') {
295+
return `{ ${stringifyAsciiPLIST(keys[0])} = ${stringifyAsciiPLIST(Object.values(value)[0], space, indent + 1, value)}; }`;
296+
}
290297
}
291-
return `{${(Array.isArray(parent) || !parent) ? space.startsWith(' ') ? space.slice(1) || ' ' : space : `\n${space.repeat(indent + 1)}`}${Object.keys(value)
298+
return `{${(!parent || Array.isArray(parent)) ? space.startsWith(' ') ? space.slice(1) || ' ' : space : `\n${space.repeat(indent + 1)}`}${keys
292299
.map(key =>
293300
`${stringifyAsciiPLIST(key)} = ${stringifyAsciiPLIST(value[key], space, indent + 1, value)};`
294301
).join(`\n${space.repeat(indent + 1)}`)
@@ -322,7 +329,7 @@ function nextToken(regex: RegExp, string: string): match {
322329
// const matchKey = Object.keys(match)[matchIndex] as NonNullable<match>['key'];
323330
// const matchValue = Object.values(match)[matchIndex]!;
324331
// return { key: matchKey, value: matchValue };
325-
const matchedGroup = Object.entries(match).find(group => group[1]) as [NonNullable<match>['key'], string];
332+
const matchedGroup = Object.entries(match).find(group => group[1]) as [NonNullable<match>['key'], string] | undefined;
326333
if (matchedGroup === undefined) {
327334
return null;
328335
}
@@ -333,10 +340,10 @@ function parseElement(regex: RegExp, string: string, match: match = nextToken(re
333340
return;
334341
}
335342
switch (match.key) {
336-
// case 'whitespace':
337-
// case 'comment':
338-
// case 'forwardSlash':
339-
// continue;
343+
// case 'whitespace':
344+
// case 'comment':
345+
// case 'forwardSlash':
346+
// continue;
340347

341348
case 'curlyOpen':
342349
const object: Dictionary = {};
@@ -433,7 +440,9 @@ function parseElement(regex: RegExp, string: string, match: match = nextToken(re
433440
// return;
434441
// }
435442
const date = DATE.parse(match.value, '@YYYY-MM-DD HH:mm:ss Z', true);
436-
if (date.getUTCFullYear() < 1970) { }
443+
if (date.getUTCFullYear() < 1970) {
444+
// TextMate2.0 can't handle dates before 1970
445+
}
437446
return date;
438447

439448
case 'curlyClose':
@@ -487,7 +496,7 @@ function parseAsciiPLIST(string: string): Value {
487496
[
488497
/(?<whitespace>[ \t\r\n]+)/,
489498
/(?<comment>\/\/.*$|\/\*.*?\*\/)/,
490-
/(?<forwardSlash>\/(?!\s))/, // for some reason, Apple ignores single forward slashes before any non-whitespace token
499+
/(?<forwardSlash>\/(?!\s))/, // TextMate2.0 doesn't backtrack matching a single forward slash when attempting to match a comment. Effectively ignores a single forward slash before any non-whitespace token
491500
/(?<curlyOpen>{)/,
492501
/(?<curlyClose>})/,
493502
/(?<parenOpen>\()/,

0 commit comments

Comments
 (0)