From 6d10994eb784fdb463d20aea0d5b55f4e4f3f7c0 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Fri, 21 Feb 2014 16:44:46 +0100 Subject: [PATCH] Improve selection --- butterfly/static/coffees/selection.coffee | 120 ++++++++++++++++- butterfly/static/javascripts/main.js | 154 +++++++++++++++++++++- 2 files changed, 269 insertions(+), 5 deletions(-) diff --git a/butterfly/static/coffees/selection.coffee b/butterfly/static/coffees/selection.coffee index e2d061e..b4f5e54 100644 --- a/butterfly/static/coffees/selection.coffee +++ b/butterfly/static/coffees/selection.coffee @@ -14,10 +14,126 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +selection = null + +# get_line_range = (y) -> +# first_node = term.children[y].firstChild +# last_node = term.children[y].lastChild + +# range = document.createRange() +# range.setStart first_node, 0 +# range.setEnd last_node, last_node.length +# range +class Selection + constructor: -> + @reset() + + reset: -> + @selection = getSelection() + fake_range = document.createRange() + fake_range.setStart(@selection.anchorNode, @selection.anchorOffset) + fake_range.setEnd(@selection.focusNode, @selection.focusOffset) + @start = + node: @selection.anchorNode + off: @selection.anchorOffset + @end = + node: @selection.focusNode + off: @selection.focusOffset + + if fake_range.collapsed + [@start, @end] = [@end, @start] + + clear: -> + @selection.removeAllRanges() + + text: -> + @selection.toString() + + up: -> + @go -1 + + down: -> + @go +1 + + go: (n) -> + index = term.children.indexOf @get_selected_line() + + if 0 <= index + n < term.children.length + @clear() + @selection.addRange get_line_range(index + n) + + get_selected_line: -> + node = @start.node + while not node.classList or 'line' not in node.classList + node = node.parentNode + node + + +nextLeaf = (node) -> + next = node.nextSibling + if not next + next = node.parentNode.nextSibling + if not next + next = node.parentNode.parentNode.nextSibling.firstChild + next + +find_node_offset = (line, backward=false) -> + step = if backward then -1 else 1 + + for node in line.childNodes by step + if node.nodeType != node.TEXT_NODE + node = node.firstChild + for c, offset in node.textContent by step + if not c.match /\s/ + return [node, offset + if backward then 1 else 0] + return [line.firstChild, 0] + +get_line_range = (y) -> + line = term.children[y] + range = document.createRange() + range.setStart.apply range, find_node_offset(line) + range.setEnd.apply range, find_node_offset(line, true) + range + + +sel_to_line = (y) -> + selection = getSelection() + selection.removeAllRanges() + selection.addRange get_line_range(y) + + +document.addEventListener 'keydown', (e) -> + if selection + selection.reset() + if not e.ctrlKey and e.shiftKey and 37 <= e.keyCode <= 40 + return true + if e.shiftKey and e.ctrlKey + if e.keyCode == 38 + selection.up() + return cancel e + else if e.keyCode == 40 + selection.down() + return cancel e + + else if e.keyCode == 13 + term.handler selection.text() + selection.clear() + selection = null + else + selection.clear() + selection = null + return true + return cancel e + + # Start selection mode with shift up + if not selection and e.ctrlKey and e.shiftKey and e.keyCode == 38 + sel_to_line term.y - 1 + selection = new Selection() + return cancel e document.addEventListener 'dblclick', (e) -> return if e.ctrlKey or e.altkey - sel = window.getSelection() + sel = getSelection() return if sel.isCollapsed or sel.toString().match /\s/ range = document.createRange() @@ -26,8 +142,8 @@ document.addEventListener 'dblclick', (e) -> if range.collapsed sel.removeAllRanges() new_range = document.createRange() - new_range.setEnd(sel.anchorNode, sel.anchorOffset) new_range.setStart(sel.focusNode, sel.focusOffset) + new_range.setEnd(sel.anchorNode, sel.anchorOffset) sel.addRange(new_range) range.detach() diff --git a/butterfly/static/javascripts/main.js b/butterfly/static/javascripts/main.js index 313ea3c..9d161a7 100644 --- a/butterfly/static/javascripts/main.js +++ b/butterfly/static/javascripts/main.js @@ -1,5 +1,6 @@ // Generated by CoffeeScript 1.6.3 -var $, State, Terminal, alt, bench, cancel, cbench, cols, ctl, ctrl, first, quit, rows, s, send, term, virtual_input, ws, ws_url, +var $, Selection, State, Terminal, alt, bench, cancel, cbench, cols, ctl, ctrl, find_node_offset, first, get_line_range, nextLeaf, quit, rows, s, sel_to_line, selection, send, term, virtual_input, ws, ws_url, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __slice = [].slice; cancel = function(ev) { @@ -2411,12 +2412,159 @@ Terminal = (function() { })(); +selection = null; + +Selection = (function() { + function Selection() { + this.reset(); + } + + Selection.prototype.reset = function() { + var fake_range, _ref; + this.selection = getSelection(); + fake_range = document.createRange(); + fake_range.setStart(this.selection.anchorNode, this.selection.anchorOffset); + fake_range.setEnd(this.selection.focusNode, this.selection.focusOffset); + this.start = { + node: this.selection.anchorNode, + off: this.selection.anchorOffset + }; + this.end = { + node: this.selection.focusNode, + off: this.selection.focusOffset + }; + if (fake_range.collapsed) { + return _ref = [this.end, this.start], this.start = _ref[0], this.end = _ref[1], _ref; + } + }; + + Selection.prototype.clear = function() { + return this.selection.removeAllRanges(); + }; + + Selection.prototype.text = function() { + return this.selection.toString(); + }; + + Selection.prototype.up = function() { + return this.go(-1); + }; + + Selection.prototype.down = function() { + return this.go(+1); + }; + + Selection.prototype.go = function(n) { + var index, _ref; + index = term.children.indexOf(this.get_selected_line()); + if ((0 <= (_ref = index + n) && _ref < term.children.length)) { + this.clear(); + return this.selection.addRange(get_line_range(index + n)); + } + }; + + Selection.prototype.get_selected_line = function() { + var node; + node = this.start.node; + while (!node.classList || __indexOf.call(node.classList, 'line') < 0) { + node = node.parentNode; + } + return node; + }; + + return Selection; + +})(); + +nextLeaf = function(node) { + var next; + next = node.nextSibling; + if (!next) { + next = node.parentNode.nextSibling; + } + if (!next) { + next = node.parentNode.parentNode.nextSibling.firstChild; + } + return next; +}; + +find_node_offset = function(line, backward) { + var c, node, offset, step, _i, _j, _len, _len1, _ref, _ref1; + if (backward == null) { + backward = false; + } + step = backward ? -1 : 1; + _ref = line.childNodes; + for ((step > 0 ? (_i = 0, _len = _ref.length) : _i = _ref.length - 1); step > 0 ? _i < _len : _i >= 0; _i += step) { + node = _ref[_i]; + if (node.nodeType !== node.TEXT_NODE) { + node = node.firstChild; + } + _ref1 = node.textContent; + for ((step > 0 ? (offset = _j = 0, _len1 = _ref1.length) : offset = _j = _ref1.length - 1); step > 0 ? _j < _len1 : _j >= 0; offset = _j += step) { + c = _ref1[offset]; + if (!c.match(/\s/)) { + return [node, offset + (backward ? 1 : 0)]; + } + } + } + return [line.firstChild, 0]; +}; + +get_line_range = function(y) { + var line, range; + line = term.children[y]; + range = document.createRange(); + range.setStart.apply(range, find_node_offset(line)); + range.setEnd.apply(range, find_node_offset(line, true)); + return range; +}; + +sel_to_line = function(y) { + selection = getSelection(); + selection.removeAllRanges(); + return selection.addRange(get_line_range(y)); +}; + +document.addEventListener('keydown', function(e) { + var _ref; + if (selection) { + selection.reset(); + if (!e.ctrlKey && e.shiftKey && (37 <= (_ref = e.keyCode) && _ref <= 40)) { + return true; + } + if (e.shiftKey && e.ctrlKey) { + if (e.keyCode === 38) { + selection.up(); + return cancel(e); + } else if (e.keyCode === 40) { + selection.down(); + return cancel(e); + } + } else if (e.keyCode === 13) { + term.handler(selection.text()); + selection.clear(); + selection = null; + } else { + selection.clear(); + selection = null; + return true; + } + return cancel(e); + } + if (!selection && e.ctrlKey && e.shiftKey && e.keyCode === 38) { + sel_to_line(term.y - 1); + selection = new Selection(); + return cancel(e); + } +}); + document.addEventListener('dblclick', function(e) { var anchorNode, anchorOffset, new_range, range, sel; if (e.ctrlKey || e.altkey) { return; } - sel = window.getSelection(); + sel = getSelection(); if (sel.isCollapsed || sel.toString().match(/\s/)) { return; } @@ -2426,8 +2574,8 @@ document.addEventListener('dblclick', function(e) { if (range.collapsed) { sel.removeAllRanges(); new_range = document.createRange(); - new_range.setEnd(sel.anchorNode, sel.anchorOffset); new_range.setStart(sel.focusNode, sel.focusOffset); + new_range.setEnd(sel.anchorNode, sel.anchorOffset); sel.addRange(new_range); } range.detach();