smartymixed.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. var htmlTagName = state.htmlMixedState.htmlState.context && state.htmlMixedState.htmlState.context.tagName
  69. ? state.htmlMixedState.htmlState.context.tagName
  70. : null;
  71. if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false) && htmlTagName === null) {
  72. state.tokenize = parsers.smarty;
  73. state.localMode = smartyMode;
  74. state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
  75. return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
  76. } else if (!state.inLiteral && stream.match(settings.leftDelimiter, false)) {
  77. state.tokenize = parsers.smarty;
  78. state.localMode = smartyMode;
  79. state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
  80. return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
  81. }
  82. return htmlMixedMode.token(stream, state.htmlMixedState);
  83. },
  84. smarty: function(stream, state) {
  85. if (stream.match(settings.leftDelimiter, false)) {
  86. if (stream.match(regs.smartyComment, false)) {
  87. return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
  88. }
  89. } else if (stream.match(settings.rightDelimiter, false)) {
  90. stream.eat(settings.rightDelimiter);
  91. state.tokenize = parsers.html;
  92. state.localMode = htmlMixedMode;
  93. state.localState = state.htmlMixedState;
  94. return "tag";
  95. }
  96. return helpers.maybeBackup(stream, settings.rightDelimiter, smartyMode.token(stream, state.localState));
  97. },
  98. inBlock: function(style, terminator) {
  99. return function(stream, state) {
  100. while (!stream.eol()) {
  101. if (stream.match(terminator)) {
  102. helpers.cleanChain(stream, state, "");
  103. break;
  104. }
  105. stream.next();
  106. }
  107. return style;
  108. };
  109. }
  110. };
  111. return {
  112. startState: function() {
  113. var state = htmlMixedMode.startState();
  114. return {
  115. token: parsers.html,
  116. localMode: null,
  117. localState: null,
  118. htmlMixedState: state,
  119. tokenize: null,
  120. inLiteral: false
  121. };
  122. },
  123. copyState: function(state) {
  124. var local = null, tok = (state.tokenize || state.token);
  125. if (state.localState) {
  126. local = CodeMirror.copyState((tok != parsers.html ? smartyMode : htmlMixedMode), state.localState);
  127. }
  128. return {
  129. token: state.token,
  130. tokenize: state.tokenize,
  131. localMode: state.localMode,
  132. localState: local,
  133. htmlMixedState: CodeMirror.copyState(htmlMixedMode, state.htmlMixedState),
  134. inLiteral: state.inLiteral
  135. };
  136. },
  137. token: function(stream, state) {
  138. if (stream.match(settings.leftDelimiter, false)) {
  139. if (!state.inLiteral && stream.match(regs.literalOpen, true)) {
  140. state.inLiteral = true;
  141. return "keyword";
  142. } else if (state.inLiteral && stream.match(regs.literalClose, true)) {
  143. state.inLiteral = false;
  144. return "keyword";
  145. }
  146. }
  147. if (state.inLiteral && state.localState != state.htmlMixedState) {
  148. state.tokenize = parsers.html;
  149. state.localMode = htmlMixedMode;
  150. state.localState = state.htmlMixedState;
  151. }
  152. var style = (state.tokenize || state.token)(stream, state);
  153. return style;
  154. },
  155. indent: function(state, textAfter) {
  156. if (state.localMode == smartyMode
  157. || (state.inLiteral && !state.localMode)
  158. || regs.hasLeftDelimeter.test(textAfter)) {
  159. return CodeMirror.Pass;
  160. }
  161. return htmlMixedMode.indent(state.htmlMixedState, textAfter);
  162. },
  163. innerMode: function(state) {
  164. return {
  165. state: state.localState || state.htmlMixedState,
  166. mode: state.localMode || htmlMixedMode
  167. };
  168. }
  169. };
  170. }, "htmlmixed", "smarty");
  171. CodeMirror.defineMIME("text/x-smarty", "smartymixed");
  172. // vim: et ts=2 sts=2 sw=2
  173. });