mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Merge pull request #3611 from atom/ns-activation-commands
Activation commands
This commit is contained in:
@@ -46,13 +46,15 @@ class CommandRegistry
|
||||
constructor: (@rootNode) ->
|
||||
@listenersByCommandName = {}
|
||||
|
||||
getRootNode: -> @rootNode
|
||||
|
||||
setRootNode: (newRootNode) ->
|
||||
oldRootNode = @rootNode
|
||||
@rootNode = newRootNode
|
||||
|
||||
for commandName of @listenersByCommandName
|
||||
oldRootNode?.removeEventListener(commandName, @dispatchCommand, true)
|
||||
newRootNode?.addEventListener(commandName, @dispatchCommand, true)
|
||||
@removeCommandListener(oldRootNode, commandName)
|
||||
@addCommandListener(newRootNode, commandName)
|
||||
|
||||
# Public: Add one or more command listeners associated with a selector.
|
||||
#
|
||||
@@ -88,7 +90,7 @@ class CommandRegistry
|
||||
return disposable
|
||||
|
||||
unless @listenersByCommandName[commandName]?
|
||||
@rootNode?.addEventListener(commandName, @dispatchCommand, true)
|
||||
@addCommandListener(@rootNode, commandName)
|
||||
@listenersByCommandName[commandName] = []
|
||||
|
||||
listener = new CommandListener(selector, callback)
|
||||
@@ -99,35 +101,7 @@ class CommandRegistry
|
||||
listenersForCommand.splice(listenersForCommand.indexOf(listener), 1)
|
||||
if listenersForCommand.length is 0
|
||||
delete @listenersByCommandName[commandName]
|
||||
@rootNode.removeEventListener(commandName, @dispatchCommand, true)
|
||||
|
||||
dispatchCommand: (event) =>
|
||||
propagationStopped = false
|
||||
immediatePropagationStopped = false
|
||||
currentTarget = event.target
|
||||
|
||||
syntheticEvent = Object.create event,
|
||||
eventPhase: value: Event.BUBBLING_PHASE
|
||||
currentTarget: get: -> currentTarget
|
||||
stopPropagation: value: ->
|
||||
propagationStopped = true
|
||||
stopImmediatePropagation: value: ->
|
||||
propagationStopped = true
|
||||
immediatePropagationStopped = true
|
||||
|
||||
loop
|
||||
matchingListeners =
|
||||
@listenersByCommandName[event.type]
|
||||
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
|
||||
.sort (a, b) -> a.compare(b)
|
||||
|
||||
for listener in matchingListeners
|
||||
break if immediatePropagationStopped
|
||||
listener.callback.call(currentTarget, syntheticEvent)
|
||||
|
||||
break if propagationStopped
|
||||
break if currentTarget is @rootNode
|
||||
currentTarget = currentTarget.parentNode
|
||||
@removeCommandListener(@rootNode, commandName)
|
||||
|
||||
# Public: Find all registered commands matching a query.
|
||||
#
|
||||
@@ -163,10 +137,86 @@ class CommandRegistry
|
||||
|
||||
commands
|
||||
|
||||
clear: ->
|
||||
@listenersByCommandName = {}
|
||||
# Public: Simulate the dispatch of a command on a DOM node.
|
||||
#
|
||||
# This can be useful for testing when you want to simulate the invocation of a
|
||||
# command on a detached DOM node. Otherwise, the DOM node in question needs to
|
||||
# be attached to the document so the event bubbles up to the root node to be
|
||||
# processed.
|
||||
#
|
||||
# * `target` The DOM node at which to start bubbling the command event.
|
||||
# * `commandName` {String} indicating the name of the command to dispatch.
|
||||
dispatch: (target, commandName) ->
|
||||
event = new CustomEvent(commandName, bubbles: true)
|
||||
eventWithTarget = Object.create(event, target: value: target)
|
||||
@handleCommandEvent(eventWithTarget)
|
||||
|
||||
getSnapshot: ->
|
||||
_.deepClone(@listenersByCommandName)
|
||||
|
||||
restoreSnapshot: (snapshot) ->
|
||||
rootNode = @getRootNode()
|
||||
@setRootNode(null) # clear listeners for current commands
|
||||
@listenersByCommandName = _.deepClone(snapshot)
|
||||
@setRootNode(rootNode) # restore listeners for commands in snapshot
|
||||
|
||||
handleCommandEvent: (originalEvent) =>
|
||||
originalEvent.__handledByCommandRegistry = true
|
||||
|
||||
propagationStopped = false
|
||||
immediatePropagationStopped = false
|
||||
matched = false
|
||||
currentTarget = originalEvent.target
|
||||
invokedListeners = []
|
||||
|
||||
syntheticEvent = Object.create originalEvent,
|
||||
eventPhase: value: Event.BUBBLING_PHASE
|
||||
currentTarget: get: -> currentTarget
|
||||
stopPropagation: value: ->
|
||||
originalEvent.stopPropagation()
|
||||
propagationStopped = true
|
||||
stopImmediatePropagation: value: ->
|
||||
originalEvent.stopImmediatePropagation()
|
||||
propagationStopped = true
|
||||
immediatePropagationStopped = true
|
||||
disableInvokedListeners: value: ->
|
||||
listener.enabled = false for listener in invokedListeners
|
||||
-> listener.enabled = true for listener in invokedListeners
|
||||
|
||||
loop
|
||||
matchingListeners =
|
||||
(@listenersByCommandName[originalEvent.type] ? [])
|
||||
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
|
||||
.sort (a, b) -> a.compare(b)
|
||||
|
||||
matched = true if matchingListeners.length > 0
|
||||
|
||||
for listener in matchingListeners when listener.enabled
|
||||
break if immediatePropagationStopped
|
||||
invokedListeners.push(listener)
|
||||
listener.callback.call(currentTarget, syntheticEvent)
|
||||
|
||||
break unless currentTarget?
|
||||
break if currentTarget is @rootNode
|
||||
break if propagationStopped
|
||||
currentTarget = currentTarget.parentNode
|
||||
|
||||
matched
|
||||
|
||||
handleJQueryCommandEvent: (event) =>
|
||||
@handleCommandEvent(event) unless event.originalEvent?.__handledByCommandRegistry
|
||||
|
||||
addCommandListener: (node, commandName, listener) ->
|
||||
node?.addEventListener(commandName, @handleCommandEvent, true)
|
||||
$(node).on commandName, @handleJQueryCommandEvent
|
||||
|
||||
removeCommandListener: (node, commandName) ->
|
||||
node?.removeEventListener(commandName, @handleCommandEvent, true)
|
||||
$(node).off commandName, @handleJQueryCommandEvent
|
||||
|
||||
class CommandListener
|
||||
enabled: true
|
||||
|
||||
constructor: (@selector, @callback) ->
|
||||
@specificity = (SpecificityCache[@selector] ?= specificity(@selector))
|
||||
@sequenceNumber = SequenceCount++
|
||||
|
||||
@@ -5,7 +5,7 @@ async = require 'async'
|
||||
CSON = require 'season'
|
||||
fs = require 'fs-plus'
|
||||
EmitterMixin = require('emissary').Emitter
|
||||
{Emitter} = require 'event-kit'
|
||||
{Emitter, CompositeDisposable} = require 'event-kit'
|
||||
Q = require 'q'
|
||||
{deprecate} = require 'grim'
|
||||
|
||||
@@ -99,7 +99,7 @@ class Package
|
||||
@loadMenus()
|
||||
@loadStylesheets()
|
||||
@scopedPropertiesPromise = @loadScopedProperties()
|
||||
@requireMainModule() unless @hasActivationEvents()
|
||||
@requireMainModule() unless @hasActivationCommands()
|
||||
|
||||
catch error
|
||||
console.warn "Failed to load package named '#{@name}'", error.stack ? error
|
||||
@@ -119,8 +119,8 @@ class Package
|
||||
@activationDeferred = Q.defer()
|
||||
@measure 'activateTime', =>
|
||||
@activateResources()
|
||||
if @hasActivationEvents()
|
||||
@subscribeToActivationEvents()
|
||||
if @hasActivationCommands()
|
||||
@subscribeToActivationCommands()
|
||||
else
|
||||
@activateNow()
|
||||
|
||||
@@ -270,7 +270,7 @@ class Package
|
||||
deactivate: ->
|
||||
@activationDeferred?.reject()
|
||||
@activationDeferred = null
|
||||
@unsubscribeFromActivationEvents()
|
||||
@activationCommandSubscriptions?.dispose()
|
||||
@deactivateResources()
|
||||
@deactivateConfig()
|
||||
if @mainActivated
|
||||
@@ -319,64 +319,56 @@ class Package
|
||||
path.join(@path, 'index')
|
||||
@mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...])
|
||||
|
||||
hasActivationEvents: ->
|
||||
if _.isArray(@metadata.activationEvents)
|
||||
return @metadata.activationEvents.some (activationEvent) ->
|
||||
activationEvent?.length > 0
|
||||
else if _.isString(@metadata.activationEvents)
|
||||
return @metadata.activationEvents.length > 0
|
||||
else if _.isObject(@metadata.activationEvents)
|
||||
for event, selector of @metadata.activationEvents
|
||||
return true if event.length > 0 and selector.length > 0
|
||||
|
||||
hasActivationCommands: ->
|
||||
for selector, commands of @getActivationCommands()
|
||||
return true if commands.length > 0
|
||||
false
|
||||
|
||||
subscribeToActivationEvents: ->
|
||||
return unless @metadata.activationEvents?
|
||||
if _.isArray(@metadata.activationEvents)
|
||||
atom.workspaceView.command(event, @handleActivationEvent) for event in @metadata.activationEvents
|
||||
else if _.isString(@metadata.activationEvents)
|
||||
atom.workspaceView.command(@metadata.activationEvents, @handleActivationEvent)
|
||||
else
|
||||
atom.workspaceView.command(event, selector, @handleActivationEvent) for event, selector of @metadata.activationEvents
|
||||
subscribeToActivationCommands: ->
|
||||
@activationCommandSubscriptions = new CompositeDisposable
|
||||
for selector, commands of @getActivationCommands()
|
||||
for command in commands
|
||||
@activationCommandSubscriptions.add(
|
||||
atom.commands.add(selector, command, @handleActivationCommand)
|
||||
)
|
||||
|
||||
handleActivationEvent: (event) =>
|
||||
bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event)
|
||||
getActivationCommands: ->
|
||||
return @activationCommands if @activationCommands?
|
||||
|
||||
@activationCommands = {}
|
||||
|
||||
if @metadata.activationCommands?
|
||||
for selector, commands of @metadata.activationCommands
|
||||
@activationCommands[selector] ?= []
|
||||
if _.isString(commands)
|
||||
@activationCommands[selector].push(commands)
|
||||
else if _.isArray(commands)
|
||||
@activationCommands[selector].push(commands...)
|
||||
|
||||
if @metadata.activationEvents?
|
||||
if _.isArray(@metadata.activationEvents)
|
||||
for eventName in @metadata.activationEvents
|
||||
@activationCommands['.workspace'] ?= []
|
||||
@activationCommands['.workspace'].push(eventName)
|
||||
else if _.isString(@metadata.activationEvents)
|
||||
eventName = @metadata.activationEvents
|
||||
@activationCommands['.workspace'] ?= []
|
||||
@activationCommands['.workspace'].push(eventName)
|
||||
else
|
||||
for eventName, selector of @metadata.activationEvents
|
||||
selector ?= '.workspace'
|
||||
@activationCommands[selector] ?= []
|
||||
@activationCommands[selector].push(eventName)
|
||||
|
||||
@activationCommands
|
||||
|
||||
handleActivationCommand: (event) =>
|
||||
event.stopImmediatePropagation()
|
||||
@activationCommandSubscriptions.dispose()
|
||||
reenableInvokedListeners = event.disableInvokedListeners()
|
||||
@activateNow()
|
||||
$ ?= require('./space-pen-extensions').$
|
||||
$(event.target).trigger(event)
|
||||
@restoreEventHandlersOnBubblePath(bubblePathEventHandlers)
|
||||
@unsubscribeFromActivationEvents()
|
||||
false
|
||||
|
||||
unsubscribeFromActivationEvents: ->
|
||||
return unless atom.workspaceView?
|
||||
|
||||
if _.isArray(@metadata.activationEvents)
|
||||
atom.workspaceView.off(event, @handleActivationEvent) for event in @metadata.activationEvents
|
||||
else if _.isString(@metadata.activationEvents)
|
||||
atom.workspaceView.off(@metadata.activationEvents, @handleActivationEvent)
|
||||
else
|
||||
atom.workspaceView.off(event, selector, @handleActivationEvent) for event, selector of @metadata.activationEvents
|
||||
|
||||
disableEventHandlersOnBubblePath: (event) ->
|
||||
bubblePathEventHandlers = []
|
||||
disabledHandler = ->
|
||||
$ ?= require('./space-pen-extensions').$
|
||||
element = $(event.target)
|
||||
while element.length
|
||||
if eventHandlers = element.handlers()?[event.type]
|
||||
for eventHandler in eventHandlers
|
||||
eventHandler.disabledHandler = eventHandler.handler
|
||||
eventHandler.handler = disabledHandler
|
||||
bubblePathEventHandlers.push(eventHandler)
|
||||
element = element.parent()
|
||||
bubblePathEventHandlers
|
||||
|
||||
restoreEventHandlersOnBubblePath: (eventHandlers) ->
|
||||
for eventHandler in eventHandlers
|
||||
eventHandler.handler = eventHandler.disabledHandler
|
||||
delete eventHandler.disabledHandler
|
||||
event.target.dispatchEvent(new CustomEvent(event.type, bubbles: true))
|
||||
reenableInvokedListeners()
|
||||
|
||||
# Does the given module path contain native code?
|
||||
isNativeModule: (modulePath) ->
|
||||
|
||||
Reference in New Issue
Block a user