Relativize buffer paths for replication

TextBuffers now maintain a reference to their containing project to
make it easier to test replication of buffers between environments
without worrying about the value of the `project` global.

We're also starting the process of moving the `git` global into Project
as the `Project.repository` property.
This commit is contained in:
Kevin Sawicki & Nathan Sobo
2013-07-22 14:13:55 -07:00
parent d051cbe0f6
commit fa59aafffb
16 changed files with 94 additions and 52 deletions

View File

@@ -1,20 +1,29 @@
{createSite} = require 'telepath' {createSite} = require 'telepath'
fsUtils = require 'fs-utils' fsUtils = require 'fs-utils'
Project = require 'project' Project = require 'project'
Git = require 'git'
describe "Project replication", -> describe "Project replication", ->
[project1, project2] = [] [doc1, doc2, project1, project2] = []
beforeEach -> beforeEach ->
project1 = new Project(fsUtils.resolveOnLoadPath('fixtures/dir')) # pretend that home-1/project and home-2/project map to the same git repository url
project1.bufferForPath('a') spyOn(Git, 'open').andReturn
project1.bufferForPath('a') getOriginUrl: -> 'git://server/project.git'
destroy: ->
config.set('core.projectHome', fsUtils.resolveOnLoadPath('fixtures/replication/home-1'))
project1 = new Project(fsUtils.resolveOnLoadPath('fixtures/replication/home-1/project'))
project1.bufferForPath('file-1.txt')
project1.bufferForPath('file-1.txt')
expect(project1.getBuffers().length).toBe 1 expect(project1.getBuffers().length).toBe 1
doc1 = project1.getState() doc1 = project1.getState()
doc2 = doc1.clone(createSite(2)) doc2 = doc1.clone(createSite(2))
doc1.connect(doc2) connection = doc1.connect(doc2)
# pretend we're bootstrapping a joining window
config.set('core.projectHome', fsUtils.resolveOnLoadPath('fixtures/replication/home-2'))
project2 = deserialize(doc2) project2 = deserialize(doc2)
afterEach -> afterEach ->
@@ -22,16 +31,19 @@ describe "Project replication", ->
project2.destroy() project2.destroy()
it "replicates the initial path and open buffers of the project", -> it "replicates the initial path and open buffers of the project", ->
expect(project2.getPath()).toBe project1.getPath() expect(project2.getPath()).not.toBe project1.getPath()
expect(project2.getBuffers().length).toBe 1 expect(project2.getBuffers().length).toBe 1
expect(project2.getBuffers()[0].getPath()).toBe project1.getBuffers()[0].getPath() expect(project2.getBuffers()[0].getRelativePath()).toBe project1.getBuffers()[0].getRelativePath()
expect(project2.getBuffers()[0].getPath()).not.toBe project1.getBuffers()[0].getPath()
it "replicates insertion and removal of open buffers", -> it "replicates insertion and removal of open buffers", ->
bufferB = project2.bufferForPath('b') project2.bufferForPath('file-2.txt')
expect(project1.getBuffers().length).toBe 2 expect(project1.getBuffers().length).toBe 2
expect(project2.getBuffers()[0].getPath()).toBe project1.getBuffers()[0].getPath() expect(project2.getBuffers()[0].getRelativePath()).toBe project1.getBuffers()[0].getRelativePath()
expect(project2.getBuffers()[1].getPath()).toBe project1.getBuffers()[1].getPath() expect(project2.getBuffers()[1].getRelativePath()).toBe project1.getBuffers()[1].getRelativePath()
expect(project2.getBuffers()[0].getRelativePath()).not.toBe project1.getBuffers()[0].getPath()
expect(project2.getBuffers()[1].getRelativePath()).not.toBe project1.getBuffers()[1].getPath()
project1.removeBuffer(project1.bufferForPath('b')) project1.removeBuffer(project1.bufferForPath('file-2.txt'))
expect(project1.getBuffers().length).toBe 1 expect(project1.getBuffers().length).toBe 1
expect(project2.getBuffers()[0].getPath()).toBe project1.getBuffers()[0].getPath() expect(project2.getBuffers()[0].getRelativePath()).toBe project1.getBuffers()[0].getRelativePath()

View File

@@ -1,4 +1,3 @@
Buffer = require 'text-buffer'
EditSession = require 'edit-session' EditSession = require 'edit-session'
{Range} = require 'telepath' {Range} = require 'telepath'
@@ -6,7 +5,7 @@ describe "Selection", ->
[buffer, editSession, selection] = [] [buffer, editSession, selection] = []
beforeEach -> beforeEach ->
buffer = new Buffer(require.resolve('fixtures/sample.js')) buffer = project.buildBuffer('sample.js')
editSession = new EditSession(buffer: buffer, tabLength: 2) editSession = new EditSession(buffer: buffer, tabLength: 2)
selection = editSession.getSelection() selection = editSession.getSelection()

View File

@@ -1,16 +1,15 @@
{createSite} = require 'telepath' {createSite} = require 'telepath'
TextBuffer = require 'text-buffer'
describe "TextBuffer replication", -> describe "TextBuffer replication", ->
[buffer1, buffer2] = [] [buffer1, buffer2] = []
beforeEach -> beforeEach ->
buffer1 = new TextBuffer(project.resolve('sample.js')) buffer1 = project.buildBuffer('sample.js')
buffer1.insert([0, 0], 'changed\n') buffer1.insert([0, 0], 'changed\n')
doc1 = buffer1.getState() doc1 = buffer1.getState()
doc2 = doc1.clone(createSite(2)) doc2 = doc1.clone(createSite(2))
doc1.connect(doc2) doc1.connect(doc2)
buffer2 = deserialize(doc2) buffer2 = deserialize(doc2, {project})
afterEach -> afterEach ->
buffer1.destroy() buffer1.destroy()

View File

@@ -1,5 +1,4 @@
TokenizedBuffer = require 'tokenized-buffer' TokenizedBuffer = require 'tokenized-buffer'
Buffer = require 'text-buffer'
{Range} = require 'telepath' {Range} = require 'telepath'
_ = require 'underscore' _ = require 'underscore'
@@ -336,7 +335,7 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains surrogate pairs", -> describe "when the buffer contains surrogate pairs", ->
beforeEach -> beforeEach ->
atom.activatePackage('javascript-tmbundle', sync: true) atom.activatePackage('javascript-tmbundle', sync: true)
buffer = new Buffer 'sample-with-pairs.js', """ buffer = project.buildBuffer 'sample-with-pairs.js', """
'abc\uD835\uDF97def' 'abc\uD835\uDF97def'
//\uD835\uDF97xyz //\uD835\uDF97xyz
""" """

View File

@@ -1,12 +1,11 @@
UndoManager = require 'undo-manager' UndoManager = require 'undo-manager'
Buffer = require 'text-buffer'
{Range} = require 'telepath' {Range} = require 'telepath'
describe "UndoManager", -> describe "UndoManager", ->
[buffer, undoManager] = [] [buffer, undoManager] = []
beforeEach -> beforeEach ->
buffer = new Buffer(require.resolve('fixtures/sample.js')) buffer = project.buildBuffer('sample.js')
undoManager = buffer.undoManager undoManager = buffer.undoManager
afterEach -> afterEach ->

View File

@@ -0,0 +1 @@
Hello World!

View File

@@ -0,0 +1 @@
Goodbye World!

View File

@@ -0,0 +1 @@
Hello World!

View File

@@ -0,0 +1 @@
Goodbye World!

View File

@@ -8,6 +8,7 @@ remote = require 'remote'
crypto = require 'crypto' crypto = require 'crypto'
path = require 'path' path = require 'path'
dialog = remote.require 'dialog' dialog = remote.require 'dialog'
app = remote.require 'app'
telepath = require 'telepath' telepath = require 'telepath'
window.atom = window.atom =
@@ -224,7 +225,7 @@ window.atom =
remote.getCurrentWindow().hide() remote.getCurrentWindow().hide()
exit: (status) -> exit: (status) ->
remote.require('app').exit(status) app.exit(status)
toggleFullScreen: -> toggleFullScreen: ->
@setFullScreen(!@isFullScreen()) @setFullScreen(!@isFullScreen())
@@ -235,6 +236,9 @@ window.atom =
isFullScreen: -> isFullScreen: ->
remote.getCurrentWindow().isFullScreen() remote.getCurrentWindow().isFullScreen()
getHomeDirPath: ->
app.getHomeDir()
getWindowStatePath: -> getWindowStatePath: ->
switch @windowMode switch @windowMode
when 'config', 'spec' when 'config', 'spec'

View File

@@ -1,5 +1,5 @@
{View, $$} = require 'space-pen' {View, $$} = require 'space-pen'
Buffer = require 'text-buffer' TextBuffer = require 'text-buffer'
Gutter = require 'gutter' Gutter = require 'gutter'
{Point, Range} = require 'telepath' {Point, Range} = require 'telepath'
EditSession = require 'edit-session' EditSession = require 'edit-session'
@@ -96,7 +96,7 @@ class Editor extends View
@edit(editSession) @edit(editSession)
else if @mini else if @mini
@edit(new EditSession @edit(new EditSession
buffer: new Buffer() buffer: new TextBuffer
softWrap: false softWrap: false
tabLength: 2 tabLength: 2
softTabs: true softTabs: true

View File

@@ -223,6 +223,8 @@ class Git
getConfigValue: (key) -> @getRepo().getConfigValue(key) getConfigValue: (key) -> @getRepo().getConfigValue(key)
getOriginUrl: -> @getConfigValue('remote.origin.url')
getReferenceTarget: (reference) -> @getRepo().getReferenceTarget(reference) getReferenceTarget: (reference) -> @getRepo().getReferenceTarget(reference)
getAheadBehindCount: (reference) -> @getRepo().getAheadBehindCount(reference) getAheadBehindCount: (reference) -> @getRepo().getAheadBehindCount(reference)

View File

@@ -1,14 +1,17 @@
fsUtils = require 'fs-utils' fsUtils = require 'fs-utils'
path = require 'path' path = require 'path'
url = require 'url'
_ = require 'underscore' _ = require 'underscore'
$ = require 'jquery' $ = require 'jquery'
telepath = require 'telepath' telepath = require 'telepath'
{Range} = telepath {Range} = telepath
Buffer = require 'text-buffer' TextBuffer = require 'text-buffer'
EditSession = require 'edit-session' EditSession = require 'edit-session'
EventEmitter = require 'event-emitter' EventEmitter = require 'event-emitter'
Directory = require 'directory' Directory = require 'directory'
BufferedProcess = require 'buffered-process' BufferedProcess = require 'buffered-process'
Git = require 'git'
# Public: Represents a project that's opened in Atom. # Public: Represents a project that's opened in Atom.
# #
@@ -43,6 +46,8 @@ class Project
destroy: -> destroy: ->
editSession.destroy() for editSession in @getEditSessions() editSession.destroy() for editSession in @getEditSessions()
buffer.release() for buffer in @getBuffers() buffer.release() for buffer in @getBuffers()
window.git?.destroy()
delete window.git
### Public ### ### Public ###
@@ -55,9 +60,13 @@ class Project
if pathOrState instanceof telepath.Document if pathOrState instanceof telepath.Document
@state = pathOrState @state = pathOrState
@setPath(pathOrState.get('path')) if projectPath = @state.get('path')
@setPath(projectPath)
else
@setPath(@pathForRepositoryUrl(@state.get('repoUrl')))
@state.get('buffers').each (bufferState) => @state.get('buffers').each (bufferState) =>
if buffer = deserialize(bufferState) if buffer = deserialize(bufferState, project: this)
@addBuffer(buffer, updateState: false) @addBuffer(buffer, updateState: false)
else else
@state = telepath.Document.create(deserializer: @constructor.name, version: @constructor.version, buffers: []) @state = telepath.Document.create(deserializer: @constructor.name, version: @constructor.version, buffers: [])
@@ -69,10 +78,16 @@ class Project
for removedBuffer in removed for removedBuffer in removed
@removeBufferAtIndex(index, updateState: false) @removeBufferAtIndex(index, updateState: false)
for insertedBuffer, i in inserted for insertedBuffer, i in inserted
@addBufferAtIndex(deserialize(insertedBuffer), index + i, updateState: false) @addBufferAtIndex(deserialize(insertedBuffer, project: this), index + i, updateState: false)
pathForRepositoryUrl: (repoUrl) ->
[repoName] = url.parse(repoUrl).path.split('/')[-1..]
repoName = repoName.replace(/\.git$/, '')
path.join(config.get('core.projectHome'), repoName)
serialize: -> serialize: ->
state = @state.clone() state = @state.clone()
state.set('path', @getPath())
state.set('buffers', buffer.serialize() for buffer in @getBuffers()) state.set('buffers', buffer.serialize() for buffer in @getBuffers())
state state
@@ -93,10 +108,15 @@ class Project
if projectPath? if projectPath?
directory = if fsUtils.isDirectorySync(projectPath) then projectPath else path.dirname(projectPath) directory = if fsUtils.isDirectorySync(projectPath) then projectPath else path.dirname(projectPath)
@rootDirectory = new Directory(directory) @rootDirectory = new Directory(directory)
window.git = Git.open(projectPath)
else else
@rootDirectory = null @rootDirectory = null
window.git?.destroy()
delete window.git
if originUrl = window.git?.getOriginUrl()
@state.set('repoUrl', originUrl)
@state.set('path', projectPath)
@trigger "path-changed" @trigger "path-changed"
# Retrieves the name of the root directory. # Retrieves the name of the root directory.
@@ -246,8 +266,8 @@ class Project
# text - The {String} text to use as a buffer # text - The {String} text to use as a buffer
# #
# Returns the {Buffer}. # Returns the {Buffer}.
buildBuffer: (filePath, text) -> buildBuffer: (filePath, initialText) ->
buffer = new Buffer(filePath, text) buffer = new TextBuffer({project: this, filePath, initialText})
@addBuffer(buffer) @addBuffer(buffer)
@trigger 'buffer-created', buffer @trigger 'buffer-created', buffer
buffer buffer
@@ -263,7 +283,8 @@ class Project
# #
# Returns the removed {Buffer}. # Returns the removed {Buffer}.
removeBuffer: (buffer) -> removeBuffer: (buffer) ->
@removeBufferAtIndex(@buffers.indexOf(buffer)) index = @buffers.indexOf(buffer)
@removeBufferAtIndex(index) unless index is -1
removeBufferAtIndex: (index, options={}) -> removeBufferAtIndex: (index, options={}) ->
[buffer] = @buffers.splice(index, 1) [buffer] = @buffers.splice(index, 1)

View File

@@ -1,3 +1,4 @@
path = require 'path'
$ = require 'jquery' $ = require 'jquery'
{$$} = require 'space-pen' {$$} = require 'space-pen'
fsUtils = require 'fs-utils' fsUtils = require 'fs-utils'
@@ -24,6 +25,7 @@ class RootView extends View
ignoredNames: [".git", ".svn", ".DS_Store"] ignoredNames: [".git", ".svn", ".DS_Store"]
disabledPackages: [] disabledPackages: []
themes: ['atom-dark-ui', 'atom-dark-syntax'] themes: ['atom-dark-ui', 'atom-dark-syntax']
projectHome: path.join(atom.getHomeDirPath(), 'github')
### Internal ### ### Internal ###
@acceptsDocuments: true @acceptsDocuments: true

View File

@@ -18,8 +18,8 @@ class TextBuffer
@version: 2 @version: 2
registerDeserializer(this) registerDeserializer(this)
@deserialize: (state) -> @deserialize: (state, params) ->
new TextBuffer(state) new this(state, params)
stoppedChangingDelay: 300 stoppedChangingDelay: 300
stoppedChangingTimeout: null stoppedChangingTimeout: null
@@ -34,14 +34,15 @@ class TextBuffer
# #
# path - A {String} representing the file path # path - A {String} representing the file path
# initialText - A {String} setting the starting text # initialText - A {String} setting the starting text
constructor: (args...) -> constructor: (optionsOrState={}, params={}) ->
if args[0] instanceof telepath.Document if optionsOrState instanceof telepath.Document
@state = args[0] @state = optionsOrState
{@project} = params
@text = @state.get('text') @text = @state.get('text')
path = @state.get('path') filePath = @state.get('relativePath')
@id = @state.get('id') @id = @state.get('id')
else else
[path, initialText] = args {@project, filePath, initialText} = optionsOrState
@text = telepath.Document.create(initialText, shareStrings: true) if initialText @text = telepath.Document.create(initialText, shareStrings: true) if initialText
@id = guid.create().toString() @id = guid.create().toString()
@state = telepath.Document.create @state = telepath.Document.create
@@ -49,13 +50,13 @@ class TextBuffer
deserializer: @constructor.name deserializer: @constructor.name
version: @constructor.version version: @constructor.version
if path if filePath
@setPath(path) @setPath(@project.resolve(filePath))
if @text if @text
@updateCachedDiskContents() @updateCachedDiskContents()
else else
@text = telepath.Document.create('', shareStrings: true) @text = telepath.Document.create('', shareStrings: true)
@reload() if fsUtils.exists(path) @reload() if fsUtils.exists(filePath)
else else
@text ?= telepath.Document.create('', shareStrings: true) @text ?= telepath.Document.create('', shareStrings: true)
@@ -148,7 +149,13 @@ class TextBuffer
@file?.getPath() @file?.getPath()
getUri: -> getUri: ->
project?.relativize(@getPath()) ? @getPath() @getRelativePath()
getRelativePath: ->
@state.get('relativePath')
setRelativePath: (relativePath) ->
@setPath(@project.resolve(relativePath))
# Sets the path for the file. # Sets the path for the file.
# #
@@ -160,9 +167,7 @@ class TextBuffer
@file = new File(path) @file = new File(path)
@file.read() if @file.exists() @file.read() if @file.exists()
@subscribeToFile() @subscribeToFile()
@state.set('relativePath', @project.relativize(path))
@state.set('path', path)
@trigger "path-changed", this @trigger "path-changed", this
# Retrieves the current buffer's file extension. # Retrieves the current buffer's file extension.

View File

@@ -132,14 +132,10 @@ window.deserializeEditorWindow = ->
$(rootViewParentSelector).append(rootView) $(rootViewParentSelector).append(rootView)
window.git = Git.open(project.getPath())
project.on 'path-changed', -> project.on 'path-changed', ->
projectPath = project.getPath() projectPath = project.getPath()
atom.getLoadSettings().initialPath = projectPath atom.getLoadSettings().initialPath = projectPath
window.git?.destroy()
window.git = Git.open(projectPath)
window.deserializeConfigWindow = -> window.deserializeConfigWindow = ->
ConfigView = require 'config-view' ConfigView = require 'config-view'
window.configView = deserialize(atom.getWindowState('configView')) ? new ConfigView() window.configView = deserialize(atom.getWindowState('configView')) ? new ConfigView()
@@ -232,13 +228,13 @@ window.registerDeferredDeserializer = (name, fn) ->
window.unregisterDeserializer = (klass) -> window.unregisterDeserializer = (klass) ->
delete deserializers[klass.name] delete deserializers[klass.name]
window.deserialize = (state) -> window.deserialize = (state, params) ->
if deserializer = getDeserializer(state) if deserializer = getDeserializer(state)
stateVersion = state.get?('version') ? state.version stateVersion = state.get?('version') ? state.version
return if deserializer.version? and deserializer.version isnt stateVersion return if deserializer.version? and deserializer.version isnt stateVersion
if (state instanceof telepath.Document) and not deserializer.acceptsDocuments if (state instanceof telepath.Document) and not deserializer.acceptsDocuments
state = state.toObject() state = state.toObject()
deserializer.deserialize(state) deserializer.deserialize(state, params)
window.getDeserializer = (state) -> window.getDeserializer = (state) ->
return unless state? return unless state?