mirror of
https://github.com/atom/atom.git
synced 2026-01-25 06:48:28 -05:00
243 lines
6.2 KiB
CoffeeScript
243 lines
6.2 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
|
|
|
|
@deserialize: (state, project) ->
|
|
project.open(state.path)
|
|
|
|
constructor: (path) ->
|
|
@id = @constructor.idCounter++
|
|
@setPath(path)
|
|
@lines = ['']
|
|
if fs.exists(@getPath())
|
|
@setText(fs.read(@getPath()))
|
|
else
|
|
@setText('')
|
|
@undoManager = new UndoManager(this)
|
|
@modified = false
|
|
|
|
serialize: ->
|
|
path: @getPath()
|
|
|
|
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)
|