Files
atom/src/app/buffer.coffee
Nathan Sobo 4c8aeb16bb Merge branch 'master' into snippets
Conflicts:
	src/app/window.coffee
2012-06-20 17:11:25 -06:00

237 lines
6.1 KiB
CoffeeScript

_ = require 'underscore'
fs = require 'fs'
Point = require 'point'
Range = require 'range'
EventEmitter = require 'event-emitter'
UndoManager = require 'undo-manager'
module.exports =
class Buffer
@idCounter = 1
modified: null
lines: null
path: null
constructor: (path) ->
@id = @constructor.idCounter++
@setPath(path)
@lines = ['']
if fs.exists(@getPath())
@setText(fs.read(@getPath()))
else
@setText('')
@undoManager = new UndoManager(this)
@modified = false
getPath: ->
@path
getExtension: ->
if @getPath()
@getPath().split('/').pop().split('.').pop()
else
null
setPath: (path) ->
@path = path
@trigger "path-change", this
getText: ->
@lines.join('\n')
setText: (text) ->
@change(@getRange(), text)
getRange: ->
new Range([0, 0], [@getLastRow(), @lastLine().length])
getTextInRange: (range) ->
range = Range.fromObject(range)
if range.start.row == range.end.row
return @lines[range.start.row][range.start.column...range.end.column]
multipleLines = []
multipleLines.push @lines[range.start.row][range.start.column..] # first line
for row in [range.start.row + 1...range.end.row]
multipleLines.push @lines[row] # middle lines
multipleLines.push @lines[range.end.row][0...range.end.column] # last line
return multipleLines.join '\n'
getLines: ->
@lines
lineForRow: (row) ->
@lines[row]
lineLengthForRow: (row) ->
@lines[row].length
rangeForRow: (row) ->
new Range([row, 0], [row, @lineLengthForRow(row)])
getLineCount: ->
@getLines().length
getLastRow: ->
@getLines().length - 1
lastLine: ->
@lineForRow(@getLastRow())
getEofPosition: ->
lastRow = @getLastRow()
new Point(lastRow, @lineLengthForRow(lastRow))
characterIndexForPosition: (position) ->
position = Point.fromObject(position)
index = 0
index += @lineLengthForRow(row) + 1 for row in [0...position.row]
index + position.column
positionForCharacterIndex: (index) ->
row = 0
while index >= (lineLength = @lineLengthForRow(row) + 1)
index -= lineLength
row++
new Point(row, index)
deleteRow: (row) ->
range = null
if row == @getLastRow()
range = new Range([row - 1, @lineLengthForRow(row - 1)], [row, @lineLengthForRow(row)])
else
range = new Range([row, 0], [row + 1, 0])
@change(range, '')
insert: (point, text) ->
@change(new Range(point, point), text)
delete: (range) ->
@change(range, '')
change: (oldRange, newText) ->
oldRange = Range.fromObject(oldRange)
newRange = new Range(oldRange.start.copy(), oldRange.start.copy())
prefix = @lines[oldRange.start.row][0...oldRange.start.column]
suffix = @lines[oldRange.end.row][oldRange.end.column..]
oldText = @getTextInRange(oldRange)
newTextLines = newText.split('\n')
if newTextLines.length == 1
newRange.end.column += newText.length
newTextLines = [prefix + newText + suffix]
else
lastLineIndex = newTextLines.length - 1
newTextLines[0] = prefix + newTextLines[0]
newRange.end.row += lastLineIndex
newRange.end.column = newTextLines[lastLineIndex].length
newTextLines[lastLineIndex] += suffix
@lines[oldRange.start.row..oldRange.end.row] = newTextLines
@modified = true
@trigger 'change', { oldRange, newRange, oldText, newText }
newRange
startUndoBatch: (selectedBufferRanges) ->
@undoManager.startUndoBatch(selectedBufferRanges)
endUndoBatch: (selectedBufferRanges) ->
@undoManager.endUndoBatch(selectedBufferRanges)
undo: ->
@undoManager.undo()
redo: ->
@undoManager.redo()
save: ->
if not @getPath() then throw new Error("Can't save buffer with no file path")
@trigger 'before-save'
fs.write @getPath(), @getText()
@modified = false
@trigger 'after-save'
saveAs: (path) ->
@setPath(path)
@save()
isModified: ->
@modified
matchesInCharacterRange: (regex, startIndex, endIndex) ->
text = @getText()
matches = []
regex.lastIndex = startIndex
while match = regex.exec(text)
matchLength = match[0].length
matchStartIndex = match.index
matchEndIndex = matchStartIndex + matchLength
if matchEndIndex > endIndex
regex.lastIndex = 0
if matchStartIndex < endIndex and submatch = regex.exec(text[matchStartIndex...endIndex])
submatch.index = matchStartIndex
matches.push submatch
break
matchEndIndex++ if matchLength is 0
regex.lastIndex = matchEndIndex
matches.push match
matches
scan: (regex, iterator) ->
@scanInRange(regex, @getRange(), iterator)
scanInRange: (regex, range, iterator, reverse=false) ->
range = Range.fromObject(range)
global = regex.global
regex = new RegExp(regex.source, 'gm')
startIndex = @characterIndexForPosition(range.start)
endIndex = @characterIndexForPosition(range.end)
matches = @matchesInCharacterRange(regex, startIndex, endIndex)
lengthDelta = 0
keepLooping = null
replacementText = null
stop = -> keepLooping = false
replace = (text) -> replacementText = text
matches.reverse() if reverse
for match in matches
matchLength = match[0].length
matchStartIndex = match.index
matchEndIndex = matchStartIndex + matchLength
startPosition = @positionForCharacterIndex(matchStartIndex + lengthDelta)
endPosition = @positionForCharacterIndex(matchEndIndex + lengthDelta)
range = new Range(startPosition, endPosition)
keepLooping = true
replacementText = null
iterator(match, range, { stop, replace })
if replacementText?
@change(range, replacementText)
lengthDelta += replacementText.length - matchLength unless reverse
break unless global and keepLooping
backwardsScanInRange: (regex, range, iterator) ->
@scanInRange regex, range, iterator, true
logLines: (start=0, end=@getLastRow())->
for row in [start..end]
line = @lineForRow(row)
console.log row, line, line.length
_.extend(Buffer.prototype, EventEmitter)