smartymixed.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: http://codemirror.net/LICENSE
  3. /**
  4. * @file smartymixed.js
  5. * @brief Smarty Mixed Codemirror mode (Smarty + Mixed HTML)
  6. * @author Ruslan Osmanov <rrosmanov at gmail dot com>
  7. * @version 3.0
  8. * @date 05.07.2013
  9. */
  10. // Warning: Don't base other modes on this one. This here is a
  11. // terrible way to write a mixed mode.
  12. (function(mod) {
  13. if (typeof exports == "object" && typeof module == "object") // CommonJS
  14. mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../smarty/smarty"));
  15. else if (typeof define == "function" && define.amd) // AMD
  16. define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../smarty/smarty"], mod);
  17. else // Plain browser env
  18. mod(CodeMirror);
  19. })(function(CodeMirror) {
  20. "use strict";
  21. CodeMirror.defineMode("smartymixed", function(config) {
  22. var htmlMixedMode = CodeMirror.getMode(config, "htmlmixed");
  23. var smartyMode = CodeMirror.getMode(config, "smarty");
  24. var settings = {
  25. rightDelimiter: '}',
  26. leftDelimiter: '{'
  27. };
  28. if (config.hasOwnProperty("leftDelimiter")) {
  29. settings.leftDelimiter = config.leftDelimiter;
  30. }
  31. if (config.hasOwnProperty("rightDelimiter")) {
  32. settings.rightDelimiter = config.rightDelimiter;
  33. }
  34. function reEsc(str) { return str.replace(/[^\s\w]/g, "\\$&"); }
  35. var reLeft = reEsc(settings.leftDelimiter), reRight = reEsc(settings.rightDelimiter);
  36. var regs = {
  37. smartyComment: new RegExp("^" + reRight + "\\*"),
  38. literalOpen: new RegExp(reLeft + "literal" + reRight),
  39. literalClose: new RegExp(reLeft + "\/literal" + reRight),
  40. hasLeftDelimeter: new RegExp(".*" + reLeft),
  41. htmlHasLeftDelimeter: new RegExp("[^<>]*" + reLeft)
  42. };
  43. var helpers = {
  44. chain: function(stream, state, parser) {
  45. state.tokenize = parser;
  46. return parser(stream, state);
  47. },
  48. cleanChain: function(stream, state, parser) {
  49. state.tokenize = null;
  50. state.localState = null;
  51. state.localMode = null;
  52. return (typeof parser == "string") ? (parser ? parser : null) : parser(stream, state);
  53. },
  54. maybeBackup: function(stream, pat, style) {
  55. var cur = stream.current();
  56. var close = cur.search(pat),
  57. m;
  58. if (close > - 1) stream.backUp(cur.length - close);
  59. else if (m = cur.match(/<\/?$/)) {
  60. stream.backUp(cur.length);
  61. if (!stream.match(pat, false)) stream.match(cur[0]);
  62. }
  63. return style;
  64. }
  65. };
  66. var parsers = {
  67. html: function(stream, state) {
  68. if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false) && state.htmlMixedState.htmlState.tagName === null) {
  69. state.tokenize = parsers.smarty;
  70. state.localMode = smartyMode;
  71. state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
  72. return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
  73. } else if (!state.inLiteral && stream.match(settings.leftDelimiter, false)) {
  74. state.tokenize = parsers.smarty;
  75. state.localMode = smartyMode;
  76. state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
  77. return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
  78. }
  79. return htmlMixedMode.token(stream, state.htmlMixedState);
  80. },
  81. smarty: function(stream, state) {
  82. if (stream.match(settings.leftDelimiter, false)) {
  83. if (stream.match(regs.smartyComment, false)) {
  84. return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
  85. }
  86. } else if (stream.match(settings.rightDelimiter, false)) {
  87. stream.eat(settings.rightDelimiter);
  88. state.tokenize = parsers.html;
  89. state.localMode = htmlMixedMode;
  90. state.localState = state.htmlMixedState;
  91. return "tag";
  92. }
  93. return helpers.maybeBackup(stream, settings.rightDelimiter, smartyMode.token(stream, state.localState));
  94. },
  95. inBlock: function(style, terminator) {
  96. return function(stream, state) {
  97. while (!stream.eol()) {
  98. if (stream.match(terminator)) {
  99. helpers.cleanChain(stream, state, "");
  100. break;
  101. }
  102. stream.next();
  103. }
  104. return style;
  105. };
  106. }
  107. };
  108. return {
  109. startState: function() {
  110. var state = htmlMixedMode.startState();
  111. return {
  112. token: parsers.html,
  113. localMode: null,
  114. localState: null,
  115. htmlMixedState: state,
  116. tokenize: null,
  117. inLiteral: false
  118. };
  119. },
  120. copyState: function(state) {
  121. var local = null, tok = (state.tokenize || state.token);
  122. if (state.localState) {
  123. local = CodeMirror.copyState((tok != parsers.html ? smartyMode : htmlMixedMode), state.localState);
  124. }
  125. return {
  126. token: state.token,
  127. tokenize: state.tokenize,
  128. localMode: state.localMode,
  129. localState: local,
  130. htmlMixedState: CodeMirror.copyState(htmlMixedMode, state.htmlMixedState),
  131. inLiteral: state.inLiteral
  132. };
  133. },
  134. token: function(stream, state) {
  135. if (stream.match(settings.leftDelimiter, false)) {
  136. if (!state.inLiteral && stream.match(regs.literalOpen, true)) {
  137. state.inLiteral = true;
  138. return "keyword";
  139. } else if (state.inLiteral && stream.match(regs.literalClose, true)) {
  140. state.inLiteral = false;
  141. return "keyword";
  142. }
  143. }
  144. if (state.inLiteral && state.localState != state.htmlMixedState) {
  145. state.tokenize = parsers.html;
  146. state.localMode = htmlMixedMode;
  147. state.localState = state.htmlMixedState;
  148. }
  149. var style = (state.tokenize || state.token)(stream, state);
  150. return style;
  151. },
  152. indent: function(state, textAfter) {
  153. if (state.localMode == smartyMode
  154. || (state.inLiteral && !state.localMode)
  155. || regs.hasLeftDelimeter.test(textAfter)) {
  156. return CodeMirror.Pass;
  157. }
  158. return htmlMixedMode.indent(state.htmlMixedState, textAfter);
  159. },
  160. innerMode: function(state) {
  161. return {
  162. state: state.localState || state.htmlMixedState,
  163. mode: state.localMode || htmlMixedMode
  164. };
  165. }
  166. };
  167. }, "htmlmixed", "smarty");
  168. CodeMirror.defineMIME("text/x-smarty", "smartymixed");
  169. // vim: et ts=2 sts=2 sw=2
  170. });