From c5bc3d96e0a2a03882a32b5b5b979da3960c4081 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 17 Apr 2012 19:06:30 +0400 Subject: [PATCH 1/8] rename selection.selectionLead to selection.lead --- lib/ace/selection.js | 93 +++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/lib/ace/selection.js b/lib/ace/selection.js index be534116..79527565 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -64,11 +64,11 @@ var Selection = function(session) { this.doc = session.getDocument(); this.clearSelection(); - this.selectionLead = this.doc.createAnchor(0, 0); - this.selectionAnchor = this.doc.createAnchor(0, 0); + this.lead = this.selectionLead = this.doc.createAnchor(0, 0); + this.anchor = this.selectionAnchor = this.doc.createAnchor(0, 0); var self = this; - this.selectionLead.on("change", function(e) { + this.lead.on("change", function(e) { self._emit("changeCursor"); if (!self.$isEmpty) self._emit("changeSelection"); @@ -93,8 +93,8 @@ var Selection = function(session) { **/ this.isEmpty = function() { return (this.$isEmpty || ( - this.selectionAnchor.row == this.selectionLead.row && - this.selectionAnchor.column == this.selectionLead.column + this.anchor.row == this.lead.row && + this.anchor.column == this.lead.column )); }; @@ -117,7 +117,7 @@ var Selection = function(session) { * Gets the current position of the cursor. **/ this.getCursor = function() { - return this.selectionLead.getPosition(); + return this.lead.getPosition(); }; /** @@ -128,7 +128,7 @@ var Selection = function(session) { * Sets the row and column position of the anchor. This function also emits the `'changeSelection'` event. **/ this.setSelectionAnchor = function(row, column) { - this.selectionAnchor.setPosition(row, column); + this.anchor.setPosition(row, column); if (this.$isEmpty) { this.$isEmpty = false; @@ -146,7 +146,7 @@ var Selection = function(session) { if (this.$isEmpty) return this.getSelectionLead() else - return this.selectionAnchor.getPosition(); + return this.anchor.getPosition(); }; /** @@ -155,7 +155,7 @@ var Selection = function(session) { * Returns an object containing the `row` and `column` of the calling selection lead. **/ this.getSelectionLead = function() { - return this.selectionLead.getPosition(); + return this.lead.getPosition(); }; /** @@ -167,7 +167,7 @@ var Selection = function(session) { **/ this.shiftSelection = function(columns) { if (this.$isEmpty) { - this.moveCursorTo(this.selectionLead.row, this.selectionLead.column + columns); + this.moveCursorTo(this.lead.row, this.lead.column + columns); return; }; @@ -192,8 +192,8 @@ var Selection = function(session) { * Returns `true` if the selection is going backwards in the document. **/ this.isBackwards = function() { - var anchor = this.selectionAnchor; - var lead = this.selectionLead; + var anchor = this.anchor; + var lead = this.lead; return (anchor.row > lead.row || (anchor.row == lead.row && anchor.column > lead.column)); }; @@ -203,8 +203,8 @@ var Selection = function(session) { * [Returns the [[Range `Range`]] for the selected text.]{: #Selection.getRange} **/ this.getRange = function() { - var anchor = this.selectionAnchor; - var lead = this.selectionLead; + var anchor = this.anchor; + var lead = this.lead; if (this.isEmpty()) return Range.fromPoints(lead, lead); @@ -249,18 +249,23 @@ var Selection = function(session) { * **/ this.setSelectionRange = function(range, reverse) { - if (reverse) { - this.setSelectionAnchor(range.end.row, range.end.column); - this.selectTo(range.start.row, range.start.column); + if (range.isEmpty()) { + this.lead.setPosition(range.start.row, range.start.column); + this.clearSelection(); + } else if (reverse) { + this.$isEmpty = false; + this.anchor.setPosition(range.end.row, range.end.column); + this.lead.setPosition(range.start.row, range.start.column); } else { - this.setSelectionAnchor(range.start.row, range.start.column); - this.selectTo(range.end.row, range.end.column); + this.$isEmpty = false; + this.anchor.setPosition(range.start.row, range.start.column); + this.lead.setPosition(range.end.row, range.end.column); } this.$desiredColumn = null; }; this.$moveSelection = function(mover) { - var lead = this.selectionLead; + var lead = this.lead; if (this.$isEmpty) this.setSelectionAnchor(lead.row, lead.column); @@ -391,7 +396,7 @@ var Selection = function(session) { **/ this.getWordRange = function(row, column) { if (typeof column == "undefined") { - var cursor = row || this.selectionLead; + var cursor = row || this.lead; row = cursor.row; column = cursor.column; } @@ -414,7 +419,7 @@ var Selection = function(session) { }; this.getLineRange = function(row, excludeLastChar) { - var rowStart = typeof row == "number" ? row : this.selectionLead.row; + var rowStart = typeof row == "number" ? row : this.lead.row; var rowEnd; var foldLine = this.session.getFoldLine(rowStart); @@ -463,7 +468,7 @@ var Selection = function(session) { * Moves the cursor left one column. **/ this.moveCursorLeft = function() { - var cursor = this.selectionLead.getPosition(), + var cursor = this.lead.getPosition(), fold; if (fold = this.session.getFoldAt(cursor.row, cursor.column, -1)) { @@ -489,19 +494,19 @@ var Selection = function(session) { * Moves the cursor right one column. **/ this.moveCursorRight = function() { - var cursor = this.selectionLead.getPosition(), + var cursor = this.lead.getPosition(), fold; if (fold = this.session.getFoldAt(cursor.row, cursor.column, 1)) { this.moveCursorTo(fold.end.row, fold.end.column); } - else if (this.selectionLead.column == this.doc.getLine(this.selectionLead.row).length) { - if (this.selectionLead.row < this.doc.getLength() - 1) { - this.moveCursorTo(this.selectionLead.row + 1, 0); + else if (this.lead.column == this.doc.getLine(this.lead.row).length) { + if (this.lead.row < this.doc.getLength() - 1) { + this.moveCursorTo(this.lead.row + 1, 0); } } else { var tabSize = this.session.getTabSize(); - var cursor = this.selectionLead; + var cursor = this.lead; if (this.session.isTabStop(cursor) && this.doc.getLine(cursor.row).slice(cursor.column, cursor.column+tabSize).split(" ").length-1 == tabSize) this.moveCursorBy(0, tabSize); else @@ -515,8 +520,8 @@ var Selection = function(session) { * Moves the cursor to the start of the line. **/ this.moveCursorLineStart = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; var screenRow = this.session.documentToScreenRow(row, column); // Determ the doc-position of the first character at the screen line. @@ -548,7 +553,7 @@ var Selection = function(session) { * Moves the cursor to the end of the line. **/ this.moveCursorLineEnd = function() { - var lead = this.selectionLead; + var lead = this.lead; var lastRowColumnPosition = this.session.getDocumentLastRowColumnPosition(lead.row, lead.column); this.moveCursorTo( @@ -583,8 +588,8 @@ var Selection = function(session) { * Moves the cursor to the word on the right. **/ this.moveCursorLongWordRight = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; var line = this.doc.getLine(row); var rightOfCursor = line.substring(column); @@ -630,8 +635,8 @@ var Selection = function(session) { * Moves the cursor to the word on the left. **/ this.moveCursorLongWordLeft = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; // skip folds var fold; @@ -712,8 +717,8 @@ var Selection = function(session) { }; this.moveCursorShortWordRight = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; var line = this.doc.getLine(row); var rightOfCursor = line.substring(column); @@ -730,8 +735,8 @@ var Selection = function(session) { }; this.moveCursorShortWordLeft = function() { - var row = this.selectionLead.row; - var column = this.selectionLead.column; + var row = this.lead.row; + var column = this.lead.column; var fold; if (fold = this.session.getFoldAt(row, column, -1)) @@ -770,8 +775,8 @@ var Selection = function(session) { **/ this.moveCursorBy = function(rows, chars) { var screenPos = this.session.documentToScreenPosition( - this.selectionLead.row, - this.selectionLead.column + this.lead.row, + this.lead.column ); if (chars === 0) { @@ -814,7 +819,7 @@ var Selection = function(session) { } this.$keepDesiredColumnOnChange = true; - this.selectionLead.setPosition(row, column); + this.lead.setPosition(row, column); this.$keepDesiredColumnOnChange = false; if (!keepDesiredColumn) @@ -836,8 +841,8 @@ var Selection = function(session) { // remove listeners from document this.detach = function() { - this.selectionLead.detach(); - this.selectionAnchor.detach(); + this.lead.detach(); + this.anchor.detach(); this.session = this.doc = null; } From 6411152d004ca687efadc6b38f2499c8e701b3a8 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 17 Apr 2012 19:36:31 +0400 Subject: [PATCH 2/8] do not compute pageX only to convert it back to clientX --- lib/ace/lib/event.js | 16 ---------------- lib/ace/mouse/mouse_event.js | 13 ++++--------- lib/ace/mouse/multi_select_handler.js | 6 +++--- lib/ace/virtual_renderer.js | 16 +++++++++++++--- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/lib/ace/lib/event.js b/lib/ace/lib/event.js index e5888bc2..42ed97ff 100644 --- a/lib/ace/lib/event.js +++ b/lib/ace/lib/event.js @@ -87,22 +87,6 @@ exports.preventDefault = function(e) { e.returnValue = false; }; -exports.getDocumentX = function(e) { - if (e.clientX) { - return e.clientX + dom.getPageScrollLeft(); - } else { - return e.pageX; - } -}; - -exports.getDocumentY = function(e) { - if (e.clientY) { - return e.clientY + dom.getPageScrollTop(); - } else { - return e.pageY; - } -}; - /* * @return {Number} 0 for left button, 1 for middle button, 2 for right button */ diff --git a/lib/ace/mouse/mouse_event.js b/lib/ace/mouse/mouse_event.js index 35a67313..2c813cde 100644 --- a/lib/ace/mouse/mouse_event.js +++ b/lib/ace/mouse/mouse_event.js @@ -48,11 +48,8 @@ var MouseEvent = exports.MouseEvent = function(domEvent, editor) { this.domEvent = domEvent; this.editor = editor; - this.pageX = event.getDocumentX(domEvent); - this.pageY = event.getDocumentY(domEvent); - - this.clientX = domEvent.clientX; - this.clientY = domEvent.clientY; + this.x = this.clientX = domEvent.clientX; + this.y = this.clientY = domEvent.clientY; this.$pos = null; this.$inSelection = null; @@ -86,10 +83,8 @@ var MouseEvent = exports.MouseEvent = function(domEvent, editor) { this.getDocumentPosition = function() { if (this.$pos) return this.$pos; - - var pageX = event.getDocumentX(this.domEvent); - var pageY = event.getDocumentY(this.domEvent); - this.$pos = this.editor.renderer.screenToTextCoordinates(pageX, pageY); + + this.$pos = this.editor.renderer.screenToTextCoordinates(this.clientX, this.clientY); return this.$pos; }; diff --git a/lib/ace/mouse/multi_select_handler.js b/lib/ace/mouse/multi_select_handler.js index 3019e397..880bc13c 100644 --- a/lib/ace/mouse/multi_select_handler.js +++ b/lib/ace/mouse/multi_select_handler.js @@ -76,10 +76,10 @@ function onMouseDown(e) { var inSelection = e.inSelection() || (selection.isEmpty() && isSamePoint(pos, cursor)); - var mouseX = e.pageX, mouseY = e.pageY; + var mouseX = e.x, mouseY = e.y; var onMouseSelection = function(e) { - mouseX = event.getDocumentX(e); - mouseY = event.getDocumentY(e); + mouseX = e.clientX; + mouseY = e.clientY; }; var blockSelect = function() { diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 69909c6a..8c1ebaaa 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -1103,14 +1103,24 @@ var VirtualRenderer = function(container, theme) { // todo: handle horizontal scrolling }; - this.screenToTextCoordinates = function(pageX, pageY) { + this.pixelToScreenCoordinates = function(x, y) { + var canvasPos = this.scroller.getBoundingClientRect(); + + var offset = (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth; + var row = Math.floor((y + this.scrollTop - canvasPos.top) / this.lineHeight); + var col = Math.round(offset); + + return {row: row, column: col, side: offset - col}; + }; + + this.screenToTextCoordinates = function(x, y) { var canvasPos = this.scroller.getBoundingClientRect(); var col = Math.round( - (pageX + this.scrollLeft - canvasPos.left - this.$padding - dom.getPageScrollLeft()) / this.characterWidth + (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth ); var row = Math.floor( - (pageY + this.scrollTop - canvasPos.top - dom.getPageScrollTop()) / this.lineHeight + (y + this.scrollTop - canvasPos.top) / this.lineHeight ); return this.session.screenToDocumentPosition(row, Math.max(col, 0)); From 7a50a070e9212e58dfc93701d56a31379b3881df Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 27 Apr 2012 11:27:37 +0400 Subject: [PATCH 3/8] cleanup mouse handling --- lib/ace/mouse/default_gutter_handler.js | 17 +- lib/ace/mouse/default_handlers.js | 376 +++++++++++++----------- lib/ace/mouse/mouse_handler.js | 46 ++- lib/ace/virtual_renderer.js | 2 +- 4 files changed, 265 insertions(+), 176 deletions(-) diff --git a/lib/ace/mouse/default_gutter_handler.js b/lib/ace/mouse/default_gutter_handler.js index c9e69927..d03e18ce 100644 --- a/lib/ace/mouse/default_gutter_handler.js +++ b/lib/ace/mouse/default_gutter_handler.js @@ -39,13 +39,24 @@ define(function(require, exports, module) { "use strict"; -function GutterHandler(editor) { - editor.setDefaultHandler("gutterclick", function(e) { +function GutterHandler(mouseHandler) { + var editor = mouseHandler.editor; + + mouseHandler.editor.setDefaultHandler("guttermousedown", function(e) { + if (e.domEvent.target.className.indexOf("ace_gutter-cell") == -1) + return; + + if (!editor.isFocused()) + return; + var row = e.getDocumentPosition().row; var selection = editor.session.selection; - + selection.moveCursorTo(row, 0); selection.selectLine(); + + mouseHandler.$clickSelection = selection.getRange(); + mouseHandler.captureMouse(e, "selectByLines"); }); } diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index c7343cf5..8233185c 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -22,6 +22,7 @@ * Contributor(s): * Fabian Jakobs * Mike de Boer + * Harutyun Amirjanyan * * 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 @@ -44,38 +45,46 @@ var event = require("../lib/event"); var dom = require("../lib/dom"); var BrowserFocus = require("../lib/browser_focus").BrowserFocus; -var STATE_UNKNOWN = 0; -var STATE_SELECT = 1; -var STATE_DRAG = 2; var DRAG_OFFSET = 5; // pixels -function DefaultHandlers(editor) { - this.editor = editor; - this.$clickSelection = null; - this.browserFocus = new BrowserFocus(); - editor.setDefaultHandler("mousedown", this.onMouseDown.bind(this)); - editor.setDefaultHandler("dblclick", this.onDoubleClick.bind(this)); - editor.setDefaultHandler("tripleclick", this.onTripleClick.bind(this)); - editor.setDefaultHandler("quadclick", this.onQuadClick.bind(this)); - editor.setDefaultHandler("mousewheel", this.onScroll.bind(this)); + +function DefaultHandlers(mouseHandler) { + mouseHandler.$clickSelection = null; + mouseHandler.browserFocus = new BrowserFocus(); + + var editor = mouseHandler.editor; + editor.setDefaultHandler("mousedown", this.onMouseDown.bind(mouseHandler)); + editor.setDefaultHandler("dblclick", this.onDoubleClick.bind(mouseHandler)); + editor.setDefaultHandler("tripleclick", this.onTripleClick.bind(mouseHandler)); + editor.setDefaultHandler("quadclick", this.onQuadClick.bind(mouseHandler)); + editor.setDefaultHandler("mousewheel", this.onScroll.bind(mouseHandler)); + + var exports = ["select", "startSelect", "drag", "dragEnd", "dragWait", + "dragWaitEnd", "startDrag"]; + + exports.forEach(function(x) { + mouseHandler[x] = this[x]; + }, this); + + mouseHandler.selectByLines = this.extendSelectionBy.bind(mouseHandler, "getLineRange"); + mouseHandler.selectByWords = this.extendSelectionBy.bind(mouseHandler, "getWordRange"); } (function() { - + this.onMouseDown = function(ev) { + this.mousedownEvent = ev; var inSelection = ev.inSelection(); - var pageX = ev.pageX; - var pageY = ev.pageY; var pos = ev.getDocumentPosition(); var editor = this.editor; var _self = this; - + + this.ev = ev var selectionRange = editor.getSelectionRange(); var selectionEmpty = selectionRange.isEmpty(); - var state = STATE_UNKNOWN; - + var button = ev.getButton(); if (button !== 0) { if (selectionEmpty) { @@ -91,187 +100,224 @@ function DefaultHandlers(editor) { // if this click caused the editor to be focused should not clear the // selection - if ( - inSelection && ( - !this.browserFocus.isFocused() - || new Date().getTime() - this.browserFocus.lastFocus < 20 - || !editor.isFocused() - ) - ) { + if (inSelection && !editor.isFocused()) { editor.focus(); return; } - if (!inSelection) { + if (!inSelection || this.$clickSelection || ev.getShiftKey()) { // Directly pick STATE_SELECT, since the user is not clicking inside // a selection. - onStartSelect(pos); - } - - var mousePageX = pageX, mousePageY = pageY; - var mousedownTime = (new Date()).getTime(); - var dragCursor, dragRange, dragSelectionMarker; - - var onMouseSelection = function(e) { - mousePageX = event.getDocumentX(e); - mousePageY = event.getDocumentY(e); - }; - - var onMouseSelectionEnd = function(e) { - clearInterval(timerId); - if (state == STATE_UNKNOWN) - onStartSelect(pos); - else if (state == STATE_DRAG) - onMouseDragSelectionEnd(e); - - _self.$clickSelection = null; - state = STATE_UNKNOWN; - }; - - var onMouseDragSelectionEnd = function(e) { - dom.removeCssClass(editor.container, "ace_dragging"); - editor.session.removeMarker(dragSelectionMarker); - - if (!editor.$mouseHandler.$clickSelection) { - if (!dragCursor) { - editor.moveCursorToPosition(pos); - editor.selection.clearSelection(); - } - } - - if (!dragCursor) - return; - - if (dragRange.contains(dragCursor.row, dragCursor.column)) { - dragCursor = null; - return; - } - - editor.clearSelection(); - if (e && (e.ctrlKey || e.altKey)) { - var session = editor.session; - var newRange = session.insert(dragCursor, session.getTextRange(dragRange)); + this.startSelect(pos); + } else if (inSelection) { + var e = ev.domEvent; + if ((e.ctrlKey || e.altKey)) { + this.startDrag(); } else { - var newRange = editor.moveText(dragRange, dragCursor); + this.mousedownEvent.time = (new Date()).getTime(); + this.setState("dragWait"); } - if (!newRange) { - dragCursor = null; - return; - } - - editor.selection.setSelectionRange(newRange); - }; - - var onSelectionInterval = function() { - if (state == STATE_UNKNOWN) { - var distance = calcDistance(pageX, pageY, mousePageX, mousePageY); - var time = (new Date()).getTime(); - - if (distance > DRAG_OFFSET) { - state = STATE_SELECT; - var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); - onStartSelect(cursor); - } - else if ((time - mousedownTime) > editor.getDragDelay()) { - state = STATE_DRAG; - dragRange = editor.getSelectionRange(); - var style = editor.getSelectionStyle(); - dragSelectionMarker = editor.session.addMarker(dragRange, "ace_selection", style); - editor.clearSelection(); - dom.addCssClass(editor.container, "ace_dragging"); - } - - } - - if (state == STATE_DRAG) - onDragSelectionInterval(); - else if (state == STATE_SELECT) - onUpdateSelectionInterval(); - }; - - function onStartSelect(pos) { - if (ev.getShiftKey()) { - editor.selection.selectToPosition(pos); - } - else { - if (!_self.$clickSelection) { - editor.moveCursorToPosition(pos); - editor.selection.clearSelection(); - } - } - state = STATE_SELECT; } - var onUpdateSelectionInterval = function() { - var anchor; - var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); - - if (_self.$clickSelection) { - if (_self.$clickSelection.contains(cursor.row, cursor.column)) { - editor.selection.setSelectionRange(_self.$clickSelection); - } - else { - if (_self.$clickSelection.compare(cursor.row, cursor.column) == -1) { - anchor = _self.$clickSelection.end; - } - else { - anchor = _self.$clickSelection.start; - } - editor.selection.setSelectionAnchor(anchor.row, anchor.column); - editor.selection.selectToPosition(cursor); - } - } - else { - editor.selection.selectToPosition(cursor); - } - - editor.renderer.scrollCursorIntoView(); - }; - - var onDragSelectionInterval = function() { - dragCursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); - editor.moveCursorToPosition(dragCursor); - }; - - event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); - var timerId = setInterval(onSelectionInterval, 20); - - return ev.preventDefault(); + this.captureMouse(ev) }; - + + this.startSelect = function(pos) { + pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y); + if (this.mousedownEvent.getShiftKey()) { + this.editor.selection.selectToPosition(pos); + } + else if (!this.$clickSelection) { + this.editor.moveCursorToPosition(pos); + this.editor.selection.clearSelection(); + } + this.setState("select"); + } + + this.select = function() { + var anchor, editor = this.editor; + var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y); + + if (this.$clickSelection) { + var cmp = this.$clickSelection.comparePoint(cursor); + + if (cmp == -1) { + anchor = this.$clickSelection.end; + } else if (cmp == 1) { + anchor = this.$clickSelection.start; + } else { + cursor = this.$clickSelection.end; + anchor = this.$clickSelection.start; + } + editor.selection.setSelectionAnchor(anchor.row, anchor.column); + } + editor.selection.selectToPosition(cursor); + + editor.renderer.scrollCursorIntoView(); + }; + + this.extendSelectionBy = function(unitName) { + var anchor, editor = this.editor; + var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y); + var range = editor.selection[unitName](cursor.row, cursor.column); + + if (this.$clickSelection) { + var cmpStart = this.$clickSelection.comparePoint(range.start); + var cmpEnd = this.$clickSelection.comparePoint(range.end); + + if (cmpStart == -1 && cmpEnd <= 0) { + anchor = this.$clickSelection.end; + cursor = range.start; + } else if (cmpEnd == 1 && cmpStart >= 0) { + anchor = this.$clickSelection.start; + cursor = range.end; + } else if (cmpStart == -1 && cmpEnd == 1) { + cursor = range.end; + anchor = range.start; + } else { + cursor = this.$clickSelection.end; + anchor = this.$clickSelection.start; + } + editor.selection.setSelectionAnchor(anchor.row, anchor.column); + } + editor.selection.selectToPosition(cursor); + + editor.renderer.scrollCursorIntoView(); + }; + + this.startDrag = function() { + var editor = this.editor; + this.setState("drag"); + this.dragRange = editor.getSelectionRange(); + var style = editor.getSelectionStyle(); + this.dragSelectionMarker = editor.session.addMarker(this.dragRange, "ace_selection", style); + editor.clearSelection(); + dom.addCssClass(editor.container, "ace_dragging"); + if (!this.$dragKeybinding) { + this.$dragKeybinding = { + handleKeyboard: function(data, hashId, keyString, keyCode) { + if (keyString == "esc") + return {command: this.command}; + }, + command: { + exec: function(editor) { + var self = editor.$mouseHandler; + self.dragCursor = null + self.dragEnd(); + self.startSelect(); + } + } + } + } + + editor.keyBinding.addKeyboardHandler(this.$dragKeybinding); + }; + + this.dragWait = function() { + var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); + var time = (new Date()).getTime(); + var editor = this.editor; + + if (distance > DRAG_OFFSET) { + this.startSelect(); + } else if ((time - this.mousedownEvent.time) > editor.getDragDelay()) { + this.startDrag() + } + }; + + this.dragWaitEnd = function(e) { + this.mousedownEvent.domEvent = e; + this.startSelect(); + }; + + this.drag = function() { + var editor = this.editor; + this.dragCursor = editor.renderer.screenToTextCoordinates(this.x, this.y); + editor.moveCursorToPosition(this.dragCursor); + editor.renderer.scrollCursorIntoView(); + }; + + this.dragEnd = function(e) { + var editor = this.editor; + var dragCursor = this.dragCursor; + var dragRange = this.dragRange; + dom.removeCssClass(editor.container, "ace_dragging"); + editor.session.removeMarker(this.dragSelectionMarker); + editor.keyBinding.removeKeyboardHandler(this.$dragKeybinding); + + if (!dragCursor) + return; + + editor.clearSelection(); + if (e && (e.ctrlKey || e.altKey)) { + var session = editor.session; + var newRange = dragRange; + newRange.end = session.insert(dragCursor, session.getTextRange(dragRange)); + newRange.start = dragCursor; + } else if (dragRange.contains(dragCursor.row, dragCursor.column)) { + return; + } else { + var newRange = editor.moveText(dragRange, dragCursor); + } + + if (!newRange) + return; + + editor.selection.setSelectionRange(newRange); + }; + this.onDoubleClick = function(ev) { var pos = ev.getDocumentPosition(); var editor = this.editor; - + + this.setState("selectByWords"); + editor.moveCursorToPosition(pos); editor.selection.selectWord(); this.$clickSelection = editor.getSelectionRange(); }; - + this.onTripleClick = function(ev) { var pos = ev.getDocumentPosition(); var editor = this.editor; - + + this.setState("selectByLines"); + editor.moveCursorToPosition(pos); editor.selection.selectLine(); this.$clickSelection = editor.getSelectionRange(); }; - + this.onQuadClick = function(ev) { var editor = this.editor; - + editor.selectAll(); this.$clickSelection = editor.getSelectionRange(); + this.setState("select"); }; - + this.onScroll = function(ev) { var editor = this.editor; - + var isScrolable = editor.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed); + if (isScrolable) { + this.$passScrollEvent = false; + } else { + if (this.$passScrollEvent) + return; + + if (!this.$scrollStopTimeout) { + var self = this; + this.$scrollStopTimeout = setTimeout(function() { + self.$passScrollEvent = true; + self.$scrollStopTimeout = null; + }, 200); + } + } + editor.renderer.scrollBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed); - if (editor.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed)) - return ev.preventDefault(); + return ev.preventDefault(); }; - + }).call(DefaultHandlers.prototype); exports.DefaultHandlers = DefaultHandlers; diff --git a/lib/ace/mouse/mouse_handler.js b/lib/ace/mouse/mouse_handler.js index df4f0640..c44d03ba 100644 --- a/lib/ace/mouse/mouse_handler.js +++ b/lib/ace/mouse/mouse_handler.js @@ -47,10 +47,10 @@ var MouseEvent = require("./mouse_event").MouseEvent; var MouseHandler = function(editor) { this.editor = editor; - - new DefaultHandlers(editor); - new DefaultGutterHandler(editor); - + + new DefaultHandlers(this); + new DefaultGutterHandler(this); + event.addListener(editor.container, "mousedown", function(e) { editor.focus(); return event.preventDefault(e); @@ -67,7 +67,7 @@ var MouseHandler = function(editor) { event.addMultiMouseDownListener(mouseTarget, 0, 3, 600, this.onMouseEvent.bind(this, "tripleclick")); event.addMultiMouseDownListener(mouseTarget, 0, 4, 600, this.onMouseEvent.bind(this, "quadclick")); event.addMouseWheelListener(editor.container, this.onMouseWheel.bind(this, "mousewheel")); - + var gutterEl = editor.renderer.$gutter; event.addListener(gutterEl, "mousedown", this.onMouseEvent.bind(this, "guttermousedown")); event.addListener(gutterEl, "click", this.onMouseEvent.bind(this, "gutterclick")); @@ -89,7 +89,7 @@ var MouseHandler = function(editor) { this.onMouseEvent = function(name, e) { this.editor._emit(name, new MouseEvent(e, this.editor)); }; - + this.$dragDelay = 250; this.setDragDelay = function(dragDelay) { this.$dragDelay = dragDelay; @@ -113,10 +113,42 @@ var MouseHandler = function(editor) { mouseEvent.speed = this.$scrollSpeed * 2; mouseEvent.wheelX = e.wheelX; mouseEvent.wheelY = e.wheelY; - + this.editor._emit(name, mouseEvent); }; + this.setState = function(state) { + this.state = state; + }; + + this.captureMouse = function(ev, state) { + if (state) + this.setState(state); + + this.x = ev.x; + this.y = ev.y; + + var self = this; + var onMouseSelection = function(e) { + self.x = e.clientX; + self.y = e.clientY; + }; + + var onMouseSelectionEnd = function(e) { + clearInterval(timerId); + self[self.state + "End"] && self[self.state + "End"](e); + self.$clickSelection = null; + }; + + var onSelectionInterval = function() { + self[self.state] && self[self.state](); + } + + event.capture(this.editor.container, onMouseSelection, onMouseSelectionEnd); + var timerId = setInterval(onSelectionInterval, 20); + + ev.preventDefault(); + }; }).call(MouseHandler.prototype); exports.MouseHandler = MouseHandler; diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 8c1ebaaa..0c2c0880 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -1110,7 +1110,7 @@ var VirtualRenderer = function(container, theme) { var row = Math.floor((y + this.scrollTop - canvasPos.top) / this.lineHeight); var col = Math.round(offset); - return {row: row, column: col, side: offset - col}; + return {row: row, column: col, side: offset - col > 0 ? 1 : -1}; }; this.screenToTextCoordinates = function(x, y) { From 2f6a6ce7e0992a2eccbb8a517aa2d6f4954f1214 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 2 Apr 2012 23:55:15 +0400 Subject: [PATCH 4/8] move text area to cursor only when cursor is moved --- lib/ace/css/editor.css | 4 ++-- lib/ace/editor.js | 7 ++---- lib/ace/keyboard/textinput.js | 4 +++- lib/ace/layer/cursor.js | 3 +++ lib/ace/virtual_renderer.js | 41 +++++++++++++++++------------------ 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 5a544076..58d1e7e6 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -94,8 +94,8 @@ .ace_editor textarea { position: fixed; z-index: 0; - width: 10px; - height: 30px; + width: 0.5em; + height: 1em; opacity: 0; background: transparent; appearance: none; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 53295878..1feebf68 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -80,6 +80,7 @@ var Editor = function(renderer, session) { this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands); this.textInput = new TextInput(renderer.getTextAreaContainer(), this); + this.renderer.textarea = this.textInput.getElement(); this.keyBinding = new KeyBinding(this); // TODO detect touch event support @@ -385,11 +386,7 @@ var Editor = function(renderer, session) { this.$cursorChange = function() { this.renderer.updateCursor(); - - // move text input over the cursor - // this is required for iOS and IME - this.renderer.moveTextAreaToCursor(this.textInput.getElement()); - } + }; /** * Editor@onDocumentChange(e) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 10f12865..fa570e66 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -49,7 +49,9 @@ var TextInput = function(parentNode, host) { var text = dom.createElement("textarea"); if (useragent.isTouchPad) text.setAttribute("x-palm-disable-auto-cap", true); - + + text.setAttribute("wrap", "off"); + text.style.left = "-10000px"; text.style.position = "fixed"; parentNode.insertBefore(text, parentNode.firstChild); diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index 1c55d7a8..f43b36b7 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -175,6 +175,9 @@ var Cursor = function(parentEl) { if (overwrite != this.overwrite) this.$setOverite(overwrite); + // cache for textarea and gutter highlight + this.$pixelPos = pixelPos; + this.restartTimer(); }; diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 0c2c0880..fc477c58 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -82,6 +82,9 @@ var VirtualRenderer = function(container, theme) { // TODO: this breaks rendering in Cloud9 with multiple ace instances // // Imports CSS once per DOM document ('ace_editor' serves as an identifier). // dom.importCssString(editorCss, "ace_editor", container.ownerDocument); + + // in IE <= 9 the native cursor always shines through + this.$keepTextAreaAtCursor = !useragent.isIE; dom.addCssClass(container, "ace_editor"); @@ -496,30 +499,22 @@ var VirtualRenderer = function(container, theme) { return this.container; }; - /** - * VirtualRenderer.moveTextAreaToCursor(textarea) -> Void - * - textarea (DOMElement): A text area to work with - * - * Changes the position of `textarea` to where the cursor is pointing. - **/ - this.moveTextAreaToCursor = function(textarea) { - // in IE the native cursor always shines through - // this persists in IE9 - if (useragent.isIE) + // move text input over the cursor + // this is required for iOS and IME + this.$moveTextAreaToCursor = function() { + if (!this.$keepTextAreaAtCursor) return; - if (this.layerConfig.lastRow === 0) + var pos = this.$cursorLayer.$pixelPos; + pos.top -= this.layerConfig.offset; + + if (pos.top < 0 || pos.top > this.layerConfig.height) return; - var pos = this.$cursorLayer.getPixelPosition(); - if (!pos) - return; - - var bounds = this.content.getBoundingClientRect(); - var offset = this.layerConfig.offset; - - textarea.style.left = (bounds.left + pos.left) + "px"; - textarea.style.top = (bounds.top + pos.top - this.scrollTop + offset) + "px"; + pos.left += (this.showGutter ? this.$gutterLayer.gutterWidth : 0) - this.scrollLeft; + var bounds = this.container.getBoundingClientRect(); + this.textarea.style.left = (bounds.left + pos.left) + "px"; + this.textarea.style.top = (bounds.top + pos.top) + "px"; }; /** @@ -640,6 +635,7 @@ var VirtualRenderer = function(container, theme) { this.$markerBack.update(this.layerConfig); this.$markerFront.update(this.layerConfig); this.$cursorLayer.update(this.layerConfig); + this.$moveTextAreaToCursor(); return; } @@ -656,6 +652,7 @@ var VirtualRenderer = function(container, theme) { this.$markerBack.update(this.layerConfig); this.$markerFront.update(this.layerConfig); this.$cursorLayer.update(this.layerConfig); + this.$moveTextAreaToCursor(); return; } @@ -675,8 +672,10 @@ var VirtualRenderer = function(container, theme) { this.$gutterLayer.update(this.layerConfig); } - if (changes & this.CHANGE_CURSOR) + if (changes & this.CHANGE_CURSOR) { this.$cursorLayer.update(this.layerConfig); + this.$moveTextAreaToCursor(); + } if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_FRONT)) { this.$markerFront.update(this.layerConfig); From b5b5e1e97f3b81906a5cbf7b3c0b5089f0d13379 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 1 May 2012 17:44:47 +0400 Subject: [PATCH 5/8] do not move textarea during mouse selection --- lib/ace/mouse/mouse_handler.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ace/mouse/mouse_handler.js b/lib/ace/mouse/mouse_handler.js index c44d03ba..a85e9672 100644 --- a/lib/ace/mouse/mouse_handler.js +++ b/lib/ace/mouse/mouse_handler.js @@ -128,6 +128,10 @@ var MouseHandler = function(editor) { this.x = ev.x; this.y = ev.y; + // do not move textarea during selection + var kt = this.editor.renderer.$keepTextAreaAtCursor; + this.editor.renderer.$keepTextAreaAtCursor = false; + var self = this; var onMouseSelection = function(e) { self.x = e.clientX; @@ -138,6 +142,8 @@ var MouseHandler = function(editor) { clearInterval(timerId); self[self.state + "End"] && self[self.state + "End"](e); self.$clickSelection = null; + self.editor.renderer.$keepTextAreaAtCursor = kt; + self.editor.renderer.$moveTextAreaToCursor(); }; var onSelectionInterval = function() { From 16fb6c90cced2a79678667c4e04cf4e717cbb7b7 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 1 May 2012 17:53:28 +0400 Subject: [PATCH 6/8] paste the linux way :)copy seems to be impossible:( --- lib/ace/mouse/default_handlers.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index 8233185c..34d6ef1b 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -41,7 +41,6 @@ define(function(require, exports, module) { "use strict"; -var event = require("../lib/event"); var dom = require("../lib/dom"); var BrowserFocus = require("../lib/browser_focus").BrowserFocus; @@ -91,10 +90,15 @@ function DefaultHandlers(mouseHandler) { editor.moveCursorToPosition(pos); editor.selection.clearSelection(); } - if (button == 2) { - editor.textInput.onContextMenu({x: ev.clientX, y: ev.clientY}, selectionEmpty); - event.capture(editor.container, function(){}, editor.textInput.onContextMenuClose); - } + // 2: contextmenu, 1: linux paste + this.moveTextarea = function() { + editor.textInput.onContextMenu({x: _self.x, y: _self.y}); + }; + this.moveTextareaEnd = editor.textInput.onContextMenuClose; + + editor.textInput.onContextMenu({x: this.x, y: this.y}, selectionEmpty); + this.captureMouse(ev, "moveTextarea"); + return; } From 5527015a9a3c7cd1a89fe97fb50533447c702d65 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 1 May 2012 17:55:22 +0400 Subject: [PATCH 7/8] fix `&` breaking old gecko --- lib/ace/layer/text.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index e4069659..bb8d733d 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -395,9 +395,9 @@ var Text = function(parentEl) { if (a) { return new Array(c.length+1).join(" "); } else if (c == "&") { - return useragent.isOldGecko ? "&" : "&"; + return "&"; } else if (c == "<") { - return "<"; + return "<"; } else if (c == "\t") { var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx); screenColumn += tabSize - 1; @@ -411,10 +411,7 @@ var Text = function(parentEl) { (self.config.characterWidth * 2) + "px'>" + space + ""; } else if (b) { - if (self.showInvisibles) - return "" + self.SPACE_CHAR + ""; - else - return " "; + return "" + self.SPACE_CHAR + ""; } else { screenColumn += 1; return "