diff --git a/src/app/cursor.coffee b/src/app/cursor.coffee index fefc3f237..e571b2a05 100644 --- a/src/app/cursor.coffee +++ b/src/app/cursor.coffee @@ -134,9 +134,7 @@ class Cursor # Deselects whatever the cursor is selecting. clearSelection: -> - if @selection - @selection.goalBufferRange = null - @selection.clear() unless @selection.retainSelection + @selection?.clear() # Retrieves the cursor's screen row. # diff --git a/src/app/display-buffer.coffee b/src/app/display-buffer.coffee index d011fc883..8933bb740 100644 --- a/src/app/display-buffer.coffee +++ b/src/app/display-buffer.coffee @@ -487,6 +487,7 @@ class DisplayBuffer resumeMarkerObservers: -> marker.resumeEvents() for marker in @getMarkers() + @trigger 'markers-updated' refreshMarkerScreenPositions: -> for marker in @getMarkers() diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index a30342869..05f5e6fd7 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -34,6 +34,7 @@ class EditSession selections: null softTabs: true softWrap: false + suppressSelectionMerging: false constructor: (optionsOrState) -> @cursors = [] @@ -78,15 +79,14 @@ class EditSession @trigger "title-changed" @trigger "path-changed" @subscribe @buffer, "contents-conflicted", => @trigger "contents-conflicted" - @subscribe @buffer, "markers-updated", => @mergeCursors() @subscribe @buffer, "modified-status-changed", => @trigger "modified-status-changed" @preserveCursorPositionOnBufferReload() buildDisplayBuffer: ({tabLength}) -> @displayBuffer = new DisplayBuffer(@buffer, { tabLength }) @subscribe @displayBuffer, 'marker-created', @handleMarkerCreated - @subscribe @displayBuffer, "changed", (e) => - @trigger 'screen-lines-changed', e + @subscribe @displayBuffer, "changed", (e) => @trigger 'screen-lines-changed', e + @subscribe @displayBuffer, "markers-updated", => @mergeIntersectingSelections() @subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange() getViewClass: -> @@ -872,7 +872,7 @@ class EditSession selection = new Selection(_.extend({editSession: this, marker, cursor}, options)) @selections.push(selection) selectionBufferRange = selection.getBufferRange() - @mergeIntersectingSelections() unless options.suppressMerge + @mergeIntersectingSelections() if selection.destroyed for selection in @getSelections() if selection.intersectsBufferRange(selectionBufferRange) @@ -908,13 +908,13 @@ class EditSession selections = @getSelections() selection.destroy() for selection in selections[bufferRanges.length...] - for bufferRange, i in bufferRanges - bufferRange = Range.fromObject(bufferRange) - if selections[i] - selections[i].setBufferRange(bufferRange, options) - else - @addSelectionForBufferRange(bufferRange, options) - @mergeIntersectingSelections(options) + @mergeIntersectingSelections options, => + for bufferRange, i in bufferRanges + bufferRange = Range.fromObject(bufferRange) + if selections[i] + selections[i].setBufferRange(bufferRange, options) + else + @addSelectionForBufferRange(bufferRange, options) # Unselects a given selection. # @@ -1252,25 +1252,38 @@ class EditSession positions.push(position) expandSelectionsForward: (fn) -> - fn(selection) for selection in @getSelections() - @mergeIntersectingSelections() + @mergeIntersectingSelections => + fn(selection) for selection in @getSelections() expandSelectionsBackward: (fn) -> - fn(selection) for selection in @getSelections() - @mergeIntersectingSelections(isReversed: true) + @mergeIntersectingSelections isReversed: true, => + fn(selection) for selection in @getSelections() finalizeSelections: -> selection.finalize() for selection in @getSelections() - mergeIntersectingSelections: (options) -> - for selection in @getSelections() - otherSelections = @getSelections() - _.remove(otherSelections, selection) - for otherSelection in otherSelections - if selection.intersectsWith(otherSelection) - selection.merge(otherSelection, options) - @mergeIntersectingSelections(options) - return + # Merges intersecting selections. If passed a function, it executes the function + # with merging suppressed, then merges intersecting selections afterward. + mergeIntersectingSelections: (args...) -> + fn = args.pop() if _.isFunction(_.last(args)) + options = args.pop() ? {} + + return fn?() if @suppressSelectionMerging + + if fn? + @suppressSelectionMerging = true + result = fn() + @suppressSelectionMerging = false + + reducer = (disjointSelections, selection) -> + intersectingSelection = _.find(disjointSelections, (s) -> s.intersectsWith(selection)) + if intersectingSelection? + intersectingSelection.merge(selection, options) + disjointSelections + else + disjointSelections.concat([selection]) + + _.reduce(@getSelections(), reducer, []) preserveCursorPositionOnBufferReload: -> cursorPosition = null diff --git a/src/app/selection.coffee b/src/app/selection.coffee index c47bec1fa..25dda9bed 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -9,13 +9,12 @@ class Selection marker: null editSession: null initialScreenRange: null - goalBufferRange: null wordwise: false needsAutoscroll: null ### Internal ### - constructor: ({@cursor, @marker, @editSession, @goalBufferRange}) -> + constructor: ({@cursor, @marker, @editSession}) -> @cursor.selection = this @marker.on 'changed', => @screenRangeChanged() @marker.on 'destroyed', => @@ -109,7 +108,8 @@ class Selection # Clears the selection, moving the marker to move to the head. clear: -> - @marker.clearTail() + @setGoalBufferRange(null) + @marker.clearTail() unless @retainSelection # Modifies the selection to mark the current word. # @@ -213,7 +213,7 @@ class Selection # Moves the selection down one row. addSelectionBelow: -> - range = (@goalBufferRange ? @getBufferRange()).copy() + range = (@getGoalBufferRange() ? @getBufferRange()).copy() nextRow = range.end.row + 1 for row in [nextRow..@editSession.getLastBufferRow()] @@ -226,12 +226,18 @@ class Selection else continue if clippedRange.isEmpty() - @editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true) + @editSession.addSelectionForBufferRange(range, goalBufferRange: range) break + getGoalBufferRange: -> + @marker.getAttributes().goalBufferRange + + setGoalBufferRange: (goalBufferRange) -> + @marker.getAttributes().goalBufferRange = goalBufferRange + # Moves the selection up one row. addSelectionAbove: -> - range = (@goalBufferRange ? @getBufferRange()).copy() + range = (@getGoalBufferRange() ? @getBufferRange()).copy() previousRow = range.end.row - 1 for row in [previousRow..0] @@ -244,7 +250,7 @@ class Selection else continue if clippedRange.isEmpty() - @editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true) + @editSession.addSelectionForBufferRange(range, goalBufferRange: range) break # Replaces text at the current selection. @@ -509,10 +515,13 @@ class Selection # options - A hash of options matching those found in {.setBufferRange} merge: (otherSelection, options) -> @setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options) - if @goalBufferRange and otherSelection.goalBufferRange - @goalBufferRange = @goalBufferRange.union(otherSelection.goalBufferRange) - else if otherSelection.goalBufferRange - @goalBufferRange = otherSelection.goalBufferRange + + myGoalBufferRange = @getGoalBufferRange() + otherGoalBufferRange = otherSelection.getGoalBufferRange() + if myGoalBufferRange? and otherGoalBufferRange? + @setGoalBufferRange(myGoalBufferRange.union(otherGoalBufferRange)) + else if otherGoalBufferRange + @setGoalBufferRange(otherGoalBufferRange) otherSelection.destroy() ### Internal ### diff --git a/vendor/telepath b/vendor/telepath index 73dd98ba0..93c2eff34 160000 --- a/vendor/telepath +++ b/vendor/telepath @@ -1 +1 @@ -Subproject commit 73dd98ba087c2b1b59a6e67f3e2a4952f54c35ea +Subproject commit 93c2eff34a3600e0f6c8ea73f7ae01f1f713559d