Update text buffer to use telepath markers

This commit is contained in:
Kevin Sawicki & Nathan Sobo
2013-07-03 18:06:02 -06:00
committed by probablycorey
parent 010fa219aa
commit abc20b3a05
21 changed files with 128 additions and 861 deletions

View File

@@ -19,7 +19,7 @@ class BufferChangeOperation
do: ->
@buffer.pauseEvents()
@pauseMarkerObservation()
@markersToRestoreOnUndo = @invalidateMarkers(@oldRange)
# @markersToRestoreOnUndo = @invalidateMarkers(@oldRange)
if @oldRange?
@oldText = @buffer.getTextInRange(@oldRange)
@newRange = Range.fromText(@oldRange.start, @newText)
@@ -28,7 +28,7 @@ class BufferChangeOperation
newRange: @newRange
oldText: @oldText
newText: @newText
@restoreMarkers(@markersToRestoreOnRedo) if @markersToRestoreOnRedo
# @restoreMarkers(@markersToRestoreOnRedo) if @markersToRestoreOnRedo
@buffer.resumeEvents()
@resumeMarkerObservation()
newRange
@@ -36,14 +36,14 @@ class BufferChangeOperation
undo: ->
@buffer.pauseEvents()
@pauseMarkerObservation()
@markersToRestoreOnRedo = @invalidateMarkers(@newRange)
# @markersToRestoreOnRedo = @invalidateMarkers(@newRange)
if @oldRange?
@changeBuffer
oldRange: @newRange
newRange: @oldRange
oldText: @newText
newText: @oldText
@restoreMarkers(@markersToRestoreOnUndo)
# @restoreMarkers(@markersToRestoreOnUndo)
@buffer.resumeEvents()
@resumeMarkerObservation()

View File

@@ -1,255 +0,0 @@
_ = require 'underscore'
{Point, Range} = require 'telepath'
EventEmitter = require 'event-emitter'
module.exports =
class BufferMarker
headPosition: null
tailPosition: null
suppressObserverNotification: false
invalidationStrategy: null
### Internal ###
constructor: ({@id, @buffer, range, @invalidationStrategy, @attributes, noTail, reverse}) ->
@invalidationStrategy ?= 'contains'
@setRange(range, {noTail, reverse})
### Public ###
# Sets the marker's range, potentialy modifying both its head and tail.
#
# range - The new {Range} the marker should cover
# options - A hash of options with the following keys:
# reverse: if `true`, the marker is reversed; that is, its tail is "above" the head
# noTail: if `true`, the marker doesn't have a tail
setRange: (range, options={}) ->
@consolidateObserverNotifications false, =>
range = Range.fromObject(range)
if options.reverse
@setTailPosition(range.end) unless options.noTail
@setHeadPosition(range.start)
else
@setTailPosition(range.start) unless options.noTail
@setHeadPosition(range.end)
# Identifies if the ending position of a marker is greater than the starting position.
#
# This can happen when, for example, you highlight text "up" in a {Buffer}.
#
# Returns a {Boolean}.
isReversed: ->
@tailPosition? and @headPosition.isLessThan(@tailPosition)
# Checks that the marker's attributes match the given attributes
#
# Returns a {Boolean}.
matchesAttributes: (queryAttributes) ->
for key, value of queryAttributes
switch key
when 'startRow'
return false unless @getRange().start.row == value
when 'endRow'
return false unless @getRange().end.row == value
when 'containsRange'
return false unless @getRange().containsRange(value, exclusive: true)
when 'containsRow'
return false unless @getRange().containsRow(value)
else
return false unless _.isEqual(@attributes[key], value)
true
# Identifies if the marker's head position is equal to its tail.
#
# Returns a {Boolean}.
isRangeEmpty: ->
@getHeadPosition().isEqual(@getTailPosition())
# Retrieves the {Range} between a marker's head and its tail.
#
# Returns a {Range}.
getRange: ->
if @tailPosition
new Range(@getTailPosition(), @getHeadPosition())
else
new Range(@getHeadPosition(), @getHeadPosition())
# Retrieves the position of the marker's head.
#
# Returns a {Point}.
getHeadPosition: -> @headPosition?.copy()
# Retrieves the position of the marker's tail.
#
# Returns a {Point}.
getTailPosition: -> @tailPosition?.copy() ? @getHeadPosition()
# Sets the position of the marker's head.
#
# newHeadPosition - The new {Point} to place the head
# options - A hash with the following keys:
# clip: if `true`, the point is [clipped]{Buffer.clipPosition}
# bufferChanged: if `true`, indicates that the {Buffer} should trigger an event that it's changed
#
# Returns a {Point} representing the new head position.
setHeadPosition: (newHeadPosition, options={}) ->
oldHeadPosition = @getHeadPosition()
newHeadPosition = Point.fromObject(newHeadPosition)
newHeadPosition = @buffer.clipPosition(newHeadPosition) if options.clip ? true
return if newHeadPosition.isEqual(@headPosition)
@headPosition = newHeadPosition
bufferChanged = !!options.bufferChanged
@notifyObservers({oldHeadPosition, newHeadPosition, bufferChanged})
@headPosition
# Sets the position of the marker's tail.
#
# newHeadPosition - The new {Point} to place the tail
# options - A hash with the following keys:
# clip: if `true`, the point is [clipped]{Buffer.clipPosition}
# bufferChanged: if `true`, indicates that the {Buffer} should trigger an event that it's changed
#
# Returns a {Point} representing the new tail position.
setTailPosition: (newTailPosition, options={}) ->
oldTailPosition = @getTailPosition()
newTailPosition = Point.fromObject(newTailPosition)
newTailPosition = @buffer.clipPosition(newTailPosition) if options.clip ? true
return if newTailPosition.isEqual(@tailPosition)
@tailPosition = newTailPosition
bufferChanged = !!options.bufferChanged
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged})
@tailPosition
# Retrieves the starting position of the marker.
#
# Returns a {Point}.
getStartPosition: ->
@getRange().start
# Retrieves the ending position of the marker.
#
# Returns a {Point}.
getEndPosition: ->
@getRange().end
# Sets the marker's tail to the same position as the marker's head.
#
# This only works if there isn't already a tail position.
#
# Returns a {Point} representing the new tail position.
placeTail: ->
@setTailPosition(@getHeadPosition()) unless @tailPosition
# Removes the tail from the marker.
clearTail: ->
oldTailPosition = @getTailPosition()
@tailPosition = null
newTailPosition = @getTailPosition()
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false})
# Identifies if a {Point} is within the marker.
#
# Returns a {Boolean}.
containsPoint: (point) ->
@getRange().containsPoint(point)
# Destroys the marker
destroy: ->
@buffer.destroyMarker(@id)
@trigger 'destroyed'
# Returns a {Boolean} indicating whether the marker is valid. Markers can be
# invalidated when a region surrounding them in the buffer is changed.
isValid: ->
@buffer.getMarker(@id)?
# Returns a {Boolean} indicating whether the marker has been destroyed. A marker
# can be invalid without being destroyed, in which case undoing the invalidating
# operation would restore the marker. Once a marker is destroyed by calling
# {BufferMarker.destroy}, no undo/redo operation can ever bring it back.
isDestroyed: ->
not (@buffer.validMarkers[@id]? or @buffer.invalidMarkers[@id]?)
### Internal ###
tryToInvalidate: (changedRange) ->
previousRange = @getRange()
if changedRange
betweenStartAndEnd = @getRange().containsRange(changedRange, exclusive: false)
containsStart = changedRange.containsPoint(@getStartPosition(), exclusive: true)
containsEnd = changedRange.containsPoint(@getEndPosition(), exclusive: true)
switch @invalidationStrategy
when 'between'
@invalidate() if betweenStartAndEnd or containsStart or containsEnd
when 'contains'
@invalidate() if containsStart or containsEnd
when 'never'
if containsStart or containsEnd
if containsStart and containsEnd
@setRange([changedRange.end, changedRange.end])
else if containsStart
@setRange([changedRange.end, @getEndPosition()])
else
@setRange([@getStartPosition(), changedRange.start])
[@id, previousRange]
handleBufferChange: (bufferChange) ->
@consolidateObserverNotifications true, =>
@setHeadPosition(@updatePosition(@headPosition, bufferChange, true), clip: false, bufferChanged: true)
@setTailPosition(@updatePosition(@tailPosition, bufferChange, false), clip: false, bufferChanged: true) if @tailPosition
updatePosition: (position, bufferChange, isHead) ->
{ oldRange, newRange } = bufferChange
return position if not isHead and oldRange.start.isEqual(position)
return position if position.isLessThan(oldRange.end)
newRow = newRange.end.row
newColumn = newRange.end.column
if position.row == oldRange.end.row
newColumn += position.column - oldRange.end.column
else
newColumn = position.column
newRow += position.row - oldRange.end.row
[newRow, newColumn]
notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged} = {}) ->
return if @suppressObserverNotification
if newHeadPosition? and newTailPosition?
return if _.isEqual(newHeadPosition, oldHeadPosition) and _.isEqual(newTailPosition, oldTailPosition)
else if newHeadPosition?
return if _.isEqual(newHeadPosition, oldHeadPosition)
else if newTailPosition?
return if _.isEqual(newTailPosition, oldTailPosition)
oldHeadPosition ?= @getHeadPosition()
newHeadPosition ?= @getHeadPosition()
oldTailPosition ?= @getTailPosition()
newTailPosition ?= @getTailPosition()
valid = @isValid()
@trigger 'changed', {oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}
consolidateObserverNotifications: (bufferChanged, fn) ->
@suppressObserverNotification = true
oldHeadPosition = @getHeadPosition()
oldTailPosition = @getTailPosition()
fn()
newHeadPosition = @getHeadPosition()
newTailPosition = @getTailPosition()
@suppressObserverNotification = false
@notifyObservers({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged})
invalidate: ->
delete @buffer.validMarkers[@id]
@buffer.invalidMarkers[@id] = this
@notifyObservers(bufferChanged: true)
revalidate: ->
delete @buffer.invalidMarkers[@id]
@buffer.validMarkers[@id] = this
@notifyObservers(bufferChanged: true)
_.extend BufferMarker.prototype, EventEmitter

View File

@@ -21,17 +21,17 @@ class Cursor
@updateVisibility()
{oldHeadScreenPosition, newHeadScreenPosition} = e
{oldHeadBufferPosition, newHeadBufferPosition} = e
{bufferChanged} = e
{textChanged} = e
return if oldHeadScreenPosition.isEqual(newHeadScreenPosition)
@needsAutoscroll ?= @isLastCursor() and !bufferChanged
@needsAutoscroll ?= @isLastCursor() and !textChanged
movedEvent =
oldBufferPosition: oldHeadBufferPosition
oldScreenPosition: oldHeadScreenPosition
newBufferPosition: newHeadBufferPosition
newScreenPosition: newHeadScreenPosition
bufferChanged: bufferChanged
textChanged: textChanged
@trigger 'moved', movedEvent
@editSession.trigger 'cursor-moved', movedEvent

View File

@@ -6,15 +6,24 @@ Subscriber = require 'subscriber'
module.exports =
class DisplayBufferMarker
bufferMarkerSubscription: null
headScreenPosition: null
tailScreenPosition: null
valid: true
oldHeadBufferPosition: null
oldHeadScreenPosition: null
oldTailBufferPosition: null
oldTailScreenPosition: null
wasValid: true
### Internal ###
constructor: ({@bufferMarker, @displayBuffer}) ->
@id = @bufferMarker.id
@observeBufferMarker()
@oldHeadBufferPosition = @getHeadBufferPosition()
@oldHeadScreenPosition = @getHeadScreenPosition()
@oldTailBufferPosition = @getTailBufferPosition()
@oldTailScreenPosition = @getTailScreenPosition()
@wasValid = @isValid()
@subscribe @bufferMarker, 'destroyed', => @destroyed()
@subscribe @bufferMarker, 'changed', (event) => @notifyObservers(event)
### Public ###
@@ -48,7 +57,7 @@ class DisplayBufferMarker
#
# Returns a {Point}.
getHeadScreenPosition: ->
@headScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
@displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
# Sets the screen position of the marker's head.
#
@@ -75,7 +84,7 @@ class DisplayBufferMarker
#
# Returns a {Point}.
getTailScreenPosition: ->
@tailScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
@displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
# Sets the screen position of the marker's tail.
#
@@ -103,8 +112,8 @@ class DisplayBufferMarker
# This only works if there isn't already a tail position.
#
# Returns a {Point} representing the new tail position.
placeTail: ->
@bufferMarker.placeTail()
plantTail: ->
@bufferMarker.plantTail()
# Removes the tail from the marker.
clearTail: ->
@@ -144,54 +153,37 @@ class DisplayBufferMarker
delete @displayBuffer.markers[@id]
@trigger 'destroyed'
observeBufferMarker: ->
@subscribe @bufferMarker, 'destroyed', => @destroyed()
notifyObservers: ({textChanged}) ->
textChanged ?= false
@getHeadScreenPosition() # memoize current value
@getTailScreenPosition() # memoize current value
@subscribe @bufferMarker, 'changed', ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}) =>
@notifyObservers
oldHeadBufferPosition: oldHeadPosition
newHeadBufferPosition: newHeadPosition
oldTailBufferPosition: oldTailPosition
newTailBufferPosition: newTailPosition
bufferChanged: bufferChanged
valid: valid
newHeadBufferPosition = @getHeadBufferPosition()
newHeadScreenPosition = @getHeadScreenPosition()
newTailBufferPosition = @getTailBufferPosition()
newTailScreenPosition = @getTailScreenPosition()
isValid = @isValid()
notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged, valid} = {}) ->
return unless @valid or @isValid()
oldHeadScreenPosition = @getHeadScreenPosition()
newHeadScreenPosition = oldHeadScreenPosition
oldTailScreenPosition = @getTailScreenPosition()
newTailScreenPosition = oldTailScreenPosition
valid ?= true
if valid
@headScreenPosition = null
newHeadScreenPosition = @getHeadScreenPosition()
@tailScreenPosition = null
newTailScreenPosition = @getTailScreenPosition()
validChanged = valid isnt @valid
headScreenPositionChanged = not _.isEqual(newHeadScreenPosition, oldHeadScreenPosition)
tailScreenPositionChanged = not _.isEqual(newTailScreenPosition, oldTailScreenPosition)
return unless validChanged or headScreenPositionChanged or tailScreenPositionChanged
oldHeadBufferPosition ?= @getHeadBufferPosition()
newHeadBufferPosition = @getHeadBufferPosition() ? oldHeadBufferPosition
oldTailBufferPosition ?= @getTailBufferPosition()
newTailBufferPosition = @getTailBufferPosition() ? oldTailBufferPosition
@valid = valid
changed = false
changed = true unless _.isEqual(newHeadBufferPosition, @oldHeadBufferPosition)
changed = true unless _.isEqual(newHeadScreenPosition, @oldHeadScreenPosition)
changed = true unless _.isEqual(newTailBufferPosition, @oldTailBufferPosition)
changed = true unless _.isEqual(newTailScreenPosition, @oldTailScreenPosition)
changed = true unless _.isEqual(isValid, @wasValid)
return unless changed
@trigger 'changed', {
oldHeadScreenPosition, newHeadScreenPosition,
oldTailScreenPosition, newTailScreenPosition,
oldHeadBufferPosition, newHeadBufferPosition,
oldTailBufferPosition, newTailBufferPosition,
bufferChanged
valid
@oldHeadScreenPosition, newHeadScreenPosition,
@oldTailScreenPosition, newTailScreenPosition,
@oldHeadBufferPosition, newHeadBufferPosition,
@oldTailBufferPosition, newTailBufferPosition,
textChanged,
isValid
}
@oldHeadBufferPosition = newHeadBufferPosition
@oldHeadScreenPosition = newHeadScreenPosition
@oldTailBufferPosition = newTailBufferPosition
@oldTailScreenPosition = newTailScreenPosition
@wasValid = isValid
_.extend DisplayBufferMarker.prototype, EventEmitter
_.extend DisplayBufferMarker.prototype, Subscriber

View File

@@ -466,12 +466,11 @@ class DisplayBuffer
#
# Returns an {Array} of {DisplayBufferMarker}s
findMarkers: (attributes) ->
{ startBufferRow, endBufferRow, containsBufferRange, containsBufferRow } = attributes
{ startBufferRow, endBufferRow, containsBufferRange } = attributes
attributes.startRow = startBufferRow if startBufferRow?
attributes.endRow = endBufferRow if endBufferRow?
attributes.containsRange = containsBufferRange if containsBufferRange?
attributes.containsRow = containsBufferRow if containsBufferRow?
attributes = _.omit(attributes, ['startBufferRow', 'endBufferRow', 'containsBufferRange', 'containsBufferRow'])
attributes = _.omit(attributes, ['startBufferRow', 'endBufferRow', 'containsBufferRange'])
@buffer.findMarkers(attributes).map ({id}) => @getMarker(id)
findFoldMarker: (attributes) ->
@@ -491,7 +490,7 @@ class DisplayBuffer
refreshMarkerScreenPositions: ->
for marker in @getMarkers()
marker.notifyObservers(bufferChanged: false)
marker.notifyObservers(textChanged: false)
destroy: ->
marker.unsubscribe() for marker in @getMarkers()

View File

@@ -74,7 +74,7 @@ class EditSession
@displayBuffer.on 'grammar-changed', => @handleGrammarChange()
@state.observe ({key, newValue}) =>
@state.on 'changed', ({key, newValue}) =>
switch key
when 'scrollTop'
@trigger 'scroll-top-changed', newValue
@@ -1112,7 +1112,7 @@ class EditSession
selectToScreenPosition: (position) ->
lastSelection = @getLastSelection()
lastSelection.selectToScreenPosition(position)
@mergeIntersectingSelections(reverse: lastSelection.isReversed())
@mergeIntersectingSelections(isReversed: lastSelection.isReversed())
# Selects the text one position right of the cursor.
selectRight: ->
@@ -1247,7 +1247,7 @@ class EditSession
expandSelectionsBackward: (fn) ->
fn(selection) for selection in @getSelections()
@mergeIntersectingSelections(reverse: true)
@mergeIntersectingSelections(isReversed: true)
finalizeSelections: ->
selection.finalize() for selection in @getSelections()

View File

@@ -17,10 +17,14 @@ class Fold
@displayBuffer.foldsByMarkerId[@marker.id] = this
@updateDisplayBuffer()
@marker.on 'destroyed', => @destroyed()
@marker.on 'changed', ({isValid}) => @destroy() unless isValid
# Returns whether this fold is contained within another fold
isInsideLargerFold: ->
@displayBuffer.findMarker(class: 'fold', containsBufferRange: @getBufferRange())?
if largestContainingFoldMarker = @displayBuffer.findMarker(class: 'fold', containsBufferRange: @getBufferRange())
not largestContainingFoldMarker.getBufferRange().isEqual(@getBufferRange())
else
false
# Destroys this fold
destroy: ->

View File

@@ -18,7 +18,7 @@ class PaneAxis extends View
@state = telepath.Document.create(deserializer: @className(), children: [])
@addChild(child) for child in args
@state.get('children').observe ({index, inserted, removed, site}) =>
@state.get('children').on 'changed', ({index, inserted, removed, site}) =>
return if site is @state.site.id
for childState in removed
@removeChild(@children(":eq(#{index})").view(), updateState: false)

View File

@@ -24,7 +24,7 @@ class PaneContainer extends View
else
@state = telepath.Document.create(deserializer: 'PaneContainer')
@state.observe ({key, newValue, site}) =>
@state.on 'changed', ({key, newValue, site}) =>
return if site is @state.site.id
if key is 'root'
@setRoot(deserialize(newValue), updateState: false)

View File

@@ -35,14 +35,14 @@ class Pane extends View
deserializer: 'Pane'
items: @items.map (item) -> item.getState?() ? item.serialize()
@state.get('items').observe ({index, removed, inserted, site}) =>
@state.get('items').on 'changed', ({index, removed, inserted, site}) =>
return if site is @state.site.id
for itemState in removed
@removeItemAtIndex(index, updateState: false)
for itemState, i in inserted
@addItem(deserialize(itemState), index + i, updateState: false)
@state.observe ({key, newValue, site}) =>
@state.on 'changed', ({key, newValue, site}) =>
return if site is @state.site.id
@showItemForUri(newValue) if key is 'activeItemUri'

View File

@@ -63,7 +63,7 @@ class Project
@state = telepath.Document.create(deserializer: @constructor.name, version: @constructor.version, buffers: [])
@setPath(pathOrState)
@state.get('buffers').observe ({inserted, removed, index, site}) =>
@state.get('buffers').on 'changed', ({inserted, removed, index, site}) =>
return if site is @state.site.id
for removedBuffer in removed

View File

@@ -149,7 +149,7 @@ class Selection
@modifySelection =>
if @initialScreenRange
if position.isLessThan(@initialScreenRange.start)
@marker.setScreenRange([position, @initialScreenRange.end], reverse: true)
@marker.setScreenRange([position, @initialScreenRange.end], isReversed: true)
else
@marker.setScreenRange([@initialScreenRange.start, position])
else
@@ -271,7 +271,7 @@ class Selection
newBufferRange = @editSession.buffer.change(oldBufferRange, text)
if options.select
@setBufferRange(newBufferRange, reverse: wasReversed)
@setBufferRange(newBufferRange, isReversed: wasReversed)
else
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
@@ -478,7 +478,7 @@ class Selection
modifySelection: (fn) ->
@retainSelection = true
@placeTail()
@plantTail()
fn()
@retainSelection = false
@@ -487,8 +487,8 @@ class Selection
# This only works if there isn't already a tail position.
#
# Returns a {Point} representing the new tail position.
placeTail: ->
@marker.placeTail()
plantTail: ->
@marker.plantTail()
# Identifies if a selection intersects with a given buffer range.
#

View File

@@ -6,7 +6,6 @@ File = require 'file'
EventEmitter = require 'event-emitter'
UndoManager = require 'undo-manager'
BufferChangeOperation = require 'buffer-change-operation'
BufferMarker = require 'buffer-marker'
guid = require 'guid'
# Public: Represents the contents of a file.
@@ -29,8 +28,6 @@ class TextBuffer
cachedMemoryContents: null
conflict: false
file: null
validMarkers: null
invalidMarkers: null
refcount: 0
# Creates a new buffer.
@@ -38,10 +35,6 @@ class TextBuffer
# path - A {String} representing the file path
# initialText - A {String} setting the starting text
constructor: (args...) ->
@nextMarkerId = 1
@validMarkers = {}
@invalidMarkers = {}
if args[0] instanceof telepath.Document
@state = args[0]
@text = @state.get('text')
@@ -67,7 +60,9 @@ class TextBuffer
@text ?= telepath.Document.create('', shareStrings: true)
@state.set('text', @text)
@text.observe(@handleTextChange)
@text.on 'changed', @handleTextChange
@text.on 'marker-created', (marker) => @trigger 'marker-created', marker
@text.on 'markers-updated', => @trigger 'markers-updated'
@undoManager = new UndoManager(this)
### Internal ###
@@ -76,10 +71,8 @@ class TextBuffer
@cachedMemoryContents = null
@conflict = false if @conflict and !@isModified()
bufferChangeEvent = _.pick(event, 'oldRange', 'newRange', 'oldText', 'newText')
marker.handleBufferChange(bufferChangeEvent) for marker in @getMarkers()
@trigger 'changed', bufferChangeEvent
@scheduleModifiedEvents()
@trigger 'markers-updated' if @state.site.id isnt event.site
destroy: ->
unless @destroyed
@@ -314,7 +307,6 @@ class TextBuffer
else
startPoint = [start, 0]
endPoint = [end + 1, 0]
@delete(new Range(startPoint, endPoint))
# Adds text to the end of the buffer.
@@ -325,10 +317,10 @@ class TextBuffer
# Adds text to a specific point in the buffer
#
# point - A {Point} in the buffer to insert into
# position - A {Point} in the buffer to insert into
# text - A {String} of text to add
insert: (point, text) ->
@change(new Range(point, point), text)
insert: (position, text) ->
@change(new Range(position, position), text)
# Deletes text from the buffer
#
@@ -344,15 +336,7 @@ class TextBuffer
#
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
clipPosition: (position) ->
position = Point.fromObject(position)
eofPosition = @getEofPosition()
if position.isGreaterThan(eofPosition)
eofPosition
else
row = Math.max(position.row, 0)
column = Math.max(position.column, 0)
column = Math.min(@lineLengthForRow(row), column)
new Point(row, column)
@text.clipPosition(position)
# Given a range, this clips it to a real range.
#
@@ -414,22 +398,18 @@ class TextBuffer
isEmpty: -> @text.isEmpty()
# Returns all valid {BufferMarker}s on the buffer.
getMarkers: ({includeInvalid} = {}) ->
markers = _.values(@validMarkers)
if includeInvalid
markers.concat(_.values(@invalidMarkers))
else
markers
getMarkers: ->
@text.getMarkers()
# Returns the {BufferMarker} with the given id.
getMarker: (id) ->
@validMarkers[id]
@text.getMarker(id)
# Public: Finds the first marker satisfying the given attributes
#
# Returns a {String} marker-identifier
findMarker: (attributes) ->
@findMarkers(attributes)[0]
@text.findMarker(attributes)
# Public: Finds all markers satisfying the given attributes
#
@@ -440,14 +420,13 @@ class TextBuffer
#
# Returns an {Array} of {BufferMarker}s
findMarkers: (attributes) ->
markers = @getMarkers().filter (marker) -> marker.matchesAttributes(attributes)
markers.sort (a, b) -> a.getRange().compare(b.getRange())
@text.findMarkers(attributes)
# Retrieves the quantity of markers in a buffer.
#
# Returns a {Number}.
getMarkerCount: ->
_.size(@validMarkers)
@text.getMarkers().length
# Constructs a new marker at a given range.
#
@@ -456,23 +435,12 @@ class TextBuffer
# Any attributes you pass will be associated with the marker and can be retrieved
# or used in marker queries.
# The following attribute keys reserved, and control the marker's initial range
# reverse - if `true`, the marker is reversed; that is, its head precedes the tail
# noTail - if `true`, the marker is created without a tail
# isReversed - if `true`, the marker is reversed; that is, its head precedes the tail
# hasTail - if `false`, the marker is created without a tail
#
# Returns a {Number} representing the new marker's ID.
markRange: (range, attributes={}) ->
optionKeys = ['invalidationStrategy', 'noTail', 'reverse']
options = _.pick(attributes, optionKeys)
attributes = _.omit(attributes, optionKeys)
marker = new BufferMarker(_.defaults({
id: (@nextMarkerId++).toString()
buffer: this
range
attributes
}, options))
@validMarkers[marker.id] = marker
@trigger 'marker-created', marker
marker
@text.markRange(range, attributes)
# Constructs a new marker at a given position.
#
@@ -481,16 +449,7 @@ class TextBuffer
#
# Returns a {Number} representing the new marker's ID.
markPosition: (position, options) ->
@markRange([position, position], _.defaults({noTail: true}, options))
# Given a buffer position, this finds all markers that contain the position.
#
# bufferPosition - A {Point} to check
#
# Returns an {Array} of {Numbers}, representing marker IDs containing `bufferPosition`.
markersForPosition: (position) ->
position = Point.fromObject(position)
@getMarkers().filter (marker) -> marker.containsPoint(position)
@text.markPosition(position, options)
# Identifies if a character sequence is within a certain range.
#
@@ -662,9 +621,7 @@ class TextBuffer
change: (oldRange, newText, options={}) ->
oldRange = @clipRange(oldRange)
newText = @normalizeLineEndings(oldRange.start.row, newText) if options.normalizeLineEndings ? true
operation = new BufferChangeOperation({buffer: this, oldRange, newText, options})
range = @pushOperation(operation)
range
@text.change(oldRange, newText, options)
normalizeLineEndings: (startRow, text) ->
if lineEnding = @suggestedLineEndingForRow(startRow)
@@ -672,11 +629,6 @@ class TextBuffer
else
text
destroyMarker: (id) ->
if marker = @validMarkers[id] ? @invalidMarkers[id]
delete @validMarkers[id]
delete @invalidMarkers[id]
scheduleModifiedEvents: ->
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
stoppedChangingCallback = =>

View File

@@ -203,7 +203,7 @@ module.exports =
return if selection.isEmpty()
range = selection.getBufferRange()
options = reverse: selection.isReversed()
options = isReversed: selection.isReversed()
selection.insertText("#{bracket}#{selection.getText()}#{pair}")
selectionStart = range.start.add([0, 1])
if range.start.row is range.end.row

View File

@@ -23,8 +23,8 @@ class SnippetExpansion
@editSession.normalizeTabsInBufferRange(newRange)
@indentSubsequentLines(startPosition.row, snippet) if snippet.lineCount > 1
cursorMoved: ({oldBufferPosition, newBufferPosition, bufferChanged}) ->
return if @settingTabStop or bufferChanged
cursorMoved: ({oldBufferPosition, newBufferPosition, textChanged}) ->
return if @settingTabStop or textChanged
oldTabStops = @tabStopsForBufferPosition(oldBufferPosition)
newTabStops = @tabStopsForBufferPosition(newBufferPosition)
@destroy() unless _.intersection(oldTabStops, newTabStops).length