diff --git a/spec/tokenized-buffer-iterator-spec.js b/spec/tokenized-buffer-iterator-spec.js index 639714136..8d0e458f4 100644 --- a/spec/tokenized-buffer-iterator-spec.js +++ b/spec/tokenized-buffer-iterator-spec.js @@ -37,4 +37,67 @@ describe('TokenizedBufferIterator', () => { expect(iterator.getCloseTags()).toEqual(['foo']) expect(iterator.getOpenTags()).toEqual([]) }) + + it("reports a boundary at line end if the next line's open scopes don't match the containing tags for the current line", () => { + const tokenizedBuffer = { + tokenizedLineForRow (row) { + if (row === 0) { + return { + tags: [-1, 3, -2, -3], + text: 'bar', + openScopes: [] + } + } else if (row === 1) { + return { + tags: [3], + text: 'baz', + openScopes: [-1] + } + } else if (row === 2) { + return { + tags: [-2], + text: '', + openScopes: [-1] + } + } + } + } + + const grammarRegistry = { + scopeForId (id) { + if (id === -2 || id === -1) { + return 'foo' + } else if (id === -3) { + return 'qux' + } + } + } + + const iterator = new TokenizedBufferIterator(tokenizedBuffer, grammarRegistry) + + iterator.seek(Point(0, 0)) + expect(iterator.getPosition()).toEqual(Point(0, 0)) + expect(iterator.getCloseTags()).toEqual([]) + expect(iterator.getOpenTags()).toEqual(['foo']) + + iterator.moveToSuccessor() + expect(iterator.getPosition()).toEqual(Point(0, 3)) + expect(iterator.getCloseTags()).toEqual(['foo']) + expect(iterator.getOpenTags()).toEqual(['qux']) + + iterator.moveToSuccessor() + expect(iterator.getPosition()).toEqual(Point(0, 3)) + expect(iterator.getCloseTags()).toEqual(['qux']) + expect(iterator.getOpenTags()).toEqual([]) + + iterator.moveToSuccessor() + expect(iterator.getPosition()).toEqual(Point(1, 0)) + expect(iterator.getCloseTags()).toEqual([]) + expect(iterator.getOpenTags()).toEqual(['foo']) + + iterator.moveToSuccessor() + expect(iterator.getPosition()).toEqual(Point(2, 0)) + expect(iterator.getCloseTags()).toEqual(['foo']) + expect(iterator.getOpenTags()).toEqual([]) + }) }) diff --git a/src/tokenized-buffer-iterator.coffee b/src/tokenized-buffer-iterator.coffee index 725041def..ad1834f09 100644 --- a/src/tokenized-buffer-iterator.coffee +++ b/src/tokenized-buffer-iterator.coffee @@ -1,10 +1,12 @@ {Point} = require 'text-buffer' +{isEqual} = require 'underscore-plus' module.exports = class TokenizedBufferIterator constructor: (@tokenizedBuffer, @grammarRegistry) -> @openTags = null @closeTags = null + @containingTags = null seek: (position) -> @openTags = [] @@ -12,9 +14,10 @@ class TokenizedBufferIterator @tagIndex = null currentLine = @tokenizedBuffer.tokenizedLineForRow(position.row) - containingTags = currentLine.openScopes.map (id) => @grammarRegistry.scopeForId(id) @currentTags = currentLine.tags + @currentLineOpenTags = currentLine.openScopes @currentLineLength = currentLine.text.length + @containingTags = @currentLineOpenTags.map (id) => @grammarRegistry.scopeForId(id) currentColumn = 0 for tag, index in @currentTags if tag >= 0 @@ -23,8 +26,8 @@ class TokenizedBufferIterator break else currentColumn += tag - containingTags.pop() while @closeTags.shift() - containingTags.push(tag) while tag = @openTags.shift() + @containingTags.pop() while @closeTags.shift() + @containingTags.push(tag) while tag = @openTags.shift() else scopeName = @grammarRegistry.scopeForId(tag) if tag % 2 is 0 @@ -38,9 +41,11 @@ class TokenizedBufferIterator @tagIndex ?= @currentTags.length @position = Point(position.row, Math.min(@currentLineLength, currentColumn)) - containingTags + @containingTags.slice() moveToSuccessor: -> + @containingTags.pop() for tag in @closeTags + @containingTags.push(tag) for tag in @openTags @openTags = [] @closeTags = [] @@ -49,7 +54,16 @@ class TokenizedBufferIterator if @isAtTagBoundary() break else - return false unless @moveToNextLine() + if @shouldMoveToNextLine + @moveToNextLine() + @openTags = @currentLineOpenTags.map (id) => @grammarRegistry.scopeForId(id) + @shouldMoveToNextLine = false + else if @hasNextLine() and not isEqual(@containingTags, @nextLineOpeningScopes()) + @closeTags = @containingTags.slice().reverse() + @containingTags = [] + @shouldMoveToNextLine = true + else + return false unless @moveToNextLine() else tag = @currentTags[@tagIndex] if tag >= 0 @@ -70,16 +84,6 @@ class TokenizedBufferIterator true - # Private - moveToNextLine: -> - @position = Point(@position.row + 1, 0) - tokenizedLine = @tokenizedBuffer.tokenizedLineForRow(@position.row) - return false unless tokenizedLine? - @currentTags = tokenizedLine.tags - @currentLineLength = tokenizedLine.text.length - @tagIndex = 0 - true - getPosition: -> @position @@ -89,5 +93,27 @@ class TokenizedBufferIterator getOpenTags: -> @openTags.slice() + ### + Section: Private Methods + ### + + hasNextLine: -> + @tokenizedBuffer.tokenizedLineForRow(@position.row + 1)? + + nextLineOpeningScopes: -> + line = @tokenizedBuffer.tokenizedLineForRow(@position.row + 1) + line.openScopes.map (id) => @grammarRegistry.scopeForId(id) + + 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