Merge pull request #7127 from atom/as-opaque-tiles

Bring Subpixel Anti-Aliasing Back
This commit is contained in:
Antonio Scandurra
2015-06-08 20:51:02 +02:00
8 changed files with 289 additions and 177 deletions

View File

@@ -89,19 +89,19 @@ describe "TextEditorComponent", ->
expect(tilesNodes.length).toBe(3)
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expect(tilesNodes[0].children.length).toBe(tileSize)
expect(tilesNodes[0].querySelectorAll(".line").length).toBe(tileSize)
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
expect(tilesNodes[1].children.length).toBe(tileSize)
expect(tilesNodes[1].querySelectorAll(".line").length).toBe(tileSize)
expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)
expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)"
expect(tilesNodes[2].children.length).toBe(tileSize)
expect(tilesNodes[2].querySelectorAll(".line").length).toBe(tileSize)
expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels)
@@ -118,19 +118,19 @@ describe "TextEditorComponent", ->
expect(tilesNodes.length).toBe(3)
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, -5px, 0px)"
expect(tilesNodes[0].children.length).toBe(tileSize)
expect(tilesNodes[0].querySelectorAll(".line").length).toBe(tileSize)
expectTileContainsRow(tilesNodes[0], 3, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 4, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[0], 5, top: 2 * lineHeightInPixels)
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight - 5}px, 0px)"
expect(tilesNodes[1].children.length).toBe(tileSize)
expect(tilesNodes[1].querySelectorAll(".line").length).toBe(tileSize)
expectTileContainsRow(tilesNodes[1], 6, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 7, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[1], 8, top: 2 * lineHeightInPixels)
expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight - 5}px, 0px)"
expect(tilesNodes[2].children.length).toBe(tileSize)
expect(tilesNodes[2].querySelectorAll(".line").length).toBe(tileSize)
expectTileContainsRow(tilesNodes[2], 9, top: 0 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 10, top: 1 * lineHeightInPixels)
expectTileContainsRow(tilesNodes[2], 11, top: 2 * lineHeightInPixels)
@@ -246,8 +246,10 @@ describe "TextEditorComponent", ->
# lines caused full-screen repaints after switching away from an editor
# and back again Please ensure you don't cause a performance regression if
# you change this behavior.
editorFullWidth = editor.getScrollWidth() + editor.getVerticalScrollbarWidth()
for lineNode in lineNodes
expect(lineNode.style.width).toBe editor.getScrollWidth() + 'px'
expect(lineNode.style.width).toBe editorFullWidth + 'px'
componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px'
component.measureDimensions()
@@ -957,14 +959,15 @@ describe "TextEditorComponent", ->
it "renders 2 regions for 2-line selections", ->
editor.setSelectedScreenRange([[1, 6], [2, 10]])
nextAnimationFrame()
regions = componentNode.querySelectorAll('.selection .region')
tileNode = componentNode.querySelectorAll(".tile")[0]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe 2
region1Rect = regions[0].getBoundingClientRect()
expect(region1Rect.top).toBe 1 * lineHeightInPixels
expect(region1Rect.height).toBe 1 * lineHeightInPixels
expect(region1Rect.left).toBe scrollViewClientLeft + 6 * charWidth
expect(region1Rect.right).toBe scrollViewNode.getBoundingClientRect().right
expect(region1Rect.right).toBe tileNode.getBoundingClientRect().right
region2Rect = regions[1].getBoundingClientRect()
expect(region2Rect.top).toBe 2 * lineHeightInPixels
@@ -972,23 +975,49 @@ describe "TextEditorComponent", ->
expect(region2Rect.left).toBe scrollViewClientLeft + 0
expect(region2Rect.width).toBe 10 * charWidth
it "renders 3 regions for selections with more than 2 lines", ->
editor.setSelectedScreenRange([[1, 6], [5, 10]])
it "renders 3 regions per tile for selections with more than 2 lines", ->
editor.setSelectedScreenRange([[0, 6], [5, 10]])
nextAnimationFrame()
regions = componentNode.querySelectorAll('.selection .region')
expect(regions.length).toBe 3
# Tile 0
tileNode = componentNode.querySelectorAll(".tile")[0]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe(3)
region1Rect = regions[0].getBoundingClientRect()
expect(region1Rect.top).toBe 1 * lineHeightInPixels
expect(region1Rect.top).toBe 0
expect(region1Rect.height).toBe 1 * lineHeightInPixels
expect(region1Rect.left).toBe scrollViewClientLeft + 6 * charWidth
expect(region1Rect.right).toBe scrollViewNode.getBoundingClientRect().right
expect(region1Rect.right).toBe tileNode.getBoundingClientRect().right
region2Rect = regions[1].getBoundingClientRect()
expect(region2Rect.top).toBe 2 * lineHeightInPixels
expect(region2Rect.height).toBe 3 * lineHeightInPixels
expect(region2Rect.top).toBe 1 * lineHeightInPixels
expect(region2Rect.height).toBe 1 * lineHeightInPixels
expect(region2Rect.left).toBe scrollViewClientLeft + 0
expect(region2Rect.right).toBe scrollViewNode.getBoundingClientRect().right
expect(region2Rect.right).toBe tileNode.getBoundingClientRect().right
region3Rect = regions[2].getBoundingClientRect()
expect(region3Rect.top).toBe 2 * lineHeightInPixels
expect(region3Rect.height).toBe 1 * lineHeightInPixels
expect(region3Rect.left).toBe scrollViewClientLeft + 0
expect(region3Rect.right).toBe tileNode.getBoundingClientRect().right
# Tile 3
tileNode = componentNode.querySelectorAll(".tile")[1]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe(3)
region1Rect = regions[0].getBoundingClientRect()
expect(region1Rect.top).toBe 3 * lineHeightInPixels
expect(region1Rect.height).toBe 1 * lineHeightInPixels
expect(region1Rect.left).toBe scrollViewClientLeft + 0
expect(region1Rect.right).toBe tileNode.getBoundingClientRect().right
region2Rect = regions[1].getBoundingClientRect()
expect(region2Rect.top).toBe 4 * lineHeightInPixels
expect(region2Rect.height).toBe 1 * lineHeightInPixels
expect(region2Rect.left).toBe scrollViewClientLeft + 0
expect(region2Rect.right).toBe tileNode.getBoundingClientRect().right
region3Rect = regions[2].getBoundingClientRect()
expect(region3Rect.top).toBe 5 * lineHeightInPixels
@@ -1219,7 +1248,7 @@ describe "TextEditorComponent", ->
expect(regions.length).toBe 1
regionRect = regions[0].style
expect(regionRect.top).toBe (9 * lineHeightInPixels - verticalScrollbarNode.scrollTop) + 'px'
expect(regionRect.top).toBe (0 + 'px')
expect(regionRect.height).toBe 1 * lineHeightInPixels + 'px'
expect(regionRect.left).toBe 2 * charWidth + 'px'
expect(regionRect.width).toBe 2 * charWidth + 'px'
@@ -1263,10 +1292,10 @@ describe "TextEditorComponent", ->
it "allows multiple space-delimited decoration classes", ->
decoration.setProperties(type: 'highlight', class: 'foo bar')
nextAnimationFrame()
expect(componentNode.querySelectorAll('.foo.bar').length).toBe 1
expect(componentNode.querySelectorAll('.foo.bar').length).toBe 2
decoration.setProperties(type: 'highlight', class: 'bar baz')
nextAnimationFrame()
expect(componentNode.querySelectorAll('.bar.baz').length).toBe 1
expect(componentNode.querySelectorAll('.bar.baz').length).toBe 2
it "renders classes on the regions directly if 'deprecatedRegionClass' option is defined", ->
decoration = editor.decorateMarker(marker, type: 'highlight', class: 'test-highlight', deprecatedRegionClass: 'test-highlight-region')
@@ -1278,7 +1307,7 @@ describe "TextEditorComponent", ->
describe "when flashing a decoration via Decoration::flash()", ->
highlightNode = null
beforeEach ->
highlightNode = componentNode.querySelector('.test-highlight')
highlightNode = componentNode.querySelectorAll('.test-highlight')[1]
it "adds and removes the flash class specified in ::flash", ->
expect(highlightNode.classList.contains('flash-class')).toBe false
@@ -1314,13 +1343,15 @@ describe "TextEditorComponent", ->
regionStyle = componentNode.querySelector('.test-highlight .region').style
originalTop = parseInt(regionStyle.top)
expect(originalTop).toBe(2 * lineHeightInPixels)
editor.getBuffer().insert([0, 0], '\n')
nextAnimationFrame()
regionStyle = componentNode.querySelector('.test-highlight .region').style
newTop = parseInt(regionStyle.top)
expect(newTop).toBe originalTop + lineHeightInPixels
expect(newTop).toBe(0)
it "moves rendered highlights when the marker is manually moved", ->
regionStyle = componentNode.querySelector('.test-highlight .region').style
@@ -1330,7 +1361,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
regionStyle = componentNode.querySelector('.test-highlight .region').style
expect(parseInt(regionStyle.top)).toBe 5 * lineHeightInPixels
expect(parseInt(regionStyle.top)).toBe 2 * lineHeightInPixels
describe "when a decoration is updated via Decoration::update", ->
it "renders the decoration's new params", ->

View File

@@ -1277,12 +1277,22 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.cursorsVisible).toBe false
describe ".highlights", ->
stateForHighlight = (presenter, decoration) ->
presenter.getState().content.highlights[decoration.id]
expectUndefinedStateForHighlight = (presenter, decoration) ->
for tileId, tileState of presenter.getState().content.tiles
state = stateForHighlightInTile(presenter, decoration, tileId)
expect(state).toBeUndefined()
stateForSelection = (presenter, selectionIndex) ->
stateForHighlightInTile = (presenter, decoration, tile) ->
presenter.getState().content.tiles[tile]?.highlights[decoration.id]
stateForSelectionInTile = (presenter, selectionIndex, tile) ->
selection = presenter.model.getSelections()[selectionIndex]
stateForHighlight(presenter, selection.decoration)
stateForHighlightInTile(presenter, selection.decoration, tile)
expectUndefinedStateForSelection = (presenter, selectionIndex) ->
for tileId, tileState of presenter.getState().content.tiles
state = stateForSelectionInTile(presenter, selectionIndex, tileId)
expect(state).toBeUndefined()
it "contains states for highlights that are visible on screen", ->
# off-screen above
@@ -1297,11 +1307,11 @@ describe "TextEditorPresenter", ->
marker3 = editor.markBufferRange([[0, 6], [3, 6]])
highlight3 = editor.decorateMarker(marker3, type: 'highlight', class: 'c')
# on-screen
# on-screen, spans over 2 tiles
marker4 = editor.markBufferRange([[2, 6], [4, 6]])
highlight4 = editor.decorateMarker(marker4, type: 'highlight', class: 'd')
# partially off-screen below, 2 of 3 regions on screen
# partially off-screen below, spans over 3 tiles, 2 of 3 regions on screen
marker5 = editor.markBufferRange([[3, 6], [6, 6]])
highlight5 = editor.decorateMarker(marker5, type: 'highlight', class: 'e')
@@ -1317,107 +1327,133 @@ describe "TextEditorPresenter", ->
marker8 = editor.markBufferRange([[2, 2], [2, 2]])
highlight8 = editor.decorateMarker(marker8, type: 'highlight', class: 'h')
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
expect(stateForHighlight(presenter, highlight1)).toBeUndefined()
expectUndefinedStateForHighlight(presenter, highlight1)
expectValues stateForHighlight(presenter, highlight2), {
expectValues stateForHighlightInTile(presenter, highlight2, 2), {
class: 'b'
regions: [
{top: 2 * 10 - 20, left: 0 * 10, width: 6 * 10, height: 1 * 10}
{top: 0, left: 0 * 10, width: 6 * 10, height: 1 * 10}
]
}
expectValues stateForHighlight(presenter, highlight3), {
expectValues stateForHighlightInTile(presenter, highlight3, 2), {
class: 'c'
regions: [
{top: 2 * 10 - 20, left: 0 * 10, right: 0, height: 1 * 10}
{top: 3 * 10 - 20, left: 0 * 10, width: 6 * 10, height: 1 * 10}
{top: 0, left: 0 * 10, right: 0, height: 1 * 10}
{top: 10, left: 0 * 10, width: 6 * 10, height: 1 * 10}
]
}
expectValues stateForHighlight(presenter, highlight4), {
expectValues stateForHighlightInTile(presenter, highlight4, 2), {
class: 'd'
regions: [
{top: 2 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10}
{top: 3 * 10 - 20, left: 0, right: 0, height: 1 * 10}
{top: 4 * 10 - 20, left: 0, width: 6 * 10, height: 1 * 10}
{top: 0, left: 6 * 10, right: 0, height: 1 * 10}
{top: 10, left: 0, right: 0, height: 1 * 10}
]
}
expectValues stateForHighlightInTile(presenter, highlight4, 4), {
class: 'd'
regions: [
{top: 0, left: 0, width: 60, height: 1 * 10}
]
}
expectValues stateForHighlight(presenter, highlight5), {
expectValues stateForHighlightInTile(presenter, highlight5, 2), {
class: 'e'
regions: [
{top: 3 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10}
{top: 4 * 10 - 20, left: 0 * 10, right: 0, height: 2 * 10}
{top: 10, left: 6 * 10, right: 0, height: 1 * 10}
]
}
expectValues stateForHighlight(presenter, highlight6), {
expectValues stateForHighlightInTile(presenter, highlight5, 4), {
class: 'e'
regions: [
{top: 0, left: 0, right: 0, height: 1 * 10}
{top: 10, left: 0, right: 0, height: 1 * 10}
]
}
expect(stateForHighlightInTile(presenter, highlight5, 6)).toBeUndefined()
expectValues stateForHighlightInTile(presenter, highlight6, 4), {
class: 'f'
regions: [
{top: 5 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10}
{top: 10, left: 6 * 10, right: 0, height: 1 * 10}
]
}
expect(stateForHighlight(presenter, highlight7)).toBeUndefined()
expect(stateForHighlight(presenter, highlight8)).toBeUndefined()
expect(stateForHighlightInTile(presenter, highlight6, 6)).toBeUndefined()
expectUndefinedStateForHighlight(presenter, highlight7)
expectUndefinedStateForHighlight(presenter, highlight8)
it "is empty until all of the required measurements are assigned", ->
editor.setSelectedBufferRanges([
[[0, 2], [2, 4]],
])
presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null)
expect(presenter.getState().content.highlights).toEqual({})
presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null, tileSize: 2)
for tileId, tileState of presenter.getState().content.tiles
expect(tileState.highlights).toEqual({})
presenter.setExplicitHeight(25)
expect(presenter.getState().content.highlights).toEqual({})
for tileId, tileState of presenter.getState().content.tiles
expect(tileState.highlights).toEqual({})
presenter.setLineHeight(10)
expect(presenter.getState().content.highlights).toEqual({})
for tileId, tileState of presenter.getState().content.tiles
expect(tileState.highlights).toEqual({})
presenter.setScrollTop(0)
expect(presenter.getState().content.highlights).toEqual({})
for tileId, tileState of presenter.getState().content.tiles
expect(tileState.highlights).toEqual({})
presenter.setBaseCharacterWidth(8)
expect(presenter.getState().content.highlights).not.toEqual({})
assignedAnyHighlight = false
for tileId, tileState of presenter.getState().content.tiles
assignedAnyHighlight ||= _.isEqual(tileState.highlights, {})
expect(assignedAnyHighlight).toBe(true)
it "does not include highlights for invalid markers", ->
marker = editor.markBufferRange([[2, 2], [2, 4]], invalidate: 'touch')
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'h')
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
expect(stateForHighlightInTile(presenter, highlight, 2)).toBeDefined()
expect(stateForHighlight(presenter, highlight)).toBeDefined()
expectStateUpdate presenter, -> editor.getBuffer().insert([2, 2], "stuff")
expect(stateForHighlight(presenter, highlight)).toBeUndefined()
expectUndefinedStateForHighlight(presenter, highlight)
it "updates when ::scrollTop changes", ->
editor.setSelectedBufferRanges([
[[6, 2], [6, 4]],
])
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
expect(stateForSelection(presenter, 0)).toBeUndefined()
expectUndefinedStateForSelection(presenter, 0)
expectStateUpdate presenter, -> presenter.setScrollTop(5 * 10)
expect(stateForSelection(presenter, 0)).toBeDefined()
expect(stateForSelectionInTile(presenter, 0, 6)).toBeDefined()
expectStateUpdate presenter, -> presenter.setScrollTop(2 * 10)
expect(stateForSelection(presenter, 0)).toBeUndefined()
expectUndefinedStateForSelection(presenter, 0)
it "updates when ::explicitHeight changes", ->
editor.setSelectedBufferRanges([
[[6, 2], [6, 4]],
])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 20)
presenter = buildPresenter(explicitHeight: 20, scrollTop: 20, tileSize: 2)
expect(stateForSelection(presenter, 0)).toBeUndefined()
expectUndefinedStateForSelection(presenter, 0)
expectStateUpdate presenter, -> presenter.setExplicitHeight(60)
expect(stateForSelection(presenter, 0)).toBeDefined()
expect(stateForSelectionInTile(presenter, 0, 6)).toBeDefined()
expectStateUpdate presenter, -> presenter.setExplicitHeight(20)
expect(stateForSelection(presenter, 0)).toBeUndefined()
expectUndefinedStateForSelection(presenter, 0)
it "updates when ::lineHeight changes", ->
editor.setSelectedBufferRanges([
@@ -1425,26 +1461,26 @@ describe "TextEditorPresenter", ->
[[3, 4], [3, 6]],
])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0)
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2)
expectValues stateForSelection(presenter, 0), {
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [
{top: 2 * 10, left: 2 * 10, width: 2 * 10, height: 10}
{top: 0, left: 2 * 10, width: 2 * 10, height: 10}
]
}
expect(stateForSelection(presenter, 1)).toBeUndefined()
expectUndefinedStateForSelection(presenter, 1)
expectStateUpdate presenter, -> presenter.setLineHeight(5)
expectValues stateForSelection(presenter, 0), {
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [
{top: 2 * 5, left: 2 * 10, width: 2 * 10, height: 5}
{top: 0, left: 2 * 10, width: 2 * 10, height: 5}
]
}
expectValues stateForSelection(presenter, 1), {
expectValues stateForSelectionInTile(presenter, 1, 2), {
regions: [
{top: 3 * 5, left: 4 * 10, width: 2 * 10, height: 5}
{top: 5, left: 4 * 10, width: 2 * 10, height: 5}
]
}
@@ -1453,14 +1489,14 @@ describe "TextEditorPresenter", ->
[[2, 2], [2, 4]],
])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0)
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2)
expectValues stateForSelection(presenter, 0), {
regions: [{top: 2 * 10, left: 2 * 10, width: 2 * 10, height: 10}]
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 2 * 10, width: 2 * 10, height: 10}]
}
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20)
expectValues stateForSelection(presenter, 0), {
regions: [{top: 2 * 10, left: 2 * 20, width: 2 * 20, height: 10}]
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 2 * 20, width: 2 * 20, height: 10}]
}
it "updates when scoped character widths change", ->
@@ -1472,14 +1508,14 @@ describe "TextEditorPresenter", ->
[[2, 4], [2, 6]],
])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0)
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2)
expectValues stateForSelection(presenter, 0), {
regions: [{top: 2 * 10, left: 4 * 10, width: 2 * 10, height: 10}]
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 stateForSelection(presenter, 0), {
regions: [{top: 2 * 10, left: 4 * 10, width: 20 + 10, height: 10}]
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", ->
@@ -1487,74 +1523,79 @@ describe "TextEditorPresenter", ->
[[1, 2], [1, 4]],
[[3, 4], [3, 6]]
])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0)
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2)
expectValues stateForSelection(presenter, 0), {
regions: [{top: 1 * 10, left: 2 * 10, width: 2 * 10, height: 10}]
expectValues stateForSelectionInTile(presenter, 0, 0), {
regions: [{top: 10, left: 2 * 10, width: 2 * 10, height: 10}]
}
expect(stateForSelection(presenter, 1)).toBeUndefined()
expectUndefinedStateForSelection(presenter, 1)
# moving into view
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
expectValues stateForSelection(presenter, 1), {
regions: [{top: 2 * 10, left: 4 * 10, width: 2 * 10, height: 10}]
expectValues stateForSelectionInTile(presenter, 1, 2), {
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
}
# becoming empty
expectStateUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false)
expect(stateForSelection(presenter, 1)).toBeUndefined()
expectUndefinedStateForSelection(presenter, 1)
# becoming non-empty
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
expectValues stateForSelection(presenter, 1), {
regions: [{top: 2 * 10, left: 4 * 10, width: 2 * 10, height: 10}]
expectValues stateForSelectionInTile(presenter, 1, 2), {
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
}
# moving out of view
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
expect(stateForSelection(presenter, 1)).toBeUndefined()
expectUndefinedStateForSelection(presenter, 1)
# adding
expectStateUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
expectValues stateForSelection(presenter, 2), {
regions: [{top: 1 * 10, left: 4 * 10, width: 2 * 10, height: 10}]
expectValues stateForSelectionInTile(presenter, 2, 0), {
regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}]
}
# moving added selection
expectStateUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
expectValues stateForSelection(presenter, 2), {
regions: [{top: 1 * 10, left: 4 * 10, width: 4 * 10, height: 10}]
expectValues stateForSelectionInTile(presenter, 2, 0), {
regions: [{top: 10, left: 4 * 10, width: 4 * 10, height: 10}]
}
# destroying
destroyedSelection = editor.getSelections()[2]
expectStateUpdate presenter, -> destroyedSelection.destroy()
expect(stateForHighlight(presenter, destroyedSelection.decoration)).toBeUndefined()
expectUndefinedStateForHighlight(presenter, destroyedSelection.decoration)
it "updates when highlight decorations' properties are updated", ->
marker = editor.markBufferPosition([2, 2])
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
expect(stateForHighlight(presenter, highlight)).toBeUndefined()
expectUndefinedStateForHighlight(presenter, highlight)
expectStateUpdate presenter, ->
marker.setBufferRange([[2, 2], [2, 4]])
highlight.setProperties(class: 'b', type: 'highlight')
expectValues stateForHighlight(presenter, highlight), {class: 'b'}
expectValues stateForHighlightInTile(presenter, highlight, 2), {class: 'b'}
it "increments the .flashCount and sets the .flashClass and .flashDuration when the highlight model flashes", ->
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
marker = editor.markBufferPosition([2, 2])
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
expectStateUpdate presenter, ->
marker.setBufferRange([[2, 2], [2, 4]])
marker.setBufferRange([[2, 2], [5, 2]])
highlight.flash('b', 500)
expectValues stateForHighlight(presenter, highlight), {
expectValues stateForHighlightInTile(presenter, highlight, 2), {
flashClass: 'b'
flashDuration: 500
flashCount: 1
}
expectValues stateForHighlightInTile(presenter, highlight, 4), {
flashClass: 'b'
flashDuration: 500
flashCount: 1
@@ -1562,7 +1603,12 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> highlight.flash('c', 600)
expectValues stateForHighlight(presenter, highlight), {
expectValues stateForHighlightInTile(presenter, highlight, 2), {
flashClass: 'c'
flashDuration: 600
flashCount: 2
}
expectValues stateForHighlightInTile(presenter, highlight, 4), {
flashClass: 'c'
flashDuration: 600
flashCount: 2

View File

@@ -12,16 +12,11 @@ class HighlightsComponent
@domNode = document.createElement('div')
@domNode.classList.add('highlights')
if atom.config.get('editor.useShadowDOM')
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', '.underlayer')
@domNode.appendChild(insertionPoint)
getDomNode: ->
@domNode
updateSync: (state) ->
newState = state.content.highlights
newState = state.highlights
@oldState ?= {}
# remove highlights

View File

@@ -1,7 +1,6 @@
{$$} = require 'space-pen'
CursorsComponent = require './cursors-component'
HighlightsComponent = require './highlights-component'
TileComponent = require './tile-component'
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
@@ -24,9 +23,6 @@ class LinesComponent
@cursorsComponent = new CursorsComponent(@presenter)
@domNode.appendChild(@cursorsComponent.getDomNode())
@highlightsComponent = new HighlightsComponent(@presenter)
@domNode.appendChild(@highlightsComponent.getDomNode())
if @useShadowDOM
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', '.overlayer')
@@ -58,15 +54,14 @@ class LinesComponent
@removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
@updateTileNodes()
if @newState.scrollWidth isnt @oldState.scrollWidth
@domNode.style.width = @newState.scrollWidth + 'px'
@oldState.scrollWidth = @newState.scrollWidth
if @newState.width isnt @oldState.width
@domNode.style.width = @newState.width + 'px'
@cursorsComponent.updateSync(state)
@highlightsComponent.updateSync(state)
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth
@oldState.width = @newState.width
removeTileNodes: ->
@removeTileNode(id) for id of @oldState.tiles

View File

@@ -24,6 +24,7 @@ class TextEditorPresenter
@disposables = new CompositeDisposable
@emitter = new Emitter
@visibleHighlights = {}
@characterWidthsByScope = {}
@rangesByDecorationId = {}
@lineDecorationsByScreenRow = {}
@@ -306,6 +307,7 @@ class TextEditorPresenter
@state.hiddenInput.width = Math.max(width, 2)
updateContentState: ->
@state.content.width = Math.max(@contentWidth + @verticalScrollbarWidth, @contentFrameWidth)
@state.content.scrollWidth = @scrollWidth
@state.content.scrollLeft = @scrollLeft
@state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide
@@ -335,6 +337,7 @@ class TextEditorPresenter
tile.left = -@scrollLeft
tile.height = @tileSize * @lineHeight
tile.display = "block"
tile.highlights ?= {}
@updateLinesState(tile, startRow, endRow)
@@ -1161,8 +1164,8 @@ class TextEditorPresenter
@lineDecorationsByScreenRow = {}
@lineNumberDecorationsByScreenRow = {}
@customGutterDecorationsByGutterNameAndScreenRow = {}
@visibleHighlights = {}
visibleHighlights = {}
return unless 0 <= @startRow <= @endRow <= Infinity
for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1)
@@ -1171,11 +1174,11 @@ class TextEditorPresenter
if decoration.isType('line') or decoration.isType('gutter')
@addToLineDecorationCaches(decoration, range)
else if decoration.isType('highlight')
visibleHighlights[decoration.id] = @updateHighlightState(decoration)
@updateHighlightState(decoration)
for id of @state.content.highlights
unless visibleHighlights[id]
delete @state.content.highlights[id]
for tileId, tileState of @state.content.tiles
for id, highlight of tileState.highlights
delete tileState.highlights[id] unless @visibleHighlights[tileId]?[id]?
return
@@ -1226,6 +1229,22 @@ class TextEditorPresenter
return
intersectRangeWithTile: (range, tileStartRow) ->
intersectingStartRow = Math.max(tileStartRow, range.start.row)
intersectingEndRow = Math.min(tileStartRow + @tileSize - 1, range.end.row)
intersectingRange = new Range(
new Point(intersectingStartRow, 0),
new Point(intersectingEndRow, Infinity)
)
if intersectingStartRow is range.start.row
intersectingRange.start.column = range.start.column
if intersectingEndRow is range.end.row
intersectingRange.end.column = range.end.column
intersectingRange
updateHighlightState: (decoration) ->
return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements()
@@ -1234,7 +1253,12 @@ class TextEditorPresenter
range = marker.getScreenRange()
if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1)
delete @state.content.highlights[decoration.id]
tileStartRow = @tileForRow(range.start.row)
tileEndRow = @tileForRow(range.end.row)
for tile in [tileStartRow..tileEndRow] by @tileSize
delete @state.content.tiles[tile]?.highlights[decoration.id]
@emitDidUpdateState()
return
@@ -1246,44 +1270,72 @@ class TextEditorPresenter
range.end.column = 0
if range.isEmpty()
delete @state.content.highlights[decoration.id]
tileState = @state.content.tiles[@tileForRow(range.start.row)]
delete tileState.highlights[decoration.id]
@emitDidUpdateState()
return
highlightState = @state.content.highlights[decoration.id] ?= {
flashCount: 0
flashDuration: null
flashClass: null
}
flash = decoration.consumeNextFlash()
if flash = decoration.consumeNextFlash()
highlightState.flashCount++
highlightState.flashClass = flash.class
highlightState.flashDuration = flash.duration
startTile = @tileForRow(range.start.row)
endTile = @tileForRow(range.end.row)
for tileStartRow in [startTile..endTile] by @tileSize
rangeWithinTile = @intersectRangeWithTile(range, tileStartRow)
continue if rangeWithinTile.isEmpty()
tileState = @state.content.tiles[tileStartRow] ?= {highlights: {}}
highlightState = tileState.highlights[decoration.id] ?= {
flashCount: 0
flashDuration: null
flashClass: null
}
if flash?
highlightState.flashCount++
highlightState.flashClass = flash.class
highlightState.flashDuration = flash.duration
highlightState.class = properties.class
highlightState.deprecatedRegionClass = properties.deprecatedRegionClass
highlightState.regions = @buildHighlightRegions(rangeWithinTile)
for region in highlightState.regions
@repositionRegionWithinTile(region, tileStartRow)
@visibleHighlights[tileStartRow] ?= {}
@visibleHighlights[tileStartRow][decoration.id] = true
highlightState.class = properties.class
highlightState.deprecatedRegionClass = properties.deprecatedRegionClass
highlightState.regions = @buildHighlightRegions(range)
@emitDidUpdateState()
true
repositionRegionWithinTile: (region, tileStartRow) ->
region.top += @scrollTop - tileStartRow * @lineHeight
region.left += @scrollLeft
buildHighlightRegions: (screenRange) ->
lineHeightInPixels = @lineHeight
startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, true)
endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, true)
startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, false)
endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, false)
spannedRows = screenRange.end.row - screenRange.start.row + 1
regions = []
if spannedRows is 1
[
region =
top: startPixelPosition.top
height: lineHeightInPixels
left: startPixelPosition.left
width: endPixelPosition.left - startPixelPosition.left
]
else
regions = []
if screenRange.end.column is Infinity
region.right = 0
else
region.width = endPixelPosition.left - startPixelPosition.left
regions.push(region)
else
# First row, extending from selection start to the right side of screen
regions.push(
top: startPixelPosition.top
@@ -1303,14 +1355,19 @@ class TextEditorPresenter
# Last row, extending from left side of screen to selection end
if screenRange.end.column > 0
regions.push(
region =
top: endPixelPosition.top
height: lineHeightInPixels
left: 0
width: endPixelPosition.left
)
regions
if screenRange.end.column is Infinity
region.right = 0
else
region.width = endPixelPosition.left
regions.push(region)
regions
setOverlayDimensions: (decorationId, itemWidth, itemHeight, contentMargin) ->
@overlayDimensions[decorationId] ?= {}

View File

@@ -1,5 +1,6 @@
_ = require 'underscore-plus'
HighlightsComponent = require './highlights-component'
TokenIterator = require './token-iterator'
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
WrapperDiv = document.createElement('div')
@@ -13,8 +14,6 @@ cloneObject = (object) ->
module.exports =
class TileComponent
placeholderTextDiv: null
constructor: ({@presenter, @id}) ->
@tokenIterator = new TokenIterator
@measuredLines = new Set
@@ -26,6 +25,9 @@ class TileComponent
@domNode.style.position = "absolute"
@domNode.style.display = "block"
@highlightsComponent = new HighlightsComponent(@presenter)
@domNode.appendChild(@highlightsComponent.getDomNode())
getDomNode: ->
@domNode
@@ -38,6 +40,10 @@ class TileComponent
@newTileState = @newState.tiles[@id]
@oldTileState = @oldState.tiles[@id]
if @newState.backgroundColor isnt @oldState.backgroundColor
@domNode.style.backgroundColor = @newState.backgroundColor
@oldState.backgroundColor = @newState.backgroundColor
if @newTileState.display isnt @oldTileState.display
@domNode.style.display = @newTileState.display
@oldTileState.display = @newTileState.display
@@ -46,6 +52,9 @@ class TileComponent
@domNode.style.height = @newTileState.height + 'px'
@oldTileState.height = @newTileState.height
if @newState.width isnt @oldState.width
@domNode.style.width = @newState.width + 'px'
if @newTileState.top isnt @oldTileState.top or @newTileState.left isnt @oldTileState.left
@domNode.style['-webkit-transform'] = "translate3d(#{@newTileState.left}px, #{@newTileState.top}px, 0px)"
@oldTileState.top = @newTileState.top
@@ -54,12 +63,9 @@ class TileComponent
@removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
@updateLineNodes()
if @newState.scrollWidth isnt @oldState.scrollWidth
@domNode.style.width = @newState.scrollWidth + 'px'
@oldState.scrollWidth = @newState.scrollWidth
@highlightsComponent.updateSync(@newTileState)
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth
removeLineNodes: ->
@removeLineNode(id) for id of @oldTileState.lines
@@ -104,7 +110,7 @@ class TileComponent
return
buildLineHTML: (id) ->
{scrollWidth} = @newState
{width} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
classes = ''
@@ -113,7 +119,7 @@ class TileComponent
classes += decorationClass + ' '
classes += 'line'
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{scrollWidth}px;\" data-screen-row=\"#{screenRow}\">"
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{width}px;\" data-screen-row=\"#{screenRow}\">"
if text is ""
lineHTML += @buildEmptyLineInnerHTML(id)
@@ -273,8 +279,8 @@ class TileComponent
lineNode = @lineNodesByLineId[id]
if @newState.scrollWidth isnt @oldState.scrollWidth
lineNode.style.width = @newState.scrollWidth + 'px'
if @newState.width isnt @oldState.width
lineNode.style.width = @newState.width + 'px'
newDecorationClasses = newLineState.decorationClasses
oldDecorationClasses = oldLineState.decorationClasses

View File

@@ -100,15 +100,6 @@ atom-text-editor {
min-width: 0;
}
.underlayer {
position: absolute;
z-index: -2;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.highlight {
background: none;
padding: 0;

View File

@@ -81,15 +81,6 @@
min-width: 0;
}
.underlayer {
position: absolute;
z-index: -2;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.highlight {
background: none;
padding: 0;