From 80eb31679ff9cd54bfeffb448ea60fcaf7c3d62c Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 2 Jul 2014 17:58:23 -0700 Subject: [PATCH] Add a Decoration object. Rework to use this object --- spec/display-buffer-spec.coffee | 8 ++++-- src/decoration.coffee | 32 +++++++++++++++++++++ src/display-buffer.coffee | 49 ++++++++------------------------- src/editor-component.coffee | 16 ++++++----- src/editor.coffee | 4 +-- src/gutter-component.coffee | 7 +++-- src/lines-component.coffee | 7 +++-- src/selection.coffee | 17 +----------- 8 files changed, 69 insertions(+), 71 deletions(-) create mode 100644 src/decoration.coffee diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 8c459b392..5b1883652 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -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 -> diff --git a/src/decoration.coffee b/src/decoration.coffee new file mode 100644 index 000000000..666faf6dc --- /dev/null +++ b/src/decoration.coffee @@ -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 diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 08a7c8056..48392d089 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -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 diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 88908bbb4..3b9b76bfc 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -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 diff --git a/src/editor.coffee b/src/editor.coffee index 7f988759e..2c1e5ec3c 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -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) -> diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index ba6c07988..cf2f5e486 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -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 diff --git a/src/lines-component.coffee b/src/lines-component.coffee index c8e1cdb42..7d54d3459 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -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 diff --git a/src/selection.coffee b/src/selection.coffee index bc2193ba4..c91a0c81d 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -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