Add a Decoration object. Rework to use this object

This commit is contained in:
Ben Ogle 2014-07-02 17:58:23 -07:00
parent 54039e9d3b
commit 80eb31679f
8 changed files with 69 additions and 71 deletions

View File

@ -1031,11 +1031,15 @@ describe "DisplayBuffer", ->
decoration = {type: 'gutter', class: 'one'}
marker = displayBuffer.markBufferRange([[2, 13], [3, 15]])
displayBuffer.addDecorationForMarker(marker, decoration)
expect(displayBuffer.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
decorationObject = displayBuffer.addDecorationForMarker(marker, decoration)
expect(decorationObject).toBeDefined()
expect(decorationObject.getParams()).toBe decoration
expect(displayBuffer.decorationForId(decoration.id)).toBe decorationObject
expect(displayBuffer.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decorationObject
displayBuffer.removeDecorationForMarker(marker, decoration)
expect(displayBuffer.decorationsForScreenRowRange(2, 3)[marker.id]).not.toBeDefined()
expect(displayBuffer.decorationForId(decoration.id)).not.toBeDefined()
describe "::setScrollTop", ->
beforeEach ->

32
src/decoration.coffee Normal file
View File

@ -0,0 +1,32 @@
_ = require 'underscore-plus'
{Subscriber, Emitter} = require 'emissary'
idCounter = 0
nextId = -> idCounter++
module.exports =
class Decoration
Emitter.includeInto(this)
Subscriber.includeInto(this)
@isType: (decorationParams, type) ->
if _.isArray(decorationParams.type)
type in decorationParams.type
else
type is decorationParams.type
constructor: (@marker, @params) ->
@id = nextId()
@params.id = @id
getParams: ->
@params
isType: (type) ->
Decoration.isType(@params, type)
matchesPattern: (decorationPattern) ->
return false unless decorationPattern?
for key, value of decorationPattern
return false if @params[key] != value
true

View File

@ -8,6 +8,7 @@ TokenizedBuffer = require './tokenized-buffer'
RowMap = require './row-map'
Fold = require './fold'
Token = require './token'
Decoration = require './decoration'
DisplayBufferMarker = require './display-buffer-marker'
class BufferToScreenConversionError extends Error
@ -45,6 +46,7 @@ class DisplayBuffer extends Model
@charWidthsByScope = {}
@markers = {}
@foldsByMarkerId = {}
@decorationsById = {}
@decorationsByMarkerId = {}
@decorationMarkerChangedSubscriptions = {}
@decorationMarkerDestroyedSubscriptions = {}
@ -752,31 +754,17 @@ class DisplayBuffer extends Model
rangeForAllLines: ->
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
decorationForId: (id) ->
@decorationsById[id]
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
decorationsByMarkerId = {}
for marker in @findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
if decorations = @decorationsByMarkerId[marker.id]
decorationsByMarkerId[marker.id] = decorations
decorationsByMarkerId
decorationMatchesType: (decoration, type) ->
if _.isArray(decoration.type)
type in decoration.type
else
type is decoration.type
decorationMatchesPattern: (decoration, decorationPattern) ->
return false unless decoration? and decorationPattern?
for key, value of decorationPattern
return false if decoration[key] != value
true
addDecorationForMarker: (marker, decoration) ->
unless marker?
console.warn 'A null marker cannot be decorated'
return
addDecorationForMarker: (marker, decorationParams) ->
marker = @getMarker(marker.id)
@decorationMarkerDestroyedSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', =>
@ -791,36 +779,21 @@ class DisplayBuffer extends Model
for decoration in decorations
@emit 'decoration-changed', marker, decoration, event
decoration = new Decoration(marker, decorationParams)
@decorationsByMarkerId[marker.id] ?= []
@decorationsByMarkerId[marker.id].push(decoration)
@decorationsById[decoration.id] = decoration
@emit 'decoration-added', marker, decoration
updateDecorationForMarker: (marker, decorationPattern, newDecoration) ->
unless marker?
console.warn 'A null marker cannot be decorated'
return
marker = @getMarker(marker.id)
return unless decorations = @decorationsByMarkerId[marker.id]
for decoration, i in decorations
if @decorationMatchesPattern(decoration, decorationPattern) and not _.isEqual(decoration, newDecoration)
decorations[i] = newDecoration
@emit 'decoration-updated', marker, decoration, newDecoration
return
decoration
removeDecorationForMarker: (marker, decorationPattern) ->
unless marker?
console.warn 'A decoration cannot be removed from a null marker'
return
return unless decorations = @decorationsByMarkerId[marker.id]
for i in [decorations.length - 1..0]
decoration = decorations[i]
if @decorationMatchesPattern(decoration, decorationPattern)
if decoration.matchesPattern(decorationPattern)
decorations.splice(i, 1)
delete @decorationsById[decoration.id]
@emit 'decoration-removed', marker, decoration
@removedAllMarkerDecorations(marker) if decorations.length is 0

View File

@ -280,21 +280,22 @@ EditorComponent = React.createClass
headScreenRow = null
if marker.isValid()
for decoration in decorations
if editor.decorationMatchesType(decoration, 'gutter') or editor.decorationMatchesType(decoration, 'line')
if decoration.isType('gutter') or decoration.isType(decoration, 'line')
decorationParams = decoration.getParams()
screenRange ?= marker.getScreenRange()
headScreenRow ?= marker.getHeadScreenPosition().row
startRow = screenRange.start.row
endRow = screenRange.end.row
endRow-- if not screenRange.isEmpty() and screenRange.end.column == 0
for screenRow in [startRow..endRow]
continue if decoration.onlyHead and screenRow isnt headScreenRow
continue if decorationParams.onlyHead and screenRow isnt headScreenRow
if screenRange.isEmpty()
continue if decoration.onlyNonEmpty
continue if decorationParams.onlyNonEmpty
else
continue if decoration.onlyEmpty
continue if decorationParams.onlyEmpty
decorationsByScreenRow[screenRow] ?= []
decorationsByScreenRow[screenRow].push decoration
decorationsByScreenRow[screenRow].push decorationParams
decorationsByScreenRow
@ -306,13 +307,14 @@ EditorComponent = React.createClass
screenRange = marker.getScreenRange()
if marker.isValid() and not screenRange.isEmpty()
for decoration in decorations
if editor.decorationMatchesType(decoration, 'highlight')
if decoration.isType('highlight')
decorationParams = decoration.getParams()
filteredDecorations[markerId] ?=
id: markerId
startPixelPosition: editor.pixelPositionForScreenPosition(screenRange.start)
endPixelPosition: editor.pixelPositionForScreenPosition(screenRange.end)
decorations: []
filteredDecorations[markerId].decorations.push decoration
filteredDecorations[markerId].decorations.push decorationParams
# At least in Chromium 31, removing the last highlight causes a rendering
# artifact where chunks of the lines disappear, so we always leave this

View File

@ -1147,8 +1147,8 @@ class Editor extends Model
removeDecorationForMarker: (marker, decorationPattern) ->
@displayBuffer.removeDecorationForMarker(marker, decorationPattern)
decorationMatchesType: (decoration, type) ->
@displayBuffer.decorationMatchesType(decoration, type)
decorationForId: (id) ->
@displayBuffer.decorationForId(id)
# Public: Get the {DisplayBufferMarker} for the given marker id.
getMarker: (id) ->

View File

@ -2,6 +2,7 @@ _ = require 'underscore-plus'
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
{isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
Decoration = require './decoration'
SubscriberMixin = require './subscriber-mixin'
WrapperDiv = document.createElement('div')
@ -154,7 +155,7 @@ GutterComponent = React.createClass
classes = ''
if lineDecorations? and decorations = lineDecorations[screenRow]
for decoration in decorations
if editor.decorationMatchesType(decoration, 'gutter')
if Decoration.isType(decoration, 'gutter')
classes += decoration.class + ' '
classes += "foldable " if bufferRow >= 0 and editor.isFoldableAtBufferRow(bufferRow)
@ -186,11 +187,11 @@ GutterComponent = React.createClass
if previousDecorations?
for decoration in previousDecorations
node.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'gutter') and not _.deepContains(decorations, decoration)
node.classList.remove(decoration.class) if Decoration.isType(decoration, 'gutter') and not _.deepContains(decorations, decoration)
if decorations?
for decoration in decorations
if editor.decorationMatchesType(decoration, 'gutter') and not _.deepContains(previousDecorations, decoration)
if Decoration.isType(decoration, 'gutter') and not _.deepContains(previousDecorations, decoration)
node.classList.add(decoration.class)
unless @screenRowsByLineNumberId[lineNumberId] is screenRow

View File

@ -4,6 +4,7 @@ React = require 'react-atom-fork'
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
{$$} = require 'space-pen'
Decoration = require './decoration'
HighlightsComponent = require './highlights-component'
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
@ -135,7 +136,7 @@ LinesComponent = React.createClass
classes = ''
if decorations = lineDecorations[screenRow]
for decoration in decorations
if editor.decorationMatchesType(decoration, 'line')
if Decoration.isType(decoration, 'line')
classes += decoration.class + ' '
classes += 'line'
@ -223,11 +224,11 @@ LinesComponent = React.createClass
if previousDecorations?
for decoration in previousDecorations
lineNode.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(decorations, decoration)
lineNode.classList.remove(decoration.class) if Decoration.isType(decoration, 'line') and not _.deepContains(decorations, decoration)
if decorations?
for decoration in decorations
if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(previousDecorations, decoration)
if Decoration.isType(decoration, 'line') and not _.deepContains(previousDecorations, decoration)
lineNode.classList.add(decoration.class)
lineNode.style.width = lineWidth + 'px' if updateWidth

View File

@ -15,7 +15,7 @@ class Selection extends Model
constructor: ({@cursor, @marker, @editor, id}) ->
@assignId(id)
@cursor.selection = this
@addDecoration(@marker.getAttributes())
@decoration = @editor.addDecorationForMarker(@marker, type: 'highlight', class: 'selection')
@marker.on 'changed', => @screenRangeChanged()
@marker.on 'destroyed', =>
@ -77,7 +77,6 @@ class Selection extends Model
@needsAutoscroll = options.autoscroll
options.reversed ?= @isReversed()
@editor.destroyFoldsIntersectingBufferRange(bufferRange) unless options.preserveFolds
@updateDecoration(options)
@modifySelection =>
@cursor.needsAutoscroll = false if @needsAutoscroll?
@marker.setBufferRange(bufferRange, options)
@ -642,20 +641,6 @@ class Selection extends Model
compare: (otherSelection) ->
@getBufferRange().compare(otherSelection.getBufferRange())
addDecoration: (options)->
@decoration = @getDecoration(options)
@editor.addDecorationForMarker(@marker, @decoration)
updateDecoration: (options) ->
newDecoration = @getDecoration(options)
@editor.updateDecorationForMarker(@marker, @decoration, newDecoration)
@decoration = newDecoration
getDecoration: ({flash}) ->
decoration = {type: 'highlight', class: 'selection'}
decoration.flash = {class: 'highlighted', duration: 300} if flash
decoration
screenRangeChanged: ->
screenRange = @getScreenRange()
@emit 'screen-range-changed', screenRange