smarty.js 6.1 KB

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