diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index a7ff07df3..cddb8bdf6 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1626,7 +1626,54 @@ describe "TextEditor", -> [[6, 22], [6, 28]] ] + it "can add selections to soft-wrapped line segments", -> + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(40) + + editor.setSelectedScreenRange([[3, 10], [3, 15]]) + editor.addSelectionBelow() + expect(editor.getSelectedScreenRanges()).toEqual [ + [[3, 10], [3, 15]] + [[4, 10], [4, 15]] + ] + + 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", -> + describe "when lines are soft-wrapped", -> + beforeEach -> + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(40) + + it "skips soft-wrap indentation tokens", -> + editor.setCursorScreenPosition([3, 0]) + editor.addSelectionBelow() + + expect(editor.getSelectedScreenRanges()).toEqual [ + [[3, 0], [3, 0]] + [[4, 4], [4, 4]] + ] + + it "does not skip them if they're shorter than the current column", -> + editor.setCursorScreenPosition([3, 37]) + editor.addSelectionBelow() + + expect(editor.getSelectedScreenRanges()).toEqual [ + [[3, 37], [3, 37]] + [[4, 26], [4, 26]] + ] + it "does not skip lines that are shorter than the current column", -> editor.setCursorBufferPosition([3, 36]) editor.addSelectionBelow() @@ -1690,7 +1737,54 @@ describe "TextEditor", -> [[3, 22], [3, 38]] ] + it "can add selections to soft-wrapped line segments", -> + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(40) + + editor.setSelectedScreenRange([[4, 10], [4, 15]]) + editor.addSelectionAbove() + expect(editor.getSelectedScreenRanges()).toEqual [ + [[4, 10], [4, 15]] + [[3, 10], [3, 15]] + ] + + 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", -> + describe "when lines are soft-wrapped", -> + beforeEach -> + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(40) + + it "skips soft-wrap indentation tokens", -> + editor.setCursorScreenPosition([5, 0]) + editor.addSelectionAbove() + + expect(editor.getSelectedScreenRanges()).toEqual [ + [[5, 0], [5, 0]] + [[4, 4], [4, 4]] + ] + + it "does not skip them if they're shorter than the current column", -> + editor.setCursorScreenPosition([5, 29]) + editor.addSelectionAbove() + + expect(editor.getSelectedScreenRanges()).toEqual [ + [[5, 29], [5, 29]] + [[4, 26], [4, 26]] + ] + it "does not skip lines that are shorter than the current column", -> editor.setCursorBufferPosition([6, 36]) editor.addSelectionAbove() 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 2f4a16005..c23d106fe 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(goalScreenRange: null) @marker.clearTail() unless @retainSelection @finalize() @@ -656,38 +656,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, skipSoftWrapIndentation: true) 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(clippedRange, 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, skipSoftWrapIndentation: true) 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(clippedRange, goalScreenRange: range) break # Public: Combines the given selection into this selection and then destroys @@ -696,12 +696,14 @@ class Selection extends Model # * `otherSelection` A {Selection} to merge with. # * `options` (optional) {Object} options matching those found in {::setBufferRange}. 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 + options.goalScreenRange = myGoalScreenRange ? otherGoalScreenRange + @setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options) otherSelection.destroy() @@ -763,6 +765,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) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index b5c8c5290..229335c11 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 ###