From f4b791d33bbaa3599601c92a3d96f7960161b64c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 13 Mar 2015 10:57:45 +0100 Subject: [PATCH 1/7] Use screenPosition to select above and below --- src/display-buffer.coffee | 12 ++++++++++++ src/selection.coffee | 20 ++++++++++++-------- src/text-editor.coffee | 8 ++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 7845fa365..5659d38a0 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -859,6 +859,18 @@ class DisplayBuffer extends Model column = screenLine.clipScreenColumn(column, options) new Point(row, column) + # Clip the start and end of the given range to valid positions on screen. + # See {::clipScreenPosition} for more information. + # + # * `range` The {Range} to clip. + # * `options` (optional) See {::clipScreenPosition} `options`. + # Returns a {Range}. + clipScreenRange: (range, options) -> + start = @clipScreenPosition(range.start, options) + end = @clipScreenPosition(range.end, options) + + new Range(start, end) + # Calculates a {Range} representing the start of the {TextBuffer} until the end. # # Returns a {Range}. diff --git a/src/selection.coffee b/src/selection.coffee index 1ecd35157..c094265b9 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -183,7 +183,7 @@ class Selection extends Model # Public: Clears the selection, moving the marker to the head. clear: -> - @marker.setProperties(goalBufferRange: null) + @marker.setProperties(goalBufferRange: null, goalScreenRange: null) @marker.clearTail() unless @retainSelection @finalize() @@ -657,38 +657,38 @@ class Selection extends Model # Public: Moves the selection down one row. addSelectionBelow: -> - range = (@getGoalBufferRange() ? @getBufferRange()).copy() + range = (@getGoalScreenRange() ? @getScreenRange()).copy() nextRow = range.end.row + 1 - for row in [nextRow..@editor.getLastBufferRow()] + for row in [nextRow..@editor.getLastScreenRow()] range.start.row = row range.end.row = row - clippedRange = @editor.clipBufferRange(range) + clippedRange = @editor.clipScreenRange(range) if range.isEmpty() continue if range.end.column > 0 and clippedRange.end.column is 0 else continue if clippedRange.isEmpty() - @editor.addSelectionForBufferRange(range, goalBufferRange: range) + @editor.addSelectionForScreenRange(range, goalScreenRange: range) break # Public: Moves the selection up one row. addSelectionAbove: -> - range = (@getGoalBufferRange() ? @getBufferRange()).copy() + range = (@getGoalScreenRange() ? @getScreenRange()).copy() previousRow = range.end.row - 1 for row in [previousRow..0] range.start.row = row range.end.row = row - clippedRange = @editor.clipBufferRange(range) + clippedRange = @editor.clipScreenRange(range) if range.isEmpty() continue if range.end.column > 0 and clippedRange.end.column is 0 else continue if clippedRange.isEmpty() - @editor.addSelectionForBufferRange(range, goalBufferRange: range) + @editor.addSelectionForScreenRange(range, goalScreenRange: range) break # Public: Combines the given selection into this selection and then destroys @@ -767,3 +767,7 @@ class Selection extends Model getGoalBufferRange: -> if goalBufferRange = @marker.getProperties().goalBufferRange Range.fromObject(goalBufferRange) + + getGoalScreenRange: -> + if goalScreenRange = @marker.getProperties().goalScreenRange + Range.fromObject(goalScreenRange) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 44389db17..d22d84649 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1275,6 +1275,14 @@ class TextEditor extends Model # Returns a {Point}. clipScreenPosition: (screenPosition, options) -> @displayBuffer.clipScreenPosition(screenPosition, options) + # Extended: Clip the start and end of the given range to valid positions on screen. + # See {::clipScreenPosition} for more information. + # + # * `range` The {Range} to clip. + # * `options` (optional) See {::clipScreenPosition} `options`. + # Returns a {Range}. + clipScreenRange: (range, options) -> @displayBuffer.clipScreenRange(range, options) + ### Section: Decorations ### From 02ad2e8ff7876a25582aa959f0026ac5dd3df167 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 13 Mar 2015 11:32:42 +0100 Subject: [PATCH 2/7] :white_check_mark: Write specs for soft-wrapped lines selection --- spec/text-editor-spec.coffee | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index c03990bf2..2d7a8e5a9 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1623,7 +1623,31 @@ describe "TextEditor", -> [[6, 22], [6, 28]] ] + it "selects also soft-wrapped lines", -> + editor.setSoftWrapped(true) + editor.setDefaultCharWidth(10) + editor.setEditorWidthInChars(40) + + editor.setSelectedScreenRange([[6, 20], [6, 25]]) + editor.addSelectionBelow() + expect(editor.getSelectedScreenRanges()).toEqual [ + [[6, 20], [6, 25]] + [[7, 20], [7, 25]] + ] + describe "when the selection is empty", -> + it "does not skip soft-wrapped lines shorter than the current column", -> + editor.setSoftWrapped(true) + editor.setDefaultCharWidth(10) + editor.setEditorWidthInChars(40) + + editor.setCursorScreenPosition([6, 44]) + editor.addSelectionBelow() + expect(editor.getSelectedScreenRanges()).toEqual [ + [[6, 44], [6, 44]] + [[7, 26], [7, 26]] + ] + it "does not skip lines that are shorter than the current column", -> editor.setCursorBufferPosition([3, 36]) editor.addSelectionBelow() @@ -1687,7 +1711,31 @@ describe "TextEditor", -> [[3, 22], [3, 38]] ] + it "selects also soft-wrapped lines", -> + editor.setSoftWrapped(true) + editor.setDefaultCharWidth(10) + editor.setEditorWidthInChars(40) + + editor.setSelectedScreenRange([[7, 20], [7, 25]]) + editor.addSelectionAbove() + expect(editor.getSelectedScreenRanges()).toEqual [ + [[7, 20], [7, 25]] + [[6, 20], [6, 25]] + ] + describe "when the selection is empty", -> + it "does not skip soft-wrapped lines shorter than the current column", -> + editor.setSoftWrapped(true) + editor.setDefaultCharWidth(10) + editor.setEditorWidthInChars(40) + + editor.setCursorScreenPosition([6, 44]) + editor.addSelectionAbove() + expect(editor.getSelectedScreenRanges()).toEqual [ + [[6, 44], [6, 44]] + [[5, 30], [5, 30]] + ] + it "does not skip lines that are shorter than the current column", -> editor.setCursorBufferPosition([6, 36]) editor.addSelectionAbove() From c319b804642869758e6994a82420974debb4e28f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 13 Mar 2015 11:56:00 +0100 Subject: [PATCH 3/7] :white_check_mark: Write specs for atomic tokens --- spec/text-editor-spec.coffee | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 2d7a8e5a9..833128085 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1635,6 +1635,19 @@ describe "TextEditor", -> [[7, 20], [7, 25]] ] + it "takes atomic tokens into account", -> + waitsForPromise -> + atom.project.open('sample-with-tabs-and-leading-comment.coffee', autoIndent: false).then (o) -> editor = o + + runs -> + editor.setSelectedBufferRange([[2, 1], [2, 3]]) + editor.addSelectionBelow() + + expect(editor.getSelectedBufferRanges()).toEqual [ + [[2, 1], [2, 3]] + [[3, 1], [3, 2]] + ] + describe "when the selection is empty", -> it "does not skip soft-wrapped lines shorter than the current column", -> editor.setSoftWrapped(true) @@ -1723,6 +1736,19 @@ describe "TextEditor", -> [[6, 20], [6, 25]] ] + it "takes atomic tokens into account", -> + waitsForPromise -> + atom.project.open('sample-with-tabs-and-leading-comment.coffee', autoIndent: false).then (o) -> editor = o + + runs -> + editor.setSelectedBufferRange([[3, 1], [3, 2]]) + editor.addSelectionAbove() + + expect(editor.getSelectedBufferRanges()).toEqual [ + [[3, 1], [3, 2]] + [[2, 1], [2, 3]] + ] + describe "when the selection is empty", -> it "does not skip soft-wrapped lines shorter than the current column", -> editor.setSoftWrapped(true) From 8ac4848805f67c6640f8d7fd791caf31306419ca Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 13 Mar 2015 12:05:18 +0100 Subject: [PATCH 4/7] Skip soft-wrap indentation tokens while selecting * :art: Restructure specs a bit * :white_check_mark: Write specs for this new behavior --- spec/text-editor-spec.coffee | 64 +++++++++++++++++++++++++----------- src/selection.coffee | 8 ++--- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 833128085..afbc8aa7d 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1649,17 +1649,29 @@ describe "TextEditor", -> ] describe "when the selection is empty", -> - it "does not skip soft-wrapped lines shorter than the current column", -> - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(10) - editor.setEditorWidthInChars(40) + describe "when lines are soft-wrapped", -> + beforeEach -> + editor.setSoftWrapped(true) + editor.setDefaultCharWidth(10) + editor.setEditorWidthInChars(40) - editor.setCursorScreenPosition([6, 44]) - editor.addSelectionBelow() - expect(editor.getSelectedScreenRanges()).toEqual [ - [[6, 44], [6, 44]] - [[7, 26], [7, 26]] - ] + it "skips soft-wrap indentation tokens", -> + editor.setCursorScreenPosition([6, 2]) + editor.addSelectionBelow() + + expect(editor.getSelectedScreenRanges()).toEqual [ + [[6, 2], [6, 2]] + [[7, 6], [7, 6]] + ] + + it "does not skip them if they're shorter than the current column", -> + editor.setCursorScreenPosition([6, 44]) + editor.addSelectionBelow() + + expect(editor.getSelectedScreenRanges()).toEqual [ + [[6, 44], [6, 44]] + [[7, 26], [7, 26]] + ] it "does not skip lines that are shorter than the current column", -> editor.setCursorBufferPosition([3, 36]) @@ -1750,17 +1762,29 @@ describe "TextEditor", -> ] describe "when the selection is empty", -> - it "does not skip soft-wrapped lines shorter than the current column", -> - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(10) - editor.setEditorWidthInChars(40) + describe "when lines are soft-wrapped", -> + beforeEach -> + editor.setSoftWrapped(true) + editor.setDefaultCharWidth(10) + editor.setEditorWidthInChars(40) - editor.setCursorScreenPosition([6, 44]) - editor.addSelectionAbove() - expect(editor.getSelectedScreenRanges()).toEqual [ - [[6, 44], [6, 44]] - [[5, 30], [5, 30]] - ] + it "skips soft-wrap indentation tokens", -> + editor.setCursorScreenPosition([8, 0]) + editor.addSelectionAbove() + + expect(editor.getSelectedScreenRanges()).toEqual [ + [[8, 0], [8, 0]] + [[7, 6], [7, 6]] + ] + + it "does not skip them if they're shorter than the current column", -> + editor.setCursorScreenPosition([6, 44]) + editor.addSelectionAbove() + + expect(editor.getSelectedScreenRanges()).toEqual [ + [[6, 44], [6, 44]] + [[5, 30], [5, 30]] + ] it "does not skip lines that are shorter than the current column", -> editor.setCursorBufferPosition([6, 36]) diff --git a/src/selection.coffee b/src/selection.coffee index c094265b9..30a64d873 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -663,14 +663,14 @@ class Selection extends Model for row in [nextRow..@editor.getLastScreenRow()] range.start.row = row range.end.row = row - clippedRange = @editor.clipScreenRange(range) + clippedRange = @editor.clipScreenRange(range, skipSoftWrapIndentation: true) if range.isEmpty() continue if range.end.column > 0 and clippedRange.end.column is 0 else continue if clippedRange.isEmpty() - @editor.addSelectionForScreenRange(range, goalScreenRange: range) + @editor.addSelectionForScreenRange(clippedRange, goalScreenRange: range) break # Public: Moves the selection up one row. @@ -681,14 +681,14 @@ class Selection extends Model for row in [previousRow..0] range.start.row = row range.end.row = row - clippedRange = @editor.clipScreenRange(range) + clippedRange = @editor.clipScreenRange(range, skipSoftWrapIndentation: true) if range.isEmpty() continue if range.end.column > 0 and clippedRange.end.column is 0 else continue if clippedRange.isEmpty() - @editor.addSelectionForScreenRange(range, goalScreenRange: range) + @editor.addSelectionForScreenRange(clippedRange, goalScreenRange: range) break # Public: Combines the given selection into this selection and then destroys From bfe8f7c7406bd42ec3f8429b8bb1c3391fb8e288 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 14 Mar 2015 09:26:29 +0100 Subject: [PATCH 5/7] Fix soft-wrapping specs --- spec/text-editor-spec.coffee | 44 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index afbc8aa7d..fddaf573d 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1623,16 +1623,15 @@ describe "TextEditor", -> [[6, 22], [6, 28]] ] - it "selects also soft-wrapped lines", -> + it "can add selections to soft-wrapped line segments", -> editor.setSoftWrapped(true) - editor.setDefaultCharWidth(10) editor.setEditorWidthInChars(40) - editor.setSelectedScreenRange([[6, 20], [6, 25]]) + editor.setSelectedScreenRange([[3, 10], [3, 15]]) editor.addSelectionBelow() expect(editor.getSelectedScreenRanges()).toEqual [ - [[6, 20], [6, 25]] - [[7, 20], [7, 25]] + [[3, 10], [3, 15]] + [[4, 10], [4, 15]] ] it "takes atomic tokens into account", -> @@ -1652,25 +1651,24 @@ describe "TextEditor", -> describe "when lines are soft-wrapped", -> beforeEach -> editor.setSoftWrapped(true) - editor.setDefaultCharWidth(10) editor.setEditorWidthInChars(40) it "skips soft-wrap indentation tokens", -> - editor.setCursorScreenPosition([6, 2]) + editor.setCursorScreenPosition([3, 0]) editor.addSelectionBelow() expect(editor.getSelectedScreenRanges()).toEqual [ - [[6, 2], [6, 2]] - [[7, 6], [7, 6]] + [[3, 0], [3, 0]] + [[4, 4], [4, 4]] ] it "does not skip them if they're shorter than the current column", -> - editor.setCursorScreenPosition([6, 44]) + editor.setCursorScreenPosition([3, 37]) editor.addSelectionBelow() expect(editor.getSelectedScreenRanges()).toEqual [ - [[6, 44], [6, 44]] - [[7, 26], [7, 26]] + [[3, 37], [3, 37]] + [[4, 26], [4, 26]] ] it "does not skip lines that are shorter than the current column", -> @@ -1736,16 +1734,15 @@ describe "TextEditor", -> [[3, 22], [3, 38]] ] - it "selects also soft-wrapped lines", -> + it "can add selections to soft-wrapped line segments", -> editor.setSoftWrapped(true) - editor.setDefaultCharWidth(10) editor.setEditorWidthInChars(40) - editor.setSelectedScreenRange([[7, 20], [7, 25]]) + editor.setSelectedScreenRange([[4, 10], [4, 15]]) editor.addSelectionAbove() expect(editor.getSelectedScreenRanges()).toEqual [ - [[7, 20], [7, 25]] - [[6, 20], [6, 25]] + [[4, 10], [4, 15]] + [[3, 10], [3, 15]] ] it "takes atomic tokens into account", -> @@ -1765,25 +1762,24 @@ describe "TextEditor", -> describe "when lines are soft-wrapped", -> beforeEach -> editor.setSoftWrapped(true) - editor.setDefaultCharWidth(10) editor.setEditorWidthInChars(40) it "skips soft-wrap indentation tokens", -> - editor.setCursorScreenPosition([8, 0]) + editor.setCursorScreenPosition([5, 0]) editor.addSelectionAbove() expect(editor.getSelectedScreenRanges()).toEqual [ - [[8, 0], [8, 0]] - [[7, 6], [7, 6]] + [[5, 0], [5, 0]] + [[4, 4], [4, 4]] ] it "does not skip them if they're shorter than the current column", -> - editor.setCursorScreenPosition([6, 44]) + editor.setCursorScreenPosition([5, 29]) editor.addSelectionAbove() expect(editor.getSelectedScreenRanges()).toEqual [ - [[6, 44], [6, 44]] - [[5, 30], [5, 30]] + [[5, 29], [5, 29]] + [[4, 26], [4, 26]] ] it "does not skip lines that are shorter than the current column", -> From 0a23a219534327b60736dcaa0eb53f15fae8fbfb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 14 Mar 2015 09:48:19 +0100 Subject: [PATCH 6/7] :art: Get rid of goalBufferRange --- spec/text-editor-spec.coffee | 4 ++-- src/selection.coffee | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index fddaf573d..6154af055 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1456,8 +1456,8 @@ describe "TextEditor", -> expect(editor.getSelectedBufferRanges()).toEqual [[[5, 5], [6, 6]]] it "merges intersecting selections", -> - editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 5]]]) - expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [5, 5]]] + editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 4]]]) + expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [5, 4]]] it "does not merge non-empty adjacent selections", -> editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 3], [5, 5]]]) diff --git a/src/selection.coffee b/src/selection.coffee index 30a64d873..d4538f9dc 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -183,7 +183,7 @@ class Selection extends Model # Public: Clears the selection, moving the marker to the head. clear: -> - @marker.setProperties(goalBufferRange: null, goalScreenRange: null) + @marker.setProperties(goalScreenRange: null) @marker.clearTail() unless @retainSelection @finalize() @@ -695,15 +695,17 @@ class Selection extends Model # the given selection. # # * `otherSelection` A {Selection} to merge with. - # * `options` (optional) {Object} options matching those found in {::setBufferRange}. + # * `options` (optional) {Object} options matching those found in {::setScreenRange}. merge: (otherSelection, options) -> - myGoalBufferRange = @getGoalBufferRange() - otherGoalBufferRange = otherSelection.getGoalBufferRange() - if myGoalBufferRange? and otherGoalBufferRange? - options.goalBufferRange = myGoalBufferRange.union(otherGoalBufferRange) + myGoalScreenRange = @getGoalScreenRange() + otherGoalScreenRange = otherSelection.getGoalScreenRange() + + if myGoalScreenRange? and otherGoalScreenRange? + options.goalScreenRange = myGoalScreenRange.union(otherGoalScreenRange) else - options.goalBufferRange = myGoalBufferRange ? otherGoalBufferRange - @setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options) + options.goalScreenRange = myGoalScreenRange ? otherGoalScreenRange + + @setScreenRange(@getScreenRange().union(otherSelection.getScreenRange()), options) otherSelection.destroy() ### @@ -764,10 +766,6 @@ class Selection extends Model plantTail: -> @marker.plantTail() - getGoalBufferRange: -> - if goalBufferRange = @marker.getProperties().goalBufferRange - Range.fromObject(goalBufferRange) - getGoalScreenRange: -> if goalScreenRange = @marker.getProperties().goalScreenRange Range.fromObject(goalScreenRange) From 6633c90af870c1c361da4ea59e5094b9289e6ae4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 14 Mar 2015 10:05:04 +0100 Subject: [PATCH 7/7] Use buffer ranges when merging selections --- spec/text-editor-spec.coffee | 4 ++-- src/selection.coffee | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 6154af055..fddaf573d 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1456,8 +1456,8 @@ describe "TextEditor", -> expect(editor.getSelectedBufferRanges()).toEqual [[[5, 5], [6, 6]]] it "merges intersecting selections", -> - editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 4]]]) - expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [5, 4]]] + editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 5]]]) + expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [5, 5]]] it "does not merge non-empty adjacent selections", -> editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 3], [5, 5]]]) diff --git a/src/selection.coffee b/src/selection.coffee index d4538f9dc..cada3aa6c 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -695,7 +695,7 @@ class Selection extends Model # the given selection. # # * `otherSelection` A {Selection} to merge with. - # * `options` (optional) {Object} options matching those found in {::setScreenRange}. + # * `options` (optional) {Object} options matching those found in {::setBufferRange}. merge: (otherSelection, options) -> myGoalScreenRange = @getGoalScreenRange() otherGoalScreenRange = otherSelection.getGoalScreenRange() @@ -705,7 +705,7 @@ class Selection extends Model else options.goalScreenRange = myGoalScreenRange ? otherGoalScreenRange - @setScreenRange(@getScreenRange().union(otherSelection.getScreenRange()), options) + @setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options) otherSelection.destroy() ###