mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-21 16:08:24 +03:00
Decorations can now only be attached to markers.
The basics work. It will render them on the gutter.
This commit is contained in:
parent
120e2a3bdb
commit
2d4360dcf0
16
src/decoration.coffee
Normal file
16
src/decoration.coffee
Normal 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
36
src/decorations.coffee
Normal 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
|
@ -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) ->
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user