Merge branch 'master' into api-docs

This commit is contained in:
Matt Colyer
2013-10-22 16:19:18 -07:00
20 changed files with 549 additions and 221 deletions

View File

@@ -25,20 +25,18 @@ class AtomPackage extends Package
resolvedMainModulePath: false
mainModule: null
constructor: (path, {@metadata}) ->
super(path)
@reset()
getType: -> 'atom'
load: ->
@metadata = {}
@stylesheets = []
@keymaps = []
@menus = []
@grammars = []
@scopedProperties = []
getStylesheetType: -> 'bundled'
load: ->
@measure 'loadTime', =>
try
@metadata = Package.loadMetadata(@path)
return if @isTheme()
@metadata ?= Package.loadMetadata(@path)
@loadKeymaps()
@loadMenus()
@@ -55,9 +53,21 @@ class AtomPackage extends Package
console.warn "Failed to load package named '#{@name}'", e.stack ? e
this
enable: ->
atom.config.removeAtKeyPath('core.disabledPackages', @metadata.name)
disable: ->
atom.config.pushAtKeyPath('core.disabledPackages', @metadata.name)
reset: ->
@stylesheets = []
@keymaps = []
@menus = []
@grammars = []
@scopedProperties = []
activate: ({immediate}={}) ->
@measure 'activateTime', =>
@loadStylesheets() if @isTheme()
@activateResources()
if @metadata.activationEvents? and not immediate
@subscribeToActivationEvents()
@@ -69,7 +79,7 @@ class AtomPackage extends Package
@activateConfig()
@activateStylesheets()
if @requireMainModule()
@mainModule.activate(atom.getPackageState(@name) ? {})
@mainModule.activate(atom.packages.getPackageState(@name) ? {})
@mainActivated = true
catch e
console.warn "Failed to activate package named '#{@name}'", e.stack
@@ -86,7 +96,7 @@ class AtomPackage extends Package
activateStylesheets: ->
return if @stylesheetsActivated
type = if @metadata.theme then 'theme' else 'bundled'
type = @getStylesheetType()
for [stylesheetPath, content] in @stylesheets
atom.themes.applyStylesheet(stylesheetPath, content, type)
@stylesheetsActivated = true
@@ -183,8 +193,7 @@ class AtomPackage extends Package
@reloadStylesheet(stylesheetPath, content) for [stylesheetPath, content] in @stylesheets
reloadStylesheet: (stylesheetPath, content) ->
type = if @metadata.theme then 'theme' else 'bundled'
atom.themes.applyStylesheet(stylesheetPath, content, type)
atom.themes.applyStylesheet(stylesheetPath, content, @getStylesheetType())
requireMainModule: ->
return @mainModule if @mainModule?

View File

@@ -55,7 +55,7 @@ class Atom
@__defineSetter__ 'packageStates', (packageStates) => @packages.packageStates = packageStates
@subscribe @packages, 'loaded', => @watchThemes()
@themes = new ThemeManager()
@themes = new ThemeManager(@packages)
@contextMenu = new ContextMenuManager(devMode)
@menu = new MenuManager()
@pasteboard = new Pasteboard()
@@ -220,6 +220,9 @@ class Atom
inDevMode: ->
@getLoadSettings().devMode
inSpecMode: ->
@getLoadSettings().isSpec
toggleFullScreen: ->
@setFullScreen(!@isFullScreen())

View File

@@ -193,7 +193,7 @@ class Config
# `callback` is fired whenever the value of the key is changed and will
# be fired immediately unless the `callNow` option is `false`.
#
# keyPath - The {String} name of the key to watch
# keyPath - The {String} name of the key to observe
# options - An optional {Object} containing the `callNow` key.
# callback - The {Function} that fires when the. It is given a single argument, `value`,
# which is the new value of `keyPath`.
@@ -207,14 +207,22 @@ class Config
updateCallback = =>
value = @get(keyPath)
unless _.isEqual(value, previousValue)
previous = previousValue
previousValue = _.clone(value)
callback(value)
callback(value, {previous})
subscription = { cancel: => @off 'updated', updateCallback }
@on 'updated', updateCallback
@on "updated.#{keyPath.replace(/\./, '-')}", updateCallback
callback(value) if options.callNow ? true
subscription
# Public: Unobserve all callbacks on a given key
#
# keyPath - The {String} name of the key to unobserve
unobserve: (keyPath) ->
@off("updated.#{keyPath.replace(/\./, '-')}")
# Private:
update: ->
return if @configFileHasErrors

View File

@@ -30,7 +30,10 @@ class MenuManager
# Public: Refreshes the currently visible menu.
update: ->
@sendToBrowserProcess(@template, atom.keymap.keystrokesByCommandForSelector('body'))
keystrokesByCommand = atom.keymap.keystrokesByCommandForSelector('body')
_.extend(keystrokesByCommand, atom.keymap.keystrokesByCommandForSelector('.editor'))
_.extend(keystrokesByCommand, atom.keymap.keystrokesByCommandForSelector('.editor:not(.mini)'))
@sendToBrowserProcess(@template, keystrokesByCommand)
# Private
loadCoreItems: ->
@@ -49,6 +52,20 @@ class MenuManager
else
menu.push(item) unless _.find(menu, (i) -> i.label == item.label)
# Private: OSX can't handle displaying accelerators for multiple keystrokes.
# If they are sent across, it will stop processing accelerators for the rest
# of the menu items.
filterMultipleKeystrokes: (keystrokesByCommand) ->
filtered = {}
for key, bindings of keystrokesByCommand
for binding in bindings
continue if binding.indexOf(' ') != -1
filtered[key] ?= []
filtered[key].push(binding)
filtered
# Private
sendToBrowserProcess: (template, keystrokesByCommand) ->
keystrokesByCommand = @filterMultipleKeystrokes(keystrokesByCommand)
ipc.sendChannel 'update-application-menu', template, keystrokesByCommand

View File

@@ -4,6 +4,21 @@ _ = require 'underscore-plus'
Package = require './package'
path = require 'path'
###
Packages have a lifecycle
* The paths to all non-disabled packages and themes are found on disk (these are available packages)
* Every package (except those in core.disabledPackages) is 'loaded', meaning
`Package` objects are created, and their metadata loaded. This includes themes,
as themes are packages
* The ThemeManager.activateThemes() is called 'activating' all the themes, meaning
their stylesheets are loaded into the window.
* The PackageManager.activatePackages() function is called 'activating' non-theme
package, meaning its resources -- keymaps, classes, etc. -- are loaded, and
the package's activate() method is called.
* Packages and themes can then be enabled and disabled via the public
.enablePackage(name) and .disablePackage(name) functions.
###
module.exports =
class PackageManager
Emitter.includeInto(this)
@@ -16,6 +31,14 @@ class PackageManager
@loadedPackages = {}
@activePackages = {}
@packageStates = {}
@observingDisabledPackages = false
@packageActivators = []
@registerPackageActivator(this, ['atom', 'textmate'])
# Public: Get the path to the apm command
getApmPath: ->
@apmPath ?= require.resolve('atom-package-manager/bin/apm')
getPackageState: (name) ->
@packageStates[name]
@@ -23,10 +46,37 @@ class PackageManager
setPackageState: (name, state) ->
@packageStates[name] = state
activatePackages: ->
@activatePackage(pack.name) for pack in @getLoadedPackages()
# Public:
enablePackage: (name) ->
pack = @loadPackage(name)
pack?.enable()
pack
# Public:
disablePackage: (name) ->
pack = @loadPackage(name)
pack?.disable()
pack
# Internal-only: Activate all the packages that should be activated.
activate: ->
for [activator, types] in @packageActivators
packages = @getLoadedPackagesForTypes(types)
activator.activatePackages(packages)
# Public: another type of package manager can handle other package types.
# See ThemeManager
registerPackageActivator: (activator, types) ->
@packageActivators.push([activator, types])
# Internal-only:
activatePackages: (packages) ->
@activatePackage(pack.name) for pack in packages
@observeDisabledPackages()
# Internal-only: Activate a single package by name
activatePackage: (name, options) ->
return pack if pack = @getActivePackage(name)
if pack = @loadPackage(name, options)
@activePackages[pack.name] = pack
pack.activate(options)
@@ -34,6 +84,7 @@ class PackageManager
deactivatePackages: ->
@deactivatePackage(pack.name) for pack in @getActivePackages()
@unobserveDisabledPackages()
deactivatePackage: (name) ->
if pack = @getActivePackage(name)
@@ -43,14 +94,32 @@ class PackageManager
else
throw new Error("No active package for name '#{name}'")
getActivePackages: ->
_.values(@activePackages)
getActivePackage: (name) ->
@activePackages[name]
isPackageActive: (name) ->
@getActivePackage(name)?
getActivePackages: ->
_.values(@activePackages)
unobserveDisabledPackages: ->
return unless @observingDisabledPackages
config.unobserve('core.disabledPackages')
@observingDisabledPackages = false
observeDisabledPackages: ->
return if @observingDisabledPackages
config.observe 'core.disabledPackages', callNow: false, (disabledPackages, {previous}) =>
packagesToEnable = _.difference(previous, disabledPackages)
packagesToDisable = _.difference(disabledPackages, previous)
@deactivatePackage(packageName) for packageName in packagesToDisable when @getActivePackage(packageName)
@activatePackage(packageName) for packageName in packagesToEnable
null
@observingDisabledPackages = true
loadPackages: ->
# Ensure atom exports is already in the require cache so the load time
@@ -61,21 +130,19 @@ class PackageManager
@emit 'loaded'
loadPackage: (name, options) ->
if @isPackageDisabled(name)
return console.warn("Tried to load disabled package '#{name}'")
if packagePath = @resolvePackagePath(name)
return pack if pack = @getLoadedPackage(name)
pack = Package.load(packagePath, options)
if pack.metadata?.theme
atom.themes.register(pack)
else
@loadedPackages[pack.name] = pack
@loadedPackages[pack.name] = pack if pack?
pack
else
throw new Error("Could not resolve '#{name}' to a package path")
unloadPackages: ->
@unloadPackage(name) for name in _.keys(@loadedPackages)
null
unloadPackage: (name) ->
if @isPackageActive(name)
throw new Error("Tried to unload active package '#{name}'")
@@ -85,19 +152,6 @@ class PackageManager
else
throw new Error("No loaded package for name '#{name}'")
resolvePackagePath: (name) ->
return name if fsUtils.isDirectorySync(name)
packagePath = fsUtils.resolve(@packageDirPaths..., name)
return packagePath if fsUtils.isDirectorySync(packagePath)
packagePath = path.join(@resourcePath, 'node_modules', name)
return packagePath if @isInternalPackage(packagePath)
isInternalPackage: (packagePath) ->
{engines} = Package.loadMetadata(packagePath, true)
engines?.atom?
getLoadedPackage: (name) ->
@loadedPackages[name]
@@ -107,9 +161,28 @@ class PackageManager
getLoadedPackages: ->
_.values(@loadedPackages)
# Private: Get packages for a certain package type
#
# * types: an {Array} of {String}s like ['atom', 'textmate']
getLoadedPackagesForTypes: (types) ->
pack for pack in @getLoadedPackages() when pack.getType() in types
resolvePackagePath: (name) ->
return name if fsUtils.isDirectorySync(name)
packagePath = fsUtils.resolve(@packageDirPaths..., name)
return packagePath if fsUtils.isDirectorySync(packagePath)
packagePath = path.join(@resourcePath, 'node_modules', name)
return packagePath if @isInternalPackage(packagePath)
isPackageDisabled: (name) ->
_.include(config.get('core.disabledPackages') ? [], name)
isInternalPackage: (packagePath) ->
{engines} = Package.loadMetadata(packagePath, true)
engines?.atom?
getAvailablePackagePaths: ->
packagePaths = []

View File

@@ -7,15 +7,25 @@ class Package
@build: (path) ->
TextMatePackage = require './text-mate-package'
AtomPackage = require './atom-package'
ThemePackage = require './theme-package'
if TextMatePackage.testName(path)
new TextMatePackage(path)
pack = new TextMatePackage(path)
else
new AtomPackage(path)
try
metadata = @loadMetadata(path)
if metadata.theme
pack = new ThemePackage(path, {metadata})
else
pack = new AtomPackage(path, {metadata})
catch e
console.warn "Failed to load package.json '#{basename(path)}'", e.stack ? e
pack
@load: (path, options) ->
pack = @build(path)
pack.load(options)
pack?.load(options)
pack
@loadMetadata: (path, ignoreErrors=false) ->

View File

@@ -8,31 +8,89 @@ _ = require 'underscore-plus'
fsUtils = require './fs-utils'
# Private: Handles discovering and loading available themes.
###
Themes are a subset of packages
###
module.exports =
class ThemeManager
Emitter.includeInto(this)
constructor: ->
@loadedThemes = []
@activeThemes = []
constructor: (@packageManager) ->
@lessCache = null
# Internal-only:
register: (theme) ->
@loadedThemes.push(theme)
theme
@packageManager.registerPackageActivator(this, ['theme'])
# Internal-only:
getAvailableNames: ->
_.map @loadedThemes, (theme) -> theme.metadata.name
# TODO: Maybe should change to list all the available themes out there?
@getLoadedNames()
getLoadedNames: ->
theme.name for theme in @getLoadedThemes()
# Internal-only:
getActiveNames: ->
theme.name for theme in @getActiveThemes()
# Internal-only:
getActiveThemes: ->
_.clone(@activeThemes)
pack for pack in @packageManager.getActivePackages() when pack.isTheme()
# Internal-only:
getLoadedThemes: ->
_.clone(@loadedThemes)
pack for pack in @packageManager.getLoadedPackages() when pack.isTheme()
# Internal-only: adhere to the PackageActivator interface
activatePackages: (themePackages) -> @activateThemes()
# Internal-only:
activateThemes: ->
# atom.config.observe runs the callback once, then on subsequent changes.
atom.config.observe 'core.themes', (themeNames) =>
@deactivateThemes()
themeNames = [themeNames] unless _.isArray(themeNames)
# Reverse so the first (top) theme is loaded after the others. We want
# the first/top theme to override later themes in the stack.
themeNames = _.clone(themeNames).reverse()
@packageManager.activatePackage(themeName) for themeName in themeNames
@loadUserStylesheet()
@reloadBaseStylesheets()
@emit('reloaded')
# Internal-only:
deactivateThemes: ->
@removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
@packageManager.deactivatePackage(pack.name) for pack in @getActiveThemes()
null
# Public:
getImportPaths: ->
activeThemes = @getActiveThemes()
if activeThemes.length > 0
themePaths = (theme.getStylesheetsPath() for theme in activeThemes when theme)
else
themePaths = []
for themeName in atom.config.get('core.themes') ? []
if themePath = @packageManager.resolvePackagePath(themeName)
themePaths.push(path.join(themePath, AtomPackage.stylesheetsDir))
themePath for themePath in themePaths when fsUtils.isDirectorySync(themePath)
# Public:
getUserStylesheetPath: ->
stylesheetPath = fsUtils.resolve(path.join(atom.config.configDirPath, 'user'), ['css', 'less'])
if fsUtils.isFileSync(stylesheetPath)
stylesheetPath
else
null
# Private:
loadUserStylesheet: ->
if userStylesheetPath = @getUserStylesheetPath()
@userStylesheetPath = userStylesheetPath
userStylesheetContents = @loadStylesheet(userStylesheetPath)
@applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')
# Internal-only:
loadBaseStylesheets: ->
@@ -109,91 +167,3 @@ class ThemeManager
$("head style.#{ttype}:last").after "<style class='#{ttype}' id='#{id}'>#{text}</style>"
else
$("head").append "<style class='#{ttype}' id='#{id}'>#{text}</style>"
# Internal-only:
unload: ->
@removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
theme.deactivate() while theme = @activeThemes.pop()
# Internal-only:
load: ->
config.observe 'core.themes', (themeNames) =>
@unload()
themeNames = [themeNames] unless _.isArray(themeNames)
# Reverse so the first (top) theme is loaded after the others. We want
# the first/top theme to override later themes in the stack.
themeNames = _.clone(themeNames).reverse()
@activateTheme(themeName) for themeName in themeNames
@loadUserStylesheet()
@reloadBaseStylesheets()
@emit('reloaded')
# Private:
loadTheme: (name, options) ->
if themePath = @resolveThemePath(name)
return theme if theme = @getLoadedTheme(name)
pack = Package.load(themePath, options)
if pack.isTheme()
@register(pack)
else
throw new Error("Attempted to load a non-theme package '#{name}' as a theme")
else
throw new Error("Could not resolve '#{name}' to a theme path")
# Private:
getLoadedTheme: (name) ->
_.find @loadedThemes, (theme) -> theme.metadata.name is name
# Private:
resolveThemePath: (name) ->
return name if fsUtils.isDirectorySync(name)
packagePath = fsUtils.resolve(config.packageDirPaths..., name)
return packagePath if fsUtils.isDirectorySync(packagePath)
packagePath = path.join(window.resourcePath, 'node_modules', name)
return packagePath if @isThemePath(packagePath)
# Private:
isThemePath: (packagePath) ->
{engines, theme} = Package.loadMetadata(packagePath, true)
engines?.atom? and theme
# Private:
activateTheme: (name) ->
try
theme = @loadTheme(name)
theme.activate()
@activeThemes.push(theme)
@emit('theme-activated', theme)
catch error
console.warn("Failed to load theme #{name}", error.stack ? error)
# Public:
getUserStylesheetPath: ->
stylesheetPath = fsUtils.resolve(path.join(config.configDirPath, 'user'), ['css', 'less'])
if fsUtils.isFileSync(stylesheetPath)
stylesheetPath
else
null
# Public:
getImportPaths: ->
if @activeThemes.length > 0
themePaths = (theme.getStylesheetsPath() for theme in @activeThemes when theme)
else
themePaths = []
for themeName in config.get('core.themes') ? []
if themePath = @resolveThemePath(themeName)
themePaths.push(path.join(themePath, AtomPackage.stylesheetsDir))
themePath for themePath in themePaths when fsUtils.isDirectorySync(themePath)
# Private:
loadUserStylesheet: ->
if userStylesheetPath = @getUserStylesheetPath()
@userStylesheetPath = userStylesheetPath
userStylesheetContents = @loadStylesheet(userStylesheetPath)
@applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')

32
src/theme-package.coffee Normal file
View File

@@ -0,0 +1,32 @@
AtomPackage = require './atom-package'
Package = require './package'
### Internal: Loads and resolves packages. ###
module.exports =
class ThemePackage extends AtomPackage
getType: -> 'theme'
getStylesheetType: -> 'theme'
enable: ->
themes = atom.config.get('core.themes')
themes = [@metadata.name].concat(themes)
atom.config.set('core.themes', themes)
disable: ->
atom.config.removeAtKeyPath('core.themes', @metadata.name)
load: ->
@measure 'loadTime', =>
try
@metadata ?= Package.loadMetadata(@path)
catch e
console.warn "Failed to load theme named '#{@name}'", e.stack ? e
this
activate: ->
@measure 'activateTime', =>
@loadStylesheets()
@activateNow()

View File

@@ -51,9 +51,8 @@ window.startEditorWindow = ->
atom.keymap.loadBundledKeymaps()
atom.themes.loadBaseStylesheets()
atom.packages.loadPackages()
atom.themes.load()
deserializeEditorWindow()
atom.packages.activatePackages()
atom.packages.activate()
atom.keymap.loadUserKeymaps()
atom.requireUserInitScript()
atom.menu.update()