diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee
index 1edefec27..b78803401 100644
--- a/spec/text-editor-presenter-spec.coffee
+++ b/spec/text-editor-presenter-spec.coffee
@@ -272,3 +272,55 @@ describe "TextEditorPresenter", ->
text: line3.text
tokens: line3.tokens
}
+
+ describe "when line decorations are added, updated, or destroyed", ->
+ it "updates the .decorationClasses of the relevant lines", ->
+ marker1 = editor.markBufferRange([[4, 0], [6, 0]], invalidate: 'touch')
+ decoration1 = editor.decorateMarker(marker1, type: 'line', class: 'a')
+ presenter = new TextEditorPresenter(model: editor, clientHeight: 130, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 0)
+ marker2 = editor.markBufferRange([[4, 0], [6, 0]], invalidate: 'touch')
+ decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
+
+ expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
+ expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
+ expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
+ expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
+
+ editor.getBuffer().insert([5, 0], 'x')
+ expect(marker1.isValid()).toBe false
+ expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
+
+ editor.undo()
+ expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
+ expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
+ expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
+ expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
+
+ marker1.setBufferRange([[2, 0], [4, 0]])
+ expect(lineStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
+ expect(lineStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a']
+ expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
+ expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
+ expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
+ expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
+
+ decoration1.destroy()
+ expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b']
+ expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
+ expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
+ expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
+
+ marker2.destroy()
+ expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
+ expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
diff --git a/src/lines-component.coffee b/src/lines-component.coffee
index 4b233c55d..c8f54c51e 100644
--- a/src/lines-component.coffee
+++ b/src/lines-component.coffee
@@ -4,7 +4,6 @@ React = require 'react-atom-fork'
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
{$$} = require 'space-pen'
-Decoration = require './decoration'
CursorsComponent = require './cursors-component'
HighlightsComponent = require './highlights-component'
OverlayManager = require './overlay-manager'
@@ -140,13 +139,12 @@ LinesComponent = React.createClass
buildLineHTML: (id) ->
{presenter, lineDecorations} = @props
{scrollWidth} = @newState
- {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel} = @newState.lines[id]
+ {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id]
classes = ''
- if decorations = lineDecorations[screenRow]
- for decorationId, decoration of decorations
- if Decoration.isType(decoration, 'line')
- classes += decoration.class + ' '
+ if decorationClasses?
+ for decorationClass in decorationClasses
+ classes += decorationClass + ' '
classes += 'line'
lineHTML = "
"
@@ -243,18 +241,18 @@ LinesComponent = React.createClass
lineNode = @lineNodesByLineId[id]
- decorations = lineDecorations[screenRow]
- previousDecorations = @renderedDecorationsByLineId[id]
+ newDecorationClasses = @newState.lines[id].decorationClasses
+ oldDecorationClasses = @oldState.lines[id].decorationClasses
- if previousDecorations?
- for decorationId, decoration of previousDecorations
- if Decoration.isType(decoration, 'line') and not @hasDecoration(decorations, decoration)
- lineNode.classList.remove(decoration.class)
+ if oldDecorationClasses?
+ for decorationClass in oldDecorationClasses
+ unless newDecorationClasses? and decorationClass in newDecorationClasses
+ lineNode.classList.remove(decorationClass)
- if decorations?
- for decorationId, decoration of decorations
- if Decoration.isType(decoration, 'line') and not @hasDecoration(previousDecorations, decoration)
- lineNode.classList.add(decoration.class)
+ if newDecorationClasses?
+ for decorationClass in newDecorationClasses
+ unless oldDecorationClasses? and decorationClass in oldDecorationClasses
+ lineNode.classList.add(decorationClass)
lineNode.style.width = scrollWidth + 'px'
lineNode.style.top = top + 'px'
@@ -262,9 +260,6 @@ LinesComponent = React.createClass
@screenRowsByLineId[id] = screenRow
@lineIdsByScreenRow[screenRow] = id
- hasDecoration: (decorations, decoration) ->
- decorations? and decorations[decoration.id] is decoration
-
lineNodeForScreenRow: (screenRow) ->
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee
index 74f97eaa5..00ad78e99 100644
--- a/src/text-editor-presenter.coffee
+++ b/src/text-editor-presenter.coffee
@@ -15,10 +15,10 @@ class TextEditorPresenter
observeModel: ->
@disposables.add @model.onDidChange(@updateLinesState.bind(this))
- @disposables.add @model.onDidChangeSoftWrapped =>
- @updateContentState()
- @updateLinesState()
+ @disposables.add @model.onDidChangeSoftWrapped(@updateState.bind(this))
@disposables.add @model.onDidChangeGrammar(@updateContentState.bind(this))
+ @disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
+ @observeDecoration(decoration) for decoration in @model.getLineDecorations()
observeConfig: ->
@disposables.add atom.config.onDidChange 'editor.showIndentGuide', scope: @model.getRootScopeDescriptor(), @updateContentState.bind(this)
@@ -37,6 +37,10 @@ class TextEditorPresenter
@state.content.lines = {}
@updateLinesState()
+ updateState: ->
+ @updateContentState()
+ @updateLinesState()
+
updateContentState: ->
@state.content.scrollWidth = @computeScrollWidth()
@state.content.indentGuidesVisible = atom.config.get('editor.showIndentGuide', scope: @model.getRootScopeDescriptor())
@@ -64,14 +68,9 @@ class TextEditorPresenter
lineState = @state.content.lines[line.id]
lineState.screenRow = row
lineState.top = row * @getLineHeight()
+ lineState.decorationClasses = @lineDecorationClassesForRow(row)
buildLineState: (row, line) ->
- decorationClasses = null
- for markerId, decorations of @model.decorationsForScreenRowRange(row, row) when @model.getMarker(markerId).isValid()
- for decoration in decorations when decoration.isType('line')
- decorationClasses ?= []
- decorationClasses.push(decoration.getProperties().class)
-
@state.content.lines[line.id] =
screenRow: row
text: line.text
@@ -81,7 +80,7 @@ class TextEditorPresenter
tabLength: line.tabLength
fold: line.fold
top: row * @getLineHeight()
- decorationClasses: decorationClasses
+ decorationClasses: @lineDecorationClassesForRow(row)
getStartRow: ->
startRow = Math.floor(@getScrollTop() / @getLineHeight()) - @lineOverdrawMargin
@@ -98,6 +97,14 @@ class TextEditorPresenter
contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
Math.max(contentWidth, @getClientWidth())
+ lineDecorationClassesForRow: (row) ->
+ decorationClasses = null
+ for markerId, decorations of @model.decorationsForScreenRowRange(row, row) when @model.getMarker(markerId).isValid()
+ for decoration in decorations when decoration.isType('line')
+ decorationClasses ?= []
+ decorationClasses.push(decoration.getProperties().class)
+ decorationClasses
+
setScrollTop: (@scrollTop) ->
@updateLinesState()
@@ -186,3 +193,18 @@ class TextEditorPresenter
left += charWidths[char] ? baseCharacterWidth unless char is '\0'
column += charLength
{top, left}
+
+ observeDecoration: (decoration) ->
+ markerChangeDisposable = decoration.getMarker().onDidChange(@updateLinesState.bind(this))
+ destroyDisposable = decoration.onDidDestroy =>
+ @disposables.remove(markerChangeDisposable)
+ @disposables.remove(destroyDisposable)
+ @updateLinesState()
+
+ @disposables.add(markerChangeDisposable)
+ @disposables.add(destroyDisposable)
+
+ didAddDecoration: (decoration) ->
+ if decoration.isType('line')
+ @observeDecoration(decoration)
+ @updateLinesState()