Fix gutter specs and update lines when digit counts change

This commit is contained in:
Nathan Sobo 2014-05-16 14:52:24 -06:00
parent fe82e3e30f
commit e74dfe3438
3 changed files with 78 additions and 67 deletions

View File

@ -151,41 +151,39 @@ describe "EditorComponent", ->
node.style.height = 4.5 * lineHeightInPixels + 'px'
component.measureHeightAndWidth()
lineNumberNodes = node.querySelectorAll('.line-number')
expect(lineNumberNodes.length).toBe 6
expect(lineNumberNodes[0].textContent).toBe "#{nbsp}1"
expect(lineNumberNodes[5].textContent).toBe "#{nbsp}6"
expect(node.querySelectorAll('.line-number').length).toBe 6 + 2 + 1 # line overdraw margin below + dummy line number
expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1"
expect(component.lineNumberNodeForScreenRow(5).textContent).toBe "#{nbsp}6"
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
lineNumberNodes = node.querySelectorAll('.line-number')
expect(lineNumberNodes.length).toBe 6
expect(node.querySelectorAll('.line-number').length).toBe 6 + 4 + 1 # line overdraw margin above/below + dummy line number
expect(lineNumberNodes[0].textContent).toBe "#{nbsp}3"
expect(lineNumberNodes[0].style['-webkit-transform']).toBe "translate3d(0px, #{-.5 * lineHeightInPixels}px, 0px)"
expect(lineNumberNodes[5].textContent).toBe "#{nbsp}8"
expect(lineNumberNodes[5].style['-webkit-transform']).toBe "translate3d(0px, #{4.5 * lineHeightInPixels}px, 0px)"
expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}3"
expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
return
expect(component.lineNumberNodeForScreenRow(7).textContent).toBe "#{nbsp}8"
expect(component.lineNumberNodeForScreenRow(7).offsetTop).toBe 7 * lineHeightInPixels
it "updates the translation of subsequent line numbers when lines are inserted or removed", ->
editor.getBuffer().insert([0, 0], '\n\n')
lineNumberNodes = node.querySelectorAll('.line-number')
expect(lineNumberNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expect(lineNumberNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * lineHeightInPixels}px, 0px)"
expect(lineNumberNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * lineHeightInPixels}px, 0px)"
expect(lineNumberNodes[3].style['-webkit-transform']).toBe "translate3d(0px, #{3 * lineHeightInPixels}px, 0px)"
expect(lineNumberNodes[4].style['-webkit-transform']).toBe "translate3d(0px, #{4 * lineHeightInPixels}px, 0px)"
expect(component.lineNumberNodeForScreenRow(0).offsetTop).toBe 0
expect(component.lineNumberNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels
expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
expect(component.lineNumberNodeForScreenRow(3).offsetTop).toBe 3 * lineHeightInPixels
expect(component.lineNumberNodeForScreenRow(4).offsetTop).toBe 4 * lineHeightInPixels
editor.getBuffer().insert([0, 0], '\n\n')
lineNumberNodes = node.querySelectorAll('.line-number')
expect(lineNumberNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expect(lineNumberNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * lineHeightInPixels}px, 0px)"
expect(lineNumberNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * lineHeightInPixels}px, 0px)"
expect(lineNumberNodes[3].style['-webkit-transform']).toBe "translate3d(0px, #{3 * lineHeightInPixels}px, 0px)"
expect(lineNumberNodes[4].style['-webkit-transform']).toBe "translate3d(0px, #{4 * lineHeightInPixels}px, 0px)"
expect(lineNumberNodes[5].style['-webkit-transform']).toBe "translate3d(0px, #{5 * lineHeightInPixels}px, 0px)"
expect(lineNumberNodes[6].style['-webkit-transform']).toBe "translate3d(0px, #{6 * lineHeightInPixels}px, 0px)"
expect(component.lineNumberNodeForScreenRow(0).offsetTop).toBe 0
expect(component.lineNumberNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels
expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
expect(component.lineNumberNodeForScreenRow(3).offsetTop).toBe 3 * lineHeightInPixels
expect(component.lineNumberNodeForScreenRow(4).offsetTop).toBe 4 * lineHeightInPixels
expect(component.lineNumberNodeForScreenRow(5).offsetTop).toBe 5 * lineHeightInPixels
expect(component.lineNumberNodeForScreenRow(6).offsetTop).toBe 6 * lineHeightInPixels
it "renders • characters for soft-wrapped lines", ->
editor.setSoftWrap(true)
@ -193,28 +191,24 @@ describe "EditorComponent", ->
node.style.width = 30 * charWidth + 'px'
component.measureHeightAndWidth()
lines = node.querySelectorAll('.line-number')
expect(lines.length).toBe 6
expect(lines[0].textContent).toBe "#{nbsp}1"
expect(lines[1].textContent).toBe "#{nbsp}"
expect(lines[2].textContent).toBe "#{nbsp}2"
expect(lines[3].textContent).toBe "#{nbsp}"
expect(lines[4].textContent).toBe "#{nbsp}3"
expect(lines[5].textContent).toBe "#{nbsp}"
expect(node.querySelectorAll('.line-number').length).toBe 6 + lineOverdrawMargin + 1 # 1 dummy line node
expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1"
expect(component.lineNumberNodeForScreenRow(1).textContent).toBe "#{nbsp}"
expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}2"
expect(component.lineNumberNodeForScreenRow(3).textContent).toBe "#{nbsp}"
expect(component.lineNumberNodeForScreenRow(4).textContent).toBe "#{nbsp}3"
expect(component.lineNumberNodeForScreenRow(5).textContent).toBe "#{nbsp}"
it "pads line numbers to be right justified based on the maximum number of line number digits", ->
it "pads line numbers to be right-justified based on the maximum number of line number digits", ->
editor.getBuffer().setText([1..10].join('\n'))
lineNumberNodes = toArray(node.querySelectorAll('.line-number'))
for node, i in lineNumberNodes[0..8]
expect(node.textContent).toBe "#{nbsp}#{i + 1}"
expect(lineNumberNodes[9].textContent).toBe '10'
for screenRow in [0..8]
expect(component.lineNumberNodeForScreenRow(screenRow).textContent).toBe "#{nbsp}#{screenRow + 1}"
expect(component.lineNumberNodeForScreenRow(9).textContent).toBe "10"
# Removes padding when the max number of digits goes down
editor.getBuffer().delete([[1, 0], [2, 0]])
lineNumberNodes = toArray(node.querySelectorAll('.line-number'))
for node, i in lineNumberNodes
expect(node.textContent).toBe "#{i + 1}"
for screenRow in [0..8]
expect(component.lineNumberNodeForScreenRow(screenRow).textContent).toBe "#{screenRow + 1}"
describe "cursor rendering", ->
it "renders the currently visible cursors, translated relative to the scroll position", ->

View File

@ -51,8 +51,8 @@ EditorComponent = React.createClass
div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1,
GutterComponent {
editor, visibleRowRange, lineOverdrawMargin, maxLineNumberDigits, scrollTop,
scrollHeight, lineHeight: lineHeightInPixels, fontSize, fontFamily,
ref: 'gutter', editor, visibleRowRange, lineOverdrawMargin, maxLineNumberDigits,
scrollTop, scrollHeight, lineHeight: lineHeightInPixels, fontSize, fontFamily,
@pendingChanges, onWidthChanged: @onGutterWidthChanged
}
@ -424,3 +424,5 @@ EditorComponent = React.createClass
e.abortKeyBinding() unless @props.editor.consolidateSelections()
lineNodeForScreenRow: (screenRow) -> @refs.scrollView.lineNodeForScreenRow(screenRow)
lineNumberNodeForScreenRow: (screenRow) -> @refs.gutter.lineNumberNodeForScreenRow(screenRow)

View File

@ -22,7 +22,8 @@ GutterComponent = React.createClass
componentWillMount: ->
@lineNumberNodesById = {}
@lineNumberNodeTopPositions = {}
@lineNumberIdsByScreenRow = {}
@screenRowsByLineNumberId = {}
componentDidMount: ->
@appendDummyLineNumber()
@ -40,10 +41,18 @@ GutterComponent = React.createClass
false
componentDidUpdate: (oldProps) ->
@updateDummyLineNumber() if oldProps.maxLineNumberDigits isnt @props.maxLineNumberDigits
unless oldProps.maxLineNumberDigits is @props.maxLineNumberDigits
@updateDummyLineNumber()
@removeLineNumberNodes()
@measureWidth() unless @lastMeasuredWidth? and isEqualForProperties(oldProps, @props, 'maxLineNumberDigits', 'fontSize', 'fontFamily')
@clearScreenRowCaches() unless oldProps.lineHeight is @props.lineHeight
@updateLineNumbers()
clearScreenRowCaches: ->
@lineNumberIdsByScreenRow = {}
@screenRowsByLineNumberId = {}
# This dummy line number element holds the gutter to the appropriate width,
# since the real line numbers are absolutely positioned for performance reasons.
appendDummyLineNumber: ->
@ -56,11 +65,11 @@ GutterComponent = React.createClass
WrapperDiv.innerHTML = @buildLineNumberInnerHTML(0, false, @props.maxLineNumberDigits)
updateLineNumbers: ->
visibleLineNumberIds = @appendOrUpdateVisibleLineNumberNodes()
@removeNonVisibleLineNumberNodes(visibleLineNumberIds)
lineNumberIdsToPreserve = @appendOrUpdateVisibleLineNumberNodes()
@removeLineNumberNodes(lineNumberIdsToPreserve)
appendOrUpdateVisibleLineNumberNodes: ->
{editor, visibleRowRange, scrollTop, lineHeight, maxLineNumberDigits, lineOverdrawMargin} = @props
{editor, visibleRowRange, scrollTop, maxLineNumberDigits, lineOverdrawMargin} = @props
[startRow, endRow] = visibleRowRange
startRow = Math.max(0, startRow - lineOverdrawMargin)
endRow = Math.min(editor.getLineCount(), endRow + lineOverdrawMargin)
@ -71,6 +80,8 @@ GutterComponent = React.createClass
wrapCount = 0
for bufferRow, index in editor.bufferRowsForScreenRows(startRow, endRow - 1)
screenRow = startRow + index
if bufferRow is lastBufferRow
id = "#{bufferRow}-#{wrapCount++}"
else
@ -80,17 +91,16 @@ GutterComponent = React.createClass
visibleLineNumberIds.add(id)
screenRow = startRow + index
top = screenRow * lineHeight
if @hasLineNumberNode(id)
@updateLineNumberNode(id, top)
@updateLineNumberNode(id, screenRow)
else
newLineNumberIds ?= []
newLineNumbersHTML ?= ""
newLineNumberIds.push(id)
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, top)
@lineNumberNodeTopPositions[id] = top
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
@screenRowsByLineNumberId[id] = screenRow
@lineNumberIdsByScreenRow[screenRow] = id
if newLineNumberIds?
WrapperDiv.innerHTML = newLineNumbersHTML
@ -104,23 +114,26 @@ GutterComponent = React.createClass
visibleLineNumberIds
removeNonVisibleLineNumberNodes: (visibleLineNumberIds) ->
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
node = @refs.lineNumbers.getDOMNode()
for id, lineNumberNode of @lineNumberNodesById when not visibleLineNumberIds.has(id)
delete @lineNumberNodesById[id]
delete @lineNumberNodeTopPositions[id]
for lineNumberId, lineNumberNode of @lineNumberNodesById when not lineNumberIdsToPreserve?.has(lineNumberId)
delete @lineNumberNodesById[lineNumberId]
screenRow = @screenRowsByLineNumberId[lineNumberId]
delete @lineNumberIdsByScreenRow[screenRow] if @lineNumberIdsByScreenRow[screenRow] is lineNumberId
delete @screenRowsByLineNumberId[lineNumberId]
node.removeChild(lineNumberNode)
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, top) ->
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
if top?
style = "position: absolute; top: #{top}px;"
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
if screenRow?
{lineHeight} = @props
style = "position: absolute; top: #{screenRow * lineHeight}px;"
else
style = "visibility: hidden;"
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
"<div class=\"line-number editor-colors\" style=\"#{style}\">#{innerHTML}</div>"
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits, top) ->
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
if softWrapped
lineNumber = ""
else
@ -130,16 +143,18 @@ GutterComponent = React.createClass
iconHTML = '<div class="icon-right"></div>'
padding + lineNumber + iconHTML
updateLineNumberNode: (lineNumberId, top) ->
unless @lineNumberNodeTopPositions[lineNumberId] is top
@lineNumberNodesById[lineNumberId].style.top = top + 'px'
@lineNumberNodeTopPositions[lineNumberId] = top
updateLineNumberNode: (lineNumberId, screenRow) ->
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
{lineHeight} = @props
@lineNumberNodesById[lineNumberId].style.top = screenRow * lineHeight + 'px'
@screenRowsByLineNumberId[lineNumberId] = screenRow
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
hasLineNumberNode: (lineNumberId) ->
@lineNumberNodesById.hasOwnProperty(lineNumberId)
buildTranslate3d: (top) ->
"translate3d(0px, #{top}px, 0px)"
lineNumberNodeForScreenRow: (screenRow) ->
@lineNumberNodesById[@lineNumberIdsByScreenRow[screenRow]]
measureWidth: ->
lineNumberNode = @refs.lineNumbers.getDOMNode().firstChild