Add TextEditor-level marker layers and use them for selections

This commit is contained in:
Nathan Sobo
2015-11-02 16:50:01 -06:00
parent 1ee6384332
commit 29bb1bb31b
9 changed files with 112 additions and 82 deletions

View File

@@ -54,15 +54,18 @@ class DisplayBuffer extends Model
@buffer = @tokenizedBuffer.buffer
@charWidthsByScope = {}
@defaultMarkerLayer = new TextEditorMarkerLayer(this, @buffer.getDefaultMarkerLayer(), true)
@customMarkerLayersById = {}
@foldsByMarkerId = {}
@decorationsById = {}
@decorationsByMarkerId = {}
@overlayDecorationsById = {}
@layerDecorationsByMarkerLayerId = {}
@decorationCountsByLayerId = {}
@layerUpdateDisposablesByLayerId = {}
@disposables.add @tokenizedBuffer.observeGrammar @subscribeToScopedConfigSettings
@disposables.add @tokenizedBuffer.onDidChange @handleTokenizedBufferChange
@disposables.add @buffer.onDidCreateMarker @handleBufferMarkerCreated
@disposables.add @buffer.getDefaultMarkerLayer().onDidUpdate => @scheduleUpdateDecorationsEvent()
@foldMarkerAttributes = Object.freeze({class: 'fold', displayBufferId: @id})
folds = (new Fold(this, marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes()))
@@ -777,41 +780,39 @@ class DisplayBuffer extends Model
decorationsByMarkerId
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
decorationState = {}
decorationsState = {}
startBufferRow = @bufferRowForScreenRow(startScreenRow)
endBufferRow = @bufferRowForScreenRow(endScreenRow)
for layerId of @decorationCountsByLayerId
layer = @getMarkerLayer(layerId)
defaultLayer = @buffer.getDefaultMarkerLayer()
for marker in defaultLayer.findMarkers(intersectsRowRange: [startBufferRow, endBufferRow]) when marker.isValid()
if decorations = @decorationsByMarkerId[marker.id]
for decoration in decorations
decorationState[decoration.id] = {
properties: decoration.properties
screenRange: @screenRangeForBufferRange(marker.getRange())
rangeIsReversed: marker.isReversed()
}
for markerLayerId, layerDecorations of @layerDecorationsByMarkerLayerId
markerLayer = @buffer.getMarkerLayer(markerLayerId)
for marker in markerLayer.findMarkers(intersectsRowRange: [startBufferRow, endBufferRow]) when marker.isValid()
screenRange = @screenRangeForBufferRange(marker.getRange())
for marker in layer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow]) when marker.isValid()
screenRange = marker.getScreenRange()
rangeIsReversed = marker.isReversed()
for layerDecoration in layerDecorations
decorationState["#{layerDecoration.id}-#{marker.id}"] = {
properties: layerDecoration.overridePropertiesByMarkerId[marker.id] ? layerDecoration.properties
screenRange, rangeIsReversed
}
decorationState
if decorations = @decorationsByMarkerId[marker.id]
for decoration in decorations
decorationsState[decoration.id] = {
properties: decoration.properties
screenRange, rangeIsReversed
}
if layerDecorations = @layerDecorationsByMarkerLayerId[layerId]
for layerDecoration in layerDecorations
decorationsState["#{layerDecoration.id}-#{marker.id}"] = {
properties: layerDecoration.overridePropertiesByMarkerId[marker.id] ? layerDecoration.properties
screenRange, rangeIsReversed
}
decorationsState
decorateMarker: (marker, decorationParams) ->
marker = @getMarker(marker.id)
marker = @getMarkerLayer(marker.layer.id).getMarker(marker.id)
decoration = new Decoration(marker, this, decorationParams)
@decorationsByMarkerId[marker.id] ?= []
@decorationsByMarkerId[marker.id].push(decoration)
@overlayDecorationsById[decoration.id] = decoration if decoration.isType('overlay')
@decorationsById[decoration.id] = decoration
@observeDecoratedLayer(marker.layer)
@scheduleUpdateDecorationsEvent()
@emitter.emit 'did-add-decoration', decoration
decoration
@@ -820,6 +821,7 @@ class DisplayBuffer extends Model
decoration = new LayerDecoration(markerLayer, this, decorationParams)
@layerDecorationsByMarkerLayerId[markerLayer.id] ?= []
@layerDecorationsByMarkerLayerId[markerLayer.id].push(decoration)
@observeDecoratedLayer(markerLayer)
@scheduleUpdateDecorationsEvent()
decoration
@@ -908,6 +910,16 @@ class DisplayBuffer extends Model
findMarkers: (params) ->
@defaultMarkerLayer.findMarkers(params)
addMarkerLayer: (options) ->
bufferLayer = @buffer.addMarkerLayer(options)
@getMarkerLayer(bufferLayer.id)
getMarkerLayer: (id) ->
if layer = @customMarkerLayersById[id]
layer
else if bufferLayer = @buffer.getMarkerLayer(id)
@customMarkerLayersById[id] = new TextEditorMarkerLayer(this, bufferLayer)
findFoldMarker: (attributes) ->
@findFoldMarkers(attributes)[0]
@@ -921,8 +933,8 @@ class DisplayBuffer extends Model
@foldMarkerAttributes
refreshMarkerScreenPositions: ->
for marker in @getMarkers()
marker.notifyObservers(textChanged: false)
@defaultMarkerLayer.refreshMarkerScreenPositions()
layer.refreshMarkerScreenPositions() for id, layer of @customMarkerLayersById
return
destroyed: ->
@@ -1090,6 +1102,7 @@ class DisplayBuffer extends Model
@emitter.emit 'did-remove-decoration', decoration
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
delete @overlayDecorationsById[decoration.id]
@unobserveDecoratedLayer(marker.layer)
@scheduleUpdateDecorationsEvent()
didDestroyLayerDecoration: (decoration) ->
@@ -1100,8 +1113,20 @@ class DisplayBuffer extends Model
if index > -1
decorations.splice(index, 1)
delete @layerDecorationsByMarkerLayerId[markerLayer.id] if decorations.length is 0
@unobserveDecoratedLayer(markerLayer)
@scheduleUpdateDecorationsEvent()
observeDecoratedLayer: (layer) ->
@decorationCountsByLayerId[layer.id] ?= 0
if ++@decorationCountsByLayerId[layer.id] is 1
@layerUpdateDisposablesByLayerId[layer.id] = layer.onDidUpdate(@scheduleUpdateDecorationsEvent.bind(this))
unobserveDecoratedLayer: (layer) ->
if --@decorationCountsByLayerId[layer.id] is 0
@layerUpdateDisposablesByLayerId[layer.id].dispose()
delete @decorationCountsByLayerId[layer.id]
delete @layerUpdateDisposablesByLayerId[layer.id]
checkScreenLinesInvariant: ->
return if @isSoftWrapped()
return if _.size(@foldsByMarkerId) > 0

View File

@@ -3,6 +3,7 @@ TextEditorMarker = require './text-editor-marker'
module.exports =
class TextEditorMarkerLayer
constructor: (@displayBuffer, @bufferMarkerLayer, @isDefaultLayer) ->
@id = @bufferMarkerLayer.id
@markersById = {}
getMarker: (id) ->
@@ -38,6 +39,11 @@ class TextEditorMarkerLayer
else
@bufferMarkerLayer.destroy()
refreshMarkerScreenPositions: ->
for marker in @getMarkers()
marker.notifyObservers(textChanged: false)
return
didDestroyMarker: (marker) ->
delete @markersById[marker.id]
@@ -78,3 +84,10 @@ class TextEditorMarkerLayer
bufferMarkerParams[key] = value
bufferMarkerParams
onDidCreateMarker: (callback) ->
@bufferMarkerLayer.onDidCreateMarker (bufferMarker) =>
callback(@getMarker(bufferMarker.id))
onDidUpdate: (callback) ->
@bufferMarkerLayer.onDidUpdate(callback)

View File

@@ -53,8 +53,8 @@ class TextEditorMarker
Section: Construction and Destruction
###
constructor: (@markerLayer, @bufferMarker) ->
{@displayBuffer} = @markerLayer
constructor: (@layer, @bufferMarker) ->
{@displayBuffer} = @layer
@emitter = new Emitter
@disposables = new CompositeDisposable
@id = @bufferMarker.id
@@ -82,7 +82,7 @@ class TextEditorMarker
#
# Returns a {TextEditorMarker}.
copy: (properties) ->
@markerLayer.getMarker(@bufferMarker.copy(properties).id)
@layer.getMarker(@bufferMarker.copy(properties).id)
###
Section: Event Subscription
@@ -170,7 +170,7 @@ class TextEditorMarker
@bufferMarker.setProperties(properties)
matchesProperties: (attributes) ->
attributes = @markerLayer.translateToBufferMarkerParams(attributes)
attributes = @layer.translateToBufferMarkerParams(attributes)
@bufferMarker.matchesParams(attributes)
###
@@ -334,7 +334,7 @@ class TextEditorMarker
"TextEditorMarker(id: #{@id}, bufferRange: #{@getBufferRange()})"
destroyed: ->
@markerLayer.didDestroyMarker(this)
@layer.didDestroyMarker(this)
@emitter.emit 'did-destroy'
@emitter.dispose()

View File

@@ -74,6 +74,7 @@ class TextEditor extends Model
throw error
state.displayBuffer = displayBuffer
state.selectionsMarkerLayer = displayBuffer.getMarkerLayer(state.selectionsMarkerLayerId)
state.config = atomEnvironment.config
state.notificationManager = atomEnvironment.notifications
state.packageManager = atomEnvironment.packages
@@ -90,9 +91,10 @@ class TextEditor extends Model
{
@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength,
softWrapped, @displayBuffer, buffer, suppressCursorCreation, @mini, @placeholderText,
lineNumberGutterVisible, largeFileMode, @config, @notificationManager, @packageManager,
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
@project, @assert, @applicationDelegate
} = params
throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
@@ -115,8 +117,9 @@ class TextEditor extends Model
@config, @assert, @grammarRegistry, @packageManager
})
@buffer = @displayBuffer.buffer
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true)
for marker in @findMarkers(@getSelectionMarkerAttributes())
for marker in @selectionsMarkerLayer.getMarkers()
marker.setProperties(preserveFolds: true)
@addSelection(marker)
@@ -146,6 +149,7 @@ class TextEditor extends Model
scrollRow: @getScrollRow()
scrollColumn: @getScrollColumn()
displayBuffer: @displayBuffer.serialize()
selectionsMarkerLayerId: @selectionsMarkerLayer.id
subscribeToBuffer: ->
@buffer.retain()
@@ -161,9 +165,9 @@ class TextEditor extends Model
@preserveCursorPositionOnBufferReload()
subscribeToDisplayBuffer: ->
@disposables.add @displayBuffer.onDidCreateMarker @handleMarkerCreated
@disposables.add @displayBuffer.onDidChangeGrammar => @handleGrammarChange()
@disposables.add @displayBuffer.onDidTokenize => @handleTokenization()
@disposables.add @selectionsMarkerLayer.onDidCreateMarker @addSelection.bind(this)
@disposables.add @displayBuffer.onDidChangeGrammar @handleGrammarChange.bind(this)
@disposables.add @displayBuffer.onDidTokenize @handleTokenization.bind(this)
@disposables.add @displayBuffer.onDidChange (e) =>
@mergeIntersectingSelections()
@emitter.emit 'did-change', e
@@ -480,14 +484,13 @@ class TextEditor extends Model
# Create an {TextEditor} with its initial state based on this object
copy: ->
displayBuffer = @displayBuffer.copy()
selectionsMarkerLayer = displayBuffer.getMarkerLayer(@buffer.getMarkerLayer(@selectionsMarkerLayer.id).copy().id)
softTabs = @getSoftTabs()
newEditor = new TextEditor({
@buffer, displayBuffer, @tabLength, softTabs, suppressCursorCreation: true,
@config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
@grammarRegistry, @project, @assert, @applicationDelegate
@buffer, displayBuffer, selectionsMarkerLayer, @tabLength, softTabs,
suppressCursorCreation: true, @config, @notificationManager, @packageManager,
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
})
for marker in @findMarkers(editorId: @id)
marker.copy(editorId: newEditor.id, preserveFolds: true)
newEditor
# Controls visibility based on the given {Boolean}.
@@ -1681,6 +1684,15 @@ class TextEditor extends Model
getMarkerCount: ->
@buffer.getMarkerCount()
destroyMarker: (id) ->
@getMarker(id)?.destroy()
addMarkerLayer: (options) ->
@displayBuffer.addMarkerLayer(options)
getMarkerLayer: (id) ->
@displayBuffer.getMarkerLayer(id)
###
Section: Cursors
###
@@ -1749,7 +1761,7 @@ class TextEditor extends Model
#
# Returns a {Cursor}.
addCursorAtBufferPosition: (bufferPosition, options) ->
@markBufferPosition(bufferPosition, @getSelectionMarkerAttributes())
@selectionsMarkerLayer.markBufferPosition(bufferPosition, @getSelectionMarkerAttributes())
@getLastSelection().cursor.autoscroll() unless options?.autoscroll is false
@getLastSelection().cursor
@@ -1759,7 +1771,7 @@ class TextEditor extends Model
#
# Returns a {Cursor}.
addCursorAtScreenPosition: (screenPosition, options) ->
@markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
@selectionsMarkerLayer.markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
@getLastSelection().cursor.autoscroll() unless options?.autoscroll is false
@getLastSelection().cursor
@@ -2037,7 +2049,7 @@ class TextEditor extends Model
#
# Returns the added {Selection}.
addSelectionForBufferRange: (bufferRange, options={}) ->
@markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
@selectionsMarkerLayer.markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
@getLastSelection().autoscroll() unless options.autoscroll is false
@getLastSelection()
@@ -2050,7 +2062,7 @@ class TextEditor extends Model
#
# Returns the added {Selection}.
addSelectionForScreenRange: (screenRange, options={}) ->
@markScreenRange(screenRange, _.defaults(@getSelectionMarkerAttributes(), options))
@selectionsMarkerLayer.markScreenRange(screenRange, _.defaults(@getSelectionMarkerAttributes(), options))
@getLastSelection().autoscroll() unless options.autoscroll is false
@getLastSelection()
@@ -3069,10 +3081,6 @@ class TextEditor extends Model
@subscribeToTabTypeConfig()
@emitter.emit 'did-change-grammar', @getGrammar()
handleMarkerCreated: (marker) =>
if marker.matchesProperties(@getSelectionMarkerAttributes())
@addSelection(marker)
###
Section: TextEditor Rendering
###
@@ -3109,7 +3117,7 @@ class TextEditor extends Model
@viewRegistry.getView(this).pixelPositionForScreenPosition(screenPosition)
getSelectionMarkerAttributes: ->
{type: 'selection', editorId: @id, invalidate: 'never', maintainHistory: true}
{type: 'selection', invalidate: 'never'}
getVerticalScrollMargin: -> @displayBuffer.getVerticalScrollMargin()
setVerticalScrollMargin: (verticalScrollMargin) -> @displayBuffer.setVerticalScrollMargin(verticalScrollMargin)