mirror of
https://github.com/atom/atom.git
synced 2026-01-22 21:38:10 -05:00
We've gotten one rogue error but I have no idea how to reproduce it. This will tell us if soft wrap is enabled and if any folds are present so hopefully we can start narrowing these down.
693 lines
23 KiB
CoffeeScript
693 lines
23 KiB
CoffeeScript
_ = require 'underscore-plus'
|
|
{Emitter, Subscriber} = require 'emissary'
|
|
guid = require 'guid'
|
|
Serializable = require 'serializable'
|
|
{Model} = require 'theorist'
|
|
{Point, Range} = require 'text-buffer'
|
|
TokenizedBuffer = require './tokenized-buffer'
|
|
RowMap = require './row-map'
|
|
Fold = require './fold'
|
|
Token = require './token'
|
|
DisplayBufferMarker = require './display-buffer-marker'
|
|
ConfigObserver = require './config-observer'
|
|
|
|
# Private:
|
|
module.exports =
|
|
class DisplayBuffer extends Model
|
|
Serializable.includeInto(this)
|
|
ConfigObserver.includeInto(this)
|
|
|
|
@properties
|
|
softWrap: null
|
|
editorWidthInChars: null
|
|
|
|
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer}={}) ->
|
|
super
|
|
@softWrap ?= atom.config.get('editor.softWrap') ? false
|
|
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer})
|
|
@buffer = @tokenizedBuffer.buffer
|
|
@markers = {}
|
|
@foldsByMarkerId = {}
|
|
@updateAllScreenLines()
|
|
@createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())
|
|
@subscribe @tokenizedBuffer, 'grammar-changed', (grammar) => @emit 'grammar-changed', grammar
|
|
@subscribe @tokenizedBuffer, 'changed', @handleTokenizedBufferChange
|
|
@subscribe @buffer, 'markers-updated', @handleBufferMarkersUpdated
|
|
@subscribe @buffer, 'marker-created', @handleBufferMarkerCreated
|
|
|
|
@subscribe @$softWrap, (softWrap) =>
|
|
@emit 'soft-wrap-changed', softWrap
|
|
@updateWrappedScreenLines()
|
|
|
|
@observeConfig 'editor.preferredLineLength', callNow: false, =>
|
|
@updateWrappedScreenLines() if @softWrap and atom.config.get('editor.softWrapAtPreferredLineLength')
|
|
|
|
@observeConfig 'editor.softWrapAtPreferredLineLength', callNow: false, =>
|
|
@updateWrappedScreenLines() if @softWrap
|
|
|
|
serializeParams: ->
|
|
id: @id
|
|
softWrap: @softWrap
|
|
editorWidthInChars: @editorWidthInChars
|
|
tokenizedBuffer: @tokenizedBuffer.serialize()
|
|
|
|
deserializeParams: (params) ->
|
|
params.tokenizedBuffer = TokenizedBuffer.deserialize(params.tokenizedBuffer)
|
|
params
|
|
|
|
copy: ->
|
|
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength()})
|
|
for marker in @findMarkers(displayBufferId: @id)
|
|
marker.copy(displayBufferId: newDisplayBuffer.id)
|
|
newDisplayBuffer
|
|
|
|
updateAllScreenLines: ->
|
|
@maxLineLength = 0
|
|
@screenLines = []
|
|
@rowMap = new RowMap
|
|
@updateScreenLines(0, @buffer.getLineCount(), null, suppressChangeEvent: true)
|
|
|
|
emitChanged: (eventProperties, refreshMarkers=true) ->
|
|
if refreshMarkers
|
|
@pauseMarkerObservers()
|
|
@refreshMarkerScreenPositions()
|
|
@emit 'changed', eventProperties
|
|
@resumeMarkerObservers()
|
|
|
|
updateWrappedScreenLines: ->
|
|
start = 0
|
|
end = @getLastRow()
|
|
@updateAllScreenLines()
|
|
screenDelta = @getLastRow() - end
|
|
bufferDelta = 0
|
|
@emitChanged({ start, end, screenDelta, bufferDelta })
|
|
|
|
### Public ###
|
|
|
|
# Sets the visibility of the tokenized buffer.
|
|
#
|
|
# visible - A {Boolean} indicating of the tokenized buffer is shown
|
|
setVisible: (visible) -> @tokenizedBuffer.setVisible(visible)
|
|
|
|
# Deprecated: Use the softWrap property directly
|
|
setSoftWrap: (@softWrap) -> @softWrap
|
|
|
|
# Deprecated: Use the softWrap property directly
|
|
getSoftWrap: -> @softWrap
|
|
|
|
# Set the number of characters that fit horizontally in the editor.
|
|
#
|
|
# editorWidthInChars - A {Number} of characters.
|
|
setEditorWidthInChars: (editorWidthInChars) ->
|
|
previousWidthInChars = @editorWidthInChars
|
|
@editorWidthInChars = editorWidthInChars
|
|
if editorWidthInChars isnt previousWidthInChars and @softWrap
|
|
@updateWrappedScreenLines()
|
|
|
|
getSoftWrapColumn: ->
|
|
if atom.config.get('editor.softWrapAtPreferredLineLength')
|
|
Math.min(@editorWidthInChars, atom.config.getPositiveInt('editor.preferredLineLength', @editorWidthInChars))
|
|
else
|
|
@editorWidthInChars
|
|
|
|
# Gets the screen line for the given screen row.
|
|
#
|
|
# screenRow - A {Number} indicating the screen row.
|
|
#
|
|
# Returns a {ScreenLine}.
|
|
lineForRow: (row) ->
|
|
@screenLines[row]
|
|
|
|
# Gets the screen lines for the given screen row range.
|
|
#
|
|
# startRow - A {Number} indicating the beginning screen row.
|
|
# endRow - A {Number} indicating the ending screen row.
|
|
#
|
|
# Returns an {Array} of {ScreenLine}s.
|
|
linesForRows: (startRow, endRow) ->
|
|
@screenLines[startRow..endRow]
|
|
|
|
# Gets all the screen lines.
|
|
#
|
|
# Returns an {Array} of {ScreenLines}s.
|
|
getLines: ->
|
|
new Array(@screenLines...)
|
|
|
|
# Given starting and ending screen rows, this returns an array of the
|
|
# buffer rows corresponding to every screen row in the range
|
|
#
|
|
# startScreenRow - The screen row {Number} to start at
|
|
# endScreenRow - The screen row {Number} to end at (default: the last screen row)
|
|
#
|
|
# Returns an {Array} of buffer rows as {Numbers}s.
|
|
bufferRowsForScreenRows: (startScreenRow, endScreenRow) ->
|
|
for screenRow in [startScreenRow..endScreenRow]
|
|
@rowMap.bufferRowRangeForScreenRow(screenRow)[0]
|
|
|
|
# Creates a new fold between two row numbers.
|
|
#
|
|
# startRow - The row {Number} to start folding at
|
|
# endRow - The row {Number} to end the fold
|
|
#
|
|
# Returns the new {Fold}.
|
|
createFold: (startRow, endRow) ->
|
|
foldMarker =
|
|
@findFoldMarker({startRow, endRow}) ?
|
|
@buffer.markRange([[startRow, 0], [endRow, Infinity]], @getFoldMarkerAttributes())
|
|
@foldForMarker(foldMarker)
|
|
|
|
isFoldedAtBufferRow: (bufferRow) ->
|
|
@largestFoldContainingBufferRow(bufferRow)?
|
|
|
|
isFoldedAtScreenRow: (screenRow) ->
|
|
@largestFoldContainingBufferRow(@bufferRowForScreenRow(screenRow))?
|
|
|
|
# Destroys the fold with the given id
|
|
destroyFoldWithId: (id) ->
|
|
@foldsByMarkerId[id]?.destroy()
|
|
|
|
# Removes any folds found that contain the given buffer row.
|
|
#
|
|
# bufferRow - The buffer row {Number} to check against
|
|
destroyFoldsContainingBufferRow: (bufferRow) ->
|
|
fold.destroy() for fold in @foldsContainingBufferRow(bufferRow)
|
|
|
|
# Given a buffer row, this returns the largest fold that starts there.
|
|
#
|
|
# Largest is defined as the fold whose difference between its start and end points
|
|
# are the greatest.
|
|
#
|
|
# bufferRow - A {Number} indicating the buffer row
|
|
#
|
|
# Returns a {Fold} or null if none exists.
|
|
largestFoldStartingAtBufferRow: (bufferRow) ->
|
|
@foldsStartingAtBufferRow(bufferRow)[0]
|
|
|
|
# Public: Given a buffer row, this returns all folds that start there.
|
|
#
|
|
# bufferRow - A {Number} indicating the buffer row
|
|
#
|
|
# Returns an {Array} of {Fold}s.
|
|
foldsStartingAtBufferRow: (bufferRow) ->
|
|
for marker in @findFoldMarkers(startRow: bufferRow)
|
|
@foldForMarker(marker)
|
|
|
|
# Given a screen row, this returns the largest fold that starts there.
|
|
#
|
|
# Largest is defined as the fold whose difference between its start and end points
|
|
# are the greatest.
|
|
#
|
|
# screenRow - A {Number} indicating the screen row
|
|
#
|
|
# Returns a {Fold}.
|
|
largestFoldStartingAtScreenRow: (screenRow) ->
|
|
@largestFoldStartingAtBufferRow(@bufferRowForScreenRow(screenRow))
|
|
|
|
# Given a buffer row, this returns the largest fold that includes it.
|
|
#
|
|
# Largest is defined as the fold whose difference between its start and end rows
|
|
# is the greatest.
|
|
#
|
|
# bufferRow - A {Number} indicating the buffer row
|
|
#
|
|
# Returns a {Fold}.
|
|
largestFoldContainingBufferRow: (bufferRow) ->
|
|
@foldsContainingBufferRow(bufferRow)[0]
|
|
|
|
# Public: Given a buffer row, this returns folds that include it.
|
|
#
|
|
#
|
|
# bufferRow - A {Number} indicating the buffer row
|
|
#
|
|
# Returns an {Array} of {Fold}s.
|
|
foldsContainingBufferRow: (bufferRow) ->
|
|
for marker in @findFoldMarkers(intersectsRow: bufferRow)
|
|
@foldForMarker(marker)
|
|
|
|
# Given a buffer row, this converts it into a screen row.
|
|
#
|
|
# bufferRow - A {Number} representing a buffer row
|
|
#
|
|
# Returns a {Number}.
|
|
screenRowForBufferRow: (bufferRow) ->
|
|
@rowMap.screenRowRangeForBufferRow(bufferRow)[0]
|
|
|
|
lastScreenRowForBufferRow: (bufferRow) ->
|
|
@rowMap.screenRowRangeForBufferRow(bufferRow)[1] - 1
|
|
|
|
# Given a screen row, this converts it into a buffer row.
|
|
#
|
|
# screenRow - A {Number} representing a screen row
|
|
#
|
|
# Returns a {Number}.
|
|
bufferRowForScreenRow: (screenRow) ->
|
|
@rowMap.bufferRowRangeForScreenRow(screenRow)[0]
|
|
|
|
# Given a buffer range, this converts it into a screen position.
|
|
#
|
|
# bufferRange - The {Range} to convert
|
|
#
|
|
# Returns a {Range}.
|
|
screenRangeForBufferRange: (bufferRange) ->
|
|
bufferRange = Range.fromObject(bufferRange)
|
|
start = @screenPositionForBufferPosition(bufferRange.start)
|
|
end = @screenPositionForBufferPosition(bufferRange.end)
|
|
new Range(start, end)
|
|
|
|
# Given a screen range, this converts it into a buffer position.
|
|
#
|
|
# screenRange - The {Range} to convert
|
|
#
|
|
# Returns a {Range}.
|
|
bufferRangeForScreenRange: (screenRange) ->
|
|
screenRange = Range.fromObject(screenRange)
|
|
start = @bufferPositionForScreenPosition(screenRange.start)
|
|
end = @bufferPositionForScreenPosition(screenRange.end)
|
|
new Range(start, end)
|
|
|
|
# Gets the number of screen lines.
|
|
#
|
|
# Returns a {Number}.
|
|
getLineCount: ->
|
|
@screenLines.length
|
|
|
|
# Gets the number of the last screen line.
|
|
#
|
|
# Returns a {Number}.
|
|
getLastRow: ->
|
|
@getLineCount() - 1
|
|
|
|
# Gets the length of the longest screen line.
|
|
#
|
|
# Returns a {Number}.
|
|
getMaxLineLength: ->
|
|
@maxLineLength
|
|
|
|
# Given a buffer position, this converts it into a screen position.
|
|
#
|
|
# bufferPosition - An object that represents a buffer position. It can be either
|
|
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
|
# options - A hash of options with the following keys:
|
|
# wrapBeyondNewlines:
|
|
# wrapAtSoftNewlines:
|
|
#
|
|
# Returns a {Point}.
|
|
screenPositionForBufferPosition: (bufferPosition, options) ->
|
|
{ row, column } = @buffer.clipPosition(bufferPosition)
|
|
[startScreenRow, endScreenRow] = @rowMap.screenRowRangeForBufferRow(row)
|
|
for screenRow in [startScreenRow...endScreenRow]
|
|
unless screenLine = @screenLines[screenRow]
|
|
throw new Error """
|
|
No screen line exists for screen row #{screenRow}, converted from buffer position (#{row}, #{column})
|
|
Soft wrap enabled: #{@getSoftWrap()}
|
|
Fold count: #{@findFoldMarkers().length}
|
|
Last buffer row: #{@getLastRow()}
|
|
"""
|
|
maxBufferColumn = screenLine.getMaxBufferColumn()
|
|
if screenLine.isSoftWrapped() and column > maxBufferColumn
|
|
continue
|
|
else
|
|
if column <= maxBufferColumn
|
|
screenColumn = screenLine.screenColumnForBufferColumn(column)
|
|
else
|
|
screenColumn = Infinity
|
|
break
|
|
|
|
new Point(screenRow, screenColumn)
|
|
@clipScreenPosition([screenRow, screenColumn], options)
|
|
|
|
# Given a buffer position, this converts it into a screen position.
|
|
#
|
|
# screenPosition - An object that represents a buffer position. It can be either
|
|
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
|
# options - A hash of options with the following keys:
|
|
# wrapBeyondNewlines:
|
|
# wrapAtSoftNewlines:
|
|
#
|
|
# Returns a {Point}.
|
|
bufferPositionForScreenPosition: (screenPosition, options) ->
|
|
{ row, column } = @clipScreenPosition(Point.fromObject(screenPosition), options)
|
|
[bufferRow] = @rowMap.bufferRowRangeForScreenRow(row)
|
|
new Point(bufferRow, @screenLines[row].bufferColumnForScreenColumn(column))
|
|
|
|
# Retrieves the grammar's token scopes for a buffer position.
|
|
#
|
|
# bufferPosition - A {Point} in the {TextBuffer}
|
|
#
|
|
# Returns an {Array} of {String}s.
|
|
scopesForBufferPosition: (bufferPosition) ->
|
|
@tokenizedBuffer.scopesForPosition(bufferPosition)
|
|
|
|
bufferRangeForScopeAtPosition: (selector, position) ->
|
|
@tokenizedBuffer.bufferRangeForScopeAtPosition(selector, position)
|
|
|
|
# Retrieves the grammar's token for a buffer position.
|
|
#
|
|
# bufferPosition - A {Point} in the {TextBuffer}.
|
|
#
|
|
# Returns a {Token}.
|
|
tokenForBufferPosition: (bufferPosition) ->
|
|
@tokenizedBuffer.tokenForPosition(bufferPosition)
|
|
|
|
# Retrieves the current tab length.
|
|
#
|
|
# Returns a {Number}.
|
|
getTabLength: ->
|
|
@tokenizedBuffer.getTabLength()
|
|
|
|
# Specifies the tab length.
|
|
#
|
|
# tabLength - A {Number} that defines the new tab length.
|
|
setTabLength: (tabLength) ->
|
|
@tokenizedBuffer.setTabLength(tabLength)
|
|
|
|
# Get the grammar for this buffer.
|
|
#
|
|
# Returns the current {Grammar} or the {NullGrammar}.
|
|
getGrammar: ->
|
|
@tokenizedBuffer.grammar
|
|
|
|
# Sets the grammar for the buffer.
|
|
#
|
|
# grammar - Sets the new grammar rules
|
|
setGrammar: (grammar) ->
|
|
@tokenizedBuffer.setGrammar(grammar)
|
|
|
|
# Reloads the current grammar.
|
|
reloadGrammar: ->
|
|
@tokenizedBuffer.reloadGrammar()
|
|
|
|
# Given a position, this clips it to a real position.
|
|
#
|
|
# For example, if `position`'s row exceeds the row count of the buffer,
|
|
# or if its column goes beyond a line's length, this "sanitizes" the value
|
|
# to a real position.
|
|
#
|
|
# position - The {Point} to clip
|
|
# options - A hash with the following values:
|
|
# wrapBeyondNewlines: if `true`, continues wrapping past newlines
|
|
# wrapAtSoftNewlines: if `true`, continues wrapping past soft newlines
|
|
# screenLine: if `true`, indicates that you're using a line number, not a row number
|
|
#
|
|
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
|
|
clipScreenPosition: (screenPosition, options={}) ->
|
|
{ wrapBeyondNewlines, wrapAtSoftNewlines } = options
|
|
{ row, column } = Point.fromObject(screenPosition)
|
|
|
|
if row < 0
|
|
row = 0
|
|
column = 0
|
|
else if row > @getLastRow()
|
|
row = @getLastRow()
|
|
column = Infinity
|
|
else if column < 0
|
|
column = 0
|
|
|
|
screenLine = @screenLines[row]
|
|
maxScreenColumn = screenLine.getMaxScreenColumn()
|
|
|
|
if screenLine.isSoftWrapped() and column >= maxScreenColumn
|
|
if wrapAtSoftNewlines
|
|
row++
|
|
column = 0
|
|
else
|
|
column = screenLine.clipScreenColumn(maxScreenColumn - 1)
|
|
else if wrapBeyondNewlines and column > maxScreenColumn and row < @getLastRow()
|
|
row++
|
|
column = 0
|
|
else
|
|
column = screenLine.clipScreenColumn(column, options)
|
|
new Point(row, column)
|
|
|
|
### Public ###
|
|
|
|
# Given a line, finds the point where it would wrap.
|
|
#
|
|
# line - The {String} to check
|
|
# softWrapColumn - The {Number} where you want soft wrapping to occur
|
|
#
|
|
# Returns a {Number} representing the `line` position where the wrap would take place.
|
|
# Returns `null` if a wrap wouldn't occur.
|
|
findWrapColumn: (line, softWrapColumn=@getSoftWrapColumn()) ->
|
|
return unless @softWrap
|
|
return unless line.length > softWrapColumn
|
|
|
|
if /\s/.test(line[softWrapColumn])
|
|
# search forward for the start of a word past the boundary
|
|
for column in [softWrapColumn..line.length]
|
|
return column if /\S/.test(line[column])
|
|
return line.length
|
|
else
|
|
# search backward for the start of the word on the boundary
|
|
for column in [softWrapColumn..0]
|
|
return column + 1 if /\s/.test(line[column])
|
|
return softWrapColumn
|
|
|
|
# Calculates a {Range} representing the start of the {TextBuffer} until the end.
|
|
#
|
|
# Returns a {Range}.
|
|
rangeForAllLines: ->
|
|
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
|
|
|
|
# Retrieves a {DisplayBufferMarker} based on its id.
|
|
#
|
|
# id - A {Number} representing a marker id
|
|
#
|
|
# Returns the {DisplayBufferMarker} (if it exists).
|
|
getMarker: (id) ->
|
|
unless marker = @markers[id]
|
|
if bufferMarker = @buffer.getMarker(id)
|
|
marker = new DisplayBufferMarker({bufferMarker, displayBuffer: this})
|
|
@markers[id] = marker
|
|
marker
|
|
|
|
# Retrieves the active markers in the buffer.
|
|
#
|
|
# Returns an {Array} of existing {DisplayBufferMarker}s.
|
|
getMarkers: ->
|
|
@buffer.getMarkers().map ({id}) => @getMarker(id)
|
|
|
|
getMarkerCount: ->
|
|
@buffer.getMarkerCount()
|
|
|
|
# Constructs a new marker at the given screen range.
|
|
#
|
|
# range - The marker {Range} (representing the distance between the head and tail)
|
|
# options - Options to pass to the {Marker} constructor
|
|
#
|
|
# Returns a {Number} representing the new marker's ID.
|
|
markScreenRange: (args...) ->
|
|
bufferRange = @bufferRangeForScreenRange(args.shift())
|
|
@markBufferRange(bufferRange, args...)
|
|
|
|
# Constructs a new marker at the given buffer range.
|
|
#
|
|
# range - The marker {Range} (representing the distance between the head and tail)
|
|
# options - Options to pass to the {Marker} constructor
|
|
#
|
|
# Returns a {Number} representing the new marker's ID.
|
|
markBufferRange: (args...) ->
|
|
@getMarker(@buffer.markRange(args...).id)
|
|
|
|
# Constructs a new marker at the given screen position.
|
|
#
|
|
# range - The marker {Range} (representing the distance between the head and tail)
|
|
# options - Options to pass to the {Marker} constructor
|
|
#
|
|
# Returns a {Number} representing the new marker's ID.
|
|
markScreenPosition: (screenPosition, options) ->
|
|
@markBufferPosition(@bufferPositionForScreenPosition(screenPosition), options)
|
|
|
|
# Constructs a new marker at the given buffer position.
|
|
#
|
|
# range - The marker {Range} (representing the distance between the head and tail)
|
|
# options - Options to pass to the {Marker} constructor
|
|
#
|
|
# Returns a {Number} representing the new marker's ID.
|
|
markBufferPosition: (bufferPosition, options) ->
|
|
@getMarker(@buffer.markPosition(bufferPosition, options).id)
|
|
|
|
# Removes the marker with the given id.
|
|
#
|
|
# id - The {Number} of the ID to remove
|
|
destroyMarker: (id) ->
|
|
@buffer.destroyMarker(id)
|
|
delete @markers[id]
|
|
|
|
# Finds the first marker satisfying the given attributes
|
|
#
|
|
# Refer to {DisplayBuffer.findMarkers} for details.
|
|
#
|
|
# Returns a {DisplayBufferMarker} or null
|
|
findMarker: (attributes) ->
|
|
@findMarkers(attributes)[0]
|
|
|
|
# Finds all valid markers satisfying the given attributes
|
|
#
|
|
# attributes - The attributes against which to compare the markers' attributes
|
|
# There are some reserved keys that match against derived marker properties:
|
|
# startBufferRow - The buffer row at which the marker starts
|
|
# endBufferRow - The buffer row at which the marker ends
|
|
#
|
|
# Returns an {Array} of {DisplayBufferMarker}s
|
|
findMarkers: (attributes) ->
|
|
attributes = @translateToBufferMarkerAttributes(attributes)
|
|
@buffer.findMarkers(attributes).map (stringMarker) => @getMarker(stringMarker.id)
|
|
|
|
translateToBufferMarkerAttributes: (attributes) ->
|
|
stringMarkerAttributes = {}
|
|
for key, value of attributes
|
|
switch key
|
|
when 'startBufferRow'
|
|
key = 'startRow'
|
|
when 'endBufferRow'
|
|
key = 'endRow'
|
|
when 'containsBufferRange'
|
|
key = 'containsRange'
|
|
when 'containsBufferPosition'
|
|
key = 'containsPosition'
|
|
stringMarkerAttributes[key] = value
|
|
stringMarkerAttributes
|
|
|
|
findFoldMarker: (attributes) ->
|
|
@findFoldMarkers(attributes)[0]
|
|
|
|
findFoldMarkers: (attributes) ->
|
|
@buffer.findMarkers(@getFoldMarkerAttributes(attributes))
|
|
|
|
getFoldMarkerAttributes: (attributes={}) ->
|
|
_.extend(attributes, class: 'fold', displayBufferId: @id)
|
|
|
|
pauseMarkerObservers: ->
|
|
marker.pauseEvents() for marker in @getMarkers()
|
|
|
|
resumeMarkerObservers: ->
|
|
marker.resumeEvents() for marker in @getMarkers()
|
|
@emit 'markers-updated'
|
|
|
|
refreshMarkerScreenPositions: ->
|
|
for marker in @getMarkers()
|
|
marker.notifyObservers(textChanged: false)
|
|
|
|
destroy: ->
|
|
marker.unsubscribe() for marker in @getMarkers()
|
|
@tokenizedBuffer.destroy()
|
|
@unsubscribe()
|
|
@unobserveConfig()
|
|
|
|
logLines: (start=0, end=@getLastRow())->
|
|
for row in [start..end]
|
|
line = @lineForRow(row).text
|
|
console.log row, line, line.length
|
|
|
|
### Internal ###
|
|
|
|
handleTokenizedBufferChange: (tokenizedBufferChange) =>
|
|
{start, end, delta, bufferChange} = tokenizedBufferChange
|
|
@updateScreenLines(start, end + 1, delta, delayChangeEvent: bufferChange?)
|
|
|
|
updateScreenLines: (startBufferRow, endBufferRow, bufferDelta=0, options={}) ->
|
|
startBufferRow = @rowMap.bufferRowRangeForBufferRow(startBufferRow)[0]
|
|
startScreenRow = @rowMap.screenRowRangeForBufferRow(startBufferRow)[0]
|
|
endScreenRow = @rowMap.screenRowRangeForBufferRow(endBufferRow - 1)[1]
|
|
|
|
@rowMap.applyBufferDelta(startBufferRow, bufferDelta)
|
|
|
|
{ newScreenLines, newMappings } = @buildScreenLines(startBufferRow, endBufferRow + bufferDelta)
|
|
@screenLines[startScreenRow...endScreenRow] = newScreenLines
|
|
screenDelta = newScreenLines.length - (endScreenRow - startScreenRow)
|
|
@rowMap.applyScreenDelta(startScreenRow, screenDelta)
|
|
@rowMap.mapBufferRowRange(mapping...) for mapping in newMappings
|
|
@findMaxLineLength(startScreenRow, endScreenRow, newScreenLines)
|
|
|
|
return if options.suppressChangeEvent
|
|
|
|
changeEvent =
|
|
start: startScreenRow
|
|
end: endScreenRow - 1
|
|
screenDelta: screenDelta
|
|
bufferDelta: bufferDelta
|
|
|
|
if options.delayChangeEvent
|
|
@pauseMarkerObservers()
|
|
@pendingChangeEvent = changeEvent
|
|
else
|
|
@emitChanged(changeEvent, options.refreshMarkers)
|
|
|
|
buildScreenLines: (startBufferRow, endBufferRow) ->
|
|
newScreenLines = []
|
|
newMappings = []
|
|
pendingIsoMapping = null
|
|
|
|
pushNewMapping = (startBufferRow, endBufferRow, screenRows) ->
|
|
if endBufferRow - startBufferRow == screenRows
|
|
if pendingIsoMapping
|
|
pendingIsoMapping[1] = endBufferRow
|
|
else
|
|
pendingIsoMapping = [startBufferRow, endBufferRow]
|
|
else
|
|
clearPendingIsoMapping()
|
|
newMappings.push([startBufferRow, endBufferRow, screenRows])
|
|
|
|
clearPendingIsoMapping = ->
|
|
if pendingIsoMapping
|
|
[isoStart, isoEnd] = pendingIsoMapping
|
|
pendingIsoMapping.push(isoEnd - isoStart)
|
|
newMappings.push(pendingIsoMapping)
|
|
pendingIsoMapping = null
|
|
|
|
bufferRow = startBufferRow
|
|
while bufferRow < endBufferRow
|
|
tokenizedLine = @tokenizedBuffer.lineForScreenRow(bufferRow)
|
|
|
|
if fold = @largestFoldStartingAtBufferRow(bufferRow)
|
|
foldLine = tokenizedLine.copy()
|
|
foldLine.fold = fold
|
|
newScreenLines.push(foldLine)
|
|
pushNewMapping(bufferRow, fold.getEndRow() + 1, 1)
|
|
bufferRow = fold.getEndRow() + 1
|
|
else
|
|
softWraps = 0
|
|
while wrapScreenColumn = @findWrapColumn(tokenizedLine.text)
|
|
[wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(wrapScreenColumn)
|
|
|
|
newScreenLines.push(wrappedLine)
|
|
softWraps++
|
|
newScreenLines.push(tokenizedLine)
|
|
pushNewMapping(bufferRow, bufferRow + 1, softWraps + 1)
|
|
bufferRow++
|
|
clearPendingIsoMapping()
|
|
|
|
{ newScreenLines, newMappings }
|
|
|
|
findMaxLineLength: (startScreenRow, endScreenRow, newScreenLines) ->
|
|
if startScreenRow <= @longestScreenRow < endScreenRow
|
|
@longestScreenRow = 0
|
|
@maxLineLength = 0
|
|
maxLengthCandidatesStartRow = 0
|
|
maxLengthCandidates = @screenLines
|
|
else
|
|
maxLengthCandidatesStartRow = startScreenRow
|
|
maxLengthCandidates = newScreenLines
|
|
|
|
for screenLine, screenRow in maxLengthCandidates
|
|
length = screenLine.text.length
|
|
if length > @maxLineLength
|
|
@longestScreenRow = maxLengthCandidatesStartRow + screenRow
|
|
@maxLineLength = length
|
|
|
|
handleBufferMarkersUpdated: =>
|
|
if event = @pendingChangeEvent
|
|
@pendingChangeEvent = null
|
|
@emitChanged(event, false)
|
|
|
|
handleBufferMarkerCreated: (marker) =>
|
|
@createFoldForMarker(marker) if marker.matchesAttributes(@getFoldMarkerAttributes())
|
|
@emit 'marker-created', @getMarker(marker.id)
|
|
|
|
createFoldForMarker: (marker) ->
|
|
new Fold(this, marker)
|
|
|
|
foldForMarker: (marker) ->
|
|
@foldsByMarkerId[marker.id]
|