diff --git a/.gitmodules b/.gitmodules index 363ee63b2..e4335ef79 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "vendor/apm"] path = vendor/apm - url = git@github.com:atom/apm.git + url = https://github.com/atom/apm.git diff --git a/build/tasks/docs-task.coffee b/build/tasks/docs-task.coffee index 42856f8ee..413c219f7 100644 --- a/build/tasks/docs-task.coffee +++ b/build/tasks/docs-task.coffee @@ -9,7 +9,7 @@ module.exports = (grunt) -> grunt.registerTask 'build-docs', 'Builds the API docs in src/app', -> done = @async() - args = [commonArgs..., '--title', 'Atom API Documentation', '-o', 'docs/output/api', 'src/', '../telepath/src/range.coffee', '../telepath/src/point.coffee', '../telepath/src/string-marker.coffee'] + args = [commonArgs..., '--title', 'Atom API Documentation', '-o', 'docs/output/api', 'src/', '../text-buffer/src/range.coffee', '../text-buffer/src/point.coffee', '../text-buffer/src/marker.coffee'] grunt.util.spawn({cmd, args, opts}, done) grunt.registerTask 'lint-docs', 'Generate stats about the doc coverage', -> diff --git a/build/tasks/spec-task.coffee b/build/tasks/spec-task.coffee index 8123dad3d..13ef31852 100644 --- a/build/tasks/spec-task.coffee +++ b/build/tasks/spec-task.coffee @@ -39,7 +39,7 @@ module.exports = (grunt) -> grunt.verbose.writeln "Launching #{path.basename(packagePath)} specs." spawn options, (error, results, code) -> if process.platform is 'win32' - process.stdout.write(fs.readFileSync(path.join(packagePath, 'ci.log'))) + process.stderr.write(fs.readFileSync(path.join(packagePath, 'ci.log'))) fs.unlinkSync(path.join(packagePath, 'ci.log')) failedPackages.push path.basename(packagePath) if error @@ -77,7 +77,7 @@ module.exports = (grunt) -> spawn options, (error, results, code) -> if process.platform is 'win32' - process.stdout.write(fs.readFileSync('ci.log')) + process.stderr.write(fs.readFileSync('ci.log')) fs.unlinkSync('ci.log') else # TODO: Restore concurrency on Windows diff --git a/exports/atom.coffee b/exports/atom.coffee index 1b1927737..c5a7ffb63 100644 --- a/exports/atom.coffee +++ b/exports/atom.coffee @@ -1,16 +1,13 @@ -{TelepathicObject, Model, Point, Range} = require 'telepath' +{Point, Range} = require 'text-buffer' module.exports = _: require 'underscore-plus' BufferedNodeProcess: require '../src/buffered-node-process' BufferedProcess: require '../src/buffered-process' Directory: require '../src/directory' - TelepathicObject: TelepathicObject - Document: TelepathicObject # Deprecated Shim File: require '../src/file' fs: require 'fs-plus' Git: require '../src/git' - Model: Model Point: Point Range: Range diff --git a/package.json b/package.json index a46676f77..c50582fff 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "diff": "git://github.com/benogle/jsdiff.git", "emissary": "0.19.0", "first-mate": "0.12.0", - "fs-plus": "0.13.0", + "fs-plus": "0.14.0", "fstream": "0.1.24", "fuzzaldrin": "0.1.0", "git-utils": "0.30.0", @@ -37,20 +37,22 @@ "mkdirp": "0.3.5", "keytar": "0.15.0", "less-cache": "0.10.0", + "serializable": "0.1.0", "nslog": "0.3.0", "oniguruma": "0.26.0", "optimist": "0.4.0", "pathwatcher": "0.13.0", "pegjs": "0.7.0", "q": "0.9.7", - "scandal": "0.10.0", + "scandal": "0.11.0", "season": "0.14.0", "semver": "1.1.4", - "space-pen": "2.0.2", - "telepath": "0.81.0", + "space-pen": "3.0.3", "temp": "0.5.0", + "text-buffer": "0.9.0", "underscore-plus": "0.6.1", "vm-compatibility-layer": "0.1.0" + "theorist": "~0.7.0" }, "packageDependencies": { "atom-dark-syntax": "0.10.0", @@ -60,7 +62,7 @@ "base16-tomorrow-dark-theme": "0.8.0", "solarized-dark-syntax": "0.6.0", "solarized-light-syntax": "0.2.0", - "archive-view": "0.17.0", + "archive-view": "0.18.0", "autocomplete": "0.19.0", "autoflow": "0.11.0", "autosave": "0.10.0", @@ -72,7 +74,7 @@ "dev-live-reload": "0.20.0", "editor-stats": "0.12.0", "exception-reporting": "0.9.0", - "feedback": "0.21.0", + "feedback": "0.22.0", "find-and-replace": "0.68.0", "fuzzy-finder": "0.28.0", "gists": "0.14.0", @@ -80,14 +82,13 @@ "github-sign-in": "0.16.0", "go-to-line": "0.12.0", "grammar-selector": "0.15.0", - "image-view": "0.11.0", + "image-view": "0.14.0", "keybinding-resolver": "0.8.0", - "link": "0.11.0", "markdown-preview": "0.22.0", "metrics": "0.20.0", "package-generator": "0.23.0", "release-notes": "0.15.0", - "settings-view": "0.52.0", + "settings-view": "0.54.0", "snippets": "0.17.0", "spell-check": "0.19.0", "status-bar": "0.29.0", @@ -95,9 +96,9 @@ "symbols-view": "0.28.0", "tabs": "0.16.0", "terminal": "0.24.0", - "timecop": "0.11.0", + "timecop": "0.12.0", "to-the-hubs": "0.16.0", - "tree-view": "0.53.0", + "tree-view": "0.57.0", "visual-bell": "0.6.0", "welcome": "0.4.0", "whitespace": "0.10.0", diff --git a/spec/directory-spec.coffee b/spec/directory-spec.coffee index c611a7b05..d11a5c067 100644 --- a/spec/directory-spec.coffee +++ b/spec/directory-spec.coffee @@ -68,7 +68,7 @@ describe "Directory", -> describe "on #darwin or #linux", -> it "includes symlink information about entries", -> - entries = directory.getEntries() + entries = directory.getEntriesSync() for entry in entries name = entry.getBaseName() if name is 'symlink-to-dir' or name is 'symlink-to-file' @@ -76,6 +76,20 @@ describe "Directory", -> else expect(entry.symlink).toBeFalsy() + callback = jasmine.createSpy('getEntries') + directory.getEntries(callback) + + waitsFor -> callback.callCount is 1 + + runs -> + entries = callback.mostRecentCall.args[1] + for entry in entries + name = entry.getBaseName() + if name is 'symlink-to-dir' or name is 'symlink-to-file' + expect(entry.symlink).toBeTruthy() + else + expect(entry.symlink).toBeFalsy() + describe ".relativize(path)", -> describe "on #darwin or #linux", -> it "returns a relative path based on the directory's path", -> diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index caed9092b..e85a05049 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -7,7 +7,7 @@ describe "DisplayBuffer", -> tabLength = 2 atom.packages.activatePackage('language-javascript', sync: true) buffer = atom.project.bufferForPathSync('sample.js') - displayBuffer = atom.create(new DisplayBuffer({buffer, tabLength})) + displayBuffer = new DisplayBuffer({buffer, tabLength}) changeHandler = jasmine.createSpy 'changeHandler' displayBuffer.on 'changed', changeHandler @@ -150,7 +150,7 @@ describe "DisplayBuffer", -> describe "when a newline is inserted, deleted, and re-inserted at the end of a wrapped line (regression)", -> it "correctly renders the original wrapped line", -> buffer = atom.project.buildBufferSync(null, '') - displayBuffer = atom.create(new DisplayBuffer({buffer, tabLength, editorWidthInChars: 30, softWrap: true})) + displayBuffer = new DisplayBuffer({buffer, tabLength, editorWidthInChars: 30, softWrap: true}) buffer.insert([0, 0], "the quick brown fox jumps over the lazy dog.") buffer.insert([0, Infinity], '\n') @@ -202,7 +202,7 @@ describe "DisplayBuffer", -> displayBuffer.destroy() buffer.release() buffer = atom.project.bufferForPathSync('two-hundred.txt') - displayBuffer = atom.create(new DisplayBuffer({buffer, tabLength})) + displayBuffer = new DisplayBuffer({buffer, tabLength}) displayBuffer.on 'changed', changeHandler describe "when folds are created and destroyed", -> @@ -308,7 +308,7 @@ describe "DisplayBuffer", -> describe "when there is another display buffer pointing to the same buffer", -> it "does not create folds in the other display buffer", -> - otherDisplayBuffer = atom.create(new DisplayBuffer({buffer, tabLength})) + otherDisplayBuffer = new DisplayBuffer({buffer, tabLength}) displayBuffer.createFold(2, 4) expect(otherDisplayBuffer.foldsStartingAtBufferRow(2).length).toBe 0 diff --git a/spec/editor-spec.coffee b/spec/editor-spec.coffee index 0a93eb91b..821c9becb 100644 --- a/spec/editor-spec.coffee +++ b/spec/editor-spec.coffee @@ -28,10 +28,7 @@ describe "Editor", -> editor.foldBufferRow(4) expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - # Simulate serialization with replicate - editor2 = editor.testPersistence() - # FIXME: The created hook is called manually on deserialization because globals aren't ready otherwise - editor2.created() + editor2 = editor.testSerialization() expect(editor2.id).toBe editor.id expect(editor2.getBuffer().getPath()).toBe editor.getBuffer().getPath() diff --git a/spec/git-spec.coffee b/spec/git-spec.coffee index c6ef5dde2..50e5f5cce 100644 --- a/spec/git-spec.coffee +++ b/spec/git-spec.coffee @@ -257,7 +257,7 @@ describe "Git", -> it "subscribes to all the serialized buffers in the project", -> atom.project.openSync('sample.js') - project2 = atom.project.testPersistence() + project2 = atom.project.testSerialization() buffer = project2.getBuffers()[0] waitsFor -> diff --git a/spec/jasmine-helper.coffee b/spec/jasmine-helper.coffee index 88249e867..c57a5dc37 100644 --- a/spec/jasmine-helper.coffee +++ b/spec/jasmine-helper.coffee @@ -10,21 +10,24 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) -> TimeReporter = require './time-reporter' timeReporter = new TimeReporter() - if logFile? - logStream = fs.createWriteStream(logFile, flags: 'w') - process.__defineGetter__ 'stdout', -> logStream - process.__defineGetter__ 'stderr', -> logStream + logStream = fs.openSync(logFile, 'w') if logFile? + log = (str) -> + if logStream? + fs.writeSync(logStream, str) + else + process.stderr.write(str) if atom.getLoadSettings().exitWhenDone {jasmineNode} = require 'jasmine-node/lib/jasmine-node/reporter' reporter = new jasmineNode.TerminalReporter - print: (args...) -> - process.stderr.write(args...) + print: (str) -> + log(str) onComplete: (runner) -> - process.stdout.write('\n') - timeReporter.logLongestSuites 10, (line) -> process.stdout.write("#{line}\n") - process.stdout.write('\n') - timeReporter.logLongestSpecs 10, (line) -> process.stdout.write("#{line}\n") + log('\n') + timeReporter.logLongestSuites 10, (line) -> log("#{line}\n") + log('\n') + timeReporter.logLongestSpecs 10, (line) -> log("#{line}\n") + fs.closeSync(logStream) if logStream? atom.exit(runner.results().failedCount > 0 ? 1 : 0) else AtomReporter = require './atom-reporter' diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index 540603bf5..6851bb8f4 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -677,12 +677,12 @@ describe "Pane", -> describe "serialization", -> it "can serialize and deserialize the pane and all its items", -> - newPane = atom.deserializers.deserialize(pane.serialize().testPersistence()) + newPane = pane.testSerialization() expect(newPane.getItems()).toEqual [view1, editor1, view2, editor2] it "restores the active item on deserialization", -> pane.showItem(editor2) - newPane = atom.deserializers.deserialize(pane.serialize().testPersistence()) + newPane = pane.testSerialization() expect(newPane.activeItem).toEqual editor2 it "does not show items that cannot be deserialized", -> @@ -693,8 +693,7 @@ describe "Pane", -> pane.showItem(new Unserializable) - state = pane.serialize().testPersistence() - newPane = atom.deserializers.deserialize(state) + newPane = pane.testSerialization() expect(newPane.activeItem).toEqual pane.items[0] expect(newPane.items.length).toBe pane.items.length - 1 @@ -702,13 +701,13 @@ describe "Pane", -> container.attachToDom() pane.focus() - container2 = atom.deserializers.deserialize(container.serialize().testPersistence()) + container2 = container.testSerialization() pane2 = container2.getRoot() container2.attachToDom() expect(pane2).toMatchSelector(':has(:focus)') $(document.activeElement).blur() - container3 = atom.deserializers.deserialize(container.serialize().testPersistence()) + container3 = container.testSerialization() pane3 = container3.getRoot() container3.attachToDom() expect(pane3).not.toMatchSelector(':has(:focus)') diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 36de21747..1ac76e204 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -16,21 +16,17 @@ describe "Project", -> afterEach -> deserializedProject?.destroy() - it "destroys unretained buffers and does not include them in the serialized state", -> + it "does not include unretained buffers in the serialized state", -> atom.project.bufferForPathSync('a') expect(atom.project.getBuffers().length).toBe 1 - atom.project.getState().serializeForPersistence() - deserializedProject = atom.project.testPersistence() - + deserializedProject = atom.project.testSerialization() expect(deserializedProject.getBuffers().length).toBe 0 - expect(atom.project.getBuffers().length).toBe 0 it "listens for destroyed events on deserialized buffers and removes them when they are destroyed", -> atom.project.openSync('a') expect(atom.project.getBuffers().length).toBe 1 - atom.project.getState().serializeForPersistence() - deserializedProject = atom.project.testPersistence() + deserializedProject = atom.project.testSerialization() expect(deserializedProject.getBuffers().length).toBe 1 deserializedProject.getBuffers()[0].destroy() diff --git a/spec/selection-spec.coffee b/spec/selection-spec.coffee index cc6f26597..a9290c5e1 100644 --- a/spec/selection-spec.coffee +++ b/spec/selection-spec.coffee @@ -5,7 +5,7 @@ describe "Selection", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - editor = atom.create(new Editor(buffer: buffer, tabLength: 2)) + editor = new Editor(buffer: buffer, tabLength: 2) selection = editor.getSelection() afterEach -> diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 59766b88d..9c672e7a9 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -7,7 +7,7 @@ path = require 'path' {_, $, File, WorkspaceView, fs} = require 'atom' Keymap = require '../src/keymap' Config = require '../src/config' -{Point} = require 'telepath' +{Point} = require 'text-buffer' Project = require '../src/project' Editor = require '../src/editor' EditorView = require '../src/editor-view' @@ -107,15 +107,14 @@ afterEach -> atom.workspaceView?.remove?() atom.workspaceView = null - atom.state.remove('workspaceView') + delete atom.state.workspaceView atom.project?.destroy?() atom.project = null - atom.state.remove('packageStates') + delete atom.state.packageStates $('#jasmine-content').empty() unless window.debugContent - atom.destroyOrphans() jasmine.unspy(atom, 'saveSync') ensureNoPathSubscriptions() diff --git a/spec/text-buffer-spec.coffee b/spec/text-buffer-spec.coffee index 502c1af39..7f26c663b 100644 --- a/spec/text-buffer-spec.coffee +++ b/spec/text-buffer-spec.coffee @@ -1,7 +1,6 @@ {_, fs} = require 'atom' path = require 'path' temp = require 'temp' -{Site} = require 'telepath' TextBuffer = require '../src/text-buffer' describe 'TextBuffer', -> @@ -971,7 +970,7 @@ describe 'TextBuffer', -> expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n" describe "serialization", -> - [buffer2, project2] = [] + buffer2 = null beforeEach -> buffer.destroy() @@ -981,13 +980,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 +1001,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 +1017,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 +1031,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") diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 654205c0e..a9df78a56 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -21,11 +21,11 @@ describe "TokenizedBuffer", -> describe "when the buffer is destroyed", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = atom.create(new TokenizedBuffer({buffer})) + tokenizedBuffer = new TokenizedBuffer({buffer}) startTokenizing(tokenizedBuffer) it "stops tokenization", -> - tokenizedBuffer.state.destroy() + tokenizedBuffer.destroy() spyOn(tokenizedBuffer, 'tokenizeNextChunk') advanceClock() expect(tokenizedBuffer.tokenizeNextChunk).not.toHaveBeenCalled() @@ -33,7 +33,7 @@ describe "TokenizedBuffer", -> describe "when the buffer contains soft-tabs", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = atom.create(new TokenizedBuffer({buffer})) + tokenizedBuffer = new TokenizedBuffer({buffer}) startTokenizing(tokenizedBuffer) tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler') @@ -313,7 +313,7 @@ describe "TokenizedBuffer", -> beforeEach -> atom.packages.activatePackage('language-coffee-script', sync: true) buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee') - tokenizedBuffer = atom.create(new TokenizedBuffer({buffer})) + tokenizedBuffer = new TokenizedBuffer({buffer}) startTokenizing(tokenizedBuffer) afterEach -> @@ -347,7 +347,7 @@ describe "TokenizedBuffer", -> 'abc\uD835\uDF97def' //\uD835\uDF97xyz """ - tokenizedBuffer = atom.create(new TokenizedBuffer({buffer})) + tokenizedBuffer = new TokenizedBuffer({buffer}) fullyTokenize(tokenizedBuffer) afterEach -> @@ -384,7 +384,7 @@ describe "TokenizedBuffer", -> buffer = atom.project.bufferForPathSync() buffer.setText "
<%= User.find(2).full_name %>
" - tokenizedBuffer = atom.create(new TokenizedBuffer({buffer})) + tokenizedBuffer = new TokenizedBuffer({buffer}) tokenizedBuffer.setGrammar(atom.syntax.selectGrammar('test.erb')) fullyTokenize(tokenizedBuffer) @@ -403,7 +403,7 @@ describe "TokenizedBuffer", -> it "returns the correct token (regression)", -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = atom.create(new TokenizedBuffer({buffer})) + tokenizedBuffer = new TokenizedBuffer({buffer}) fullyTokenize(tokenizedBuffer) expect(tokenizedBuffer.tokenForPosition([1,0]).scopes).toEqual ["source.js"] expect(tokenizedBuffer.tokenForPosition([1,1]).scopes).toEqual ["source.js"] @@ -412,7 +412,7 @@ describe "TokenizedBuffer", -> describe ".bufferRangeForScopeAtPosition(selector, position)", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = atom.create(new TokenizedBuffer({buffer})) + tokenizedBuffer = new TokenizedBuffer({buffer}) fullyTokenize(tokenizedBuffer) describe "when the selector does not match the token at the position", -> @@ -431,7 +431,7 @@ describe "TokenizedBuffer", -> it "updates the tab length of the tokenized lines", -> buffer = atom.project.bufferForPathSync('sample.js') buffer.setText('\ttest') - tokenizedBuffer = atom.create(new TokenizedBuffer({buffer})) + tokenizedBuffer = new TokenizedBuffer({buffer}) fullyTokenize(tokenizedBuffer) expect(tokenizedBuffer.tokenForPosition([0,0]).value).toBe ' ' atom.config.set('editor.tabLength', 6) diff --git a/spec/window-spec.coffee b/spec/window-spec.coffee index de01bf375..53f992555 100644 --- a/spec/window-spec.coffee +++ b/spec/window-spec.coffee @@ -93,8 +93,8 @@ describe "Window", -> atom.unloadEditorWindow() - expect(atom.state.getObject('workspaceView')).toEqual workspaceViewState.toObject() - expect(atom.state.getObject('syntax')).toEqual syntaxState + expect(atom.state.workspaceView).toEqual workspaceViewState + expect(atom.state.syntax).toEqual syntaxState expect(atom.saveSync).toHaveBeenCalled() it "unsubscribes from all buffers", -> diff --git a/spec/workspace-view-spec.coffee b/spec/workspace-view-spec.coffee index 9a1f75297..216466aee 100644 --- a/spec/workspace-view-spec.coffee +++ b/spec/workspace-view-spec.coffee @@ -19,9 +19,11 @@ describe "WorkspaceView", -> viewState = null simulateReload = -> - state = atom.workspaceView.serialize().testPersistence() + workspaceState = atom.workspaceView.serialize() + projectState = atom.project.serialize() atom.workspaceView.remove() - atom.workspaceView = WorkspaceView.deserialize(state) + atom.project = atom.deserializers.deserialize(projectState) + atom.workspaceView = WorkspaceView.deserialize(workspaceState) atom.workspaceView.attachToDom() describe "when the serialized WorkspaceView has an unsaved buffer", -> diff --git a/src/atom.coffee b/src/atom.coffee index 8a7b005d2..9ddc4dfd8 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -9,12 +9,10 @@ dialog = remote.require 'dialog' app = remote.require 'app' _ = require 'underscore-plus' -telepath = require 'telepath' -{Model} = telepath +{Model} = require 'theorist' fs = require 'fs-plus' {$} = require './space-pen-extensions' -SiteShim = require './site-shim' WindowEventHandler = require './window-event-handler' # Public: Atom global for dealing with packages, themes, menus, and the window. @@ -35,6 +33,8 @@ WindowEventHandler = require './window-event-handler' # * `atom.themes` - A {ThemeManager} instance module.exports = class Atom extends Model + @version: 1 + # Public: Load or create the Atom environment in the given mode # # - mode: Pass 'editor' or 'spec' depending on the kind of environment you @@ -42,28 +42,27 @@ class Atom extends Model # # Returns an Atom instance, fully initialized @loadOrCreate: (mode) -> - telepath.devMode = not @isReleasedVersion() + @deserialize(@loadState(mode)) ? new this({mode, @version}) - if documentState = @loadDocumentState(mode) - environment = @deserialize(documentState) - - environment ? @createAsRoot({mode}) + # Private: Deserializes the Atom environment from a state object + @deserialize: (state) -> + new this(state) if state?.version is @version # Private: Loads and returns the serialized state corresponding to this window # if it exists; otherwise returns undefined. - @loadDocumentState: (mode) -> + @loadState: (mode) -> statePath = @getStatePath(mode) if fs.existsSync(statePath) try - documentStateString = fs.readFileSync(statePath, 'utf8') + stateString = fs.readFileSync(statePath, 'utf8') catch error console.warn "Error reading window state: #{statePath}", error.stack, error else - documentStateString = @getLoadSettings().windowState + stateString = @getLoadSettings().windowState try - JSON.parse(documentStateString) if documentStateString? + JSON.parse(stateString) if stateString? catch error console.warn "Error parsing window state: #{statePath} #{error.stack}", error @@ -112,14 +111,11 @@ class Atom extends Model @isReleasedVersion: -> not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix - @properties - mode: null - project: null - workspaceViewParentSelector: 'body' - # Private: Called by telepath. I'd like this to be merged with initialize eventually. - created: -> + # Private: Call .loadOrCreate instead + constructor: (@state) -> + {@mode} = @state DeserializerManager = require './deserializer-manager' @deserializers = new DeserializerManager(this) @@ -150,8 +146,7 @@ class Atom extends Model @contextMenu = new ContextMenuManager(devMode) @menu = new MenuManager({resourcePath}) @pasteboard = new Pasteboard() - @syntax = @deserializers.deserialize(@state.get('syntax')) ? new Syntax() - @site = new SiteShim(this) + @syntax = @deserializers.deserialize(@state.syntax) ? new Syntax() @subscribe @packages, 'activated', => @watchThemes() @@ -160,11 +155,15 @@ class Atom extends Model TokenizedBuffer = require './tokenized-buffer' DisplayBuffer = require './display-buffer' Editor = require './editor' - @registerRepresentationClasses(Project, TextBuffer, TokenizedBuffer, DisplayBuffer, Editor) - @createRepresentations() @windowEventHandler = new WindowEventHandler + # Deprecated: Callers should be converted to use atom.deserializers + registerRepresentationClass: -> + + # Deprecated: Callers should be converted to use atom.deserializers + registerRepresentationClasses: -> + # Private: setBodyPlatformClass: -> document.body.classList.add("platform-#{process.platform}") @@ -204,7 +203,7 @@ class Atom extends Model # Private: restoreWindowDimensions: -> - windowDimensions = @state.getObject('windowDimensions') ? {} + windowDimensions = @state.windowDimensions ? {} {initialSize} = @getLoadSettings() windowDimensions.height ?= initialSize?.height ? global.screen.availHeight windowDimensions.width ?= initialSize?.width ? Math.min(global.screen.availWidth, 1024) @@ -212,7 +211,7 @@ class Atom extends Model # Private: storeWindowDimensions: -> - @state.set('windowDimensions', @getWindowDimensions()) + @state.windowDimensions = @getWindowDimensions() # Public: Get the load settings for the current window. # @@ -223,21 +222,18 @@ class Atom extends Model # Private: deserializeProject: -> Project = require './project' - @project ?= new Project(path: @getLoadSettings().initialPath) + @project ?= @deserializers.deserialize(@project) ? new Project(path: @getLoadSettings().initialPath) # Private: deserializeWorkspaceView: -> WorkspaceView = require './workspace-view' - @workspaceView = @deserializers.deserialize(@state.get('workspaceView')) - unless @workspaceView? - @workspaceView = new WorkspaceView() - @state.set('workspaceView', @workspaceView.getState()) + @workspaceView = @deserializers.deserialize(@state.workspaceView) ? new WorkspaceView $(@workspaceViewParentSelector).append(@workspaceView) # Private: deserializePackageStates: -> - @packages.packageStates = @state.getObject('packageStates') ? {} - @state.remove('packageStates') + @packages.packageStates = @state.packageStates ? {} + delete @state.packageStates # Private: deserializeEditorWindow: -> @@ -259,7 +255,6 @@ class Atom extends Model @keymap.loadBundledKeymaps() @themes.loadBaseStylesheets() @packages.loadPackages() - @createRepresentations() @deserializeEditorWindow() @packages.activate() @keymap.loadUserKeymap() @@ -277,10 +272,10 @@ class Atom extends Model unloadEditorWindow: -> return if not @project and not @workspaceView - @state.set('syntax', @syntax.serialize()) - @state.set('workspaceView', @workspaceView.serialize()) + @state.syntax = @syntax.serialize() + @state.workspaceView = @workspaceView.serialize() @packages.deactivatePackages() - @state.set('packageStates', @packages.packageStates) + @state.packageStates = @packages.packageStates @saveSync() @workspaceView.remove() @workspaceView = null @@ -393,7 +388,7 @@ class Atom extends Model setImmediate => @show() @focus() - @setFullScreen(true) if @workspaceView.getState().get('fullScreen') + @setFullScreen(true) if @workspaceView.fullScreen # Public: Close the current window. close: -> @@ -449,11 +444,11 @@ class Atom extends Model # Private: saveSync: -> + stateString = JSON.stringify(@state) if statePath = @constructor.getStatePath(@mode) - super(statePath) + fs.writeFileSync(statePath, stateString, 'utf8') else - @getCurrentWindow().loadSettings.windowState = JSON.stringify(@serializeForPersistence()) - + @getCurrentWindow().loadSettings.windowState = stateString # Public: Get the time taken to completely load the current window. # diff --git a/src/cursor-view.coffee b/src/cursor-view.coffee index 1af9e2204..5a5bff89f 100644 --- a/src/cursor-view.coffee +++ b/src/cursor-view.coffee @@ -1,5 +1,5 @@ {View} = require './space-pen-extensions' -{Point, Range} = require 'telepath' +{Point, Range} = require 'text-buffer' _ = require 'underscore-plus' ### Internal ### @@ -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') diff --git a/src/cursor.coffee b/src/cursor.coffee index 718d260b1..daf91e4dc 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -1,4 +1,4 @@ -{Point, Range} = require 'telepath' +{Point, Range} = require 'text-buffer' {Emitter} = require 'emissary' _ = require 'underscore-plus' @@ -6,7 +6,7 @@ _ = require 'underscore-plus' # where text can be inserted. # # Cursors belong to {Editor}s and have some metadata attached in the form -# of a {StringMarker}. +# of a {Marker}. module.exports = class Cursor Emitter.includeInto(this) diff --git a/src/deserializer-manager.coffee b/src/deserializer-manager.coffee index 6bcc332ce..60df24b0a 100644 --- a/src/deserializer-manager.coffee +++ b/src/deserializer-manager.coffee @@ -1,5 +1,3 @@ -{TelepathicObject, Model} = require 'telepath' - # Public: Manages the deserializers used for serialized state # # Should be accessed via `atom.deserializers` @@ -12,12 +10,10 @@ class DeserializerManager # Public: Register the given class(es) as deserializers. add: (klasses...) -> @deserializers[klass.name] = klass for klass in klasses - @environment?.registerRepresentationClasses(klasses...) # Public: Add a deferred deserializer for the given class name. addDeferred: (name, fn) -> @deferredDeserializers[name] = fn - @environment?.registerDeferredRepresentationClass(name, fn) # Public: Remove the given class(es) as deserializers. remove: (klasses...) -> @@ -26,13 +22,10 @@ class DeserializerManager # Public: Deserialize the state and params. deserialize: (state, params) -> return unless state? - return state unless state.constructor is Object or state instanceof TelepathicObject if deserializer = @get(state) stateVersion = state.get?('version') ? state.version return if deserializer.version? and deserializer.version isnt stateVersion - if (state instanceof TelepathicObject) and not deserializer.acceptsDocuments - state = state.toObject() deserializer.deserialize(state, params) else console.warn "No deserializer found for", state diff --git a/src/directory.coffee b/src/directory.coffee index 54433f6ca..8a17435b6 100644 --- a/src/directory.coffee +++ b/src/directory.coffee @@ -1,8 +1,11 @@ path = require 'path' + +async = require 'async' +{Emitter} = require 'emissary' fs = require 'fs-plus' pathWatcher = require 'pathwatcher' + File = require './file' -{Emitter} = require 'emissary' # Public: Represents a directory using {File}s module.exports = @@ -41,7 +44,7 @@ class Directory # # All relative directory entries are removed and symlinks are resolved to # their final destination. - getRealPath: -> + getRealPathSync: -> unless @realPath? try @realPath = fs.realpathSync(@path) @@ -56,7 +59,7 @@ class Directory if pathToCheck.indexOf(path.join(@getPath(), path.sep)) is 0 true - else if pathToCheck.indexOf(path.join(@getRealPath(), path.sep)) is 0 + else if pathToCheck.indexOf(path.join(@getRealPathSync(), path.sep)) is 0 true else false @@ -72,26 +75,24 @@ class Directory '' else if fullPath.indexOf(path.join(@getPath(), path.sep)) is 0 fullPath.substring(@getPath().length + 1) - else if fullPath is @getRealPath() + else if fullPath is @getRealPathSync() '' - else if fullPath.indexOf(path.join(@getRealPath(), path.sep)) is 0 - fullPath.substring(@getRealPath().length + 1) + else if fullPath.indexOf(path.join(@getRealPathSync(), path.sep)) is 0 + fullPath.substring(@getRealPathSync().length + 1) else fullPath - # Public: Reads file entries in this directory from disk. + # Public: Reads file entries in this directory from disk synchronously. # - # Note: It follows symlinks. - # - # Returns an Array of {Files}. - getEntries: -> + # Returns an Array of {File} and {Directory} objects. + getEntriesSync: -> directories = [] files = [] for entryPath in fs.listSync(@path) - if stat = fs.statSyncNoException(entryPath) - symlink = fs.isSymbolicLinkSync(entryPath) - else - continue + if stat = fs.lstatSyncNoException(entryPath) + symlink = stat.isSymbolicLink() + stat = fs.statSyncNoException(entryPath) if symlink + continue unless stat if stat.isDirectory() directories.push(new Directory(entryPath, symlink)) else if stat.isFile() @@ -99,6 +100,34 @@ class Directory directories.concat(files) + # Public: Reads file entries in this directory from disk asynchronously. + # + # * callback: A function to call with an Error as the first argument and + # an Array of {File} and {Directory} objects as the second argument. + getEntries: (callback) -> + fs.list @path, (error, entries) -> + return callback(error) if error? + + directories = [] + files = [] + addEntry = (entryPath, stat, symlink, callback) -> + if stat?.isDirectory() + directories.push(new Directory(entryPath, symlink)) + else if stat?.isFile() + files.push(new File(entryPath, symlink)) + callback() + + statEntry = (entryPath, callback) -> + fs.lstat entryPath, (error, stat) -> + if stat?.isSymbolicLink() + fs.stat entryPath, (error, stat) -> + addEntry(entryPath, stat, true, callback) + else + addEntry(entryPath, stat, false, callback) + + async.eachLimit entries, 1, statEntry, -> + callback(null, directories.concat(files)) + # Private: subscribeToNativeChangeEvents: -> unless @watchSubscription? diff --git a/src/display-buffer-marker.coffee b/src/display-buffer-marker.coffee index 1420cadbc..05f5d67b3 100644 --- a/src/display-buffer-marker.coffee +++ b/src/display-buffer-marker.coffee @@ -1,4 +1,4 @@ -{Range} = require 'telepath' +{Range} = require 'text-buffer' _ = require 'underscore-plus' {Emitter, Subscriber} = require 'emissary' @@ -42,7 +42,7 @@ class DisplayBufferMarker # Modifies the screen range of the display marker. # # screenRange - The new {Range} to use - # options - A hash of options matching those found in {StringMarker.setRange} + # options - A hash of options matching those found in {Marker.setRange} setScreenRange: (screenRange, options) -> @setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options) @@ -55,7 +55,7 @@ class DisplayBufferMarker # Modifies the buffer range of the display marker. # # screenRange - The new {Range} to use - # options - A hash of options matching those found in {StringMarker.setRange} + # options - A hash of options matching those found in {Marker.setRange} setBufferRange: (bufferRange, options) -> @bufferMarker.setRange(bufferRange, options) @@ -140,19 +140,10 @@ class DisplayBufferMarker # Returns a {Boolean} indicating whether the marker has been destroyed. A marker # can be invalid without being destroyed, in which case undoing the invalidating # operation would restore the marker. Once a marker is destroyed by calling - # {StringMarker.destroy}, no undo/redo operation can ever bring it back. + # {Marker.destroy}, no undo/redo operation can ever bring it back. isDestroyed: -> @bufferMarker.isDestroyed() - getOriginSiteId: -> - @bufferMarker.getOriginSiteId() - - isLocal: -> - @bufferMarker.isLocal() - - isRemote: -> - @bufferMarker.isRemote() - getAttributes: -> @bufferMarker.getAttributes() @@ -160,7 +151,7 @@ class DisplayBufferMarker @bufferMarker.setAttributes(attributes) matchesAttributes: (attributes) -> - attributes = @displayBuffer.translateToStringMarkerAttributes(attributes) + attributes = @displayBuffer.translateToBufferMarkerAttributes(attributes) @bufferMarker.matchesAttributes(attributes) # Destroys the marker diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 2dca4dea6..a7904fcad 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -1,7 +1,9 @@ _ = require 'underscore-plus' {Emitter, Subscriber} = require 'emissary' guid = require 'guid' -{Model, Point, Range} = require 'telepath' +Serializable = require 'serializable' +{Model} = require 'theorist' +{Point, Range} = require 'text-buffer' TokenizedBuffer = require './tokenized-buffer' RowMap = require './row-map' Fold = require './fold' @@ -12,28 +14,18 @@ ConfigObserver = require './config-observer' # Private: module.exports = class DisplayBuffer extends Model + Serializable.includeInto(this) _.extend @prototype, ConfigObserver @properties - tokenizedBuffer: null - softWrap: -> atom.config.get('editor.softWrap') ? false + softWrap: null editorWidthInChars: null - constructor: -> + constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer}={}) -> super - @deserializing = @state? - - created: -> - if @deserializing - @deserializing = false - return - - if @tokenizedBuffer? - @tokenizedBuffer?.created() - else - @tokenizedBuffer = new TokenizedBuffer({@tabLength, @buffer, project: atom.project}) + @softWrap ?= atom.config.get('editor.softWrap') ? false + @tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer}) @buffer = @tokenizedBuffer.buffer - @markers = {} @foldsByMarkerId = {} @updateAllScreenLines() @@ -43,10 +35,9 @@ class DisplayBuffer extends Model @subscribe @buffer, 'markers-updated', @handleBufferMarkersUpdated @subscribe @buffer, 'marker-created', @handleBufferMarkerCreated - @subscribe @state, 'changed', ({newValues}) => - if newValues.softWrap? - @emit 'soft-wrap-changed', newValues.softWrap - @updateWrappedScreenLines() + @subscribe @$softWrap, 'value', (softWrap) => + @emit 'soft-wrap-changed', softWrap + @updateWrappedScreenLines() @observeConfig 'editor.preferredLineLength', callNow: false, => @updateWrappedScreenLines() if @softWrap and atom.config.get('editor.softWrapAtPreferredLineLength') @@ -54,8 +45,18 @@ class DisplayBuffer extends Model @observeConfig 'editor.softWrapAtPreferredLineLength', callNow: false, => @updateWrappedScreenLines() if @softWrap + serializeParams: -> + id: @id + softWrap: @softWrap + editorWidthInChars: @editorWidthInChars + tokenizedBuffer: @tokenizedBuffer.serialize() + + deserializeParams: (params) -> + params.tokenizedBuffer = TokenizedBuffer.deserialize(params.tokenizedBuffer) + params + copy: -> - newDisplayBuffer = atom.create(new DisplayBuffer({@buffer, tabLength: @getTabLength()})) + newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength()}) for marker in @findMarkers(displayBufferId: @id) marker.copy(displayBufferId: newDisplayBuffer.id) newDisplayBuffer @@ -468,7 +469,7 @@ class DisplayBuffer extends Model # Constructs a new marker at the given screen range. # # range - The marker {Range} (representing the distance between the head and tail) - # options - Options to pass to the {StringMarker} constructor + # options - Options to pass to the {Marker} constructor # # Returns a {Number} representing the new marker's ID. markScreenRange: (args...) -> @@ -478,7 +479,7 @@ class DisplayBuffer extends Model # Constructs a new marker at the given buffer range. # # range - The marker {Range} (representing the distance between the head and tail) - # options - Options to pass to the {StringMarker} constructor + # options - Options to pass to the {Marker} constructor # # Returns a {Number} representing the new marker's ID. markBufferRange: (args...) -> @@ -487,7 +488,7 @@ class DisplayBuffer extends Model # Constructs a new marker at the given screen position. # # range - The marker {Range} (representing the distance between the head and tail) - # options - Options to pass to the {StringMarker} constructor + # options - Options to pass to the {Marker} constructor # # Returns a {Number} representing the new marker's ID. markScreenPosition: (screenPosition, options) -> @@ -496,7 +497,7 @@ class DisplayBuffer extends Model # Constructs a new marker at the given buffer position. # # range - The marker {Range} (representing the distance between the head and tail) - # options - Options to pass to the {StringMarker} constructor + # options - Options to pass to the {Marker} constructor # # Returns a {Number} representing the new marker's ID. markBufferPosition: (bufferPosition, options) -> @@ -526,10 +527,10 @@ class DisplayBuffer extends Model # # Returns an {Array} of {DisplayBufferMarker}s findMarkers: (attributes) -> - attributes = @translateToStringMarkerAttributes(attributes) + attributes = @translateToBufferMarkerAttributes(attributes) @buffer.findMarkers(attributes).map (stringMarker) => @getMarker(stringMarker.id) - translateToStringMarkerAttributes: (attributes) -> + translateToBufferMarkerAttributes: (attributes) -> stringMarkerAttributes = {} for key, value of attributes switch key diff --git a/src/editor-view.coffee b/src/editor-view.coffee index d11ddb4cc..9c51a5554 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -1,7 +1,7 @@ {View, $, $$$} = require './space-pen-extensions' TextBuffer = require './text-buffer' Gutter = require './gutter' -{Point, Range} = require 'telepath' +{Point, Range} = require 'text-buffer' Editor = require './editor' CursorView = require './cursor-view' SelectionView = require './selection-view' @@ -105,12 +105,12 @@ class EditorView extends View if editor? @edit(editor) else if @mini - @edit(atom.create(new Editor - buffer: atom.create(new TextBuffer) + @edit(new Editor + buffer: new TextBuffer softWrap: false tabLength: 2 softTabs: true - )) + ) else throw new Error("Must supply an Editor or mini: true") @@ -1217,8 +1217,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: -> diff --git a/src/editor.coffee b/src/editor.coffee index 24be86dfe..cff06ddfc 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1,6 +1,8 @@ _ = require 'underscore-plus' path = require 'path' -{Model, Point, Range} = require 'telepath' +Serializable = require 'serializable' +{Model} = require 'theorist' +{Point, Range} = require 'text-buffer' LanguageMode = require './language-mode' DisplayBuffer = require './display-buffer' Cursor = require './cursor' @@ -27,17 +29,12 @@ 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 + Serializable.includeInto(this) + atom.deserializers.add(this) @properties - displayBuffer: null - softTabs: null scrollTop: 0 scrollLeft: 0 @@ -47,33 +44,18 @@ class Editor extends Model buffer: null languageMode: null cursors: null - remoteCursors: null selections: null - remoteSelections: null suppressSelectionMerging: false - constructor: -> + constructor: ({@softTabs, initialLine, tabLength, softWrap, @displayBuffer, buffer, registerEditor, suppressCursorCreation}) -> super - @deserializing = @state? - - created: -> - if @deserializing - @deserializing = false - @callDisplayBufferCreatedHook = true - @registerEditor = true - return @cursors = [] - @remoteCursors = [] @selections = [] - @remoteSelections = [] - unless @displayBuffer? - @displayBuffer = new DisplayBuffer({@buffer, @tabLength, @softWrap}) - @softTabs = @buffer.usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true - - @displayBuffer.created() if @callDisplayBufferCreatedHook + @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrap}) @buffer = @displayBuffer.buffer + @softTabs = @buffer.usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true for marker in @findMarkers(@getSelectionMarkerAttributes()) marker.setAttributes(preserveFolds: true) @@ -82,11 +64,11 @@ class Editor extends Model @subscribeToBuffer() @subscribeToDisplayBuffer() - if @getCursors().length is 0 and not @suppressCursorCreation - if @initialLine - position = [@initialLine, 0] + if @getCursors().length is 0 and not suppressCursorCreation + if initialLine + position = [initialLine, 0] else - position = _.last(@getRemoteCursors())?.getBufferPosition() ? [0, 0] + position = [0, 0] @addCursorAtBufferPosition(position) @languageMode = new LanguageMode(this, @buffer.getExtension()) @@ -94,10 +76,19 @@ class Editor extends Model @subscribe @$scrollTop, 'value', (scrollTop) => @emit 'scroll-top-changed', scrollTop @subscribe @$scrollLeft, 'value', (scrollLeft) => @emit 'scroll-left-changed', scrollLeft - atom.project.addEditor(this) if @registerEditor + atom.project.addEditor(this) if registerEditor - # Deprecated: The goal is a world where we don't call serialize explicitly - serialize: -> this + serializeParams: -> + id: @id + softTabs: @softTabs + scrollTop: @scrollTop + scrollLeft: @scrollLeft + displayBuffer: @displayBuffer.serialize() + + deserializeParams: (params) -> + params.displayBuffer = DisplayBuffer.deserialize(params.displayBuffer) + params.registerEditor = true + params # Private: subscribeToBuffer: -> @@ -133,15 +124,13 @@ class Editor extends Model @displayBuffer.destroy() @languageMode.destroy() atom.project?.removeEditor(this) - @emit 'destroyed' - @off() # Private: Creates an {Editor} with the same initial state copy: -> tabLength = @getTabLength() displayBuffer = @displayBuffer.copy() softTabs = @getSoftTabs() - newEditor = @create(new Editor({@buffer, displayBuffer, tabLength, softTabs, suppressCursorCreation: true})) + newEditor = new Editor({@buffer, displayBuffer, tabLength, softTabs, suppressCursorCreation: true}) newEditor.setScrollTop(@getScrollTop()) newEditor.setScrollLeft(@getScrollLeft()) for marker in @findMarkers(editorId: @id) @@ -823,11 +812,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...) @@ -835,9 +819,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()) @@ -852,10 +833,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 @@ -876,12 +854,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 @@ -939,10 +912,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. # @@ -962,10 +932,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. @@ -980,21 +946,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}. @@ -1064,12 +1021,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() diff --git a/src/fold.coffee b/src/fold.coffee index 07ce988ec..32de3f988 100644 --- a/src/fold.coffee +++ b/src/fold.coffee @@ -1,4 +1,4 @@ -{Point, Range} = require 'telepath' +{Point, Range} = require 'text-buffer' # Private: Represents a fold that collapses multiple buffer lines into a single # line on the screen. @@ -38,6 +38,7 @@ class Fold getBufferRange: ({includeNewline}={}) -> range = @marker.getRange() if includeNewline + range = range.copy() range.end.row++ range.end.column = 0 range diff --git a/src/git.coffee b/src/git.coffee index 0137a6b6d..a88d0618f 100644 --- a/src/git.coffee +++ b/src/git.coffee @@ -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) -> diff --git a/src/gutter.coffee b/src/gutter.coffee index 710200473..7b7ee15a1 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -1,5 +1,5 @@ {View, $, $$, $$$} = require './space-pen-extensions' -{Range} = require 'telepath' +{Range} = require 'text-buffer' _ = require 'underscore-plus' # Private: Represents the portion of the {EditorView} containing row numbers. diff --git a/src/language-mode.coffee b/src/language-mode.coffee index 2da82e8dc..426526185 100644 --- a/src/language-mode.coffee +++ b/src/language-mode.coffee @@ -1,4 +1,4 @@ -{Range} = require 'telepath' +{Range} = require 'text-buffer' _ = require 'underscore-plus' {OnigRegExp} = require 'oniguruma' {Emitter, Subscriber} = require 'emissary' diff --git a/src/pane-axis.coffee b/src/pane-axis.coffee index f8ed436a3..e256c58be 100644 --- a/src/pane-axis.coffee +++ b/src/pane-axis.coffee @@ -1,39 +1,26 @@ +Serializable = require 'serializable' {$, View} = require './space-pen-extensions' -{TelepathicObject} = require 'telepath' ### Internal ### module.exports = class PaneAxis extends View - @acceptsDocuments: true + Serializable.includeInto(this) - @deserialize: (state) -> - new this(state) + initialize: ({children}={}) -> + @addChild(child) for child in children ? [] - initialize: (args...) -> - if args[0] instanceof TelepathicObject - @state = args[0] - @state.get('children').each (child, index) => - @addChild(atom.deserializers.deserialize(child), index, updateState: false) - else - @state = atom.create(deserializer: @className(), children: []) - @addChild(child) for child in args + serializeParams: -> + children: @children().views().map (child) -> child.serialize() - @state.get('children').on 'changed', ({index, insertedValues, removedValues, siteId}) => - return if siteId is @state.siteId - for childState in removedValues - @removeChild(@children(":eq(#{index})").view(), updateState: false) - for childState, i in insertedValues - @addChild(atom.deserializers.deserialize(childState), index + i, updateState: false) + deserializeParams: (params) -> + params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState) + params - addChild: (child, index=@children().length, options={}) -> + addChild: (child, index=@children().length) -> @insertAt(index, child) - state = child.getState() - @state.get('children').insert(index, state) if options.updateState ? true @getContainer()?.adjustPaneDimensions() - removeChild: (child, options={}) -> - options.updateState ?= true - + removeChild: (child) -> parent = @parent().view() container = @getContainer() childWasInactive = not child.isActive?() @@ -54,11 +41,10 @@ class PaneAxis extends View if parent.setRoot? parent.setRoot(sibling, suppressPaneItemChangeEvents: childWasInactive) else - parent.insertChildBefore(this, sibling, options) - parent.removeChild(this, options) + parent.insertChildBefore(this, sibling) + parent.removeChild(this) sibling.focus() if siblingFocused else - @state.get('children').remove(@indexOf(child)) if options.updateState primitiveRemove(child) container.adjustPaneDimensions() @@ -66,7 +52,6 @@ class PaneAxis extends View container.trigger 'pane:removed', [child] if child instanceof Pane detachChild: (child) -> - @state.get('children').remove(@indexOf(child)) child.detach() getContainer: -> @@ -78,25 +63,11 @@ class PaneAxis extends View getActivePane: -> @find('.pane.active').view() ? @find('.pane:first').view() - insertChildBefore: (child, newChild, options={}) -> + insertChildBefore: (child, newChild) -> newChild.insertBefore(child) - if options.updateState ? true - children = @state.get('children') - childIndex = children.indexOf(child.getState()) - children.insert(childIndex, newChild.getState()) insertChildAfter: (child, newChild) -> newChild.insertAfter(child) - children = @state.get('children') - childIndex = children.indexOf(child.getState()) - children.insert(childIndex + 1, newChild.getState()) - - serialize: -> - state = @state.clone() - state.set('children', child.serialize() for child in @children().views()) - state - - getState: -> @state horizontalChildUnits: -> $(child).view().horizontalGridUnits() for child in @children() diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 97818aef7..4ecda8359 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -1,37 +1,18 @@ +Serializable = require 'serializable' {$, View} = require './space-pen-extensions' Pane = require './pane' -{TelepathicObject} = require 'telepath' # Private: Manages the list of panes within a {WorkspaceView} module.exports = class PaneContainer extends View + Serializable.includeInto(this) atom.deserializers.add(this) - ### Internal ### - @acceptsDocuments: true - - @deserialize: (state) -> - container = new PaneContainer(state) - container.removeEmptyPanes() - container - @content: -> @div class: 'panes' - initialize: (state) -> - if state instanceof TelepathicObject - @state = state - @setRoot(atom.deserializers.deserialize(@state.get('root'))) - else - @state = atom.create(deserializer: 'PaneContainer') - - @subscribe @state, 'changed', ({newValues, siteId}) => - return if siteId is @state.siteId - if newValues.hasOwnProperty('root') - if rootState = newValues.root - @setRoot(deserialize(rootState)) - else - @setRoot(null) + initialize: ({root}={}) -> + @setRoot(root) @subscribe this, 'pane:attached', (event, pane) => @triggerActiveItemChange() if @getActivePane() is pane @@ -48,12 +29,12 @@ class PaneContainer extends View triggerActiveItemChange: -> @trigger 'pane-container:active-pane-item-changed', [@getActivePaneItem()] - serialize: -> - state = @state.clone() - state.set('root', @getRoot()?.serialize()) - state + serializeParams: -> + root: @getRoot()?.serialize() - getState: -> @state + deserializeParams: (params) -> + params.root = atom.deserializers.deserialize(params.root) + params ### Public ### @@ -95,7 +76,6 @@ class PaneContainer extends View if root? @append(root) root.makeActive?() - @state.set(root: root?.getState()) removeChild: (child) -> throw new Error("Removing non-existant child") unless @getRoot() is child diff --git a/src/pane.coffee b/src/pane.coffee index 3e2fb8643..d976a95f0 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -1,7 +1,8 @@ {dirname} = require 'path' {$, View} = require './space-pen-extensions' _ = require 'underscore-plus' -{TelepathicObject} = require 'telepath' +Serializable = require 'serializable' + PaneRow = require './pane-row' PaneColumn = require './pane-column' @@ -13,51 +14,31 @@ PaneColumn = require './pane-column' # building a package that deals with switching between panes or tiems. module.exports = class Pane extends View + Serializable.includeInto(this) - @acceptsDocuments: true + @version: 1 @content: (wrappedView) -> @div class: 'pane', tabindex: -1, => @div class: 'item-views', outlet: 'itemViews' - @deserialize: (state) -> - pane = new Pane(state) - pane.focusOnAttach = true if state.get('focused') - pane - activeItem: null items: null viewsByItem: null # Views without a setModel() method are stored here # Private: initialize: (args...) -> - @items = [] - if args[0] instanceof TelepathicObject - @state = args[0] - @items = _.compact(@state.get('items').getValues()) - item?.created?() for item in @getItems() + if args[0]?.items # deserializing + {@items, activeItemUri, @focusOnAttach} = args[0] else @items = args - @state = atom.create - deserializer: 'Pane' - items: @items + + @items ?= [] @handleItemEvents(item) for item in @items - @subscribe @state.get('items'), 'changed', ({index, removedValues, insertedValues, siteId}) => - return if siteId is @state.siteId - for item in removedValues - @removeItemAtIndex(index, updateState: false) - for item, i in insertedValues - @addItem(itemState, index + i, updateState: false) - - @subscribe @state, 'changed', ({newValues, siteId}) => - return if siteId is @state.siteId - if newValues.activeItemUri - @showItemForUri(newValues.activeItemUri) - @viewsByItem = new WeakMap() - activeItemUri = @state.get('activeItemUri') + unless activeItemUri? and @showItemForUri(activeItemUri) @showItem(@items[0]) if @items.length > 0 @@ -84,6 +65,15 @@ class Pane extends View @on 'focus', => @activeView?.focus(); false @on 'focusin', => @makeActive() + deserializeParams: (params) -> + params.items = _.compact(params.items.map (itemState) -> atom.deserializers.deserialize(itemState)) + params + + serializeParams: -> + items: _.compact(@items.map (item) -> item.serialize?()) + focusOnAttach: @is(':has(:focus)') + activeItemUri: @getActivePaneItem()?.getUri?() + # Private: afterAttach: (onDom) -> if @focusOnAttach and onDom @@ -173,17 +163,14 @@ class Pane extends View @activeView = view @trigger 'pane:active-item-changed', [item] - @state.set('activeItemUri', item.getUri?()) - # Private: activeItemTitleChanged: => @trigger 'pane:active-item-title-changed' # Public: Add an additional item at the specified index. - addItem: (item, index=@getActiveItemIndex()+1, options={}) -> + addItem: (item, index=@getActiveItemIndex() + 1) -> return if _.include(@items, item) - @state.get('items').splice(index, 0, item) if options.updateState ? true @items.splice(index, 0, item) @trigger 'pane:item-added', [item, index] @handleItemEvents(item) @@ -191,8 +178,7 @@ class Pane extends View handleItemEvents: (item) -> if _.isFunction(item.on) - @subscribe item, 'destroyed', => - @destroyItem(item, updateState: false) if @state.isAlive() + @subscribe item, 'destroyed', => @destroyItem(item) # Public: Remove the currently active item. destroyActiveItem: => @@ -268,17 +254,16 @@ class Pane extends View @saveItem(item) for item in @getItems() # Public: - removeItem: (item, options) -> + removeItem: (item) -> index = @items.indexOf(item) - @removeItemAtIndex(index, options) if index >= 0 + @removeItemAtIndex(index) if index >= 0 # Public: Just remove the item at the given index. - removeItemAtIndex: (index, options={}) -> + removeItemAtIndex: (index) -> item = @items[index] @activeItem.off? 'title-changed', @activeItemTitleChanged if item is @activeItem @showNextItem() if item is @activeItem and @items.length > 1 _.remove(@items, item) - @state.get('items').splice(index, 1) if options.updateState ? true @cleanupItemView(item) @trigger 'pane:item-removed', [item, index] @@ -287,14 +272,13 @@ class Pane extends View oldIndex = @items.indexOf(item) @items.splice(oldIndex, 1) @items.splice(newIndex, 0, item) - @state.get('items').insert(newIndex, item) @trigger 'pane:item-moved', [item, newIndex] # Public: Moves the given item to another pane. moveItemToPane: (item, pane, index) -> @isMovingItem = true pane.addItem(item, index) - @removeItem(item, updateState: false) + @removeItem(item) @isMovingItem = false # Public: Finds the first item that matches the given uri. @@ -330,7 +314,7 @@ class Pane extends View else if @isMovingItem and viewToRemove?.setModel viewToRemove.setModel(null) # dont want to destroy the model, so set to null - @parent().view().removeChild(this, updateState: false) + @parent().view().removeChild(this) # Private: viewForItem: (item) -> @@ -348,16 +332,6 @@ class Pane extends View viewForActiveItem: -> @viewForItem(@activeItem) - # Private: - serialize: -> - state = @state.clone() - state.set('items', @items) - state.set('focused', @is(':has(:focus)')) - state - - # Private: - getState: -> @state - # Private: adjustDimensions: -> # do nothing diff --git a/src/project.coffee b/src/project.coffee index 49effe0bb..7a32a933e 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -4,7 +4,9 @@ url = require 'url' _ = require 'underscore-plus' fs = require 'fs-plus' Q = require 'q' -{Model} = require 'telepath' +{Model} = require 'theorist' +{Emitter, Subscriber} = require 'emissary' +Serializable = require 'serializable' TextBuffer = require './text-buffer' Editor = require './editor' @@ -18,10 +20,8 @@ Git = require './git' # of directories and files that you can operate on. module.exports = class Project extends Model - - @properties - buffers: [] - path: null + atom.deserializers.add(this) + Serializable.includeInto(this) # Public: Find the local path for the given repository URL. @pathForRepositoryUrl: (repoUrl) -> @@ -29,18 +29,23 @@ class Project extends Model repoName = repoName.replace(/\.git$/, '') path.join(atom.config.get('core.projectHome'), repoName) - # Private: Called by telepath. - created: -> - for buffer in @buffers.getValues() - buffer.once 'destroyed', (buffer) => @removeBuffer(buffer) if @isAlive() + constructor: ({path, @buffers}={}) -> + @buffers ?= [] + for buffer in @buffers + do (buffer) => + buffer.once 'destroyed', => @removeBuffer(buffer) @openers = [] @editors = [] - @setPath(@path) + @setPath(path) - # Private: Called by telepath. - willBePersisted: -> - @destroyUnretainedBuffers() + serializeParams: -> + path: @path + buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained()) + + deserializeParams: (params) -> + params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState) + params # Public: Register an opener for project files. # @@ -177,7 +182,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 +190,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,11 +238,12 @@ 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) - buffer.once 'destroyed', => @removeBuffer(buffer) if @isAlive() + @buffers.splice(index, 0, buffer) + buffer.once 'destroyed', => @removeBuffer(buffer) @emit 'buffer-created', buffer buffer @@ -285,7 +291,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 +312,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 +330,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 @@ -336,7 +342,7 @@ class Project extends Model # Private: buildEditorForBuffer: (buffer, editorOptions) -> - editor = @create(new Editor(_.extend({buffer}, editorOptions))) + editor = new Editor(_.extend({buffer}, editorOptions)) @addEditor(editor) editor diff --git a/src/selection-view.coffee b/src/selection-view.coffee index 6dc301bab..beea12f47 100644 --- a/src/selection-view.coffee +++ b/src/selection-view.coffee @@ -1,4 +1,4 @@ -{Point, Range} = require 'telepath' +{Point, Range} = require 'text-buffer' {View, $$} = require './space-pen-extensions' # Internal: @@ -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() diff --git a/src/selection.coffee b/src/selection.coffee index 343c55179..fa5078b67 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -1,4 +1,4 @@ -{Range} = require 'telepath' +{Range} = require 'text-buffer' {Emitter} = require 'emissary' {pick} = require 'underscore-plus' @@ -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() diff --git a/src/site-shim.coffee b/src/site-shim.coffee deleted file mode 100644 index 0f1751a01..000000000 --- a/src/site-shim.coffee +++ /dev/null @@ -1,8 +0,0 @@ -# Private: TODO remove once telepath upgrades are complete. -module.exports = -class SiteShim - constructor: (@environment) -> - {@id} = @environment.state.siteId - - createDocument: (values) -> - @environment.create(values) diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index 8ea770bcc..f988975b6 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -2,24 +2,24 @@ _ = require 'underscore-plus' diff = require 'diff' Q = require 'q' {P} = require 'scandal' -telepath = require 'telepath' +Serializable = require 'serializable' +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 extends TextBufferCore + atom.deserializers.add(this) + + Serializable.includeInto(this) + Subscriber.includeInto(this) + Emitter.includeInto(this) stoppedChangingDelay: 300 stoppedChangingTimeout: null @@ -29,28 +29,32 @@ class TextBuffer extends telepath.Model file: null refcount: 0 - constructor: -> + constructor: ({filePath, @modifiedWhenLastPersisted, @digestWhenLastPersisted, loadWhenAttached}={}) -> super - - @loadWhenAttached = @getState()? - - # Private: Called by telepath. - created: -> @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 this, 'changed', @handleTextChange - @setPath(@filePath) + @setPath(filePath) - @load() if @loadWhenAttached + @load() if loadWhenAttached - # Private: Called by telepath. - willBePersisted: -> - @modifiedWhenLastPersisted = @isModified() - @digestWhenLastPersisted = @file?.getDigest() + # Private: + serializeParams: -> + params = super + _.extend params, + filePath: @getPath() + modifiedWhenLastPersisted: @isModified() + digestWhenLastPersisted: @file?.getDigest() + + # Private: + deserializeParams: (params) -> + params = super(params) + params.loadWhenAttached = true + params loadSync: -> @updateCachedDiskContentsSync() @@ -66,7 +70,7 @@ class TextBuffer extends telepath.Model @emitModifiedStatusChanged(true) else @reload() - @text.clearUndoStack() + @clearUndoStack() this ### Internal ### @@ -74,16 +78,19 @@ 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 @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 @@ -255,50 +262,12 @@ class TextBuffer extends telepath.Model lastRow = @getLastRow() new Range([0, 0], [lastRow, @lineLengthForRow(lastRow)]) - # Given a range, returns the lines of text within it. - # - # range - A {Range} object specifying your points of interest - # - # Returns a {String} of the combined lines. - getTextInRange: (range) -> - @text.getTextInRange(@clipRange(range)) - - # Gets all the lines in a file. - # - # Returns an {Array} of {String}s. - getLines: -> - @text.getLines() - - # Given a row, returns the line of text. - # - # row - A {Number} indicating the row. - # - # Returns a {String}. - lineForRow: (row) -> - @text.lineForRow(row) - - # Given a row, returns its line ending. - # - # row - A {Number} indicating the row. - # - # Returns a {String}, or `undefined` if `row` is the final row. - lineEndingForRow: (row) -> - @text.lineEndingForRow(row) - suggestedLineEndingForRow: (row) -> if row is @getLastRow() @lineEndingForRow(row - 1) else @lineEndingForRow(row) - # Given a row, returns the length of the line of text. - # - # row - A {Number} indicating the row. - # - # Returns a {Number}. - lineLengthForRow: (row) -> - @text.lineLengthForRow(row) - # Given a row, returns the length of the line ending # # row - A {Number} indicating the row. @@ -320,18 +289,6 @@ class TextBuffer extends telepath.Model else new Range([row, 0], [row, @lineLengthForRow(row)]) - # Gets the number of lines in a file. - # - # Returns a {Number}. - getLineCount: -> - @text.getLineCount() - - # Gets the row number of the last line. - # - # Returns a {Number}. - getLastRow: -> - @getLineCount() - 1 - # Finds the last line in the current buffer. # # Returns a {String}. @@ -345,12 +302,6 @@ class TextBuffer extends telepath.Model lastRow = @getLastRow() new Point(lastRow, @lineLengthForRow(lastRow)) - characterIndexForPosition: (position) -> - @text.indexForPoint(@clipPosition(position)) - - positionForCharacterIndex: (index) -> - @text.pointForIndex(index) - # Given a row, this deletes it from the buffer. # # row - A {Number} representing the row to delete @@ -394,35 +345,6 @@ class TextBuffer extends telepath.Model delete: (range) -> @change(range, '') - # Given a position, this clips it to a real position. - # - # For example, if `position`'s row exceeds the row count of the buffer, - # or if its column goes beyond a line's length, this "sanitizes" the value - # to a real position. - # - # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. - clipPosition: (position) -> - @text.clipPosition(position) - - # Given a range, this clips it to a real range. - # - # For example, if `range`'s row exceeds the row count of the buffer, - # or if its column goes beyond a line's length, this "sanitizes" the value - # to a real range. - # - # range - The {Range} to clip - # - # Returns the new, clipped {Range}. Note that this could be the same as `range` if no clipping was performed. - clipRange: (range) -> - range = Range.fromObject(range) - new Range(@clipPosition(range.start), @clipPosition(range.end)) - - undo: -> - @text.undo() - - redo: -> - @text.redo() - # Saves the buffer. save: -> @saveAs(@getPath()) if @isModified() @@ -458,67 +380,14 @@ class TextBuffer extends telepath.Model # Returns a {Boolean}. isInConflict: -> @conflict - # Identifies if a buffer is empty. - # - # Returns a {Boolean}. - isEmpty: -> @text.isEmpty() - - # Returns all valid {StringMarker}s on the buffer. - getMarkers: -> - @text.getMarkers() - - # Returns the {StringMarker} with the given id. - getMarker: (id) -> - @text.getMarker(id) - destroyMarker: (id) -> @getMarker(id)?.destroy() - # Public: Finds the first marker satisfying the given attributes - # - # Returns a {String} marker-identifier - findMarker: (attributes) -> - @text.findMarker(attributes) - - # Public: Finds all markers satisfying the given attributes - # - # attributes - The attributes against which to compare the markers' attributes - # There are some reserved keys that match against derived marker properties: - # startRow - The row at which the marker starts - # endRow - The row at which the marker ends - # - # Returns an {Array} of {StringMarker}s - findMarkers: (attributes) -> - @text.findMarkers(attributes) - # Retrieves the quantity of markers in a buffer. # # Returns a {Number}. getMarkerCount: -> - @text.getMarkers().length - - # Constructs a new marker at a given range. - # - # range - The marker {Range} (representing the distance between the head and tail) - # attributes - An optional hash of serializable attributes - # Any attributes you pass will be associated with the marker and can be retrieved - # or used in marker queries. - # The following attribute keys reserved, and control the marker's initial range - # isReversed - if `true`, the marker is reversed; that is, its head precedes the tail - # hasTail - if `false`, the marker is created without a tail - # - # Returns a {Number} representing the new marker's ID. - markRange: (range, options={}) -> - @text.markRange(range, options) - - # Constructs a new marker at a given position. - # - # position - The marker {Point}; there won't be a tail - # options - Options to pass to the {StringMarker} constructor - # - # Returns a {Number} representing the new marker's ID. - markPosition: (position, options) -> - @text.markPosition(position, options) + @getMarkers().length # Identifies if a character sequence is within a certain range. # @@ -677,18 +546,10 @@ class TextBuffer extends telepath.Model ### Internal ### - transact: (fn) -> @text.transact fn - - beginTransaction: -> @text.beginTransaction() - - commitTransaction: -> @text.commitTransaction() - - abortTransaction: -> @text.abortTransaction() - change: (oldRange, newText, options={}) -> oldRange = @clipRange(oldRange) newText = @normalizeLineEndings(oldRange.start.row, newText) if options.normalizeLineEndings ? true - @text.setTextInRange(oldRange, newText, options) + @setTextInRange(oldRange, newText, options) normalizeLineEndings: (startRow, text) -> if lineEnding = @suggestedLineEndingForRow(startRow) diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index ac3cf01b9..bb28a708e 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -1,5 +1,7 @@ _ = require 'underscore-plus' -{Model, Point, Range} = require 'telepath' +{Model} = require 'theorist' +{Point, Range} = require 'text-buffer' +Serializable = require 'serializable' TokenizedLine = require './tokenized-line' Token = require './token' @@ -7,10 +9,9 @@ Token = require './token' module.exports = class TokenizedBuffer extends Model - @properties - bufferPath: null - tabLength: -> atom.config.get('editor.tabLength') ? 2 - project: null + Serializable.includeInto(this) + + @property 'tabLength' grammar: null currentGrammarScore: null @@ -20,19 +21,8 @@ class TokenizedBuffer extends Model invalidRows: null visible: false - constructor: -> - super - @deserializing = @state? - - created: -> - if @deserializing - @deserializing = false - return - - if @buffer? and @buffer.isAlive() - @bufferPath = @buffer.getPath() - else - @buffer = @project.bufferForPathSync(@bufferPath) + constructor: ({@buffer, @tabLength}) -> + @tabLength ?= atom.config.get('editor.tabLength') ? 2 @subscribe atom.syntax, 'grammar-added grammar-updated', (grammar) => if grammar.injectionSelector? @@ -56,12 +46,13 @@ class TokenizedBuffer extends Model @reloadGrammar() - # TODO: Remove when everything is a telepath model - destroy: -> - @destroyed() + serializeParams: -> + bufferPath: @buffer.getPath() + tabLength: @tabLength - destroyed: -> - @unsubscribe() + deserializeParams: (params) -> + params.buffer = atom.project.bufferForPathSync(params.bufferPath) + params setGrammar: (grammar, score) -> return if grammar is @grammar diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index a8f5f15ad..e13dab341 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -4,7 +4,7 @@ Q = require 'q' {$, $$, View} = require './space-pen-extensions' _ = require 'underscore-plus' fs = require 'fs-plus' -{TelepathicObject} = require 'telepath' +Serializable = require 'serializable' EditorView = require './editor-view' Pane = require './pane' PaneColumn = require './pane-column' @@ -38,9 +38,10 @@ Editor = require './editor' # module.exports = class WorkspaceView extends View + Serializable.includeInto(this) atom.deserializers.add(this, Pane, PaneRow, PaneColumn, EditorView) - @version: 1 + @version: 2 @configDefaults: ignoredNames: [".git", ".svn", ".DS_Store"] @@ -50,31 +51,16 @@ class WorkspaceView extends View projectHome: path.join(fs.getHomeDirectory(), 'github') audioBeep: true - @acceptsDocuments: true - # Private: - @content: (state) -> + @content: -> @div class: 'workspace', tabindex: -1, => @div class: 'horizontal', outlet: 'horizontal', => @div class: 'vertical', outlet: 'vertical', => @div class: 'panes', outlet: 'panes' # Private: - @deserialize: (state) -> - new WorkspaceView(state) - - # Private: - initialize: (state={}) -> - if state instanceof TelepathicObject - @state = state - panes = atom.deserializers.deserialize(state.get('panes')) - else - panes = new PaneContainer - @state = atom.create - deserializer: @constructor.name - version: @constructor.version - panes: panes.getState() - + initialize: ({panes, @fullScreen}={}) -> + panes ?= new PaneContainer @panes.replaceWith(panes) @panes = panes @@ -131,14 +117,14 @@ class WorkspaceView extends View @command 'core:save-as', => @saveActivePaneItemAs() # Private: - serialize: -> - state = @state.clone() - state.set('panes', @panes.serialize()) - state.set('fullScreen', atom.isFullScreen()) - state + deserializeParams: (params) -> + params.panes = atom.deserializers.deserialize(params.panes) + params # Private: - getState: -> @state + serializeParams: -> + panes: @panes.serialize() + fullScreen: atom.isFullScreen() # Private: handleFocus: (e) ->