File modules/editor/codemirror/mode/sass/sass.js

Last commit: Sun Dec 17 01:14:09 2017 +0100	Jan Dankert	Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 // Distributed under an MIT license: http://codemirror.net/LICENSE 3 4 (function(mod) { 5 if (typeof exports == "object" && typeof module == "object") // CommonJS 6 mod(require("../../lib/codemirror"), require("../css/css")); 7 else if (typeof define == "function" && define.amd) // AMD 8 define(["../../lib/codemirror", "../css/css"], mod); 9 else // Plain browser env 10 mod(CodeMirror); 11 })(function(CodeMirror) { 12 "use strict"; 13 14 CodeMirror.defineMode("sass", function(config) { 15 var cssMode = CodeMirror.mimeModes["text/css"]; 16 var propertyKeywords = cssMode.propertyKeywords || {}, 17 colorKeywords = cssMode.colorKeywords || {}, 18 valueKeywords = cssMode.valueKeywords || {}, 19 fontProperties = cssMode.fontProperties || {}; 20 21 function tokenRegexp(words) { 22 return new RegExp("^" + words.join("|")); 23 } 24 25 var keywords = ["true", "false", "null", "auto"]; 26 var keywordsRegexp = new RegExp("^" + keywords.join("|")); 27 28 var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-", 29 "\\!=", "/", "\\*", "%", "and", "or", "not", ";","\\{","\\}",":"]; 30 var opRegexp = tokenRegexp(operators); 31 32 var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/; 33 34 var word; 35 36 function isEndLine(stream) { 37 return !stream.peek() || stream.match(/\s+$/, false); 38 } 39 40 function urlTokens(stream, state) { 41 var ch = stream.peek(); 42 43 if (ch === ")") { 44 stream.next(); 45 state.tokenizer = tokenBase; 46 return "operator"; 47 } else if (ch === "(") { 48 stream.next(); 49 stream.eatSpace(); 50 51 return "operator"; 52 } else if (ch === "'" || ch === '"') { 53 state.tokenizer = buildStringTokenizer(stream.next()); 54 return "string"; 55 } else { 56 state.tokenizer = buildStringTokenizer(")", false); 57 return "string"; 58 } 59 } 60 function comment(indentation, multiLine) { 61 return function(stream, state) { 62 if (stream.sol() && stream.indentation() <= indentation) { 63 state.tokenizer = tokenBase; 64 return tokenBase(stream, state); 65 } 66 67 if (multiLine && stream.skipTo("*/")) { 68 stream.next(); 69 stream.next(); 70 state.tokenizer = tokenBase; 71 } else { 72 stream.skipToEnd(); 73 } 74 75 return "comment"; 76 }; 77 } 78 79 function buildStringTokenizer(quote, greedy) { 80 if (greedy == null) { greedy = true; } 81 82 function stringTokenizer(stream, state) { 83 var nextChar = stream.next(); 84 var peekChar = stream.peek(); 85 var previousChar = stream.string.charAt(stream.pos-2); 86 87 var endingString = ((nextChar !== "\\" && peekChar === quote) || (nextChar === quote && previousChar !== "\\")); 88 89 if (endingString) { 90 if (nextChar !== quote && greedy) { stream.next(); } 91 if (isEndLine(stream)) { 92 state.cursorHalf = 0; 93 } 94 state.tokenizer = tokenBase; 95 return "string"; 96 } else if (nextChar === "#" && peekChar === "{") { 97 state.tokenizer = buildInterpolationTokenizer(stringTokenizer); 98 stream.next(); 99 return "operator"; 100 } else { 101 return "string"; 102 } 103 } 104 105 return stringTokenizer; 106 } 107 108 function buildInterpolationTokenizer(currentTokenizer) { 109 return function(stream, state) { 110 if (stream.peek() === "}") { 111 stream.next(); 112 state.tokenizer = currentTokenizer; 113 return "operator"; 114 } else { 115 return tokenBase(stream, state); 116 } 117 }; 118 } 119 120 function indent(state) { 121 if (state.indentCount == 0) { 122 state.indentCount++; 123 var lastScopeOffset = state.scopes[0].offset; 124 var currentOffset = lastScopeOffset + config.indentUnit; 125 state.scopes.unshift({ offset:currentOffset }); 126 } 127 } 128 129 function dedent(state) { 130 if (state.scopes.length == 1) return; 131 132 state.scopes.shift(); 133 } 134 135 function tokenBase(stream, state) { 136 var ch = stream.peek(); 137 138 // Comment 139 if (stream.match("/*")) { 140 state.tokenizer = comment(stream.indentation(), true); 141 return state.tokenizer(stream, state); 142 } 143 if (stream.match("//")) { 144 state.tokenizer = comment(stream.indentation(), false); 145 return state.tokenizer(stream, state); 146 } 147 148 // Interpolation 149 if (stream.match("#{")) { 150 state.tokenizer = buildInterpolationTokenizer(tokenBase); 151 return "operator"; 152 } 153 154 // Strings 155 if (ch === '"' || ch === "'") { 156 stream.next(); 157 state.tokenizer = buildStringTokenizer(ch); 158 return "string"; 159 } 160 161 if(!state.cursorHalf){// state.cursorHalf === 0 162 // first half i.e. before : for key-value pairs 163 // including selectors 164 165 if (ch === "-") { 166 if (stream.match(/^-\w+-/)) { 167 return "meta"; 168 } 169 } 170 171 if (ch === ".") { 172 stream.next(); 173 if (stream.match(/^[\w-]+/)) { 174 indent(state); 175 return "qualifier"; 176 } else if (stream.peek() === "#") { 177 indent(state); 178 return "tag"; 179 } 180 } 181 182 if (ch === "#") { 183 stream.next(); 184 // ID selectors 185 if (stream.match(/^[\w-]+/)) { 186 indent(state); 187 return "builtin"; 188 } 189 if (stream.peek() === "#") { 190 indent(state); 191 return "tag"; 192 } 193 } 194 195 // Variables 196 if (ch === "$") { 197 stream.next(); 198 stream.eatWhile(/[\w-]/); 199 return "variable-2"; 200 } 201 202 // Numbers 203 if (stream.match(/^-?[0-9\.]+/)) 204 return "number"; 205 206 // Units 207 if (stream.match(/^(px|em|in)\b/)) 208 return "unit"; 209 210 if (stream.match(keywordsRegexp)) 211 return "keyword"; 212 213 if (stream.match(/^url/) && stream.peek() === "(") { 214 state.tokenizer = urlTokens; 215 return "atom"; 216 } 217 218 if (ch === "=") { 219 // Match shortcut mixin definition 220 if (stream.match(/^=[\w-]+/)) { 221 indent(state); 222 return "meta"; 223 } 224 } 225 226 if (ch === "+") { 227 // Match shortcut mixin definition 228 if (stream.match(/^\+[\w-]+/)){ 229 return "variable-3"; 230 } 231 } 232 233 if(ch === "@"){ 234 if(stream.match(/@extend/)){ 235 if(!stream.match(/\s*[\w]/)) 236 dedent(state); 237 } 238 } 239 240 241 // Indent Directives 242 if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) { 243 indent(state); 244 return "def"; 245 } 246 247 // Other Directives 248 if (ch === "@") { 249 stream.next(); 250 stream.eatWhile(/[\w-]/); 251 return "def"; 252 } 253 254 if (stream.eatWhile(/[\w-]/)){ 255 if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){ 256 word = stream.current().toLowerCase(); 257 var prop = state.prevProp + "-" + word; 258 if (propertyKeywords.hasOwnProperty(prop)) { 259 return "property"; 260 } else if (propertyKeywords.hasOwnProperty(word)) { 261 state.prevProp = word; 262 return "property"; 263 } else if (fontProperties.hasOwnProperty(word)) { 264 return "property"; 265 } 266 return "tag"; 267 } 268 else if(stream.match(/ *:/,false)){ 269 indent(state); 270 state.cursorHalf = 1; 271 state.prevProp = stream.current().toLowerCase(); 272 return "property"; 273 } 274 else if(stream.match(/ *,/,false)){ 275 return "tag"; 276 } 277 else{ 278 indent(state); 279 return "tag"; 280 } 281 } 282 283 if(ch === ":"){ 284 if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element 285 return "variable-3"; 286 } 287 stream.next(); 288 state.cursorHalf=1; 289 return "operator"; 290 } 291 292 } // cursorHalf===0 ends here 293 else{ 294 295 if (ch === "#") { 296 stream.next(); 297 // Hex numbers 298 if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){ 299 if (isEndLine(stream)) { 300 state.cursorHalf = 0; 301 } 302 return "number"; 303 } 304 } 305 306 // Numbers 307 if (stream.match(/^-?[0-9\.]+/)){ 308 if (isEndLine(stream)) { 309 state.cursorHalf = 0; 310 } 311 return "number"; 312 } 313 314 // Units 315 if (stream.match(/^(px|em|in)\b/)){ 316 if (isEndLine(stream)) { 317 state.cursorHalf = 0; 318 } 319 return "unit"; 320 } 321 322 if (stream.match(keywordsRegexp)){ 323 if (isEndLine(stream)) { 324 state.cursorHalf = 0; 325 } 326 return "keyword"; 327 } 328 329 if (stream.match(/^url/) && stream.peek() === "(") { 330 state.tokenizer = urlTokens; 331 if (isEndLine(stream)) { 332 state.cursorHalf = 0; 333 } 334 return "atom"; 335 } 336 337 // Variables 338 if (ch === "$") { 339 stream.next(); 340 stream.eatWhile(/[\w-]/); 341 if (isEndLine(stream)) { 342 state.cursorHalf = 0; 343 } 344 return "variable-2"; 345 } 346 347 // bang character for !important, !default, etc. 348 if (ch === "!") { 349 stream.next(); 350 state.cursorHalf = 0; 351 return stream.match(/^[\w]+/) ? "keyword": "operator"; 352 } 353 354 if (stream.match(opRegexp)){ 355 if (isEndLine(stream)) { 356 state.cursorHalf = 0; 357 } 358 return "operator"; 359 } 360 361 // attributes 362 if (stream.eatWhile(/[\w-]/)) { 363 if (isEndLine(stream)) { 364 state.cursorHalf = 0; 365 } 366 word = stream.current().toLowerCase(); 367 if (valueKeywords.hasOwnProperty(word)) { 368 return "atom"; 369 } else if (colorKeywords.hasOwnProperty(word)) { 370 return "keyword"; 371 } else if (propertyKeywords.hasOwnProperty(word)) { 372 state.prevProp = stream.current().toLowerCase(); 373 return "property"; 374 } else { 375 return "tag"; 376 } 377 } 378 379 //stream.eatSpace(); 380 if (isEndLine(stream)) { 381 state.cursorHalf = 0; 382 return null; 383 } 384 385 } // else ends here 386 387 if (stream.match(opRegexp)) 388 return "operator"; 389 390 // If we haven't returned by now, we move 1 character 391 // and return an error 392 stream.next(); 393 return null; 394 } 395 396 function tokenLexer(stream, state) { 397 if (stream.sol()) state.indentCount = 0; 398 var style = state.tokenizer(stream, state); 399 var current = stream.current(); 400 401 if (current === "@return" || current === "}"){ 402 dedent(state); 403 } 404 405 if (style !== null) { 406 var startOfToken = stream.pos - current.length; 407 408 var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount); 409 410 var newScopes = []; 411 412 for (var i = 0; i < state.scopes.length; i++) { 413 var scope = state.scopes[i]; 414 415 if (scope.offset <= withCurrentIndent) 416 newScopes.push(scope); 417 } 418 419 state.scopes = newScopes; 420 } 421 422 423 return style; 424 } 425 426 return { 427 startState: function() { 428 return { 429 tokenizer: tokenBase, 430 scopes: [{offset: 0, type: "sass"}], 431 indentCount: 0, 432 cursorHalf: 0, // cursor half tells us if cursor lies after (1) 433 // or before (0) colon (well... more or less) 434 definedVars: [], 435 definedMixins: [] 436 }; 437 }, 438 token: function(stream, state) { 439 var style = tokenLexer(stream, state); 440 441 state.lastToken = { style: style, content: stream.current() }; 442 443 return style; 444 }, 445 446 indent: function(state) { 447 return state.scopes[0].offset; 448 } 449 }; 450 }, "css"); 451 452 CodeMirror.defineMIME("text/x-sass", "sass"); 453 454 });
Download modules/editor/codemirror/mode/sass/sass.js
History Sun, 17 Dec 2017 01:14:09 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.