1/* CodeMirror main module 2 * 3 * Implements the CodeMirror constructor and prototype, which take care 4 * of initializing the editor frame, and providing the outside interface. 5 * 6 * modified by Farshad Mohajeri, [email protected] 7 */ 8 9// The CodeMirrorConfig object is used to specify a default 10// configuration. If you specify such an object before loading this 11// file, the values you put into it will override the defaults given 12// below. You can also assign to it after loading. 13 14var CodeMirrorConfig = window.CodeMirrorConfig || {}; 15 16var CodeMirror = (function(){ 17 function setDefaults(object, defaults) { 18 for (var option in defaults) { 19 if (!object.hasOwnProperty(option)) 20 object[option] = defaults[option]; 21 } 22 } 23 function forEach(array, action) { 24 for (var i = 0; i < array.length; i++) 25 action(array[i]); 26 } 27 28 // These default options can be overridden by passing a set of 29 // options to a specific CodeMirror constructor. See manual.html for 30 // their meaning. 31 setDefaults(CodeMirrorConfig, { 32 stylesheet: [], 33 path: "", 34 parserfile: [], 35 basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"], 36 iframeClass: null, 37 passDelay: 200, 38 passTime: 50, 39 lineNumberDelay: 200, 40 lineNumberTime: 50, 41 continuousScanning: false, 42 saveFunction: null, 43 onChange: null, 44 undoDepth: 50, 45 undoDelay: 800, 46 disableSpellcheck: true, 47 textWrapping: true, 48 readOnly: false, 49 width: "", 50 height: "300px", 51 minHeight: 100, 52 autoMatchParens: false, 53 parserConfig: null, 54 tabMode: "indent", // or "spaces", "default", "shift" 55 reindentOnLoad: false, 56 activeTokens: null, 57 cursorActivity: null, 58 lineNumbers: false, 59 indentUnit: 2, 60 domain: null 61 }); 62 63 function addLineNumberDiv(container, options) { 64 var nums = document.createElement("DIV"), 65 scroller = document.createElement("DIV"); 66 67 nums.id="CodeMirror-line-numbers-"+options.id; 68 nums.style.position = "absolute"; 69 nums.style.height = "100%"; 70 if (nums.style.setExpression) { 71 try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");} 72 catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions 73 } 74 nums.style.top = "0px"; 75 nums.style.left = "0px"; 76 nums.style.width = options.gwidth+"px"; // FM added 77 nums.style.overflow = "hidden"; 78 container.appendChild(nums); 79 scroller.className = "CodeMirror-line-numbers"; 80 nums.appendChild(scroller); 81 scroller.innerHTML = "<div>1</div>"; 82 return nums; 83 } 84 85 function frameHTML(options) { 86 if (typeof options.parserfile == "string") 87 options.parserfile = [options.parserfile]; 88 if (typeof options.stylesheet == "string") 89 options.stylesheet = [options.stylesheet]; 90 91 var html = ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><head>"]; 92 // Hack to work around a bunch of IE8-specific problems. 93 html.push("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=EmulateIE7\"/>"); 94 forEach(options.stylesheet, function(file) { 95 html.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + file + "\"/>"); 96 }); 97 forEach(options.basefiles.concat(options.parserfile), function(file) { 98 if (!/^https?:/.test(file)) file = options.path + file; 99 html.push("<script type=\"text/javascript\" src=\"" + file + "\"><" + "/script>"); 100 }); 101 html.push("</head><body style=\"border-width: 0;\" class=\"editbox\" spellcheck=\"" + 102 (options.disableSpellcheck ? "false" : "true") + "\"></body></html>"); 103 return html.join(""); 104 } 105 106 var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent); 107 108 function CodeMirror(place, options) { 109 // Use passed options, if any, to override defaults. 110 this.options = options = options || {}; 111 setDefaults(options, CodeMirrorConfig); 112 113 // Backward compatibility for deprecated options. 114 if (options.dumbTabs) options.tabMode = "spaces";
115 else if (options.normalTab) options.tabMode = "default"; 116 117 var frame = this.frame = document.createElement("IFRAME"); 118 if (options.iframeClass) frame.className = options.iframeClass; 119 frame.frameBorder = 0; 120 frame.style.border = "0"; 121 frame.style.width = '100%'; 122 frame.style.height = '100%'; 123 // display: block occasionally suppresses some Firefox bugs, so we 124 // always add it, redundant as it sounds. 125 frame.style.display = "block"; 126 127 var div = this.wrapping = document.createElement("DIV"); 128 this.options.div = div; 129// div.style.position = "relative"; // FM 130 div.style.position = "absolute"; // FM added 131 if (options.hidden === true) { 132 div.style.visibility = "hidden"; 133 }; 134 135 div.id="CodeMirror-wrapping-"+options.id; // FM added 136 if (options.x!=undefined) div.style.left=options.x+"px"; // FM added 137 if (options.y!=undefined) div.style.top=options.y+"px"; // FM added 138 if (options.width != undefined) { 139 var eWidth = options.width; 140 div.style.width = eWidth+"px"; 141 } 142 143 div.className = "CodeMirror-wrapping"; 144 145 146 div.style.height = (options.height == "dynamic") ? options.minHeight + "px" : options.height; 147 // This is used by Editor.reroutePasteEvent 148 var teHack = this.textareaHack = document.createElement("TEXTAREA"); 149 div.appendChild(teHack); 150 teHack.style.position = "absolute"; 151 teHack.style.left = "-10000px"; 152 teHack.style.width = "10px"; 153 154 // Link back to this object, so that the editor can fetch options 155 // and add a reference to itself. 156 frame.CodeMirror = this; 157 if (options.domain && internetExplorer) { 158 this.html = frameHTML(options); 159 frame.src = "javascript:(function(){document.open();" + 160 (options.domain ? "document.domain=\"" + options.domain + "\";" : "") + 161 "document.write(window.frameElement.CodeMirror.html);document.close();})()"; 162 } 163 else { 164 frame.src = "javascript:false"; 165 } 166 167 if (place.appendChild) place.appendChild(div); 168 else place(div); 169 div.appendChild(frame); 170 if (options.lineNumbers) this.lineNumbers = addLineNumberDiv(div, options); 171 172 this.win = frame.contentWindow; 173 if (!options.domain || !internetExplorer) { 174 this.win.document.open(); 175 this.win.document.write(frameHTML(options)); 176 this.win.document.close(); 177 } 178 } 179 180 CodeMirror.prototype = { 181 init: function() { 182 if (this.options.initCallback) this.options.initCallback(this); 183 if (this.options.lineNumbers) this.activateLineNumbers(); 184 if (this.options.reindentOnLoad) this.reindent(); 185 if (this.options.height == "dynamic") this.setDynamicHeight(); 186 }, 187 188 getCode: function() {return this.editor.getCode();}, 189 setCode: function(code) {this.editor.importCode(code);}, 190 selection: function() {this.focusIfIE(); return this.editor.selectedText();}, 191 reindent: function() {this.editor.reindent();}, 192 reindentSelection: function() {this.focusIfIE(); this.editor.reindentSelection(null);}, 193 194 focusIfIE: function() { 195 // in IE, a lot of selection-related functionality only works when the frame is focused 196 if (this.win.select.ie_selection) this.focus(); 197 }, 198 focus: function() { 199 this.win.focus(); 200 if (this.editor.selectionSnapshot) // IE hack 201 this.win.select.setBookmark(this.win.document.body, this.editor.selectionSnapshot); 202 }, 203 replaceSelection: function(text) { 204 this.focus(); 205 this.editor.replaceSelection(text); 206 return true; 207 }, 208 replaceChars: function(text, start, end) { 209 this.editor.replaceChars(text, start, end); 210 }, 211 getSearchCursor: function(string, fromCursor, caseFold) { 212 return this.editor.getSearchCursor(string, fromCursor, caseFold); 213 }, 214 215 undo: function() {this.editor.history.undo();}, 216 redo: function() {this.editor.history.redo();}, 217 historySize: function() {return this.editor.history.historySize();}, 218 clearHistory: function() {this.editor.history.clear();}, 219 220 grabKeys: function(callback, filter) {this.editor.grabKeys(callback, filter);}, 221 ungrabKeys: function() {this.editor.ungrabKeys();}, 222 223 setParser: function(name, parserConfig) {this.editor.setParser(name, parserConfig);}, 224 setSpellcheck: function(on) {this.win.document.body.spellcheck = on;}, 225 setStylesheet: function(names) { 226 if (typeof names === "string") names = [names]; 227 var activeStylesheets = {}; 228 var matchedNames = {}; 229 var links = this.win.document.getElementsByTagName("link"); 230 // Create hashes of active stylesheets and matched names. 231 // This is O(n^2) but n is expected to be very small. 232 for (var x = 0, link; link = links[x]; x++) { 233 if (link.rel.indexOf("stylesheet") !== -1) { 234 for (var y = 0; y < names.length; y++) { 235 var name = names[y]; 236 if (link.href.substring(link.href.length - name.length) === name) { 237 activeStylesheets[link.href] = true; 238 matchedNames[name] = true; 239 } 240 } 241 } 242 } 243 // Activate the selected stylesheets and disable the rest. 244 for (var x = 0, link; link = links[x]; x++) { 245 if (link.rel.indexOf("stylesheet") !== -1) { 246 link.disabled = !(link.href in activeStylesheets); 247 } 248 } 249 // Create any new stylesheets. 250 for (var y = 0; y < names.length; y++) { 251 var name = names[y]; 252 if (!(name in matchedNames)) { 253 var link = this.win.document.createElement("link"); 254 link.rel = "stylesheet"; 255 link.type = "text/css"; 256 link.href = name; 257 this.win.document.getElementsByTagName('head')[0].appendChild(link); 258 } 259 } 260 }, 261 setTextWrapping: function(on) { 262 if (on == this.options.textWrapping) return; 263 this.win.document.body.style.whiteSpace = on ? "" : "nowrap"; 264 this.options.textWrapping = on; 265 if (this.lineNumbers) { 266 this.setLineNumbers(false); 267 this.setLineNumbers(true); 268 } 269 }, 270 setIndentUnit: function(unit) {this.win.indentUnit = unit;}, 271 setUndoDepth: function(depth) {this.editor.history.maxDepth = depth;}, 272 setTabMode: function(mode) {this.options.tabMode = mode;},
273 setLineNumbers: function(on) { 274 if (on && !this.lineNumbers) { 275 this.lineNumbers = addLineNumberDiv(this.wrapping, this.options); 276 this.activateLineNumbers(); 277 } 278 else if (!on && this.lineNumbers) { 279 this.wrapping.removeChild(this.lineNumbers); 280 this.wrapping.style.marginLeft = ""; 281 this.lineNumbers = null; 282 } 283 }, 284 285 cursorPosition: function(start) {this.focusIfIE(); return this.editor.cursorPosition(start);}, 286 firstLine: function() {return this.editor.firstLine();}, 287 lastLine: function() {return this.editor.lastLine();}, 288 nextLine: function(line) {return this.editor.nextLine(line);}, 289 prevLine: function(line) {return this.editor.prevLine(line);}, 290 lineContent: function(line) {return this.editor.lineContent(line);}, 291 setLineContent: function(line, content) {this.editor.setLineContent(line, content);}, 292 removeLine: function(line){this.editor.removeLine(line);}, 293 insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);}, 294 selectLines: function(startLine, startOffset, endLine, endOffset) { 295 this.win.focus(); 296 this.editor.selectLines(startLine, startOffset, endLine, endOffset); 297 }, 298 nthLine: function(n) { 299 var line = this.firstLine(); 300 for (; n > 1 && line !== false; n--) 301 line = this.nextLine(line); 302 return line; 303 }, 304 lineNumber: function(line) { 305 var num = 0; 306 while (line !== false) { 307 num++; 308 line = this.prevLine(line); 309 } 310 return num; 311 }, 312 jumpToLine: function(line) { 313 if (typeof line == "number") line = this.nthLine(line); 314 this.selectLines(line, 0); 315 this.win.focus(); 316 }, 317 currentLine: function() { // Deprecated, but still there for backward compatibility 318 return this.lineNumber(this.cursorLine()); 319 }, 320 cursorLine: function() { 321 return this.cursorPosition().line; 322 }, 323 cursorCoords: function(start) {return this.editor.cursorCoords(start);}, 324 325 activateLineNumbers: function() { 326 var frame = this.frame, win = frame.contentWindow, doc = win.document, body = doc.body, 327 nums = this.lineNumbers, scroller = nums.firstChild, self = this; 328 var barWidth = null; 329 330 function sizeBar() { 331 if (frame.offsetWidth == 0) return; 332 for (var root = frame; root.parentNode; root = root.parentNode); 333 if (!nums.parentNode || root != document || !win.Editor) { 334 // Clear event handlers (their nodes might already be collected, so try/catch) 335 try{clear();}catch(e){} 336 clearInterval(sizeInterval); 337 return; 338 } 339 340 if (nums.offsetWidth != barWidth) { 341 barWidth = nums.offsetWidth; 342 frame.parentNode.style.paddingLeft = barWidth + "px"; 343 } 344 } 345 function doScroll() { 346 nums.scrollTop = body.scrollTop || doc.documentElement.scrollTop || 0; 347 } 348 // Cleanup function, registered by nonWrapping and wrapping. 349 var clear = function(){}; 350 sizeBar(); 351 var sizeInterval = setInterval(sizeBar, 500); 352 353 function ensureEnoughLineNumbers(fill) { 354 var lineHeight = scroller.firstChild.offsetHeight; 355 if (lineHeight == 0) return; 356 var targetHeight = 50 + Math.max(body.offsetHeight, Math.max(frame.offsetHeight, body.scrollHeight || 0)), 357 lastNumber = Math.ceil(targetHeight / lineHeight); 358 for (var i = scroller.childNodes.length; i <= lastNumber; i++) { 359 var div = document.createElement("DIV"); 360 div.appendChild(document.createTextNode(fill ? String(i + 1) : "\u00a0")); 361 scroller.appendChild(div); 362 } 363 } 364 365 function nonWrapping() { 366 function update() { 367 ensureEnoughLineNumbers(true); 368 doScroll(); 369 } 370 self.updateNumbers = update; 371 var onScroll = win.addEventHandler(win, "scroll", doScroll, true), 372 onResize = win.addEventHandler(win, "resize", update, true); 373 clear = function(){ 374 onScroll(); onResize(); 375 if (self.updateNumbers == update) self.updateNumbers = null; 376 }; 377 update(); 378 } 379 380 function wrapping() { 381 var node, lineNum, next, pos, changes = [], styleNums = self.options.styleNumbers; 382 383 function setNum(n, node) {
384 // Does not typically happen (but can, if you mess with the 385 // document during the numbering) 386 if (!lineNum) lineNum = scroller.appendChild(document.createElement("DIV")); 387 if (styleNums) styleNums(lineNum, node, n); 388 // Changes are accumulated, so that the document layout 389 // doesn't have to be recomputed during the pass 390 changes.push(lineNum); changes.push(n); 391 pos = lineNum.offsetHeight + lineNum.offsetTop; 392 lineNum = lineNum.nextSibling; 393 } 394 function commitChanges() { 395 for (var i = 0; i < changes.length; i += 2) 396 changes[i].innerHTML = changes[i + 1]; 397 changes = []; 398 } 399 function work() { 400 if (!scroller.parentNode || scroller.parentNode != self.lineNumbers) return; 401 402 var endTime = new Date().getTime() + self.options.lineNumberTime; 403 while (node) { 404 setNum(next++, node.previousSibling); 405 for (; node && !win.isBR(node); node = node.nextSibling) { 406 var bott = node.offsetTop + node.offsetHeight; 407 while (scroller.offsetHeight && bott - 3 > pos) setNum(" "); 408 } 409 if (node) node = node.nextSibling; 410 if (new Date().getTime() > endTime) { 411 commitChanges(); 412 pending = setTimeout(work, self.options.lineNumberDelay); 413 return; 414 } 415 } 416 while (lineNum) setNum(next++); 417 commitChanges(); 418 doScroll(); 419 } 420 function start(firstTime) { 421 doScroll(); 422 ensureEnoughLineNumbers(firstTime); 423 node = body.firstChild; 424 lineNum = scroller.firstChild; 425 pos = 0; 426 next = 1; 427 work(); 428 } 429 430 start(true); 431 var pending = null; 432 function update() { 433 if (pending) clearTimeout(pending); 434 if (self.editor.allClean()) start(); 435 else pending = setTimeout(update, 200); 436 } 437 self.updateNumbers = update; 438 var onScroll = win.addEventHandler(win, "scroll", doScroll, true), 439 onResize = win.addEventHandler(win, "resize", update, true); 440 clear = function(){ 441 if (pending) clearTimeout(pending); 442 if (self.updateNumbers == update) self.updateNumbers = null; 443 onScroll(); 444 onResize(); 445 }; 446 } 447 (this.options.textWrapping || this.options.styleNumbers ? wrapping : nonWrapping)(); 448 }, 449 450 setDynamicHeight: function() { 451 var self = this, activity = self.options.cursorActivity, win = self.win, body = win.document.body, 452 lineHeight = null, timeout = null, vmargin = 2 * self.frame.offsetTop; 453 body.style.overflowY = "hidden"; 454 win.document.documentElement.style.overflowY = "hidden"; 455 this.frame.scrolling = "no"; 456 457 function updateHeight() { 458 for (var span = body.firstChild, sawBR = false; span; span = span.nextSibling) 459 if (win.isSpan(span) && span.offsetHeight) { 460 lineHeight = span.offsetHeight; 461 if (!sawBR) vmargin = 2 * (self.frame.offsetTop + span.offsetTop + body.offsetTop + (internetExplorer ? 10 : 0)); 462 break; 463 } 464 if (lineHeight) 465 self.wrapping.style.height = Math.max(vmargin + lineHeight * (body.getElementsByTagName("BR").length + 1), 466 self.options.minHeight) + "px"; 467 } 468 setTimeout(updateHeight, 100); 469 self.options.cursorActivity = function(x) { 470 if (activity) activity(x); 471 clearTimeout(timeout); 472 timeout = setTimeout(updateHeight, 200); 473 }; 474 } 475 }; 476 477 CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}}; 478 479 CodeMirror.replace = function(element) { 480 if (typeof element == "string") 481 element = document.getElementById(element); 482 return function(newElement) { 483 element.parentNode.replaceChild(newElement, element); 484 }; 485 }; 486 487 CodeMirror.fromTextArea = function(area, options) { 488 if (typeof area == "string") 489 area = document.getElementById(area); 490 491 options = options || {}; 492 if (area.style.width && options.width == null) 493 options.width = area.style.width; 494 if (area.style.height && options.height == null) 495 options.height = area.style.height; 496 if (options.content == null) options.content = area.value; 497 498 if (area.form) { 499 function updateField() { 500 area.value = mirror.getCode(); 501 } 502 if (typeof area.form.addEventListener == "function") 503 area.form.addEventListener("submit", updateField, false); 504 else 505 area.form.attachEvent("onsubmit", updateField); 506 var realSubmit = area.form.submit; 507 function wrapSubmit() { 508 updateField(); 509 // Can't use realSubmit.apply because IE6 is too stupid 510 area.form.submit = realSubmit; 511 area.form.submit(); 512 area.form.submit = wrapSubmit; 513 } 514 area.form.submit = wrapSubmit; 515 } 516 517 function insert(frame) { 518 if (area.nextSibling) 519 area.parentNode.insertBefore(frame, area.nextSibling); 520 else 521 area.parentNode.appendChild(frame); 522 } 523 524 area.style.display = "none"; 525 var mirror = new CodeMirror(insert, options); 526 mirror.toTextArea = function() { 527 area.parentNode.removeChild(mirror.wrapping); 528 area.style.display = ""; 529 if (area.form) { 530 area.form.submit = realSubmit; 531 if (typeof area.form.removeEventListener == "function") 532 area.form.removeEventListener("submit", updateField, false); 533 else 534 area.form.detachEvent("onsubmit", updateField); 535 } 536 }; 537 538 return mirror; 539 }; 540 541 CodeMirror.isProbablySupported = function() { 542 // This is rather awful, but can be useful. 543 var match; 544 if (window.opera) 545 return Number(window.opera.version()) >= 9.52; 546 else if (/Apple Computers, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./))) 547 return Number(match[1]) >= 3; 548 else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/))) 549 return Number(match[1]) >= 6; 550 else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i)) 551 return Number(match[1]) >= 20050901;
552 else if (match = navigator.userAgent.match(/AppleWebKit\/(\d+)/)) 553 return Number(match[1]) >= 525; 554 else 555 return null; 556 }; 557 558 return CodeMirror; 559})();
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.