diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js index 30b23ffbf..56a37cfd4 100644 --- a/benchmarks/benchmark-runner.js +++ b/benchmarks/benchmark-runner.js @@ -65,7 +65,7 @@ export default async function ({test, benchmarkPaths}) { console.log(textualOutput) } - global.atom.reset() + await global.atom.reset() } } diff --git a/spec/grammars-spec.coffee b/spec/grammars-spec.coffee index 7d4754397..7b70797ba 100644 --- a/spec/grammars-spec.coffee +++ b/spec/grammars-spec.coffee @@ -22,10 +22,12 @@ describe "the `grammars` global", -> atom.packages.activatePackage('language-git') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() - try - temp.cleanupSync() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() + try + temp.cleanupSync() describe ".selectGrammar(filePath)", -> it "always returns a grammar", -> diff --git a/spec/language-mode-spec.coffee b/spec/language-mode-spec.coffee index 4025472af..aca96d2cc 100644 --- a/spec/language-mode-spec.coffee +++ b/spec/language-mode-spec.coffee @@ -15,8 +15,10 @@ describe "LanguageMode", -> atom.packages.activatePackage('language-javascript') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() describe ".minIndentLevelForRowRange(startRow, endRow)", -> it "returns the minimum indent level for the given row range", -> @@ -175,8 +177,10 @@ describe "LanguageMode", -> atom.packages.activatePackage('language-coffee-script') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() describe ".toggleLineCommentsForBufferRows(start, end)", -> it "comments/uncomments lines in the given range", -> @@ -222,8 +226,10 @@ describe "LanguageMode", -> atom.packages.activatePackage('language-css') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() describe ".toggleLineCommentsForBufferRows(start, end)", -> it "comments/uncomments lines in the given range", -> @@ -274,8 +280,10 @@ describe "LanguageMode", -> atom.packages.activatePackage('language-css') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() describe "when commenting lines", -> it "only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart`", -> @@ -294,8 +302,10 @@ describe "LanguageMode", -> atom.packages.activatePackage('language-xml') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() describe "when uncommenting lines", -> it "removes the leading whitespace from the comment end pattern match", -> @@ -313,8 +323,10 @@ describe "LanguageMode", -> atom.packages.activatePackage('language-javascript') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() it "maintains cursor buffer position when a folding/unfolding", -> editor.setCursorBufferPosition([5, 5]) @@ -403,8 +415,10 @@ describe "LanguageMode", -> atom.packages.activatePackage('language-javascript') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() describe ".unfoldAll()", -> it "unfolds every folded line", -> @@ -481,8 +495,10 @@ describe "LanguageMode", -> atom.packages.activatePackage('language-css') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() describe "suggestedIndentForBufferRow", -> it "does not return negative values (regression)", -> diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 88efff4ae..9b7d46340 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -56,8 +56,10 @@ describe "PackageManager", -> spyOn(atom.packages, 'loadAvailablePackage') afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() it "sets hasLoadedInitialPackages", -> expect(atom.packages.hasLoadedInitialPackages()).toBe false @@ -172,8 +174,10 @@ describe "PackageManager", -> model2 = {worksWithViewProvider2: true} afterEach -> - atom.packages.deactivatePackage('package-with-view-providers') - atom.packages.unloadPackage('package-with-view-providers') + waitsForPromise -> + atom.packages.deactivatePackage('package-with-view-providers') + runs -> + atom.packages.unloadPackage('package-with-view-providers') it "does not load the view providers immediately", -> pack = atom.packages.loadPackage("package-with-view-providers") @@ -641,7 +645,11 @@ describe "PackageManager", -> runs -> expect(mainModule.activate.callCount).toBe 1 + + waitsForPromise -> atom.packages.deactivatePackage('package-with-activation-hooks') + + runs -> promise = atom.packages.activatePackage('package-with-activation-hooks') atom.packages.triggerActivationHook('language-fictitious:grammar-used') atom.packages.triggerDeferredActivationHooks() @@ -704,7 +712,9 @@ describe "PackageManager", -> expect(pack.mainModule.someNumber).not.toBe 77 pack.mainModule.someNumber = 77 atom.packages.serializePackage("package-with-serialization") + waitsForPromise -> atom.packages.deactivatePackage("package-with-serialization") + runs -> spyOn(pack.mainModule, 'activate').andCallThrough() waitsForPromise -> atom.packages.activatePackage("package-with-serialization") @@ -872,6 +882,7 @@ describe "PackageManager", -> expect(events.length).toBe(1) expect(events[0].type).toBe("user-command") + waitsForPromise -> atom.packages.deactivatePackage("package-with-keymaps") waitsForPromise -> @@ -1041,12 +1052,15 @@ describe "PackageManager", -> consumerModule.consumeFirstServiceV4.reset() consumerModule.consumeSecondService.reset() + waitsForPromise -> atom.packages.deactivatePackage("package-with-provided-services") + runs -> expect(firstServiceV3Disposed).toBe true expect(firstServiceV4Disposed).toBe true expect(secondServiceDisposed).toBe true + waitsForPromise -> atom.packages.deactivatePackage("package-with-consumed-services") waitsForPromise -> @@ -1112,8 +1126,11 @@ describe "PackageManager", -> runs -> spyOn(pack1.mainModule, 'deactivate') spyOn(pack2.mainModule, 'serialize') + + waitsForPromise -> atom.packages.deactivatePackages() + runs -> expect(pack1.mainModule.deactivate).toHaveBeenCalled() expect(pack2.mainModule.serialize).not.toHaveBeenCalled() @@ -1131,7 +1148,10 @@ describe "PackageManager", -> expect(atom.packages.isPackageActive("package-with-deactivate")).toBeTruthy() spyOn(pack.mainModule, 'deactivate').andCallThrough() + waitsForPromise -> atom.packages.deactivatePackage("package-with-deactivate") + + runs -> expect(pack.mainModule.deactivate).toHaveBeenCalled() expect(atom.packages.isPackageActive("package-with-module")).toBeFalsy() @@ -1145,26 +1165,38 @@ describe "PackageManager", -> expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeTruthy() spyOn(badPack.mainModule, 'deactivate').andCallThrough() + waitsForPromise -> atom.packages.deactivatePackage("package-that-throws-on-activate") + + runs -> expect(badPack.mainModule.deactivate).not.toHaveBeenCalled() expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy() it "absorbs exceptions that are thrown by the package module's deactivate method", -> spyOn(console, 'error') + thrownError = null waitsForPromise -> atom.packages.activatePackage("package-that-throws-on-deactivate") + waitsForPromise -> + try + atom.packages.deactivatePackage("package-that-throws-on-deactivate") + catch error + thrownError = error + runs -> - expect(-> atom.packages.deactivatePackage("package-that-throws-on-deactivate")).not.toThrow() + expect(thrownError).toBeNull() expect(console.error).toHaveBeenCalled() it "removes the package's grammars", -> waitsForPromise -> atom.packages.activatePackage('package-with-grammars') - runs -> + waitsForPromise -> atom.packages.deactivatePackage('package-with-grammars') + + runs -> expect(atom.grammars.selectGrammar('a.alot').name).toBe 'Null Grammar' expect(atom.grammars.selectGrammar('a.alittle').name).toBe 'Null Grammar' @@ -1172,8 +1204,10 @@ describe "PackageManager", -> waitsForPromise -> atom.packages.activatePackage('package-with-keymaps') - runs -> + waitsForPromise -> atom.packages.deactivatePackage('package-with-keymaps') + + runs -> expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: createTestElement('test-1'))).toHaveLength 0 expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: createTestElement('test-2'))).toHaveLength 0 @@ -1181,8 +1215,10 @@ describe "PackageManager", -> waitsForPromise -> atom.packages.activatePackage('package-with-styles') - runs -> + waitsForPromise -> atom.packages.deactivatePackage('package-with-styles') + + runs -> one = require.resolve("./fixtures/packages/package-with-style-sheets-manifest/styles/1.css") two = require.resolve("./fixtures/packages/package-with-style-sheets-manifest/styles/2.less") three = require.resolve("./fixtures/packages/package-with-style-sheets-manifest/styles/3.css") @@ -1196,17 +1232,26 @@ describe "PackageManager", -> runs -> expect(atom.config.get 'editor.increaseIndentPattern', scope: ['.source.omg']).toBe '^a' + + waitsForPromise -> atom.packages.deactivatePackage("package-with-settings") + + runs -> expect(atom.config.get 'editor.increaseIndentPattern', scope: ['.source.omg']).toBeUndefined() it "invokes ::onDidDeactivatePackage listeners with the deactivated package", -> + deactivatedPackage = null + waitsForPromise -> atom.packages.activatePackage("package-with-main") runs -> - deactivatedPackage = null atom.packages.onDidDeactivatePackage (pack) -> deactivatedPackage = pack + + waitsForPromise -> atom.packages.deactivatePackage("package-with-main") + + runs -> expect(deactivatedPackage.name).toBe "package-with-main" describe "::activate()", -> @@ -1220,10 +1265,11 @@ describe "PackageManager", -> expect(loadedPackages.length).toBeGreaterThan 0 afterEach -> - atom.packages.deactivatePackages() - atom.packages.unloadPackages() - - jasmine.restoreDeprecationsSnapshot() + waitsForPromise -> + atom.packages.deactivatePackages() + runs -> + atom.packages.unloadPackages() + jasmine.restoreDeprecationsSnapshot() it "sets hasActivatedInitialPackages", -> spyOn(atom.styles, 'getUserStyleSheetPath').andReturn(null) @@ -1286,6 +1332,9 @@ describe "PackageManager", -> it "disables an enabled package", -> packageName = 'package-with-main' + pack = null + activatedPackages = null + waitsForPromise -> atom.packages.activatePackage(packageName) @@ -1295,7 +1344,11 @@ describe "PackageManager", -> pack = atom.packages.disablePackage(packageName) + waitsFor -> activatedPackages = atom.packages.getActivePackages() + activatedPackages.length is 0 + + runs -> expect(activatedPackages).not.toContain(pack) expect(atom.config.get('core.disabledPackages')).toContain packageName @@ -1322,7 +1375,8 @@ describe "PackageManager", -> atom.themes.activateThemes() afterEach -> - atom.themes.deactivateThemes() + waitsForPromise -> + atom.themes.deactivateThemes() it "enables and disables a theme", -> packageName = 'theme-with-package-file' diff --git a/spec/package-spec.coffee b/spec/package-spec.coffee index 5018d2eb4..e7bfd0249 100644 --- a/spec/package-spec.coffee +++ b/spec/package-spec.coffee @@ -138,7 +138,8 @@ describe "Package", -> jasmine.attachToDOM(editorElement) afterEach -> - theme.deactivate() if theme? + waitsForPromise -> + Promise.resolve(theme.deactivate()) if theme? describe "when the theme contains a single style file", -> it "loads and applies css", -> @@ -200,8 +201,10 @@ describe "Package", -> it "deactivated event fires on .deactivate()", -> theme.onDidDeactivate spy = jasmine.createSpy() - theme.deactivate() - expect(spy).toHaveBeenCalled() + waitsForPromise -> + Promise.resolve(theme.deactivate()) + runs -> + expect(spy).toHaveBeenCalled() describe ".loadMetadata()", -> [packagePath, metadata] = [] diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index eec8ce5fb..c20bfc827 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -108,10 +108,14 @@ beforeEach -> afterEach -> ensureNoDeprecatedFunctionCalls() ensureNoDeprecatedStylesheets() - atom.reset() - document.getElementById('jasmine-content').innerHTML = '' unless window.debugContent - warnIfLeakingPathSubscriptions() - waits(0) # yield to ui thread to make screen update more frequently + + waitsForPromise -> + atom.reset() + + runs -> + document.getElementById('jasmine-content').innerHTML = '' unless window.debugContent + warnIfLeakingPathSubscriptions() + waits(0) # yield to ui thread to make screen update more frequently warnIfLeakingPathSubscriptions = -> watchedPaths = pathwatcher.getWatchedPaths() diff --git a/spec/text-editor-registry-spec.js b/spec/text-editor-registry-spec.js index 79d575a5f..2479bff9b 100644 --- a/spec/text-editor-registry-spec.js +++ b/spec/text-editor-registry-spec.js @@ -685,7 +685,7 @@ describe('TextEditorRegistry', function () { registry.setGrammarOverride(editor, 'source.c') registry.setGrammarOverride(editor2, 'source.js') - atom.packages.deactivatePackage('language-javascript') + await atom.packages.deactivatePackage('language-javascript') const editorCopy = TextEditor.deserialize(editor.serialize(), atom) const editor2Copy = TextEditor.deserialize(editor2.serialize(), atom) diff --git a/spec/theme-manager-spec.coffee b/spec/theme-manager-spec.coffee index 5d2912f5b..86237b71d 100644 --- a/spec/theme-manager-spec.coffee +++ b/spec/theme-manager-spec.coffee @@ -8,9 +8,11 @@ describe "atom.themes", -> spyOn(console, 'warn') afterEach -> - atom.themes.deactivateThemes() - try - temp.cleanupSync() + waitsForPromise -> + atom.themes.deactivateThemes() + runs -> + try + temp.cleanupSync() describe "theme getters and setters", -> beforeEach -> diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index b37acddd1..bd631435e 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -328,20 +328,14 @@ class AtomEnvironment extends Model @contextMenu.clear() - @packages.reset() - - @workspace.reset(@packages) - @registerDefaultOpeners() - - @project.reset(@packages) - - @workspace.subscribeToEvents() - - @grammars.clear() - - @textEditors.clear() - - @views.clear() + @packages.reset().then => + @workspace.reset(@packages) + @registerDefaultOpeners() + @project.reset(@packages) + @workspace.subscribeToEvents() + @grammars.clear() + @textEditors.clear() + @views.clear() destroy: -> return if not @project @@ -702,6 +696,11 @@ class AtomEnvironment extends Model windowCloseRequested: true, projectHasPaths: @project.getPaths().length > 0 }) + .then (closing) => + if closing + @packages.deactivatePackages().then -> closing + else + closing @listenForUpdates() @@ -758,7 +757,6 @@ class AtomEnvironment extends Model return if not @project @storeWindowBackground() - @packages.deactivatePackages() @saveBlobStoreSync() @unloaded = true diff --git a/src/package-manager.js b/src/package-manager.js index 73855ae37..b52e29cad 100644 --- a/src/package-manager.js +++ b/src/package-manager.js @@ -77,9 +77,9 @@ module.exports = class PackageManager { this.themeManager = themeManager } - reset () { + async reset () { this.serviceHub.clear() - this.deactivatePackages() + await this.deactivatePackages() this.loadedPackages = {} this.preloadedPackages = {} this.packageStates = {} @@ -744,21 +744,30 @@ module.exports = class PackageManager { } // Deactivate all packages - deactivatePackages () { - this.config.transact(() => { - this.getLoadedPackages().forEach(pack => this.deactivatePackage(pack.name, true)) - }) + async deactivatePackages () { + await this.config.transactAsync(() => + Promise.all(this.getLoadedPackages().map(pack => this.deactivatePackage(pack.name, true))) + ) this.unobserveDisabledPackages() this.unobservePackagesWithKeymapsDisabled() } // Deactivate the package with the given name - deactivatePackage (name, suppressSerialization) { + async deactivatePackage (name, suppressSerialization) { const pack = this.getLoadedPackage(name) + if (pack == null) { + return + } + if (!suppressSerialization && this.isPackageActive(pack.name)) { this.serializePackage(pack) } - pack.deactivate() + + const deactivationResult = pack.deactivate() + if (deactivationResult && typeof deactivationResult.then === 'function') { + await deactivationResult + } + delete this.activePackages[pack.name] delete this.activatingPackages[pack.name] this.emitter.emit('did-deactivate-package', pack) diff --git a/src/package.coffee b/src/package.coffee index 039ccf9d3..fdd89bc74 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -506,14 +506,29 @@ class Package @configSchemaRegisteredOnActivate = false @deactivateResources() @deactivateKeymaps() - if @mainActivated - try - @mainModule?.deactivate?() - @mainModule?.deactivateConfig?() - @mainActivated = false - @mainInitialized = false - catch e - console.error "Error deactivating package '#{@name}'", e.stack + + unless @mainActivated + @emitter.emit 'did-deactivate' + return + + try + deactivationResult = @mainModule?.deactivate?() + catch e + console.error "Error deactivating package '#{@name}'", e.stack + + # We support then-able async promises as well as sync ones from deactivate + if deactivationResult?.then is 'function' + deactivationResult.then => @afterDeactivation() + else + @afterDeactivation() + + afterDeactivation: -> + try + @mainModule?.deactivateConfig?() + catch e + console.error "Error deactivating package '#{@name}'", e.stack + @mainActivated = false + @mainInitialized = false @emitter.emit 'did-deactivate' deactivateResources: -> diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee index 0f019dbf0..d6a67ee61 100644 --- a/src/theme-manager.coffee +++ b/src/theme-manager.coffee @@ -262,33 +262,31 @@ class ThemeManager new Promise (resolve) => # @config.observe runs the callback once, then on subsequent changes. @config.observe 'core.themes', => - @deactivateThemes() + @deactivateThemes().then => + @warnForNonExistentThemes() + @refreshLessCache() # Update cache for packages in core.themes config - @warnForNonExistentThemes() + promises = [] + for themeName in @getEnabledThemeNames() + if @packageManager.resolvePackagePath(themeName) + promises.push(@packageManager.activatePackage(themeName)) + else + console.warn("Failed to activate theme '#{themeName}' because it isn't installed.") - @refreshLessCache() # Update cache for packages in core.themes config - - promises = [] - for themeName in @getEnabledThemeNames() - if @packageManager.resolvePackagePath(themeName) - promises.push(@packageManager.activatePackage(themeName)) - else - console.warn("Failed to activate theme '#{themeName}' because it isn't installed.") - - Promise.all(promises).then => - @addActiveThemeClasses() - @refreshLessCache() # Update cache again now that @getActiveThemes() is populated - @loadUserStylesheet() - @reloadBaseStylesheets() - @initialLoadComplete = true - @emitter.emit 'did-change-active-themes' - resolve() + Promise.all(promises).then => + @addActiveThemeClasses() + @refreshLessCache() # Update cache again now that @getActiveThemes() is populated + @loadUserStylesheet() + @reloadBaseStylesheets() + @initialLoadComplete = true + @emitter.emit 'did-change-active-themes' + resolve() deactivateThemes: -> @removeActiveThemeClasses() @unwatchUserStylesheet() - @packageManager.deactivatePackage(pack.name) for pack in @getActiveThemes() - null + results = @getActiveThemes().map((pack) => @packageManager.deactivatePackage(pack.name)) + Promise.all(results.filter((r) -> typeof r?.then is 'function')) isInitialLoadComplete: -> @initialLoadComplete