scripts/CodeMirror/mode/smarty/smarty.js (view raw)
1// CodeMirror, copyright (c) by Marijn Haverbeke and others
2// Distributed under an MIT license: https://codemirror.net/LICENSE
3
4/**
5 * Smarty 2 and 3 mode.
6 */
7
8(function(mod) {
9 if (typeof exports == "object" && typeof module == "object") // CommonJS
10 mod(require("../../lib/codemirror"));
11 else if (typeof define == "function" && define.amd) // AMD
12 define(["../../lib/codemirror"], mod);
13 else // Plain browser env
14 mod(CodeMirror);
15})(function(CodeMirror) {
16 "use strict";
17
18 CodeMirror.defineMode("smarty", function(config, parserConf) {
19 var rightDelimiter = parserConf.rightDelimiter || "}";
20 var leftDelimiter = parserConf.leftDelimiter || "{";
21 var version = parserConf.version || 2;
22 var baseMode = CodeMirror.getMode(config, parserConf.baseMode || "null");
23
24 var keyFunctions = ["debug", "extends", "function", "include", "literal"];
25 var regs = {
26 operatorChars: /[+\-*&%=<>!?]/,
27 validIdentifier: /[a-zA-Z0-9_]/,
28 stringChar: /['"]/
29 };
30
31 var last;
32 function cont(style, lastType) {
33 last = lastType;
34 return style;
35 }
36
37 function chain(stream, state, parser) {
38 state.tokenize = parser;
39 return parser(stream, state);
40 }
41
42 // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode
43 function doesNotCount(stream, pos) {
44 if (pos == null) pos = stream.pos;
45 return version === 3 && leftDelimiter == "{" &&
46 (pos == stream.string.length || /\s/.test(stream.string.charAt(pos)));
47 }
48
49 function tokenTop(stream, state) {
50 var string = stream.string;
51 for (var scan = stream.pos;;) {
52 var nextMatch = string.indexOf(leftDelimiter, scan);
53 scan = nextMatch + leftDelimiter.length;
54 if (nextMatch == -1 || !doesNotCount(stream, nextMatch + leftDelimiter.length)) break;
55 }
56 if (nextMatch == stream.pos) {
57 stream.match(leftDelimiter);
58 if (stream.eat("*")) {
59 return chain(stream, state, tokenBlock("comment", "*" + rightDelimiter));
60 } else {
61 state.depth++;
62 state.tokenize = tokenSmarty;
63 last = "startTag";
64 return "tag";
65 }
66 }
67
68 if (nextMatch > -1) stream.string = string.slice(0, nextMatch);
69 var token = baseMode.token(stream, state.base);
70 if (nextMatch > -1) stream.string = string;
71 return token;
72 }
73
74 // parsing Smarty content
75 function tokenSmarty(stream, state) {
76 if (stream.match(rightDelimiter, true)) {
77 if (version === 3) {
78 state.depth--;
79 if (state.depth <= 0) {
80 state.tokenize = tokenTop;
81 }
82 } else {
83 state.tokenize = tokenTop;
84 }
85 return cont("tag", null);
86 }
87
88 if (stream.match(leftDelimiter, true)) {
89 state.depth++;
90 return cont("tag", "startTag");
91 }
92
93 var ch = stream.next();
94 if (ch == "$") {
95 stream.eatWhile(regs.validIdentifier);
96 return cont("variable-2", "variable");
97 } else if (ch == "|") {
98 return cont("operator", "pipe");
99 } else if (ch == ".") {
100 return cont("operator", "property");
101 } else if (regs.stringChar.test(ch)) {
102 state.tokenize = tokenAttribute(ch);
103 return cont("string", "string");
104 } else if (regs.operatorChars.test(ch)) {
105 stream.eatWhile(regs.operatorChars);
106 return cont("operator", "operator");
107 } else if (ch == "[" || ch == "]") {
108 return cont("bracket", "bracket");
109 } else if (ch == "(" || ch == ")") {
110 return cont("bracket", "operator");
111 } else if (/\d/.test(ch)) {
112 stream.eatWhile(/\d/);
113 return cont("number", "number");
114 } else {
115
116 if (state.last == "variable") {
117 if (ch == "@") {
118 stream.eatWhile(regs.validIdentifier);
119 return cont("property", "property");
120 } else if (ch == "|") {
121 stream.eatWhile(regs.validIdentifier);
122 return cont("qualifier", "modifier");
123 }
124 } else if (state.last == "pipe") {
125 stream.eatWhile(regs.validIdentifier);
126 return cont("qualifier", "modifier");
127 } else if (state.last == "whitespace") {
128 stream.eatWhile(regs.validIdentifier);
129 return cont("attribute", "modifier");
130 } if (state.last == "property") {
131 stream.eatWhile(regs.validIdentifier);
132 return cont("property", null);
133 } else if (/\s/.test(ch)) {
134 last = "whitespace";
135 return null;
136 }
137
138 var str = "";
139 if (ch != "/") {
140 str += ch;
141 }
142 var c = null;
143 while (c = stream.eat(regs.validIdentifier)) {
144 str += c;
145 }
146 for (var i=0, j=keyFunctions.length; i<j; i++) {
147 if (keyFunctions[i] == str) {
148 return cont("keyword", "keyword");
149 }
150 }
151 if (/\s/.test(ch)) {
152 return null;
153 }
154 return cont("tag", "tag");
155 }
156 }
157
158 function tokenAttribute(quote) {
159 return function(stream, state) {
160 var prevChar = null;
161 var currChar = null;
162 while (!stream.eol()) {
163 currChar = stream.peek();
164 if (stream.next() == quote && prevChar !== '\\') {
165 state.tokenize = tokenSmarty;
166 break;
167 }
168 prevChar = currChar;
169 }
170 return "string";
171 };
172 }
173
174 function tokenBlock(style, terminator) {
175 return function(stream, state) {
176 while (!stream.eol()) {
177 if (stream.match(terminator)) {
178 state.tokenize = tokenTop;
179 break;
180 }
181 stream.next();
182 }
183 return style;
184 };
185 }
186
187 return {
188 startState: function() {
189 return {
190 base: CodeMirror.startState(baseMode),
191 tokenize: tokenTop,
192 last: null,
193 depth: 0
194 };
195 },
196 copyState: function(state) {
197 return {
198 base: CodeMirror.copyState(baseMode, state.base),
199 tokenize: state.tokenize,
200 last: state.last,
201 depth: state.depth
202 };
203 },
204 innerMode: function(state) {
205 if (state.tokenize == tokenTop)
206 return {mode: baseMode, state: state.base};
207 },
208 token: function(stream, state) {
209 var style = state.tokenize(stream, state);
210 state.last = last;
211 return style;
212 },
213 indent: function(state, text, line) {
214 if (state.tokenize == tokenTop && baseMode.indent)
215 return baseMode.indent(state.base, text, line);
216 else
217 return CodeMirror.Pass;
218 },
219 blockCommentStart: leftDelimiter + "*",
220 blockCommentEnd: "*" + rightDelimiter
221 };
222 });
223
224 CodeMirror.defineMIME("text/x-smarty", "smarty");
225});