From b3b046aa6657fef0388ec104c3712446504c9528 Mon Sep 17 00:00:00 2001 From: Joe Fitzgerald Date: Wed, 1 Jul 2015 14:15:00 -0600 Subject: [PATCH 1/2] Add activationHooks To package.json - Trigger the `{grammar-package-name}:grammar-used` hook when grammar is set for tokenized-buffer --- .../index.coffee | 5 ++ .../package.cson | 5 ++ .../index.coffee | 1 + .../package.json | 5 ++ spec/package-manager-spec.coffee | 46 ++++++++++++++-- spec/tokenized-buffer-spec.coffee | 54 +++++++++++++++++++ src/package-manager.coffee | 9 ++++ src/package.coffee | 44 +++++++++++++-- src/text-editor-element.coffee | 3 +- src/text-editor.coffee | 1 - src/tokenized-buffer.coffee | 5 +- 11 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 spec/fixtures/packages/package-with-activation-hooks/index.coffee create mode 100644 spec/fixtures/packages/package-with-activation-hooks/package.cson create mode 100644 spec/fixtures/packages/package-with-empty-activation-hooks/index.coffee create mode 100644 spec/fixtures/packages/package-with-empty-activation-hooks/package.json diff --git a/spec/fixtures/packages/package-with-activation-hooks/index.coffee b/spec/fixtures/packages/package-with-activation-hooks/index.coffee new file mode 100644 index 000000000..e5ddc2ca1 --- /dev/null +++ b/spec/fixtures/packages/package-with-activation-hooks/index.coffee @@ -0,0 +1,5 @@ +module.exports = + activateCallCount: 0 + + activate: -> + @activateCallCount++ diff --git a/spec/fixtures/packages/package-with-activation-hooks/package.cson b/spec/fixtures/packages/package-with-activation-hooks/package.cson new file mode 100644 index 000000000..53296a269 --- /dev/null +++ b/spec/fixtures/packages/package-with-activation-hooks/package.cson @@ -0,0 +1,5 @@ +{ + "name": "package-with-activation-hooks", + "version": "0.1.0", + "activationHooks": ['language-fictitious:grammar-used'] +} diff --git a/spec/fixtures/packages/package-with-empty-activation-hooks/index.coffee b/spec/fixtures/packages/package-with-empty-activation-hooks/index.coffee new file mode 100644 index 000000000..78d8802a9 --- /dev/null +++ b/spec/fixtures/packages/package-with-empty-activation-hooks/index.coffee @@ -0,0 +1 @@ +module.exports = activate: -> diff --git a/spec/fixtures/packages/package-with-empty-activation-hooks/package.json b/spec/fixtures/packages/package-with-empty-activation-hooks/package.json new file mode 100644 index 000000000..52be5496d --- /dev/null +++ b/spec/fixtures/packages/package-with-empty-activation-hooks/package.json @@ -0,0 +1,5 @@ +{ + "name": "package-with-empty-activation-hooks", + "version": "0.1.0", + "activationHooks": [] +} diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 26b675975..0165494e1 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -189,7 +189,7 @@ describe "PackageManager", -> expect(atom.config.get('package-with-config-defaults.numbers.two')).toBe 2 describe "when the package metadata includes `activationCommands`", -> - [mainModule, promise, workspaceCommandListener] = [] + [mainModule, promise, workspaceCommandListener, registration] = [] beforeEach -> jasmine.attachToDOM(workspaceElement) @@ -200,10 +200,14 @@ describe "PackageManager", -> spyOn(Package.prototype, 'requireMainModule').andCallThrough() workspaceCommandListener = jasmine.createSpy('workspaceCommandListener') - atom.commands.add '.workspace', 'activation-command', workspaceCommandListener + registration = atom.commands.add '.workspace', 'activation-command', workspaceCommandListener promise = atom.packages.activatePackage('package-with-activation-commands') + afterEach -> + registration?.dispose() + mainModule = null + it "defers requiring/activating the main module until an activation event bubbles to the root view", -> expect(promise.isFulfilled()).not.toBeTruthy() workspaceElement.dispatchEvent(new CustomEvent('activation-command', bubbles: true)) @@ -286,6 +290,41 @@ describe "PackageManager", -> expect(addErrorHandler.callCount).toBe 1 expect(addErrorHandler.argsForCall[0][0].message).toContain("Failed to load the package-with-invalid-settings package settings") + describe "when the package metadata includes `activationHooks`", -> + [mainModule, promise] = [] + + beforeEach -> + mainModule = require './fixtures/packages/package-with-activation-hooks/index' + spyOn(mainModule, 'activate').andCallThrough() + spyOn(Package.prototype, 'requireMainModule').andCallThrough() + + promise = atom.packages.activatePackage('package-with-activation-hooks') + + it "defers requiring/activating the main module until an triggering of an activation hook occurs", -> + expect(promise.isFulfilled()).not.toBeTruthy() + expect(Package.prototype.requireMainModule.callCount).toBe 0 + atom.packages.triggerActivationHook('language-fictitious:grammar-used') + + waitsForPromise -> + promise + + runs -> + expect(Package.prototype.requireMainModule.callCount).toBe 1 + + it "activates the package immediately when activationHooks is empty", -> + mainModule = require './fixtures/packages/package-with-empty-activation-hooks/index' + spyOn(mainModule, 'activate').andCallThrough() + + runs -> + expect(Package.prototype.requireMainModule.callCount).toBe 0 + + waitsForPromise -> + atom.packages.activatePackage('package-with-empty-activation-hooks') + + runs -> + expect(mainModule.activate.callCount).toBe 1 + expect(Package.prototype.requireMainModule.callCount).toBe 1 + describe "when the package has no main module", -> it "does not throw an exception", -> spyOn(console, "error") @@ -750,8 +789,7 @@ describe "PackageManager", -> package1 = atom.packages.loadPackage('package-with-main') package2 = atom.packages.loadPackage('package-with-index') package3 = atom.packages.loadPackage('package-with-activation-commands') - spyOn(atom.packages, 'getLoadedPackages').andReturn([package1, package2]) - + spyOn(atom.packages, 'getLoadedPackages').andReturn([package1, package2, package3]) activateSpy = jasmine.createSpy('activateSpy') atom.packages.onDidActivateInitialPackages(activateSpy) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 23520518a..2a4f23de6 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -895,3 +895,57 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].value).toBe 'b' expect(tokenizedBuffer.tokenizedLineForRow(2).tokens.length).toBe 1 expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].value).toBe 'c' + + describe 'when a file is opened', -> + [registration, editor, called] = [] + beforeEach -> + runs -> + called = false + registration = atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', -> called = true) + + waitsForPromise -> + atom.project.open('sample.js', autoIndent: false).then (o) -> + editor = o + + waitsForPromise -> + atom.packages.activatePackage('language-javascript') + + afterEach: -> + registration?.dispose?() + atom.packages.deactivatePackages() + atom.packages.unloadPackages() + + it 'triggers the grammar-used hook', -> + waitsFor -> + called is true + + runs -> + expect(called).toBe true + + describe 'when changing the grammar of an open file', -> + [coffeeRegistration, coffeeCalled] = [] + + beforeEach -> + coffeeCalled = false + coffeeRegistration = atom.packages.onDidTriggerActivationHook('language-coffee-script:grammar-used', -> coffeeCalled = true) + + waitsForPromise -> + atom.packages.activatePackage('language-coffee-script') + + afterEach -> + coffeeRegistration?.dispose() + + it 'triggers the grammar-used hook', -> + waitsFor -> + called is true + + runs -> + expect(called).toBe true + expect(coffeeCalled).toBe false + editor.setGrammar(atom.grammars.selectGrammar('.coffee')) + + waitsFor -> + coffeeCalled is true + + runs -> + expect(coffeeCalled).toBe true diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 20a285db5..4a25ba33a 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -30,6 +30,7 @@ module.exports = class PackageManager constructor: ({configDirPath, @devMode, safeMode, @resourcePath}) -> @emitter = new Emitter + @hooks = new Emitter @packageDirPaths = [] unless safeMode if @devMode @@ -409,6 +410,14 @@ class PackageManager else Q.reject(new Error("Failed to load package '#{name}'")) + triggerActivationHook: (hook) -> + return new Error("Cannot trigger an empty activation hook") unless hook? and _.isString(hook) and hook.length > 0 + @hooks.emit(hook) + + onDidTriggerActivationHook: (hook, callback) -> + return unless hook? and _.isString(hook) and hook.length > 0 + @hooks.on(hook, callback) + # Deactivate all packages deactivatePackages: -> atom.config.transact => diff --git a/src/package.coffee b/src/package.coffee index 3de79ff51..a287a2a98 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -67,6 +67,7 @@ class Package mainModulePath: null resolvedMainModulePath: false mainModule: null + mainActivated: false ### Section: Construction @@ -122,7 +123,7 @@ class Package @loadMenus() @loadStylesheets() @settingsPromise = @loadSettings() - @requireMainModule() unless @hasActivationCommands() + @requireMainModule() unless @mainModule? or @activationShouldBeDeferred() catch error @handleError("Failed to load the #{@name} package", error) this @@ -133,6 +134,7 @@ class Package @menus = [] @grammars = [] @settings = [] + @mainActivated = false activate: -> @grammarsPromise ?= @loadGrammars() @@ -142,8 +144,8 @@ class Package @measure 'activateTime', => try @activateResources() - if @hasActivationCommands() - @subscribeToActivationCommands() + if @activationShouldBeDeferred() + @subscribeToDeferredActivation() else @activateNow() catch error @@ -155,7 +157,7 @@ class Package try @activateConfig() @activateStylesheets() - if @requireMainModule() + if @mainModule? and not @mainActivated @mainModule.activate?(atom.packages.getPackageState(@name) ? {}) @mainActivated = true @activateServices() @@ -167,7 +169,7 @@ class Package activateConfig: -> return if @configActivated - @requireMainModule() + @requireMainModule() unless @mainModule? if @mainModule? if @mainModule.config? and typeof @mainModule.config is 'object' atom.config.setSchema @name, {type: 'object', properties: @mainModule.config} @@ -382,6 +384,7 @@ class Package if @mainActivated try @mainModule?.deactivate?() + @mainActivated = false catch e console.error "Error deactivating package '#{@name}'", e.stack @emit 'deactivated' if includeDeprecatedAPIs @@ -443,11 +446,21 @@ class Package path.join(@path, 'index') @mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...]) + activationShouldBeDeferred: -> + @hasActivationCommands() or @hasActivationHooks() + + hasActivationHooks: -> + @getActivationHooks()?.length > 0 + hasActivationCommands: -> for selector, commands of @getActivationCommands() return true if commands.length > 0 false + subscribeToDeferredActivation: -> + @subscribeToActivationCommands() + @subscribeToActivationHooks() + subscribeToActivationCommands: -> @activationCommandSubscriptions = new CompositeDisposable for selector, commands of @getActivationCommands() @@ -516,6 +529,27 @@ class Package @activationCommands + subscribeToActivationHooks: -> + @activationHookSubscriptions = new CompositeDisposable + for hook in @getActivationHooks() + do (hook) => + @activationHookSubscriptions.add(atom.packages.onDidTriggerActivationHook(hook, => @activateNow())) if hook? and _.isString(hook) and hook.trim().length > 0 + + return + + getActivationHooks: -> + return @activationHooks if @metadata? and @activationHooks? + + @activationHooks = [] + + if @metadata.activationHooks? + if _.isArray(@metadata.activationHooks) + @activationHooks.push(@metadata.activationHooks...) + else if _.isString(@metadata.activationHooks) + @activationHooks.push(@metadata.activationHooks) + + @activationHooks = _.uniq(@activationHooks) + # Does the given module path contain native code? isNativeModule: (modulePath) -> try diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 387e002d1..85bcfd145 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -154,8 +154,7 @@ class TextEditorElement extends HTMLElement @component.focused() if event.relatedTarget is this addGrammarScopeAttribute: -> - grammarScope = @model.getGrammar()?.scopeName?.replace(/\./g, ' ') - @dataset.grammar = grammarScope + @dataset.grammar = @model.getGrammar()?.scopeName?.replace(/\./g, ' ') addMiniAttribute: -> @setAttributeNode(document.createAttribute("mini")) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 2f7b4de39..5677742b6 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2861,7 +2861,6 @@ class TextEditor extends Model handleGrammarChange: -> @unfoldAll() - @emit 'grammar-changed' if includeDeprecatedAPIs @emitter.emit 'did-change-grammar', @getGrammar() handleMarkerCreated: (marker) => diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 37922002e..8ca7d6d96 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -71,7 +71,7 @@ class TokenizedBuffer extends Model @setGrammar(grammar, newScore) if newScore > @currentGrammarScore setGrammar: (grammar, score) -> - return if grammar is @grammar + return unless grammar? and grammar isnt @grammar @grammar = grammar @rootScopeDescriptor = new ScopeDescriptor(scopes: [@grammar.scopeName]) @@ -102,8 +102,7 @@ class TokenizedBuffer extends Model @disposables.add(@configSubscriptions) @retokenizeLines() - - @emit 'grammar-changed', grammar if Grim.includeDeprecatedAPIs + atom.packages.triggerActivationHook("#{grammar.packageName}:grammar-used") @emitter.emit 'did-change-grammar', grammar getGrammarSelectionContent: -> From f954aa2732521ce88893630ceff7d53ffc138e7f Mon Sep 17 00:00:00 2001 From: Joe Fitzgerald Date: Thu, 9 Jul 2015 02:38:06 -0400 Subject: [PATCH 2/2] Rename @hooks >> @ activationHookEmitter --- src/package-manager.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 4a25ba33a..da349b6b3 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -30,7 +30,7 @@ module.exports = class PackageManager constructor: ({configDirPath, @devMode, safeMode, @resourcePath}) -> @emitter = new Emitter - @hooks = new Emitter + @activationHookEmitter = new Emitter @packageDirPaths = [] unless safeMode if @devMode @@ -412,11 +412,11 @@ class PackageManager triggerActivationHook: (hook) -> return new Error("Cannot trigger an empty activation hook") unless hook? and _.isString(hook) and hook.length > 0 - @hooks.emit(hook) + @activationHookEmitter.emit(hook) onDidTriggerActivationHook: (hook, callback) -> return unless hook? and _.isString(hook) and hook.length > 0 - @hooks.on(hook, callback) + @activationHookEmitter.on(hook, callback) # Deactivate all packages deactivatePackages: ->