Files
atom/src/gutter-container-component.coffee
Christopher Chedeau c8a398e6e9 Fix prepending multiple gutters at once
There's a bug when multiple gutters are prepended at once where their order is not correctly preserved in the dom. The issue is that we do not update indexInOldGutters even though we inserted a dom node in the old gutters dom.

Honestly, I'm unconvinced that this entire logic is correct, but this fixes the use case we have in Nuclide so it's more correct than before :)

Released under CC0
2016-11-19 12:22:59 -08:00

113 lines
4.4 KiB
CoffeeScript

_ = require 'underscore-plus'
CustomGutterComponent = require './custom-gutter-component'
LineNumberGutterComponent = require './line-number-gutter-component'
# The GutterContainerComponent manages the GutterComponents of a particular
# TextEditorComponent.
module.exports =
class GutterContainerComponent
constructor: ({@onLineNumberGutterMouseDown, @editor, @domElementPool, @views}) ->
# An array of objects of the form: {name: {String}, component: {Object}}
@gutterComponents = []
@gutterComponentsByGutterName = {}
@lineNumberGutterComponent = null
@domNode = document.createElement('div')
@domNode.classList.add('gutter-container')
@domNode.style.display = 'flex'
destroy: ->
for {component} in @gutterComponents
component.destroy?()
return
getDomNode: ->
@domNode
getLineNumberGutterComponent: ->
@lineNumberGutterComponent
updateSync: (state) ->
# The GutterContainerComponent expects the gutters to be sorted in the order
# they should appear.
newState = state.gutters
newGutterComponents = []
newGutterComponentsByGutterName = {}
for {gutter, visible, styles, content} in newState
gutterComponent = @gutterComponentsByGutterName[gutter.name]
if not gutterComponent
if gutter.name is 'line-number'
gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, gutter, @domElementPool, @views})
@lineNumberGutterComponent = gutterComponent
else
gutterComponent = new CustomGutterComponent({gutter, @views})
if visible then gutterComponent.showNode() else gutterComponent.hideNode()
# Pass the gutter only the state that it needs.
if gutter.name is 'line-number'
# For ease of use in the line number gutter component, set the shared
# 'styles' as a field under the 'content'.
gutterSubstate = _.clone(content)
gutterSubstate.styles = styles
else
# Custom gutter 'content' is keyed on gutter name, so we cannot set
# 'styles' as a subfield directly under it.
gutterSubstate = {content, styles}
gutterComponent.updateSync(gutterSubstate)
newGutterComponents.push({
name: gutter.name,
component: gutterComponent,
})
newGutterComponentsByGutterName[gutter.name] = gutterComponent
@reorderGutters(newGutterComponents, newGutterComponentsByGutterName)
@gutterComponents = newGutterComponents
@gutterComponentsByGutterName = newGutterComponentsByGutterName
###
Section: Private Methods
###
reorderGutters: (newGutterComponents, newGutterComponentsByGutterName) ->
# First, insert new gutters into the DOM.
indexInOldGutters = 0
oldGuttersLength = @gutterComponents.length
for gutterComponentDescription in newGutterComponents
gutterComponent = gutterComponentDescription.component
gutterName = gutterComponentDescription.name
if @gutterComponentsByGutterName[gutterName]
# If the gutter existed previously, we first try to move the cursor to
# the point at which it occurs in the previous gutters.
matchingGutterFound = false
while indexInOldGutters < oldGuttersLength
existingGutterComponentDescription = @gutterComponents[indexInOldGutters]
existingGutterComponent = existingGutterComponentDescription.component
indexInOldGutters++
if existingGutterComponent is gutterComponent
matchingGutterFound = true
break
if not matchingGutterFound
# If we've reached this point, the gutter previously existed, but its
# position has moved. Remove it from the DOM and re-insert it.
gutterComponent.getDomNode().remove()
@domNode.appendChild(gutterComponent.getDomNode())
else
if indexInOldGutters is oldGuttersLength
@domNode.appendChild(gutterComponent.getDomNode())
else
@domNode.insertBefore(gutterComponent.getDomNode(), @domNode.children[indexInOldGutters])
indexInOldGutters += 1
# Remove any gutters that were not present in the new gutters state.
for gutterComponentDescription in @gutterComponents
if not newGutterComponentsByGutterName[gutterComponentDescription.name]
gutterComponent = gutterComponentDescription.component
gutterComponent.getDomNode().remove()