Merge remote-tracking branch 'origin/master' into know-when-to-foldem

Conflicts:
	src/app/buffer-marker.coffee
	src/app/cursor.coffee
	src/app/display-buffer-marker.coffee
	src/app/display-buffer.coffee
	src/app/edit-session.coffee
	src/app/fold.coffee
	src/app/line-map.coffee
	src/app/range.coffee
	src/app/selection.coffee
	src/app/text-buffer.coffee
This commit is contained in:
Nathan Sobo
2013-05-03 18:29:52 -06:00
87 changed files with 1665 additions and 1860 deletions

View File

@@ -18,9 +18,7 @@ class DisplayBuffer
markers: null
foldsByMarkerId: null
###
# Internal #
###
### Internal ###
constructor: (@buffer, options={}) ->
@id = @constructor.idCounter++
@@ -45,13 +43,14 @@ class DisplayBuffer
@trigger 'changed', eventProperties
@resumeMarkerObservers()
###
# Public #
###
### Public ###
# Sets the visibility of the tokenized buffer.
#
# visible - A {Boolean} indicating of the tokenized buffer is shown
setVisible: (visible) -> @tokenizedBuffer.setVisible(visible)
# Public: Defines the limit at which the buffer begins to soft wrap text.
# Defines the limit at which the buffer begins to soft wrap text.
#
# softWrapColumn - A {Number} defining the soft wrap limit.
setSoftWrapColumn: (@softWrapColumn) ->
@@ -62,7 +61,7 @@ class DisplayBuffer
bufferDelta = 0
@triggerChanged({ start, end, screenDelta, bufferDelta })
# Public: Gets the screen line for the given screen row.
# Gets the screen line for the given screen row.
#
# screenRow - A {Number} indicating the screen row.
#
@@ -70,7 +69,7 @@ class DisplayBuffer
lineForRow: (row) ->
@lineMap.lineForScreenRow(row)
# Public: Gets the screen lines for the given screen row range.
# 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.
@@ -79,14 +78,14 @@ class DisplayBuffer
linesForRows: (startRow, endRow) ->
@lineMap.linesForScreenRows(startRow, endRow)
# Public: Gets all the screen lines.
# Gets all the screen lines.
#
# Returns an {Array} of {ScreenLines}s.
getLines: ->
@lineMap.linesForScreenRows(0, @lineMap.lastScreenRow())
# Public: Given starting and ending screen rows, this returns an array of the
# Given starting and ending screen rows, this returns an array of the
# buffer rows corresponding to every screen row in the range
#
# startRow - The screen row {Number} to start at
@@ -96,7 +95,7 @@ class DisplayBuffer
bufferRowsForScreenRows: (startRow, endRow) ->
@lineMap.bufferRowsForScreenRows(startRow, endRow)
# Public: Creates a new fold between two row numbers.
# Creates a new fold between two row numbers.
#
# startRow - The row {Number} to start folding at
# endRow - The row {Number} to end the fold
@@ -108,14 +107,16 @@ class DisplayBuffer
@buffer.markRange([[startRow, 0], [endRow, Infinity]], @foldMarkerAttributes())
@foldForMarker(foldMarker)
# Public: Removes any folds found that contain the given buffer row.
# 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)
# Largest is defined as the fold whose difference between its start and end rows
# is the greatest.
# 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
#
@@ -132,7 +133,7 @@ class DisplayBuffer
for marker in @findFoldMarkers(startRow: bufferRow)
@foldForMarker(marker)
# Public: Given a screen row, this returns the largest fold that starts there.
# 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.
@@ -143,7 +144,7 @@ class DisplayBuffer
largestFoldStartingAtScreenRow: (screenRow) ->
@largestFoldStartingAtBufferRow(@bufferRowForScreenRow(screenRow))
# Public: Given a buffer row, this returns the largest fold that includes it.
# 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.
@@ -164,7 +165,7 @@ class DisplayBuffer
for marker in @findFoldMarkers(containsRow: bufferRow)
@foldForMarker(marker)
# Public: Given a buffer range, this converts it into a screen range.
# Given a buffer range, this converts it into a screen range.
#
# bufferRange - A {Range} consisting of buffer positions
#
@@ -174,7 +175,7 @@ class DisplayBuffer
@lineMap.screenRangeForBufferRange(
@expandBufferRangeToLineEnds(bufferRange)))
# Public: Given a buffer row, this converts it into a screen row.
# Given a buffer row, this converts it into a screen row.
#
# bufferRow - A {Number} representing a buffer row
#
@@ -185,7 +186,7 @@ class DisplayBuffer
lastScreenRowForBufferRow: (bufferRow) ->
@lineMap.screenPositionForBufferPosition([bufferRow, Infinity]).row
# Public: Given a screen row, this converts it into a buffer row.
# Given a screen row, this converts it into a buffer row.
#
# screenRow - A {Number} representing a screen row
#
@@ -193,7 +194,7 @@ class DisplayBuffer
bufferRowForScreenRow: (screenRow) ->
@lineMap.bufferPositionForScreenPosition([screenRow, 0]).row
# Public: Given a buffer range, this converts it into a screen position.
# Given a buffer range, this converts it into a screen position.
#
# bufferRange - The {Range} to convert
#
@@ -201,7 +202,7 @@ class DisplayBuffer
screenRangeForBufferRange: (bufferRange) ->
@lineMap.screenRangeForBufferRange(bufferRange)
# Public: Given a screen range, this converts it into a buffer position.
# Given a screen range, this converts it into a buffer position.
#
# screenRange - The {Range} to convert
#
@@ -209,49 +210,49 @@ class DisplayBuffer
bufferRangeForScreenRange: (screenRange) ->
@lineMap.bufferRangeForScreenRange(screenRange)
# Public: Gets the number of lines in the buffer.
# Gets the number of screen lines.
#
# Returns a {Number}.
getLineCount: ->
@lineMap.getScreenLineCount()
# Public: Gets the number of the last row in the buffer.
# Gets the number of the last screen line.
#
# Returns a {Number}.
getLastRow: ->
@getLineCount() - 1
# Public: Gets the length of the longest screen line.
# Gets the length of the longest screen line.
#
# Returns a {Number}.
maxLineLength: ->
@lineMap.maxScreenLineLength
# Public: Given a buffer position, this converts it into a screen position.
# 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 -
# wrapBeyondNewlines:
# wrapAtSoftNewlines:
#
# Returns a {Point}.
screenPositionForBufferPosition: (position, options) ->
@lineMap.screenPositionForBufferPosition(position, options)
# Public: Given a buffer range, this converts it into a screen position.
# Given a buffer range, 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 -
# wrapBeyondNewlines:
# wrapAtSoftNewlines:
#
# Returns a {Point}.
bufferPositionForScreenPosition: (position, options) ->
@lineMap.bufferPositionForScreenPosition(position, options)
# Public: Retrieves the grammar's token scopes for a buffer position.
# Retrieves the grammar's token scopes for a buffer position.
#
# bufferPosition - A {Point} in the {Buffer}
#
@@ -259,7 +260,7 @@ class DisplayBuffer
scopesForBufferPosition: (bufferPosition) ->
@tokenizedBuffer.scopesForPosition(bufferPosition)
# Public: Retrieves the grammar's token for a buffer position.
# Retrieves the grammar's token for a buffer position.
#
# bufferPosition - A {Point} in the {Buffer}.
#
@@ -267,28 +268,33 @@ class DisplayBuffer
tokenForBufferPosition: (bufferPosition) ->
@tokenizedBuffer.tokenForPosition(bufferPosition)
# Public: Retrieves the current tab length.
# Retrieves the current tab length.
#
# Returns a {Number}.
getTabLength: ->
@tokenizedBuffer.getTabLength()
# Public: Specifies the tab length.
# Specifies the tab length.
#
# tabLength - A {Number} that defines the new tab length.
setTabLength: (tabLength) ->
@tokenizedBuffer.setTabLength(tabLength)
# Retrieves the grammar for the buffer.
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()
# Public: Given a position, this clips it to a real position.
# 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
@@ -296,17 +302,181 @@ class DisplayBuffer
#
# 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
# 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: (position, options) ->
@lineMap.clipScreenPosition(position, options)
###
# Internal #
###
### 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) ->
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
# Given a range in screen coordinates, this expands it to the start and end of a line
#
# screenRange - The {Range} to expand
#
# Returns a new {Range}.
expandScreenRangeToLineEnds: (screenRange) ->
screenRange = Range.fromObject(screenRange)
{ start, end } = screenRange
new Range([start.row, 0], [end.row, @lineMap.lineForScreenRow(end.row).text.length])
# Given a range in buffer coordinates, this expands it to the start and end of a line
#
# screenRange - The {Range} to expand
#
# Returns a new {Range}.
expandBufferRangeToLineEnds: (bufferRange) ->
bufferRange = Range.fromObject(bufferRange)
{ start, end } = bufferRange
new Range([start.row, 0], [end.row, Infinity])
# Calculates a {Range} representing the start of the {Buffer} 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) ->
@markers[id] ?= do =>
if bufferMarker = @buffer.getMarker(id)
new DisplayBufferMarker({bufferMarker, displayBuffer: this})
# Retrieves the active markers in the buffer.
#
# Returns an {Array} of existing {DisplayBufferMarker}s.
getMarkers: ->
_.values(@markers)
# 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 {BufferMarker} 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 {BufferMarker} 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 {BufferMarker} 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 {BufferMarker} 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) ->
{ startBufferRow, endBufferRow, containsBufferRange, containsBufferRow } = attributes
attributes.startRow = startBufferRow if startBufferRow?
attributes.endRow = endBufferRow if 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)
findFoldMarker: (attributes) ->
@findFoldMarkers(attributes)[0]
findFoldMarkers: (attributes) ->
@buffer.findMarkers(@foldMarkerAttributes(attributes))
foldMarkerAttributes: (attributes={}) ->
_.extend(attributes, class: 'fold', displayBufferId: @id)
pauseMarkerObservers: ->
marker.pauseEvents() for marker in @getMarkers()
resumeMarkerObservers: ->
marker.resumeEvents() for marker in @getMarkers()
refreshMarkerScreenPositions: ->
for marker in @getMarkers()
marker.notifyObservers(bufferChanged: false)
destroy: ->
@tokenizedBuffer.destroy()
@unsubscribe()
logLines: (start, end) ->
@lineMap.logLines(start, end)
getDebugSnapshot: ->
lines = ["Display Buffer:"]
for screenLine, row in @lineMap.linesForScreenRows(0, @getLastRow())
lines.push "#{row}: #{screenLine.text}"
lines.join('\n')
### Internal ###
handleTokenizedBufferChange: (tokenizedBufferChange) =>
{start, end, delta, bufferChange} = tokenizedBufferChange
@@ -381,177 +551,6 @@ class DisplayBuffer
foldForMarker: (marker) ->
@foldsByMarkerId[marker.id]
###
# Public #
###
# 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) ->
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
# Public: Given a range in screen coordinates, this expands it to the start and end of a line
#
# screenRange - The {Range} to expand
#
# Returns a new {Range}.
expandScreenRangeToLineEnds: (screenRange) ->
screenRange = Range.fromObject(screenRange)
{ start, end } = screenRange
new Range([start.row, 0], [end.row, @lineMap.lineForScreenRow(end.row).text.length])
# Public: Given a range in buffer coordinates, this expands it to the start and end of a line
#
# screenRange - The {Range} to expand
#
# Returns a new {Range}.
expandBufferRangeToLineEnds: (bufferRange) ->
bufferRange = Range.fromObject(bufferRange)
{ start, end } = bufferRange
new Range([start.row, 0], [end.row, Infinity])
# Public: Calculates a {Range} representing the start of the {Buffer} until the end.
#
# Returns a {Range}.
rangeForAllLines: ->
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
# Public: Retrieves a {DisplayBufferMarker} based on its id.
#
# id - A {Number} representing a marker id
#
# Returns the {DisplayBufferMarker} (if it exists).
getMarker: (id) ->
@markers[id] ?= do =>
if bufferMarker = @buffer.getMarker(id)
new DisplayBufferMarker({bufferMarker, displayBuffer: this})
# Public: Retrieves the active markers in the buffer.
#
# Returns an {Array} of existing {DisplayBufferMarker}s.
getMarkers: ->
_.values(@markers)
# Public: 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 {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markScreenRange: (args...) ->
bufferRange = @bufferRangeForScreenRange(args.shift())
@markBufferRange(bufferRange, args...)
# Public: 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 {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markBufferRange: (args...) ->
@getMarker(@buffer.markRange(args...).id)
# Public: 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 {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markScreenPosition: (screenPosition, options) ->
@markBufferPosition(@bufferPositionForScreenPosition(screenPosition), options)
# Public: 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 {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markBufferPosition: (bufferPosition, options) ->
@getMarker(@buffer.markPosition(bufferPosition, options).id)
# Public: 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) ->
{ startBufferRow, endBufferRow, containsBufferRange, containsBufferRow } = attributes
attributes.startRow = startBufferRow if startBufferRow?
attributes.endRow = endBufferRow if 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 #
###
findFoldMarker: (attributes) ->
@findFoldMarkers(attributes)[0]
findFoldMarkers: (attributes) ->
@buffer.findMarkers(@foldMarkerAttributes(attributes))
foldMarkerAttributes: (attributes={}) ->
_.extend(attributes, class: 'fold', displayBufferId: @id)
pauseMarkerObservers: ->
marker.pauseEvents() for marker in @getMarkers()
resumeMarkerObservers: ->
marker.resumeEvents() for marker in @getMarkers()
refreshMarkerScreenPositions: ->
for marker in @getMarkers()
marker.notifyObservers(bufferChanged: false)
destroy: ->
@tokenizedBuffer.destroy()
@unsubscribe()
logLines: (start, end) ->
@lineMap.logLines(start, end)
getDebugSnapshot: ->
lines = ["Display Buffer:"]
for screenLine, row in @lineMap.linesForScreenRows(0, @getLastRow())
lines.push "#{row}: #{screenLine.text}"
lines.join('\n')
_.extend DisplayBuffer.prototype, EventEmitter
_.extend DisplayBuffer.prototype, Subscriber