From cf079109d720b07925f221635ea5c80b0d882ae8 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Mon, 26 Nov 2012 16:37:39 -0800 Subject: [PATCH 01/16] Stash --- lib/ace/editor.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 4039e0b7..21f33eac 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -769,6 +769,51 @@ var Editor = function(renderer, session) { } if (shouldOutdent) mode.autoOutdent(lineState, session, cursor.row); + + if (true || this.session.getElasticTabstops()) { + var row = 0, rows = this.session.getLength(), + tabString = "\t"; + + while (row <= rows) { + var cellWidths = this.$findCellWidthsForBlock(row, tabString), + cellWidthsByRow = cellWidths.byRow, rowIndex = cellWidths.rowIndex; + } + } + }; + + + this.$findCellWidthsForBlock = function(row, tabString) { + var cellWidths = []; + + var rowIter = row; + + while (rowIter >= 0) { + var widths = this.$cellWidthsForRow(rowIter, tabString); + } + }; + + this.$cellWidthsForRow = function(row, tabString) { + var selectionColumns = this.$selectionColumnsForRow(row, tabString); + }; + + this.$selectionColumnsForRow = function(row, tabString) { + var selections = []; + + + }; + + this.$tabsForRow = function(row, tabString) { + var rowTabs = []; + + var matches = session.getLine(row).match(new RegExp(tabString, "g")); + + if (matches !== null) { + for (var m = 0, matchLength = matches.length; m < matchLength; m++) { + rowTabs.push(matches[m].length); + } + } + + return rowTabs; }; this.onTextInput = function(text) { @@ -1212,6 +1257,7 @@ var Editor = function(renderer, session) { indentString = lang.stringRepeat(" ", count); } else indentString = "\t"; + return this.insert(indentString); } }; From 9dc94741a6b4551465ea00a09fed5e302a554926 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Fri, 30 Nov 2012 17:27:27 -0800 Subject: [PATCH 02/16] Stash --- lib/ace/editor.js | 69 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 21f33eac..9115b6ca 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -771,35 +771,74 @@ var Editor = function(renderer, session) { mode.autoOutdent(lineState, session, cursor.row); if (true || this.session.getElasticTabstops()) { - var row = 0, rows = this.session.getLength(), - tabString = "\t"; + // todo: make this a unique set + var row = cursor.row; - while (row <= rows) { - var cellWidths = this.$findCellWidthsForBlock(row, tabString), - cellWidthsByRow = cellWidths.byRow, rowIndex = cellWidths.rowIndex; - } + this.$processRow([row]); } }; + this.$processRow = function(rows) { + for (var r = 0, rowCount = rows.length; r < rowCount; r++) { + var cellWidthObj = this.$findCellWidthsForBlock(rows[r]); + } + }; + this.$findCellWidthsForBlock = function(row, tabString) { - var cellWidths = []; + var cellWidths = [], widths; - var rowIter = row; - - while (rowIter >= 0) { - var widths = this.$cellWidthsForRow(rowIter, tabString); + // starting row and backward + var row_iter = row + while (row_iter >= 0) { + widths = cellWidthsForRow(row_iter); + if (len(widths) == 0) + break; + cellWidths.unshift(widths); + row_iter--; } + + first_row = row_iter++; + + // forward (not including starting row) + row_iter = row; + var num_rows = lines_in_buffer(view); + + while (row_iter < num_rows - 1) { + row_iter++; + widths = cellWidthsForRow(row_iter); + if (len(widths) == 0) + break; + cellWidths.push(widths); + } + + return {cellWidths: cellWidths, firstRow: firstRow}; }; - this.$cellWidthsForRow = function(row, tabString) { - var selectionColumns = this.$selectionColumnsForRow(row, tabString); + this.$cellWidthsForRow = function(row) { + var selectionColumns = this.$selectionColumnsForRow(row); + //var tabs = [-1] + this.$tabsForRow(row); + //widths = [0] * (len(tabs) - 1); + //line = view.substr(view.line(view.text_point(row,0))) + //for (var i = 0, len = tabs.length; i < len; i++) { + //left_edge = tabs[i]+1 + //right_edge = tabs[i+1] + //rightmost_selection = rightmost_selection_in_cell(selection_columns, right_edge) + //cell = line[left_edge:right_edge] + //widths[i] = max(len(cell.rstrip()), rightmost_selection - left_edge) + //} + return widths;; }; this.$selectionColumnsForRow = function(row, tabString) { var selections = []; - - + selections = [] + for s in view.sel(): + if s.empty(): + r, c =view.rowcol(s.a) + if r == row: + selections.append(c) + return selections }; this.$tabsForRow = function(row, tabString) { From cb5a8a4be4c17e61e5a4302895e8fdea6a27fb65 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Fri, 30 Nov 2012 17:51:48 -0800 Subject: [PATCH 03/16] Stash --- lib/ace/editor.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 9115b6ca..7f705145 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -771,7 +771,7 @@ var Editor = function(renderer, session) { mode.autoOutdent(lineState, session, cursor.row); if (true || this.session.getElasticTabstops()) { - // todo: make this a unique set + // todo: support multicursor var row = cursor.row; this.$processRow([row]); @@ -791,7 +791,7 @@ var Editor = function(renderer, session) { // starting row and backward var row_iter = row while (row_iter >= 0) { - widths = cellWidthsForRow(row_iter); + widths = this.$cellWidthsForRow(row_iter); if (len(widths) == 0) break; cellWidths.unshift(widths); @@ -806,7 +806,7 @@ var Editor = function(renderer, session) { while (row_iter < num_rows - 1) { row_iter++; - widths = cellWidthsForRow(row_iter); + widths = this.$cellWidthsForRow(row_iter); if (len(widths) == 0) break; cellWidths.push(widths); @@ -827,18 +827,20 @@ var Editor = function(renderer, session) { //cell = line[left_edge:right_edge] //widths[i] = max(len(cell.rstrip()), rightmost_selection - left_edge) //} - return widths;; + //return widths; }; this.$selectionColumnsForRow = function(row, tabString) { var selections = []; - selections = [] - for s in view.sel(): - if s.empty(): - r, c =view.rowcol(s.a) - if r == row: - selections.append(c) - return selections + // todo: support multicursor + console.log("roar"); + console.log(this.getCursorPositionScreen()); + //for s in view.sel(): + //if (s.empty()) + //r, c =view.rowcol(s.a) + //if (r == row) + // selections.append(c) + return selections; }; this.$tabsForRow = function(row, tabString) { From 18d12d53756580f094dba218d5830b9389ac7b5b Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Tue, 11 Dec 2012 15:59:29 -0800 Subject: [PATCH 04/16] Hell yes, get all the Python implementation out of the way --- lib/ace/editor.js | 179 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 132 insertions(+), 47 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 7f705145..b3c0030a 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -774,89 +774,174 @@ var Editor = function(renderer, session) { // todo: support multicursor var row = cursor.row; - this.$processRow([row]); + this.$processRow(cursor, [row]); } }; - this.$processRow = function(rows) { + this.$processRow = function(cursor, rows) { for (var r = 0, rowCount = rows.length; r < rowCount; r++) { - var cellWidthObj = this.$findCellWidthsForBlock(rows[r]); + var cellWidthObj = this.$findCellWidthsForBlock(cursor, rows[r]); + cellWidthObj.cellWidths = this.$setBlockCellWidthsToMax(cellWidthObj.cellWidths); + console.log(cellWidthObj.cellWidths) + //for widths in cell_widths_by_row: + // checked_rows.add(row_index) + // edit = adjust_row(view, edit, row_index, widths) + // row_index += 1 } }; - this.$findCellWidthsForBlock = function(row, tabString) { - var cellWidths = [], widths; + this.$findCellWidthsForBlock = function(cursor, row) { + var cellWidths = [], widths; // starting row and backward - var row_iter = row - while (row_iter >= 0) { - widths = this.$cellWidthsForRow(row_iter); - if (len(widths) == 0) + var rowIter = row; + while (rowIter >= 0) { + widths = this.$cellWidthsForRow(cursor, rowIter); + if (widths.length == 0) break; - cellWidths.unshift(widths); - row_iter--; - } - first_row = row_iter++; + cellWidths.unshift(widths); + rowIter--; + } + var firstRow = rowIter + 1; // forward (not including starting row) - row_iter = row; - var num_rows = lines_in_buffer(view); + rowIter = row; + var numRows = this.session.getLength(); - while (row_iter < num_rows - 1) { - row_iter++; - widths = this.$cellWidthsForRow(row_iter); - if (len(widths) == 0) + while (rowIter < numRows - 1) { + rowIter++; + + widths = this.$cellWidthsForRow(cursor, rowIter); + if (widths.length == 0) break; + cellWidths.push(widths); } - return {cellWidths: cellWidths, firstRow: firstRow}; + return { cellWidths: cellWidths, firstRow: firstRow }; }; - this.$cellWidthsForRow = function(row) { - var selectionColumns = this.$selectionColumnsForRow(row); - //var tabs = [-1] + this.$tabsForRow(row); - //widths = [0] * (len(tabs) - 1); - //line = view.substr(view.line(view.text_point(row,0))) - //for (var i = 0, len = tabs.length; i < len; i++) { - //left_edge = tabs[i]+1 - //right_edge = tabs[i+1] - //rightmost_selection = rightmost_selection_in_cell(selection_columns, right_edge) - //cell = line[left_edge:right_edge] - //widths[i] = max(len(cell.rstrip()), rightmost_selection - left_edge) - //} - //return widths; + this.$cellWidthsForRow = function(cursor, row) { + var selectionColumns = this.$selectionColumnsForRow(cursor, row); + // todo: support multicursor + + var tabs = [-1].concat(this.$tabsForRow(row)); + var widths = tabs.map(function (el) { return 0; } ).slice(1); + var line = this.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, tabString) { + this.$selectionColumnsForRow = function(cursor, row) { var selections = []; // todo: support multicursor - console.log("roar"); - console.log(this.getCursorPositionScreen()); - //for s in view.sel(): - //if (s.empty()) - //r, c =view.rowcol(s.a) - //if (r == row) - // selections.append(c) + selections.push(cursor.column + 1); return selections; }; - this.$tabsForRow = function(row, tabString) { - var rowTabs = []; + this.$setBlockCellWidthsToMax = function(cellWidths) { + var startingNewBlock = true, blockStartRow, blockEndRow, maxWidth; - var matches = session.getLine(row).match(new RegExp(tabString, "g")); + var columnInfo = this.$izip_longest(cellWidths); - if (matches !== null) { - for (var m = 0, matchLength = matches.length; m < matchLength; m++) { - rowTabs.push(matches[m].length); + for (var c = 0, l = columnInfo.length; c < l; c++) { + var column = columnInfo[c]; + // add an extra None to the end so that the end of the column automatically + // finishes a block + 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)) { + // block ended + 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.session.getLine(row), + re = /\t/g, match; + + while ((match = re.exec(line)) != null) { + rowTabs.push(match.index); + } + return rowTabs; }; + // the is a (naive) Python port--but works for these purposes + this.$izip_longest = function(iterables) { + 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.onTextInput = function(text) { this.keyBinding.onTextInput(text); }; From b63b84292a46792b6fc37e906c1136b5ba545b34 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Wed, 12 Dec 2012 15:55:29 -0800 Subject: [PATCH 05/16] Save state --- lib/ace/document.js | 2 +- lib/ace/editor.js | 95 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/lib/ace/document.js b/lib/ace/document.js index b0d3811c..710bd712 100644 --- a/lib/ace/document.js +++ b/lib/ace/document.js @@ -227,7 +227,7 @@ var Document = function(text) { * [Given a range within the document, this function returns all the text within that range as a single string.]{: #Document.getTextRange.desc} * @param {Range} range The range to work with * - * + * @returns {String} **/ this.getTextRange = function(range) { if (range.start.row == range.end.row) { diff --git a/lib/ace/editor.js b/lib/ace/editor.js index b3c0030a..201eeed8 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -780,15 +780,27 @@ var Editor = function(renderer, session) { this.$processRow = function(cursor, rows) { + var edit = false; + var checkedRows = []; + for (var r = 0, rowCount = rows.length; r < rowCount; r++) { - var cellWidthObj = this.$findCellWidthsForBlock(cursor, rows[r]); - cellWidthObj.cellWidths = this.$setBlockCellWidthsToMax(cellWidthObj.cellWidths); - console.log(cellWidthObj.cellWidths) - //for widths in cell_widths_by_row: - // checked_rows.add(row_index) - // edit = adjust_row(view, edit, row_index, widths) - // row_index += 1 + var row = rows[r]; + + if (checkedRows.indexOf(row) > -1) + continue; + var cellWidthObj = this.$findCellWidthsForBlock(cursor, 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); + edit = this.$adjustRow(edit, rowIndex, widths); + rowIndex++; + } } + + return edit; }; this.$findCellWidthsForBlock = function(cursor, row) { @@ -913,6 +925,62 @@ var Editor = function(renderer, session) { return rowTabs; }; + this.$adjustRow = function(edit, row, widths) { + var rowTabs = this.$tabsForRow(row); + + if (rowTabs.length == 0) + return edit; + + var bias = 0, location = -1; + + // this always only contains two elements, so we're safe in the loop below + 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 endTabPoint = this.textPoint(row, it); + //console.log("wot " + row + " " + " " + it + " " + endTabPoint); + var partialLine = this.session.getLine(endTabPoint) + } + + /* + end_tab_point = view.text_point(row, it) + partial_line = view.substr(view.line(end_tab_point))[0:it] + stripped_partial_line = partial_line.rstrip() + ispaces = len(partial_line) - len(stripped_partial_line) + if difference > 0: + if not edit: + edit = view.begin_edit() + #put the spaces after the tab and then delete the tab, so any insertion + #points behave as expected + view.insert(edit, end_tab_point+1, (' ' * difference) + "\t") + view.erase(edit, sublime.Region(end_tab_point, end_tab_point + 1)) + bias += difference + if difference < 0 and ispaces >= -difference: + if not edit: + edit = view.begin_edit() + view.erase(edit, sublime.Region(end_tab_point, end_tab_point + difference)) + bias += difference + */ + + return edit; + }; + + this.textPoint = function(row, col) { + var selRange = new Range(0, 0, row, col); + + var text = this.session.getTextRange(selRange); + + return text.length; + }; + // the is a (naive) Python port--but works for these purposes this.$izip_longest = function(iterables) { var longest = iterables[0].length; @@ -942,6 +1010,19 @@ var Editor = function(renderer, session) { return expandedSet; }; + // an even more (naive) Python port + this.$izip = function(widths, tabs) { + // grab the shorter size + 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; + }; + this.onTextInput = function(text) { this.keyBinding.onTextInput(text); }; From 148f7cc0abbddf1f22eaf7f6697d5c922c2020d3 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Wed, 12 Dec 2012 16:34:48 -0800 Subject: [PATCH 06/16] Working implementation --- lib/ace/document.js | 6 ++-- lib/ace/edit_session.js | 16 +++++----- lib/ace/editor.js | 70 +++++++++++++++++++++++------------------ 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/lib/ace/document.js b/lib/ace/document.js index 710bd712..fb3734a2 100644 --- a/lib/ace/document.js +++ b/lib/ace/document.js @@ -252,8 +252,8 @@ var Document = function(text) { }; /** - * Inserts a block of `text` and the indicated `position`. - * @param {Object} position The position to start inserting at + * Inserts a block of `text` at the indicated `position`. + * @param {Object} position The position to start inserting at; it's an object that looks like `{ row: row, column: column}` * @param {String} text A chunk of text to insert * @returns {Object} The position ({row, column}) of the last line of `text`. If the length of `text` is 0, this function simply returns `position`. * @@ -379,7 +379,7 @@ var Document = function(text) { /** * Inserts `text` into the `position` at the current row. This method also triggers the `'change'` event. - * @param {Object} position The position to insert at + * @param {Object} position The position to insert at; it's an object that looks like `{ row: row, column: column}` * @param {String} text A chunk of text * @returns {Object} Returns an object containing the final row and column, like this: * ``` diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 72a70034..2258d0fc 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1119,25 +1119,25 @@ var EditSession = function(text, mode) { }; /** - * Inserts a block of `text` and the indicated `position`. + * Inserts a block of `text` and the indicated `position`. * @param {Object} position The position {row, column} to start inserting at * @param {String} text A chunk of text to insert * @returns {Object} The position of the last line of `text`. If the length of `text` is 0, this function simply returns `position`. - * - * - **/ + * + * + **/ this.insert = function(position, text) { return this.doc.insert(position, text); }; /** - * Removes the `range` from the document. + * Removes the `range` from the document. * @param {Range} range A specified Range to remove * @returns {Object} The new `start` property of the range, which contains `startRow` and `startColumn`. If `range` is empty, this function returns the unmodified value of `range.start`. - * + * * @related Document.remove - * - **/ + * + **/ this.remove = function(range) { return this.doc.remove(range); }; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 201eeed8..6bd6c225 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -780,7 +780,6 @@ var Editor = function(renderer, session) { this.$processRow = function(cursor, rows) { - var edit = false; var checkedRows = []; for (var r = 0, rowCount = rows.length; r < rowCount; r++) { @@ -795,12 +794,10 @@ var Editor = function(renderer, session) { for (var w = 0, l = cellWidths.length; w < l; w++) { var widths = cellWidths[w]; checkedRows.push(rowIndex); - edit = this.$adjustRow(edit, rowIndex, widths); + this.$adjustRow(rowIndex, widths); rowIndex++; } } - - return edit; }; this.$findCellWidthsForBlock = function(cursor, row) { @@ -925,11 +922,11 @@ var Editor = function(renderer, session) { return rowTabs; }; - this.$adjustRow = function(edit, row, widths) { + this.$adjustRow = function(row, widths) { var rowTabs = this.$tabsForRow(row); if (rowTabs.length == 0) - return edit; + return; var bias = 0, location = -1; @@ -945,34 +942,36 @@ var Editor = function(renderer, session) { if (difference == 0) continue; - var endTabPoint = this.textPoint(row, it); - //console.log("wot " + row + " " + " " + it + " " + endTabPoint); - var partialLine = this.session.getLine(endTabPoint) + var partialLine = this.session.getLine(row).substr(0, it); + var strippedPartialLine = partialLine.replace(/\s*$/g, ""); + var ispaces = partialLine.length - strippedPartialLine.length; + + if (difference > 0) { + // some Kris Kowal code to multiply strings + var acc = [], str = " "; + for (var i = 0; (1 << i) <= difference; i++) { + if ((1 << i) & difference) + acc.push(str); + str += str; + } + acc = acc.join(""); + + // put the spaces after the tab and then delete the tab, so any insertion + // points behave as expected + this.session.getDocument().insertInLine({row: row, column: it + 1}, acc + "\t"); + this.session.getDocument().removeInLine(row, it, it + 1); + + bias += difference; + } + + if (difference < 0 && ispaces >= -difference) { + this.session.getDocument().removeInLine(row, it, it + difference); + bias += difference; + } } - - /* - end_tab_point = view.text_point(row, it) - partial_line = view.substr(view.line(end_tab_point))[0:it] - stripped_partial_line = partial_line.rstrip() - ispaces = len(partial_line) - len(stripped_partial_line) - if difference > 0: - if not edit: - edit = view.begin_edit() - #put the spaces after the tab and then delete the tab, so any insertion - #points behave as expected - view.insert(edit, end_tab_point+1, (' ' * difference) + "\t") - view.erase(edit, sublime.Region(end_tab_point, end_tab_point + 1)) - bias += difference - if difference < 0 and ispaces >= -difference: - if not edit: - edit = view.begin_edit() - view.erase(edit, sublime.Region(end_tab_point, end_tab_point + difference)) - bias += difference - */ - - return edit; }; + // gets the text point of the character from the start of the document this.textPoint = function(row, col) { var selRange = new Range(0, 0, row, col); @@ -981,6 +980,15 @@ var Editor = function(renderer, session) { return text.length; }; + // gets the range of a character, from the start of its row to the given column + this.lineRange = function(col) { + var selRange = new Range(0, 0, row, 0); + + var text = this.session.getTextRange(selRange); + + return { start: text.length, end: text.length + col }; + }; + // the is a (naive) Python port--but works for these purposes this.$izip_longest = function(iterables) { var longest = iterables[0].length; From 4d94636247ca341dcb5b7c9ee85b604592260cde Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Wed, 12 Dec 2012 17:01:07 -0800 Subject: [PATCH 07/16] Make a better multipler --- lib/ace/editor.js | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 6bd6c225..47749f5d 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -947,18 +947,9 @@ var Editor = function(renderer, session) { var ispaces = partialLine.length - strippedPartialLine.length; if (difference > 0) { - // some Kris Kowal code to multiply strings - var acc = [], str = " "; - for (var i = 0; (1 << i) <= difference; i++) { - if ((1 << i) & difference) - acc.push(str); - str += str; - } - acc = acc.join(""); - // put the spaces after the tab and then delete the tab, so any insertion // points behave as expected - this.session.getDocument().insertInLine({row: row, column: it + 1}, acc + "\t"); + this.session.getDocument().insertInLine({row: row, column: it + 1}, Array(difference).join(" ") + "\t"); this.session.getDocument().removeInLine(row, it, it + 1); bias += difference; @@ -971,24 +962,6 @@ var Editor = function(renderer, session) { } }; - // gets the text point of the character from the start of the document - this.textPoint = function(row, col) { - var selRange = new Range(0, 0, row, col); - - var text = this.session.getTextRange(selRange); - - return text.length; - }; - - // gets the range of a character, from the start of its row to the given column - this.lineRange = function(col) { - var selRange = new Range(0, 0, row, 0); - - var text = this.session.getTextRange(selRange); - - return { start: text.length, end: text.length + col }; - }; - // the is a (naive) Python port--but works for these purposes this.$izip_longest = function(iterables) { var longest = iterables[0].length; From d54921fcc97208e42a1e183fef36fbe55f7547f7 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Thu, 13 Dec 2012 00:03:56 -0800 Subject: [PATCH 08/16] Remove cursor propogation --- lib/ace/editor.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 47749f5d..f2945c83 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -774,12 +774,12 @@ var Editor = function(renderer, session) { // todo: support multicursor var row = cursor.row; - this.$processRow(cursor, [row]); + this.$processRow([row]); } }; - this.$processRow = function(cursor, rows) { + this.$processRow = function(rows) { var checkedRows = []; for (var r = 0, rowCount = rows.length; r < rowCount; r++) { @@ -787,7 +787,7 @@ var Editor = function(renderer, session) { if (checkedRows.indexOf(row) > -1) continue; - var cellWidthObj = this.$findCellWidthsForBlock(cursor, row); + var cellWidthObj = this.$findCellWidthsForBlock(row); var cellWidths = this.$setBlockCellWidthsToMax(cellWidthObj.cellWidths); var rowIndex = cellWidthObj.firstRow; @@ -800,13 +800,13 @@ var Editor = function(renderer, session) { } }; - this.$findCellWidthsForBlock = function(cursor, row) { + this.$findCellWidthsForBlock = function(row) { var cellWidths = [], widths; // starting row and backward var rowIter = row; while (rowIter >= 0) { - widths = this.$cellWidthsForRow(cursor, rowIter); + widths = this.$cellWidthsForRow(rowIter); if (widths.length == 0) break; @@ -822,7 +822,7 @@ var Editor = function(renderer, session) { while (rowIter < numRows - 1) { rowIter++; - widths = this.$cellWidthsForRow(cursor, rowIter); + widths = this.$cellWidthsForRow(rowIter); if (widths.length == 0) break; @@ -832,8 +832,8 @@ var Editor = function(renderer, session) { return { cellWidths: cellWidths, firstRow: firstRow }; }; - this.$cellWidthsForRow = function(cursor, row) { - var selectionColumns = this.$selectionColumnsForRow(cursor, row); + this.$cellWidthsForRow = function(row) { + var selectionColumns = this.$selectionColumnsForRow(row); // todo: support multicursor var tabs = [-1].concat(this.$tabsForRow(row)); @@ -852,10 +852,10 @@ var Editor = function(renderer, session) { return widths; }; - this.$selectionColumnsForRow = function(cursor, row) { + this.$selectionColumnsForRow = function(row) { var selections = []; // todo: support multicursor - selections.push(cursor.column + 1); + selections.push(this.getCursorPosition().column + 1); return selections; }; From 6e1398273c4eb5a2df3f0dcaee0e621a74fb3480 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Thu, 13 Dec 2012 13:52:34 -0800 Subject: [PATCH 09/16] Turn into separate plugin --- demo/kitchen-sink/demo.js | 8 +- kitchen-sink.html | 8 ++ lib/ace/editor.js | 271 +++++------------------------------- lib/ace/elastic_tabstops.js | 271 ++++++++++++++++++++++++++++++++++++ 4 files changed, 324 insertions(+), 234 deletions(-) create mode 100644 lib/ace/elastic_tabstops.js diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 08c5098c..f7d3a147 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -362,7 +362,7 @@ bindCheckbox("read_only", function(checked) { bindDropdown("split", function(value) { var sp = env.split; - if (value == "none") { + if (value == "none") { sp.setSplits(1); } else { var newEditor = (sp.getSplits() == 1); @@ -377,6 +377,12 @@ bindDropdown("split", function(value) { } }); + +bindCheckbox("elastic_tabstops", function(checked) { + env.editor.setUseElasticTabstops(checked); +}); + + function synchroniseScrolling() { var s1 = env.split.$editors[0].session; var s2 = env.split.$editors[1].session; diff --git a/kitchen-sink.html b/kitchen-sink.html index c846e2aa..33bc19a7 100644 --- a/kitchen-sink.html +++ b/kitchen-sink.html @@ -241,6 +241,14 @@ + + + + + + + + diff --git a/lib/ace/editor.js b/lib/ace/editor.js index f2945c83..3a5d7f05 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -462,6 +462,21 @@ var Editor = function(renderer, session) { // update cursor because tab characters can influence the cursor position this.$cursorChange(); + + if (this.$useElasticTabstops) { + if (this.ElasticTabstops === undefined) { + var ElasticTabstops = require("./elastic_tabstops").ElasticTabstops; + this.ElasticTabstops = new ElasticTabstops(this, this.session); + } + + if (!this.ElasticTabstops.$inChange) { + // todo: support multicursor + var row = this.getCursorPosition().row; + + // block event calling, because this method makes changes + this.ElasticTabstops.processRow([row]); + } + } }; this.onTokenizerUpdate = function(e) { @@ -769,239 +784,6 @@ var Editor = function(renderer, session) { } if (shouldOutdent) mode.autoOutdent(lineState, session, cursor.row); - - if (true || this.session.getElasticTabstops()) { - // todo: support multicursor - var row = cursor.row; - - this.$processRow([row]); - } - }; - - - this.$processRow = function(rows) { - 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.$findCellWidthsForBlock = function(row) { - var cellWidths = [], widths; - - // starting row and backward - var rowIter = row; - while (rowIter >= 0) { - widths = this.$cellWidthsForRow(rowIter); - if (widths.length == 0) - break; - - cellWidths.unshift(widths); - rowIter--; - } - var firstRow = rowIter + 1; - - // forward (not including starting row) - rowIter = row; - var numRows = this.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); - // todo: support multicursor - - var tabs = [-1].concat(this.$tabsForRow(row)); - var widths = tabs.map(function (el) { return 0; } ).slice(1); - var line = this.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 = []; - // todo: support multicursor - selections.push(this.getCursorPosition().column + 1); - 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]; - // add an extra None to the end so that the end of the column automatically - // finishes a block - 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)) { - // block ended - 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.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; - - // this always only contains two elements, so we're safe in the loop below - 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.session.getLine(row).substr(0, it); - var strippedPartialLine = partialLine.replace(/\s*$/g, ""); - var ispaces = partialLine.length - strippedPartialLine.length; - - if (difference > 0) { - // put the spaces after the tab and then delete the tab, so any insertion - // points behave as expected - this.session.getDocument().insertInLine({row: row, column: it + 1}, Array(difference).join(" ") + "\t"); - this.session.getDocument().removeInLine(row, it, it + 1); - - bias += difference; - } - - if (difference < 0 && ispaces >= -difference) { - this.session.getDocument().removeInLine(row, it, it + difference); - bias += difference; - } - } - }; - - // the is a (naive) Python port--but works for these purposes - this.$izip_longest = function(iterables) { - 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; - }; - - // an even more (naive) Python port - this.$izip = function(widths, tabs) { - // grab the shorter size - 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; }; this.onTextInput = function(text) { @@ -1275,6 +1057,29 @@ var Editor = function(renderer, session) { return this.getOption("fadeFoldWidgets"); }; + this.$useElasticTabstops = false; + + /** + * Determines whether or not elastic tabstops should be used. + * @param {Boolean} useElasticTabstops Set to `true` to enable elastic tabstops + * + **/ + this.setUseElasticTabstops = function(useElasticTabstops) { + if (this.$useElasticTabstops == useElasticTabstops) + return; + + this.$useElasticTabstops = useElasticTabstops; + }; + + /** + * Returns whether or not elastic tabstops are set. + * @returns {Boolean} The current elastic tabstops setting + * + **/ + this.getUseElasticTabstops = function() { + return this.$useElasticTabstops; + }; + /** * Removes words of text from the editor. A "word" is defined as a string of characters bookended by whitespace. * @param {String} dir The direction of the deletion to occur, either "left" or "right" diff --git a/lib/ace/elastic_tabstops.js b/lib/ace/elastic_tabstops.js new file mode 100644 index 00000000..fee4ff33 --- /dev/null +++ b/lib/ace/elastic_tabstops.js @@ -0,0 +1,271 @@ +/* ***** 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(function(require, exports, module) { +"use strict"; + +var ElasticTabstops = function(editor, session) { + this.$editor = editor; + this.$session = session; +}; + +(function() { + this.processRow = 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; + + // starting row and backward + var rowIter = row; + while (rowIter >= 0) { + widths = this.$cellWidthsForRow(rowIter); + if (widths.length == 0) + break; + + cellWidths.unshift(widths); + rowIter--; + } + var firstRow = rowIter + 1; + + // forward (not including starting row) + rowIter = row; + var numRows = this.$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); + // todo: support multicursor + + var tabs = [-1].concat(this.$tabsForRow(row)); + var widths = tabs.map(function (el) { return 0; } ).slice(1); + var line = this.$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 = []; + // todo: support multicursor + selections.push(this.$editor.getCursorPosition().column + 1); + 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]; + // add an extra None to the end so that the end of the column automatically + // finishes a block + 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)) { + // block ended + 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.$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; + + // this always only contains two elements, so we're safe in the loop below + 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.$session.getLine(row).substr(0, it); + var strippedPartialLine = partialLine.replace(/\s*$/g, ""); + var ispaces = partialLine.length - strippedPartialLine.length; + + if (difference > 0) { + // put the spaces after the tab and then delete the tab, so any insertion + // points behave as expected + this.$session.getDocument().insertInLine({row: row, column: it + 1}, Array(difference).join(" ") + "\t"); + this.$session.getDocument().removeInLine(row, it, it + 1); + + bias += difference; + } + + if (difference < 0 && ispaces >= -difference) { + this.$session.getDocument().removeInLine(row, it, it + difference); + bias += difference; + } + } + }; + + // the is a (naive) Python port--but works for these purposes + this.$izip_longest = function(iterables) { + 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; + }; + + // an even more (naive) Python port + this.$izip = function(widths, tabs) { + // grab the shorter size + 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(ElasticTabstops.prototype); + +exports.ElasticTabstops = ElasticTabstops; + +}); \ No newline at end of file From face7728658a8d2c81c3a6bf1695cd6f5441bd14 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sat, 15 Dec 2012 12:16:22 -0800 Subject: [PATCH 10/16] Yesss fix positional bug --- lib/ace/elastic_tabstops.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/ace/elastic_tabstops.js b/lib/ace/elastic_tabstops.js index fee4ff33..85162cc5 100644 --- a/lib/ace/elastic_tabstops.js +++ b/lib/ace/elastic_tabstops.js @@ -46,6 +46,7 @@ var ElasticTabstops = function(editor, session) { if (checkedRows.indexOf(row) > -1) continue; + var cellWidthObj = this.$findCellWidthsForBlock(row); var cellWidths = this.$setBlockCellWidthsToMax(cellWidthObj.cellWidths); var rowIndex = cellWidthObj.firstRow; @@ -113,9 +114,13 @@ var ElasticTabstops = function(editor, session) { }; this.$selectionColumnsForRow = function(row) { - var selections = []; - // todo: support multicursor - selections.push(this.$editor.getCursorPosition().column + 1); + var selections = [], cursor = this.$editor.getCursorPosition(); + if (this.$session.getSelection().isEmpty()) { + // todo: support multicursor + if (row == cursor.row) + selections.push(cursor.column); + } + return selections; }; @@ -192,7 +197,7 @@ var ElasticTabstops = function(editor, session) { // this always only contains two elements, so we're safe in the loop below 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; @@ -202,7 +207,7 @@ var ElasticTabstops = function(editor, session) { if (difference == 0) continue; - var partialLine = this.$session.getLine(row).substr(0, it); + var partialLine = this.$session.getLine(row).substr(0, it ); var strippedPartialLine = partialLine.replace(/\s*$/g, ""); var ispaces = partialLine.length - strippedPartialLine.length; @@ -216,7 +221,7 @@ var ElasticTabstops = function(editor, session) { } if (difference < 0 && ispaces >= -difference) { - this.$session.getDocument().removeInLine(row, it, it + difference); + this.$session.getDocument().removeInLine(row, it + difference, it); bias += difference; } } From 9efccbb77692921986768f2d7fc7ce3db16e2cfe Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sat, 15 Dec 2012 12:17:32 -0800 Subject: [PATCH 11/16] Uhh actually commit fix --- lib/ace/elastic_tabstops.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ace/elastic_tabstops.js b/lib/ace/elastic_tabstops.js index 85162cc5..c3fa0411 100644 --- a/lib/ace/elastic_tabstops.js +++ b/lib/ace/elastic_tabstops.js @@ -214,7 +214,7 @@ var ElasticTabstops = function(editor, session) { if (difference > 0) { // put the spaces after the tab and then delete the tab, so any insertion // points behave as expected - this.$session.getDocument().insertInLine({row: row, column: it + 1}, Array(difference).join(" ") + "\t"); + this.$session.getDocument().insertInLine({row: row, column: it + 1}, Array(difference + 1).join(" ") + "\t"); this.$session.getDocument().removeInLine(row, it, it + 1); bias += difference; From 3d0f30c36dca38f78d0805876c2f76d8b7349da6 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Wed, 26 Dec 2012 01:33:20 -0800 Subject: [PATCH 12/16] Start addressing comments --- demo/kitchen-sink/demo.js | 5 +++- lib/ace/editor.js | 29 ++----------------- ...c_tabstops.js => elastic_tabstops_lite.js} | 23 +++++++-------- 3 files changed, 18 insertions(+), 39 deletions(-) rename lib/ace/{elastic_tabstops.js => elastic_tabstops_lite.js} (91%) diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index f7d3a147..80b25b06 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -62,6 +62,8 @@ var fillDropdown = util.fillDropdown; var bindCheckbox = util.bindCheckbox; var bindDropdown = util.bindDropdown; +var ElasticTabstopsLite = require("ace/elastic_tabstops_lite").ElasticTabstopsLite; + /*********** create editor ***************************/ var container = document.getElementById("editor-container"); @@ -379,7 +381,8 @@ bindDropdown("split", function(value) { bindCheckbox("elastic_tabstops", function(checked) { - env.editor.setUseElasticTabstops(checked); + if (checked === true) + new ElasticTabstopsLite(env.editor); }); diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 3a5d7f05..8e39d25c 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -462,8 +462,8 @@ var Editor = function(renderer, session) { // update cursor because tab characters can influence the cursor position this.$cursorChange(); - - if (this.$useElasticTabstops) { + console.log(e); + /*if (this.$useElasticTabstops) { if (this.ElasticTabstops === undefined) { var ElasticTabstops = require("./elastic_tabstops").ElasticTabstops; this.ElasticTabstops = new ElasticTabstops(this, this.session); @@ -476,7 +476,7 @@ var Editor = function(renderer, session) { // block event calling, because this method makes changes this.ElasticTabstops.processRow([row]); } - } + }*/ }; this.onTokenizerUpdate = function(e) { @@ -1057,29 +1057,6 @@ var Editor = function(renderer, session) { return this.getOption("fadeFoldWidgets"); }; - this.$useElasticTabstops = false; - - /** - * Determines whether or not elastic tabstops should be used. - * @param {Boolean} useElasticTabstops Set to `true` to enable elastic tabstops - * - **/ - this.setUseElasticTabstops = function(useElasticTabstops) { - if (this.$useElasticTabstops == useElasticTabstops) - return; - - this.$useElasticTabstops = useElasticTabstops; - }; - - /** - * Returns whether or not elastic tabstops are set. - * @returns {Boolean} The current elastic tabstops setting - * - **/ - this.getUseElasticTabstops = function() { - return this.$useElasticTabstops; - }; - /** * Removes words of text from the editor. A "word" is defined as a string of characters bookended by whitespace. * @param {String} dir The direction of the deletion to occur, either "left" or "right" diff --git a/lib/ace/elastic_tabstops.js b/lib/ace/elastic_tabstops_lite.js similarity index 91% rename from lib/ace/elastic_tabstops.js rename to lib/ace/elastic_tabstops_lite.js index c3fa0411..5eb9dcf6 100644 --- a/lib/ace/elastic_tabstops.js +++ b/lib/ace/elastic_tabstops_lite.js @@ -31,9 +31,8 @@ define(function(require, exports, module) { "use strict"; -var ElasticTabstops = function(editor, session) { +var ElasticTabstopsLite = function(editor) { this.$editor = editor; - this.$session = session; }; (function() { @@ -78,7 +77,7 @@ var ElasticTabstops = function(editor, session) { // forward (not including starting row) rowIter = row; - var numRows = this.$session.getLength(); + var numRows = this.$editor.session.getLength(); while (rowIter < numRows - 1) { rowIter++; @@ -99,7 +98,7 @@ var ElasticTabstops = function(editor, session) { var tabs = [-1].concat(this.$tabsForRow(row)); var widths = tabs.map(function (el) { return 0; } ).slice(1); - var line = this.$session.getLine(row); + var line = this.$editor.session.getLine(row); for (var i = 0, len = tabs.length - 1; i < len; i++) { var leftEdge = tabs[i]+1; @@ -115,7 +114,7 @@ var ElasticTabstops = function(editor, session) { this.$selectionColumnsForRow = function(row) { var selections = [], cursor = this.$editor.getCursorPosition(); - if (this.$session.getSelection().isEmpty()) { + if (this.$editor.session.getSelection().isEmpty()) { // todo: support multicursor if (row == cursor.row) selections.push(cursor.column); @@ -177,7 +176,7 @@ var ElasticTabstops = function(editor, session) { }; this.$tabsForRow = function(row) { - var rowTabs = [], line = this.$session.getLine(row), + var rowTabs = [], line = this.$editor.session.getLine(row), re = /\t/g, match; while ((match = re.exec(line)) != null) { @@ -207,21 +206,21 @@ var ElasticTabstops = function(editor, session) { if (difference == 0) continue; - var partialLine = this.$session.getLine(row).substr(0, it ); + 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) { // put the spaces after the tab and then delete the tab, so any insertion // points behave as expected - this.$session.getDocument().insertInLine({row: row, column: it + 1}, Array(difference + 1).join(" ") + "\t"); - this.$session.getDocument().removeInLine(row, it, it + 1); + 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.$session.getDocument().removeInLine(row, it + difference, it); + this.$editor.session.getDocument().removeInLine(row, it + difference, it); bias += difference; } } @@ -269,8 +268,8 @@ var ElasticTabstops = function(editor, session) { return expandedSet; }; -}).call(ElasticTabstops.prototype); +}).call(ElasticTabstopsLite.prototype); -exports.ElasticTabstops = ElasticTabstops; +exports.ElasticTabstopsLite = ElasticTabstopsLite; }); \ No newline at end of file From 1228b2d30dfb96b08e4b2b996cd7078c980608c5 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 21 Feb 2013 00:19:38 +0400 Subject: [PATCH 13/16] remove traces of elastic tabstops from editor.js --- lib/ace/editor.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 8e39d25c..4039e0b7 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -462,21 +462,6 @@ var Editor = function(renderer, session) { // update cursor because tab characters can influence the cursor position this.$cursorChange(); - console.log(e); - /*if (this.$useElasticTabstops) { - if (this.ElasticTabstops === undefined) { - var ElasticTabstops = require("./elastic_tabstops").ElasticTabstops; - this.ElasticTabstops = new ElasticTabstops(this, this.session); - } - - if (!this.ElasticTabstops.$inChange) { - // todo: support multicursor - var row = this.getCursorPosition().row; - - // block event calling, because this method makes changes - this.ElasticTabstops.processRow([row]); - } - }*/ }; this.onTokenizerUpdate = function(e) { @@ -1227,7 +1212,6 @@ var Editor = function(renderer, session) { indentString = lang.stringRepeat(" ", count); } else indentString = "\t"; - return this.insert(indentString); } }; From 04f06ebefefd0137d41eb5eaa249be3bc701088e Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 21 Feb 2013 10:23:57 +0400 Subject: [PATCH 14/16] use config defineOptions --- demo/kitchen-sink/demo.js | 3 +-- lib/ace/elastic_tabstops_lite.js | 41 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 80b25b06..4836a78e 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -381,8 +381,7 @@ bindDropdown("split", function(value) { bindCheckbox("elastic_tabstops", function(checked) { - if (checked === true) - new ElasticTabstopsLite(env.editor); + env.editor.setOption("useElasticTabstops", checked); }); diff --git a/lib/ace/elastic_tabstops_lite.js b/lib/ace/elastic_tabstops_lite.js index 5eb9dcf6..efbe7882 100644 --- a/lib/ace/elastic_tabstops_lite.js +++ b/lib/ace/elastic_tabstops_lite.js @@ -33,10 +33,30 @@ define(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.processRow = function(rows) { + this.processRows = function(rows) { this.$inChange = true; var checkedRows = []; @@ -272,4 +292,23 @@ var ElasticTabstopsLite = function(editor) { 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); + } + } + } +}); + }); \ No newline at end of file From b29dc7c49ed7f532d09f1186e93c7d8a7a5efd9a Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 21 Feb 2013 10:51:54 +0400 Subject: [PATCH 15/16] fix undefined errors when there is no tabstop cell --- lib/ace/elastic_tabstops_lite.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ace/elastic_tabstops_lite.js b/lib/ace/elastic_tabstops_lite.js index efbe7882..c2ed9285 100644 --- a/lib/ace/elastic_tabstops_lite.js +++ b/lib/ace/elastic_tabstops_lite.js @@ -150,6 +150,10 @@ var ElasticTabstopsLite = function(editor) { for (var c = 0, l = columnInfo.length; c < l; c++) { var column = columnInfo[c]; + if (!column.push) { + console.error(column) + continue + } // add an extra None to the end so that the end of the column automatically // finishes a block column.push(NaN); @@ -248,6 +252,8 @@ var ElasticTabstopsLite = function(editor) { // the is a (naive) Python port--but works for these purposes this.$izip_longest = function(iterables) { + if (!iterables[0]) + return console.error(iterables); var longest = iterables[0].length; var iterablesLength = iterables.length; From e6fd9dc9771ef8905ebb283a8aef83105ca8686d Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 22 Feb 2013 16:39:45 +0400 Subject: [PATCH 16/16] move elastic_tabstops into ext to automatically include in build --- demo/kitchen-sink/demo.js | 2 +- lib/ace/{ => ext}/elastic_tabstops_lite.js | 11 +++++------ lib/ace/ext/searchbox.js | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) rename lib/ace/{ => ext}/elastic_tabstops_lite.js (98%) diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 4836a78e..b12eb95b 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -62,7 +62,7 @@ var fillDropdown = util.fillDropdown; var bindCheckbox = util.bindCheckbox; var bindDropdown = util.bindDropdown; -var ElasticTabstopsLite = require("ace/elastic_tabstops_lite").ElasticTabstopsLite; +var ElasticTabstopsLite = require("ace/ext/elastic_tabstops_lite").ElasticTabstopsLite; /*********** create editor ***************************/ var container = document.getElementById("editor-container"); diff --git a/lib/ace/elastic_tabstops_lite.js b/lib/ace/ext/elastic_tabstops_lite.js similarity index 98% rename from lib/ace/elastic_tabstops_lite.js rename to lib/ace/ext/elastic_tabstops_lite.js index c2ed9285..b9e9891f 100644 --- a/lib/ace/elastic_tabstops_lite.js +++ b/lib/ace/ext/elastic_tabstops_lite.js @@ -145,14 +145,13 @@ var ElasticTabstopsLite = function(editor) { 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 + console.error(column); + continue; } // add an extra None to the end so that the end of the column automatically // finishes a block @@ -253,7 +252,7 @@ var ElasticTabstopsLite = function(editor) { // the is a (naive) Python port--but works for these purposes this.$izip_longest = function(iterables) { if (!iterables[0]) - return console.error(iterables); + return []; var longest = iterables[0].length; var iterablesLength = iterables.length; @@ -298,8 +297,8 @@ var ElasticTabstopsLite = function(editor) { exports.ElasticTabstopsLite = ElasticTabstopsLite; -var Editor = require("./editor").Editor; -require("./config").defineOptions(Editor.prototype, "editor", { +var Editor = require("../editor").Editor; +require("../config").defineOptions(Editor.prototype, "editor", { useElasticTabstops: { set: function(val) { if (val) { diff --git a/lib/ace/ext/searchbox.js b/lib/ace/ext/searchbox.js index eecac88c..8efd08a7 100644 --- a/lib/ace/ext/searchbox.js +++ b/lib/ace/ext/searchbox.js @@ -43,12 +43,12 @@ dom.importCssString(searchboxCss, "ace_searchbox"); var html = '