(function() {
  var ATOMIC, CR, DisplayIndex, DisplayLayer, DisplayMarkerLayer, Emitter, EmptyDecorationLayer, FOLD, HARD_TAB, INVISIBLE_CHARACTER, LEADING_WHITESPACE, LF, LINE_ENDING, Patch, Point, Range, SHOW_INDENT_GUIDE, SOFT_LINE_BREAK, SOFT_WRAP_INDENTATION, TRAILING_WHITESPACE, VOID, clipNegativePoint, comparePoints, getSpatialTokenTextDecoration, isCharacterPair, isWordStart, maxPoint, normalizePatchChanges, pointHelpers, ref, spatialTokenTextDecorationCache, traversal, traverse;

  Patch = require('atom-patch');

  DisplayIndex = require('display-index');

  Emitter = require('event-kit').Emitter;

  Point = require('./point');

  Range = require('./range');

  DisplayMarkerLayer = require('./display-marker-layer');

  EmptyDecorationLayer = require('./empty-decoration-layer');

  ref = pointHelpers = require('./point-helpers'), traverse = ref.traverse, traversal = ref.traversal, clipNegativePoint = ref.clipNegativePoint;

  comparePoints = pointHelpers.compare;

  maxPoint = pointHelpers.max;

  normalizePatchChanges = require('./helpers').normalizePatchChanges;

  isCharacterPair = require('./is-character-pair');

  VOID = 1 << 0;

  ATOMIC = 1 << 1;

  INVISIBLE_CHARACTER = 1 << 2;

  FOLD = 1 << 3;

  HARD_TAB = 1 << 4;

  LEADING_WHITESPACE = 1 << 5;

  TRAILING_WHITESPACE = 1 << 6;

  SHOW_INDENT_GUIDE = 1 << 7;

  SOFT_LINE_BREAK = 1 << 8;

  SOFT_WRAP_INDENTATION = 1 << 9;

  LINE_ENDING = 1 << 10;

  CR = 1 << 11;

  LF = 1 << 12;

  isWordStart = function(previousCharacter, character) {
    return (previousCharacter === ' ' || previousCharacter === '\t') && (character !== ' ' && character !== '\t');
  };

  spatialTokenTextDecorationCache = new Map;

  getSpatialTokenTextDecoration = function(metadata) {
    var decoration;
    if (metadata) {
      if (spatialTokenTextDecorationCache.has(metadata)) {
        return spatialTokenTextDecorationCache.get(metadata);
      } else {
        decoration = '';
        if (metadata & INVISIBLE_CHARACTER) {
          decoration += 'invisible-character ';
        }
        if (metadata & HARD_TAB) {
          decoration += 'hard-tab ';
        }
        if (metadata & LEADING_WHITESPACE) {
          decoration += 'leading-whitespace ';
        }
        if (metadata & TRAILING_WHITESPACE) {
          decoration += 'trailing-whitespace ';
        }
        if (metadata & LINE_ENDING) {
          decoration += 'eol ';
        }
        if (metadata & SHOW_INDENT_GUIDE) {
          decoration += 'indent-guide ';
        }
        if (metadata & FOLD) {
          decoration += 'fold-marker ';
        }
        if (decoration.length > 0) {
          decoration = decoration.trim();
        } else {
          decoration = void 0;
        }
        spatialTokenTextDecorationCache.set(metadata, decoration);
        return decoration;
      }
    }
  };

  module.exports = DisplayLayer = (function() {
    DisplayLayer.prototype.VOID_TOKEN = VOID;

    DisplayLayer.prototype.ATOMIC_TOKEN = ATOMIC;

    DisplayLayer.deserialize = function(buffer, params) {
      var foldsMarkerLayer;
      foldsMarkerLayer = buffer.getMarkerLayer(params.foldsMarkerLayerId);
      return new DisplayLayer(params.id, buffer, {
        foldsMarkerLayer: foldsMarkerLayer
      });
    };

    function DisplayLayer(id1, buffer1, settings) {
      var ref1, ref10, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9;
      this.id = id1;
      this.buffer = buffer1;
      if (settings == null) {
        settings = {};
      }
      this.displayMarkerLayersById = {};
      this.foldsMarkerLayer = (ref1 = settings.foldsMarkerLayer) != null ? ref1 : this.buffer.addMarkerLayer({
        maintainHistory: false,
        persistent: true,
        destroyInvalidatedMarkers: true
      });
      this.foldIdCounter = 1;
      this.displayIndex = new DisplayIndex;
      this.spatialTokenIterator = this.displayIndex.buildTokenIterator();
      this.spatialLineIterator = this.displayIndex.buildScreenLineIterator();
      this.textDecorationLayer = new EmptyDecorationLayer;
      this.emitter = new Emitter;
      this.invalidationCountsBySpatialLineId = new Map;
      this.screenLinesBySpatialLineId = new Map;
      this.codesByTag = new Map;
      this.tagsByCode = new Map;
      this.nextOpenTagCode = -1;
      this.reset({
        invisibles: (ref2 = settings.invisibles) != null ? ref2 : {},
        tabLength: (ref3 = settings.tabLength) != null ? ref3 : 4,
        softWrapColumn: (ref4 = settings.softWrapColumn) != null ? ref4 : Infinity,
        softWrapHangingIndent: (ref5 = settings.softWrapHangingIndent) != null ? ref5 : 0,
        showIndentGuides: (ref6 = settings.showIndentGuides) != null ? ref6 : false,
        ratioForCharacter: (ref7 = settings.ratioForCharacter) != null ? ref7 : function() {
          return 1.0;
        },
        isWrapBoundary: (ref8 = settings.isWrapBoundary) != null ? ref8 : isWordStart,
        foldCharacter: (ref9 = settings.foldCharacter) != null ? ref9 : '⋯',
        atomicSoftTabs: (ref10 = settings.atomicSoftTabs) != null ? ref10 : true
      });
    }

    DisplayLayer.prototype.serialize = function() {
      return {
        id: this.id,
        foldsMarkerLayerId: this.foldsMarkerLayer.id
      };
    };

    DisplayLayer.prototype.copy = function() {
      var copy, foldsMarkerLayer, newId;
      newId = this.buffer.nextDisplayLayerId++;
      foldsMarkerLayer = this.foldsMarkerLayer.copy();
      copy = new DisplayLayer(newId, this.buffer, {
        foldsMarkerLayer: foldsMarkerLayer,
        invisibles: this.invisibles,
        tabLength: this.tabLength,
        softWrapColumn: this.softWrapColumn,
        softWrapHangingIndent: this.softWrapHangingIndent,
        showIndentGuides: this.showIndentGuides,
        ratioForCharacter: this.ratioForCharacter,
        isWrapBoundary: this.isWrapBoundary,
        foldCharacter: this.foldCharacter,
        atomicSoftTabs: this.atomicSoftTabs
      });
      return this.buffer.displayLayers[newId] = copy;
    };

    DisplayLayer.prototype.destroy = function() {
      var displayMarkerLayer, id, ref1;
      this.foldsMarkerLayer.destroy();
      ref1 = this.displayMarkerLayersById;
      for (id in ref1) {
        displayMarkerLayer = ref1[id];
        displayMarkerLayer.destroy();
      }
      return delete this.buffer.displayLayers[this.id];
    };

    DisplayLayer.prototype.reset = function(params) {
      var endScreenRow, newLines, newRowExtent, oldRowExtent, ref1, startScreenRow;
      if (params.hasOwnProperty('tabLength')) {
        this.tabLength = params.tabLength;
      }
      if (params.hasOwnProperty('invisibles')) {
        this.invisibles = params.invisibles;
      }
      if (params.hasOwnProperty('showIndentGuides')) {
        this.showIndentGuides = params.showIndentGuides;
      }
      if (params.hasOwnProperty('softWrapColumn')) {
        this.softWrapColumn = params.softWrapColumn;
      }
      if (params.hasOwnProperty('softWrapHangingIndent')) {
        this.softWrapHangingIndent = params.softWrapHangingIndent;
      }
      if (params.hasOwnProperty('ratioForCharacter')) {
        this.ratioForCharacter = params.ratioForCharacter;
      }
      if (params.hasOwnProperty('isWrapBoundary')) {
        this.isWrapBoundary = params.isWrapBoundary;
      }
      if (params.hasOwnProperty('foldCharacter')) {
        this.foldCharacter = params.foldCharacter;
      }
      if (params.hasOwnProperty('atomicSoftTabs')) {
        this.atomicSoftTabs = params.atomicSoftTabs;
      }
      this.eolInvisibles = {
        "\r": this.invisibles.cr,
        "\n": this.invisibles.eol,
        "\r\n": this.invisibles.cr + this.invisibles.eol
      };
      ref1 = this.expandBufferRangeToLineBoundaries(Range(Point.ZERO, Point(this.buffer.getLineCount(), 0))), startScreenRow = ref1.startScreenRow, endScreenRow = ref1.endScreenRow;
      newLines = this.buildSpatialScreenLines(0, this.buffer.getLineCount());
      oldRowExtent = endScreenRow - startScreenRow;
      newRowExtent = newLines.length;
      this.spliceDisplayIndex(startScreenRow, oldRowExtent, newLines);
      this.emitDidChangeSyncEvent(Object.freeze([
        {
          start: Point(startScreenRow, 0),
          oldExtent: Point(oldRowExtent, 0),
          newExtent: Point(newRowExtent, 0)
        }
      ]));
      return this.notifyObserversIfMarkerScreenPositionsChanged();
    };

    DisplayLayer.prototype.addMarkerLayer = function(options) {
      var markerLayer;
      markerLayer = new DisplayMarkerLayer(this, this.buffer.addMarkerLayer(options), true);
      return this.displayMarkerLayersById[markerLayer.id] = markerLayer;
    };

    DisplayLayer.prototype.getMarkerLayer = function(id) {
      var base, bufferMarkerLayer;
      if (bufferMarkerLayer = this.buffer.getMarkerLayer(id)) {
        return (base = this.displayMarkerLayersById)[id] != null ? base[id] : base[id] = new DisplayMarkerLayer(this, bufferMarkerLayer, false);
      }
    };

    DisplayLayer.prototype.notifyObserversIfMarkerScreenPositionsChanged = function() {
      var displayMarkerLayer, id, ref1, results;
      ref1 = this.displayMarkerLayersById;
      results = [];
      for (id in ref1) {
        displayMarkerLayer = ref1[id];
        results.push(displayMarkerLayer.notifyObserversIfMarkerScreenPositionsChanged());
      }
      return results;
    };

    DisplayLayer.prototype.setTextDecorationLayer = function(layer) {
      var ref1;
      if ((ref1 = this.decorationLayerDisposable) != null) {
        ref1.dispose();
      }
      this.textDecorationLayer = layer;
      return this.decorationLayerDisposable = typeof layer.onDidInvalidateRange === "function" ? layer.onDidInvalidateRange(this.decorationLayerDidInvalidateRange.bind(this)) : void 0;
    };

    DisplayLayer.prototype.bufferRangeForFold = function(id) {
      return this.foldsMarkerLayer.getMarkerRange(id);
    };

    DisplayLayer.prototype.foldBufferRange = function(bufferRange) {
      var endBufferRow, endScreenRow, foldMarker, newRowExtent, newScreenLines, oldRowExtent, ref1, startBufferRow, startScreenRow;
      bufferRange = this.buffer.clipRange(bufferRange);
      foldMarker = this.foldsMarkerLayer.markRange(bufferRange, {
        invalidate: 'overlap',
        exclusive: true
      });
      if (this.findFoldMarkers({
        containsRange: bufferRange,
        valid: true
      }).length === 1) {
        ref1 = this.expandBufferRangeToLineBoundaries(bufferRange), startScreenRow = ref1.startScreenRow, endScreenRow = ref1.endScreenRow, startBufferRow = ref1.startBufferRow, endBufferRow = ref1.endBufferRow;
        oldRowExtent = endScreenRow - startScreenRow;
        newScreenLines = this.buildSpatialScreenLines(startBufferRow, endBufferRow);
        newRowExtent = newScreenLines.length;
        this.spliceDisplayIndex(startScreenRow, oldRowExtent, newScreenLines);
        this.emitDidChangeSyncEvent(Object.freeze([
          {
            start: Point(startScreenRow, 0),
            oldExtent: Point(oldRowExtent, 0),
            newExtent: Point(newRowExtent, 0)
          }
        ]));
        this.notifyObserversIfMarkerScreenPositionsChanged();
      }
      return foldMarker.id;
    };

    DisplayLayer.prototype.foldsIntersectingBufferRange = function(bufferRange) {
      return this.findFoldMarkers({
        intersectsRange: bufferRange
      }).map(function(arg) {
        var id;
        id = arg.id;
        return id;
      });
    };

    DisplayLayer.prototype.findFoldMarkers = function(params) {
      params.valid = true;
      return this.foldsMarkerLayer.findMarkers(params);
    };

    DisplayLayer.prototype.destroyFold = function(foldId) {
      var foldMarker;
      if (foldMarker = this.foldsMarkerLayer.getMarker(foldId)) {
        return this.destroyFoldMarkers([foldMarker]);
      }
    };

    DisplayLayer.prototype.destroyFoldsIntersectingBufferRange = function(bufferRange) {
      bufferRange = this.buffer.clipRange(bufferRange);
      return this.destroyFoldMarkers(this.findFoldMarkers({
        intersectsRange: bufferRange
      }));
    };

    DisplayLayer.prototype.destroyAllFolds = function() {
      return this.destroyFoldMarkers(this.foldsMarkerLayer.getMarkers());
    };

    DisplayLayer.prototype.destroyFoldMarkers = function(foldMarkers) {
      var combinedRange, combinedRangeEnd, combinedRangeStart, endBufferRow, endScreenRow, foldMarker, j, len, newRowExtent, newScreenLines, oldRowExtent, ref1, startBufferRow, startScreenRow;
      if (foldMarkers.length === 0) {
        return [];
      }
      combinedRangeStart = combinedRangeEnd = foldMarkers[0].getStartPosition();
      for (j = 0, len = foldMarkers.length; j < len; j++) {
        foldMarker = foldMarkers[j];
        combinedRangeEnd = maxPoint(combinedRangeEnd, foldMarker.getEndPosition());
        foldMarker.destroy();
      }
      combinedRange = Range(combinedRangeStart, combinedRangeEnd);
      ref1 = this.expandBufferRangeToLineBoundaries(combinedRange), startScreenRow = ref1.startScreenRow, endScreenRow = ref1.endScreenRow, startBufferRow = ref1.startBufferRow, endBufferRow = ref1.endBufferRow;
      oldRowExtent = endScreenRow - startScreenRow;
      newScreenLines = this.buildSpatialScreenLines(startBufferRow, endBufferRow);
      newRowExtent = newScreenLines.length;
      this.spliceDisplayIndex(startScreenRow, oldRowExtent, newScreenLines);
      this.emitDidChangeSyncEvent(Object.freeze([
        {
          start: Point(startScreenRow, 0),
          oldExtent: Point(oldRowExtent, 0),
          newExtent: Point(newRowExtent, 0)
        }
      ]));
      this.notifyObserversIfMarkerScreenPositionsChanged();
      return foldMarkers.map(function(marker) {
        return marker.getRange();
      });
    };

    DisplayLayer.prototype.emitDidChangeSyncEvent = function(event) {
      return this.emitter.emit('did-change-sync', event);
    };

    DisplayLayer.prototype.onDidChangeSync = function(callback) {
      return this.emitter.on('did-change-sync', callback);
    };

    DisplayLayer.prototype.bufferDidChange = function(change) {
      var combinedChanges, endBufferRow, endScreenRow, extent, invalidatedRanges, j, len, newExtent, newRange, newRowExtent, newScreenLines, oldExtent, oldRange, oldRowExtent, range, ref1, ref2, start, startBufferRow, startScreenRow;
      ref1 = this.expandChangeRegionToSurroundingEmptyLines(change.oldRange, change.newRange), oldRange = ref1.oldRange, newRange = ref1.newRange;
      ref2 = this.expandBufferRangeToLineBoundaries(oldRange), startScreenRow = ref2.startScreenRow, endScreenRow = ref2.endScreenRow, startBufferRow = ref2.startBufferRow, endBufferRow = ref2.endBufferRow;
      endBufferRow = newRange.end.row + (endBufferRow - oldRange.end.row);
      oldRowExtent = endScreenRow - startScreenRow;
      newScreenLines = this.buildSpatialScreenLines(startBufferRow, endBufferRow);
      newRowExtent = newScreenLines.length;
      this.spliceDisplayIndex(startScreenRow, oldRowExtent, newScreenLines);
      start = Point(startScreenRow, 0);
      oldExtent = Point(oldRowExtent, 0);
      newExtent = Point(newRowExtent, 0);
      combinedChanges = new Patch;
      combinedChanges.splice(start, oldExtent, newExtent);
      if (this.textDecorationLayer != null) {
        invalidatedRanges = this.textDecorationLayer.getInvalidatedRanges();
        for (j = 0, len = invalidatedRanges.length; j < len; j++) {
          range = invalidatedRanges[j];
          range = this.translateBufferRange(range);
          this.invalidateScreenLines(range);
          range.start.column = 0;
          range.end.row++;
          range.end.column = 0;
          extent = range.getExtent();
          combinedChanges.splice(range.start, extent, extent);
        }
      }
      return Object.freeze(normalizePatchChanges(combinedChanges.getChanges()));
    };

    DisplayLayer.prototype.spliceDisplayIndex = function(startScreenRow, oldRowExtent, newScreenLines) {
      var deletedSpatialLineIds, id, j, len;
      deletedSpatialLineIds = this.displayIndex.splice(startScreenRow, oldRowExtent, newScreenLines);
      for (j = 0, len = deletedSpatialLineIds.length; j < len; j++) {
        id = deletedSpatialLineIds[j];
        this.invalidationCountsBySpatialLineId["delete"](id);
        this.screenLinesBySpatialLineId["delete"](id);
      }
    };

    DisplayLayer.prototype.invalidateScreenLines = function(screenRange) {
      var id, invalidationCount, j, len, ref1, ref2;
      ref1 = this.spatialLineIdsForScreenRange(screenRange);
      for (j = 0, len = ref1.length; j < len; j++) {
        id = ref1[j];
        invalidationCount = (ref2 = this.invalidationCountsBySpatialLineId.get(id)) != null ? ref2 : 0;
        this.invalidationCountsBySpatialLineId.set(id, invalidationCount + 1);
        this.screenLinesBySpatialLineId["delete"](id);
      }
    };

    DisplayLayer.prototype.decorationLayerDidInvalidateRange = function(bufferRange) {
      var extent, screenRange;
      screenRange = this.translateBufferRange(bufferRange);
      this.invalidateScreenLines(screenRange);
      extent = screenRange.getExtent();
      return this.emitDidChangeSyncEvent([
        {
          start: screenRange.start,
          oldExtent: extent,
          newExtent: extent
        }
      ]);
    };

    DisplayLayer.prototype.spatialLineIdsForScreenRange = function(screenRange) {
      var ids;
      this.spatialLineIterator.seekToScreenRow(screenRange.start.row);
      ids = [];
      while (this.spatialLineIterator.getScreenRow() <= screenRange.end.row) {
        ids.push(this.spatialLineIterator.getId());
        if (!this.spatialLineIterator.moveToSuccessor()) {
          break;
        }
      }
      return ids;
    };

    DisplayLayer.prototype.expandChangeRegionToSurroundingEmptyLines = function(oldRange, newRange) {
      oldRange = oldRange.copy();
      newRange = newRange.copy();
      while (oldRange.start.row > 0) {
        if (this.buffer.lineForRow(oldRange.start.row - 1).length !== 0) {
          break;
        }
        oldRange.start.row--;
        newRange.start.row--;
      }
      while (newRange.end.row < this.buffer.getLastRow()) {
        if (this.buffer.lineForRow(newRange.end.row + 1).length !== 0) {
          break;
        }
        oldRange.end.row++;
        newRange.end.row++;
      }
      return {
        oldRange: oldRange,
        newRange: newRange
      };
    };

    DisplayLayer.prototype.lineStartBoundaryForBufferRow = function(bufferRow) {
      this.spatialLineIterator.seekToBufferPosition(Point(bufferRow, 0));
      while (this.spatialLineIterator.isSoftWrappedAtStart()) {
        this.spatialLineIterator.moveToPredecessor();
      }
      return {
        screenRow: this.spatialLineIterator.getScreenRow(),
        bufferRow: this.spatialLineIterator.getBufferStart().row
      };
    };

    DisplayLayer.prototype.lineEndBoundaryForBufferRow = function(bufferRow) {
      this.spatialLineIterator.seekToBufferPosition(Point(bufferRow, Infinity));
      while (this.spatialLineIterator.isSoftWrappedAtEnd()) {
        this.spatialLineIterator.moveToSuccessor();
      }
      return {
        screenRow: this.spatialLineIterator.getScreenRow() + 1,
        bufferRow: this.spatialLineIterator.getBufferEnd().row
      };
    };

    DisplayLayer.prototype.expandBufferRangeToLineBoundaries = function(range) {
      var endBufferRow, endScreenRow, ref1, ref2, startBufferRow, startScreenRow;
      ref1 = this.lineStartBoundaryForBufferRow(range.start.row), startScreenRow = ref1.screenRow, startBufferRow = ref1.bufferRow;
      ref2 = this.lineEndBoundaryForBufferRow(range.end.row), endScreenRow = ref2.screenRow, endBufferRow = ref2.bufferRow;
      return {
        startScreenRow: startScreenRow,
        endScreenRow: endScreenRow,
        startBufferRow: startBufferRow,
        endBufferRow: endBufferRow
      };
    };

    DisplayLayer.prototype.buildSpatialScreenLines = function(startBufferRow, endBufferRow) {
      var atSoftTabBoundary, behindCount, bufferColumn, bufferLine, bufferLineLength, bufferRow, character, characterWidth, column, continuingSoftWrappedLine, distanceToNextTabStop, emptyLineWhitespaceLength, eolInvisibleReplacement, excessTokens, excessTokensScreenExtent, foldEndBufferPosition, foldStartBufferPosition, folds, inLeadingWhitespace, inTrailingWhitespace, indentGuidesCount, indentLength, isBlankLine, isEmptyLine, j, lastWrapBufferColumn, lineEnding, metadata, nextCharacter, previousCharacter, previousPositionWasFold, ref1, ref2, ref3, screenColumn, screenExtent, screenLineBufferEnd, screenLineBufferStart, screenLineWidth, screenLineWidthAtWrapCharacter, screenLineWidthAtWrapColumn, screenLines, softWrapIndent, spaceCount, tokens, tokensScreenExtent, trailingWhitespaceStartBufferColumn, trailingWhitespaceStartScreenColumn, wrapBoundaryBufferColumn, wrapBoundaryEndsLeadingWhitespace, wrapBoundaryScreenColumn, wrapBufferColumn, wrapScreenColumn;
      ref1 = this.computeFoldsInBufferRowRange(startBufferRow, endBufferRow), startBufferRow = ref1.startBufferRow, endBufferRow = ref1.endBufferRow, folds = ref1.folds;
      screenLines = [];
      bufferRow = startBufferRow;
      bufferColumn = 0;
      screenColumn = 0;
      screenLineWidth = 0;
      while (bufferRow < endBufferRow) {
        tokens = [];
        tokensScreenExtent = 0;
        screenLineBufferStart = Point(bufferRow, 0);
        bufferLine = this.buffer.lineForRow(bufferRow);
        bufferLineLength = bufferLine.length;
        previousPositionWasFold = false;
        trailingWhitespaceStartBufferColumn = this.findTrailingWhitespaceStartBufferColumn(bufferLine);
        isBlankLine = trailingWhitespaceStartBufferColumn === 0;
        isEmptyLine = bufferLineLength === 0;
        inLeadingWhitespace = !isBlankLine;
        continuingSoftWrappedLine = false;
        lastWrapBufferColumn = 0;
        wrapBoundaryScreenColumn = 0;
        wrapBoundaryBufferColumn = 0;
        screenLineWidthAtWrapCharacter = 0;
        wrapBoundaryEndsLeadingWhitespace = true;
        softWrapIndent = null;
        while (bufferColumn <= bufferLineLength) {
          previousCharacter = bufferLine[bufferColumn - 1];
          character = bufferLine[bufferColumn];
          nextCharacter = bufferLine[bufferColumn + 1];
          foldEndBufferPosition = (ref2 = folds[bufferRow]) != null ? ref2[bufferColumn] : void 0;
          if (character == null) {
            characterWidth = 0;
          } else if (foldEndBufferPosition != null) {
            characterWidth = this.ratioForCharacter(this.foldCharacter);
          } else if (character === '\t') {
            distanceToNextTabStop = this.tabLength - (screenColumn % this.tabLength);
            characterWidth = this.ratioForCharacter(' ') * distanceToNextTabStop;
          } else {
            characterWidth = this.ratioForCharacter(character);
          }
          inTrailingWhitespace = bufferColumn >= trailingWhitespaceStartBufferColumn;
          if (bufferColumn === trailingWhitespaceStartBufferColumn) {
            trailingWhitespaceStartScreenColumn = screenColumn;
          }
          atSoftTabBoundary = (inLeadingWhitespace || isBlankLine && inTrailingWhitespace) && (screenColumn % this.tabLength) === 0 && (screenColumn - tokensScreenExtent) === this.tabLength;
          if ((character != null) && this.isWrapBoundary(previousCharacter, character)) {
            wrapBoundaryScreenColumn = screenColumn;
            wrapBoundaryBufferColumn = bufferColumn;
            screenLineWidthAtWrapCharacter = screenLineWidth;
            wrapBoundaryEndsLeadingWhitespace = inLeadingWhitespace;
          }
          if (character !== ' ' || (foldEndBufferPosition != null) || atSoftTabBoundary) {
            if (inLeadingWhitespace && bufferColumn < bufferLineLength) {
              if (!(character === ' ' || character === '\t')) {
                inLeadingWhitespace = false;
                softWrapIndent = screenColumn;
              }
              if (screenColumn > tokensScreenExtent) {
                spaceCount = screenColumn - tokensScreenExtent;
                metadata = LEADING_WHITESPACE;
                if (this.invisibles.space != null) {
                  metadata |= INVISIBLE_CHARACTER;
                }
                if (atSoftTabBoundary && this.atomicSoftTabs) {
                  metadata |= ATOMIC;
                }
                if (this.showIndentGuides && (tokensScreenExtent % this.tabLength) === 0) {
                  metadata |= SHOW_INDENT_GUIDE;
                }
                tokens.push({
                  screenExtent: spaceCount,
                  bufferExtent: Point(0, spaceCount),
                  metadata: metadata
                });
                tokensScreenExtent = screenColumn;
              }
            }
            if (inTrailingWhitespace && screenColumn > tokensScreenExtent) {
              if (trailingWhitespaceStartScreenColumn > tokensScreenExtent) {
                behindCount = trailingWhitespaceStartScreenColumn - tokensScreenExtent;
                tokens.push({
                  screenExtent: behindCount,
                  bufferExtent: Point(0, behindCount),
                  metadata: 0
                });
                tokensScreenExtent = trailingWhitespaceStartScreenColumn;
              }
              if (screenColumn > tokensScreenExtent) {
                spaceCount = screenColumn - tokensScreenExtent;
                metadata = TRAILING_WHITESPACE;
                if (this.invisibles.space != null) {
                  metadata |= INVISIBLE_CHARACTER;
                }
                if (atSoftTabBoundary) {
                  metadata |= ATOMIC;
                }
                if (this.showIndentGuides && isBlankLine && (tokensScreenExtent % this.tabLength) === 0) {
                  metadata |= SHOW_INDENT_GUIDE;
                }
                tokens.push({
                  screenExtent: spaceCount,
                  bufferExtent: Point(0, spaceCount),
                  metadata: metadata
                });
                tokensScreenExtent = screenColumn;
              }
            }
          }
          if ((character != null) && (nextCharacter != null) && isCharacterPair(character, nextCharacter)) {
            if (screenColumn > tokensScreenExtent) {
              behindCount = screenColumn - tokensScreenExtent;
              tokens.push({
                screenExtent: behindCount,
                bufferExtent: Point(0, behindCount),
                metadata: 0
              });
              tokensScreenExtent = screenColumn;
            }
            tokens.push({
              screenExtent: 2,
              bufferExtent: Point(0, 2),
              metadata: ATOMIC
            });
            bufferColumn += 2;
            screenColumn += 2;
            tokensScreenExtent += 2;
            screenLineWidth += 2;
            continue;
          }
          if ((character != null) && ((screenLineWidth + characterWidth) > this.softWrapColumn) && screenColumn > 0) {
            if (wrapBoundaryBufferColumn > lastWrapBufferColumn && !wrapBoundaryEndsLeadingWhitespace) {
              wrapScreenColumn = wrapBoundaryScreenColumn;
              wrapBufferColumn = wrapBoundaryBufferColumn;
              screenLineWidthAtWrapColumn = screenLineWidthAtWrapCharacter;
            } else {
              wrapScreenColumn = screenColumn;
              wrapBufferColumn = bufferColumn;
              screenLineWidthAtWrapColumn = screenLineWidth;
            }
            if (inTrailingWhitespace && trailingWhitespaceStartScreenColumn > tokensScreenExtent) {
              behindCount = trailingWhitespaceStartScreenColumn - tokensScreenExtent;
              tokens.push({
                screenExtent: behindCount,
                bufferExtent: Point(0, behindCount),
                metadata: 0
              });
              tokensScreenExtent = trailingWhitespaceStartScreenColumn;
            }
            if (wrapScreenColumn >= tokensScreenExtent) {
              behindCount = wrapScreenColumn - tokensScreenExtent;
              if (behindCount > 0) {
                metadata = 0;
                if (inTrailingWhitespace) {
                  metadata |= TRAILING_WHITESPACE;
                  if (this.invisibles.space != null) {
                    metadata |= INVISIBLE_CHARACTER;
                  }
                }
                tokens.push({
                  screenExtent: behindCount,
                  bufferExtent: Point(0, behindCount),
                  metadata: metadata
                });
              }
            } else {
              excessTokensScreenExtent = tokensScreenExtent - wrapScreenColumn;
              excessTokens = this.truncateTokens(tokens, tokensScreenExtent, wrapScreenColumn);
            }
            tokens.push({
              screenExtent: 0,
              bufferExtent: Point(0, 0),
              metadata: VOID | SOFT_LINE_BREAK
            });
            tokensScreenExtent = wrapScreenColumn;
            screenLineBufferEnd = Point(bufferRow, wrapBufferColumn);
            screenLines.push({
              screenExtent: tokensScreenExtent,
              bufferExtent: traversal(screenLineBufferEnd, screenLineBufferStart),
              tokens: tokens,
              softWrappedAtStart: continuingSoftWrappedLine,
              softWrappedAtEnd: true
            });
            continuingSoftWrappedLine = true;
            tokens = [];
            tokensScreenExtent = 0;
            screenLineBufferStart = screenLineBufferEnd;
            screenColumn = screenColumn - wrapScreenColumn;
            screenLineWidth = screenLineWidth - screenLineWidthAtWrapColumn;
            lastWrapBufferColumn = wrapBufferColumn;
            if (inTrailingWhitespace) {
              trailingWhitespaceStartScreenColumn = 0;
            }
            if (softWrapIndent < this.softWrapColumn) {
              indentLength = softWrapIndent;
            } else {
              indentLength = 0;
            }
            if ((indentLength + this.softWrapHangingIndent) < this.softWrapColumn) {
              indentLength += +this.softWrapHangingIndent;
            }
            if (indentLength > 0) {
              if (this.showIndentGuides) {
                indentGuidesCount = Math.ceil(indentLength / this.tabLength);
                while (indentGuidesCount-- > 1) {
                  tokens.push({
                    screenExtent: this.tabLength,
                    bufferExtent: Point.ZERO,
                    metadata: VOID | SOFT_WRAP_INDENTATION | SHOW_INDENT_GUIDE
                  });
                }
                tokens.push({
                  screenExtent: (indentLength % this.tabLength) || this.tabLength,
                  bufferExtent: Point.ZERO,
                  metadata: VOID | SOFT_WRAP_INDENTATION | SHOW_INDENT_GUIDE
                });
              } else {
                tokens.push({
                  screenExtent: indentLength,
                  bufferExtent: Point.ZERO,
                  metadata: VOID | SOFT_WRAP_INDENTATION
                });
              }
              tokensScreenExtent += indentLength;
              screenColumn += indentLength;
              screenLineWidth += this.ratioForCharacter(' ') * indentLength;
            }
            if (excessTokens != null) {
              tokens.push.apply(tokens, excessTokens);
              tokensScreenExtent += excessTokensScreenExtent;
              excessTokens = null;
              excessTokensScreenExtent = 0;
            }
          }
          if (foldEndBufferPosition != null) {
            if (screenColumn > tokensScreenExtent) {
              behindCount = screenColumn - tokensScreenExtent;
              tokens.push({
                screenExtent: behindCount,
                bufferExtent: Point(0, behindCount),
                metadata: 0
              });
              tokensScreenExtent = screenColumn;
            }
            previousPositionWasFold = true;
            foldStartBufferPosition = Point(bufferRow, bufferColumn);
            tokens.push({
              screenExtent: 1,
              bufferExtent: traversal(foldEndBufferPosition, foldStartBufferPosition),
              metadata: FOLD | ATOMIC
            });
            bufferRow = foldEndBufferPosition.row;
            bufferColumn = foldEndBufferPosition.column;
            bufferLine = this.buffer.lineForRow(bufferRow);
            bufferLineLength = bufferLine.length;
            isEmptyLine && (isEmptyLine = bufferLineLength === 0);
            screenColumn += 1;
            screenLineWidth += this.ratioForCharacter(this.foldCharacter);
            tokensScreenExtent = screenColumn;
            wrapBoundaryBufferColumn = bufferColumn;
            wrapBoundaryScreenColumn = screenColumn;
            wrapBoundaryEndsLeadingWhitespace = false;
            screenLineWidthAtWrapCharacter = screenLineWidth;
            inLeadingWhitespace = true;
            for (column = j = 0, ref3 = bufferColumn; j < ref3; column = j += 1) {
              character = bufferLine[column];
              if (!(character === ' ' || character === '\t')) {
                inLeadingWhitespace = false;
                break;
              }
            }
            trailingWhitespaceStartBufferColumn = this.findTrailingWhitespaceStartBufferColumn(bufferLine);
            if (bufferColumn >= trailingWhitespaceStartBufferColumn) {
              trailingWhitespaceStartBufferColumn = bufferColumn;
            }
          } else {
            if (character === '\t') {
              if (screenColumn > tokensScreenExtent) {
                behindCount = screenColumn - tokensScreenExtent;
                tokens.push({
                  screenExtent: behindCount,
                  bufferExtent: Point(0, behindCount),
                  metadata: 0
                });
                tokensScreenExtent = screenColumn;
              }
              distanceToNextTabStop = this.tabLength - (screenColumn % this.tabLength);
              metadata = HARD_TAB | ATOMIC;
              if (inLeadingWhitespace) {
                metadata |= LEADING_WHITESPACE;
              }
              if (inTrailingWhitespace) {
                metadata |= TRAILING_WHITESPACE;
              }
              if (this.invisibles.tab != null) {
                metadata |= INVISIBLE_CHARACTER;
              }
              if (this.showIndentGuides && (inLeadingWhitespace || isBlankLine && inTrailingWhitespace) && distanceToNextTabStop === this.tabLength) {
                metadata |= SHOW_INDENT_GUIDE;
              }
              tokens.push({
                screenExtent: distanceToNextTabStop,
                bufferExtent: Point(0, 1),
                metadata: metadata
              });
              bufferColumn += 1;
              screenColumn += distanceToNextTabStop;
              screenLineWidth += this.ratioForCharacter(' ') * distanceToNextTabStop;
              tokensScreenExtent = screenColumn;
            } else {
              bufferColumn += 1;
              if (character != null) {
                screenColumn += 1;
                screenLineWidth += this.ratioForCharacter(character);
              }
            }
          }
        }
        if (screenColumn > tokensScreenExtent) {
          behindCount = screenColumn - tokensScreenExtent;
          tokens.push({
            screenExtent: behindCount,
            bufferExtent: Point(0, behindCount),
            metadata: 0
          });
          tokensScreenExtent = screenColumn;
        }
        if (isEmptyLine) {
          emptyLineWhitespaceLength = this.whitespaceLengthForEmptyBufferRow(bufferRow);
        } else {
          emptyLineWhitespaceLength = 0;
        }
        lineEnding = this.buffer.lineEndingForRow(bufferRow);
        if (eolInvisibleReplacement = this.eolInvisibles[lineEnding]) {
          metadata = LINE_ENDING | VOID | INVISIBLE_CHARACTER;
          if (this.showIndentGuides && emptyLineWhitespaceLength > 0) {
            metadata |= SHOW_INDENT_GUIDE;
          }
          if (lineEnding === '\n') {
            metadata |= LF;
          } else if (lineEnding === '\r\n') {
            metadata |= CR | LF;
          } else if (lineEnding === '\r') {
            metadata |= CR;
          }
          tokens.push({
            screenExtent: eolInvisibleReplacement.length,
            bufferExtent: Point(0, 0),
            metadata: metadata
          });
          screenColumn += eolInvisibleReplacement.length;
          tokensScreenExtent = screenColumn;
          emptyLineWhitespaceLength -= eolInvisibleReplacement.length;
        }
        while (this.showIndentGuides && emptyLineWhitespaceLength > 0 && !previousPositionWasFold) {
          distanceToNextTabStop = this.tabLength - (screenColumn % this.tabLength);
          screenExtent = Math.min(distanceToNextTabStop, emptyLineWhitespaceLength);
          metadata = VOID;
          if (screenColumn % this.tabLength === 0) {
            metadata |= SHOW_INDENT_GUIDE;
          }
          tokens.push({
            screenExtent: screenExtent,
            bufferExtent: Point(0, 0),
            metadata: metadata
          });
          screenColumn += screenExtent;
          tokensScreenExtent = screenColumn;
          emptyLineWhitespaceLength -= screenExtent;
        }
        if (isEmptyLine) {
          tokens.unshift({
            screenExtent: 0,
            bufferExtent: Point(0, 0)
          });
        }
        bufferRow += 1;
        bufferColumn = 0;
        screenColumn = 0;
        screenLines.push({
          screenExtent: tokensScreenExtent,
          bufferExtent: traversal(Point(bufferRow, bufferColumn), screenLineBufferStart),
          tokens: tokens,
          softWrappedAtStart: continuingSoftWrappedLine,
          softWrappedAtEnd: false
        });
        tokens = [];
        screenLineWidth = 0;
      }
      return screenLines;
    };

    DisplayLayer.prototype.computeFoldsInBufferRowRange = function(startBufferRow, endBufferRow) {
      var foldEnd, foldMarkers, foldStart, folds, foldsStartBufferRow, followingFoldMarkers, i, name, precedingFoldMarkers;
      folds = {};
      foldMarkers = this.findFoldMarkers({
        intersectsRowRange: [startBufferRow, endBufferRow - 1],
        valid: true
      });
      if (foldMarkers.length > 0) {
        while (true) {
          foldsStartBufferRow = foldMarkers[0].getStartPosition().row;
          if (!(foldsStartBufferRow < startBufferRow)) {
            break;
          }
          precedingFoldMarkers = this.findFoldMarkers({
            intersectsRowRange: [foldsStartBufferRow, startBufferRow - 1]
          });
          foldMarkers.unshift.apply(foldMarkers, precedingFoldMarkers);
          startBufferRow = foldsStartBufferRow;
        }
        i = 0;
        while (i < foldMarkers.length) {
          foldStart = foldMarkers[i].getStartPosition();
          foldEnd = foldMarkers[i].getEndPosition();
          while (true) {
            if (foldEnd.row >= endBufferRow) {
              followingFoldMarkers = this.findFoldMarkers({
                intersectsRowRange: [endBufferRow, foldEnd.row]
              });
              foldMarkers.push.apply(foldMarkers, followingFoldMarkers);
              endBufferRow = foldEnd.row + 1;
            }
            if (i < (foldMarkers.length - 1) && comparePoints(foldMarkers[i + 1].getStartPosition(), foldEnd) < 0) {
              if (comparePoints(foldMarkers[i + 1].getEndPosition(), foldEnd) > 0) {
                foldEnd = foldMarkers[i + 1].getEndPosition();
              }
              i++;
            } else {
              break;
            }
          }
          if (comparePoints(foldEnd, foldStart) > 0) {
            if (folds[name = foldStart.row] == null) {
              folds[name] = {};
            }
            folds[foldStart.row][foldStart.column] = foldEnd;
          }
          i++;
        }
      }
      return {
        folds: folds,
        startBufferRow: startBufferRow,
        endBufferRow: endBufferRow
      };
    };

    DisplayLayer.prototype.whitespaceLengthForEmptyBufferRow = function(bufferRow) {
      var maxLeadingWhitespace, nextBufferRow, nextLine, previousBufferRow, previousLine;
      if (this.buffer.lineForRow(bufferRow).length > 0) {
        return 0;
      }
      previousBufferRow = bufferRow - 1;
      nextBufferRow = bufferRow + 1;
      while (true) {
        previousLine = this.buffer.lineForRow(previousBufferRow--);
        if ((previousLine == null) || previousLine.length > 0) {
          break;
        }
      }
      while (true) {
        nextLine = this.buffer.lineForRow(nextBufferRow++);
        if ((nextLine == null) || nextLine.length > 0) {
          break;
        }
      }
      maxLeadingWhitespace = 0;
      if (previousLine != null) {
        maxLeadingWhitespace = Math.max(maxLeadingWhitespace, this.findLeadingWhitespaceEndScreenColumn(previousLine));
      }
      if (nextLine != null) {
        maxLeadingWhitespace = Math.max(maxLeadingWhitespace, this.findLeadingWhitespaceEndScreenColumn(nextLine));
      }
      return maxLeadingWhitespace;
    };

    DisplayLayer.prototype.findLeadingWhitespaceEndScreenColumn = function(line) {
      var character, j, len, screenExtent;
      screenExtent = 0;
      for (j = 0, len = line.length; j < len; j += 1) {
        character = line[j];
        if (character === '\t') {
          screenExtent += this.tabLength - (screenExtent % this.tabLength);
        } else if (character === ' ') {
          screenExtent += 1;
        } else {
          break;
        }
      }
      return screenExtent;
    };

    DisplayLayer.prototype.findTrailingWhitespaceStartBufferColumn = function(line) {
      var character, column, j;
      for (column = j = line.length - 1; j >= 0; column = j += -1) {
        character = line[column];
        if (!(character === ' ' || character === '\t')) {
          return column + 1;
        }
      }
      return 0;
    };

    DisplayLayer.prototype.truncateTokens = function(tokens, screenExtent, truncationScreenColumn) {
      var excess, excessTokens, token, tokenStart;
      excessTokens = [];
      while (token = tokens.pop()) {
        tokenStart = screenExtent - token.screenExtent;
        if (tokenStart < truncationScreenColumn) {
          excess = truncationScreenColumn - tokenStart;
          excessTokens.unshift({
            bufferExtent: Point(token.bufferExtent.row, token.bufferExtent.column - excess),
            screenExtent: token.screenExtent - excess,
            metadata: token.metadata
          });
          token.screenExtent = excess;
          token.bufferExtent.column = excess;
          tokens.push(token);
        } else {
          excessTokens.unshift(token);
        }
        if (tokenStart <= truncationScreenColumn) {
          break;
        }
        screenExtent = tokenStart;
      }
      return excessTokens;
    };

    DisplayLayer.prototype.getText = function() {
      return this.getScreenLines().map(function(screenLine) {
        return screenLine.lineText;
      }).join('\n');
    };

    DisplayLayer.prototype.getScreenLines = function(startRow, endRow) {
      var atLineStart, bufferExtent, bufferLine, bufferLines, bufferRow, bufferStart, closeTag, closeTags, containingTag, containingTags, decorationIterator, decorationIteratorPositionBeforeSeek, endIndex, error, invalidationCount, iteratorPosition, j, k, l, len, len1, len2, len3, len4, len5, len6, len7, len8, m, metadata, n, o, openScopes, openTags, p, previousLineWasCached, q, r, ref1, ref10, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, s, screenExtent, screenLine, screenLineId, screenLineText, screenLines, screenRow, spatialDecoration, spatialScreenLines, spatialTokenBufferEnd, spatialTokenText, startIndex, tag, tagCodes, tags, tagsToClose, tagsToOpen, tagsToReopenAfterFold, tokenizedLine, tokenizedLines, wasCached;
      if (startRow == null) {
        startRow = 0;
      }
      if (endRow == null) {
        endRow = this.getScreenLineCount();
      }
      decorationIterator = this.textDecorationLayer.buildIterator();
      screenLines = [];
      this.spatialLineIterator.seekToScreenRow(startRow);
      containingTags = decorationIterator.seek(this.spatialLineIterator.getBufferStart());
      previousLineWasCached = false;
      while (this.spatialLineIterator.getScreenRow() < endRow) {
        screenLineId = this.spatialLineIterator.getId();
        if (this.screenLinesBySpatialLineId.has(screenLineId)) {
          screenLines.push(this.screenLinesBySpatialLineId.get(screenLineId));
          previousLineWasCached = true;
        } else {
          wasCached = previousLineWasCached;
          decorationIteratorPositionBeforeSeek = decorationIterator.getPosition();
          bufferStart = this.spatialLineIterator.getBufferStart();
          if (previousLineWasCached) {
            containingTags = decorationIterator.seek(bufferStart);
            previousLineWasCached = false;
          }
          screenLineText = '';
          tagCodes = [];
          spatialDecoration = null;
          closeTags = [];
          openTags = containingTags.slice();
          atLineStart = true;
          if (comparePoints(decorationIterator.getPosition(), bufferStart) < 0) {
            iteratorPosition = decorationIterator.getPosition();
            error = new Error("Invalid text decoration iterator position");
            bufferLines = [];
            ref1 = this.buffer.getLines();
            for (bufferRow = j = 0, len = ref1.length; j < len; bufferRow = ++j) {
              bufferLine = ref1[bufferRow];
              bufferLines[bufferRow] = bufferLine.length;
            }
            tokenizedLines = [];
            ref4 = (ref2 = (ref3 = this.textDecorationLayer) != null ? ref3.tokenizedLines : void 0) != null ? ref2 : [];
            for (bufferRow = k = 0, len1 = ref4.length; k < len1; bufferRow = ++k) {
              tokenizedLine = ref4[bufferRow];
              if ((bufferRow - 10 <= (ref5 = bufferStart.row) && ref5 <= bufferRow)) {
                tags = tokenizedLine.tags, openScopes = tokenizedLine.openScopes;
                tokenizedLines[bufferRow] = [tags, openScopes];
              } else {
                tokenizedLines[bufferRow] = tokenizedLine.text.length;
              }
            }
            screenLines = this.displayIndex.getScreenLines();
            spatialScreenLines = [];
            for (screenRow = l = 0, len2 = screenLines.length; l < len2; screenRow = ++l) {
              screenLine = screenLines[screenRow];
              if ((this.spatialLineIterator.getScreenRow() - 10 <= screenRow && screenRow <= this.spatialLineIterator.getScreenRow())) {
                spatialScreenLines[screenRow] = screenLines[screenRow];
              } else {
                spatialScreenLines[screenRow] = [screenLines[screenRow].screenExtent, Point.fromObject(screenLines[screenRow].bufferExtent).toString()];
              }
            }
            error.metadata = {
              spatialLineBufferStart: Point.fromObject(bufferStart).toString(),
              decorationIteratorPosition: Point.fromObject(iteratorPosition).toString(),
              previousLineWasCached: wasCached,
              decorationIteratorPositionBeforeSeek: Point.fromObject(decorationIteratorPositionBeforeSeek).toString(),
              spatialScreenLines: spatialScreenLines,
              tokenizedLines: tokenizedLines,
              bufferLines: bufferLines,
              grammarScopeName: (ref6 = this.textDecorationLayer.grammar) != null ? ref6.scopeName : void 0,
              tabLength: this.tabLength,
              invisibles: JSON.stringify(this.invisibles),
              showIndentGuides: this.showIndentGuides,
              softWrapColumn: this.softWrapColumn,
              softWrapHangingIndent: this.softWrapHangingIndent,
              foldCount: this.foldsMarkerLayer.getMarkerCount(),
              atomicSoftTabs: this.atomicSoftTabs,
              tokenizedBufferInvalidRows: this.textDecorationLayer.invalidRows
            };
            throw error;
          }
          ref7 = this.spatialLineIterator.getTokens();
          for (m = 0, len3 = ref7.length; m < len3; m++) {
            ref8 = ref7[m], screenExtent = ref8.screenExtent, bufferExtent = ref8.bufferExtent, metadata = ref8.metadata;
            spatialTokenBufferEnd = traverse(bufferStart, bufferExtent);
            tagsToClose = [];
            tagsToOpen = [];
            if (metadata & FOLD) {
              this.updateTags(closeTags, openTags, containingTags, containingTags.slice().reverse(), [], atLineStart);
              tagsToReopenAfterFold = decorationIterator.seek(spatialTokenBufferEnd);
              while (comparePoints(decorationIterator.getPosition(), spatialTokenBufferEnd) === 0) {
                ref9 = decorationIterator.getCloseTags();
                for (n = 0, len4 = ref9.length; n < len4; n++) {
                  closeTag = ref9[n];
                  tagsToReopenAfterFold.splice(tagsToReopenAfterFold.lastIndexOf(closeTag), 1);
                }
                tagsToReopenAfterFold.push.apply(tagsToReopenAfterFold, decorationIterator.getOpenTags());
                decorationIterator.moveToSuccessor();
              }
            } else {
              if (spatialDecoration != null) {
                tagsToClose.push(spatialDecoration);
              }
              if (!(metadata & SOFT_LINE_BREAK)) {
                if (tagsToReopenAfterFold != null) {
                  tagsToOpen.push.apply(tagsToOpen, tagsToReopenAfterFold);
                  tagsToReopenAfterFold = null;
                }
                if (comparePoints(decorationIterator.getPosition(), bufferStart) === 0) {
                  tagsToClose.push.apply(tagsToClose, decorationIterator.getCloseTags());
                  tagsToOpen.push.apply(tagsToOpen, decorationIterator.getOpenTags());
                  decorationIterator.moveToSuccessor();
                }
              }
            }
            if (spatialDecoration = getSpatialTokenTextDecoration(metadata)) {
              tagsToOpen.push(spatialDecoration);
            }
            this.updateTags(closeTags, openTags, containingTags, tagsToClose, tagsToOpen, atLineStart);
            spatialTokenText = this.buildTokenText(metadata, screenExtent, bufferStart, spatialTokenBufferEnd);
            startIndex = 0;
            while (comparePoints(decorationIterator.getPosition(), spatialTokenBufferEnd) < 0) {
              endIndex = startIndex + decorationIterator.getPosition().column - bufferStart.column;
              for (o = 0, len5 = closeTags.length; o < len5; o++) {
                tag = closeTags[o];
                tagCodes.push(this.codeForCloseTag(tag));
              }
              for (p = 0, len6 = openTags.length; p < len6; p++) {
                tag = openTags[p];
                tagCodes.push(this.codeForOpenTag(tag));
              }
              tagCodes.push(endIndex - startIndex);
              bufferStart = decorationIterator.getPosition();
              startIndex = endIndex;
              closeTags = [];
              openTags = [];
              this.updateTags(closeTags, openTags, containingTags, decorationIterator.getCloseTags(), decorationIterator.getOpenTags());
              decorationIterator.moveToSuccessor();
            }
            for (q = 0, len7 = closeTags.length; q < len7; q++) {
              tag = closeTags[q];
              tagCodes.push(this.codeForCloseTag(tag));
            }
            for (r = 0, len8 = openTags.length; r < len8; r++) {
              tag = openTags[r];
              tagCodes.push(this.codeForOpenTag(tag));
            }
            tagCodes.push(spatialTokenText.length - startIndex);
            screenLineText += spatialTokenText;
            closeTags = [];
            openTags = [];
            bufferStart = spatialTokenBufferEnd;
            atLineStart = false;
          }
          if (containingTags.length > 0) {
            for (s = containingTags.length - 1; s >= 0; s += -1) {
              containingTag = containingTags[s];
              tagCodes.push(this.codeForCloseTag(containingTag));
            }
          }
          if (tagsToReopenAfterFold != null) {
            containingTags = tagsToReopenAfterFold;
            tagsToReopenAfterFold = null;
          } else if (spatialDecoration != null) {
            containingTags.splice(containingTags.indexOf(spatialDecoration), 1);
          }
          while (!this.spatialLineIterator.isSoftWrappedAtEnd() && comparePoints(decorationIterator.getPosition(), spatialTokenBufferEnd) === 0) {
            this.updateTags(closeTags, openTags, containingTags, decorationIterator.getCloseTags(), decorationIterator.getOpenTags());
            decorationIterator.moveToSuccessor();
          }
          invalidationCount = (ref10 = this.invalidationCountsBySpatialLineId.get(screenLineId)) != null ? ref10 : 0;
          screenLine = {
            id: screenLineId + "-" + invalidationCount,
            lineText: screenLineText,
            tagCodes: tagCodes
          };
          this.screenLinesBySpatialLineId.set(screenLineId, screenLine);
          screenLines.push(screenLine);
        }
        if (!this.spatialLineIterator.moveToSuccessor()) {
          break;
        }
      }
      return screenLines;
    };

    DisplayLayer.prototype.isOpenTagCode = function(tagCode) {
      return tagCode < 0 && tagCode % 2 === -1;
    };

    DisplayLayer.prototype.isCloseTagCode = function(tagCode) {
      return tagCode < 0 && tagCode % 2 === 0;
    };

    DisplayLayer.prototype.tagForCode = function(tagCode) {
      if (this.isCloseTagCode(tagCode)) {
        tagCode++;
      }
      return this.tagsByCode.get(tagCode);
    };

    DisplayLayer.prototype.codeForOpenTag = function(tag) {
      var codeForTag;
      if (this.codesByTag.has(tag)) {
        return this.codesByTag.get(tag);
      } else {
        codeForTag = this.nextOpenTagCode;
        this.codesByTag.set(tag, this.nextOpenTagCode);
        this.tagsByCode.set(this.nextOpenTagCode, tag);
        this.nextOpenTagCode -= 2;
        return codeForTag;
      }
    };

    DisplayLayer.prototype.codeForCloseTag = function(tag) {
      return this.codeForOpenTag(tag) - 1;
    };

    DisplayLayer.prototype.buildTokenText = function(metadata, screenExtent, bufferStart, bufferEnd) {
      if (metadata & HARD_TAB) {
        if (this.invisibles.tab != null) {
          return this.invisibles.tab + ' '.repeat(screenExtent - 1);
        } else {
          return ' '.repeat(screenExtent);
        }
      } else if (((metadata & LEADING_WHITESPACE) || (metadata & TRAILING_WHITESPACE)) && (this.invisibles.space != null)) {
        return this.invisibles.space.repeat(screenExtent);
      } else if (metadata & FOLD) {
        return this.foldCharacter;
      } else if (metadata & VOID) {
        if (metadata & LINE_ENDING) {
          if ((metadata & CR) && (metadata & LF)) {
            return this.eolInvisibles['\r\n'];
          } else if (metadata & LF) {
            return this.eolInvisibles['\n'];
          } else {
            return this.eolInvisibles['\r'];
          }
        } else {
          return ' '.repeat(screenExtent);
        }
      } else {
        return this.buffer.getTextInRange(Range(bufferStart, bufferEnd));
      }
    };

    DisplayLayer.prototype.updateTags = function(closeTags, openTags, containingTags, tagsToClose, tagsToOpen, atLineStart) {
      var closeTag, containingTagsIndex, j, k, l, len, len1, len2, mostRecentOpenTag, tag, tagsToCloseCounts;
      if (atLineStart) {
        for (j = 0, len = tagsToClose.length; j < len; j++) {
          closeTag = tagsToClose[j];
          openTags.splice(openTags.lastIndexOf(closeTag), 1);
          containingTags.splice(containingTags.lastIndexOf(closeTag), 1);
        }
      } else {
        tagsToCloseCounts = {};
        for (k = 0, len1 = tagsToClose.length; k < len1; k++) {
          tag = tagsToClose[k];
          if (tagsToCloseCounts[tag] == null) {
            tagsToCloseCounts[tag] = 0;
          }
          tagsToCloseCounts[tag]++;
        }
        containingTagsIndex = containingTags.length;
        for (l = 0, len2 = tagsToClose.length; l < len2; l++) {
          closeTag = tagsToClose[l];
          if (!(tagsToCloseCounts[closeTag] > 0)) {
            continue;
          }
          while (mostRecentOpenTag = containingTags[--containingTagsIndex]) {
            if (mostRecentOpenTag === closeTag) {
              containingTags.splice(containingTagsIndex, 1);
              tagsToCloseCounts[mostRecentOpenTag]--;
              break;
            }
            closeTags.push(mostRecentOpenTag);
            if (tagsToCloseCounts[mostRecentOpenTag] > 0) {
              containingTags.splice(containingTagsIndex, 1);
              tagsToCloseCounts[mostRecentOpenTag]--;
            } else {
              openTags.unshift(mostRecentOpenTag);
            }
          }
          if (mostRecentOpenTag != null) {
            closeTags.push(closeTag);
          }
        }
      }
      openTags.push.apply(openTags, tagsToOpen);
      return containingTags.push.apply(containingTags, tagsToOpen);
    };

    DisplayLayer.prototype.translateBufferPosition = function(bufferPosition, options) {
      var clipDirection, distanceFromEnd, distanceFromStart, screenPosition;
      bufferPosition = this.buffer.clipPosition(bufferPosition, options);
      clipDirection = options != null ? options.clipDirection : void 0;
      this.spatialTokenIterator.seekToBufferPosition(bufferPosition);
      if (this.spatialTokenIterator.getMetadata() & SOFT_LINE_BREAK || this.spatialTokenIterator.getMetadata() & SOFT_WRAP_INDENTATION) {
        clipDirection = 'forward';
      }
      while (this.spatialTokenIterator.getMetadata() & VOID) {
        if (clipDirection === 'forward') {
          if (this.spatialTokenIterator.moveToSuccessor()) {
            bufferPosition = this.spatialTokenIterator.getBufferStart();
          } else {
            clipDirection = 'backward';
          }
        } else {
          this.spatialTokenIterator.moveToPredecessor();
          bufferPosition = this.spatialTokenIterator.getBufferEnd();
        }
      }
      if (this.spatialTokenIterator.getMetadata() & ATOMIC) {
        if (comparePoints(bufferPosition, this.spatialTokenIterator.getBufferStart()) === 0) {
          screenPosition = this.spatialTokenIterator.getScreenStart();
        } else if (comparePoints(bufferPosition, this.spatialTokenIterator.getBufferEnd()) === 0 || (options != null ? options.clipDirection : void 0) === 'forward') {
          screenPosition = this.spatialTokenIterator.getScreenEnd();
        } else if ((options != null ? options.clipDirection : void 0) === 'backward') {
          screenPosition = this.spatialTokenIterator.getScreenStart();
        } else {
          distanceFromStart = traversal(bufferPosition, this.spatialTokenIterator.getBufferStart());
          distanceFromEnd = traversal(this.spatialTokenIterator.getBufferEnd(), bufferPosition);
          if (distanceFromEnd.compare(distanceFromStart) < 0) {
            screenPosition = this.spatialTokenIterator.getScreenEnd();
          } else {
            screenPosition = this.spatialTokenIterator.getScreenStart();
          }
        }
      } else {
        screenPosition = this.spatialTokenIterator.translateBufferPosition(bufferPosition);
      }
      return Point.fromObject(screenPosition);
    };

    DisplayLayer.prototype.translateBufferRange = function(bufferRange, options) {
      var end, start;
      bufferRange = Range.fromObject(bufferRange);
      start = this.translateBufferPosition(bufferRange.start, options);
      end = this.translateBufferPosition(bufferRange.end, options);
      return Range(start, end);
    };

    DisplayLayer.prototype.translateScreenPosition = function(screenPosition, options) {
      var bufferPosition, clipDirection, screenEndColumn, screenStartColumn, softLineBreak;
      screenPosition = Point.fromObject(screenPosition);
      screenPosition = clipNegativePoint(screenPosition);
      clipDirection = options != null ? options.clipDirection : void 0;
      this.spatialTokenIterator.seekToScreenPosition(screenPosition);
      while (this.spatialTokenIterator.getMetadata() & VOID) {
        if (this.spatialTokenIterator.getMetadata() & LINE_ENDING && comparePoints(screenPosition, this.spatialTokenIterator.getScreenStart()) === 0) {
          break;
        }
        if (clipDirection === 'forward' || ((options != null ? options.skipSoftWrapIndentation : void 0) && this.spatialTokenIterator.getMetadata() & SOFT_WRAP_INDENTATION)) {
          if (this.spatialTokenIterator.moveToSuccessor()) {
            screenPosition = this.spatialTokenIterator.getScreenStart();
          } else {
            clipDirection = 'backward';
          }
        } else {
          softLineBreak = this.spatialTokenIterator.getMetadata() & SOFT_LINE_BREAK;
          this.spatialTokenIterator.moveToPredecessor();
          screenPosition = this.spatialTokenIterator.getScreenEnd();
          if (softLineBreak) {
            screenPosition = traverse(screenPosition, Point(0, -1));
          }
        }
      }
      if (this.spatialTokenIterator.getMetadata() & ATOMIC) {
        if (comparePoints(screenPosition, this.spatialTokenIterator.getScreenStart()) === 0) {
          bufferPosition = this.spatialTokenIterator.getBufferStart();
        } else if (comparePoints(screenPosition, this.spatialTokenIterator.getScreenEnd()) === 0 || (options != null ? options.clipDirection : void 0) === 'forward') {
          bufferPosition = this.spatialTokenIterator.getBufferEnd();
        } else if ((options != null ? options.clipDirection : void 0) === 'backward') {
          bufferPosition = this.spatialTokenIterator.getBufferStart();
        } else {
          screenStartColumn = this.spatialTokenIterator.getScreenStart().column;
          screenEndColumn = this.spatialTokenIterator.getScreenEnd().column;
          if (screenPosition.column > ((screenStartColumn + screenEndColumn) / 2)) {
            bufferPosition = this.spatialTokenIterator.getBufferEnd();
          } else {
            bufferPosition = this.spatialTokenIterator.getBufferStart();
          }
        }
      } else {
        bufferPosition = this.spatialTokenIterator.translateScreenPosition(screenPosition);
      }
      if (comparePoints(screenPosition, this.spatialTokenIterator.getScreenEnd()) > 0) {
        bufferPosition = this.buffer.clipPosition(bufferPosition, options);
      }
      return Point.fromObject(bufferPosition);
    };

    DisplayLayer.prototype.translateScreenRange = function(screenRange, options) {
      var end, start;
      screenRange = Range.fromObject(screenRange);
      start = this.translateScreenPosition(screenRange.start, options);
      end = this.translateScreenPosition(screenRange.end, options);
      return Range(start, end);
    };

    DisplayLayer.prototype.clipScreenPosition = function(screenPosition, options) {
      var clipDirection, screenEndColumn, screenStartColumn, softLineBreak;
      screenPosition = Point.fromObject(screenPosition);
      screenPosition = clipNegativePoint(screenPosition);
      clipDirection = options != null ? options.clipDirection : void 0;
      this.spatialTokenIterator.seekToScreenPosition(screenPosition);
      while (this.spatialTokenIterator.getMetadata() & VOID) {
        if (this.spatialTokenIterator.getMetadata() & LINE_ENDING && comparePoints(screenPosition, this.spatialTokenIterator.getScreenStart()) === 0) {
          break;
        }
        if (clipDirection === 'forward' || ((options != null ? options.skipSoftWrapIndentation : void 0) && this.spatialTokenIterator.getMetadata() & SOFT_WRAP_INDENTATION)) {
          if (this.spatialTokenIterator.moveToSuccessor()) {
            screenPosition = this.spatialTokenIterator.getScreenStart();
          } else {
            clipDirection = 'backward';
          }
        } else {
          softLineBreak = this.spatialTokenIterator.getMetadata() & SOFT_LINE_BREAK;
          this.spatialTokenIterator.moveToPredecessor();
          screenPosition = this.spatialTokenIterator.getScreenEnd();
          if (softLineBreak) {
            screenPosition = traverse(screenPosition, Point(0, -1));
          }
        }
      }
      if (comparePoints(screenPosition, this.spatialTokenIterator.getScreenEnd()) <= 0) {
        if (this.spatialTokenIterator.getMetadata() & ATOMIC && comparePoints(screenPosition, this.spatialTokenIterator.getScreenStart()) > 0 && comparePoints(screenPosition, this.spatialTokenIterator.getScreenEnd()) < 0) {
          if ((options != null ? options.clipDirection : void 0) === 'forward') {
            screenPosition = this.spatialTokenIterator.getScreenEnd();
          } else if ((options != null ? options.clipDirection : void 0) === 'backward') {
            screenPosition = this.spatialTokenIterator.getScreenStart();
          } else {
            screenStartColumn = this.spatialTokenIterator.getScreenStart().column;
            screenEndColumn = this.spatialTokenIterator.getScreenEnd().column;
            if (screenPosition.column > ((screenStartColumn + screenEndColumn) / 2)) {
              screenPosition = this.spatialTokenIterator.getScreenEnd();
            } else {
              screenPosition = this.spatialTokenIterator.getScreenStart();
            }
          }
        }
      } else {
        if ((options != null ? options.clipDirection : void 0) === 'forward' && this.spatialTokenIterator.moveToSuccessor()) {
          screenPosition = this.spatialTokenIterator.getScreenStart();
        } else {
          screenPosition = this.spatialTokenIterator.getScreenEnd();
        }
      }
      return Point.fromObject(screenPosition);
    };

    DisplayLayer.prototype.softWrapDescriptorForScreenRow = function(row) {
      this.spatialLineIterator.seekToScreenRow(row);
      return {
        softWrappedAtStart: this.spatialLineIterator.isSoftWrappedAtStart(),
        softWrappedAtEnd: this.spatialLineIterator.isSoftWrappedAtEnd(),
        bufferRow: this.spatialLineIterator.getBufferStart().row
      };
    };

    DisplayLayer.prototype.getScreenLineCount = function() {
      return this.displayIndex.getScreenLineCount();
    };

    DisplayLayer.prototype.getRightmostScreenPosition = function() {
      return this.displayIndex.getScreenPositionWithMaxLineLength() || Point.ZERO;
    };

    DisplayLayer.prototype.lineLengthForScreenRow = function(screenRow) {
      return this.displayIndex.lineLengthForScreenRow(screenRow) || 0;
    };

    return DisplayLayer;

  })();

}).call(this);
