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