2014-06-06 04:56:39 +04:00
|
|
|
_ = require 'underscore-plus'
|
2014-05-21 09:05:13 +04:00
|
|
|
React = require 'react-atom-fork'
|
|
|
|
{div} = require 'reactionary-atom-fork'
|
2014-05-14 18:47:15 +04:00
|
|
|
{isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
2014-04-15 23:56:58 +04:00
|
|
|
SubscriberMixin = require './subscriber-mixin'
|
2014-04-14 20:11:11 +04:00
|
|
|
|
2014-05-14 18:47:15 +04:00
|
|
|
WrapperDiv = document.createElement('div')
|
|
|
|
|
2014-04-14 20:11:11 +04:00
|
|
|
module.exports =
|
|
|
|
GutterComponent = React.createClass
|
2014-04-15 20:39:47 +04:00
|
|
|
displayName: 'GutterComponent'
|
2014-04-15 23:56:58 +04:00
|
|
|
mixins: [SubscriberMixin]
|
2014-04-15 20:39:47 +04:00
|
|
|
|
2014-05-17 01:56:18 +04:00
|
|
|
dummyLineNumberNode: null
|
2014-04-25 05:07:17 +04:00
|
|
|
|
2014-04-14 20:11:11 +04:00
|
|
|
render: ->
|
2014-06-06 23:23:12 +04:00
|
|
|
{scrollHeight, scrollTop, onClick} = @props
|
2014-05-11 01:30:52 +04:00
|
|
|
|
2014-06-06 23:23:12 +04:00
|
|
|
div className: 'gutter', onClick: onClick,
|
2014-05-15 04:05:54 +04:00
|
|
|
div className: 'line-numbers', ref: 'lineNumbers', style:
|
|
|
|
height: scrollHeight
|
|
|
|
WebkitTransform: "translate3d(0px, #{-scrollTop}px, 0px)"
|
2014-05-11 01:30:52 +04:00
|
|
|
|
2014-05-14 18:47:15 +04:00
|
|
|
componentWillMount: ->
|
|
|
|
@lineNumberNodesById = {}
|
2014-05-17 00:52:24 +04:00
|
|
|
@lineNumberIdsByScreenRow = {}
|
|
|
|
@screenRowsByLineNumberId = {}
|
2014-06-06 04:56:39 +04:00
|
|
|
@previousDecorations = {}
|
2014-04-14 20:11:11 +04:00
|
|
|
|
2014-05-16 03:11:54 +04:00
|
|
|
componentDidMount: ->
|
|
|
|
@appendDummyLineNumber()
|
|
|
|
|
2014-04-15 23:56:58 +04:00
|
|
|
# Only update the gutter if the visible row range has changed or if a
|
|
|
|
# non-zero-delta change to the screen lines has occurred within the current
|
|
|
|
# visible row range.
|
|
|
|
shouldComponentUpdate: (newProps) ->
|
2014-05-30 01:28:41 +04:00
|
|
|
return true unless isEqualForProperties(newProps, @props,
|
2014-06-03 13:25:25 +04:00
|
|
|
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow'
|
2014-05-30 01:28:41 +04:00
|
|
|
)
|
2014-04-15 23:56:58 +04:00
|
|
|
|
2014-06-06 04:56:39 +04:00
|
|
|
{renderedRowRange, pendingChanges, decorations} = newProps
|
2014-05-14 18:47:15 +04:00
|
|
|
for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0
|
2014-05-17 06:58:40 +04:00
|
|
|
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
|
2014-04-15 23:56:58 +04:00
|
|
|
|
2014-06-06 04:56:39 +04:00
|
|
|
return true unless _.isEqual(@previousDecorations, decorations)
|
|
|
|
|
2014-04-15 23:56:58 +04:00
|
|
|
false
|
|
|
|
|
2014-04-25 05:07:17 +04:00
|
|
|
componentDidUpdate: (oldProps) ->
|
2014-05-17 00:52:24 +04:00
|
|
|
unless oldProps.maxLineNumberDigits is @props.maxLineNumberDigits
|
|
|
|
@updateDummyLineNumber()
|
|
|
|
@removeLineNumberNodes()
|
|
|
|
|
2014-05-22 04:04:44 +04:00
|
|
|
@clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels
|
2014-05-14 18:47:15 +04:00
|
|
|
@updateLineNumbers()
|
2014-05-16 03:11:54 +04:00
|
|
|
|
2014-05-17 00:52:24 +04:00
|
|
|
clearScreenRowCaches: ->
|
|
|
|
@lineNumberIdsByScreenRow = {}
|
|
|
|
@screenRowsByLineNumberId = {}
|
|
|
|
|
2014-05-16 03:11:54 +04:00
|
|
|
# This dummy line number element holds the gutter to the appropriate width,
|
|
|
|
# since the real line numbers are absolutely positioned for performance reasons.
|
|
|
|
appendDummyLineNumber: ->
|
|
|
|
{maxLineNumberDigits} = @props
|
|
|
|
WrapperDiv.innerHTML = @buildLineNumberHTML(0, false, maxLineNumberDigits)
|
|
|
|
@dummyLineNumberNode = WrapperDiv.children[0]
|
|
|
|
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
|
|
|
|
|
|
|
|
updateDummyLineNumber: ->
|
2014-05-17 01:56:18 +04:00
|
|
|
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false, @props.maxLineNumberDigits)
|
2014-04-25 05:07:17 +04:00
|
|
|
|
2014-05-14 18:47:15 +04:00
|
|
|
updateLineNumbers: ->
|
2014-05-17 00:52:24 +04:00
|
|
|
lineNumberIdsToPreserve = @appendOrUpdateVisibleLineNumberNodes()
|
|
|
|
@removeLineNumberNodes(lineNumberIdsToPreserve)
|
2014-04-16 03:17:30 +04:00
|
|
|
|
2014-05-14 18:47:15 +04:00
|
|
|
appendOrUpdateVisibleLineNumberNodes: ->
|
2014-06-06 04:56:39 +04:00
|
|
|
{editor, renderedRowRange, scrollTop, maxLineNumberDigits, decorations} = @props
|
2014-05-17 06:58:40 +04:00
|
|
|
[startRow, endRow] = renderedRowRange
|
2014-05-15 04:05:54 +04:00
|
|
|
|
2014-05-14 18:47:15 +04:00
|
|
|
newLineNumberIds = null
|
|
|
|
newLineNumbersHTML = null
|
|
|
|
visibleLineNumberIds = new Set
|
|
|
|
|
|
|
|
wrapCount = 0
|
|
|
|
for bufferRow, index in editor.bufferRowsForScreenRows(startRow, endRow - 1)
|
2014-05-17 00:52:24 +04:00
|
|
|
screenRow = startRow + index
|
|
|
|
|
2014-05-14 18:47:15 +04:00
|
|
|
if bufferRow is lastBufferRow
|
|
|
|
id = "#{bufferRow}-#{wrapCount++}"
|
|
|
|
else
|
|
|
|
id = bufferRow.toString()
|
|
|
|
lastBufferRow = bufferRow
|
|
|
|
wrapCount = 0
|
|
|
|
|
|
|
|
visibleLineNumberIds.add(id)
|
|
|
|
|
|
|
|
if @hasLineNumberNode(id)
|
2014-06-06 04:56:39 +04:00
|
|
|
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0, decorations[bufferRow])
|
2014-05-14 18:47:15 +04:00
|
|
|
else
|
|
|
|
newLineNumberIds ?= []
|
|
|
|
newLineNumbersHTML ?= ""
|
|
|
|
newLineNumberIds.push(id)
|
2014-06-06 04:56:39 +04:00
|
|
|
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow, decorations[bufferRow])
|
2014-05-17 00:52:24 +04:00
|
|
|
@screenRowsByLineNumberId[id] = screenRow
|
|
|
|
@lineNumberIdsByScreenRow[screenRow] = id
|
2014-05-14 18:47:15 +04:00
|
|
|
|
|
|
|
if newLineNumberIds?
|
|
|
|
WrapperDiv.innerHTML = newLineNumbersHTML
|
|
|
|
newLineNumberNodes = toArray(WrapperDiv.children)
|
|
|
|
|
|
|
|
node = @refs.lineNumbers.getDOMNode()
|
|
|
|
for lineNumberId, i in newLineNumberIds
|
|
|
|
lineNumberNode = newLineNumberNodes[i]
|
|
|
|
@lineNumberNodesById[lineNumberId] = lineNumberNode
|
|
|
|
node.appendChild(lineNumberNode)
|
|
|
|
|
2014-06-06 04:56:39 +04:00
|
|
|
@previousDecorations = decorations
|
2014-05-14 18:47:15 +04:00
|
|
|
visibleLineNumberIds
|
|
|
|
|
2014-05-17 00:52:24 +04:00
|
|
|
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
|
2014-05-20 00:03:34 +04:00
|
|
|
{mouseWheelScreenRow} = @props
|
2014-05-14 18:47:15 +04:00
|
|
|
node = @refs.lineNumbers.getDOMNode()
|
2014-05-17 00:52:24 +04:00
|
|
|
for lineNumberId, lineNumberNode of @lineNumberNodesById when not lineNumberIdsToPreserve?.has(lineNumberId)
|
|
|
|
screenRow = @screenRowsByLineNumberId[lineNumberId]
|
2014-06-03 12:43:21 +04:00
|
|
|
if not screenRow? or screenRow isnt mouseWheelScreenRow
|
2014-05-20 00:03:34 +04:00
|
|
|
delete @lineNumberNodesById[lineNumberId]
|
|
|
|
delete @lineNumberIdsByScreenRow[screenRow] if @lineNumberIdsByScreenRow[screenRow] is lineNumberId
|
|
|
|
delete @screenRowsByLineNumberId[lineNumberId]
|
|
|
|
node.removeChild(lineNumberNode)
|
2014-05-14 18:47:15 +04:00
|
|
|
|
2014-06-06 04:56:39 +04:00
|
|
|
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow, decorations) ->
|
2014-05-17 00:52:24 +04:00
|
|
|
if screenRow?
|
2014-05-22 04:04:44 +04:00
|
|
|
{lineHeightInPixels} = @props
|
|
|
|
style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
|
2014-05-16 03:11:54 +04:00
|
|
|
else
|
|
|
|
style = "visibility: hidden;"
|
2014-05-17 00:52:24 +04:00
|
|
|
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
|
2014-05-16 03:11:54 +04:00
|
|
|
|
2014-06-06 04:56:39 +04:00
|
|
|
classes = ''
|
|
|
|
if decorations?
|
|
|
|
for decoration in decorations
|
|
|
|
classes += decoration.class + ' ' if not softWrapped or softWrapped and decoration.softWrap
|
|
|
|
classes += 'line-number'
|
2014-06-04 20:53:22 +04:00
|
|
|
|
2014-06-06 04:56:39 +04:00
|
|
|
"<div class=\"#{classes}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
2014-05-16 03:11:54 +04:00
|
|
|
|
2014-05-17 00:52:24 +04:00
|
|
|
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
|
2014-05-14 18:47:15 +04:00
|
|
|
if softWrapped
|
|
|
|
lineNumber = "•"
|
2014-04-14 20:11:11 +04:00
|
|
|
else
|
2014-05-14 18:47:15 +04:00
|
|
|
lineNumber = (bufferRow + 1).toString()
|
2014-04-14 20:11:11 +04:00
|
|
|
|
2014-05-14 18:47:15 +04:00
|
|
|
padding = multiplyString(' ', maxLineNumberDigits - lineNumber.length)
|
|
|
|
iconHTML = '<div class="icon-right"></div>'
|
2014-05-16 03:11:54 +04:00
|
|
|
padding + lineNumber + iconHTML
|
2014-05-14 18:47:15 +04:00
|
|
|
|
2014-06-06 04:56:39 +04:00
|
|
|
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped, decorations) ->
|
2014-06-04 04:26:57 +04:00
|
|
|
node = @lineNumberNodesById[lineNumberId]
|
2014-06-06 04:56:39 +04:00
|
|
|
previousDecorations = @previousDecorations[bufferRow]
|
2014-06-04 04:26:57 +04:00
|
|
|
|
2014-06-06 04:56:39 +04:00
|
|
|
if previousDecorations?
|
|
|
|
for decoration in previousDecorations
|
|
|
|
node.classList.remove(decoration.class) if not contains(decorations, decoration)
|
2014-06-04 04:26:57 +04:00
|
|
|
|
2014-06-06 05:12:36 +04:00
|
|
|
if decorations?
|
|
|
|
for decoration in decorations
|
|
|
|
if not contains(previousDecorations, decoration) and (not softWrapped or softWrapped and decoration.softWrap)
|
|
|
|
node.classList.add(decoration.class)
|
2014-06-04 05:44:35 +04:00
|
|
|
|
2014-05-17 00:52:24 +04:00
|
|
|
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
|
2014-05-22 04:04:44 +04:00
|
|
|
{lineHeightInPixels} = @props
|
2014-06-04 04:26:57 +04:00
|
|
|
node.style.top = screenRow * lineHeightInPixels + 'px'
|
|
|
|
node.dataset.screenRow = screenRow
|
2014-05-17 00:52:24 +04:00
|
|
|
@screenRowsByLineNumberId[lineNumberId] = screenRow
|
|
|
|
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
|
2014-05-14 18:47:15 +04:00
|
|
|
|
|
|
|
hasLineNumberNode: (lineNumberId) ->
|
|
|
|
@lineNumberNodesById.hasOwnProperty(lineNumberId)
|
|
|
|
|
2014-05-17 00:52:24 +04:00
|
|
|
lineNumberNodeForScreenRow: (screenRow) ->
|
|
|
|
@lineNumberNodesById[@lineNumberIdsByScreenRow[screenRow]]
|
2014-06-04 04:26:57 +04:00
|
|
|
|
2014-06-06 04:56:39 +04:00
|
|
|
# Created because underscore uses === not _.isEqual, which we need
|
|
|
|
contains = (array, target) ->
|
|
|
|
return false unless array?
|
|
|
|
for object in array
|
|
|
|
return true if _.isEqual(object, target)
|
|
|
|
false
|