Merge pull request #2682 from atom/bo-line-decorations

Render line decorations
This commit is contained in:
Ben Ogle 2014-06-20 11:02:35 -07:00
commit 633b08b9de
4 changed files with 116 additions and 18 deletions

View File

@ -58,7 +58,7 @@
"temp": "0.7.0",
"text-buffer": "^2.4.2",
"theorist": "^1",
"underscore-plus": "^1.4.1",
"underscore-plus": "^1.5.0",
"vm-compatibility-layer": "0.1.0"
},
"packageDependencies": {
@ -83,7 +83,7 @@
"feedback": "0.33.0",
"find-and-replace": "0.120.0",
"fuzzy-finder": "0.55.0",
"git-diff": "0.33.0",
"git-diff": "0.34.0",
"go-to-line": "0.23.0",
"grammar-selector": "0.27.0",
"image-view": "0.35.0",

View File

@ -245,6 +245,88 @@ describe "EditorComponent", ->
foldedLineNode = component.lineNodeForScreenRow(4)
expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy()
describe "when line decorations are attached to markers", ->
{marker, decoration} = {}
lineHasClass = (screenRow, klass) ->
component.lineNodeForScreenRow(screenRow).classList.contains(klass)
beforeEach ->
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], invalidate: 'inside')
decoration = {type: 'line', class: 'someclass'}
editor.addDecorationForMarker(marker, decoration)
waitsFor -> not component.decorationChangedImmediate?
it "does not render off-screen lines with line number classes until they are with in the rendered row range", ->
node.style.height = 4.5 * lineHeightInPixels + 'px'
component.measureScrollView()
expect(component.lineNodeForScreenRow(9)).not.toBeDefined()
marker = editor.displayBuffer.markBufferRange([[9, 0], [9, 0]], invalidate: 'inside')
editor.addDecorationForMarker(marker, type: 'line', class: 'fancy-class')
editor.addDecorationForMarker(marker, type: 'gutter', class: 'nope-class')
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
expect(lineHasClass(9, 'fancy-class')).toBe true
expect(lineHasClass(9, 'nope-class')).toBe false
it "renders the specified decoration class on the correct lines", ->
expect(lineHasClass(1, 'someclass')).toBe false
expect(lineHasClass(2, 'someclass')).toBe true
expect(lineHasClass(3, 'someclass')).toBe true
expect(lineHasClass(4, 'someclass')).toBe false
it "removes line classes when a decoration's marker is invalidated", ->
editor.getBuffer().insert([3, 2], 'n')
waitsFor -> not component.decorationChangedImmediate?
runs ->
expect(marker.isValid()).toBe false
expect(lineHasClass(1, 'someclass')).toBe false
expect(lineHasClass(2, 'someclass')).toBe false
expect(lineHasClass(3, 'someclass')).toBe false
expect(lineHasClass(4, 'someclass')).toBe false
editor.getBuffer().undo()
waitsFor -> not component.decorationChangedImmediate?
runs ->
expect(marker.isValid()).toBe true
expect(lineHasClass(1, 'someclass')).toBe false
expect(lineHasClass(2, 'someclass')).toBe true
expect(lineHasClass(3, 'someclass')).toBe true
expect(lineHasClass(4, 'someclass')).toBe false
it "removes the classes and unsubscribes from the marker when decoration is removed", ->
editor.removeDecorationForMarker(marker, decoration)
waitsFor -> not component.decorationChangedImmediate?
runs ->
expect(lineHasClass(1, 'someclass')).toBe false
expect(lineHasClass(2, 'someclass')).toBe false
expect(lineHasClass(3, 'someclass')).toBe false
expect(lineHasClass(4, 'someclass')).toBe false
editor.getBuffer().insert([0, 0], '\n')
waitsFor -> not component.decorationChangedImmediate?
runs ->
expect(lineHasClass(2, 'someclass')).toBe false
expect(lineHasClass(3, 'someclass')).toBe false
it "removes the line number classes when the decoration's marker is destroyed", ->
marker.destroy()
waitsFor -> not component.decorationChangedImmediate?
runs ->
expect(lineHasClass(1, 'someclass')).toBe false
expect(lineHasClass(2, 'someclass')).toBe false
expect(lineHasClass(3, 'someclass')).toBe false
expect(lineHasClass(4, 'someclass')).toBe false
describe "gutter rendering", ->
[gutter] = []
@ -455,7 +537,7 @@ describe "EditorComponent", ->
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
expect(lineNumberHasClass(3, 'cursor-line')).toBe false
describe "when decorations are applied to markers", ->
describe "when gutter decorations are attached to markers", ->
{marker, decoration} = {}
beforeEach ->
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], invalidate: 'inside')

View File

@ -174,11 +174,11 @@ GutterComponent = React.createClass
if previousDecorations?
for decoration in previousDecorations
node.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'gutter') and not contains(decorations, decoration)
node.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'gutter') and not _.deepContains(decorations, decoration)
if decorations?
for decoration in decorations
if editor.decorationMatchesType(decoration, 'gutter') and not contains(previousDecorations, decoration)
if editor.decorationMatchesType(decoration, 'gutter') and not _.deepContains(previousDecorations, decoration)
node.classList.add(decoration.class)
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
@ -211,10 +211,3 @@ GutterComponent = React.createClass
unless width is @measuredWidth
@measuredWidth = width
@props.onWidthChanged?(width)
# 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

View File

@ -1,3 +1,4 @@
_ = require 'underscore-plus'
React = require 'react-atom-fork'
{div, span} = require 'reactionary-atom-fork'
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
@ -32,10 +33,11 @@ LinesComponent = React.createClass
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@renderedDecorationsByLineId = {}
shouldComponentUpdate: (newProps) ->
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles', 'visible',
'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount'
)
@ -60,7 +62,7 @@ LinesComponent = React.createClass
@lineIdsByScreenRow = {}
updateLines: ->
{editor, renderedRowRange, showIndentGuide, selectionChanged} = @props
{editor, renderedRowRange, showIndentGuide, selectionChanged, lineDecorations} = @props
[startRow, endRow] = renderedRowRange
visibleLines = editor.linesForScreenRows(startRow, endRow - 1)
@ -81,6 +83,8 @@ LinesComponent = React.createClass
node.removeChild(lineNode)
appendOrUpdateVisibleLineNodes: (visibleLines, startRow) ->
{lineDecorations} = @props
newLines = null
newLinesHTML = null
@ -97,6 +101,8 @@ LinesComponent = React.createClass
@screenRowsByLineId[line.id] = screenRow
@lineIdsByScreenRow[screenRow] = line.id
@renderedDecorationsByLineId[line.id] = lineDecorations[screenRow]
return unless newLines?
WrapperDiv.innerHTML = newLinesHTML
@ -111,11 +117,18 @@ LinesComponent = React.createClass
@lineNodesByLineId.hasOwnProperty(lineId)
buildLineHTML: (line, screenRow) ->
{editor, mini, showIndentGuide, lineHeightInPixels} = @props
{editor, mini, showIndentGuide, lineHeightInPixels, lineDecorations} = @props
{tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line
classes = ''
if decorations = lineDecorations[screenRow]
for decoration in decorations
if editor.decorationMatchesType(decoration, 'line')
classes += decoration.class + ' '
classes += 'line'
top = screenRow * lineHeightInPixels
lineHTML = "<div class=\"line\" style=\"position: absolute; top: #{top}px;\" data-screen-row=\"#{screenRow}\">"
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px;\" data-screen-row=\"#{screenRow}\">"
if text is ""
lineHTML += @buildEmptyLineInnerHTML(line)
@ -190,9 +203,19 @@ LinesComponent = React.createClass
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
updateLineNode: (line, screenRow) ->
{editor, lineHeightInPixels, lineDecorations} = @props
lineNode = @lineNodesByLineId[line.id]
if previousDecorations = @renderedDecorationsByLineId[line.id]
for decoration in previousDecorations
lineNode.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(decorations, decoration)
if decorations = lineDecorations[screenRow]
for decoration in decorations
if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(previousDecorations, decoration)
lineNode.classList.add(decoration.class)
unless @screenRowsByLineId[line.id] is screenRow
{lineHeightInPixels} = @props
lineNode = @lineNodesByLineId[line.id]
lineNode.style.top = screenRow * lineHeightInPixels + 'px'
lineNode.dataset.screenRow = screenRow
@screenRowsByLineId[line.id] = screenRow