diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 6526d0fd..06bc87b9 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -152,3 +152,7 @@ -moz-box-sizing: border-box; -webkit-box-sizing: border-box; } + +.ace_dragging .ace_marker-layer, .ace_dragging .ace_text-layer { + cursor: move; +} diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index cc8af85c..9d007dc7 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1,4 +1,5 @@ -/* ***** BEGIN LICENSE BLOCK ***** +/* vim:ts=4:sts=4:sw=4: + * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version @@ -20,6 +21,7 @@ * * Contributor(s): * Fabian Jakobs + * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -585,7 +587,7 @@ var EditSession = function(text, mode) { action.start = delta.range.start; } } - + // update selection based on last operation this.selection.clearSelection(); var action = actions[actions.length-1]; @@ -599,6 +601,45 @@ var EditSession = function(text, mode) { return this.doc.replace(range, text); }; + /** + * Move a range of text from the given range to the given position. + * + * @param fromRange {Range} The range of text you want moved within the + * document. + * @param toPosition {Object} The location (row and column) where you want + * to move the text to. + * @return {Range} The new range where the text was moved to. + */ + this.moveText = function(fromRange, toPosition) { + var text = this.getTextRange(fromRange); + this.remove(fromRange); + + var toRow = toPosition.row; + var toColumn = toPosition.column; + + // Make sure to update the insert location, when text is removed in + // front of the chosen point of insertion. + if (!fromRange.isMultiLine() && fromRange.start.row == toRow && + fromRange.end.column < toColumn) + toColumn -= text.length; + + if (fromRange.isMultiLine() && fromRange.end.row < toRow) { + var lines = this.doc.$split(text); + toRow -= lines.length - 1; + } + + var endRow = toRow + fromRange.end.row - fromRange.start.row; + var endColumn = fromRange.isMultiLine() ? + fromRange.end.column : + toColumn + fromRange.end.column - fromRange.start.column; + + var toRange = new Range(toRow, toColumn, endRow, endColumn); + + this.insert(toRange.start, text); + + return toRange; + }; + this.indentRows = function(startRow, endRow, indentString) { indentString = indentString.replace(/\t/g, this.getTabString()); for (var row=startRow; row<=endRow; row++) { diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 676fe8ff..974ae509 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -762,6 +762,13 @@ var Editor =function(renderer, session) { }); }; + this.moveText = function(range, toPosition) { + if (this.$readOnly) + return null; + + return this.session.moveText(range, toPosition); + }; + this.copyLinesUp = function() { if (this.$readOnly) return; diff --git a/lib/ace/mouse_handler.js b/lib/ace/mouse_handler.js index b5227c0a..6e7fa316 100644 --- a/lib/ace/mouse_handler.js +++ b/lib/ace/mouse_handler.js @@ -1,4 +1,5 @@ -/* ***** BEGIN LICENSE BLOCK ***** +/* vim:ts=4:sts=4:sw=4: + * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version @@ -20,6 +21,7 @@ * * Contributor(s): * Fabian Jakobs + * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -38,6 +40,14 @@ define(function(require, exports, module) { var event = require("pilot/event"); +var dom = require("pilot/dom"); + +var STATE_UNKNOWN = 0; +var STATE_SELECT = 1; +var STATE_DRAG = 2; + +var DRAG_TIMER = 250; // milliseconds +var DRAG_OFFSET = 5; // pixels var MouseHandler = function(editor) { this.editor = editor; @@ -67,40 +77,57 @@ var MouseHandler = function(editor) { this.getScrollSpeed = function() { return this.$scrollSpeed; }; - + + this.$getEventPosition = function(e) { + var pageX = event.getDocumentX(e); + var pageY = event.getDocumentY(e); + var pos = this.editor.renderer.screenToTextCoordinates(pageX, pageY); + pos.row = Math.max(0, Math.min(pos.row, this.editor.session.getLength()-1)); + return pos; + }; + + this.$distance = function(ax, ay, bx, by) { + return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2)); + }; + this.onMouseDown = function(e) { var pageX = event.getDocumentX(e); var pageY = event.getDocumentY(e); + var pos = this.$getEventPosition(e); var editor = this.editor; - - var pos = editor.renderer.screenToTextCoordinates(pageX, pageY); - pos.row = Math.max(0, Math.min(pos.row, editor.session.getLength()-1)); + var self = this; + var selectionRange = editor.getSelectionRange(); + var selectionEmpty = selectionRange.isEmpty(); + var state = STATE_UNKNOWN; + var inSelection = false; var button = event.getButton(e) if (button != 0) { - var isEmpty = editor.selection.isEmpty() - if (isEmpty) { + if (selectionEmpty) { editor.moveCursorToPosition(pos); } if(button == 2) { - editor.textInput.onContextMenu({x: pageX, y: pageY}, isEmpty); + editor.textInput.onContextMenu({x: pageX, y: pageY}, selectionEmpty); event.capture(editor.container, function(){}, editor.textInput.onContextMenuClose); } return; + } else + inSelection = !editor.getReadOnly() && + !selectionEmpty && + selectionRange.contains(pos.row, pos.column); + + if (!inSelection) { + // Directly pick STATE_SELECT, since the user is not clicking inside + // a selection. + onStartSelect(pos); } - - if (e.shiftKey) - editor.selection.selectToPosition(pos) - else { - editor.moveCursorToPosition(pos); - if (!editor.$clickSelection) - editor.selection.clearSelection(pos.row, pos.column); - } - + editor.renderer.scrollCursorIntoView(); - var self = this; var mousePageX, mousePageY; + var overwrite = editor.getOverwrite(); + var dragCursor = null; + var mousedownTime = (new Date()).getTime(); var onMouseSelection = function(e) { mousePageX = event.getDocumentX(e); @@ -109,17 +136,88 @@ var MouseHandler = function(editor) { var onMouseSelectionEnd = function() { clearInterval(timerId); + if (state == STATE_UNKNOWN) + onStartSelect(pos); + else if (state == STATE_DRAG) + onMouseDragSelectionEnd(); + self.$clickSelection = null; + state = STATE_UNKNOWN; + }; + + var onMouseDragSelectionEnd = function() { + dom.removeCssClass(editor.container, "ace_dragging"); + + if (!self.$clickSelection) { + if (!dragCursor) { + editor.moveCursorToPosition(pos); + editor.selection.clearSelection(pos.row, pos.column); + } + } + + if (!dragCursor) + return; + + var selection = editor.getSelectionRange(); + if (selection.contains(dragCursor.row, dragCursor.column)) { + dragCursor = null; + return; + } + + editor.clearSelection(); + var newRange = editor.moveText(selection, dragCursor); + if (!newRange) { + dragCursor = null; + return; + } + + editor.selection.setSelectionRange(newRange); }; var onSelectionInterval = function() { if (mousePageX === undefined || mousePageY === undefined) return; + + if (state == STATE_UNKNOWN) { + var distance = self.$distance(pageX, pageY, mousePageX, mousePageY); + var time = (new Date()).getTime(); + + + if (distance > DRAG_OFFSET) { + state = STATE_SELECT; + var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); + cursor.row = Math.max(0, Math.min(cursor.row, editor.session.getLength()-1)); + onStartSelect(cursor); + } else if ((time - mousedownTime) > DRAG_TIMER) { + state = STATE_DRAG; + dom.addCssClass(editor.container, "ace_dragging"); + } + + } + + if (state == STATE_DRAG) + onDragSelectionInterval(); + else if (state == STATE_SELECT) + onUpdateSelectionInterval(); + }; + function onStartSelect(pos) { + if (e.shiftKey) + editor.selection.selectToPosition(pos) + else { + if (!self.$clickSelection) { + editor.moveCursorToPosition(pos); + editor.selection.clearSelection(pos.row, pos.column); + } + } + state = STATE_SELECT; + } + + var onUpdateSelectionInterval = function() { var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); cursor.row = Math.max(0, Math.min(cursor.row, editor.session.getLength()-1)); - if (self.$clickSelection) { + if (self.$clickSelection) { if (self.$clickSelection.contains(cursor.row, cursor.column)) { editor.selection.setSelectionRange(self.$clickSelection); } else { @@ -138,7 +236,16 @@ var MouseHandler = function(editor) { editor.renderer.scrollCursorIntoView(); }; - + + var onDragSelectionInterval = function() { + dragCursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); + dragCursor.row = Math.max(0, Math.min(dragCursor.row, + editor.session.getLength() - 1)); + + editor.renderer.updateCursor(dragCursor, overwrite); + editor.renderer.scrollCursorIntoView(); + }; + event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); var timerId = setInterval(onSelectionInterval, 20); @@ -146,11 +253,15 @@ var MouseHandler = function(editor) { }; this.onMouseDoubleClick = function(e) { + var pos = this.$getEventPosition(e); + this.editor.moveCursorToPosition(pos); this.editor.selection.selectWord(); this.$clickSelection = this.editor.getSelectionRange(); }; this.onMouseTripleClick = function(e) { + var pos = this.$getEventPosition(e); + this.editor.moveCursorToPosition(pos); this.editor.selection.selectLine(); this.$clickSelection = this.editor.getSelectionRange(); };