smartymixed.js 5.2 KB

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