mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Improve scrolling performance
This commit is contained in:
@@ -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
|
||||
|
||||
19
spec/tokenized-line-spec.coffee
Normal file
19
spec/tokenized-line-spec.coffee
Normal 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 '{'
|
||||
@@ -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> </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
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
Reference in New Issue
Block a user