File modules/editor/codemirror/mode/slim/slim.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 // Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh 5 6 (function(mod) { 7 if (typeof exports == "object" && typeof module == "object") // CommonJS 8 mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby")); 9 else if (typeof define == "function" && define.amd) // AMD 10 define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod); 11 else // Plain browser env 12 mod(CodeMirror); 13 })(function(CodeMirror) { 14 "use strict"; 15 16 CodeMirror.defineMode("slim", function(config) { 17 var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"}); 18 var rubyMode = CodeMirror.getMode(config, "ruby"); 19 var modes = { html: htmlMode, ruby: rubyMode }; 20 var embedded = { 21 ruby: "ruby", 22 javascript: "javascript", 23 css: "text/css", 24 sass: "text/x-sass", 25 scss: "text/x-scss", 26 less: "text/x-less", 27 styl: "text/x-styl", // no highlighting so far 28 coffee: "coffeescript", 29 asciidoc: "text/x-asciidoc", 30 markdown: "text/x-markdown", 31 textile: "text/x-textile", // no highlighting so far 32 creole: "text/x-creole", // no highlighting so far 33 wiki: "text/x-wiki", // no highlighting so far 34 mediawiki: "text/x-mediawiki", // no highlighting so far 35 rdoc: "text/x-rdoc", // no highlighting so far 36 builder: "text/x-builder", // no highlighting so far 37 nokogiri: "text/x-nokogiri", // no highlighting so far 38 erb: "application/x-erb" 39 }; 40 var embeddedRegexp = function(map){ 41 var arr = []; 42 for(var key in map) arr.push(key); 43 return new RegExp("^("+arr.join('|')+"):"); 44 }(embedded); 45 46 var styleMap = { 47 "commentLine": "comment", 48 "slimSwitch": "operator special", 49 "slimTag": "tag", 50 "slimId": "attribute def", 51 "slimClass": "attribute qualifier", 52 "slimAttribute": "attribute", 53 "slimSubmode": "keyword special", 54 "closeAttributeTag": null, 55 "slimDoctype": null, 56 "lineContinuation": null 57 }; 58 var closing = { 59 "{": "}", 60 "[": "]", 61 "(": ")" 62 }; 63 64 var nameStartChar = "_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD"; 65 var nameChar = nameStartChar + "\\-0-9\xB7\u0300-\u036F\u203F-\u2040"; 66 var nameRegexp = new RegExp("^[:"+nameStartChar+"](?::["+nameChar+"]|["+nameChar+"]*)"); 67 var attributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*(?=\\s*=)"); 68 var wrappedAttributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*"); 69 var classNameRegexp = /^\.-?[_a-zA-Z]+[\w\-]*/; 70 var classIdRegexp = /^#[_a-zA-Z]+[\w\-]*/; 71 72 function backup(pos, tokenize, style) { 73 var restore = function(stream, state) { 74 state.tokenize = tokenize; 75 if (stream.pos < pos) { 76 stream.pos = pos; 77 return style; 78 } 79 return state.tokenize(stream, state); 80 }; 81 return function(stream, state) { 82 state.tokenize = restore; 83 return tokenize(stream, state); 84 }; 85 } 86 87 function maybeBackup(stream, state, pat, offset, style) { 88 var cur = stream.current(); 89 var idx = cur.search(pat); 90 if (idx > -1) { 91 state.tokenize = backup(stream.pos, state.tokenize, style); 92 stream.backUp(cur.length - idx - offset); 93 } 94 return style; 95 } 96 97 function continueLine(state, column) { 98 state.stack = { 99 parent: state.stack, 100 style: "continuation", 101 indented: column, 102 tokenize: state.line 103 }; 104 state.line = state.tokenize; 105 } 106 function finishContinue(state) { 107 if (state.line == state.tokenize) { 108 state.line = state.stack.tokenize; 109 state.stack = state.stack.parent; 110 } 111 } 112 113 function lineContinuable(column, tokenize) { 114 return function(stream, state) { 115 finishContinue(state); 116 if (stream.match(/^\\$/)) { 117 continueLine(state, column); 118 return "lineContinuation"; 119 } 120 var style = tokenize(stream, state); 121 if (stream.eol() && stream.current().match(/(?:^|[^\\])(?:\\\\)*\\$/)) { 122 stream.backUp(1); 123 } 124 return style; 125 }; 126 } 127 function commaContinuable(column, tokenize) { 128 return function(stream, state) { 129 finishContinue(state); 130 var style = tokenize(stream, state); 131 if (stream.eol() && stream.current().match(/,$/)) { 132 continueLine(state, column); 133 } 134 return style; 135 }; 136 } 137 138 function rubyInQuote(endQuote, tokenize) { 139 // TODO: add multi line support 140 return function(stream, state) { 141 var ch = stream.peek(); 142 if (ch == endQuote && state.rubyState.tokenize.length == 1) { 143 // step out of ruby context as it seems to complete processing all the braces 144 stream.next(); 145 state.tokenize = tokenize; 146 return "closeAttributeTag"; 147 } else { 148 return ruby(stream, state); 149 } 150 }; 151 } 152 function startRubySplat(tokenize) { 153 var rubyState; 154 var runSplat = function(stream, state) { 155 if (state.rubyState.tokenize.length == 1 && !state.rubyState.context.prev) { 156 stream.backUp(1); 157 if (stream.eatSpace()) { 158 state.rubyState = rubyState; 159 state.tokenize = tokenize; 160 return tokenize(stream, state); 161 } 162 stream.next(); 163 } 164 return ruby(stream, state); 165 }; 166 return function(stream, state) { 167 rubyState = state.rubyState; 168 state.rubyState = CodeMirror.startState(rubyMode); 169 state.tokenize = runSplat; 170 return ruby(stream, state); 171 }; 172 } 173 174 function ruby(stream, state) { 175 return rubyMode.token(stream, state.rubyState); 176 } 177 178 function htmlLine(stream, state) { 179 if (stream.match(/^\\$/)) { 180 return "lineContinuation"; 181 } 182 return html(stream, state); 183 } 184 function html(stream, state) { 185 if (stream.match(/^#\{/)) { 186 state.tokenize = rubyInQuote("}", state.tokenize); 187 return null; 188 } 189 return maybeBackup(stream, state, /[^\\]#\{/, 1, htmlMode.token(stream, state.htmlState)); 190 } 191 192 function startHtmlLine(lastTokenize) { 193 return function(stream, state) { 194 var style = htmlLine(stream, state); 195 if (stream.eol()) state.tokenize = lastTokenize; 196 return style; 197 }; 198 } 199 200 function startHtmlMode(stream, state, offset) { 201 state.stack = { 202 parent: state.stack, 203 style: "html", 204 indented: stream.column() + offset, // pipe + space 205 tokenize: state.line 206 }; 207 state.line = state.tokenize = html; 208 return null; 209 } 210 211 function comment(stream, state) { 212 stream.skipToEnd(); 213 return state.stack.style; 214 } 215 216 function commentMode(stream, state) { 217 state.stack = { 218 parent: state.stack, 219 style: "comment", 220 indented: state.indented + 1, 221 tokenize: state.line 222 }; 223 state.line = comment; 224 return comment(stream, state); 225 } 226 227 function attributeWrapper(stream, state) { 228 if (stream.eat(state.stack.endQuote)) { 229 state.line = state.stack.line; 230 state.tokenize = state.stack.tokenize; 231 state.stack = state.stack.parent; 232 return null; 233 } 234 if (stream.match(wrappedAttributeNameRegexp)) { 235 state.tokenize = attributeWrapperAssign; 236 return "slimAttribute"; 237 } 238 stream.next(); 239 return null; 240 } 241 function attributeWrapperAssign(stream, state) { 242 if (stream.match(/^==?/)) { 243 state.tokenize = attributeWrapperValue; 244 return null; 245 } 246 return attributeWrapper(stream, state); 247 } 248 function attributeWrapperValue(stream, state) { 249 var ch = stream.peek(); 250 if (ch == '"' || ch == "\'") { 251 state.tokenize = readQuoted(ch, "string", true, false, attributeWrapper); 252 stream.next(); 253 return state.tokenize(stream, state); 254 } 255 if (ch == '[') { 256 return startRubySplat(attributeWrapper)(stream, state); 257 } 258 if (stream.match(/^(true|false|nil)\b/)) { 259 state.tokenize = attributeWrapper; 260 return "keyword"; 261 } 262 return startRubySplat(attributeWrapper)(stream, state); 263 } 264 265 function startAttributeWrapperMode(state, endQuote, tokenize) { 266 state.stack = { 267 parent: state.stack, 268 style: "wrapper", 269 indented: state.indented + 1, 270 tokenize: tokenize, 271 line: state.line, 272 endQuote: endQuote 273 }; 274 state.line = state.tokenize = attributeWrapper; 275 return null; 276 } 277 278 function sub(stream, state) { 279 if (stream.match(/^#\{/)) { 280 state.tokenize = rubyInQuote("}", state.tokenize); 281 return null; 282 } 283 var subStream = new CodeMirror.StringStream(stream.string.slice(state.stack.indented), stream.tabSize); 284 subStream.pos = stream.pos - state.stack.indented; 285 subStream.start = stream.start - state.stack.indented; 286 subStream.lastColumnPos = stream.lastColumnPos - state.stack.indented; 287 subStream.lastColumnValue = stream.lastColumnValue - state.stack.indented; 288 var style = state.subMode.token(subStream, state.subState); 289 stream.pos = subStream.pos + state.stack.indented; 290 return style; 291 } 292 function firstSub(stream, state) { 293 state.stack.indented = stream.column(); 294 state.line = state.tokenize = sub; 295 return state.tokenize(stream, state); 296 } 297 298 function createMode(mode) { 299 var query = embedded[mode]; 300 var spec = CodeMirror.mimeModes[query]; 301 if (spec) { 302 return CodeMirror.getMode(config, spec); 303 } 304 var factory = CodeMirror.modes[query]; 305 if (factory) { 306 return factory(config, {name: query}); 307 } 308 return CodeMirror.getMode(config, "null"); 309 } 310 311 function getMode(mode) { 312 if (!modes.hasOwnProperty(mode)) { 313 return modes[mode] = createMode(mode); 314 } 315 return modes[mode]; 316 } 317 318 function startSubMode(mode, state) { 319 var subMode = getMode(mode); 320 var subState = CodeMirror.startState(subMode); 321 322 state.subMode = subMode; 323 state.subState = subState; 324 325 state.stack = { 326 parent: state.stack, 327 style: "sub", 328 indented: state.indented + 1, 329 tokenize: state.line 330 }; 331 state.line = state.tokenize = firstSub; 332 return "slimSubmode"; 333 } 334 335 function doctypeLine(stream, _state) { 336 stream.skipToEnd(); 337 return "slimDoctype"; 338 } 339 340 function startLine(stream, state) { 341 var ch = stream.peek(); 342 if (ch == '<') { 343 return (state.tokenize = startHtmlLine(state.tokenize))(stream, state); 344 } 345 if (stream.match(/^[|']/)) { 346 return startHtmlMode(stream, state, 1); 347 } 348 if (stream.match(/^\/(!|\[\w+])?/)) { 349 return commentMode(stream, state); 350 } 351 if (stream.match(/^(-|==?[<>]?)/)) { 352 state.tokenize = lineContinuable(stream.column(), commaContinuable(stream.column(), ruby)); 353 return "slimSwitch"; 354 } 355 if (stream.match(/^doctype\b/)) { 356 state.tokenize = doctypeLine; 357 return "keyword"; 358 } 359 360 var m = stream.match(embeddedRegexp); 361 if (m) { 362 return startSubMode(m[1], state); 363 } 364 365 return slimTag(stream, state); 366 } 367 368 function slim(stream, state) { 369 if (state.startOfLine) { 370 return startLine(stream, state); 371 } 372 return slimTag(stream, state); 373 } 374 375 function slimTag(stream, state) { 376 if (stream.eat('*')) { 377 state.tokenize = startRubySplat(slimTagExtras); 378 return null; 379 } 380 if (stream.match(nameRegexp)) { 381 state.tokenize = slimTagExtras; 382 return "slimTag"; 383 } 384 return slimClass(stream, state); 385 } 386 function slimTagExtras(stream, state) { 387 if (stream.match(/^(<>?|><?)/)) { 388 state.tokenize = slimClass; 389 return null; 390 } 391 return slimClass(stream, state); 392 } 393 function slimClass(stream, state) { 394 if (stream.match(classIdRegexp)) { 395 state.tokenize = slimClass; 396 return "slimId"; 397 } 398 if (stream.match(classNameRegexp)) { 399 state.tokenize = slimClass; 400 return "slimClass"; 401 } 402 return slimAttribute(stream, state); 403 } 404 function slimAttribute(stream, state) { 405 if (stream.match(/^([\[\{\(])/)) { 406 return startAttributeWrapperMode(state, closing[RegExp.$1], slimAttribute); 407 } 408 if (stream.match(attributeNameRegexp)) { 409 state.tokenize = slimAttributeAssign; 410 return "slimAttribute"; 411 } 412 if (stream.peek() == '*') { 413 stream.next(); 414 state.tokenize = startRubySplat(slimContent); 415 return null; 416 } 417 return slimContent(stream, state); 418 } 419 function slimAttributeAssign(stream, state) { 420 if (stream.match(/^==?/)) { 421 state.tokenize = slimAttributeValue; 422 return null; 423 } 424 // should never happen, because of forward lookup 425 return slimAttribute(stream, state); 426 } 427 428 function slimAttributeValue(stream, state) { 429 var ch = stream.peek(); 430 if (ch == '"' || ch == "\'") { 431 state.tokenize = readQuoted(ch, "string", true, false, slimAttribute); 432 stream.next(); 433 return state.tokenize(stream, state); 434 } 435 if (ch == '[') { 436 return startRubySplat(slimAttribute)(stream, state); 437 } 438 if (ch == ':') { 439 return startRubySplat(slimAttributeSymbols)(stream, state); 440 } 441 if (stream.match(/^(true|false|nil)\b/)) { 442 state.tokenize = slimAttribute; 443 return "keyword"; 444 } 445 return startRubySplat(slimAttribute)(stream, state); 446 } 447 function slimAttributeSymbols(stream, state) { 448 stream.backUp(1); 449 if (stream.match(/^[^\s],(?=:)/)) { 450 state.tokenize = startRubySplat(slimAttributeSymbols); 451 return null; 452 } 453 stream.next(); 454 return slimAttribute(stream, state); 455 } 456 function readQuoted(quote, style, embed, unescaped, nextTokenize) { 457 return function(stream, state) { 458 finishContinue(state); 459 var fresh = stream.current().length == 0; 460 if (stream.match(/^\\$/, fresh)) { 461 if (!fresh) return style; 462 continueLine(state, state.indented); 463 return "lineContinuation"; 464 } 465 if (stream.match(/^#\{/, fresh)) { 466 if (!fresh) return style; 467 state.tokenize = rubyInQuote("}", state.tokenize); 468 return null; 469 } 470 var escaped = false, ch; 471 while ((ch = stream.next()) != null) { 472 if (ch == quote && (unescaped || !escaped)) { 473 state.tokenize = nextTokenize; 474 break; 475 } 476 if (embed && ch == "#" && !escaped) { 477 if (stream.eat("{")) { 478 stream.backUp(2); 479 break; 480 } 481 } 482 escaped = !escaped && ch == "\\"; 483 } 484 if (stream.eol() && escaped) { 485 stream.backUp(1); 486 } 487 return style; 488 }; 489 } 490 function slimContent(stream, state) { 491 if (stream.match(/^==?/)) { 492 state.tokenize = ruby; 493 return "slimSwitch"; 494 } 495 if (stream.match(/^\/$/)) { // tag close hint 496 state.tokenize = slim; 497 return null; 498 } 499 if (stream.match(/^:/)) { // inline tag 500 state.tokenize = slimTag; 501 return "slimSwitch"; 502 } 503 startHtmlMode(stream, state, 0); 504 return state.tokenize(stream, state); 505 } 506 507 var mode = { 508 // default to html mode 509 startState: function() { 510 var htmlState = CodeMirror.startState(htmlMode); 511 var rubyState = CodeMirror.startState(rubyMode); 512 return { 513 htmlState: htmlState, 514 rubyState: rubyState, 515 stack: null, 516 last: null, 517 tokenize: slim, 518 line: slim, 519 indented: 0 520 }; 521 }, 522 523 copyState: function(state) { 524 return { 525 htmlState : CodeMirror.copyState(htmlMode, state.htmlState), 526 rubyState: CodeMirror.copyState(rubyMode, state.rubyState), 527 subMode: state.subMode, 528 subState: state.subMode && CodeMirror.copyState(state.subMode, state.subState), 529 stack: state.stack, 530 last: state.last, 531 tokenize: state.tokenize, 532 line: state.line 533 }; 534 }, 535 536 token: function(stream, state) { 537 if (stream.sol()) { 538 state.indented = stream.indentation(); 539 state.startOfLine = true; 540 state.tokenize = state.line; 541 while (state.stack && state.stack.indented > state.indented && state.last != "slimSubmode") { 542 state.line = state.tokenize = state.stack.tokenize; 543 state.stack = state.stack.parent; 544 state.subMode = null; 545 state.subState = null; 546 } 547 } 548 if (stream.eatSpace()) return null; 549 var style = state.tokenize(stream, state); 550 state.startOfLine = false; 551 if (style) state.last = style; 552 return styleMap.hasOwnProperty(style) ? styleMap[style] : style; 553 }, 554 555 blankLine: function(state) { 556 if (state.subMode && state.subMode.blankLine) { 557 return state.subMode.blankLine(state.subState); 558 } 559 }, 560 561 innerMode: function(state) { 562 if (state.subMode) return {state: state.subState, mode: state.subMode}; 563 return {state: state, mode: mode}; 564 } 565 566 //indent: function(state) { 567 // return state.indented; 568 //} 569 }; 570 return mode; 571 }, "htmlmixed", "ruby"); 572 573 CodeMirror.defineMIME("text/x-slim", "slim"); 574 CodeMirror.defineMIME("application/x-slim", "slim"); 575 });
Download modules/editor/codemirror/mode/slim/slim.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.