mirror of
https://github.com/atom/atom.git
synced 2026-02-15 09:05:58 -05:00
Reimplement block decorations without the shadow DOM
This commit is contained in:
@@ -1,86 +0,0 @@
|
||||
cloneObject = (object) ->
|
||||
clone = {}
|
||||
clone[key] = value for key, value of object
|
||||
clone
|
||||
|
||||
module.exports =
|
||||
class BlockDecorationsComponent
|
||||
constructor: (@container, @views, @presenter, @domElementPool) ->
|
||||
@newState = null
|
||||
@oldState = null
|
||||
@blockDecorationNodesById = {}
|
||||
@domNode = @domElementPool.buildElement("content")
|
||||
@domNode.setAttribute("select", ".atom--invisible-block-decoration")
|
||||
@domNode.style.visibility = "hidden"
|
||||
|
||||
getDomNode: ->
|
||||
@domNode
|
||||
|
||||
updateSync: (state) ->
|
||||
@newState = state.content
|
||||
@oldState ?= {blockDecorations: {}, width: 0}
|
||||
|
||||
if @newState.width isnt @oldState.width
|
||||
@domNode.style.width = @newState.width + "px"
|
||||
@oldState.width = @newState.width
|
||||
|
||||
for id of @oldState.blockDecorations
|
||||
unless @newState.blockDecorations.hasOwnProperty(id)
|
||||
blockDecorationNode = @blockDecorationNodesById[id]
|
||||
blockDecorationNode.previousSibling.remove()
|
||||
blockDecorationNode.nextSibling.remove()
|
||||
blockDecorationNode.remove()
|
||||
delete @blockDecorationNodesById[id]
|
||||
delete @oldState.blockDecorations[id]
|
||||
|
||||
for id of @newState.blockDecorations
|
||||
if @oldState.blockDecorations.hasOwnProperty(id)
|
||||
@updateBlockDecorationNode(id)
|
||||
else
|
||||
@oldState.blockDecorations[id] = {}
|
||||
@createAndAppendBlockDecorationNode(id)
|
||||
|
||||
measureBlockDecorations: ->
|
||||
for decorationId, blockDecorationNode of @blockDecorationNodesById
|
||||
decoration = @newState.blockDecorations[decorationId].decoration
|
||||
topRuler = blockDecorationNode.previousSibling
|
||||
bottomRuler = blockDecorationNode.nextSibling
|
||||
|
||||
width = blockDecorationNode.offsetWidth
|
||||
height = bottomRuler.offsetTop - topRuler.offsetTop
|
||||
@presenter.setBlockDecorationDimensions(decoration, width, height)
|
||||
|
||||
createAndAppendBlockDecorationNode: (id) ->
|
||||
blockDecorationState = @newState.blockDecorations[id]
|
||||
blockDecorationClass = "atom--block-decoration-#{id}"
|
||||
topRuler = document.createElement("div")
|
||||
blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item)
|
||||
bottomRuler = document.createElement("div")
|
||||
topRuler.classList.add(blockDecorationClass)
|
||||
blockDecorationNode.classList.add(blockDecorationClass)
|
||||
bottomRuler.classList.add(blockDecorationClass)
|
||||
|
||||
@container.appendChild(topRuler)
|
||||
@container.appendChild(blockDecorationNode)
|
||||
@container.appendChild(bottomRuler)
|
||||
|
||||
@blockDecorationNodesById[id] = blockDecorationNode
|
||||
@updateBlockDecorationNode(id)
|
||||
|
||||
updateBlockDecorationNode: (id) ->
|
||||
newBlockDecorationState = @newState.blockDecorations[id]
|
||||
oldBlockDecorationState = @oldState.blockDecorations[id]
|
||||
blockDecorationNode = @blockDecorationNodesById[id]
|
||||
|
||||
if newBlockDecorationState.isVisible
|
||||
blockDecorationNode.previousSibling.classList.remove("atom--invisible-block-decoration")
|
||||
blockDecorationNode.classList.remove("atom--invisible-block-decoration")
|
||||
blockDecorationNode.nextSibling.classList.remove("atom--invisible-block-decoration")
|
||||
else
|
||||
blockDecorationNode.previousSibling.classList.add("atom--invisible-block-decoration")
|
||||
blockDecorationNode.classList.add("atom--invisible-block-decoration")
|
||||
blockDecorationNode.nextSibling.classList.add("atom--invisible-block-decoration")
|
||||
|
||||
if oldBlockDecorationState.screenRow isnt newBlockDecorationState.screenRow
|
||||
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
oldBlockDecorationState.screenRow = newBlockDecorationState.screenRow
|
||||
@@ -19,7 +19,7 @@ module.exports =
|
||||
class LinesComponent extends TiledComponent
|
||||
placeholderTextDiv: null
|
||||
|
||||
constructor: ({@presenter, @domElementPool, @assert}) ->
|
||||
constructor: ({@views, @presenter, @domElementPool, @assert}) ->
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.classList.add('lines')
|
||||
@tilesNode = document.createElement("div")
|
||||
@@ -57,9 +57,17 @@ class LinesComponent extends TiledComponent
|
||||
@domNode.appendChild(@placeholderTextDiv)
|
||||
@oldState.placeholderText = @newState.placeholderText
|
||||
|
||||
# Removing and updating block decorations needs to be done in two different
|
||||
# steps, so that the same decoration node can be moved from one tile to
|
||||
# another in the same animation frame.
|
||||
for component in @getComponents()
|
||||
component.removeDeletedBlockDecorations()
|
||||
for component in @getComponents()
|
||||
component.updateBlockDecorations()
|
||||
|
||||
@cursorsComponent.updateSync(state)
|
||||
|
||||
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool, @assert})
|
||||
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool, @assert, @views})
|
||||
|
||||
buildEmptyState: ->
|
||||
{tiles: {}}
|
||||
@@ -83,6 +91,10 @@ class LinesComponent extends TiledComponent
|
||||
@presenter.setLineHeight(lineHeightInPixels)
|
||||
@presenter.setBaseCharacterWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
|
||||
|
||||
measureBlockDecorations: ->
|
||||
for component in @getComponents()
|
||||
component.measureBlockDecorations()
|
||||
|
||||
lineIdForScreenRow: (screenRow) ->
|
||||
tile = @presenter.tileForRow(screenRow)
|
||||
@getComponentForTile(tile)?.lineIdForScreenRow(screenRow)
|
||||
|
||||
@@ -2,18 +2,17 @@ const HighlightsComponent = require('./highlights-component')
|
||||
const ZERO_WIDTH_NBSP = '\ufeff'
|
||||
|
||||
module.exports = class LinesTileComponent {
|
||||
constructor ({presenter, id, domElementPool, assert}) {
|
||||
this.presenter = presenter
|
||||
constructor ({presenter, id, domElementPool, assert, views}) {
|
||||
this.id = id
|
||||
this.presenter = presenter
|
||||
this.views = views
|
||||
this.domElementPool = domElementPool
|
||||
this.assert = assert
|
||||
this.measuredLines = new Set()
|
||||
this.lineNodesByLineId = {}
|
||||
this.screenRowsByLineId = {}
|
||||
this.lineIdsByScreenRow = {}
|
||||
this.textNodesByLineId = {}
|
||||
this.insertionPointsBeforeLineById = {}
|
||||
this.insertionPointsAfterLineById = {}
|
||||
this.blockDecorationNodesByLineIdAndDecorationId = {}
|
||||
this.domNode = this.domElementPool.buildElement('div')
|
||||
this.domNode.style.position = 'absolute'
|
||||
this.domNode.style.display = 'block'
|
||||
@@ -22,6 +21,7 @@ module.exports = class LinesTileComponent {
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.removeLineNodes()
|
||||
this.domElementPool.freeElementAndDescendants(this.domNode)
|
||||
}
|
||||
|
||||
@@ -80,15 +80,29 @@ module.exports = class LinesTileComponent {
|
||||
}
|
||||
}
|
||||
|
||||
removeLineNode (id) {
|
||||
this.domElementPool.freeElementAndDescendants(this.lineNodesByLineId[id])
|
||||
this.removeBlockDecorationInsertionPointBeforeLine(id)
|
||||
this.removeBlockDecorationInsertionPointAfterLine(id)
|
||||
delete this.lineNodesByLineId[id]
|
||||
delete this.textNodesByLineId[id]
|
||||
delete this.lineIdsByScreenRow[this.screenRowsByLineId[id]]
|
||||
delete this.screenRowsByLineId[id]
|
||||
delete this.oldTileState.lines[id]
|
||||
removeLineNode (lineId) {
|
||||
this.domElementPool.freeElementAndDescendants(this.lineNodesByLineId[lineId])
|
||||
for (const decorationId of Object.keys(this.oldTileState.lines[lineId].precedingBlockDecorations)) {
|
||||
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
topRulerNode.remove()
|
||||
blockDecorationNode.remove()
|
||||
bottomRulerNode.remove()
|
||||
}
|
||||
for (const decorationId of Object.keys(this.oldTileState.lines[lineId].followingBlockDecorations)) {
|
||||
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
topRulerNode.remove()
|
||||
blockDecorationNode.remove()
|
||||
bottomRulerNode.remove()
|
||||
}
|
||||
|
||||
delete this.blockDecorationNodesByLineIdAndDecorationId[lineId]
|
||||
delete this.lineNodesByLineId[lineId]
|
||||
delete this.textNodesByLineId[lineId]
|
||||
delete this.lineIdsByScreenRow[this.screenRowsByLineId[lineId]]
|
||||
delete this.screenRowsByLineId[lineId]
|
||||
delete this.oldTileState.lines[lineId]
|
||||
}
|
||||
|
||||
updateLineNodes () {
|
||||
@@ -110,6 +124,10 @@ module.exports = class LinesTileComponent {
|
||||
this.screenRowsByLineId[id] = lineState.screenRow
|
||||
this.lineIdsByScreenRow[lineState.screenRow] = id
|
||||
this.oldTileState.lines[id] = Object.assign({}, lineState)
|
||||
// Avoid assigning state for block decorations, because we need to
|
||||
// process it later when updating the DOM.
|
||||
this.oldTileState.lines[id].precedingBlockDecorations = {}
|
||||
this.oldTileState.lines[id].followingBlockDecorations = {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,87 +141,6 @@ module.exports = class LinesTileComponent {
|
||||
} else {
|
||||
this.domNode.insertBefore(lineNode, nextNode)
|
||||
}
|
||||
this.insertBlockDecorationInsertionPointBeforeLine(id)
|
||||
this.insertBlockDecorationInsertionPointAfterLine(id)
|
||||
}
|
||||
}
|
||||
|
||||
removeBlockDecorationInsertionPointBeforeLine (id) {
|
||||
const insertionPoint = this.insertionPointsBeforeLineById[id]
|
||||
if (insertionPoint != null) {
|
||||
this.domElementPool.freeElementAndDescendants(insertionPoint)
|
||||
delete this.insertionPointsBeforeLineById[id]
|
||||
}
|
||||
}
|
||||
|
||||
insertBlockDecorationInsertionPointBeforeLine (id) {
|
||||
const {hasPrecedingBlockDecorations, screenRow} = this.newTileState.lines[id]
|
||||
if (hasPrecedingBlockDecorations) {
|
||||
const lineNode = this.lineNodesByLineId[id]
|
||||
const insertionPoint = this.domElementPool.buildElement('content')
|
||||
this.domNode.insertBefore(insertionPoint, lineNode)
|
||||
this.insertionPointsBeforeLineById[id] = insertionPoint
|
||||
insertionPoint.dataset.screenRow = screenRow
|
||||
this.updateBlockDecorationInsertionPointBeforeLine(id)
|
||||
}
|
||||
}
|
||||
|
||||
updateBlockDecorationInsertionPointBeforeLine (id) {
|
||||
const oldLineState = this.oldTileState.lines[id]
|
||||
const newLineState = this.newTileState.lines[id]
|
||||
const insertionPoint = this.insertionPointsBeforeLineById[id]
|
||||
if (insertionPoint != null) {
|
||||
if (newLineState.screenRow !== oldLineState.screenRow) {
|
||||
insertionPoint.dataset.screenRow = newLineState.screenRow
|
||||
}
|
||||
|
||||
const precedingBlockDecorationsSelector = newLineState.precedingBlockDecorations
|
||||
.map((d) => `.atom--block-decoration-${d.id}`)
|
||||
.join(',')
|
||||
if (precedingBlockDecorationsSelector !== oldLineState.precedingBlockDecorationsSelector) {
|
||||
insertionPoint.setAttribute('select', precedingBlockDecorationsSelector)
|
||||
oldLineState.precedingBlockDecorationsSelector = precedingBlockDecorationsSelector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeBlockDecorationInsertionPointAfterLine (id) {
|
||||
const insertionPoint = this.insertionPointsAfterLineById[id]
|
||||
if (insertionPoint != null) {
|
||||
this.domElementPool.freeElementAndDescendants(insertionPoint)
|
||||
delete this.insertionPointsAfterLineById[id]
|
||||
}
|
||||
}
|
||||
|
||||
insertBlockDecorationInsertionPointAfterLine (id) {
|
||||
const {hasFollowingBlockDecorations, screenRow} = this.newTileState.lines[id]
|
||||
if (hasFollowingBlockDecorations) {
|
||||
const lineNode = this.lineNodesByLineId[id]
|
||||
const insertionPoint = this.domElementPool.buildElement('content')
|
||||
this.domNode.insertBefore(insertionPoint, lineNode.nextSibling)
|
||||
this.insertionPointsAfterLineById[id] = insertionPoint
|
||||
insertionPoint.dataset.screenRow = screenRow
|
||||
this.updateBlockDecorationInsertionPointAfterLine(id)
|
||||
}
|
||||
}
|
||||
|
||||
updateBlockDecorationInsertionPointAfterLine (id) {
|
||||
const oldLineState = this.oldTileState.lines[id]
|
||||
const newLineState = this.newTileState.lines[id]
|
||||
const insertionPoint = this.insertionPointsAfterLineById[id]
|
||||
|
||||
if (insertionPoint != null) {
|
||||
if (newLineState.screenRow !== oldLineState.screenRow) {
|
||||
insertionPoint.dataset.screenRow = newLineState.screenRow
|
||||
}
|
||||
|
||||
const followingBlockDecorationsSelector = newLineState.followingBlockDecorations
|
||||
.map((d) => `.atom--block-decoration-${d.id}`)
|
||||
.join(',')
|
||||
if (followingBlockDecorationsSelector !== oldLineState.followingBlockDecorationsSelector) {
|
||||
insertionPoint.setAttribute('select', followingBlockDecorationsSelector)
|
||||
oldLineState.followingBlockDecorationsSelector = followingBlockDecorationsSelector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,29 +233,143 @@ module.exports = class LinesTileComponent {
|
||||
|
||||
oldLineState.decorationClasses = newLineState.decorationClasses
|
||||
|
||||
if (!oldLineState.hasPrecedingBlockDecorations && newLineState.hasPrecedingBlockDecorations) {
|
||||
this.insertBlockDecorationInsertionPointBeforeLine(id)
|
||||
} else if (oldLineState.hasPrecedingBlockDecorations && !newLineState.hasPrecedingBlockDecorations) {
|
||||
this.removeBlockDecorationInsertionPointBeforeLine(id)
|
||||
}
|
||||
|
||||
if (!oldLineState.hasFollowingBlockDecorations && newLineState.hasFollowingBlockDecorations) {
|
||||
this.insertBlockDecorationInsertionPointAfterLine(id)
|
||||
} else if (oldLineState.hasFollowingBlockDecorations && !newLineState.hasFollowingBlockDecorations) {
|
||||
this.removeBlockDecorationInsertionPointAfterLine(id)
|
||||
}
|
||||
|
||||
if (newLineState.screenRow !== oldLineState.screenRow) {
|
||||
lineNode.dataset.screenRow = newLineState.screenRow
|
||||
this.lineIdsByScreenRow[newLineState.screenRow] = id
|
||||
this.screenRowsByLineId[id] = newLineState.screenRow
|
||||
}
|
||||
|
||||
this.updateBlockDecorationInsertionPointBeforeLine(id)
|
||||
this.updateBlockDecorationInsertionPointAfterLine(id)
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
oldLineState.hasPrecedingBlockDecorations = newLineState.hasPrecedingBlockDecorations
|
||||
oldLineState.hasFollowingBlockDecorations = newLineState.hasFollowingBlockDecorations
|
||||
}
|
||||
|
||||
removeDeletedBlockDecorations () {
|
||||
for (const lineId of Object.keys(this.newTileState.lines)) {
|
||||
const oldLineState = this.oldTileState.lines[lineId]
|
||||
const newLineState = this.newTileState.lines[lineId]
|
||||
const lineNode = this.lineNodesByLineId[lineId]
|
||||
for (const decorationId of Object.keys(oldLineState.precedingBlockDecorations)) {
|
||||
if (!newLineState.precedingBlockDecorations.hasOwnProperty(decorationId)) {
|
||||
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
topRulerNode.remove()
|
||||
blockDecorationNode.remove()
|
||||
bottomRulerNode.remove()
|
||||
delete this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
delete oldLineState.precedingBlockDecorations[decorationId]
|
||||
}
|
||||
}
|
||||
for (const decorationId of Object.keys(oldLineState.followingBlockDecorations)) {
|
||||
if (!newLineState.followingBlockDecorations.hasOwnProperty(decorationId)) {
|
||||
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
topRulerNode.remove()
|
||||
blockDecorationNode.remove()
|
||||
bottomRulerNode.remove()
|
||||
delete this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
delete oldLineState.followingBlockDecorations[decorationId]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateBlockDecorations () {
|
||||
for (const lineId of Object.keys(this.newTileState.lines)) {
|
||||
const oldLineState = this.oldTileState.lines[lineId]
|
||||
const newLineState = this.newTileState.lines[lineId]
|
||||
const lineNode = this.lineNodesByLineId[lineId]
|
||||
if (!this.blockDecorationNodesByLineIdAndDecorationId.hasOwnProperty(lineId)) {
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId] = {}
|
||||
}
|
||||
for (const decorationId of Object.keys(newLineState.precedingBlockDecorations)) {
|
||||
const oldBlockDecorationState = oldLineState.precedingBlockDecorations[decorationId]
|
||||
const newBlockDecorationState = newLineState.precedingBlockDecorations[decorationId]
|
||||
if (oldBlockDecorationState != null) {
|
||||
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
if (oldBlockDecorationState.screenRow !== newBlockDecorationState.screenRow) {
|
||||
topRulerNode.remove()
|
||||
blockDecorationNode.remove()
|
||||
bottomRulerNode.remove()
|
||||
topRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(topRulerNode, lineNode)
|
||||
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(blockDecorationNode, lineNode)
|
||||
bottomRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(bottomRulerNode, lineNode)
|
||||
}
|
||||
} else {
|
||||
const topRulerNode = document.createElement('div')
|
||||
topRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(topRulerNode, lineNode)
|
||||
const blockDecorationNode = this.views.getView(newBlockDecorationState.decoration.getProperties().item)
|
||||
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(blockDecorationNode, lineNode)
|
||||
const bottomRulerNode = document.createElement('div')
|
||||
bottomRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(bottomRulerNode, lineNode)
|
||||
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId] =
|
||||
{topRulerNode, blockDecorationNode, bottomRulerNode}
|
||||
}
|
||||
oldLineState.precedingBlockDecorations[decorationId] = Object.assign({}, newBlockDecorationState)
|
||||
}
|
||||
for (const decorationId of Object.keys(newLineState.followingBlockDecorations)) {
|
||||
const oldBlockDecorationState = oldLineState.followingBlockDecorations[decorationId]
|
||||
const newBlockDecorationState = newLineState.followingBlockDecorations[decorationId]
|
||||
if (oldBlockDecorationState != null) {
|
||||
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
if (oldBlockDecorationState.screenRow !== newBlockDecorationState.screenRow) {
|
||||
topRulerNode.remove()
|
||||
blockDecorationNode.remove()
|
||||
bottomRulerNode.remove()
|
||||
bottomRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(bottomRulerNode, lineNode.nextSibling)
|
||||
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(blockDecorationNode, lineNode.nextSibling)
|
||||
topRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(topRulerNode, lineNode.nextSibling)
|
||||
}
|
||||
} else {
|
||||
const bottomRulerNode = document.createElement('div')
|
||||
bottomRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(bottomRulerNode, lineNode.nextSibling)
|
||||
const blockDecorationNode = this.views.getView(newBlockDecorationState.decoration.getProperties().item)
|
||||
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(blockDecorationNode, lineNode.nextSibling)
|
||||
const topRulerNode = document.createElement('div')
|
||||
topRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
this.domNode.insertBefore(topRulerNode, lineNode.nextSibling)
|
||||
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId] =
|
||||
{topRulerNode, blockDecorationNode, bottomRulerNode}
|
||||
}
|
||||
oldLineState.followingBlockDecorations[decorationId] = Object.assign({}, newBlockDecorationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
measureBlockDecorations () {
|
||||
for (const lineId of Object.keys(this.newTileState.lines)) {
|
||||
const newLineState = this.newTileState.lines[lineId]
|
||||
|
||||
for (const decorationId of Object.keys(newLineState.precedingBlockDecorations)) {
|
||||
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
const width = blockDecorationNode.offsetWidth
|
||||
const height = bottomRulerNode.offsetTop - topRulerNode.offsetTop
|
||||
const {decoration} = newLineState.precedingBlockDecorations[decorationId]
|
||||
this.presenter.setBlockDecorationDimensions(decoration, width, height)
|
||||
}
|
||||
for (const decorationId of Object.keys(newLineState.followingBlockDecorations)) {
|
||||
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
|
||||
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
|
||||
const width = blockDecorationNode.offsetWidth
|
||||
const height = bottomRulerNode.offsetTop - topRulerNode.offsetTop
|
||||
const {decoration} = newLineState.followingBlockDecorations[decorationId]
|
||||
this.presenter.setBlockDecorationDimensions(decoration, width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lineNodeForScreenRow (screenRow) {
|
||||
|
||||
61
src/off-screen-block-decorations-component.js
Normal file
61
src/off-screen-block-decorations-component.js
Normal file
@@ -0,0 +1,61 @@
|
||||
module.exports = class OffScreenBlockDecorationsComponent {
|
||||
constructor ({presenter, views}) {
|
||||
this.presenter = presenter
|
||||
this.views = views
|
||||
this.newState = {offScreenBlockDecorations: {}, width: 0}
|
||||
this.oldState = {offScreenBlockDecorations: {}, width: 0}
|
||||
this.domNode = document.createElement('div')
|
||||
this.domNode.style.visibility = 'hidden'
|
||||
this.blockDecorationNodesById = {}
|
||||
}
|
||||
|
||||
getDomNode () {
|
||||
return this.domNode
|
||||
}
|
||||
|
||||
updateSync (state) {
|
||||
this.newState = state.content
|
||||
|
||||
if (this.newState.width !== this.oldState.width) {
|
||||
this.domNode.style.width = `${this.newState.width}px`
|
||||
this.oldState.width = this.newState.width
|
||||
}
|
||||
|
||||
for (const id of Object.keys(this.oldState.offScreenBlockDecorations)) {
|
||||
if (!this.newState.offScreenBlockDecorations.hasOwnProperty(id)) {
|
||||
const {topRuler, blockDecoration, bottomRuler} = this.blockDecorationNodesById[id]
|
||||
topRuler.remove()
|
||||
blockDecoration.remove()
|
||||
bottomRuler.remove()
|
||||
delete this.blockDecorationNodesById[id]
|
||||
delete this.oldState.offScreenBlockDecorations[id]
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of Object.keys(this.newState.offScreenBlockDecorations)) {
|
||||
const decoration = this.newState.offScreenBlockDecorations[id]
|
||||
if (!this.oldState.offScreenBlockDecorations.hasOwnProperty(id)) {
|
||||
const topRuler = document.createElement('div')
|
||||
this.domNode.appendChild(topRuler)
|
||||
const blockDecoration = this.views.getView(decoration.getProperties().item)
|
||||
this.domNode.appendChild(blockDecoration)
|
||||
const bottomRuler = document.createElement('div')
|
||||
this.domNode.appendChild(bottomRuler)
|
||||
|
||||
this.blockDecorationNodesById[id] = {topRuler, blockDecoration, bottomRuler}
|
||||
}
|
||||
|
||||
this.oldState.offScreenBlockDecorations[id] = decoration
|
||||
}
|
||||
}
|
||||
|
||||
measureBlockDecorations () {
|
||||
for (const id of Object.keys(this.blockDecorationNodesById)) {
|
||||
const {topRuler, blockDecoration, bottomRuler} = this.blockDecorationNodesById[id]
|
||||
const width = blockDecoration.offsetWidth
|
||||
const height = bottomRuler.offsetTop - topRuler.offsetTop
|
||||
const decoration = this.newState.offScreenBlockDecorations[id]
|
||||
this.presenter.setBlockDecorationDimensions(decoration, width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,12 @@ TextEditorPresenter = require './text-editor-presenter'
|
||||
GutterContainerComponent = require './gutter-container-component'
|
||||
InputComponent = require './input-component'
|
||||
LinesComponent = require './lines-component'
|
||||
OffScreenBlockDecorationsComponent = require './off-screen-block-decorations-component'
|
||||
ScrollbarComponent = require './scrollbar-component'
|
||||
ScrollbarCornerComponent = require './scrollbar-corner-component'
|
||||
OverlayManager = require './overlay-manager'
|
||||
DOMElementPool = require './dom-element-pool'
|
||||
LinesYardstick = require './lines-yardstick'
|
||||
BlockDecorationsComponent = require './block-decorations-component'
|
||||
LineTopIndex = require 'line-top-index'
|
||||
|
||||
module.exports =
|
||||
@@ -69,7 +69,6 @@ class TextEditorComponent
|
||||
@domNode.classList.add('editor--private')
|
||||
|
||||
@overlayManager = new OverlayManager(@presenter, @domNode, @views)
|
||||
@blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool)
|
||||
|
||||
@scrollViewNode = document.createElement('div')
|
||||
@scrollViewNode.classList.add('scroll-view')
|
||||
@@ -78,10 +77,11 @@ class TextEditorComponent
|
||||
@hiddenInputComponent = new InputComponent
|
||||
@scrollViewNode.appendChild(@hiddenInputComponent.getDomNode())
|
||||
|
||||
@linesComponent = new LinesComponent({@presenter, @hostElement, @domElementPool, @assert, @grammars})
|
||||
@linesComponent = new LinesComponent({@presenter, @hostElement, @domElementPool, @assert, @grammars, @views})
|
||||
@scrollViewNode.appendChild(@linesComponent.getDomNode())
|
||||
|
||||
@linesComponent.getDomNode().appendChild(@blockDecorationsComponent.getDomNode())
|
||||
@offScreenBlockDecorationsComponent = new OffScreenBlockDecorationsComponent({@presenter, @views})
|
||||
@scrollViewNode.appendChild(@offScreenBlockDecorationsComponent.getDomNode())
|
||||
|
||||
@linesYardstick = new LinesYardstick(@editor, @linesComponent, lineTopIndex)
|
||||
@presenter.setLinesYardstick(@linesYardstick)
|
||||
@@ -165,8 +165,8 @@ class TextEditorComponent
|
||||
@gutterContainerComponent = null
|
||||
|
||||
@hiddenInputComponent.updateSync(@newState)
|
||||
@offScreenBlockDecorationsComponent.updateSync(@newState)
|
||||
@linesComponent.updateSync(@newState)
|
||||
@blockDecorationsComponent?.updateSync(@newState)
|
||||
@horizontalScrollbarComponent.updateSync(@newState)
|
||||
@verticalScrollbarComponent.updateSync(@newState)
|
||||
@scrollbarCornerComponent.updateSync(@newState)
|
||||
@@ -186,7 +186,8 @@ class TextEditorComponent
|
||||
|
||||
readAfterUpdateSync: =>
|
||||
@overlayManager?.measureOverlays()
|
||||
@blockDecorationsComponent?.measureBlockDecorations() if @isVisible()
|
||||
@linesComponent.measureBlockDecorations()
|
||||
@offScreenBlockDecorationsComponent.measureBlockDecorations()
|
||||
|
||||
mountGutterContainerComponent: ->
|
||||
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views})
|
||||
@@ -543,7 +544,7 @@ class TextEditorComponent
|
||||
|
||||
screenPosition = @screenPositionForMouseEvent(event)
|
||||
|
||||
if event.target?.classList.contains('syntax--fold-marker')
|
||||
if event.target?.classList.contains('fold-marker')
|
||||
bufferPosition = @editor.bufferPositionForScreenPosition(screenPosition)
|
||||
@editor.destroyFoldsIntersectingBufferRange([bufferPosition, bufferPosition])
|
||||
return
|
||||
|
||||
@@ -34,6 +34,8 @@ class TextEditorPresenter
|
||||
@observedBlockDecorations = new Set()
|
||||
@invalidatedDimensionsByBlockDecoration = new Set()
|
||||
@invalidateAllBlockDecorationsDimensions = false
|
||||
@precedingBlockDecorationsByScreenRowAndId = {}
|
||||
@followingBlockDecorationsByScreenRowAndId = {}
|
||||
@screenRowsToMeasure = []
|
||||
@transferMeasurementsToModel()
|
||||
@transferMeasurementsFromModel()
|
||||
@@ -191,7 +193,7 @@ class TextEditorPresenter
|
||||
highlights: {}
|
||||
overlays: {}
|
||||
cursors: {}
|
||||
blockDecorations: {}
|
||||
offScreenBlockDecorations: {}
|
||||
gutters: []
|
||||
# Shared state that is copied into ``@state.gutters`.
|
||||
@sharedGutterStyles = {}
|
||||
@@ -412,16 +414,14 @@ class TextEditorPresenter
|
||||
throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}")
|
||||
|
||||
visibleLineIds[line.id] = true
|
||||
precedingBlockDecorations = @precedingBlockDecorationsByScreenRow[screenRow] ? []
|
||||
followingBlockDecorations = @followingBlockDecorationsByScreenRow[screenRow] ? []
|
||||
precedingBlockDecorations = @precedingBlockDecorationsByScreenRowAndId[screenRow] ? {}
|
||||
followingBlockDecorations = @followingBlockDecorationsByScreenRowAndId[screenRow] ? {}
|
||||
if tileState.lines.hasOwnProperty(line.id)
|
||||
lineState = tileState.lines[line.id]
|
||||
lineState.screenRow = screenRow
|
||||
lineState.decorationClasses = @lineDecorationClassesForRow(screenRow)
|
||||
lineState.precedingBlockDecorations = precedingBlockDecorations
|
||||
lineState.followingBlockDecorations = followingBlockDecorations
|
||||
lineState.hasPrecedingBlockDecorations = precedingBlockDecorations.length > 0
|
||||
lineState.hasFollowingBlockDecorations = followingBlockDecorations.length > 0
|
||||
else
|
||||
tileState.lines[line.id] =
|
||||
screenRow: screenRow
|
||||
@@ -430,8 +430,6 @@ class TextEditorPresenter
|
||||
decorationClasses: @lineDecorationClassesForRow(screenRow)
|
||||
precedingBlockDecorations: precedingBlockDecorations
|
||||
followingBlockDecorations: followingBlockDecorations
|
||||
hasPrecedingBlockDecorations: precedingBlockDecorations.length > 0
|
||||
hasFollowingBlockDecorations: followingBlockDecorations.length > 0
|
||||
|
||||
for id, line of tileState.lines
|
||||
delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id)
|
||||
@@ -1059,41 +1057,42 @@ class TextEditorPresenter
|
||||
@decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1)
|
||||
|
||||
updateBlockDecorations: ->
|
||||
@blockDecorationsToRenderById = {}
|
||||
@precedingBlockDecorationsByScreenRow = {}
|
||||
@followingBlockDecorationsByScreenRow = {}
|
||||
visibleDecorationsByMarkerId = @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1)
|
||||
|
||||
if @invalidateAllBlockDecorationsDimensions
|
||||
for decoration in @model.getDecorations(type: 'block')
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
@invalidateAllBlockDecorationsDimensions = false
|
||||
|
||||
for markerId, decorations of visibleDecorationsByMarkerId
|
||||
visibleDecorationsById = {}
|
||||
visibleDecorationsByScreenRowAndId = {}
|
||||
for markerId, decorations of @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1)
|
||||
for decoration in decorations when decoration.isType('block')
|
||||
@updateBlockDecorationState(decoration, true)
|
||||
screenRow = decoration.getMarker().getHeadScreenPosition().row
|
||||
if decoration.getProperties().position is "after"
|
||||
@followingBlockDecorationsByScreenRowAndId[screenRow] ?= {}
|
||||
@followingBlockDecorationsByScreenRowAndId[screenRow][decoration.id] = {screenRow, decoration}
|
||||
else
|
||||
@precedingBlockDecorationsByScreenRowAndId[screenRow] ?= {}
|
||||
@precedingBlockDecorationsByScreenRowAndId[screenRow][decoration.id] = {screenRow, decoration}
|
||||
visibleDecorationsById[decoration.id] = true
|
||||
visibleDecorationsByScreenRowAndId[screenRow] ?= {}
|
||||
visibleDecorationsByScreenRowAndId[screenRow][decoration.id] = true
|
||||
|
||||
for screenRow, blockDecorations of @precedingBlockDecorationsByScreenRowAndId
|
||||
if Number(screenRow) isnt @mouseWheelScreenRow
|
||||
for id, blockDecoration of blockDecorations
|
||||
unless visibleDecorationsByScreenRowAndId[screenRow]?[id]
|
||||
delete @precedingBlockDecorationsByScreenRowAndId[screenRow][id]
|
||||
|
||||
for screenRow, blockDecorations of @followingBlockDecorationsByScreenRowAndId
|
||||
if Number(screenRow) isnt @mouseWheelScreenRow
|
||||
for id, blockDecoration of blockDecorations
|
||||
unless visibleDecorationsByScreenRowAndId[screenRow]?[id]
|
||||
delete @followingBlockDecorationsByScreenRowAndId[screenRow][id]
|
||||
|
||||
@state.content.offScreenBlockDecorations = {}
|
||||
@invalidatedDimensionsByBlockDecoration.forEach (decoration) =>
|
||||
@updateBlockDecorationState(decoration, false)
|
||||
|
||||
for decorationId, decorationState of @state.content.blockDecorations
|
||||
continue if @blockDecorationsToRenderById[decorationId]
|
||||
continue if decorationState.screenRow is @mouseWheelScreenRow
|
||||
|
||||
delete @state.content.blockDecorations[decorationId]
|
||||
|
||||
updateBlockDecorationState: (decoration, isVisible) ->
|
||||
return if @blockDecorationsToRenderById[decoration.getId()]
|
||||
|
||||
screenRow = decoration.getMarker().getHeadScreenPosition().row
|
||||
if decoration.getProperties().position is "after"
|
||||
@followingBlockDecorationsByScreenRow[screenRow] ?= []
|
||||
@followingBlockDecorationsByScreenRow[screenRow].push(decoration)
|
||||
else
|
||||
@precedingBlockDecorationsByScreenRow[screenRow] ?= []
|
||||
@precedingBlockDecorationsByScreenRow[screenRow].push(decoration)
|
||||
@state.content.blockDecorations[decoration.getId()] = {decoration, screenRow, isVisible}
|
||||
@blockDecorationsToRenderById[decoration.getId()] = true
|
||||
unless visibleDecorationsById[decoration.id]
|
||||
@state.content.offScreenBlockDecorations[decoration.id] = decoration
|
||||
|
||||
updateLineDecorations: ->
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@@ -1295,7 +1294,7 @@ class TextEditorPresenter
|
||||
setBlockDecorationDimensions: (decoration, width, height) ->
|
||||
return unless @observedBlockDecorations.has(decoration)
|
||||
|
||||
@lineTopIndex.resizeBlock(decoration.getId(), height)
|
||||
@lineTopIndex.resizeBlock(decoration.id, height)
|
||||
|
||||
@invalidatedDimensionsByBlockDecoration.delete(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@@ -1332,7 +1331,7 @@ class TextEditorPresenter
|
||||
@didDestroyBlockDecoration(decoration)
|
||||
|
||||
isAfter = decoration.getProperties().position is "after"
|
||||
@lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row, 0, isAfter)
|
||||
@lineTopIndex.insertBlock(decoration.id, decoration.getMarker().getHeadScreenPosition().row, 0, isAfter)
|
||||
|
||||
@observedBlockDecorations.add(decoration)
|
||||
@invalidateBlockDecorationDimensions(decoration)
|
||||
@@ -1346,14 +1345,14 @@ class TextEditorPresenter
|
||||
# change.
|
||||
return if markerEvent.textChanged
|
||||
|
||||
@lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row)
|
||||
@lineTopIndex.moveBlock(decoration.id, decoration.getMarker().getHeadScreenPosition().row)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
didDestroyBlockDecoration: (decoration) ->
|
||||
return unless @observedBlockDecorations.has(decoration)
|
||||
|
||||
@lineTopIndex.removeBlock(decoration.getId())
|
||||
@lineTopIndex.removeBlock(decoration.id)
|
||||
@observedBlockDecorations.delete(decoration)
|
||||
@invalidatedDimensionsByBlockDecoration.delete(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
{values} = require 'underscore-plus'
|
||||
|
||||
cloneObject = (object) ->
|
||||
clone = {}
|
||||
clone[key] = value for key, value of object
|
||||
clone
|
||||
|
||||
module.exports =
|
||||
class TiledComponent
|
||||
updateSync: (state) ->
|
||||
@@ -41,7 +34,7 @@ class TiledComponent
|
||||
component = @componentsByTileId[tileRow] = @buildComponentForTile(tileRow)
|
||||
|
||||
@getTilesNode().appendChild(component.getDomNode())
|
||||
@oldState.tiles[tileRow] = cloneObject(tileState)
|
||||
@oldState.tiles[tileRow] = Object.assign({}, tileState)
|
||||
|
||||
component.updateSync(@newState)
|
||||
|
||||
@@ -50,5 +43,9 @@ class TiledComponent
|
||||
getComponentForTile: (tileRow) ->
|
||||
@componentsByTileId[tileRow]
|
||||
|
||||
getComponents: ->
|
||||
for _, component of @componentsByTileId
|
||||
component
|
||||
|
||||
getTiles: ->
|
||||
values(@componentsByTileId).map (component) -> component.getDomNode()
|
||||
@getComponents().map((component) -> component.getDomNode())
|
||||
|
||||
Reference in New Issue
Block a user