diff --git a/demo/kitchen-sink/docs/jinja.jinja b/demo/kitchen-sink/docs/jinja.jinja new file mode 100644 index 00000000000..4b89d4a8cb7 --- /dev/null +++ b/demo/kitchen-sink/docs/jinja.jinja @@ -0,0 +1,2 @@ +TODO add a nice demo! +Try to keep it short! \ No newline at end of file diff --git a/src/ext/modelist.js b/src/ext/modelist.js index 791174fe07d..5af189b7803 100644 --- a/src/ext/modelist.js +++ b/src/ext/modelist.js @@ -128,6 +128,7 @@ var supportedModes = { Java: ["java"], JavaScript: ["js|jsm|cjs|mjs"], JEXL: ["jexl"], + jinja: [""], JSON: ["json"], JSON5: ["json5"], JSONiq: ["jq"], diff --git a/src/mode/jinja.js b/src/mode/jinja.js new file mode 100644 index 00000000000..66807f4da48 --- /dev/null +++ b/src/mode/jinja.js @@ -0,0 +1,62 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2012, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +/* + THIS FILE WAS AUTOGENERATED BY mode.tmpl.js +*/ + +"use strict"; + +var oop = require("../lib/oop"); +var TextMode = require("./text").Mode; + +var JinjaCompletions = require("./jinja_completions").JinjaCompletions; +var JinjaHighlightRules = require("./jinja_highlight_rules").JinjaHighlightRules; +// TODO: pick appropriate fold mode +var FoldMode = require("./folding/cstyle").FoldMode; + +var Mode = function() { + this.$completer = new JinjaCompletions(); + this.HighlightRules = JinjaHighlightRules; + this.foldingRules = new FoldMode(); +}; +oop.inherits(Mode, TextMode); + +(function() { + // this.lineCommentStart = ""{#-?""; + // this.blockComment = {start: ""/*"", end: ""*/""}; + // Extra logic goes here. + this.getCompletions = function(state, session, pos, prefix) { + return this.$completer.getCompletions(this.$highlightRules.$keywordList, state, session, pos, prefix); + }; + this.$id = "ace/mode/jinja"; +}).call(Mode.prototype); + +exports.Mode = Mode; diff --git a/src/mode/jinja_completions.js b/src/mode/jinja_completions.js new file mode 100644 index 00000000000..b1c742c048c --- /dev/null +++ b/src/mode/jinja_completions.js @@ -0,0 +1,89 @@ + +"use strict"; + +var TokenIterator = require("../token_iterator").TokenIterator; + +var jinjaFilters = [ + "abs", "float", "lower", "round", "tojson", "attr", + "forceescape", "map", "safe", "trim", "batch", "format", + "max", "select", "truncate", "capitalize", "groupby", "min", + "selectattr", "unique", "center", "indent", "pprint", "slice", + "upper", "default", "int", "random", "sort", "urlencode", + "dictsort", "join", "reject", "string", "urlize", "escape", + "last", "rejectattr", "striptags", "wordcount", "filesizeformat", + "length", "replace", "sum", "wordwrap", "first", "list", "reverse", + "title", "xmlattr" +]; + +var JinjaCompletions = function() { + +}; + +(function() { + + this.getCompletions = function(keywordList, state, session, pos, prefix) { + var token = session.getTokenAt(pos.row, pos.column); + + if (!token) + return []; + + if (this.mayBeJinjaKeyword(token)) { + return this.getKeywordCompletions(keywordList, state, session, pos, prefix); + } + + if (this.mayBeJinjaFilter(token)) { + return this.getFilterCompletions(state, session, pos, prefix); + } + + if (this.mayBeJinjaVariable(token)) { + return this.getVariableCompletions(state, session, pos, prefix); + } + + return []; + }; + + this.mayBeJinjaKeyword = function(token) { + return token.type === "meta.scope.jinja.tag"; + }; + + this.mayBeJinjaFilter = function(token) { + return token.type === "support.function.other.jinja.filter"; + }; + + this.mayBeJinjaVariable = function(token) { + return token.type === "variable"; + }; + + this.getKeywordCompletions = function(keywordList, state, session, pos, prefix) { + return keywordList.map(function(keyword) { + return { + caption: keyword, + snippet: keyword, + meta: "keyword", + score: 1000000 + }; + }); + }; + + this.getFilterCompletions = function(state, session, pos, prefix) { + return jinjaFilters.map(function(filter) { + return { + caption: filter, + snippet: filter, + meta: "filter", + score: 1000000 + }; + }); + }; + + this.getVariableCompletions = function(state, session, pos, prefix) { + // This is a placeholder. In a real implementation, you'd need to + // analyze the context to suggest relevant variables. + return [ + { caption: "loop", snippet: "loop", meta: "Nunjucks loop object", score: 1000000 }, + { caption: "super", snippet: "super()", meta: "Nunjucks super function", score: 1000000 } + ]; + }; +}).call(JinjaCompletions.prototype); + +exports.JinjaCompletions = JinjaCompletions; diff --git a/src/mode/jinja_highlight_rules.js b/src/mode/jinja_highlight_rules.js new file mode 100644 index 00000000000..d99bd7ac91a --- /dev/null +++ b/src/mode/jinja_highlight_rules.js @@ -0,0 +1,327 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2012, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +/* This file was autogenerated from ../src/mode/jinja.tmLanguage.json (uuid: ) */ +/**************************************************************************************** + * IT MIGHT NOT BE PERFECT ...But it's a good start from an existing *.tmlanguage file. * + * fileTypes * + ****************************************************************************************/ + +"use strict"; + +var oop = require("../lib/oop"); +var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; +var HtmlHighlightRules = require("./html_highlight_rules").HtmlHighlightRules; + +var JinjaHighlightRules = function() { + HtmlHighlightRules.call(this); + // regexp must not have capturing parentheses. Use (?:) instead. + // regexps are ordered -> the first match is used + + var statementsKeywordMap = { + "keyword.control.jinja.tag": ( + "block|endblock|extends|if|endif|elif|for|endfor|asyncEach|" + + "endeach|include|asyncAll|endall|macro|endmacro|set|" + + "endset|ignore missing|as|from|raw|verbatim|filter|endfilter" + ), + }; + // a special attribude which is magically used for completion + var keywordMapper = this.createKeywordMapper(statementsKeywordMap, "meta.scope.jinja.tag"); + + this.$rules["start"].unshift({ + token: [ + "meta.jinja.delimiter.tag", + "comment.block.jinja.raw", + "keyword.control.jinja", + "comment.block.jinja.raw", + "meta.jinja.delimiter.tag" + ], + regex: /({%)(\s*)(raw)(\s*)(%})/, + push: [{ + token: [ + "meta.jinja.delimiter.tag", + "comment.block.jinja.raw", + "keyword.control.jinja", + "comment.block.jinja.raw", + "meta.jinja.delimiter.tag" + ], + regex: /({%)(\s*)(endraw)(\s*)(%})/, + next: "pop" + }, { + defaultToken: "comment.block.jinja.raw" + }] + }, { + include: "jinja#comments" + }, { + token: "variable.meta.jinja.delimiter", + regex: /{{-?/, + push: [{ + token: "variable.meta.jinja.delimiter", + regex: /-?}}/, + next: "pop" + }, { + include: "jinja#expression" + }, { + defaultToken: "variable.meta.scope.jinja" + }] + }, { + token: "comment.jinja.delimiter.tag", + regex: /({%-?)/, + push: [{ + token: "comment.jinja.delimiter.tag", + regex: /(-?%})/, + next: "pop" + }, { + include: "jinja#statement" + }, { + defaultToken: "meta.scope.jinja.tag" + }] + }); + + this.addRules({ + "jinja#comments": [{ + start: /{#-?/, + token: "comment.block.jinja", + end: /-?#}/, + }], + "jinja#statement": [{ + token: [ + "text", + "keyword.control.jinja", + "text", + "variable.other.jinja.block" + ], + regex: /(\s*\b)(block)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)\b/ + }, { + token: [ + "text", + "keyword.control.jinja", + "text", + "variable.other.jinja.filter" + ], + regex: /(\s*\b)(filter)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)\b/ + }, { + token: [ + "text", + "keyword.control.jinja", + "text", + "variable.other.jinja.extend" + ], + regex: /(\s*\b)(extend)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)\b/ + }, { + token: [ + "text", + "keyword.control.jinja", + "text", + "variable.other.jinja.includ" + ], + regex: /(\s*\b)(include)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)\b/ + }, { + token: [ + "text", + "keyword.control.jinja", + "text", + "variable.other.jinja.macro" + ], + regex: /(\s*\b)(macro)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)\b/ + }, { + token: [ + "text", + "keyword.control.jinja", + "text", + "variable.other.jinja.set" + ], + regex: /(\s*\b)(set)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)\b/ + }, { + token: keywordMapper, + regex: /\b[a-zA-Z_][a-zA-Z0-9_]*\b/ + }, { + include: "jinja#expression" + }], + "jinja#expression": [{ + token: [ + "text", + "keyword.control.jinja", + "text", + "variable.other.jinja.test" + ], + regex: /(\s*\b)(is)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)\b/ + }, { + token: "keyword.control.jinja", + regex: /\b(?:and|else|if|in|import|not|or|recursive|with(?:out)?\s+context)\b/ + }, { + token: "constant.language.jinja", + regex: /\b(?:true|false|none)\b/ + }, { + token: "variable.language.jinja", + regex: /\b(?:loop|super|self|varargs|kwargs)\b/ + }, { + token: "keyword.operator.arithmetic.jinja", + regex: /\+|\-|\*\*|\*|\/\/|\/|%/ + }, { + token: [ + "punctuation.other.jinja", + "support.function.other.jinja.filter" + ], + regex: /(\|\s*)([a-zA-Z_][a-zA-Z0-9_]*)/ + }, { + token: [ + "punctuation.other.jinja", + "variable.other.jinja.attribute" + ], + regex: /(\.)([a-zA-Z_][a-zA-Z0-9_]*)/ + }, { + token: "punctuation.other.jinja", + regex: /\[/, + push: [{ + token: "punctuation.other.jinja", + regex: /\]/, + next: "pop" + }, { + include: "jinja#expression" + }] + }, { + token: "punctuation.other.jinja", + regex: /\(/, + push: [{ + token: "punctuation.other.jinja", + regex: /\)/, + next: "pop" + }, { + include: "jinja#expression" + }] + }, { + token: "punctuation.other.jinja", + regex: /\{/, + push: [{ + token: "punctuation.other.jinja", + regex: /\}/, + next: "pop" + }, { + include: "jinja#expression" + }] + }, { + token: "punctuation.other.jinja", + regex: /\.|:|\||,/ + }, { + token: "keyword.operator.comparison.jinja", + regex: /==|<=|=>|<|>|!=/ + }, { + token: "keyword.operator.assignment.jinja", + regex: /=/ + }, { + token: "punctuation.definition.string.begin.jinja", + regex: /"/, + push: [{ + token: "punctuation.definition.string.end.jinja", + regex: /"/, + next: "pop" + }, { + include: "jinja#string" + }, { + defaultToken: "string.quoted.double.jinja" + }] + }, { + token: "punctuation.definition.string.begin.jinja", + regex: /'/, + push: [{ + token: "punctuation.definition.string.end.jinja", + regex: /'/, + next: "pop" + }, { + include: "jinja#string" + }, { + defaultToken: "string.quoted.single.jinja" + }] + }, { + token: "punctuation.definition.regexp.begin.jinja", + regex: /@\//, + push: [{ + token: "punctuation.definition.regexp.end.jinja", + regex: /\//, + next: "pop" + }, { + include: "jinja#simple_escapes" + }, { + defaultToken: "string.regexp.jinja" + }] + }], + "jinja#escaped_char": [{ + token: "constant.character.escape.hex.jinja", + regex: /\\x[0-9A-F]{2}/ + }], + "jinja#escaped_unicode_char": [{ + token: [ + "constant.character.escape.unicode.16-bit-hex.jinja", + "constant.character.escape.unicode.32-bit-hex.jinja", + "constant.character.escape.unicode.name.jinja" + ], + regex: /(\\U[0-9A-Fa-f]{8})|(\\u[0-9A-Fa-f]{4})|(\\N\{[a-zA-Z ]+\})/ + }], + "jinja#simple_escapes": [{ + token: [ + "constant.character.escape.newline.jinja", + "constant.character.escape.backlash.jinja", + "constant.character.escape.double-quote.jinja", + "constant.character.escape.single-quote.jinja", + "constant.character.escape.bell.jinja", + "constant.character.escape.backspace.jinja", + "constant.character.escape.formfeed.jinja", + "constant.character.escape.linefeed.jinja", + "constant.character.escape.return.jinja", + "constant.character.escape.tab.jinja", + "constant.character.escape.vertical-tab.jinja" + ], + regex: /(\\$)|(\\\\)|(\\\")|(\\')|(\\a)|(\\b)|(\\f)|(\\n)|(\\r)|(\\t)|(\\v)/ + }], + "jinja#string": [{ + include: "jinja#simple_escapes" + }, { + include: "jinja#escaped_char" + }, { + include: "jinja#escaped_unicode_char" + }] + }) + + this.normalizeRules(); +}; + +JinjaHighlightRules.metaData = { + name: "jinja", + scopeName: "source.jinja", + comment: "Jinja Templates", + foldingStartMarker: "({%\\s*(block|filter|for|if|macro|raw))", + foldingStopMarker: "({%\\s*(endblock|endfilter|endfor|endif|endmacro|endraw)\\s*%})" +} + + +oop.inherits(JinjaHighlightRules, TextHighlightRules); + +exports.JinjaHighlightRules = JinjaHighlightRules;