From f326c818fdeda3183039e643a335ab4474f105d3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 6 Feb 2015 18:42:31 -0700 Subject: [PATCH] Update .foldable on tokenized lines based on indentation --- spec/tokenized-buffer-spec.coffee | 66 +++++++++++++++++++++++++------ src/tokenized-buffer.coffee | 46 +++++++++++++-------- 2 files changed, 84 insertions(+), 28 deletions(-) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index ea44fa1b0..41a63a562 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -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 diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index aea80449a..eea600b97 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -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