diff options
Diffstat (limited to 'trunk/infrastructure/ace/www/ace2_inner.js')
-rw-r--r-- | trunk/infrastructure/ace/www/ace2_inner.js | 4817 |
1 files changed, 0 insertions, 4817 deletions
diff --git a/trunk/infrastructure/ace/www/ace2_inner.js b/trunk/infrastructure/ace/www/ace2_inner.js deleted file mode 100644 index 8bd1096..0000000 --- a/trunk/infrastructure/ace/www/ace2_inner.js +++ /dev/null @@ -1,4817 +0,0 @@ -/** - * Copyright 2009 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -function OUTER(gscope) { - - var DEBUG=true;//$$ build script replaces the string "var DEBUG=true;//$$" with "var DEBUG=false;" - - var isSetUp = false; - - var THE_TAB = ' ';//4 - var MAX_LIST_LEVEL = 8; - - var LINE_NUMBER_PADDING_RIGHT = 4; - var LINE_NUMBER_PADDING_LEFT = 4; - var MIN_LINEDIV_WIDTH = 20; - var EDIT_BODY_PADDING_TOP = 8; - var EDIT_BODY_PADDING_LEFT = 8; - - var caughtErrors = []; - - var thisAuthor = ''; - - var disposed = false; - - var editorInfo = parent.editorInfo; - - var iframe = window.frameElement; - var outerWin = iframe.ace_outerWin; - iframe.ace_outerWin = null; // prevent IE 6 memory leak - var sideDiv = iframe.nextSibling; - var lineMetricsDiv = sideDiv.nextSibling; - var overlaysdiv = lineMetricsDiv.nextSibling; - initLineNumbers(); - - var outsideKeyDown = function(evt) {}; - var outsideKeyPress = function(evt) { return true; }; - var outsideNotifyDirty = function() {}; - - // selFocusAtStart -- determines whether the selection extends "backwards", so that the focus - // point (controlled with the arrow keys) is at the beginning; not supported in IE, though - // native IE selections have that behavior (which we try not to interfere with). - // Must be false if selection is collapsed! - var rep = { lines: newSkipList(), selStart: null, selEnd: null, selFocusAtStart: false, - alltext: "", alines: [], - apool: new AttribPool() }; - // lines, alltext, alines, and DOM are set up in setup() - if (undoModule.enabled) { - undoModule.apool = rep.apool; - } - - var root, doc; // set in setup() - - var isEditable = true; - var doesWrap = true; - var hasLineNumbers = true; - var isStyled = true; - - // space around the innermost iframe element - var iframePadLeft = MIN_LINEDIV_WIDTH + LINE_NUMBER_PADDING_RIGHT + EDIT_BODY_PADDING_LEFT; - var iframePadTop = EDIT_BODY_PADDING_TOP; - var iframePadBottom = 0, iframePadRight = 0; - - var console = (DEBUG && top.console); - if (! console) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", - "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; - console = {}; - for (var i = 0; i < names.length; ++i) - console[names[i]] = function() {}; - //console.error = function(str) { alert(str); }; - } - var PROFILER = window.PROFILER; - if (!PROFILER) { - PROFILER = function() { return {start:noop, mark:noop, literal:noop, end:noop, cancel:noop}; }; - } - function noop() {} - function identity(x) { return x; } - - // "dmesg" is for displaying messages in the in-page output pane - // visible when "?djs=1" is appended to the pad URL. It generally - // remains a no-op unless djs is enabled, but we make a habit of - // only calling it in error cases or while debugging. - var dmesg = noop; - window.dmesg = noop; - - var scheduler = parent; - - var textFace = 'monospace'; - var textSize = 12; - function textLineHeight() { return Math.round(textSize * 4/3); } - - var dynamicCSS = null; - function initDynamicCSS() { - dynamicCSS = makeCSSManager("dynamicsyntax"); - } - - var changesetTracker = makeChangesetTracker(scheduler, rep.apool, { - withCallbacks: function(operationName, f) { - inCallStackIfNecessary(operationName, function() { - fastIncorp(1); - f({ - setDocumentAttributedText: function(atext) { - setDocAText(atext); - }, - applyChangesetToDocument: function(changeset, preferInsertionAfterCaret) { - var oldEventType = currentCallStack.editEvent.eventType; - currentCallStack.startNewEvent("nonundoable"); - - performDocumentApplyChangeset(changeset, preferInsertionAfterCaret); - - currentCallStack.startNewEvent(oldEventType); - } - }); - }); - } - }); - - var authorInfos = {}; // presence of key determines if author is present in doc - - function setAuthorInfo(author, info) { - if ((typeof author) != "string") { - throw new Error("setAuthorInfo: author ("+author+") is not a string"); - } - if (! info) { - delete authorInfos[author]; - if (dynamicCSS) { - dynamicCSS.removeSelectorStyle(getAuthorColorClassSelector(getAuthorClassName(author))); - } - } - else { - authorInfos[author] = info; - if (info.bgcolor) { - if (dynamicCSS) { - var bgcolor = info.bgcolor; - if ((typeof info.fade) == "number") { - bgcolor = fadeColor(bgcolor, info.fade); - } - - dynamicCSS.selectorStyle(getAuthorColorClassSelector( - getAuthorClassName(author))).backgroundColor = bgcolor; - } - } - } - } - - function getAuthorClassName(author) { - return "author-"+author.replace(/[^a-y0-9]/g, function(c) { - if (c == ".") return "-"; - return 'z'+c.charCodeAt(0)+'z'; - }); - } - function className2Author(className) { - if (className.substring(0,7) == "author-") { - return className.substring(7).replace(/[a-y0-9]+|-|z.+?z/g, function(cc) { - if (cc == '-') return '.'; - else if (cc.charAt(0) == 'z') { - return String.fromCharCode(Number(cc.slice(1,-1))); - } - else { - return cc; - } - }); - } - return null; - } - function getAuthorColorClassSelector(oneClassName) { - return ".authorColors ."+oneClassName; - } - function setUpTrackingCSS() { - if (dynamicCSS) { - var backgroundHeight = lineMetricsDiv.offsetHeight; - var lineHeight = textLineHeight(); - var extraBodding = 0; - var extraTodding = 0; - if (backgroundHeight < lineHeight) { - extraBodding = Math.ceil((lineHeight - backgroundHeight)/2); - extraTodding = lineHeight - backgroundHeight - extraBodding; - } - var spanStyle = dynamicCSS.selectorStyle("#innerdocbody span"); - spanStyle.paddingTop = extraTodding+"px"; - spanStyle.paddingBottom = extraBodding+"px"; - } - } - function boldColorFromColor(lightColorCSS) { - var color = colorutils.css2triple(lightColorCSS); - - // amp up the saturation to full - color = colorutils.saturate(color); - - // normalize brightness based on luminosity - color = colorutils.scaleColor(color, 0, 0.5 / colorutils.luminosity(color)); - - return colorutils.triple2css(color); - } - function fadeColor(colorCSS, fadeFrac) { - var color = colorutils.css2triple(colorCSS); - color = colorutils.blend(color, [1,1,1], fadeFrac); - return colorutils.triple2css(color); - } - - function doAlert(str) { - scheduler.setTimeout(function() { alert(str); }, 0); - } - - var currentCallStack = null; - function inCallStack(type, action) { - if (disposed) return; - - if (currentCallStack) { - console.error("Can't enter callstack "+type+", already in "+ - currentCallStack.type); - } - - var profiling = false; - function profileRest() { - profiling = true; - console.profile(); - } - - function newEditEvent(eventType) { - return {eventType:eventType, backset: null}; - } - - function submitOldEvent(evt) { - if (rep.selStart && rep.selEnd) { - var selStartChar = - rep.lines.offsetOfIndex(rep.selStart[0]) + rep.selStart[1]; - var selEndChar = - rep.lines.offsetOfIndex(rep.selEnd[0]) + rep.selEnd[1]; - evt.selStart = selStartChar; - evt.selEnd = selEndChar; - evt.selFocusAtStart = rep.selFocusAtStart; - } - if (undoModule.enabled) { - var undoWorked = false; - try { - if (evt.eventType == "setup" || evt.eventType == "importText" || - evt.eventType == "setBaseText") { - undoModule.clearHistory(); - } - else if (evt.eventType == "nonundoable") { - if (evt.changeset) { - undoModule.reportExternalChange(evt.changeset); - } - } - else { - undoModule.reportEvent(evt); - } - undoWorked = true; - } - finally { - if (! undoWorked) { - undoModule.enabled = false; // for safety - } - } - } - } - - function startNewEvent(eventType, dontSubmitOld) { - var oldEvent = currentCallStack.editEvent; - if (! dontSubmitOld) { - submitOldEvent(oldEvent); - } - currentCallStack.editEvent = newEditEvent(eventType); - return oldEvent; - } - - currentCallStack = {type: type, docTextChanged: false, selectionAffected: false, - userChangedSelection: false, - domClean: false, profileRest:profileRest, - isUserChange: false, // is this a "user change" type of call-stack - repChanged: false, editEvent: newEditEvent(type), - startNewEvent:startNewEvent}; - var cleanExit = false; - var result; - try { - result = action(); - //console.log("Just did action for: "+type); - cleanExit = true; - } - catch (e) { - caughtErrors.push({error: e, time: +new Date()}); - dmesg(e.toString()); - throw e; - } - finally { - var cs = currentCallStack; - //console.log("Finished action for: "+type); - if (cleanExit) { - submitOldEvent(cs.editEvent); - if (cs.domClean && cs.type != "setup") { - if (cs.isUserChange) { - if (cs.repChanged) parenModule.notifyChange(); - else parenModule.notifyTick(); - } - recolorModule.recolorLines(); - if (cs.selectionAffected) { - updateBrowserSelectionFromRep(); - } - if ((cs.docTextChanged || cs.userChangedSelection) && cs.type != "applyChangesToBase") { - scrollSelectionIntoView(); - } - if (cs.docTextChanged && cs.type.indexOf("importText") < 0) { - outsideNotifyDirty(); - } - } - } - else { - // non-clean exit - if (currentCallStack.type == "idleWorkTimer") { - idleWorkTimer.atLeast(1000); - } - } - currentCallStack = null; - if (profiling) console.profileEnd(); - } - return result; - } - - function inCallStackIfNecessary(type, action) { - if (! currentCallStack) { - inCallStack(type, action); - } - else { - action(); - } - } - - function recolorLineByKey(key) { - if (rep.lines.containsKey(key)) { - var offset = rep.lines.offsetOfKey(key); - var width = rep.lines.atKey(key).width; - recolorLinesInRange(offset, offset + width); - } - } - - function getLineKeyForOffset(charOffset) { - return rep.lines.atOffset(charOffset).key; - } - - var recolorModule = (function() { - var dirtyLineKeys = {}; - - var module = {}; - module.setCharNeedsRecoloring = function(offset) { - if (offset >= rep.alltext.length) { - offset = rep.alltext.length-1; - } - dirtyLineKeys[getLineKeyForOffset(offset)] = true; - } - - module.setCharRangeNeedsRecoloring = function(offset1, offset2) { - if (offset1 >= rep.alltext.length) { - offset1 = rep.alltext.length-1; - } - if (offset2 >= rep.alltext.length) { - offset2 = rep.alltext.length-1; - } - var firstEntry = rep.lines.atOffset(offset1); - var lastKey = rep.lines.atOffset(offset2).key; - dirtyLineKeys[lastKey] = true; - var entry = firstEntry; - while (entry && entry.key != lastKey) { - dirtyLineKeys[entry.key] = true; - entry = rep.lines.next(entry); - } - } - - module.recolorLines = function() { - for(var k in dirtyLineKeys) { - recolorLineByKey(k); - } - dirtyLineKeys = {}; - } - - return module; - })(); - - var parenModule = (function() { - var module = {}; - module.notifyTick = function() { handleFlashing(false); }; - module.notifyChange = function() { handleFlashing(true); }; - module.shouldNormalizeOnChar = function (c) { - if (parenFlashRep.active) { - // avoid highlight style from carrying on to typed text - return true; - } - c = String.fromCharCode(c); - return !! (bracketMap[c]); - } - - var parenFlashRep = { active: false, whichChars: null, whichLineKeys: null, expireTime: null }; - var bracketMap = {'(': 1, ')':-1, '[':2, ']':-2, '{':3, '}':-3}; - var bracketRegex = /[{}\[\]()]/g; - function handleFlashing(docChanged) { - function getSearchRange(aroundLoc) { - var rng = getVisibleCharRange(); - var d = 100; // minimum radius - var e = 3000; // maximum radius; - if (rng[0] > aroundLoc-d) rng[0] = aroundLoc-d; - if (rng[0] < aroundLoc-e) rng[0] = aroundLoc-e; - if (rng[0] < 0) rng[0] = 0; - if (rng[1] < aroundLoc+d) rng[1] = aroundLoc+d; - if (rng[1] > aroundLoc+e) rng[1] = aroundLoc+e; - if (rng[1] > rep.lines.totalWidth()) rng[1] = rep.lines.totalWidth(); - return rng; - } - function findMatchingVisibleBracket(startLoc, forwards) { - var rng = getSearchRange(startLoc); - var str = rep.alltext.substring(rng[0], rng[1]); - var bstr = str.replace(bracketRegex, '('); // handy for searching - var loc = startLoc - rng[0]; - var bracketState = []; - var foundParen = false; - var goodParen = false; - function nextLoc() { - if (loc < 0) return; - if (forwards) loc++; else loc--; - if (loc < 0 || loc >= str.length) loc = -1; - if (loc >= 0) { - if (forwards) loc = bstr.indexOf('(', loc); - else loc = bstr.lastIndexOf('(', loc); - } - } - while ((! foundParen) && (loc >= 0)) { - if (getCharType(loc + rng[0]) == "p") { - var b = bracketMap[str.charAt(loc)]; // -1, 1, -2, 2, -3, 3 - var into = forwards; - var typ = b; - if (typ < 0) { into = ! into; typ = -typ; } - if (into) bracketState.push(typ); - else { - var recent = bracketState.pop(); - if (recent != typ) { - foundParen = true; goodParen = false; - } - else if (bracketState.length == 0) { - foundParen = true; goodParen = true; - } - } - } - //console.log(bracketState.toSource()); - if ((! foundParen) && (loc >= 0)) nextLoc(); - } - if (! foundParen) return null; - return {chr: (loc + rng[0]), good: goodParen}; - } - - var r = parenFlashRep; - var charsToHighlight = null; - var linesToUnhighlight = null; - if (r.active && (docChanged || (now() > r.expireTime))) { - linesToUnhighlight = r.whichLineKeys; - r.active = false; - } - if ((! r.active) && docChanged && isCaret() && caretColumn() > 0) { - var caret = caretDocChar(); - if (caret > 0 && getCharType(caret-1) == "p") { - var charBefore = rep.alltext.charAt(caret-1); - if (bracketMap[charBefore]) { - var lookForwards = (bracketMap[charBefore] > 0); - var findResult = findMatchingVisibleBracket(caret-1, lookForwards); - if (findResult) { - var mateLoc = findResult.chr; - var mateGood = findResult.good; - r.active = true; - charsToHighlight = {}; - charsToHighlight[caret-1] = 'flash'; - charsToHighlight[mateLoc] = (mateGood ? 'flash' : 'flashbad'); - r.whichLineKeys = []; - r.whichLineKeys.push(getLineKeyForOffset(caret-1)); - r.whichLineKeys.push(getLineKeyForOffset(mateLoc)); - r.expireTime = now() + 4000; - newlyActive = true; - } - } - } - - } - if (linesToUnhighlight) { - recolorLineByKey(linesToUnhighlight[0]); - recolorLineByKey(linesToUnhighlight[1]); - } - if (r.active && charsToHighlight) { - function f(txt, cls, next, ofst) { - var flashClass = charsToHighlight[ofst]; - if (cls) { - next(txt, cls+" "+flashClass); - } - else next(txt, cls); - } - for(var c in charsToHighlight) { - recolorLinesInRange((+c), (+c)+1, null, f); - } - } - } - - return module; - })(); - - function dispose() { - disposed = true; - if (idleWorkTimer) idleWorkTimer.never(); - teardown(); - } - - function checkALines() { - return; // disable for speed - function error() { throw new Error("checkALines"); } - if (rep.alines.length != rep.lines.length()) { - error(); - } - for(var i=0;i<rep.alines.length;i++) { - var aline = rep.alines[i]; - var lineText = rep.lines.atIndex(i).text+"\n"; - var lineTextLength = lineText.length; - var opIter = Changeset.opIterator(aline); - var alineLength = 0; - while (opIter.hasNext()) { - var o = opIter.next(); - alineLength += o.chars; - if (opIter.hasNext()) { - if (o.lines != 0) error(); - } - else { - if (o.lines != 1) error(); - } - } - if (alineLength != lineTextLength) { - error(); - } - } - } - - function setWraps(newVal) { - doesWrap = newVal; - var dwClass = "doesWrap"; - setClassPresence(root, "doesWrap", doesWrap); - scheduler.setTimeout(function() { - inCallStackIfNecessary("setWraps", function() { - fastIncorp(7); - recreateDOM(); - fixView(); - }); - }, 0); - } - - function setStyled(newVal) { - var oldVal = isStyled; - isStyled = !!newVal; - - if (newVal != oldVal) { - if (! newVal) { - // clear styles - inCallStackIfNecessary("setStyled", function() { - fastIncorp(12); - var clearStyles = []; - for(var k in STYLE_ATTRIBS) { - clearStyles.push([k,'']); - } - performDocumentApplyAttributesToCharRange(0, rep.alltext.length, clearStyles); - }); - } - } - } - - function setTextFace(face) { - textFace = face; - root.style.fontFamily = textFace; - lineMetricsDiv.style.fontFamily = textFace; - scheduler.setTimeout(function() { - setUpTrackingCSS(); - }, 0); - } - - function setTextSize(size) { - textSize = size; - root.style.fontSize = textSize+"px"; - root.style.lineHeight = textLineHeight()+"px"; - sideDiv.style.lineHeight = textLineHeight()+"px"; - lineMetricsDiv.style.fontSize = textSize+"px"; - scheduler.setTimeout(function() { - setUpTrackingCSS(); - }, 0); - } - - function recreateDOM() { - // precond: normalized - recolorLinesInRange(0, rep.alltext.length); - } - - function setEditable(newVal) { - isEditable = newVal; - - // the following may fail, e.g. if iframe is hidden - if (! isEditable) { - setDesignMode(false); - } - else { - setDesignMode(true); - } - setClassPresence(root, "static", ! isEditable); - } - - function enforceEditability() { - setEditable(isEditable); - } - - function importText(text, undoable, dontProcess) { - var lines; - if (dontProcess) { - if (text.charAt(text.length-1) != "\n") { - throw new Error("new raw text must end with newline"); - } - if (/[\r\t\xa0]/.exec(text)) { - throw new Error("new raw text must not contain CR, tab, or nbsp"); - } - lines = text.substring(0, text.length-1).split('\n'); - } - else { - lines = map(text.split('\n'), textify); - } - var newText = "\n"; - if (lines.length > 0) { - newText = lines.join('\n')+'\n'; - } - - inCallStackIfNecessary("importText"+(undoable?"Undoable":""), function() { - setDocText(newText); - }); - - if (dontProcess && rep.alltext != text) { - throw new Error("mismatch error setting raw text in importText"); - } - } - - function importAText(atext, apoolJsonObj, undoable) { - atext = Changeset.cloneAText(atext); - if (apoolJsonObj) { - var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj); - atext.attribs = Changeset.moveOpsToNewPool(atext.attribs, wireApool, rep.apool); - } - inCallStackIfNecessary("importText"+(undoable?"Undoable":""), function() { - setDocAText(atext); - }); - } - - function setDocAText(atext) { - fastIncorp(8); - - var oldLen = rep.lines.totalWidth(); - var numLines = rep.lines.length(); - var upToLastLine = rep.lines.offsetOfIndex(numLines-1); - var lastLineLength = rep.lines.atIndex(numLines-1).text.length; - var assem = Changeset.smartOpAssembler(); - var o = Changeset.newOp('-'); - o.chars = upToLastLine; - o.lines = numLines-1; - assem.append(o); - o.chars = lastLineLength; - o.lines = 0; - assem.append(o); - Changeset.appendATextToAssembler(atext, assem); - var newLen = oldLen + assem.getLengthChange(); - var changeset = Changeset.checkRep( - Changeset.pack(oldLen, newLen, assem.toString(), - atext.text.slice(0, -1))); - performDocumentApplyChangeset(changeset); - - performSelectionChange([0,rep.lines.atIndex(0).lineMarker], - [0,rep.lines.atIndex(0).lineMarker]); - - idleWorkTimer.atMost(100); - - if (rep.alltext != atext.text) { - dmesg(htmlPrettyEscape(rep.alltext)); - dmesg(htmlPrettyEscape(atext.text)); - throw new Error("mismatch error setting raw text in setDocAText"); - } - } - - function setDocText(text) { - setDocAText(Changeset.makeAText(text)); - } - - function getDocText() { - var alltext = rep.alltext; - var len = alltext.length; - if (len > 0) len--; // final extra newline - return alltext.substring(0, len); - } - - function exportText() { - if (currentCallStack && ! currentCallStack.domClean) { - inCallStackIfNecessary("exportText", function() { fastIncorp(2); }); - } - return getDocText(); - } - - function editorChangedSize() { - fixView(); - } - - function setOnKeyPress(handler) { - outsideKeyPress = handler; - } - - function setOnKeyDown(handler) { - outsideKeyDown = handler; - } - - function setNotifyDirty(handler) { - outsideNotifyDirty = handler; - } - - function getFormattedCode() { - if (currentCallStack && ! currentCallStack.domClean) { - inCallStackIfNecessary("getFormattedCode", incorporateUserChanges); - } - var buf = []; - if (rep.lines.length() > 0) { - // should be the case, even for empty file - var entry = rep.lines.atIndex(0); - while (entry) { - var domInfo = entry.domInfo; - buf.push((domInfo && domInfo.getInnerHTML()) || - domline.processSpaces(domline.escapeHTML(entry.text), - doesWrap) || - ' ' /*empty line*/); - entry = rep.lines.next(entry); - } - } - return '<div class="syntax"><div>'+buf.join('</div>\n<div>')+ - '</div></div>'; - } - - var CMDS = { - bold: function() { toggleAttributeOnSelection('bold'); }, - italic: function() { toggleAttributeOnSelection('italic'); }, - underline: function() { toggleAttributeOnSelection('underline'); }, - strikethrough: function() { toggleAttributeOnSelection('strikethrough'); }, - h1: function() { toggleAttributeOnSelection('h1'); }, - h2: function() { toggleAttributeOnSelection('h2'); }, - h3: function() { toggleAttributeOnSelection('h3'); }, - h4: function() { toggleAttributeOnSelection('h4'); }, - h5: function() { toggleAttributeOnSelection('h5'); }, - h6: function() { toggleAttributeOnSelection('h6'); }, - undo: function() { doUndoRedo('undo'); }, - redo: function() { doUndoRedo('redo'); }, - clearauthorship: function(prompt) { - if ((!(rep.selStart && rep.selEnd)) || isCaret()) { - if (prompt) { - prompt(); - } - else { - performDocumentApplyAttributesToCharRange(0, rep.alltext.length, - [['author', '']]); - } - } - else { - setAttributeOnSelection('author', ''); - } - }, - insertunorderedlist: doInsertUnorderedList, - indent: function() { - if (! doIndentOutdent(false)) { - doInsertUnorderedList(); - } - }, - outdent: function() { doIndentOutdent(true); } - }; - - function execCommand(cmd) { - cmd = cmd.toLowerCase(); - var cmdArgs = Array.prototype.slice.call(arguments, 1); - if (CMDS[cmd]) { - inCallStack(cmd, function() { - fastIncorp(9); - CMDS[cmd].apply(CMDS, cmdArgs); - }); - } - } - - editorInfo.ace_focus = focus; - editorInfo.ace_importText = importText; - editorInfo.ace_importAText = importAText; - editorInfo.ace_exportText = exportText; - editorInfo.ace_editorChangedSize = editorChangedSize; - editorInfo.ace_setOnKeyPress = setOnKeyPress; - editorInfo.ace_setOnKeyDown = setOnKeyDown; - editorInfo.ace_setNotifyDirty = setNotifyDirty; - editorInfo.ace_dispose = dispose; - editorInfo.ace_getFormattedCode = getFormattedCode; - editorInfo.ace_setEditable = setEditable; - editorInfo.ace_execCommand = execCommand; - - editorInfo.ace_setProperty = function(key, value) { - var k = key.toLowerCase(); - if (k == "wraps") { - setWraps(value); - } - else if (k == "showsauthorcolors") { - setClassPresence(root, "authorColors", !!value); - } - else if (k == "showsuserselections") { - setClassPresence(root, "userSelections", !!value); - } - else if (k == "showslinenumbers") { - hasLineNumbers = !!value; - setClassPresence(sideDiv, "sidedivhidden", ! hasLineNumbers); - fixView(); - } - else if (k == "grayedout") { - setClassPresence(outerWin.document.body, "grayedout", !!value); - } - else if (k == "dmesg") { - dmesg = value; - window.dmesg = value; - } - else if (k == 'userauthor') { - thisAuthor = String(value); - } - else if (k == 'styled') { - setStyled(value); - } - else if (k == 'textface') { - setTextFace(value); - } - else if (k == 'textsize') { - setTextSize(value); - } - } - - editorInfo.ace_setBaseText = function(txt) { - changesetTracker.setBaseText(txt); - }; - editorInfo.ace_setBaseAttributedText = function(atxt, apoolJsonObj) { - setUpTrackingCSS(); - changesetTracker.setBaseAttributedText(atxt, apoolJsonObj); - }; - editorInfo.ace_applyChangesToBase = function(c, optAuthor, apoolJsonObj) { - changesetTracker.applyChangesToBase(c, optAuthor, apoolJsonObj); - }; - editorInfo.ace_prepareUserChangeset = function() { - return changesetTracker.prepareUserChangeset(); - }; - editorInfo.ace_applyPreparedChangesetToBase = function() { - changesetTracker.applyPreparedChangesetToBase(); - }; - editorInfo.ace_setUserChangeNotificationCallback = function(f) { - changesetTracker.setUserChangeNotificationCallback(f); - }; - editorInfo.ace_setAuthorInfo = function(author, info) { - setAuthorInfo(author, info); - }; - editorInfo.ace_setAuthorSelectionRange = function(author, start, end) { - changesetTracker.setAuthorSelectionRange(author, start, end); - }; - - editorInfo.ace_getUnhandledErrors = function() { - return caughtErrors.slice(); - }; - - editorInfo.ace_getDebugProperty = function(prop) { - if (prop == "debugger") { - // obfuscate "eval" so as not to scare yuicompressor - window['ev'+'al']("debugger"); - } - else if (prop == "rep") { - return rep; - } - else if (prop == "window") { - return window; - } - else if (prop == "document") { - return document; - } - return undefined; - }; - - function now() { return (new Date()).getTime(); } - - function newTimeLimit(ms) { - //console.debug("new time limit"); - var startTime = now(); - var lastElapsed = 0; - var exceededAlready = false; - var printedTrace = false; - var isTimeUp = function () { - if (exceededAlready) { - if ((! printedTrace)) {// && now() - startTime - ms > 300) { - //console.trace(); - printedTrace = true; - } - return true; - } - var elapsed = now() - startTime; - if (elapsed > ms) { - exceededAlready = true; - //console.debug("time limit hit, before was %d/%d", lastElapsed, ms); - //console.trace(); - return true; - } - else { - lastElapsed = elapsed; - return false; - } - } - isTimeUp.elapsed = function() { return now() - startTime; } - return isTimeUp; - } - - - function makeIdleAction(func) { - var scheduledTimeout = null; - var scheduledTime = 0; - function unschedule() { - if (scheduledTimeout) { - scheduler.clearTimeout(scheduledTimeout); - scheduledTimeout = null; - } - } - function reschedule(time) { - unschedule(); - scheduledTime = time; - var delay = time - now(); - if (delay < 0) delay = 0; - scheduledTimeout = scheduler.setTimeout(callback, delay); - } - function callback() { - scheduledTimeout = null; - // func may reschedule the action - func(); - } - return { - atMost: function (ms) { - var latestTime = now() + ms; - if ((! scheduledTimeout) || scheduledTime > latestTime) { - reschedule(latestTime); - } - }, - // atLeast(ms) will schedule the action if not scheduled yet. - // In other words, "infinity" is replaced by ms, even though - // it is technically larger. - atLeast: function (ms) { - var earliestTime = now()+ms; - if ((! scheduledTimeout) || scheduledTime < earliestTime) { - reschedule(earliestTime); - } - }, - never: function() { - unschedule(); - } - } - } - - function fastIncorp(n) { - // normalize but don't do any lexing or anything - incorporateUserChanges(newTimeLimit(0)); - } - - function incorpIfQuick() { - var me = incorpIfQuick; - var failures = (me.failures || 0); - if (failures < 5) { - var isTimeUp = newTimeLimit(40); - var madeChanges = incorporateUserChanges(isTimeUp); - if (isTimeUp()) { - me.failures = failures+1; - } - return true; - } - else { - var skipCount = (me.skipCount || 0); - skipCount++; - if (skipCount == 20) { - skipCount = 0; - me.failures = 0; - } - me.skipCount = skipCount; - } - return false; - } - - var idleWorkTimer = makeIdleAction(function() { - - //if (! top.BEFORE) top.BEFORE = []; - //top.BEFORE.push(magicdom.root.dom.innerHTML); - - if (! isEditable) return; // and don't reschedule - - if (inInternationalComposition) { - // don't do idle input incorporation during international input composition - idleWorkTimer.atLeast(500); - return; - } - - inCallStack("idleWorkTimer", function() { - - var isTimeUp = newTimeLimit(250); - - //console.time("idlework"); - - var finishedImportantWork = false; - var finishedWork = false; - - try { - - // isTimeUp() is a soft constraint for incorporateUserChanges, - // which always renormalizes the DOM, no matter how long it takes, - // but doesn't necessarily lex and highlight it - incorporateUserChanges(isTimeUp); - - if (isTimeUp()) return; - - updateLineNumbers(); // update line numbers if any time left - - if (isTimeUp()) return; - - var visibleRange = getVisibleCharRange(); - var docRange = [0, rep.lines.totalWidth()]; - //console.log("%o %o", docRange, visibleRange); - - finishedImportantWork = true; - finishedWork = true; - } - finally { - //console.timeEnd("idlework"); - if (finishedWork) { - idleWorkTimer.atMost(1000); - } - else if (finishedImportantWork) { - // if we've finished highlighting the view area, - // more highlighting could be counter-productive, - // e.g. if the user just opened a triple-quote and will soon close it. - idleWorkTimer.atMost(500); - } - else { - var timeToWait = Math.round(isTimeUp.elapsed() / 2); - if (timeToWait < 100) timeToWait = 100; - idleWorkTimer.atMost(timeToWait); - } - } - }); - - //if (! top.AFTER) top.AFTER = []; - //top.AFTER.push(magicdom.root.dom.innerHTML); - - }); - - var _nextId = 1; - function uniqueId(n) { - // not actually guaranteed to be unique, e.g. if user copy-pastes - // nodes with ids - var nid = n.id; - if (nid) return nid; - return (n.id = "magicdomid"+(_nextId++)); - } - - - function recolorLinesInRange(startChar, endChar, isTimeUp, optModFunc) { - if (endChar <= startChar) return; - if (startChar < 0 || startChar >= rep.lines.totalWidth()) return; - var lineEntry = rep.lines.atOffset(startChar); // rounds down to line boundary - var lineStart = rep.lines.offsetOfEntry(lineEntry); - var lineIndex = rep.lines.indexOfEntry(lineEntry); - var selectionNeedsResetting = false; - var firstLine = null; - var lastLine = null; - isTimeUp = (isTimeUp || noop); - - // tokenFunc function; accesses current value of lineEntry and curDocChar, - // also mutates curDocChar - var curDocChar; - var tokenFunc = function(tokenText, tokenClass) { - lineEntry.domInfo.appendSpan(tokenText, tokenClass); - }; - if (optModFunc) { - var f = tokenFunc; - tokenFunc = function(tokenText, tokenClass) { - optModFunc(tokenText, tokenClass, f, curDocChar); - curDocChar += tokenText.length; - }; - } - - while (lineEntry && lineStart < endChar && ! isTimeUp()) { - //var timer = newTimeLimit(200); - var lineEnd = lineStart + lineEntry.width; - - curDocChar = lineStart; - lineEntry.domInfo.clearSpans(); - getSpansForLine(lineEntry, tokenFunc, lineStart); - lineEntry.domInfo.finishUpdate(); - - markNodeClean(lineEntry.lineNode); - - if (rep.selStart && rep.selStart[0] == lineIndex || - rep.selEnd && rep.selEnd[0] == lineIndex) { - selectionNeedsResetting = true; - } - - //if (timer()) console.dirxml(lineEntry.lineNode.dom); - - if (firstLine === null) firstLine = lineIndex; - lastLine = lineIndex; - lineStart = lineEnd; - lineEntry = rep.lines.next(lineEntry); - lineIndex++; - } - if (selectionNeedsResetting) { - currentCallStack.selectionAffected = true; - } - //console.debug("Recolored line range %d-%d", firstLine, lastLine); - } - - // like getSpansForRange, but for a line, and the func takes (text,class) - // instead of (width,class); excludes the trailing '\n' from - // consideration by func - function getSpansForLine(lineEntry, textAndClassFunc, lineEntryOffsetHint) { - var lineEntryOffset = lineEntryOffsetHint; - if ((typeof lineEntryOffset) != "number") { - lineEntryOffset = rep.lines.offsetOfEntry(lineEntry); - } - var text = lineEntry.text; - var width = lineEntry.width; // text.length+1 - - if (text.length == 0) { - // allow getLineStyleFilter to set line-div styles - var func = linestylefilter.getLineStyleFilter( - 0, '', textAndClassFunc, rep.apool); - func('', ''); - } - else { - var offsetIntoLine = 0; - var filteredFunc = textAndClassFunc; - filteredFunc = linestylefilter.getURLFilter(text, filteredFunc); - if (browser.msie) { - // IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com. - // We then normalize it back to text with no angle brackets. It's weird. So always - // break spans at an "at" sign. - filteredFunc = linestylefilter.getAtSignSplitterFilter( - text, filteredFunc); - } - var lineNum = rep.lines.indexOfEntry(lineEntry); - var aline = rep.alines[lineNum]; - filteredFunc = linestylefilter.getLineStyleFilter( - text.length, aline, filteredFunc, rep.apool); - filteredFunc(text, ''); - } - } - - - function getCharType(charIndex) { - return ''; - } - - var observedChanges; - function clearObservedChanges() { - observedChanges = { cleanNodesNearChanges: {} }; - } - clearObservedChanges(); - - function getCleanNodeByKey(key) { - var p = PROFILER("getCleanNodeByKey", false); - p.extra = 0; - var n = doc.getElementById(key); - // copying and pasting can lead to duplicate ids - while (n && isNodeDirty(n)) { - p.extra++; - n.id = ""; - n = doc.getElementById(key); - } - p.literal(p.extra, "extra"); - p.end(); - return n; - } - - function observeChangesAroundNode(node) { - // Around this top-level DOM node, look for changes to the document - // (from how it looks in our representation) and record them in a way - // that can be used to "normalize" the document (apply the changes to our - // representation, and put the DOM in a canonical form). - - //top.console.log("observeChangesAroundNode(%o)", node); - - var cleanNode; - var hasAdjacentDirtyness; - if (! isNodeDirty(node)) { - cleanNode = node; - var prevSib = cleanNode.previousSibling; - var nextSib = cleanNode.nextSibling; - hasAdjacentDirtyness = ((prevSib && isNodeDirty(prevSib)) - || (nextSib && isNodeDirty(nextSib))); - } - else { - // node is dirty, look for clean node above - var upNode = node.previousSibling; - while (upNode && isNodeDirty(upNode)) { - upNode = upNode.previousSibling; - } - if (upNode) { - cleanNode = upNode; - } - else { - var downNode = node.nextSibling; - while (downNode && isNodeDirty(downNode)) { - downNode = downNode.nextSibling; - } - if (downNode) { - cleanNode = downNode; - } - } - if (! cleanNode) { - // Couldn't find any adjacent clean nodes! - // Since top and bottom of doc is dirty, the dirty area will be detected. - return; - } - hasAdjacentDirtyness = true; - } - - if (hasAdjacentDirtyness) { - // previous or next line is dirty - observedChanges.cleanNodesNearChanges['$'+uniqueId(cleanNode)] = true; - } - else { - // next and prev lines are clean (if they exist) - var lineKey = uniqueId(cleanNode); - var prevSib = cleanNode.previousSibling; - var nextSib = cleanNode.nextSibling; - var actualPrevKey = ((prevSib && uniqueId(prevSib)) || null); - var actualNextKey = ((nextSib && uniqueId(nextSib)) || null); - var repPrevEntry = rep.lines.prev(rep.lines.atKey(lineKey)); - var repNextEntry = rep.lines.next(rep.lines.atKey(lineKey)); - var repPrevKey = ((repPrevEntry && repPrevEntry.key) || null); - var repNextKey = ((repNextEntry && repNextEntry.key) || null); - if (actualPrevKey != repPrevKey || actualNextKey != repNextKey) { - observedChanges.cleanNodesNearChanges['$'+uniqueId(cleanNode)] = true; - } - } - } - - function observeChangesAroundSelection() { - if (currentCallStack.observedSelection) return; - currentCallStack.observedSelection = true; - - var p = PROFILER("getSelection", false); - var selection = getSelection(); - p.end(); - if (selection) { - function topLevel(n) { - if ((!n) || n == root) return null; - while (n.parentNode != root) { - n = n.parentNode; - } - return n; - } - var node1 = topLevel(selection.startPoint.node); - var node2 = topLevel(selection.endPoint.node); - if (node1) observeChangesAroundNode(node1); - if (node2 && node1 != node2) { - observeChangesAroundNode(node2); - } - } - } - - function observeSuspiciousNodes() { - // inspired by Firefox bug #473255, where pasting formatted text - // causes the cursor to jump away, making the new HTML never found. - if (root.getElementsByTagName) { - var nds = root.getElementsByTagName("style"); - for(var i=0;i<nds.length;i++) { - var n = nds[i]; - while (n.parentNode && n.parentNode != root) { - n = n.parentNode; - } - if (n.parentNode == root) { - observeChangesAroundNode(n); - } - } - } - } - - function incorporateUserChanges(isTimeUp) { - - if (currentCallStack.domClean) return false; - - inInternationalComposition = false; // if we need the document normalized, so be it - - currentCallStack.isUserChange = true; - - isTimeUp = (isTimeUp || function() { return false; }); - - if (DEBUG && top.DONT_INCORP || window.DEBUG_DONT_INCORP) return false; - - var p = PROFILER("incorp", false); - - //if (doc.body.innerHTML.indexOf("AppJet") >= 0) - //dmesg(htmlPrettyEscape(doc.body.innerHTML)); - //if (top.RECORD) top.RECORD.push(doc.body.innerHTML); - - // returns true if dom changes were made - - if (! root.firstChild) { - root.innerHTML = "<div><!-- --></div>"; - } - - p.mark("obs"); - observeChangesAroundSelection(); - observeSuspiciousNodes(); - p.mark("dirty"); - var dirtyRanges = getDirtyRanges(); - //console.log("dirtyRanges: "+toSource(dirtyRanges)); - - var dirtyRangesCheckOut = true; - var j = 0; - var a,b; - while (j < dirtyRanges.length) { - a = dirtyRanges[j][0]; - b = dirtyRanges[j][1]; - if (! ((a == 0 || getCleanNodeByKey(rep.lines.atIndex(a-1).key)) && - (b == rep.lines.length() || getCleanNodeByKey(rep.lines.atIndex(b).key)))) { - dirtyRangesCheckOut = false; - break; - } - j++; - } - if (! dirtyRangesCheckOut) { - var numBodyNodes = root.childNodes.length; - for(var k=0;k<numBodyNodes;k++) { - var bodyNode = root.childNodes.item(k); - if ((bodyNode.tagName) && ((! bodyNode.id) || (! rep.lines.containsKey(bodyNode.id)))) { - observeChangesAroundNode(bodyNode); - } - } - dirtyRanges = getDirtyRanges(); - } - - clearObservedChanges(); - - p.mark("getsel"); - var selection = getSelection(); - - //console.log(magicdom.root.dom.innerHTML); - //console.log("got selection: %o", selection); - var selStart, selEnd; // each one, if truthy, has [line,char] needed to set selection - - var i = 0; - var splicesToDo = []; - var netNumLinesChangeSoFar = 0; - var toDeleteAtEnd = []; - p.mark("ranges"); - p.literal(dirtyRanges.length, "numdirt"); - var domInsertsNeeded = []; // each entry is [nodeToInsertAfter, [info1, info2, ...]] - while (i < dirtyRanges.length) { - var range = dirtyRanges[i]; - a = range[0]; - b = range[1]; - var firstDirtyNode = (((a == 0) && root.firstChild) || - getCleanNodeByKey(rep.lines.atIndex(a-1).key).nextSibling); - firstDirtyNode = (firstDirtyNode && isNodeDirty(firstDirtyNode) && firstDirtyNode); - var lastDirtyNode = (((b == rep.lines.length()) && root.lastChild) || - getCleanNodeByKey(rep.lines.atIndex(b).key).previousSibling); - lastDirtyNode = (lastDirtyNode && isNodeDirty(lastDirtyNode) && lastDirtyNode); - if (firstDirtyNode && lastDirtyNode) { - var cc = makeContentCollector(isStyled, browser, rep.apool, null, - className2Author); - cc.notifySelection(selection); - var dirtyNodes = []; - for(var n = firstDirtyNode; n && ! (n.previousSibling && - n.previousSibling == lastDirtyNode); - n = n.nextSibling) { - if (browser.msie) { - // try to undo IE's pesky and overzealous linkification - try { n.createTextRange().execCommand("unlink", false, null); } - catch (e) {} - } - cc.collectContent(n); - dirtyNodes.push(n); - } - cc.notifyNextNode(lastDirtyNode.nextSibling); - var lines = cc.getLines(); - if ((lines.length <= 1 || lines[lines.length-1] !== "") - && lastDirtyNode.nextSibling) { - // dirty region doesn't currently end a line, even taking the following node - // (or lack of node) into account, so include the following clean node. - // It could be SPAN or a DIV; basically this is any case where the contentCollector - // decides it isn't done. - // Note that this clean node might need to be there for the next dirty range. - //console.log("inclusive of "+lastDirtyNode.next().dom.tagName); - b++; - var cleanLine = lastDirtyNode.nextSibling; - cc.collectContent(cleanLine); - toDeleteAtEnd.push(cleanLine); - cc.notifyNextNode(cleanLine.nextSibling); - } - - var ccData = cc.finish(); - var ss = ccData.selStart; - var se = ccData.selEnd; - lines = ccData.lines; - var lineAttribs = ccData.lineAttribs; - var linesWrapped = ccData.linesWrapped; - - if (linesWrapped > 0) { - doAlert("Editor warning: "+linesWrapped+" long line"+ - (linesWrapped == 1 ? " was" : "s were")+" hard-wrapped into "+ - ccData.numLinesAfter - +" lines."); - } - - if (ss[0] >= 0) selStart = [ss[0]+a+netNumLinesChangeSoFar, ss[1]]; - if (se[0] >= 0) selEnd = [se[0]+a+netNumLinesChangeSoFar, se[1]]; - - /*var oldLines = rep.alltext.substring(rep.lines.offsetOfIndex(a), - rep.lines.offsetOfIndex(b)); - var newLines = lines.join('\n')+'\n'; - dmesg("OLD: "+htmlPrettyEscape(oldLines)); - dmesg("NEW: "+htmlPrettyEscape(newLines));*/ - - var entries = []; - var nodeToAddAfter = lastDirtyNode; - var lineNodeInfos = new Array(lines.length); - for(var k=0;k<lines.length;k++) { - var lineString = lines[k]; - var newEntry = createDomLineEntry(lineString); - entries.push(newEntry); - lineNodeInfos[k] = newEntry.domInfo; - } - //var fragment = magicdom.wrapDom(document.createDocumentFragment()); - domInsertsNeeded.push([nodeToAddAfter, lineNodeInfos]); - forEach(dirtyNodes, function (n) { toDeleteAtEnd.push(n); }); - var spliceHints = {}; - if (selStart) spliceHints.selStart = selStart; - if (selEnd) spliceHints.selEnd = selEnd; - splicesToDo.push([a+netNumLinesChangeSoFar, b-a, entries, lineAttribs, spliceHints]); - netNumLinesChangeSoFar += (lines.length - (b-a)); - } - else if (b > a) { - splicesToDo.push([a+netNumLinesChangeSoFar, b-a, [], []]); - } - i++; - } - - var domChanges = (splicesToDo.length > 0); - - // update the representation - p.mark("splice"); - forEach(splicesToDo, function (splice) { - doIncorpLineSplice(splice[0], splice[1], splice[2], splice[3], splice[4]); - }); - - //p.mark("relex"); - //rep.lexer.lexCharRange(getVisibleCharRange(), function() { return false; }); - //var isTimeUp = newTimeLimit(100); - - // do DOM inserts - p.mark("insert"); - forEach(domInsertsNeeded, function (ins) { - insertDomLines(ins[0], ins[1], isTimeUp); - }); - - p.mark("del"); - // delete old dom nodes - forEach(toDeleteAtEnd, function (n) { - //var id = n.uniqueId(); - - // parent of n may not be "root" in IE due to non-tree-shaped DOM (wtf) - n.parentNode.removeChild(n); - - //dmesg(htmlPrettyEscape(htmlForRemovedChild(n))); - //console.log("removed: "+id); - }); - - p.mark("findsel"); - // if the nodes that define the selection weren't encountered during - // content collection, figure out where those nodes are now. - if (selection && !selStart) { - //if (domChanges) dmesg("selection not collected"); - selStart = getLineAndCharForPoint(selection.startPoint); - } - if (selection && !selEnd) { - selEnd = getLineAndCharForPoint(selection.endPoint); - } - - // selection from content collection can, in various ways, extend past final - // BR in firefox DOM, so cap the line - var numLines = rep.lines.length(); - if (selStart && selStart[0] >= numLines) { - selStart[0] = numLines-1; - selStart[1] = rep.lines.atIndex(selStart[0]).text.length; - } - if (selEnd && selEnd[0] >= numLines) { - selEnd[0] = numLines-1; - selEnd[1] = rep.lines.atIndex(selEnd[0]).text.length; - } - - p.mark("repsel"); - // update rep - repSelectionChange(selStart, selEnd, selection && selection.focusAtStart); - // update browser selection - p.mark("browsel"); - if (selection && (domChanges || isCaret())) { - // if no DOM changes (not this case), want to treat range selection delicately, - // e.g. in IE not lose which end of the selection is the focus/anchor; - // on the other hand, we may have just noticed a press of PageUp/PageDown - currentCallStack.selectionAffected = true; - } - - currentCallStack.domClean = true; - - p.mark("fixview"); - - fixView(); - - p.end("END"); - - return domChanges; - } - - function htmlForRemovedChild(n) { - var div = doc.createElement("DIV"); - div.appendChild(n); - return div.innerHTML; - } - - var STYLE_ATTRIBS = {bold: true, italic: true, underline: true, - strikethrough: true, h1: true, h2: true, - h3: true, h4: true, h5: true, h6: true, - list: true}; - var OTHER_INCORPED_ATTRIBS = {insertorder: true, author: true}; - - function isStyleAttribute(aname) { - return !! STYLE_ATTRIBS[aname]; - } - function isIncorpedAttribute(aname) { - return (!! STYLE_ATTRIBS[aname]) || (!! OTHER_INCORPED_ATTRIBS[aname]); - } - - function insertDomLines(nodeToAddAfter, infoStructs, isTimeUp) { - isTimeUp = (isTimeUp || function() { return false; }); - - var lastEntry; - var lineStartOffset; - if (infoStructs.length < 1) return; - var startEntry = rep.lines.atKey(uniqueId(infoStructs[0].node)); - var endEntry = rep.lines.atKey(uniqueId(infoStructs[infoStructs.length-1].node)); - var charStart = rep.lines.offsetOfEntry(startEntry); - var charEnd = rep.lines.offsetOfEntry(endEntry) + endEntry.width; - - //rep.lexer.lexCharRange([charStart, charEnd], isTimeUp); - - forEach(infoStructs, function (info) { - var p2 = PROFILER("insertLine", false); - var node = info.node; - var key = uniqueId(node); - var entry; - p2.mark("findEntry"); - if (lastEntry) { - // optimization to avoid recalculation - var next = rep.lines.next(lastEntry); - if (next && next.key == key) { - entry = next; - lineStartOffset += lastEntry.width; - } - } - if (! entry) { - p2.literal(1, "nonopt"); - entry = rep.lines.atKey(key); - lineStartOffset = rep.lines.offsetOfKey(key); - } - else p2.literal(0, "nonopt"); - lastEntry = entry; - p2.mark("spans"); - getSpansForLine(entry, function (tokenText, tokenClass) { - info.appendSpan(tokenText, tokenClass); - }, lineStartOffset, isTimeUp()); - //else if (entry.text.length > 0) { - //info.appendSpan(entry.text, 'dirty'); - //} - p2.mark("addLine"); - info.prepareForAdd(); - entry.lineMarker = info.lineMarker; - if (! nodeToAddAfter) { - root.insertBefore(node, root.firstChild); - } - else { - root.insertBefore(node, nodeToAddAfter.nextSibling); - } - nodeToAddAfter = node; - info.notifyAdded(); - p2.mark("markClean"); - markNodeClean(node); - p2.end(); - }); - } - - function isCaret() { - return (rep.selStart && rep.selEnd && rep.selStart[0] == rep.selEnd[0] && - rep.selStart[1] == rep.selEnd[1]); - } - - // prereq: isCaret() - function caretLine() { return rep.selStart[0]; } - function caretColumn() { return rep.selStart[1]; } - function caretDocChar() { - return rep.lines.offsetOfIndex(caretLine()) + caretColumn(); - } - - function handleReturnIndentation() { - // on return, indent to level of previous line - if (isCaret() && caretColumn() == 0 && caretLine() > 0) { - var lineNum = caretLine(); - var thisLine = rep.lines.atIndex(lineNum); - var prevLine = rep.lines.prev(thisLine); - var prevLineText = prevLine.text; - var theIndent = /^ *(?:)/.exec(prevLineText)[0]; - if (/[\[\(\{]\s*$/.exec(prevLineText)) theIndent += THE_TAB; - var cs = Changeset.builder(rep.lines.totalWidth()).keep( - rep.lines.offsetOfIndex(lineNum), lineNum).insert( - theIndent, [['author',thisAuthor]], rep.apool).toString(); - performDocumentApplyChangeset(cs); - performSelectionChange([lineNum, theIndent.length], [lineNum, theIndent.length]); - } - } - - - function setupMozillaCaretHack(lineNum) { - // This is really ugly, but by god, it works! - // Fixes annoying Firefox caret artifact (observed in 2.0.0.12 - // and unfixed in Firefox 2 as of now) where mutating the DOM - // and then moving the caret to the beginning of a line causes - // an image of the caret to be XORed at the top of the iframe. - // The previous solution involved remembering to set the selection - // later, in response to the next event in the queue, which was hugely - // annoying. - // This solution: add a space character (0x20) to the beginning of the line. - // After setting the selection, remove the space. - var lineNode = rep.lines.atIndex(lineNum).lineNode; - - var fc = lineNode.firstChild; - while (isBlockElement(fc) && fc.firstChild) { - fc = fc.firstChild; - } - var textNode; - if (isNodeText(fc)) { - fc.nodeValue = " "+fc.nodeValue; - textNode = fc; - } - else { - textNode = doc.createTextNode(" "); - fc.parentNode.insertBefore(textNode, fc); - } - markNodeClean(lineNode); - return { unhack: function() { - if (textNode.nodeValue == " ") { - textNode.parentNode.removeChild(textNode); - } - else { - textNode.nodeValue = textNode.nodeValue.substring(1); - } - markNodeClean(lineNode); - } }; - } - - - function getPointForLineAndChar(lineAndChar) { - var line = lineAndChar[0]; - var charsLeft = lineAndChar[1]; - //console.log("line: %d, key: %s, node: %o", line, rep.lines.atIndex(line).key, - //getCleanNodeByKey(rep.lines.atIndex(line).key)); - var lineEntry = rep.lines.atIndex(line); - charsLeft -= lineEntry.lineMarker; - if (charsLeft < 0) { - charsLeft = 0; - } - var lineNode = lineEntry.lineNode; - var n = lineNode; - var after = false; - if (charsLeft == 0) { - var index = 0; - if (browser.msie && line == (rep.lines.length()-1) && lineNode.childNodes.length == 0) { - // best to stay at end of last empty div in IE - index = 1; - } - return {node: lineNode, index:index, maxIndex:1}; - } - while (!(n == lineNode && after)) { - if (after) { - if (n.nextSibling) { - n = n.nextSibling; - after = false; - } - else n = n.parentNode; - } - else { - if (isNodeText(n)) { - var len = n.nodeValue.length; - if (charsLeft <= len) { - return {node: n, index:charsLeft, maxIndex:len}; - } - charsLeft -= len; - after = true; - } - else { - if (n.firstChild) n = n.firstChild; - else after = true; - } - } - } - return {node: lineNode, index:1, maxIndex:1}; - } - - function nodeText(n) { - return n.innerText || n.textContent || n.nodeValue || ''; - } - - function getLineAndCharForPoint(point) { - // Turn DOM node selection into [line,char] selection. - // This method has to work when the DOM is not pristine, - // assuming the point is not in a dirty node. - if (point.node == root) { - if (point.index == 0) { - return [0, 0]; - } - else { - var N = rep.lines.length(); - var ln = rep.lines.atIndex(N-1); - return [N-1, ln.text.length]; - } - } - else { - var n = point.node; - var col = 0; - // if this part fails, it probably means the selection node - // was dirty, and we didn't see it when collecting dirty nodes. - if (isNodeText(n)) { - col = point.index; - } - else if (point.index > 0) { - col = nodeText(n).length; - } - var parNode, prevSib; - while ((parNode = n.parentNode) != root) { - if ((prevSib = n.previousSibling)) { - n = prevSib; - col += nodeText(n).length; - } - else { - n = parNode; - } - } - if (n.id == "") console.debug("BAD"); - if (n.firstChild && isBlockElement(n.firstChild)) { - col += 1; // lineMarker - } - var lineEntry = rep.lines.atKey(n.id); - var lineNum = rep.lines.indexOfEntry(lineEntry); - return [lineNum, col]; - } - } - - function createDomLineEntry(lineString) { - var info = doCreateDomLine(lineString.length > 0); - var newNode = info.node; - return {key: uniqueId(newNode), text: lineString, lineNode: newNode, - domInfo: info, lineMarker: 0}; - } - - function canApplyChangesetToDocument(changes) { - return Changeset.oldLen(changes) == rep.alltext.length; - } - - function performDocumentApplyChangeset(changes, insertsAfterSelection) { - doRepApplyChangeset(changes, insertsAfterSelection); - - var requiredSelectionSetting = null; - if (rep.selStart && rep.selEnd) { - var selStartChar = rep.lines.offsetOfIndex(rep.selStart[0]) + rep.selStart[1]; - var selEndChar = rep.lines.offsetOfIndex(rep.selEnd[0]) + rep.selEnd[1]; - var result = Changeset.characterRangeFollow(changes, selStartChar, selEndChar, - insertsAfterSelection); - requiredSelectionSetting = [result[0], result[1], rep.selFocusAtStart]; - } - - var linesMutatee = { - splice: function(start, numRemoved, newLinesVA) { - domAndRepSplice(start, numRemoved, - map(Array.prototype.slice.call(arguments, 2), - function(s) { return s.slice(0,-1); }), - null); - }, - get: function(i) { return rep.lines.atIndex(i).text+'\n'; }, - length: function() { return rep.lines.length(); }, - slice_notused: function(start, end) { - return map(rep.lines.slice(start, end), function(e) { return e.text+'\n'; }); - } - }; - - Changeset.mutateTextLines(changes, linesMutatee); - - checkALines(); - - if (requiredSelectionSetting) { - performSelectionChange(lineAndColumnFromChar(requiredSelectionSetting[0]), - lineAndColumnFromChar(requiredSelectionSetting[1]), - requiredSelectionSetting[2]); - } - - function domAndRepSplice(startLine, deleteCount, newLineStrings, isTimeUp) { - // dgreensp 3/2009: the spliced lines may be in the middle of a dirty region, - // so if no explicit time limit, don't spend a lot of time highlighting - isTimeUp = (isTimeUp || newTimeLimit(50)); - - var keysToDelete = []; - if (deleteCount > 0) { - var entryToDelete = rep.lines.atIndex(startLine); - for(var i=0;i<deleteCount;i++) { - keysToDelete.push(entryToDelete.key); - entryToDelete = rep.lines.next(entryToDelete); - } - } - - var lineEntries = map(newLineStrings, createDomLineEntry); - - doRepLineSplice(startLine, deleteCount, lineEntries); - - var nodeToAddAfter; - if (startLine > 0) { - nodeToAddAfter = getCleanNodeByKey(rep.lines.atIndex(startLine-1).key); - } - else nodeToAddAfter = null; - - insertDomLines(nodeToAddAfter, map(lineEntries, function (entry) { return entry.domInfo; }), - isTimeUp); - - forEach(keysToDelete, function (k) { - var n = doc.getElementById(k); - n.parentNode.removeChild(n); - }); - - if ((rep.selStart && rep.selStart[0] >= startLine && rep.selStart[0] <= startLine+deleteCount) || - (rep.selEnd && rep.selEnd[0] >= startLine && rep.selEnd[0] <= startLine+deleteCount)) { - currentCallStack.selectionAffected = true; - } - } - } - - function checkChangesetLineInformationAgainstRep(changes) { - return true; // disable for speed - var opIter = Changeset.opIterator(Changeset.unpack(changes).ops); - var curOffset = 0; - var curLine = 0; - var curCol = 0; - while (opIter.hasNext()) { - var o = opIter.next(); - if (o.opcode == '-' || o.opcode == '=') { - curOffset += o.chars; - if (o.lines) { - curLine += o.lines; - curCol = 0; - } - else { - curCol += o.chars; - } - } - var calcLine = rep.lines.indexOfOffset(curOffset); - var calcLineStart = rep.lines.offsetOfIndex(calcLine); - var calcCol = curOffset - calcLineStart; - if (calcCol != curCol || calcLine != curLine) { - return false; - } - } - return true; - } - - function doRepApplyChangeset(changes, insertsAfterSelection) { - Changeset.checkRep(changes); - - if (Changeset.oldLen(changes) != rep.alltext.length) - throw new Error("doRepApplyChangeset length mismatch: "+ - Changeset.oldLen(changes)+"/"+rep.alltext.length); - - if (! checkChangesetLineInformationAgainstRep(changes)) { - throw new Error("doRepApplyChangeset line break mismatch"); - } - - (function doRecordUndoInformation(changes) { - var editEvent = currentCallStack.editEvent; - if (editEvent.eventType == "nonundoable") { - if (! editEvent.changeset) { - editEvent.changeset = changes; - } - else { - editEvent.changeset = Changeset.compose(editEvent.changeset, changes, - rep.apool); - } - } - else { - var inverseChangeset = Changeset.inverse(changes, {get: function(i) { - return rep.lines.atIndex(i).text+'\n'; - }, length: function() { return rep.lines.length(); }}, - rep.alines, rep.apool); - - if (! editEvent.backset) { - editEvent.backset = inverseChangeset; - } - else { - editEvent.backset = Changeset.compose(inverseChangeset, - editEvent.backset, rep.apool); - } - } - })(changes); - - //rep.alltext = Changeset.applyToText(changes, rep.alltext); - Changeset.mutateAttributionLines(changes, rep.alines, rep.apool); - - if (changesetTracker.isTracking()) { - changesetTracker.composeUserChangeset(changes); - } - - } - - function lineAndColumnFromChar(x) { - var lineEntry = rep.lines.atOffset(x); - var lineStart = rep.lines.offsetOfEntry(lineEntry); - var lineNum = rep.lines.indexOfEntry(lineEntry); - return [lineNum, x - lineStart]; - } - - function performDocumentReplaceCharRange(startChar, endChar, newText) { - if (startChar == endChar && newText.length == 0) { - return; - } - // Requires that the replacement preserve the property that the - // internal document text ends in a newline. Given this, we - // rewrite the splice so that it doesn't touch the very last - // char of the document. - if (endChar == rep.alltext.length) { - if (startChar == endChar) { - // an insert at end - startChar--; - endChar--; - newText = '\n'+newText.substring(0, newText.length-1); - } - else if (newText.length == 0) { - // a delete at end - startChar--; - endChar--; - } - else { - // a replace at end - endChar--; - newText = newText.substring(0, newText.length-1); - } - } - performDocumentReplaceRange(lineAndColumnFromChar(startChar), - lineAndColumnFromChar(endChar), - newText); - } - - function performDocumentReplaceRange(start, end, newText) { - //dmesg(String([start.toSource(),end.toSource(),newText.toSource()])); - - // start[0]: <--- start[1] --->CCCCCCCCCCC\n - // CCCCCCCCCCCCCCCCCCCC\n - // CCCC\n - // end[0]: <CCC end[1] CCC>-------\n - - var builder = Changeset.builder(rep.lines.totalWidth()); - buildKeepToStartOfRange(builder, start); - buildRemoveRange(builder, start, end); - builder.insert(newText, [['author',thisAuthor]], rep.apool); - var cs = builder.toString(); - - performDocumentApplyChangeset(cs); - } - - function performDocumentApplyAttributesToCharRange(start, end, attribs) { - if (end >= rep.alltext.length) { - end = rep.alltext.length-1; - } - performDocumentApplyAttributesToRange(lineAndColumnFromChar(start), - lineAndColumnFromChar(end), attribs); - } - - function performDocumentApplyAttributesToRange(start, end, attribs) { - var builder = Changeset.builder(rep.lines.totalWidth()); - buildKeepToStartOfRange(builder, start); - buildKeepRange(builder, start, end, attribs, rep.apool); - var cs = builder.toString(); - performDocumentApplyChangeset(cs); - } - - function buildKeepToStartOfRange(builder, start) { - var startLineOffset = rep.lines.offsetOfIndex(start[0]); - - builder.keep(startLineOffset, start[0]); - builder.keep(start[1]); - } - function buildRemoveRange(builder, start, end) { - var startLineOffset = rep.lines.offsetOfIndex(start[0]); - var endLineOffset = rep.lines.offsetOfIndex(end[0]); - - if (end[0] > start[0]) { - builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]); - builder.remove(end[1]); - } - else { - builder.remove(end[1] - start[1]); - } - } - function buildKeepRange(builder, start, end, attribs, pool) { - var startLineOffset = rep.lines.offsetOfIndex(start[0]); - var endLineOffset = rep.lines.offsetOfIndex(end[0]); - - if (end[0] > start[0]) { - builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool); - builder.keep(end[1], 0, attribs, pool); - } - else { - builder.keep(end[1] - start[1], 0, attribs, pool); - } - } - - function setAttributeOnSelection(attributeName, attributeValue) { - if (!(rep.selStart && rep.selEnd)) return; - - performDocumentApplyAttributesToRange(rep.selStart, rep.selEnd, - [[attributeName, attributeValue]]); - } - - function toggleAttributeOnSelection(attributeName) { - if (!(rep.selStart && rep.selEnd)) return; - - var selectionAllHasIt = true; - var withIt = Changeset.makeAttribsString('+', [[attributeName, 'true']], rep.apool); - var withItRegex = new RegExp(withIt.replace(/\*/g,'\\*')+"(\\*|$)"); - function hasIt(attribs) { return withItRegex.test(attribs); } - - var selStartLine = rep.selStart[0]; - var selEndLine = rep.selEnd[0]; - for(var n=selStartLine; n<=selEndLine; n++) { - var opIter = Changeset.opIterator(rep.alines[n]); - var indexIntoLine = 0; - var selectionStartInLine = 0; - var selectionEndInLine = rep.lines.atIndex(n).text.length; // exclude newline - if (n == selStartLine) { - selectionStartInLine = rep.selStart[1]; - } - if (n == selEndLine) { - selectionEndInLine = rep.selEnd[1]; - } - while (opIter.hasNext()) { - var op = opIter.next(); - var opStartInLine = indexIntoLine; - var opEndInLine = opStartInLine + op.chars; - if (! hasIt(op.attribs)) { - // does op overlap selection? - if (! (opEndInLine <= selectionStartInLine || opStartInLine >= selectionEndInLine)) { - selectionAllHasIt = false; - break; - } - } - indexIntoLine = opEndInLine; - } - if (! selectionAllHasIt) { - break; - } - } - - if (selectionAllHasIt) { - performDocumentApplyAttributesToRange(rep.selStart, rep.selEnd, - [[attributeName,'']]); - } - else { - var settings = [[attributeName, 'true']]; - - if (attributeName == 'h1' || attributeName == 'h2' || attributeName == 'h3' || - attributeName == 'h4' || attributeName == 'h5' || attributeName == 'h6') { - - settings = [['h1',''], ['h2',''], ['h3',''], - ['h4',''], ['h5',''], ['h6','']]; - if (attributeName == 'h1') { - settings[0] = [attributeName, 'true']; - } - else if (attributeName == 'h2') { - settings[1] = [attributeName, 'true']; - } - else if (attributeName == 'h3') { - settings[2] = [attributeName, 'true']; - } - else if (attributeName == 'h4') { - settings[3] = [attributeName, 'true']; - } - else if (attributeName == 'h5') { - settings[4] = [attributeName, 'true']; - } - else if (attributeName == 'h6') { - settings[5] = [attributeName, 'true']; - } - } - - performDocumentApplyAttributesToRange(rep.selStart, rep.selEnd, settings); - } - } - - function performDocumentReplaceSelection(newText) { - if (!(rep.selStart && rep.selEnd)) return; - performDocumentReplaceRange(rep.selStart, rep.selEnd, newText); - } - - // Change the abstract representation of the document to have a different set of lines. - // Must be called after rep.alltext is set. - function doRepLineSplice(startLine, deleteCount, newLineEntries) { - - forEach(newLineEntries, function (entry) { entry.width = entry.text.length+1; }); - - var startOldChar = rep.lines.offsetOfIndex(startLine); - var endOldChar = rep.lines.offsetOfIndex(startLine+deleteCount); - - var oldRegionStart = rep.lines.offsetOfIndex(startLine); - var oldRegionEnd = rep.lines.offsetOfIndex(startLine+deleteCount); - rep.lines.splice(startLine, deleteCount, newLineEntries); - currentCallStack.docTextChanged = true; - currentCallStack.repChanged = true; - var newRegionEnd = rep.lines.offsetOfIndex(startLine + newLineEntries.length); - - var newText = map(newLineEntries, function (e) { return e.text+'\n'; }).join(''); - - rep.alltext = rep.alltext.substring(0, startOldChar) + newText + - rep.alltext.substring(endOldChar, rep.alltext.length); - - //var newTotalLength = rep.alltext.length; - - //rep.lexer.updateBuffer(rep.alltext, oldRegionStart, oldRegionEnd - oldRegionStart, - //newRegionEnd - oldRegionStart); - } - - function doIncorpLineSplice(startLine, deleteCount, newLineEntries, lineAttribs, hints) { - - var startOldChar = rep.lines.offsetOfIndex(startLine); - var endOldChar = rep.lines.offsetOfIndex(startLine+deleteCount); - - var oldRegionStart = rep.lines.offsetOfIndex(startLine); - - var selStartHintChar, selEndHintChar; - if (hints && hints.selStart) { - selStartHintChar = rep.lines.offsetOfIndex(hints.selStart[0]) + hints.selStart[1] - - oldRegionStart; - } - if (hints && hints.selEnd) { - selEndHintChar = rep.lines.offsetOfIndex(hints.selEnd[0]) + hints.selEnd[1] - - oldRegionStart; - } - - var newText = map(newLineEntries, function (e) { return e.text+'\n'; }).join(''); - var oldText = rep.alltext.substring(startOldChar, endOldChar); - var oldAttribs = rep.alines.slice(startLine, startLine+deleteCount).join(''); - var newAttribs = lineAttribs.join('|1+1')+'|1+1'; // not valid in a changeset - var analysis = analyzeChange(oldText, newText, oldAttribs, newAttribs, - selStartHintChar, selEndHintChar); - var commonStart = analysis[0]; - var commonEnd = analysis[1]; - var shortOldText = oldText.substring(commonStart, oldText.length - commonEnd); - var shortNewText = newText.substring(commonStart, newText.length - commonEnd); - var spliceStart = startOldChar+commonStart; - var spliceEnd = endOldChar-commonEnd; - var shiftFinalNewlineToBeforeNewText = false; - - // adjust the splice to not involve the final newline of the document; - // be very defensive - if (shortOldText.charAt(shortOldText.length-1) == '\n' && - shortNewText.charAt(shortNewText.length-1) == '\n') { - // replacing text that ends in newline with text that also ends in newline - // (still, after analysis, somehow) - shortOldText = shortOldText.slice(0,-1); - shortNewText = shortNewText.slice(0,-1); - spliceEnd--; - commonEnd++; - } - if (shortOldText.length == 0 && spliceStart == rep.alltext.length - && shortNewText.length > 0) { - // inserting after final newline, bad - spliceStart--; - spliceEnd--; - shortNewText = '\n'+shortNewText.slice(0,-1); - shiftFinalNewlineToBeforeNewText = true; - } - if (spliceEnd == rep.alltext.length && shortOldText.length > 0 && - shortNewText.length == 0) { - // deletion at end of rep.alltext - if (rep.alltext.charAt(spliceStart-1) == '\n') { - // (if not then what the heck? it will definitely lead - // to a rep.alltext without a final newline) - spliceStart--; - spliceEnd--; - } - } - - if (! (shortOldText.length == 0 && shortNewText.length == 0)) { - var oldDocText = rep.alltext; - var oldLen = oldDocText.length; - - var spliceStartLine = rep.lines.indexOfOffset(spliceStart); - var spliceStartLineStart = rep.lines.offsetOfIndex(spliceStartLine); - function startBuilder() { - var builder = Changeset.builder(oldLen); - builder.keep(spliceStartLineStart, spliceStartLine); - builder.keep(spliceStart - spliceStartLineStart); - return builder; - } - - function eachAttribRun(attribs, func/*(startInNewText, endInNewText, attribs)*/) { - var attribsIter = Changeset.opIterator(attribs); - var textIndex = 0; - var newTextStart = commonStart; - var newTextEnd = newText.length - commonEnd - (shiftFinalNewlineToBeforeNewText ? 1 : 0); - while (attribsIter.hasNext()) { - var op = attribsIter.next(); - var nextIndex = textIndex + op.chars; - if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { - func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs); - } - textIndex = nextIndex; - } - } - - var justApplyStyles = (shortNewText == shortOldText); - var theChangeset; - - if (justApplyStyles) { - // create changeset that clears the incorporated styles on - // the existing text. we compose this with the - // changeset the applies the styles found in the DOM. - // This allows us to incorporate, e.g., Safari's native "unbold". - - var incorpedAttribClearer = cachedStrFunc(function (oldAtts) { - return Changeset.mapAttribNumbers(oldAtts, function(n) { - var k = rep.apool.getAttribKey(n); - if (isStyleAttribute(k)) { - return rep.apool.putAttrib([k,'']); - } - return false; - }); - }); - - var builder1 = startBuilder(); - if (shiftFinalNewlineToBeforeNewText) { - builder1.keep(1, 1); - } - eachAttribRun(oldAttribs, function(start, end, attribs) { - builder1.keepText(newText.substring(start, end), incorpedAttribClearer(attribs)); - }); - var clearer = builder1.toString(); - - var builder2 = startBuilder(); - if (shiftFinalNewlineToBeforeNewText) { - builder2.keep(1, 1); - } - eachAttribRun(newAttribs, function(start, end, attribs) { - builder2.keepText(newText.substring(start, end), attribs); - }); - var styler = builder2.toString(); - - theChangeset = Changeset.compose(clearer, styler, rep.apool); - } - else { - var builder = startBuilder(); - - var spliceEndLine = rep.lines.indexOfOffset(spliceEnd); - var spliceEndLineStart = rep.lines.offsetOfIndex(spliceEndLine); - if (spliceEndLineStart > spliceStart) { - builder.remove(spliceEndLineStart - spliceStart, spliceEndLine - spliceStartLine); - builder.remove(spliceEnd - spliceEndLineStart); - } - else { - builder.remove(spliceEnd - spliceStart); - } - - var isNewTextMultiauthor = false; - var authorAtt = Changeset.makeAttribsString( - '+', (thisAuthor ? [['author', thisAuthor]] : []), rep.apool); - var authorizer = cachedStrFunc(function(oldAtts) { - if (isNewTextMultiauthor) { - // prefer colors from DOM - return Changeset.composeAttributes(authorAtt, oldAtts, true, rep.apool); - } - else { - // use this author's color - return Changeset.composeAttributes(oldAtts, authorAtt, true, rep.apool); - } - }); - - var foundDomAuthor = ''; - eachAttribRun(newAttribs, function(start, end, attribs) { - var a = Changeset.attribsAttributeValue(attribs, 'author', rep.apool); - if (a && a != foundDomAuthor) { - if (! foundDomAuthor) { - foundDomAuthor = a; - } - else { - isNewTextMultiauthor = true; // multiple authors in DOM! - } - } - }); - - if (shiftFinalNewlineToBeforeNewText) { - builder.insert('\n', authorizer('')); - } - - eachAttribRun(newAttribs, function(start, end, attribs) { - builder.insert(newText.substring(start, end), authorizer(attribs)); - }); - theChangeset = builder.toString(); - } - - //dmesg(htmlPrettyEscape(theChangeset)); - - doRepApplyChangeset(theChangeset); - } - - // do this no matter what, because we need to get the right - // line keys into the rep. - doRepLineSplice(startLine, deleteCount, newLineEntries); - - checkALines(); - } - - function cachedStrFunc(func) { - var cache = {}; - return function(s) { - if (! cache[s]) { - cache[s] = func(s); - } - return cache[s]; - }; - } - - function analyzeChange(oldText, newText, oldAttribs, newAttribs, optSelStartHint, optSelEndHint) { - function incorpedAttribFilter(anum) { - return isStyleAttribute(rep.apool.getAttribKey(anum)); - } - function attribRuns(attribs) { - var lengs = []; - var atts = []; - var iter = Changeset.opIterator(attribs); - while (iter.hasNext()) { - var op = iter.next(); - lengs.push(op.chars); - atts.push(op.attribs); - } - return [lengs,atts]; - } - function attribIterator(runs, backward) { - var lengs = runs[0]; - var atts = runs[1]; - var i = (backward ? lengs.length-1 : 0); - var j = 0; - return function next() { - while (j >= lengs[i]) { - if (backward) i--; else i++; - j = 0; - } - var a = atts[i]; - j++; - return a; - }; - } - - var oldLen = oldText.length; - var newLen = newText.length; - var minLen = Math.min(oldLen, newLen); - - var oldARuns = attribRuns(Changeset.filterAttribNumbers(oldAttribs, incorpedAttribFilter)); - var newARuns = attribRuns(Changeset.filterAttribNumbers(newAttribs, incorpedAttribFilter)); - - var commonStart = 0; - var oldStartIter = attribIterator(oldARuns, false); - var newStartIter = attribIterator(newARuns, false); - while (commonStart < minLen) { - if (oldText.charAt(commonStart) == newText.charAt(commonStart) && - oldStartIter() == newStartIter()) { - commonStart++; - } - else break; - } - - var commonEnd = 0; - var oldEndIter = attribIterator(oldARuns, true); - var newEndIter = attribIterator(newARuns, true); - while (commonEnd < minLen) { - if (commonEnd == 0) { - // assume newline in common - oldEndIter(); newEndIter(); - commonEnd++; - } - else if (oldText.charAt(oldLen-1-commonEnd) == newText.charAt(newLen-1-commonEnd) && - oldEndIter() == newEndIter()) { - commonEnd++; - } - else break; - } - - var hintedCommonEnd = -1; - if ((typeof optSelEndHint) == "number") { - hintedCommonEnd = newLen - optSelEndHint; - } - - - if (commonStart + commonEnd > oldLen) { - // ambiguous insertion - var minCommonEnd = oldLen - commonStart; - var maxCommonEnd = commonEnd; - if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) { - commonEnd = hintedCommonEnd; - } - else { - commonEnd = minCommonEnd; - } - commonStart = oldLen - commonEnd; - } - if (commonStart + commonEnd > newLen) { - // ambiguous deletion - var minCommonEnd = newLen - commonStart; - var maxCommonEnd = commonEnd; - if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) { - commonEnd = hintedCommonEnd; - } - else { - commonEnd = minCommonEnd; - } - commonStart = newLen - commonEnd; - } - - return [commonStart, commonEnd]; - } - - function equalLineAndChars(a, b) { - if (!a) return !b; - if (!b) return !a; - return (a[0] == b[0] && a[1] == b[1]); - } - - function performSelectionChange(selectStart, selectEnd, focusAtStart) { - if (repSelectionChange(selectStart, selectEnd, focusAtStart)) { - currentCallStack.selectionAffected = true; - } - } - - // Change the abstract representation of the document to have a different selection. - // Should not rely on the line representation. Should not affect the DOM. - function repSelectionChange(selectStart, selectEnd, focusAtStart) { - focusAtStart = !! focusAtStart; - - var newSelFocusAtStart = (focusAtStart && - ((! selectStart) || (! selectEnd) || - (selectStart[0] != selectEnd[0]) || - (selectStart[1] != selectEnd[1]))); - - if ((! equalLineAndChars(rep.selStart, selectStart)) || - (! equalLineAndChars(rep.selEnd, selectEnd)) || - (rep.selFocusAtStart != newSelFocusAtStart)) { - rep.selStart = selectStart; - rep.selEnd = selectEnd; - rep.selFocusAtStart = newSelFocusAtStart; - if (mozillaFakeArrows) mozillaFakeArrows.notifySelectionChanged(); - currentCallStack.repChanged = true; - - return true; - //console.log("selStart: %o, selEnd: %o, focusAtStart: %s", rep.selStart, rep.selEnd, - //String(!!rep.selFocusAtStart)); - } - return false; - //console.log("%o %o %s", rep.selStart, rep.selEnd, rep.selFocusAtStart); - } - - /*function escapeHTML(s) { - var re = /[&<>'"]/g; /']/; // stupid indentation thing - if (! re.MAP) { - // persisted across function calls! - re.MAP = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - } - return s.replace(re, function(c) { return re.MAP[c]; }); - }*/ - - function doCreateDomLine(nonEmpty) { - if (browser.msie && (! nonEmpty)) { - var result = { node: null, - appendSpan: noop, - prepareForAdd: noop, - notifyAdded: noop, - clearSpans: noop, - finishUpdate: noop, - lineMarker: 0 }; - - var lineElem = doc.createElement("div"); - result.node = lineElem; - - result.notifyAdded = function() { - // magic -- settng an empty div's innerHTML to the empty string - // keeps it from collapsing. Apparently innerHTML must be set *after* - // adding the node to the DOM. - // Such a div is what IE 6 creates naturally when you make a blank line - // in a document of divs. However, when copy-and-pasted the div will - // contain a space, so we note its emptiness with a property. - lineElem.innerHTML = ""; - // a primitive-valued property survives copy-and-paste - setAssoc(lineElem, "shouldBeEmpty", true); - // an object property doesn't - setAssoc(lineElem, "unpasted", {}); - }; - var lineClass = 'ace-line'; - result.appendSpan = function(txt, cls) { - if ((! txt) && cls) { - // gain a whole-line style (currently to show insertion point in CSS) - lineClass = domline.addToLineClass(lineClass, cls); - } - // otherwise, ignore appendSpan, this is an empty line - }; - result.clearSpans = function() { - lineClass = ''; // non-null to cause update - }; - function writeClass() { - if (lineClass !== null) lineElem.className = lineClass; - } - result.prepareForAdd = writeClass; - result.finishUpdate = writeClass; - result.getInnerHTML = function() { return ""; }; - - return result; - } - else { - return domline.createDomLine(nonEmpty, doesWrap, browser, doc); - } - } - - function textify(str) { - return str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' '); - } - - var _blockElems = { "div":1, "p":1, "pre":1, "li":1, "ol":1, "ul":1 }; - function isBlockElement(n) { - return !!_blockElems[(n.tagName || "").toLowerCase()]; - } - - function getDirtyRanges() { - // based on observedChanges, return a list of ranges of original lines - // that need to be removed or replaced with new user content to incorporate - // the user's changes into the line representation. ranges may be zero-length, - // indicating inserted content. for example, [0,0] means content was inserted - // at the top of the document, while [3,4] means line 3 was deleted, modified, - // or replaced with one or more new lines of content. ranges do not touch. - - var p = PROFILER("getDirtyRanges", false); - p.forIndices = 0; - p.consecutives = 0; - p.corrections = 0; - - var cleanNodeForIndexCache = {}; - var N = rep.lines.length(); // old number of lines - function cleanNodeForIndex(i) { - // if line (i) in the un-updated line representation maps to a clean node - // in the document, return that node. - // if (i) is out of bounds, return true. else return false. - if (cleanNodeForIndexCache[i] === undefined) { - p.forIndices++; - var result; - if (i < 0 || i >= N) { - result = true; // truthy, but no actual node - } - else { - var key = rep.lines.atIndex(i).key; - result = (getCleanNodeByKey(key) || false); - } - cleanNodeForIndexCache[i] = result; - } - return cleanNodeForIndexCache[i]; - } - var isConsecutiveCache = {}; - function isConsecutive(i) { - if (isConsecutiveCache[i] === undefined) { - p.consecutives++; - isConsecutiveCache[i] = (function() { - // returns whether line (i) and line (i-1), assumed to be map to clean DOM nodes, - // or document boundaries, are consecutive in the changed DOM - var a = cleanNodeForIndex(i-1); - var b = cleanNodeForIndex(i); - if ((!a) || (!b)) return false; // violates precondition - if ((a === true) && (b === true)) return ! root.firstChild; - if ((a === true) && b.previousSibling) return false; - if ((b === true) && a.nextSibling) return false; - if ((a === true) || (b === true)) return true; - return a.nextSibling == b; - })(); - } - return isConsecutiveCache[i]; - } - function isClean(i) { - // returns whether line (i) in the un-updated representation maps to a clean node, - // or is outside the bounds of the document - return !! cleanNodeForIndex(i); - } - // list of pairs, each representing a range of lines that is clean and consecutive - // in the changed DOM. lines (-1) and (N) are always clean, but may or may not - // be consecutive with lines in the document. pairs are in sorted order. - var cleanRanges = [[-1,N+1]]; - function rangeForLine(i) { - // returns index of cleanRange containing i, or -1 if none - var answer = -1; - forEach(cleanRanges, function (r, idx) { - if (i >= r[1]) return false; // keep looking - if (i < r[0]) return true; // not found, stop looking - answer = idx; - return true; // found, stop looking - }); - return answer; - } - function removeLineFromRange(rng, line) { - // rng is index into cleanRanges, line is line number - // precond: line is in rng - var a = cleanRanges[rng][0]; - var b = cleanRanges[rng][1]; - if ((a+1) == b) cleanRanges.splice(rng, 1); - else if (line == a) cleanRanges[rng][0]++; - else if (line == (b-1)) cleanRanges[rng][1]--; - else cleanRanges.splice(rng, 1, [a,line], [line+1,b]); - } - function splitRange(rng, pt) { - // precond: pt splits cleanRanges[rng] into two non-empty ranges - var a = cleanRanges[rng][0]; - var b = cleanRanges[rng][1]; - cleanRanges.splice(rng, 1, [a,pt], [pt,b]); - } - var correctedLines = {}; - function correctlyAssignLine(line) { - if (correctedLines[line]) return true; - p.corrections++; - correctedLines[line] = true; - // "line" is an index of a line in the un-updated rep. - // returns whether line was already correctly assigned (i.e. correctly - // clean or dirty, according to cleanRanges, and if clean, correctly - // attached or not attached (i.e. in the same range as) the prev and next lines). - //console.log("correctly assigning: %d", line); - var rng = rangeForLine(line); - var lineClean = isClean(line); - if (rng < 0) { - if (lineClean) { - console.debug("somehow lost clean line"); - } - return true; - } - if (! lineClean) { - // a clean-range includes this dirty line, fix it - removeLineFromRange(rng, line); - return false; - } - else { - // line is clean, but could be wrongly connected to a clean line - // above or below - var a = cleanRanges[rng][0]; - var b = cleanRanges[rng][1]; - var didSomething = false; - // we'll leave non-clean adjacent nodes in the clean range for the caller to - // detect and deal with. we deal with whether the range should be split - // just above or just below this line. - if (a < line && isClean(line-1) && ! isConsecutive(line)) { - splitRange(rng, line); - didSomething = true; - } - if (b > (line+1) && isClean(line+1) && ! isConsecutive(line+1)) { - splitRange(rng, line+1); - didSomething = true; - } - return ! didSomething; - } - } - function detectChangesAroundLine(line, reqInARow) { - // make sure cleanRanges is correct about line number "line" and the surrounding - // lines; only stops checking at end of document or after no changes need - // making for several consecutive lines. note that iteration is over old lines, - // so this operation takes time proportional to the number of old lines - // that are changed or missing, not the number of new lines inserted. - var correctInARow = 0; - var currentIndex = line; - while (correctInARow < reqInARow && currentIndex >= 0) { - if (correctlyAssignLine(currentIndex)) { - correctInARow++; - } - else correctInARow = 0; - currentIndex--; - } - correctInARow = 0; - currentIndex = line; - while (correctInARow < reqInARow && currentIndex < N) { - if (correctlyAssignLine(currentIndex)) { - correctInARow++; - } - else correctInARow = 0; - currentIndex++; - } - } - - if (N == 0) { - p.cancel(); - if (! isConsecutive(0)) { - splitRange(0, 0); - } - } - else { - p.mark("topbot"); - detectChangesAroundLine(0,1); - detectChangesAroundLine(N-1,1); - - p.mark("obs"); - //console.log("observedChanges: "+toSource(observedChanges)); - for (var k in observedChanges.cleanNodesNearChanges) { - var key = k.substring(1); - if (rep.lines.containsKey(key)) { - var line = rep.lines.indexOfKey(key); - detectChangesAroundLine(line,2); - } - } - p.mark("stats&calc"); - p.literal(p.forIndices, "byidx"); - p.literal(p.consecutives, "cons"); - p.literal(p.corrections, "corr"); - } - - var dirtyRanges = []; - for(var r=0;r<cleanRanges.length-1;r++) { - dirtyRanges.push([cleanRanges[r][1], cleanRanges[r+1][0]]); - } - - p.end(); - - return dirtyRanges; - } - - function markNodeClean(n) { - // clean nodes have knownHTML that matches their innerHTML - var dirtiness = {}; - dirtiness.nodeId = uniqueId(n); - dirtiness.knownHTML = n.innerHTML; - if (browser.msie) { - // adding a space to an "empty" div in IE designMode doesn't - // change the innerHTML of the div's parent; also, other - // browsers don't support innerText - dirtiness.knownText = n.innerText; - } - setAssoc(n, "dirtiness", dirtiness); - } - - function isNodeDirty(n) { - var p = PROFILER("cleanCheck", false); - if (n.parentNode != root) return true; - var data = getAssoc(n, "dirtiness"); - if (!data) return true; - if (n.id !== data.nodeId) return true; - if (browser.msie) { - if (n.innerText !== data.knownText) return true; - } - if (n.innerHTML !== data.knownHTML) return true; - p.end(); - return false; - } - - function getLineEntryTopBottom(entry, destObj) { - var dom = entry.lineNode; - var top = dom.offsetTop; - var height = dom.offsetHeight; - var obj = (destObj || {}); - obj.top = top; - obj.bottom = (top+height); - return obj; - } - - function getViewPortTopBottom() { - var theTop = getScrollY(); - var doc = outerWin.document; - var height = doc.documentElement.clientHeight; - return {top:theTop, bottom:(theTop+height)}; - } - - function getVisibleLineRange() { - var viewport = getViewPortTopBottom(); - //console.log("viewport top/bottom: %o", viewport); - var obj = {}; - var start = rep.lines.search(function (e) { - return getLineEntryTopBottom(e, obj).bottom > viewport.top; - }); - var end = rep.lines.search(function(e) { - return getLineEntryTopBottom(e, obj).top >= viewport.bottom; - }); - if (end < start) end = start; // unlikely - //console.log(start+","+end); - return [start,end]; - } - - function getVisibleCharRange() { - var lineRange = getVisibleLineRange(); - return [rep.lines.offsetOfIndex(lineRange[0]), - rep.lines.offsetOfIndex(lineRange[1])]; - } - - function handleClick(evt) { - inCallStack("handleClick", function() { - idleWorkTimer.atMost(200); - }); - - // only want to catch left-click - if ((! evt.ctrlKey) && (evt.button != 2) && (evt.button != 3)) { - // find A tag with HREF - function isLink(n) { return (n.tagName||'').toLowerCase() == "a" && n.href; } - var n = evt.target; - while (n && n.parentNode && ! isLink(n)) { n = n.parentNode; } - if (n && isLink(n)) { - try { - var newWindow = window.open(n.href, '_blank'); - newWindow.focus(); - } - catch (e) { - // absorb "user canceled" error in IE for certain prompts - } - evt.preventDefault(); - } - } - } - - function doReturnKey() { - if (! (rep.selStart && rep.selEnd)) { - return; - } - var lineNum = rep.selStart[0]; - var listType = getLineListType(lineNum); - - performDocumentReplaceSelection('\n'); - if (listType) { - if (lineNum+1 < rep.lines.length()) { - setLineListType(lineNum+1, listType); - } - } - else { - handleReturnIndentation(); - } - } - - function doIndentOutdent(isOut) { - if (! (rep.selStart && rep.selEnd)) { - return false; - } - - var firstLine, lastLine; - firstLine = rep.selStart[0]; - lastLine = Math.max(firstLine, - rep.selEnd[0] - ((rep.selEnd[1] == 0) ? 1 : 0)); - - var mods = []; - var foundLists = false; - for(var n=firstLine;n<=lastLine;n++) { - var listType = getLineListType(n); - if (listType) { - listType = /([a-z]+)([12345678])/.exec(listType); - if (listType) { - foundLists = true; - var t = listType[1]; - var level = Number(listType[2]); - var newLevel = - Math.max(1, Math.min(MAX_LIST_LEVEL, - level + (isOut ? -1 : 1))); - if (level != newLevel) { - mods.push([n, t+newLevel]); - } - } - } - } - - if (mods.length > 0) { - setLineListTypes(mods); - } - - return foundLists; - } - - function doTabKey(shiftDown) { - if (! doIndentOutdent(shiftDown)) { - performDocumentReplaceSelection(THE_TAB); - } - } - - function doDeleteKey(optEvt) { - var evt = optEvt || {}; - var handled = false; - if (rep.selStart) { - if (isCaret()) { - var lineNum = caretLine(); - var col = caretColumn(); - var lineEntry = rep.lines.atIndex(lineNum); - var lineText = lineEntry.text; - var lineMarker = lineEntry.lineMarker; - if (/^ +$/.exec(lineText.substring(lineMarker, col))) { - var col2 = col - lineMarker; - var tabSize = THE_TAB.length; - var toDelete = ((col2 - 1) % tabSize)+1; - performDocumentReplaceRange([lineNum,col-toDelete], - [lineNum,col], ''); - //scrollSelectionIntoView(); - handled = true; - } - } - if (! handled) { - if (isCaret()) { - var theLine = caretLine(); - var lineEntry = rep.lines.atIndex(theLine); - if (caretColumn() <= lineEntry.lineMarker) { - // delete at beginning of line - var action = 'delete_newline'; - var prevLineListType = - (theLine > 0 ? getLineListType(theLine-1) : ''); - var thisLineListType = getLineListType(theLine); - var prevLineEntry = (theLine > 0 && - rep.lines.atIndex(theLine-1)); - var prevLineBlank = (prevLineEntry && - prevLineEntry.text.length == - prevLineEntry.lineMarker); - if (thisLineListType) { - // this line is a list - /*if (prevLineListType) { - // prev line is a list too, remove this bullet - performDocumentReplaceRange([theLine-1, prevLineEntry.text.length], - [theLine, lineEntry.lineMarker], ''); - } - else*/ if (prevLineBlank && ! prevLineListType) { - // previous line is blank, remove it - performDocumentReplaceRange([theLine-1, prevLineEntry.text.length], - [theLine, 0], ''); - } - else { - // delistify - performDocumentReplaceRange([theLine, 0], - [theLine, lineEntry.lineMarker], ''); - } - } - else if (theLine > 0) { - // remove newline - performDocumentReplaceRange([theLine-1, prevLineEntry.text.length], - [theLine, 0], ''); - } - } - else { - var docChar = caretDocChar(); - if (docChar > 0) { - if (evt.metaKey || evt.ctrlKey || evt.altKey) { - // delete as many unicode "letters or digits" in a row as possible; - // always delete one char, delete further even if that first char - // isn't actually a word char. - var deleteBackTo = docChar-1; - while (deleteBackTo > lineEntry.lineMarker && - isWordChar(rep.alltext.charAt(deleteBackTo-1))) { - deleteBackTo--; - } - performDocumentReplaceCharRange(deleteBackTo, docChar, ''); - } - else { - // normal delete - performDocumentReplaceCharRange(docChar-1, docChar, ''); - } - } - } - } - else { - performDocumentReplaceSelection(''); - } - } - } - } - - // set of "letter or digit" chars is based on section 20.5.16 of the original Java Language Spec - var REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; - var REGEX_SPACE = /\s/; - - function isWordChar(c) { - return !! REGEX_WORDCHAR.exec(c); - } - function isSpaceChar(c) { - return !! REGEX_SPACE.exec(c); - } - - function moveByWordInLine(lineText, initialIndex, forwardNotBack) { - var i = initialIndex; - function nextChar() { - if (forwardNotBack) return lineText.charAt(i); - else return lineText.charAt(i-1); - } - function advance() { if (forwardNotBack) i++; else i--; } - function isDone() { - if (forwardNotBack) return i >= lineText.length; - else return i <= 0; - } - - // On Mac and Linux, move right moves to end of word and move left moves to start; - // on Windows, always move to start of word. - // On Windows, Firefox and IE disagree on whether to stop for punctuation (FF says no). - if (browser.windows && forwardNotBack) { - while ((! isDone()) && isWordChar(nextChar())) { advance(); } - while ((! isDone()) && ! isWordChar(nextChar())) { advance(); } - } - else { - while ((! isDone()) && ! isWordChar(nextChar())) { advance(); } - while ((! isDone()) && isWordChar(nextChar())) { advance(); } - } - - return i; - } - - function handleKeyEvent(evt) { - if (DEBUG && top.DONT_INCORP) return; - - /*if (evt.which == 48) { - //setEditable(! isEditable); - //doAlert(getInnerWidth()); - //doAlert(doc.documentElement.innerWidth) - alert(eval(prompt())); - evt.preventDefault(); - return; - }*/ - /*if (evt.which == 48) { - alert(doc.body.innerHTML); - }*/ - /*if (evt.which == 48 && evt.type == "keydown") { - var lineHeights = []; - function eachChild(node, func) { - if (node.firstChild) { - var n = node.firstChild; - while (n) { - func(n); - n = n.nextSibling; - } - } - } - eachChild(doc.body, function (n) { - if (n.clientHeight) { - lineHeights.push(n.clientHeight); - } - }); - alert(lineHeights.join(',')); - }*/ - /*if (evt.which == 48) { - top.DONT_INCORP = true; - var cmdTarget = doc; - if (browser.msie) { - if (doc.selection) { - cmdTarget = doc.selection.createRange(); - } - else cmdTarget = null; - } - if (cmdTarget) { - cmdTarget.execCommand("Bold", false, null); - } - alert(doc.body.innerHTML); - evt.preventDefault(); - return; - }*/ - /*if (evt.which == 48) { - if (evt.type == "keypress") { - top.console.log(window.getSelection().getRangeAt(0)); - evt.preventDefault(); - } - return; - }*/ - /*if (evt.which == 48) { - if (evt.type == "keypress") { - inCallStack("bold", function() { - fastIncorp(9); - toggleAttributeOnSelection('bold'); - }); - evt.preventDefault(); - } - return; - }*/ - /*if (evt.which == 48) { - if (evt.type == "keypress") { - inCallStack("insertunorderedlist", function() { - fastIncorp(9); - doInsertUnorderedList(); - }); - evt.preventDefault(); - } - return; - }*/ - - if (! isEditable) return; - - var type = evt.type; - var charCode = evt.charCode; - var keyCode = evt.keyCode; - var mods = ""; - if (evt.altKey) mods = mods+"A"; - if (evt.ctrlKey) mods = mods+"C"; - if (evt.shiftKey) mods = mods+"S"; - if (evt.metaKey) mods = mods+"M"; - var modsPrfx = ""; - if (mods) modsPrfx = mods+"-"; - var which = evt.which; - - //dmesg("keyevent type: "+type+", which: "+which); - - // Don't take action based on modifier keys going up and down. - // Modifier keys do not generate "keypress" events. - // 224 is the command-key under Mac Firefox. - // 91 is the Windows key in IE; it is ASCII for open-bracket but isn't the keycode for that key - // 20 is capslock in IE. - var isModKey = ((!charCode) && - ((type == "keyup") || (type == "keydown")) && - (keyCode == 16 || keyCode == 17 || keyCode == 18 || keyCode == 20 || keyCode == 224 - || keyCode == 91)); - if (isModKey) return; - - var specialHandled = false; - var isTypeForSpecialKey = ((browser.msie || browser.safari) ? - (type == "keydown") : (type == "keypress")); - var isTypeForCmdKey = ((browser.msie || browser.safari) ? (type == "keydown") : (type == "keypress")); - - var stopped = false; - - inCallStack("handleKeyEvent", function() { - - if (type == "keypress" || - (isTypeForSpecialKey && keyCode == 13/*return*/)) { - // in IE, special keys don't send keypress, the keydown does the action - if (! outsideKeyPress(evt)) { - evt.preventDefault(); - stopped = true; - } - } - else if (type == "keydown") { - outsideKeyDown(evt); - } - - if (! stopped) { - if (isTypeForSpecialKey && keyCode == 8) { - // "delete" key; in mozilla, if we're at the beginning of a line, normalize now, - // or else deleting a blank line can take two delete presses. - // -- - // we do deletes completely customly now: - // - allows consistent (and better) meta-delete behavior - // - normalizing and then allowing default behavior confused IE - // - probably eliminates a few minor quirks - fastIncorp(3); - evt.preventDefault(); - doDeleteKey(evt); - specialHandled = true; - } - if ((!specialHandled) && isTypeForSpecialKey && keyCode == 13) { - // return key, handle specially; - // note that in mozilla we need to do an incorporation for proper return behavior anyway. - fastIncorp(4); - evt.preventDefault(); - doReturnKey(); - //scrollSelectionIntoView(); - scheduler.setTimeout(function() {outerWin.scrollBy(-100,0);}, 0); - specialHandled = true; - } - if ((!specialHandled) && isTypeForSpecialKey && keyCode == 9 && - ! (evt.metaKey || evt.ctrlKey)) { - // tab - fastIncorp(5); - evt.preventDefault(); - doTabKey(evt.shiftKey); - //scrollSelectionIntoView(); - specialHandled = true; - } - if ((!specialHandled) && isTypeForCmdKey && - String.fromCharCode(which).toLowerCase() == "z" && - (evt.metaKey || evt.ctrlKey)) { - // cmd-Z (undo) - fastIncorp(6); - evt.preventDefault(); - if (evt.shiftKey) { - doUndoRedo("redo"); - } - else { - doUndoRedo("undo"); - } - specialHandled = true; - } - if ((!specialHandled) && isTypeForCmdKey && - String.fromCharCode(which).toLowerCase() == "y" && - (evt.metaKey || evt.ctrlKey)) { - // cmd-Y (redo) - fastIncorp(10); - evt.preventDefault(); - doUndoRedo("redo"); - specialHandled = true; - } - if ((!specialHandled) && isTypeForCmdKey && - String.fromCharCode(which).toLowerCase() == "b" && - (evt.metaKey || evt.ctrlKey)) { - // cmd-B (bold) - fastIncorp(13); - evt.preventDefault(); - toggleAttributeOnSelection('bold'); - specialHandled = true; - } - if ((!specialHandled) && isTypeForCmdKey && - String.fromCharCode(which).toLowerCase() == "i" && - (evt.metaKey || evt.ctrlKey)) { - // cmd-I (italic) - fastIncorp(14); - evt.preventDefault(); - toggleAttributeOnSelection('italic'); - specialHandled = true; - } - if ((!specialHandled) && isTypeForCmdKey && - String.fromCharCode(which).toLowerCase() == "u" && - (evt.metaKey || evt.ctrlKey)) { - // cmd-U (underline) - fastIncorp(15); - evt.preventDefault(); - toggleAttributeOnSelection('underline'); - specialHandled = true; - } - if ((!specialHandled) && isTypeForCmdKey && - String.fromCharCode(which).toLowerCase() == "h" && - (evt.ctrlKey)) { - // cmd-H (backspace) - fastIncorp(20); - evt.preventDefault(); - doDeleteKey(); - specialHandled = true; - } - /*if ((!specialHandled) && isTypeForCmdKey && - String.fromCharCode(which).toLowerCase() == "u" && - (evt.metaKey || evt.ctrlKey)) { - // cmd-U - doc.body.innerHTML = ''; - evt.preventDefault(); - specialHandled = true; - }*/ - - if (mozillaFakeArrows && mozillaFakeArrows.handleKeyEvent(evt)) { - evt.preventDefault(); - specialHandled = true; - } - } - - if (type == "keydown") { - idleWorkTimer.atLeast(500); - } - else if (type == "keypress") { - if ((! specialHandled) && parenModule.shouldNormalizeOnChar(charCode)) { - idleWorkTimer.atMost(0); - } - else { - idleWorkTimer.atLeast(500); - } - } - else if (type == "keyup") { - var wait = 200; - idleWorkTimer.atLeast(wait); - idleWorkTimer.atMost(wait); - } - - // Is part of multi-keystroke international character on Firefox Mac - var isFirefoxHalfCharacter = - (browser.mozilla && evt.altKey && charCode == 0 && keyCode == 0); - - // Is part of multi-keystroke international character on Safari Mac - var isSafariHalfCharacter = - (browser.safari && evt.altKey && keyCode == 229); - - if (thisKeyDoesntTriggerNormalize || isFirefoxHalfCharacter || isSafariHalfCharacter) { - idleWorkTimer.atLeast(3000); // give user time to type - // if this is a keydown, e.g., the keyup shouldn't trigger a normalize - thisKeyDoesntTriggerNormalize = true; - } - - if ((! specialHandled) && (! thisKeyDoesntTriggerNormalize) && - (! inInternationalComposition)) { - if (type != "keyup" || ! incorpIfQuick()) { - observeChangesAroundSelection(); - } - } - - if (type == "keyup") { - thisKeyDoesntTriggerNormalize = false; - } - }); - } - - var thisKeyDoesntTriggerNormalize = false; - - function doUndoRedo(which) { - // precond: normalized DOM - if (undoModule.enabled) { - var whichMethod; - if (which == "undo") whichMethod = 'performUndo'; - if (which == "redo") whichMethod = 'performRedo'; - if (whichMethod) { - var oldEventType = currentCallStack.editEvent.eventType; - currentCallStack.startNewEvent(which); - undoModule[whichMethod](function(backset, selectionInfo) { - if (backset) { - performDocumentApplyChangeset(backset); - } - if (selectionInfo) { - performSelectionChange(lineAndColumnFromChar(selectionInfo.selStart), - lineAndColumnFromChar(selectionInfo.selEnd), - selectionInfo.selFocusAtStart); - } - var oldEvent = currentCallStack.startNewEvent(oldEventType, true); - return oldEvent; - }); - } - } - } - - /*function enforceNewTextTypedStyle() { - var sel = getSelection(); - var n = (sel && sel.startPoint && sel.startPoint.node); - if (!n) return; - var isInOurNode = false; - while (n) { - if (n.tagName) { - var tag = n.tagName.toLowerCase(); - if (tag == "b" || tag == "strong") { - isInOurNode = true; - break; - } - if (((typeof n.className) == "string") && - n.className.toLowerCase().indexOf("Apple-style-span") >= 0) { - isInOurNode = true; - break; - } - } - n = n.parentNode; - } - - if (! isInOurNode) { - doc.execCommand("Bold", false, null); - } - - if (! browser.msie) { - var browserSelection = window.getSelection(); - if (browserSelection && browserSelection.type != "None" && - browserSelection.rangeCount !== 0) { - var range = browserSelection.getRangeAt(0); - var surrounder = doc.createElement("B"); - range.surroundContents(surrounder); - range.selectNodeContents(surrounder); - browserSelection.removeAllRanges(); - browserSelection.addRange(range); - } - } - }*/ - - function updateBrowserSelectionFromRep() { - // requires normalized DOM! - var selStart = rep.selStart, selEnd = rep.selEnd; - - if (!(selStart && selEnd)) { - setSelection(null); - return; - } - - var mozillaCaretHack = (false && browser.mozilla && selStart && selEnd && - selStart[0] == selEnd[0] - && selStart[1] == rep.lines.atIndex(selStart[0]).lineMarker - && selEnd[1] == rep.lines.atIndex(selEnd[0]).lineMarker && - setupMozillaCaretHack(selStart[0])); - - var selection = {}; - - var ss = [selStart[0], selStart[1]]; - if (mozillaCaretHack) ss[1] += 1; - selection.startPoint = getPointForLineAndChar(ss); - - var se = [selEnd[0], selEnd[1]]; - if (mozillaCaretHack) se[1] += 1; - selection.endPoint = getPointForLineAndChar(se); - - selection.focusAtStart = !!rep.selFocusAtStart; - - setSelection(selection); - - if (mozillaCaretHack) { - mozillaCaretHack.unhack(); - } - } - - function getRepHTML() { - /*function lineWithSelection(text, lineNum) { - var haveSelStart = (rep.selStart && rep.selStart[0] == lineNum); - var haveSelEnd = (rep.selEnd && rep.selEnd[0] == lineNum); - var startCol = (haveSelStart && rep.selStart[1]); - var endCol = (haveSelEnd && rep.selEnd[1]); - var len = text.length; - if (haveSelStart && haveSelEnd && startCol == endCol) { - var color = "#000"; - if (endCol == len) { - return '<span style="border-right: 1px solid '+color+'">'+ - htmlEscape(text)+'</span>'; - } - else { - return htmlEscape - } - } - }*/ - - return map(rep.lines.slice(), function (entry) { - var text = entry.text; - var content; - if (text.length == 0) { - content = '<span style="color: #aaa">--</span>'; - } - else { - content = htmlPrettyEscape(text); - } - return '<div><code>'+content+'</div></code>'; - }).join(''); - } - - function nodeMaxIndex(nd) { - if (isNodeText(nd)) return nd.nodeValue.length; - else return 1; - } - - function hasIESelection() { - var browserSelection; - try { browserSelection = doc.selection; } catch (e) {} - if (! browserSelection) return false; - var origSelectionRange; - try { origSelectionRange = browserSelection.createRange(); } catch (e) {} - if (! origSelectionRange) return false; - var selectionParent = origSelectionRange.parentElement(); - if (selectionParent.ownerDocument != doc) return false; - return true; - } - - function getSelection() { - // returns null, or a structure containing startPoint and endPoint, - // each of which has node (a magicdom node), index, and maxIndex. If the node - // is a text node, maxIndex is the length of the text; else maxIndex is 1. - // index is between 0 and maxIndex, inclusive. - if (browser.msie) { - var browserSelection; - try { browserSelection = doc.selection; } catch (e) {} - if (! browserSelection) return null; - var origSelectionRange; - try { origSelectionRange = browserSelection.createRange(); } catch (e) {} - if (! origSelectionRange) return null; - var selectionParent = origSelectionRange.parentElement(); - if (selectionParent.ownerDocument != doc) return null; - function newRange() { - return doc.body.createTextRange(); - } - function rangeForElementNode(nd) { - var rng = newRange(); - // doesn't work on text nodes - rng.moveToElementText(nd); - return rng; - } - function pointFromCollapsedRange(rng) { - var parNode = rng.parentElement(); - var elemBelow = -1; - var elemAbove = parNode.childNodes.length; - var rangeWithin = rangeForElementNode(parNode); - - if (rng.compareEndPoints("StartToStart", rangeWithin) == 0) { - return {node:parNode, index:0, maxIndex:1}; - } - else if (rng.compareEndPoints("EndToEnd", rangeWithin) == 0) { - if (isBlockElement(parNode) && parNode.nextSibling) { - // caret after block is not consistent across browsers - // (same line vs next) so put caret before next node - return {node:parNode.nextSibling, index:0, maxIndex:1}; - } - return {node:parNode, index:1, maxIndex:1}; - } - else if (parNode.childNodes.length == 0) { - return {node:parNode, index:0, maxIndex:1}; - } - - for(var i=0;i<parNode.childNodes.length;i++) { - var n = parNode.childNodes.item(i); - if (! isNodeText(n)) { - var nodeRange = rangeForElementNode(n); - var startComp = rng.compareEndPoints("StartToStart", nodeRange); - var endComp = rng.compareEndPoints("EndToEnd", nodeRange); - if (startComp >= 0 && endComp <= 0) { - var index = 0; - if (startComp > 0) { - index = 1; - } - return {node:n, index:index, maxIndex:1}; - } - else if (endComp > 0) { - if (i > elemBelow) { - elemBelow = i; - rangeWithin.setEndPoint("StartToEnd", nodeRange); - } - } - else if (startComp < 0) { - if (i < elemAbove) { - elemAbove = i; - rangeWithin.setEndPoint("EndToStart", nodeRange); - } - } - } - } - if ((elemAbove - elemBelow) == 1) { - if (elemBelow >= 0) { - return {node:parNode.childNodes.item(elemBelow), index:1, maxIndex:1}; - } - else { - return {node:parNode.childNodes.item(elemAbove), index:0, maxIndex:1}; - } - } - var idx = 0; - var r = rng.duplicate(); - // infinite stateful binary search! call function for values 0 to inf, - // expecting the answer to be about 40. return index of smallest - // true value. - var indexIntoRange = binarySearchInfinite(40, function (i) { - // the search algorithm whips the caret back and forth, - // though it has to be moved relatively and may hit - // the end of the buffer - var delta = i-idx; - var moved = Math.abs(r.move("character", -delta)); - // next line is work-around for fact that when moving left, the beginning - // of a text node is considered to be after the start of the parent element: - if (r.move("character", -1)) r.move("character", 1); - if (delta < 0) idx -= moved; - else idx += moved; - return (r.compareEndPoints("StartToStart", rangeWithin) <= 0); - }); - // iterate over consecutive text nodes, point is in one of them - var textNode = elemBelow+1; - var indexLeft = indexIntoRange; - while (textNode < elemAbove) { - var tn = parNode.childNodes.item(textNode); - if (indexLeft <= tn.nodeValue.length) { - return {node:tn, index:indexLeft, maxIndex:tn.nodeValue.length}; - } - indexLeft -= tn.nodeValue.length; - textNode++; - } - var tn = parNode.childNodes.item(textNode-1); - return {node:tn, index:tn.nodeValue.length, maxIndex:tn.nodeValue.length}; - } - var selection = {}; - if (origSelectionRange.compareEndPoints("StartToEnd", origSelectionRange) == 0) { - // collapsed - var pnt = pointFromCollapsedRange(origSelectionRange); - selection.startPoint = pnt; - selection.endPoint = {node:pnt.node, index:pnt.index, maxIndex:pnt.maxIndex}; - } - else { - var start = origSelectionRange.duplicate(); - start.collapse(true); - var end = origSelectionRange.duplicate(); - end.collapse(false); - selection.startPoint = pointFromCollapsedRange(start); - selection.endPoint = pointFromCollapsedRange(end); - /*if ((!selection.startPoint.node.isText) && (!selection.endPoint.node.isText)) { - console.log(selection.startPoint.node.uniqueId()+","+ - selection.startPoint.index+" / "+ - selection.endPoint.node.uniqueId()+","+ - selection.endPoint.index); - }*/ - } - return selection; - } - else { - // non-IE browser - var browserSelection = window.getSelection(); - if (browserSelection && browserSelection.type != "None" && - browserSelection.rangeCount !== 0) { - var range = browserSelection.getRangeAt(0); - function isInBody(n) { - while (n && ! (n.tagName && n.tagName.toLowerCase() == "body")) { - n = n.parentNode; - } - return !!n; - } - function pointFromRangeBound(container, offset) { - if (! isInBody(container)) { - // command-click in Firefox selects whole document, HEAD and BODY! - return {node:root, index:0, maxIndex:1}; - } - var n = container; - var childCount = n.childNodes.length; - if (isNodeText(n)) { - return {node:n, index:offset, maxIndex:n.nodeValue.length}; - } - else if (childCount == 0) { - return {node:n, index:0, maxIndex:1}; - } - // treat point between two nodes as BEFORE the second (rather than after the first) - // if possible; this way point at end of a line block-element is treated as - // at beginning of next line - else if (offset == childCount) { - var nd = n.childNodes.item(childCount-1); - var max = nodeMaxIndex(nd); - return {node:nd, index:max, maxIndex:max}; - } - else { - var nd = n.childNodes.item(offset); - var max = nodeMaxIndex(nd); - return {node:nd, index:0, maxIndex:max}; - } - } - var selection = {}; - selection.startPoint = pointFromRangeBound(range.startContainer, range.startOffset); - selection.endPoint = pointFromRangeBound(range.endContainer, range.endOffset); - selection.focusAtStart = (((range.startContainer != range.endContainer) || - (range.startOffset != range.endOffset)) && - browserSelection.anchorNode && - (browserSelection.anchorNode == range.endContainer) && - (browserSelection.anchorOffset == range.endOffset)); - return selection; - } - else return null; - } - } - - function setSelection(selection) { - function copyPoint(pt) { - return {node:pt.node, index:pt.index, maxIndex:pt.maxIndex}; - } - if (browser.msie) { - // Oddly enough, accessing scrollHeight fixes return key handling on IE 8, - // presumably by forcing some kind of internal DOM update. - doc.body.scrollHeight; - - function moveToElementText(s, n) { - while (n.firstChild && ! isNodeText(n.firstChild)) { - n = n.firstChild; - } - s.moveToElementText(n); - } - function newRange() { - return doc.body.createTextRange(); - } - function setCollapsedBefore(s, n) { - // s is an IE TextRange, n is a dom node - if (isNodeText(n)) { - // previous node should not also be text, but prevent inf recurs - if (n.previousSibling && ! isNodeText(n.previousSibling)) { - setCollapsedAfter(s, n.previousSibling); - } - else { - setCollapsedBefore(s, n.parentNode); - } - } - else { - moveToElementText(s, n); - // work around for issue that caret at beginning of line - // somehow ends up at end of previous line - if (s.move('character', 1)) { - s.move('character', -1); - } - s.collapse(true); // to start - } - } - function setCollapsedAfter(s, n) { - // s is an IE TextRange, n is a magicdom node - if (isNodeText(n)) { - // can't use end of container when no nextSibling (could be on next line), - // so use previousSibling or start of container and move forward. - setCollapsedBefore(s, n); - s.move("character", n.nodeValue.length); - } - else { - moveToElementText(s, n); - s.collapse(false); // to end - } - } - function getPointRange(point) { - var s = newRange(); - var n = point.node; - if (isNodeText(n)) { - setCollapsedBefore(s, n); - s.move("character", point.index); - } - else if (point.index == 0) { - setCollapsedBefore(s, n); - } - else { - setCollapsedAfter(s, n); - } - return s; - } - - if (selection) { - if (! hasIESelection()) { - return; // don't steal focus - } - - var startPoint = copyPoint(selection.startPoint); - var endPoint = copyPoint(selection.endPoint); - - // fix issue where selection can't be extended past end of line - // with shift-rightarrow or shift-downarrow - if (endPoint.index == endPoint.maxIndex && endPoint.node.nextSibling) { - endPoint.node = endPoint.node.nextSibling; - endPoint.index = 0; - endPoint.maxIndex = nodeMaxIndex(endPoint.node); - } - var range = getPointRange(startPoint); - range.setEndPoint("EndToEnd", getPointRange(endPoint)); - - // setting the selection in IE causes everything to scroll - // so that the selection is visible. if setting the selection - // definitely accomplishes nothing, don't do it. - function isEqualToDocumentSelection(rng) { - var browserSelection; - try { browserSelection = doc.selection; } catch (e) {} - if (! browserSelection) return false; - var rng2 = browserSelection.createRange(); - if (rng2.parentElement().ownerDocument != doc) return false; - if (rng.compareEndPoints("StartToStart", rng2) !== 0) return false; - if (rng.compareEndPoints("EndToEnd", rng2) !== 0) return false; - return true; - } - if (! isEqualToDocumentSelection(range)) { - //dmesg(toSource(selection)); - //dmesg(escapeHTML(doc.body.innerHTML)); - range.select(); - } - } - else { - try { doc.selection.empty(); } catch (e) {} - } - } - else { - // non-IE browser - var isCollapsed; - function pointToRangeBound(pt) { - var p = copyPoint(pt); - // Make sure Firefox cursor is deep enough; fixes cursor jumping when at top level, - // and also problem where cut/copy of a whole line selected with fake arrow-keys - // copies the next line too. - if (isCollapsed) { - function diveDeep() { - while (p.node.childNodes.length > 0) { - //&& (p.node == root || p.node.parentNode == root)) { - if (p.index == 0) { - p.node = p.node.firstChild; - p.maxIndex = nodeMaxIndex(p.node); - } - else if (p.index == p.maxIndex) { - p.node = p.node.lastChild; - p.maxIndex = nodeMaxIndex(p.node); - p.index = p.maxIndex; - } - else break; - } - } - // now fix problem where cursor at end of text node at end of span-like element - // with background doesn't seem to show up... - if (isNodeText(p.node) && p.index == p.maxIndex) { - var n = p.node; - while ((! n.nextSibling) && (n != root) && (n.parentNode != root)) { - n = n.parentNode; - } - if (n.nextSibling && - (! ((typeof n.nextSibling.tagName) == "string" && - n.nextSibling.tagName.toLowerCase() == "br")) && - (n != p.node) && (n != root) && (n.parentNode != root)) { - // found a parent, go to next node and dive in - p.node = n.nextSibling; - p.maxIndex = nodeMaxIndex(p.node); - p.index = 0; - diveDeep(); - } - } - // try to make sure insertion point is styled; - // also fixes other FF problems - if (! isNodeText(p.node)) { - diveDeep(); - } - } - /*// make sure Firefox cursor is shallow enough; - // to fix problem where "return" between two spans doesn't move the caret to - // the next line - // (decided against) - while (!(p.node.isRoot || p.node.parent().isRoot || p.node.parent().parent().isRoot)) { - if (p.index == 0 && ! p.node.prev()) { - p.node = p.node.parent(); - p.maxIndex = 1; - } - else if (p.index == p.maxIndex && ! p.node.next()) { - p.node = p.node.parent(); - p.maxIndex = 1; - p.index = 1; - } - else break; - } - if ((! p.node.isRoot) && (!p.node.parent().isRoot) && - (p.index == p.maxIndex) && p.node.next()) { - p.node = p.node.next(); - p.maxIndex = nodeMaxIndex(p.node); - p.index = 0; - }*/ - if (isNodeText(p.node)) { - return { container: p.node, offset: p.index }; - } - else { - // p.index in {0,1} - return { container: p.node.parentNode, offset: childIndex(p.node) + p.index }; - } - } - var browserSelection = window.getSelection(); - if (browserSelection) { - browserSelection.removeAllRanges(); - if (selection) { - isCollapsed = (selection.startPoint.node === selection.endPoint.node && - selection.startPoint.index === selection.endPoint.index); - var start = pointToRangeBound(selection.startPoint); - var end = pointToRangeBound(selection.endPoint); - - if ((!isCollapsed) && selection.focusAtStart && browserSelection.collapse && browserSelection.extend) { - // can handle "backwards"-oriented selection, shift-arrow-keys move start - // of selection - browserSelection.collapse(end.container, end.offset); - //console.trace(); - //console.log(htmlPrettyEscape(rep.alltext)); - //console.log("%o %o", rep.selStart, rep.selEnd); - //console.log("%o %d", start.container, start.offset); - browserSelection.extend(start.container, start.offset); - } - else { - var range = doc.createRange(); - range.setStart(start.container, start.offset); - range.setEnd(end.container, end.offset); - browserSelection.removeAllRanges(); - browserSelection.addRange(range); - } - } - } - } - } - - function childIndex(n) { - var idx = 0; - while (n.previousSibling) { - idx++; - n = n.previousSibling; - } - return idx; - } - - function fixView() { - // calling this method repeatedly should be fast - - if (getInnerWidth() == 0 || getInnerHeight() == 0) { - return; - } - - function setIfNecessary(obj, prop, value) { - if (obj[prop] != value) { - obj[prop] = value; - } - } - - var lineNumberWidth = sideDiv.firstChild.offsetWidth; - var newSideDivWidth = lineNumberWidth + LINE_NUMBER_PADDING_LEFT; - if (newSideDivWidth < MIN_LINEDIV_WIDTH) newSideDivWidth = MIN_LINEDIV_WIDTH; - iframePadLeft = EDIT_BODY_PADDING_LEFT; - if (hasLineNumbers) iframePadLeft += newSideDivWidth + LINE_NUMBER_PADDING_RIGHT; - setIfNecessary(iframe.style, "left", iframePadLeft+"px"); - setIfNecessary(sideDiv.style, "width", newSideDivWidth+"px"); - - for(var i=0;i<2;i++) { - var newHeight = root.clientHeight; - var newWidth = (browser.msie ? root.createTextRange().boundingWidth : root.clientWidth); - var viewHeight = getInnerHeight() - iframePadBottom - iframePadTop; - var viewWidth = getInnerWidth() - iframePadLeft - iframePadRight; - if (newHeight < viewHeight) { - newHeight = viewHeight; - if (browser.msie) setIfNecessary(outerWin.document.documentElement.style, 'overflowY', 'auto'); - } - else { - if (browser.msie) setIfNecessary(outerWin.document.documentElement.style, 'overflowY', 'scroll'); - } - if (doesWrap) { - newWidth = viewWidth; - } - else { - if (newWidth < viewWidth) newWidth = viewWidth; - } - if (newHeight > 32000) newHeight = 32000; - if (newWidth > 32000) newWidth = 32000; - setIfNecessary(iframe.style, "height", newHeight+"px"); - setIfNecessary(iframe.style, "width", newWidth+"px"); - setIfNecessary(sideDiv.style, "height", newHeight+"px"); - } - if (browser.mozilla) { - if (! doesWrap) { - // the body:display:table-cell hack makes mozilla do scrolling - // correctly by shrinking the <body> to fit around its content, - // but mozilla won't act on clicks below the body. We keep the - // style.height property set to the viewport height (editor height - // not including scrollbar), so it will never shrink so that part of - // the editor isn't clickable. - var body = root; - var styleHeight = viewHeight+"px"; - setIfNecessary(body.style, "height", styleHeight); - } - else { - setIfNecessary(root.style, "height", ""); - } - } - // if near edge, scroll to edge - var scrollX = getScrollX(); - var scrollY = getScrollY(); - var win = outerWin; - var r = 20; - /*if (scrollX <= iframePadLeft+r) win.scrollBy(-iframePadLeft-r, 0); - else if (getPageWidth() - scrollX - getInnerWidth() <= iframePadRight+r) - scrollBy(iframePadRight+r, 0);*/ - /*if (scrollY <= iframePadTop+r) win.scrollBy(0, -iframePadTop-r); - else if (getPageHeight() - scrollY - getInnerHeight() <= iframePadBottom+r) - scrollBy(0, iframePadBottom+r);*/ - - enforceEditability(); - - addClass(sideDiv, 'sidedivdelayed'); - } - - function getScrollXY() { - var win = outerWin; - var odoc = outerWin.document; - if (typeof(win.pageYOffset) == "number") { - return {x: win.pageXOffset, y: win.pageYOffset}; - } - var docel = odoc.documentElement; - if (docel && typeof(docel.scrollTop) == "number") { - return {x:docel.scrollLeft, y:docel.scrollTop}; - } - } - - function getScrollX() { - return getScrollXY().x; - } - - function getScrollY() { - return getScrollXY().y; - } - - function setScrollX(x) { - outerWin.scrollTo(x, getScrollY()); - } - - function setScrollY(y) { - outerWin.scrollTo(getScrollX(), y); - } - - function setScrollXY(x, y) { - outerWin.scrollTo(x, y); - } - - var _teardownActions = []; - function teardown() { - forEach(_teardownActions, function (a) { a(); }); - } - - bindEventHandler(window, "load", setup); - - function setDesignMode(newVal) { - try { - function setIfNecessary(target, prop, val) { - if (String(target[prop]).toLowerCase() != val) { - target[prop] = val; - return true; - } - return false; - } - if (browser.msie || browser.safari) { - setIfNecessary(root, 'contentEditable', (newVal ? 'true' : 'false')); - } - else { - var wasSet = setIfNecessary(doc, 'designMode', (newVal ? 'on' : 'off')); - if (wasSet && newVal && browser.opera) { - // turning on designMode clears event handlers - bindTheEventHandlers(); - } - } - return true; - } - catch (e) { - return false; - } - } - - var iePastedLines = null; - function handleIEPaste(evt) { - // Pasting in IE loses blank lines in a way that loses information; - // "one\n\ntwo\nthree" becomes "<p>one</p><p>two</p><p>three</p>", - // which becomes "one\ntwo\nthree". We can get the correct text - // from the clipboard directly, but we still have to let the paste - // happen to get the style information. - - var clipText = window.clipboardData && window.clipboardData.getData("Text"); - if (clipText && doc.selection) { - // this "paste" event seems to mess with the selection whether we try to - // stop it or not, so can't really do document-level manipulation now - // or in an idle call-stack. instead, use IE native manipulation - //function escapeLine(txt) { - //return processSpaces(escapeHTML(textify(txt))); - //} - //var newHTML = map(clipText.replace(/\r/g,'').split('\n'), escapeLine).join('<br>'); - //doc.selection.createRange().pasteHTML(newHTML); - //evt.preventDefault(); - - //iePastedLines = map(clipText.replace(/\r/g,'').split('\n'), textify); - } - } - - var inInternationalComposition = false; - - function handleCompositionEvent(evt) { - // international input events, fired in FF3, at least; allow e.g. Japanese input - if (evt.type == "compositionstart") { - inInternationalComposition = true; - } - else if (evt.type == "compositionend") { - inInternationalComposition = false; - } - } - - /*function handleTextEvent(evt) { - top.console.log("TEXT EVENT"); - inCallStackIfNecessary("handleTextEvent", function() { - observeChangesAroundSelection(); - }); - }*/ - - function bindTheEventHandlers() { - bindEventHandler(window, "unload", teardown); - bindEventHandler(document, "keydown", handleKeyEvent); - bindEventHandler(document, "keypress", handleKeyEvent); - bindEventHandler(document, "keyup", handleKeyEvent); - bindEventHandler(document, "click", handleClick); - bindEventHandler(root, "blur", handleBlur); - if (browser.msie) { - bindEventHandler(document, "click", handleIEOuterClick); - } - if (browser.msie) bindEventHandler(root, "paste", handleIEPaste); - if ((! browser.msie) && document.documentElement) { - bindEventHandler(document.documentElement, "compositionstart", handleCompositionEvent); - bindEventHandler(document.documentElement, "compositionend", handleCompositionEvent); - } - - /*bindEventHandler(window, "mousemove", function(e) { - if (e.pageX < 10) { - window.DEBUG_DONT_INCORP = (e.pageX < 2); - } - });*/ - } - - function handleIEOuterClick(evt) { - if ((evt.target.tagName||'').toLowerCase() != "html") { - return; - } - if (!(evt.pageY > root.clientHeight)) { - return; - } - - // click below the body - inCallStack("handleOuterClick", function() { - // put caret at bottom of doc - fastIncorp(11); - if (isCaret()) { // don't interfere with drag - var lastLine = rep.lines.length()-1; - var lastCol = rep.lines.atIndex(lastLine).text.length; - performSelectionChange([lastLine,lastCol],[lastLine,lastCol]); - } - }); - } - - function getClassArray(elem, optFilter) { - var bodyClasses = []; - (elem.className || '').replace(/\S+/g, function (c) { - if ((! optFilter) || (optFilter(c))) { - bodyClasses.push(c); - } - }); - return bodyClasses; - } - function setClassArray(elem, array) { - elem.className = array.join(' '); - } - function addClass(elem, className) { - var seen = false; - var cc = getClassArray(elem, function(c) { if (c == className) seen = true; return true; }); - if (! seen) { - cc.push(className); - setClassArray(elem, cc); - } - } - function removeClass(elem, className) { - var seen = false; - var cc = getClassArray(elem, function(c) { - if (c == className) { seen = true; return false; } return true; }); - if (seen) { - setClassArray(elem, cc); - } - } - function setClassPresence(elem, className, present) { - if (present) addClass(elem, className); - else removeClass(elem, className); - } - - function setup() { - doc = document; // defined as a var in scope outside - inCallStack("setup", function() { - var body = doc.getElementById("innerdocbody"); - root = body; // defined as a var in scope outside - - if (browser.mozilla) addClass(root, "mozilla"); - if (browser.safari) addClass(root, "safari"); - if (browser.msie) addClass(root, "msie"); - if (browser.msie) { - // cache CSS background images - try { - doc.execCommand("BackgroundImageCache", false, true); - } - catch (e) { - /* throws an error in some IE 6 but not others! */ - } - } - setClassPresence(root, "authorColors", true); - setClassPresence(root, "doesWrap", doesWrap); - - initDynamicCSS(); - - enforceEditability(); - - // set up dom and rep - while (root.firstChild) root.removeChild(root.firstChild); - var oneEntry = createDomLineEntry(""); - doRepLineSplice(0, rep.lines.length(), [oneEntry]); - insertDomLines(null, [oneEntry.domInfo], null); - rep.alines = Changeset.splitAttributionLines( - Changeset.makeAttribution("\n"), "\n"); - - bindTheEventHandlers(); - - }); - - scheduler.setTimeout(function() { - parent.readyFunc(); // defined in code that sets up the inner iframe - }, 0); - - isSetUp = true; - } - - function focus() { - window.focus(); - } - - function handleBlur(evt) { - if (browser.msie) { - // a fix: in IE, clicking on a control like a button outside the - // iframe can "blur" the editor, causing it to stop getting - // events, though typing still affects it(!). - setSelection(null); - } - } - - function bindEventHandler(target, type, func) { - var handler; - if ((typeof func._wrapper) != "function") { - func._wrapper = function(event) { - func(fixEvent(event || window.event || {})); - } - } - var handler = func._wrapper; - if (target.addEventListener) - target.addEventListener(type, handler, false); - else - target.attachEvent("on" + type, handler); - _teardownActions.push(function() { - unbindEventHandler(target, type, func); - }); - } - - function unbindEventHandler(target, type, func) { - var handler = func._wrapper; - if (target.removeEventListener) - target.removeEventListener(type, handler, false); - else - target.detachEvent("on" + type, handler); - } - - /*forEach(['rep', 'getCleanNodeByKey', 'getDirtyRanges', 'isNodeDirty', - 'getSelection', 'setSelection', 'updateBrowserSelectionFromRep', - 'makeRecentSet', 'resetProfiler', 'getScrollXY', 'makeIdleAction'], function (k) { - top['_'+k] = eval(k); - });*/ - - function getSelectionPointX(point) { - // doesn't work in wrap-mode - var node = point.node; - var index = point.index; - function leftOf(n) { return n.offsetLeft; } - function rightOf(n) { return n.offsetLeft + n.offsetWidth; } - if (! isNodeText(node)) { - if (index == 0) return leftOf(node); - else return rightOf(node); - } - else { - // we can get bounds of element nodes, so look for those. - // allow consecutive text nodes for robustness. - var charsToLeft = index; - var charsToRight = node.nodeValue.length - index; - var n; - for(n = node.previousSibling; n && isNodeText(n); n = n.previousSibling) - charsToLeft += n.nodeValue; - var leftEdge = (n ? rightOf(n) : leftOf(node.parentNode)); - for(n = node.nextSibling; n && isNodeText(n); n = n.nextSibling) - charsToRight += n.nodeValue; - var rightEdge = (n ? leftOf(n) : rightOf(node.parentNode)); - var frac = (charsToLeft / (charsToLeft + charsToRight)); - var pixLoc = leftEdge + frac*(rightEdge - leftEdge); - return Math.round(pixLoc); - } - } - - function getPageHeight() { - var win = outerWin; - var odoc = win.document; - if (win.innerHeight && win.scrollMaxY) return win.innerHeight + win.scrollMaxY; - else if (odoc.body.scrollHeight > odoc.body.offsetHeight) return odoc.body.scrollHeight; - else return odoc.body.offsetHeight; - } - - function getPageWidth() { - var win = outerWin; - var odoc = win.document; - if (win.innerWidth && win.scrollMaxX) return win.innerWidth + win.scrollMaxX; - else if (odoc.body.scrollWidth > odoc.body.offsetWidth) return odoc.body.scrollWidth; - else return odoc.body.offsetWidth; - } - - function getInnerHeight() { - var win = outerWin; - var odoc = win.document; - var h; - if (browser.opera) h = win.innerHeight; - else h = odoc.documentElement.clientHeight; - if (h) return h; - - // deal with case where iframe is hidden, hope that - // style.height of iframe container is set in px - return Number(editorInfo.frame.parentNode.style.height.replace(/[^0-9]/g,'') - || 0); - } - - function getInnerWidth() { - var win = outerWin; - var odoc = win.document; - return odoc.documentElement.clientWidth; - } - - function scrollNodeVerticallyIntoView(node) { - // requires element (non-text) node; - // if node extends above top of viewport or below bottom of viewport (or top of scrollbar), - // scroll it the minimum distance needed to be completely in view. - var win = outerWin; - var odoc = outerWin.document; - var distBelowTop = node.offsetTop + iframePadTop - win.scrollY; - var distAboveBottom = win.scrollY + getInnerHeight() - - (node.offsetTop +iframePadTop + node.offsetHeight); - - if (distBelowTop < 0) { - win.scrollBy(0, distBelowTop); - } - else if (distAboveBottom < 0) { - win.scrollBy(0, -distAboveBottom); - } - } - - function scrollXHorizontallyIntoView(pixelX) { - var win = outerWin; - var odoc = outerWin.document; - pixelX += iframePadLeft; - var distInsideLeft = pixelX - win.scrollX; - var distInsideRight = win.scrollX + getInnerWidth() - pixelX; - if (distInsideLeft < 0) { - win.scrollBy(distInsideLeft, 0); - } - else if (distInsideRight < 0) { - win.scrollBy(-distInsideRight+1, 0); - } - } - - function scrollSelectionIntoView() { - if (! rep.selStart) return; - fixView(); - var focusLine = (rep.selFocusAtStart ? rep.selStart[0] : rep.selEnd[0]); - scrollNodeVerticallyIntoView(rep.lines.atIndex(focusLine).lineNode); - if (! doesWrap) { - var browserSelection = getSelection(); - if (browserSelection) { - var focusPoint = (browserSelection.focusAtStart ? browserSelection.startPoint : - browserSelection.endPoint); - var selectionPointX = getSelectionPointX(focusPoint); - scrollXHorizontallyIntoView(selectionPointX); - fixView(); - } - } - } - - function getLineListType(lineNum) { - // get "list" attribute of first char of line - var aline = rep.alines[lineNum]; - if (aline) { - var opIter = Changeset.opIterator(aline); - if (opIter.hasNext()) { - return Changeset.opAttributeValue(opIter.next(), 'list', rep.apool) || ''; - } - } - return ''; - } - - function setLineListType(lineNum, listType) { - setLineListTypes([[lineNum, listType]]); - } - - function setLineListTypes(lineNumTypePairsInOrder) { - var loc = [0,0]; - var builder = Changeset.builder(rep.lines.totalWidth()); - for(var i=0;i<lineNumTypePairsInOrder.length;i++) { - var pair = lineNumTypePairsInOrder[i]; - var lineNum = pair[0]; - var listType = pair[1]; - buildKeepRange(builder, loc, (loc = [lineNum,0])); - if (getLineListType(lineNum)) { - // already a line marker - if (listType) { - // make different list type - buildKeepRange(builder, loc, (loc = [lineNum,1]), - [['list',listType]], rep.apool); - } - else { - // remove list marker - buildRemoveRange(builder, loc, (loc = [lineNum,1])); - } - } - else { - // currently no line marker - if (listType) { - // add a line marker - builder.insert('*', [['author', thisAuthor], - ['insertorder', 'first'], - ['list', listType]], rep.apool); - } - } - } - - var cs = builder.toString(); - if (! Changeset.isIdentity(cs)) { - performDocumentApplyChangeset(cs); - } - } - - function doInsertUnorderedList() { - if (! (rep.selStart && rep.selEnd)) { - return; - } - - var firstLine, lastLine; - firstLine = rep.selStart[0]; - lastLine = Math.max(firstLine, - rep.selEnd[0] - ((rep.selEnd[1] == 0) ? 1 : 0)); - - var allLinesAreList = true; - for(var n=firstLine;n<=lastLine;n++) { - if (! getLineListType(n)) { - allLinesAreList = false; - break; - } - } - - var mods = []; - for(var n=firstLine;n<=lastLine;n++) { - var t = getLineListType(n); - mods.push([n, allLinesAreList ? '' : (t ? t : 'bullet1')]); - } - setLineListTypes(mods); - } - - var mozillaFakeArrows = (browser.mozilla && (function() { - // In Firefox 2, arrow keys are unstable while DOM-manipulating - // operations are going on. Specifically, if an operation - // (computation that ties up the event queue) is going on (in the - // call-stack of some event, like a timeout) that at some point - // mutates nodes involved in the selection, then the arrow - // keypress may (randomly) move the caret to the beginning or end - // of the document. If the operation also mutates the selection - // range, the old selection or the new selection may be used, or - // neither. - - // As long as the arrow is pressed during the busy operation, it - // doesn't seem to matter that the keydown and keypress events - // aren't generated until afterwards, or that the arrow movement - // can still be stopped (meaning it hasn't been performed yet); - // Firefox must be preserving some old information about the - // selection or the DOM from when the key was initially pressed. - // However, it also doesn't seem to matter when the key was - // actually pressed relative to the time of the mutation within - // the prolonged operation. Also, even in very controlled tests - // (like a mutation followed by a long period of busyWaiting), the - // problem shows up often but not every time, with no discernable - // pattern. Who knows, it could have something to do with the - // caret-blinking timer, or DOM changes not being applied - // immediately. - - // This problem, mercifully, does not show up at all in IE or - // Safari. My solution is to have my own, full-featured arrow-key - // implementation for Firefox. - - // Note that the problem addressed here is potentially very subtle, - // especially if the operation is quick and is timed to usually happen - // when the user is idle. - - // features: - // - 'up' and 'down' arrows preserve column when passing through shorter lines - // - shift-arrows extend the "focus" point, which may be start or end of range - // - the focus point is kept horizontally and vertically scrolled into view - // - arrows without shift cause caret to move to beginning or end of selection (left,right) - // or move focus point up or down a line (up,down) - // - command-(left,right,up,down) on Mac acts like (line-start, line-end, doc-start, doc-end) - // - takes wrapping into account when doesWrap is true, i.e. up-arrow and down-arrow move - // between the virtual lines within a wrapped line; this was difficult, and unfortunately - // requires mutating the DOM to get the necessary information - - var savedFocusColumn = 0; // a value of 0 has no effect - var updatingSelectionNow = false; - - function getVirtualLineView(lineNum) { - var lineNode = rep.lines.atIndex(lineNum).lineNode; - while (lineNode.firstChild && isBlockElement(lineNode.firstChild)) { - lineNode = lineNode.firstChild; - } - return makeVirtualLineView(lineNode); - } - - function markerlessLineAndChar(line, chr) { - return [line, chr - rep.lines.atIndex(line).lineMarker]; - } - function markerfulLineAndChar(line, chr) { - return [line, chr + rep.lines.atIndex(line).lineMarker]; - } - - return { - notifySelectionChanged: function() { - if (! updatingSelectionNow) { - savedFocusColumn = 0; - } - }, - handleKeyEvent: function(evt) { - // returns "true" if handled - if (evt.type != "keypress") return false; - var keyCode = evt.keyCode; - if (keyCode < 37 || keyCode > 40) return false; - incorporateUserChanges(); - - if (!(rep.selStart && rep.selEnd)) return true; - - // {byWord,toEnd,normal} - var moveMode = (evt.altKey ? "byWord" : - (evt.ctrlKey ? "byWord" : - (evt.metaKey ? "toEnd" : - "normal"))); - - var anchorCaret = - markerlessLineAndChar(rep.selStart[0], rep.selStart[1]); - var focusCaret = - markerlessLineAndChar(rep.selEnd[0], rep.selEnd[1]); - var wasCaret = isCaret(); - if (rep.selFocusAtStart) { - var tmp = anchorCaret; anchorCaret = focusCaret; focusCaret = tmp; - } - var K_UP = 38, K_DOWN = 40, K_LEFT = 37, K_RIGHT = 39; - var dontMove = false; - if (wasCaret && ! evt.shiftKey) { - // collapse, will mutate both together - anchorCaret = focusCaret; - } - else if ((! wasCaret) && (! evt.shiftKey)) { - if (keyCode == K_LEFT) { - // place caret at beginning - if (rep.selFocusAtStart) anchorCaret = focusCaret; - else focusCaret = anchorCaret; - if (moveMode == "normal") dontMove = true; - } - else if (keyCode == K_RIGHT) { - // place caret at end - if (rep.selFocusAtStart) focusCaret = anchorCaret; - else anchorCaret = focusCaret; - if (moveMode == "normal") dontMove = true; - } - else { - // collapse, will mutate both together - anchorCaret = focusCaret; - } - } - if (! dontMove) { - function lineLength(i) { - var entry = rep.lines.atIndex(i); - return entry.text.length - entry.lineMarker; - } - function lineText(i) { - var entry = rep.lines.atIndex(i); - return entry.text.substring(entry.lineMarker); - } - - if (keyCode == K_UP || keyCode == K_DOWN) { - var up = (keyCode == K_UP); - var canChangeLines = ((up && focusCaret[0]) || - ((!up) && focusCaret[0] < rep.lines.length()-1)); - var virtualLineView, virtualLineSpot, canChangeVirtualLines = false; - if (doesWrap) { - virtualLineView = getVirtualLineView(focusCaret[0]); - virtualLineSpot = virtualLineView.getVLineAndOffsetForChar(focusCaret[1]); - canChangeVirtualLines = ((up && virtualLineSpot.vline > 0) || - ((!up) && virtualLineSpot.vline < ( - virtualLineView.getNumVirtualLines() - 1))); - } - var newColByVirtualLineChange; - if (moveMode == "toEnd") { - if (up) { - focusCaret[0] = 0; - focusCaret[1] = 0; - } - else { - focusCaret[0] = rep.lines.length()-1; - focusCaret[1] = lineLength(focusCaret[0]); - } - } - else if (moveMode == "byWord") { - // move by "paragraph", a feature that Firefox lacks but IE and Safari both have - if (up) { - if (focusCaret[1] == 0 && canChangeLines) { - focusCaret[0]--; - focusCaret[1] = 0; - } - else focusCaret[1] = 0; - } - else { - var lineLen = lineLength(focusCaret[0]); - if (browser.windows) { - if (canChangeLines) { - focusCaret[0]++; - focusCaret[1] = 0; - } - else { - focusCaret[1] = lineLen; - } - } - else { - if (focusCaret[1] == lineLen && canChangeLines) { - focusCaret[0]++; - focusCaret[1] = lineLength(focusCaret[0]); - } - else { - focusCaret[1] = lineLen; - } - } - } - savedFocusColumn = 0; - } - else if (canChangeVirtualLines) { - var vline = virtualLineSpot.vline; - var offset = virtualLineSpot.offset; - if (up) vline--; - else vline++; - if (savedFocusColumn > offset) offset = savedFocusColumn; - else { - savedFocusColumn = offset; - } - var newSpot = virtualLineView.getCharForVLineAndOffset(vline, offset); - focusCaret[1] = newSpot.lineChar; - } - else if (canChangeLines) { - if (up) focusCaret[0]--; - else focusCaret[0]++; - var offset = focusCaret[1]; - if (doesWrap) { - offset = virtualLineSpot.offset; - } - if (savedFocusColumn > offset) offset = savedFocusColumn; - else { - savedFocusColumn = offset; - } - if (doesWrap) { - var newLineView = getVirtualLineView(focusCaret[0]); - var vline = (up ? newLineView.getNumVirtualLines()-1 : 0); - var newSpot = newLineView.getCharForVLineAndOffset(vline, offset); - focusCaret[1] = newSpot.lineChar; - } - else { - var lineLen = lineLength(focusCaret[0]); - if (offset > lineLen) offset = lineLen; - focusCaret[1] = offset; - } - } - else { - if (up) focusCaret[1] = 0; - else focusCaret[1] = lineLength(focusCaret[0]); - savedFocusColumn = 0; - } - } - else if (keyCode == K_LEFT || keyCode == K_RIGHT) { - var left = (keyCode == K_LEFT); - if (left) { - if (moveMode == "toEnd") focusCaret[1] = 0; - else if (focusCaret[1] > 0) { - if (moveMode == "byWord") { - focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], false); - } - else { - focusCaret[1]--; - } - } - else if (focusCaret[0] > 0) { - focusCaret[0]--; - focusCaret[1] = lineLength(focusCaret[0]); - if (moveMode == "byWord") { - focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], false); - } - } - } - else { - var lineLen = lineLength(focusCaret[0]); - if (moveMode == "toEnd") focusCaret[1] = lineLen; - else if (focusCaret[1] < lineLen) { - if (moveMode == "byWord") { - focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], true); - } - else { - focusCaret[1]++; - } - } - else if (focusCaret[0] < rep.lines.length()-1) { - focusCaret[0]++; - focusCaret[1] = 0; - if (moveMode == "byWord") { - focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], true); - } - } - } - savedFocusColumn = 0; - } - } - - var newSelFocusAtStart = ((focusCaret[0] < anchorCaret[0]) || - (focusCaret[0] == anchorCaret[0] && - focusCaret[1] < anchorCaret[1])); - var newSelStart = (newSelFocusAtStart ? focusCaret : anchorCaret); - var newSelEnd = (newSelFocusAtStart ? anchorCaret : focusCaret); - updatingSelectionNow = true; - performSelectionChange(markerfulLineAndChar(newSelStart[0], - newSelStart[1]), - markerfulLineAndChar(newSelEnd[0], - newSelEnd[1]), - newSelFocusAtStart); - updatingSelectionNow = false; - currentCallStack.userChangedSelection = true; - return true; - } - }; - })()); - - - // stolen from jquery-1.2.1 - function fixEvent(event) { - // store a copy of the original event object - // and clone to set read-only properties - var originalEvent = event; - event = extend({}, originalEvent); - - // add preventDefault and stopPropagation since - // they will not work on the clone - event.preventDefault = function() { - // if preventDefault exists run it on the original event - if (originalEvent.preventDefault) - originalEvent.preventDefault(); - // otherwise set the returnValue property of the original event to false (IE) - originalEvent.returnValue = false; - }; - event.stopPropagation = function() { - // if stopPropagation exists run it on the original event - if (originalEvent.stopPropagation) - originalEvent.stopPropagation(); - // otherwise set the cancelBubble property of the original event to true (IE) - originalEvent.cancelBubble = true; - }; - - // Fix target property, if necessary - if ( !event.target && event.srcElement ) - event.target = event.srcElement; - - // check if target is a textnode (safari) - if (browser.safari && event.target.nodeType == 3) - event.target = originalEvent.target.parentNode; - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && event.fromElement ) - event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - var e = document.documentElement, b = document.body; - event.pageX = event.clientX + (e && e.scrollLeft || b.scrollLeft || 0); - event.pageY = event.clientY + (e && e.scrollTop || b.scrollTop || 0); - } - - // Add which for key events - if ( !event.which && (event.charCode || event.keyCode) ) - event.which = event.charCode || event.keyCode; - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) - event.metaKey = event.ctrlKey; - - // Add which for click: 1 == left; 2 == middle; 3 == right - // Note: button is not normalized, so don't use it - if ( !event.which && event.button ) - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); - - return event; - } - - var lineNumbersShown; - var sideDivInner; - function initLineNumbers() { - lineNumbersShown = 1; - sideDiv.innerHTML = - '<table border="0" cellpadding="0" cellspacing="0" align="right">'+ - '<tr><td id="sidedivinner"><div>1</div></td></tr></table>'; - sideDivInner = outerWin.document.getElementById("sidedivinner"); - } - - function updateLineNumbers() { - var newNumLines = rep.lines.length(); - if (newNumLines < 1) newNumLines = 1; - if (newNumLines != lineNumbersShown) { - var container = sideDivInner; - var odoc = outerWin.document; - while (lineNumbersShown < newNumLines) { - lineNumbersShown++; - var n = lineNumbersShown; - var div = odoc.createElement("DIV"); - div.appendChild(odoc.createTextNode(String(n))); - container.appendChild(div); - } - while (lineNumbersShown > newNumLines) { - container.removeChild(container.lastChild); - lineNumbersShown--; - } - } - - if (currentCallStack && currentCallStack.domClean) { - var a = sideDivInner.firstChild; - var b = doc.body.firstChild; - while (a && b) { - var h = (b.clientHeight || b.offsetHeight); - if (b.nextSibling) { - // when text is zoomed in mozilla, divs have fractional - // heights (though the properties are always integers) - // and the line-numbers don't line up unless we pay - // attention to where the divs are actually placed... - // (also: padding on TTs/SPANs in IE...) - h = b.nextSibling.offsetTop - b.offsetTop; - } - if (h) { - var hpx = h+"px"; - if (a.style.height != hpx) - a.style.height = hpx; - } - a = a.nextSibling; - b = b.nextSibling; - } - - // fix if first line has margin (f.e. h1 in first line) - sideDivInner.firstChild.style.marginTop = - (doc.body.firstChild.offsetTop - sideDivInner.firstChild.offsetTop + - parseInt(sideDivInner.firstChild.style.marginTop + "0")) + "px"; - } - } - -}; - -OUTER(this); |