diff options
Diffstat (limited to 'infrastructure/ace/www/virtual_lines.js')
-rw-r--r-- | infrastructure/ace/www/virtual_lines.js | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/infrastructure/ace/www/virtual_lines.js b/infrastructure/ace/www/virtual_lines.js new file mode 100644 index 0000000..86e3dea --- /dev/null +++ b/infrastructure/ace/www/virtual_lines.js @@ -0,0 +1,287 @@ +/** + * 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 makeVirtualLineView(lineNode) { + + // how much to jump forward or backward at once in a charSeeker before + // constructing a DOM node and checking the coordinates (which takes a + // significant fraction of a millisecond). From the + // coordinates and the approximate line height we can estimate how + // many lines we have moved. We risk being off if the number of lines + // we move is on the order of the line height in pixels. Fortunately, + // when the user boosts the font-size they increase both. + var maxCharIncrement = 20; + var seekerAtEnd = null; + + function getNumChars() { + return lineNode.textContent.length; + } + + function getNumVirtualLines() { + if (! seekerAtEnd) { + var seeker = makeCharSeeker(); + seeker.forwardByWhile(maxCharIncrement); + seekerAtEnd = seeker; + } + return seekerAtEnd.getVirtualLine() + 1; + } + + function getVLineAndOffsetForChar(lineChar) { + var seeker = makeCharSeeker(); + seeker.forwardByWhile(maxCharIncrement, null, lineChar); + var theLine = seeker.getVirtualLine(); + seeker.backwardByWhile(8, function() { return seeker.getVirtualLine() == theLine; }); + seeker.forwardByWhile(1, function() { return seeker.getVirtualLine() != theLine; }); + var lineStartChar = seeker.getOffset(); + return {vline:theLine, offset:(lineChar - lineStartChar)}; + } + + function getCharForVLineAndOffset(vline, offset) { + // returns revised vline and offset as well as absolute char index within line. + // if offset is beyond end of line, for example, will give new offset at end of line. + var seeker = makeCharSeeker(); + // go to start of line + seeker.binarySearch(function() { + return seeker.getVirtualLine() >= vline; + }); + var lineStart = seeker.getOffset(); + var theLine = seeker.getVirtualLine(); + // go to offset, overshooting the virtual line only if offset is too large for it + seeker.forwardByWhile(maxCharIncrement, null, lineStart+offset); + // get back into line + seeker.backwardByWhile(1, function() { return seeker.getVirtualLine() != theLine; }, lineStart); + var lineChar = seeker.getOffset(); + var theOffset = lineChar - lineStart; + // handle case of last virtual line; should be able to be at end of it + if (theOffset < offset && theLine == (getNumVirtualLines()-1)) { + var lineLen = getNumChars(); + theOffset += lineLen-lineChar; + lineChar = lineLen; + } + + return { vline:theLine, offset:theOffset, lineChar:lineChar }; + } + + return {getNumVirtualLines:getNumVirtualLines, getVLineAndOffsetForChar:getVLineAndOffsetForChar, + getCharForVLineAndOffset:getCharForVLineAndOffset, + makeCharSeeker: function() { return makeCharSeeker(); } }; + + function deepFirstChildTextNode(nd) { + nd = nd.firstChild; + while (nd && nd.firstChild) nd = nd.firstChild; + if (nd.data) return nd; + return null; + } + + function makeCharSeeker(/*lineNode*/) { + + function charCoords(tnode, i) { + var container = tnode.parentNode; + + // treat space specially; a space at the end of a virtual line + // will have weird coordinates + var isSpace = (tnode.nodeValue.charAt(i) === " "); + if (isSpace) { + if (i == 0) { + if (container.previousSibling && deepFirstChildTextNode(container.previousSibling)) { + tnode = deepFirstChildTextNode(container.previousSibling); + i = tnode.length-1; + container = tnode.parentNode; + } + else { + return {top:container.offsetTop, left:container.offsetLeft}; + } + } + else { + i--; // use previous char + } + } + + + var charWrapper = document.createElement("SPAN"); + + // wrap the character + var tnodeText = tnode.nodeValue; + var frag = document.createDocumentFragment(); + frag.appendChild(document.createTextNode(tnodeText.substring(0, i))); + charWrapper.appendChild(document.createTextNode(tnodeText.substr(i, 1))); + frag.appendChild(charWrapper); + frag.appendChild(document.createTextNode(tnodeText.substring(i+1))); + container.replaceChild(frag, tnode); + + var result = {top:charWrapper.offsetTop, + left:charWrapper.offsetLeft + (isSpace ? charWrapper.offsetWidth : 0), + height:charWrapper.offsetHeight}; + + while (container.firstChild) container.removeChild(container.firstChild); + container.appendChild(tnode); + + return result; + } + + var lineText = lineNode.textContent; + var lineLength = lineText.length; + + var curNode = null; + var curChar = 0; + var curCharWithinNode = 0 + var curTop; + var curLeft; + var approxLineHeight; + var whichLine = 0; + + function nextNode() { + var n = curNode; + if (! n) n = lineNode.firstChild; + else n = n.nextSibling; + while (n && ! deepFirstChildTextNode(n)) { + n = n.nextSibling; + } + return n; + } + function prevNode() { + var n = curNode; + if (! n) n = lineNode.lastChild; + else n = n.previousSibling; + while (n && ! deepFirstChildTextNode(n)) { + n = n.previousSibling; + } + return n; + } + + var seeker; + if (lineLength > 0) { + curNode = nextNode(); + var firstCharData = charCoords(deepFirstChildTextNode(curNode), 0); + approxLineHeight = firstCharData.height; + curTop = firstCharData.top; + curLeft = firstCharData.left; + + function updateCharData(tnode, i) { + var coords = charCoords(tnode, i); + whichLine += Math.round((coords.top - curTop) / approxLineHeight); + curTop = coords.top; + curLeft = coords.left; + } + + seeker = { + forward: function(numChars) { + var oldChar = curChar; + var newChar = curChar + numChars; + if (newChar > (lineLength-1)) + newChar = lineLength-1; + while (curChar < newChar) { + var curNodeLength = deepFirstChildTextNode(curNode).length; + var toGo = curNodeLength - curCharWithinNode; + if (curChar + toGo > newChar || ! nextNode()) { + // going to next node would be too far + var n = newChar - curChar; + if (n >= toGo) n = toGo-1; + curChar += n; + curCharWithinNode += n; + break; + } + else { + // go to next node + curChar += toGo; + curCharWithinNode = 0; + curNode = nextNode(); + } + } + updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode); + return curChar - oldChar; + }, + backward: function(numChars) { + var oldChar = curChar; + var newChar = curChar - numChars; + if (newChar < 0) newChar = 0; + while (curChar > newChar) { + if (curChar - curCharWithinNode <= newChar || !prevNode()) { + // going to prev node would be too far + var n = curChar - newChar; + if (n > curCharWithinNode) n = curCharWithinNode; + curChar -= n; + curCharWithinNode -= n; + break; + } + else { + // go to prev node + curChar -= curCharWithinNode+1; + curNode = prevNode(); + curCharWithinNode = deepFirstChildTextNode(curNode).length-1; + } + } + updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode); + return oldChar - curChar; + }, + getVirtualLine: function() { return whichLine; }, + getLeftCoord: function() { return curLeft; } + }; + } + else { + curLeft = lineNode.offsetLeft; + seeker = { forward: function(numChars) { return 0; }, + backward: function(numChars) { return 0; }, + getVirtualLine: function() { return 0; }, + getLeftCoord: function() { return curLeft; } + }; + } + seeker.getOffset = function() { return curChar; }; + seeker.getLineLength = function() { return lineLength; }; + seeker.toString = function() { + return "seeker[curChar: "+curChar+"("+lineText.charAt(curChar)+"), left: "+seeker.getLeftCoord()+", vline: "+seeker.getVirtualLine()+"]"; + }; + + function moveByWhile(isBackward, amount, optCondFunc, optCharLimit) { + var charsMovedLast = null; + var hasCondFunc = ((typeof optCondFunc) == "function"); + var condFunc = optCondFunc; + var hasCharLimit = ((typeof optCharLimit) == "number"); + var charLimit = optCharLimit; + while (charsMovedLast !== 0 && ((! hasCondFunc) || condFunc())) { + var toMove = amount; + if (hasCharLimit) { + var untilLimit = (isBackward ? curChar - charLimit : charLimit - curChar); + if (untilLimit < toMove) toMove = untilLimit; + } + if (toMove < 0) break; + charsMovedLast = (isBackward ? seeker.backward(toMove) : seeker.forward(toMove)); + } + } + + seeker.forwardByWhile = function(amount, optCondFunc, optCharLimit) { + moveByWhile(false, amount, optCondFunc, optCharLimit); + } + seeker.backwardByWhile = function(amount, optCondFunc, optCharLimit) { + moveByWhile(true, amount, optCondFunc, optCharLimit); + } + seeker.binarySearch = function(condFunc) { + // returns index of boundary between false chars and true chars; + // positions seeker at first true char, or else last char + var trueFunc = condFunc; + var falseFunc = function() { return ! condFunc(); }; + seeker.forwardByWhile(20, falseFunc); + seeker.backwardByWhile(20, trueFunc); + seeker.forwardByWhile(10, falseFunc); + seeker.backwardByWhile(5, trueFunc); + seeker.forwardByWhile(1, falseFunc); + return seeker.getOffset() + (condFunc() ? 0 : 1); + } + + return seeker; + } + +} |