Remove yardstick to start with a clean slate

This commit is contained in:
Antonio Scandurra
2015-09-29 10:25:54 +02:00
parent a49fe3140f
commit 49577313e4
4 changed files with 183 additions and 178 deletions

View File

@@ -473,7 +473,8 @@ describe "TextEditorComponent", ->
editor.setText "a line that wraps \n"
editor.setSoftWrapped(true)
nextAnimationFrame()
wrapperNode.setWidth(16 * charWidth)
componentNode.style.width = 16 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
component.measureDimensions()
nextAnimationFrame()
it "doesn't show end of line invisibles at the end of wrapped lines", ->

View File

@@ -4,98 +4,60 @@ TextBuffer = require 'text-buffer'
{Point, Range} = TextBuffer
TextEditor = require '../src/text-editor'
TextEditorPresenter = require '../src/text-editor-presenter'
LinesYardstick = require '../src/lines-yardstick'
MockLinesComponent = require './mock-lines-component'
describe "TextEditorPresenter", ->
[buffer, editor, linesYardstick, mockLinesComponent] = []
beforeEach ->
# These *should* be mocked in the spec helper, but changing that now would break packages :-(
spyOn(window, "setInterval").andCallFake window.fakeSetInterval
spyOn(window, "clearInterval").andCallFake window.fakeClearInterval
buffer = new TextBuffer(filePath: require.resolve('./fixtures/sample.js'))
editor = new TextEditor({buffer})
waitsForPromise ->
buffer.load()
afterEach ->
editor.destroy()
buffer.destroy()
mockLinesComponent.destroy()
buildLinesYardstick = (presenter) ->
mockLinesComponent = new MockLinesComponent(editor)
mockLinesComponent.setDefaultFont("16px monospace")
new LinesYardstick(editor, presenter, mockLinesComponent)
buildPresenter = (params={}) ->
_.defaults params,
model: editor
explicitHeight: 130
contentFrameWidth: 500
windowWidth: 500
windowHeight: 130
boundingClientRect: {left: 0, top: 0, width: 500, height: 130}
gutterWidth: 0
lineHeight: 10
baseCharacterWidth: 10
horizontalScrollbarHeight: 10
verticalScrollbarWidth: 10
scrollTop: 0
scrollLeft: 0
presenter = new TextEditorPresenter(params)
linesYardstick = buildLinesYardstick(presenter)
presenter.setLinesYardstick(linesYardstick)
presenter
expectValues = (actual, expected) ->
for key, value of expected
expect(actual[key]).toEqual value
expectStateUpdatedToBe = (value, presenter, fn) ->
updatedState = false
disposable = presenter.onDidUpdateState ->
updatedState = true
disposable.dispose()
fn()
expect(updatedState).toBe(value)
expectStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(true, presenter, fn)
expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)
describe "::getStateForMeasurements()", ->
it "contains states for tiles that need measurement, in addition to visible ones", ->
presenter = buildPresenter(explicitHeight: 4, scrollTop: 6, lineHeight: 1, tileSize: 2)
presenter.setScreenRowsToMeasure([4, 0])
state = presenter.getStateForMeasurements()
expect(state.content.tiles[0]).toBeDefined()
expect(state.content.tiles[2]).toBeUndefined()
expect(state.content.tiles[4]).toBeDefined()
expect(state.content.tiles[6]).toBeDefined()
expect(state.content.tiles[8]).toBeDefined()
expect(state.content.tiles[10]).toBeDefined()
expect(state.content.tiles[12]).toBeUndefined()
presenter.clearScreenRowsToMeasure()
state = presenter.getStateForMeasurements()
expect(state.content.tiles[0]).toBeUndefined()
expect(state.content.tiles[2]).toBeUndefined()
expect(state.content.tiles[4]).toBeUndefined()
expect(state.content.tiles[6]).toBeDefined()
expect(state.content.tiles[8]).toBeDefined()
expect(state.content.tiles[10]).toBeDefined()
expect(state.content.tiles[12]).toBeUndefined()
# These `describe` and `it` blocks mirror the structure of the ::state object.
# Please maintain this structure when adding specs for new state fields.
describe "::getState()", ->
[buffer, editor] = []
beforeEach ->
# These *should* be mocked in the spec helper, but changing that now would break packages :-(
spyOn(window, "setInterval").andCallFake window.fakeSetInterval
spyOn(window, "clearInterval").andCallFake window.fakeClearInterval
buffer = new TextBuffer(filePath: require.resolve('./fixtures/sample.js'))
editor = new TextEditor({buffer})
waitsForPromise -> buffer.load()
afterEach ->
editor.destroy()
buffer.destroy()
buildPresenter = (params={}) ->
_.defaults params,
model: editor
explicitHeight: 130
contentFrameWidth: 500
windowWidth: 500
windowHeight: 130
boundingClientRect: {left: 0, top: 0, width: 500, height: 130}
gutterWidth: 0
lineHeight: 10
baseCharacterWidth: 10
horizontalScrollbarHeight: 10
verticalScrollbarWidth: 10
scrollTop: 0
scrollLeft: 0
new TextEditorPresenter(params)
expectValues = (actual, expected) ->
for key, value of expected
expect(actual[key]).toEqual value
expectStateUpdatedToBe = (value, presenter, fn) ->
updatedState = false
disposable = presenter.onDidUpdateState ->
updatedState = true
disposable.dispose()
fn()
expect(updatedState).toBe(value)
expectStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(true, presenter, fn)
expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)
tiledContentContract = (stateFn) ->
it "contains states for tiles that are visible on screen", ->
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
@@ -327,15 +289,24 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setContentFrameWidth(10 * maxLineLength + 20)
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 20
it "updates when character widths change", ->
it "updates when the ::baseCharacterWidth changes", ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10)
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, ->
mockLinesComponent.setDefaultFont("33px monospace")
presenter.characterWidthsChanged()
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 20 * maxLineLength + 1
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15)
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 15 * maxLineLength + 1
it "updates when the scoped character widths change", ->
waitsForPromise -> atom.packages.activatePackage('language-javascript')
runs ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10)
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
it "updates when ::softWrapped changes on the editor", ->
presenter = buildPresenter(contentFrameWidth: 470, baseCharacterWidth: 10)
@@ -574,9 +545,10 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter()
expect(presenter.getState().hiddenInput.width).toBe 10
expectStateUpdate presenter, ->
mockLinesComponent.setDefaultFont("33px monospace")
presenter.characterWidthsChanged()
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15)
expect(presenter.getState().hiddenInput.width).toBe 15
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
expect(presenter.getState().hiddenInput.width).toBe 20
it "is 2px at the end of lines", ->
@@ -664,15 +636,24 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setContentFrameWidth(10 * maxLineLength + 20)
expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 20
it "updates when character widths change", ->
it "updates when the ::baseCharacterWidth changes", ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10)
expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, ->
mockLinesComponent.setDefaultFont("33px monospace")
presenter.characterWidthsChanged()
expect(presenter.getState().content.scrollWidth).toBe 20 * maxLineLength + 1
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15)
expect(presenter.getState().content.scrollWidth).toBe 15 * maxLineLength + 1
it "updates when the scoped character widths change", ->
waitsForPromise -> atom.packages.activatePackage('language-javascript')
runs ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10)
expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
expect(presenter.getState().content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
it "updates when ::softWrapped changes on the editor", ->
presenter = buildPresenter(contentFrameWidth: 470, baseCharacterWidth: 10)
@@ -1294,15 +1275,27 @@ describe "TextEditorPresenter", ->
expect(stateForCursor(presenter, 3)).toEqual {top: 5, left: 12 * 10, width: 10, height: 5}
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 5 - 20, left: 4 * 10, width: 10, height: 5}
it "updates when character widths change", ->
it "updates when ::baseCharacterWidth changes", ->
editor.setCursorBufferPosition([2, 4])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 20)
expectStateUpdate presenter, ->
mockLinesComponent.setDefaultFont("33px monospace")
presenter.characterWidthsChanged()
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20)
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 4 * 20, width: 20, height: 10}
it "updates when scoped character widths change", ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
runs ->
editor.setCursorBufferPosition([1, 4])
presenter = buildPresenter(explicitHeight: 20)
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'v', 20)
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 10, height: 10}
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10}
it "updates when cursors are added, moved, hidden, shown, or destroyed", ->
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
@@ -1616,7 +1609,7 @@ describe "TextEditorPresenter", ->
]
}
it "updates when character widths change", ->
it "updates when ::baseCharacterWidth changes", ->
editor.setSelectedBufferRanges([
[[2, 2], [2, 4]],
])
@@ -1626,13 +1619,30 @@ describe "TextEditorPresenter", ->
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 2 * 10, width: 2 * 10, height: 10}]
}
expectStateUpdate presenter, ->
mockLinesComponent.setDefaultFont("33px monospace")
presenter.characterWidthsChanged()
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20)
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 2 * 20, width: 2 * 20, height: 10}]
}
it "updates when scoped character widths change", ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
runs ->
editor.setSelectedBufferRanges([
[[2, 4], [2, 6]],
])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2)
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
}
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'i', 20)
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 4 * 10, width: 20 + 10, height: 10}]
}
it "updates when highlight decorations are added, moved, hidden, shown, or destroyed", ->
editor.setSelectedBufferRanges([
[[1, 2], [1, 4]],
@@ -1781,7 +1791,7 @@ describe "TextEditorPresenter", ->
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
}
it "updates when character widths change", ->
it "updates when ::baseCharacterWidth changes", ->
scrollTop = 20
marker = editor.markBufferPosition([2, 13], invalidate: 'touch')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
@@ -1792,9 +1802,7 @@ describe "TextEditorPresenter", ->
pixelPosition: {top: 3 * 10 - scrollTop, left: 13 * 10}
}
expectStateUpdate presenter, ->
mockLinesComponent.setDefaultFont("8px monospace")
presenter.characterWidthsChanged()
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(5)
expectValues stateForOverlay(presenter, decoration), {
item: item

View File

@@ -12,7 +12,6 @@ ScrollbarComponent = require './scrollbar-component'
ScrollbarCornerComponent = require './scrollbar-corner-component'
OverlayManager = require './overlay-manager'
DOMElementPool = require './dom-element-pool'
LinesYardstick = require './lines-yardstick'
module.exports =
class TextEditorComponent
@@ -86,9 +85,6 @@ class TextEditorComponent
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool})
@scrollViewNode.appendChild(@linesComponent.getDomNode())
@linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent)
@presenter.setLinesYardstick(@linesYardstick)
@horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll})
@scrollViewNode.appendChild(@horizontalScrollbarComponent.getDomNode())
@@ -716,7 +712,6 @@ class TextEditorComponent
if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight
@clearPoolAfterUpdate = true
@linesYardstick.clearCache()
@measureLineHeightAndDefaultCharWidth()
if (@fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily) and @performedInitialMeasurement

View File

@@ -41,8 +41,6 @@ class TextEditorPresenter
@startReflowing() if @continuousReflow
@updating = false
setLinesYardstick: (@linesYardstick) ->
destroy: ->
@disposables.dispose()
@@ -65,32 +63,17 @@ class TextEditorPresenter
isBatching: ->
@updating is false
getStateForMeasurements: ->
@updateVerticalDimensions()
@updateScrollbarDimensions()
@updateScrollPosition()
@updateStartRow()
@updateEndRow()
@fetchVisibleDecorations() if @shouldUpdateDecorations
@updateLineDecorations() if @shouldUpdateDecorations
@updateTilesState() if @shouldUpdateLineNumbersState or @shouldUpdateLinesState
@shouldUpdateLinesState = false
@shouldUpdateLineNumbersState = false
@state
# Public: Gets this presenter's state, updating it just in time before returning from this function.
# Returns a state {Object}, useful for rendering to screen.
getState: ->
@updating = true
@linesYardstick.prepareScreenRowsForMeasurement()
@updateContentDimensions()
@updateScrollbarDimensions()
@updateScrollPosition()
@updateStartRow()
@updateEndRow()
@updateCommonGutterState()
@updateHorizontalDimensions()
@updateReflowState()
@updateFocusedState() if @shouldUpdateFocusedState
@@ -100,13 +83,13 @@ class TextEditorPresenter
@updateScrollbarsState() if @shouldUpdateScrollbarsState
@updateHiddenInputState() if @shouldUpdateHiddenInputState
@updateContentState() if @shouldUpdateContentState
@updateHighlightDecorations() if @shouldUpdateDecorations
@updateDecorations() if @shouldUpdateDecorations
@updateTilesState() if @shouldUpdateLinesState or @shouldUpdateLineNumbersState
@updateCursorsState() if @shouldUpdateCursorsState
@updateOverlaysState() if @shouldUpdateOverlaysState
@updateLineNumberGutterState() if @shouldUpdateLineNumberGutterState
@updateGutterOrderState() if @shouldUpdateGutterOrderState
@updateCustomGutterDecorationState() if @shouldUpdateCustomGutterDecorationState
@updating = false
@resetTrackedUpdates()
@@ -355,8 +338,8 @@ class TextEditorPresenter
endRow = @constrainRow(@getEndTileRow() + @tileSize)
screenRows = [startRow...endRow]
if longestScreenRow = @model.getLongestScreenRow()
screenRows.push(longestScreenRow)
# if longestScreenRow = @model.getLongestScreenRow()
# screenRows.push(longestScreenRow)
if @screenRowsToMeasure?
screenRows.push(@screenRowsToMeasure...)
@@ -694,17 +677,11 @@ class TextEditorPresenter
@scrollHeight = scrollHeight
@updateScrollTop()
updateVerticalDimensions: ->
updateContentDimensions: ->
if @lineHeight?
oldContentHeight = @contentHeight
@contentHeight = @lineHeight * @model.getScreenLineCount()
if @contentHeight isnt oldContentHeight
@updateHeight()
@updateScrollbarDimensions()
@updateScrollHeight()
updateHorizontalDimensions: ->
if @baseCharacterWidth?
oldContentWidth = @contentWidth
clip = @model.tokenizedLineForScreenRow(@model.getLongestScreenRow())?.isSoftWrapped()
@@ -712,6 +689,11 @@ class TextEditorPresenter
@contentWidth += @scrollLeft
@contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
if @contentHeight isnt oldContentHeight
@updateHeight()
@updateScrollbarDimensions()
@updateScrollHeight()
if @contentWidth isnt oldContentWidth
@updateScrollbarDimensions()
@updateScrollWidth()
@@ -1105,8 +1087,6 @@ class TextEditorPresenter
@characterWidthsChanged() unless @batchingCharacterMeasurement
characterWidthsChanged: ->
@linesYardstick.clearCache()
@shouldUpdateHorizontalScrollState = true
@shouldUpdateVerticalScrollState = true
@shouldUpdateScrollbarsState = true
@@ -1126,12 +1106,42 @@ class TextEditorPresenter
hasPixelPositionRequirements: ->
@lineHeight? and @baseCharacterWidth?
pixelPositionForScreenPosition: (screenPosition, clip) ->
position =
@linesYardstick.pixelPositionForScreenPosition(screenPosition, clip)
position.left -= @scrollLeft
position.top -= @scrollTop
position
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = @model.clipScreenPosition(screenPosition) if clip
targetRow = screenPosition.row
targetColumn = screenPosition.column
baseCharacterWidth = @baseCharacterWidth
top = targetRow * @lineHeight
left = 0
column = 0
iterator = @model.tokenizedLineForScreenRow(targetRow).getTokenIterator()
while iterator.next()
characterWidths = @getScopedCharacterWidths(iterator.getScopes())
valueIndex = 0
text = iterator.getText()
while valueIndex < text.length
if iterator.isPairedCharacter()
char = text
charLength = 2
valueIndex += 2
else
char = text[valueIndex]
charLength = 1
valueIndex++
break if column is targetColumn
left += characterWidths[char] ? baseCharacterWidth unless char is '\0'
column += charLength
top -= @scrollTop
left -= @scrollLeft
{top, left}
hasPixelRectRequirements: ->
@hasPixelPositionRequirements() and @scrollWidth?
@@ -1211,31 +1221,22 @@ class TextEditorPresenter
@emitDidUpdateState()
fetchVisibleDecorations: ->
@visibleDecorations = []
for row in @getScreenRows()
for markerId, decorations of @model.decorationsForScreenRowRange(row, row)
range = @model.getMarker(markerId).getScreenRange()
for decoration in decorations
@visibleDecorations.push({decoration, range})
updateLineDecorations: ->
updateDecorations: ->
@rangesByDecorationId = {}
@lineDecorationsByScreenRow = {}
@lineNumberDecorationsByScreenRow = {}
@customGutterDecorationsByGutterNameAndScreenRow = {}
for {decoration, range} in @visibleDecorations
if decoration.isType('line') or decoration.isType('gutter')
@addToLineDecorationCaches(decoration, range)
updateHighlightDecorations: ->
@visibleHighlights = {}
for {decoration, range} in @visibleDecorations
if decoration.isType('highlight')
@updateHighlightState(decoration, range)
return unless 0 <= @startRow <= @endRow <= Infinity
for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1)
range = @model.getMarker(markerId).getScreenRange()
for decoration in decorations
if decoration.isType('line') or decoration.isType('gutter')
@addToLineDecorationCaches(decoration, range)
else if decoration.isType('highlight')
@updateHighlightState(decoration, range)
for tileId, tileState of @state.content.tiles
for id, highlight of tileState.highlights