Files
atom/src/tokenized-buffer-iterator.coffee
Antonio Scandurra 581790760b Clip to next boundary when seeking iterator to the middle of a text tag
Previously, when calling `TokenizedBufferIterator.seek` with a position
that lied within a text tag, we advanced the iterator by the extent of
that tag without, however, consuming it. Hence, when calling
`moveToSuccessor` afterward, we would consume that tag and advance the
iterator again, thus effectively moving it twice and making its position
inaccurate.

An option could be to clip to the left of the textual tag without
consuming it. However, this would be a little odd with respect to the
current contract between (`DisplayLayer` and) `seek`, whose promise is
to move the iterator to a position that is greater or equal than the one
asked by the caller.

Therefore, with this commit, we are changing the behavior of `seek` in
this particular scenario to consume the tag in question and process all
its siblings until a tag boundary is finally found. This ensures that
the above contract is always respected, while still preserving the "seek
to leftmost tag boundary" semantics (i.e. notice how in the changed test
case, calling `seek` with `Point(0, 1)` is the same as calling it with
`Point(0, 3)`).
2016-09-06 18:12:05 +02:00

127 lines
3.7 KiB
CoffeeScript

{Point} = require 'text-buffer'
module.exports =
class TokenizedBufferIterator
constructor: (@tokenizedBuffer) ->
@openTags = null
@closeTags = null
@containingTags = null
seek: (position) ->
@openTags = []
@closeTags = []
@tagIndex = null
currentLine = @tokenizedBuffer.tokenizedLineForRow(position.row)
@currentTags = currentLine.tags
@currentLineOpenTags = currentLine.openScopes
@currentLineLength = currentLine.text.length
@containingTags = @currentLineOpenTags.map (id) => @tokenizedBuffer.grammar.scopeForId(id)
currentColumn = 0
for tag, index in @currentTags
if tag >= 0
if currentColumn >= position.column
@tagIndex = index
break
else
currentColumn += tag
@containingTags.pop() while @closeTags.shift()
@containingTags.push(openTag) while openTag = @openTags.shift()
else
scopeName = @tokenizedBuffer.grammar.scopeForId(tag)
if tag % 2 is 0 # close tag
if @openTags.length > 0
if currentColumn >= position.column
@tagIndex = index
break
else
@containingTags.pop() while @closeTags.shift()
@containingTags.push(openTag) while openTag = @openTags.shift()
@closeTags.push(scopeName)
else # open tag
@openTags.push(scopeName)
@tagIndex ?= @currentTags.length
@position = Point(position.row, Math.min(@currentLineLength, currentColumn))
@containingTags.slice()
moveToSuccessor: ->
@containingTags.pop() for tag in @closeTags
@containingTags.push(tag) for tag in @openTags
@openTags = []
@closeTags = []
loop
if @tagIndex is @currentTags.length
if @isAtTagBoundary()
break
else
if @shouldMoveToNextLine
@moveToNextLine()
@openTags = @currentLineOpenTags.map (id) => @tokenizedBuffer.grammar.scopeForId(id)
@shouldMoveToNextLine = false
else if @nextLineHasMismatchedContainingTags()
@closeTags = @containingTags.slice().reverse()
@containingTags = []
@shouldMoveToNextLine = true
else
return false unless @moveToNextLine()
else
tag = @currentTags[@tagIndex]
if tag >= 0
if @isAtTagBoundary()
break
else
@position = Point(@position.row, Math.min(@currentLineLength, @position.column + @currentTags[@tagIndex]))
else
scopeName = @tokenizedBuffer.grammar.scopeForId(tag)
if tag % 2 is 0
if @openTags.length > 0
break
else
@closeTags.push(scopeName)
else
@openTags.push(scopeName)
@tagIndex++
true
getPosition: ->
@position
getCloseTags: ->
@closeTags.slice()
getOpenTags: ->
@openTags.slice()
###
Section: Private Methods
###
nextLineHasMismatchedContainingTags: ->
if line = @tokenizedBuffer.tokenizedLineForRow(@position.row + 1)
return true if line.openScopes.length isnt @containingTags.length
for i in [0...@containingTags.length] by 1
if @containingTags[i] isnt @tokenizedBuffer.grammar.scopeForId(line.openScopes[i])
return true
false
else
false
moveToNextLine: ->
@position = Point(@position.row + 1, 0)
if tokenizedLine = @tokenizedBuffer.tokenizedLineForRow(@position.row)
@currentTags = tokenizedLine.tags
@currentLineLength = tokenizedLine.text.length
@currentLineOpenTags = tokenizedLine.openScopes
@tagIndex = 0
true
else
false
isAtTagBoundary: ->
@closeTags.length > 0 or @openTags.length > 0