From 0ae8dbb0fe017cfb8321307e5bfe5959eb121754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=9D=CE=99=CE=9A=CE=9F=CE=9B=CE=91=CE=A3?= Date: Tue, 28 Mar 2023 11:51:10 +0200 Subject: [PATCH] fix: Improves Liquid Support (#5098) * Improve liquid grammar support Brings support for Shopify Liquid Variation embedded code blocks which include: - {% schema %} - {% javascript %} - {% style %} - {% stylesheet %} In addition, line comment highlighting is now provided. * Include the additional modes * add additional context and syntax samples * purge the mode test file (not really relevant anymore) * align behaviour for delimiter matches * fix lookbehind expressions * restore test for liquid mode --------- Co-authored-by: nightwing --- demo/kitchen-sink/docs/liquid.liquid | 57 ++- src/mode/_test/highlight_rules_test.js | 2 +- src/mode/_test/tokens_liquid.json | 570 +++++++++++++++++-------- src/mode/behaviour/liquid.js | 24 +- src/mode/liquid.js | 39 +- src/mode/liquid_highlight_rules.js | 347 +++++++++++---- 6 files changed, 755 insertions(+), 284 deletions(-) diff --git a/demo/kitchen-sink/docs/liquid.liquid b/demo/kitchen-sink/docs/liquid.liquid index 29c0b016551..a06a7a7f4e2 100644 --- a/demo/kitchen-sink/docs/liquid.liquid +++ b/demo/kitchen-sink/docs/liquid.liquid @@ -1,4 +1,8 @@ -The following examples can be found in full at http://liquidmarkup.org/ +There are a couple of different Liquid variations in circulation. This grammars supports +both the Standard and Shopify Liquid variations. The following examples can be found in full at: + +Standard Variation: https://shopify.github.io/liquid +Shopify Variation: https://shopify.dev/docs/api/liquid Liquid is an extraction from the e-commerce system Shopify. Shopify powers many thousands of e-commerce stores which all call for unique designs. @@ -37,11 +41,18 @@ Some more features include:

If

- {% if user.name == 'tobi' or user.name == 'marc' %} + {% if user.name == 'tobi' or user.name == 'marc' %} hi marc or tobi {% endif %}

+

Comments

+ +{% # Line Comment %} + +{% comment %} + Block Comment +{% endcomment %}

Case

@@ -58,7 +69,7 @@ Some more features include:

For Loops

- {% for item in array %} + {% for item in array %} {{ item }} {% endfor %}

@@ -74,3 +85,43 @@ Some more features include: {% endif %} {% endtablerow %}

+ +

Embedded Code Blocks

+ +This support Shopify Liquid variation JSON schema code blocks + +{% schema %} + { + "string": "bar", + "boolean": true, + "number": 100, + "object": { + "array": [100, false, "string", {}, [] ] + } + } +{% endschema %} + +This support Shopify Liquid variation Stylesheet and Style code blocks + +{% style %} + .class { + font-size: 10px; /* comment */ + } +{% endstyle %} + +{% stylesheet %} + .class { + font-size: 10px; /* comment */ + } +{% endstylesheet %} + +This support Shopify Liquid variation JavaScript code blocks + +{% javascript %} + + function foo (param) { + + return 'something' // comment + } + +{% endjavascript %} diff --git a/src/mode/_test/highlight_rules_test.js b/src/mode/_test/highlight_rules_test.js index e8940f99189..ccb4faf6839 100644 --- a/src/mode/_test/highlight_rules_test.js +++ b/src/mode/_test/highlight_rules_test.js @@ -13,7 +13,7 @@ if (!fs.existsSync) require("amd-loader"); var cwd = __dirname + "/"; -var root = path.normalize(cwd + Array(5).join("../")); +var root = path.normalize(cwd + Array(4).join("../")); function jsFileList(path, filter) { if (!filter) filter = /_test/; diff --git a/src/mode/_test/tokens_liquid.json b/src/mode/_test/tokens_liquid.json index f745a156aa4..cb92f661549 100644 --- a/src/mode/_test/tokens_liquid.json +++ b/src/mode/_test/tokens_liquid.json @@ -1,6 +1,17 @@ [[ "start", - ["text.xml","The following examples can be found in full at http://liquidmarkup.org/"] + ["text.xml","There are a couple of different Liquid variations in circulation. This grammars supports"] +],[ + "start", + ["text.xml","both the Standard and Shopify Liquid variations. The following examples can be found in full at:"] +],[ + "start" +],[ + "start", + ["text.xml","Standard Variation: https://shopify.github.io/liquid"] +],[ + "start", + ["text.xml","Shopify Variation: https://shopify.dev/docs/api/liquid"] ],[ "start" ],[ @@ -51,17 +62,12 @@ ],[ "start", ["text.xml"," "], - ["variable","{%"], - ["text"," "], - ["keyword.block","for"], - ["text"," "], - ["identifier","product"], - ["text"," "], - ["keyword","in"], - ["text"," "], - ["identifier","products"], - ["text"," "], - ["variable","%}"] + ["meta.tag.punctuation.tag-open","{%"], + ["keyword.block"," for"], + ["text"," product"], + ["keyword.operator"," in "], + ["text","products "], + ["meta.tag.punctuation.tag-close","%}"] ],[ "start", ["text.xml"," "], @@ -74,28 +80,29 @@ ["meta.tag.punctuation.tag-open.xml","<"], ["meta.tag.tag-name.xml","h2"], ["meta.tag.punctuation.tag-close.xml",">"], - ["variable","{{"], + ["meta.tag.punctuation.ouput-open","{{"], ["text"," "], - ["identifier","product"], - ["text","."], - ["identifier","title"], + ["support.class","product"], + ["keyword.operator","."], + ["support.object","title"], ["text"," "], - ["variable","}}"], + ["meta.tag.punctuation.ouput-close","}}"], ["meta.tag.punctuation.end-tag-open.xml",""] ],[ "start", ["text.xml"," Only "], - ["variable","{{"], + ["meta.tag.punctuation.ouput-open","{{"], + ["text"," "], + ["support.class","product"], + ["keyword.operator","."], + ["support.object","price"], ["text"," "], - ["identifier","product"], - ["text","."], - ["identifier","price"], - ["text"," | "], - ["identifier","format_as_money"], + ["keyword.operator","| "], + ["support.function","format_as_money"], ["text"," "], - ["variable","}}"] + ["meta.tag.punctuation.ouput-close","}}"] ],[ "start" ],[ @@ -104,19 +111,21 @@ ["meta.tag.punctuation.tag-open.xml","<"], ["meta.tag.tag-name.xml","p"], ["meta.tag.punctuation.tag-close.xml",">"], - ["variable","{{"], - ["text"," "], - ["identifier","product"], - ["text","."], - ["identifier","description"], - ["text"," | "], - ["identifier","prettyprint"], - ["text"," | "], + ["meta.tag.punctuation.ouput-open","{{"], + ["text"," "], + ["support.class","product"], + ["keyword.operator","."], + ["support.object","description"], + ["text"," "], + ["keyword.operator","| "], + ["support.function","prettyprint"], + ["text"," "], + ["keyword.operator","| "], ["support.function","truncate"], ["text",": "], ["constant.numeric","200"], ["text"," "], - ["variable","}}"], + ["meta.tag.punctuation.ouput-close","}}"], ["meta.tag.punctuation.end-tag-open.xml",""] @@ -131,11 +140,10 @@ ],[ "start", ["text.xml"," "], - ["variable","{%"], - ["text"," "], - ["keyword","endfor"], + ["meta.tag.punctuation.tag-open","{%"], + ["keyword.block"," endfor"], ["text"," "], - ["variable","%}"] + ["meta.tag.punctuation.tag-close","%}"] ],[ "start", ["text.xml"," "], @@ -166,13 +174,14 @@ ["meta.tag.tag-name.xml","p"], ["meta.tag.punctuation.tag-close.xml",">"], ["text.xml"," The word \"tobi\" in uppercase: "], - ["variable","{{"], + ["meta.tag.punctuation.ouput-open","{{"], ["text"," "], ["string","'tobi'"], - ["text"," | "], + ["text"," "], + ["keyword.operator","| "], ["support.function","upcase"], ["text"," "], - ["variable","}}"], + ["meta.tag.punctuation.ouput-close","}}"], ["text.xml"," "], ["meta.tag.punctuation.end-tag-open.xml",""], ["text.xml","The word \"tobi\" has "], - ["variable","{{"], + ["meta.tag.punctuation.ouput-open","{{"], ["text"," "], ["string","'tobi'"], - ["text"," | "], + ["text"," "], + ["keyword.operator","| "], ["support.function","size"], ["text"," "], - ["variable","}}"], + ["meta.tag.punctuation.ouput-close","}}"], ["text.xml"," letters! "], ["meta.tag.punctuation.end-tag-open.xml",""], ["text.xml","Change \"Hello world\" to \"Hi world\": "], - ["variable","{{"], + ["meta.tag.punctuation.ouput-open","{{"], ["text"," "], ["string","'Hello world'"], - ["text"," | "], + ["text"," "], + ["keyword.operator","| "], ["support.function","replace"], ["text",": "], ["string","'Hello'"], ["text",", "], ["string","'Hi'"], ["text"," "], - ["variable","}}"], + ["meta.tag.punctuation.ouput-close","}}"], ["text.xml"," "], ["meta.tag.punctuation.end-tag-open.xml",""], ["text.xml","The date today is "], - ["variable","{{"], + ["meta.tag.punctuation.ouput-open","{{"], ["text"," "], ["string","'now'"], - ["text"," | "], + ["text"," "], + ["keyword.operator","| "], ["support.function","date"], ["text",": "], ["string","\"%Y %b %d\""], ["text"," "], - ["variable","}}"], + ["meta.tag.punctuation.ouput-close","}}"], ["text.xml"," "], ["meta.tag.punctuation.end-tag-open.xml",""] ],[ "start" +],[ + "start", + ["meta.tag.punctuation.tag-open.xml","<"], + ["meta.tag.tag-name.xml","h2"], + ["meta.tag.punctuation.tag-close.xml",">"], + ["text.xml","Comments"], + ["meta.tag.punctuation.end-tag-open.xml",""] +],[ + "start" +],[ + "start", + ["comment.line","{% #"], + ["comment"," Line Comment "], + ["comment.line","%}"] +],[ + "start" +],[ + "comment.block", + ["comment.block","{% comment %}"] +],[ + "comment.block", + ["comment"," Block Comment"] +],[ + "start", + ["comment.block","{% endcomment %}"] ],[ "start" ],[ @@ -316,80 +350,72 @@ ],[ "start", ["text.xml"," "], - ["variable","{%"], - ["text"," "], - ["keyword.block","case"], - ["text"," "], - ["identifier","template"], - ["text"," "], - ["variable","%}"] + ["meta.tag.punctuation.tag-open","{%"], + ["keyword.block"," case"], + ["text"," template "], + ["meta.tag.punctuation.tag-close","%}"] ],[ "start", ["text.xml"," "], - ["variable","{%"], - ["text"," "], - ["keyword","when"], + ["meta.tag.punctuation.tag-open","{%"], + ["keyword.block"," when"], ["text"," "], ["string","'index'"], ["text"," "], - ["variable","%}"] + ["meta.tag.punctuation.tag-close","%}"] ],[ "start", ["text.xml"," Welcome"] ],[ "start", ["text.xml"," "], - ["variable","{%"], - ["text"," "], - ["keyword","when"], + ["meta.tag.punctuation.tag-open","{%"], + ["keyword.block"," when"], ["text"," "], ["string","'product'"], ["text"," "], - ["variable","%}"] + ["meta.tag.punctuation.tag-close","%}"] ],[ "start", ["text.xml"," "], - ["variable","{{"], + ["meta.tag.punctuation.ouput-open","{{"], + ["text"," "], + ["support.class","product"], + ["keyword.operator","."], + ["support.object","vendor"], ["text"," "], - ["identifier","product"], - ["text","."], - ["identifier","vendor"], - ["text"," | "], - ["identifier","link_to_vendor"], + ["keyword.operator","| "], + ["support.function","link_to_vendor"], ["text"," "], - ["variable","}}"], + ["meta.tag.punctuation.ouput-close","}}"], ["text.xml"," / "], - ["variable","{{"], + ["meta.tag.punctuation.ouput-open","{{"], ["text"," "], - ["identifier","product"], - ["text","."], - ["identifier","title"], + ["support.class","product"], + ["keyword.operator","."], + ["support.object","title"], ["text"," "], - ["variable","}}"] + ["meta.tag.punctuation.ouput-close","}}"] ],[ "start", ["text.xml"," "], - ["variable","{%"], - ["text"," "], - ["keyword","else"], + ["meta.tag.punctuation.tag-open","{%"], + ["keyword.block"," else"], ["text"," "], - ["variable","%}"] + ["meta.tag.punctuation.tag-close","%}"] ],[ "start", ["text.xml"," "], - ["variable","{{"], - ["text"," "], - ["identifier","page_title"], - ["text"," "], - ["variable","}}"] + ["meta.tag.punctuation.ouput-open","{{"], + ["text"," page_title "], + ["meta.tag.punctuation.ouput-close","}}"] ],[ "start", ["text.xml"," "], - ["variable","{%"], - ["text"," "], - ["keyword","endcase"], + ["meta.tag.punctuation.tag-open","{%"], + ["keyword.block"," endcase"], ["text"," "], - ["variable","%}"] + ["meta.tag.punctuation.tag-close","%}"] ],[ "start", ["meta.tag.punctuation.end-tag-open.xml",""] ],[ "start" +],[ + "start", + ["meta.tag.punctuation.tag-open.xml","<"], + ["meta.tag.tag-name.xml","h2"], + ["meta.tag.punctuation.tag-close.xml",">"], + ["text.xml","Embedded Code Blocks"], + ["meta.tag.punctuation.end-tag-open.xml",""] +],[ + "start" +],[ + "start", + ["text.xml","This support Shopify Liquid variation JSON schema code blocks"] +],[ + "start" +],[ + "schema-start", + ["meta.tag.punctuation.tag-open","{%"], + ["text"," "], + ["keyword.tagschema.tag-name","schema"], + ["text"," "], + ["meta.tag.punctuation.tag-close","%}"] +],[ + "schema-start", + ["text"," "], + ["paren.lparen","{"] +],[ + "schema-start", + ["text"," "], + ["variable","\"string\""], + ["text",": "], + ["string","\"bar\""], + ["punctuation.operator",","] +],[ + "schema-start", + ["text"," "], + ["variable","\"boolean\""], + ["text",": "], + ["constant.language.boolean","true"], + ["punctuation.operator",","] +],[ + "schema-start", + ["text"," "], + ["variable","\"number\""], + ["text",": "], + ["constant.numeric","100"], + ["punctuation.operator",","] +],[ + "schema-start", + ["text"," "], + ["variable","\"object\""], + ["text",": "], + ["paren.lparen","{"] +],[ + "schema-start", + ["text"," "], + ["variable","\"array\""], + ["text",": "], + ["paren.lparen","["], + ["constant.numeric","100"], + ["punctuation.operator",","], + ["text"," "], + ["constant.language.boolean","false"], + ["punctuation.operator",","], + ["text"," "], + ["string","\"string\""], + ["punctuation.operator",","], + ["text"," "], + ["paren.lparen","{"], + ["paren.rparen","}"], + ["punctuation.operator",","], + ["text"," "], + ["paren.lparen","["], + ["paren.rparen","]"], + ["text"," "], + ["paren.rparen","]"] +],[ + "schema-start", + ["text"," "], + ["paren.rparen","}"] +],[ + "schema-start", + ["text"," "], + ["paren.rparen","}"] +],[ + "start", + ["meta.tag.punctuation.tag-open","{%"], + ["text"," "], + ["keyword.tagendschema.tag-name","endschema"], + ["text"," "], + ["meta.tag.punctuation.tag-close","%}"] +],[ + "start" +],[ + "start", + ["text.xml","This support Shopify Liquid variation Stylesheet and Style code blocks"] +],[ + "start" +],[ + "style-start", + ["meta.tag.punctuation.tag-open","{%"], + ["text"," "], + ["keyword.tagstyle.tag-name","style"], + ["text"," "], + ["meta.tag.punctuation.tag-close","%}"] +],[ + "style-ruleset", + ["text"," "], + ["variable",".class"], + ["text"," "], + ["paren.lparen","{"] +],[ + "style-ruleset", + ["text"," "], + ["support.type","font-size"], + ["punctuation.operator",":"], + ["text"," "], + ["constant.numeric","10"], + ["keyword","px"], + ["punctuation.operator",";"], + ["text"," "], + ["comment","/* comment */"] +],[ + "style-start", + ["text"," "], + ["paren.rparen","}"] +],[ + "start", + ["meta.tag.punctuation.tag-open","{%"], + ["text"," "], + ["keyword.tagendstyle.tag-name","endstyle"], + ["text"," "], + ["meta.tag.punctuation.tag-close","%}"] +],[ + "start" +],[ + "stylesheet-start", + ["meta.tag.punctuation.tag-open","{%"], + ["text"," "], + ["keyword.tagstylesheet.tag-name","stylesheet"], + ["text"," "], + ["meta.tag.punctuation.tag-close","%}"] +],[ + "stylesheet-ruleset", + ["text"," "], + ["variable",".class"], + ["text"," "], + ["paren.lparen","{"] +],[ + "stylesheet-ruleset", + ["text"," "], + ["support.type","font-size"], + ["punctuation.operator",":"], + ["text"," "], + ["constant.numeric","10"], + ["keyword","px"], + ["punctuation.operator",";"], + ["text"," "], + ["comment","/* comment */"] +],[ + "stylesheet-start", + ["text"," "], + ["paren.rparen","}"] +],[ + "start", + ["meta.tag.punctuation.tag-open","{%"], + ["text"," "], + ["keyword.tagendstylesheet.tag-name","endstylesheet"], + ["text"," "], + ["meta.tag.punctuation.tag-close","%}"] +],[ + "start" +],[ + "start", + ["text.xml","This support Shopify Liquid variation JavaScript code blocks"] +],[ + "start" +],[ + "javascript-start", + ["meta.tag.punctuation.tag-open","{%"], + ["text"," "], + ["keyword.tagjavascript.tag-name","javascript"], + ["text"," "], + ["meta.tag.punctuation.tag-close","%}"] +],[ + "javascript-start" +],[ + "javascript-start", + ["text"," "], + ["storage.type","function"], + ["text"," "], + ["entity.name.function","foo"], + ["text"," "], + ["paren.lparen","("], + ["variable.parameter","param"], + ["paren.rparen",")"], + ["text"," "], + ["paren.lparen","{"] +],[ + "javascript-start" +],[ + "javascript-no_regex", + ["text"," "], + ["keyword","return"], + ["text"," "], + ["string","'something'"], + ["text"," "], + ["comment","// comment"] +],[ + "javascript-no_regex", + ["text"," "], + ["paren.rparen","}"] +],[ + "javascript-no_regex" +],[ + "start", + ["meta.tag.punctuation.tag-open","{%"], + ["text"," "], + ["keyword.tagendjavascript.tag-name","endjavascript"], + ["text"," "], + ["meta.tag.punctuation.tag-close","%}"] +],[ + "start" ]] \ No newline at end of file diff --git a/src/mode/behaviour/liquid.js b/src/mode/behaviour/liquid.js index 60b59d2153b..750278235d0 100644 --- a/src/mode/behaviour/liquid.js +++ b/src/mode/behaviour/liquid.js @@ -1,15 +1,15 @@ "use strict"; - + var oop = require("../../lib/oop"); var Behaviour = require("../behaviour").Behaviour; var XmlBehaviour = require("./xml").XmlBehaviour; var TokenIterator = require("../../token_iterator").TokenIterator; var lang = require("../../lib/lang"); - + function is(token, type) { return token && token.type.lastIndexOf(type + ".xml") > -1; } - + var LiquidBehaviour = function () { XmlBehaviour.call(this); this.add("autoBraceTagClosing","insertion", function (state, action, editor, session, text) { @@ -21,7 +21,7 @@ // exit if we're not in a tag if (!token || !( token.value.trim() === '%' || is(token, "tag-name") || is(token, "tag-whitespace") || is(token, "attribute-name") || is(token, "attribute-equals") || is(token, "attribute-value"))) return; - + // exit if we're inside of a quoted attribute value if (is(token, "reference.attribute-value")) return; @@ -38,26 +38,26 @@ iterator.stepBackward(); } } - // exit if the tag is empty + // exit if the tag is empty if (/{%\s*%/.test(session.getLine(position.row))) return; if (/^\s*}/.test(session.getLine(position.row).slice(position.column))) return; // find tag name - while (!token.type != 'keyword.block') { + while (!token.type != 'meta.tag.punctuation.tag-open') { token = iterator.stepBackward(); if (token.value == '{%') { while(true) { token = iterator.stepForward(); - if (token.type === 'keyword.block') { + if (token.type === 'meta.tag.punctuation.tag-open') { break; } else if (token.value.trim() == '%') { token = null; break; } } - break; + break; } } if (!token ) return ; @@ -67,11 +67,11 @@ // exit if the tag is ending if (is(iterator.stepBackward(), "end-tag-open")) return; - + var element = token.value; if (tokenRow == position.row) element = element.substring(0, position.column - tokenColumn); - + if (this.voidElements.hasOwnProperty(element.toLowerCase())) return; return { @@ -80,9 +80,9 @@ }; } }); - + }; oop.inherits(LiquidBehaviour, Behaviour); - + exports.LiquidBehaviour = LiquidBehaviour; diff --git a/src/mode/liquid.js b/src/mode/liquid.js index fb512710844..c1fe5f69b9c 100644 --- a/src/mode/liquid.js +++ b/src/mode/liquid.js @@ -1,24 +1,40 @@ var oop = require("../lib/oop"); var TextMode = require("./text").Mode; var HtmlMode = require("./html").Mode; -var HtmlCompletions = require("./html_completions").HtmlCompletions; -var LiquidBehaviour = require("./behaviour/liquid").LiquidBehaviour; +var JavascriptMode = require("./javascript").Mode; +var JsonMode = require("./json").Mode; +var CssMode = require("./css").Mode; var LiquidHighlightRules = require("./liquid_highlight_rules").LiquidHighlightRules; var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent; -var Mode = function() { - this.HighlightRules = LiquidHighlightRules; - this.$outdent = new MatchingBraceOutdent(); - this.$behaviour = new LiquidBehaviour(); - this.$completer = new HtmlCompletions(); +/* -------------------------------------------- */ +/* FOLDS */ +/* -------------------------------------------- */ + +var FoldMode = require("./folding/cstyle").FoldMode; + +/* -------------------------------------------- */ +/* MODE */ +/* -------------------------------------------- */ + +var Mode = function () { + + JsonMode.call(this); + HtmlMode.call(this); + CssMode.call(this); + JavascriptMode.call(this); + this.HighlightRules = LiquidHighlightRules; + this.foldingRules = new FoldMode(); + }; + oop.inherits(Mode, TextMode); -(function() { - // this.blockComment = {start: "{% comment %}", end: "{% endcomment %}"}; +(function () { + this.blockComment = {start: ""}; this.voidElements = new HtmlMode().voidElements; - + this.getNextLineIndent = function(state, line, tab) { var indent = this.$getIndent(line); @@ -50,6 +66,7 @@ oop.inherits(Mode, TextMode); this.$id = "ace/mode/liquid"; this.snippetFileId = "ace/snippets/liquid"; -}).call(Mode.prototype); + +}.call(Mode.prototype)); exports.Mode = Mode; diff --git a/src/mode/liquid_highlight_rules.js b/src/mode/liquid_highlight_rules.js index 4db550a62f7..c2fd4070539 100644 --- a/src/mode/liquid_highlight_rules.js +++ b/src/mode/liquid_highlight_rules.js @@ -1,103 +1,274 @@ "use strict"; var oop = require("../lib/oop"); + var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; +var CssHighlightRules = require("./css_highlight_rules").CssHighlightRules; var HtmlHighlightRules = require("./html_highlight_rules").HtmlHighlightRules; +var JsonHighlightRules = require("./json_highlight_rules").JsonHighlightRules; +var JavaScriptHighlightRules = require("./javascript_highlight_rules").JavaScriptHighlightRules; -var LiquidHighlightRules = function() { - HtmlHighlightRules.call(this); +var LiquidHighlightRules = function () { - // see: https://developer.mozilla.org/en/Liquid/Reference/Global_Objects - var functions = ( - // Standard Filters - "date|capitalize|downcase|upcase|first|last|join|sort|map|size|escape|" + - "escape_once|strip_html|strip_newlines|newline_to_br|replace|replace_first|" + - "truncate|truncatewords|prepend|append|minus|plus|times|divided_by|split" - ); + HtmlHighlightRules.call(this); - var keywords = ( - // Standard Tags - "capture|endcapture|case|endcase|when|comment|endcomment|" + - "cycle|for|endfor|in|reversed|if|endif|else|elsif|include|endinclude|unless|endunless|" + - // Commonly used tags - "style|text|image|widget|plugin|marker|endmarker|tablerow|endtablerow" - ); - - // common standard block tags that require to be closed with an end[block] token - var blocks = 'for|if|case|capture|unless|tablerow|marker|comment'; - - var builtinVariables = 'forloop|tablerowloop'; - // "forloop\\.(length|index|index0|rindex|rindex0|first|last)|limit|offset|range" + - // "tablerowloop\\.(length|index|index0|rindex|rindex0|first|last|col|col0|"+ - // "col_first|col_last)" - - var definitions = ("assign"); - - var keywordMapper = this.createKeywordMapper({ - "variable.language": builtinVariables, - "keyword": keywords, - "keyword.block": blocks, - "support.function": functions, - "keyword.definition": definitions - }, "identifier"); - - // add liquid start tags to the HTML start tags - for (var rule in this.$rules) { - this.$rules[rule].unshift({ - token : "variable", - regex : "{%", - push : "liquid-start" - }, { - token : "variable", - regex : "{{", - push : "liquid-start" - }); - } + /** + * Embedded Matches + * + * Handles `onMatch` tokens and correct parses the + * inner contents of the tag. + */ + function onMatchEmbedded(name) { + + const length = name.length; + + return function (value) { + + const idx = value.indexOf(name); + + const x = [ + { + type: "meta.tag.punctuation.tag-open", + value: "{%" + }, + { + type: "text", + value: value.slice(2, idx) + }, + { + type: "keyword.tag" + name + ".tag-name", + value: value.slice(idx, idx + length) + }, + { + type: "text", + value: value.slice(idx + length, value.indexOf("%}")) + }, + { + type: "meta.tag.punctuation.tag-close", + value: "%}" + } + ]; - this.addRules({ - "liquid-start" : [{ - token: "variable", - regex: "}}", + return x; + }; + } + + + for (var rule in this.$rules) { + + this.$rules[rule].unshift( + { + token: "comment.block", + regex: /{%-?\s*comment\s*-?%}/, + next: [ + { + token: "comment.block", + regex: /{%-?\s*endcomment\s*-?%}/, + next: "pop" + }, + { + defaultToken: "comment", + caseInsensitive: false + } + ] + }, + { + token: "comment.line", + regex: /{%-?\s*#/, + next: [ + { + token: "comment.line", + regex: /-?%}/, next: "pop" - }, { - token: "variable", - regex: "%}", + }, + { + defaultToken: "comment", + caseInsensitive: false + } + ] + }, + { + token: 'style.embedded.start', + regex: /({%-?\s*\bstyle\b\s*-?%})/, + next: "style-start", + onMatch: onMatchEmbedded("style") + }, + { + regex: /({%-?\s*\bstylesheet\b\s*-?%})/, + next: "stylesheet-start", + onMatch: onMatchEmbedded("stylesheet") + }, + { + regex: /({%-?\s*\bschema\b\s*-?%})/, + next: "schema-start", + onMatch: onMatchEmbedded("schema") + }, + { + regex: /({%-?\s*\bjavascript\b\s*-?%})/, + next: "javascript-start", + onMatch: onMatchEmbedded("javascript") + }, + { + token: "meta.tag.punctuation.tag-open", + regex: /({%)/, + next: [ + { + token: "keyword.block", + regex: /-?\s*[a-zA-Z_$][a-zA-Z0-9_$]+\b/, + next: 'liquid-start' + }, + { + token: "meta.tag.punctuation.tag-close", + regex: /(-?)(%})/, next: "pop" - }, { - token : "string", // single line - regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' - }, { - token : "string", // single line - regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" - }, { - token : "constant.numeric", // hex - regex : "0[xX][0-9a-fA-F]+\\b" - }, { - token : "constant.numeric", // float - regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" - }, { - token : "constant.language.boolean", - regex : "(?:true|false)\\b" - }, { - token : keywordMapper, - regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b" - }, { - token : "keyword.operator", - regex : "/|\\*|\\-|\\+|=|!=|\\?\\:" - }, { - token : "paren.lparen", - regex : /[\[\({]/ - }, { - token : "paren.rparen", - regex : /[\])}]/ - }, { - token : "text", - regex : "\\s+" - }] - }); - - this.normalizeRules(); + } + ] + }, + { + token: "meta.tag.punctuation.ouput-open", + regex: /({{)/, + push: "liquid-start" + } + ); + } + + /* -------------------------------------------- */ + /* EMBEDDED REGIONS */ + /* -------------------------------------------- */ + + this.embedRules(JsonHighlightRules, "schema-", [ + { + token: "schema-start", + next: "pop", + regex: /({%-?\s*\bendschema\b\s*-?%})/, + onMatch: onMatchEmbedded("endschema") + } + ]); + + this.embedRules(JavaScriptHighlightRules, "javascript-", [ + { + token: "javascript-start", + next: "pop", + regex: /({%-?\s*\bendjavascript\b\s*-?%})/, + onMatch: onMatchEmbedded("endjavascript") + } + ]); + + + + this.embedRules(CssHighlightRules, "style-", [ + { + token: "style-start", + next: "pop", + regex: /({%-?\s*\bendstyle\b\s*-?%})/, + onMatch: onMatchEmbedded("endstyle") + } + ]); + + this.embedRules(CssHighlightRules, "stylesheet-", [ + { + token: "stylesheet-start", + next: "pop", + regex: /({%-?\s*\bendstylesheet\b\s*-?%})/, + onMatch: onMatchEmbedded("endstylesheet") + } + ]); + + /* -------------------------------------------- */ + /* LIQUID GRAMMARS */ + /* -------------------------------------------- */ + + this.addRules({ + "liquid-start": [ + { + token: "meta.tag.punctuation.ouput-close", + regex: /}}/, + next: "pop" + }, + { + token: "meta.tag.punctuation.tag-close", + regex: /%}/, + next: "pop" + }, + { + token: "string", + regex: /['](?:(?:\\.)|(?:[^'\\]))*?[']/ + }, + { + token: "string", + regex: /["](?:(?:\\.)|(?:[^'\\]))*?["]/ + }, + { + token: "constant.numeric", + regex: /0[xX][0-9a-fA-F]+\b/ + }, + { + token: "constant.numeric", + regex: /[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/ + }, + { + token: "keyword.operator", + regex: /\*|\-|\+|=|!=|\?\|\:/ + }, + { + token: "constant.language.boolean", + regex: /(?:true|false|nil|empty)\b/ + }, + { + token: "keyword.operator", + regex: /\s+(?:and|contains|in|with)\b\s+/ + }, + { + token: ["keyword.operator", "support.function"], + regex: /(\|\s*)([a-zA-Z_]+)/ + + }, + { + token: "support.function", + regex: /\s*([a-zA-Z_]+\b)(?=:)/ + }, + { + token: "keyword.operator", + regex: + /(:)\s*(?=[a-zA-Z_])/ + }, + { + token: [ + "support.class", + "keyword.operator", + "support.object", + "keyword.operator", + "variable.parameter" + ], + regex: /(\w+)(\.)(\w+)(\.)?(\w+)?/ + }, + { + token: "variable.parameter", + regex: /\.([a-zA-Z_$][a-zA-Z0-9_$]*\b)$/ + }, + { + token: "support.class", + regex: /(?:additional_checkout_buttons|content_for_additional_checkout_buttons)\b/ + }, + { + token: "paren.lparen", + regex: /[\[\({]/ + }, + { + token: "paren.rparen", + regex: /[\])}]/ + }, + { + token: "text", + regex: /\s+/ + } + ] + }); + + this.normalizeRules(); + }; + oop.inherits(LiquidHighlightRules, TextHighlightRules); exports.LiquidHighlightRules = LiquidHighlightRules;