Merge pull request #7638 from atom-community/jf-dispatch-grammar-used

Add activationHooks + Trigger `{grammar-package-name}:grammar-used` Hook When A text-editor-element's Grammar Is Set
This commit is contained in:
Nathan Sobo
2015-07-09 15:42:31 -05:00
11 changed files with 163 additions and 15 deletions

View File

@@ -0,0 +1,5 @@
module.exports =
activateCallCount: 0
activate: ->
@activateCallCount++

View File

@@ -0,0 +1,5 @@
{
"name": "package-with-activation-hooks",
"version": "0.1.0",
"activationHooks": ['language-fictitious:grammar-used']
}

View File

@@ -0,0 +1 @@
module.exports = activate: ->

View File

@@ -0,0 +1,5 @@
{
"name": "package-with-empty-activation-hooks",
"version": "0.1.0",
"activationHooks": []
}

View File

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

View File

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

View File

@@ -30,6 +30,7 @@ module.exports =
class PackageManager
constructor: ({configDirPath, @devMode, safeMode, @resourcePath}) ->
@emitter = new Emitter
@activationHookEmitter = 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
@activationHookEmitter.emit(hook)
onDidTriggerActivationHook: (hook, callback) ->
return unless hook? and _.isString(hook) and hook.length > 0
@activationHookEmitter.on(hook, callback)
# Deactivate all packages
deactivatePackages: ->
atom.config.transact =>

View File

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

View File

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

View File

@@ -2861,7 +2861,6 @@ class TextEditor extends Model
handleGrammarChange: ->
@unfoldAll()
@emit 'grammar-changed' if includeDeprecatedAPIs
@emitter.emit 'did-change-grammar', @getGrammar()
handleMarkerCreated: (marker) =>

View File

@@ -72,7 +72,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])
@@ -103,8 +103,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: ->