Eliminate TextBuffer's dependence on telepath

Atom's TextBuffer now relies on the text-buffer npm for its core
functionality.
This commit is contained in:
Nathan Sobo
2013-12-30 19:13:52 -07:00
parent dfe9f5684e
commit dd2c6d2f24
12 changed files with 101 additions and 158 deletions

View File

@@ -34,6 +34,7 @@
"mkdirp": "0.3.5",
"keytar": "0.13.0",
"less-cache": "0.10.0",
"nostalgia": "0.2.0",
"nslog": "0.1.0",
"oniguruma": "0.24.0",
"optimist": "0.4.0",
@@ -46,6 +47,7 @@
"space-pen": "2.0.2",
"telepath": "0.80.0",
"temp": "0.5.0",
"text-buffer": "0.2.0",
"underscore-plus": "0.6.1"
},
"devDependencies": {

View File

@@ -971,7 +971,7 @@ describe 'TextBuffer', ->
expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n"
describe "serialization", ->
[buffer2, project2] = []
buffer2 = null
beforeEach ->
buffer.destroy()
@@ -981,13 +981,12 @@ describe 'TextBuffer', ->
buffer = atom.project.bufferForPathSync(filePath).retain()
afterEach ->
buffer2?.release()
project2?.destroy()
buffer2?.destroy()
describe "when the serialized buffer had no unsaved changes", ->
it "loads the current contents of the file at the serialized path", ->
expect(buffer.isModified()).toBeFalsy()
buffer2 = buffer.testPersistence()
buffer2 = buffer.testSerialization()
waitsForPromise ->
buffer2.load()
@@ -1003,8 +1002,7 @@ describe 'TextBuffer', ->
buffer.setText("BUFFER CHANGE")
fs.writeFileSync(filePath, "DISK CHANGE")
project2 = atom.project.testPersistence()
buffer2 = project2.getBuffers()[0]
buffer2 = buffer.testSerialization()
waitsFor ->
buffer2.cachedDiskContents
@@ -1020,9 +1018,7 @@ describe 'TextBuffer', ->
buffer.setText("abc")
buffer.retain()
buffer.getState().serializeForPersistence()
project2 = atom.project.testPersistence()
buffer2 = project2.getBuffers()[0]
buffer2 = buffer.testSerialization()
waitsForPromise ->
buffer2.load()
@@ -1036,15 +1032,11 @@ describe 'TextBuffer', ->
describe "when the serialized buffer was unsaved and had no path", ->
it "restores the previous unsaved state of the buffer", ->
buffer.release()
buffer.destroy()
buffer = atom.project.bufferForPathSync()
buffer.setText("abc")
state = buffer.getState().clone()
expect(state.get('path')).toBeUndefined()
expect(state.getObject('text')).toBe 'abc'
buffer2 = atom.project.addBuffer(new TextBuffer(state))
buffer2 = buffer.testSerialization()
expect(buffer2.getPath()).toBeUndefined()
expect(buffer2.getText()).toBe("abc")

View File

@@ -30,9 +30,6 @@ class CursorView extends View
@cursor.on 'destroyed.cursor-view', =>
@needsRemoval = true
if @cursor.marker.isRemote()
@addClass("site-#{@cursor.marker.getOriginSiteId()}")
beforeRemove: ->
@editorView.removeCursorView(this)
@cursor.off('.cursor-view')

View File

@@ -144,15 +144,6 @@ class DisplayBufferMarker
isDestroyed: ->
@bufferMarker.isDestroyed()
getOriginSiteId: ->
@bufferMarker.getOriginSiteId()
isLocal: ->
@bufferMarker.isLocal()
isRemote: ->
@bufferMarker.isRemote()
getAttributes: ->
@bufferMarker.getAttributes()

View File

@@ -1215,8 +1215,8 @@ class EditorView extends View
@scrollTop(editorScrollTop)
@scrollLeft(editorScrollLeft)
@setSoftWrap(@editor.getSoftWrap())
@newCursors = @editor.getAllCursors()
@newSelections = @editor.getAllSelections()
@newCursors = @editor.getCursors()
@newSelections = @editor.getSelections()
@updateDisplay(suppressAutoScroll: true)
requestDisplayUpdate: ->

View File

@@ -27,11 +27,6 @@ TextMateScopeSelector = require('first-mate').ScopeSelector
# atom.workspaceView.eachEditorView (editorView) ->
# editorView.insertText('Hello World')
# ```
#
# ## Collaboration builtin
#
# FIXME: Describe how there are both local and remote cursors and selections and
# why that is.
module.exports =
class Editor extends Model
@@ -47,9 +42,7 @@ class Editor extends Model
buffer: null
languageMode: null
cursors: null
remoteCursors: null
selections: null
remoteSelections: null
suppressSelectionMerging: false
constructor: ->
@@ -64,9 +57,7 @@ class Editor extends Model
return
@cursors = []
@remoteCursors = []
@selections = []
@remoteSelections = []
unless @displayBuffer?
@displayBuffer = new DisplayBuffer({@buffer, @tabLength, @softWrap})
@@ -86,7 +77,7 @@ class Editor extends Model
if @initialLine
position = [@initialLine, 0]
else
position = _.last(@getRemoteCursors())?.getBufferPosition() ? [0, 0]
position = [0, 0]
@addCursorAtBufferPosition(position)
@languageMode = new LanguageMode(this, @buffer.getExtension())
@@ -825,11 +816,6 @@ class Editor extends Model
hasMultipleCursors: ->
@getCursors().length > 1
# Public: Returns an Array of all {Cursor}s, including cursors representing
# remote users.
getAllCursors: ->
@getCursors().concat(@getRemoteCursors())
# Public: Returns an Array of all local {Cursor}s.
getCursors: -> new Array(@cursors...)
@@ -837,9 +823,6 @@ class Editor extends Model
getCursor: ->
_.last(@cursors)
# Public: Returns an Array of all remove {Cursor}s.
getRemoteCursors: -> new Array(@remoteCursors...)
# Public: Adds and returns a cursor at the given screen position.
addCursorAtScreenPosition: (screenPosition) ->
@markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
@@ -854,10 +837,7 @@ class Editor extends Model
# position.
addCursor: (marker) ->
cursor = new Cursor(editor: this, marker: marker)
if marker.isLocal()
@cursors.push(cursor)
else
@remoteCursors.push(cursor)
@cursors.push(cursor)
@emit 'cursor-added', cursor
cursor
@@ -878,12 +858,7 @@ class Editor extends Model
@destroyFoldsIntersectingBufferRange(marker.getBufferRange())
cursor = @addCursor(marker)
selection = new Selection(_.extend({editor: this, marker, cursor}, options))
if marker.isLocal()
@selections.push(selection)
else
@remoteSelections.push(selection)
@selections.push(selection)
selectionBufferRange = selection.getBufferRange()
@mergeIntersectingSelections()
if selection.destroyed
@@ -941,10 +916,7 @@ class Editor extends Model
#
# * selection - The {Selection} to remove.
removeSelection: (selection) ->
if selection.isLocal()
_.remove(@selections, selection)
else
_.remove(@remoteSelections, selection)
_.remove(@selections, selection)
# Public: Clears every selection.
#
@@ -964,10 +936,6 @@ class Editor extends Model
else
false
# Public: Returns all selections, including remote selections.
getAllSelections: ->
@getSelections().concat(@getRemoteSelections())
# Public: Gets all local selections.
#
# Returns an {Array} of {Selection}s.
@@ -982,21 +950,12 @@ class Editor extends Model
getLastSelection: ->
_.last(@selections)
# Public: Returns all remote selections.
getRemoteSelections: -> new Array(@remoteSelections...)
# Public: Gets all local selections, ordered by their position in the buffer.
#
# Returns an {Array} of {Selection}s.
getSelectionsOrderedByBufferPosition: ->
@getSelections().sort (a, b) -> a.compare(b)
# Public: Gets all remote selections, ordered by their position in the buffer.
#
# Returns an {Array} of {Selection}s.
getRemoteSelectionsOrderedByBufferPosition: ->
@getRemoteSelections().sort (a, b) -> a.compare(b)
# Public: Gets the very last local selection in the buffer.
#
# Returns a {Selection}.
@@ -1066,12 +1025,6 @@ class Editor extends Model
getSelectedBufferRanges: ->
selection.getBufferRange() for selection in @getSelectionsOrderedByBufferPosition()
# Public: Gets an Array of buffer {Range}s of all the remote {Selection}s.
#
# Sorted by their position in the file itself.
getRemoteSelectedBufferRanges: ->
selection.getBufferRange() for selection in @getRemoteSelectionsOrderedByBufferPosition()
# Public: Returns the selected text of the most recently added local {Selection}.
getSelectedText: ->
@getLastSelection().getText()

View File

@@ -38,6 +38,7 @@ class Fold
getBufferRange: ({includeNewline}={}) ->
range = @marker.getRange()
if includeNewline
range = range.copy()
range.end.row++
range.end.column = 0
range

View File

@@ -71,7 +71,7 @@ class Git
@refreshStatus()
if @project?
@subscribe @project.buffers.onEach (buffer) => @subscribeToBuffer(buffer)
@subscribe @project.eachBuffer (buffer) => @subscribeToBuffer(buffer)
# Private: Subscribes to buffer events.
subscribeToBuffer: (buffer) ->

View File

@@ -20,7 +20,6 @@ module.exports =
class Project extends Model
@properties
buffers: []
path: null
# Public: Find the local path for the given repository URL.
@@ -29,10 +28,18 @@ class Project extends Model
repoName = repoName.replace(/\.git$/, '')
path.join(atom.config.get('core.projectHome'), repoName)
constructor: ->
super
if @state?
@buffers = @state.get('buffers').map (state) -> atom.deserializers.deserialize(state)
# Private: Called by telepath.
created: ->
for buffer in @buffers.getValues()
buffer.once 'destroyed', (buffer) => @removeBuffer(buffer) if @isAlive()
@buffers ?= []
for buffer in @buffers
do (buffer) =>
buffer.once 'destroyed', => @removeBuffer(buffer) if @isAlive()
@openers = []
@editors = []
@@ -40,6 +47,7 @@ class Project extends Model
# Private: Called by telepath.
willBePersisted: ->
@state.set('buffers', @buffers.map (buffer) -> buffer.serialize())
@destroyUnretainedBuffers()
# Public: Register an opener for project files.
@@ -177,7 +185,7 @@ class Project extends Model
#
# Returns an {Array} of {TextBuffer}s.
getBuffers: ->
new Array(@buffers.getValues()...)
@buffers.slice()
# Private: Is the buffer for the given path modified?
isPathModified: (filePath) ->
@@ -185,7 +193,7 @@ class Project extends Model
# Private:
findBufferForPath: (filePath) ->
_.find @buffers.getValues(), (buffer) -> buffer.getPath() == filePath
_.find @buffers, (buffer) -> buffer.getPath() == filePath
# Private: Only to be used in specs
bufferForPathSync: (filePath) ->
@@ -233,10 +241,11 @@ class Project extends Model
# Private:
addBuffer: (buffer, options={}) ->
@addBufferAtIndex(buffer, @buffers.length, options)
buffer.once 'destroyed', => @removeBuffer(buffer)
# Private:
addBufferAtIndex: (buffer, index, options={}) ->
buffer = @buffers.insert(index, buffer)
@buffers.splice(index, 0, buffer)
buffer.once 'destroyed', => @removeBuffer(buffer) if @isAlive()
@emit 'buffer-created', buffer
buffer
@@ -285,7 +294,7 @@ class Project extends Model
task.on 'scan:paths-searched', (numberOfPathsSearched) ->
options.onPathsSearched(numberOfPathsSearched)
for buffer in @buffers.getValues() when buffer.isModified()
for buffer in @getBuffers() when buffer.isModified()
filePath = buffer.getPath()
matches = []
buffer.scan regex, (match) -> matches.push match
@@ -306,7 +315,7 @@ class Project extends Model
replace: (regex, replacementText, filePaths, iterator) ->
deferred = Q.defer()
openPaths = (buffer.getPath() for buffer in @buffers.getValues())
openPaths = (buffer.getPath() for buffer in @getBuffers())
outOfProcessPaths = _.difference(filePaths, openPaths)
inProcessFinished = !openPaths.length
@@ -324,7 +333,7 @@ class Project extends Model
task.on 'replace:path-replaced', iterator
for buffer in @buffers.getValues()
for buffer in @getBuffers()
continue unless buffer.getPath() in filePaths
replacements = buffer.replace(regex, replacementText, iterator)
iterator({filePath: buffer.getPath(), replacements}) if replacements

View File

@@ -18,9 +18,6 @@ class SelectionView extends View
@needsRemoval = true
@editorView.requestDisplayUpdate()
if @selection.marker.isRemote()
@addClass("site-#{@selection.marker.getOriginSiteId()}")
updateDisplay: ->
@clearRegions()
range = @getScreenRange()

View File

@@ -601,14 +601,6 @@ class Selection
compare: (otherSelection) ->
@getBufferRange().compare(otherSelection.getBufferRange())
# Public: Returns true if it was locally created.
isLocal: ->
@marker.isLocal()
# Public: Returns true if it was created remotely.
isRemote: ->
@marker.isRemote()
# Private:
screenRangeChanged: ->
screenRange = @getScreenRange()

View File

@@ -2,24 +2,24 @@ _ = require 'underscore-plus'
diff = require 'diff'
Q = require 'q'
{P} = require 'scandal'
telepath = require 'telepath'
Serializable = require 'nostalgia'
TextBufferCore = require 'text-buffer'
{Point, Range} = TextBufferCore
{Subscriber, Emitter} = require 'emissary'
File = require './file'
{Point, Range} = telepath
# Private: Represents the contents of a file.
#
# The `TextBuffer` is often associated with a {File}. However, this is not always
# the case, as a `TextBuffer` could be an unsaved chunk of text.
module.exports =
class TextBuffer extends telepath.Model
@properties
text: -> new telepath.String('', replicated: false)
filePath: null
relativePath: null
modifiedWhenLastPersisted: false
digestWhenLastPersisted: null
class TextBuffer
atom.deserializers.add(this)
Serializable.includeInto(this)
Subscriber.includeInto(this)
Emitter.includeInto(this)
stoppedChangingDelay: 300
stoppedChangingTimeout: null
@@ -29,28 +29,33 @@ class TextBuffer extends telepath.Model
file: null
refcount: 0
constructor: ->
super
@loadWhenAttached = @getState()?
# Private: Called by telepath.
created: ->
constructor: ({@core, text, filePath, @modifiedWhenLastPersisted, @digestWhenLastPersisted, loadWhenAttached}={}) ->
@core ?= new TextBufferCore(text: text ? '')
@loaded = false
@modifiedWhenLastPersisted ?= false
@useSerializedText = @modifiedWhenLastPersisted != false
@subscribe @text, 'changed', @handleTextChange
@subscribe @text, 'marker-created', (marker) => @emit 'marker-created', marker
@subscribe @text, 'markers-updated', => @emit 'markers-updated'
@subscribe @core, 'changed', @handleTextChange
@subscribe @core, 'marker-created', (marker) => @emit 'marker-created', marker
@subscribe @core, 'markers-updated', => @emit 'markers-updated'
@setPath(@filePath)
@setPath(filePath)
@load() if @loadWhenAttached
@load() if loadWhenAttached
# Private: Called by telepath.
willBePersisted: ->
@modifiedWhenLastPersisted = @isModified()
@digestWhenLastPersisted = @file?.getDigest()
# Private:
serializeParams: ->
core: @core.serialize()
filePath: @getPath()
modifiedWhenLastPersisted: @isModified()
digestWhenLastPersisted: @file?.getDigest()
# Private:
deserializeParams: (params) ->
params.loadWhenAttached = true
params.core = TextBufferCore.deserialize(params.core)
params
loadSync: ->
@updateCachedDiskContentsSync()
@@ -66,7 +71,7 @@ class TextBuffer extends telepath.Model
@emitModifiedStatusChanged(true)
else
@reload()
@text.clearUndoStack()
@core.clearUndoStack()
this
### Internal ###
@@ -74,16 +79,20 @@ class TextBuffer extends telepath.Model
handleTextChange: (event) =>
@cachedMemoryContents = null
@conflict = false if @conflict and !@isModified()
bufferChangeEvent = _.pick(event, 'oldRange', 'newRange', 'oldText', 'newText')
@emit 'changed', bufferChangeEvent
@emit 'changed', event
@scheduleModifiedEvents()
destroyed: ->
unless @alreadyDestroyed
destroy: ->
unless @destroyed
@cancelStoppedChangingTimeout()
@file?.off()
@unsubscribe()
@alreadyDestroyed = true
@destroyed = true
@emit 'destroyed'
isAlive: -> not @destroyed
isDestroyed: -> @destroyed
isRetained: -> @refcount > 0
@@ -261,13 +270,13 @@ class TextBuffer extends telepath.Model
#
# Returns a {String} of the combined lines.
getTextInRange: (range) ->
@text.getTextInRange(@clipRange(range))
@core.getTextInRange(@clipRange(range))
# Gets all the lines in a file.
#
# Returns an {Array} of {String}s.
getLines: ->
@text.getLines()
@core.getLines()
# Given a row, returns the line of text.
#
@@ -275,7 +284,7 @@ class TextBuffer extends telepath.Model
#
# Returns a {String}.
lineForRow: (row) ->
@text.lineForRow(row)
@core.lineForRow(row)
# Given a row, returns its line ending.
#
@@ -283,7 +292,7 @@ class TextBuffer extends telepath.Model
#
# Returns a {String}, or `undefined` if `row` is the final row.
lineEndingForRow: (row) ->
@text.lineEndingForRow(row)
@core.lineEndingForRow(row)
suggestedLineEndingForRow: (row) ->
if row is @getLastRow()
@@ -297,7 +306,7 @@ class TextBuffer extends telepath.Model
#
# Returns a {Number}.
lineLengthForRow: (row) ->
@text.lineLengthForRow(row)
@core.lineLengthForRow(row)
# Given a row, returns the length of the line ending
#
@@ -324,13 +333,13 @@ class TextBuffer extends telepath.Model
#
# Returns a {Number}.
getLineCount: ->
@text.getLineCount()
@core.getLineCount()
# Gets the row number of the last line.
#
# Returns a {Number}.
getLastRow: ->
@getLineCount() - 1
@core.getLastRow()
# Finds the last line in the current buffer.
#
@@ -346,10 +355,10 @@ class TextBuffer extends telepath.Model
new Point(lastRow, @lineLengthForRow(lastRow))
characterIndexForPosition: (position) ->
@text.indexForPoint(@clipPosition(position))
@core.offsetForPosition(@clipPosition(position))
positionForCharacterIndex: (index) ->
@text.pointForIndex(index)
@core.positionForOffset(index)
# Given a row, this deletes it from the buffer.
#
@@ -402,7 +411,7 @@ class TextBuffer extends telepath.Model
#
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
clipPosition: (position) ->
@text.clipPosition(position)
@core.clipPosition(position)
# Given a range, this clips it to a real range.
#
@@ -418,10 +427,10 @@ class TextBuffer extends telepath.Model
new Range(@clipPosition(range.start), @clipPosition(range.end))
undo: ->
@text.undo()
@core.undo()
redo: ->
@text.redo()
@core.redo()
# Saves the buffer.
save: ->
@@ -461,15 +470,15 @@ class TextBuffer extends telepath.Model
# Identifies if a buffer is empty.
#
# Returns a {Boolean}.
isEmpty: -> @text.isEmpty()
isEmpty: -> @core.isEmpty()
# Returns all valid {StringMarker}s on the buffer.
getMarkers: ->
@text.getMarkers()
@core.getMarkers()
# Returns the {StringMarker} with the given id.
getMarker: (id) ->
@text.getMarker(id)
@core.getMarker(id)
destroyMarker: (id) ->
@getMarker(id)?.destroy()
@@ -478,7 +487,7 @@ class TextBuffer extends telepath.Model
#
# Returns a {String} marker-identifier
findMarker: (attributes) ->
@text.findMarker(attributes)
@core.findMarker(attributes)
# Public: Finds all markers satisfying the given attributes
#
@@ -489,13 +498,13 @@ class TextBuffer extends telepath.Model
#
# Returns an {Array} of {StringMarker}s
findMarkers: (attributes) ->
@text.findMarkers(attributes)
@core.findMarkers(attributes)
# Retrieves the quantity of markers in a buffer.
#
# Returns a {Number}.
getMarkerCount: ->
@text.getMarkers().length
@core.getMarkers().length
# Constructs a new marker at a given range.
#
@@ -509,7 +518,7 @@ class TextBuffer extends telepath.Model
#
# Returns a {Number} representing the new marker's ID.
markRange: (range, options={}) ->
@text.markRange(range, options)
@core.markRange(range, options)
# Constructs a new marker at a given position.
#
@@ -518,7 +527,7 @@ class TextBuffer extends telepath.Model
#
# Returns a {Number} representing the new marker's ID.
markPosition: (position, options) ->
@text.markPosition(position, options)
@core.markPosition(position, options)
# Identifies if a character sequence is within a certain range.
#
@@ -677,18 +686,18 @@ class TextBuffer extends telepath.Model
### Internal ###
transact: (fn) -> @text.transact fn
transact: (fn) -> @core.transact fn
beginTransaction: -> @text.beginTransaction()
beginTransaction: -> @core.beginTransaction()
commitTransaction: -> @text.commitTransaction()
commitTransaction: -> @core.commitTransaction()
abortTransaction: -> @text.abortTransaction()
abortTransaction: -> @core.abortTransaction()
change: (oldRange, newText, options={}) ->
oldRange = @clipRange(oldRange)
newText = @normalizeLineEndings(oldRange.start.row, newText) if options.normalizeLineEndings ? true
@text.setTextInRange(oldRange, newText, options)
@core.setTextInRange(oldRange, newText, options)
normalizeLineEndings: (startRow, text) ->
if lineEnding = @suggestedLineEndingForRow(startRow)