Merge remote-tracking branch 'origin/master' into cj-keymap-cleanup

This commit is contained in:
probablycorey 2013-11-15 13:53:42 -08:00
commit 02f40688e2
19 changed files with 143 additions and 240 deletions

View File

@ -16,7 +16,7 @@
"clear-cut": "0.2.0", "clear-cut": "0.2.0",
"coffee-script": "1.6.3", "coffee-script": "1.6.3",
"coffeestack": "0.6.0", "coffeestack": "0.6.0",
"emissary": "0.9.0", "emissary": "0.17.0",
"first-mate": "0.5.0", "first-mate": "0.5.0",
"fs-plus": "0.9.0", "fs-plus": "0.9.0",
"fuzzaldrin": "0.1.0", "fuzzaldrin": "0.1.0",
@ -28,14 +28,14 @@
"nslog": "0.1.0", "nslog": "0.1.0",
"oniguruma": "0.24.0", "oniguruma": "0.24.0",
"optimist": "0.4.0", "optimist": "0.4.0",
"pathwatcher": "0.9.0", "pathwatcher": "0.10.0",
"pegjs": "0.7.0", "pegjs": "0.7.0",
"q": "0.9.7", "q": "0.9.7",
"scandal": "0.8.0", "scandal": "0.8.0",
"season": "0.14.0", "season": "0.14.0",
"semver": "1.1.4", "semver": "1.1.4",
"space-pen": "2.0.0", "space-pen": "2.0.0",
"telepath": "0.23.0", "telepath": "0.38.0",
"temp": "0.5.0", "temp": "0.5.0",
"underscore-plus": "0.3.0" "underscore-plus": "0.3.0"
}, },

View File

@ -5,9 +5,15 @@ var path = require('path');
// Executes an array of commands one by one. // Executes an array of commands one by one.
function executeCommands(commands, done, index) { function executeCommands(commands, done, index) {
index = (index == undefined ? 0 : index); index = (index == undefined ? 0 : index);
if (index < commands.length) if (index < commands.length) {
safeExec(commands[index], executeCommands.bind(this, commands, done, index + 1)); var command = commands[index];
else var options = null;
if (typeof command !== 'string') {
options = command.options;
command = command.command;
}
safeExec(command, options, executeCommands.bind(this, commands, done, index + 1));
} else
done(null); done(null);
} }
@ -21,8 +27,8 @@ var echoNewLine = process.platform == 'win32' ? 'echo.' : 'echo';
var commands = [ var commands = [
'git submodule --quiet sync', 'git submodule --quiet sync',
'git submodule --quiet update --recursive --init', 'git submodule --quiet update --recursive --init',
joinCommands('cd vendor/apm', 'npm install --silent .'), {command: joinCommands('cd vendor/apm', 'npm install --silent .'), options: {ignoreStdout: true}},
'npm install --silent vendor/apm', {command: 'npm install --silent vendor/apm', options: {ignoreStdout: true}},
echoNewLine, echoNewLine,
'node node_modules/atom-package-manager/bin/apm clean', 'node node_modules/atom-package-manager/bin/apm clean',
'node node_modules/atom-package-manager/bin/apm install --silent', 'node node_modules/atom-package-manager/bin/apm install --silent',

View File

@ -10,7 +10,7 @@ exports.safeExec = function(command, options, callback) {
if (!options) if (!options)
options = {}; options = {};
// This needed to be increase for `apm test` runs that generate tons of failures // This needed to be increased for `apm test` runs that generate many failures
// The default is 200KB. // The default is 200KB.
options.maxBuffer = 1024 * 1024; options.maxBuffer = 1024 * 1024;
@ -21,7 +21,8 @@ exports.safeExec = function(command, options, callback) {
callback(null); callback(null);
}); });
child.stderr.pipe(process.stderr); child.stderr.pipe(process.stderr);
child.stdout.pipe(process.stdout); if (!options.ignoreStdout)
child.stdout.pipe(process.stdout);
} }
// Same with safeExec but call child_process.spawn instead. // Same with safeExec but call child_process.spawn instead.

View File

@ -257,7 +257,8 @@ describe "Git", ->
it "subscribes to all the serialized buffers in the project", -> it "subscribes to all the serialized buffers in the project", ->
project.openSync('sample.js') project.openSync('sample.js')
project2 = deserialize(project.serialize()) #TODO Replace with atom.replicate().project when Atom is a telepath model
project2 = atom.replicate().get('project')
buffer = project2.getBuffers()[0] buffer = project2.getBuffers()[0]
waitsFor -> waitsFor ->

View File

@ -699,11 +699,11 @@ describe "Pane", ->
it "focuses the pane after attach only if had focus when serialized", -> it "focuses the pane after attach only if had focus when serialized", ->
reloadContainer = -> reloadContainer = ->
projectState = project.serialize() projectReplica = atom.replicate().get('project')
containerState = container.serialize() containerState = container.serialize()
container.remove() container.remove()
project.destroy() project.destroy()
window.project = deserialize(projectState) window.project = projectReplica
container = deserialize(containerState) container = deserialize(containerState)
pane = container.getRoot() pane = container.getRoot()
container.attachToDom() container.attachToDom()

View File

@ -19,7 +19,8 @@ describe "Project", ->
it "destroys unretained buffers and does not include them in the serialized state", -> it "destroys unretained buffers and does not include them in the serialized state", ->
project.bufferForPathSync('a') project.bufferForPathSync('a')
expect(project.getBuffers().length).toBe 1 expect(project.getBuffers().length).toBe 1
deserializedProject = deserialize(project.serialize()) project.getState().serializeForPersistence()
deserializedProject = atom.replicate().get('project')
expect(deserializedProject.getBuffers().length).toBe 0 expect(deserializedProject.getBuffers().length).toBe 0
expect(project.getBuffers().length).toBe 0 expect(project.getBuffers().length).toBe 0

View File

@ -20,10 +20,11 @@ describe "RootView", ->
refreshRootViewAndProject = -> refreshRootViewAndProject = ->
rootViewState = rootView.serialize() rootViewState = rootView.serialize()
projectState = project.serialize() project.getState().serializeForPersistence()
project2 = atom.replicate().get('project')
rootView.remove() rootView.remove()
project.destroy() project.destroy()
window.project = deserialize(projectState) window.project = project2
window.rootView = deserialize(rootViewState) window.rootView = deserialize(rootViewState)
rootView.attachToDom() rootView.attachToDom()

View File

@ -4,7 +4,7 @@ describe "Selection", ->
[buffer, editSession, selection] = [] [buffer, editSession, selection] = []
beforeEach -> beforeEach ->
buffer = project.buildBufferSync('sample.js') buffer = project.bufferForPathSync('sample.js')
editSession = new EditSession(buffer: buffer, tabLength: 2) editSession = new EditSession(buffer: buffer, tabLength: 2)
selection = editSession.getSelection() selection = editSession.getSelection()

View File

@ -47,10 +47,8 @@ if specDirectory = atom.getLoadSettings().specDirectory
beforeEach -> beforeEach ->
$.fx.off = true $.fx.off = true
if specProjectPath projectPath = specProjectPath ? path.join(@specDirectory, 'fixtures')
atom.project = new Project(specProjectPath) atom.project = atom.getWindowState().set('project', new Project(path: projectPath))
else
atom.project = new Project(path.join(@specDirectory, 'fixtures'))
window.project = atom.project window.project = atom.project
atom.keymap.keyBindings = _.clone(keyBindingsToRestore) atom.keymap.keyBindings = _.clone(keyBindingsToRestore)

View File

@ -13,11 +13,11 @@ describe 'TextBuffer', ->
buffer = project.bufferForPathSync(filePath) buffer = project.bufferForPathSync(filePath)
afterEach -> afterEach ->
buffer?.release() buffer?.destroy()
describe 'constructor', -> describe 'constructor', ->
beforeEach -> beforeEach ->
buffer.release() buffer.destroy()
buffer = null buffer = null
describe "when given a path", -> describe "when given a path", ->
@ -311,8 +311,8 @@ describe 'TextBuffer', ->
it "returns false until the buffer is fully loaded", -> it "returns false until the buffer is fully loaded", ->
buffer.release() buffer.release()
filePath = temp.openSync('atom').path buffer = new TextBuffer({filePath: temp.openSync('atom').path})
buffer = new TextBuffer({project, filePath}) project.addBuffer(buffer)
expect(buffer.isModified()).toBeFalsy() expect(buffer.isModified()).toBeFalsy()
@ -554,35 +554,6 @@ describe 'TextBuffer', ->
waitsFor -> waitsFor ->
changeHandler.callCount > 0 changeHandler.callCount > 0
describe ".getRelativePath()", ->
[filePath, newPath, bufferToChange, eventHandler] = []
beforeEach ->
filePath = path.join(__dirname, "fixtures", "atom-manipulate-me")
newPath = "#{filePath}-i-moved"
fs.writeFileSync(filePath, "")
bufferToChange = project.bufferForPathSync(filePath)
eventHandler = jasmine.createSpy('eventHandler')
bufferToChange.on 'path-changed', eventHandler
afterEach ->
bufferToChange.destroy()
fs.removeSync(filePath) if fs.existsSync(filePath)
fs.removeSync(newPath) if fs.existsSync(newPath)
it "updates when the text buffer's file is moved", ->
expect(bufferToChange.getRelativePath()).toBe('atom-manipulate-me')
jasmine.unspy(window, "setTimeout")
eventHandler.reset()
fs.moveSync(filePath, newPath)
waitsFor "buffer path change", ->
eventHandler.callCount > 0
runs ->
expect(bufferToChange.getRelativePath()).toBe('atom-manipulate-me-i-moved')
describe ".getTextInRange(range)", -> describe ".getTextInRange(range)", ->
describe "when range is empty", -> describe "when range is empty", ->
it "returns an empty string", -> it "returns an empty string", ->
@ -926,22 +897,28 @@ describe 'TextBuffer', ->
expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n" expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n"
describe "serialization", -> describe "serialization", ->
buffer2 = null [buffer2, project2] = []
beforeEach ->
buffer.destroy()
filePath = temp.openSync('atom').path
fs.writeFileSync(filePath, "words")
buffer = project.bufferForPathSync(filePath)
afterEach -> afterEach ->
buffer2?.release() buffer2?.release()
project2?.destroy()
describe "when the serialized buffer had no unsaved changes", -> describe "when the serialized buffer had no unsaved changes", ->
it "loads the current contents of the file at the serialized path", -> it "loads the current contents of the file at the serialized path", ->
expect(buffer.isModified()).toBeFalsy() expect(buffer.isModified()).toBeFalsy()
state = buffer.serialize() project2 = atom.replicate().get('project')
state.get('text').insertTextAtPoint([0, 0], 'simulate divergence of on-disk contents from serialized contents') buffer2 = project2.getBuffers()[0]
buffer2 = deserialize(state, {project}) waitsForPromise ->
buffer2.load()
waitsFor ->
buffer2.cachedDiskContents
runs -> runs ->
expect(buffer2.isModified()).toBeFalsy() expect(buffer2.isModified()).toBeFalsy()
@ -951,18 +928,11 @@ describe 'TextBuffer', ->
describe "when the serialized buffer had unsaved changes", -> describe "when the serialized buffer had unsaved changes", ->
describe "when the disk contents were changed since serialization", -> describe "when the disk contents were changed since serialization", ->
it "loads the disk contents instead of the previous unsaved state", -> it "loads the disk contents instead of the previous unsaved state", ->
buffer.release()
filePath = temp.openSync('atom').path
fs.writeFileSync(filePath, "words")
{buffer} = project.openSync(filePath)
buffer.setText("BUFFER CHANGE") buffer.setText("BUFFER CHANGE")
state = buffer.serialize()
expect(state.getObject('text')).toBe 'BUFFER CHANGE'
fs.writeFileSync(filePath, "DISK CHANGE") fs.writeFileSync(filePath, "DISK CHANGE")
buffer2 = deserialize(state, {project}) project2 = atom.replicate().get('project')
buffer2 = project2.getBuffers()[0]
waitsFor -> waitsFor ->
buffer2.cachedDiskContents buffer2.cachedDiskContents
@ -976,14 +946,14 @@ describe 'TextBuffer', ->
it "restores the previous unsaved state of the buffer", -> it "restores the previous unsaved state of the buffer", ->
previousText = buffer.getText() previousText = buffer.getText()
buffer.setText("abc") buffer.setText("abc")
buffer.retain()
state = buffer.serialize() buffer.getState().serializeForPersistence()
expect(state.getObject('text')).toBe 'abc' project2 = atom.replicate().get('project')
buffer2 = project2.getBuffers()[0]
buffer2 = deserialize(state, {project}) waitsForPromise ->
buffer2.load()
waitsFor ->
buffer2.cachedDiskContents
runs -> runs ->
expect(buffer2.getPath()).toBe(buffer.getPath()) expect(buffer2.getPath()).toBe(buffer.getPath())
@ -999,10 +969,10 @@ describe 'TextBuffer', ->
buffer = project.bufferForPathSync() buffer = project.bufferForPathSync()
buffer.setText("abc") buffer.setText("abc")
state = buffer.serialize() state = buffer.getState().clone()
expect(state.get('path')).toBeUndefined() expect(state.get('path')).toBeUndefined()
expect(state.getObject('text')).toBe 'abc' expect(state.getObject('text')).toBe 'abc'
buffer2 = deserialize(state) buffer2 = project.addBuffer(new TextBuffer(state))
expect(buffer2.getPath()).toBeUndefined() expect(buffer2.getPath()).toBeUndefined()
expect(buffer2.getText()).toBe("abc") expect(buffer2.getText()).toBe("abc")

View File

@ -350,7 +350,7 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains surrogate pairs", -> describe "when the buffer contains surrogate pairs", ->
beforeEach -> beforeEach ->
atom.activatePackage('language-javascript', sync: true) atom.activatePackage('language-javascript', sync: true)
buffer = project.buildBufferSync 'sample-with-pairs.js' buffer = project.bufferForPathSync 'sample-with-pairs.js'
buffer.setText """ buffer.setText """
'abc\uD835\uDF97def' 'abc\uD835\uDF97def'
//\uD835\uDF97xyz //\uD835\uDF97xyz

View File

@ -120,11 +120,10 @@ class Atom
deserializeProject: -> deserializeProject: ->
Project = require './project' Project = require './project'
state = @getWindowState() @project = @getWindowState('project')
@project = deserialize(state.get('project')) unless @project instanceof Project
unless @project? @project = new Project(path: @getLoadSettings().initialPath)
@project = new Project(@getLoadSettings().initialPath) @setWindowState('project', @project)
state.set('project', @project.getState())
deserializeRootView: -> deserializeRootView: ->
RootView = require './root-view' RootView = require './root-view'
@ -295,6 +294,7 @@ class Atom
doc = Document.deserialize(documentState) if documentState? doc = Document.deserialize(documentState) if documentState?
doc ?= Document.create() doc ?= Document.create()
doc.registerModelClasses(require('./text-buffer'), require('./project'))
# TODO: Remove this when everything is using telepath models # TODO: Remove this when everything is using telepath models
if @site? if @site?
@site.setRootDocument(doc) @site.setRootDocument(doc)
@ -316,6 +316,10 @@ class Atom
else else
@windowState @windowState
# Private: Returns a replicated copy of the current state.
replicate: ->
@getWindowState().replicate()
crashMainProcess: -> crashMainProcess: ->
remote.process.crash() remote.process.crash()

View File

@ -335,9 +335,6 @@ class EditSession
# {Delegates to: TextBuffer.getPath} # {Delegates to: TextBuffer.getPath}
getPath: -> @buffer.getPath() getPath: -> @buffer.getPath()
# {Delegates to: TextBuffer.getRelativePath}
getRelativePath: -> @buffer.getRelativePath()
# {Delegates to: TextBuffer.getText} # {Delegates to: TextBuffer.getText}
getText: -> @buffer.getText() getText: -> @buffer.getText()

View File

@ -104,7 +104,7 @@ class Editor extends View
@edit(editSession) @edit(editSession)
else if @mini else if @mini
@edit(new EditSession @edit(new EditSession
buffer: new TextBuffer buffer: TextBuffer.createAsRoot()
softWrap: false softWrap: false
tabLength: 2 tabLength: 2
softTabs: true softTabs: true
@ -586,8 +586,10 @@ class Editor extends View
@showIndentGuide = showIndentGuide @showIndentGuide = showIndentGuide
@resetDisplay() @resetDisplay()
# {Delegates to: TextBuffer.checkoutHead} # Checkout the HEAD revision of this editor's file.
checkoutHead: -> @getBuffer().checkoutHead() checkoutHead: ->
if path = @getPath()
atom.project.getRepo()?.checkoutHead(path)
# {Delegates to: EditSession.setText} # {Delegates to: EditSession.setText}
setText: (text) -> @activeEditSession.setText(text) setText: (text) -> @activeEditSession.setText(text)
@ -598,9 +600,6 @@ class Editor extends View
# {Delegates to: EditSession.getPath} # {Delegates to: EditSession.getPath}
getPath: -> @activeEditSession?.getPath() getPath: -> @activeEditSession?.getPath()
# {Delegates to: EditSession.getRelativePath}
getRelativePath: -> @activeEditSession?.getRelativePath()
# {Delegates to: TextBuffer.getLineCount} # {Delegates to: TextBuffer.getLineCount}
getLineCount: -> @getBuffer().getLineCount() getLineCount: -> @getBuffer().getLineCount()

View File

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

View File

@ -5,7 +5,6 @@ _ = require 'underscore-plus'
fs = require 'fs-plus' fs = require 'fs-plus'
Q = require 'q' Q = require 'q'
telepath = require 'telepath' telepath = require 'telepath'
{Range} = telepath
TextBuffer = require './text-buffer' TextBuffer = require './text-buffer'
EditSession = require './edit-session' EditSession = require './edit-session'
@ -19,16 +18,12 @@ Git = require './git'
# Ultimately, a project is a git directory that's been opened. It's a collection # Ultimately, a project is a git directory that's been opened. It's a collection
# of directories and files that you can operate on. # of directories and files that you can operate on.
module.exports = module.exports =
class Project class Project extends telepath.Model
Emitter.includeInto(this) Emitter.includeInto(this)
@acceptsDocuments: true @properties
@version: 1 buffers: []
path: null
registerDeserializer(this)
# Private:
@deserialize: (state) -> new Project(state)
# Public: Find the local path for the given repository URL. # Public: Find the local path for the given repository URL.
@pathForRepositoryUrl: (repoUrl) -> @pathForRepositoryUrl: (repoUrl) ->
@ -36,10 +31,15 @@ class Project
repoName = repoName.replace(/\.git$/, '') repoName = repoName.replace(/\.git$/, '')
path.join(atom.config.get('core.projectHome'), repoName) path.join(atom.config.get('core.projectHome'), repoName)
rootDirectory: null # Private: Called by telepath.
editSessions: null attached: ->
ignoredPathRegexes: null @openers = []
openers: null @editSessions = []
@setPath(@path)
# Private: Called by telepath.
beforePersistence: ->
@destroyUnretainedBuffers()
# Public: # Public:
registerOpener: (opener) -> @openers.push(opener) registerOpener: (opener) -> @openers.push(opener)
@ -59,51 +59,10 @@ class Project
@repo.destroy() @repo.destroy()
@repo = null @repo = null
# Public: Establishes a new project at a given path.
#
# path - The {String} name of the path
constructor: (pathOrState) ->
@openers = []
@editSessions = []
@buffers = []
if pathOrState instanceof telepath.Document
@state = pathOrState
if projectPath = @state.remove('path')
@setPath(projectPath)
else
@setPath(@constructor.pathForRepositoryUrl(@state.get('repoUrl')))
@state.get('buffers').each (bufferState) =>
if buffer = deserialize(bufferState, project: this)
@addBuffer(buffer, updateState: false)
else
@state = atom.site.createDocument(deserializer: @constructor.name, version: @constructor.version, buffers: [])
@setPath(pathOrState)
@state.get('buffers').on 'changed', ({index, insertedValues, removedValues, siteId}) =>
return if siteId is @state.siteId
for removedBuffer in removedValues
@removeBufferAtIndex(index, updateState: false)
for insertedBuffer, i in insertedValues
@addBufferAtIndex(deserialize(insertedBuffer, project: this), index + i, updateState: false)
# Private:
serialize: ->
state = @state.clone()
state.set('path', @getPath())
@destroyUnretainedBuffers()
state.set('buffers', buffer.serialize() for buffer in @getBuffers())
state
# Private: # Private:
destroyUnretainedBuffers: -> destroyUnretainedBuffers: ->
buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained() buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained()
# Public: ?
getState: -> @state
# Public: Returns the {Git} repository if available. # Public: Returns the {Git} repository if available.
getRepo: -> @repo getRepo: -> @repo
@ -113,6 +72,7 @@ class Project
# Public: Sets the project's fullpath. # Public: Sets the project's fullpath.
setPath: (projectPath) -> setPath: (projectPath) ->
@path = projectPath
@rootDirectory?.off() @rootDirectory?.off()
@destroyRepo() @destroyRepo()
@ -125,9 +85,6 @@ class Project
else else
@rootDirectory = null @rootDirectory = null
if originUrl = @repo?.getOriginUrl()
@state.set('repoUrl', originUrl)
@emit "path-changed" @emit "path-changed"
# Public: Returns the name of the root directory. # Public: Returns the name of the root directory.
@ -220,20 +177,18 @@ class Project
# #
# Returns an {Array} of {TextBuffer}s. # Returns an {Array} of {TextBuffer}s.
getBuffers: -> getBuffers: ->
new Array(@buffers...) new Array(@buffers.getValues()...)
isPathModified: (filePath) -> isPathModified: (filePath) ->
absoluteFilePath = @resolve(filePath) @findBufferForPath(@resolve(filePath))?.isModified()
existingBuffer = _.find @buffers, (buffer) -> buffer.getPath() == absoluteFilePath
existingBuffer?.isModified() findBufferForPath: (filePath) ->
_.find @buffers.getValues(), (buffer) -> buffer.getPath() == filePath
# Private: Only to be used in specs # Private: Only to be used in specs
bufferForPathSync: (filePath) -> bufferForPathSync: (filePath) ->
absoluteFilePath = @resolve(filePath) absoluteFilePath = @resolve(filePath)
existingBuffer = @findBufferForPath(absoluteFilePath) if filePath
if filePath
existingBuffer = _.find @buffers, (buffer) -> buffer.getPath() == absoluteFilePath
existingBuffer ? @buildBufferSync(absoluteFilePath) existingBuffer ? @buildBufferSync(absoluteFilePath)
# Private: Given a file path, this retrieves or creates a new {TextBuffer}. # Private: Given a file path, this retrieves or creates a new {TextBuffer}.
@ -246,9 +201,7 @@ class Project
# Returns a promise that resolves to the {TextBuffer}. # Returns a promise that resolves to the {TextBuffer}.
bufferForPath: (filePath) -> bufferForPath: (filePath) ->
absoluteFilePath = @resolve(filePath) absoluteFilePath = @resolve(filePath)
if absoluteFilePath existingBuffer = @findBufferForPath(absoluteFilePath) if absoluteFilePath
existingBuffer = _.find @buffers, (buffer) -> buffer.getPath() == absoluteFilePath
Q(existingBuffer ? @buildBuffer(absoluteFilePath)) Q(existingBuffer ? @buildBuffer(absoluteFilePath))
# Private: # Private:
@ -257,9 +210,9 @@ class Project
# Private: DEPRECATED # Private: DEPRECATED
buildBufferSync: (absoluteFilePath) -> buildBufferSync: (absoluteFilePath) ->
buffer = new TextBuffer({project: this, filePath: absoluteFilePath}) buffer = new TextBuffer({filePath: absoluteFilePath})
buffer.loadSync()
@addBuffer(buffer) @addBuffer(buffer)
buffer.loadSync()
buffer buffer
# Private: Given a file path, this sets its {TextBuffer}. # Private: Given a file path, this sets its {TextBuffer}.
@ -269,10 +222,11 @@ class Project
# #
# Returns a promise that resolves to the {TextBuffer}. # Returns a promise that resolves to the {TextBuffer}.
buildBuffer: (absoluteFilePath) -> buildBuffer: (absoluteFilePath) ->
buffer = new TextBuffer({project: this, filePath: absoluteFilePath}) buffer = new TextBuffer({filePath: absoluteFilePath})
buffer.load().then (buffer) => @addBuffer(buffer)
@addBuffer(buffer) buffer.load()
buffer .then((buffer) -> buffer)
.catch(=> @removeBuffer(buffer))
# Private: # Private:
addBuffer: (buffer, options={}) -> addBuffer: (buffer, options={}) ->
@ -280,9 +234,10 @@ class Project
# Private: # Private:
addBufferAtIndex: (buffer, index, options={}) -> addBufferAtIndex: (buffer, index, options={}) ->
@buffers[index] = buffer buffer = @buffers.insert(index, buffer)
@state.get('buffers').insert(index, buffer.getState()) if options.updateState ? true buffer.once 'destroyed', => @removeBuffer(buffer)
@emit 'buffer-created', buffer @emit 'buffer-created', buffer
buffer
# Private: Removes a {TextBuffer} association from the project. # Private: Removes a {TextBuffer} association from the project.
# #
@ -294,7 +249,6 @@ class Project
# Private: # Private:
removeBufferAtIndex: (index, options={}) -> removeBufferAtIndex: (index, options={}) ->
[buffer] = @buffers.splice(index, 1) [buffer] = @buffers.splice(index, 1)
@state.get('buffers')?.remove(index) if options.updateState ? true
buffer?.destroy() buffer?.destroy()
# Public: Performs a search across all the files in the project. # Public: Performs a search across all the files in the project.
@ -329,7 +283,7 @@ class Project
task.on 'scan:paths-searched', (numberOfPathsSearched) -> task.on 'scan:paths-searched', (numberOfPathsSearched) ->
options.onPathsSearched(numberOfPathsSearched) options.onPathsSearched(numberOfPathsSearched)
for buffer in @buffers when buffer.isModified() for buffer in @buffers.getValues() when buffer.isModified()
filePath = buffer.getPath() filePath = buffer.getPath()
matches = [] matches = []
buffer.scan regex, (match) -> matches.push match buffer.scan regex, (match) -> matches.push match
@ -346,7 +300,7 @@ class Project
replace: (regex, replacementText, filePaths, iterator) -> replace: (regex, replacementText, filePaths, iterator) ->
deferred = Q.defer() deferred = Q.defer()
openPaths = (buffer.getPath() for buffer in @buffers) openPaths = (buffer.getPath() for buffer in @buffers.getValues())
outOfProcessPaths = _.difference(filePaths, openPaths) outOfProcessPaths = _.difference(filePaths, openPaths)
inProcessFinished = !openPaths.length inProcessFinished = !openPaths.length
@ -364,7 +318,7 @@ class Project
task.on 'replace:path-replaced', iterator task.on 'replace:path-replaced', iterator
for buffer in @buffers for buffer in @buffers.getValues()
replacements = buffer.replace(regex, replacementText, iterator) replacements = buffer.replace(regex, replacementText, iterator)
iterator({filePath: buffer.getPath(), replacements}) if replacements iterator({filePath: buffer.getPath(), replacements}) if replacements

View File

@ -1,32 +1,28 @@
crypto = require 'crypto' _ = require 'underscore-plus'
{Emitter, Subscriber} = require 'emissary' {Emitter, Subscriber} = require 'emissary'
guid = require 'guid'
Q = require 'q' Q = require 'q'
{P} = require 'scandal' {P} = require 'scandal'
telepath = require 'telepath' telepath = require 'telepath'
_ = require 'underscore-plus'
File = require './file' File = require './file'
{Point, Range} = telepath {Point, Range} = telepath
# Private: Represents the contents of a file. # Private: Represents the contents of a file.
# #
# The `Buffer` is often associated with a {File}. However, this is not always # The `TextBuffer` is often associated with a {File}. However, this is not always
# the case, as a `Buffer` could be an unsaved chunk of text. # the case, as a `TextBuffer` could be an unsaved chunk of text.
module.exports = module.exports =
class TextBuffer class TextBuffer extends telepath.Model
Emitter.includeInto(this) Emitter.includeInto(this)
Subscriber.includeInto(this) Subscriber.includeInto(this)
@acceptsDocuments: true @properties
@version: 2 text: -> new telepath.String('', replicated: false)
registerDeserializer(this) filePath: null
relativePath: null
@deserialize: (state, params) -> modifiedWhenLastPersisted: false
buffer = new this(state, params) digestWhenLastPersisted: null
buffer.load()
buffer
stoppedChangingDelay: 300 stoppedChangingDelay: 300
stoppedChangingTimeout: null stoppedChangingTimeout: null
@ -36,34 +32,28 @@ class TextBuffer
file: null file: null
refcount: 0 refcount: 0
# Creates a new buffer. constructor: ->
# super
# * optionsOrState - An {Object} or a telepath.Document
# + filePath - A {String} representing the file path
constructor: (optionsOrState={}, params={}) ->
if optionsOrState instanceof telepath.Document
{@project} = params
@state = optionsOrState
@id = @state.get('id')
filePath = @state.get('relativePath')
@text = @state.get('text')
@useSerializedText = @state.get('isModified') != false
else
{@project, filePath} = optionsOrState
@text = new telepath.String(initialText ? '', replicated: false)
@id = guid.create().toString()
@state = atom.site.createDocument
id: @id
deserializer: @constructor.name
version: @constructor.version
text: @text
@loadWhenAttached = @getState()?
# Private: Called by telepath.
attached: ->
@loaded = false @loaded = false
@useSerializedText = @modifiedWhenLastPersisted != false
@subscribe @text, 'changed', @handleTextChange @subscribe @text, 'changed', @handleTextChange
@subscribe @text, 'marker-created', (marker) => @emit 'marker-created', marker @subscribe @text, 'marker-created', (marker) => @emit 'marker-created', marker
@subscribe @text, 'markers-updated', => @emit 'markers-updated' @subscribe @text, 'markers-updated', => @emit 'markers-updated'
@setPath(@project.resolve(filePath)) if @project @setPath(@filePath)
@load() if @loadWhenAttached
# Private: Called by telepath.
beforePersistence: ->
@modifiedWhenLastPersisted = @isModified()
@digestWhenLastPersisted = @file?.getDigest()
loadSync: -> loadSync: ->
@updateCachedDiskContentsSync() @updateCachedDiskContentsSync()
@ -74,7 +64,7 @@ class TextBuffer
finishLoading: -> finishLoading: ->
@loaded = true @loaded = true
if @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() if @useSerializedText and @digestWhenLastPersisted is @file?.getDigest()
@emitModifiedStatusChanged(true) @emitModifiedStatusChanged(true)
else else
@reload() @reload()
@ -92,10 +82,10 @@ class TextBuffer
destroy: -> destroy: ->
unless @destroyed unless @destroyed
@cancelStoppedChangingTimeout()
@file?.off() @file?.off()
@unsubscribe() @unsubscribe()
@destroyed = true @destroyed = true
@project?.removeBuffer(this)
@emit 'destroyed' @emit 'destroyed'
isRetained: -> @refcount > 0 isRetained: -> @refcount > 0
@ -109,16 +99,6 @@ class TextBuffer
@destroy() unless @isRetained() @destroy() unless @isRetained()
this this
serialize: ->
state = @state.clone()
state.set('isModified', @isModified())
state.set('diskContentsDigest', @file.getDigest()) if @file
for marker in state.get('text').getMarkers() when marker.isRemote()
marker.destroy()
state
getState: -> @state
subscribeToFile: -> subscribeToFile: ->
@file.on "contents-changed", => @file.on "contents-changed", =>
@conflict = true if @isModified() @conflict = true if @isModified()
@ -140,7 +120,6 @@ class TextBuffer
@emitModifiedStatusChanged(@isModified()) @emitModifiedStatusChanged(@isModified())
@file.on "moved", => @file.on "moved", =>
@state.set('relativePath', @project.relativize(@getPath()))
@emit "path-changed", this @emit "path-changed", this
### Public ### ### Public ###
@ -183,10 +162,7 @@ class TextBuffer
@file?.getPath() @file?.getPath()
getUri: -> getUri: ->
@getRelativePath() atom.project.relativize(@getPath())
getRelativePath: ->
@state.get('relativePath')
# Sets the path for the file. # Sets the path for the file.
# #
@ -202,7 +178,6 @@ class TextBuffer
else else
@file = null @file = null
@state.set('relativePath', @project.relativize(path))
@emit "path-changed", this @emit "path-changed", this
# Retrieves the current buffer's file extension. # Retrieves the current buffer's file extension.
@ -653,12 +628,6 @@ class TextBuffer
return match[0][0] != '\t' return match[0][0] != '\t'
undefined undefined
# Checks out the current `HEAD` revision of the file.
checkoutHead: ->
path = @getPath()
return unless path
@project.getRepo()?.checkoutHead(path)
### Internal ### ### Internal ###
transact: (fn) -> @text.transact fn transact: (fn) -> @text.transact fn
@ -680,8 +649,11 @@ class TextBuffer
else else
text text
scheduleModifiedEvents: -> cancelStoppedChangingTimeout: ->
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
scheduleModifiedEvents: ->
@cancelStoppedChangingTimeout()
stoppedChangingCallback = => stoppedChangingCallback = =>
@stoppedChangingTimeout = null @stoppedChangingTimeout = null
modifiedStatus = @isModified() modifiedStatus = @isModified()
@ -700,7 +672,7 @@ class TextBuffer
console.log row, line, line.length console.log row, line, line.length
getDebugSnapshot: -> getDebugSnapshot: ->
lines = ['Buffer:'] lines = ['TextBuffer:']
for row in [0..@getLastRow()] for row in [0..@getLastRow()]
lines.push "#{row}: #{@lineForRow(row)}" lines.push "#{row}: #{@lineForRow(row)}"
lines.join('\n') lines.join('\n')

View File

@ -36,7 +36,7 @@ class TokenizedBuffer
{ @buffer, tabLength } = optionsOrState { @buffer, tabLength } = optionsOrState
@state = atom.site.createDocument @state = atom.site.createDocument
deserializer: @constructor.name deserializer: @constructor.name
bufferPath: @buffer.getRelativePath() bufferPath: @buffer.getPath()
tabLength: tabLength ? atom.config.get('editor.tabLength') ? 2 tabLength: tabLength ? atom.config.get('editor.tabLength') ? 2
@subscribe syntax, 'grammar-added grammar-updated', (grammar) => @subscribe syntax, 'grammar-added grammar-updated', (grammar) =>
@ -48,7 +48,7 @@ class TokenizedBuffer
@on 'grammar-changed grammar-updated', => @resetTokenizedLines() @on 'grammar-changed grammar-updated', => @resetTokenizedLines()
@subscribe @buffer, "changed", (e) => @handleBufferChange(e) @subscribe @buffer, "changed", (e) => @handleBufferChange(e)
@subscribe @buffer, "path-changed", => @state.set('bufferPath', @buffer.getRelativePath()) @subscribe @buffer, "path-changed", => @state.set('bufferPath', @buffer.getPath())
@reloadGrammar() @reloadGrammar()

View File

@ -69,7 +69,7 @@ window.startEditorWindow = ->
window.unloadEditorWindow = -> window.unloadEditorWindow = ->
return if not atom.project and not atom.rootView return if not atom.project and not atom.rootView
windowState = atom.getWindowState() windowState = atom.getWindowState()
windowState.set('project', atom.project.serialize()) windowState.set('project', atom.project)
windowState.set('syntax', atom.syntax.serialize()) windowState.set('syntax', atom.syntax.serialize())
windowState.set('rootView', atom.rootView.serialize()) windowState.set('rootView', atom.rootView.serialize())
atom.packages.deactivatePackages() atom.packages.deactivatePackages()