mirror of
https://github.com/atom/atom.git
synced 2026-01-22 21:38:10 -05:00
Merge pull request #994 from atom/bo-improve-theme-load-api
Add an API for activating themes without having to touch config.themes
This commit is contained in:
@@ -78,7 +78,7 @@
|
||||
"collaboration": "0.28.0",
|
||||
"command-logger": "0.6.0",
|
||||
"command-palette": "0.5.0",
|
||||
"dev-live-reload": "0.8.0",
|
||||
"dev-live-reload": "0.11.0",
|
||||
"editor-stats": "0.5.0",
|
||||
"exception-reporting": "0.4.0",
|
||||
"find-and-replace": "0.29.0",
|
||||
@@ -95,7 +95,7 @@
|
||||
"metrics": "0.8.0",
|
||||
"package-generator": "0.13.0",
|
||||
"release-notes": "0.8.0",
|
||||
"settings-view": "0.29.0",
|
||||
"settings-view": "0.31.0",
|
||||
"snippets": "0.11.0",
|
||||
"spell-check": "0.8.0",
|
||||
"status-bar": "0.15.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{$, $$, fs, RootView} = require 'atom'
|
||||
Exec = require('child_process').exec
|
||||
path = require 'path'
|
||||
ThemeManager = require '../src/theme-manager'
|
||||
|
||||
describe "the `atom` global", ->
|
||||
beforeEach ->
|
||||
@@ -20,6 +21,7 @@ describe "the `atom` global", ->
|
||||
expect(pack.activateStylesheets).toHaveBeenCalled()
|
||||
|
||||
it "continues if the package has an invalid package.json", ->
|
||||
spyOn(console, 'warn')
|
||||
config.set("core.disabledPackages", [])
|
||||
expect(-> atom.loadPackage("package-with-broken-package-json")).not.toThrow()
|
||||
|
||||
@@ -192,7 +194,6 @@ describe "the `atom` global", ->
|
||||
expect(atom.contextMenu.definitionsForElement(element)[1].label).toBe "Menu item 1"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[2]).toBeUndefined()
|
||||
|
||||
|
||||
describe "stylesheet loading", ->
|
||||
describe "when the metadata contains a 'stylesheets' manifest", ->
|
||||
it "loads stylesheets from the stylesheets directory as specified by the manifest", ->
|
||||
@@ -336,3 +337,92 @@ describe "the `atom` global", ->
|
||||
atom.activatePackage('language-ruby', sync: true)
|
||||
atom.deactivatePackage('language-ruby')
|
||||
expect(syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
|
||||
|
||||
describe ".activate()", ->
|
||||
packageActivator = null
|
||||
themeActivator = null
|
||||
|
||||
beforeEach ->
|
||||
spyOn(console, 'warn')
|
||||
atom.packages.loadPackages()
|
||||
|
||||
loadedPackages = atom.packages.getLoadedPackages()
|
||||
expect(loadedPackages.length).toBeGreaterThan 0
|
||||
|
||||
packageActivator = spyOn(atom.packages, 'activatePackages')
|
||||
themeActivator = spyOn(atom.themes, 'activatePackages')
|
||||
|
||||
afterEach ->
|
||||
atom.packages.unloadPackages()
|
||||
|
||||
Syntax = require '../src/syntax'
|
||||
atom.syntax = window.syntax = new Syntax()
|
||||
|
||||
it "activates all the packages, and none of the themes", ->
|
||||
atom.packages.activate()
|
||||
|
||||
expect(packageActivator).toHaveBeenCalled()
|
||||
expect(themeActivator).toHaveBeenCalled()
|
||||
|
||||
packages = packageActivator.mostRecentCall.args[0]
|
||||
expect(['atom', 'textmate']).toContain(pack.getType()) for pack in packages
|
||||
|
||||
themes = themeActivator.mostRecentCall.args[0]
|
||||
expect(['theme']).toContain(theme.getType()) for theme in themes
|
||||
|
||||
describe ".en/disablePackage()", ->
|
||||
describe "with packages", ->
|
||||
it ".enablePackage() enables a disabled package", ->
|
||||
packageName = 'package-with-main'
|
||||
atom.config.pushAtKeyPath('core.disabledPackages', packageName)
|
||||
atom.packages.observeDisabledPackages()
|
||||
expect(config.get('core.disabledPackages')).toContain packageName
|
||||
|
||||
pack = atom.packages.enablePackage(packageName)
|
||||
|
||||
loadedPackages = atom.packages.getLoadedPackages()
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(loadedPackages).toContain(pack)
|
||||
expect(activatedPackages).toContain(pack)
|
||||
expect(config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
it ".disablePackage() disables an enabled package", ->
|
||||
packageName = 'package-with-main'
|
||||
atom.packages.activatePackage(packageName)
|
||||
atom.packages.observeDisabledPackages()
|
||||
expect(config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
pack = atom.packages.disablePackage(packageName)
|
||||
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(activatedPackages).not.toContain(pack)
|
||||
expect(config.get('core.disabledPackages')).toContain packageName
|
||||
|
||||
describe "with themes", ->
|
||||
beforeEach ->
|
||||
atom.themes.activateThemes()
|
||||
|
||||
afterEach ->
|
||||
atom.themes.deactivateThemes()
|
||||
atom.config.unobserve('core.themes')
|
||||
|
||||
it ".enablePackage() and .disablePackage() enables and disables a theme", ->
|
||||
packageName = 'theme-with-package-file'
|
||||
|
||||
expect(config.get('core.themes')).not.toContain packageName
|
||||
expect(config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
# enabling of theme
|
||||
pack = atom.packages.enablePackage(packageName)
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(activatedPackages).toContain(pack)
|
||||
expect(config.get('core.themes')).toContain packageName
|
||||
expect(config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
# disabling of theme
|
||||
pack = atom.packages.disablePackage(packageName)
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(activatedPackages).not.toContain(pack)
|
||||
expect(config.get('core.themes')).not.toContain packageName
|
||||
expect(config.get('core.themes')).not.toContain packageName
|
||||
expect(config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
@@ -32,7 +32,7 @@ describe "Config", ->
|
||||
config.set("foo.bar.baz", 42)
|
||||
|
||||
expect(config.save).toHaveBeenCalled()
|
||||
expect(observeHandler).toHaveBeenCalledWith 42
|
||||
expect(observeHandler).toHaveBeenCalledWith 42, {previous: undefined}
|
||||
|
||||
describe "when the value equals the default value", ->
|
||||
it "does not store the value", ->
|
||||
@@ -54,7 +54,7 @@ describe "Config", ->
|
||||
|
||||
expect(config.pushAtKeyPath("foo.bar.baz", "b")).toBe 2
|
||||
expect(config.get("foo.bar.baz")).toEqual ["a", "b"]
|
||||
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz")
|
||||
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz"), {previous: ['a']}
|
||||
|
||||
describe ".removeAtKeyPath(keyPath, value)", ->
|
||||
it "removes the given value from the array at the key path and updates observers", ->
|
||||
@@ -65,7 +65,7 @@ describe "Config", ->
|
||||
|
||||
expect(config.removeAtKeyPath("foo.bar.baz", "b")).toEqual ["a", "c"]
|
||||
expect(config.get("foo.bar.baz")).toEqual ["a", "c"]
|
||||
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz")
|
||||
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz"), {previous: ['a', 'b', 'c']}
|
||||
|
||||
describe ".getPositiveInt(keyPath, defaultValue)", ->
|
||||
it "returns the proper current or default value", ->
|
||||
@@ -142,26 +142,26 @@ describe "Config", ->
|
||||
it "fires the callback every time the observed value changes", ->
|
||||
observeHandler.reset() # clear the initial call
|
||||
config.set('foo.bar.baz', "value 2")
|
||||
expect(observeHandler).toHaveBeenCalledWith("value 2")
|
||||
expect(observeHandler).toHaveBeenCalledWith("value 2", {previous: 'value 1'})
|
||||
observeHandler.reset()
|
||||
|
||||
config.set('foo.bar.baz', "value 1")
|
||||
expect(observeHandler).toHaveBeenCalledWith("value 1")
|
||||
expect(observeHandler).toHaveBeenCalledWith("value 1", {previous: 'value 2'})
|
||||
|
||||
it "fires the callback when the observed value is deleted", ->
|
||||
observeHandler.reset() # clear the initial call
|
||||
config.set('foo.bar.baz', undefined)
|
||||
expect(observeHandler).toHaveBeenCalledWith(undefined)
|
||||
expect(observeHandler).toHaveBeenCalledWith(undefined, {previous: 'value 1'})
|
||||
|
||||
it "fires the callback when the full key path goes into and out of existence", ->
|
||||
observeHandler.reset() # clear the initial call
|
||||
config.set("foo.bar", undefined)
|
||||
|
||||
expect(observeHandler).toHaveBeenCalledWith(undefined)
|
||||
expect(observeHandler).toHaveBeenCalledWith(undefined, {previous: 'value 1'})
|
||||
observeHandler.reset()
|
||||
|
||||
config.set("foo.bar.baz", "i'm back")
|
||||
expect(observeHandler).toHaveBeenCalledWith("i'm back")
|
||||
expect(observeHandler).toHaveBeenCalledWith("i'm back", {previous: undefined})
|
||||
|
||||
describe ".initializeConfigDirectory()", ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -25,7 +25,7 @@ describe "SpacePen extensions", ->
|
||||
|
||||
config.set("foo.bar", "hello")
|
||||
|
||||
expect(observeHandler).toHaveBeenCalledWith("hello")
|
||||
expect(observeHandler).toHaveBeenCalledWith("hello", previous: undefined)
|
||||
observeHandler.reset()
|
||||
|
||||
view.unobserveConfig()
|
||||
|
||||
@@ -8,10 +8,25 @@ describe "ThemeManager", ->
|
||||
themeManager = null
|
||||
|
||||
beforeEach ->
|
||||
themeManager = new ThemeManager()
|
||||
themeManager = new ThemeManager(atom.packages)
|
||||
|
||||
afterEach ->
|
||||
themeManager.unload()
|
||||
themeManager.deactivateThemes()
|
||||
|
||||
describe "theme getters and setters", ->
|
||||
beforeEach ->
|
||||
atom.packages.loadPackages()
|
||||
|
||||
it 'getLoadedThemes get all the loaded themes', ->
|
||||
themes = themeManager.getLoadedThemes()
|
||||
expect(themes.length).toBeGreaterThan(2)
|
||||
|
||||
it 'getActiveThemes get all the active themes', ->
|
||||
themeManager.activateThemes()
|
||||
names = atom.config.get('core.themes')
|
||||
expect(names.length).toBeGreaterThan(0)
|
||||
themes = themeManager.getActiveThemes()
|
||||
expect(themes).toHaveLength(names.length)
|
||||
|
||||
describe "getImportPaths()", ->
|
||||
it "returns the theme directories before the themes are loaded", ->
|
||||
@@ -32,7 +47,7 @@ describe "ThemeManager", ->
|
||||
it "add/removes stylesheets to reflect the new config value", ->
|
||||
themeManager.on 'reloaded', reloadHandler = jasmine.createSpy()
|
||||
spyOn(themeManager, 'getUserStylesheetPath').andCallFake -> null
|
||||
themeManager.load()
|
||||
themeManager.activateThemes()
|
||||
|
||||
config.set('core.themes', [])
|
||||
expect($('style.theme').length).toBe 0
|
||||
@@ -59,21 +74,7 @@ describe "ThemeManager", ->
|
||||
describe "when a theme fails to load", ->
|
||||
it "logs a warning", ->
|
||||
spyOn(console, 'warn')
|
||||
themeManager.activateTheme('a-theme-that-will-not-be-found')
|
||||
expect(console.warn).toHaveBeenCalled()
|
||||
|
||||
describe "theme-loaded event", ->
|
||||
beforeEach ->
|
||||
spyOn(themeManager, 'getUserStylesheetPath').andCallFake -> null
|
||||
themeManager.load()
|
||||
|
||||
it "fires when a new theme has been added", ->
|
||||
themeManager.on 'theme-activated', loadHandler = jasmine.createSpy()
|
||||
|
||||
config.set('core.themes', ['atom-dark-syntax'])
|
||||
|
||||
expect(loadHandler).toHaveBeenCalled()
|
||||
expect(loadHandler.mostRecentCall.args[0]).toBeInstanceOf AtomPackage
|
||||
expect(-> atom.packages.activatePackage('a-theme-that-will-not-be-found')).toThrow()
|
||||
|
||||
describe "requireStylesheet(path)", ->
|
||||
it "synchronously loads css at the given path and installs a style tag for it in the head", ->
|
||||
@@ -140,7 +141,7 @@ describe "ThemeManager", ->
|
||||
window.rootView = new RootView
|
||||
rootView.append $$ -> @div class: 'editor'
|
||||
rootView.attachToDom()
|
||||
themeManager.load()
|
||||
themeManager.activateThemes()
|
||||
|
||||
it "loads the correct values from the theme's ui-variables file", ->
|
||||
config.set('core.themes', ['theme-with-ui-variables'])
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,10 @@ class PackageManager
|
||||
@loadedPackages = {}
|
||||
@activePackages = {}
|
||||
@packageStates = {}
|
||||
@observingDisabledPackages = false
|
||||
|
||||
@packageActivators = []
|
||||
@registerPackageActivator(this, ['atom', 'textmate'])
|
||||
|
||||
getPackageState: (name) ->
|
||||
@packageStates[name]
|
||||
@@ -23,10 +42,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 +80,7 @@ class PackageManager
|
||||
|
||||
deactivatePackages: ->
|
||||
@deactivatePackage(pack.name) for pack in @getActivePackages()
|
||||
@unobserveDisabledPackages()
|
||||
|
||||
deactivatePackage: (name) ->
|
||||
if pack = @getActivePackage(name)
|
||||
@@ -43,14 +90,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 +126,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 +148,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 +157,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 = []
|
||||
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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')
|
||||
|
||||
30
src/theme-package.coffee
Normal file
30
src/theme-package.coffee
Normal file
@@ -0,0 +1,30 @@
|
||||
AtomPackage = require './atom-package'
|
||||
Package = require './package'
|
||||
|
||||
### Internal: Loads and resolves packages. ###
|
||||
|
||||
module.exports =
|
||||
class ThemePackage extends AtomPackage
|
||||
|
||||
getType: -> 'theme'
|
||||
|
||||
getStylesheetType: -> 'theme'
|
||||
|
||||
enable: ->
|
||||
atom.config.pushAtKeyPath('core.themes', @metadata.name)
|
||||
|
||||
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()
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user