From 8422c7ad12eb23e7722e6069c97e86255d437d36 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Mon, 26 Mar 2012 12:41:36 -0700 Subject: [PATCH] Overlapping selections are merged --- spec/atom/editor-spec.coffee | 18 ++++++++++++- spec/atom/range-spec.coffee | 15 +++++++++++ spec/atom/range-spec.js | 42 +++++++++++++++++++++++++++++ src/atom/composite-selection.coffee | 12 ++++++++- src/atom/editor.coffee | 4 ++- src/atom/range.coffee | 11 ++++++++ src/atom/selection.coffee | 11 ++++++++ 7 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 spec/atom/range-spec.js diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index b2aa0d1cd..5094cd448 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -757,7 +757,7 @@ describe "Editor", -> expect(range.end).toEqual({row: 5, column: 27}) expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27) - fdescribe "multiple cursor placement", -> + fdescribe "multiple cursors", -> it "places multiple cursor with meta-click", -> editor.attachToDom() editor.lines.trigger mousedownEvent(editor: editor, point: [3, 0]) @@ -919,6 +919,22 @@ describe "Editor", -> expect(selection1.getScreenRange()).toEqual [[4, 10], [5, 27]] expect(selection2.getScreenRange()).toEqual [[6, 10], [8, 27]] + describe "when multiple selctions intersect", -> + it "merges a selection that is completely contained within another", -> + editor.attachToDom() + editor.lines.trigger mousedownEvent(editor: editor, point: [4, 10]) + editor.lines.trigger mousemoveEvent(editor: editor, point: [5, 27]) + editor.lines.trigger 'mouseup' + + editor.lines.trigger mousedownEvent(editor: editor, point: [3, 10], metaKey: true) + editor.lines.trigger mousemoveEvent(editor: editor, point: [6, 27], metaKey: true) + editor.lines.trigger 'mouseup' + + selections = editor.compositeSelection.getSelections() + expect(selections.length).toBe 1 + [selection1] = selections + expect(selection1.getScreenRange()).toEqual [[3, 10], [6, 27]] + describe "cursor merging", -> it "merges cursors when they overlap due to a buffer change", -> editor.setCursorScreenPosition([0, 0]) diff --git a/spec/atom/range-spec.coffee b/spec/atom/range-spec.coffee index 937b7e4bb..2c9b70667 100644 --- a/spec/atom/range-spec.coffee +++ b/spec/atom/range-spec.coffee @@ -15,3 +15,18 @@ describe "Range", -> expect(new Range(new Point(1, 1), new Point(1, 1)).isEmpty()).toBeTruthy() expect(new Range(new Point(1, 1), new Point(1, 2)).isEmpty()).toBeFalsy() + describe ".intersectsWith(otherRange)", -> + it "returns true if the ranges intersect", -> + expect(new Range([1, 1], [2, 10]).intersectsWith(new Range([2, 1], [3, 10]))).toBeTruthy() + expect(new Range([2, 1], [3, 10]).intersectsWith(new Range([1, 1], [2, 10]))).toBeTruthy() + expect(new Range([2, 1], [3, 10]).intersectsWith(new Range([2, 5], [3, 1]))).toBeTruthy() + expect(new Range([2, 5], [3, 1]).intersectsWith(new Range([2, 1], [3, 10]))).toBeTruthy() + expect(new Range([2, 5], [3, 1]).intersectsWith(new Range([3, 2], [3, 10]))).toBeFalsy() + expect(new Range([3, 2], [3, 10]).intersectsWith(new Range([2, 5], [3, 1]))).toBeFalsy() + + describe ".union(otherRange)", -> + it "returns the union of the two ranges", -> + expect(new Range([1, 1], [2, 10]).union(new Range([2, 1], [3, 10]))).toEqual [[1, 1], [3, 10]] + expect(new Range([2, 1], [3, 10]).union(new Range([1, 1], [2, 10]))).toEqual [[1, 1], [3, 10]] + expect(new Range([2, 1], [3, 10]).union(new Range([2, 5], [3, 1]))).toEqual [[2, 1], [3, 10]] + expect(new Range([2, 5], [3, 1]).union(new Range([2, 1], [3, 10]))).toEqual [[2, 1], [3, 10]] diff --git a/spec/atom/range-spec.js b/spec/atom/range-spec.js new file mode 100644 index 000000000..b6dae983c --- /dev/null +++ b/spec/atom/range-spec.js @@ -0,0 +1,42 @@ +(function() { + var Point, Range; + + Range = require('range'); + + Point = require('point'); + + describe("Range", function() { + describe("constructor", function() { + return it("ensures that @start <= @end", function() { + var range1, range2; + range1 = new Range(new Point(0, 1), new Point(0, 4)); + expect(range1.start).toEqual({ + row: 0, + column: 1 + }); + range2 = new Range(new Point(1, 4), new Point(0, 1)); + return expect(range2.start).toEqual({ + row: 0, + column: 1 + }); + }); + }); + describe(".isEmpty()", function() { + return it("returns true if @start equals @end", function() { + expect(new Range(new Point(1, 1), new Point(1, 1)).isEmpty()).toBeTruthy(); + return expect(new Range(new Point(1, 1), new Point(1, 2)).isEmpty()).toBeFalsy(); + }); + }); + return describe(".intersectsWith(otherRange)", function() { + return fit("returns the intersection of the two ranges", function() { + var range1, range2; + range1 = new Range([1, 1], [2, 10]); + range2 = new Range([2, 1], [3, 10]); + expect(range1.intersectsWith(range2)).toBeTruth; + range2 = range1 = new Range([2, 1], [3, 10]); + return expect(range1.intersectsWith(range2)).toBeTruth; + }); + }); + }); + +}).call(this); diff --git a/src/atom/composite-selection.coffee b/src/atom/composite-selection.coffee index f1f225a66..523aa68e4 100644 --- a/src/atom/composite-selection.coffee +++ b/src/atom/composite-selection.coffee @@ -27,4 +27,14 @@ class CompositeSeleciton selection.backspace() selectToScreenPosition: (position) -> - _.last(@selections).selectToScreenPosition(position) \ No newline at end of file + _.last(@selections).selectToScreenPosition(position) + + mergeIntersectingSelections: -> + for selection in @getSelections() + otherSelections = @getSelections() + _.remove(otherSelections, selection) + for otherSelection in otherSelections + if selection.intersectsWith(otherSelection) + selection.merge(otherSelection) + @mergeIntersectingSelections() + return diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 74113edc2..067073cfc 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -166,7 +166,9 @@ class Editor extends View selectOnMousemoveUntilMouseup: -> moveHandler = (e) => @selectToScreenPosition(@screenPositionFromMouseEvent(e)) @on 'mousemove', moveHandler - $(document).one 'mouseup', => @off 'mousemove', moveHandler + $(document).one 'mouseup', => + @off 'mousemove', moveHandler + @compositeSelection.mergeIntersectingSelections() renderLines: -> @lineCache = [] diff --git a/src/atom/range.coffee b/src/atom/range.coffee index 2f9b99789..e17e0d973 100644 --- a/src/atom/range.coffee +++ b/src/atom/range.coffee @@ -40,6 +40,17 @@ class Range inspect: -> "[#{@start.inspect()} - #{@end.inspect()}]" + intersectsWith: (otherRange) -> + if @start.isLessThanOrEqual(otherRange.start) + @end.isGreaterThan(otherRange.start) + else + otherRange.intersectsWith(this) + + union: (otherRange) -> + start = if @start.isLessThan(otherRange.start) then @start else otherRange.start + end = if @end.isGreaterThan(otherRange.end) then @end else otherRange.end + new Range(start, end) + isEmpty: -> @start.isEqual(@end) diff --git a/src/atom/selection.coffee b/src/atom/selection.coffee index c69638b70..b266d9230 100644 --- a/src/atom/selection.coffee +++ b/src/atom/selection.coffee @@ -92,6 +92,17 @@ class Selection extends View isEmpty: -> @getBufferRange().isEmpty() + intersectsWith: (otherSelection) -> + @getScreenRange().intersectsWith(otherSelection.getScreenRange()) + + merge: (otherSelection) -> + @setScreenRange(@getScreenRange().union(otherSelection.getScreenRange())) + otherSelection.remove() + + remove: -> + @cursor?.remove() + super + modifySelection: (fn) -> @placeAnchor() @modifyingSelection = true