haml.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. (function() {
  2. "use strict";
  3. // full haml mode. This handled embeded ruby and html fragments too
  4. CodeMirror.defineMode("haml", function(config) {
  5. var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"});
  6. var rubyMode = CodeMirror.getMode(config, "ruby");
  7. function rubyInQuote(endQuote) {
  8. return function(stream, state) {
  9. var ch = stream.peek();
  10. if (ch == endQuote && state.rubyState.tokenize.length == 1) {
  11. // step out of ruby context as it seems to complete processing all the braces
  12. stream.next();
  13. state.tokenize = html;
  14. return "closeAttributeTag";
  15. } else {
  16. return ruby(stream, state);
  17. }
  18. };
  19. }
  20. function ruby(stream, state) {
  21. if (stream.match("-#")) {
  22. stream.skipToEnd();
  23. return "comment";
  24. }
  25. return rubyMode.token(stream, state.rubyState);
  26. }
  27. function html(stream, state) {
  28. var ch = stream.peek();
  29. // handle haml declarations. All declarations that cant be handled here
  30. // will be passed to html mode
  31. if (state.previousToken.style == "comment" ) {
  32. if (state.indented > state.previousToken.indented) {
  33. stream.skipToEnd();
  34. return "commentLine";
  35. }
  36. }
  37. if (state.startOfLine) {
  38. if (ch == "!" && stream.match("!!")) {
  39. stream.skipToEnd();
  40. return "tag";
  41. } else if (stream.match(/^%[\w:#\.]+=/)) {
  42. state.tokenize = ruby;
  43. return "hamlTag";
  44. } else if (stream.match(/^%[\w:]+/)) {
  45. return "hamlTag";
  46. } else if (ch == "/" ) {
  47. stream.skipToEnd();
  48. return "comment";
  49. }
  50. }
  51. if (state.startOfLine || state.previousToken.style == "hamlTag") {
  52. if ( ch == "#" || ch == ".") {
  53. stream.match(/[\w-#\.]*/);
  54. return "hamlAttribute";
  55. }
  56. }
  57. // donot handle --> as valid ruby, make it HTML close comment instead
  58. if (state.startOfLine && !stream.match("-->", false) && (ch == "=" || ch == "-" )) {
  59. state.tokenize = ruby;
  60. return null;
  61. }
  62. if (state.previousToken.style == "hamlTag" ||
  63. state.previousToken.style == "closeAttributeTag" ||
  64. state.previousToken.style == "hamlAttribute") {
  65. if (ch == "(") {
  66. state.tokenize = rubyInQuote(")");
  67. return null;
  68. } else if (ch == "{") {
  69. state.tokenize = rubyInQuote("}");
  70. return null;
  71. }
  72. }
  73. return htmlMode.token(stream, state.htmlState);
  74. }
  75. return {
  76. // default to html mode
  77. startState: function() {
  78. var htmlState = htmlMode.startState();
  79. var rubyState = rubyMode.startState();
  80. return {
  81. htmlState: htmlState,
  82. rubyState: rubyState,
  83. indented: 0,
  84. previousToken: { style: null, indented: 0},
  85. tokenize: html
  86. };
  87. },
  88. copyState: function(state) {
  89. return {
  90. htmlState : CodeMirror.copyState(htmlMode, state.htmlState),
  91. rubyState: CodeMirror.copyState(rubyMode, state.rubyState),
  92. indented: state.indented,
  93. previousToken: state.previousToken,
  94. tokenize: state.tokenize
  95. };
  96. },
  97. token: function(stream, state) {
  98. if (stream.sol()) {
  99. state.indented = stream.indentation();
  100. state.startOfLine = true;
  101. }
  102. if (stream.eatSpace()) return null;
  103. var style = state.tokenize(stream, state);
  104. state.startOfLine = false;
  105. // dont record comment line as we only want to measure comment line with
  106. // the opening comment block
  107. if (style && style != "commentLine") {
  108. state.previousToken = { style: style, indented: state.indented };
  109. }
  110. // if current state is ruby and the previous token is not `,` reset the
  111. // tokenize to html
  112. if (stream.eol() && state.tokenize == ruby) {
  113. stream.backUp(1);
  114. var ch = stream.peek();
  115. stream.next();
  116. if (ch && ch != ",") {
  117. state.tokenize = html;
  118. }
  119. }
  120. // reprocess some of the specific style tag when finish setting previousToken
  121. if (style == "hamlTag") {
  122. style = "tag";
  123. } else if (style == "commentLine") {
  124. style = "comment";
  125. } else if (style == "hamlAttribute") {
  126. style = "attribute";
  127. } else if (style == "closeAttributeTag") {
  128. style = null;
  129. }
  130. return style;
  131. },
  132. indent: function(state) {
  133. return state.indented;
  134. }
  135. };
  136. }, "htmlmixed", "ruby");
  137. CodeMirror.defineMIME("text/x-haml", "haml");
  138. })();