diff --git a/src/decoration.coffee b/src/decoration.coffee new file mode 100644 index 000000000..af3204393 --- /dev/null +++ b/src/decoration.coffee @@ -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 diff --git a/src/decorations.coffee b/src/decorations.coffee new file mode 100644 index 000000000..59df5e6be --- /dev/null +++ b/src/decorations.coffee @@ -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 diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 9ad4b738d..7f7004adf 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -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) -> diff --git a/src/editor-component.coffee b/src/editor-component.coffee index fd663103b..d9e956b36 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -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 diff --git a/src/editor.coffee b/src/editor.coffee index ec162e222..2295cec96 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -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. diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index c668f0b49..60f4c7a1b 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -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