Add top-level .content object to presenter state

It contains the .scrollWidth and then all the lines in a nested .lines
object. The .width has been removed from each line and replaced with
.content.scrollWidth.
This commit is contained in:
Nathan Sobo 2015-01-20 16:04:16 -07:00
parent 3ec4b632ba
commit 0a9f7586ae
3 changed files with 137 additions and 165 deletions

View File

@ -18,13 +18,63 @@ describe "TextEditorPresenter", ->
for key, value of expected
expect(actual[key]).toBe value
describe "lines", ->
describe "::state.content", ->
describe "on initialization", ->
it "assigns .scrollWidth based on the clientWidth and the width of the longest line", ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, baseCharacterWidth: 10, lineHeight: 10, lineOverdrawMargin: 0)
expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 1
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 10 * maxLineLength + 20, scrollTop: 0, baseCharacterWidth: 10, lineHeight: 10, lineOverdrawMargin: 0)
expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 20
describe "when the ::clientWidth changes", ->
it "updates .scrollWidth", ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, scrollWidth: 70, lineHeight: 10, baseCharacterWidth: 10, lineOverdrawMargin: 0)
expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 1
presenter.setClientWidth(10 * maxLineLength + 20)
expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 20
describe "when the ::baseCharacterWidth changes", ->
it "updates the width of the lines if it changes the ::scrollWidth", ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, scrollWidth: 70, lineHeight: 10, baseCharacterWidth: 10, lineOverdrawMargin: 0)
expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 1
presenter.setBaseCharacterWidth(15)
expect(presenter.state.content.scrollWidth).toBe 15 * maxLineLength + 1
describe "when the scoped character widths change", ->
beforeEach ->
waitsForPromise -> atom.packages.activatePackage('language-javascript')
it "updates the width of the lines if the ::scrollWidth changes", ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, scrollWidth: 70, lineHeight: 10, baseCharacterWidth: 10, lineOverdrawMargin: 0)
expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 1
presenter.setScopedCharWidth(['source.js', 'support.function.js'], 'p', 20)
expect(presenter.state.content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
describe "when ::softWrapped changes on the editor", ->
it "only accounts for the cursor in .scrollWidth if ::softWrapped is false", ->
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, scrollWidth: 70, lineHeight: 10, baseCharacterWidth: 10, lineOverdrawMargin: 0)
expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1
editor.setSoftWrapped(true)
expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength()
editor.setSoftWrapped(false)
expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1
describe "::state.content.lines", ->
describe "on initialization", ->
it "contains the lines that are visible on screen, plus the overdraw margin", ->
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1)
line0 = editor.tokenizedLineForScreenRow(0)
expectValues presenter.state.lines[line0.id], {
expectValues presenter.state.content.lines[line0.id], {
screenRow: 0
text: line0.text
tokens: line0.tokens
@ -32,7 +82,7 @@ describe "TextEditorPresenter", ->
}
line1 = editor.tokenizedLineForScreenRow(1)
expectValues presenter.state.lines[line1.id], {
expectValues presenter.state.content.lines[line1.id], {
screenRow: 1
text: line1.text
tokens: line1.tokens
@ -40,7 +90,7 @@ describe "TextEditorPresenter", ->
}
line2 = editor.tokenizedLineForScreenRow(2)
expectValues presenter.state.lines[line2.id], {
expectValues presenter.state.content.lines[line2.id], {
screenRow: 2
text: line2.text
tokens: line2.tokens
@ -49,7 +99,7 @@ describe "TextEditorPresenter", ->
# this row is rendered due to the overdraw margin
line3 = editor.tokenizedLineForScreenRow(3)
expectValues presenter.state.lines[line3.id], {
expectValues presenter.state.content.lines[line3.id], {
screenRow: 3
text: line3.text
tokens: line3.tokens
@ -61,7 +111,7 @@ describe "TextEditorPresenter", ->
# this row is rendered due to the overdraw margin
line10 = editor.tokenizedLineForScreenRow(10)
expectValues presenter.state.lines[line10.id], {
expectValues presenter.state.content.lines[line10.id], {
screenRow: 10
text: line10.text
tokens: line10.tokens
@ -69,7 +119,7 @@ describe "TextEditorPresenter", ->
}
line11 = editor.tokenizedLineForScreenRow(11)
expectValues presenter.state.lines[line11.id], {
expectValues presenter.state.content.lines[line11.id], {
screenRow: 11
text: line11.text
tokens: line11.tokens
@ -77,7 +127,7 @@ describe "TextEditorPresenter", ->
}
line12 = editor.tokenizedLineForScreenRow(12)
expectValues presenter.state.lines[line12.id], {
expectValues presenter.state.content.lines[line12.id], {
screenRow: 12
text: line12.text
tokens: line12.tokens
@ -88,43 +138,26 @@ describe "TextEditorPresenter", ->
it "contains the lines that are visible on screen, plus and minus the overdraw margin", ->
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, scrollTop: 50, lineHeight: 10, lineOverdrawMargin: 1)
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(3).id]).toBeUndefined()
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(4).id]).toBeDefined()
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(9).id]).toBeDefined()
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(10).id]).toBeUndefined()
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(3).id]).toBeUndefined()
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(4).id]).toBeDefined()
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(9).id]).toBeDefined()
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(10).id]).toBeUndefined()
it "reports all lines as visible if no external ::clientHeight is assigned", ->
presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1)
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(0).id]).toBeDefined()
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(12).id]).toBeDefined()
it "uses the computed scrollWidth as the length of each line", ->
line0 = editor.tokenizedLineForScreenRow(0)
line1 = editor.tokenizedLineForScreenRow(1)
line2 = editor.tokenizedLineForScreenRow(2)
maxLineLength = editor.getMaxScreenLineLength()
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, baseCharacterWidth: 10, lineHeight: 10, lineOverdrawMargin: 0)
expect(presenter.state.lines[line0.id].width).toBe 10 * maxLineLength + 1
expect(presenter.state.lines[line1.id].width).toBe 10 * maxLineLength + 1
expect(presenter.state.lines[line2.id].width).toBe 10 * maxLineLength + 1
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 10 * maxLineLength + 20, scrollTop: 0, baseCharacterWidth: 10, lineHeight: 10, lineOverdrawMargin: 0)
expect(presenter.state.lines[line0.id].width).toBe 10 * maxLineLength + 20
expect(presenter.state.lines[line1.id].width).toBe 10 * maxLineLength + 20
expect(presenter.state.lines[line2.id].width).toBe 10 * maxLineLength + 20
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(0).id]).toBeDefined()
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(12).id]).toBeDefined()
it "includes the endOfLineInvisibles in the line state", ->
editor.setText("hello\nworld\r\n")
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, baseCharacterWidth: 10, lineHeight: 10, lineOverdrawMargin: 0)
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(0).id].endOfLineInvisibles).toBeNull()
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(1).id].endOfLineInvisibles).toBeNull()
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(0).id].endOfLineInvisibles).toBeNull()
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(1).id].endOfLineInvisibles).toBeNull()
atom.config.set('editor.showInvisibles', true)
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, baseCharacterWidth: 10, lineHeight: 10, lineOverdrawMargin: 0)
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(0).id].endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.eol')]
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(1).id].endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.cr'), atom.config.get('editor.invisibles.eol')]
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(0).id].endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.eol')]
expect(presenter.state.content.lines[editor.tokenizedLineForScreenRow(1).id].endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.cr'), atom.config.get('editor.invisibles.eol')]
describe "when ::scrollTop changes", ->
it "updates the lines that are visible on screen", ->
@ -132,10 +165,10 @@ describe "TextEditorPresenter", ->
presenter.setScrollTop(25)
line0 = editor.tokenizedLineForScreenRow(0)
expect(presenter.state.lines[line0.id]).toBeUndefined()
expect(presenter.state.content.lines[line0.id]).toBeUndefined()
line1 = editor.tokenizedLineForScreenRow(1)
expectValues presenter.state.lines[line1.id], {
expectValues presenter.state.content.lines[line1.id], {
screenRow: 1
text: line1.text
tokens: line1.tokens
@ -143,7 +176,7 @@ describe "TextEditorPresenter", ->
}
line2 = editor.tokenizedLineForScreenRow(2)
expectValues presenter.state.lines[line2.id], {
expectValues presenter.state.content.lines[line2.id], {
screenRow: 2
text: line2.text
tokens: line2.tokens
@ -151,7 +184,7 @@ describe "TextEditorPresenter", ->
}
line3 = editor.tokenizedLineForScreenRow(3)
expectValues presenter.state.lines[line3.id], {
expectValues presenter.state.content.lines[line3.id], {
screenRow: 3
text: line3.text
tokens: line3.tokens
@ -159,7 +192,7 @@ describe "TextEditorPresenter", ->
}
line4 = editor.tokenizedLineForScreenRow(4)
expectValues presenter.state.lines[line4.id], {
expectValues presenter.state.content.lines[line4.id], {
screenRow: 4
text: line4.text
tokens: line4.tokens
@ -171,12 +204,12 @@ describe "TextEditorPresenter", ->
presenter = new TextEditorPresenter(model: editor, clientHeight: 15, scrollTop: 15, lineHeight: 10, lineOverdrawMargin: 1)
line5 = editor.tokenizedLineForScreenRow(5)
expect(presenter.state.lines[line5.id]).toBeUndefined()
expect(presenter.state.content.lines[line5.id]).toBeUndefined()
presenter.setClientHeight(35)
line1 = editor.tokenizedLineForScreenRow(1)
expectValues presenter.state.lines[line1.id], {
expectValues presenter.state.content.lines[line1.id], {
screenRow: 1
text: line1.text
tokens: line1.tokens
@ -184,7 +217,7 @@ describe "TextEditorPresenter", ->
}
line2 = editor.tokenizedLineForScreenRow(2)
expectValues presenter.state.lines[line2.id], {
expectValues presenter.state.content.lines[line2.id], {
screenRow: 2
text: line2.text
tokens: line2.tokens
@ -192,7 +225,7 @@ describe "TextEditorPresenter", ->
}
line3 = editor.tokenizedLineForScreenRow(3)
expectValues presenter.state.lines[line3.id], {
expectValues presenter.state.content.lines[line3.id], {
screenRow: 3
text: line3.text
tokens: line3.tokens
@ -200,14 +233,14 @@ describe "TextEditorPresenter", ->
}
line4 = editor.tokenizedLineForScreenRow(4)
expectValues presenter.state.lines[line4.id], {
expectValues presenter.state.content.lines[line4.id], {
screenRow: 4
text: line4.text
tokens: line4.tokens
top: 10 * 4
}
expectValues presenter.state.lines[line4.id], {
expectValues presenter.state.content.lines[line4.id], {
screenRow: 4
text: line4.text
tokens: line4.tokens
@ -225,105 +258,45 @@ describe "TextEditorPresenter", ->
line5 = editor.tokenizedLineForScreenRow(5)
line6 = editor.tokenizedLineForScreenRow(6)
expect(presenter.state.lines[line1.id]).toBeDefined()
expect(presenter.state.lines[line2.id]).toBeDefined()
expect(presenter.state.lines[line3.id]).toBeDefined()
expect(presenter.state.lines[line4.id]).toBeUndefined()
expect(presenter.state.lines[line5.id]).toBeUndefined()
expect(presenter.state.content.lines[line1.id]).toBeDefined()
expect(presenter.state.content.lines[line2.id]).toBeDefined()
expect(presenter.state.content.lines[line3.id]).toBeDefined()
expect(presenter.state.content.lines[line4.id]).toBeUndefined()
expect(presenter.state.content.lines[line5.id]).toBeUndefined()
presenter.setLineHeight(5)
expect(presenter.state.lines[line1.id]).toBeUndefined()
expect(presenter.state.content.lines[line1.id]).toBeUndefined()
expectValues presenter.state.lines[line2.id], {
expectValues presenter.state.content.lines[line2.id], {
screenRow: 2
text: line2.text
tokens: line2.tokens
top: 5 * 2
}
expectValues presenter.state.lines[line3.id], {
expectValues presenter.state.content.lines[line3.id], {
screenRow: 3
text: line3.text
tokens: line3.tokens
top: 5 * 3
}
expectValues presenter.state.lines[line4.id], {
expectValues presenter.state.content.lines[line4.id], {
screenRow: 4
text: line4.text
tokens: line4.tokens
top: 5 * 4
}
expectValues presenter.state.lines[line5.id], {
expectValues presenter.state.content.lines[line5.id], {
screenRow: 5
text: line5.text
tokens: line5.tokens
top: 5 * 5
}
expect(presenter.state.lines[line6.id]).toBeUndefined()
describe "when the ::clientWidth changes", ->
it "updates the width of the lines if it changes the ::scrollWidth", ->
line0 = editor.tokenizedLineForScreenRow(0)
line1 = editor.tokenizedLineForScreenRow(1)
line2 = editor.tokenizedLineForScreenRow(2)
maxLineLength = editor.getMaxScreenLineLength()
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, scrollWidth: 70, lineHeight: 10, baseCharacterWidth: 10, lineOverdrawMargin: 0)
expect(presenter.state.lines[line0.id].width).toBe 10 * maxLineLength + 1
expect(presenter.state.lines[line1.id].width).toBe 10 * maxLineLength + 1
expect(presenter.state.lines[line2.id].width).toBe 10 * maxLineLength + 1
presenter.setClientWidth(10 * maxLineLength + 20)
expect(presenter.state.lines[line0.id].width).toBe 10 * maxLineLength + 20
expect(presenter.state.lines[line1.id].width).toBe 10 * maxLineLength + 20
expect(presenter.state.lines[line2.id].width).toBe 10 * maxLineLength + 20
describe "when the scoped character widths change", ->
beforeEach ->
waitsForPromise -> atom.packages.activatePackage('language-javascript')
it "updates the width of the lines if the ::scrollWidth changes", ->
line0 = editor.tokenizedLineForScreenRow(0)
line1 = editor.tokenizedLineForScreenRow(1)
line2 = editor.tokenizedLineForScreenRow(2)
maxLineLength = editor.getMaxScreenLineLength()
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, scrollWidth: 70, lineHeight: 10, baseCharacterWidth: 10, lineOverdrawMargin: 0)
expect(presenter.state.lines[line0.id].width).toBe 10 * maxLineLength + 1
expect(presenter.state.lines[line1.id].width).toBe 10 * maxLineLength + 1
expect(presenter.state.lines[line2.id].width).toBe 10 * maxLineLength + 1
presenter.setScopedCharWidth(['source.js', 'support.function.js'], 'p', 20)
expect(presenter.state.lines[line0.id].width).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
expect(presenter.state.lines[line1.id].width).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1
expect(presenter.state.lines[line2.id].width).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1
describe "when the ::baseCharacterWidth changes", ->
it "updates the width of the lines if it changes the ::scrollWidth", ->
line0 = editor.tokenizedLineForScreenRow(0)
line1 = editor.tokenizedLineForScreenRow(1)
line2 = editor.tokenizedLineForScreenRow(2)
maxLineLength = editor.getMaxScreenLineLength()
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, scrollWidth: 70, lineHeight: 10, baseCharacterWidth: 10, lineOverdrawMargin: 0)
expect(presenter.state.lines[line0.id].width).toBe 10 * maxLineLength + 1
expect(presenter.state.lines[line1.id].width).toBe 10 * maxLineLength + 1
expect(presenter.state.lines[line2.id].width).toBe 10 * maxLineLength + 1
presenter.setBaseCharacterWidth(15)
expect(presenter.state.lines[line0.id].width).toBe 15 * maxLineLength + 1
expect(presenter.state.lines[line1.id].width).toBe 15 * maxLineLength + 1
expect(presenter.state.lines[line2.id].width).toBe 15 * maxLineLength + 1
expect(presenter.state.content.lines[line6.id]).toBeUndefined()
describe "when the editor's content changes", ->
it "updates the lines state accordingly", ->
@ -332,7 +305,7 @@ describe "TextEditorPresenter", ->
buffer.insert([2, 0], "hello\nworld\n")
line1 = editor.tokenizedLineForScreenRow(1)
expectValues presenter.state.lines[line1.id], {
expectValues presenter.state.content.lines[line1.id], {
screenRow: 1
text: line1.text
tokens: line1.tokens
@ -340,7 +313,7 @@ describe "TextEditorPresenter", ->
}
line2 = editor.tokenizedLineForScreenRow(2)
expectValues presenter.state.lines[line2.id], {
expectValues presenter.state.content.lines[line2.id], {
screenRow: 2
text: line2.text
tokens: line2.tokens
@ -348,18 +321,9 @@ describe "TextEditorPresenter", ->
}
line3 = editor.tokenizedLineForScreenRow(3)
expectValues presenter.state.lines[line3.id], {
expectValues presenter.state.content.lines[line3.id], {
screenRow: 3
text: line3.text
tokens: line3.tokens
top: 10 * 3
}
describe "when ::softWrapped changes on the editor", ->
it "only accounts for the cursor width if ::softWrapped is false", ->
presenter = new TextEditorPresenter(model: editor, clientHeight: 25, clientWidth: 50, scrollTop: 0, scrollWidth: 70, lineHeight: 10, baseCharacterWidth: 10, lineOverdrawMargin: 0)
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(0).id].width).toBe 10 * editor.getMaxScreenLineLength() + 1
editor.setSoftWrapped(true)
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(0).id].width).toBe 10 * editor.getMaxScreenLineLength()
editor.setSoftWrapped(false)
expect(presenter.state.lines[editor.tokenizedLineForScreenRow(0).id].width).toBe 10 * editor.getMaxScreenLineLength() + 1

View File

@ -89,32 +89,31 @@ LinesComponent = React.createClass
@lineIdsByScreenRow = {}
removeLineNodes: ->
@removeLineNode(id) for id of @oldState
@removeLineNode(id) for id of @oldState.lines
removeLineNode: (id) ->
@lineNodesByLineId[id].remove()
delete @lineNodesByLineId[id]
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
delete @screenRowsByLineId[id]
delete @oldState[id]
delete @oldState.lines[id]
updateLineNodes: ->
{presenter, lineDecorations, mouseWheelScreenRow} = @props
@newState = presenter?.state.lines
return unless @newState = presenter?.state.content
return unless @newState?
@oldState ?= {}
@oldState ?= {lines: {}}
@lineNodesByLineId ?= {}
for id of @oldState
unless @newState.hasOwnProperty(id) or mouseWheelScreenRow is @screenRowsByLineId[id]
for id of @oldState.lines
unless @newState.lines.hasOwnProperty(id) or mouseWheelScreenRow is @screenRowsByLineId[id]
@removeLineNode(id)
newLineIds = null
newLinesHTML = null
for id, lineState of @newState
if @oldState.hasOwnProperty(id)
for id, lineState of @newState.lines
if @oldState.lines.hasOwnProperty(id)
@updateLineNode(id)
else
newLineIds ?= []
@ -123,10 +122,12 @@ LinesComponent = React.createClass
newLinesHTML += @buildLineHTML(id)
@screenRowsByLineId[id] = lineState.screenRow
@lineIdsByScreenRow[lineState.screenRow] = id
@oldState[id] = _.clone(lineState)
@oldState.lines[id] = _.clone(lineState)
@renderedDecorationsByLineId[id] = lineDecorations[lineState.screenRow]
@oldState.scrollWidth = @newState.scrollWidth
return unless newLineIds?
WrapperDiv.innerHTML = newLinesHTML
@ -139,7 +140,8 @@ LinesComponent = React.createClass
buildLineHTML: (id) ->
{presenter, showIndentGuide, lineHeightInPixels, lineDecorations} = @props
{screenRow, tokens, text, top, width, lineEnding, fold, isSoftWrapped, indentLevel} = @newState[id]
{scrollWidth} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel} = @newState.lines[id]
classes = ''
if decorations = lineDecorations[screenRow]
@ -148,7 +150,7 @@ LinesComponent = React.createClass
classes += decoration.class + ' '
classes += 'line'
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{width}px;\" data-screen-row=\"#{screenRow}\">"
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{scrollWidth}px;\" data-screen-row=\"#{screenRow}\">"
if text is ""
lineHTML += @buildEmptyLineInnerHTML(id)
@ -161,7 +163,7 @@ LinesComponent = React.createClass
buildEmptyLineInnerHTML: (id) ->
{showIndentGuide} = @props
{indentLevel, tabLength, endOfLineInvisibles} = @newState[id]
{indentLevel, tabLength, endOfLineInvisibles} = @newState.lines[id]
if showIndentGuide and indentLevel > 0
invisibleIndex = 0
@ -184,7 +186,7 @@ LinesComponent = React.createClass
buildLineInnerHTML: (id) ->
{editor, showIndentGuide} = @props
{tokens, text} = @newState[id]
{tokens, text} = @newState.lines[id]
innerHTML = ""
scopeStack = []
@ -200,7 +202,7 @@ LinesComponent = React.createClass
innerHTML
buildEndOfLineHTML: (id) ->
{endOfLineInvisibles} = @newState[id]
{endOfLineInvisibles} = @newState.lines[id]
html = ''
if endOfLineInvisibles?
@ -234,9 +236,9 @@ LinesComponent = React.createClass
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
updateLineNode: (id) ->
{lineHeightInPixels, lineDecorations} = @props
{screenRow, top, width} = @newState[id]
{scrollWidth} = @newState
{screenRow, top} = @newState.lines[id]
lineNode = @lineNodesByLineId[id]
@ -254,7 +256,7 @@ LinesComponent = React.createClass
if Decoration.isType(decoration, 'line') and not @hasDecoration(previousDecorations, decoration)
lineNode.classList.add(decoration.class)
lineNode.style.width = width + 'px'
lineNode.style.width = scrollWidth + 'px'
lineNode.style.top = top + 'px'
lineNode.dataset.screenRow = screenRow
@screenRowsByLineId[id] = screenRow
@ -291,7 +293,7 @@ LinesComponent = React.createClass
node = @getDOMNode()
editor.batchCharacterMeasurement =>
for id, lineState of @oldState
for id, lineState of @oldState.lines
unless @measuredLines.has(id)
lineNode = @lineNodesByLineId[id]
@measureCharactersInLine(lineState, lineNode)

View File

@ -5,10 +5,9 @@ module.exports =
class TextEditorPresenter
constructor: ({@model, @clientHeight, @clientWidth, @scrollTop, @lineHeight, @baseCharacterWidth, @lineOverdrawMargin}) ->
@disposables = new CompositeDisposable
@state = {}
@charWidthsByScope = {}
@subscribeToModel()
@buildLinesState()
@buildState()
destroy: ->
@disposables.dispose()
@ -16,13 +15,24 @@ class TextEditorPresenter
subscribeToModel: ->
@disposables.add @model.onDidChange(@updateLinesState.bind(this))
@disposables.add @model.onDidChangeSoftWrapped =>
@computeScrollWidth()
@updateContentState()
@updateLinesState()
buildState: ->
@state = {}
@buildContentState()
@buildLinesState()
buildContentState: ->
@state.content = {scrollWidth: @computeScrollWidth()}
buildLinesState: ->
@state.lines = {}
@state.content.lines = {}
@updateLinesState()
updateContentState: ->
@state.content.scrollWidth = @computeScrollWidth()
updateLinesState: ->
visibleLineIds = {}
startRow = @getStartRow()
@ -32,24 +42,23 @@ class TextEditorPresenter
while row < endRow
line = @model.tokenizedLineForScreenRow(row)
visibleLineIds[line.id] = true
if @state.lines.hasOwnProperty(line.id)
if @state.content.lines.hasOwnProperty(line.id)
@updateLineState(row, line)
else
@buildLineState(row, line)
row++
for id, line of @state.lines
for id, line of @state.content.lines
unless visibleLineIds.hasOwnProperty(id)
delete @state.lines[id]
delete @state.content.lines[id]
updateLineState: (row, line) ->
lineState = @state.lines[line.id]
lineState = @state.content.lines[line.id]
lineState.screenRow = row
lineState.top = row * @getLineHeight()
lineState.width = @getScrollWidth()
buildLineState: (row, line) ->
@state.lines[line.id] =
@state.content.lines[line.id] =
screenRow: row
text: line.text
tokens: line.tokens
@ -58,7 +67,6 @@ class TextEditorPresenter
tabLength: line.tabLength
fold: line.fold
top: row * @getLineHeight()
width: @getScrollWidth()
getStartRow: ->
startRow = Math.floor(@getScrollTop() / @getLineHeight()) - @lineOverdrawMargin
@ -73,9 +81,7 @@ class TextEditorPresenter
computeScrollWidth: ->
contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left
contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
@scrollWidth = Math.max(contentWidth, @getClientWidth())
getScrollWidth: -> @scrollWidth ? @computeScrollWidth()
Math.max(contentWidth, @getClientWidth())
setScrollTop: (@scrollTop) ->
@updateLinesState()
@ -89,7 +95,7 @@ class TextEditorPresenter
@clientHeight ? @model.getScreenLineCount() * @getLineHeight()
setClientWidth: (@clientWidth) ->
@computeScrollWidth()
@updateContentState()
@updateLinesState()
getClientWidth: -> @clientWidth
@ -100,7 +106,7 @@ class TextEditorPresenter
getLineHeight: -> @lineHeight
setBaseCharacterWidth: (@baseCharacterWidth) ->
@computeScrollWidth()
@updateContentState()
@updateLinesState()
getBaseCharacterWidth: -> @baseCharacterWidth
@ -129,7 +135,7 @@ class TextEditorPresenter
@characterWidthsChanged() unless @batchingCharacterMeasurement
characterWidthsChanged: ->
@computeScrollWidth()
@updateContentState()
@updateLinesState()
clearScopedCharWidths: ->