smartymixed.js 6.1 KB

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