Przeglądaj źródła

First pass at implementing liquid language support

Matt Vague 4 lat temu
rodzic
commit
408ad69850

+ 12 - 0
src/liquid/liquid.contribution.ts

@@ -0,0 +1,12 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { registerLanguage } from '../_.contribution';
+
+registerLanguage({
+	id: 'liquid',
+	extensions: ['.liquid', '.liquid.html', '.liquid.css'],
+	loader: () => import('./liquid')
+});

+ 115 - 0
src/liquid/liquid.test.ts

@@ -0,0 +1,115 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { testTokenization } from '../test/testRunner';
+
+testTokenization(
+	['liquid', 'css'],
+	[
+		// Just HTML
+		[
+			{
+				line: '<h1>liquid!</h1>',
+				tokens: [
+					{ startIndex: 0, type: 'delimiter.html' },
+					{ startIndex: 1, type: 'tag.html' },
+					{ startIndex: 3, type: 'delimiter.html' },
+					{ startIndex: 4, type: '' },
+					{ startIndex: 11, type: 'delimiter.html' },
+					{ startIndex: 13, type: 'tag.html' },
+					{ startIndex: 15, type: 'delimiter.html' }
+				]
+			}
+		],
+
+		// Simple output
+		[
+			{
+				line: '<h1>{{ title }}</h1>',
+				tokens: [
+					{ startIndex: 0, type: 'delimiter.html' },
+					{ startIndex: 1, type: 'tag.html' },
+					{ startIndex: 3, type: 'delimiter.html' },
+					{ startIndex: 4, type: 'delimiter.liquid' },
+					{ startIndex: 6, type: '' },
+					{ startIndex: 7, type: 'variable.liquid' },
+					{ startIndex: 12, type: '' },
+					{ startIndex: 13, type: 'delimiter.liquid' },
+					{ startIndex: 15, type: 'delimiter.html' },
+					{ startIndex: 17, type: 'tag.html' },
+					{ startIndex: 19, type: 'delimiter.html' }
+				]
+			}
+		],
+
+		// // Output filter
+		[
+			{
+				line: '<h1>{{ 3.14159265 | round | default: "pi"  }}</h1>',
+				tokens: [
+					{ startIndex: 0, type: 'delimiter.html' },
+					{ startIndex: 1, type: 'tag.html' },
+					{ startIndex: 3, type: 'delimiter.html' },
+					{ startIndex: 4, type: 'delimiter.liquid' },
+					{ startIndex: 6, type: '' },
+					{ startIndex: 7, type: 'number.liquid' },
+					{ startIndex: 17, type: '' },
+					{ startIndex: 20, type: 'variable.liquid' },
+					{ startIndex: 25, type: '' },
+					{ startIndex: 28, type: 'variable.liquid' },
+					{ startIndex: 36, type: '' },
+					{ startIndex: 37, type: 'string.liquid' },
+					{ startIndex: 41, type: '' },
+					{ startIndex: 43, type: 'delimiter.liquid' },
+					{ startIndex: 45, type: 'delimiter.html' },
+					{ startIndex: 47, type: 'tag.html' },
+					{ startIndex: 49, type: 'delimiter.html' }
+				]
+			}
+		],
+
+		// Tag
+		[
+			{
+				line: '<div>{% render "files/file123.html" %}</div>',
+				tokens: [
+					{ startIndex: 0, type: 'delimiter.html' },
+					{ startIndex: 1, type: 'tag.html' },
+					{ startIndex: 4, type: 'delimiter.html' },
+					{ startIndex: 5, type: 'delimiter.output.liquid' },
+					{ startIndex: 7, type: '' },
+					{ startIndex: 8, type: 'variable.liquid' },
+					{ startIndex: 14, type: '' },
+					{ startIndex: 15, type: 'string.liquid' },
+					{ startIndex: 35, type: '' },
+					{ startIndex: 36, type: 'delimiter.liquid' },
+					{ startIndex: 38, type: 'delimiter.html' },
+					{ startIndex: 40, type: 'tag.html' },
+					{ startIndex: 43, type: 'delimiter.html' }
+				]
+			}
+		],
+
+		// Handlebars comment
+		[
+			{
+				line: '<div>Anything you put between {% comment %} and {% endcomment %} tags</div>',
+				tokens: [
+					{ startIndex: 0, type: 'delimiter.html' },
+					{ startIndex: 1, type: 'tag.html' },
+					{ startIndex: 4, type: 'delimiter.html' },
+					{ startIndex: 5, type: '' },
+					{ startIndex: 30, type: 'comment.start.liquid' },
+					{ startIndex: 43, type: 'comment.content.liquid' },
+					{ startIndex: 48, type: 'comment.end.liquid' },
+					{ startIndex: 64, type: '' },
+					{ startIndex: 69, type: 'delimiter.html' },
+					{ startIndex: 71, type: 'tag.html' },
+					{ startIndex: 74, type: 'delimiter.html' }
+				]
+			}
+		]
+	]
+);

+ 148 - 0
src/liquid/liquid.ts

@@ -0,0 +1,148 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { languages } from '../fillers/monaco-editor-core';
+
+const EMPTY_ELEMENTS: string[] = [
+	'area',
+	'base',
+	'br',
+	'col',
+	'embed',
+	'hr',
+	'img',
+	'input',
+	'keygen',
+	'link',
+	'menuitem',
+	'meta',
+	'param',
+	'source',
+	'track',
+	'wbr'
+];
+
+export const conf: languages.LanguageConfiguration = {
+	wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
+
+	// comments: {
+	// 	blockComment: ['{{!--', '--}}']
+	// },
+
+	brackets: [
+		['<!--', '-->'],
+		['<', '>'],
+		['{{', '}}'],
+		['{%', '%}'],
+		['{', '}'],
+		['(', ')']
+	],
+
+	autoClosingPairs: [
+		{ open: '{', close: '}' },
+		{ open: '%', close: '%' },
+		{ open: '[', close: ']' },
+		{ open: '(', close: ')' },
+		{ open: '"', close: '"' },
+		{ open: "'", close: "'" }
+	],
+
+	surroundingPairs: [
+		{ open: '<', close: '>' },
+		{ open: '"', close: '"' },
+		{ open: "'", close: "'" }
+	],
+
+	onEnterRules: [
+		{
+			beforeText: new RegExp(
+				`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`,
+				'i'
+			),
+			afterText: /^<\/(\w[\w\d]*)\s*>$/i,
+			action: {
+				indentAction: languages.IndentAction.IndentOutdent
+			}
+		},
+		{
+			beforeText: new RegExp(
+				`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`,
+				'i'
+			),
+			action: { indentAction: languages.IndentAction.Indent }
+		}
+	]
+};
+
+export const language = <languages.IMonarchLanguage>{
+	defaultToken: '',
+	tokenPostfix: '',
+	// ignoreCase: true,
+
+	// The main tokenizer for our languages
+	tokenizer: {
+		root: [
+			[/\{\%\s*comment\s*\%\}/, 'comment.start.liquid', '@comment'],
+			[/\{\{/, { token: '@rematch', switchTo: '@liquidInSimpleState.root' }],
+			[/\{\%/, { token: '@rematch', switchTo: '@liquidInSimpleState.root' }],
+			[/(<)(\w+)(\/>)/, ['delimiter.html', 'tag.html', 'delimiter.html']],
+			[/(<)([:\w]+)/, ['delimiter.html', { token: 'tag.html', next: '@otherTag' }]],
+			[/(<\/)(\w+)/, ['delimiter.html', { token: 'tag.html', next: '@otherTag' }]],
+			[/</, 'delimiter.html'],
+			[/\{/, 'delimiter.html'],
+			[/[^<{]+/] // text
+		],
+
+		comment: [
+			[/\{\%\s*endcomment\s*\%\}/, 'comment.end.liquid', '@pop'],
+			[/./, 'comment.content.liquid']
+		],
+
+		otherTag: [
+			[
+				/\{\{/,
+				{
+					token: '@rematch',
+					switchTo: '@liquidInSimpleState.otherTag'
+				}
+			],
+			[
+				/\{%/,
+				{
+					token: '@rematch',
+					switchTo: '@liquidInSimpleState.otherTag'
+				}
+			],
+			[/\/?>/, 'delimiter.html', '@pop'],
+			[/"([^"]*)"/, 'attribute.value'],
+			[/'([^']*)'/, 'attribute.value'],
+			[/[\w\-]+/, 'attribute.name'],
+			[/=/, 'delimiter'],
+			[/[ \t\r\n]+/] // whitespace
+		],
+
+		liquidInSimpleState: [
+			[/\{\{/, 'delimiter.liquid'],
+			[/\}\}/, { token: 'delimiter.liquid', switchTo: '@$S2.$S3' }],
+			[/\{\%/, 'delimiter.output.liquid'],
+			[/\%\}/, { token: 'delimiter.liquid', switchTo: '@$S2.$S3' }],
+			{ include: 'liquidRoot' }
+		],
+
+		liquidInTagState: [
+			[/%\}/, { token: 'delimiter.output.liquid', switchTo: '@$S2.$S3' }],
+			// { include: 'liquidRoot' },
+			[/[^%]/, 'wut']
+		],
+
+		liquidRoot: [
+			[/\d+(\.\d+)?/, 'number.liquid'],
+			[/"[^"]*"/, 'string.liquid'],
+			[/'[^']*'/, 'string.liquid'],
+			[/[\s]+/],
+			[/[^}|%]/, 'variable.liquid']
+		]
+	}
+};

+ 1 - 0
src/monaco.contribution.ts

@@ -31,6 +31,7 @@ import './kotlin/kotlin.contribution';
 import './less/less.contribution';
 import './lexon/lexon.contribution';
 import './lua/lua.contribution';
+import './liquid/liquid.contribution';
 import './m3/m3.contribution';
 import './markdown/markdown.contribution';
 import './mips/mips.contribution';