mirror of
https://github.com/atom/atom.git
synced 2026-01-24 06:18:03 -05:00
Handle updates to line decorations in TextEditorPresenter
This isn’t a super efficient approach, but it is simple and should be correct. Once we move all state to the presenter we can perform a more efficient synchronous update when markers change.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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 = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{scrollWidth}px;\" data-screen-row=\"#{screenRow}\">"
|
||||
@@ -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]]
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user