text: /* ***** BEGIN LICENSE BLOCK *****
   * Distributed under the BSD license:
   *
   * Copyright (c) 2012, Ajax.org B.V.
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *     * Redistributions of source code must retain the above copyright
   *       notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above copyright
   *       notice, this list of conditions and the following disclaimer in the
   *       documentation and/or other materials provided with the distribution.
   *     * Neither the name of Ajax.org B.V. nor the
   *       names of its contributors may be used to endorse or promote products
   *       derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * ***** END LICENSE BLOCK ***** */
  
  define('ace/ext/elastic_tabstops_lite', ['require', 'exports', 'module' , 'ace/editor', 'ace/config'], function(require, exports, module) {
  
  
  var ElasticTabstopsLite = function(editor) {
      this.$editor = editor;
      var self = this;
      var changedRows = [];
      var recordChanges = false;
      this.onAfterExec = function() {
          recordChanges = false;
          self.processRows(changedRows);
          changedRows = [];
      };
      this.onExec = function() {
          recordChanges = true;
      };
      this.onChange = function(e) {
          var range = e.data.range
          if (recordChanges) {
              if (changedRows.indexOf(range.start.row) == -1)
                  changedRows.push(range.start.row);
              if (range.end.row != range.start.row)
                  changedRows.push(range.end.row);
          }
      };
  };
  
  (function() {
      this.processRows = function(rows) {
          this.$inChange = true;
          var checkedRows = [];
  
          for (var r = 0, rowCount = rows.length; r < rowCount; r++) {
              var row = rows[r];
  
              if (checkedRows.indexOf(row) > -1)
                  continue;
  
              var cellWidthObj = this.$findCellWidthsForBlock(row);
              var cellWidths = this.$setBlockCellWidthsToMax(cellWidthObj.cellWidths);
              var rowIndex = cellWidthObj.firstRow;
  
              for (var w = 0, l = cellWidths.length; w < l; w++) {
                  var widths = cellWidths[w];
                  checkedRows.push(rowIndex);
                  this.$adjustRow(rowIndex, widths);
                  rowIndex++;
              }
          }
          this.$inChange = false;
      };
  
      this.$findCellWidthsForBlock = function(row) {
          var cellWidths = [], widths;
          var rowIter = row;
          while (rowIter >= 0) {
              widths = this.$cellWidthsForRow(rowIter);
              if (widths.length == 0)
                  break;
  
              cellWidths.unshift(widths);
              rowIter--;
          }
          var firstRow = rowIter + 1;
          rowIter = row;
          var numRows = this.$editor.session.getLength();
  
          while (rowIter < numRows - 1) {
              rowIter++;
  
              widths = this.$cellWidthsForRow(rowIter);
              if (widths.length == 0)
                  break;
  
              cellWidths.push(widths);
          }
  
          return { cellWidths: cellWidths, firstRow: firstRow };
      };
  
      this.$cellWidthsForRow = function(row) {
          var selectionColumns = this.$selectionColumnsForRow(row);
  
          var tabs = [-1].concat(this.$tabsForRow(row));
          var widths = tabs.map(function(el) { return 0; } ).slice(1);
          var line = this.$editor.session.getLine(row);
  
          for (var i = 0, len = tabs.length - 1; i < len; i++) {
              var leftEdge = tabs[i]+1;
              var rightEdge = tabs[i+1];
  
              var rightmostSelection = this.$rightmostSelectionInCell(selectionColumns, rightEdge);
              var cell = line.substring(leftEdge, rightEdge);
              widths[i] = Math.max(cell.replace(/\s+$/g,'').length, rightmostSelection - leftEdge);
          }
  
          return widths;
      };
  
      this.$selectionColumnsForRow = function(row) {
          var selections = [], cursor = this.$editor.getCursorPosition();
          if (this.$editor.session.getSelection().isEmpty()) {
              if (row == cursor.row)
                  selections.push(cursor.column);
          }
  
          return selections;
      };
  
      this.$setBlockCellWidthsToMax = function(cellWidths) {
          var startingNewBlock = true, blockStartRow, blockEndRow, maxWidth;
          var columnInfo = this.$izip_longest(cellWidths);
  
          for (var c = 0, l = columnInfo.length; c < l; c++) {
              var column = columnInfo[c];
              if (!column.push) {
                  console.error(column);
                  continue;
              }
              column.push(NaN);
  
              for (var r = 0, s = column.length; r < s; r++) {
                  var width = column[r];
                  if (startingNewBlock) {
                      blockStartRow = r;
                      maxWidth = 0;
                      startingNewBlock = false;
                  }
                  if (isNaN(width)) {
                      blockEndRow = r;
  
                      for (var j = blockStartRow; j < blockEndRow; j++) {
                          cellWidths[j][c] = maxWidth;
                      }
                      startingNewBlock = true;
                  }
  
                  maxWidth = Math.max(maxWidth, width);
              }
          }
  
          return cellWidths;
      };
  
      this.$rightmostSelectionInCell = function(selectionColumns, cellRightEdge) {
          var rightmost = 0;
  
          if (selectionColumns.length) {
              var lengths = [];
              for (var s = 0, length = selectionColumns.length; s < length; s++) {
                  if (selectionColumns[s] <= cellRightEdge)
                      lengths.push(s);
                  else
                      lengths.push(0);
              }
              rightmost = Math.max.apply(Math, lengths);
          }
  
          return rightmost;
      };
  
      this.$tabsForRow = function(row) {
          var rowTabs = [], line = this.$editor.session.getLine(row),
              re = /\t/g, match;
  
          while ((match = re.exec(line)) != null) {
              rowTabs.push(match.index);
          }
  
          return rowTabs;
      };
  
      this.$adjustRow = function(row, widths) {
          var rowTabs = this.$tabsForRow(row);
  
          if (rowTabs.length == 0)
              return;
  
          var bias = 0, location = -1;
          var expandedSet = this.$izip(widths, rowTabs);
  
          for (var i = 0, l = expandedSet.length; i < l; i++) {
              var w = expandedSet[i][0], it = expandedSet[i][1];
              location += 1 + w;
              it += bias;
              var difference = location - it;
  
              if (difference == 0)
                  continue;
  
              var partialLine = this.$editor.session.getLine(row).substr(0, it );
              var strippedPartialLine = partialLine.replace(/\s*$/g, "");
              var ispaces = partialLine.length - strippedPartialLine.length;
  
              if (difference > 0) {
                  this.$editor.session.getDocument().insertInLine({row: row, column: it + 1}, Array(difference + 1).join(" ") + "\t");
                  this.$editor.session.getDocument().removeInLine(row, it, it + 1);
  
                  bias += difference;
              }
  
              if (difference < 0 && ispaces >= -difference) {
                  this.$editor.session.getDocument().removeInLine(row, it + difference, it);
                  bias += difference;
              }
          }
      };
      this.$izip_longest = function(iterables) {
          if (!iterables[0])
              return [];
          var longest = iterables[0].length;
          var iterablesLength = iterables.length;
  
          for (var i = 1; i < iterablesLength; i++) {
              var iLength = iterables[i].length;
              if (iLength > longest)
                  longest = iLength;
          }
  
          var expandedSet = [];
  
          for (var l = 0; l < longest; l++) {
              var set = [];
              for (var i = 0; i < iterablesLength; i++) {
                  if (iterables[i][l] === "")
                      set.push(NaN);
                  else
                      set.push(iterables[i][l]);
              }
  
              expandedSet.push(set);
          }
  
  
          return expandedSet;
      };
      this.$izip = function(widths, tabs) {
          var size = widths.length >= tabs.length ? tabs.length : widths.length;
  
          var expandedSet = [];
          for (var i = 0; i < size; i++) {
              var set = [ widths[i], tabs[i] ];
              expandedSet.push(set);
          }
          return expandedSet;
      };
  
  }).call(ElasticTabstopsLite.prototype);
  
  exports.ElasticTabstopsLite = ElasticTabstopsLite;
  
  var Editor = require("../editor").Editor;
  require("../config").defineOptions(Editor.prototype, "editor", {
      useElasticTabstops: {
          set: function(val) {
              if (val) {
                  if (!this.elasticTabstops)
                      this.elasticTabstops = new ElasticTabstopsLite(this);
                  this.commands.on("afterExec", this.elasticTabstops.onAfterExec);
                  this.commands.on("exec", this.elasticTabstops.onExec);
                  this.on("change", this.elasticTabstops.onChange);
              } else if (this.elasticTabstops) {
                  this.commands.removeListener("afterExec", this.elasticTabstops.onAfterExec);
                  this.commands.removeListener("exec", this.elasticTabstops.onExec);
                  this.removeListener("change", this.elasticTabstops.onChange);
              }
          }
      }
  });
  
  });