You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2013/10/29 16:40:29 UTC
[44/51] [partial] working replacement
http://git-wip-us.apache.org/repos/asf/couchdb/blob/9abd128c/src/fauxton/assets/js/libs/ace/editor_highlight_selected_word_test.js
----------------------------------------------------------------------
diff --git a/src/fauxton/assets/js/libs/ace/editor_highlight_selected_word_test.js b/src/fauxton/assets/js/libs/ace/editor_highlight_selected_word_test.js
new file mode 100644
index 0000000..13e19c2
--- /dev/null
+++ b/src/fauxton/assets/js/libs/ace/editor_highlight_selected_word_test.js
@@ -0,0 +1,223 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Distributed under the BSD license:
+ *
+ * Copyright (c) 2010, 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 ***** */
+
+if (typeof process !== "undefined") {
+ require("amd-loader");
+ require("./test/mockdom");
+}
+
+define(function(require, exports, module) {
+"use strict";
+
+var EditSession = require("./edit_session").EditSession;
+var Editor = require("./editor").Editor;
+var MockRenderer = require("./test/mockrenderer").MockRenderer;
+var assert = require("./test/assertions");
+
+var lipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
+ "Mauris at arcu mi, eu lobortis mauris. Quisque ut libero eget " +
+ "diam congue vehicula. Quisque ut odio ut mi aliquam tincidunt. " +
+ "Duis lacinia aliquam lorem eget eleifend. Morbi eget felis mi. " +
+ "Duis quam ligula, consequat vitae convallis volutpat, blandit " +
+ "nec neque. Nulla facilisi. Etiam suscipit lorem ac justo " +
+ "sollicitudin tristique. Phasellus ut posuere nunc. Aliquam " +
+ "scelerisque mollis felis non gravida. Vestibulum lacus sem, " +
+ "posuere non bibendum id, luctus non dolor. Aenean id metus " +
+ "lorem, vel dapibus est. Donec gravida feugiat augue nec " +
+ "accumsan.Lorem ipsum dolor sit amet, consectetur adipiscing " +
+ "elit. Nulla vulputate, velit vitae tincidunt congue, nunc " +
+ "augue accumsan velit, eu consequat turpis lectus ac orci. " +
+ "Pellentesque ornare dolor feugiat dui auctor eu varius nulla " +
+ "fermentum. Sed aliquam odio at velit lacinia vel fermentum " +
+ "felis sodales. In dignissim magna eget nunc lobortis non " +
+ "fringilla nibh ullamcorper. Donec facilisis malesuada elit " +
+ "at egestas. Etiam bibendum, diam vitae tempor aliquet, dui " +
+ "libero vehicula odio, eget bibendum mauris velit eu lorem.\n" +
+ "consectetur";
+
+function callHighlighterUpdate(session, firstRow, lastRow) {
+ var rangeCount = 0;
+ var mockMarkerLayer = { drawSingleLineMarker: function() {rangeCount++;} }
+ session.$searchHighlight.update([], mockMarkerLayer, session, {
+ firstRow: firstRow,
+ lastRow: lastRow
+ });
+ return rangeCount;
+}
+
+module.exports = {
+ setUp: function(next) {
+ this.session = new EditSession(lipsum);
+ this.editor = new Editor(new MockRenderer(), this.session);
+ this.selection = this.session.getSelection();
+ this.search = this.editor.$search;
+ next();
+ },
+
+ "test: highlight selected words by default": function() {
+ assert.equal(this.editor.getHighlightSelectedWord(), true);
+ },
+
+ "test: highlight a word": function() {
+ this.editor.moveCursorTo(0, 9);
+ this.selection.selectWord();
+
+ var highlighter = this.editor.session.$searchHighlight;
+ assert.ok(highlighter != null);
+
+ var range = this.selection.getRange();
+ assert.equal(this.session.getTextRange(range), "ipsum");
+ assert.equal(highlighter.cache.length, 0);
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 2);
+ },
+
+ "test: highlight a word and clear highlight": function() {
+ this.editor.moveCursorTo(0, 8);
+ this.selection.selectWord();
+
+ var range = this.selection.getRange();
+ assert.equal(this.session.getTextRange(range), "ipsum");
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 2);
+
+ this.session.highlight("");
+ assert.equal(this.session.$searchHighlight.cache.length, 0);
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
+ },
+
+ "test: highlight another word": function() {
+ this.selection.moveCursorTo(0, 14);
+ this.selection.selectWord();
+
+ var range = this.selection.getRange();
+ assert.equal(this.session.getTextRange(range), "dolor");
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 4);
+ },
+
+ "test: no selection, no highlight": function() {
+ this.selection.clearSelection();
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
+ },
+
+ "test: select a word, no highlight": function() {
+ this.selection.moveCursorTo(0, 14);
+ this.selection.selectWord();
+
+ this.editor.setHighlightSelectedWord(false);
+
+ var range = this.selection.getRange();
+ assert.equal(this.session.getTextRange(range), "dolor");
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
+ },
+
+ "test: select a word with no matches": function() {
+ this.editor.setHighlightSelectedWord(true);
+
+ var currentOptions = this.search.getOptions();
+ var newOptions = {
+ wrap: true,
+ wholeWord: true,
+ caseSensitive: true,
+ needle: "Mauris"
+ };
+ this.search.set(newOptions);
+
+ var match = this.search.find(this.session);
+ assert.notEqual(match, null, "found a match for 'Mauris'");
+
+ this.search.set(currentOptions);
+
+ this.selection.setSelectionRange(match);
+
+ assert.equal(this.session.getTextRange(match), "Mauris");
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 1);
+ },
+
+ "test: partial word selection 1": function() {
+ this.selection.moveCursorTo(0, 14);
+ this.selection.selectWord();
+ this.selection.selectLeft();
+
+ var range = this.selection.getRange();
+ assert.equal(this.session.getTextRange(range), "dolo");
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
+ },
+
+ "test: partial word selection 2": function() {
+ this.selection.moveCursorTo(0, 13);
+ this.selection.selectWord();
+ this.selection.selectRight();
+
+ var range = this.selection.getRange();
+ assert.equal(this.session.getTextRange(range), "dolor ");
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
+ },
+
+ "test: partial word selection 3": function() {
+ this.selection.moveCursorTo(0, 14);
+ this.selection.selectWord();
+ this.selection.selectLeft();
+ this.selection.shiftSelection(1);
+
+ var range = this.selection.getRange();
+ assert.equal(this.session.getTextRange(range), "olor");
+ assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
+ },
+
+ "test: select last word": function() {
+ this.selection.moveCursorTo(0, 1);
+
+ var currentOptions = this.search.getOptions();
+ var newOptions = {
+ wrap: true,
+ wholeWord: true,
+ caseSensitive: true,
+ backwards: true,
+ needle: "consectetur"
+ };
+ this.search.set(newOptions);
+
+ var match = this.search.find(this.session);
+ assert.notEqual(match, null, "found a match for 'consectetur'");
+ assert.position(match.start, 1, 0);
+
+ this.search.set(currentOptions);
+
+ this.selection.setSelectionRange(match);
+
+ assert.equal(this.session.getTextRange(match), "consectetur");
+ assert.equal(callHighlighterUpdate(this.session, 0, 1), 3);
+ }
+};
+
+});
+
+if (typeof module !== "undefined" && module === require.main) {
+ require("asyncjs").test.testcase(module.exports).exec();
+}
http://git-wip-us.apache.org/repos/asf/couchdb/blob/9abd128c/src/fauxton/assets/js/libs/ace/editor_navigation_test.js
----------------------------------------------------------------------
diff --git a/src/fauxton/assets/js/libs/ace/editor_navigation_test.js b/src/fauxton/assets/js/libs/ace/editor_navigation_test.js
new file mode 100644
index 0000000..ab34824
--- /dev/null
+++ b/src/fauxton/assets/js/libs/ace/editor_navigation_test.js
@@ -0,0 +1,164 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Distributed under the BSD license:
+ *
+ * Copyright (c) 2010, 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 ***** */
+
+if (typeof process !== "undefined") {
+ require("amd-loader");
+ require("./test/mockdom");
+}
+
+define(function(require, exports, module) {
+"use strict";
+
+var EditSession = require("./edit_session").EditSession;
+var Editor = require("./editor").Editor;
+var MockRenderer = require("./test/mockrenderer").MockRenderer;
+var assert = require("./test/assertions");
+
+module.exports = {
+ createEditSession : function(rows, cols) {
+ var line = new Array(cols + 1).join("a");
+ var text = new Array(rows).join(line + "\n") + line;
+ return new EditSession(text);
+ },
+
+ "test: navigate to end of file should scroll the last line into view" : function() {
+ var doc = this.createEditSession(200, 10);
+ var editor = new Editor(new MockRenderer(), doc);
+
+ editor.navigateFileEnd();
+ var cursor = editor.getCursorPosition();
+
+ assert.ok(editor.getFirstVisibleRow() <= cursor.row);
+ assert.ok(editor.getLastVisibleRow() >= cursor.row);
+ },
+
+ "test: navigate to start of file should scroll the first row into view" : function() {
+ var doc = this.createEditSession(200, 10);
+ var editor = new Editor(new MockRenderer(), doc);
+
+ editor.moveCursorTo(editor.getLastVisibleRow() + 20);
+ editor.navigateFileStart();
+
+ assert.equal(editor.getFirstVisibleRow(), 0);
+ },
+
+ "test: goto hidden line should scroll the line into the middle of the viewport" : function() {
+ var editor = new Editor(new MockRenderer(), this.createEditSession(200, 5));
+
+ editor.navigateTo(0, 0);
+ editor.gotoLine(101);
+ assert.position(editor.getCursorPosition(), 100, 0);
+ assert.equal(editor.getFirstVisibleRow(), 89);
+
+ editor.navigateTo(100, 0);
+ editor.gotoLine(11);
+ assert.position(editor.getCursorPosition(), 10, 0);
+ assert.equal(editor.getFirstVisibleRow(), 0);
+
+ editor.navigateTo(100, 0);
+ editor.gotoLine(6);
+ assert.position(editor.getCursorPosition(), 5, 0);
+ assert.equal(0, editor.getFirstVisibleRow(), 0);
+
+ editor.navigateTo(100, 0);
+ editor.gotoLine(1);
+ assert.position(editor.getCursorPosition(), 0, 0);
+ assert.equal(editor.getFirstVisibleRow(), 0);
+
+ editor.navigateTo(0, 0);
+ editor.gotoLine(191);
+ assert.position(editor.getCursorPosition(), 190, 0);
+ assert.equal(editor.getFirstVisibleRow(), 179);
+
+ editor.navigateTo(0, 0);
+ editor.gotoLine(196);
+ assert.position(editor.getCursorPosition(), 195, 0);
+ assert.equal(editor.getFirstVisibleRow(), 180);
+ },
+
+ "test: goto visible line should only move the cursor and not scroll": function() {
+ var editor = new Editor(new MockRenderer(), this.createEditSession(200, 5));
+
+ editor.navigateTo(0, 0);
+ editor.gotoLine(12);
+ assert.position(editor.getCursorPosition(), 11, 0);
+ assert.equal(editor.getFirstVisibleRow(), 0);
+
+ editor.navigateTo(30, 0);
+ editor.gotoLine(33);
+ assert.position(editor.getCursorPosition(), 32, 0);
+ assert.equal(editor.getFirstVisibleRow(), 30);
+ },
+
+ "test: navigate from the end of a long line down to a short line and back should maintain the curser column": function() {
+ var editor = new Editor(new MockRenderer(), new EditSession(["123456", "1"]));
+
+ editor.navigateTo(0, 6);
+ assert.position(editor.getCursorPosition(), 0, 6);
+
+ editor.navigateDown();
+ assert.position(editor.getCursorPosition(), 1, 1);
+
+ editor.navigateUp();
+ assert.position(editor.getCursorPosition(), 0, 6);
+ },
+
+ "test: reset desired column on navigate left or right": function() {
+ var editor = new Editor(new MockRenderer(), new EditSession(["123456", "12"]));
+
+ editor.navigateTo(0, 6);
+ assert.position(editor.getCursorPosition(), 0, 6);
+
+ editor.navigateDown();
+ assert.position(editor.getCursorPosition(), 1, 2);
+
+ editor.navigateLeft();
+ assert.position(editor.getCursorPosition(), 1, 1);
+
+ editor.navigateUp();
+ assert.position(editor.getCursorPosition(), 0, 1);
+ },
+
+ "test: typing text should update the desired column": function() {
+ var editor = new Editor(new MockRenderer(), new EditSession(["1234", "1234567890"]));
+
+ editor.navigateTo(0, 3);
+ editor.insert("juhu");
+
+ editor.navigateDown();
+ assert.position(editor.getCursorPosition(), 1, 7);
+ }
+};
+
+});
+
+if (typeof module !== "undefined" && module === require.main) {
+ require("asyncjs").test.testcase(module.exports).exec()
+}
http://git-wip-us.apache.org/repos/asf/couchdb/blob/9abd128c/src/fauxton/assets/js/libs/ace/editor_text_edit_test.js
----------------------------------------------------------------------
diff --git a/src/fauxton/assets/js/libs/ace/editor_text_edit_test.js b/src/fauxton/assets/js/libs/ace/editor_text_edit_test.js
new file mode 100644
index 0000000..77ec34e
--- /dev/null
+++ b/src/fauxton/assets/js/libs/ace/editor_text_edit_test.js
@@ -0,0 +1,557 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Distributed under the BSD license:
+ *
+ * Copyright (c) 2010, 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 ***** */
+
+if (typeof process !== "undefined") {
+ require("amd-loader");
+ require("./test/mockdom");
+}
+
+define(function(require, exports, module) {
+"use strict";
+
+var EditSession = require("./edit_session").EditSession;
+var Editor = require("./editor").Editor;
+var JavaScriptMode = require("./mode/javascript").Mode;
+var UndoManager = require("./undomanager").UndoManager;
+var MockRenderer = require("./test/mockrenderer").MockRenderer;
+var assert = require("./test/assertions");
+var whitespace = require("./ext/whitespace");
+
+module.exports = {
+ "test: delete line from the middle" : function() {
+ var session = new EditSession(["a", "b", "c", "d"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(1, 1);
+ editor.removeLines();
+
+ assert.equal(session.toString(), "a\nc\nd");
+ assert.position(editor.getCursorPosition(), 1, 0);
+
+ editor.removeLines();
+
+ assert.equal(session.toString(), "a\nd");
+ assert.position(editor.getCursorPosition(), 1, 0);
+
+ editor.removeLines();
+
+ assert.equal(session.toString(), "a");
+ assert.position(editor.getCursorPosition(), 0, 1);
+
+ editor.removeLines();
+
+ assert.equal(session.toString(), "");
+ assert.position(editor.getCursorPosition(), 0, 0);
+ },
+
+ "test: delete multiple selected lines" : function() {
+ var session = new EditSession(["a", "b", "c", "d"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(1, 1);
+ editor.getSelection().selectDown();
+
+ editor.removeLines();
+ assert.equal(session.toString(), "a\nd");
+ assert.position(editor.getCursorPosition(), 1, 0);
+ },
+
+ "test: delete first line" : function() {
+ var session = new EditSession(["a", "b", "c"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.removeLines();
+
+ assert.equal(session.toString(), "b\nc");
+ assert.position(editor.getCursorPosition(), 0, 0);
+ },
+
+ "test: delete last should also delete the new line of the previous line" : function() {
+ var session = new EditSession(["a", "b", "c", ""].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(3, 0);
+
+ editor.removeLines();
+ assert.equal(session.toString(), "a\nb\nc");
+ assert.position(editor.getCursorPosition(), 2, 1);
+
+ editor.removeLines();
+ assert.equal(session.toString(), "a\nb");
+ assert.position(editor.getCursorPosition(), 1, 1);
+ },
+
+ "test: indent block" : function() {
+ var session = new EditSession(["a12345", "b12345", "c12345"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(1, 3);
+ editor.getSelection().selectDown();
+
+ editor.indent();
+
+ assert.equal(["a12345", " b12345", " c12345"].join("\n"), session.toString());
+
+ assert.position(editor.getCursorPosition(), 2, 7);
+
+ var range = editor.getSelectionRange();
+ assert.position(range.start, 1, 7);
+ assert.position(range.end, 2, 7);
+ },
+
+ "test: indent selected lines" : function() {
+ var session = new EditSession(["a12345", "b12345", "c12345"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(1, 0);
+ editor.getSelection().selectDown();
+
+ editor.indent();
+ assert.equal(["a12345", " b12345", "c12345"].join("\n"), session.toString());
+ },
+
+ "test: no auto indent if cursor is before the {" : function() {
+ var session = new EditSession("{", new JavaScriptMode());
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(0, 0);
+ editor.onTextInput("\n");
+ assert.equal(["", "{"].join("\n"), session.toString());
+ },
+
+ "test: outdent block" : function() {
+ var session = new EditSession([" a12345", " b12345", " c12345"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(0, 5);
+ editor.getSelection().selectDown();
+ editor.getSelection().selectDown();
+
+ editor.blockOutdent();
+ assert.equal(session.toString(), [" a12345", "b12345", " c12345"].join("\n"));
+
+ assert.position(editor.getCursorPosition(), 2, 1);
+
+ var range = editor.getSelectionRange();
+ assert.position(range.start, 0, 1);
+ assert.position(range.end, 2, 1);
+
+ editor.blockOutdent();
+ assert.equal(session.toString(), ["a12345", "b12345", "c12345"].join("\n"));
+
+ var range = editor.getSelectionRange();
+ assert.position(range.start, 0, 0);
+ assert.position(range.end, 2, 0);
+ },
+
+ "test: outent without a selection should update cursor" : function() {
+ var session = new EditSession(" 12");
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(0, 3);
+ editor.blockOutdent(" ");
+
+ assert.equal(session.toString(), " 12");
+ assert.position(editor.getCursorPosition(), 0, 0);
+ },
+
+ "test: comment lines should perserve selection" : function() {
+ var session = new EditSession([" abc", "cde"].join("\n"), new JavaScriptMode());
+ var editor = new Editor(new MockRenderer(), session);
+ whitespace.detectIndentation(session);
+
+ editor.moveCursorTo(0, 2);
+ editor.getSelection().selectDown();
+ editor.toggleCommentLines();
+
+ assert.equal(["// abc", "// cde"].join("\n"), session.toString());
+
+ var selection = editor.getSelectionRange();
+ assert.position(selection.start, 0, 5);
+ assert.position(selection.end, 1, 5);
+ },
+
+ "test: uncomment lines should perserve selection" : function() {
+ var session = new EditSession(["// abc", "//cde"].join("\n"), new JavaScriptMode());
+ var editor = new Editor(new MockRenderer(), session);
+ session.setTabSize(2);
+
+ editor.moveCursorTo(0, 1);
+ editor.getSelection().selectDown();
+ editor.getSelection().selectRight();
+ editor.getSelection().selectRight();
+
+ editor.toggleCommentLines();
+
+ assert.equal([" abc", "cde"].join("\n"), session.toString());
+ assert.range(editor.getSelectionRange(), 0, 0, 1, 1);
+ },
+
+ "test: toggle comment lines twice should return the original text" : function() {
+ var session = new EditSession([" abc", "cde", "fg"], new JavaScriptMode());
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(0, 0);
+ editor.getSelection().selectDown();
+ editor.getSelection().selectDown();
+
+ editor.toggleCommentLines();
+ editor.toggleCommentLines();
+
+ assert.equal([" abc", "cde", "fg"].join("\n"), session.toString());
+ },
+
+
+ "test: comment lines - if the selection end is at the line start it should stay there": function() {
+ //select down
+ var session = new EditSession(["abc", "cde"].join("\n"), new JavaScriptMode());
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(0, 0);
+ editor.getSelection().selectDown();
+
+ editor.toggleCommentLines();
+ assert.range(editor.getSelectionRange(), 0, 3, 1, 0);
+
+ // select up
+ var session = new EditSession(["abc", "cde"].join("\n"), new JavaScriptMode());
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(1, 0);
+ editor.getSelection().selectUp();
+
+ editor.toggleCommentLines();
+ assert.range(editor.getSelectionRange(), 0, 3, 1, 0);
+ },
+
+ "test: move lines down should keep selection on moved lines" : function() {
+ var session = new EditSession(["11", "22", "33", "44"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(0, 1);
+ editor.getSelection().selectDown();
+
+ editor.moveLinesDown();
+ assert.equal(["33", "11", "22", "44"].join("\n"), session.toString());
+ assert.position(editor.getCursorPosition(), 2, 1);
+ assert.position(editor.getSelection().getSelectionAnchor(), 1, 1);
+ assert.position(editor.getSelection().getSelectionLead(), 2, 1);
+
+ editor.moveLinesDown();
+ assert.equal(["33", "44", "11", "22"].join("\n"), session.toString());
+ assert.position(editor.getCursorPosition(), 3, 1);
+ assert.position(editor.getSelection().getSelectionAnchor(), 2, 1);
+ assert.position(editor.getSelection().getSelectionLead(), 3, 1);
+
+ // moving again should have no effect
+ editor.moveLinesDown();
+ assert.equal(["33", "44", "11", "22"].join("\n"), session.toString());
+ assert.position(editor.getCursorPosition(), 3, 1);
+ assert.position(editor.getSelection().getSelectionAnchor(), 2, 1);
+ assert.position(editor.getSelection().getSelectionLead(), 3, 1);
+ },
+
+ "test: move lines up should keep selection on moved lines" : function() {
+ var session = new EditSession(["11", "22", "33", "44"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(2, 1);
+ editor.getSelection().selectDown();
+
+ editor.moveLinesUp();
+ assert.equal(session.toString(), ["11", "33", "44", "22"].join("\n"));
+ assert.position(editor.getCursorPosition(), 2, 1);
+ assert.position(editor.getSelection().getSelectionAnchor(), 1, 1);
+ assert.position(editor.getSelection().getSelectionLead(), 2, 1);
+
+ editor.moveLinesUp();
+ assert.equal(session.toString(), ["33", "44", "11", "22"].join("\n"));
+ assert.position(editor.getCursorPosition(), 1, 1);
+ assert.position(editor.getSelection().getSelectionAnchor(), 0, 1);
+ assert.position(editor.getSelection().getSelectionLead(), 1, 1);
+ },
+
+ "test: move line without active selection should not move cursor relative to the moved line" : function() {
+ var session = new EditSession(["11", "22", "33", "44"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(1, 1);
+ editor.clearSelection();
+
+ editor.moveLinesDown();
+ assert.equal(["11", "33", "22", "44"].join("\n"), session.toString());
+ assert.position(editor.getCursorPosition(), 2, 1);
+
+ editor.clearSelection();
+
+ editor.moveLinesUp();
+ assert.equal(["11", "22", "33", "44"].join("\n"), session.toString());
+ assert.position(editor.getCursorPosition(), 1, 1);
+ },
+
+ "test: copy lines down should keep selection" : function() {
+ var session = new EditSession(["11", "22", "33", "44"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(1, 1);
+ editor.getSelection().selectDown();
+
+ editor.copyLinesDown();
+ assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString());
+
+ assert.position(editor.getCursorPosition(), 4, 1);
+ assert.position(editor.getSelection().getSelectionAnchor(), 3, 1);
+ assert.position(editor.getSelection().getSelectionLead(), 4, 1);
+ },
+
+ "test: copy lines up should keep selection" : function() {
+ var session = new EditSession(["11", "22", "33", "44"].join("\n"));
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.moveCursorTo(1, 1);
+ editor.getSelection().selectDown();
+
+ editor.copyLinesUp();
+ assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString());
+
+ assert.position(editor.getCursorPosition(), 2, 1);
+ assert.position(editor.getSelection().getSelectionAnchor(), 1, 1);
+ assert.position(editor.getSelection().getSelectionLead(), 2, 1);
+ },
+
+ "test: input a tab with soft tab should convert it to spaces" : function() {
+ var session = new EditSession("");
+ var editor = new Editor(new MockRenderer(), session);
+
+ session.setTabSize(2);
+ session.setUseSoftTabs(true);
+
+ editor.onTextInput("\t");
+ assert.equal(session.toString(), " ");
+
+ session.setTabSize(5);
+ editor.onTextInput("\t");
+ assert.equal(session.toString(), " ");
+ },
+
+ "test: input tab without soft tabs should keep the tab character" : function() {
+ var session = new EditSession("");
+ var editor = new Editor(new MockRenderer(), session);
+
+ session.setUseSoftTabs(false);
+
+ editor.onTextInput("\t");
+ assert.equal(session.toString(), "\t");
+ },
+
+ "test: undo/redo for delete line" : function() {
+ var session = new EditSession(["111", "222", "333"]);
+ var undoManager = new UndoManager();
+ session.setUndoManager(undoManager);
+
+ var initialText = session.toString();
+ var editor = new Editor(new MockRenderer(), session);
+
+ editor.removeLines();
+ var step1 = session.toString();
+ assert.equal(step1, "222\n333");
+ session.$syncInformUndoManager();
+
+ editor.removeLines();
+ var step2 = session.toString();
+ assert.equal(step2, "333");
+ session.$syncInformUndoManager();
+
+ editor.removeLines();
+ var step3 = session.toString();
+ assert.equal(step3, "");
+ session.$syncInformUndoManager();
+
+ undoManager.undo();
+ session.$syncInformUndoManager();
+ assert.equal(session.toString(), step2);
+
+ undoManager.undo();
+ session.$syncInformUndoManager();
+ assert.equal(session.toString(), step1);
+
+ undoManager.undo();
+ session.$syncInformUndoManager();
+ assert.equal(session.toString(), initialText);
+
+ undoManager.undo();
+ session.$syncInformUndoManager();
+ assert.equal(session.toString(), initialText);
+ },
+
+ "test: remove left should remove character left of the cursor" : function() {
+ var session = new EditSession(["123", "456"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 1);
+ editor.remove("left");
+ assert.equal(session.toString(), "123\n56");
+ },
+
+ "test: remove left should remove line break if cursor is at line start" : function() {
+ var session = new EditSession(["123", "456"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 0);
+ editor.remove("left");
+ assert.equal(session.toString(), "123456");
+ },
+
+ "test: remove left should remove tabsize spaces if cursor is on a tab stop and preceeded by spaces" : function() {
+ var session = new EditSession(["123", " 456"]);
+ session.setUseSoftTabs(true);
+ session.setTabSize(4);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 8);
+ editor.remove("left");
+ assert.equal(session.toString(), "123\n 456");
+ },
+
+ "test: transpose at line start should be a noop": function() {
+ var session = new EditSession(["123", "4567", "89"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 0);
+ editor.transposeLetters();
+
+ assert.equal(session.getValue(), ["123", "4567", "89"].join("\n"));
+ },
+
+ "test: transpose in line should swap the charaters before and after the cursor": function() {
+ var session = new EditSession(["123", "4567", "89"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 2);
+ editor.transposeLetters();
+
+ assert.equal(session.getValue(), ["123", "4657", "89"].join("\n"));
+ },
+
+ "test: transpose at line end should swap the last two characters": function() {
+ var session = new EditSession(["123", "4567", "89"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 4);
+ editor.transposeLetters();
+
+ assert.equal(session.getValue(), ["123", "4576", "89"].join("\n"));
+ },
+
+ "test: transpose with non empty selection should be a noop": function() {
+ var session = new EditSession(["123", "4567", "89"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 1);
+ editor.getSelection().selectRight();
+ editor.transposeLetters();
+
+ assert.equal(session.getValue(), ["123", "4567", "89"].join("\n"));
+ },
+
+ "test: transpose should move the cursor behind the last swapped character": function() {
+ var session = new EditSession(["123", "4567", "89"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 2);
+ editor.transposeLetters();
+ assert.position(editor.getCursorPosition(), 1, 3);
+ },
+
+ "test: remove to line end": function() {
+ var session = new EditSession(["123", "4567", "89"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 2);
+ editor.removeToLineEnd();
+ assert.equal(session.getValue(), ["123", "45", "89"].join("\n"));
+ },
+
+ "test: remove to line end at line end should remove the new line": function() {
+ var session = new EditSession(["123", "4567", "89"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 4);
+ editor.removeToLineEnd();
+ assert.position(editor.getCursorPosition(), 1, 4);
+ assert.equal(session.getValue(), ["123", "456789"].join("\n"));
+ },
+
+ "test: transform selection to uppercase": function() {
+ var session = new EditSession(["ajax", "dot", "org"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 0);
+ editor.getSelection().selectLineEnd();
+ editor.toUpperCase()
+ assert.equal(session.getValue(), ["ajax", "DOT", "org"].join("\n"));
+ },
+
+ "test: transform word to uppercase": function() {
+ var session = new EditSession(["ajax", "dot", "org"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 0);
+ editor.toUpperCase()
+ assert.equal(session.getValue(), ["ajax", "DOT", "org"].join("\n"));
+ assert.position(editor.getCursorPosition(), 1, 0);
+ },
+
+ "test: transform selection to lowercase": function() {
+ var session = new EditSession(["AJAX", "DOT", "ORG"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 0);
+ editor.getSelection().selectLineEnd();
+ editor.toLowerCase()
+ assert.equal(session.getValue(), ["AJAX", "dot", "ORG"].join("\n"));
+ },
+
+ "test: transform word to lowercase": function() {
+ var session = new EditSession(["AJAX", "DOT", "ORG"]);
+
+ var editor = new Editor(new MockRenderer(), session);
+ editor.moveCursorTo(1, 0);
+ editor.toLowerCase()
+ assert.equal(session.getValue(), ["AJAX", "dot", "ORG"].join("\n"));
+ assert.position(editor.getCursorPosition(), 1, 0);
+ }
+};
+
+});
+
+if (typeof module !== "undefined" && module === require.main) {
+ require("asyncjs").test.testcase(module.exports).exec()
+}
http://git-wip-us.apache.org/repos/asf/couchdb/blob/9abd128c/src/fauxton/assets/js/libs/ace/ext/chromevox.js
----------------------------------------------------------------------
diff --git a/src/fauxton/assets/js/libs/ace/ext/chromevox.js b/src/fauxton/assets/js/libs/ace/ext/chromevox.js
new file mode 100644
index 0000000..9f7a799
--- /dev/null
+++ b/src/fauxton/assets/js/libs/ace/ext/chromevox.js
@@ -0,0 +1,980 @@
+define(function(require, exports, module) {
+
+/* ChromeVox Ace namespace. */
+var cvoxAce = {};
+
+/* Typedefs for Closure compiler. */
+/**
+ * @typedef {{
+ rate: number,
+ pitch: number,
+ volume: number,
+ relativePitch: number,
+ punctuationEcho: string
+ }}
+ */
+/* TODO(peterxiao): Export this typedef through cvox.Api. */
+cvoxAce.SpeechProperty;
+
+/**
+ * @typedef {{
+ * row: number,
+ * column: number
+ * }}
+ */
+cvoxAce.Cursor;
+
+/**
+ * @typedef {{
+ type: string,
+ value: string
+ }}
+ }
+ */
+cvoxAce.Token;
+
+/**
+ * These are errors and information that Ace will display in the gutter.
+ * @typedef {{
+ row: number,
+ column: number,
+ value: string
+ }}
+ }
+ */
+cvoxAce.Annotation;
+
+/* Speech Properties. */
+/**
+ * Speech property for speaking constant tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var CONSTANT_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.4,
+ 'volume': 0.9
+};
+
+/**
+ * Default speech property for speaking tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var DEFAULT_PROP = {
+ 'rate': 1,
+ 'pitch': 0.5,
+ 'volume': 0.9
+};
+
+/**
+ * Speech property for speaking entity tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var ENTITY_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.8,
+ 'volume': 0.9
+};
+
+/**
+ * Speech property for speaking keywords.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var KEYWORD_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.3,
+ 'volume': 0.9
+};
+
+/**
+ * Speech property for speaking storage tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var STORAGE_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.7,
+ 'volume': 0.9
+};
+
+/**
+ * Speech property for speaking variable tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var VARIABLE_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.8,
+ 'volume': 0.9
+};
+
+/**
+ * Speech property for speaking deleted text.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var DELETED_PROP = {
+ 'punctuationEcho': 'none',
+ 'relativePitch': -0.6
+};
+
+/* Constants for Earcons. */
+var ERROR_EARCON = 'ALERT_NONMODAL';
+var MODE_SWITCH_EARCON = 'ALERT_MODAL';
+var NO_MATCH_EARCON = 'INVALID_KEYPRESS';
+
+/* Constants for vim state. */
+var INSERT_MODE_STATE = 'insertMode';
+var COMMAND_MODE_STATE = 'start';
+
+var REPLACE_LIST = [
+ {
+ substr: ';',
+ newSubstr: ' semicolon '
+ },
+ {
+ substr: ':',
+ newSubstr: ' colon '
+ }
+];
+
+/**
+ * Context menu commands.
+ */
+var Command = {
+ SPEAK_ANNOT: 'annots',
+ SPEAK_ALL_ANNOTS: 'all_annots',
+ TOGGLE_LOCATION: 'toggle_location',
+ SPEAK_MODE: 'mode',
+ SPEAK_ROW_COL: 'row_col',
+ TOGGLE_DISPLACEMENT: 'toggle_displacement',
+ FOCUS_TEXT: 'focus_text'
+};
+
+/**
+ * Key prefix for each shortcut.
+ */
+var KEY_PREFIX = 'CONTROL + SHIFT ';
+
+/* Globals. */
+cvoxAce.editor = null;
+/**
+ * Last cursor position.
+ * @type {cvoxAce.Cursor}
+ */
+var lastCursor = null;
+
+/**
+ * Table of annotations.
+ * @typedef {!Object.<number, Object<number, cvoxAce.Annotation>>}
+ */
+var annotTable = {};
+
+/**
+ * Whether to speak character, word, and then line. This allows blind users
+ * to know the location of the cursor when they change lines.
+ * @typedef {boolean}
+ */
+var shouldSpeakRowLocation = false;
+
+/**
+ * Whether to speak displacement.
+ * @typedef {boolean}
+ */
+var shouldSpeakDisplacement = false;
+
+/**
+ * Whether text was changed to cause a cursor change event.
+ * @typedef {boolean}
+ */
+var changed = false;
+
+/**
+ * Current state vim is in.
+ */
+var vimState = null;
+
+/**
+ * Mapping from key code to shortcut.
+ */
+var keyCodeToShortcutMap = {};
+
+/**
+ * Mapping from command to shortcut.
+ */
+var cmdToShortcutMap = {};
+
+/**
+ * Get shortcut string from keyCode.
+ * @param {number} keyCode Key code of shortcut.
+ * @return {string} String representation of shortcut.
+ */
+var getKeyShortcutString = function(keyCode) {
+ return KEY_PREFIX + String.fromCharCode(keyCode);
+};
+
+/**
+ * Return if in vim mode.
+ * @return {boolean} True if in Vim mode.
+ */
+var isVimMode = function() {
+ var keyboardHandler = cvoxAce.editor.keyBinding.getKeyboardHandler();
+ return keyboardHandler.$id === 'ace/keyboard/vim';
+};
+
+/**
+ * Gets the current token.
+ * @param {!cvoxAce.Cursor} cursor Current position of the cursor.
+ * @return {!cvoxAce.Token} Token at the current position.
+ */
+var getCurrentToken = function(cursor) {
+ return cvoxAce.editor.getSession().getTokenAt(cursor.row, cursor.column + 1);
+};
+
+/**
+ * Gets the current line the cursor is under.
+ * @param {!cvoxAce.Cursor} cursor Current cursor position.
+ */
+var getCurrentLine = function(cursor) {
+ return cvoxAce.editor.getSession().getLine(cursor.row);
+};
+
+/**
+ * Event handler for row changes. When the user changes rows we want to speak
+ * the line so the user can work on this line. If shouldSpeakRowLocation is on
+ * then we speak the character, then the row, then the line so the user knows
+ * where the cursor is.
+ * @param {!cvoxAce.Cursor} currCursor Current cursor position.
+ */
+var onRowChange = function(currCursor) {
+ /* Notify that this line has an annotation. */
+ if (annotTable[currCursor.row]) {
+ cvox.Api.playEarcon(ERROR_EARCON);
+ }
+ if (shouldSpeakRowLocation) {
+ cvox.Api.stop();
+ speakChar(currCursor);
+ speakTokenQueue(getCurrentToken(currCursor));
+ speakLine(currCursor.row, 1);
+ } else {
+ speakLine(currCursor.row, 0);
+ }
+};
+
+/**
+ * Returns whether the cursor is at the beginning of a word. A word is
+ * a grouping of alphanumeric characters including underscores.
+ * @param {!cvoxAce.Cursor} cursor Current cursor position.
+ * @return {boolean} Whether there is word.
+ */
+var isWord = function(cursor) {
+ var line = getCurrentLine(cursor);
+ var lineSuffix = line.substr(cursor.column - 1);
+ if (cursor.column === 0) {
+ lineSuffix = ' ' + line;
+ }
+ /* Use regex to tell if the suffix is at the start of a new word. */
+ var firstWordRegExp = /^\W(\w+)/;
+ var words = firstWordRegExp.exec(lineSuffix);
+ return words !== null;
+};
+
+/**
+ * A mapping of syntax type to speech properties / expanding rules.
+ */
+var rules = {
+ 'constant': {
+ prop: CONSTANT_PROP
+ },
+ 'entity': {
+ prop: ENTITY_PROP
+ },
+ 'keyword': {
+ prop: KEYWORD_PROP
+ },
+ 'storage': {
+ prop: STORAGE_PROP
+ },
+ 'variable': {
+ prop: VARIABLE_PROP
+ },
+ 'meta': {
+ prop: DEFAULT_PROP,
+ replace: [
+ {
+ substr: '</',
+ newSubstr: ' closing tag '
+ },
+ {
+ substr: '/>',
+ newSubstr: ' close tag '
+ },
+ {
+ substr: '<',
+ newSubstr: ' tag start '
+ },
+ {
+ substr: '>',
+ newSubstr: ' tag end '
+ }
+ ]
+ }
+};
+
+/**
+ * Default rule to be used.
+ */
+var DEFAULT_RULE = {
+ prop: DEFAULT_RULE
+};
+
+/**
+ * Expands substrings to how they are read based on the given rules.
+ * @param {string} value Text to be expanded.
+ * @param {Array.<Object>} replaceRules Rules to determine expansion.
+ * @return {string} New expanded value.
+ */
+var expand = function(value, replaceRules) {
+ var newValue = value;
+ for (var i = 0; i < replaceRules.length; i++) {
+ var replaceRule = replaceRules[i];
+ var regexp = new RegExp(replaceRule.substr, 'g');
+ newValue = newValue.replace(regexp, replaceRule.newSubstr);
+ }
+ return newValue;
+};
+
+/**
+ * Merges tokens from start inclusive to end exclusive.
+ * @param {Array.<cvoxAce.Token>} Tokens to be merged.
+ * @param {number} start Start index inclusive.
+ * @param {number} end End index exclusive.
+ * @return {cvoxAce.Token} Merged token.
+ */
+var mergeTokens = function(tokens, start, end) {
+ /* Different type of token found! Merge all previous like tokens. */
+ var newToken = {};
+ newToken.value = '';
+ newToken.type = tokens[start].type;
+ for (var j = start; j < end; j++) {
+ newToken.value += tokens[j].value;
+ }
+ return newToken;
+};
+
+/**
+ * Merges tokens that use the same speech properties.
+ * @param {Array.<cvoxAce.Token>} tokens Tokens to be merged.
+ * @return {Array.<cvoxAce.Token>} Merged tokens.
+ */
+var mergeLikeTokens = function(tokens) {
+ if (tokens.length <= 1) {
+ return tokens;
+ }
+ var newTokens = [];
+ var lastLikeIndex = 0;
+ for (var i = 1; i < tokens.length; i++) {
+ var lastLikeToken = tokens[lastLikeIndex];
+ var currToken = tokens[i];
+ if (getTokenRule(lastLikeToken) !== getTokenRule(currToken)) {
+ newTokens.push(mergeTokens(tokens, lastLikeIndex, i));
+ lastLikeIndex = i;
+ }
+ }
+ newTokens.push(mergeTokens(tokens, lastLikeIndex, tokens.length));
+ return newTokens;
+};
+
+/**
+ * Returns if given row is a whitespace row.
+ * @param {number} row Row.
+ * @return {boolean} True if row is whitespaces.
+ */
+var isRowWhiteSpace = function(row) {
+ var line = cvoxAce.editor.getSession().getLine(row);
+ var whiteSpaceRegexp = /^\s*$/;
+ return whiteSpaceRegexp.exec(line) !== null;
+};
+
+/**
+ * Speak the line with syntax properties.
+ * @param {number} row Row to speak.
+ * @param {number} queue Queue mode to speak.
+ */
+var speakLine = function(row, queue) {
+ var tokens = cvoxAce.editor.getSession().getTokens(row);
+ if (tokens.length === 0 || isRowWhiteSpace(row)) {
+ cvox.Api.playEarcon('EDITABLE_TEXT');
+ return;
+ }
+ tokens = mergeLikeTokens(tokens);
+ var firstToken = tokens[0];
+ /* Filter out first token. */
+ tokens = tokens.filter(function(token) {
+ return token !== firstToken;
+ });
+ /* Speak first token separately to flush if queue. */
+ speakToken_(firstToken, queue);
+ /* Speak rest of tokens. */
+ tokens.forEach(speakTokenQueue);
+};
+
+/**
+ * Speak the token based on the syntax of the token, flushing.
+ * @param {!cvoxAce.Token} token Token to speak.
+ * @param {number} queue Queue mode.
+ */
+var speakTokenFlush = function(token) {
+ speakToken_(token, 0);
+};
+
+/**
+ * Speak the token based on the syntax of the token, queueing.
+ * @param {!cvoxAce.Token} token Token to speak.
+ * @param {number} queue Queue mode.
+ */
+var speakTokenQueue = function(token) {
+ speakToken_(token, 1);
+};
+
+/**
+ * @param {!cvoxAce.Token} token Token to speak.
+ * Get the token speech property.
+ */
+var getTokenRule = function(token) {
+ /* Types are period delimited. In this case, we only syntax speak the outer
+ * most type of token. */
+ if (!token || !token.type) {
+ return;
+ }
+ var split = token.type.split('.');
+ if (split.length === 0) {
+ return;
+ }
+ var type = split[0];
+ var rule = rules[type];
+ if (!rule) {
+ return DEFAULT_RULE;
+ }
+ return rule;
+};
+
+/**
+ * Speak the token based on the syntax of the token.
+ * @private
+ * @param {!cvoxAce.Token} token Token to speak.
+ * @param {number} queue Queue mode.
+ */
+var speakToken_ = function(token, queue) {
+ var rule = getTokenRule(token);
+ var value = expand(token.value, REPLACE_LIST);
+ if (rule.replace) {
+ value = expand(value, rule.replace);
+ }
+ cvox.Api.speak(value, queue, rule.prop);
+};
+
+/**
+ * Speaks the character under the cursor. This is queued.
+ * @param {!cvoxAce.Cursor} cursor Current cursor position.
+ * @return {string} Character.
+ */
+var speakChar = function(cursor) {
+ var line = getCurrentLine(cursor);
+ cvox.Api.speak(line[cursor.column], 1);
+};
+
+/**
+ * Speaks the jump from lastCursor to currCursor. This function assumes the
+ * jump takes place on the current line.
+ * @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
+ * @param {!cvoxAce.Cursor} currCursor Current cursor position.
+ */
+var speakDisplacement = function(lastCursor, currCursor) {
+ var line = getCurrentLine(currCursor);
+
+ /* Get the text that we jumped past. */
+ var displace = line.substring(lastCursor.column, currCursor.column);
+
+ /* Speak out loud spaces. */
+ displace = displace.replace(/ /g, ' space ');
+ cvox.Api.speak(displace);
+};
+
+/**
+ * Speaks the word if the cursor jumped to a new word or to the beginning
+ * of the line. Otherwise speak the charactor.
+ * @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
+ * @param {!cvoxAce.Cursor} currCursor Current cursor position.
+ */
+var speakCharOrWordOrLine = function(lastCursor, currCursor) {
+ /* Say word only if jump. */
+ if (Math.abs(lastCursor.column - currCursor.column) !== 1) {
+ var currLineLength = getCurrentLine(currCursor).length;
+ /* Speak line if jumping to beginning or end of line. */
+ if (currCursor.column === 0 || currCursor.column === currLineLength) {
+ speakLine(currCursor.row, 0);
+ return;
+ }
+ if (isWord(currCursor)) {
+ cvox.Api.stop();
+ speakTokenQueue(getCurrentToken(currCursor));
+ return;
+ }
+ }
+ speakChar(currCursor);
+};
+
+/**
+ * Event handler for column changes. If shouldSpeakDisplacement is on, then
+ * we just speak displacements in row changes. Otherwise, we either speak
+ * the character for single character movements, the word when jumping to the
+ * next word, or the entire line if jumping to beginning or end of the line.
+ * @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
+ * @param {!cvoxAce.Cursor} currCursor Current cursor position.
+ */
+var onColumnChange = function(lastCursor, currCursor) {
+ if (!cvoxAce.editor.selection.isEmpty()) {
+ speakDisplacement(lastCursor, currCursor);
+ cvox.Api.speak('selected', 1);
+ }
+ else if (shouldSpeakDisplacement) {
+ speakDisplacement(lastCursor, currCursor);
+ } else {
+ speakCharOrWordOrLine(lastCursor, currCursor);
+ }
+};
+
+/**
+ * Event handler for cursor changes. Classify cursor changes as either row or
+ * column changes, then delegate accordingly.
+ * @param {!Event} evt The event.
+ */
+var onCursorChange = function(evt) {
+ /* Do not speak if cursor change was a result of text insertion. We want to
+ * speak the text that was inserted and not where the cursor lands. */
+ if (changed) {
+ changed = false;
+ return;
+ }
+ var currCursor = cvoxAce.editor.selection.getCursor();
+ if (currCursor.row !== lastCursor.row) {
+ onRowChange(currCursor);
+ } else {
+ onColumnChange(lastCursor, currCursor);
+ }
+ lastCursor = currCursor;
+};
+
+/**
+ * Event handler for selection changes.
+ * @param {!Event} evt The event.
+ */
+var onSelectionChange = function(evt) {
+ /* Assumes that when selection changes to empty, the user has unselected. */
+ if (cvoxAce.editor.selection.isEmpty()) {
+ cvox.Api.speak('unselected');
+ }
+};
+
+/**
+ * Event handler for source changes. We want auditory feedback for inserting
+ * and deleting text.
+ * @param {!Event} evt The event.
+ */
+var onChange = function(evt) {
+ var data = evt.data;
+ switch (data.action) {
+ case 'removeText':
+ cvox.Api.speak(data.text, 0, DELETED_PROP);
+ /* Let the future cursor change event know it's from text change. */
+ changed = true;
+ break;
+ case 'insertText':
+ cvox.Api.speak(data.text, 0);
+ /* Let the future cursor change event know it's from text change. */
+ changed = true;
+ break;
+ }
+};
+
+/**
+ * Returns whether or not the annotation is new.
+ * @param {!cvoxAce.Annotation} annot Annotation in question.
+ * @return {boolean} Whether annot is new.
+ */
+var isNewAnnotation = function(annot) {
+ var row = annot.row;
+ var col = annot.column;
+ return !annotTable[row] || !annotTable[row][col];
+};
+
+/**
+ * Populates the annotation table.
+ * @param {!Array.<cvoxAce.Annotation>} annotations Array of annotations.
+ */
+var populateAnnotations = function(annotations) {
+ annotTable = {};
+ for (var i = 0; i < annotations.length; i++) {
+ var annotation = annotations[i];
+ var row = annotation.row;
+ var col = annotation.column;
+ if (!annotTable[row]) {
+ annotTable[row] = {};
+ }
+ annotTable[row][col] = annotation;
+ }
+};
+
+/**
+ * Event handler for annotation changes. We want to notify the user when an
+ * a new annotation appears.
+ * @param {!Event} evt Event.
+ */
+var onAnnotationChange = function(evt) {
+ var annotations = cvoxAce.editor.getSession().getAnnotations();
+ var newAnnotations = annotations.filter(isNewAnnotation);
+ if (newAnnotations.length > 0) {
+ cvox.Api.playEarcon(ERROR_EARCON);
+ }
+ populateAnnotations(annotations);
+};
+
+/**
+ * Speak annotation.
+ * @param {!cvoxAce.Annotation} annot Annotation to speak.
+ */
+var speakAnnot = function(annot) {
+ var annotText = annot.type + ' ' + annot.text + ' on ' +
+ rowColToString(annot.row, annot.column);
+ annotText = annotText.replace(';', 'semicolon');
+ cvox.Api.speak(annotText, 1);
+};
+
+/**
+ * Speak annotations in a row.
+ * @param {number} row Row of annotations to speak.
+ */
+var speakAnnotsByRow = function(row) {
+ var annots = annotTable[row];
+ for (var col in annots) {
+ speakAnnot(annots[col]);
+ }
+};
+
+/**
+ * Get a string representation of a row and column.
+ * @param {boolean} row Zero indexed row.
+ * @param {boolean} col Zero indexed column.
+ * @return {string} Row and column to be spoken.
+ */
+var rowColToString = function(row, col) {
+ return 'row ' + (row + 1) + ' column ' + (col + 1);
+};
+
+/**
+ * Speaks the row and column.
+ */
+var speakCurrRowAndCol = function() {
+ cvox.Api.speak(rowColToString(lastCursor.row, lastCursor.column));
+};
+
+/**
+ * Speaks all annotations.
+ */
+var speakAllAnnots = function() {
+ for (var row in annotTable) {
+ speakAnnotsByRow(row);
+ }
+};
+
+/**
+ * Speak the vim mode. If no vim mode, this function does nothing.
+ */
+var speakMode = function() {
+ if (!isVimMode()) {
+ return;
+ }
+ switch (cvoxAce.editor.keyBinding.$data.state) {
+ case INSERT_MODE_STATE:
+ cvox.Api.speak('Insert mode');
+ break;
+ case COMMAND_MODE_STATE:
+ cvox.Api.speak('Command mode');
+ break;
+ }
+};
+
+/**
+ * Toggle speak location.
+ */
+var toggleSpeakRowLocation = function() {
+ shouldSpeakRowLocation = !shouldSpeakRowLocation;
+ /* Auditory feedback of the change. */
+ if (shouldSpeakRowLocation) {
+ cvox.Api.speak('Speak location on row change enabled.');
+ } else {
+ cvox.Api.speak('Speak location on row change disabled.');
+ }
+};
+
+/**
+ * Toggle speak displacement.
+ */
+var toggleSpeakDisplacement = function() {
+ shouldSpeakDisplacement = !shouldSpeakDisplacement;
+ /* Auditory feedback of the change. */
+ if (shouldSpeakDisplacement) {
+ cvox.Api.speak('Speak displacement on column changes.');
+ } else {
+ cvox.Api.speak('Speak current character or word on column changes.');
+ }
+};
+
+/**
+ * Event handler for key down events. Gets the right shortcut from the map,
+ * and calls the associated function.
+ * @param {!Event} evt Keyboard event.
+ */
+var onKeyDown = function(evt) {
+ if (evt.ctrlKey && evt.shiftKey) {
+ var shortcut = keyCodeToShortcutMap[evt.keyCode];
+ if (shortcut) {
+ shortcut.func();
+ }
+ }
+};
+
+/**
+ * Event handler for status change events. Auditory feedback of changing
+ * between vim states.
+ * @param {!Event} evt Change status event.
+ * @param {!Object} editor Editor state.
+ */
+var onChangeStatus = function(evt, editor) {
+ if (!isVimMode()) {
+ return;
+ }
+ var state = editor.keyBinding.$data.state;
+ if (state === vimState) {
+ /* State hasn't changed, do nothing. */
+ return;
+ }
+ switch (state) {
+ case INSERT_MODE_STATE:
+ cvox.Api.playEarcon(MODE_SWITCH_EARCON);
+ /* When in insert mode, we want to speak out keys as feedback. */
+ cvox.Api.setKeyEcho(true);
+ break;
+ case COMMAND_MODE_STATE:
+ cvox.Api.playEarcon(MODE_SWITCH_EARCON);
+ /* When in command mode, we want don't speak out keys because those keys
+ * are not being inserted in the document. */
+ cvox.Api.setKeyEcho(false);
+ break;
+ }
+ vimState = state;
+};
+
+/**
+ * Handles context menu events. This is a ChromeVox feature where hitting
+ * the shortcut ChromeVox + comma will open up a search bar where you can
+ * type in various commands. All keyboard shortcuts are also commands that
+ * can be invoked. This handles the event that ChromeVox sends to the page.
+ * @param {Event} evt Event received.
+ */
+var contextMenuHandler = function(evt) {
+ var cmd = evt.detail['customCommand'];
+ var shortcut = cmdToShortcutMap[cmd];
+ if (shortcut) {
+ shortcut.func();
+ /* ChromeVox will bring focus to an element near the cursor instead of the
+ * text input. */
+ cvoxAce.editor.focus();
+ }
+};
+
+/**
+ * Initialize the ChromeVox context menu.
+ */
+var initContextMenu = function() {
+ var ACTIONS = SHORTCUTS.map(function(shortcut) {
+ return {
+ desc: shortcut.desc + getKeyShortcutString(shortcut.keyCode),
+ cmd: shortcut.cmd
+ };
+ });
+
+ /* Attach ContextMenuActions. */
+ var body = document.querySelector('body');
+ body.setAttribute('contextMenuActions', JSON.stringify(ACTIONS));
+
+ /* Listen for ContextMenu events. */
+ body.addEventListener('ATCustomEvent', contextMenuHandler, true);
+};
+
+/**
+ * Event handler for find events. When there is a match, we want to speak the
+ * line we are now at. Otherwise, we want to notify the user there was no
+ * match
+ * @param {!Event} evt The event.
+ */
+var onFindSearchbox = function(evt) {
+ if (evt.match) {
+ /* There is still a match! Speak the line. */
+ speakLine(lastCursor.row, 0);
+ } else {
+ /* No match, give auditory feedback! */
+ cvox.Api.playEarcon(NO_MATCH_EARCON);
+ }
+};
+
+/**
+ * Focus to text input.
+ */
+var focus = function() {
+ cvoxAce.editor.focus();
+};
+
+/**
+ * Shortcut definitions.
+ */
+var SHORTCUTS = [
+ {
+ /* 1 key. */
+ keyCode: 49,
+ func: function() {
+ speakAnnotsByRow(lastCursor.row);
+ },
+ cmd: Command.SPEAK_ANNOT,
+ desc: 'Speak annotations on line'
+ },
+ {
+ /* 2 key. */
+ keyCode: 50,
+ func: speakAllAnnots,
+ cmd: Command.SPEAK_ALL_ANNOTS,
+ desc: 'Speak all annotations'
+ },
+ {
+ /* 3 key. */
+ keyCode: 51,
+ func: speakMode,
+ cmd: Command.SPEAK_MODE,
+ desc: 'Speak Vim mode'
+ },
+ {
+ /* 4 key. */
+ keyCode: 52,
+ func: toggleSpeakRowLocation,
+ cmd: Command.TOGGLE_LOCATION,
+ desc: 'Toggle speak row location'
+ },
+ {
+ /* 5 key. */
+ keyCode: 53,
+ func: speakCurrRowAndCol,
+ cmd: Command.SPEAK_ROW_COL,
+ desc: 'Speak row and column'
+ },
+ {
+ /* 6 key. */
+ keyCode: 54,
+ func: toggleSpeakDisplacement,
+ cmd: Command.TOGGLE_DISPLACEMENT,
+ desc: 'Toggle speak displacement'
+ },
+ {
+ /* 7 key. */
+ keyCode: 55,
+ func: focus,
+ cmd: Command.FOCUS_TEXT,
+ desc: 'Focus text'
+ }
+];
+
+/**
+ * Event handler for focus events.
+ */
+var onFocus = function() {
+ cvoxAce.editor = editor;
+
+ /* Set up listeners. */
+ editor.getSession().selection.on('changeCursor', onCursorChange);
+ editor.getSession().selection.on('changeSelection', onSelectionChange);
+ editor.getSession().on('change', onChange);
+ editor.getSession().on('changeAnnotation', onAnnotationChange);
+ editor.on('changeStatus', onChangeStatus);
+ editor.on('findSearchBox', onFindSearchbox);
+ editor.container.addEventListener('keydown', onKeyDown);
+
+ lastCursor = editor.selection.getCursor();
+};
+
+/**
+ * Initialize the theme.
+ * @param {Object} editor Editor to use.
+ */
+var init = function(editor) {
+ onFocus();
+
+ /* Construct maps. */
+ SHORTCUTS.forEach(function(shortcut) {
+ keyCodeToShortcutMap[shortcut.keyCode] = shortcut;
+ cmdToShortcutMap[shortcut.cmd] = shortcut;
+ });
+
+ editor.on('focus', onFocus);
+
+ /* Assume we start in command mode if vim. */
+ if (isVimMode()) {
+ cvox.Api.setKeyEcho(false);
+ }
+ initContextMenu();
+};
+
+/**
+ * Returns if cvox exists, and the api exists.
+ * @return {boolean} Whether not Cvox Api exists.
+ */
+function cvoxApiExists() {
+ return (typeof(cvox) !== 'undefined') && cvox && cvox.Api;
+}
+
+/**
+ * Number of tries for Cvox loading.
+ * @type {number}
+ */
+var tries = 0;
+
+/**
+ * Max number of tries to watch for Cvox loading.
+ * @type {number}
+ */
+var MAX_TRIES = 15;
+
+/**
+ * Check for ChromeVox load.
+ * @param {Object} editor Editor to use.
+ */
+function watchForCvoxLoad(editor) {
+ if (cvoxApiExists()) {
+ init(editor);
+ } else {
+ tries++;
+ if (tries >= MAX_TRIES) {
+ return;
+ }
+ window.setTimeout(watchForCvoxLoad, 500, editor);
+ }
+}
+
+var Editor = require('../editor').Editor;
+require('../config').defineOptions(Editor.prototype, 'editor', {
+ enableChromevoxEnhancements: {
+ set: function(val) {
+ if (val) {
+ watchForCvoxLoad(this);
+ }
+ },
+ value: true // turn it on by default or check for window.cvox
+ }
+});
+
+});
http://git-wip-us.apache.org/repos/asf/couchdb/blob/9abd128c/src/fauxton/assets/js/libs/ace/ext/elastic_tabstops_lite.js
----------------------------------------------------------------------
diff --git a/src/fauxton/assets/js/libs/ace/ext/elastic_tabstops_lite.js b/src/fauxton/assets/js/libs/ace/ext/elastic_tabstops_lite.js
new file mode 100644
index 0000000..9901c5d
--- /dev/null
+++ b/src/fauxton/assets/js/libs/ace/ext/elastic_tabstops_lite.js
@@ -0,0 +1,319 @@
+/* ***** 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 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;
+
+ // 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.$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);
+ // todo: support multicursor
+
+ 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()) {
+ // todo: support multicursor
+ 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;
+ }
+ // 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.$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;
+
+ // 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.$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.$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;
+ }
+ }
+ };
+
+ // the is a (naive) Python port--but works for these purposes
+ 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;
+ };
+
+ // 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(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);
+ }
+ }
+ }
+});
+
+});
\ No newline at end of file