Improve scrolling performance

This commit is contained in:
Nathan Sobo
2014-03-28 10:28:06 -06:00
parent 33ed403818
commit 958bc638d7
4 changed files with 65 additions and 41 deletions

View File

@@ -25,9 +25,10 @@ describe "EditorComponent", ->
expect(lines[4].textContent).toBe editor.lineForScreenRow(4).text
node.querySelector('.vertical-scrollbar').scrollTop = 2.5 * lineHeight
spyOn(window, 'requestAnimationFrame').andCallFake (fn) -> fn()
component.onVerticalScroll()
expect(node.querySelector('.lines').offsetTop).toBe -2.5 * lineHeight
expect(node.querySelector('.lines').style['-webkit-transform']).toBe "translateY(#{-2.5 * lineHeight}px)"
lines = node.querySelectorAll('.line')
expect(lines.length).toBe 5

View File

@@ -0,0 +1,19 @@
describe "TokenizedLine", ->
editor = null
beforeEach ->
waitsForPromise -> atom.packages.activatePackage('language-javascript')
runs -> editor = atom.project.openSync('sample.js')
describe "::getScopeTree()", ->
it "returns a tree whose inner nodes are scopes and whose leaf nodes are tokens in those scopes", ->
scopeTree = editor.lineForScreenRow(1).getScopeTree()
expect(scopeTree.scope).toBe 'source.js'
expect(scopeTree.children[0].value).toBe ' '
expect(scopeTree.children[1].scope).toBe 'storage.modifier.js'
expect(scopeTree.children[1].children[0].value).toBe 'var'
expect(scopeTree.children[2].value).toBe ' '
expect(scopeTree.children[3].scope).toBe 'meta.function.js'
expect(scopeTree.children[4].value).toBe ' '
expect(scopeTree.children[5].scope).toBe 'meta.brace.curly.js'
expect(scopeTree.children[5].children[0].value).toBe '{'

View File

@@ -7,6 +7,8 @@ DummyLineNode = $$ ->
module.exports =
React.createClass
pendingScrollTop: null
render: ->
div class: 'editor',
div class: 'scroll-view', ref: 'scrollView',
@@ -44,13 +46,13 @@ React.createClass
@props.editor.off 'screen-lines-changed', @onScreenLinesChanged
@getDOMNode().removeEventListener 'mousewheel', @onMousewheel
componentWilUpdate: (nextProps, nextState) ->
if nextState.scrollTop?
@refs.verticalScrollbar.getDOMNode().scrollTop = nextState.scrollTop
onVerticalScroll: ->
scrollTop = @refs.verticalScrollbar.getDOMNode().scrollTop
@setState({scrollTop})
animationFramePending = @pendingScrollTop?
@pendingScrollTop = @refs.verticalScrollbar.getDOMNode().scrollTop
unless animationFramePending
requestAnimationFrame =>
@setState({scrollTop: @pendingScrollTop})
@pendingScrollTop = null
onMousewheel: (event) ->
@refs.verticalScrollbar.getDOMNode().scrollTop -= event.wheelDeltaY
@@ -90,17 +92,20 @@ React.createClass
LineComponent = React.createClass
render: ->
div class: 'line',
if @props.tokenizedLine.text.length is 0
span String.fromCharCode(160) # non-breaking space; bypasses escaping
else
@renderScopeTree(@props.tokenizedLine.getScopeTree())
div class: 'line', dangerouslySetInnerHTML: {__html: @buildInnerHTML()}
renderScopeTree: (scopeTree) ->
if scopeTree.scope?
span class: scopeTree.scope.split('.').join(' '),
scopeTree.children.map((child) => @renderScopeTree(child))...
buildInnerHTML: ->
if @props.tokenizedLine.text.length is 0
"<span>&nbsp;</span>"
else
span scopeTree.value
@buildScopeTreeHTML(@props.tokenizedLine.getScopeTree())
buildScopeTreeHTML: (scopeTree) ->
if scopeTree.children?
html = "<span class='#{scopeTree.scope.replace(/\./g, ' ')}'>"
html += @buildScopeTreeHTML(child) for child in scopeTree.children
html
else
"<span>#{scopeTree.value}</span>"
shouldComponentUpdate: -> false

View File

@@ -139,32 +139,31 @@ class TokenizedLine
column += token.bufferDelta
getScopeTree: ->
@scopeTree ?= new ScopeTree(@tokens)
class ScopeTree
constructor: (@tokens, @scope, @depth=0) ->
@scope ?= @tokens[0].scopes[@depth]
@children = []
childDepth = @depth + 1
currentChildScope = null
currentChildTokens = []
return @scopeTree if @scopeTree?
scopeStack = []
for token in @tokens
tokenScope = token.scopes[childDepth]
@updateScopeStack(scopeStack, token.scopes)
_.last(scopeStack).children.push(token)
if tokenScope is currentChildScope
currentChildTokens.push(token)
else
if currentChildScope?
@children.push(new ScopeTree(currentChildTokens, currentChildScope, childDepth))
currentChildScope = null
currentChildTokens = []
@scopeTree = scopeStack[0]
@updateScopeStack(scopeStack, [])
@scopeTree
if tokenScope?
currentChildScope = tokenScope
currentChildTokens.push(token)
else
@children.push(token)
updateScopeStack: (scopeStack, desiredScopes) ->
# Find a common prefix
for scope, i in desiredScopes
break unless scopeStack[i]?.scope is desiredScopes[i]
if currentChildScope?
@children.push(new ScopeTree(currentChildTokens, currentChildScope, childDepth))
# Pop scopes until we're at the common prefx
until scopeStack.length is i
poppedScope = scopeStack.pop()
_.last(scopeStack)?.children.push(poppedScope)
# Push onto common prefix until scopeStack equals desiredScopes
for j in [i...desiredScopes.length]
scopeStack.push(new Scope(scope))
class Scope
constructor: (@scope) ->
@children = []