diff --git a/spec/language-mode-spec.coffee b/spec/language-mode-spec.coffee index 7ea4a1ae9..cd32e29c7 100644 --- a/spec/language-mode-spec.coffee +++ b/spec/language-mode-spec.coffee @@ -463,17 +463,6 @@ describe "LanguageMode", -> fold2 = editor.tokenizedLineForScreenRow(5).fold expect(fold2).toBeFalsy() - describe ".isFoldableAtBufferRow(bufferRow)", -> - it "returns true if the line starts a multi-line comment", -> - expect(languageMode.isFoldableAtBufferRow(1)).toBe true - expect(languageMode.isFoldableAtBufferRow(6)).toBe true - expect(languageMode.isFoldableAtBufferRow(17)).toBe false - - it "does not return true for a line in the middle of a comment that's followed by an indented line", -> - expect(languageMode.isFoldableAtBufferRow(7)).toBe false - editor.buffer.insert([8, 0], ' ') - expect(languageMode.isFoldableAtBufferRow(7)).toBe false - describe "css", -> beforeEach -> waitsForPromise -> diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 72b292cc2..88c095f68 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -202,8 +202,7 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.firstInvalidRow()).toBe 3 advanceClock() - # we discover that row 2 starts a foldable region when line 3 gets tokenized - expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 7, delta: 0) + expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 7, delta: 0) expect(tokenizedBuffer.firstInvalidRow()).toBe 8 describe "when there is a buffer change surrounding an invalid row", -> @@ -253,7 +252,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - expect(event).toEqual(start: 1, end: 2, delta: 0) + expect(event).toEqual(start: 2, end: 2, delta: 0) changeHandler.reset() advanceClock() @@ -263,8 +262,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - # we discover that row 2 starts a foldable region when line 3 gets tokenized - expect(event).toEqual(start: 2, end: 5, delta: 0) + expect(event).toEqual(start: 3, end: 5, delta: 0) it "resumes highlighting with the state of the previous line", -> buffer.insert([0, 0], '/*') @@ -292,7 +290,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - expect(event).toEqual(start: 0, end: 3, delta: -2) # starts at 0 because foldable on row 0 becomes false + expect(event).toEqual(start: 1, end: 3, delta: -2) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> @@ -305,7 +303,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - expect(event).toEqual(start: 1, end: 3, delta: -1) + expect(event).toEqual(start: 2, end: 3, delta: -1) changeHandler.reset() advanceClock() @@ -314,8 +312,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - # we discover that row 2 starts a foldable region when line 3 gets tokenized - expect(event).toEqual(start: 2, end: 4, delta: 0) + expect(event).toEqual(start: 3, end: 4, delta: 0) describe "when lines are both updated and inserted", -> it "updates tokens to reflect the change", -> @@ -339,7 +336,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - expect(event).toEqual(start: 0, end: 2, delta: 2) # starts at 0 because .foldable becomes false on row 0 + expect(event).toEqual(start: 1, end: 2, delta: 2) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> @@ -350,7 +347,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: 2, 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'] @@ -894,7 +891,7 @@ describe "TokenizedBuffer", -> buffer.setTextInRange([[7, 0], [8, 65]], ' ok') delete changeHandler.argsForCall[0][0].bufferChange - expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 10, delta: -1) # starts at row 4 because it became foldable + expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: -1) expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 2 expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 2 @@ -903,7 +900,7 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 2 expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 2 # } - describe ".foldable on tokenized lines", -> + describe "::isFoldableAtRow(row)", -> changes = null beforeEach -> @@ -915,74 +912,66 @@ describe "TokenizedBuffer", -> buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert }) fullyTokenize(tokenizedBuffer) - tokenizedBuffer.onDidChange (change) -> - delete change.bufferChange - changes.push(change) - it "sets .foldable to true on the first line of multi-line comments", -> - 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 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 - expect(tokenizedBuffer.tokenizedLineForRow(16).foldable).toBe false + it "includes the first line of multi-line comments", -> + expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent + expect(tokenizedBuffer.isFoldableAtRow(13)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(14)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(15)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(16)).toBe false buffer.insert([0, Infinity], '\n') - expect(changes).toEqual [{start: 0, end: 1, delta: 1}] - expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe false + expect(tokenizedBuffer.isFoldableAtRow(0)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(2)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(3)).toBe false - changes = [] 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 true # because of indent - it "sets .foldable to true on non-comment lines that precede an increase in indentation", -> + expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent + + it "includes 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 = [] + expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(4)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(5)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + 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 = [] + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + 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 = [] + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + 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 = [] + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + 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 + + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false describe "when the buffer is configured with the null grammar", -> it "uses the placeholder tokens and does not actually tokenize using the grammar", -> diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 8b95656f9..d01ad03c9 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -413,6 +413,9 @@ class DisplayBuffer extends Model isFoldedAtScreenRow: (screenRow) -> @largestFoldContainingBufferRow(@bufferRowForScreenRow(screenRow))? + isFoldableAtBufferRow: (row) -> + @tokenizedBuffer.isFoldableAtRow(row) + # Destroys the fold with the given id destroyFoldWithId: (id) -> @foldsByMarkerId[id]?.destroy() diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index a36c860ed..a3504caa8 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -599,7 +599,14 @@ class TextEditorPresenter if endRow > startRow bufferRows = @model.bufferRowsForScreenRows(startRow, endRow - 1) + previousBufferRow = -1 + foldable = false for bufferRow, i in bufferRows + # don't compute foldability more than once per buffer row + if previousBufferRow isnt bufferRow + foldable = @model.isFoldableAtBufferRow(bufferRow) + previousBufferRow = bufferRow + if bufferRow is lastBufferRow softWrapped = true else @@ -609,7 +616,6 @@ class TextEditorPresenter screenRow = startRow + i line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) - foldable = @model.isFoldableAtScreenRow(screenRow) blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow) - @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow) blockDecorationsHeight = blockDecorationsBeforeCurrentScreenRowHeight if screenRow % @tileSize isnt 0 diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 55f6d84ad..c0a6f2057 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2939,8 +2939,7 @@ class TextEditor extends Model # # Returns a {Boolean}. isFoldableAtBufferRow: (bufferRow) -> - # @languageMode.isFoldableAtBufferRow(bufferRow) - @displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow)?.foldable ? false + @displayBuffer.isFoldableAtBufferRow(bufferRow) # Extended: Determine whether the given row in screen coordinates is foldable. # diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 0fdf4eea8..5c62f9ecd 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -210,8 +210,6 @@ class TokenizedBuffer extends Model @validateRow(endRow) @invalidateRow(endRow + 1) unless filledRegion - [startRow, endRow] = @updateFoldableStatus(startRow, endRow) - event = {start: startRow, end: endRow, delta: 0} @emitter.emit 'did-change', event @@ -271,9 +269,6 @@ class TokenizedBuffer extends Model if newEndStack and not _.isEqual(newEndStack, previousEndStack) @invalidateRow(end + delta + 1) - [start, end] = @updateFoldableStatus(start, end + delta) - end -= delta - event = {start, end, delta, bufferChange: e} @emitter.emit 'did-change', event @@ -287,23 +282,6 @@ class TokenizedBuffer extends Model row - increment - updateFoldableStatus: (startRow, endRow) -> - return [startRow, endRow] if @largeFileMode - - scanStartRow = @buffer.previousNonBlankRow(startRow) ? startRow - scanStartRow-- while scanStartRow > 0 and @tokenizedLineForRow(scanStartRow).isComment() - scanEndRow = @buffer.nextNonBlankRow(endRow) ? endRow - - for row in [scanStartRow..scanEndRow] by 1 - 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) -> if @largeFileMode false diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index c97a621ac..bf6a6dd2b 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -33,7 +33,6 @@ class TokenizedLine endOfLineInvisibles: null lineIsWhitespaceOnly: false firstNonWhitespaceIndex: 0 - foldable: false constructor: (properties) -> @id = idCounter++ @@ -492,15 +491,20 @@ class TokenizedLine @endOfLineInvisibles.push(eol) if eol isComment: -> + return @isCommentLine if @isCommentLine? + + @isCommentLine = false iterator = @getTokenIterator() while iterator.next() scopes = iterator.getScopes() continue if scopes.length is 1 continue unless NonWhitespaceRegex.test(iterator.getText()) for scope in scopes - return true if CommentScopeRegex.test(scope) + if CommentScopeRegex.test(scope) + @isCommentLine = true + break break - false + @isCommentLine isOnlyWhitespace: -> @lineIsWhitespaceOnly