diff --git a/package.json b/package.json index d6618ab3f..b45719b3d 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "pathwatcher": "0.14.2", "pegjs": "0.8.0", "property-accessors": "1.x", - "q": "0.9.7", + "q": "1.0.x", "scandal": "0.14.0", "season": "1.x", "semver": "1.1.4", @@ -63,48 +63,48 @@ "solarized-dark-syntax": "0.9.0", "solarized-light-syntax": "0.5.0", "archive-view": "0.21.0", - "autocomplete": "0.21.0", - "autoflow": "0.12.0", + "autocomplete": "0.22.0", + "autoflow": "0.13.0", "autosave": "0.10.0", "background-tips": "0.6.0", "bookmarks": "0.18.0", - "bracket-matcher": "0.19.0", - "command-logger": "0.10.0", - "command-palette": "0.15.0", - "dev-live-reload": "0.23.0", - "editor-stats": "0.12.0", + "bracket-matcher": "0.20.0", + "command-logger": "0.11.0", + "command-palette": "0.16.0", + "dev-live-reload": "0.24.0", + "editor-stats": "0.13.0", "exception-reporting": "0.13.0", - "feedback": "0.22.0", + "feedback": "0.23.0", "find-and-replace": "0.81.0", - "fuzzy-finder": "0.33.0", - "gists": "0.16.0", - "git-diff": "0.23.0", + "fuzzy-finder": "0.34.0", + "gists": "0.17.0", + "git-diff": "0.24.0", "github-sign-in": "0.18.0", "go-to-line": "0.16.0", - "grammar-selector": "0.18.0", - "image-view": "0.18.0", + "grammar-selector": "0.19.0", + "image-view": "0.19.0", "keybinding-resolver": "0.9.0", - "link": "0.15.0", + "link": "0.16.0", "markdown-preview": "0.25.1", - "metrics": "0.24.0", - "package-generator": "0.25.0", + "metrics": "0.25.0", + "package-generator": "0.26.0", "release-notes": "0.18.0", - "settings-view": "0.70.0", - "snippets": "0.25.0", + "settings-view": "0.71.0", + "snippets": "0.27.0", "spell-check": "0.23.0", "status-bar": "0.32.0", "styleguide": "0.23.0", - "symbols-view": "0.31.0", - "tabs": "0.18.0", + "symbols-view": "0.33.0", + "tabs": "0.19.0", "terminal": "0.27.0", "timecop": "0.13.0", "to-the-hubs": "0.19.0", - "tree-view": "0.68.0", + "tree-view": "0.69.0", "update-package-dependencies": "0.2.0", "visual-bell": "0.6.0", "welcome": "0.4.0", - "whitespace": "0.10.0", - "wrap-guide": "0.13.0", + "whitespace": "0.11.0", + "wrap-guide": "0.14.0", "language-c": "0.4.0", "language-clojure": "0.1.0", "language-coffee-script": "0.7.0", diff --git a/spec/atom-spec.coffee b/spec/atom-spec.coffee index 24b646b95..9e37e2ac6 100644 --- a/spec/atom-spec.coffee +++ b/spec/atom-spec.coffee @@ -1,6 +1,7 @@ {$, $$, fs, WorkspaceView} = require 'atom' Exec = require('child_process').exec path = require 'path' +AtomPackage = require '../src/atom-package' ThemeManager = require '../src/theme-manager' describe "the `atom` global", -> @@ -32,12 +33,16 @@ describe "the `atom` global", -> describe ".unloadPackage(name)", -> describe "when the package is active", -> it "throws an error", -> - pack = atom.packages.activatePackage('package-with-main') - expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() - expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() - expect( -> atom.packages.unloadPackage(pack.name)).toThrow() - expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() - expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() + pack = null + waitsForPromise -> + atom.packages.activatePackage('package-with-main').then (p) -> pack = p + + runs -> + expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() + expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() + expect( -> atom.packages.unloadPackage(pack.name)).toThrow() + expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() + expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() describe "when the package is not loaded", -> it "throws an error", -> @@ -54,22 +59,42 @@ describe "the `atom` global", -> describe ".activatePackage(id)", -> describe "atom packages", -> + describe "when called multiple times", -> + it "it only calls activate on the package once", -> + spyOn(AtomPackage.prototype, 'activateNow').andCallThrough() + atom.packages.activatePackage('package-with-index') + atom.packages.activatePackage('package-with-index') + + waitsForPromise -> + atom.packages.activatePackage('package-with-index') + + runs -> + expect(AtomPackage.prototype.activateNow.callCount).toBe 1 + describe "when the package has a main module", -> describe "when the metadata specifies a main module path˜", -> it "requires the module at the specified path", -> mainModule = require('./fixtures/packages/package-with-main/main-module') spyOn(mainModule, 'activate') - pack = atom.packages.activatePackage('package-with-main') - expect(mainModule.activate).toHaveBeenCalled() - expect(pack.mainModule).toBe mainModule + pack = null + waitsForPromise -> + atom.packages.activatePackage('package-with-main').then (p) -> pack = p + + runs -> + expect(mainModule.activate).toHaveBeenCalled() + expect(pack.mainModule).toBe mainModule describe "when the metadata does not specify a main module", -> it "requires index.coffee", -> indexModule = require('./fixtures/packages/package-with-index/index') spyOn(indexModule, 'activate') - pack = atom.packages.activatePackage('package-with-index') - expect(indexModule.activate).toHaveBeenCalled() - expect(pack.mainModule).toBe indexModule + pack = null + waitsForPromise -> + atom.packages.activatePackage('package-with-index').then (p) -> pack = p + + runs -> + expect(indexModule.activate).toHaveBeenCalled() + expect(pack.mainModule).toBe indexModule it "assigns config defaults from the module", -> expect(atom.config.get('package-with-config-defaults.numbers.one')).toBeUndefined() @@ -78,20 +103,22 @@ describe "the `atom` global", -> expect(atom.config.get('package-with-config-defaults.numbers.two')).toBe 2 describe "when the package metadata includes activation events", -> - [mainModule, pack] = [] + [mainModule, promise] = [] beforeEach -> mainModule = require './fixtures/packages/package-with-activation-events/index' spyOn(mainModule, 'activate').andCallThrough() AtomPackage = require '../src/atom-package' spyOn(AtomPackage.prototype, 'requireMainModule').andCallThrough() - pack = atom.packages.activatePackage('package-with-activation-events') + + promise = atom.packages.activatePackage('package-with-activation-events') it "defers requiring/activating the main module until an activation event bubbles to the root view", -> - expect(pack.requireMainModule).not.toHaveBeenCalled() - expect(mainModule.activate).not.toHaveBeenCalled() + expect(promise.isFulfilled()).not.toBeTruthy() atom.workspaceView.trigger 'activation-event' - expect(mainModule.activate).toHaveBeenCalled() + + waitsForPromise -> + promise it "triggers the activation event on all handlers registered during activation", -> atom.workspaceView.openSync() @@ -116,13 +143,17 @@ describe "the `atom` global", -> expect(console.warn).not.toHaveBeenCalled() it "passes the activate method the package's previously serialized state if it exists", -> - pack = atom.packages.activatePackage("package-with-serialization") - expect(pack.mainModule.someNumber).not.toBe 77 - pack.mainModule.someNumber = 77 - atom.packages.deactivatePackage("package-with-serialization") - spyOn(pack.mainModule, 'activate').andCallThrough() - atom.packages.activatePackage("package-with-serialization") - expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77}) + pack = null + waitsForPromise -> + atom.packages.activatePackage("package-with-serialization").then (p) -> pack = p + + runs -> + expect(pack.mainModule.someNumber).not.toBe 77 + pack.mainModule.someNumber = 77 + atom.packages.deactivatePackage("package-with-serialization") + spyOn(pack.mainModule, 'activate').andCallThrough() + atom.packages.activatePackage("package-with-serialization") + expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77}) it "logs warning instead of throwing an exception if the package fails to load", -> atom.config.set("core.disabledPackages", []) @@ -245,29 +276,38 @@ describe "the `atom` global", -> describe "scoped-property loading", -> it "loads the scoped properties", -> - atom.packages.activatePackage("package-with-scoped-properties") - expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' + waitsForPromise -> + atom.packages.activatePackage("package-with-scoped-properties") + + runs -> + expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' describe "textmate packages", -> it "loads the package's grammars", -> expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar" - atom.packages.activatePackage('language-ruby', sync: true) - expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby" + + waitsForPromise -> + atom.packages.activatePackage('language-ruby') + + runs -> + expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby" it "translates the package's scoped properties to Atom terms", -> expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined() - atom.packages.activatePackage('language-ruby', sync: true) - expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBe '# ' + + waitsForPromise -> + atom.packages.activatePackage('language-ruby') + + runs -> + expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBe '# ' describe "when the package has no grammars but does have preferences", -> it "loads the package's preferences as scoped properties", -> jasmine.unspy(window, 'setTimeout') spyOn(atom.syntax, 'addProperties').andCallThrough() - atom.packages.activatePackage('package-with-preferences-tmbundle') - - waitsFor -> - atom.syntax.addProperties.callCount > 0 + waitsForPromise -> + atom.packages.activatePackage('package-with-preferences-tmbundle') runs -> expect(atom.syntax.getProperty(['.source.pref'], 'editor.increaseIndentPattern')).toBe '^abc$' @@ -275,30 +315,43 @@ describe "the `atom` global", -> describe ".deactivatePackage(id)", -> describe "atom packages", -> it "calls `deactivate` on the package's main module if activate was successful", -> - pack = atom.packages.activatePackage("package-with-deactivate") - expect(atom.packages.isPackageActive("package-with-deactivate")).toBeTruthy() - spyOn(pack.mainModule, 'deactivate').andCallThrough() + pack = null + waitsForPromise -> + atom.packages.activatePackage("package-with-deactivate").then (p) -> pack = p - atom.packages.deactivatePackage("package-with-deactivate") - expect(pack.mainModule.deactivate).toHaveBeenCalled() - expect(atom.packages.isPackageActive("package-with-module")).toBeFalsy() + runs -> + expect(atom.packages.isPackageActive("package-with-deactivate")).toBeTruthy() + spyOn(pack.mainModule, 'deactivate').andCallThrough() - spyOn(console, 'warn') - badPack = atom.packages.activatePackage("package-that-throws-on-activate") - expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeTruthy() - spyOn(badPack.mainModule, 'deactivate').andCallThrough() + atom.packages.deactivatePackage("package-with-deactivate") + expect(pack.mainModule.deactivate).toHaveBeenCalled() + expect(atom.packages.isPackageActive("package-with-module")).toBeFalsy() - atom.packages.deactivatePackage("package-that-throws-on-activate") - expect(badPack.mainModule.deactivate).not.toHaveBeenCalled() - expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy() + spyOn(console, 'warn') + + badPack = null + waitsForPromise -> + atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p + + runs -> + expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeTruthy() + spyOn(badPack.mainModule, 'deactivate').andCallThrough() + + atom.packages.deactivatePackage("package-that-throws-on-activate") + expect(badPack.mainModule.deactivate).not.toHaveBeenCalled() + expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy() it "does not serialize packages that have not been activated called on their main module", -> spyOn(console, 'warn') - badPack = atom.packages.activatePackage("package-that-throws-on-activate") - spyOn(badPack.mainModule, 'serialize').andCallThrough() + badPack = null + waitsForPromise -> + atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p - atom.packages.deactivatePackage("package-that-throws-on-activate") - expect(badPack.mainModule.serialize).not.toHaveBeenCalled() + runs -> + spyOn(badPack.mainModule, 'serialize').andCallThrough() + + atom.packages.deactivatePackage("package-that-throws-on-activate") + expect(badPack.mainModule.serialize).not.toHaveBeenCalled() it "absorbs exceptions that are thrown by the package module's serialize methods", -> spyOn(console, 'error') @@ -310,32 +363,44 @@ describe "the `atom` global", -> expect(console.error).toHaveBeenCalled() it "removes the package's grammars", -> - atom.packages.activatePackage('package-with-grammars') - atom.packages.deactivatePackage('package-with-grammars') - expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Null Grammar' - expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Null Grammar' + waitsForPromise -> + atom.packages.activatePackage('package-with-grammars') + + runs -> + atom.packages.deactivatePackage('package-with-grammars') + expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Null Grammar' + expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Null Grammar' it "removes the package's keymaps", -> - atom.packages.activatePackage('package-with-keymaps') - atom.packages.deactivatePackage('package-with-keymaps') - expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0 - expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0 + waitsForPromise -> + atom.packages.activatePackage('package-with-keymaps') + + runs -> + atom.packages.deactivatePackage('package-with-keymaps') + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0 it "removes the package's stylesheets", -> - atom.packages.activatePackage('package-with-stylesheets') - atom.packages.deactivatePackage('package-with-stylesheets') - one = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/1.css") - two = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/2.less") - three = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/3.css") - expect(atom.themes.stylesheetElementForId(one)).not.toExist() - expect(atom.themes.stylesheetElementForId(two)).not.toExist() - expect(atom.themes.stylesheetElementForId(three)).not.toExist() + waitsForPromise -> + atom.packages.activatePackage('package-with-stylesheets') + + runs -> + atom.packages.deactivatePackage('package-with-stylesheets') + one = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/1.css") + two = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/2.less") + three = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/3.css") + expect(atom.themes.stylesheetElementForId(one)).not.toExist() + expect(atom.themes.stylesheetElementForId(two)).not.toExist() + expect(atom.themes.stylesheetElementForId(three)).not.toExist() it "removes the package's scoped-properties", -> - atom.packages.activatePackage("package-with-scoped-properties") - expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' - atom.packages.deactivatePackage("package-with-scoped-properties") - expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBeUndefined() + waitsForPromise -> + atom.packages.activatePackage("package-with-scoped-properties") + + runs -> + expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' + atom.packages.deactivatePackage("package-with-scoped-properties") + expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBeUndefined() describe "textmate packages", -> it "removes the package's grammars", -> @@ -382,7 +447,7 @@ describe "the `atom` global", -> themes = themeActivator.mostRecentCall.args[0] expect(['theme']).toContain(theme.getType()) for theme in themes - describe ".en/disablePackage()", -> + describe ".enablePackage() and disablePackage()", -> describe "with packages", -> it ".enablePackage() enables a disabled package", -> packageName = 'package-with-main' @@ -391,28 +456,36 @@ describe "the `atom` global", -> expect(atom.config.get('core.disabledPackages')).toContain packageName pack = atom.packages.enablePackage(packageName) - loadedPackages = atom.packages.getLoadedPackages() - activatedPackages = atom.packages.getActivePackages() - expect(loadedPackages).toContain(pack) - expect(activatedPackages).toContain(pack) - expect(atom.config.get('core.disabledPackages')).not.toContain packageName + activatedPackages = null + waitsFor -> + activatedPackages = atom.packages.getActivePackages() + activatedPackages.length > 0 + + runs -> + expect(loadedPackages).toContain(pack) + expect(activatedPackages).toContain(pack) + expect(atom.config.get('core.disabledPackages')).not.toContain packageName it ".disablePackage() disables an enabled package", -> packageName = 'package-with-main' - atom.packages.activatePackage(packageName) - atom.packages.observeDisabledPackages() - expect(atom.config.get('core.disabledPackages')).not.toContain packageName + waitsForPromise -> + atom.packages.activatePackage(packageName) - pack = atom.packages.disablePackage(packageName) + runs -> + atom.packages.observeDisabledPackages() + expect(atom.config.get('core.disabledPackages')).not.toContain packageName - activatedPackages = atom.packages.getActivePackages() - expect(activatedPackages).not.toContain(pack) - expect(atom.config.get('core.disabledPackages')).toContain packageName + pack = atom.packages.disablePackage(packageName) + + activatedPackages = atom.packages.getActivePackages() + expect(activatedPackages).not.toContain(pack) + expect(atom.config.get('core.disabledPackages')).toContain packageName describe "with themes", -> beforeEach -> - atom.themes.activateThemes() + waitsForPromise -> + atom.themes.activateThemes() afterEach -> atom.themes.deactivateThemes() @@ -426,18 +499,24 @@ describe "the `atom` global", -> # enabling of theme pack = atom.packages.enablePackage(packageName) - activatedPackages = atom.packages.getActivePackages() - expect(activatedPackages).toContain(pack) - expect(atom.config.get('core.themes')).toContain packageName - expect(atom.config.get('core.disabledPackages')).not.toContain packageName - # disabling of theme - pack = atom.packages.disablePackage(packageName) - activatedPackages = atom.packages.getActivePackages() - expect(activatedPackages).not.toContain(pack) - expect(atom.config.get('core.themes')).not.toContain packageName - expect(atom.config.get('core.themes')).not.toContain packageName - expect(atom.config.get('core.disabledPackages')).not.toContain packageName + activatedPackages = null + waitsFor -> + activatedPackages = atom.packages.getActivePackages() + activatedPackages.length > 0 + + runs -> + expect(activatedPackages).toContain(pack) + expect(atom.config.get('core.themes')).toContain packageName + expect(atom.config.get('core.disabledPackages')).not.toContain packageName + + # disabling of theme + pack = atom.packages.disablePackage(packageName) + activatedPackages = atom.packages.getActivePackages() + expect(activatedPackages).not.toContain(pack) + expect(atom.config.get('core.themes')).not.toContain packageName + expect(atom.config.get('core.themes')).not.toContain packageName + expect(atom.config.get('core.disabledPackages')).not.toContain packageName describe ".isReleasedVersion()", -> it "returns false if the version is a SHA and true otherwise", -> diff --git a/spec/theme-manager-spec.coffee b/spec/theme-manager-spec.coffee index c8d257b54..384844e93 100644 --- a/spec/theme-manager-spec.coffee +++ b/spec/theme-manager-spec.coffee @@ -26,11 +26,14 @@ describe "ThemeManager", -> expect(themes.length).toBeGreaterThan(2) it 'getActiveThemes get all the active themes', -> - themeManager.activateThemes() - names = atom.config.get('core.themes') - expect(names.length).toBeGreaterThan(0) - themes = themeManager.getActiveThemes() - expect(themes).toHaveLength(names.length) + waitsForPromise -> + themeManager.activateThemes() + + runs -> + names = atom.config.get('core.themes') + expect(names.length).toBeGreaterThan(0) + themes = themeManager.getActiveThemes() + expect(themes).toHaveLength(names.length) describe "getImportPaths()", -> it "returns the theme directories before the themes are loaded", -> @@ -51,29 +54,58 @@ describe "ThemeManager", -> it "add/removes stylesheets to reflect the new config value", -> themeManager.on 'reloaded', reloadHandler = jasmine.createSpy() spyOn(themeManager, 'getUserStylesheetPath').andCallFake -> null - themeManager.activateThemes() - atom.config.set('core.themes', []) - expect($('style.theme').length).toBe 0 - expect(reloadHandler).toHaveBeenCalled() + waitsForPromise -> + themeManager.activateThemes() - atom.config.set('core.themes', ['atom-dark-syntax']) - expect($('style.theme').length).toBe 1 - expect($('style.theme:eq(0)').attr('id')).toMatch /atom-dark-syntax/ + runs -> + reloadHandler.reset() + atom.config.set('core.themes', []) - atom.config.set('core.themes', ['atom-light-syntax', 'atom-dark-syntax']) - expect($('style.theme').length).toBe 2 - expect($('style.theme:eq(0)').attr('id')).toMatch /atom-dark-syntax/ - expect($('style.theme:eq(1)').attr('id')).toMatch /atom-light-syntax/ + waitsFor -> + reloadHandler.callCount == 1 - atom.config.set('core.themes', []) - expect($('style.theme').length).toBe 0 + runs -> + reloadHandler.reset() + expect($('style.theme')).toHaveLength 0 + atom.config.set('core.themes', ['atom-dark-syntax']) - # atom-dark-ui has an directory path, the syntax one doesn't - atom.config.set('core.themes', ['theme-with-index-less', 'atom-dark-ui']) - importPaths = themeManager.getImportPaths() - expect(importPaths.length).toBe 1 - expect(importPaths[0]).toContain 'atom-dark-ui' + waitsFor -> + reloadHandler.callCount == 1 + + runs -> + reloadHandler.reset() + expect($('style.theme')).toHaveLength 1 + expect($('style.theme:eq(0)').attr('id')).toMatch /atom-dark-syntax/ + atom.config.set('core.themes', ['atom-light-syntax', 'atom-dark-syntax']) + + waitsFor -> + reloadHandler.callCount == 1 + + runs -> + reloadHandler.reset() + expect($('style.theme')).toHaveLength 2 + expect($('style.theme:eq(0)').attr('id')).toMatch /atom-dark-syntax/ + expect($('style.theme:eq(1)').attr('id')).toMatch /atom-light-syntax/ + atom.config.set('core.themes', []) + + waitsFor -> + reloadHandler.callCount == 1 + + runs -> + reloadHandler.reset() + expect($('style.theme')).toHaveLength 0 + # atom-dark-ui has an directory path, the syntax one doesn't + atom.config.set('core.themes', ['theme-with-index-less', 'atom-dark-ui']) + + waitsFor -> + reloadHandler.callCount == 1 + + runs -> + expect($('style.theme')).toHaveLength 2 + importPaths = themeManager.getImportPaths() + expect(importPaths.length).toBe 1 + expect(importPaths[0]).toContain 'atom-dark-ui' describe "when a theme fails to load", -> it "logs a warning", -> @@ -145,18 +177,25 @@ describe "ThemeManager", -> atom.workspaceView = new WorkspaceView atom.workspaceView.append $$ -> @div class: 'editor' atom.workspaceView.attachToDom() - themeManager.activateThemes() + + waitsForPromise -> + themeManager.activateThemes() it "loads the correct values from the theme's ui-variables file", -> + themeManager.on 'reloaded', reloadHandler = jasmine.createSpy() atom.config.set('core.themes', ['theme-with-ui-variables']) - # an override loaded in the base css - expect(atom.workspaceView.css("background-color")).toBe "rgb(0, 0, 255)" + waitsFor -> + reloadHandler.callCount > 0 - # from within the theme itself - expect($(".editor").css("padding-top")).toBe "150px" - expect($(".editor").css("padding-right")).toBe "150px" - expect($(".editor").css("padding-bottom")).toBe "150px" + runs -> + # an override loaded in the base css + expect(atom.workspaceView.css("background-color")).toBe "rgb(0, 0, 255)" + + # from within the theme itself + expect($(".editor").css("padding-top")).toBe "150px" + expect($(".editor").css("padding-right")).toBe "150px" + expect($(".editor").css("padding-bottom")).toBe "150px" describe "when the user stylesheet changes", -> it "reloads it", -> @@ -164,12 +203,14 @@ describe "ThemeManager", -> fs.writeFileSync(userStylesheetPath, 'body {border-style: dotted !important;}') spyOn(themeManager, 'getUserStylesheetPath').andReturn userStylesheetPath - themeManager.activateThemes() - expect($(document.body).css('border-style')).toBe 'dotted' - spyOn(themeManager, 'loadUserStylesheet').andCallThrough() + waitsForPromise -> + themeManager.activateThemes() - fs.writeFileSync(userStylesheetPath, 'body {border-style: dashed}') + runs -> + expect($(document.body).css('border-style')).toBe 'dotted' + spyOn(themeManager, 'loadUserStylesheet').andCallThrough() + fs.writeFileSync(userStylesheetPath, 'body {border-style: dashed}') waitsFor -> themeManager.loadUserStylesheet.callCount is 1 diff --git a/src/atom-package.coffee b/src/atom-package.coffee index f31ae8d8c..b0a187a3a 100644 --- a/src/atom-package.coffee +++ b/src/atom-package.coffee @@ -2,6 +2,7 @@ Package = require './package' fs = require 'fs-plus' path = require 'path' _ = require 'underscore-plus' +Q = require 'q' {$} = require './space-pen-extensions' CSON = require 'season' {Emitter} = require 'emissary' @@ -59,14 +60,34 @@ class AtomPackage extends Package @grammars = [] @scopedProperties = [] - activate: ({immediate}={}) -> + activate: -> + return @activationDeferred.promise if @activationDeferred? + + @activationDeferred = Q.defer() @measure 'activateTime', => @activateResources() - if @metadata.activationEvents? and not immediate + if @metadata.activationEvents? @subscribeToActivationEvents() else @activateNow() + @activationDeferred.promise + + # Deprecated + activateSync: ({immediate}={}) -> + @activateResources() + if @metadata.activationEvents? and not immediate + @subscribeToActivationEvents() + else + try + @activateConfig() + @activateStylesheets() + if @requireMainModule() + @mainModule.activate(atom.packages.getPackageState(@name) ? {}) + @mainActivated = true + catch e + console.warn "Failed to activate package named '#{@name}'", e.stack + activateNow: -> try @activateConfig() @@ -77,6 +98,8 @@ class AtomPackage extends Package catch e console.warn "Failed to activate package named '#{@name}'", e.stack + @activationDeferred.resolve() + activateConfig: -> return if @configActivated @@ -162,6 +185,8 @@ class AtomPackage extends Package console.error "Error serializing package '#{@name}'", e.stack deactivate: -> + @activationDeferred?.reject() + @activationDeferred = null @unsubscribeFromActivationEvents() @deactivateResources() @deactivateConfig() @@ -226,6 +251,8 @@ class AtomPackage extends Package @unsubscribeFromActivationEvents() unsubscribeFromActivationEvents: -> + return unless atom.workspaceView? + if _.isArray(@metadata.activationEvents) atom.workspaceView.off(event, @handleActivationEvent) for event in @metadata.activationEvents else if _.isString(@metadata.activationEvents) diff --git a/src/package-manager.coffee b/src/package-manager.coffee index cecf5c589..03052512f 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -1,6 +1,7 @@ {Emitter} = require 'emissary' fs = require 'fs-plus' _ = require 'underscore-plus' +Q = require 'q' Package = require './package' path = require 'path' @@ -31,7 +32,6 @@ class PackageManager @loadedPackages = {} @activePackages = {} @packageStates = {} - @observingDisabledPackages = false @packageActivators = [] @registerPackageActivator(this, ['atom', 'textmate']) @@ -81,26 +81,38 @@ class PackageManager @observeDisabledPackages() # Activate a single package by name - activatePackage: (name, options) -> + activatePackage: (name, options={}) -> + if options.sync? or options.immediate? + return @activatePackageSync(name, options) + + if pack = @getActivePackage(name) + Q(pack) + else + pack = @loadPackage(name) + pack.activate(options).then => + @activePackages[pack.name] = pack + pack + + # Deprecated + activatePackageSync: (name, options) -> return pack if pack = @getActivePackage(name) - if pack = @loadPackage(name, options) + if pack = @loadPackage(name) @activePackages[pack.name] = pack - pack.activate(options) + pack.activateSync(options) pack # Deactivate all packages deactivatePackages: -> - @deactivatePackage(pack.name) for pack in @getActivePackages() + @deactivatePackage(pack.name) for pack in @getLoadedPackages() @unobserveDisabledPackages() # Deactivate the package with the given name deactivatePackage: (name) -> - if pack = @getActivePackage(name) + pack = @getLoadedPackage(name) + if @isPackageActive(name) @setPackageState(pack.name, state) if state = pack.serialize?() - pack.deactivate() - delete @activePackages[pack.name] - else - throw new Error("No active package for name '#{name}'") + pack.deactivate() + delete @activePackages[pack.name] # Public: Get an array of all the active packages getActivePackages: -> @@ -115,14 +127,11 @@ class PackageManager @getActivePackage(name)? unobserveDisabledPackages: -> - return unless @observingDisabledPackages - atom.config.unobserve('core.disabledPackages') - @observingDisabledPackages = false + @disabledPackagesSubscription?.off() + @disabledPackagesSubscription = null observeDisabledPackages: -> - return if @observingDisabledPackages - - atom.config.observe 'core.disabledPackages', callNow: false, (disabledPackages, {previous}) => + @disabledPackagesSubscription ?= atom.config.observe 'core.disabledPackages', callNow: false, (disabledPackages, {previous}) => packagesToEnable = _.difference(previous, disabledPackages) packagesToDisable = _.difference(disabledPackages, previous) @@ -130,9 +139,7 @@ class PackageManager @activatePackage(packageName) for packageName in packagesToEnable null - @observingDisabledPackages = true - - loadPackages: (options) -> + loadPackages: -> # Ensure atom exports is already in the require cache so the load time # of the first package isn't skewed by being the first to require atom require '../exports/atom' @@ -140,15 +147,15 @@ class PackageManager packagePaths = @getAvailablePackagePaths() packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath)) packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath) - @loadPackage(packagePath, options) for packagePath in packagePaths + @loadPackage(packagePath) for packagePath in packagePaths @emit 'loaded' - loadPackage: (nameOrPath, options) -> + loadPackage: (nameOrPath) -> if packagePath = @resolvePackagePath(nameOrPath) name = path.basename(nameOrPath) return pack if pack = @getLoadedPackage(name) - pack = Package.load(packagePath, options) + pack = Package.load(packagePath) @loadedPackages[pack.name] = pack if pack? pack else diff --git a/src/package.coffee b/src/package.coffee index 02ee62793..7618e80c5 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -22,9 +22,9 @@ class Package pack - @load: (path, options) -> + @load: (path) -> pack = @build(path) - pack?.load(options) + pack?.load() pack @loadMetadata: (path, ignoreErrors=false) -> @@ -43,9 +43,6 @@ class Package constructor: (@path) -> @name = basename(@path) - isActive: -> - atom.packages.isPackageActive(@name) - enable: -> atom.config.removeAtKeyPath('core.disabledPackages', @metadata.name) diff --git a/src/text-mate-package.coffee b/src/text-mate-package.coffee index 7fcd9b647..5e39ab53b 100644 --- a/src/text-mate-package.coffee +++ b/src/text-mate-package.coffee @@ -2,7 +2,7 @@ Package = require './package' path = require 'path' _ = require 'underscore-plus' fs = require 'fs-plus' -async = require 'async' +Q = require 'q' module.exports = class TextMatePackage extends Package @@ -10,13 +10,12 @@ class TextMatePackage extends Package packageName = path.basename(packageName) /(^language-.+)|((\.|_|-)tmbundle$)/.test(packageName) - @getLoadQueue: -> - return @loadQueue if @loadQueue? - - @loadQueue = async.queue (pack, done) -> - pack.loadGrammars -> pack.loadScopedProperties(done) - @loadQueue.concurreny = 10 - @loadQueue + @addToActivationPromise = (pack) -> + @activationPromise ?= Q() + @activationPromise = @activationPromise.then => + pack.loadGrammars() + .then -> pack.loadScopedProperties() + .fail (error) -> console.log pack.name, error constructor: -> super @@ -26,21 +25,16 @@ class TextMatePackage extends Package getType: -> 'textmate' - load: ({sync}={}) -> + load: -> @measure 'loadTime', => @metadata = Package.loadMetadata(@path, true) - if sync - @loadGrammarsSync() - @loadScopedPropertiesSync() - else - TextMatePackage.getLoadQueue().push(this) + activate: ({sync, immediate}={})-> + TextMatePackage.addToActivationPromise(this) - activate: -> - @measure 'activateTime', => - grammar.activate() for grammar in @grammars - for { selector, properties } in @scopedProperties - atom.syntax.addProperties(@path, selector, properties) + activateSync: -> + @loadGrammarsSync() + @loadScopedPropertiesSync() activateConfig: -> # noop @@ -50,33 +44,35 @@ class TextMatePackage extends Package legalGrammarExtensions: ['plist', 'tmLanguage', 'tmlanguage', 'json', 'cson'] - loadGrammars: (done) -> + loadGrammars: -> + deferred = Q.defer() fs.isDirectory @getSyntaxesPath(), (isDirectory) => - if isDirectory - fs.list @getSyntaxesPath(), @legalGrammarExtensions, (error, paths) => - if error? - console.log("Error loading grammars of TextMate package '#{@path}':", error.stack, error) - done() - else - async.eachSeries(paths, @loadGrammarAtPath, done) - else - done() + return deferred.resolve() unless isDirectory - loadGrammarAtPath: (grammarPath, done) => + fs.list @getSyntaxesPath(), @legalGrammarExtensions, (error, paths) => + if error? + console.log("Error loading grammars of TextMate package '#{@path}':", error.stack, error) + deferred.resolve() + else + promises = paths.map (path) => @loadGrammarAtPath(path) + Q.all(promises).then -> deferred.resolve() + + deferred.promise + + loadGrammarAtPath: (grammarPath) -> + deferred = Q.defer() atom.syntax.readGrammar grammarPath, (error, grammar) => if error? console.log("Error loading grammar at path '#{grammarPath}':", error.stack ? error) else @addGrammar(grammar) - done?() + deferred.resolve() - loadGrammarsSync: -> - for grammarPath in fs.listSync(@getSyntaxesPath(), @legalGrammarExtensions) - @addGrammar(atom.syntax.readGrammarSync(grammarPath)) + deferred.promise addGrammar: (grammar) -> @grammars.push(grammar) - grammar.activate() if @isActive() + grammar.activate() getGrammars: -> @grammars @@ -94,23 +90,7 @@ class TextMatePackage extends Package else path.join(@path, "Preferences") - loadScopedPropertiesSync: -> - for grammar in @getGrammars() - if properties = @propertiesFromTextMateSettings(grammar) - selector = atom.syntax.cssSelectorFromScopeSelector(grammar.scopeName) - @scopedProperties.push({selector, properties}) - - for preferencePath in fs.listSync(@getPreferencesPath()) - {scope, settings} = fs.readObjectSync(preferencePath) - if properties = @propertiesFromTextMateSettings(settings) - selector = atom.syntax.cssSelectorFromScopeSelector(scope) if scope? - @scopedProperties.push({selector, properties}) - - if @isActive() - for {selector, properties} in @scopedProperties - atom.syntax.addProperties(@path, selector, properties) - - loadScopedProperties: (callback) -> + loadScopedProperties: -> scopedProperties = [] for grammar in @getGrammars() @@ -118,38 +98,37 @@ class TextMatePackage extends Package selector = atom.syntax.cssSelectorFromScopeSelector(grammar.scopeName) scopedProperties.push({selector, properties}) - preferenceObjects = [] - done = => + @loadTextMatePreferenceObjects().then (preferenceObjects=[]) => for {scope, settings} in preferenceObjects if properties = @propertiesFromTextMateSettings(settings) selector = atom.syntax.cssSelectorFromScopeSelector(scope) if scope? scopedProperties.push({selector, properties}) @scopedProperties = scopedProperties - if @isActive() - for {selector, properties} in @scopedProperties - atom.syntax.addProperties(@path, selector, properties) - callback?() - @loadTextMatePreferenceObjects(preferenceObjects, done) + for {selector, properties} in @scopedProperties + atom.syntax.addProperties(@path, selector, properties) - loadTextMatePreferenceObjects: (preferenceObjects, done) -> + loadTextMatePreferenceObjects: -> + deferred = Q.defer() fs.isDirectory @getPreferencesPath(), (isDirectory) => - return done() unless isDirectory - + return deferred.resolve() unless isDirectory fs.list @getPreferencesPath(), (error, paths) => if error? console.log("Error loading preferences of TextMate package '#{@path}':", error.stack, error) - done() - return + deferred.resolve() + else + promises = paths.map (path) => @loadPreferencesAtPath(path) + Q.all(promises).then (preferenceObjects) -> deferred.resolve(preferenceObjects) - loadPreferencesAtPath = (preferencePath, done) -> - fs.readObject preferencePath, (error, preferences) => - if error? - console.warn("Failed to parse preference at path '#{preferencePath}'", error.stack, error) - else - preferenceObjects.push(preferences) - done() - async.eachSeries paths, loadPreferencesAtPath, done + deferred.promise + + loadPreferencesAtPath: (preferencePath) -> + deferred = Q.defer() + fs.readObject preferencePath, (error, preference) -> + if error? + console.warn("Failed to parse preference at path '#{preferencePath}'", error.stack, error) + deferred.resolve(preference) + deferred.promise propertiesFromTextMateSettings: (textMateSettings) -> if textMateSettings.shellVariables @@ -167,3 +146,24 @@ class TextMatePackage extends Package completions: textMateSettings.completions ) { editor: editorProperties } if _.size(editorProperties) > 0 + + # Deprecated + loadGrammarsSync: -> + for grammarPath in fs.listSync(@getSyntaxesPath(), @legalGrammarExtensions) + @addGrammar(atom.syntax.readGrammarSync(grammarPath)) + + # Deprecated + loadScopedPropertiesSync: -> + for grammar in @getGrammars() + if properties = @propertiesFromTextMateSettings(grammar) + selector = atom.syntax.cssSelectorFromScopeSelector(grammar.scopeName) + @scopedProperties.push({selector, properties}) + + for preferencePath in fs.listSync(@getPreferencesPath()) + {scope, settings} = fs.readObjectSync(preferencePath) + if properties = @propertiesFromTextMateSettings(settings) + selector = atom.syntax.cssSelectorFromScopeSelector(scope) if scope? + @scopedProperties.push({selector, properties}) + + for {selector, properties} in @scopedProperties + atom.syntax.addProperties(@path, selector, properties) diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee index a5fa5a0c8..08ecc0d47 100644 --- a/src/theme-manager.coffee +++ b/src/theme-manager.coffee @@ -3,6 +3,7 @@ path = require 'path' _ = require 'underscore-plus' {Emitter} = require 'emissary' fs = require 'fs-plus' +Q = require 'q' {$} = require './space-pen-extensions' AtomPackage = require './atom-package' @@ -53,19 +54,22 @@ class ThemeManager themeNames.reverse() activateThemes: -> + deferred = Q.defer() + # atom.config.observe runs the callback once, then on subsequent changes. atom.config.observe 'core.themes', => @deactivateThemes() @refreshLessCache() # Update cache for packages in core.themes config - for themeName in @getEnabledThemeNames() - @packageManager.activatePackage(themeName) + promises = @getEnabledThemeNames().map (themeName) => @packageManager.activatePackage(themeName) + Q.all(promises).then => + @refreshLessCache() # Update cache again now that @getActiveThemes() is populated + @loadUserStylesheet() + @reloadBaseStylesheets() + @emit('reloaded') + deferred.resolve() - @refreshLessCache() # Update cache again now that @getActiveThemes() is populated - @loadUserStylesheet() - @reloadBaseStylesheets() - - @emit('reloaded') + deferred.promise deactivateThemes: -> @unwatchUserStylesheet() @@ -91,7 +95,7 @@ class ThemeManager if themePath = @packageManager.resolvePackagePath(themeName) themePaths.push(path.join(themePath, AtomPackage.stylesheetsDir)) - themePath for themePath in themePaths when fs.isDirectorySync(themePath) + themePaths.filter (themePath) -> fs.isDirectorySync(themePath) # Public: Returns the {String} path to the user's stylesheet under ~/.atom getUserStylesheetPath: -> diff --git a/src/theme-package.coffee b/src/theme-package.coffee index 2115276fd..2d4f611a6 100644 --- a/src/theme-package.coffee +++ b/src/theme-package.coffee @@ -1,11 +1,8 @@ +Q = require 'q' AtomPackage = require './atom-package' -Package = require './package' - -### Internal: Loads and resolves packages. ### module.exports = class ThemePackage extends AtomPackage - getType: -> 'theme' getStylesheetType: -> 'theme' @@ -25,6 +22,11 @@ class ThemePackage extends AtomPackage this activate: -> + return @activationDeferred.promise if @activationDeferred? + + @activationDeferred = Q.defer() @measure 'activateTime', => @loadStylesheets() @activateNow() + + @activationDeferred.promise