AtteanX-Endpoint

 view release on metacpan or  search on metacpan

share/endpoint/www/js/editor.js  view on Meta::CPAN

          end = select.cursorPos(this.container, false);
      if (!start || !end) return;

      end = this.replaceRange(start, end, text);
      select.setCursorPos(this.container, end);
      webkitLastLineHack(this.container);
    },

    cursorCoords: function(start) {
      var sel = select.cursorPos(this.container, start);
      if (!sel) return null;
      var off = sel.offset, node = sel.node, self = this;
      function measureFromNode(node, xOffset) {
        var y = -(document.body.scrollTop || document.documentElement.scrollTop || 0),
            x = -(document.body.scrollLeft || document.documentElement.scrollLeft || 0) + xOffset;
        forEach([node, window.frameElement], function(n) {
          while (n) {x += n.offsetLeft; y += n.offsetTop;n = n.offsetParent;}
        });
        return {x: x, y: y, yBot: y + node.offsetHeight};
      }
      function withTempNode(text, f) {
        var node = document.createElement("SPAN");
        node.appendChild(document.createTextNode(text));
        try {return f(node);}
        finally {if (node.parentNode) node.parentNode.removeChild(node);}
      }

      while (off) {
        node = node ? node.nextSibling : this.container.firstChild;
        var txt = nodeText(node);
        if (off < txt.length)
          return withTempNode(txt.substr(0, off), function(tmp) {
            tmp.style.position = "absolute"; tmp.style.visibility = "hidden";
            tmp.className = node.className;
            self.container.appendChild(tmp);
            return measureFromNode(node, tmp.offsetWidth);
          });
        off -= txt.length;
      }
      if (node && isSpan(node))
        return measureFromNode(node, node.offsetWidth);
      else if (node && node.nextSibling && isSpan(node.nextSibling))
        return measureFromNode(node.nextSibling, 0);
      else
        return withTempNode("\u200b", function(tmp) {
          if (node) node.parentNode.insertBefore(tmp, node.nextSibling);
          else self.container.insertBefore(tmp, self.container.firstChild);
          return measureFromNode(tmp, 0);
        });
    },

    reroutePasteEvent: function() {
      if (this.capturingPaste || window.opera || (gecko && gecko >= 20101026)) return;
      this.capturingPaste = true;
      var te = window.frameElement.CodeMirror.textareaHack;
      parent.focus();
      te.value = "";
      te.focus();

      var self = this;
      this.parent.setTimeout(function() {
        self.capturingPaste = false;
        window.focus();
        if (self.selectionSnapshot) // IE hack
          window.select.setBookmark(self.container, self.selectionSnapshot);
        var text = te.value;
        if (text) {
          self.replaceSelection(text);
          select.scrollToCursor(self.container);
        }
      }, 10);
    },

    replaceRange: function(from, to, text) {
      var lines = asEditorLines(text);
      lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0];
      var lastLine = lines[lines.length - 1];
      lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset);
      var end = this.history.nodeAfter(to.node);
      this.history.push(from.node, end, lines);
      return {node: this.history.nodeBefore(end),
              offset: lastLine.length};
    },

    getSearchCursor: function(string, fromCursor, caseFold) {
      return new SearchCursor(this, string, fromCursor, caseFold);
    },

    // Re-indent the whole buffer
    reindent: function() {
      if (this.container.firstChild)
        this.indentRegion(null, this.container.lastChild);
    },

    reindentSelection: function(direction) {
      if (!select.somethingSelected()) {
        this.indentAtCursor(direction);
      }
      else {
        var start = select.selectionTopNode(this.container, true),
            end = select.selectionTopNode(this.container, false);
        if (start === false || end === false) return;
        this.indentRegion(start, end, direction);
      }
    },

    grabKeys: function(eventHandler, filter) {
      this.frozen = eventHandler;
      this.keyFilter = filter;
    },
    ungrabKeys: function() {
      this.frozen = "leave";
    },

    setParser: function(name, parserConfig) {
      Editor.Parser = window[name];
      parserConfig = parserConfig || this.options.parserConfig;
      if (parserConfig && Editor.Parser.configure)
        Editor.Parser.configure(parserConfig);

      if (this.container.firstChild) {

share/endpoint/www/js/editor.js  view on Meta::CPAN

      else if (code == 35 && !event.shiftKey && !event.ctrlKey) { // end
        if (this.end()) event.stop();
      }
      // Only in Firefox is the default behavior for PgUp/PgDn correct.
      else if (code == 33 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgUp
        if (this.pageUp()) event.stop();
      }
      else if (code == 34 && !event.shiftKey && !event.ctrlKey && !gecko) {  // PgDn
        if (this.pageDown()) event.stop();
      }
      else if ((code == 219 || code == 221) && event.ctrlKey && !event.altKey) { // [, ]
        this.highlightParens(event.shiftKey, true);
        event.stop();
      }
      else if (event.metaKey && !event.shiftKey && (code == 37 || code == 39)) { // Meta-left/right
        var cursor = select.selectionTopNode(this.container);
        if (cursor === false || !this.container.firstChild) return;

        if (code == 37) select.focusAfterNode(startOfLine(cursor), this.container);
        else {
          var end = endOfLine(cursor, this.container);
          select.focusAfterNode(end ? end.previousSibling : this.container.lastChild, this.container);
        }
        event.stop();
      }
      else if ((event.ctrlKey || event.metaKey) && !event.altKey) {
        if ((event.shiftKey && code == 90) || code == 89) { // shift-Z, Y
          select.scrollToNode(this.history.redo());
          event.stop();
        }
        else if (code == 90 || (safari && code == 8)) { // Z, backspace
          select.scrollToNode(this.history.undo());
          event.stop();
        }
        else if (code == 83 && this.options.saveFunction) { // S
          this.options.saveFunction();
          event.stop();
        }
        else if (code == 86 && !mac) { // V
          this.reroutePasteEvent();
        }
      }
    },

    // Check for characters that should re-indent the current line,
    // and prevent Opera from handling enter and tab anyway.
    keyPress: function(event) {
      var electric = this.options.electricChars && Editor.Parser.electricChars, self = this;
      // Hack for Opera, and Firefox on OS X, in which stopping a
      // keydown event does not prevent the associated keypress event
      // from happening, so we have to cancel enter and tab again
      // here.
      if ((this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode || event.code, event))) ||
          event.code == 13 || (event.code == 9 && this.options.tabMode != "default") ||
          (event.code == 32 && event.shiftKey && this.options.tabMode == "default"))
        event.stop();
      else if (mac && (event.ctrlKey || event.metaKey) && event.character == "v") {
        this.reroutePasteEvent();
      }
      else if (electric && electric.indexOf(event.character) != -1)
        this.parent.setTimeout(function(){self.indentAtCursor(null);}, 0);
      // Work around a bug where pressing backspace at the end of a
      // line, or delete at the start, often causes the cursor to jump
      // to the start of the line in Opera 10.60.
      else if (brokenOpera) {
        if (event.code == 8) { // backspace
          var sel = select.selectionTopNode(this.container), self = this,
              next = sel ? sel.nextSibling : this.container.firstChild;
          if (sel !== false && next && isBR(next))
            this.parent.setTimeout(function(){
              if (select.selectionTopNode(self.container) == next)
                select.focusAfterNode(next.previousSibling, self.container);
            }, 20);
        }
        else if (event.code == 46) { // delete
          var sel = select.selectionTopNode(this.container), self = this;
          if (sel && isBR(sel)) {
            this.parent.setTimeout(function(){
              if (select.selectionTopNode(self.container) != sel)
                select.focusAfterNode(sel, self.container);
            }, 20);
          }
        }
      }
      // In 533.* WebKit versions, when the document is big, typing
      // something at the end of a line causes the browser to do some
      // kind of stupid heavy operation, creating delays of several
      // seconds before the typed characters appear. This very crude
      // hack inserts a temporary zero-width space after the cursor to
      // make it not be at the end of the line.
      else if (slowWebkit) {
        var sel = select.selectionTopNode(this.container),
            next = sel ? sel.nextSibling : this.container.firstChild;
        // Doesn't work on empty lines, for some reason those always
        // trigger the delay.
        if (sel && next && isBR(next) && !isBR(sel)) {
          var cheat = document.createTextNode("\u200b");
          this.container.insertBefore(cheat, next);
          this.parent.setTimeout(function() {
            if (cheat.nodeValue == "\u200b") removeElement(cheat);
            else cheat.nodeValue = cheat.nodeValue.replace("\u200b", "");
          }, 20);
        }
      }

      // Magic incantation that works abound a webkit bug when you
      // can't type on a blank line following a line that's wider than
      // the window.
      if (webkit && !this.options.textWrapping)
        setTimeout(function () {
          var node = select.selectionTopNode(self.container, true);
          if (node && node.nodeType == 3 && node.previousSibling && isBR(node.previousSibling)
              && node.nextSibling && isBR(node.nextSibling))
            node.parentNode.replaceChild(document.createElement("BR"), node.previousSibling);
        }, 50);
    },

    // Mark the node at the cursor dirty when a non-safe key is
    // released.
    keyUp: function(event) {
      this.cursorActivity(isSafeKey(event.keyCode));
    },

    // Indent the line following a given <br>, or null for the first
    // line. If given a <br> element, this must have been highlighted
    // so that it has an indentation method. Returns the whitespace
    // element that has been modified or created (if any).
    indentLineAfter: function(start, direction) {
      function whiteSpaceAfter(node) {
        var ws = node ? node.nextSibling : self.container.firstChild;
        if (!ws || !hasClass(ws, "whitespace")) return null;
        return ws;
      }

      // whiteSpace is the whitespace span at the start of the line,
      // or null if there is no such node.
      var self = this, whiteSpace = whiteSpaceAfter(start);
      var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0;

      var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
      if (direction == "keep") {
        if (start) {
          var prevWS = whiteSpaceAfter(startOfLine(start.previousSibling))
          if (prevWS) newIndent = prevWS.currentText.length;
        }
      }
      else {
        // Sometimes the start of the line can influence the correct
        // indentation, so we retrieve it.
        var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : "";

        // Ask the lexical context for the correct indentation, and
        // compute how much this differs from the current indentation.
        if (direction != null && this.options.tabMode == "shift")
          newIndent = direction ? curIndent + indentUnit : Math.max(0, curIndent - indentUnit)
        else if (start)
          newIndent = start.indentation(nextChars, curIndent, direction);
        else if (Editor.Parser.firstIndentation)
          newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction);
      }
      
      var indentDiff = newIndent - curIndent;

      // If there is too much, this is just a matter of shrinking a span.
      if (indentDiff < 0) {
        if (newIndent == 0) {
          if (firstText) select.snapshotMove(whiteSpace.firstChild, firstText.firstChild || firstText, 0);
          removeElement(whiteSpace);
          whiteSpace = null;
        }

share/endpoint/www/js/editor.js  view on Meta::CPAN

      var cur = select.selectionTopNode(this.container, true), start = cur;
      if (cur === false || !(!cur || cur.isPart || isBR(cur)) || !this.container.firstChild)
        return false;

      while (cur && !isBR(cur)) cur = cur.previousSibling;
      var next = cur ? cur.nextSibling : this.container.firstChild;
      if (next && next != start && next.isPart && hasClass(next, "whitespace"))
        select.focusAfterNode(next, this.container);
      else
        select.focusAfterNode(cur, this.container);

      select.scrollToCursor(this.container);
      return true;
    },

    // Some browsers (Opera) don't manage to handle the end key
    // properly in the face of vertical scrolling.
    end: function() {
      var cur = select.selectionTopNode(this.container, true);
      if (cur === false) return false;
      cur = endOfLine(cur, this.container);
      if (!cur) return false;
      select.focusAfterNode(cur.previousSibling, this.container);
      select.scrollToCursor(this.container);
      return true;
    },

    pageUp: function() {
      var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
      if (line === false || scrollAmount === false) return false;
      // Try to keep one line on the screen.
      scrollAmount -= 2;
      for (var i = 0; i < scrollAmount; i++) {
        line = this.prevLine(line);
        if (line === false) break;
      }
      if (i == 0) return false; // Already at first line
      select.setCursorPos(this.container, {node: line, offset: 0});
      select.scrollToCursor(this.container);
      return true;
    },

    pageDown: function() {
      var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
      if (line === false || scrollAmount === false) return false;
      // Try to move to the last line of the current page.
      scrollAmount -= 2;
      for (var i = 0; i < scrollAmount; i++) {
        var nextLine = this.nextLine(line);
        if (nextLine === false) break;
        line = nextLine;
      }
      if (i == 0) return false; // Already at last line
      select.setCursorPos(this.container, {node: line, offset: 0});
      select.scrollToCursor(this.container);
      return true;
    },

    // Delay (or initiate) the next paren highlight event.
    scheduleParenHighlight: function() {
      if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
      var self = this;
      this.parenEvent = this.parent.setTimeout(function(){self.highlightParens();}, 300);
    },

    // Take the token before the cursor. If it contains a character in
    // '()[]{}', search for the matching paren/brace/bracket, and
    // highlight them in green for a moment, or red if no proper match
    // was found.
    highlightParens: function(jump, fromKey) {
      var self = this;
      // give the relevant nodes a colour.
      function highlight(node, ok) {
        if (!node) return;
        if (self.options.markParen) {
          self.options.markParen(node, ok);
        }
        else {
          node.style.fontWeight = "bold";
          node.style.color = ok ? "#8F8" : "#F88";
        }
      }
      function unhighlight(node) {
        if (!node) return;
        if (self.options.unmarkParen) {
          self.options.unmarkParen(node);
        }
        else {
          node.style.fontWeight = "";
          node.style.color = "";
        }
      }
      if (!fromKey && self.highlighted) {
        unhighlight(self.highlighted[0]);
        unhighlight(self.highlighted[1]);
      }

      if (!window || !window.parent || !window.select) return;
      // Clear the event property.
      if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
      this.parenEvent = null;

      // Extract a 'paren' from a piece of text.
      function paren(node) {
        if (node.currentText) {
          var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/);
          return match && match[1];
        }
      }
      // Determine the direction a paren is facing.
      function forward(ch) {
        return /[\(\[\{]/.test(ch);
      }

      var ch, cursor = select.selectionTopNode(this.container, true);
      if (!cursor || !this.highlightAtCursor()) return;
      cursor = select.selectionTopNode(this.container, true);
      if (!(cursor && ((ch = paren(cursor)) || (cursor = cursor.nextSibling) && (ch = paren(cursor)))))
        return;
      // We only look for tokens with the same className.
      var className = cursor.className, dir = forward(ch), match = matching[ch];

      // Since parts of the document might not have been properly
      // highlighted, and it is hard to know in advance which part we
      // have to scan, we just try, and when we find dirty nodes we
      // abort, parse them, and re-try.
      function tryFindMatch() {
        var stack = [], ch, ok = true;
        for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) {
          if (runner.className == className && isSpan(runner) && (ch = paren(runner))) {
            if (forward(ch) == dir)
              stack.push(ch);
            else if (!stack.length)
              ok = false;
            else if (stack.pop() != matching[ch])
              ok = false;
            if (!stack.length) break;
          }
          else if (runner.dirty || !isSpan(runner) && !isBR(runner)) {
            return {node: runner, status: "dirty"};
          }
        }
        return {node: runner, status: runner && ok};
      }

      while (true) {
        var found = tryFindMatch();
        if (found.status == "dirty") {
          this.highlight(found.node, endOfLine(found.node));
          // Needed because in some corner cases a highlight does not
          // reach a node.
          found.node.dirty = false;
          continue;
        }
        else {
          highlight(cursor, found.status);
          highlight(found.node, found.status);
          if (fromKey)
            self.parent.setTimeout(function() {unhighlight(cursor); unhighlight(found.node);}, 500);
          else
            self.highlighted = [cursor, found.node];
          if (jump && found.node)
            select.focusAfterNode(found.node.previousSibling, this.container);
          break;
        }
      }
    },

    // Adjust the amount of whitespace at the start of the line that
    // the cursor is on so that it is indented properly.
    indentAtCursor: function(direction) {
      if (!this.container.firstChild) return;
      // The line has to have up-to-date lexical information, so we
      // highlight it first.
      if (!this.highlightAtCursor()) return;
      var cursor = select.selectionTopNode(this.container, false);
      // If we couldn't determine the place of the cursor,
      // there's nothing to indent.
      if (cursor === false)
        return;
      select.markSelection();
      this.indentLineAfter(startOfLine(cursor), direction);
      select.selectMarked();
    },

    // Indent all lines whose start falls inside of the current
    // selection.
    indentRegion: function(start, end, direction) {
      var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling);
      if (!isBR(end)) end = endOfLine(end, this.container);
      this.addDirtyNode(start);

      do {
        var next = endOfLine(current, this.container);
        if (current) this.highlight(before, next, true);
        this.indentLineAfter(current, direction);
        before = current;
        current = next;
      } while (current != end);
      select.setCursorPos(this.container, {node: start, offset: 0}, {node: end, offset: 0});
    },

    // Find the node that the cursor is in, mark it as dirty, and make
    // sure a highlight pass is scheduled.
    cursorActivity: function(safe) {
      // pagehide event hack above
      if (this.unloaded) {
        window.document.designMode = "off";
        window.document.designMode = "on";
        this.unloaded = false;
      }

      if (internetExplorer) {
        this.container.createTextRange().execCommand("unlink");
        clearTimeout(this.saveSelectionSnapshot);
        var self = this;
        this.saveSelectionSnapshot = setTimeout(function() {
          var snapshot = select.getBookmark(self.container);
          if (snapshot) self.selectionSnapshot = snapshot;
        }, 200);
      }

      var activity = this.options.cursorActivity;
      if (!safe || activity) {
        var cursor = select.selectionTopNode(this.container, false);
        if (cursor === false || !this.container.firstChild) return;
        cursor = cursor || this.container.firstChild;
        if (activity) activity(cursor);
        if (!safe) {
          this.scheduleHighlight();
          this.addDirtyNode(cursor);
        }
      }
    },

    reparseBuffer: function() {
      forEach(this.container.childNodes, function(node) {node.dirty = true;});
      if (this.container.firstChild)
        this.addDirtyNode(this.container.firstChild);
    },

    // Add a node to the set of dirty nodes, if it isn't already in
    // there.
    addDirtyNode: function(node) {
      node = node || this.container.firstChild;
      if (!node) return;

      for (var i = 0; i < this.dirty.length; i++)
        if (this.dirty[i] == node) return;

      if (node.nodeType != 3)
        node.dirty = true;
      this.dirty.push(node);
    },

    allClean: function() {
      return !this.dirty.length;
    },

    // Cause a highlight pass to happen in options.passDelay
    // milliseconds. Clear the existing timeout, if one exists. This
    // way, the passes do not happen while the user is typing, and
    // should as unobtrusive as possible.
    scheduleHighlight: function() {
      // Timeouts are routed through the parent window, because on
      // some browsers designMode windows do not fire timeouts.
      var self = this;
      this.parent.clearTimeout(this.highlightTimeout);
      this.highlightTimeout = this.parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay);
    },

    // Fetch one dirty node, and remove it from the dirty set.
    getDirtyNode: function() {
      while (this.dirty.length > 0) {
        var found = this.dirty.pop();
        // IE8 sometimes throws an unexplainable 'invalid argument'
        // exception for found.parentNode
        try {
          // If the node has been coloured in the meantime, or is no
          // longer in the document, it should not be returned.
          while (found && found.parentNode != this.container)
            found = found.parentNode;
          if (found && (found.dirty || found.nodeType == 3))
            return found;
        } catch (e) {}
      }
      return null;
    },

    // Pick dirty nodes, and highlight them, until options.passTime
    // milliseconds have gone by. The highlight method will continue
    // to next lines as long as it finds dirty nodes. It returns
    // information about the place where it stopped. If there are
    // dirty nodes left after this function has spent all its lines,
    // it shedules another highlight to finish the job.
    highlightDirty: function(force) {
      // Prevent FF from raising an error when it is firing timeouts
      // on a page that's no longer loaded.
      if (!window || !window.parent || !window.select) return false;

      if (!this.options.readOnly) select.markSelection();
      var start, endTime = force ? null : time() + this.options.passTime;
      while ((time() < endTime || force) && (start = this.getDirtyNode())) {
        var result = this.highlight(start, endTime);
        if (result && result.node && result.dirty)
          this.addDirtyNode(result.node.nextSibling);
      }
      if (!this.options.readOnly) select.selectMarked();
      if (start) this.scheduleHighlight();
      return this.dirty.length == 0;
    },

    // Creates a function that, when called through a timeout, will
    // continuously re-parse the document.
    documentScanner: function(passTime) {
      var self = this, pos = null;
      return function() {
        // FF timeout weirdness workaround.
        if (!window || !window.parent || !window.select) return;
        // If the current node is no longer in the document... oh
        // well, we start over.
        if (pos && pos.parentNode != self.container)
          pos = null;
        select.markSelection();
        var result = self.highlight(pos, time() + passTime, true);
        select.selectMarked();
        var newPos = result ? (result.node && result.node.nextSibling) : null;
        pos = (pos == newPos) ? null : newPos;
        self.delayScanning();
      };
    },

    // Starts the continuous scanning process for this document after
    // a given interval.
    delayScanning: function() {
      if (this.scanner) {
        this.parent.clearTimeout(this.documentScan);
        this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning);
      }
    },

    // The function that does the actual highlighting/colouring (with
    // help from the parser and the DOM normalizer). Its interface is
    // rather overcomplicated, because it is used in different
    // situations: ensuring that a certain line is highlighted, or
    // highlighting up to X milliseconds starting from a certain
    // point. The 'from' argument gives the node at which it should
    // start. If this is null, it will start at the beginning of the
    // document. When a timestamp is given with the 'target' argument,
    // it will stop highlighting at that time. If this argument holds
    // a DOM node, it will highlight until it reaches that node. If at
    // any time it comes across two 'clean' lines (no dirty nodes), it
    // will stop, except when 'cleanLines' is true. maxBacktrack is
    // the maximum number of lines to backtrack to find an existing
    // parser instance. This is used to give up in situations where a
    // highlight would take too long and freeze the browser interface.
    highlight: function(from, target, cleanLines, maxBacktrack){
      var container = this.container, self = this, active = this.options.activeTokens;
      var endTime = (typeof target == "number" ? target : null);

      if (!container.firstChild)
        return false;
      // Backtrack to the first node before from that has a partial
      // parse stored.
      while (from && (!from.parserFromHere || from.dirty)) {
        if (maxBacktrack != null && isBR(from) && (--maxBacktrack) < 0)
          return false;
        from = from.previousSibling;
      }
      // If we are at the end of the document, do nothing.
      if (from && !from.nextSibling)
        return false;

      // Check whether a part (<span> node) and the corresponding token
      // match.
      function correctPart(token, part){
        return !part.reduced && part.currentText == token.value && part.className == token.style;
      }
      // Shorten the text associated with a part by chopping off
      // characters from the front. Note that only the currentText
      // property gets changed. For efficiency reasons, we leave the
      // nodeValue alone -- we set the reduced flag to indicate that
      // this part must be replaced.
      function shortenPart(part, minus){
        part.currentText = part.currentText.substring(minus);
        part.reduced = true;
      }
      // Create a part corresponding to a given token.
      function tokenPart(token){
        var part = makePartSpan(token.value);     
        part.className = token.style;
        return part;
      }

      function maybeTouch(node) {
        if (node) {
          var old = node.oldNextSibling;
          if (lineDirty || old === undefined || node.nextSibling != old)

share/endpoint/www/js/editor.js  view on Meta::CPAN

          parts.next();
        }
        else {
          if (!isSpan(part))
            throw "Parser out of sync. Expected SPAN.";
          if (part.dirty)
            lineDirty = true;
          lineNodes++;

          // If the part matches the token, we can leave it alone.
          if (correctPart(token, part)){
            part.dirty = false;
            parts.next();
          }
          // Otherwise, we have to fix it.
          else {
            lineDirty = true;
            // Insert the correct part.
            var newPart = tokenPart(token);
            container.insertBefore(newPart, part);
            if (active) active(newPart, token, self);
            var tokensize = token.value.length;
            var offset = 0;
            // Eat up parts until the text for this token has been
            // removed, adjusting the stored selection info (see
            // select.js) in the process.
            while (tokensize > 0) {
              part = parts.get();
              var partsize = part.currentText.length;
              select.snapshotReplaceNode(part.firstChild, newPart.firstChild, tokensize, offset);
              if (partsize > tokensize){
                shortenPart(part, tokensize);
                tokensize = 0;
              }
              else {
                tokensize -= partsize;
                offset += partsize;
                parts.remove();
              }
            }
          }
        }
      });
      maybeTouch(from);
      webkitLastLineHack(this.container);

      // The function returns some status information that is used by
      // hightlightDirty to determine whether and where it has to
      // continue.
      return {node: parts.getNonEmpty(),
              dirty: lineDirty};
    }
  };

  return Editor;
})();

addEventHandler(window, "load", function() {
  var CodeMirror = window.frameElement.CodeMirror;
  var e = CodeMirror.editor = new Editor(CodeMirror.options);
  this.parent.setTimeout(method(CodeMirror, "init"), 0);
});



( run in 2.598 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )