diff --git a/build/package.json b/build/package.json index de9053006..40e5b309a 100644 --- a/build/package.json +++ b/build/package.json @@ -8,6 +8,7 @@ "dependencies": { "asar": "^0.8.0", "async": "~0.2.9", + "aws-sdk": "^2.2.18", "donna": "^1.0.13", "formidable": "~1.0.14", "fs-plus": "2.x", diff --git a/build/tasks/publish-build-task.coffee b/build/tasks/publish-build-task.coffee index fc96121ae..4f8df6336 100644 --- a/build/tasks/publish-build-task.coffee +++ b/build/tasks/publish-build-task.coffee @@ -6,6 +6,7 @@ async = require 'async' fs = require 'fs-plus' GitHub = require 'github-releases' request = require 'request' +AWS = require 'aws-sdk' grunt = null @@ -210,7 +211,7 @@ deleteExistingAssets = (release, assetNames, callback) -> async.parallel(tasks, callback) uploadAssets = (release, buildDir, assets, callback) -> - upload = (release, assetName, assetPath, callback) -> + uploadToReleases = (release, assetName, assetPath, callback) -> options = uri: release.upload_url.replace(/\{.*$/, "?name=#{assetName}") method: 'POST' @@ -221,15 +222,43 @@ uploadAssets = (release, buildDir, assets, callback) -> assetRequest = request options, (error, response, body='') -> if error? or response.statusCode >= 400 - logError("Upload release asset #{assetName} failed", error, body) + logError("Upload release asset #{assetName} to Releases failed", error, body) callback(error ? new Error(response.statusCode)) else callback(null, release) fs.createReadStream(assetPath).pipe(assetRequest) + uploadToS3 = (release, assetName, assetPath, callback) -> + s3Key = process.env.BUILD_ATOM_RELEASES_S3_KEY + s3Secret = process.env.BUILD_ATOM_RELEASES_S3_SECRET + s3Bucket = process.env.BUILD_ATOM_RELEASES_S3_BUCKET + + unless s3Key and s3Secret and s3Bucket + callback(new Error('BUILD_ATOM_RELEASES_S3_KEY, BUILD_ATOM_RELEASES_S3_SECRET, and BUILD_ATOM_RELEASES_S3_BUCKET environment variables must be set.')) + return + + s3Info = + accessKeyId: s3Key + secretAccessKey: s3Secret + s3 = new AWS.S3 s3Info + + key = "releases/#{release.tag_name}/#{assetName}" + uploadParams = + Bucket: s3Bucket + ACL: 'public-read' + Key: key + Body: fs.createReadStream(assetPath) + s3.upload uploadParams, (error, data) -> + if error? + logError("Upload release asset #{assetName} to S3 failed", error) + callback(error) + else + callback(null, release) + tasks = [] for {assetName} in assets assetPath = path.join(buildDir, assetName) - tasks.push(upload.bind(this, release, assetName, assetPath)) + tasks.push(uploadToReleases.bind(this, release, assetName, assetPath)) + tasks.push(uploadToS3.bind(this, release, assetName, assetPath)) async.parallel(tasks, callback) diff --git a/package.json b/package.json index 2c2a93284..5f16bf814 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "autocomplete-css": "0.11.0", "autocomplete-html": "0.7.2", "autocomplete-plus": "2.23.1", - "autocomplete-snippets": "1.8.0", + "autocomplete-snippets": "1.9.0", "autoflow": "0.26.0", "autosave": "0.23.0", "background-tips": "0.26.0", @@ -98,7 +98,7 @@ "keybinding-resolver": "0.33.0", "line-ending-selector": "0.3.0", "link": "0.31.0", - "markdown-preview": "0.156.2", + "markdown-preview": "0.157.0", "metrics": "0.53.1", "notifications": "0.62.1", "open-on-github": "0.40.0", @@ -125,13 +125,13 @@ "language-gfm": "0.81.0", "language-git": "0.10.0", "language-go": "0.40.0", - "language-html": "0.42.0", + "language-html": "0.43.0", "language-hyperlink": "0.15.0", "language-java": "0.17.0", - "language-javascript": "0.102.2", + "language-javascript": "0.103.0", "language-json": "0.17.1", "language-less": "0.29.0", - "language-make": "0.20.0", + "language-make": "0.21.0", "language-mustache": "0.13.0", "language-objective-c": "0.15.0", "language-perl": "0.31.0", @@ -141,7 +141,7 @@ "language-ruby": "0.64.1", "language-ruby-on-rails": "0.24.0", "language-sass": "0.44.1", - "language-shellscript": "0.20.0", + "language-shellscript": "0.21.0", "language-source": "0.9.0", "language-sql": "0.19.0", "language-text": "0.7.0", diff --git a/script/cibuild b/script/cibuild index b3f0b3f83..860e0a938 100755 --- a/script/cibuild +++ b/script/cibuild @@ -40,6 +40,10 @@ function setEnvironmentVariables() { process.env.CC = 'clang'; process.env.CXX = 'clang++'; process.env.npm_config_clang = '1'; + } else if (process.platform === 'win32') { + process.env.BUILD_ATOM_RELEASES_S3_KEY = process.env.BUILD_ATOM_WIN_RELEASES_S3_KEY + process.env.BUILD_ATOM_RELEASES_S3_SECRET = process.env.BUILD_ATOM_WIN_RELEASES_S3_SECRET + process.env.BUILD_ATOM_RELEASES_S3_BUCKET = process.env.BUILD_ATOM_WIN_RELEASES_S3_BUCKET } } diff --git a/script/cibuild-atom-linux b/script/cibuild-atom-linux index c4e957189..2c3395608 100755 --- a/script/cibuild-atom-linux +++ b/script/cibuild-atom-linux @@ -3,6 +3,9 @@ set -e export ATOM_ACCESS_TOKEN=$BUILD_ATOM_LINUX_ACCESS_TOKEN +export BUILD_ATOM_RELEASES_S3_KEY=$BUILD_ATOM_LINUX_RELEASES_S3_KEY +export BUILD_ATOM_RELEASES_S3_SECRET=$BUILD_ATOM_LINUX_RELEASES_S3_SECRET +export BUILD_ATOM_RELEASES_S3_BUCKET=$BUILD_ATOM_LINUX_RELEASES_S3_BUCKET if [ -d /usr/local/share/nodenv ]; then export NODENV_ROOT=/usr/local/share/nodenv diff --git a/script/cibuild-atom-rpm b/script/cibuild-atom-rpm index a861a068b..2faa89347 100755 --- a/script/cibuild-atom-rpm +++ b/script/cibuild-atom-rpm @@ -8,5 +8,8 @@ docker run \ --env JANKY_SHA1="$JANKY_SHA1" \ --env JANKY_BRANCH="$JANKY_BRANCH" \ --env ATOM_ACCESS_TOKEN="$BUILD_ATOM_RPM_ACCESS_TOKEN" \ + --env BUILD_ATOM_RELEASES_S3_KEY="$BUILD_ATOM_RPM_RELEASES_S3_KEY" \ + --env BUILD_ATOM_RELEASES_S3_SECRET="$BUILD_ATOM_RPM_RELEASES_S3_SECRET" \ + --env BUILD_ATOM_RELEASES_S3_BUCKET="$BUILD_ATOM_RPM_RELEASES_S3_BUCKET" \ atom-rpm /atom/script/rpmbuild docker rmi atom-rpm diff --git a/spec/compile-cache-spec.coffee b/spec/compile-cache-spec.coffee index 8a6cc214e..848da6b49 100644 --- a/spec/compile-cache-spec.coffee +++ b/spec/compile-cache-spec.coffee @@ -81,6 +81,27 @@ describe 'CompileCache', -> waits(1) runs -> error = new Error("Oops again") - console.log error.stack expect(error.stack).toContain('compile-cache-spec.coffee') expect(Array.isArray(error.getRawStack())).toBe true + + it 'does not infinitely loop when the original prepareStackTrace value is reassigned', -> + originalPrepareStackTrace = Error.prepareStackTrace + + Error.prepareStackTrace = -> 'a-stack-trace' + Error.prepareStackTrace = originalPrepareStackTrace + + error = new Error('Oops') + expect(error.stack).toContain('compile-cache-spec.coffee') + expect(Array.isArray(error.getRawStack())).toBe true + + it 'does not infinitely loop when the assigned prepareStackTrace calls the original prepareStackTrace', -> + originalPrepareStackTrace = Error.prepareStackTrace + + Error.prepareStackTrace = (error, stack) -> + error.foo = 'bar' + originalPrepareStackTrace(error, stack) + + error = new Error('Oops') + expect(error.stack).toContain('compile-cache-spec.coffee') + expect(error.foo).toBe('bar') + expect(Array.isArray(error.getRawStack())).toBe true diff --git a/spec/fixtures/packages/package-with-deserializers/deserializer-1.js b/spec/fixtures/packages/package-with-deserializers/deserializer-1.js new file mode 100644 index 000000000..f4d7a1488 --- /dev/null +++ b/spec/fixtures/packages/package-with-deserializers/deserializer-1.js @@ -0,0 +1,6 @@ +module.exports = function (state) { + return { + wasDeserializedBy: 'Deserializer1', + state: state + } +} diff --git a/spec/fixtures/packages/package-with-deserializers/deserializer-2.js b/spec/fixtures/packages/package-with-deserializers/deserializer-2.js new file mode 100644 index 000000000..3099d2b15 --- /dev/null +++ b/spec/fixtures/packages/package-with-deserializers/deserializer-2.js @@ -0,0 +1,6 @@ +module.exports = function (state) { + return { + wasDeserializedBy: 'Deserializer2', + state: state + } +} diff --git a/spec/fixtures/packages/package-with-deserializers/index.js b/spec/fixtures/packages/package-with-deserializers/index.js new file mode 100644 index 000000000..19bba5ecb --- /dev/null +++ b/spec/fixtures/packages/package-with-deserializers/index.js @@ -0,0 +1,3 @@ +module.exports = { + activate: function() {} +} diff --git a/spec/fixtures/packages/package-with-deserializers/package.json b/spec/fixtures/packages/package-with-deserializers/package.json new file mode 100644 index 000000000..daa5776bf --- /dev/null +++ b/spec/fixtures/packages/package-with-deserializers/package.json @@ -0,0 +1,9 @@ +{ + "name": "package-with-deserializers", + "version": "1.0.0", + "main": "./index", + "deserializers": { + "Deserializer1": "./deserializer-1.js", + "Deserializer2": "./deserializer-2.js" + } +} diff --git a/spec/fixtures/packages/package-with-eval-time-api-calls/index.js b/spec/fixtures/packages/package-with-eval-time-api-calls/index.js new file mode 100644 index 000000000..e16db0743 --- /dev/null +++ b/spec/fixtures/packages/package-with-eval-time-api-calls/index.js @@ -0,0 +1,5 @@ +atom.deserializers.add('MyDeserializer', function (state) { + return {state: state, a: 'b'} +}) + +exports.activate = function () {} diff --git a/spec/fixtures/packages/package-with-eval-time-api-calls/package.json b/spec/fixtures/packages/package-with-eval-time-api-calls/package.json new file mode 100644 index 000000000..7a76abb5f --- /dev/null +++ b/spec/fixtures/packages/package-with-eval-time-api-calls/package.json @@ -0,0 +1,5 @@ +{ + "name": "package-with-eval-time-api-calls", + "version": "1.2.3", + "main": "./index" +} diff --git a/spec/fixtures/packages/package-with-json-config-schema/package.json b/spec/fixtures/packages/package-with-json-config-schema/package.json new file mode 100644 index 000000000..960d87fc1 --- /dev/null +++ b/spec/fixtures/packages/package-with-json-config-schema/package.json @@ -0,0 +1,13 @@ +{ + "name": "package-with-json-config-schema", + "configSchema": { + "a": { + "type": "number", + "default": 5 + }, + "b": { + "type": "string", + "default": "five" + } + } +} diff --git a/spec/fixtures/packages/package-with-main/package.cson b/spec/fixtures/packages/package-with-main/package.cson index e799f6ca8..75910bbcc 100644 --- a/spec/fixtures/packages/package-with-main/package.cson +++ b/spec/fixtures/packages/package-with-main/package.cson @@ -1 +1,2 @@ 'main': 'main-module.coffee' +'version': '2.3.4' diff --git a/spec/fixtures/packages/package-with-view-providers/deserializer.js b/spec/fixtures/packages/package-with-view-providers/deserializer.js new file mode 100644 index 000000000..334e7b2ab --- /dev/null +++ b/spec/fixtures/packages/package-with-view-providers/deserializer.js @@ -0,0 +1,3 @@ +module.exports = function (state) { + return {state: state} +} diff --git a/spec/fixtures/packages/package-with-view-providers/index.js b/spec/fixtures/packages/package-with-view-providers/index.js new file mode 100644 index 000000000..19bba5ecb --- /dev/null +++ b/spec/fixtures/packages/package-with-view-providers/index.js @@ -0,0 +1,3 @@ +module.exports = { + activate: function() {} +} diff --git a/spec/fixtures/packages/package-with-view-providers/package.json b/spec/fixtures/packages/package-with-view-providers/package.json new file mode 100644 index 000000000..f67477280 --- /dev/null +++ b/spec/fixtures/packages/package-with-view-providers/package.json @@ -0,0 +1,12 @@ +{ + "name": "package-with-view-providers", + "main": "./index", + "version": "1.0.0", + "deserializers": { + "DeserializerFromPackageWithViewProviders": "./deserializer" + }, + "viewProviders": [ + "./view-provider-1", + "./view-provider-2" + ] +} diff --git a/spec/fixtures/packages/package-with-view-providers/view-provider-1.js b/spec/fixtures/packages/package-with-view-providers/view-provider-1.js new file mode 100644 index 000000000..e4f0dcc0b --- /dev/null +++ b/spec/fixtures/packages/package-with-view-providers/view-provider-1.js @@ -0,0 +1,9 @@ +'use strict' + +module.exports = function (model) { + if (model.worksWithViewProvider1) { + let element = document.createElement('div') + element.dataset['createdBy'] = 'view-provider-1' + return element + } +} diff --git a/spec/fixtures/packages/package-with-view-providers/view-provider-2.js b/spec/fixtures/packages/package-with-view-providers/view-provider-2.js new file mode 100644 index 000000000..a3b58a3aa --- /dev/null +++ b/spec/fixtures/packages/package-with-view-providers/view-provider-2.js @@ -0,0 +1,9 @@ +'use strict' + +module.exports = function (model) { + if (model.worksWithViewProvider2) { + let element = document.createElement('div') + element.dataset['createdBy'] = 'view-provider-2' + return element + } +} diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 4b5f3c26d..4e88a1c2f 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -1,6 +1,7 @@ path = require 'path' Package = require '../src/package' {Disposable} = require 'atom' +{mockLocalStorage} = require './spec-helper' describe "PackageManager", -> workspaceElement = null @@ -79,6 +80,111 @@ describe "PackageManager", -> expect(loadedPackage.name).toBe "package-with-main" + it "registers any deserializers specified in the package's package.json", -> + pack = atom.packages.loadPackage("package-with-deserializers") + + state1 = {deserializer: 'Deserializer1', a: 'b'} + expect(atom.deserializers.deserialize(state1)).toEqual { + wasDeserializedBy: 'Deserializer1' + state: state1 + } + + state2 = {deserializer: 'Deserializer2', c: 'd'} + expect(atom.deserializers.deserialize(state2)).toEqual { + wasDeserializedBy: 'Deserializer2' + state: state2 + } + + expect(pack.mainModule).toBeNull() + + describe "when there are view providers specified in the package's package.json", -> + model1 = {worksWithViewProvider1: true} + model2 = {worksWithViewProvider2: true} + + afterEach -> + atom.packages.deactivatePackage('package-with-view-providers') + atom.packages.unloadPackage('package-with-view-providers') + + it "does not load the view providers immediately", -> + pack = atom.packages.loadPackage("package-with-view-providers") + expect(pack.mainModule).toBeNull() + + expect(-> atom.views.getView(model1)).toThrow() + expect(-> atom.views.getView(model2)).toThrow() + + it "registers the view providers when the package is activated", -> + pack = atom.packages.loadPackage("package-with-view-providers") + + waitsForPromise -> + atom.packages.activatePackage("package-with-view-providers").then -> + element1 = atom.views.getView(model1) + expect(element1 instanceof HTMLDivElement).toBe true + expect(element1.dataset.createdBy).toBe 'view-provider-1' + + element2 = atom.views.getView(model2) + expect(element2 instanceof HTMLDivElement).toBe true + expect(element2.dataset.createdBy).toBe 'view-provider-2' + + it "registers the view providers when any of the package's deserializers are used", -> + pack = atom.packages.loadPackage("package-with-view-providers") + + spyOn(atom.views, 'addViewProvider').andCallThrough() + atom.deserializers.deserialize({ + deserializer: 'DeserializerFromPackageWithViewProviders', + a: 'b' + }) + expect(atom.views.addViewProvider.callCount).toBe 2 + + atom.deserializers.deserialize({ + deserializer: 'DeserializerFromPackageWithViewProviders', + a: 'b' + }) + expect(atom.views.addViewProvider.callCount).toBe 2 + + element1 = atom.views.getView(model1) + expect(element1 instanceof HTMLDivElement).toBe true + expect(element1.dataset.createdBy).toBe 'view-provider-1' + + element2 = atom.views.getView(model2) + expect(element2 instanceof HTMLDivElement).toBe true + expect(element2.dataset.createdBy).toBe 'view-provider-2' + + it "registers the config schema in the package's metadata, if present", -> + pack = atom.packages.loadPackage("package-with-json-config-schema") + + expect(atom.config.getSchema('package-with-json-config-schema')).toEqual { + type: 'object' + properties: { + a: {type: 'number', default: 5} + b: {type: 'string', default: 'five'} + } + } + + expect(pack.mainModule).toBeNull() + + describe "when a package does not have deserializers, view providers or a config schema in its package.json", -> + beforeEach -> + atom.packages.unloadPackage('package-with-main') + mockLocalStorage() + + it "defers loading the package's main module if the package previously used no Atom APIs when its main module was required", -> + pack1 = atom.packages.loadPackage('package-with-main') + expect(pack1.mainModule).toBeDefined() + + atom.packages.unloadPackage('package-with-main') + + pack2 = atom.packages.loadPackage('package-with-main') + expect(pack2.mainModule).toBeNull() + + it "does not defer loading the package's main module if the package previously used Atom APIs when its main module was required", -> + pack1 = atom.packages.loadPackage('package-with-eval-time-api-calls') + expect(pack1.mainModule).toBeDefined() + + atom.packages.unloadPackage('package-with-eval-time-api-calls') + + pack2 = atom.packages.loadPackage('package-with-eval-time-api-calls') + expect(pack2.mainModule).not.toBeNull() + describe "::unloadPackage(name)", -> describe "when the package is active", -> it "throws an error", -> diff --git a/spec/package-spec.coffee b/spec/package-spec.coffee index 63a80a7db..92218e749 100644 --- a/spec/package-spec.coffee +++ b/spec/package-spec.coffee @@ -1,6 +1,7 @@ path = require 'path' Package = require '../src/package' ThemePackage = require '../src/theme-package' +{mockLocalStorage} = require './spec-helper' describe "Package", -> build = (constructor, path) -> @@ -10,6 +11,7 @@ describe "Package", -> keymapManager: atom.keymaps, commandRegistry: atom.command, grammarRegistry: atom.grammars, themeManager: atom.themes, menuManager: atom.menu, contextMenuManager: atom.contextMenu, + deserializerManager: atom.deserializers, viewRegistry: atom.views, devMode: false ) @@ -19,10 +21,7 @@ describe "Package", -> describe "when the package contains incompatible native modules", -> beforeEach -> - items = {} - spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined - spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null - spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined + mockLocalStorage() it "does not activate it", -> packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module') @@ -54,10 +53,7 @@ describe "Package", -> describe "::rebuild()", -> beforeEach -> - items = {} - spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined - spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null - spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined + mockLocalStorage() it "returns a promise resolving to the results of `apm rebuild`", -> packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index') diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index f31c67298..67883511b 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -265,3 +265,9 @@ window.advanceClock = (delta=1) -> true callback() for callback in callbacks + +exports.mockLocalStorage = -> + items = {} + spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item.toString(); undefined + spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null + spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined diff --git a/spec/view-registry-spec.coffee b/spec/view-registry-spec.coffee index a2b4965a5..16672b25d 100644 --- a/spec/view-registry-spec.coffee +++ b/spec/view-registry-spec.coffee @@ -47,6 +47,21 @@ describe "ViewRegistry", -> expect(view2 instanceof TestView).toBe true expect(view2.model).toBe subclassModel + describe "when a view provider is registered generically, and works with the object", -> + it "constructs a view element and assigns the model on it", -> + model = {a: 'b'} + + registry.addViewProvider (model) -> + if model.a is 'b' + element = document.createElement('div') + element.className = 'test-element' + element + + view = registry.getView({a: 'b'}) + expect(view.className).toBe 'test-element' + + expect(-> registry.getView({a: 'c'})).toThrow() + describe "when no view provider is registered for the object's constructor", -> it "throws an exception", -> expect(-> registry.getView(new Object)).toThrow() diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index e150761af..0587a2629 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -661,7 +661,7 @@ describe "Workspace", -> describe "::isTextEditor(obj)", -> it "returns true when the passed object is an instance of `TextEditor`", -> expect(workspace.isTextEditor(atom.workspace.buildTextEditor())).toBe(true) - expect(workspace.isTextEditor({getText: ->})).toBe(false) + expect(workspace.isTextEditor({getText: -> null})).toBe(false) expect(workspace.isTextEditor(null)).toBe(false) expect(workspace.isTextEditor(undefined)).toBe(false) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 125d0310e..e8d656a43 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -151,7 +151,7 @@ class AtomEnvironment extends Model @packages = new PackageManager({ devMode, configDirPath, resourcePath, safeMode, @config, styleManager: @styles, commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications, - grammarRegistry: @grammars + grammarRegistry: @grammars, deserializerManager: @deserializers, viewRegistry: @views }) @themes = new ThemeManager({ diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 8e8e4af54..1987f9dec 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -82,6 +82,7 @@ class AtomApplication @listenForArgumentsFromNewProcess() @setupJavaScriptArguments() @handleEvents() + @setupDockMenu() @storageFolder = new StorageFolder(process.env.ATOM_HOME) if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test @@ -280,6 +281,13 @@ class AtomApplication ipc.on 'write-to-stderr', (event, output) -> process.stderr.write(output) + setupDockMenu: -> + if process.platform is 'darwin' + dockMenu = Menu.buildFromTemplate [ + {label: 'New Window', click: => @emit('application:new-window')} + ] + app.dock.setMenu dockMenu + # Public: Executes the given command. # # If it isn't handled globally, delegate to the currently focused window. diff --git a/src/compile-cache.js b/src/compile-cache.js index bedbd2549..8da229a50 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -163,8 +163,12 @@ var prepareStackTraceWithSourceMapping = Error.prepareStackTrace let prepareStackTrace = prepareStackTraceWithSourceMapping function prepareStackTraceWithRawStackAssignment (error, frames) { - error.rawStack = frames - return prepareStackTrace(error, frames) + if (error.rawStack) { // avoid infinite recursion + return prepareStackTraceWithSourceMapping(error, frames) + } else { + error.rawStack = frames + return prepareStackTrace(error, frames) + } } Object.defineProperty(Error, 'prepareStackTrace', { diff --git a/src/config.coffee b/src/config.coffee index 489e16016..d2759fcb4 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -381,8 +381,8 @@ class Config # ``` # # * `keyPath` {String} name of the key to observe - # * `options` {Object} - # * `scopeDescriptor` (optional) {ScopeDescriptor} describing a path from + # * `options` (optional) {Object} + # * `scope` (optional) {ScopeDescriptor} describing a path from # the root of the syntax tree to a token. Get one by calling # {editor.getLastCursor().getScopeDescriptor()}. See {::get} for examples. # See [the scopes docs](https://atom.io/docs/latest/behind-atom-scoped-settings-scopes-and-scope-descriptors) @@ -412,8 +412,8 @@ class Config # # * `keyPath` (optional) {String} name of the key to observe. Must be # specified if `scopeDescriptor` is specified. - # * `optional` (optional) {Object} - # * `scopeDescriptor` (optional) {ScopeDescriptor} describing a path from + # * `options` (optional) {Object} + # * `scope` (optional) {ScopeDescriptor} describing a path from # the root of the syntax tree to a token. Get one by calling # {editor.getLastCursor().getScopeDescriptor()}. See {::get} for examples. # See [the scopes docs](https://atom.io/docs/latest/behind-atom-scoped-settings-scopes-and-scope-descriptors) diff --git a/src/deserializer-manager.coffee b/src/deserializer-manager.coffee index 7f6cb0f65..3c73a0b02 100644 --- a/src/deserializer-manager.coffee +++ b/src/deserializer-manager.coffee @@ -39,6 +39,9 @@ class DeserializerManager delete @deserializers[deserializer.name] for deserializer in deserializers return + getDeserializerCount: -> + Object.keys(@deserializers).length + # Public: Deserialize the state and params. # # * `state` The state {Object} to deserialize. diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 789b2eae5..5b0264212 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -31,7 +31,8 @@ class PackageManager constructor: (params) -> { configDirPath, @devMode, safeMode, @resourcePath, @config, @styleManager, - @notificationManager, @keymapManager, @commandRegistry, @grammarRegistry + @notificationManager, @keymapManager, @commandRegistry, @grammarRegistry, + @deserializerManager, @viewRegistry } = params @emitter = new Emitter @@ -375,7 +376,8 @@ class PackageManager options = { path: packagePath, metadata, packageManager: this, @config, @styleManager, @commandRegistry, @keymapManager, @devMode, @notificationManager, - @grammarRegistry, @themeManager, @menuManager, @contextMenuManager + @grammarRegistry, @themeManager, @menuManager, @contextMenuManager, + @deserializerManager, @viewRegistry } if metadata.theme pack = new ThemePackage(options) diff --git a/src/package.coffee b/src/package.coffee index 4cd6a18fd..b831b3c55 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -33,7 +33,7 @@ class Package { @path, @metadata, @packageManager, @config, @styleManager, @commandRegistry, @keymapManager, @devMode, @notificationManager, @grammarRegistry, @themeManager, - @menuManager, @contextMenuManager + @menuManager, @contextMenuManager, @deserializerManager, @viewRegistry } = params @emitter = new Emitter @@ -84,12 +84,24 @@ class Package @loadKeymaps() @loadMenus() @loadStylesheets() + @loadDeserializers() + @configSchemaRegisteredOnLoad = @registerConfigSchemaFromMetadata() @settingsPromise = @loadSettings() - @requireMainModule() unless @mainModule? or @activationShouldBeDeferred() + if @shouldRequireMainModuleOnLoad() and not @mainModule? + @requireMainModule() catch error @handleError("Failed to load the #{@name} package", error) this + shouldRequireMainModuleOnLoad: -> + not ( + @metadata.deserializers? or + @metadata.viewProviders? or + @metadata.configSchema? or + @activationShouldBeDeferred() or + localStorage.getItem(@getCanDeferMainModuleRequireStorageKey()) is 'true' + ) + reset: -> @stylesheets = [] @keymaps = [] @@ -117,9 +129,12 @@ class Package activateNow: -> try - @activateConfig() + @requireMainModule() unless @mainModule? + @configSchemaRegisteredOnActivate = @registerConfigSchemaFromMainModule() + @registerViewProviders() @activateStylesheets() if @mainModule? and not @mainActivated + @mainModule.activateConfig?() @mainModule.activate?(@packageManager.getPackageState(@name) ? {}) @mainActivated = true @activateServices() @@ -128,15 +143,22 @@ class Package @resolveActivationPromise?() - activateConfig: -> - return if @configActivated + registerConfigSchemaFromMetadata: -> + if configSchema = @metadata.configSchema + @config.setSchema @name, {type: 'object', properties: configSchema} + true + else + false - @requireMainModule() unless @mainModule? - if @mainModule? + registerConfigSchemaFromMainModule: -> + if @mainModule? and not @configSchemaRegisteredOnLoad if @mainModule.config? and typeof @mainModule.config is 'object' @config.setSchema @name, {type: 'object', properties: @mainModule.config} - @mainModule.activateConfig?() - @configActivated = true + return true + false + + # TODO: Remove. Settings view calls this method currently. + activateConfig: -> @registerConfigSchemaFromMainModule() activateStylesheets: -> return if @stylesheetsActivated @@ -253,6 +275,26 @@ class Package @stylesheets = @getStylesheetPaths().map (stylesheetPath) => [stylesheetPath, @themeManager.loadStylesheet(stylesheetPath, true)] + loadDeserializers: -> + if @metadata.deserializers? + for name, implementationPath of @metadata.deserializers + do => + deserializePath = path.join(@path, implementationPath) + deserializeFunction = null + atom.deserializers.add + name: name, + deserialize: => + @registerViewProviders() + deserializeFunction ?= require(deserializePath) + deserializeFunction.apply(this, arguments) + return + + registerViewProviders: -> + if @metadata.viewProviders? and not @registeredViewProviders + for implementationPath in @metadata.viewProviders + @viewRegistry.addViewProvider(require(path.join(@path, implementationPath))) + @registeredViewProviders = true + getStylesheetsPath: -> path.join(@path, 'styles') @@ -343,21 +385,18 @@ class Package @activationPromise = null @resolveActivationPromise = null @activationCommandSubscriptions?.dispose() + @configSchemaRegisteredOnActivate = false @deactivateResources() - @deactivateConfig() @deactivateKeymaps() if @mainActivated try @mainModule?.deactivate?() + @mainModule?.deactivateConfig?() @mainActivated = false catch e console.error "Error deactivating package '#{@name}'", e.stack @emitter.emit 'did-deactivate' - deactivateConfig: -> - @mainModule?.deactivateConfig?() - @configActivated = false - deactivateResources: -> grammar.deactivate() for grammar in @grammars settings.deactivate() for settings in @settings @@ -392,7 +431,13 @@ class Package mainModulePath = @getMainModulePath() if fs.isFileSync(mainModulePath) @mainModuleRequired = true + + previousViewProviderCount = @viewRegistry.getViewProviderCount() + previousDeserializerCount = @deserializerManager.getDeserializerCount() @mainModule = require(mainModulePath) + if (@viewRegistry.getViewProviderCount() is previousViewProviderCount and + @deserializerManager.getDeserializerCount() is previousDeserializerCount) + localStorage.setItem(@getCanDeferMainModuleRequireStorageKey(), 'true') getMainModulePath: -> return @mainModulePath if @resolvedMainModulePath @@ -586,6 +631,9 @@ class Package electronVersion = process.versions['electron'] ? process.versions['atom-shell'] "installed-packages:#{@name}:#{@metadata.version}:electron-#{electronVersion}:incompatible-native-modules" + getCanDeferMainModuleRequireStorageKey: -> + "installed-packages:#{@name}:#{@metadata.version}:can-defer-main-module-require" + # Get the incompatible native modules that this package depends on. # This recurses through all dependencies and requires all modules that # contain a `.node` file. diff --git a/src/view-registry.coffee b/src/view-registry.coffee index 0f07600ae..ef7151353 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -3,6 +3,8 @@ Grim = require 'grim' {Disposable} = require 'event-kit' _ = require 'underscore-plus' +AnyConstructor = Symbol('any-constructor') + # Essential: `ViewRegistry` handles the association between model and view # types in Atom. We call this association a View Provider. As in, for a given # model, this class can provide a view via {::getView}, as long as the @@ -76,16 +78,27 @@ class ViewRegistry # textEditorElement # ``` # - # * `modelConstructor` Constructor {Function} for your model. + # * `modelConstructor` (optional) Constructor {Function} for your model. If + # a constructor is given, the `createView` function will only be used + # for model objects inheriting from that constructor. Otherwise, it will + # will be called for any object. # * `createView` Factory {Function} that is passed an instance of your model - # and must return a subclass of `HTMLElement` or `undefined`. + # and must return a subclass of `HTMLElement` or `undefined`. If it returns + # `undefined`, then the registry will continue to search for other view + # providers. # # Returns a {Disposable} on which `.dispose()` can be called to remove the # added provider. addViewProvider: (modelConstructor, createView) -> if arguments.length is 1 - Grim.deprecate("atom.views.addViewProvider now takes 2 arguments: a model constructor and a createView function. See docs for details.") - provider = modelConstructor + switch typeof modelConstructor + when 'function' + provider = {createView: modelConstructor, modelConstructor: AnyConstructor} + when 'object' + Grim.deprecate("atom.views.addViewProvider now takes 2 arguments: a model constructor and a createView function. See docs for details.") + provider = modelConstructor + else + throw new TypeError("Arguments to addViewProvider must be functions") else provider = {modelConstructor, createView} @@ -93,6 +106,9 @@ class ViewRegistry new Disposable => @providers = @providers.filter (p) -> p isnt provider + getViewProviderCount: -> + @providers.length + # Essential: Get the view associated with an object in the workspace. # # If you're just *using* the workspace, you shouldn't need to access the view @@ -153,25 +169,34 @@ class ViewRegistry createView: (object) -> if object instanceof HTMLElement - object - else if object?.element instanceof HTMLElement - object.element - else if object?.jquery - object[0] - else if provider = @findProvider(object) - element = provider.createView?(object, @atomEnvironment) - unless element? - element = new provider.viewConstructor - element.initialize?(object) ? element.setModel?(object) - element - else if viewConstructor = object?.getViewClass?() - view = new viewConstructor(object) - view[0] - else - throw new Error("Can't create a view for #{object.constructor.name} instance. Please register a view provider.") + return object - findProvider: (object) -> - find @providers, ({modelConstructor}) -> object instanceof modelConstructor + if object?.element instanceof HTMLElement + return object.element + + if object?.jquery + return object[0] + + for provider in @providers + if provider.modelConstructor is AnyConstructor + if element = provider.createView(object, @atomEnvironment) + return element + continue + + if object instanceof provider.modelConstructor + if element = provider.createView?(object, @atomEnvironment) + return element + + if viewConstructor = provider.viewConstructor + element = new viewConstructor + element.initialize?(object) ? element.setModel?(object) + return element + + if viewConstructor = object?.getViewClass?() + view = new viewConstructor(object) + return view[0] + + throw new Error("Can't create a view for #{object.constructor.name} instance. Please register a view provider.") updateDocument: (fn) -> @documentWriters.push(fn)