Merge pull request #1518 from atom/cj-make-package-activation-consistent

Make package loading options consistent and asynchronous
This commit is contained in:
Corey Johnson
2014-02-10 14:18:27 -08:00
9 changed files with 424 additions and 267 deletions

View File

@@ -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",

View File

@@ -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", ->

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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: ->

View File

@@ -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