1// All functions that need access to the editor's state live inside 2// the CodeMirror function. Below that, at the bottom of the file, 3// some utilities are defined. 4 5// CodeMirror is the only global var we claim 6var CodeMirror = (function() { 7 // This is the function that produces an editor instance. It's 8 // closure is used to store the editor state. 9 function CodeMirror(place, givenOptions) { 10 // Determine effective options based on given values and defaults. 11 var options = {}, defaults = CodeMirror.defaults; 12 for (var opt in defaults) 13 if (defaults.hasOwnProperty(opt)) 14 options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; 15 16 var targetDocument = options["document"]; 17 // The element in which the editor lives. 18 var wrapper = targetDocument.createElement("div"); 19 wrapper.className = "CodeMirror"; 20 // This mess creates the base DOM structure for the editor. 21 wrapper.innerHTML = 22 '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea 23 '<textarea style="position: absolute; width: 2px;" wrap="off" ' + 24 'autocorrect="off" autocapitalize="off"></textarea></div>' + 25 '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' + 26 '<div style="position: relative">' + // Set to the height of the text, causes scrolling 27 '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' + 28 '<div style="position: relative">' + // Moved around its parent to cover visible view 29 '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' + 30 // Provides positioning relative to (visible) text origin 31 '<div class="CodeMirror-lines"><div style="position: relative" draggable="true">' + 32 '<pre class="CodeMirror-cursor"> </pre>' + // Absolutely positioned blinky cursor 33 '<div></div>' + // This DIV contains the actual code 34 '</div></div></div></div></div>'; 35 if (place.appendChild) place.appendChild(wrapper); else place(wrapper); 36 // I've never seen more elegant code in my life. 37 var inputDiv = wrapper.firstChild, input = inputDiv.firstChild, 38 scroller = wrapper.lastChild, code = scroller.firstChild, 39 measure = code.firstChild, mover = measure.nextSibling, 40 gutter = mover.firstChild, gutterText = gutter.firstChild, 41 lineSpace = gutter.nextSibling.firstChild, 42 cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling; 43 if (options.tabindex != null) input.tabindex = options.tabindex; 44 if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; 45 46 // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. 47 var poll = new Delayed(), highlight = new Delayed(), blinker; 48 49 // mode holds a mode API object. lines an array of Line objects 50 // (see Line constructor), work an array of lines that should be 51 // parsed, and history the undo history (instance of History 52 // constructor). 53 var mode, lines = [new Line("")], work, history = new History(), focused; 54 loadMode(); 55 // The selection. These are always maintained to point at valid 56 // positions. Inverted is used to remember that the user is 57 // selecting bottom-to-top. 58 var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; 59 // Selection-related flags. shiftSelecting obviously tracks 60 // whether the user is holding shift. reducedSelection is a hack 61 // to get around the fact that we can't create inverted 62 // selections. See below. 63 var shiftSelecting, reducedSelection, lastClick, lastDoubleClick; 64 // Variables used by startOperation/endOperation to track what 65 // happened during the operation. 66 var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty; 67 // Current visible range (may be bigger than the view window). 68 var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null; 69 // editing will hold an object describing the things we put in the 70 // textarea, to help figure out whether something changed. 71 // bracketHighlighted is used to remember that a backet has been 72 // marked. 73 var editing, bracketHighlighted; 74 // Tracks the maximum line length so that the horizontal scrollbar 75 // can be kept static when scrolling. 76 var maxLine = "", maxWidth; 77 78 // Initialize the content. 79 operation(function(){setValue(options.value || ""); updateInput = false;})(); 80 81 // Register our event handlers. 82 connect(scroller, "mousedown", operation(onMouseDown)); 83 connect(lineSpace, "dragstart", onDragStart); 84 // Gecko browsers fire contextmenu *after* opening the menu, at 85 // which point we can't mess with it anymore. Context menu is 86 // handled in onMouseDown for Gecko. 87 if (!gecko) connect(scroller, "contextmenu", onContextMenu); 88 connect(scroller, "scroll", function() { 89 updateDisplay([]); 90 if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; 91 if (options.onScroll) options.onScroll(instance); 92 }); 93 connect(window, "resize", function() {updateDisplay(true);}); 94 connect(input, "keyup", operation(onKeyUp)); 95 connect(input, "keydown", operation(onKeyDown)); 96 connect(input, "keypress", operation(onKeyPress));
97 connect(input, "focus", onFocus); 98 connect(input, "blur", onBlur); 99 100 connect(scroller, "dragenter", e_stop); 101 connect(scroller, "dragover", e_stop); 102 connect(scroller, "drop", operation(onDrop)); 103 connect(scroller, "paste", function(){focusInput(); fastPoll();}); 104 connect(input, "paste", function(){fastPoll();}); 105 connect(input, "cut", function(){fastPoll();}); 106 107 // IE throws unspecified error in certain cases, when 108 // trying to access activeElement before onload 109 var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { } 110 if (hasFocus) setTimeout(onFocus, 20); 111 else onBlur(); 112 113 function isLine(l) {return l >= 0 && l < lines.length;} 114 // The instance object that we'll return. Mostly calls out to 115 // local functions in the CodeMirror function. Some do some extra 116 // range checking and/or clipping. operation is used to wrap the 117 // call so that changes it makes are tracked, and the display is 118 // updated afterwards. 119 var instance = wrapper.CodeMirror = { 120 getValue: getValue, 121 setValue: operation(setValue), 122 getSelection: getSelection, 123 replaceSelection: operation(replaceSelection), 124 focus: function(){focusInput(); onFocus(); fastPoll();}, 125 setOption: function(option, value) { 126 options[option] = value; 127 if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber") 128 operation(gutterChanged)(); 129 else if (option == "mode" || option == "indentUnit") loadMode(); 130 else if (option == "readOnly" && value == "nocursor") input.blur(); 131 else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value); 132 }, 133 getOption: function(option) {return options[option];}, 134 undo: operation(undo), 135 redo: operation(redo), 136 indentLine: operation(function(n, dir) { 137 if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract"); 138 }), 139 historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, 140 matchBrackets: operation(function(){matchBrackets(true);}), 141 getTokenAt: function(pos) { 142 pos = clipPos(pos); 143 return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch); 144 }, 145 getStateAfter: function(line) { 146 line = clipLine(line == null ? lines.length - 1: line); 147 return getStateBefore(line + 1); 148 }, 149 cursorCoords: function(start){ 150 if (start == null) start = sel.inverted; 151 return pageCoords(start ? sel.from : sel.to); 152 }, 153 charCoords: function(pos){return pageCoords(clipPos(pos));}, 154 coordsChar: function(coords) { 155 var off = eltOffset(lineSpace); 156 var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight()))); 157 return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)}); 158 }, 159 getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);}, 160 markText: operation(function(a, b, c){return operation(markText(a, b, c));}), 161 setMarker: operation(addGutterMarker), 162 clearMarker: operation(removeGutterMarker), 163 setLineClass: operation(setLineClass), 164 lineInfo: lineInfo, 165 addWidget: function(pos, node, scroll, vert, horiz) { 166 pos = localCoords(clipPos(pos)); 167 var top = pos.yBot, left = pos.x; 168 node.style.position = "absolute"; 169 code.appendChild(node); 170 if (vert == "over") top = pos.y; 171 else if (vert == "near") { 172 var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()), 173 hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft(); 174 if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) 175 top = pos.y - node.offsetHeight; 176 if (left + node.offsetWidth > hspace) 177 left = hspace - node.offsetWidth; 178 } 179 node.style.top = (top + paddingTop()) + "px"; 180 node.style.left = node.style.right = ""; 181 if (horiz == "right") { 182 left = code.clientWidth - node.offsetWidth; 183 node.style.right = "0px"; 184 } else { 185 if (horiz == "left") left = 0; 186 else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2; 187 node.style.left = (left + paddingLeft()) + "px"; 188 } 189 if (scroll) 190 scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); 191 }, 192 193 lineCount: function() {return lines.length;}, 194 getCursor: function(start) { 195 if (start == null) start = sel.inverted; 196 return copyPos(start ? sel.from : sel.to); 197 }, 198 somethingSelected: function() {return !posEq(sel.from, sel.to);}, 199 setCursor: operation(function(line, ch) { 200 if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch); 201 else setCursor(line, ch); 202 }), 203 setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}), 204 getLine: function(line) {if (isLine(line)) return lines[line].text;}, 205 setLine: operation(function(line, text) { 206 if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: lines[line].text.length}); 207 }), 208 removeLine: operation(function(line) { 209 if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); 210 }), 211 replaceRange: operation(replaceRange), 212 getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, 213 214 operation: function(f){return operation(f)();}, 215 refresh: function(){updateDisplay(true);}, 216 getInputField: function(){return input;}, 217 getWrapperElement: function(){return wrapper;}, 218 getScrollerElement: function(){return scroller;}, 219 getGutterElement: function(){return gutter;} 220 }; 221 222 function setValue(code) { 223 history = null; 224 var top = {line: 0, ch: 0}; 225 updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length}, 226 splitLines(code), top, top); 227 history = new History(); 228 updateInput = true; 229 } 230 function getValue(code) { 231 var text = []; 232 for (var i = 0, l = lines.length; i < l; ++i) 233 text.push(lines[i].text); 234 return text.join("\n"); 235 } 236 237 function onMouseDown(e) { 238 // Check whether this is a click in a widget 239 for (var n = e_target(e); n != wrapper; n = n.parentNode) { 240 /* !mini_swatch */ 241 /* 242 * CARGO intervention to allow the color picker to be clicked 243 * Assumes classname == "mini_swatch" 244 * Set the cursor down where we clicked (used for replacing the swatch) 245 */ 246 if(n.className == "mini_swatch") { 247 var start = posFromMouse(e); 248 setCursor(start.line, start.ch-2, true); 249 return; 250 } 251
252 // This is default code mirror behavior 253 if (n.parentNode == code && n != mover) return; 254 } 255 256 // First, see if this is a click in the gutter 257 for (var n = e_target(e); n != wrapper; n = n.parentNode) 258 if (n.parentNode == gutterText) { 259 if (options.onGutterClick) 260 options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); 261 return e_preventDefault(e); 262 } 263 264 var start = posFromMouse(e); 265 266 switch (e_button(e)) { 267 case 3: 268 if (gecko && !mac) onContextMenu(e); 269 return; 270 case 2: 271 if (start) setCursor(start.line, start.ch, true); 272 return; 273 } 274 // For button 1, if it was clicked inside the editor 275 // (posFromMouse returning non-null), we have to adjust the 276 // selection. 277 if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;} 278 279 if (!focused) onFocus(); 280 281 var now = +new Date; 282 if (lastDoubleClick > now - 400) { 283 e_preventDefault(e); 284 return selectLine(start.line); 285 } else if (lastClick > now - 400) { 286 lastDoubleClick = now; 287 e_preventDefault(e); 288 return selectWordAt(start); 289 } else { lastClick = now; } 290 291 var last = start, going; 292 if (dragAndDrop && !posEq(sel.from, sel.to) && 293 !posLess(start, sel.from) && !posLess(sel.to, start)) { 294 // Let the drag handler handle this. 295 return; 296 } 297 e_preventDefault(e); 298 setCursor(start.line, start.ch, true); 299 300 function extend(e) { 301 var cur = posFromMouse(e, true); 302 if (cur && !posEq(cur, last)) { 303 if (!focused) onFocus(); 304 last = cur; 305 setSelectionUser(start, cur); 306 updateInput = false; 307 var visible = visibleLines(); 308 if (cur.line >= visible.to || cur.line < visible.from) 309 going = setTimeout(operation(function(){extend(e);}), 150); 310 } 311 } 312 313 var move = connect(targetDocument, "mousemove", operation(function(e) { 314 clearTimeout(going); 315 e_preventDefault(e); 316 extend(e); 317 }), true); 318 var up = connect(targetDocument, "mouseup", operation(function(e) { 319 clearTimeout(going); 320 var cur = posFromMouse(e); 321 if (cur) setSelectionUser(start, cur); 322 e_preventDefault(e); 323 focusInput(); 324 updateInput = true; 325 move(); up(); 326 }), true); 327 } 328 function onDrop(e) { 329 e.preventDefault(); 330 var pos = posFromMouse(e, true), files = e.dataTransfer.files; 331 if (!pos || options.readOnly) return; 332 if (files && files.length && window.FileReader && window.File) { 333 function loadFile(file, i) { 334 var reader = new FileReader; 335 reader.onload = function() { 336 text[i] = reader.result; 337 if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos)); 338 }; 339 reader.readAsText(file); 340 } 341 var n = files.length, text = Array(n), read = 0; 342 for (var i = 0; i < n; ++i) loadFile(files[i], i); 343 } 344 else { 345 try { 346 var text = e.dataTransfer.getData("Text"); 347 if (text) replaceRange(text, pos, pos); 348 } 349 catch(e){} 350 } 351 } 352 function onDragStart(e) { 353 var txt = getSelection(); 354 // This will reset escapeElement 355 //htmlEscape(txt); 356 //e.dataTransfer.setDragImage(escapeElement, 0, 0); 357 //e.dataTransfer.setData("Text", txt); 358 } 359 function onKeyDown(e) { 360 if (!focused) onFocus(); 361 362 var code = e.keyCode; 363 // IE does strange things with escape. 364 if (ie && code == 27) { e.returnValue = false; } 365 // Tries to detect ctrl on non-mac, cmd on mac. 366 var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey; 367 if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); 368 else shiftSelecting = null; 369 // First give onKeyEvent option a chance to handle this. 370 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; 371 372 if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down 373 if (mod && ((code == 36 || code == 35) || // ctrl-home/end 374 mac && (code == 38 || code == 40))) { // cmd-up/down 375 scrollEnd(code == 36 || code == 38); return e_preventDefault(e); 376 } 377 if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a 378 if (!options.readOnly) { 379 if (!anyMod && code == 13) {return;} // enter 380 if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab 381 if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z 382 if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y 383 } 384 if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } } 385 386 // Key id to use in the movementKeys map. We also pass it to 387 // fastPoll in order to 'self learn'. We need this because 388 // reducedSelection, the hack where we collapse the selection to 389 // its start when it is inverted and a movement key is pressed 390 // (and later restore it again), shouldn't be used for 391 // non-movement keys. 392 curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code; 393 if (sel.inverted && movementKeys[curKeyId] === true) { 394 var range = selRange(input); 395 if (range) { 396 reducedSelection = {anchor: range.start}; 397 setSelRange(input, range.start, range.start); 398 } 399 } 400 // Don't save the key as a movementkey unless it had a modifier 401 if (!mod && !e.altKey) curKeyId = null; 402 fastPoll(curKeyId); 403 } 404 function onKeyUp(e) { 405 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; 406 if (reducedSelection) { 407 reducedSelection = null; 408 updateInput = true; 409 } 410 if (e.keyCode == 16) shiftSelecting = null; 411 } 412 function onKeyPress(e) { 413 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; 414 if (options.electricChars && mode.electricChars) { 415 var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode); 416 if (mode.electricChars.indexOf(ch) > -1) 417 setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50); 418 } 419 var code = e.keyCode; 420 // Re-stop tab and enter. Necessary on some browsers. 421 if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);} 422 else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e); 423 else fastPoll(curKeyId); 424 } 425 426 function onFocus() { 427 if (options.readOnly == "nocursor") return; 428 if (!focused) { 429 if (options.onFocus) options.onFocus(instance); 430 focused = true; 431 if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) 432 wrapper.className += " CodeMirror-focused"; 433 if (!leaveInputAlone) prepareInput(); 434 } 435 slowPoll(); 436 restartBlink(); 437 } 438 function onBlur() { 439 if (focused) { 440 if (options.onBlur) options.onBlur(instance); 441 focused = false;
442 wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); 443 } 444 clearInterval(blinker); 445 setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); 446 } 447 448 // Replace the range from from to to by the strings in newText. 449 // Afterwards, set the selection to selFrom, selTo. 450 function updateLines(from, to, newText, selFrom, selTo) { 451 if (history) { 452 var old = []; 453 for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text); 454 history.addChange(from.line, newText.length, old); 455 while (history.done.length > options.undoDepth) history.done.shift(); 456 } 457 updateLinesNoUndo(from, to, newText, selFrom, selTo); 458 } 459 function unredoHelper(from, to) { 460 var change = from.pop(); 461 if (change) { 462 var replaced = [], end = change.start + change.added; 463 for (var i = change.start; i < end; ++i) replaced.push(lines[i].text); 464 to.push({start: change.start, added: change.old.length, old: replaced}); 465 var pos = clipPos({line: change.start + change.old.length - 1, 466 ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); 467 updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos); 468 updateInput = true; 469 } 470 } 471 function undo() {unredoHelper(history.done, history.undone);} 472 function redo() {unredoHelper(history.undone, history.done);} 473 474 function updateLinesNoUndo(from, to, newText, selFrom, selTo) { 475 var recomputeMaxLength = false, maxLineLength = maxLine.length; 476 for (var i = from.line; i <= to.line; ++i) { 477 if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;} 478 } 479 480 var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line]; 481 // First adjust the line structure, taking some care to leave highlighting intact. 482 if (firstLine == lastLine) { 483 if (newText.length == 1) 484 firstLine.replace(from.ch, to.ch, newText[0]); 485 else { 486 lastLine = firstLine.split(to.ch, newText[newText.length-1]); 487 var spliceargs = [from.line + 1, nlines]; 488 firstLine.replace(from.ch, firstLine.text.length, newText[0]); 489 for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i])); 490 spliceargs.push(lastLine); 491 lines.splice.apply(lines, spliceargs); 492 } 493 } 494 else if (newText.length == 1) { 495 firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch)); 496 lines.splice(from.line + 1, nlines); 497 } 498 else { 499 var spliceargs = [from.line + 1, nlines - 1]; 500 firstLine.replace(from.ch, firstLine.text.length, newText[0]); 501 lastLine.replace(0, to.ch, newText[newText.length-1]); 502 for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i])); 503 lines.splice.apply(lines, spliceargs); 504 } 505 506 507 for (var i = from.line, e = i + newText.length; i < e; ++i) { 508 var l = lines[i].text; 509 if (l.length > maxLineLength) { 510 maxLine = l; maxLineLength = l.length; maxWidth = null; 511 recomputeMaxLength = false; 512 } 513 } 514 if (recomputeMaxLength) { 515 maxLineLength = 0; maxLine = ""; maxWidth = null; 516 for (var i = 0, e = lines.length; i < e; ++i) { 517 var l = lines[i].text; 518 if (l.length > maxLineLength) { 519 maxLineLength = l.length; maxLine = l; 520 } 521 } 522 } 523 524 // Add these lines to the work array, so that they will be 525 // highlighted. Adjust work lines if lines were added/removed. 526 var newWork = [], lendiff = newText.length - nlines - 1; 527 for (var i = 0, l = work.length; i < l; ++i) { 528 var task = work[i]; 529 if (task < from.line) newWork.push(task); 530 else if (task > to.line) newWork.push(task + lendiff); 531 } 532 if (newText.length < 5) { 533 highlightLines(from.line, from.line + newText.length); 534 newWork.push(from.line + newText.length); 535 } else { 536 newWork.push(from.line); 537 } 538 work = newWork; 539 startWorker(100); 540 // Remember that these lines changed, for updating the display 541 changes.push({from: from.line, to: to.line + 1, diff: lendiff}); 542 textChanged = {from: from, to: to, text: newText}; 543 544 // Update the selection 545 function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} 546 setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); 547 548 // Make sure the scroll-size div has the correct height. 549 code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px"; 550 } 551 552 function replaceRange(code, from, to) { 553 from = clipPos(from); 554 if (!to) to = from; else to = clipPos(to); 555 code = splitLines(code); 556 function adjustPos(pos) { 557 if (posLess(pos, from)) return pos; 558 if (!posLess(to, pos)) return end; 559 var line = pos.line + code.length - (to.line - from.line) - 1; 560 var ch = pos.ch; 561 if (pos.line == to.line) 562 ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0)); 563 return {line: line, ch: ch}; 564 } 565 var end;
566 replaceRange1(code, from, to, function(end1) { 567 end = end1; 568 return {from: adjustPos(sel.from), to: adjustPos(sel.to)}; 569 }); 570 return end; 571 } 572 function replaceSelection(code, collapse) { 573 replaceRange1(splitLines(code), sel.from, sel.to, function(end) { 574 if (collapse == "end") return {from: end, to: end}; 575 else if (collapse == "start") return {from: sel.from, to: sel.from}; 576 else return {from: sel.from, to: end}; 577 }); 578 } 579 function replaceRange1(code, from, to, computeSel) { 580 var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length; 581 var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); 582 updateLines(from, to, code, newSel.from, newSel.to); 583 } 584 585 function getRange(from, to) { 586 var l1 = from.line, l2 = to.line; 587 if (l1 == l2) return lines[l1].text.slice(from.ch, to.ch); 588 var code = [lines[l1].text.slice(from.ch)]; 589 for (var i = l1 + 1; i < l2; ++i) code.push(lines[i].text); 590 code.push(lines[l2].text.slice(0, to.ch)); 591 return code.join("\n"); 592 } 593 function getSelection() { 594 return getRange(sel.from, sel.to); 595 } 596 597 var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll 598 function slowPoll() { 599 if (pollingFast) return; 600 poll.set(2000, function() { 601 startOperation(); 602 readInput(); 603 if (focused) slowPoll(); 604 endOperation(); 605 }); 606 } 607 function fastPoll(keyId) { 608 var missed = false; 609 pollingFast = true; 610 function p() { 611 startOperation(); 612 var changed = readInput(); 613 if (changed && keyId) { 614 if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true; 615 if (changed == "changed") movementKeys[keyId] = false; 616 } 617 if (!changed && !missed) {missed = true; poll.set(80, p);} 618 else {pollingFast = false; slowPoll();} 619 endOperation(); 620 } 621 poll.set(20, p); 622 } 623 624 // Inspects the textarea, compares its state (content, selection) 625 // to the data in the editing variable, and updates the editor 626 // content or cursor if something changed. 627 function readInput() { 628 if (leaveInputAlone || !focused) return; 629 var changed = false, text = input.value, sr = selRange(input); 630 if (!sr) return false; 631 var changed = editing.text != text, rs = reducedSelection; 632 var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end); 633 if (!moved && !rs) return false; 634 if (changed) { 635 shiftSelecting = reducedSelection = null; 636 if (options.readOnly) {updateInput = true; return "changed";} 637 } 638 639 // Compute selection start and end based on start/end offsets in textarea 640 function computeOffset(n, startLine) { 641 var pos = 0; 642 for (;;) { 643 var found = text.indexOf("\n", pos); 644 if (found == -1 || (text.charAt(found-1) == "\r" ? found - 1 : found) >= n) 645 return {line: startLine, ch: n - pos}; 646 ++startLine; 647 pos = found + 1; 648 } 649 } 650 var from = computeOffset(sr.start, editing.from), 651 to = computeOffset(sr.end, editing.from); 652 // Here we have to take the reducedSelection hack into account, 653 // so that you can, for example, press shift-up at the start of 654 // your selection and have the right thing happen. 655 if (rs) { 656 var head = sr.start == rs.anchor ? to : from; 657 var tail = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to; 658 if (sel.inverted = posLess(head, tail)) { from = head; to = tail; } 659 else { reducedSelection = null; from = tail; to = head; } 660 } 661 662 // In some cases (cursor on same line as before), we don't have 663 // to update the textarea content at all. 664 if (from.line == to.line && from.line == sel.from.line && from.line == sel.to.line && !shiftSelecting) 665 updateInput = false; 666 667 // Magic mess to extract precise edited range from the changed 668 // string. 669 if (changed) { 670 var start = 0, end = text.length, len = Math.min(end, editing.text.length); 671 var c, line = editing.from, nl = -1; 672 while (start < len && (c = text.charAt(start)) == editing.text.charAt(start)) { 673 ++start; 674 if (c == "\n") {line++; nl = start;} 675 } 676 var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length; 677 for (;;) { 678 c = editing.text.charAt(edend); 679 if (text.charAt(end) != c) {++end; ++edend; break;} 680 if (c == "\n") endline--; 681 if (edend <= start || end <= start) break; 682 --end; --edend; 683 } 684 var nl = editing.text.lastIndexOf("\n", edend - 1), endch = nl == -1 ? edend : edend - nl - 1; 685 updateLines({line: line, ch: ch}, {line: endline, ch: endch}, splitLines(text.slice(start, end)), from, to); 686 if (line != endline || from.line != line) updateInput = true; 687 } 688 else setSelection(from, to); 689 690 editing.text = text; editing.start = sr.start; editing.end = sr.end; 691 return changed ? "changed" : moved ? "moved" : false;
692 } 693 694 // Set the textarea content and selection range to match the 695 // editor state. 696 function prepareInput() { 697 var text = []; 698 var from = Math.max(0, sel.from.line - 1), to = Math.min(lines.length, sel.to.line + 2); 699 for (var i = from; i < to; ++i) text.push(lines[i].text); 700 text = input.value = text.join(lineSep); 701 var startch = sel.from.ch, endch = sel.to.ch; 702 for (var i = from; i < sel.from.line; ++i) 703 startch += lineSep.length + lines[i].text.length; 704 for (var i = from; i < sel.to.line; ++i) 705 endch += lineSep.length + lines[i].text.length; 706 editing = {text: text, from: from, to: to, start: startch, end: endch}; 707 setSelRange(input, startch, reducedSelection ? startch : endch); 708 } 709 function focusInput() { 710 if (options.readOnly != "nocursor") input.focus(); 711 } 712 713 function scrollEditorIntoView() { 714 if (!cursor.getBoundingClientRect) return; 715 var rect = cursor.getBoundingClientRect(); 716 var winH = window.innerHeight || document.body.offsetHeight || document.documentElement.offsetHeight; 717 if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView(); 718 } 719 function scrollCursorIntoView() { 720 var cursor = localCoords(sel.inverted ? sel.from : sel.to); 721 return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot); 722 } 723 function scrollIntoView(x1, y1, x2, y2) { 724 var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight(); 725 y1 += pt; y2 += pt; x1 += pl; x2 += pl; 726 var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; 727 if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;} 728 else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;} 729 730 var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; 731 var gutterw = options.fixedGutter ? gutter.clientWidth : 0; 732 if (x1 < screenleft + gutterw) { 733 if (x1 < 50) x1 = 0; 734 scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw); 735 scrolled = true; 736 } 737 else if (x2 > screenw + screenleft) { 738 scroller.scrollLeft = x2 + 10 - screenw; 739 scrolled = true; 740 if (x2 > code.clientWidth) result = false;
741 } 742 if (scrolled && options.onScroll) options.onScroll(instance); 743 return result; 744 } 745 746 function visibleLines() { 747 var lh = lineHeight(), top = scroller.scrollTop - paddingTop(); 748 return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))), 749 to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))}; 750 } 751 // Uses a set of changes plus the current scroll position to 752 // determine which DOM updates have to be made, and makes the 753 // updates. 754 function updateDisplay(changes) { 755 if (!scroller.clientWidth) { 756 showingFrom = showingTo = 0; 757 return; 758 } 759 // First create a range of theoretically intact lines, and punch 760 // holes in that using the change info. 761 var intact = changes === true ? [] : [{from: showingFrom, to: showingTo, domStart: 0}]; 762 for (var i = 0, l = changes.length || 0; i < l; ++i) { 763 var change = changes[i], intact2 = [], diff = change.diff || 0; 764 for (var j = 0, l2 = intact.length; j < l2; ++j) { 765 var range = intact[j]; 766 if (change.to <= range.from) 767 intact2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart}); 768 else if (range.to <= change.from) 769 intact2.push(range); 770 else { 771 if (change.from > range.from) 772 intact2.push({from: range.from, to: change.from, domStart: range.domStart}) 773 if (change.to < range.to) 774 intact2.push({from: change.to + diff, to: range.to + diff, 775 domStart: range.domStart + (change.to - range.from)}); 776 } 777 } 778 intact = intact2; 779 } 780 781 // Then, determine which lines we'd want to see, and which 782 // updates have to be made to get there. 783 var visible = visibleLines(); 784 var from = Math.min(showingFrom, Math.max(visible.from - 3, 0)), 785 to = Math.min(lines.length, Math.max(showingTo, visible.to + 3)), 786 updates = [], domPos = 0, domEnd = showingTo - showingFrom, pos = from, changedLines = 0; 787 788 for (var i = 0, l = intact.length; i < l; ++i) { 789 var range = intact[i]; 790 if (range.to <= from) continue; 791 if (range.from >= to) break; 792 if (range.domStart > domPos || range.from > pos) { 793 updates.push({from: pos, to: range.from, domSize: range.domStart - domPos, domStart: domPos}); 794 changedLines += range.from - pos; 795 } 796 pos = range.to; 797 domPos = range.domStart + (range.to - range.from); 798 } 799 if (domPos != domEnd || pos != to) { 800 changedLines += Math.abs(to - pos); 801 updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos}); 802 if (to - pos != domEnd - domPos) gutterDirty = true; 803 } 804 805 if (!updates.length) return; 806 lineDiv.style.display = "none"; 807 // If more than 30% of the screen needs update, just do a full 808 // redraw (which is quicker than patching) 809 if (changedLines > (visible.to - visible.from) * .3) 810 refreshDisplay(from = Math.max(visible.from - 10, 0), to = Math.min(visible.to + 7, lines.length)); 811 // Otherwise, only update the stuff that needs updating. 812 else 813 patchDisplay(updates); 814 lineDiv.style.display = ""; 815 816 // Position the mover div to align with the lines it's supposed 817 // to be showing (which will cover the visible display) 818 var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight; 819 showingFrom = from; showingTo = to; 820 mover.style.top = (from * lineHeight()) + "px"; 821 if (different) { 822 lastHeight = scroller.clientHeight; 823 code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px"; 824 } 825 if (different || gutterDirty) updateGutter(); 826 827 if (maxWidth == null) maxWidth = stringWidth(maxLine); 828 if (maxWidth > scroller.clientWidth) { 829 lineSpace.style.width = maxWidth + "px"; 830 // Needed to prevent odd wrapping/hiding of widgets placed in here. 831 code.style.width = ""; 832 code.style.width = scroller.scrollWidth + "px"; 833 } else { 834 lineSpace.style.width = code.style.width = ""; 835 } 836 837 // Since this is all rather error prone, it is honoured with the 838 // only assertion in the whole file. 839 if (lineDiv.childNodes.length != showingTo - showingFrom) 840 throw new Error("BAD PATCH! " + JSON.stringify(updates) + " size=" + (showingTo - showingFrom) + 841 " nodes=" + lineDiv.childNodes.length);
842 updateCursor(); 843 } 844 845 function refreshDisplay(from, to) { 846 var html = [], start = {line: from, ch: 0}, inSel = posLess(sel.from, start) && !posLess(sel.to, start); 847 for (var i = from; i < to; ++i) { 848 var ch1 = null, ch2 = null; 849 if (inSel) { 850 ch1 = 0; 851 if (sel.to.line == i) {inSel = false; ch2 = sel.to.ch;} 852 } 853 else if (sel.from.line == i) { 854 if (sel.to.line == i) {ch1 = sel.from.ch; ch2 = sel.to.ch;} 855 else {inSel = true; ch1 = sel.from.ch;} 856 } 857 html.push(lines[i].getHTML(ch1, ch2, true)); 858 } 859 lineDiv.innerHTML = html.join(""); 860 } 861 function patchDisplay(updates) { 862 // Slightly different algorithm for IE (badInnerHTML), since 863 // there .innerHTML on PRE nodes is dumb, and discards 864 // whitespace. 865 var sfrom = sel.from.line, sto = sel.to.line, off = 0, 866 scratch = badInnerHTML && targetDocument.createElement("div"); 867 for (var i = 0, e = updates.length; i < e; ++i) { 868 var rec = updates[i]; 869 var extra = (rec.to - rec.from) - rec.domSize; 870 var nodeAfter = lineDiv.childNodes[rec.domStart + rec.domSize + off] || null; 871 if (badInnerHTML) 872 for (var j = Math.max(-extra, rec.domSize); j > 0; --j) 873 lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild); 874 else if (extra) { 875 for (var j = Math.max(0, extra); j > 0; --j) 876 lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter); 877 for (var j = Math.max(0, -extra); j > 0; --j) 878 lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild); 879 } 880 var node = lineDiv.childNodes[rec.domStart + off], inSel = sfrom < rec.from && sto >= rec.from; 881 for (var j = rec.from; j < rec.to; ++j) { 882 var ch1 = null, ch2 = null; 883 if (inSel) { 884 ch1 = 0; 885 if (sto == j) {inSel = false; ch2 = sel.to.ch;} 886 } 887 else if (sfrom == j) { 888 if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;} 889 else {inSel = true; ch1 = sel.from.ch;} 890 } 891 if (badInnerHTML) { 892 scratch.innerHTML = lines[j].getHTML(ch1, ch2, true); 893 lineDiv.insertBefore(scratch.firstChild, nodeAfter); 894 } 895 else { 896 node.innerHTML = lines[j].getHTML(ch1, ch2, false); 897 node.className = lines[j].className || ""; 898 node = node.nextSibling; 899 } 900 } 901 off += extra; 902 } 903 } 904 905 function updateGutter() { 906 if (!options.gutter && !options.lineNumbers) return; 907 var hText = mover.offsetHeight, hEditor = scroller.clientHeight; 908 gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; 909 var html = []; 910 for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) { 911 var marker = lines[i].gutterMarker; 912 var text = options.lineNumbers ? i + options.firstLineNumber : null; 913 if (marker && marker.text) 914 text = marker.text.replace("%N%", text != null ? text : ""); 915 else if (text == null) 916 text = "\u00a0"; 917 html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text, "</pre>"); 918 } 919 gutter.style.display = "none"; 920 gutterText.innerHTML = html.join(""); 921 var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; 922 while (val.length + pad.length < minwidth) pad += "\u00a0"; 923 if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild); 924 gutter.style.display = ""; 925 lineSpace.style.marginLeft = gutter.offsetWidth + "px"; 926 gutterDirty = false; 927 } 928 function updateCursor() { 929 var head = sel.inverted ? sel.from : sel.to, lh = lineHeight(); 930 var x = charX(head.line, head.ch); 931 var top = head.line * lh - scroller.scrollTop; 932 inputDiv.style.top = Math.max(Math.min(top, scroller.offsetHeight), 0) + "px"; 933 inputDiv.style.left = (x - scroller.scrollLeft) + "px"; 934 if (posEq(sel.from, sel.to)) { 935 cursor.style.top = (head.line - showingFrom) * lh + "px"; 936 cursor.style.left = x + "px"; 937 cursor.style.display = ""; 938 } 939 else cursor.style.display = "none"; 940 } 941 942 function setSelectionUser(from, to) { 943 var sh = shiftSelecting && clipPos(shiftSelecting); 944 if (sh) { 945 if (posLess(sh, from)) from = sh;
946 else if (posLess(to, sh)) to = sh; 947 } 948 setSelection(from, to); 949 } 950 // Update the selection. Last two args are only used by 951 // updateLines, since they have to be expressed in the line 952 // numbers before the update. 953 function setSelection(from, to, oldFrom, oldTo) { 954 if (posEq(sel.from, from) && posEq(sel.to, to)) return; 955 if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} 956 957 if (posEq(from, to)) sel.inverted = false; 958 else if (posEq(from, sel.to)) sel.inverted = false; 959 else if (posEq(to, sel.from)) sel.inverted = true; 960 961 // Some ugly logic used to only mark the lines that actually did 962 // see a change in selection as changed, rather than the whole 963 // selected range. 964 if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} 965 if (posEq(from, to)) { 966 if (!posEq(sel.from, sel.to)) 967 changes.push({from: oldFrom, to: oldTo + 1}); 968 } 969 else if (posEq(sel.from, sel.to)) { 970 changes.push({from: from.line, to: to.line + 1}); 971 } 972 else { 973 if (!posEq(from, sel.from)) { 974 if (from.line < oldFrom) 975 changes.push({from: from.line, to: Math.min(to.line, oldFrom) + 1}); 976 else 977 changes.push({from: oldFrom, to: Math.min(oldTo, from.line) + 1}); 978 } 979 if (!posEq(to, sel.to)) { 980 if (to.line < oldTo) 981 changes.push({from: Math.max(oldFrom, from.line), to: oldTo + 1}); 982 else 983 changes.push({from: Math.max(from.line, oldTo), to: to.line + 1}); 984 } 985 } 986 sel.from = from; sel.to = to; 987 selectionChanged = true; 988 } 989 function setCursor(line, ch, user) { 990 var pos = clipPos({line: line, ch: ch || 0}); 991 (user ? setSelectionUser : setSelection)(pos, pos); 992 } 993 994 function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));} 995 function clipPos(pos) { 996 if (pos.line < 0) return {line: 0, ch: 0}; 997 if (pos.line >= lines.length) return {line: lines.length-1, ch: lines[lines.length-1].text.length}; 998 var ch = pos.ch, linelen = lines[pos.line].text.length; 999 if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; 1000 else if (ch < 0) return {line: pos.line, ch: 0}; 1001 else return pos; 1002 } 1003 1004 function scrollPage(down) { 1005 var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to; 1006 setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true); 1007 } 1008 function scrollEnd(top) { 1009 var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length}; 1010 setSelectionUser(pos, pos); 1011 } 1012 function selectAll() { 1013 var endLine = lines.length - 1; 1014 setSelection({line: 0, ch: 0}, {line: endLine, ch: lines[endLine].text.length}); 1015 } 1016 function selectWordAt(pos) { 1017 var line = lines[pos.line].text; 1018 var start = pos.ch, end = pos.ch; 1019 while (start > 0 && /\w/.test(line.charAt(start - 1))) --start; 1020 while (end < line.length && /\w/.test(line.charAt(end))) ++end; 1021 setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end}); 1022 } 1023 function selectLine(line) { 1024 setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length}); 1025 } 1026 function handleEnter() { 1027 replaceSelection("\n", "end"); 1028 if (options.enterMode != "flat") 1029 indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart"); 1030 } 1031 function handleTab(shift) { 1032 function indentSelected(mode) { 1033 if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); 1034 var e = sel.to.line - (sel.to.ch ? 0 : 1); 1035 for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); 1036 } 1037 shiftSelecting = null; 1038 switch (options.tabMode) { 1039 case "default": 1040 return false; 1041 case "indent": 1042 indentSelected("smart"); 1043 break; 1044 case "classic": 1045 if (posEq(sel.from, sel.to)) { 1046 if (shift) indentLine(sel.from.line, "smart"); 1047 else replaceSelection("\t", "end"); 1048 break; 1049 } 1050 case "shift": 1051 indentSelected(shift ? "subtract" : "add"); 1052 break; 1053 } 1054 return true; 1055 } 1056 function smartHome() { 1057 var firstNonWS = Math.max(0, lines[sel.from.line].text.search(/\S/)); 1058 setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true); 1059 } 1060 1061 function indentLine(n, how) { 1062 if (how == "smart") { 1063 if (!mode.indent) how = "prev"; 1064 else var state = getStateBefore(n); 1065 } 1066 1067 var line = lines[n], curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation; 1068 if (how == "prev") { 1069 if (n) indentation = lines[n-1].indentation(); 1070 else indentation = 0; 1071 } 1072 else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length)); 1073 else if (how == "add") indentation = curSpace + options.indentUnit; 1074 else if (how == "subtract") indentation = curSpace - options.indentUnit; 1075 indentation = Math.max(0, indentation); 1076 var diff = indentation - curSpace; 1077 1078 if (!diff) { 1079 if (sel.from.line != n && sel.to.line != n) return;
1080 var indentString = curSpaceString; 1081 } 1082 else { 1083 var indentString = "", pos = 0; 1084 if (options.indentWithTabs) 1085 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} 1086 while (pos < indentation) {++pos; indentString += " ";} 1087 } 1088 1089 replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); 1090 } 1091 1092 function loadMode() { 1093 mode = CodeMirror.getMode(options, options.mode); 1094 for (var i = 0, l = lines.length; i < l; ++i) 1095 lines[i].stateAfter = null; 1096 work = [0]; 1097 startWorker(); 1098 } 1099 function gutterChanged() { 1100 var visible = options.gutter || options.lineNumbers; 1101 gutter.style.display = visible ? "" : "none"; 1102 if (visible) gutterDirty = true; 1103 else lineDiv.parentNode.style.marginLeft = 0; 1104 } 1105 1106 function markText(from, to, className) { 1107 from = clipPos(from); to = clipPos(to); 1108 var accum = []; 1109 function add(line, from, to, className) { 1110 var line = lines[line], mark = line.addMark(from, to, className); 1111 mark.line = line; 1112 accum.push(mark); 1113 } 1114 if (from.line == to.line) add(from.line, from.ch, to.ch, className); 1115 else { 1116 add(from.line, from.ch, null, className); 1117 for (var i = from.line + 1, e = to.line; i < e; ++i) 1118 add(i, 0, null, className); 1119 add(to.line, 0, to.ch, className); 1120 } 1121 changes.push({from: from.line, to: to.line + 1}); 1122 return function() { 1123 var start, end; 1124 for (var i = 0; i < accum.length; ++i) { 1125 var mark = accum[i], found = indexOf(lines, mark.line); 1126 mark.line.removeMark(mark); 1127 if (found > -1) { 1128 if (start == null) start = found; 1129 end = found; 1130 } 1131 } 1132 if (start != null) changes.push({from: start, to: end + 1}); 1133 }; 1134 } 1135 1136 function addGutterMarker(line, text, className) { 1137 if (typeof line == "number") line = lines[clipLine(line)]; 1138 line.gutterMarker = {text: text, style: className}; 1139 gutterDirty = true; 1140 return line; 1141 } 1142 function removeGutterMarker(line) { 1143 if (typeof line == "number") line = lines[clipLine(line)]; 1144 line.gutterMarker = null; 1145 gutterDirty = true; 1146 } 1147 function setLineClass(line, className) { 1148 if (typeof line == "number") { 1149 var no = line; 1150 line = lines[clipLine(line)]; 1151 } 1152 else { 1153 var no = indexOf(lines, line); 1154 if (no == -1) return null; 1155 } 1156 if (line.className != className) { 1157 line.className = className; 1158 changes.push({from: no, to: no + 1}); 1159 } 1160 return line; 1161 } 1162 1163 function lineInfo(line) { 1164 if (typeof line == "number") { 1165 var n = line; 1166 line = lines[line]; 1167 if (!line) return null; 1168 } 1169 else { 1170 var n = indexOf(lines, line); 1171 if (n == -1) return null; 1172 } 1173 var marker = line.gutterMarker; 1174 return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style}; 1175 } 1176 1177 function stringWidth(str) { 1178 measure.innerHTML = "<pre><span>x</span></pre>"; 1179 measure.firstChild.firstChild.firstChild.nodeValue = str; 1180 return measure.firstChild.firstChild.offsetWidth || 10; 1181 } 1182 // These are used to go from pixel positions to character 1183 // positions, taking varying character widths into account. 1184 function charX(line, pos) { 1185 if (pos == 0) return 0; 1186 measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>"; 1187 return measure.firstChild.firstChild.offsetWidth; 1188 } 1189 function charFromX(line, x) { 1190 if (x <= 0) return 0; 1191 var lineObj = lines[line], text = lineObj.text; 1192 function getX(len) { 1193 measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>"; 1194 return measure.firstChild.firstChild.offsetWidth; 1195 } 1196 var from = 0, fromX = 0, to = text.length, toX; 1197 // Guess a suitable upper bound for our search. 1198 var estimated = Math.min(to, Math.ceil(x / stringWidth("x"))); 1199 for (;;) { 1200 var estX = getX(estimated); 1201 if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); 1202 else {toX = estX; to = estimated; break;} 1203 } 1204 if (x > toX) return to; 1205 // Try to guess a suitable lower bound as well. 1206 estimated = Math.floor(to * 0.8); estX = getX(estimated); 1207 if (estX < x) {from = estimated; fromX = estX;} 1208 // Do a binary search between these bounds. 1209 for (;;) { 1210 if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
1211 var middle = Math.ceil((from + to) / 2), middleX = getX(middle); 1212 if (middleX > x) {to = middle; toX = middleX;} 1213 else {from = middle; fromX = middleX;} 1214 } 1215 } 1216 1217 function localCoords(pos, inLineWrap) { 1218 var lh = lineHeight(), line = pos.line - (inLineWrap ? showingFrom : 0); 1219 return {x: charX(pos.line, pos.ch), y: line * lh, yBot: (line + 1) * lh}; 1220 } 1221 function pageCoords(pos) { 1222 var local = localCoords(pos, true), off = eltOffset(lineSpace); 1223 return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; 1224 } 1225 1226 function lineHeight() { 1227 var nlines = lineDiv.childNodes.length; 1228 if (nlines) return (lineDiv.offsetHeight / nlines) || 1; 1229 measure.innerHTML = "<pre>x</pre>"; 1230 return measure.firstChild.offsetHeight || 1; 1231 } 1232 function paddingTop() {return lineSpace.offsetTop;} 1233 function paddingLeft() {return lineSpace.offsetLeft;} 1234 1235 function posFromMouse(e, liberal) { 1236 var offW = eltOffset(scroller, true), x, y; 1237 // Fails unpredictably on IE[67] when mouse is dragged around quickly. 1238 try { x = e.clientX; y = e.clientY; } catch (e) { return null; } 1239 // This is a mess of a heuristic to try and determine whether a 1240 // scroll-bar was clicked or not, and to return null if one was 1241 // (and !liberal). 1242 if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) 1243 return null; 1244 var offL = eltOffset(lineSpace, true); 1245 var line = showingFrom + Math.floor((y - offL.top) / lineHeight()); 1246 return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)}); 1247 } 1248 function onContextMenu(e) { 1249 var pos = posFromMouse(e); 1250 if (!pos || window.opera) return; // Opera is difficult. 1251 if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) 1252 operation(setCursor)(pos.line, pos.ch); 1253 1254 var oldCSS = input.style.cssText; 1255 inputDiv.style.position = "absolute"; 1256 input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + 1257 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + 1258 "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; 1259 leaveInputAlone = true; 1260 var val = input.value = getSelection(); 1261 focusInput(); 1262 setSelRange(input, 0, input.value.length); 1263 function rehide() { 1264 var newVal = splitLines(input.value).join("\n"); 1265 if (newVal != val) operation(replaceSelection)(newVal, "end"); 1266 inputDiv.style.position = "relative"; 1267 input.style.cssText = oldCSS; 1268 leaveInputAlone = false; 1269 prepareInput(); 1270 slowPoll(); 1271 } 1272 1273 if (gecko) { 1274 e_stop(e); 1275 var mouseup = connect(window, "mouseup", function() { 1276 mouseup(); 1277 setTimeout(rehide, 20); 1278 }, true); 1279 } 1280 else { 1281 setTimeout(rehide, 50); 1282 } 1283 } 1284 1285 // Cursor-blinking 1286 function restartBlink() { 1287 clearInterval(blinker); 1288 var on = true; 1289 cursor.style.visibility = ""; 1290 blinker = setInterval(function() { 1291 cursor.style.visibility = (on = !on) ? "" : "hidden"; 1292 }, 650); 1293 } 1294 1295 var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; 1296 function matchBrackets(autoclear) { 1297 var head = sel.inverted ? sel.from : sel.to, line = lines[head.line], pos = head.ch - 1; 1298 var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; 1299 if (!match) return; 1300 var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; 1301 for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2) 1302 if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;} 1303 1304 var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; 1305 function scan(line, from, to) { 1306 if (!line.text) return; 1307 var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; 1308 for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { 1309 var text = st[i]; 1310 if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;} 1311 for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { 1312 if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { 1313 var match = matching[cur]; 1314 if (match.charAt(1) == ">" == forward) stack.push(cur); 1315 else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; 1316 else if (!stack.length) return {pos: pos, match: true}; 1317 } 1318 } 1319 } 1320 } 1321 for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) { 1322 var line = lines[i], first = i == head.line; 1323 var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); 1324 if (found) break; 1325 } 1326 if (!found) found = {pos: null, match: false}; 1327 var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; 1328 var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), 1329 two = found.pos != null 1330 ? markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style) 1331 : function() {}; 1332 var clear = operation(function(){one(); two();}); 1333 if (autoclear) setTimeout(clear, 800); 1334 else bracketHighlighted = clear; 1335 } 1336 1337 // Finds the line to start with when starting a parse. Tries to 1338 // find a line with a stateAfter, so that it can start with a 1339 // valid state. If that fails, it returns the line with the 1340 // smallest indentation, which tends to need the least context to 1341 // parse correctly. 1342 function findStartLine(n) { 1343 var minindent, minline; 1344 for (var search = n, lim = n - 40; search > lim; --search) { 1345 if (search == 0) return 0; 1346 var line = lines[search-1]; 1347 if (line.stateAfter) return search; 1348 var indented = line.indentation(); 1349 if (minline == null || minindent > indented) { 1350 minline = search - 1; 1351 minindent = indented; 1352 } 1353 } 1354 return minline; 1355 } 1356 function getStateBefore(n) { 1357 var start = findStartLine(n), state = start && lines[start-1].stateAfter; 1358 if (!state) state = startState(mode); 1359 else state = copyState(mode, state); 1360 for (var i = start; i < n; ++i) { 1361 var line = lines[i]; 1362 line.highlight(mode, state); 1363 line.stateAfter = copyState(mode, state); 1364 } 1365 if (n < lines.length && !lines[n].stateAfter) work.push(n); 1366 return state; 1367 } 1368 function highlightLines(start, end) { 1369 var state = getStateBefore(start); 1370 for (var i = start; i < end; ++i) { 1371 var line = lines[i]; 1372 line.highlight(mode, state); 1373 line.stateAfter = copyState(mode, state); 1374 } 1375 } 1376 function highlightWorker() { 1377 var end = +new Date + options.workTime; 1378 var foundWork = work.length; 1379 while (work.length) { 1380 if (!lines[showingFrom].stateAfter) var task = showingFrom; 1381 else var task = work.pop(); 1382 if (task >= lines.length) continue; 1383 var start = findStartLine(task), state = start && lines[start-1].stateAfter; 1384 if (state) state = copyState(mode, state); 1385 else state = startState(mode); 1386 1387 var unchanged = 0, compare = mode.compareStates, realChange = false;
1388 for (var i = start, l = lines.length; i < l; ++i) { 1389 var line = lines[i], hadState = line.stateAfter; 1390 if (+new Date > end) { 1391 work.push(i); 1392 startWorker(options.workDelay); 1393 if (realChange) changes.push({from: task, to: i + 1}); 1394 return; 1395 } 1396 var changed = line.highlight(mode, state); 1397 if (changed) realChange = true; 1398 line.stateAfter = copyState(mode, state); 1399 if (compare) { 1400 if (hadState && compare(hadState, state)) break; 1401 } else { 1402 if (changed !== false || !hadState) unchanged = 0; 1403 else if (++unchanged > 3) break; 1404 } 1405 } 1406 if (realChange) changes.push({from: task, to: i + 1}); 1407 } 1408 if (foundWork && options.onHighlightComplete) 1409 options.onHighlightComplete(instance); 1410 } 1411 function startWorker(time) { 1412 if (!work.length) return; 1413 highlight.set(time, operation(highlightWorker)); 1414 } 1415 1416 // Operations are used to wrap changes in such a way that each 1417 // change won't have to update the cursor and display (which would 1418 // be awkward, slow, and error-prone), but instead updates are 1419 // batched and then all combined and executed at once. 1420 function startOperation() { 1421 updateInput = null; changes = []; textChanged = selectionChanged = false; 1422 } 1423 function endOperation() { 1424 var reScroll = false; 1425 if (selectionChanged) reScroll = !scrollCursorIntoView(); 1426 if (changes.length) updateDisplay(changes); 1427 else { 1428 if (selectionChanged) updateCursor(); 1429 if (gutterDirty) updateGutter(); 1430 } 1431 if (reScroll) scrollCursorIntoView(); 1432 if (selectionChanged) {scrollEditorIntoView(); restartBlink();} 1433 1434 // updateInput can be set to a boolean value to force/prevent an 1435 // update. 1436 if (focused && !leaveInputAlone && 1437 (updateInput === true || (updateInput !== false && selectionChanged))) 1438 prepareInput(); 1439 1440 if (selectionChanged && options.matchBrackets) 1441 setTimeout(operation(function() { 1442 if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} 1443 matchBrackets(false); 1444 }), 20); 1445 var tc = textChanged; // textChanged can be reset by cursoractivity callback 1446 if (selectionChanged && options.onCursorActivity) 1447 options.onCursorActivity(instance); 1448 if (tc && options.onChange && instance) 1449 options.onChange(instance, tc); 1450 } 1451 var nestedOperation = 0; 1452 function operation(f) { 1453 return function() { 1454 if (!nestedOperation++) startOperation(); 1455 try {var result = f.apply(this, arguments);} 1456 finally {if (!--nestedOperation) endOperation();} 1457 return result; 1458 }; 1459 } 1460 1461 function SearchCursor(query, pos, caseFold) { 1462 this.atOccurrence = false; 1463 if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase(); 1464 1465 if (pos && typeof pos == "object") pos = clipPos(pos); 1466 else pos = {line: 0, ch: 0}; 1467 this.pos = {from: pos, to: pos}; 1468 1469 // The matches method is filled in based on the type of query. 1470 // It takes a position and a direction, and returns an object 1471 // describing the next occurrence of the query, or null if no 1472 // more matches were found. 1473 if (typeof query != "string") // Regexp match 1474 this.matches = function(reverse, pos) { 1475 if (reverse) { 1476 var line = lines[pos.line].text.slice(0, pos.ch), match = line.match(query), start = 0; 1477 while (match) { 1478 var ind = line.indexOf(match[0]); 1479 start += ind; 1480 line = line.slice(ind + 1); 1481 var newmatch = line.match(query); 1482 if (newmatch) match = newmatch; 1483 else break; 1484 start++; 1485 } 1486 } 1487 else { 1488 var line = lines[pos.line].text.slice(pos.ch), match = line.match(query), 1489 start = match && pos.ch + line.indexOf(match[0]); 1490 } 1491 if (match) 1492 return {from: {line: pos.line, ch: start}, 1493 to: {line: pos.line, ch: start + match[0].length}, 1494 match: match}; 1495 }; 1496 else { // String query 1497 if (caseFold) query = query.toLowerCase(); 1498 var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; 1499 var target = query.split("\n"); 1500 // Different methods for single-line and multi-line queries 1501 if (target.length == 1) 1502 this.matches = function(reverse, pos) { 1503 var line = fold(lines[pos.line].text), len = query.length, match; 1504 if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1) 1505 : (match = line.indexOf(query, pos.ch)) != -1) 1506 return {from: {line: pos.line, ch: match}, 1507 to: {line: pos.line, ch: match + len}}; 1508 }; 1509 else 1510 this.matches = function(reverse, pos) { 1511 var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(lines[ln].text); 1512 var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match)); 1513 if (reverse ? offsetA >= pos.ch || offsetA != match.length 1514 : offsetA <= pos.ch || offsetA != line.length - match.length) 1515 return; 1516 for (;;) { 1517 if (reverse ? !ln : ln == lines.length - 1) return; 1518 line = fold(lines[ln += reverse ? -1 : 1].text); 1519 match = target[reverse ? --idx : ++idx]; 1520 if (idx > 0 && idx < target.length - 1) { 1521 if (line != match) return; 1522 else continue; 1523 } 1524 var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length); 1525 if (reverse ? offsetB != line.length - match.length : offsetB != match.length) 1526 return; 1527 var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB}; 1528 return {from: reverse ? end : start, to: reverse ? start : end}; 1529 } 1530 }; 1531 } 1532 } 1533 1534 SearchCursor.prototype = { 1535 findNext: function() {return this.find(false);},
1536 findPrevious: function() {return this.find(true);}, 1537 1538 find: function(reverse) { 1539 var self = this, pos = clipPos(reverse ? this.pos.from : this.pos.to); 1540 function savePosAndFail(line) { 1541 var pos = {line: line, ch: 0}; 1542 self.pos = {from: pos, to: pos}; 1543 self.atOccurrence = false; 1544 return false; 1545 } 1546 1547 for (;;) { 1548 if (this.pos = this.matches(reverse, pos)) { 1549 this.atOccurrence = true; 1550 return this.pos.match || true; 1551 } 1552 if (reverse) { 1553 if (!pos.line) return savePosAndFail(0); 1554 pos = {line: pos.line-1, ch: lines[pos.line-1].text.length}; 1555 } 1556 else { 1557 if (pos.line == lines.length - 1) return savePosAndFail(lines.length); 1558 pos = {line: pos.line+1, ch: 0}; 1559 } 1560 } 1561 }, 1562 1563 from: function() {if (this.atOccurrence) return copyPos(this.pos.from);}, 1564 to: function() {if (this.atOccurrence) return copyPos(this.pos.to);}, 1565 1566 replace: function(newText) { 1567 var self = this; 1568 if (this.atOccurrence) 1569 operation(function() { 1570 self.pos.to = replaceRange(newText, self.pos.from, self.pos.to); 1571 })(); 1572 } 1573 }; 1574 1575 for (var ext in extensions) 1576 if (extensions.propertyIsEnumerable(ext) && 1577 !instance.propertyIsEnumerable(ext)) 1578 instance[ext] = extensions[ext]; 1579 return instance; 1580 } // (end of function CodeMirror) 1581 1582 // The default configuration options. 1583 CodeMirror.defaults = { 1584 value: "", 1585 mode: null, 1586 theme: "default", 1587 indentUnit: 2, 1588 indentWithTabs: false, 1589 tabMode: "classic", 1590 enterMode: "indent", 1591 electricChars: true, 1592 onKeyEvent: null, 1593 lineNumbers: false, 1594 gutter: false, 1595 fixedGutter: false, 1596 firstLineNumber: 1, 1597 readOnly: false, 1598 smartHome: true, 1599 onChange: null, 1600 onCursorActivity: null, 1601 onGutterClick: null, 1602 onHighlightComplete: null, 1603 onFocus: null, onBlur: null, onScroll: null, 1604 matchBrackets: false, 1605 workTime: 100, 1606 workDelay: 200, 1607 undoDepth: 40, 1608 tabindex: null, 1609 document: window.document 1610 }; 1611 1612 // Known modes, by name and by MIME 1613 var modes = {}, mimeModes = {}; 1614 CodeMirror.defineMode = function(name, mode) { 1615 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; 1616 modes[name] = mode; 1617 }; 1618 CodeMirror.defineMIME = function(mime, spec) { 1619 mimeModes[mime] = spec; 1620 }; 1621 CodeMirror.getMode = function(options, spec) { 1622 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) 1623 spec = mimeModes[spec]; 1624 if (typeof spec == "string") 1625 var mname = spec, config = {}; 1626 else if (spec != null) 1627 var mname = spec.name, config = spec; 1628 var mfactory = modes[mname]; 1629 if (!mfactory) { 1630 if (window.console) console.warn("No mode " + mname + " found, falling back to plain text."); 1631 return CodeMirror.getMode(options, "text/plain"); 1632 } 1633 return mfactory(options, config || {}); 1634 }; 1635 CodeMirror.listModes = function() { 1636 var list = []; 1637 for (var m in modes) 1638 if (modes.propertyIsEnumerable(m)) list.push(m); 1639 return list; 1640 }; 1641 CodeMirror.listMIMEs = function() { 1642 var list = []; 1643 for (var m in mimeModes) 1644 if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]}); 1645 return list; 1646 }; 1647 1648 var extensions = {}; 1649 CodeMirror.defineExtension = function(name, func) { 1650 extensions[name] = func; 1651 }; 1652 1653 CodeMirror.fromTextArea = function(textarea, options) { 1654 if (!options) options = {}; 1655 options.value = textarea.value; 1656 if (!options.tabindex && textarea.tabindex) 1657 options.tabindex = textarea.tabindex; 1658 1659 function save() {textarea.value = instance.getValue();} 1660 if (textarea.form) { 1661 // Deplorable hack to make the submit method do the right thing. 1662 var rmSubmit = connect(textarea.form, "submit", save, true); 1663 if (typeof textarea.form.submit == "function") { 1664 var realSubmit = textarea.form.submit; 1665 function wrappedSubmit() { 1666 save(); 1667 textarea.form.submit = realSubmit; 1668 textarea.form.submit(); 1669 textarea.form.submit = wrappedSubmit; 1670 } 1671 textarea.form.submit = wrappedSubmit; 1672 } 1673 } 1674 1675 textarea.style.display = "none"; 1676 var instance = CodeMirror(function(node) { 1677 textarea.parentNode.insertBefore(node, textarea.nextSibling); 1678 }, options); 1679 instance.save = save; 1680 instance.toTextArea = function() { 1681 save(); 1682 textarea.parentNode.removeChild(instance.getWrapperElement()); 1683 textarea.style.display = ""; 1684 if (textarea.form) { 1685 rmSubmit(); 1686 if (typeof textarea.form.submit == "function") 1687 textarea.form.submit = realSubmit; 1688 } 1689 }; 1690 return instance; 1691 }; 1692 1693 // Utility functions for working with state. Exported because modes 1694 // sometimes need to do this. 1695 function copyState(mode, state) { 1696 if (state === true) return state; 1697 if (mode.copyState) return mode.copyState(state); 1698 var nstate = {}; 1699 for (var n in state) { 1700 var val = state[n]; 1701 if (val instanceof Array) val = val.concat([]); 1702 nstate[n] = val; 1703 } 1704 return nstate; 1705 } 1706 CodeMirror.startState = startState; 1707 function startState(mode, a1, a2) { 1708 return mode.startState ? mode.startState(a1, a2) : true; 1709 } 1710 CodeMirror.copyState = copyState; 1711 1712 // The character stream used by a mode's parser. 1713 function StringStream(string) { 1714 this.pos = this.start = 0; 1715 this.string = string; 1716 } 1717 StringStream.prototype = { 1718 eol: function() {return this.pos >= this.string.length;}, 1719 sol: function() {return this.pos == 0;}, 1720 peek: function() {return this.string.charAt(this.pos);}, 1721 next: function() { 1722 if (this.pos < this.string.length) 1723 return this.string.charAt(this.pos++); 1724 }, 1725 eat: function(match) { 1726 var ch = this.string.charAt(this.pos); 1727 if (typeof match == "string") var ok = ch == match; 1728 else var ok = ch && (match.test ? match.test(ch) : match(ch)); 1729 if (ok) {++this.pos; return ch;} 1730 }, 1731 eatWhile: function(match) { 1732 var start = this.pos; 1733 while (this.eat(match)){} 1734 return this.pos > start; 1735 }, 1736 eatSpace: function() { 1737 var start = this.pos; 1738 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; 1739 return this.pos > start; 1740 }, 1741 skipToEnd: function() {this.pos = this.string.length;}, 1742 skipTo: function(ch) { 1743 var found = this.string.indexOf(ch, this.pos); 1744 if (found > -1) {this.pos = found; return true;} 1745 }, 1746 backUp: function(n) {this.pos -= n;}, 1747 column: function() {return countColumn(this.string, this.start);}, 1748 indentation: function() {return countColumn(this.string);}, 1749 match: function(pattern, consume, caseInsensitive) { 1750 if (typeof pattern == "string") { 1751 function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} 1752 if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { 1753 if (consume !== false) this.pos += pattern.length; 1754 return true; 1755 } 1756 } 1757 else { 1758 var match = this.string.slice(this.pos).match(pattern); 1759 if (match && consume !== false) this.pos += match[0].length; 1760 return match; 1761 } 1762 }, 1763 current: function(){return this.string.slice(this.start, this.pos);} 1764 }; 1765 CodeMirror.StringStream = StringStream; 1766 1767 // Line objects. These hold state related to a line, including 1768 // highlighting info (the styles array). 1769 function Line(text, styles) { 1770 this.styles = styles || [text, null]; 1771 this.stateAfter = null; 1772 this.text = text; 1773 this.marked = this.gutterMarker = this.className = null; 1774 } 1775 Line.prototype = { 1776 // Replace a piece of a line, keeping the styles around it intact. 1777 replace: function(from, to, text) { 1778 var st = [], mk = this.marked; 1779 copyStyles(0, from, this.styles, st); 1780 if (text) st.push(text, null); 1781 copyStyles(to, this.text.length, this.styles, st); 1782 this.styles = st; 1783 this.text = this.text.slice(0, from) + text + this.text.slice(to); 1784 this.stateAfter = null; 1785 if (mk) { 1786 var diff = text.length - (to - from), end = this.text.length; 1787 function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;} 1788 for (var i = 0; i < mk.length; ++i) { 1789 var mark = mk[i], del = false;
1790 if (mark.from >= end) del = true; 1791 else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);} 1792 if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;} 1793 } 1794 } 1795 }, 1796 // Split a line in two, again keeping styles intact. 1797 split: function(pos, textBefore) { 1798 var st = [textBefore, null]; 1799 copyStyles(pos, this.text.length, this.styles, st); 1800 return new Line(textBefore + this.text.slice(pos), st); 1801 }, 1802 addMark: function(from, to, style) { 1803 var mk = this.marked, mark = {from: from, to: to, style: style}; 1804 if (this.marked == null) this.marked = []; 1805 this.marked.push(mark); 1806 this.marked.sort(function(a, b){return a.from - b.from;}); 1807 return mark; 1808 }, 1809 removeMark: function(mark) { 1810 var mk = this.marked; 1811 if (!mk) return; 1812 for (var i = 0; i < mk.length; ++i) 1813 if (mk[i] == mark) {mk.splice(i, 1); break;} 1814 }, 1815 // Run the given mode's parser over a line, update the styles 1816 // array, which contains alternating fragments of text and CSS 1817 // classes. 1818 highlight: function(mode, state) { 1819 var stream = new StringStream(this.text), st = this.styles, pos = 0; 1820 var changed = false, curWord = st[0], prevWord; 1821 if (this.text == "" && mode.blankLine) mode.blankLine(state); 1822 while (!stream.eol()) { 1823 var style = mode.token(stream, state); 1824 var substr = this.text.slice(stream.start, stream.pos); 1825 stream.start = stream.pos; 1826 if (pos && st[pos-1] == style) 1827 st[pos-2] += substr; 1828 else if (substr) { 1829 if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true; 1830 st[pos++] = substr; st[pos++] = style; 1831 prevWord = curWord; curWord = st[pos]; 1832 } 1833 // Give up when line is ridiculously long 1834 if (stream.pos > 5000) { 1835 st[pos++] = this.text.slice(stream.pos); st[pos++] = null; 1836 break; 1837 } 1838 } 1839 if (st.length != pos) {st.length = pos; changed = true;} 1840 if (pos && st[pos-2] != prevWord) changed = true; 1841 // Short lines with simple highlights return null, and are 1842 // counted as changed by the driver because they are likely to 1843 // highlight the same way in various contexts. 1844 return changed || (st.length < 5 && this.text.length < 10 ? null : false); 1845 }, 1846 // Fetch the parser token for a given character. Useful for hacks 1847 // that want to inspect the mode state (say, for completion). 1848 getTokenAt: function(mode, state, ch) { 1849 var txt = this.text, stream = new StringStream(txt); 1850 while (stream.pos < ch && !stream.eol()) { 1851 stream.start = stream.pos; 1852 var style = mode.token(stream, state); 1853 } 1854 return {start: stream.start, 1855 end: stream.pos, 1856 string: stream.current(), 1857 className: style || null, 1858 state: state}; 1859 }, 1860 indentation: function() {return countColumn(this.text);}, 1861 // Produces an HTML fragment for the line, taking selection, 1862 // marking, and highlighting into account. 1863 getHTML: function(sfrom, sto, includePre, endAt) { 1864 var html = []; 1865 if (includePre) 1866 html.push(this.className ? '<pre class="' + this.className + '">': "<pre>"); 1867 function span(text, style) { 1868 if (!text) return; 1869 if (style) { 1870 /* !html write mini_swatch */ 1871 /* 1872 * CARGO intervention to add the color picker 1873 * If the "cm-atom" style is associated, this is a # 1874 * Match a 3-6 char value with only varchar that exists in CSS 1875 * eg: #333 || #aAfF00 1876 * (#[a-fA-F0-9]{3,6}\s) 1877 */ 1878 if(style == "cm-atom" && /(#[a-fA-F0-9]{3,3}$)|(#[a-fA-F0-9]{6,6}$)/.test(text) === true) { 1879 var color = '<a href="#" class="mini_swatch" style="background-color:'+htmlEscape(text)+'" rel="'+htmlEscape(text)+'"><!-- --></a>'; 1880 html.push(color+'<span class="', style, '">', htmlEscape(text), '</span>'); 1881 1882 // Match just the #, or if you just started typing match 1883 } else if(style == "cm-atom" && (text == "#" || /(#[a-fA-F0-9]{1,2}$)|(#[a-fA-F0-9]{4,5}$)/.test(text) === true) ) { 1884 var color = '<a href="#" class="mini_swatch" style="background-color:#FFFFFF" rel="'+htmlEscape(text)+'"><!-- --></a>'; 1885 html.push(color+'<span class="', style, '">
1885', htmlEscape(text), '</span>'); 1886 1887 } else { 1888 html.push('<span class="', style, '">', htmlEscape(text), "</span>"); 1889 } 1890 } 1891 else html.push(htmlEscape(text)); 1892 } 1893 var st = this.styles, allText = this.text, marked = this.marked; 1894 if (sfrom == sto) sfrom = null; 1895 var len = allText.length; 1896 if (endAt != null) len = Math.min(endAt, len); 1897 1898 if (!allText && endAt == null) 1899 span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null); 1900 else if (!marked && sfrom == null) 1901 for (var i = 0, ch = 0; ch < len; i+=2) { 1902 var str = st[i], l = str.length; 1903 if (ch + l > len) str = str.slice(0, len - ch); 1904 ch += l; 1905 span(str, "cm-" + st[i+1]); 1906 } 1907 else { 1908 var pos = 0, i = 0, text = "", style, sg = 0; 1909 var markpos = -1, mark = null; 1910 function nextMark() { 1911 if (marked) { 1912 markpos += 1; 1913 mark = (markpos < marked.length) ? marked[markpos] : null; 1914 } 1915 } 1916 nextMark(); 1917 while (pos < len) { 1918 var upto = len; 1919 var extraStyle = ""; 1920 if (sfrom != null) { 1921 if (sfrom > pos) upto = sfrom; 1922 else if (sto == null || sto > pos) { 1923 extraStyle = " CodeMirror-selected"; 1924 if (sto != null) upto = Math.min(upto, sto); 1925 } 1926 } 1927 while (mark && mark.to != null && mark.to <= pos) nextMark(); 1928 if (mark) { 1929 if (mark.from > pos) upto = Math.min(upto, mark.from); 1930 else { 1931 extraStyle += " " + mark.style; 1932 if (mark.to != null) upto = Math.min(upto, mark.to); 1933 } 1934 } 1935 for (;;) { 1936 var end = pos + text.length; 1937 var appliedStyle = style; 1938 if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle; 1939 span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); 1940 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} 1941 pos = end; 1942 text = st[i++]; style = "cm-" + st[i++]; 1943 } 1944 } 1945 if (sfrom != null && sto == null) span(" ", "CodeMirror-selected"); 1946 } 1947 if (includePre) html.push("</pre>"); 1948 return html.join(""); 1949 } 1950 }; 1951 // Utility used by replace and split above 1952 function copyStyles(from, to, source, dest) { 1953 for (var i = 0, pos = 0, state = 0; pos < to; i+=2) { 1954 var part = source[i], end = pos + part.length; 1955 if (state == 0) { 1956 if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]); 1957 if (end >= from) state = 1; 1958 } 1959 else if (state == 1) { 1960 if (end > to) dest.push(part.slice(0, to - pos), source[i+1]); 1961 else dest.push(part, source[i+1]); 1962 } 1963 pos = end; 1964 } 1965 } 1966 1967 // The history object 'chunks' changes that are made close together 1968 // and at almost the same time into bigger undoable units. 1969 function History() { 1970 this.time = 0; 1971 this.done = []; this.undone = []; 1972 } 1973 History.prototype = { 1974 addChange: function(start, added, old) { 1975 this.undone.length = 0; 1976 var time = +new Date, last = this.done[this.done.length - 1]; 1977 if (time - this.time > 400 || !last || 1978 last.start > start + added || last.start + last.added < start - last.added + last.old.length) 1979 this.done.push({start: start, added: added, old: old}); 1980 else { 1981 var oldoff = 0; 1982 if (start < last.start) { 1983 for (var i = last.start - start - 1; i >= 0; --i) 1984 last.old.unshift(old[i]); 1985 last.added += last.start - start; 1986 last.start = start; 1987 } 1988 else if (last.start < start) { 1989 oldoff = start - last.start; 1990 added += oldoff; 1991 } 1992 for (var i = last.added - oldoff, e = old.length; i < e; ++i) 1993 last.old.push(old[i]); 1994 if (last.added < added) last.added = added; 1995 } 1996 this.time = time; 1997 } 1998 }; 1999 2000 function stopMethod() {e_stop(this);} 2001 // Ensure an event has a stop method. 2002 function addStop(event) { 2003 if (!event.stop) event.stop = stopMethod; 2004 return event; 2005 } 2006 2007 function e_preventDefault(e) { 2008 if (e.preventDefault) e.preventDefault(); 2009 else e.returnValue = false;
2010 } 2011 function e_stopPropagation(e) { 2012 if (e.stopPropagation) e.stopPropagation(); 2013 else e.cancelBubble = true; 2014 } 2015 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} 2016 function e_target(e) {return e.target || e.srcElement;} 2017 function e_button(e) { 2018 if (e.which) return e.which; 2019 else if (e.button & 1) return 1; 2020 else if (e.button & 2) return 3; 2021 else if (e.button & 4) return 2; 2022 } 2023 2024 // Event handler registration. If disconnect is true, it'll return a 2025 // function that unregisters the handler. 2026 function connect(node, type, handler, disconnect) { 2027 function wrapHandler(event) {handler(event || window.event);} 2028 if (typeof node.addEventListener == "function") { 2029 node.addEventListener(type, wrapHandler, false); 2030 if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);}; 2031 } 2032 else { 2033 node.attachEvent("on" + type, wrapHandler); 2034 if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; 2035 } 2036 } 2037 2038 function Delayed() {this.id = null;} 2039 Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; 2040 2041 // Some IE versions don't preserve whitespace when setting the 2042 // innerHTML of a PRE tag. 2043 var badInnerHTML = (function() { 2044 var pre = document.createElement("pre"); 2045 pre.innerHTML = " "; return !pre.innerHTML; 2046 })(); 2047 2048 // Detect drag-and-drop 2049 var dragAndDrop = (function() { 2050 // IE8 has ondragstart and ondrop properties, but doesn't seem to 2051 // actually support ondragstart the way it's supposed to work. 2052 if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false; 2053 var div = document.createElement('div'); 2054 return "ondragstart" in div && "ondrop" in div; 2055 })(); 2056 2057 var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); 2058 var ie = /MSIE \d/.test(navigator.userAgent); 2059 var safari = /Apple Computer/.test(navigator.vendor); 2060 2061 var lineSep = "\n"; 2062 // Feature-detect whether newlines in textareas are converted to \r\n 2063 (function () { 2064 var te = document.createElement("textarea"); 2065 te.value = "foo\nbar"; 2066 if (te.value.indexOf("\r") > -1) lineSep = "\r\n"; 2067 }()); 2068 2069 var tabSize = 8; 2070 var mac = /Mac/.test(navigator.platform); 2071 var movementKeys = {}; 2072 for (var i = 35; i <= 40; ++i) 2073 movementKeys[i] = movementKeys["c" + i] = true; 2074 2075 // Counts the column offset in a string, taking tabs into account. 2076 // Used mostly to find indentation. 2077 function countColumn(string, end) { 2078 if (end == null) { 2079 end = string.search(/[^\s\u00a0]/); 2080 if (end == -1) end = string.length; 2081 } 2082 for (var i = 0, n = 0; i < end; ++i) { 2083 if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); 2084 else ++n; 2085 } 2086 return n; 2087 } 2088 2089 function computedStyle(elt) { 2090 if (elt.currentStyle) return elt.currentStyle; 2091 return window.getComputedStyle(elt, null); 2092 } 2093 // Find the position of an element by following the offsetParent chain. 2094 // If screen==true, it returns screen (rather than page) coordinates. 2095 function eltOffset(node, screen) { 2096 var doc = node.ownerDocument.body; 2097 var x = 0, y = 0, skipDoc = false; 2098 for (var n = node; n; n = n.offsetParent) { 2099 x += n.offsetLeft; y += n.offsetTop; 2100 if (screen && computedStyle(n).position == "fixed") 2101 skipDoc = true; 2102 } 2103 var e = screen && !skipDoc ? null : doc; 2104 for (var n = node.parentNode; n != e; n = n.parentNode) 2105 if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;} 2106 return {left: x, top: y}; 2107 } 2108 // Get a node's text content. 2109 function eltText(node) { 2110 return node.textContent || node.innerText || node.nodeValue || ""; 2111 } 2112 2113 // Operations on {line, ch} objects. 2114 function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} 2115 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} 2116 function copyPos(x) {return {line: x.line, ch: x.ch};} 2117 2118 var escapeElement = document.createElement("div"); 2119 function htmlEscape(str) { 2120 escapeElement.innerText = escapeElement.textContent = str; 2121 return escapeElement.innerHTML; 2122 } 2123 CodeMirror.htmlEscape = htmlEscape; 2124 2125 // Used to position the cursor after an undo/redo by finding the 2126 // last edited character. 2127 function editEnd(from, to) { 2128 if (!to) return from ? from.length : 0; 2129 if (!from) return to.length; 2130 for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) 2131 if (from.charAt(i) != to.charAt(j)) break; 2132 return j + 1; 2133 } 2134 2135 function indexOf(collection, elt) { 2136 if (collection.indexOf) return collection.indexOf(elt); 2137 for (var i = 0, e = collection.length; i < e; ++i) 2138 if (collection[i] == elt) return i; 2139 return -1; 2140 } 2141 2142 // See if "".split is the broken IE version, if so, provide an 2143 // alternative way to split lines. 2144 var splitLines, selRange, setSelRange; 2145 if ("\n\nb".split(/\n/).length != 3) 2146 splitLines = function(string) { 2147 var pos = 0, nl, result = []; 2148 while ((nl = string.indexOf("\n", pos)) > -1) { 2149 result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); 2150 pos = nl + 1; 2151 } 2152 result.push(string.slice(pos)); 2153 return result; 2154 }; 2155 else 2156 splitLines = function(string){return string.split(/\r?\n/);}; 2157 CodeMirror.splitLines = splitLines; 2158 2159 // Sane model of finding and setting the selection in a textarea 2160 if (window.getSelection) { 2161 selRange = function(te) {
2162 try {return {start: te.selectionStart, end: te.selectionEnd};} 2163 catch(e) {return null;} 2164 }; 2165 if (safari) 2166 // On Safari, selection set with setSelectionRange are in a sort 2167 // of limbo wrt their anchor. If you press shift-left in them, 2168 // the anchor is put at the end, and the selection expanded to 2169 // the left. If you press shift-right, the anchor ends up at the 2170 // front. This is not what CodeMirror wants, so it does a 2171 // spurious modify() call to get out of limbo. 2172 setSelRange = function(te, start, end) { 2173 if (start == end) 2174 te.setSelectionRange(start, end); 2175 else { 2176 te.setSelectionRange(start, end - 1); 2177 window.getSelection().modify("extend", "forward", "character"); 2178 } 2179 }; 2180 else 2181 setSelRange = function(te, start, end) { 2182 try {te.setSelectionRange(start, end);} 2183 catch(e) {} // Fails on Firefox when textarea isn't part of the document 2184 }; 2185 } 2186 // IE model. Don't ask. 2187 else { 2188 selRange = function(te) { 2189 try {var range = te.ownerDocument.selection.createRange();} 2190 catch(e) {return null;} 2191 if (!range || range.parentElement() != te) return null; 2192 var val = te.value, len = val.length, localRange = te.createTextRange(); 2193 localRange.moveToBookmark(range.getBookmark()); 2194 var endRange = te.createTextRange(); 2195 endRange.collapse(false); 2196 2197 if (localRange.compareEndPoints("StartToEnd", endRange) > -1) 2198 return {start: len, end: len}; 2199 2200 var start = -localRange.moveStart("character", -len); 2201 for (var i = val.indexOf("\r"); i > -1 && i < start; i = val.indexOf("\r", i+1), start++) {} 2202 2203 if (localRange.compareEndPoints("EndToEnd", endRange) > -1) 2204 return {start: start, end: len}; 2205 2206 var end = -localRange.moveEnd("character", -len); 2207 for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {} 2208 return {start: start, end: end}; 2209 }; 2210 setSelRange = function(te, start, end) { 2211 var range = te.createTextRange(); 2212 range.collapse(true); 2213 var endrange = range.duplicate(); 2214 var newlines = 0, txt = te.value; 2215 for (var pos = txt.indexOf("\n"); pos > -1 && pos < start; pos = txt.indexOf("\n", pos + 1)) 2216 ++newlines; 2217 range.move("character", start - newlines); 2218 for (; pos > -1 && pos < end; pos = txt.indexOf("\n", pos + 1)) 2219 ++newlines; 2220 endrange.move("character", end - newlines); 2221 range.setEndPoint("EndToEnd", endrange); 2222 range.select(); 2223 }; 2224 } 2225 2226 CodeMirror.defineMode("null", function() { 2227 return {token: function(stream) {stream.skipToEnd();}}; 2228 }); 2229 CodeMirror.defineMIME("text/plain", "null"); 2230 2231 return CodeMirror; 2232})(); 2233 2234 2235 2236 2237 2238 2239///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2240 2241///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2242 2243///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2244 2245///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2246 2247 2248 2249 2250CodeMirror.defineMode("css", function(config) { 2251 var indentUnit = config.indentUnit, type; 2252 function ret(style, tp) {type = tp; return style;} 2253 2254 function tokenBase(stream, state) { 2255 var ch = stream.next(); 2256 /* !grab an id based on $ */ 2257 /* 2258 * CARGO intervention to grab the ID 2259 * used to mark the ID of a specific class 2260 * NOTE: As of 3/29/12 this is not in use 2261 */ 2262 if(ch == "$") { 2263 stream.eatWhile(/[\d]/); 2264 return ret("cargo-key", "cargo"); 2265 } 2266 else if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());} 2267 else if (ch == "/" && stream.eat("*")) { 2268 state.tokenize = tokenCComment; 2269 return tokenCComment(stream, state); 2270 } 2271 else if (ch == "<" && stream.eat("!")) { 2272 state.tokenize = tokenSGMLComment; 2273 return tokenSGMLComment(stream, state); 2274 } 2275 else if (ch == "=") ret(null, "compare"); 2276 else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); 2277 else if (ch == "\"" || ch == "'") { 2278 state.tokenize = tokenString(ch); 2279 return state.tokenize(stream, state); 2280 } 2281 else if (ch == "#") { 2282 stream.eatWhile(/[\w\\\-]/); 2283 return ret("atom", "hash"); 2284 } 2285 else if (ch == "!") { 2286 stream.match(/^\s*\w*/); 2287 return ret("keyword", "important"); 2288 } 2289 else if (/\d/.test(ch)) { 2290 stream.eatWhile(/[\w.%]/); 2291 return ret("number", "unit"); 2292 } 2293 else if (/[,.+>*\/]/.test(ch)) { 2294 return ret(null, "select-op"); 2295 } 2296 else if (/[;{}:\[\]]/.test(ch)) { 2297 return ret(null, ch); 2298 } 2299 else { 2300 stream.eatWhile(/[\w\\\-]/); 2301 return ret("variable", "variable"); 2302 } 2303 } 2304 2305 function tokenCComment(stream, state) { 2306 var maybeEnd = false, ch; 2307 while ((ch = stream.next()) != null) { 2308 if (maybeEnd && ch == "/") { 2309 state.tokenize = tokenBase; 2310 break; 2311 } 2312 maybeEnd = (ch == "*"); 2313 } 2314 return ret("comment", "comment"); 2315 } 2316 2317 function tokenSGMLComment(stream, state) { 2318 var dashes = 0, ch; 2319 while ((ch = stream.next()) != null) { 2320 if (dashes >= 2 && ch == ">") { 2321 state.tokenize = tokenBase; 2322 break; 2323 } 2324 dashes = (ch == "-") ? dashes + 1 : 0; 2325 } 2326 return ret("comment", "comment"); 2327 } 2328 2329 function tokenString(quote) { 2330 return function(stream, state) { 2331 var escaped = false, ch; 2332 while ((ch = stream.next()) != null) { 2333 if (ch == quote && !escaped) 2334 break; 2335 escaped = !escaped && ch == "\\"; 2336 } 2337 if (!escaped) state.tokenize = tokenBase; 2338 return ret("string", "string"); 2339 }; 2340 } 2341 2342 return { 2343 startState: function(base) { 2344 return {tokenize: tokenBase, 2345 baseIndent: base || 0, 2346 stack: []}; 2347 }, 2348 2349 token: function(stream, state) { 2350 if (stream.eatSpace()) return null; 2351 var style = state.tokenize(stream, state); 2352 2353 var context = state.stack[state.stack.length-1]; 2354 if (type == "hash" && context == "rule") style = "atom"; 2355 else if (style == "variable") { 2356 if (context == "rule") style = "number"; 2357 else if (!context || context == "@media{") style = "tag"; 2358 } 2359 2360 if (context == "rule" && /^[\{\};]$/.test(type)) 2361 state.stack.pop(); 2362 if (type == "{") { 2363 if (context == "@media") state.stack[state.stack.length-1] = "@media{"; 2364 else state.stack.push("{"); 2365 } 2366 else if (type == "}") state.stack.pop(); 2367 else if (type == "@media") state.stack.push("@media"); 2368 else if (context == "{" && type != "comment") state.stack.push("rule"); 2369 return style; 2370 }, 2371 2372 indent: function(state, textAfter) { 2373 var n = state.stack.length; 2374 if (/^\}/.test(textAfter)) 2375 n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1; 2376 return state.baseIndent + n * indentUnit; 2377 }, 2378 2379 electricChars: "}" 2380 }; 2381}); 2382 2383CodeMirror.defineMIME("text/css", "css"); 2384 2385 2386 2387 2388 2389 2390///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2391
2392///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2393 2394///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2395 2396///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2397 2398 2399 2400 2401CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { 2402 var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true}); 2403 var jsMode = CodeMirror.getMode(config, "javascript"); 2404 var cssMode = CodeMirror.getMode(config, "css"); 2405 2406 function html(stream, state) { 2407 var style = htmlMode.token(stream, state.htmlState); 2408 if (style == "tag" && stream.current() == ">" && state.htmlState.context) { 2409 if (/^script$/i.test(state.htmlState.context.tagName)) { 2410 state.token = javascript; 2411 state.localState = jsMode.startState(htmlMode.indent(state.htmlState, "")); 2412 state.mode = "javascript"; 2413 } 2414 else if (/^style$/i.test(state.htmlState.context.tagName)) { 2415 state.token = css; 2416 state.localState = cssMode.startState(htmlMode.indent(state.htmlState, "")); 2417 state.mode = "css"; 2418 } 2419 } 2420 return style; 2421 } 2422 function maybeBackup(stream, pat, style) { 2423 var cur = stream.current(); 2424 var close = cur.search(pat); 2425 if (close > -1) stream.backUp(cur.length - close); 2426 return style; 2427 } 2428 function javascript(stream, state) { 2429 if (stream.match(/^<\/\s*script\s*>/i, false)) { 2430 state.token = html; 2431 state.curState = null; 2432 state.mode = "html"; 2433 return html(stream, state); 2434 } 2435 return maybeBackup(stream, /<\/\s*script\s*>/, 2436 jsMode.token(stream, state.localState)); 2437 } 2438 function css(stream, state) { 2439 if (stream.match(/^<\/\s*style\s*>/i, false)) { 2440 state.token = html; 2441 state.localState = null; 2442 state.mode = "html"; 2443 return html(stream, state); 2444 } 2445 return maybeBackup(stream, /<\/\s*style\s*>/, 2446 cssMode.token(stream, state.localState)); 2447 } 2448 2449 return { 2450 startState: function() { 2451 var state = htmlMode.startState(); 2452 return {token: html, localState: null, mode: "html", htmlState: state}; 2453 }, 2454 2455 copyState: function(state) { 2456 if (state.localState) 2457 var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState); 2458 return {token: state.token, localState: local, mode: state.mode, 2459 htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; 2460 }, 2461 2462 token: function(stream, state) { 2463 return state.token(stream, state); 2464 }, 2465 2466 indent: function(state, textAfter) { 2467 if (state.token == html || /^\s*<\//.test(textAfter)) 2468 return htmlMode.indent(state.htmlState, textAfter); 2469 else if (state.token == javascript) 2470 return jsMode.indent(state.localState, textAfter); 2471 else 2472 return cssMode.indent(state.localState, textAfter); 2473 }, 2474 2475 electricChars: "/{}:" 2476 } 2477}); 2478 2479CodeMirror.defineMIME("text/html", "htmlmixed"); 2480 2481 2482 2483 2484 2485 2486 2487///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2488 2489///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2490 2491///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2492 2493///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2494 2495 2496 2497 2498CodeMirror.defineMode("javascript", function(config, parserConfig) { 2499 var indentUnit = config.indentUnit; 2500 var jsonMode = parserConfig.json; 2501 2502 // Tokenizer 2503 2504 var keywords = function(){ 2505 function kw(type) {return {type: type, style: "keyword"};} 2506 var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 2507 var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 2508 return { 2509 "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 2510 "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, 2511 "var": kw("var"), "function": kw("function"), "catch": kw("catch"), 2512 "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 2513 "in": operator, "typeof": operator, "instanceof": operator, 2514 "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom 2515 }; 2516 }(); 2517 2518 var isOperatorChar = /[+\-*&%=<>!?|]/; 2519 2520 function chain(stream, state, f) { 2521 state.tokenize = f; 2522 return f(stream, state); 2523 } 2524 2525 function nextUntilUnescaped(stream, end) { 2526 var escaped = false, next; 2527 while ((next = stream.next()) != null) { 2528 if (next == end && !escaped) 2529 return false;
2530 escaped = !escaped && next == "\\"; 2531 } 2532 return escaped; 2533 } 2534 2535 // Used as scratch variables to communicate multiple values without 2536 // consing up tons of objects. 2537 var type, content; 2538 function ret(tp, style, cont) { 2539 type = tp; content = cont; 2540 return style; 2541 } 2542 2543 function jsTokenBase(stream, state) { 2544 var ch = stream.next(); 2545 if (ch == '"' || ch == "'") 2546 return chain(stream, state, jsTokenString(ch)); 2547 else if (/[\[\]{}\(\),;\:\.]/.test(ch)) 2548 return ret(ch); 2549 else if (ch == "0" && stream.eat(/x/i)) { 2550 stream.eatWhile(/[\da-f]/i); 2551 return ret("number", "number"); 2552 } 2553 else if (/\d/.test(ch)) { 2554 stream.match(/^\d*(?:\.\d*)?(?:e[+\-]?\d+)?/); 2555 return ret("number", "number"); 2556 } 2557 else if (ch == "/") { 2558 if (stream.eat("*")) { 2559 return chain(stream, state, jsTokenComment); 2560 } 2561 else if (stream.eat("/")) { 2562 stream.skipToEnd(); 2563 return ret("comment", "comment"); 2564 } 2565 else if (state.reAllowed) { 2566 nextUntilUnescaped(stream, "/"); 2567 stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla 2568 return ret("regexp", "string"); 2569 } 2570 else { 2571 stream.eatWhile(isOperatorChar); 2572 return ret("operator", null, stream.current()); 2573 } 2574 } 2575 else if (ch == "#") { 2576 stream.skipToEnd(); 2577 return ret("error", "error"); 2578 } 2579 else if (isOperatorChar.test(ch)) { 2580 stream.eatWhile(isOperatorChar); 2581 return ret("operator", null, stream.current()); 2582 } 2583 else { 2584 stream.eatWhile(/[\w\$_]/); 2585 var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 2586 return known ? ret(known.type, known.style, word) : 2587 ret("variable", "variable", word); 2588 } 2589 } 2590 2591 function jsTokenString(quote) { 2592 return function(stream, state) { 2593 if (!nextUntilUnescaped(stream, quote)) 2594 state.tokenize = jsTokenBase; 2595 return ret("string", "string"); 2596 }; 2597 } 2598 2599 function jsTokenComment(stream, state) { 2600 var maybeEnd = false, ch; 2601 while (ch = stream.next()) { 2602 if (ch == "/" && maybeEnd) { 2603 state.tokenize = jsTokenBase; 2604 break; 2605 } 2606 maybeEnd = (ch == "*"); 2607 } 2608 return ret("comment", "comment"); 2609 } 2610 2611 // Parser 2612 2613 var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; 2614 2615 function JSLexical(indented, column, type, align, prev, info) { 2616 this.indented = indented; 2617 this.column = column; 2618 this.type = type; 2619 this.prev = prev; 2620 this.info = info; 2621 if (align != null) this.align = align; 2622 } 2623 2624 function inScope(state, varname) { 2625 for (var v = state.localVars; v; v = v.next) 2626 if (v.name == varname) return true; 2627 } 2628 2629 function parseJS(state, style, type, content, stream) { 2630 var cc = state.cc; 2631 // Communicate our context to the combinators. 2632 // (Less wasteful than consing up a hundred closures on every call.) 2633 cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; 2634 2635 if (!state.lexical.hasOwnProperty("align")) 2636 state.lexical.align = true; 2637 2638 while(true) { 2639 var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 2640 if (combinator(type, content)) { 2641 while(cc.length && cc[cc.length - 1].lex) 2642 cc.pop()(); 2643 if (cx.marked) return cx.marked; 2644 if (type == "variable" && inScope(state, content)) return "variable-2"; 2645 return style; 2646 } 2647 } 2648 } 2649 2650 // Combinator utils 2651 2652 var cx = {state: null, column: null, marked: null, cc: null}; 2653 function pass() { 2654 for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 2655 } 2656 function cont() { 2657 pass.apply(null, arguments); 2658 return true; 2659 } 2660 function register(varname) { 2661 var state = cx.state; 2662 if (state.context) { 2663 cx.marked = "def"; 2664 for (var v = state.localVars; v; v = v.next) 2665 if (v.name == varname) return; 2666 state.localVars = {name: varname, next: state.localVars}; 2667 } 2668 } 2669 2670 // Combinators 2671 2672 var defaultVars = {name: "this", next: {name: "arguments"}}; 2673 function pushcontext() { 2674 if (!cx.state.context) cx.state.localVars = defaultVars; 2675 cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 2676 } 2677 function popcontext() { 2678 cx.state.localVars = cx.state.context.vars; 2679 cx.state.context = cx.state.context.prev; 2680 } 2681 function pushlex(type, info) { 2682 var result = function() { 2683 var state = cx.state; 2684 state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info) 2685 }; 2686 result.lex = true; 2687 return result; 2688 } 2689 function poplex() { 2690 var state = cx.state; 2691 if (state.lexical.prev) { 2692 if (state.lexical.type == ")") 2693 state.indented = state.lexical.indented; 2694 state.lexical = state.lexical.prev; 2695 } 2696 } 2697 poplex.lex = true; 2698 2699 function expect(wanted) { 2700 return function expecting(type) { 2701 if (type == wanted) return cont(); 2702 else if (wanted == ";") return pass(); 2703 else return cont(arguments.callee); 2704 }; 2705 } 2706
2707 function statement(type) { 2708 if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); 2709 if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); 2710 if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 2711 if (type == "{") return cont(pushlex("}"), block, poplex); 2712 if (type == ";") return cont(); 2713 if (type == "function") return cont(functiondef); 2714 if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), 2715 poplex, statement, poplex); 2716 if (type == "variable") return cont(pushlex("stat"), maybelabel); 2717 if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), 2718 block, poplex, poplex); 2719 if (type == "case") return cont(expression, expect(":")); 2720 if (type == "default") return cont(expect(":")); 2721 if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 2722 statement, poplex, popcontext); 2723 return pass(pushlex("stat"), expression, expect(";"), poplex); 2724 } 2725 function expression(type) { 2726 if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); 2727 if (type == "function") return cont(functiondef); 2728 if (type == "keyword c") return cont(expression); 2729 if (type == "(") return cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator); 2730 if (type == "operator") return cont(expression); 2731 if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); 2732 if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); 2733 return cont(); 2734 } 2735 function maybeoperator(type, value) { 2736 if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); 2737 if (type == "operator") return cont(expression); 2738 if (type == ";") return; 2739 if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); 2740 if (type == ".") return cont(property, maybeoperator); 2741 if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); 2742 } 2743 function maybelabel(type) { 2744 if (type == ":") return cont(poplex, statement); 2745 return pass(maybeoperator, expect(";"), poplex); 2746 } 2747 function property(type) { 2748 if (type == "variable") {cx.marked = "property"; return cont();} 2749 } 2750 function objprop(type) { 2751 if (type == "variable") cx.marked = "property"; 2752 if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); 2753 } 2754 function commasep(what, end) { 2755 function proceed(type) { 2756 if (type == ",") return cont(what, proceed); 2757 if (type == end) return cont(); 2758 return cont(expect(end)); 2759 } 2760 return function commaSeparated(type) { 2761 if (type == end) return cont(); 2762 else return pass(what, proceed); 2763 }; 2764 } 2765 function block(type) { 2766 if (type == "}") return cont(); 2767 return pass(statement, block); 2768 } 2769 function vardef1(type, value) { 2770 if (type == "variable"){register(value); return cont(vardef2);} 2771 return cont(); 2772 } 2773 function vardef2(type, value) { 2774 if (value == "=") return cont(expression, vardef2); 2775 if (type == ",") return cont(vardef1); 2776 } 2777 function forspec1(type) { 2778 if (type == "var") return cont(vardef1, forspec2); 2779 if (type == ";") return pass(forspec2); 2780 if (type == "variable") return cont(formaybein); 2781 return pass(forspec2); 2782 } 2783 function formaybein(type, value) { 2784 if (value == "in") return cont(expression); 2785 return cont(maybeoperator, forspec2); 2786 } 2787 function forspec2(type, value) { 2788 if (type == ";") return cont(forspec3); 2789 if (value == "in") return cont(expression); 2790 return cont(expression, expect(";"), forspec3); 2791 } 2792 function forspec3(type) { 2793 if (type != ")") cont(expression); 2794 } 2795 function functiondef(type, value) { 2796 if (type == "variable") {register(value); return cont(functiondef);} 2797 if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); 2798 } 2799 function funarg(type, value) { 2800 if (type == "variable") {register(value); return cont();} 2801 } 2802 2803 // Interface 2804 2805 return { 2806 startState: function(basecolumn) { 2807 return { 2808 tokenize: jsTokenBase, 2809 reAllowed: true, 2810 cc: [], 2811 lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 2812 localVars: null, 2813 context: null, 2814 indented: 0 2815 }; 2816 }, 2817 2818 token: function(stream, state) {
2819 if (stream.sol()) { 2820 if (!state.lexical.hasOwnProperty("align")) 2821 state.lexical.align = false; 2822 state.indented = stream.indentation(); 2823 } 2824 if (stream.eatSpace()) return null; 2825 var style = state.tokenize(stream, state); 2826 if (type == "comment") return style; 2827 state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/); 2828 return parseJS(state, style, type, content, stream); 2829 }, 2830 2831 indent: function(state, textAfter) { 2832 if (state.tokenize != jsTokenBase) return 0; 2833 var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, 2834 type = lexical.type, closing = firstChar == type; 2835 if (type == "vardef") return lexical.indented + 4; 2836 else if (type == "form" && firstChar == "{") return lexical.indented; 2837 else if (type == "stat" || type == "form") return lexical.indented + indentUnit; 2838 else if (lexical.info == "switch" && !closing) 2839 return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 2840 else if (lexical.align) return lexical.column + (closing ? 0 : 1); 2841 else return lexical.indented + (closing ? 0 : indentUnit); 2842 }, 2843 2844 electricChars: ":{}" 2845 }; 2846}); 2847 2848CodeMirror.defineMIME("text/javascript", "javascript"); 2849CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 2850 2851 2852///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2853 2854///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2855 2856///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2857 2858 2859CodeMirror.defineMode("xml", function(config, parserConfig) { 2860 var indentUnit = config.indentUnit; 2861 var Kludges = parserConfig.htmlMode ? { 2862 autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true, 2863 "meta": true, "col": true, "frame": true, "base": true, "area": true}, 2864 doNotIndent: {"pre": true}, 2865 allowUnquoted: true, 2866 allowMissing: false 2867 } : {autoSelfClosers: {}, doNotIndent: {}, allowUnquoted: false, allowMissing: false}; 2868 var alignCDATA = parserConfig.alignCDATA; 2869 2870 // Return variables for tokenizers 2871 var tagName, type; 2872 2873 function inText(stream, state) { 2874 function chain(parser) { 2875 state.tokenize = parser; 2876 return parser(stream, state); 2877 } 2878 2879 var ch = stream.next(); 2880 if (ch == "<") { 2881 if (stream.eat("!")) { 2882 if (stream.eat("[")) { 2883 if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); 2884 else return null; 2885 } 2886 else if (stream.match("--")) return chain(inBlock("comment", "-->")); 2887 else if (stream.match("DOCTYPE", true, true)) { 2888 stream.eatWhile(/[\w\._\-]/); 2889 return chain(doctype(1)); 2890 } 2891 else return null; 2892 } 2893 else if (stream.eat("?")) { 2894 stream.eatWhile(/[\w\._\-]/); 2895 state.tokenize = inBlock("meta", "?>"); 2896 return "meta"; 2897 } 2898 else { 2899 type = stream.eat("/") ? "closeTag" : "openTag"; 2900 stream.eatSpace(); 2901 tagName = ""; 2902 var c; 2903 while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; 2904 state.tokenize = inTag; 2905 return "tag"; 2906 } 2907 } 2908 else if (ch == "&") { 2909 var ok; 2910 if (stream.eat("#")) { 2911 if (stream.eat("x")) { 2912 ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); 2913 } else { 2914 ok = stream.eatWhile(/[\d]/) && stream.eat(";"); 2915 } 2916 } else { 2917 ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); 2918 } 2919 return ok ? "atom" : "error"; 2920 } 2921 else { 2922 stream.eatWhile(/[^&<]/); 2923 return null; 2924 } 2925 } 2926 2927 function inTag(stream, state) { 2928 var ch = stream.next(); 2929 if (ch == ">" || (ch == "/" && stream.eat(">"))) { 2930 state.tokenize = inText; 2931 type = ch == ">" ? "endTag" : "selfcloseTag"; 2932 return "tag"; 2933 } 2934 else if (ch == "=") { 2935 type = "equals"; 2936 return null; 2937 } 2938 else if (/[\'\"]/.test(ch)) { 2939 state.tokenize = inAttribute(ch); 2940 return state.tokenize(stream, state); 2941 } 2942 else { 2943 stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/); 2944 return "word"; 2945 } 2946 } 2947 2948 function inAttribute(quote) { 2949 return function(stream, state) { 2950 while (!stream.eol()) { 2951 if (stream.next() == quote) { 2952 state.tokenize = inTag; 2953 break; 2954 } 2955 } 2956 return "string"; 2957 }; 2958 } 2959 2960 function inBlock(style, terminator) { 2961 return function(stream, state) { 2962 while (!stream.eol()) { 2963 if (stream.match(terminator)) { 2964 state.tokenize = inText; 2965 break; 2966 } 2967 stream.next(); 2968 } 2969 return style; 2970 }; 2971 } 2972 function doctype(depth) { 2973 return function(stream, state) { 2974 var ch; 2975 while ((ch = stream.next()) != null) { 2976 if (ch == "<") { 2977 state.tokenize = doctype(depth + 1); 2978 return state.tokenize(stream, state); 2979 } else if (ch == ">") { 2980 if (depth == 1) { 2981 state.tokenize = inText; 2982 break; 2983 } else { 2984 state.tokenize = doctype(depth - 1); 2985 return state.tokenize(stream, state); 2986 } 2987 } 2988 } 2989 return "meta"; 2990 }; 2991 } 2992 2993 var curState, setStyle; 2994 function pass() { 2995 for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); 2996 } 2997 function cont() { 2998 pass.apply(null, arguments); 2999 return true; 3000 } 3001 3002 function pushContext(tagName, startOfLine) { 3003 var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent); 3004 curState.context = { 3005 prev: curState.context, 3006 tagName: tagName, 3007 indent: curState.indented, 3008 startOfLine: startOfLine, 3009 noIndent: noIndent 3010 }; 3011 } 3012 function popContext() { 3013 if (curState.context) curState.context = curState.context.prev; 3014 } 3015 3016 function element(type) { 3017 if (type == "openTag") { 3018 curState.tagName = tagName; 3019 return cont(attributes, endtag(curState.startOfLine)); 3020 } else if (type == "closeTag") { 3021 var err = false;
3022 if (curState.context) { 3023 err = curState.context.tagName != tagName; 3024 } else { 3025 err = true; 3026 } 3027 if (err) setStyle = "error"; 3028 return cont(endclosetag(err)); 3029 } 3030 return cont(); 3031 } 3032 function endtag(startOfLine) { 3033 return function(type) { 3034 if (type == "selfcloseTag" || 3035 (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) 3036 return cont(); 3037 if (type == "endTag") {pushContext(curState.tagName, startOfLine); return cont();} 3038 return cont(); 3039 }; 3040 } 3041 function endclosetag(err) { 3042 return function(type) { 3043 if (err) setStyle = "error"; 3044 if (type == "endTag") { popContext(); return cont(); } 3045 setStyle = "error"; 3046 return cont(arguments.callee); 3047 } 3048 } 3049 3050 function attributes(type) { 3051 if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);} 3052 if (type == "endTag" || type == "selfcloseTag") return pass(); 3053 setStyle = "error"; 3054 return cont(attributes); 3055 } 3056 function attribute(type) { 3057 if (type == "equals") return cont(attvalue, attributes); 3058 if (!Kludges.allowMissing) setStyle = "error"; 3059 return (type == "endTag" || type == "selfcloseTag") ? pass() : cont(); 3060 } 3061 function attvalue(type) { 3062 if (type == "string") return cont(attvaluemaybe); 3063 if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();} 3064 setStyle = "error"; 3065 return (type == "endTag" || type == "selfCloseTag") ? pass() : cont(); 3066 } 3067 function attvaluemaybe(type) { 3068 if (type == "string") return cont(attvaluemaybe); 3069 else return pass(); 3070 } 3071 3072 return { 3073 startState: function() { 3074 return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null}; 3075 }, 3076 3077 token: function(stream, state) { 3078 if (stream.sol()) { 3079 state.startOfLine = true; 3080 state.indented = stream.indentation(); 3081 } 3082 if (stream.eatSpace()) return null; 3083 3084 setStyle = type = tagName = null; 3085 var style = state.tokenize(stream, state); 3086 state.type = type; 3087 if ((style || type) && style != "comment") { 3088 curState = state; 3089 while (true) { 3090 var comb = state.cc.pop() || element; 3091 if (comb(type || style)) break; 3092 } 3093 } 3094 state.startOfLine = false; 3095 return setStyle || style; 3096 }, 3097 3098 indent: function(state, textAfter, fullLine) { 3099 var context = state.context; 3100 if ((state.tokenize != inTag && state.tokenize != inText) || 3101 context && context.noIndent) 3102 return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; 3103 if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0; 3104 if (context && /^<\//.test(textAfter)) 3105 context = context.prev; 3106 while (context && !context.startOfLine) 3107 context = context.prev; 3108 if (context) return context.indent + indentUnit; 3109 else return 0; 3110 }, 3111 3112 compareStates: function(a, b) { 3113 if (a.indented != b.indented || a.tokenize != b.tokenize) return false; 3114 for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) { 3115 if (!ca || !cb) return ca == cb; 3116 if (ca.tagName != cb.tagName) return false; 3117 } 3118 }, 3119 3120 electricChars: "/" 3121 }; 3122}); 3123 3124CodeMirror.defineMIME("application/xml", "xml"); 3125CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); 3126 3127
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.