mirror of
https://github.com/atom/atom.git
synced 2026-02-16 09:35:54 -05:00
WIP: Base folds on markers.
Still a couple of specs failing on this for changes that straddle the start / end of a fold. We need a new marker invalidation strategy for these cases.
This commit is contained in:
@@ -55,6 +55,10 @@ class BufferMarker
|
||||
return false unless @getRange().start.row == value
|
||||
when 'endRow'
|
||||
return false unless @getRange().end.row == value
|
||||
when 'containsRange'
|
||||
return false unless @getRange().containsRange(value, exclusive: true)
|
||||
when 'containsRow'
|
||||
return false unless @getRange().containsRow(value)
|
||||
else
|
||||
return false unless _.isEqual(@attributes[key], value)
|
||||
true
|
||||
|
||||
@@ -129,6 +129,9 @@ class DisplayBufferMarker
|
||||
isDestroyed: ->
|
||||
@bufferMarker.isDestroyed()
|
||||
|
||||
matchesAttributes: (attributes) ->
|
||||
@bufferMarker.matchesAttributes(attributes)
|
||||
|
||||
# Destroys the marker
|
||||
destroy: ->
|
||||
@bufferMarker.destroy()
|
||||
|
||||
@@ -8,15 +8,15 @@ Fold = require 'fold'
|
||||
ScreenLine = require 'screen-line'
|
||||
Token = require 'token'
|
||||
DisplayBufferMarker = require 'display-buffer-marker'
|
||||
Subscriber = require 'subscriber'
|
||||
|
||||
module.exports =
|
||||
class DisplayBuffer
|
||||
@idCounter: 1
|
||||
lineMap: null
|
||||
tokenizedBuffer: null
|
||||
activeFolds: null
|
||||
foldsById: null
|
||||
markers: null
|
||||
foldsByMarkerId: null
|
||||
|
||||
###
|
||||
# Internal #
|
||||
@@ -26,14 +26,13 @@ class DisplayBuffer
|
||||
@id = @constructor.idCounter++
|
||||
@tokenizedBuffer = new TokenizedBuffer(@buffer, options)
|
||||
@softWrapColumn = options.softWrapColumn ? Infinity
|
||||
@activeFolds = {}
|
||||
@foldsById = {}
|
||||
@markers = {}
|
||||
@foldsByMarkerId = {}
|
||||
@buildLineMap()
|
||||
@tokenizedBuffer.on 'grammar-changed', (grammar) => @trigger 'grammar-changed', grammar
|
||||
@tokenizedBuffer.on 'changed', @handleTokenizedBufferChange
|
||||
@buffer.on 'markers-updated', @handleMarkersUpdated
|
||||
@buffer.on 'marker-created', (marker) => @trigger 'marker-created', @getMarker(marker.id)
|
||||
@buffer.on 'marker-created', @handleMarkerCreated
|
||||
|
||||
buildLineMap: ->
|
||||
@lineMap = new LineMap
|
||||
@@ -104,43 +103,17 @@ class DisplayBuffer
|
||||
#
|
||||
# Returns the new {Fold}.
|
||||
createFold: (startRow, endRow) ->
|
||||
return fold if fold = @foldFor(startRow, endRow, 0, refreshMarkers: true)
|
||||
fold = new Fold(this, startRow, endRow)
|
||||
@registerFold(fold)
|
||||
unless @isFoldContainedByActiveFold(fold)
|
||||
@updateScreenLines(startRow, endRow, 0, refreshMarkers: true)
|
||||
fold
|
||||
|
||||
# Public: Given a {Fold}, determines if it is contained within another fold.
|
||||
#
|
||||
# fold - The {Fold} to check
|
||||
#
|
||||
# Returns the contaiing {Fold} (if it exists), `null` otherwise.
|
||||
isFoldContainedByActiveFold: (fold) ->
|
||||
for row, folds of @activeFolds
|
||||
for otherFold in folds
|
||||
return otherFold if fold != otherFold and fold.isContainedByFold(otherFold)
|
||||
|
||||
# Public: Given a starting and ending row, tries to find an existing fold.
|
||||
#
|
||||
# startRow - A {Number} representing a fold's starting row
|
||||
# endRow - A {Number} representing a fold's ending row
|
||||
#
|
||||
# Returns a {Fold} (if it exists).
|
||||
foldFor: (startRow, endRow) ->
|
||||
_.find @activeFolds[startRow] ? [], (fold) ->
|
||||
fold.startRow == startRow and fold.endRow == endRow
|
||||
foldMarker =
|
||||
@findMarker({class: 'fold', startRow, endRow}) ?
|
||||
@markBufferRange([[startRow, 0], [endRow, Infinity]], class: 'fold')
|
||||
@foldForMarker(foldMarker)
|
||||
|
||||
# Public: Removes any folds found that contain the given buffer row.
|
||||
#
|
||||
# bufferRow - The buffer row {Number} to check against
|
||||
destroyFoldsContainingBufferRow: (bufferRow) ->
|
||||
for row, folds of @activeFolds
|
||||
for fold in new Array(folds...)
|
||||
fold.destroy() if fold.getBufferRange().containsRow(bufferRow)
|
||||
|
||||
foldsStartingAtBufferRow: (bufferRow) ->
|
||||
new Array((@activeFolds[bufferRow] ? [])...)
|
||||
for marker in @findMarkers(class: 'fold', containsBufferRow: bufferRow)
|
||||
marker.destroy()
|
||||
|
||||
# Public: Given a buffer row, this returns the largest fold that starts there.
|
||||
#
|
||||
@@ -151,8 +124,8 @@ class DisplayBuffer
|
||||
#
|
||||
# Returns a {Fold}.
|
||||
largestFoldStartingAtBufferRow: (bufferRow) ->
|
||||
return unless folds = @activeFolds[bufferRow]
|
||||
(folds.sort (a, b) -> b.endRow - a.endRow)[0]
|
||||
if marker = @findMarker(class: 'fold', startBufferRow: bufferRow)
|
||||
@foldForMarker(marker)
|
||||
|
||||
# Public: Given a screen row, this returns the largest fold that starts there.
|
||||
#
|
||||
@@ -177,9 +150,13 @@ class DisplayBuffer
|
||||
largestFold = null
|
||||
for currentBufferRow in [bufferRow..0]
|
||||
if fold = @largestFoldStartingAtBufferRow(currentBufferRow)
|
||||
largestFold = fold if fold.endRow >= bufferRow
|
||||
largestFold = fold if fold.getEndRow() >= bufferRow
|
||||
largestFold
|
||||
|
||||
largestFoldStartingAtBufferRange: (bufferRange) ->
|
||||
if marker = @findMarker(class: 'fold', containingBufferRange: bufferRange)
|
||||
@foldForMarker(marker)
|
||||
|
||||
# Public: Given a buffer range, this converts it into a screen range.
|
||||
#
|
||||
# bufferRange - A {Range} consisting of buffer positions
|
||||
@@ -324,30 +301,8 @@ class DisplayBuffer
|
||||
# Internal #
|
||||
###
|
||||
|
||||
registerFold: (fold) ->
|
||||
@activeFolds[fold.startRow] ?= []
|
||||
@activeFolds[fold.startRow].push(fold)
|
||||
@foldsById[fold.id] = fold
|
||||
|
||||
unregisterFold: (bufferRow, fold) ->
|
||||
folds = @activeFolds[bufferRow]
|
||||
_.remove(folds, fold)
|
||||
delete @foldsById[fold.id]
|
||||
delete @activeFolds[bufferRow] if folds.length == 0
|
||||
|
||||
destroyFold: (fold) ->
|
||||
@unregisterFold(fold.startRow, fold)
|
||||
unless @isFoldContainedByActiveFold(fold)
|
||||
@updateScreenLines(fold.startRow, fold.endRow, 0, refreshMarkers: true)
|
||||
|
||||
handleBufferChange: (e) ->
|
||||
allFolds = [] # Folds can modify @activeFolds, so first make sure we have a stable array of folds
|
||||
allFolds.push(folds...) for row, folds of @activeFolds
|
||||
fold.handleBufferChange(e) for fold in allFolds
|
||||
|
||||
handleTokenizedBufferChange: (tokenizedBufferChange) =>
|
||||
{start, end, delta, bufferChange} = tokenizedBufferChange
|
||||
@handleBufferChange(bufferChange) if bufferChange
|
||||
@updateScreenLines(start, end, delta, delayChangeEvent: bufferChange?)
|
||||
|
||||
updateScreenLines: (startBufferRow, endBufferRow, bufferDelta, options={}) ->
|
||||
@@ -371,11 +326,6 @@ class DisplayBuffer
|
||||
else
|
||||
@triggerChanged(changeEvent, options.refreshMarkers)
|
||||
|
||||
handleMarkersUpdated: =>
|
||||
event = @pendingChangeEvent
|
||||
@pendingChangeEvent = null
|
||||
@triggerChanged(event, false)
|
||||
|
||||
buildLineForBufferRow: (bufferRow) ->
|
||||
@buildLinesForBufferRows(bufferRow, bufferRow)
|
||||
|
||||
@@ -394,7 +344,7 @@ class DisplayBuffer
|
||||
screenLine.fold = fold
|
||||
screenLine.bufferRows = fold.getBufferRowCount()
|
||||
lineFragments.push(screenLine)
|
||||
currentBufferRow = fold.endRow + 1
|
||||
currentBufferRow = fold.getEndRow() + 1
|
||||
continue
|
||||
|
||||
startBufferColumn ?= 0
|
||||
@@ -412,6 +362,22 @@ class DisplayBuffer
|
||||
|
||||
lineFragments
|
||||
|
||||
handleMarkersUpdated: =>
|
||||
event = @pendingChangeEvent
|
||||
@pendingChangeEvent = null
|
||||
@triggerChanged(event, false)
|
||||
|
||||
handleMarkerCreated: (marker) =>
|
||||
marker = @getMarker(marker.id)
|
||||
new Fold(this, marker) if marker.matchesAttributes(class: 'fold')
|
||||
@trigger 'marker-created', marker
|
||||
|
||||
buildFoldForMarker: (marker) ->
|
||||
|
||||
|
||||
foldForMarker: (marker) ->
|
||||
@foldsByMarkerId[marker.id]
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
@@ -540,15 +506,18 @@ class DisplayBuffer
|
||||
#
|
||||
# Returns an {Array} of {DisplayBufferMarker}s
|
||||
findMarkers: (attributes) ->
|
||||
{ startBufferRow, endBufferRow } = attributes
|
||||
{ startBufferRow, endBufferRow, containsBufferRange, containsBufferRow } = attributes
|
||||
attributes.startRow = startBufferRow if startBufferRow?
|
||||
attributes.endRow = endBufferRow if endBufferRow?
|
||||
attributes = _.omit(attributes, ['startBufferRow', 'endBufferRow'])
|
||||
attributes.containsRange = containsBufferRange if containsBufferRange?
|
||||
attributes.containsRow = containsBufferRow if containsBufferRow?
|
||||
attributes = _.omit(attributes, ['startBufferRow', 'endBufferRow', 'containsBufferRange', 'containsBufferRow'])
|
||||
@buffer.findMarkers(attributes).map ({id}) => @getMarker(id)
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
|
||||
pauseMarkerObservers: ->
|
||||
marker.pauseEvents() for marker in @getMarkers()
|
||||
|
||||
@@ -573,3 +542,4 @@ class DisplayBuffer
|
||||
lines.join('\n')
|
||||
|
||||
_.extend DisplayBuffer.prototype, EventEmitter
|
||||
_.extend DisplayBuffer.prototype, Subscriber
|
||||
|
||||
@@ -1,86 +1,54 @@
|
||||
Range = require 'range'
|
||||
Point = require 'point'
|
||||
|
||||
# Public: Represents a fold that's hiding text from the screen.
|
||||
# Public: Represents a fold that's hiding text from the screen.
|
||||
#
|
||||
# Folds are the primary reason that screen ranges and buffer ranges vary. Their
|
||||
# creation is managed by the {DisplayBuffer}.
|
||||
module.exports =
|
||||
class Fold
|
||||
@idCounter: 1
|
||||
|
||||
displayBuffer: null
|
||||
startRow: null
|
||||
endRow: null
|
||||
marker: null
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
constructor: (@displayBuffer, @marker) ->
|
||||
@displayBuffer.foldsByMarkerId[@marker.id] = this
|
||||
@updateDisplayBuffer()
|
||||
@marker.on 'changed', (e) =>
|
||||
oldRange = new Range(e.oldHeadBufferPosition, e.oldTailBufferPosition)
|
||||
newRange = new Range(e.newHeadBufferPosition, e.newTailBufferPosition)
|
||||
@updateDisplayBuffer() unless newRange.isEqual(oldRange)
|
||||
@marker.on 'destroyed', => @destroyed()
|
||||
|
||||
constructor: (@displayBuffer, @startRow, @endRow) ->
|
||||
@id = @constructor.idCounter++
|
||||
updateDisplayBuffer: ->
|
||||
unless @isInsideLargerFold()
|
||||
@displayBuffer.updateScreenLines(@getStartRow(), @getEndRow(), 0, updateMarkers: true)
|
||||
|
||||
isInsideLargerFold: ->
|
||||
@displayBuffer.findMarker(class: 'fold', containsBufferRange: @getBufferRange())?
|
||||
|
||||
destroy: ->
|
||||
@displayBuffer.destroyFold(this)
|
||||
@marker.destroy()
|
||||
|
||||
getBufferRange: ->
|
||||
@marker.getBufferRange()
|
||||
|
||||
getStartRow: ->
|
||||
@getBufferRange().start.row
|
||||
|
||||
getEndRow: ->
|
||||
@getBufferRange().end.row
|
||||
|
||||
inspect: ->
|
||||
"Fold(#{@startRow}, #{@endRow})"
|
||||
"Fold(#{@getStartRow()}, #{@getEndRow()})"
|
||||
|
||||
# Public: Retrieves the buffer row range that a fold occupies.
|
||||
#
|
||||
# includeNewline - A {Boolean} which, if `true`, includes the trailing newline
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getBufferRange: ({includeNewline}={}) ->
|
||||
if includeNewline
|
||||
end = [@endRow + 1, 0]
|
||||
else
|
||||
end = [@endRow, Infinity]
|
||||
|
||||
new Range([@startRow, 0], end)
|
||||
|
||||
# Public: Retrieves the number of buffer rows a fold occupies.
|
||||
# Retrieves the number of buffer rows spanned by the fold.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getBufferRowCount: ->
|
||||
@endRow - @startRow + 1
|
||||
@getEndRow() - @getStartRow() + 1
|
||||
|
||||
handleBufferChange: (event) ->
|
||||
oldStartRow = @startRow
|
||||
## Internal ##
|
||||
|
||||
if @isContainedByRange(event.oldRange)
|
||||
@displayBuffer.unregisterFold(@startRow, this)
|
||||
return
|
||||
|
||||
@startRow += @getRowDelta(event, @startRow)
|
||||
@endRow += @getRowDelta(event, @endRow)
|
||||
|
||||
if @startRow != oldStartRow
|
||||
@displayBuffer.unregisterFold(oldStartRow, this)
|
||||
@displayBuffer.registerFold(this)
|
||||
|
||||
# Public: Identifies if a {Range} occurs within a fold.
|
||||
#
|
||||
# range - A {Range} to check
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isContainedByRange: (range) ->
|
||||
range.start.row <= @startRow and @endRow <= range.end.row
|
||||
|
||||
# Public: Identifies if a fold is nested within a fold.
|
||||
#
|
||||
# fold - A {Fold} to check
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isContainedByFold: (fold) ->
|
||||
@isContainedByRange(fold.getBufferRange())
|
||||
|
||||
getRowDelta: (event, row) ->
|
||||
{ newRange, oldRange } = event
|
||||
|
||||
if oldRange.end.row <= row
|
||||
newRange.end.row - oldRange.end.row
|
||||
else if newRange.end.row < row
|
||||
newRange.end.row - row
|
||||
else
|
||||
0
|
||||
destroyed: ->
|
||||
delete @displayBuffer.foldsByMarkerId[@marker.id]
|
||||
@updateDisplayBuffer()
|
||||
|
||||
Reference in New Issue
Block a user