From 4c4ab098709e24ae7d2a3cbb444561783ea6203e Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 29 Dec 2013 19:56:29 +0400 Subject: [PATCH] add goToNextError command --- lib/ace/commands/default_commands.js | 38 +++++ lib/ace/ext/error_marker.js | 212 +++++++++++++++++++++++++++ lib/ace/line_widgets.js | 51 ++++--- 3 files changed, 278 insertions(+), 23 deletions(-) create mode 100644 lib/ace/ext/error_marker.js diff --git a/lib/ace/commands/default_commands.js b/lib/ace/commands/default_commands.js index e4a8212e..b3a0c3a0 100644 --- a/lib/ace/commands/default_commands.js +++ b/lib/ace/commands/default_commands.js @@ -52,6 +52,26 @@ exports.commands = [{ }); }, readOnly: true +}, { + name: "goToNextError", + bindKey: bindKey("Alt-E", "Ctrl-E"), + exec: function(editor) { + config.loadModule("ace/ext/error_marker", function(module) { + module.showErrorMarker(editor, 1); + }); + }, + scrollIntoView: "center", + readOnly: true +}, { + name: "goToPreviousError", + bindKey: bindKey("Alt-Shift-E", "Ctrl-Shift-E"), + exec: function(editor) { + config.loadModule("ace/ext/error_marker", function(module) { + module.showErrorMarker(editor, -1); + }); + }, + scrollIntoView: "center", + readOnly: true }, { name: "selectall", bindKey: bindKey("Ctrl-A", "Command-A"), @@ -165,6 +185,7 @@ exports.commands = [{ exec: function(editor) { editor.getSelection().selectFileStart(); }, multiSelectAction: "forEach", readOnly: true, + scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "gotostart", @@ -172,6 +193,7 @@ exports.commands = [{ exec: function(editor) { editor.navigateFileStart(); }, multiSelectAction: "forEach", readOnly: true, + scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "selectup", @@ -191,6 +213,7 @@ exports.commands = [{ exec: function(editor) { editor.getSelection().selectFileEnd(); }, multiSelectAction: "forEach", readOnly: true, + scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "gotoend", @@ -198,90 +221,105 @@ exports.commands = [{ exec: function(editor) { editor.navigateFileEnd(); }, multiSelectAction: "forEach", readOnly: true, + scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "selectdown", bindKey: bindKey("Shift-Down", "Shift-Down"), exec: function(editor) { editor.getSelection().selectDown(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "golinedown", bindKey: bindKey("Down", "Down|Ctrl-N"), exec: function(editor, args) { editor.navigateDown(args.times); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "selectwordleft", bindKey: bindKey("Ctrl-Shift-Left", "Option-Shift-Left"), exec: function(editor) { editor.getSelection().selectWordLeft(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "gotowordleft", bindKey: bindKey("Ctrl-Left", "Option-Left"), exec: function(editor) { editor.navigateWordLeft(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "selecttolinestart", bindKey: bindKey("Alt-Shift-Left", "Command-Shift-Left"), exec: function(editor) { editor.getSelection().selectLineStart(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "gotolinestart", bindKey: bindKey("Alt-Left|Home", "Command-Left|Home|Ctrl-A"), exec: function(editor) { editor.navigateLineStart(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "selectleft", bindKey: bindKey("Shift-Left", "Shift-Left"), exec: function(editor) { editor.getSelection().selectLeft(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "gotoleft", bindKey: bindKey("Left", "Left|Ctrl-B"), exec: function(editor, args) { editor.navigateLeft(args.times); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "selectwordright", bindKey: bindKey("Ctrl-Shift-Right", "Option-Shift-Right"), exec: function(editor) { editor.getSelection().selectWordRight(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "gotowordright", bindKey: bindKey("Ctrl-Right", "Option-Right"), exec: function(editor) { editor.navigateWordRight(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "selecttolineend", bindKey: bindKey("Alt-Shift-Right", "Command-Shift-Right"), exec: function(editor) { editor.getSelection().selectLineEnd(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "gotolineend", bindKey: bindKey("Alt-Right|End", "Command-Right|End|Ctrl-E"), exec: function(editor) { editor.navigateLineEnd(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "selectright", bindKey: bindKey("Shift-Right", "Shift-Right"), exec: function(editor) { editor.getSelection().selectRight(); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "gotoright", bindKey: bindKey("Right", "Right|Ctrl-F"), exec: function(editor, args) { editor.navigateRight(args.times); }, multiSelectAction: "forEach", + scrollIntoView: "cursor", readOnly: true }, { name: "selectpagedown", diff --git a/lib/ace/ext/error_marker.js b/lib/ace/ext/error_marker.js new file mode 100644 index 00000000..a55e9a25 --- /dev/null +++ b/lib/ace/ext/error_marker.js @@ -0,0 +1,212 @@ +/* ***** 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 LineWidgets = require("ace/line_widgets").LineWidgets; +var dom = require("ace/lib/dom"); +var Range = require("ace/range").Range; + +function binarySearch(array, needle, comparator) { + var first = 0; + var last = array.length - 1; + + while (first <= last) { + var mid = (first + last) >> 1; + var c = comparator(needle, array[mid]); + if (c > 0) + first = mid + 1; + else if (c < 0) + last = mid - 1; + else + return mid; + } + + // Return the nearest lesser index, "-1" means "0, "-2" means "1", etc. + return -(first + 1); +} + +function findAnnotations(session, row, dir) { + var annotations = session.getAnnotations().sort(Range.comparePoints); + if (!annotations.length) + return; + + var i = binarySearch(annotations, {row: row, column: -1}, Range.comparePoints); + if (i < 0) + i = -i - 1; + + if (i >= annotations.length - 1) + i = dir > 0 ? 0 : annotations.length - 1; + else if (i === 0 && dir < 0) + i = annotations.length - 1; + + var annotation = annotations[i]; + if (!annotation || !dir) + return; + + if (annotation.row === row) { + do { + annotation = annotations[i += dir]; + } while (annotation && annotation.row === row); + if (!annotation) + return annotations.slice(); + } + + + var matched = []; + row = annotation.row; + do { + matched[dir < 0 ? "unshift" : "push"](annotation); + annotation = annotations[i += dir]; + } while (annotation && annotation.row == row); + return matched.length && matched; +} + +exports.showErrorMarker = function(editor, dir) { + var session = editor.session; + if (!session.widgetManager) { + session.widgetManager = new LineWidgets(session); + session.widgetManager.attach(editor); + } + + var pos = editor.getCursorPosition(); + var row = pos.row; + var oldWidget = session.lineWidgets && session.lineWidgets[row]; + if (oldWidget) { + oldWidget.destroy(); + } else { + row -= dir; + } + var annotations = findAnnotations(session, row, dir); + var gutterAnno; + if (annotations) { + var annotation = annotations[0]; + if (annotation.pos && annotation.column == null) + pos.column = annotation.pos.sc; + pos.row = annotation.row; + gutterAnno = editor.renderer.$gutterLayer.$annotations[pos.row]; + } else if (oldWidget) { + return; + } else { + gutterAnno = { + text: ["Looks good!"], + className: "ace_ok" + }; + } + editor.session.unfold(pos.row); + editor.selection.moveCursorToPosition(pos); + editor.selection.clearSelection(); + + var w = { + row: pos.row, + fixedWidth: true, + coverGutter: true, + el: dom.createElement("div") + }; + var el = w.el.appendChild(dom.createElement("div")); + var arrow = w.el.appendChild(dom.createElement("div")); + arrow.className = "error_widget_arrow " + gutterAnno.className; + + var left = editor.renderer.$cursorLayer + .getPixelPosition(pos).left; + arrow.style.left = left + editor.renderer.gutterWidth - 5 + "px"; + + w.el.className = "error_widget_wrapper"; + el.className = "error_widget " + gutterAnno.className; + el.innerHTML = gutterAnno.text.join("
"); + + var kb = { + handleKeyboard:function(_,hashId, keyString) { + if (hashId === 0 && keyString === "esc") { + w.destroy(); + return true; + } + } + }; + + w.destroy = function() { + if (editor.$mouseHandler.isMousePressed) + return; + editor.keyBinding.removeKeyboardHandler(kb); + session.widgetManager.removeLineWidget(w); + editor.off("changeSelection", w.destroy); + editor.off("changeSession", w.destroy); + editor.off("mouseup", w.destroy); + editor.off("change", w.destroy); + }; + + editor.keyBinding.addKeyboardHandler(kb); + editor.on("changeSelection", w.destroy); + editor.on("changeSession", w.destroy); + editor.on("mouseup", w.destroy); + editor.on("change", w.destroy); + + editor.session.widgetManager.addLineWidget(w); + + w.el.onmousedown = editor.focus.bind(editor); +}; + + +dom.importCssString("\ + .error_widget_wrapper {\ + background: inherit;\ + color: inherit;\ + border:none\ + }\ + .error_widget {\ + border-top: solid 2px;\ + border-bottom: solid 2px;\ + margin: 5px 0;\ + padding: 10px 40px;\ + white-space: pre-wrap;\ + }\ + .error_widget.ace_error, .error_widget_arrow.ace_error{\ + border-color: #ff5a5a\ + }\ + .error_widget.ace_warning, .error_widget_arrow.ace_warning{\ + border-color: #F1D817\ + }\ + .error_widget.ace_info, .error_widget_arrow.ace_info{\ + border-color: #5a5a5a\ + }\ + .error_widget.ace_ok, .error_widget_arrow.ace_ok{\ + border-color: #5aaa5a\ + }\ + .error_widget_arrow {\ + position: absolute;\ + border: solid 5px;\ + border-top-color: transparent!important;\ + border-right-color: transparent!important;\ + border-left-color: transparent!important;\ + top: -5px;\ + }\ +", ""); + +}); \ No newline at end of file diff --git a/lib/ace/line_widgets.js b/lib/ace/line_widgets.js index 597b3ca2..94ad9ddf 100644 --- a/lib/ace/line_widgets.js +++ b/lib/ace/line_widgets.js @@ -48,12 +48,13 @@ function LineWidgets(session) { this.detach = this.detach.bind(this); this.session.on("change", this.updateOnChange); -}; +} (function() { this.getRowLength = function(row) { + var h; if (this.lineWidgets) - var h = this.lineWidgets[row] && this.lineWidgets[row].rowCount || 0; + h = this.lineWidgets[row] && this.lineWidgets[row].rowCount || 0; else h = 0; if (!this.$useWrapMode || !this.$wrapData[row]) { @@ -104,7 +105,8 @@ function LineWidgets(session) { editor.renderer.off("beforeRender", this.measureWidgets); editor.renderer.off("afterRender", this.renderWidgets); - this.session.lineWidgets.forEach(function(w) { + var lineWidgets = this.session.lineWidgets; + lineWidgets && lineWidgets.forEach(function(w) { if (w && w.el && w.el.parentNode) { w._inDocument = false; w.el.parentNode.removeChild(w.el); @@ -113,8 +115,8 @@ function LineWidgets(session) { }; this.updateOnChange = function(e) { - var cells = this.session.lineWidgets; - if (!cells) return; + var lineWidgets = this.session.lineWidgets; + if (!lineWidgets) return; var delta = e.data; var range = delta.range; @@ -124,24 +126,24 @@ function LineWidgets(session) { if (len === 0) { // return } else if (delta.action == "removeText" || delta.action == "removeLines") { - var removed = cells.splice(startRow + 1, len); + var removed = lineWidgets.splice(startRow + 1, len); removed.forEach(function(w) { w && this.removeLineWidget(w); }, this); this.$updateRows(); } else { - var args = Array(len); + var args = new Array(len); args.unshift(startRow, 0); - cells.splice.apply(cells, args); + lineWidgets.splice.apply(lineWidgets, args); this.$updateRows(); } }; this.$updateRows = function() { - var lw = this.session.lineWidgets; - if (!lw) return; + var lineWidgets = this.session.lineWidgets; + if (!lineWidgets) return; var noWidgets = true; - lw.forEach(function(w, i) { + lineWidgets.forEach(function(w, i) { if (w) { noWidgets = false; w.row = i; @@ -149,11 +151,11 @@ function LineWidgets(session) { }); if (noWidgets) this.session.lineWidgets = null; - } + }; this.addLineWidget = function(w) { if (!this.session.lineWidgets) - this.session.lineWidgets = Array(this.session.getLength()) + this.session.lineWidgets = new Array(this.session.getLength()); this.session.lineWidgets[w.row] = w; @@ -164,6 +166,8 @@ function LineWidgets(session) { } if (w.el) { dom.addCssClass(w.el, "ace_lineWidgetContainer"); + w.el.style.position = "absolute"; + w.el.style.zIndex = 5; renderer.container.appendChild(w.el); w._inDocument = true; } @@ -191,7 +195,8 @@ function LineWidgets(session) { if (w.editor && w.editor.destroy) try { w.editor.destroy(); } catch(e){} - this.session.lineWidgets[w.row] = undefined; + if (this.session.lineWidgets) + this.session.lineWidgets[w.row] = undefined; this.session._emit("changeFold", {data:{start:{row: w.row}}}); this.$updateRows(); }; @@ -202,13 +207,13 @@ function LineWidgets(session) { }; this.measureWidgets = function(e, renderer) { - var ws = this.session._changedWidgets; + var changedWidgets = this.session._changedWidgets; var config = renderer.layerConfig; - if (!ws || !ws.length) return; + if (!changedWidgets || !changedWidgets.length) return; var min = Infinity; - for (var i = 0; i < ws.length; i++) { - var w = ws[i].lineWidget; + for (var i = 0; i < changedWidgets.length; i++) { + var w = changedWidgets[i]; if (!w._inDocument) { w._inDocument = true; renderer.container.appendChild(w.el); @@ -242,13 +247,13 @@ function LineWidgets(session) { this.renderWidgets = function(e, renderer) { var config = renderer.layerConfig; - var ws = this.session.lineWidgets; - if (!ws) + var lineWidgets = this.session.lineWidgets; + if (!lineWidgets) return; var first = Math.min(this.firstRow, config.firstRow); - var last = Math.max(this.lastRow, config.lastRow, ws.length); + var last = Math.max(this.lastRow, config.lastRow, lineWidgets.length); - while (first > 0 && !ws[first]) + while (first > 0 && !lineWidgets[first]) first--; this.firstRow = config.firstRow; @@ -256,7 +261,7 @@ function LineWidgets(session) { renderer.$cursorLayer.config = config; for (var i = first; i <= last; i++) { - var w = ws[i]; + var w = lineWidgets[i]; if (!w || !w.el) continue; if (!w._inDocument) {