mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
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:
@@ -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", ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -61,6 +61,9 @@ _.extend atom,
|
||||
|
||||
new LoadTextMatePackagesTask(textMatePackages).start() if textMatePackages.length > 0
|
||||
|
||||
activatePackages: ->
|
||||
pack.activate() for pack in @loadedPackages
|
||||
|
||||
getLoadedPackages: ->
|
||||
_.clone(@loadedPackages)
|
||||
|
||||
|
||||
@@ -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: ->
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
'main': 'lib/markdown-preview'
|
||||
'activationEvents':
|
||||
'markdown-preview:show': '.editor'
|
||||
'deferredDeserializers': ['MarkdownPreviewView']
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user