Decorations can now only be attached to markers.

The basics work. It will render them on the gutter.
This commit is contained in:
Ben Ogle 2014-06-12 17:23:03 -07:00
parent 120e2a3bdb
commit 2d4360dcf0
6 changed files with 102 additions and 125 deletions

16
src/decoration.coffee Normal file
View File

@ -0,0 +1,16 @@
_ = require 'underscore-plus'
module.exports =
class Decoration
constructor: (@marker, properties) ->
_.extend(this, properties)
getScreenRange: ->
@marker?.getScreenRange()
isValid: ->
@marker?.isValid()
isType: (decorationType) ->
return true unless @type
decorationType is @type

36
src/decorations.coffee Normal file
View File

@ -0,0 +1,36 @@
_ = require 'underscore-plus'
Decoration = require './decoration'
module.exports =
class Decorations
constructor: (@editor, @startScreenRow, @endScreenRow) ->
@decorationsCache = {}
@decorationsByMarkerId = @editor.decorationsForScreenRowRange(@startScreenRow, @endScreenRow)
@decorationsByScreenRow = @indexDecorationsByScreenRow(@decorationsByMarkerId)
decorationsByScreenRowForType: (decorationType) ->
unless @decorationsCache[decorationType]?
filteredDecorations = {}
for screenRow, decorations of @decorationsByScreenRow
for decoration in decorations
if decoration.isType(decorationType)
filteredDecorations[screenRow] ?= []
filteredDecorations[screenRow].push decoration
# if @editor.isFoldableAtScreenRow(screenRow)
# filteredDecorations[screenRow].push new Decoration(null, {class: 'foldable'})
@decorationsCache[decorationType] = filteredDecorations
@decorationsCache[decorationType]
indexDecorationsByScreenRow: (decorationsByMarkerId) ->
decorationsByScreenRow = {}
for id, decorations of decorationsByMarkerId
for decoration in decorations
continue unless decoration.isValid()
range = decoration.getScreenRange()
for screenRow in [range.start.row..range.end.row]
decorationsByScreenRow[screenRow] ?= []
decorationsByScreenRow[screenRow].push(decoration)
decorationsByScreenRow

View File

@ -9,6 +9,7 @@ RowMap = require './row-map'
Fold = require './fold'
Token = require './token'
DisplayBufferMarker = require './display-buffer-marker'
Decoration = require './decoration'
class BufferToScreenConversionError extends Error
constructor: (@message, @metadata) ->
@ -43,7 +44,7 @@ class DisplayBuffer extends Model
@charWidthsByScope = {}
@markers = {}
@foldsByMarkerId = {}
@decorations = {}
@decorationsByMarkerId = {}
@decorationMarkerSubscriptions = {}
@updateAllScreenLines()
@createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())
@ -718,51 +719,13 @@ class DisplayBuffer extends Model
rangeForAllLines: ->
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
decorationsForBufferRow: (bufferRow, decorationType) ->
decorations = @decorations[bufferRow] ? []
decorations = (dec for dec in decorations when not dec.type? or dec.type is decorationType) if decorationType?
decorations
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
decorationsByMarkerId = {}
decorationsForBufferRowRange: (startBufferRow, endBufferRow, decorationType) ->
decorations = {}
for bufferRow in [startBufferRow..endBufferRow]
decorations[bufferRow] = @decorationsForBufferRow(bufferRow, decorationType)
decorations
addDecorationToBufferRow: (bufferRow, decoration) ->
@decorations[bufferRow] ?= []
for current in @decorations[bufferRow]
return if _.isEqual(current, decoration)
@decorations[bufferRow].push(decoration)
@emit 'decoration-changed', {bufferRow, decoration, action: 'add'}
removeDecorationFromBufferRow: (bufferRow, decorationPattern) ->
return unless decorations = @decorations[bufferRow]
removed = []
i = decorations.length - 1
while i >= 0
if @decorationMatchesPattern(decorations[i], decorationPattern)
removed.push decorations[i]
decorations.splice(i, 1)
i--
delete @decorations[bufferRow] unless @decorations[bufferRow]?
for decoration in removed
@emit 'decoration-changed', {bufferRow, decoration, action: 'remove'}
removed
addDecorationToBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
for bufferRow in [startBufferRow..endBufferRow]
@addDecorationToBufferRow(bufferRow, decoration)
return
removeDecorationFromBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
for bufferRow in [startBufferRow..endBufferRow]
@removeDecorationFromBufferRow(bufferRow, decoration)
return
for marker in @findMarkers()
if decorations = @decorationsByMarkerId[marker.id]
decorationsByMarkerId[marker.id] = decorations
decorationsByMarkerId
decorationMatchesPattern: (decoration, decorationPattern) ->
return false unless decoration? and decorationPattern?
@ -770,44 +733,38 @@ class DisplayBuffer extends Model
return false if decoration[key] != value
true
addDecorationForMarker: (marker, decoration) ->
startRow = marker.getStartBufferPosition().row
endRow = marker.getEndBufferPosition().row
@addDecorationToBufferRowRange(startRow, endRow, decoration)
addDecorationForMarker: (marker, properties) ->
marker = @getMarker(marker.id)
@decorationMarkerSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', => @removeAllDecorationsForMarker(marker)
changedSubscription = @subscribe marker, 'changed', (e) =>
oldStartRow = e.oldHeadBufferPosition.row
oldEndRow = e.oldTailBufferPosition.row
newStartRow = e.newHeadBufferPosition.row
newEndRow = e.newTailBufferPosition.row
decoration = new Decoration(marker, properties)
# swap so head is always <= than tail
[oldEndRow, oldStartRow] = [oldStartRow, oldEndRow] if oldStartRow > oldEndRow
[newEndRow, newStartRow] = [newStartRow, newEndRow] if newStartRow > newEndRow
@removeDecorationFromBufferRowRange(oldStartRow, oldEndRow, decoration)
@addDecorationToBufferRowRange(newStartRow, newEndRow, decoration) if e.isValid
destroyedSubscription = @subscribe marker, 'destroyed', (e) =>
@removeDecorationForMarker(marker, decoration)
@decorationMarkerSubscriptions[marker.id] ?= []
@decorationMarkerSubscriptions[marker.id].push {decoration, changedSubscription, destroyedSubscription}
@decorationsByMarkerId[marker.id] ?= []
@decorationsByMarkerId[marker.id].push(decoration)
@emit 'decoration-added', marker, decoration
removeDecorationForMarker: (marker, decorationPattern) ->
return unless @decorationMarkerSubscriptions[marker.id]?
startRow = marker.getStartBufferPosition().row
endRow = marker.getEndBufferPosition().row
@removeDecorationFromBufferRowRange(startRow, endRow, decorationPattern)
decorations = @decorationsByMarkerId[marker.id]
for i in [decorations.length - 1..0]
decoration = decorations[i]
if @decorationMatchesPattern(decoration, decorationPattern)
decorations.splice(i, 1)
@emit 'decoration-removed', marker, decoration
for subscription in _.clone(@decorationMarkerSubscriptions[marker.id])
if @decorationMatchesPattern(subscription.decoration, decorationPattern)
subscription.changedSubscription.off()
subscription.destroyedSubscription.off()
@decorationMarkerSubscriptions[marker.id] = _.without(@decorationMarkerSubscriptions[marker.id], subscription)
@removedAllMarkerDecorations(marker) if decorations.length is 0
return
removeAllDecorationsForMarker: (marker) ->
decorations = @decorationsByMarkerId[marker.id].slice()
for decoration in decorations
@emit 'decoration-removed', marker, decoration
@removedAllMarkerDecorations(marker)
removedAllMarkerDecorations: (marker) ->
@decorationMarkerSubscriptions[marker.id].off()
delete @decorationsByMarkerId[marker.id]
delete @decorationMarkerSubscriptions[marker.id]
# Retrieves a {DisplayBufferMarker} based on its id.
#
@ -940,7 +897,6 @@ class DisplayBuffer extends Model
value = @bufferRangeForScreenRange(value)
bufferMarkerParams[key] = value
console.log bufferMarkerParams
bufferMarkerParams
findFoldMarker: (attributes) ->
@ -1074,7 +1030,7 @@ class DisplayBuffer extends Model
@emit 'marker-created', @getMarker(marker.id)
createFoldForMarker: (marker) ->
@addDecorationForMarker(@getMarker(marker.id), type: 'gutter', class: 'folded')
@addDecorationForMarker(marker, type: 'gutter', class: 'folded')
new Fold(this, marker)
foldForMarker: (marker) ->

View File

@ -4,6 +4,7 @@ React = require 'react-atom-fork'
scrollbarStyle = require 'scrollbar-style'
{Range, Point} = require 'text-buffer'
Decorations = require './decorations'
GutterComponent = require './gutter-component'
InputComponent = require './input-component'
CursorsComponent = require './cursors-component'
@ -50,7 +51,7 @@ EditorComponent = React.createClass
[renderedStartRow, renderedEndRow] = renderedRowRange
cursorScreenRanges = @getCursorScreenRanges(renderedRowRange)
selectionScreenRanges = @getSelectionScreenRanges(renderedRowRange)
decorations = @getGutterDecorations(renderedRowRange)
decorations = new Decorations(editor, renderedStartRow, renderedEndRow)
scrollHeight = editor.getScrollHeight()
scrollWidth = editor.getScrollWidth()
scrollTop = editor.getScrollTop()
@ -73,9 +74,9 @@ EditorComponent = React.createClass
div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1,
GutterComponent {
ref: 'gutter', editor, renderedRowRange, maxLineNumberDigits, scrollTop,
scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow,
decorations
ref: 'gutter',
decorations, editor, renderedRowRange, maxLineNumberDigits, scrollTop,
scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow
}
div ref: 'scrollView', className: 'scroll-view', onMouseDown: @onMouseDown,
@ -233,7 +234,6 @@ EditorComponent = React.createClass
selectionScreenRanges
getGutterDecorations: (renderedRowRange) ->
{editor} = @props
[renderedStartRow, renderedEndRow] = renderedRowRange
bufferRows = editor.bufferRowsForScreenRows(renderedStartRow, renderedEndRow - 1)
@ -252,7 +252,8 @@ EditorComponent = React.createClass
@subscribe editor, 'cursors-moved', @onCursorsMoved
@subscribe editor, 'selection-removed selection-screen-range-changed', @onSelectionChanged
@subscribe editor, 'selection-added', @onSelectionAdded
@subscribe editor, 'decoration-changed', @onDecorationChanged
@subscribe editor, 'decoration-added', @onDecorationChanged
@subscribe editor, 'decoration-removed', @onDecorationChanged
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
@subscribe editor.$scrollLeft.changes, @requestUpdate
@subscribe editor.$height.changes, @requestUpdate

View File

@ -214,7 +214,8 @@ class Editor extends Model
@subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
@subscribe @displayBuffer, 'tokenized', => @handleTokenization()
@subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...
@subscribe @displayBuffer, "decoration-changed", (e) => @emit 'decoration-changed', e
@subscribe @displayBuffer, "decoration-added", (args...) => @emit 'decoration-added', args...
@subscribe @displayBuffer, "decoration-removed", (args...) => @emit 'decoration-removed', args...
getViewClass: ->
if atom.config.get('core.useReactEditor')
@ -1065,29 +1066,8 @@ class Editor extends Model
#
# Returns an {Array} of decorations in the form `[{type: 'gutter', class: 'someclass'}, ...]`
# Returns an empty array when no decorations are found
decorationsForBufferRow: (bufferRow, decorationType) ->
@displayBuffer.decorationsForBufferRow(bufferRow, decorationType)
# Public: Get all the decorations for a range of buffer rows (inclusive)
#
# startBufferRow - the {int} start of the buffer row range
# endBufferRow - the {int} end of the buffer row range (inclusive)
# decorationType - the {String} decoration type to filter by eg. 'gutter'
#
# Returns an {Object} of decorations in the form `{23: [{type: 'gutter', class: 'someclass'}, ...], 24: [...]}`
# Returns an {Object} with keyed with all buffer rows in the range containing empty {Array}s when no decorations are found
decorationsForBufferRowRange: (startBufferRow, endBufferRow, decorationType) ->
@displayBuffer.decorationsForBufferRowRange(startBufferRow, endBufferRow, decorationType)
# Public: Adds a decoration to a buffer row. For example, use to mark a gutter
# line number with a class by using the form `{type: 'gutter', class: 'linter-error'}`
#
# bufferRow - the {int} buffer row
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
#
# Returns nothing
addDecorationToBufferRow: (bufferRow, decoration) ->
@displayBuffer.addDecorationToBufferRow(bufferRow, decoration)
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
@displayBuffer.decorationsForScreenRowRange(startScreenRow, endScreenRow)
# Public: Removes a decoration from a buffer row.
#
@ -1115,8 +1095,6 @@ class Editor extends Model
# decorationPattern - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
#
# Returns an {Array} of the removed decorations
removeDecorationFromBufferRow: (bufferRow, decorationPattern) ->
@displayBuffer.removeDecorationFromBufferRow(bufferRow, decorationPattern)
# Public: Adds a decoration to line numbers in a buffer row range
#
@ -1125,18 +1103,6 @@ class Editor extends Model
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
#
# Returns nothing
addDecorationToBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
@displayBuffer.addDecorationToBufferRowRange(startBufferRow, endBufferRow, decoration)
# Public: Removes a decoration from line numbers in a buffer row range
#
# startBufferRow - the {int} start of the buffer row range
# endBufferRow - the {int} end of the buffer row range (inclusive)
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
#
# Returns nothing
removeDecorationFromBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
@displayBuffer.removeDecorationFromBufferRowRange(startBufferRow, endBufferRow, decoration)
# Public: Adds a decoration that tracks a {Marker}. When the marker moves,
# is invalidated, or is destroyed, the decoration will be updated to reflect the marker's state.

View File

@ -81,6 +81,8 @@ GutterComponent = React.createClass
newLineNumbersHTML = null
visibleLineNumberIds = new Set
decorationsByScreenRow = decorations.decorationsByScreenRowForType('gutter')
wrapCount = 0
for bufferRow, index in editor.bufferRowsForScreenRows(startRow, endRow - 1)
screenRow = startRow + index
@ -95,12 +97,12 @@ GutterComponent = React.createClass
visibleLineNumberIds.add(id)
if @hasLineNumberNode(id)
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0, decorations[bufferRow])
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0, decorationsByScreenRow[screenRow])
else
newLineNumberIds ?= []
newLineNumbersHTML ?= ""
newLineNumberIds.push(id)
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow, decorations[bufferRow])
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow, decorationsByScreenRow[screenRow])
@screenRowsByLineNumberId[id] = screenRow
@lineNumberIdsByScreenRow[screenRow] = id
@ -114,7 +116,7 @@ GutterComponent = React.createClass
@lineNumberNodesById[lineNumberId] = lineNumberNode
node.appendChild(lineNumberNode)
@previousDecorations = decorations
@previousDecorations = decorationsByScreenRow
visibleLineNumberIds
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
@ -156,7 +158,7 @@ GutterComponent = React.createClass
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped, decorations) ->
node = @lineNumberNodesById[lineNumberId]
previousDecorations = @previousDecorations[bufferRow]
previousDecorations = @previousDecorations[screenRow]
if previousDecorations?
for decoration in previousDecorations