Files
atom/src/decoration-manager.js
Antonio Scandurra 6a083e14a2 Schedule component updates directly from the model
The only event-based APIs we kept are for listening to changes in block
decoration markers.
2017-05-05 09:29:29 +02:00

290 lines
10 KiB
JavaScript

const {Emitter} = require('event-kit')
const Decoration = require('./decoration')
const LayerDecoration = require('./layer-decoration')
module.exports =
class DecorationManager {
constructor (editor) {
this.editor = editor
this.displayLayer = this.editor.displayLayer
this.emitter = new Emitter()
this.decorationCountsByLayer = new Map()
this.markerDecorationCountsByLayer = new Map()
this.decorationsByMarker = new Map()
this.layerDecorationsByMarkerLayer = new Map()
this.overlayDecorations = new Set()
this.layerUpdateDisposablesByLayer = new WeakMap()
}
observeDecorations (callback) {
const decorations = this.getDecorations()
for (let i = 0; i < decorations.length; i++) {
callback(decorations[i])
}
return this.onDidAddDecoration(callback)
}
onDidAddDecoration (callback) {
return this.emitter.on('did-add-decoration', callback)
}
onDidRemoveDecoration (callback) {
return this.emitter.on('did-remove-decoration', callback)
}
onDidUpdateDecorations (callback) {
return this.emitter.on('did-update-decorations', callback)
}
getDecorations (propertyFilter) {
let allDecorations = []
this.decorationsByMarker.forEach((decorations) => {
decorations.forEach((decoration) => allDecorations.push(decoration))
})
if (propertyFilter != null) {
allDecorations = allDecorations.filter(function (decoration) {
for (let key in propertyFilter) {
const value = propertyFilter[key]
if (decoration.properties[key] !== value) return false
}
return true
})
}
return allDecorations
}
getLineDecorations (propertyFilter) {
return this.getDecorations(propertyFilter).filter(decoration => decoration.isType('line'))
}
getLineNumberDecorations (propertyFilter) {
return this.getDecorations(propertyFilter).filter(decoration => decoration.isType('line-number'))
}
getHighlightDecorations (propertyFilter) {
return this.getDecorations(propertyFilter).filter(decoration => decoration.isType('highlight'))
}
getOverlayDecorations (propertyFilter) {
const result = []
result.push(...Array.from(this.overlayDecorations))
if (propertyFilter != null) {
return result.filter(function (decoration) {
for (let key in propertyFilter) {
const value = propertyFilter[key]
if (decoration.properties[key] !== value) {
return false
}
}
return true
})
} else {
return result
}
}
decorationPropertiesByMarkerForScreenRowRange (startScreenRow, endScreenRow) {
const decorationPropertiesByMarker = new Map()
this.decorationCountsByLayer.forEach((count, markerLayer) => {
const markers = markerLayer.findMarkers({intersectsScreenRowRange: [startScreenRow, endScreenRow - 1]})
const layerDecorations = this.layerDecorationsByMarkerLayer.get(markerLayer)
const hasMarkerDecorations = this.markerDecorationCountsByLayer.get(markerLayer) > 0
for (let i = 0; i < markers.length; i++) {
const marker = markers[i]
if (!marker.isValid()) continue
let decorationPropertiesForMarker = decorationPropertiesByMarker.get(marker)
if (decorationPropertiesForMarker == null) {
decorationPropertiesForMarker = []
decorationPropertiesByMarker.set(marker, decorationPropertiesForMarker)
}
if (layerDecorations) {
layerDecorations.forEach((layerDecoration) => {
const properties = layerDecoration.getPropertiesForMarker(marker) || layerDecoration.getProperties()
decorationPropertiesForMarker.push(properties)
})
}
if (hasMarkerDecorations) {
const decorationsForMarker = this.decorationsByMarker.get(marker)
if (decorationsForMarker) {
decorationsForMarker.forEach((decoration) => {
decorationPropertiesForMarker.push(decoration.getProperties())
})
}
}
}
})
return decorationPropertiesByMarker
}
decorationsForScreenRowRange (startScreenRow, endScreenRow) {
const decorationsByMarkerId = {}
for (const layer of this.decorationCountsByLayer.keys()) {
for (const marker of layer.findMarkers({intersectsScreenRowRange: [startScreenRow, endScreenRow]})) {
const decorations = this.decorationsByMarker.get(marker)
if (decorations) {
decorationsByMarkerId[marker.id] = Array.from(decorations)
}
}
}
return decorationsByMarkerId
}
decorationsStateForScreenRowRange (startScreenRow, endScreenRow) {
const decorationsState = {}
for (const layer of this.decorationCountsByLayer.keys()) {
for (const marker of layer.findMarkers({intersectsScreenRowRange: [startScreenRow, endScreenRow]})) {
if (marker.isValid()) {
const screenRange = marker.getScreenRange()
const bufferRange = marker.getBufferRange()
const rangeIsReversed = marker.isReversed()
const decorations = this.decorationsByMarker.get(marker)
if (decorations) {
decorations.forEach((decoration) => {
decorationsState[decoration.id] = {
properties: decoration.properties,
screenRange,
bufferRange,
rangeIsReversed
}
})
}
const layerDecorations = this.layerDecorationsByMarkerLayer.get(layer)
if (layerDecorations) {
layerDecorations.forEach((layerDecoration) => {
const properties = layerDecoration.getPropertiesForMarker(marker) || layerDecoration.getProperties()
decorationsState[`${layerDecoration.id}-${marker.id}`] = {
properties,
screenRange,
bufferRange,
rangeIsReversed
}
})
}
}
}
}
return decorationsState
}
decorateMarker (marker, decorationParams) {
if (marker.isDestroyed()) {
const error = new Error('Cannot decorate a destroyed marker')
error.metadata = {markerLayerIsDestroyed: marker.layer.isDestroyed()}
if (marker.destroyStackTrace != null) {
error.metadata.destroyStackTrace = marker.destroyStackTrace
}
if (marker.bufferMarker != null && marker.bufferMarker.destroyStackTrace != null) {
error.metadata.destroyStackTrace = marker.bufferMarker.destroyStackTrace
}
throw error
}
marker = this.displayLayer.getMarkerLayer(marker.layer.id).getMarker(marker.id)
const decoration = new Decoration(marker, this, decorationParams)
let decorationsForMarker = this.decorationsByMarker.get(marker)
if (!decorationsForMarker) {
decorationsForMarker = new Set()
this.decorationsByMarker.set(marker, decorationsForMarker)
}
decorationsForMarker.add(decoration)
if (decoration.isType('overlay')) this.overlayDecorations.add(decoration)
this.observeDecoratedLayer(marker.layer, true)
this.editor.didAddDecoration(decoration)
this.emitDidUpdateDecorations()
this.emitter.emit('did-add-decoration', decoration)
return decoration
}
decorateMarkerLayer (markerLayer, decorationParams) {
if (markerLayer.isDestroyed()) {
throw new Error('Cannot decorate a destroyed marker layer')
}
markerLayer = this.displayLayer.getMarkerLayer(markerLayer.id)
const decoration = new LayerDecoration(markerLayer, this, decorationParams)
let layerDecorations = this.layerDecorationsByMarkerLayer.get(markerLayer)
if (layerDecorations == null) {
layerDecorations = new Set()
this.layerDecorationsByMarkerLayer.set(markerLayer, layerDecorations)
}
layerDecorations.add(decoration)
this.observeDecoratedLayer(markerLayer, false)
this.emitDidUpdateDecorations()
return decoration
}
emitDidUpdateDecorations () {
this.editor.scheduleComponentUpdate()
this.emitter.emit('did-update-decorations')
}
decorationDidChangeType (decoration) {
if (decoration.isType('overlay')) {
this.overlayDecorations.add(decoration)
} else {
this.overlayDecorations.delete(decoration)
}
}
didDestroyMarkerDecoration (decoration) {
const {marker} = decoration
const decorations = this.decorationsByMarker.get(marker)
if (decorations && decorations.has(decoration)) {
decorations.delete(decoration)
if (decorations.size === 0) this.decorationsByMarker.delete(marker)
this.overlayDecorations.delete(decoration)
this.unobserveDecoratedLayer(marker.layer, true)
this.emitter.emit('did-remove-decoration', decoration)
this.emitDidUpdateDecorations()
}
}
didDestroyLayerDecoration (decoration) {
const {markerLayer} = decoration
const decorations = this.layerDecorationsByMarkerLayer.get(markerLayer)
if (decorations && decorations.has(decoration)) {
decorations.delete(decoration)
if (decorations.size === 0) {
this.layerDecorationsByMarkerLayer.delete(markerLayer)
}
this.unobserveDecoratedLayer(markerLayer, true)
this.emitDidUpdateDecorations()
}
}
observeDecoratedLayer (layer, isMarkerDecoration) {
const newCount = (this.decorationCountsByLayer.get(layer) || 0) + 1
this.decorationCountsByLayer.set(layer, newCount)
if (newCount === 1) {
this.layerUpdateDisposablesByLayer.set(layer, layer.onDidUpdate(this.emitDidUpdateDecorations.bind(this)))
}
if (isMarkerDecoration) {
this.markerDecorationCountsByLayer.set(layer, (this.markerDecorationCountsByLayer.get(layer) || 0) + 1)
}
}
unobserveDecoratedLayer (layer, isMarkerDecoration) {
const newCount = this.decorationCountsByLayer.get(layer) - 1
if (newCount === 0) {
this.layerUpdateDisposablesByLayer.get(layer).dispose()
this.decorationCountsByLayer.delete(layer)
} else {
this.decorationCountsByLayer.set(layer, newCount)
}
if (isMarkerDecoration) {
this.markerDecorationCountsByLayer.set(this.markerDecorationCountsByLayer.get(layer) - 1)
}
}
}