diff --git a/lib/ace/commands/occur_commands.js b/lib/ace/commands/occur_commands.js new file mode 100644 index 00000000..88e05eda --- /dev/null +++ b/lib/ace/commands/occur_commands.js @@ -0,0 +1,108 @@ +/* ***** 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) { + +var config = require("../config"), + Occur = require("../occur").Occur; + +// These commands can be installed in a normal command handler to start occur: +var occurStartCommands = [{ + name: "occur", + exec: function(editor, options) { + var alreadyInOccur = !!editor.session.$occur; + var occurSessionActive = new Occur().enter(editor, options); + if (occurSessionActive && !alreadyInOccur) OccurKeyboardHandler.installIn(editor); + }, + readOnly: true +}]; + +var occurCommands = [{ + name: "occurexit", + bindKey: 'esc|Ctrl-G', + exec: function(editor) { + var occur = editor.session.$occur; + if (!occur) return; + occur.exit(editor, {}); + if (!editor.session.$occur) OccurKeyboardHandler.uninstallFrom(editor); + }, + readOnly: true +}, { + name: "occuraccept", + bindKey: 'enter', + exec: function(editor) { + var occur = editor.session.$occur; + if (!occur) return; + occur.exit(editor, {translatePosition: true}); + if (!editor.session.$occur) OccurKeyboardHandler.uninstallFrom(editor); + }, + readOnly: true +}]; + +var HashHandler = require("../keyboard/hash_handler").HashHandler; +var oop = require("../lib/oop"); + + +function OccurKeyboardHandler() {} + +oop.inherits(OccurKeyboardHandler, HashHandler); + +;(function() { + + this.isOccurHandler = true; + + this.attach = function(editor) { + HashHandler.call(this, occurCommands, editor.commands.platform); + this.$editor = editor; + } + + var handleKeyboard$super = this.handleKeyboard; + this.handleKeyboard = function(data, hashId, key, keyCode) { + var cmd = handleKeyboard$super.call(this, data, hashId, key, keyCode); + return (cmd && cmd.command) ? cmd : undefined; + } + +}).call(OccurKeyboardHandler.prototype); + +OccurKeyboardHandler.installIn = function(editor) { + var handler = new this(); + editor.keyBinding.addKeyboardHandler(handler); + editor.commands.addCommands(occurCommands); +} + +OccurKeyboardHandler.uninstallFrom = function(editor) { + editor.commands.removeCommands(occurCommands); + var handler = editor.getKeyboardHandler(); + if (handler.isOccurHandler) editor.keyBinding.removeKeyboardHandler(handler); +} + +exports.commands = occurStartCommands; + +}); diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 81f8fbe6..0716003f 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -209,6 +209,14 @@ box-sizing: border-box; } +.ace_marker-layer .ace_occur-highlight { + position: absolute; + z-index: 4; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + .ace_line .ace_fold { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 92a10add..ce36ae47 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -1457,7 +1457,7 @@ var Editor = function(renderer, session) { } else { var ranges = selection.rangeList.ranges; selection.rangeList.detach(this.session); - + for (var i = ranges.length; i--; ) { var rangeIndex = i; var rows = ranges[i].collapseRows(); @@ -1471,7 +1471,7 @@ var Editor = function(renderer, session) { break; } i++; - + var linesMoved = mover.call(this, first, last); while (rangeIndex >= i) { ranges[rangeIndex].moveBy(linesMoved, 0); @@ -2210,7 +2210,8 @@ config.defineOptions(Editor.prototype, "editor", { readOnly: { set: function(readOnly) { this.textInput.setReadOnly(readOnly); - this.renderer.$cursorLayer.setBlinking(!readOnly); + var cursorLayer = this.renderer.$cursorLayer; + cursorLayer && cursorLayer.setBlinking(!readOnly); }, initialValue: false }, diff --git a/lib/ace/lib/oop.js b/lib/ace/lib/oop.js index f9ba6fe8..f3dbb1d9 100644 --- a/lib/ace/lib/oop.js +++ b/lib/ace/lib/oop.js @@ -45,6 +45,7 @@ exports.mixin = function(obj, mixin) { for (var key in mixin) { obj[key] = mixin[key]; } + return obj; }; exports.implement = function(proto, mixin) { diff --git a/lib/ace/occur.js b/lib/ace/occur.js new file mode 100644 index 00000000..902e025a --- /dev/null +++ b/lib/ace/occur.js @@ -0,0 +1,188 @@ +/* ***** 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) { +"use strict"; + +var oop = require("./lib/oop"); +var Range = require("./range").Range; +var Search = require("./search").Search; +var EditSession = require("./edit_session").EditSession; +var SearchHighlight = require("./search_highlight").SearchHighlight; + +/** + * @class Occur + * + * Finds all lines matching a search term in the current [[Document + * `Document`]] and displays them instead of the original `Document`. Keeps + * track of the mapping between the occur doc and the original doc. + * + **/ + + +/** + * + * + * Creates a new `Occur` object. + * + * @constructor + **/ +function Occur() {} + +oop.inherits(Occur, Search); + +(function() { + + /** + * Enables occur mode. expects that `options.needle` is a search term. + * This search term is used to filter out all the lines that include it + * and these are then used as the content of a new [[Document + * `Document`]]. The current cursor position of editor will be translated + * so that the cursor is on the matching row/column as it was before. + * @param {Editor} editor + * @param {Object} options options.needle should be a String + * @return {Boolean} Whether occur activation was successful + * + **/ + this.enter = function(editor, options) { + if (!options.needle) return false; + var pos = editor.getCursorPosition(); + this.displayOccurContent(editor, options); + var translatedPos = this.originalToOccurPosition(editor.session, pos); + editor.moveCursorToPosition(translatedPos); + return true; + } + + /** + * Disables occur mode. Resets the [[Sessions `EditSession`]] [[Document + * `Document`]] back to the original doc. If options.translatePosition is + * truthy also maps the [[Editors `Editor`]] cursor position accordingly. + * @param {Editor} editor + * @param {Object} options options.translatePosition + * @return {Boolean} Whether occur deactivation was successful + * + **/ + this.exit = function(editor, options) { + var pos = options.translatePosition && editor.getCursorPosition(), + translatedPos = pos && this.occurToOriginalPosition(editor.session, pos); + this.displayOriginalContent(editor); + if (translatedPos) editor.moveCursorToPosition(translatedPos); + return true; + } + + this.highlight = function(sess, regexp) { + var hl = sess.$occurHighlight = sess.$occurHighlight || sess.addDynamicMarker( + new SearchHighlight(null, "ace_occur-highlight", "text")); + hl.setRegexp(regexp); + sess._emit("changeBackMarker"); // force highlight layer redraw + } + + this.displayOccurContent = function(editor, options) { + // this.setSession(session || new EditSession("")) + this.$originalSession = editor.session; + var found = this.matchingLines(editor.session, options), + lines = found.map(function(foundLine) { return foundLine.content; }), + occurSession = new EditSession(lines.join('\n')); + occurSession.$occur = this; + occurSession.$occurMatchingLines = found; + editor.setSession(occurSession); + this.highlight(occurSession, options.re); + occurSession._emit('changeBackMarker'); + } + + this.displayOriginalContent = function(editor) { + editor.setSession(this.$originalSession); + } + + /** + * Translates the position from the original document to the occur lines in + * the document or the beginning if the doc {row: 0, column: 0} if not + * found. + * @param {EditSession} session The occur session + * @param {Object} pos The position in the original document + * @return {Object} position in occur doc + **/ + this.originalToOccurPosition = function(session, pos) { + var lines = session.$occurMatchingLines, + nullPos = {row: 0, column: 0}; + if (!lines) return nullPos; + for (var i = 0; i < lines.length; i++) { + if (lines[i].row === pos.row) return {row: i, column: pos.column} + } + return nullPos; + } + + /** + * Translates the position from the occur document to the original document + * or `pos` if not found. + * @param {EditSession} session The occur session + * @param {Object} pos The position in the occur session document + * @return {Object} position + **/ + this.occurToOriginalPosition = function(session, pos) { + var lines = session.$occurMatchingLines; + if (!lines || !lines[pos.row]) return pos; + return {row: lines[pos.row].row, column: pos.column}; + } + + this.matchingLines = function(session, options) { + options = oop.mixin({}, options); + if (!session || !options.needle) return []; + var search = new Search(); + search.set(options); + return search.findAll(session).reduce(function(lines, range) { + var row = range.start.row, + last = lines[lines.length-1]; + return last && last.row === row ? + lines : + lines.concat({row: row, content: session.getLine(row)}); + }, []); + } + +}).call(Occur.prototype); + +var dom = require('./lib/dom'); +(function patchHighlightMarkerStyling() { + var id = 'incremental-occur-highlighting', + css = 'div.ace_occur-highlight {\n' + + " border-radius: 4px;\n" + + "border: 8px solid rgba(87, 255, 8, 0.25);\n" + + "box-shadow: 0 0 4px rgb(91, 255, 50);\n" + + "}\n" + + '.ace_dark div.ace_occur-highlight {\n' + + "border: 8px solid rgb(80, 140, 85);\n" + + "box-shadow: 0 0 4px rgb(60, 120, 70);\n" + + '}\n'; + dom.importCssString(css, id); +})(); + +exports.Occur = Occur; + +}); diff --git a/lib/ace/occur_test.js b/lib/ace/occur_test.js new file mode 100644 index 00000000..88b67f5b --- /dev/null +++ b/lib/ace/occur_test.js @@ -0,0 +1,154 @@ +/* ***** 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"); +} + +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 Range = require("./range").Range; +var assert = require("./test/assertions"); +var Occur = require("./occur").Occur; +var occurCommands = require("./commands/occur_commands").commands; +var editor, occur; + +module.exports = { + + name: "ACE occur.js", + + setUp: function() { + var session = new EditSession(''); + editor = new Editor(new MockRenderer(), session); + occur = new Occur(); + }, + + "test: find lines matching" : function() { + editor.session.insert({row: 0, column: 0}, 'abc\ndef\nxyz\nbcxbc'); + var result = occur.matchingLines(editor.session, {needle: 'bc'}), + expected = [{row: 0, content: 'abc'}, {row: 3, content: 'bcxbc'}]; + assert.deepEqual(result, expected); + }, + + "test: display occurrences" : function() { + var text = 'abc\ndef\nxyz\nbcx\n'; + editor.session.insert({row: 0, column: 0}, text); + occur.displayOccurContent(editor, {needle: 'bc'}); + assert.equal(editor.getValue(), 'abc\nbcx'); + occur.displayOriginalContent(editor); + assert.equal(editor.getValue(), text); + }, + + "test: original position from occur doc" : function() { + var text = 'abc\ndef\nxyz\nbcx\n'; + editor.session.insert({row: 0, column: 0}, text); + occur.displayOccurContent(editor, {needle: 'bc'}); + assert.equal(editor.getValue(), 'abc\nbcx'); + var pos = occur.occurToOriginalPosition(editor.session, {row: 1, column: 2}); + assert.position(pos, 3, 2); + }, + + "test: occur command" : function() { + // setup + var text = 'hel\nlo\n\nwo\nrld\n'; + editor.session.insert({row: 0, column: 0}, text); + editor.commands.addCommands(occurCommands); + + // run occur for lines including 'o' + editor.execCommand('occur', {needle: 'o'}); + assert.equal(editor.getValue(), 'lo\nwo'); + // command install OK? + // assert.ok(editor.getReadOnly(), 'occur doc not marked as read only'); + assert.ok(editor.getKeyboardHandler().isOccurHandler, 'no occur handler installed'); + assert.ok(editor.commands.byName.occurexit, 'no exitoccur command installed'); + + // exit occur + editor.execCommand('occurexit'); + assert.equal(editor.getValue(), text); + + // editor state cleaned up? + // assert.ok(!editor.getReadOnly(), 'original doc is marked as read only'); + assert.ok(!editor.getKeyboardHandler().isOccurHandler, 'occur handler installed after detach'); + assert.ok(!editor.commands.byName.occurexit, 'exitoccur installed after exiting occur'); + }, + + "test: occur navigation" : function() { + // setup + var text = 'hel\nlo\n\nwo\nrld\n'; + editor.session.insert({row: 0, column: 0}, text); + editor.commands.addCommands(occurCommands); + editor.moveCursorToPosition({row: 1, column: 1}); + + // run occur for lines including 'o' + editor.execCommand('occur', {needle: 'o'}); + assert.equal(editor.getValue(), 'lo\nwo'); + assert.position(editor.getCursorPosition(), 0, 1, 'original -> occur pos'); + + // move to second line and accept + editor.moveCursorToPosition({row: 1, column: 1}); + editor.execCommand('occuraccept'); + + assert.position(editor.getCursorPosition(), 3, 1, 'occur -> original pos'); + }, + + "test: recursive occur" : function() { + // setup + var text = 'x\nabc1\nx\nabc2\n'; + editor.session.insert({row: 0, column: 0}, text); + editor.commands.addCommands(occurCommands); + + // orig -> occur1 + editor.execCommand('occur', {needle: 'abc'}); + assert.equal(editor.getValue(), 'abc1\nabc2', "orig -> occur1"); + + // occur1 -> occur2 + editor.execCommand('occur', {needle: '2'}); + assert.equal(editor.getValue(), 'abc2', "occur1 -> occur2"); + + // occur2 -> occur1 + editor.execCommand('occurexit'); + assert.equal(editor.getValue(), 'abc1\nabc2', "occur2 -> occur1"); + + // occur1 -> orig + editor.execCommand('occurexit'); + assert.equal(editor.getValue(), text, "occur1 -> orig"); + } + +}; + +}); + +if (typeof module !== "undefined" && module === require.main) { + require("asyncjs").test.testcase(module.exports).exec() +} diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index 4ce68ae3..323a4cf6 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -43,6 +43,7 @@ var testNames = [ "ace/mode/folding/xml_test", "ace/mode/folding/coffee_test", "ace/multi_select_test", + "ace/occur_test", "ace/range_test", "ace/range_list_test", "ace/search_test", diff --git a/lib/ace/test/asyncjs/assert.js b/lib/ace/test/asyncjs/assert.js index 6cdc611a..96e3f6e2 100644 --- a/lib/ace/test/asyncjs/assert.js +++ b/lib/ace/test/asyncjs/assert.js @@ -1,5 +1,5 @@ define(function(require, exports, module) { - + // http://wiki.commonjs.org/wiki/Unit_Testing/1.0 // // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! @@ -141,7 +141,7 @@ function _deepEqual(actual, expected) { if (actual === expected) { return true; - } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { + } else if (typeof Buffer !== "undefined" && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { if (actual.length != expected.length) return false; for (var i = 0; i < actual.length; i++) {