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