diff --git a/.gitignore b/.gitignore index c6a7ecc9..59ec847b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .DS_Store *.swp *.tmp +*~ # Project files that should not be in the repo .* diff --git a/demo/kitchen-sink/dev_util.js b/demo/kitchen-sink/dev_util.js index 81ab446b..ae9200b7 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,155 @@ 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; + + 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; + + 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"; + }; +}; + }); 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/autocomplete.js b/lib/ace/autocomplete.js index 2544c858..dd04426e 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -42,6 +42,7 @@ var snippetManager = require("./snippets").snippetManager; var Autocomplete = function() { this.autoInsert = true; this.autoSelect = true; + this.exactMatch = false; this.keyboardHandler = new HashHandler(); this.keyboardHandler.bindKeys(this.commands); @@ -53,7 +54,7 @@ var Autocomplete = function() { this.changeTimer = lang.delayedCall(function() { this.updateCompletions(true); }.bind(this)); - + this.tooltipTimer = lang.delayedCall(this.updateDocTooltip.bind(this), 50); }; @@ -72,7 +73,7 @@ var Autocomplete = function() { this.popup.on("changeHoverMarker", this.tooltipTimer.bind(null, null)); return this.popup; }; - + this.getPopup = function() { return this.popup || this.$init(); }; @@ -118,7 +119,7 @@ var Autocomplete = function() { this.gatherCompletionsId += 1; this.popup.hide(); } - + if (this.base) this.base.detach(); this.activated = false; @@ -141,7 +142,7 @@ var Autocomplete = function() { // on IE preventDefault doesn't stop scrollbar from being focussed var el = document.activeElement; var text = this.editor.textInput.getElement() - if (el != text && el.parentNode != this.popup.container + if (el != text && el.parentNode != this.popup.container && el != this.tooltipNode && e.relatedTarget != this.tooltipNode && e.relatedTarget != text ) { @@ -227,7 +228,7 @@ var Autocomplete = function() { this.base = session.doc.createAnchor(pos.row, pos.column - prefix.length); this.base.$insertRight = true; - + var matches = []; var total = editor.completers.length; editor.completers.forEach(function(completer, i) { @@ -297,7 +298,7 @@ var Autocomplete = function() { var prefix = results.prefix; var matches = results && results.matches; - + if (!matches || !matches.length) return detachIfFinished(); @@ -306,6 +307,10 @@ var Autocomplete = function() { return; this.completions = new FilteredList(matches); + + if (this.exactMatch) + this.completions.exactMatch = true; + this.completions.setFilter(prefix); var filtered = this.completions.filtered; @@ -328,7 +333,7 @@ var Autocomplete = function() { this.cancelContextMenu = function() { this.editor.$mouseHandler.cancelContextMenu(); }; - + this.updateDocTooltip = function() { var popup = this.popup; var all = popup.data; @@ -343,14 +348,14 @@ var Autocomplete = function() { }); if (!doc) doc = selected; - + if (typeof doc == "string") doc = {docText: doc} if (!doc || !(doc.docHTML || doc.docText)) return this.hideDocTooltip(); this.showDocTooltip(doc); }; - + this.showDocTooltip = function(item) { if (!this.tooltipNode) { this.tooltipNode = dom.createElement("div"); @@ -360,21 +365,21 @@ var Autocomplete = function() { this.tooltipNode.tabIndex = -1; this.tooltipNode.onblur = this.blurListener.bind(this); } - + var tooltipNode = this.tooltipNode; if (item.docHTML) { tooltipNode.innerHTML = item.docHTML; } else if (item.docText) { tooltipNode.textContent = item.docText; } - + if (!tooltipNode.parentNode) - document.body.appendChild(tooltipNode); + document.body.appendChild(tooltipNode); var popup = this.popup; var rect = popup.container.getBoundingClientRect(); tooltipNode.style.top = popup.container.style.top; tooltipNode.style.bottom = popup.container.style.bottom; - + if (window.innerWidth - rect.right < 320) { tooltipNode.style.right = window.innerWidth - rect.left + "px"; tooltipNode.style.left = ""; @@ -384,7 +389,7 @@ var Autocomplete = function() { } tooltipNode.style.display = "block"; }; - + this.hideDocTooltip = function() { this.tooltipTimer.cancel(); if (!this.tooltipNode) return; @@ -392,7 +397,7 @@ var Autocomplete = function() { if (!this.editor.isFocused() && document.activeElement == el) this.editor.focus(); this.tooltipNode = null; - if (el.parentNode) + if (el.parentNode) el.parentNode.removeChild(el); }; @@ -403,7 +408,7 @@ Autocomplete.startCommand = { exec: function(editor) { if (!editor.completer) editor.completer = new Autocomplete(); - editor.completer.autoInsert = + editor.completer.autoInsert = editor.completer.autoSelect = true; editor.completer.showPopup(editor); // needed for firefox on mac @@ -416,6 +421,7 @@ var FilteredList = function(array, filterText, mutateData) { this.all = array; this.filtered = array; this.filterText = filterText || ""; + this.exactMatch = false; }; (function(){ this.setFilter = function(str) { @@ -452,23 +458,29 @@ var FilteredList = function(array, filterText, mutateData) { var matchMask = 0; var penalty = 0; var index, distance; - // caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf - for (var j = 0; j < needle.length; j++) { - // TODO add penalty on case mismatch - var i1 = caption.indexOf(lower[j], lastIndex + 1); - var i2 = caption.indexOf(upper[j], lastIndex + 1); - index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2; - if (index < 0) + + if (this.exactMatch) { + if (needle !== caption.substr(0, needle.length)) continue loop; - distance = index - lastIndex - 1; - if (distance > 0) { - // first char mismatch should be more sensitive - if (lastIndex === -1) - penalty += 10; - penalty += distance; + }else{ + // caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf + for (var j = 0; j < needle.length; j++) { + // TODO add penalty on case mismatch + var i1 = caption.indexOf(lower[j], lastIndex + 1); + var i2 = caption.indexOf(upper[j], lastIndex + 1); + index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2; + if (index < 0) + continue loop; + distance = index - lastIndex - 1; + if (distance > 0) { + // first char mismatch should be more sensitive + if (lastIndex === -1) + penalty += 10; + penalty += distance; + } + matchMask = matchMask | (1 << index); + lastIndex = index; } - matchMask = matchMask | (1 << index); - lastIndex = index; } item.matchMask = matchMask; item.exactMatch = penalty ? 0 : 1; diff --git a/lib/ace/autocomplete/popup.js b/lib/ace/autocomplete/popup.js index f580e391..862a1c5e 100644 --- a/lib/ace/autocomplete/popup.js +++ b/lib/ace/autocomplete/popup.js @@ -209,6 +209,8 @@ var AcePopup = function(parentNode) { return this.screenWidth = 0; }; + popup.$blockScrolling = Infinity; + // public popup.isOpen = false; popup.isTopdown = false; @@ -241,6 +243,7 @@ var AcePopup = function(parentNode) { popup.on("changeSelection", function() { if (popup.isOpen) popup.setRow(popup.selection.lead.row); + popup.renderer.scrollCursorIntoView(); }); popup.hide = function() { diff --git a/lib/ace/commands/default_commands.js b/lib/ace/commands/default_commands.js index a150a76f..c5d34aae 100644 --- a/lib/ace/commands/default_commands.js +++ b/lib/ace/commands/default_commands.js @@ -205,12 +205,14 @@ exports.commands = [{ bindKey: bindKey("Shift-Up", "Shift-Up"), exec: function(editor) { editor.getSelection().selectUp(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "golineup", bindKey: bindKey("Up", "Up|Ctrl-P"), exec: function(editor, args) { editor.navigateUp(args.times); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "selecttoend", @@ -395,12 +397,21 @@ exports.commands = [{ bindKey: bindKey("Ctrl-P", "Ctrl-P"), exec: function(editor) { editor.jumpToMatching(); }, multiSelectAction: "forEach", + scrollIntoView: "animate", readOnly: true }, { name: "selecttomatching", bindKey: bindKey("Ctrl-Shift-P", "Ctrl-Shift-P"), exec: function(editor) { editor.jumpToMatching(true); }, multiSelectAction: "forEach", + scrollIntoView: "animate", + readOnly: true +}, { + name: "expandToMatching", + bindKey: bindKey("Ctrl-Shift-M", "Ctrl-Shift-M"), + exec: function(editor) { editor.jumpToMatching(true, true); }, + multiSelectAction: "forEach", + scrollIntoView: "animate", readOnly: true }, { name: "passKeysToBrowser", @@ -458,11 +469,13 @@ exports.commands = [{ name: "modifyNumberUp", bindKey: bindKey("Ctrl-Shift-Up", "Alt-Shift-Up"), exec: function(editor) { editor.modifyNumber(1); }, + scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "modifyNumberDown", bindKey: bindKey("Ctrl-Shift-Down", "Alt-Shift-Down"), exec: function(editor) { editor.modifyNumber(-1); }, + scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "replace", @@ -629,7 +642,7 @@ exports.commands = [{ var isBackwards = editor.selection.isBackwards(); var selectionStart = isBackwards ? editor.selection.getSelectionLead() : editor.selection.getSelectionAnchor(); var selectionEnd = isBackwards ? editor.selection.getSelectionAnchor() : editor.selection.getSelectionLead(); - var firstLineEndCol = editor.session.doc.getLine(selectionStart.row).length + var firstLineEndCol = editor.session.doc.getLine(selectionStart.row).length; var selectedText = editor.session.doc.getTextRange(editor.selection.getRange()); var selectedCount = selectedText.replace(/\n\s*/, " ").length; var insertLine = editor.session.doc.getLine(selectionStart.row); @@ -640,7 +653,7 @@ exports.commands = [{ curLine = " " + curLine; } insertLine += curLine; - }; + } if (selectionEnd.row + 1 < (editor.session.doc.getLength() - 1)) { // Don't insert a newline at the end of the document diff --git a/lib/ace/commands/multi_select_commands.js b/lib/ace/commands/multi_select_commands.js index 347eb606..ba6392bc 100644 --- a/lib/ace/commands/multi_select_commands.js +++ b/lib/ace/commands/multi_select_commands.js @@ -35,41 +35,49 @@ exports.defaultCommands = [{ name: "addCursorAbove", exec: function(editor) { editor.selectMoreLines(-1); }, bindKey: {win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up"}, + scrollIntoView: "cursor", readonly: true }, { name: "addCursorBelow", exec: function(editor) { editor.selectMoreLines(1); }, bindKey: {win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down"}, + scrollIntoView: "cursor", readonly: true }, { name: "addCursorAboveSkipCurrent", exec: function(editor) { editor.selectMoreLines(-1, true); }, bindKey: {win: "Ctrl-Alt-Shift-Up", mac: "Ctrl-Alt-Shift-Up"}, + scrollIntoView: "cursor", readonly: true }, { name: "addCursorBelowSkipCurrent", exec: function(editor) { editor.selectMoreLines(1, true); }, bindKey: {win: "Ctrl-Alt-Shift-Down", mac: "Ctrl-Alt-Shift-Down"}, + scrollIntoView: "cursor", readonly: true }, { name: "selectMoreBefore", exec: function(editor) { editor.selectMore(-1); }, bindKey: {win: "Ctrl-Alt-Left", mac: "Ctrl-Alt-Left"}, + scrollIntoView: "cursor", readonly: true }, { name: "selectMoreAfter", exec: function(editor) { editor.selectMore(1); }, bindKey: {win: "Ctrl-Alt-Right", mac: "Ctrl-Alt-Right"}, + scrollIntoView: "cursor", readonly: true }, { name: "selectNextBefore", exec: function(editor) { editor.selectMore(-1, true); }, bindKey: {win: "Ctrl-Alt-Shift-Left", mac: "Ctrl-Alt-Shift-Left"}, + scrollIntoView: "cursor", readonly: true }, { name: "selectNextAfter", exec: function(editor) { editor.selectMore(1, true); }, bindKey: {win: "Ctrl-Alt-Shift-Right", mac: "Ctrl-Alt-Shift-Right"}, + scrollIntoView: "cursor", readonly: true }, { name: "splitIntoLines", @@ -79,11 +87,13 @@ exports.defaultCommands = [{ }, { name: "alignCursors", exec: function(editor) { editor.alignCursors(); }, - bindKey: {win: "Ctrl-Alt-A", mac: "Ctrl-Alt-A"} + bindKey: {win: "Ctrl-Alt-A", mac: "Ctrl-Alt-A"}, + scrollIntoView: "cursor" }, { name: "findAll", exec: function(editor) { editor.findAll(); }, bindKey: {win: "Ctrl-Alt-K", mac: "Ctrl-Alt-G"}, + scrollIntoView: "cursor", readonly: true }]; @@ -92,6 +102,7 @@ exports.multiSelectCommands = [{ name: "singleSelection", bindKey: "esc", exec: function(editor) { editor.exitMultiSelectMode(); }, + scrollIntoView: "cursor", readonly: true, isAvailable: function(editor) {return editor && editor.inMultiSelectMode} }]; diff --git a/lib/ace/config.js b/lib/ace/config.js index 6116da96..34bf50fd 100644 --- a/lib/ace/config.js +++ b/lib/ace/config.js @@ -34,7 +34,9 @@ define(function(require, exports, module) { var lang = require("./lib/lang"); var oop = require("./lib/oop"); var net = require("./lib/net"); -var EventEmitter = require("./lib/event_emitter").EventEmitter; +var AppConfig = require("./lib/app_config").AppConfig; + +module.exports = exports = new AppConfig(); var global = (function() { return this; @@ -69,8 +71,6 @@ exports.all = function() { }; // module loading -oop.implement(exports, EventEmitter); - exports.moduleUrl = function(name, component) { if (options.$moduleUrls[name]) return options.$moduleUrls[name]; @@ -142,7 +142,6 @@ exports.loadModule = function(moduleName, onLoad) { net.loadScript(exports.moduleUrl(moduleName, moduleType), afterLoad); }; - // initialization function init(packaged) { options.packaged = packaged || require.packaged || module.packaged || (global.define && define.packaged); @@ -200,102 +199,4 @@ function deHyphenate(str) { return str.replace(/-(.)/g, function(m, m1) { return m1.toUpperCase(); }); } -var optionsProvider = { - setOptions: function(optList) { - Object.keys(optList).forEach(function(key) { - this.setOption(key, optList[key]); - }, this); - }, - getOptions: function(optionNames) { - var result = {}; - if (!optionNames) { - optionNames = Object.keys(this.$options); - } else if (!Array.isArray(optionNames)) { - result = optionNames; - optionNames = Object.keys(result); - } - optionNames.forEach(function(key) { - result[key] = this.getOption(key); - }, this); - return result; - }, - setOption: function(name, value) { - if (this["$" + name] === value) - return; - var opt = this.$options[name]; - if (!opt) { - if (typeof console != "undefined" && console.warn) - console.warn('misspelled option "' + name + '"'); - return undefined; - } - if (opt.forwardTo) - return this[opt.forwardTo] && this[opt.forwardTo].setOption(name, value); - - if (!opt.handlesSet) - this["$" + name] = value; - if (opt && opt.set) - opt.set.call(this, value); - }, - getOption: function(name) { - var opt = this.$options[name]; - if (!opt) { - if (typeof console != "undefined" && console.warn) - console.warn('misspelled option "' + name + '"'); - return undefined; - } - if (opt.forwardTo) - return this[opt.forwardTo] && this[opt.forwardTo].getOption(name); - return opt && opt.get ? opt.get.call(this) : this["$" + name]; - } -}; - -var defaultOptions = {}; -/* - * option {name, value, initialValue, setterName, set, get } - */ -exports.defineOptions = function(obj, path, options) { - if (!obj.$options) - defaultOptions[path] = obj.$options = {}; - - Object.keys(options).forEach(function(key) { - var opt = options[key]; - if (typeof opt == "string") - opt = {forwardTo: opt}; - - opt.name || (opt.name = key); - obj.$options[opt.name] = opt; - if ("initialValue" in opt) - obj["$" + opt.name] = opt.initialValue; - }); - - // implement option provider interface - oop.implement(obj, optionsProvider); - - return this; -}; - -exports.resetOptions = function(obj) { - Object.keys(obj.$options).forEach(function(key) { - var opt = obj.$options[key]; - if ("value" in opt) - obj.setOption(key, opt.value); - }); -}; - -exports.setDefaultValue = function(path, name, value) { - var opts = defaultOptions[path] || (defaultOptions[path] = {}); - if (opts[name]) { - if (opts.forwardTo) - exports.setDefaultValue(opts.forwardTo, name, value); - else - opts[name].value = value; - } -}; - -exports.setDefaultValues = function(path, optionHash) { - Object.keys(optionHash).forEach(function(key) { - exports.setDefaultValue(path, key, optionHash[key]); - }); -}; - }); diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 932a9e3d..347c854a 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -969,8 +969,7 @@ var EditSession = function(text, mode) { try { this.$worker = this.$mode.createWorker(this); } catch (e) { - if (typeof console == "object" && console.error) - console.log("Could not load worker", e); + config.warn("Could not load worker", e); this.$worker = null; } }; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 59b136b1..f487b49e 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -150,7 +150,8 @@ var Editor = function(renderer, session) { args: commadEvent.args, scrollTop: this.renderer.scrollTop }; - + if (this.curOp.command.name) + this.$blockScrolling++; // this.selections.push(this.selection.toJSON()); }; @@ -160,6 +161,8 @@ var Editor = function(renderer, session) { return this.curOp = null; this._signal("beforeEndOperation"); var command = this.curOp.command; + if (command.name && this.$blockScrolling) + this.$blockScrolling--; if (command && command.scrollIntoView) { switch (command.scrollIntoView) { case "center": @@ -714,6 +717,10 @@ var Editor = function(renderer, session) { this.$cursorChange(); if (!this.$blockScrolling) { + config.warn("Automatically scrolling cursor into view after selection change", + "this will be disabled in the next version", + "set editor.$blockScrolling = Infinity to disable this message" + ); this.renderer.scrollCursorIntoView(); } @@ -902,9 +909,28 @@ var Editor = function(renderer, session) { // todo this should change when paste becomes a command if (this.$readOnly) return; + var e = {text: text}; this._signal("paste", e); - this.insert(e.text, true); + text = e.text; + if (!this.inMultiSelectMode || this.inVirtualSelectionMode) { + this.insert(text); + } else { + var lines = text.split(/\r\n|\r|\n/); + var ranges = this.selection.rangeList.ranges; + + if (lines.length > ranges.length || lines.length < 2 || !lines[1]) + return this.commands.exec("insertstring", this, text); + + for (var i = ranges.length; i--;) { + var range = ranges[i]; + if (!range.isEmpty()) + this.session.remove(range); + + this.session.insert(range.start, lines[i]); + } + } + this.renderer.scrollCursorIntoView(); }; this.execCommand = function(command, args) { @@ -1629,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); }; /** @@ -1640,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); }; /** @@ -1666,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); }; /** @@ -1679,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; } }; @@ -1736,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/lib/app_config.js b/lib/ace/lib/app_config.js new file mode 100644 index 00000000..84b9e70c --- /dev/null +++ b/lib/ace/lib/app_config.js @@ -0,0 +1,158 @@ +/* ***** 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 ***** */ + +define(function(require, exports, module) { +"no use strict"; + +var lang = require("./lang"); +var oop = require("./oop"); +var EventEmitter = require("./event_emitter").EventEmitter; + +var optionsProvider = { + setOptions: function(optList) { + Object.keys(optList).forEach(function(key) { + this.setOption(key, optList[key]); + }, this); + }, + getOptions: function(optionNames) { + var result = {}; + if (!optionNames) { + optionNames = Object.keys(this.$options); + } else if (!Array.isArray(optionNames)) { + result = optionNames; + optionNames = Object.keys(result); + } + optionNames.forEach(function(key) { + result[key] = this.getOption(key); + }, this); + return result; + }, + setOption: function(name, value) { + if (this["$" + name] === value) + return; + var opt = this.$options[name]; + if (!opt) { + return warn('misspelled option "' + name + '"'); + } + if (opt.forwardTo) + return this[opt.forwardTo] && this[opt.forwardTo].setOption(name, value); + + if (!opt.handlesSet) + this["$" + name] = value; + if (opt && opt.set) + opt.set.call(this, value); + }, + getOption: function(name) { + var opt = this.$options[name]; + if (!opt) { + return warn('misspelled option "' + name + '"'); + } + if (opt.forwardTo) + return this[opt.forwardTo] && this[opt.forwardTo].getOption(name); + return opt && opt.get ? opt.get.call(this) : this["$" + name]; + } +}; + +function warn(message) { + if (typeof console != "undefined" && console.warn) + console.warn.apply(console, arguments); +} + +function reportError(msg, data) { + var e = new Error(msg); + e.data = data; + if (typeof console == "object" && console.error) + console.error(e); + setTimeout(function() { throw e; }); +}; + +var AppConfig = function() { + this.$defaultOptions = {}; +}; + +(function() { + // module loading + oop.implement(this, EventEmitter); + /* + * option {name, value, initialValue, setterName, set, get } + */ + this.defineOptions = function(obj, path, options) { + if (!obj.$options) + this.$defaultOptions[path] = obj.$options = {}; + + Object.keys(options).forEach(function(key) { + var opt = options[key]; + if (typeof opt == "string") + opt = {forwardTo: opt}; + + opt.name || (opt.name = key); + obj.$options[opt.name] = opt; + if ("initialValue" in opt) + obj["$" + opt.name] = opt.initialValue; + }); + + // implement option provider interface + oop.implement(obj, optionsProvider); + + return this; + }; + + this.resetOptions = function(obj) { + Object.keys(obj.$options).forEach(function(key) { + var opt = obj.$options[key]; + if ("value" in opt) + obj.setOption(key, opt.value); + }); + }; + + this.setDefaultValue = function(path, name, value) { + var opts = this.$defaultOptions[path] || (this.$defaultOptions[path] = {}); + if (opts[name]) { + if (opts.forwardTo) + this.setDefaultValue(opts.forwardTo, name, value); + else + opts[name].value = value; + } + }; + + this.setDefaultValues = function(path, optionHash) { + Object.keys(optionHash).forEach(function(key) { + this.setDefaultValue(path, key, optionHash[key]); + }); + }; + + this.warn = warn; + this.reportError = reportError; + +}).call(AppConfig.prototype); + +exports.AppConfig = AppConfig; + +}); diff --git a/lib/ace/lib/dom.js b/lib/ace/lib/dom.js index 65f20d7d..ef2f7caa 100644 --- a/lib/ace/lib/dom.js +++ b/lib/ace/lib/dom.js @@ -31,9 +31,6 @@ define(function(require, exports, module) { "use strict"; -if (typeof document == "undefined") - return; - var XHTML_NS = "http://www.w3.org/1999/xhtml"; exports.getDocumentHead = function(doc) { @@ -175,6 +172,10 @@ exports.getInnerHeight = function(element) { ); }; + +if (typeof document == "undefined") + return; + if (window.pageYOffset !== undefined) { exports.getPageScrollTop = function() { return window.pageYOffset; 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 a8a18e72..5abf08fd 100644 --- a/lib/ace/mode/behaviour/cstyle.js +++ b/lib/ace/mode/behaviour/cstyle.js @@ -260,48 +260,40 @@ 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 + if (stringBefore && stringAfter) + return null; // do not pair quotes inside strings + 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] + }; } } }); diff --git a/lib/ace/mode/eiffel_highlight_rules.js b/lib/ace/mode/eiffel_highlight_rules.js index f2cc8bd6..a3ca8896 100644 --- a/lib/ace/mode/eiffel_highlight_rules.js +++ b/lib/ace/mode/eiffel_highlight_rules.js @@ -39,7 +39,7 @@ var EiffelHighlightRules = function() { "class|convert|create|debug|deferred|detachable|do|else|elseif|end|" + "ensure|expanded|export|external|feature|from|frozen|if|inherit|" + "inspect|invariant|like|local|loop|not|note|obsolete|old|once|" + - "Precursor|redefine|rename|require|rescue|retry|select|separate|" + + "Precursor|redefine|rename|require|rescue|retry|select|separate|" + "some|then|undefine|until|variant|when"; var operatorKeywords = "and|implies|or|xor"; @@ -58,45 +58,44 @@ var EiffelHighlightRules = function() { "keyword": keywords }, "identifier", true); + var simpleString = /(?:[^"%\b\f\v]|%[A-DFHLNQR-V%'"()<>]|%\/(?:0[xX][\da-fA-F](?:_*[\da-fA-F])*|0[cC][0-7](?:_*[0-7])*|0[bB][01](?:_*[01])*|\d(?:_*\d)*)\/)+?/; + this.$rules = { "start": [{ - token : "comment.line.double-dash", - regex : /--.*$/ - }, { - token : "string.quoted.double", - regex : /"(?:%"|[^%])*?"/ - }, { - token : "string.quoted.other", // "[ ]" aligned verbatim string + token : "string.quoted.other", // Aligned-verbatim-strings (verbatim option not supported) regex : /"\[/, next: "aligned_verbatim_string" }, { - token : "string.quoted.other", // "{ }" non-aligned verbatim string + token : "string.quoted.other", // Non-aligned-verbatim-strings (verbatim option not supported) regex : /"\{/, next: "non-aligned_verbatim_string" + }, { + token : "string.quoted.double", + regex : /"(?:[^%\b\f\n\r\v]|%[A-DFHLNQR-V%'"()<>]|%\/(?:0[xX][\da-fA-F](?:_*[\da-fA-F])*|0[cC][0-7](?:_*[0-7])*|0[bB][01](?:_*[01])*|\d(?:_*\d)*)\/)*?"/ + }, { + token : "comment.line.double-dash", + regex : /--.*/ }, { token : "constant.character", - regex : /'(?:%%|%T|%R|%N|%F|%'|[^%])'/ + regex : /'(?:[^%\b\f\n\r\t\v]|%[A-DFHLNQR-V%'"()<>]|%\/(?:0[xX][\da-fA-F](?:_*[\da-fA-F])*|0[cC][0-7](?:_*[0-7])*|0[bB][01](?:_*[01])*|\d(?:_*\d)*)\/)'/ }, { - token : "constant.numeric", // real - regex : /(?:\d(?:_?\d)*\.|\.\d)(?:\d*[eE][+-]?\d+)?\b/ + token : "constant.numeric", // hexa | octal | bin + regex : /\b0(?:[xX][\da-fA-F](?:_*[\da-fA-F])*|[cC][0-7](?:_*[0-7])*|[bB][01](?:_*[01])*)\b/ }, { - token : "constant.numeric", // integer - regex : /\d(?:_?\d)*\b/ + token : "constant.numeric", + regex : /(?:\d(?:_*\d)*)?\.(?:(?:\d(?:_*\d)*)?[eE][+-]?)?\d(?:_*\d)*|\d(?:_*\d)*\.?/ }, { - token : "constant.numeric", // hex - regex : /0[xX][a-fA-F\d](?:_?[a-fA-F\d])*\b/ + token : "paren.lparen", + regex : /[\[({]|<<|\|\(/ }, { - token : "constant.numeric", // octal - regex : /0[cC][0-7](?:_?[0-7])*\b/ - },{ - token : "constant.numeric", // bin - regex : /0[bB][01](?:_?[01])*\b/ - }, { - token : "keyword.operator", - regex : /\+|\-|\*|\/|\\\\|\/\/|\^|~|\/~|<|>|<=|>=|\/=|=|:=|\|\.\.\||\.\./ + token : "paren.rparen", + regex : /[\])}]|>>|\|\)/ }, { token : "keyword.operator", // punctuation - regex : /\.|:|,|;\b/ + regex : /:=|->|\.(?=\w)|[;,:?]/ + }, { + token : "keyword.operator", + regex : /\\\\|\|\.\.\||\.\.|\/[~\/]?|[><\/]=?|[-+*^=~]/ }, { token : function (v) { var result = keywordMapper (v); @@ -106,33 +105,27 @@ var EiffelHighlightRules = function() { return result; }, regex : /[a-zA-Z][a-zA-Z\d_]*\b/ - }, { - token : "paren.lparen", - regex : /[\[({]/ - }, { - token : "paren.rparen", - regex : /[\])}]/ }, { token : "text", regex : /\s+/ } ], "aligned_verbatim_string" : [{ - token : "string", // closing multi-line comment + token : "string", regex : /]"/, next : "start" }, { - token : "string", // comment spanning whole line - regex : /[^(?:\]")]+/ + token : "string", + regex : simpleString } ], "non-aligned_verbatim_string" : [{ - token : "string.quoted.other", // closing multi-line comment + token : "string.quoted.other", regex : /}"/, next : "start" }, { - token : "string.quoted.other", // comment spanning whole line - regex : /[^(?:\}")]+/ + token : "string.quoted.other", + regex : simpleString } ]}; }; 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()); diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index bbd85484..a48d1097 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -70,10 +70,10 @@ function DefaultHandlers(mouseHandler) { if (button !== 0) { var selectionRange = editor.getSelectionRange(); var selectionEmpty = selectionRange.isEmpty(); - + editor.$blockScrolling++; if (selectionEmpty) editor.selection.moveToPosition(pos); - + editor.$blockScrolling--; // 2: contextmenu, 1: linux paste editor.textInput.onContextMenu(ev.domEvent); return; // stopping event here breaks contextmenu on ff mac @@ -100,7 +100,7 @@ function DefaultHandlers(mouseHandler) { pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y); var editor = this.editor; // allow double/triple click handlers to change selection - + editor.$blockScrolling++; if (this.mousedownEvent.getShiftKey()) editor.selection.selectToPosition(pos); else if (!waitForClickSelection) @@ -112,12 +112,13 @@ function DefaultHandlers(mouseHandler) { } editor.setStyle("ace_selecting"); this.setState("select"); + editor.$blockScrolling--; }; this.select = function() { var anchor, editor = this.editor; var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y); - + editor.$blockScrolling++; if (this.$clickSelection) { var cmp = this.$clickSelection.comparePoint(cursor); @@ -133,7 +134,7 @@ function DefaultHandlers(mouseHandler) { editor.selection.setSelectionAnchor(anchor.row, anchor.column); } editor.selection.selectToPosition(cursor); - + editor.$blockScrolling--; editor.renderer.scrollCursorIntoView(); }; @@ -141,7 +142,7 @@ function DefaultHandlers(mouseHandler) { var anchor, editor = this.editor; var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y); var range = editor.selection[unitName](cursor.row, cursor.column); - + editor.$blockScrolling++; if (this.$clickSelection) { var cmpStart = this.$clickSelection.comparePoint(range.start); var cmpEnd = this.$clickSelection.comparePoint(range.end); @@ -165,7 +166,7 @@ function DefaultHandlers(mouseHandler) { editor.selection.setSelectionAnchor(anchor.row, anchor.column); } editor.selection.selectToPosition(cursor); - + editor.$blockScrolling--; editor.renderer.scrollCursorIntoView(); }; diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 729d5253..0635afc2 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -558,33 +558,6 @@ var Editor = require("./editor").Editor; } }; - // todo this should change when paste becomes a command - this.onPaste = function(text) { - if (this.$readOnly) - return; - - - var e = {text: text}; - this._signal("paste", e); - text = e.text; - if (!this.inMultiSelectMode || this.inVirtualSelectionMode) - return this.insert(text); - - var lines = text.split(/\r\n|\r|\n/); - var ranges = this.selection.rangeList.ranges; - - if (lines.length > ranges.length || lines.length < 2 || !lines[1]) - return this.commands.exec("insertstring", this, text); - - for (var i = ranges.length; i--;) { - var range = ranges[i]; - if (!range.isEmpty()) - this.session.remove(range); - - this.session.insert(range.start, lines[i]); - } - }; - /** * Finds and selects all the occurences of `needle`. * @param {String} The text to find 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"]); diff --git a/lib/ace/tokenizer.js b/lib/ace/tokenizer.js index a0756157..72490fbb 100644 --- a/lib/ace/tokenizer.js +++ b/lib/ace/tokenizer.js @@ -31,6 +31,7 @@ define(function(require, exports, module) { "use strict"; +var config = require("./config"); // tokenizing lines longer than this makes editor very slow var MAX_TOKEN_COUNT = 2000; /** @@ -353,13 +354,8 @@ var Tokenizer = function(rules) { }; }; - this.reportError = function(msg, data) { - var e = new Error(msg); - e.data = data; - if (typeof console == "object" && console.error) - console.error(e); - setTimeout(function() { throw e; }); - }; + this.reportError = config.reportError; + }).call(Tokenizer.prototype); exports.Tokenizer = Tokenizer; diff --git a/tool/tmlanguage.js b/tool/tmlanguage.js index 9261a2f1..0e458330 100644 --- a/tool/tmlanguage.js +++ b/tool/tmlanguage.js @@ -684,13 +684,14 @@ function convertTmLanguage(name, langStr) { if (!module.parent) { var args = process.argv.splice(2); - var tmLanguageFile = args[0]; - var devMode = args[1]; - if (tmLanguageFile === undefined) { - console.error("Usage: node tmlanguage.js path/or/url/to/syntax.file"); + var devMode = args[0] == "--dev"; + if (devMode) + args.shift(); + if (args.length < 1) { + console.error("Usage: node tmlanguage.js [--dev] path/or/url/to/syntax.file ..."); process.exit(1); } - fetchAndConvert(tmLanguageFile); + args.forEach(fetchAndConvert); } else { exports.fetchAndConvert = fetchAndConvert; }