From 278f1d1674e002a737c8a74012d1d8160d41d26e Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 21 Nov 2014 00:54:29 +0400 Subject: [PATCH 1/5] add helper for creating tests --- demo/kitchen-sink/dev_util.js | 163 ++++++++++++++++++++++++++++++---- 1 file changed, 147 insertions(+), 16 deletions(-) diff --git a/demo/kitchen-sink/dev_util.js b/demo/kitchen-sink/dev_util.js index 81ab446b..4580d9be 100644 --- a/demo/kitchen-sink/dev_util.js +++ b/demo/kitchen-sink/dev_util.js @@ -29,6 +29,8 @@ * ***** END LICENSE BLOCK ***** */ define(function(require, exports, module) { +var dom = require("ace/lib/dom"); +var Range = require("ace/range").Range; // allow easy access to ace in console, but not in ace code which uses strict function isStrict() { try { return !arguments.callee.caller.caller.caller} @@ -59,21 +61,150 @@ def(window, "session", function(){ warn(); return window.env.editor.session }); def(window, "split", function(){ warn(); return window.env.split }); -/* for textinput debuggging -dom.importCssString("\ - .ace_text-input {\ - position: absolute;\ - z-index: 10!important;\ - width: 6em!important;\ - height: 1em;\ - opacity: 1!important;\ - background: rgba(0, 92, 255, 0.11);\ - border: none;\ - font: inherit;\ - padding: 0 1px;\ - margin: 0 -1px;\ - text-indent: 0em;\ -}\ -")*/ +def(window, "devUtil", function(){ warn(); return exports }); +exports.showTextArea = function(argument) { + dom.importCssString("\ + .ace_text-input {\ + position: absolute;\ + z-index: 10!important;\ + width: 6em!important;\ + height: 1em;\ + opacity: 1!important;\ + background: rgba(0, 92, 255, 0.11);\ + border: none;\ + font: inherit;\ + padding: 0 1px;\ + margin: 0 -1px;\ + text-indent: 0em;\ + }\ + "); +}; + +exports.addGlobals = function() { + window.oop = require("ace/lib/oop"); + window.dom = require("ace/lib/dom"); + window.Range = require("ace/range").Range; + window.Editor = require("ace/editor").Editor; + window.assert = require("ace/test/asyncjs/assert"); + window.asyncjs = require("ace/test/asyncjs/async"); + window.UndoManager = require("ace/undomanager").UndoManager; + window.EditSession = require("ace/edit_session").EditSession; + window.MockRenderer = require("ace/test/mockrenderer").MockRenderer; + window.EventEmitter = require("ace/lib/event_emitter").EventEmitter; +}; + +exports.recordTestCase = function() { + exports.addGlobals(); + var editor = window.editor; + var testcase = window.testcase = []; + var assert; + function getSelection(editor) { + var data = editor.multiSelect.toJSON(); + if (!data.length) data = [data]; + data = data.map(function(x) { + var a, c; + if (x.isBackwards) { + a = x.end; + c = x.start; + } else { + c = x.end; + a = x.start; + } + return Range.comparePoints(a, c) + ? [a.row, a.column, c.row, c.column] + : [a.row, a.column]; + }); + return data.length > 1 ? data : data[0]; + } + function setSelection(editor, data) { + if (typeof data[0] == "number") + data = [data]; + editor.selection.fromJSON(data.map(function(x) { + var start = {row: x[0], column: x[1]}; + var end = x.length == 2 ? start : {row: x[2], column: x[3]}; + var isBackwards = Range.comparePoints(start, end) > 0; + return isBackwards ? { + start: end, + end: start, + isBackwards: true + } : { + start: start, + end: end, + isBackwards: true + }; + })); + } + function testSelection(editor, data) { + assert.equal(getSelection(editor) + "", data + ""); + } + + testcase.push({ + type: "setValue", + data: editor.getValue() + }, { + type: "setSelection", + data: getSelection(editor) + }); + editor.commands.on("afterExec", function(e) { + testcase.push({ + type: "exec", + data: e + }); + testcase.push({ + type: "value", + data: editor.getValue() + }); + testcase.push({ + type: "selection", + data: getSelection(editor) + }); + }); + editor.on("mouseup", function() { + testcase.push({ + type: "setSelection", + data: getSelection(editor) + }); + }); + + testcase.toString = function() { + var lastValue = ""; + // var lastSelection = "" + var str = this.map(function(x) { + var data = x.data; + switch (x.type) { + case "exec": + return 'editor.execCommand("' + + data.command.name + + (data.args ? '", ' + JSON.stringify(data.args) : '"') + + ')'; + case "setSelection": + return 'setSelection(editor, ' + JSON.stringify(data) + ')'; + case "setValue": + if (lastValue != data) { + lastValue = data; + return 'editor.setValue(' + JSON.stringify(data) + ', -1)'; + } + return; + case "selection": + return 'testSelection(editor, ' + JSON.stringify(data) + ')'; + case "value": + if (lastValue != data) { + lastValue = data; + return 'assert.equal(' + + 'editor.getValue(),' + + JSON.stringify(data) + + ')'; + } + return; + } + }).filter(Boolean).join("\n"); + + return getSelection + "\n" + + testSelection + "\n" + + setSelection + "\n" + + "\n" + str + "\n"; + }; +}; + }); From 852e0673ca4dca473159f848d9b51d5e5bd11e56 Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 21 Nov 2014 00:58:25 +0400 Subject: [PATCH 2/5] fix pairing of quotes in cstyle behavior --- lib/ace/mode/behaviour/cstyle.js | 68 ++++++++++++++------------------ 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/lib/ace/mode/behaviour/cstyle.js b/lib/ace/mode/behaviour/cstyle.js index a8a18e72..db8f4a36 100644 --- a/lib/ace/mode/behaviour/cstyle.js +++ b/lib/ace/mode/behaviour/cstyle.js @@ -260,48 +260,38 @@ var CstyleBehaviour = function() { var cursor = editor.getCursorPosition(); var line = session.doc.getLine(cursor.row); var leftChar = line.substring(cursor.column-1, cursor.column); - + var rightChar = line.substring(cursor.column, cursor.column + 1); + + var token = session.getTokenAt(cursor.row, cursor.column); + var rightToken = session.getTokenAt(cursor.row, cursor.column + 1); // We're escaped. - if (leftChar == '\\') { + if (leftChar == "\\" && token && /escape/.test(token.type)) return null; + + var stringBefore = token && /string/.test(token.type); + var stringAfter = !rightToken || /string/.test(rightToken.type); + + var pair; + if (rightChar == quote) { + pair = stringBefore !== stringAfter; + } else { + if (stringBefore && !stringAfter) + return null; // wrap string with different quote + var wordRe = session.$mode.tokenRe; + wordRe.lastIndex = 0; + var isWordBefore = wordRe.test(leftChar); + wordRe.lastIndex = 0; + var isWordAfter = wordRe.test(leftChar); + if (isWordBefore || isWordAfter) + return null; // before or after alphanumeric + if (rightChar && !/[\s;,.})\]\\]/.test(rightChar)) + return null; // there is rightChar and it isn't closing + pair = true; } - - // Find what token we're inside. - var tokens = session.getTokens(selection.start.row); - var col = 0, token; - var quotepos = -1; // Track whether we're inside an open quote. - - for (var x = 0; x < tokens.length; x++) { - token = tokens[x]; - if (token.type == "string") { - quotepos = -1; - } else if (quotepos < 0) { - quotepos = token.value.indexOf(quote); - } - if ((token.value.length + col) > selection.start.column) { - break; - } - col += tokens[x].value.length; - } - - // Try and be smart about when we auto insert. - if (!token || (quotepos < 0 && token.type !== "comment" && (token.type !== "string" || ((selection.start.column !== token.value.length+col-1) && token.value.lastIndexOf(quote) === token.value.length-1)))) { - if (!CstyleBehaviour.isSaneInsertion(editor, session)) - return; - return { - text: quote + quote, - selection: [1,1] - }; - } else if (token && token.type === "string") { - // Ignore input and move right one if we're typing over the closing quote. - var rightChar = line.substring(cursor.column, cursor.column + 1); - if (rightChar == quote) { - return { - text: '', - selection: [1, 1] - }; - } - } + return { + text: pair ? quote + quote : "", + selection: [1,1] + }; } } }); From 3fa54ce97e9d14ebd0b140deb702692c569d67fc Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 21 Nov 2014 01:02:31 +0400 Subject: [PATCH 3/5] fix copyLines* for multiple selections --- demo/kitchen-sink/dev_util.js | 85 ++++++++++++++++++----------------- kitchen-sink.html | 4 +- lib/ace/editor.js | 79 ++++++++++++++++---------------- lib/ace/multi_select_test.js | 70 +++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 79 deletions(-) diff --git a/demo/kitchen-sink/dev_util.js b/demo/kitchen-sink/dev_util.js index 4580d9be..ae9200b7 100644 --- a/demo/kitchen-sink/dev_util.js +++ b/demo/kitchen-sink/dev_util.js @@ -91,53 +91,58 @@ exports.addGlobals = function() { window.EditSession = require("ace/edit_session").EditSession; window.MockRenderer = require("ace/test/mockrenderer").MockRenderer; window.EventEmitter = require("ace/lib/event_emitter").EventEmitter; + + window.getSelection = getSelection; + window.setSelection = setSelection; + window.testSelection = testSelection; }; +function getSelection(editor) { + var data = editor.multiSelect.toJSON(); + if (!data.length) data = [data]; + data = data.map(function(x) { + var a, c; + if (x.isBackwards) { + a = x.end; + c = x.start; + } else { + c = x.end; + a = x.start; + } + return Range.comparePoints(a, c) + ? [a.row, a.column, c.row, c.column] + : [a.row, a.column]; + }); + return data.length > 1 ? data : data[0]; +} +function setSelection(editor, data) { + if (typeof data[0] == "number") + data = [data]; + editor.selection.fromJSON(data.map(function(x) { + var start = {row: x[0], column: x[1]}; + var end = x.length == 2 ? start : {row: x[2], column: x[3]}; + var isBackwards = Range.comparePoints(start, end) > 0; + return isBackwards ? { + start: end, + end: start, + isBackwards: true + } : { + start: start, + end: end, + isBackwards: true + }; + })); +} +function testSelection(editor, data) { + assert.equal(getSelection(editor) + "", data + ""); +} + exports.recordTestCase = function() { exports.addGlobals(); var editor = window.editor; var testcase = window.testcase = []; var assert; - function getSelection(editor) { - var data = editor.multiSelect.toJSON(); - if (!data.length) data = [data]; - data = data.map(function(x) { - var a, c; - if (x.isBackwards) { - a = x.end; - c = x.start; - } else { - c = x.end; - a = x.start; - } - return Range.comparePoints(a, c) - ? [a.row, a.column, c.row, c.column] - : [a.row, a.column]; - }); - return data.length > 1 ? data : data[0]; - } - function setSelection(editor, data) { - if (typeof data[0] == "number") - data = [data]; - editor.selection.fromJSON(data.map(function(x) { - var start = {row: x[0], column: x[1]}; - var end = x.length == 2 ? start : {row: x[2], column: x[3]}; - var isBackwards = Range.comparePoints(start, end) > 0; - return isBackwards ? { - start: end, - end: start, - isBackwards: true - } : { - start: start, - end: end, - isBackwards: true - }; - })); - } - function testSelection(editor, data) { - assert.equal(getSelection(editor) + "", data + ""); - } - + testcase.push({ type: "setValue", data: editor.getValue() diff --git a/kitchen-sink.html b/kitchen-sink.html index 65fe2a2a..a8e1fb7b 100644 --- a/kitchen-sink.html +++ b/kitchen-sink.html @@ -257,7 +257,9 @@ - +
tests +
diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 78c7c18d..f487b49e 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -1655,9 +1655,7 @@ var Editor = function(renderer, session) { * @related EditSession.moveLinesUp **/ this.moveLinesDown = function() { - this.$moveLines(function(firstRow, lastRow) { - return this.session.moveLinesDown(firstRow, lastRow); - }); + this.$moveLines(1, false); }; /** @@ -1666,9 +1664,7 @@ var Editor = function(renderer, session) { * @related EditSession.moveLinesDown **/ this.moveLinesUp = function() { - this.$moveLines(function(firstRow, lastRow) { - return this.session.moveLinesUp(firstRow, lastRow); - }); + this.$moveLines(-1, false); }; /** @@ -1692,10 +1688,7 @@ var Editor = function(renderer, session) { * **/ this.copyLinesUp = function() { - this.$moveLines(function(firstRow, lastRow) { - this.session.duplicateLines(firstRow, lastRow); - return 0; - }); + this.$moveLines(-1, true); }; /** @@ -1705,51 +1698,61 @@ var Editor = function(renderer, session) { * **/ this.copyLinesDown = function() { - this.$moveLines(function(firstRow, lastRow) { - return this.session.duplicateLines(firstRow, lastRow); - }); + this.$moveLines(1, true); }; /** - * Executes a specific function, which can be anything that manipulates selected lines, such as copying them, duplicating them, or shifting them. - * @param {Function} mover A method to call on each selected row - * + * for internal use + * @ignore * **/ - this.$moveLines = function(mover) { + this.$moveLines = function(dir, copy) { + var rows, moved; var selection = this.selection; if (!selection.inMultiSelectMode || this.inVirtualSelectionMode) { var range = selection.toOrientedRange(); - var rows = this.$getSelectedRows(range); - var linesMoved = mover.call(this, rows.first, rows.last); - range.moveBy(linesMoved, 0); + rows = this.$getSelectedRows(range); + moved = this.session.$moveLines(rows.first, rows.last, copy ? 0 : dir); + if (copy && dir == -1) moved = 0; + range.moveBy(moved, 0); selection.fromOrientedRange(range); } else { var ranges = selection.rangeList.ranges; selection.rangeList.detach(this.session); - - for (var i = ranges.length; i--; ) { + this.inVirtualSelectionMode = true; + + var diff = 0; + var totalDiff = 0; + var l = ranges.length; + for (var i = 0; i < l; i++) { var rangeIndex = i; - var rows = ranges[i].collapseRows(); - var last = rows.end.row; - var first = rows.start.row; - while (i--) { - rows = ranges[i].collapseRows(); - if (first - rows.end.row <= 1) - first = rows.end.row; - else + ranges[i].moveBy(diff, 0); + rows = this.$getSelectedRows(ranges[i]); + var first = rows.first; + var last = rows.last; + while (++i < l) { + if (totalDiff) ranges[i].moveBy(totalDiff, 0); + var subRows = this.$getSelectedRows(ranges[i]); + if (copy && subRows.first != last) break; + else if (!copy && subRows.first > last + 1) + break; + last = subRows.last; } - i++; - - var linesMoved = mover.call(this, first, last); - while (rangeIndex >= i) { - ranges[rangeIndex].moveBy(linesMoved, 0); - rangeIndex--; + i--; + diff = this.session.$moveLines(first, last, copy ? 0 : dir); + if (copy && dir == -1) rangeIndex = i + 1; + while (rangeIndex <= i) { + ranges[rangeIndex].moveBy(diff, 0); + rangeIndex++; } + if (!copy) diff = 0; + totalDiff += diff; } + selection.fromOrientedRange(selection.ranges[0]); selection.rangeList.attach(this.session); + this.inVirtualSelectionMode = false; } }; @@ -1762,8 +1765,8 @@ var Editor = function(renderer, session) { * * @returns {Object} **/ - this.$getSelectedRows = function() { - var range = this.getSelectionRange().collapseRows(); + this.$getSelectedRows = function(range) { + range = (range || this.getSelectionRange()).collapseRows(); return { first: this.session.getRowFoldStart(range.start.row), diff --git a/lib/ace/multi_select_test.js b/lib/ace/multi_select_test.js index 2e71aefa..1eccc4ce 100644 --- a/lib/ace/multi_select_test.js +++ b/lib/ace/multi_select_test.js @@ -51,6 +51,45 @@ var exec = function(name, times, args) { var testRanges = function(str) { assert.equal(editor.selection.getAllRanges() + "", str + ""); }; +function getSelection(editor) { + var data = editor.multiSelect.toJSON(); + if (!data.length) data = [data]; + data = data.map(function(x) { + var a, c; + if (x.isBackwards) { + a = x.end; + c = x.start; + } else { + c = x.end; + a = x.start; + } + return Range.comparePoints(a, c) + ? [a.row, a.column, c.row, c.column] + : [a.row, a.column]; + }); + return data.length > 1 ? data : data[0]; +} +function testSelection(editor, data) { + assert.equal(getSelection(editor) + "", data + ""); +} +function setSelection(editor, data) { + if (typeof data[0] == "number") + data = [data]; + editor.selection.fromJSON(data.map(function(x) { + var start = {row: x[0], column: x[1]}; + var end = x.length == 2 ? start : {row: x[2], column: x[3]}; + var isBackwards = Range.comparePoints(start, end) > 0; + return isBackwards ? { + start: end, + end: start, + isBackwards: true + } : { + start: start, + end: end, + isBackwards: true + }; + })); +} module.exports = { @@ -167,6 +206,37 @@ module.exports = { editor.execCommand('insertfoo'); assert.equal('l1foo\nl2foo', editor.getValue()); }, + + "test multiselect move lines": function() { + editor = new Editor(new MockRenderer()); + + editor.setValue("l1\nl2\nl3\nl4", -1); + setSelection(editor, [[0,2],[1,2],[2,2],[3,2]]); + + exec("copylinesdown"); + assert.equal(editor.getValue(),"l1\nl1\nl2\nl2\nl3\nl3\nl4\nl4"); + testSelection(editor, [[1,2],[3,2],[5,2],[7,2]]); + exec("copylinesup"); + assert.equal(editor.getValue(),"l1\nl1\nl1\nl2\nl2\nl2\nl3\nl3\nl3\nl4\nl4\nl4"); + testSelection(editor, [[1,2],[4,2],[7,2],[10,2]]); + exec("removeline"); + assert.equal(editor.getValue(),"l1\nl1\nl2\nl2\nl3\nl3\nl4\nl4"); + testSelection(editor, [[1,0],[3,0],[5,0],[7,0]]); + + setSelection(editor, [[1,2],[1,0,1,1],[3,0,3,1],[5,0,5,1],[7,0,7,1]]); + exec("copylinesdown"); + exec("copylinesup"); + assert.equal(editor.getValue(),"l1\nl1\nl1\nl1\nl2\nl2\nl2\nl2\nl3\nl3\nl3\nl3\nl4\nl4\nl4\nl4"); + testSelection(editor, [[2,2],[2,0,2,1],[6,0,6,1],[10,0,10,1],[14,0,14,1]]); + + exec("movelinesdown", 12); + assert.equal(editor.getValue(),"l1\nl1\nl1\nl2\nl2\nl2\nl3\nl3\nl3\nl4\nl4\nl4\nl1\nl2\nl3\nl4"); + testSelection(editor, [[12,2],[12,0,12,1],[13,0,13,1],[14,0,14,1],[15,0,15,1]]); + + exec("movelinesup", 12); + assert.equal(editor.getValue(),"l1\nl2\nl3\nl4\nl1\nl1\nl1\nl2\nl2\nl2\nl3\nl3\nl3\nl4\nl4\nl4"); + testSelection(editor, [[0,2],[0,0,0,1],[1,0,1,1],[2,0,2,1],[3,0,3,1]]); + }, "test multiselect fromJSON/toJSON": function() { var doc = new EditSession(["l1", "l2"]); From ec18d9493d89805f702e66854f84c687ac0d6f89 Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 21 Nov 2014 01:04:11 +0400 Subject: [PATCH 4/5] fix toggleComment command in php mode --- lib/ace/mode/php.js | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/ace/mode/php.js b/lib/ace/mode/php.js index 2754ef51..c7688d43 100644 --- a/lib/ace/mode/php.js +++ b/lib/ace/mode/php.js @@ -55,6 +55,24 @@ oop.inherits(PhpMode, TextMode); (function() { + this.tokenRe = new RegExp("^[" + + unicode.packages.L + + unicode.packages.Mn + unicode.packages.Mc + + unicode.packages.Nd + + unicode.packages.Pc + "\_]+", "g" + ); + + this.nonTokenRe = new RegExp("^(?:[^" + + unicode.packages.L + + unicode.packages.Mn + unicode.packages.Mc + + unicode.packages.Nd + + unicode.packages.Pc + "\_]|\s])+", "g" + ); + + + this.lineCommentStart = ["//", "#"]; + this.blockComment = {start: "/*", end: "*/"}; + this.getNextLineIndent = function(state, line, tab) { var indent = this.$getIndent(line); @@ -100,8 +118,10 @@ oop.inherits(PhpMode, TextMode); var Mode = function(opts) { if (opts && opts.inline) { - PhpMode.call(this); - return; + var mode = new PhpMode(); + mode.createWorker = this.createWorker; + mode.inlinePhp = true; + return mode; } HtmlMode.call(this); this.HighlightRules = PhpHighlightRules; @@ -116,24 +136,6 @@ oop.inherits(Mode, HtmlMode); (function() { - this.tokenRe = new RegExp("^[" - + unicode.packages.L - + unicode.packages.Mn + unicode.packages.Mc - + unicode.packages.Nd - + unicode.packages.Pc + "\_]+", "g" - ); - - this.nonTokenRe = new RegExp("^(?:[^" - + unicode.packages.L - + unicode.packages.Mn + unicode.packages.Mc - + unicode.packages.Nd - + unicode.packages.Pc + "\_]|\s])+", "g" - ); - - - this.lineCommentStart = ["//", "#"]; - this.blockComment = {start: "/*", end: "*/"}; - this.createWorker = function(session) { var worker = new WorkerClient(["ace"], "ace/mode/php_worker", "PhpWorker"); worker.attachToDocument(session.getDocument()); From 6b13aedf5f45fad451dea7a0bb2431ea9737ce53 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 4 Dec 2014 14:47:17 +0400 Subject: [PATCH 5/5] do not pair quotes inside strings --- lib/ace/mode/behaviour/behaviour_test.js | 9 +++++++++ lib/ace/mode/behaviour/cstyle.js | 2 ++ 2 files changed, 11 insertions(+) diff --git a/lib/ace/mode/behaviour/behaviour_test.js b/lib/ace/mode/behaviour/behaviour_test.js index ded22ccb..ba0a5041 100644 --- a/lib/ace/mode/behaviour/behaviour_test.js +++ b/lib/ace/mode/behaviour/behaviour_test.js @@ -120,6 +120,15 @@ module.exports = { assert.equal(editor.getValue(), "{") exec("insertstring", 1, "\n"); assert.equal(editor.getValue(), "{\n \n}") + + editor.setValue(""); + exec("insertstring", 1, "("); + exec("insertstring", 1, '"'); + exec("insertstring", 1, '"'); + assert.equal(editor.getValue(), '("")'); + exec("backspace", 1); + exec("insertstring", 1, '"'); + assert.equal(editor.getValue(), '("")'); } }; diff --git a/lib/ace/mode/behaviour/cstyle.js b/lib/ace/mode/behaviour/cstyle.js index db8f4a36..5abf08fd 100644 --- a/lib/ace/mode/behaviour/cstyle.js +++ b/lib/ace/mode/behaviour/cstyle.js @@ -277,6 +277,8 @@ var CstyleBehaviour = function() { } else { if (stringBefore && !stringAfter) return null; // wrap string with different quote + if (stringBefore && stringAfter) + return null; // do not pair quotes inside strings var wordRe = session.$mode.tokenRe; wordRe.lastIndex = 0; var isWordBefore = wordRe.test(leftChar);