Update .foldable on tokenized lines based on indentation

This commit is contained in:
Nathan Sobo
2015-02-06 18:42:31 -07:00
parent 0081fa283e
commit f326c818fd
2 changed files with 84 additions and 28 deletions

View File

@@ -118,7 +118,8 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.firstInvalidRow()).toBe 3
advanceClock()
expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 7, delta: 0)
# we discover that row 2 starts a foldable region when line 3 gets tokenized
expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 7, delta: 0)
expect(tokenizedBuffer.firstInvalidRow()).toBe 8
describe "when there is a buffer change surrounding an invalid row", ->
@@ -168,7 +169,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 2, end: 2, delta: 0)
expect(event).toEqual(start: 1, end: 2, delta: 0)
changeHandler.reset()
advanceClock()
@@ -178,7 +179,8 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 3, end: 5, delta: 0)
# we discover that row 2 starts a foldable region when line 3 gets tokenized
expect(event).toEqual(start: 2, end: 5, delta: 0)
it "resumes highlighting with the state of the previous line", ->
buffer.insert([0, 0], '/*')
@@ -206,7 +208,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 1, end: 3, delta: -2)
expect(event).toEqual(start: 0, end: 3, delta: -2) # starts at 0 because foldable on row 0 becomes false
describe "when the change invalidates the tokenization of subsequent lines", ->
it "schedules the invalidated lines to be tokenized in the background", ->
@@ -219,7 +221,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 2, end: 3, delta: -1)
expect(event).toEqual(start: 1, end: 3, delta: -1)
changeHandler.reset()
advanceClock()
@@ -228,7 +230,8 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 3, end: 4, delta: 0)
# we discover that row 2 starts a foldable region when line 3 gets tokenized
expect(event).toEqual(start: 2, end: 4, delta: 0)
describe "when lines are both updated and inserted", ->
it "updates tokens to reflect the change", ->
@@ -252,7 +255,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 1, end: 2, delta: 2)
expect(event).toEqual(start: 0, end: 2, delta: 2) # starts at 0 because .foldable becomes false on row 0
describe "when the change invalidates the tokenization of subsequent lines", ->
it "schedules the invalidated lines to be tokenized in the background", ->
@@ -263,7 +266,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 2, end: 2, delta: 2)
expect(event).toEqual(start: 1, end: 2, delta: 2)
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
@@ -776,7 +779,7 @@ describe "TokenizedBuffer", ->
buffer.setTextInRange([[7, 0], [8, 65]], ' ok')
delete changeHandler.argsForCall[0][0].bufferChange
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: -1)
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 10, delta: -1) # starts at row 4 because it became foldable
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 2
@@ -803,7 +806,7 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
expect(tokenizedBuffer.tokenizedLineForRow(13).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(14).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(15).foldable).toBe false
@@ -818,9 +821,48 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe false
changes = []
buffer.delete([[0, Infinity], [1, 0]])
buffer.undo()
expect(changes).toEqual [{start: 0, end: 2, delta: -1}]
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
it "sets .foldable to true on non-comment lines that precede an increase in indentation", ->
buffer.insert([2, 0], ' ') # commented lines preceding an indent aren't foldable
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(4).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(5).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
buffer.insert([7, 0], ' ')
expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
buffer.undo()
expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
buffer.insert([7, 0], " \n x\n")
expect(changes).toEqual [{start: 6, end: 7, delta: 2}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
buffer.insert([9, 0], " ")
expect(changes).toEqual [{start: 9, end: 9, delta: 0}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false

View File

@@ -168,7 +168,7 @@ class TokenizedBuffer extends Model
@validateRow(endRow)
@invalidateRow(endRow + 1) unless filledRegion
@updateFoldableStatus(startRow, endRow)
[startRow, endRow] = @updateFoldableStatus(startRow, endRow)
event = {start: startRow, end: endRow, delta: 0}
@emit 'changed', event
@@ -238,27 +238,41 @@ class TokenizedBuffer extends Model
row - increment
updateFoldableStatus: (startRow, endRow) ->
scanStartRow = @buffer.previousNonBlankRow(startRow) ? startRow
scanStartRow-- while scanStartRow > 0 and @tokenizedLineForRow(scanStartRow).isComment()
scanEndRow = @buffer.nextNonBlankRow(endRow) ? endRow
scanEndRow++ if scanEndRow < @buffer.getLastRow()
scanStartRow = startRow
while scanStartRow > 0 and @tokenizedLineForRow(scanStartRow).isComment()
scanStartRow = @buffer.previousNonBlankRow(scanStartRow) ? 0
commentLineCount = 0
for row in [scanStartRow..scanEndRow] by 1
if @tokenizedLineForRow(row).isComment()
if row > 0
foldable = commentLineCount is 1
unless @tokenizedLineForRow(row - 1).foldable is foldable
@tokenizedLineForRow(row - 1).foldable = foldable
startRow = Math.min(startRow, row - 1)
endRow = Math.max(endRow, row - 1)
commentLineCount++
else
commentLineCount = 0
foldable = @isFoldableAtRow(row)
line = @tokenizedLineForRow(row)
unless line.foldable is foldable
line.foldable = foldable
startRow = Math.min(startRow, row)
endRow = Math.max(endRow, row)
[startRow, endRow]
isFoldableAtRow: (row) ->
@isFoldableCodeAtRow(row) or @isFoldableCommentAtRow(row)
# Returns a {Boolean} indicating whether the given buffer row starts
# a a foldable row range due to the code's indentation patterns.
isFoldableCodeAtRow: (row) ->
return false if @buffer.isRowBlank(row) or @tokenizedLineForRow(row).isComment()
nextRow = @buffer.nextNonBlankRow(row)
return false unless nextRow?
@indentLevelForRow(nextRow) > @indentLevelForRow(row)
isFoldableCommentAtRow: (row) ->
previousRow = row - 1
nextRow = row + 1
return false if nextRow > @buffer.getLastRow()
(row is 0 or not @tokenizedLineForRow(previousRow).isComment()) and
@tokenizedLineForRow(row).isComment() and
@tokenizedLineForRow(nextRow).isComment()
buildTokenizedLinesForRows: (startRow, endRow, startingStack) ->
ruleStack = startingStack
stopTokenizingAt = startRow + @chunkSize