Require a deferred package early if needed when deserializing panes

The requiring of a package's main module is now decoupled from package
activation. Non-deferred packages will always be required before the
panes are deserialized. This allows the package to register any
deserializers for objects displayed in the panes.

Deferred packages can contain a 'deferredDeserializers' array in their
package.cson. If we attempt to deserialize an object with a deserializer
in the list, the package's main module will be required first so it has
a chance to register the deserializer. But the package still won't be
activated until an activation event occurs.

We may want to add an additional optional hook called 'load' which is
called at require time. We would not guarantee that the rootView
global would exist, but we could give the package a chance to register
deserializers etc. For now, registering deserializers is a side-effect
of requiring the package.
This commit is contained in:
Nathan Sobo
2013-03-11 15:08:19 -06:00
parent a392174164
commit 10d0fdf2d7
10 changed files with 97 additions and 60 deletions

View File

@@ -3,7 +3,7 @@ AtomPackage = require 'atom-package'
fs = require 'fs'
describe "AtomPackage", ->
describe ".load()", ->
describe ".activate()", ->
beforeEach ->
window.rootView = new RootView
@@ -15,6 +15,7 @@ describe "AtomPackage", ->
packageMainModule = require 'fixtures/packages/package-with-activation-events/main'
spyOn(packageMainModule, 'activate').andCallThrough()
pack.load()
pack.activate()
it "defers activating the package until an activation event bubbles to the root view", ->
expect(packageMainModule.activate).not.toHaveBeenCalled()
@@ -44,6 +45,7 @@ describe "AtomPackage", ->
expect(packageMainModule.activate).not.toHaveBeenCalled()
pack.load()
pack.activate()
expect(packageMainModule.activate).toHaveBeenCalled()
describe "when the package doesn't have an index.coffee", ->

View File

@@ -103,7 +103,7 @@ describe "the `atom` global", ->
expect(atom.activatedAtomPackages.length).toBe 0
describe "serialization", ->
it "uses previous serialization state on unactivated packages", ->
it "uses previous serialization state on packages whose activation has been deferred", ->
atom.atomPackageStates['package-with-activation-events'] = {previousData: 'exists'}
unactivatedPackage = window.loadPackage('package-with-activation-events')
activatedPackage = window.loadPackage('package-with-module')
@@ -115,7 +115,8 @@ describe "the `atom` global", ->
'previousData': 'exists'
# ensure serialization occurs when the packageis activated
unactivatedPackage.activatePackageMain()
unactivatedPackage.deferActivation = false
unactivatedPackage.activate()
expect(atom.serializeAtomPackages()).toEqual
'package-with-module':
'someNumber': 1
@@ -124,8 +125,8 @@ describe "the `atom` global", ->
it "absorbs exceptions that are thrown by the package module's serialize methods", ->
spyOn(console, 'error')
window.loadPackage('package-with-module')
window.loadPackage('package-with-serialize-error', activateImmediately: true)
window.loadPackage('package-with-module', activateImmediately: true)
window.loadPackage('package-with-serialize-error', activateImmediately: true)
packageStates = atom.serializeAtomPackages()
expect(packageStates['package-with-module']).toEqual someNumber: 1

View File

@@ -88,12 +88,14 @@ afterEach ->
atom.presentingModal = false
waits(0) # yield to ui thread to make screen update more frequently
window.loadPackage = (name, options) ->
window.loadPackage = (name, options={}) ->
Package = require 'package'
packagePath = _.find atom.getPackagePaths(), (packagePath) -> fs.base(packagePath) == name
if pack = Package.build(packagePath)
pack.load(options)
atom.loadedPackages.push(pack)
pack.deferActivation = false if options.activateImmediately
pack.activate()
pack
# Specs rely on TextMate bundles (but not atom packages)

View File

@@ -7,66 +7,21 @@ module.exports =
class AtomPackage extends Package
metadata: null
packageMain: null
deferActivation: false
load: ({activateImmediately}={}) ->
load: ->
try
@loadMetadata()
@loadKeymaps()
@loadStylesheets()
if @metadata.activationEvents and not activateImmediately
@subscribeToActivationEvents()
if @deferActivation = @metadata.activationEvents?
@registerDeferredDeserializers()
else
@activatePackageMain()
@requirePackageMain()
catch e
console.warn "Failed to load package named '#{@name}'", e.stack
this
disableEventHandlersOnBubblePath: (event) ->
bubblePathEventHandlers = []
disabledHandler = ->
element = $(event.target)
while element.length
if eventHandlers = element.data('events')?[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
unsubscribeFromActivationEvents: (activateHandler) ->
if _.isArray(@metadata.activationEvents)
rootView.off(event, activateHandler) for event in @metadata.activationEvents
else
rootView.off(event, selector, activateHandler) for event, selector of @metadata.activationEvents
subscribeToActivationEvents: () ->
activateHandler = (event) =>
bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event)
@activatePackageMain()
$(event.target).trigger(event)
@restoreEventHandlersOnBubblePath(bubblePathEventHandlers)
@unsubscribeFromActivationEvents(activateHandler)
if _.isArray(@metadata.activationEvents)
rootView.command(event, activateHandler) for event in @metadata.activationEvents
else
rootView.command(event, selector, activateHandler) for event, selector of @metadata.activationEvents
activatePackageMain: ->
mainPath = @path
mainPath = fs.join(mainPath, @metadata.main) if @metadata.main
mainPath = require.resolve(mainPath)
if fs.isFile(mainPath)
@packageMain = require(mainPath)
config.setDefaults(@name, @packageMain.configDefaults)
atom.activateAtomPackage(this)
loadMetadata: ->
if metadataPath = fs.resolveExtension(fs.join(@path, 'package'), ['cson', 'json'])
@metadata = fs.readObject(metadataPath)
@@ -86,3 +41,65 @@ class AtomPackage extends Package
stylesheetDirPath = fs.join(@path, 'stylesheets')
for stylesheetPath in fs.list(stylesheetDirPath)
requireStylesheet(stylesheetPath)
activate: ->
if @deferActivation
@subscribeToActivationEvents()
else
try
if @requirePackageMain()
config.setDefaults(@name, @packageMain.configDefaults)
atom.activateAtomPackage(this)
catch e
console.warn "Failed to activate package named '#{@name}'", e.stack
requirePackageMain: ->
return @packageMain if @packageMain
mainPath = @path
mainPath = fs.join(mainPath, @metadata.main) if @metadata.main
mainPath = require.resolve(mainPath)
@packageMain = require(mainPath) if fs.isFile(mainPath)
registerDeferredDeserializers: ->
for deserializerName in @metadata.deferredDeserializers ? []
registerDeferredDeserializer deserializerName, => @requirePackageMain()
subscribeToActivationEvents: () ->
return unless @metadata.activationEvents?
activateHandler = (event) =>
bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event)
@deferActivation = false
@activate()
$(event.target).trigger(event)
@restoreEventHandlersOnBubblePath(bubblePathEventHandlers)
@unsubscribeFromActivationEvents(activateHandler)
if _.isArray(@metadata.activationEvents)
rootView.command(event, activateHandler) for event in @metadata.activationEvents
else
rootView.command(event, selector, activateHandler) for event, selector of @metadata.activationEvents
unsubscribeFromActivationEvents: (activateHandler) ->
if _.isArray(@metadata.activationEvents)
rootView.off(event, activateHandler) for event in @metadata.activationEvents
else
rootView.off(event, selector, activateHandler) for event, selector of @metadata.activationEvents
disableEventHandlersOnBubblePath: (event) ->
bubblePathEventHandlers = []
disabledHandler = ->
element = $(event.target)
while element.length
if eventHandlers = element.data('events')?[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

View File

@@ -61,6 +61,9 @@ _.extend atom,
new LoadTextMatePackagesTask(textMatePackages).start() if textMatePackages.length > 0
activatePackages: ->
pack.activate() for pack in @loadedPackages
getLoadedPackages: ->
_.clone(@loadedPackages)

View File

@@ -31,6 +31,8 @@ class TextMatePackage extends Package
console.warn "Failed to load package at '#{@path}'", e.stack
this
activate: -> # no-op
getGrammars: -> @grammars
readGrammars: ->

View File

@@ -6,6 +6,7 @@ require 'underscore-extensions'
require 'space-pen-extensions'
deserializers = {}
deferredDeserializers = {}
# This method is called in any window needing a general environment, including specs
window.setUpEnvironment = ->
@@ -52,10 +53,11 @@ window.startup = ->
handleWindowEvents()
config.load()
atom.loadTextPackage()
buildProjectAndRootView()
keymap.loadBundledKeymaps()
atom.loadThemes()
atom.loadPackages()
buildProjectAndRootView()
atom.activatePackages()
keymap.loadUserKeymaps()
atom.requireUserInitScript()
$(window).on 'beforeunload', -> shutdown(); false
@@ -151,6 +153,9 @@ window.registerDeserializers = (args...) ->
window.registerDeserializer = (klass) ->
deserializers[klass.name] = klass
window.registerDeferredDeserializer = (name, fn) ->
deferredDeserializers[name] = fn
window.unregisterDeserializer = (klass) ->
delete deserializers[klass.name]
@@ -160,7 +165,11 @@ window.deserialize = (state) ->
deserializer.deserialize(state)
window.getDeserializer = (state) ->
deserializers[state?.deserializer]
name = state?.deserializer
if deferredDeserializers[name]
deferredDeserializers[name]()
delete deferredDeserializers[name]
deserializers[name]
window.measure = (description, fn) ->
start = new Date().getTime()

View File

@@ -1,3 +1,4 @@
'main': 'lib/markdown-preview'
'activationEvents':
'markdown-preview:show': '.editor'
'deferredDeserializers': ['MarkdownPreviewView']

View File

@@ -6,7 +6,7 @@ describe "MarkdownPreview package", ->
beforeEach ->
project.setPath(project.resolve('markdown'))
window.rootView = new RootView
window.loadPackage("markdown-preview")
window.loadPackage("markdown-preview", activateImmediately: true)
spyOn(MarkdownPreviewView.prototype, 'fetchRenderedMarkdown')
describe "markdown-preview:show", ->

View File

@@ -7,7 +7,7 @@ describe "Spell check", ->
window.rootView = new RootView
rootView.open('sample.js')
config.set('spell-check.grammars', [])
window.loadPackage('spell-check')
window.loadPackage('spell-check', activateImmediately: true)
rootView.attachToDom()
editor = rootView.getActiveView()