From 459b256ddc7b5385a2206943e248760588e33ace Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Mon, 24 Feb 2014 11:06:29 +0100 Subject: [PATCH] Greatly improve selection --- butterfly/static/coffees/selection.coffee | 129 +++++++++----- butterfly/static/javascripts/main.js | 200 ++++++++++++++-------- 2 files changed, 217 insertions(+), 112 deletions(-) diff --git a/butterfly/static/coffees/selection.coffee b/butterfly/static/coffees/selection.coffee index b4f5e54..98c68e2 100644 --- a/butterfly/static/coffees/selection.coffee +++ b/butterfly/static/coffees/selection.coffee @@ -24,9 +24,30 @@ selection = null # range.setStart first_node, 0 # range.setEnd last_node, last_node.length # range + +previous_leaf = (node) -> + previous = node.previousSibling + if not previous + previous = node.parentNode.previousSibling + if not previous + previous = node.parentNode.parentNode.previousSibling.firstChild + while previous.lastChild + previous = previous.lastChild + previous + +next_leaf = (node) -> + next = node.nextSibling + if not next + next = node.parentNode.nextSibling + if not next + next = node.parentNode.parentNode.nextSibling + while next.firstChild + next = next.firstChild + next + class Selection constructor: -> - @reset() + @selection = getSelection() reset: -> @selection = getSelection() @@ -35,14 +56,22 @@ class Selection fake_range.setEnd(@selection.focusNode, @selection.focusOffset) @start = node: @selection.anchorNode - off: @selection.anchorOffset + offset: @selection.anchorOffset @end = node: @selection.focusNode - off: @selection.focusOffset + offset: @selection.focusOffset if fake_range.collapsed [@start, @end] = [@end, @start] + @start_line = @start.node + while not @start_line.classList or 'line' not in @start_line.classList + @start_line = @start_line.parentNode + + @end_line = @end.node + while not @end_line.classList or 'line' not in @end_line.classList + @end_line = @end_line.parentNode + clear: -> @selection.removeAllRanges() @@ -56,50 +85,60 @@ class Selection @go +1 go: (n) -> - index = term.children.indexOf @get_selected_line() + index = term.children.indexOf(@start_line) + n + if 0 <= index < term.children.length + @select_line index - if 0 <= index + n < term.children.length - @clear() - @selection.addRange get_line_range(index + n) + apply: -> + @clear() + range = document.createRange() + range.setStart @start.node, @start.offset + range.setEnd @end.node, @end.offset + @selection.addRange range - get_selected_line: -> - node = @start.node - while not node.classList or 'line' not in node.classList - node = node.parentNode - node + select_line: (y) -> + line = term.children[y] + line_start = + node: line.firstChild + offset: 0 + line_end = + node: line.lastChild + offset: line.lastChild.textContent.length -nextLeaf = (node) -> - next = node.nextSibling - if not next - next = node.parentNode.nextSibling - if not next - next = node.parentNode.parentNode.nextSibling.firstChild - next + @start = @walk line_start, /\S/ + @end = @walk line_end, /\S/, true -find_node_offset = (line, backward=false) -> - step = if backward then -1 else 1 + shrink_right: -> + node = @walk @end, /\s/, true + @end = @walk node, /\S/, true - 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] + shrink_left: -> + node = @walk @start, /\s/ + @start = @walk node, /\S/ -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 + walk: (needle, til, backward=false) -> + node = if needle.node.firstChild then needle.node.firstChild else needle.node + text = node.textContent + i = needle.offset + if backward + while node + while i > 0 + if text[--i].match til + return node: node, offset: i + 1 + node = previous_leaf node + text = node.textContent + i = text.length + else + while node + while i < text.length + if text[i++].match til + return node: node, offset: i - 1 + node = next_leaf node + text = node.textContent + i = 0 - -sel_to_line = (y) -> - selection = getSelection() - selection.removeAllRanges() - selection.addRange get_line_range(y) + return needle document.addEventListener 'keydown', (e) -> @@ -110,11 +149,12 @@ document.addEventListener 'keydown', (e) -> 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 == 39 + selection.shrink_left() + else if e.keyCode == 37 + selection.shrink_right() else if e.keyCode == 13 term.handler selection.text() selection.clear() @@ -123,12 +163,15 @@ document.addEventListener 'keydown', (e) -> selection.clear() selection = null return true + + selection?.apply() 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() + selection.select_line term.y - 1 + selection.apply() return cancel e document.addEventListener 'dblclick', (e) -> diff --git a/butterfly/static/javascripts/main.js b/butterfly/static/javascripts/main.js index c5b0aba..750761c 100644 --- a/butterfly/static/javascripts/main.js +++ b/butterfly/static/javascripts/main.js @@ -1,5 +1,5 @@ // Generated by CoffeeScript 1.6.3 -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, +var $, Selection, State, Terminal, alt, bench, cancel, cbench, cols, ctl, ctrl, first, next_leaf, previous_leaf, quit, rows, s, 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; @@ -2414,28 +2414,68 @@ Terminal = (function() { selection = null; +previous_leaf = function(node) { + var previous; + previous = node.previousSibling; + if (!previous) { + previous = node.parentNode.previousSibling; + } + if (!previous) { + previous = node.parentNode.parentNode.previousSibling.firstChild; + } + while (previous.lastChild) { + previous = previous.lastChild; + } + return previous; +}; + +next_leaf = function(node) { + var next; + next = node.nextSibling; + if (!next) { + next = node.parentNode.nextSibling; + } + if (!next) { + next = node.parentNode.parentNode.nextSibling; + } + while (next.firstChild) { + next = next.firstChild; + } + return next; +}; + Selection = (function() { function Selection() { - this.reset(); + this.selection = getSelection(); } Selection.prototype.reset = function() { - var fake_range, _ref; + var fake_range, _ref, _results; 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 + offset: this.selection.anchorOffset }; this.end = { node: this.selection.focusNode, - off: this.selection.focusOffset + offset: this.selection.focusOffset }; if (fake_range.collapsed) { - return _ref = [this.end, this.start], this.start = _ref[0], this.end = _ref[1], _ref; + _ref = [this.end, this.start], this.start = _ref[0], this.end = _ref[1]; } + this.start_line = this.start.node; + while (!this.start_line.classList || __indexOf.call(this.start_line.classList, 'line') < 0) { + this.start_line = this.start_line.parentNode; + } + this.end_line = this.end.node; + _results = []; + while (!this.end_line.classList || __indexOf.call(this.end_line.classList, 'line') < 0) { + _results.push(this.end_line = this.end_line.parentNode); + } + return _results; }; Selection.prototype.clear = function() { @@ -2455,77 +2495,93 @@ Selection = (function() { }; 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)); + var index; + index = term.children.indexOf(this.start_line) + n; + if ((0 <= index && index < term.children.length)) { + return this.select_line(index); } }; - Selection.prototype.get_selected_line = function() { + Selection.prototype.apply = function() { + var range; + this.clear(); + range = document.createRange(); + range.setStart(this.start.node, this.start.offset); + range.setEnd(this.end.node, this.end.offset); + return this.selection.addRange(range); + }; + + Selection.prototype.select_line = function(y) { + var line, line_end, line_start; + line = term.children[y]; + line_start = { + node: line.firstChild, + offset: 0 + }; + line_end = { + node: line.lastChild, + offset: line.lastChild.textContent.length + }; + this.start = this.walk(line_start, /\S/); + return this.end = this.walk(line_end, /\S/, true); + }; + + Selection.prototype.shrink_right = function() { var node; - node = this.start.node; - while (!node.classList || __indexOf.call(node.classList, 'line') < 0) { - node = node.parentNode; + node = this.walk(this.end, /\s/, true); + return this.end = this.walk(node, /\S/, true); + }; + + Selection.prototype.shrink_left = function() { + var node; + node = this.walk(this.start, /\s/); + return this.start = this.walk(node, /\S/); + }; + + Selection.prototype.walk = function(needle, til, backward) { + var i, node, text; + if (backward == null) { + backward = false; } - return node; + node = needle.node.firstChild ? needle.node.firstChild : needle.node; + text = node.textContent; + i = needle.offset; + if (backward) { + while (node) { + while (i > 0) { + if (text[--i].match(til)) { + return { + node: node, + offset: i + 1 + }; + } + } + node = previous_leaf(node); + text = node.textContent; + i = text.length; + } + } else { + while (node) { + while (i < text.length) { + if (text[i++].match(til)) { + return { + node: node, + offset: i - 1 + }; + } + } + node = next_leaf(node); + text = node.textContent; + i = 0; + } + } + return needle; }; 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) { @@ -2536,11 +2592,13 @@ document.addEventListener('keydown', function(e) { 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 === 39) { + selection.shrink_left(); + } else if (e.keyCode === 37) { + selection.shrink_right(); } else if (e.keyCode === 13) { term.handler(selection.text()); selection.clear(); @@ -2550,11 +2608,15 @@ document.addEventListener('keydown', function(e) { selection = null; return true; } + if (selection != null) { + selection.apply(); + } return cancel(e); } if (!selection && e.ctrlKey && e.shiftKey && e.keyCode === 38) { - sel_to_line(term.y - 1); selection = new Selection(); + selection.select_line(term.y - 1); + selection.apply(); return cancel(e); } });