Avoid double computation of screen lines when opening files

Previously, instantiating a TextEditor would always compute compute
screen lines twice: once when the DisplayBuffer was instantiated,
and once when the 'invisibles' property was set on the DisplayBuffer.
This commit is contained in:
Max Brunsfeld 2015-05-13 19:59:25 -07:00
parent fe2cfff7a6
commit 19d905606b
5 changed files with 54 additions and 56 deletions

View File

@ -36,23 +36,21 @@ describe "TextEditor", ->
it "preserves the invisibles setting", ->
atom.config.set('editor.showInvisibles', true)
previousInvisibles = editor.displayBuffer.invisibles
previousInvisibles = editor.tokenizedLineForScreenRow(0).invisibles
editor2 = editor.testSerialization()
expect(editor2.displayBuffer.invisibles).toEqual previousInvisibles
expect(editor2.displayBuffer.tokenizedBuffer.invisibles).toEqual previousInvisibles
expect(previousInvisibles).toBeDefined()
expect(editor2.displayBuffer.tokenizedLineForScreenRow(0).invisibles).toEqual previousInvisibles
it "updates invisibles if the settings have changed between serialization and deserialization", ->
atom.config.set('editor.showInvisibles', true)
previousInvisibles = editor.displayBuffer.invisibles
state = editor.serialize()
atom.config.set('editor.invisibles', eol: '?')
editor2 = TextEditor.deserialize(state)
expect(editor2.displayBuffer.invisibles.eol).toBe '?'
expect(editor2.displayBuffer.tokenizedBuffer.invisibles.eol).toBe '?'
expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?'
describe "when the editor is constructed with an initialLine option", ->
it "positions the cursor on the specified line", ->

View File

@ -611,7 +611,8 @@ describe "TokenizedBuffer", ->
tokenizedBuffer = new TokenizedBuffer({buffer})
fullyTokenize(tokenizedBuffer)
tokenizedBuffer.setInvisibles(space: 'S', tab: 'T')
atom.config.set("editor.showInvisibles", true)
atom.config.set("editor.invisibles", space: 'S', tab: 'T')
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "SST Sa line with tabsTand T spacesSTS"
@ -623,7 +624,7 @@ describe "TokenizedBuffer", ->
tokenizedBuffer = new TokenizedBuffer({buffer})
atom.config.set('editor.showInvisibles', true)
tokenizedBuffer.setInvisibles(cr: 'R', eol: 'N')
atom.config.set("editor.invisibles", cr: 'R', eol: 'N')
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R', 'N']
@ -634,7 +635,7 @@ describe "TokenizedBuffer", ->
expect(left.endOfLineInvisibles).toBe null
expect(right.endOfLineInvisibles).toEqual ['R', 'N']
tokenizedBuffer.setInvisibles(cr: 'R', eol: false)
atom.config.set("editor.invisibles", cr: 'R', eol: false)
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R']
expect(tokenizedBuffer.tokenizedLineForRow(1).endOfLineInvisibles).toEqual []
@ -688,7 +689,8 @@ describe "TokenizedBuffer", ->
it "sets leading and trailing whitespace correctly on a line with invisible characters that is copied", ->
buffer.setText(" \t a line with tabs\tand \tspaces \t ")
tokenizedBuffer.setInvisibles(space: 'S', tab: 'T')
atom.config.set("editor.showInvisibles", true)
atom.config.set("editor.invisibles", space: 'S', tab: 'T')
fullyTokenize(tokenizedBuffer)
line = tokenizedBuffer.tokenizedLineForRow(0).copy()
@ -696,7 +698,8 @@ describe "TokenizedBuffer", ->
expect(line.tokens[line.tokens.length - 1].firstTrailingWhitespaceIndex).toBe 0
it "sets the ::firstNonWhitespaceIndex and ::firstTrailingWhitespaceIndex correctly when tokens are split for soft-wrapping", ->
tokenizedBuffer.setInvisibles(space: 'S')
atom.config.set("editor.showInvisibles", true)
atom.config.set("editor.invisibles", space: 'S')
buffer.setText(" token ")
fullyTokenize(tokenizedBuffer)
token = tokenizedBuffer.tokenizedLines[0].tokens[0]

View File

@ -24,13 +24,13 @@ class DisplayBuffer extends Model
horizontalScrollMargin: 6
scopedCharacterWidthsChangeCount: 0
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, @invisibles}={}) ->
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles}={}) ->
super
@emitter = new Emitter
@disposables = new CompositeDisposable
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, @invisibles})
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, ignoreInvisibles})
@buffer = @tokenizedBuffer.buffer
@charWidthsByScope = {}
@markers = {}
@ -87,14 +87,13 @@ class DisplayBuffer extends Model
scrollTop: @scrollTop
scrollLeft: @scrollLeft
tokenizedBuffer: @tokenizedBuffer.serialize()
invisibles: _.clone(@invisibles)
deserializeParams: (params) ->
params.tokenizedBuffer = TokenizedBuffer.deserialize(params.tokenizedBuffer)
params
copy: ->
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength(), @invisibles})
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength()})
newDisplayBuffer.setScrollTop(@getScrollTop())
newDisplayBuffer.setScrollLeft(@getScrollLeft())
@ -429,8 +428,8 @@ class DisplayBuffer extends Model
setTabLength: (tabLength) ->
@tokenizedBuffer.setTabLength(tabLength)
setInvisibles: (@invisibles) ->
@tokenizedBuffer.setInvisibles(@invisibles)
setIgnoreInvisibles: (ignoreInvisibles) ->
@tokenizedBuffer.setIgnoreInvisibles(ignoreInvisibles)
setSoftWrapped: (softWrapped) ->
if softWrapped isnt @softWrapped

View File

@ -84,12 +84,10 @@ class TextEditor extends Model
@selections = []
buffer ?= new TextBuffer
@displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped})
@displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini})
@buffer = @displayBuffer.buffer
@softTabs = @usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true
@updateInvisibles()
for marker in @findMarkers(@getSelectionMarkerAttributes())
marker.setProperties(preserveFolds: true)
@addSelection(marker)
@ -170,21 +168,9 @@ class TextEditor extends Model
@subscribe @displayBuffer.onDidAddDecoration (decoration) => @emit 'decoration-added', decoration
@subscribe @displayBuffer.onDidRemoveDecoration (decoration) => @emit 'decoration-removed', decoration
@subscribeToScopedConfigSettings()
subscribeToScopedConfigSettings: ->
@scopedConfigSubscriptions?.dispose()
@scopedConfigSubscriptions = subscriptions = new CompositeDisposable
scopeDescriptor = @getRootScopeDescriptor()
subscriptions.add atom.config.onDidChange 'editor.showInvisibles', scope: scopeDescriptor, => @updateInvisibles()
subscriptions.add atom.config.onDidChange 'editor.invisibles', scope: scopeDescriptor, => @updateInvisibles()
destroyed: ->
@unsubscribe() if includeDeprecatedAPIs
@disposables.dispose()
@scopedConfigSubscriptions.dispose()
selection.destroy() for selection in @getSelections()
@buffer.release()
@displayBuffer.destroy()
@ -488,7 +474,7 @@ class TextEditor extends Model
setMini: (mini) ->
if mini isnt @mini
@mini = mini
@updateInvisibles()
@displayBuffer.setIgnoreInvisibles(@mini)
@emitter.emit 'did-change-mini', @mini
@mini
@ -2779,15 +2765,6 @@ class TextEditor extends Model
shouldAutoIndentOnPaste: ->
atom.config.get("editor.autoIndentOnPaste", scope: @getRootScopeDescriptor())
shouldShowInvisibles: ->
not @mini and atom.config.get('editor.showInvisibles', scope: @getRootScopeDescriptor())
updateInvisibles: ->
if @shouldShowInvisibles()
@displayBuffer.setInvisibles(atom.config.get('editor.invisibles', scope: @getRootScopeDescriptor()))
else
@displayBuffer.setInvisibles(null)
###
Section: Event Handlers
###
@ -2796,8 +2773,6 @@ class TextEditor extends Model
@softTabs = @usesSoftTabs() ? @softTabs
handleGrammarChange: ->
@updateInvisibles()
@subscribeToScopedConfigSettings()
@unfoldAll()
@emit 'grammar-changed' if includeDeprecatedAPIs
@emitter.emit 'did-change-grammar', @getGrammar()

View File

@ -20,8 +20,9 @@ class TokenizedBuffer extends Model
chunkSize: 50
invalidRows: null
visible: false
configSettings: null
constructor: ({@buffer, @tabLength, @invisibles}) ->
constructor: ({@buffer, @tabLength, @ignoreInvisibles}) ->
@emitter = new Emitter
@disposables = new CompositeDisposable
@ -39,7 +40,7 @@ class TokenizedBuffer extends Model
serializeParams: ->
bufferPath: @buffer.getPath()
tabLength: @tabLength
invisibles: _.clone(@invisibles)
ignoreInvisibles: @ignoreInvisibles
deserializeParams: (params) ->
params.buffer = atom.project.bufferForPathSync(params.bufferPath)
@ -76,13 +77,28 @@ class TokenizedBuffer extends Model
@grammarUpdateDisposable = @grammar.onDidUpdate => @retokenizeLines()
@disposables.add(@grammarUpdateDisposable)
@configSettings = tabLength: atom.config.get('editor.tabLength', scope: @rootScopeDescriptor)
scopeOptions = {scope: @rootScopeDescriptor}
@configSettings =
tabLength: atom.config.get('editor.tabLength', scopeOptions)
invisibles: atom.config.get('editor.invisibles', scopeOptions)
showInvisibles: atom.config.get('editor.showInvisibles', scopeOptions)
@grammarTabLengthSubscription?.dispose()
@grammarTabLengthSubscription = atom.config.onDidChange 'editor.tabLength', scope: @rootScopeDescriptor, ({newValue}) =>
if @configSubscriptions?
@configSubscriptions.dispose()
@disposables.remove(@configSubscriptions)
@configSubscriptions = new CompositeDisposable
@configSubscriptions.add atom.config.onDidChange 'editor.tabLength', scopeOptions, ({newValue}) =>
@configSettings.tabLength = newValue
@retokenizeLines()
@disposables.add(@grammarTabLengthSubscription)
@configSubscriptions.add atom.config.onDidChange 'editor.invisibles', scopeOptions, ({newValue}) =>
oldInvisibles = @getInvisiblesToShow()
@configSettings.invisibles = newValue
@retokenizeLines() unless _.isEqual(@getInvisiblesToShow(), oldInvisibles)
@configSubscriptions.add atom.config.onDidChange 'editor.showInvisibles', scopeOptions, ({newValue}) =>
oldInvisibles = @getInvisiblesToShow()
@configSettings.showInvisibles = newValue
@retokenizeLines() unless _.isEqual(@getInvisiblesToShow(), oldInvisibles)
@disposables.add(@configSubscriptions)
@retokenizeLines()
@ -123,10 +139,11 @@ class TokenizedBuffer extends Model
@tabLength = tabLength
@retokenizeLines()
setInvisibles: (invisibles) ->
unless _.isEqual(invisibles, @invisibles)
@invisibles = invisibles
@retokenizeLines()
setIgnoreInvisibles: (ignoreInvisibles) ->
if ignoreInvisibles isnt @ignoreInvisibles
@ignoreInvisibles = ignoreInvisibles
if @configSettings.showInvisibles and @configSettings.invisibles?
@retokenizeLines()
tokenizeInBackground: ->
return if not @visible or @pendingChunk or not @isAlive()
@ -302,7 +319,7 @@ class TokenizedBuffer extends Model
tabLength = @getTabLength()
indentLevel = @indentLevelForRow(row)
lineEnding = @buffer.lineEndingForRow(row)
new TokenizedLine({tokens, tabLength, indentLevel, @invisibles, lineEnding})
new TokenizedLine({tokens, tabLength, indentLevel, invisibles: @getInvisiblesToShow(), lineEnding})
buildTokenizedLineForRow: (row, ruleStack) ->
@buildTokenizedLineForRowWithText(row, @buffer.lineForRow(row), ruleStack)
@ -312,7 +329,13 @@ class TokenizedBuffer extends Model
tabLength = @getTabLength()
indentLevel = @indentLevelForRow(row)
{tokens, ruleStack} = @grammar.tokenizeLine(line, ruleStack, row is 0)
new TokenizedLine({tokens, ruleStack, tabLength, lineEnding, indentLevel, @invisibles})
new TokenizedLine({tokens, ruleStack, tabLength, lineEnding, indentLevel, invisibles: @getInvisiblesToShow()})
getInvisiblesToShow: ->
if @configSettings.showInvisibles and not @ignoreInvisibles
@configSettings.invisibles
else
null
tokenizedLineForRow: (bufferRow) ->
@tokenizedLines[bufferRow]