third-party: added codemirror
[unfold.git] / third-party / codemirror-3.15 / mode / smarty / smarty.js
1 /**
2  * Smarty 2 and 3 mode.
3  */
4 CodeMirror.defineMode("smarty", function(config) {
5   "use strict";
6
7   // our default settings; check to see if they're overridden
8   var settings = {
9     rightDelimiter: '}',
10     leftDelimiter: '{',
11     smartyVersion: 2 // for backward compatibility
12   };
13   if (config.hasOwnProperty("leftDelimiter")) {
14     settings.leftDelimiter = config.leftDelimiter;
15   }
16   if (config.hasOwnProperty("rightDelimiter")) {
17     settings.rightDelimiter = config.rightDelimiter;
18   }
19   if (config.hasOwnProperty("smartyVersion") && config.smartyVersion === 3) {
20     settings.smartyVersion = 3;
21   }
22
23   var keyFunctions = ["debug", "extends", "function", "include", "literal"];
24   var last;
25   var regs = {
26     operatorChars: /[+\-*&%=<>!?]/,
27     validIdentifier: /[a-zA-Z0-9_]/,
28     stringChar: /['"]/
29   };
30
31   var helpers = {
32     cont: function(style, lastType) {
33       last = lastType;
34       return style;
35     },
36     chain: function(stream, state, parser) {
37       state.tokenize = parser;
38       return parser(stream, state);
39     }
40   };
41
42
43   // our various parsers
44   var parsers = {
45
46     // the main tokenizer
47     tokenizer: function(stream, state) {
48       if (stream.match(settings.leftDelimiter, true)) {
49         if (stream.eat("*")) {
50           return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
51         } else {
52           // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode
53           state.depth++;
54           var isEol = stream.eol();
55           var isFollowedByWhitespace = /\s/.test(stream.peek());
56           if (settings.smartyVersion === 3 && settings.leftDelimiter === "{" && (isEol || isFollowedByWhitespace)) {
57             state.depth--;
58             return null;
59           } else {
60             state.tokenize = parsers.smarty;
61             last = "startTag";
62             return "tag";
63           }
64         }
65       } else {
66         stream.next();
67         return null;
68       }
69     },
70
71     // parsing Smarty content
72     smarty: function(stream, state) {
73       if (stream.match(settings.rightDelimiter, true)) {
74         if (settings.smartyVersion === 3) {
75           state.depth--;
76           if (state.depth <= 0) {
77             state.tokenize = parsers.tokenizer;
78           }
79         } else {
80           state.tokenize = parsers.tokenizer;
81         }
82         return helpers.cont("tag", null);
83       }
84
85       if (stream.match(settings.leftDelimiter, true)) {
86         state.depth++;
87         return helpers.cont("tag", "startTag");
88       }
89
90       var ch = stream.next();
91       if (ch == "$") {
92         stream.eatWhile(regs.validIdentifier);
93         return helpers.cont("variable-2", "variable");
94       } else if (ch == "|") {
95         return helpers.cont("operator", "pipe");
96       } else if (ch == ".") {
97         return helpers.cont("operator", "property");
98       } else if (regs.stringChar.test(ch)) {
99         state.tokenize = parsers.inAttribute(ch);
100         return helpers.cont("string", "string");
101       } else if (regs.operatorChars.test(ch)) {
102         stream.eatWhile(regs.operatorChars);
103         return helpers.cont("operator", "operator");
104       } else if (ch == "[" || ch == "]") {
105         return helpers.cont("bracket", "bracket");
106       } else if (ch == "(" || ch == ")") {
107         return helpers.cont("bracket", "operator");
108       } else if (/\d/.test(ch)) {
109         stream.eatWhile(/\d/);
110         return helpers.cont("number", "number");
111       } else {
112
113         if (state.last == "variable") {
114           if (ch == "@") {
115             stream.eatWhile(regs.validIdentifier);
116             return helpers.cont("property", "property");
117           } else if (ch == "|") {
118             stream.eatWhile(regs.validIdentifier);
119             return helpers.cont("qualifier", "modifier");
120           }
121         } else if (state.last == "pipe") {
122           stream.eatWhile(regs.validIdentifier);
123           return helpers.cont("qualifier", "modifier");
124         } else if (state.last == "whitespace") {
125           stream.eatWhile(regs.validIdentifier);
126           return helpers.cont("attribute", "modifier");
127         } if (state.last == "property") {
128           stream.eatWhile(regs.validIdentifier);
129           return helpers.cont("property", null);
130         } else if (/\s/.test(ch)) {
131           last = "whitespace";
132           return null;
133         }
134
135         var str = "";
136         if (ch != "/") {
137           str += ch;
138         }
139         var c = null;
140         while (c = stream.eat(regs.validIdentifier)) {
141           str += c;
142         }
143         for (var i=0, j=keyFunctions.length; i<j; i++) {
144           if (keyFunctions[i] == str) {
145             return helpers.cont("keyword", "keyword");
146           }
147         }
148         if (/\s/.test(ch)) {
149           return null;
150         }
151         return helpers.cont("tag", "tag");
152       }
153     },
154
155     inAttribute: function(quote) {
156       return function(stream, state) {
157         var prevChar = null;
158         var currChar = null;
159         while (!stream.eol()) {
160           currChar = stream.peek();
161           if (stream.next() == quote && prevChar !== '\\') {
162             state.tokenize = parsers.smarty;
163             break;
164           }
165           prevChar = currChar;
166         }
167         return "string";
168       };
169     },
170
171     inBlock: function(style, terminator) {
172       return function(stream, state) {
173         while (!stream.eol()) {
174           if (stream.match(terminator)) {
175             state.tokenize = parsers.tokenizer;
176             break;
177           }
178           stream.next();
179         }
180         return style;
181       };
182     }
183   };
184
185
186   // the public API for CodeMirror
187   return {
188     startState: function() {
189       return {
190         tokenize: parsers.tokenizer,
191         mode: "smarty",
192         last: null,
193         depth: 0
194       };
195     },
196     token: function(stream, state) {
197       var style = state.tokenize(stream, state);
198       state.last = last;
199       return style;
200     },
201     electricChars: ""
202   };
203 });
204
205 CodeMirror.defineMIME("text/x-smarty", "smarty");