Files
atom/src/app/cursor.coffee
Nathan Sobo f4f3002e6d Rename Cursor and CursorView events to passive-voice scheme
Any events emitted from DOM nodes should be prefixed with an identifier
for the node that emits them. This eliminates the possibility of ambiguity
when the events bubble up the DOM away from their emitter.
2013-01-02 13:48:11 -07:00

194 lines
5.8 KiB
CoffeeScript

Point = require 'point'
Range = require 'range'
Anchor = require 'anchor'
EventEmitter = require 'event-emitter'
_ = require 'underscore'
module.exports =
class Cursor
screenPosition: null
bufferPosition: null
goalColumn: null
wordRegex: /(\w+)|([^\w\n]+)/g
visible: true
needsAutoscroll: false
constructor: ({@editSession, screenPosition, bufferPosition}) ->
@anchor = @editSession.addAnchor(strong: true)
@anchor.on 'moved', (e) =>
@needsAutoscroll = (e.autoscroll ? true) and @isLastCursor()
@trigger 'moved', e
@editSession.trigger 'cursor-moved', e
@setScreenPosition(screenPosition) if screenPosition
@setBufferPosition(bufferPosition) if bufferPosition
@needsAutoscroll = true
destroy: ->
@anchor.destroy()
@editSession.removeCursor(this)
@trigger 'destroyed'
setScreenPosition: (screenPosition, options) ->
@goalColumn = null
@clearSelection()
@anchor.setScreenPosition(screenPosition, options)
getScreenPosition: ->
@anchor.getScreenPosition()
getScreenRow: ->
@anchor.getScreenRow()
setBufferPosition: (bufferPosition, options) ->
@goalColumn = null
@clearSelection()
@anchor.setBufferPosition(bufferPosition, options)
getBufferPosition: ->
@anchor.getBufferPosition()
setVisible: (visible) ->
if @visible != visible
@visible = visible
@needsAutoscroll = @visible and @isLastCursor()
@trigger 'visibility-changed', @visible
isVisible: -> @visible
isLastCursor: ->
this == @editSession.getLastCursor()
autoscrolled: ->
@needsAutoscroll = false
clearSelection: ->
if @selection
@selection.clear() unless @selection.retainSelection
getScreenRow: ->
@getScreenPosition().row
getScreenColumn: ->
@getScreenPosition().column
getBufferRow: ->
@getBufferPosition().row
getBufferColumn: ->
@getBufferPosition().column
getCurrentBufferLine: ->
@editSession.lineForBufferRow(@getBufferRow())
refreshScreenPosition: ->
@anchor.refreshScreenPosition()
moveUp: (rowCount = 1) ->
{ row, column } = @getScreenPosition()
column = @goalColumn if @goalColumn?
@setScreenPosition({row: row - rowCount, column: column})
@goalColumn = column
moveDown: (rowCount = 1) ->
{ row, column } = @getScreenPosition()
column = @goalColumn if @goalColumn?
@setScreenPosition({row: row + rowCount, column: column})
@goalColumn = column
moveLeft: ->
{ row, column } = @getScreenPosition()
[row, column] = if column > 0 then [row, column - 1] else [row - 1, Infinity]
@setScreenPosition({row, column})
moveRight: ->
{ row, column } = @getScreenPosition()
@setScreenPosition([row, column + 1], skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
moveToTop: ->
@setBufferPosition([0,0])
moveToBottom: ->
@setBufferPosition(@editSession.getEofBufferPosition())
moveToBeginningOfLine: ->
@setBufferPosition([@getBufferRow(), 0])
moveToFirstCharacterOfLine: ->
position = @getBufferPosition()
range = @getCurrentLineBufferRange()
newPosition = null
@editSession.scanInRange /^\s*/, range, (match, matchRange) =>
newPosition = matchRange.end
return unless newPosition
newPosition = [position.row, 0] if newPosition.isEqual(position)
@setBufferPosition(newPosition)
skipLeadingWhitespace: ->
position = @getBufferPosition()
range = @getCurrentLineBufferRange()
endOfLeadingWhitespace = null
@editSession.scanInRange /^[ \t]*/, range, (match, matchRange) =>
endOfLeadingWhitespace = matchRange.end
@setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
moveToEndOfLine: ->
@setBufferPosition([@getBufferRow(), Infinity])
moveToBeginningOfWord: ->
@setBufferPosition(@getBeginningOfCurrentWordBufferPosition())
moveToEndOfWord: ->
if position = @getEndOfCurrentWordBufferPosition()
@setBufferPosition(position)
getBeginningOfCurrentWordBufferPosition: (options = {}) ->
allowPrevious = options.allowPrevious ? true
currentBufferPosition = @getBufferPosition()
previousNonBlankRow = @editSession.buffer.previousNonBlankRow(currentBufferPosition.row)
previousLinesRange = [[previousNonBlankRow, 0], currentBufferPosition]
beginningOfWordPosition = currentBufferPosition
@editSession.backwardsScanInRange @wordRegex, previousLinesRange, (match, matchRange, { stop }) =>
if matchRange.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
beginningOfWordPosition = matchRange.start
stop()
beginningOfWordPosition
getEndOfCurrentWordBufferPosition: (options = {}) ->
allowNext = options.allowNext ? true
currentBufferPosition = @getBufferPosition()
range = [currentBufferPosition, @editSession.getEofBufferPosition()]
endOfWordPosition = null
@editSession.scanInRange @wordRegex, range, (match, matchRange, { stop }) =>
endOfWordPosition = matchRange.end
if not allowNext and matchRange.start.isGreaterThan(currentBufferPosition)
endOfWordPosition = currentBufferPosition
stop()
endOfWordPosition
getCurrentWordBufferRange: ->
new Range(@getBeginningOfCurrentWordBufferPosition(allowPrevious: false), @getEndOfCurrentWordBufferPosition(allowNext: false))
getCurrentLineBufferRange: (options) ->
@editSession.bufferRangeForBufferRow(@getBufferRow(), options)
getCurrentWordPrefix: ->
@editSession.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
isAtBeginningOfLine: ->
@getBufferPosition().column == 0
getIndentLevel: ->
if @editSession.softTabs
@getBufferColumn() / @editSession.getTabLength()
else
@getBufferColumn()
isAtEndOfLine: ->
@getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
_.extend Cursor.prototype, EventEmitter