Files
atom/src/app/cursor.coffee
Nathan Sobo 2380fa3445 Handle 'autoscroll' entirely in Cursor instead of in Anchor
This commit makes autoscroll a 3-valued property on the cursor. If it
is set to true or false, that setting will stick until the cursor's
next visual update. That means we can explicitly move the cursor with
autoscroll set to false, but also still autoscroll by default when the
cursor's anchor moves on its own.
2013-01-29 12:11:43 -07:00

218 lines
6.7 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: null
constructor: ({@editSession, screenPosition, bufferPosition}) ->
@anchor = @editSession.addAnchor(strong: true)
@anchor.on 'moved', (e) =>
@needsAutoscroll ?= @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()
@needsAutoscroll = (options.autoscroll ? true) and @isLastCursor()
@anchor.setScreenPosition(screenPosition, options)
getScreenPosition: ->
@anchor.getScreenPosition()
getScreenRow: ->
@anchor.getScreenRow()
setBufferPosition: (bufferPosition, options={}) ->
@goalColumn = null
@clearSelection()
@needsAutoscroll = options.autoscroll ? @isLastCursor()
@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.getCursor()
clearAutoscroll: ->
@needsAutoscroll = null
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 (options.wordRegex || @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 (options.wordRegex || @wordRegex), range, (match, matchRange, { stop }) =>
endOfWordPosition = matchRange.end
if not allowNext and matchRange.start.isGreaterThan(currentBufferPosition)
endOfWordPosition = currentBufferPosition
stop()
endOfWordPosition or currentBufferPosition
getCurrentWordBufferRange: (options={}) ->
startOptions = _.extend(_.clone(options), allowPrevious: false)
endOptions = _.extend(_.clone(options), allowNext: false)
new Range(@getBeginningOfCurrentWordBufferPosition(startOptions), @getEndOfCurrentWordBufferPosition(endOptions))
getCurrentLineBufferRange: (options) ->
@editSession.bufferRangeForBufferRow(@getBufferRow(), options)
getCurrentParagraphBufferRange: ->
row = @getBufferRow()
return unless /\w/.test(@editSession.lineForBufferRow(row))
startRow = row
while startRow > 0
break unless /\w/.test(@editSession.lineForBufferRow(startRow - 1))
startRow--
endRow = row
lastRow = @editSession.getLastBufferRow()
while endRow < lastRow
break unless /\w/.test(@editSession.lineForBufferRow(endRow + 1))
endRow++
new Range([startRow, 0], [endRow, @editSession.lineLengthForBufferRow(endRow)])
getCurrentWordPrefix: ->
@editSession.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
isAtBeginningOfLine: ->
@getBufferPosition().column == 0
getIndentLevel: ->
if @editSession.softTabs
@getBufferColumn() / @editSession.getTabLength()
else
@getBufferColumn()
isAtEndOfLine: ->
@getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
getScopes: ->
@editSession.scopesForBufferPosition(@getBufferPosition())
_.extend Cursor.prototype, EventEmitter