diff --git a/package.json b/package.json index 3cd19c39e..ea0f6d177 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "metrics": "0.26.0", "package-generator": "0.26.0", "release-notes": "0.20.0", - "settings-view": "0.72.0", + "settings-view": "0.73.0", "snippets": "0.27.0", "spell-check": "0.25.0", "status-bar": "0.32.0", @@ -106,39 +106,39 @@ "welcome": "0.4.0", "whitespace": "0.13.0", "wrap-guide": "0.14.0", - "language-c": "0.4.0", - "language-clojure": "0.1.0", - "language-coffee-script": "0.7.0", - "language-css": "0.3.0", - "language-gfm": "0.16.0", - "language-git": "0.4.0", - "language-go": "0.3.0", - "language-html": "0.3.0", - "language-hyperlink": "0.3.0", - "language-java": "0.3.0", - "language-javascript": "0.6.0", - "language-json": "0.3.0", - "language-less": "0.2.0", - "language-make": "0.2.0", - "language-mustache": "0.2.0", - "language-objective-c": "0.3.0", - "language-pegjs": "0.2.0", - "language-perl": "0.3.0", - "language-php": "0.4.0", - "language-property-list": "0.3.0", - "language-puppet": "0.3.0", - "language-python": "0.3.0", - "language-ruby": "0.9.0", - "language-ruby-on-rails": "0.5.0", - "language-sass": "0.4.0", - "language-shellscript": "0.3.0", - "language-source": "0.3.0", - "language-sql": "0.3.0", - "language-text": "0.3.0", - "language-todo": "0.3.0", - "language-toml": "0.8.0", - "language-xml": "0.3.0", - "language-yaml": "0.2.0" + "language-c": "0.8.0", + "language-clojure": "0.2.0", + "language-coffee-script": "0.9.0", + "language-css": "0.7.0", + "language-gfm": "0.17.0", + "language-git": "0.6.0", + "language-go": "0.4.0", + "language-html": "0.5.0", + "language-hyperlink": "0.5.0", + "language-java": "0.5.0", + "language-javascript": "0.8.0", + "language-json": "0.5.0", + "language-less": "0.4.0", + "language-make": "0.4.0", + "language-mustache": "0.3.0", + "language-objective-c": "0.5.0", + "language-pegjs": "0.3.0", + "language-perl": "0.5.0", + "language-php": "0.6.0", + "language-property-list": "0.5.0", + "language-puppet": "0.5.0", + "language-python": "0.5.0", + "language-ruby": "0.11.0", + "language-ruby-on-rails": "0.6.0", + "language-sass": "0.6.0", + "language-shellscript": "0.5.0", + "language-source": "0.5.0", + "language-sql": "0.5.0", + "language-text": "0.4.0", + "language-todo": "0.4.0", + "language-toml": "0.9.0", + "language-xml": "0.5.0", + "language-yaml": "0.4.0" }, "private": true, "scripts": { diff --git a/spec/atom-spec.coffee b/spec/atom-spec.coffee index 038318b07..fcd6cf6a2 100644 --- a/spec/atom-spec.coffee +++ b/spec/atom-spec.coffee @@ -1,7 +1,7 @@ {$, $$, fs, WorkspaceView} = require 'atom' Exec = require('child_process').exec path = require 'path' -AtomPackage = require '../src/atom-package' +Package = require '../src/package' ThemeManager = require '../src/theme-manager' describe "the `atom` global", -> @@ -50,7 +50,7 @@ describe "the `atom` global", -> describe "atom packages", -> describe "when called multiple times", -> it "it only calls activate on the package once", -> - spyOn(AtomPackage.prototype, 'activateNow').andCallThrough() + spyOn(Package.prototype, 'activateNow').andCallThrough() atom.packages.activatePackage('package-with-index') atom.packages.activatePackage('package-with-index') @@ -58,7 +58,7 @@ describe "the `atom` global", -> atom.packages.activatePackage('package-with-index') runs -> - expect(AtomPackage.prototype.activateNow.callCount).toBe 1 + expect(Package.prototype.activateNow.callCount).toBe 1 describe "when the package has a main module", -> describe "when the metadata specifies a main module path˜", -> @@ -87,9 +87,13 @@ describe "the `atom` global", -> it "assigns config defaults from the module", -> expect(atom.config.get('package-with-config-defaults.numbers.one')).toBeUndefined() - atom.packages.activatePackage('package-with-config-defaults') - expect(atom.config.get('package-with-config-defaults.numbers.one')).toBe 1 - expect(atom.config.get('package-with-config-defaults.numbers.two')).toBe 2 + + waitsForPromise -> + atom.packages.activatePackage('package-with-config-defaults') + + runs -> + expect(atom.config.get('package-with-config-defaults.numbers.one')).toBe 1 + expect(atom.config.get('package-with-config-defaults.numbers.two')).toBe 2 describe "when the package metadata includes activation events", -> [mainModule, promise] = [] @@ -97,8 +101,7 @@ describe "the `atom` global", -> beforeEach -> mainModule = require './fixtures/packages/package-with-activation-events/index' spyOn(mainModule, 'activate').andCallThrough() - AtomPackage = require '../src/atom-package' - spyOn(AtomPackage.prototype, 'requireMainModule').andCallThrough() + spyOn(Package.prototype, 'requireMainModule').andCallThrough() promise = atom.packages.activatePackage('package-with-activation-events') @@ -259,9 +262,12 @@ describe "the `atom` global", -> describe "grammar loading", -> it "loads the package's grammars", -> - atom.packages.activatePackage('package-with-grammars') - expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Alot' - expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Alittle' + waitsForPromise -> + atom.packages.activatePackage('package-with-grammars') + + runs -> + expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Alot' + expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Alittle' describe "scoped-property loading", -> it "loads the scoped properties", -> @@ -271,7 +277,7 @@ describe "the `atom` global", -> runs -> expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' - describe "textmate packages", -> + describe "converted textmate packages", -> it "loads the package's grammars", -> expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar" @@ -281,7 +287,7 @@ describe "the `atom` global", -> runs -> expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby" - it "translates the package's scoped properties to Atom terms", -> + it "loads the translated scoped properties", -> expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined() waitsForPromise -> @@ -290,17 +296,6 @@ describe "the `atom` global", -> runs -> expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBe '# ' - describe "when the package has no grammars but does have preferences", -> - it "loads the package's preferences as scoped properties", -> - jasmine.unspy(window, 'setTimeout') - spyOn(atom.syntax, 'addProperties').andCallThrough() - - waitsForPromise -> - atom.packages.activatePackage('package-with-preferences-tmbundle') - - runs -> - expect(atom.syntax.getProperty(['.source.pref'], 'editor.increaseIndentPattern')).toBe '^abc$' - describe ".deactivatePackage(id)", -> describe "atom packages", -> it "calls `deactivate` on the package's main module if activate was successful", -> diff --git a/spec/editor-spec.coffee b/spec/editor-spec.coffee index 821299f8c..4de1a2e25 100644 --- a/spec/editor-spec.coffee +++ b/spec/editor-spec.coffee @@ -2782,12 +2782,13 @@ describe "Editor", -> expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS" expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"] + waitsForPromise -> + atom.packages.activatePackage('package-with-injection-selector') - atom.packages.activatePackage('package-with-injection-selector') - - {tokens} = editor.lineForScreenRow(0) - expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS" - expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"] + runs -> + {tokens} = editor.lineForScreenRow(0) + expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS" + expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"] waitsForPromise -> atom.packages.activatePackage('language-sql') diff --git a/spec/fixtures/packages/package-with-broken-snippets.tmbundle/Snippets/.hidden-file b/spec/fixtures/packages/package-with-broken-snippets.tmbundle/Snippets/.hidden-file deleted file mode 100644 index 35b867918..000000000 --- a/spec/fixtures/packages/package-with-broken-snippets.tmbundle/Snippets/.hidden-file +++ /dev/null @@ -1 +0,0 @@ -I am hidden so I shouldn't be loaded diff --git a/spec/fixtures/packages/package-with-broken-snippets.tmbundle/Snippets/invalid.plist b/spec/fixtures/packages/package-with-broken-snippets.tmbundle/Snippets/invalid.plist deleted file mode 100644 index d6970c8b7..000000000 --- a/spec/fixtures/packages/package-with-broken-snippets.tmbundle/Snippets/invalid.plist +++ /dev/null @@ -1 +0,0 @@ -I am not a valid plist but that shouldn't cause a crisis diff --git a/spec/fixtures/packages/package-with-preferences-tmbundle/preferences/misc.json b/spec/fixtures/packages/package-with-preferences-tmbundle/preferences/misc.json deleted file mode 100644 index 784dd0cbc..000000000 --- a/spec/fixtures/packages/package-with-preferences-tmbundle/preferences/misc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "my preferences", - "scope": "source.pref", - "settings": { - "increaseIndentPattern": "^abc$" - } -} diff --git a/spec/atom-package-spec.coffee b/spec/package-spec.coffee similarity index 89% rename from spec/atom-package-spec.coffee rename to spec/package-spec.coffee index 2057294e4..e5b473930 100644 --- a/spec/atom-package-spec.coffee +++ b/spec/package-spec.coffee @@ -1,8 +1,8 @@ {$} = require 'atom' path = require 'path' -Package = require '../src/package' +ThemePackage = require '../src/theme-package' -describe "AtomPackage", -> +describe "Package", -> describe "theme", -> theme = null @@ -16,14 +16,14 @@ describe "AtomPackage", -> it "loads and applies css", -> expect($(".editor").css("padding-bottom")).not.toBe "1234px" themePath = atom.project.resolve('packages/theme-with-index-css') - theme = Package.load(themePath) + theme = new ThemePackage(themePath) theme.activate() expect($(".editor").css("padding-top")).toBe "1234px" it "parses, loads and applies less", -> expect($(".editor").css("padding-bottom")).not.toBe "1234px" themePath = atom.project.resolve('packages/theme-with-index-less') - theme = Package.load(themePath) + theme = new ThemePackage(themePath) theme.activate() expect($(".editor").css("padding-top")).toBe "4321px" @@ -34,7 +34,7 @@ describe "AtomPackage", -> expect($(".editor").css("padding-bottom")).not.toBe("103px") themePath = atom.project.resolve('packages/theme-with-package-file') - theme = Package.load(themePath) + theme = new ThemePackage(themePath) theme.activate() expect($(".editor").css("padding-top")).toBe("101px") expect($(".editor").css("padding-right")).toBe("102px") @@ -47,7 +47,7 @@ describe "AtomPackage", -> expect($(".editor").css("padding-bottom")).not.toBe "30px" themePath = atom.project.resolve('packages/theme-without-package-file') - theme = Package.load(themePath) + theme = new ThemePackage(themePath) theme.activate() expect($(".editor").css("padding-top")).toBe "10px" expect($(".editor").css("padding-right")).toBe "20px" @@ -56,7 +56,7 @@ describe "AtomPackage", -> describe "reloading a theme", -> beforeEach -> themePath = atom.project.resolve('packages/theme-with-package-file') - theme = Package.load(themePath) + theme = new ThemePackage(themePath) theme.activate() it "reloads without readding to the stylesheets list", -> @@ -67,7 +67,7 @@ describe "AtomPackage", -> describe "events", -> beforeEach -> themePath = atom.project.resolve('packages/theme-with-package-file') - theme = Package.load(themePath) + theme = new ThemePackage(themePath) theme.activate() it "deactivated event fires on .deactivate()", -> diff --git a/spec/theme-manager-spec.coffee b/spec/theme-manager-spec.coffee index 384844e93..8bd6f1f89 100644 --- a/spec/theme-manager-spec.coffee +++ b/spec/theme-manager-spec.coffee @@ -4,7 +4,7 @@ path = require 'path' temp = require 'temp' ThemeManager = require '../src/theme-manager' -AtomPackage = require '../src/atom-package' +Package = require '../src/package' describe "ThemeManager", -> themeManager = null diff --git a/src/atom-package.coffee b/src/atom-package.coffee deleted file mode 100644 index ea9358670..000000000 --- a/src/atom-package.coffee +++ /dev/null @@ -1,254 +0,0 @@ -Package = require './package' -fs = require 'fs-plus' -path = require 'path' -_ = require 'underscore-plus' -Q = require 'q' -{$} = require './space-pen-extensions' -CSON = require 'season' -{Emitter} = require 'emissary' - -# Loads and activates a package's main module and resources such as -# stylesheets, keymaps, grammar, editor properties, and menus. -module.exports = -class AtomPackage extends Package - Emitter.includeInto(this) - - @stylesheetsDir: 'stylesheets' - - metadata: null - keymaps: null - menus: null - stylesheets: null - grammars: null - scopedProperties: null - mainModulePath: null - resolvedMainModulePath: false - mainModule: null - - constructor: (path, {@metadata}) -> - super(path) - @reset() - - getType: -> 'atom' - - getStylesheetType: -> 'bundled' - - load: -> - @measure 'loadTime', => - try - @metadata ?= Package.loadMetadata(@path) - - @loadKeymaps() - @loadMenus() - @loadStylesheets() - @loadGrammars() - @loadScopedProperties() - @requireMainModule() unless @metadata.activationEvents? - - catch e - console.warn "Failed to load package named '#{@name}'", e.stack ? e - this - - reset: -> - @stylesheets = [] - @keymaps = [] - @menus = [] - @grammars = [] - @scopedProperties = [] - - activate: -> - return @activationDeferred.promise if @activationDeferred? - - @activationDeferred = Q.defer() - @measure 'activateTime', => - @activateResources() - if @metadata.activationEvents? - @subscribeToActivationEvents() - else - @activateNow() - - @activationDeferred.promise - - activateNow: -> - try - @activateConfig() - @activateStylesheets() - if @requireMainModule() - @mainModule.activate(atom.packages.getPackageState(@name) ? {}) - @mainActivated = true - catch e - console.warn "Failed to activate package named '#{@name}'", e.stack - - @activationDeferred.resolve() - - activateConfig: -> - return if @configActivated - - @requireMainModule() - if @mainModule? - atom.config.setDefaults(@name, @mainModule.configDefaults) - @mainModule.activateConfig?() - @configActivated = true - - activateStylesheets: -> - return if @stylesheetsActivated - - type = @getStylesheetType() - for [stylesheetPath, content] in @stylesheets - atom.themes.applyStylesheet(stylesheetPath, content, type) - @stylesheetsActivated = true - - activateResources: -> - atom.keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps - atom.contextMenu.add(menuPath, map['context-menu']) for [menuPath, map] in @menus - atom.menu.add(map.menu) for [menuPath, map] in @menus when map.menu - grammar.activate() for grammar in @grammars - for [scopedPropertiesPath, selector, properties] in @scopedProperties - atom.syntax.addProperties(scopedPropertiesPath, selector, properties) - - loadKeymaps: -> - @keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, CSON.readFileSync(keymapPath)] - - loadMenus: -> - @menus = @getMenuPaths().map (menuPath) -> [menuPath, CSON.readFileSync(menuPath)] - - getKeymapPaths: -> - keymapsDirPath = path.join(@path, 'keymaps') - if @metadata.keymaps - @metadata.keymaps.map (name) -> fs.resolve(keymapsDirPath, name, ['json', 'cson', '']) - else - fs.listSync(keymapsDirPath, ['cson', 'json']) - - getMenuPaths: -> - menusDirPath = path.join(@path, 'menus') - if @metadata.menus - @metadata.menus.map (name) -> fs.resolve(menusDirPath, name, ['json', 'cson', '']) - else - fs.listSync(menusDirPath, ['cson', 'json']) - - loadStylesheets: -> - @stylesheets = @getStylesheetPaths().map (stylesheetPath) -> - [stylesheetPath, atom.themes.loadStylesheet(stylesheetPath)] - - getStylesheetsPath: -> - path.join(@path, @constructor.stylesheetsDir) - - getStylesheetPaths: -> - stylesheetDirPath = @getStylesheetsPath() - - if @metadata.stylesheetMain - [fs.resolve(@path, @metadata.stylesheetMain)] - else if @metadata.stylesheets - @metadata.stylesheets.map (name) -> fs.resolve(stylesheetDirPath, name, ['css', 'less', '']) - else if indexStylesheet = fs.resolve(@path, 'index', ['css', 'less']) - [indexStylesheet] - else - fs.listSync(stylesheetDirPath, ['css', 'less']) - - loadGrammars: -> - @grammars = [] - grammarsDirPath = path.join(@path, 'grammars') - for grammarPath in fs.listSync(grammarsDirPath, ['.json', '.cson']) - @grammars.push(atom.syntax.readGrammarSync(grammarPath)) - - loadScopedProperties: -> - @scopedProperties = [] - scopedPropertiessDirPath = path.join(@path, 'scoped-properties') - for scopedPropertiesPath in fs.listSync(scopedPropertiessDirPath, ['.json', '.cson']) - for selector, properties of fs.readObjectSync(scopedPropertiesPath) - @scopedProperties.push([scopedPropertiesPath, selector, properties]) - - serialize: -> - if @mainActivated - try - @mainModule?.serialize?() - catch e - console.error "Error serializing package '#{@name}'", e.stack - - deactivate: -> - @activationDeferred?.reject() - @activationDeferred = null - @unsubscribeFromActivationEvents() - @deactivateResources() - @deactivateConfig() - @mainModule?.deactivate?() if @mainActivated - @emit('deactivated') - - deactivateConfig: -> - @mainModule?.deactivateConfig?() - @configActivated = false - - deactivateResources: -> - grammar.deactivate() for grammar in @grammars - atom.syntax.removeProperties(scopedPropertiesPath) for [scopedPropertiesPath] in @scopedProperties - atom.keymap.remove(keymapPath) for [keymapPath] in @keymaps - atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in @stylesheets - @stylesheetsActivated = false - - reloadStylesheets: -> - oldSheets = _.clone(@stylesheets) - @loadStylesheets() - atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in oldSheets - @reloadStylesheet(stylesheetPath, content) for [stylesheetPath, content] in @stylesheets - - reloadStylesheet: (stylesheetPath, content) -> - atom.themes.applyStylesheet(stylesheetPath, content, @getStylesheetType()) - - requireMainModule: -> - return @mainModule if @mainModule? - mainModulePath = @getMainModulePath() - @mainModule = require(mainModulePath) if fs.isFileSync(mainModulePath) - - getMainModulePath: -> - return @mainModulePath if @resolvedMainModulePath - @resolvedMainModulePath = true - mainModulePath = - if @metadata.main - path.join(@path, @metadata.main) - else - path.join(@path, 'index') - @mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...]) - - 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 - - handleActivationEvent: (event) => - bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event) - @activateNow() - $(event.target).trigger(event) - @restoreEventHandlersOnBubblePath(bubblePathEventHandlers) - @unsubscribeFromActivationEvents() - - 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 = -> - 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 diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 52ea5fedc..2ace669b6 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -1,9 +1,12 @@ +path = require 'path' + +_ = require 'underscore-plus' {Emitter} = require 'emissary' fs = require 'fs-plus' -_ = require 'underscore-plus' Q = require 'q' + Package = require './package' -path = require 'path' +ThemePackage = require './theme-package' # Public: Package manager for coordinating the lifecycle of Atom packages. # @@ -144,9 +147,18 @@ class PackageManager name = path.basename(nameOrPath) return pack if pack = @getLoadedPackage(name) - pack = Package.load(packagePath) - @loadedPackages[pack.name] = pack if pack? - pack + try + metadata = Package.loadMetadata(packagePath) ? {} + if metadata.theme + pack = new ThemePackage(packagePath, metadata) + else + pack = new Package(packagePath, metadata) + pack.load() + @loadedPackages[pack.name] = pack + pack + catch error + console.warn "Failed to load package.json '#{path.basename(packagePath)}'", error.stack ? error + else throw new Error("Could not resolve '#{nameOrPath}' to a package path") diff --git a/src/package.coffee b/src/package.coffee index 7618e80c5..f08e5b4f0 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -1,53 +1,52 @@ -CSON = require 'season' -{basename, join} = require 'path' +path = require 'path' +_ = require 'underscore-plus' +async = require 'async' +CSON = require 'season' +fs = require 'fs-plus' +{Emitter} = require 'emissary' +Q = require 'q' + +{$} = require './space-pen-extensions' +ScopedProperties = require './scoped-properties' + +# Loads and activates a package's main module and resources such as +# stylesheets, keymaps, grammar, editor properties, and menus. module.exports = class Package - @build: (path) -> - TextMatePackage = require './text-mate-package' - AtomPackage = require './atom-package' - ThemePackage = require './theme-package' + Emitter.includeInto(this) - if TextMatePackage.testName(path) - pack = new TextMatePackage(path) - else - 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 + @stylesheetsDir: 'stylesheets' - pack - - @load: (path) -> - pack = @build(path) - pack?.load() - pack - - @loadMetadata: (path, ignoreErrors=false) -> - if metadataPath = CSON.resolve(join(path, 'package')) + @loadMetadata: (packagePath, ignoreErrors=false) -> + if metadataPath = CSON.resolve(path.join(packagePath, 'package')) try metadata = CSON.readFileSync(metadataPath) - catch e - throw e unless ignoreErrors + catch error + throw error unless ignoreErrors metadata ?= {} - metadata.name = basename(path) + metadata.name = path.basename(packagePath) metadata - name: null - path: null + keymaps: null + menus: null + stylesheets: null + grammars: null + scopedProperties: null + mainModulePath: null + resolvedMainModulePath: false + mainModule: null - constructor: (@path) -> - @name = basename(@path) + constructor: (@path, @metadata) -> + @metadata ?= Package.loadMetadata(@path) + @name = @metadata?.name ? path.basename(@path) + @reset() enable: -> - atom.config.removeAtKeyPath('core.disabledPackages', @metadata.name) + atom.config.removeAtKeyPath('core.disabledPackages', @name) disable: -> - atom.config.pushAtKeyPath('core.disabledPackages', @metadata.name) + atom.config.pushAtKeyPath('core.disabledPackages', @name) isTheme: -> @metadata?.theme? @@ -57,3 +56,252 @@ class Package value = fn() @[key] = Date.now() - startTime value + + getType: -> 'atom' + + getStylesheetType: -> 'bundled' + + load: -> + @measure 'loadTime', => + try + @loadKeymaps() + @loadMenus() + @loadStylesheets() + @grammarsPromise = @loadGrammars() + @scopedPropertiesPromise = @loadScopedProperties() + @requireMainModule() unless @metadata.activationEvents? + + catch error + console.warn "Failed to load package named '#{@name}'", error.stack ? error + this + + reset: -> + @stylesheets = [] + @keymaps = [] + @menus = [] + @grammars = [] + @scopedProperties = [] + + activate: -> + unless @activationDeferred? + @activationDeferred = Q.defer() + @measure 'activateTime', => + @activateResources() + if @metadata.activationEvents? + @subscribeToActivationEvents() + else + @activateNow() + + Q.all([@grammarsPromise, @scopedPropertiesPromise, @activationDeferred.promise]) + + activateNow: -> + try + @activateConfig() + @activateStylesheets() + if @requireMainModule() + @mainModule.activate(atom.packages.getPackageState(@name) ? {}) + @mainActivated = true + catch e + console.warn "Failed to activate package named '#{@name}'", e.stack + + @activationDeferred.resolve() + + activateConfig: -> + return if @configActivated + + @requireMainModule() + if @mainModule? + atom.config.setDefaults(@name, @mainModule.configDefaults) + @mainModule.activateConfig?() + @configActivated = true + + activateStylesheets: -> + return if @stylesheetsActivated + + type = @getStylesheetType() + for [stylesheetPath, content] in @stylesheets + atom.themes.applyStylesheet(stylesheetPath, content, type) + @stylesheetsActivated = true + + activateResources: -> + atom.keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps + atom.contextMenu.add(menuPath, map['context-menu']) for [menuPath, map] in @menus + atom.menu.add(map.menu) for [menuPath, map] in @menus when map.menu + + grammar.activate() for grammar in @grammars + @grammarsActivated = true + + scopedProperties.activate() for scopedProperties in @scopedProperties + @scopedPropertiesActivated = true + + loadKeymaps: -> + @keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, CSON.readFileSync(keymapPath)] + + loadMenus: -> + @menus = @getMenuPaths().map (menuPath) -> [menuPath, CSON.readFileSync(menuPath)] + + getKeymapPaths: -> + keymapsDirPath = path.join(@path, 'keymaps') + if @metadata.keymaps + @metadata.keymaps.map (name) -> fs.resolve(keymapsDirPath, name, ['json', 'cson', '']) + else + fs.listSync(keymapsDirPath, ['cson', 'json']) + + getMenuPaths: -> + menusDirPath = path.join(@path, 'menus') + if @metadata.menus + @metadata.menus.map (name) -> fs.resolve(menusDirPath, name, ['json', 'cson', '']) + else + fs.listSync(menusDirPath, ['cson', 'json']) + + loadStylesheets: -> + @stylesheets = @getStylesheetPaths().map (stylesheetPath) -> + [stylesheetPath, atom.themes.loadStylesheet(stylesheetPath)] + + getStylesheetsPath: -> + path.join(@path, @constructor.stylesheetsDir) + + getStylesheetPaths: -> + stylesheetDirPath = @getStylesheetsPath() + + if @metadata.stylesheetMain + [fs.resolve(@path, @metadata.stylesheetMain)] + else if @metadata.stylesheets + @metadata.stylesheets.map (name) -> fs.resolve(stylesheetDirPath, name, ['css', 'less', '']) + else if indexStylesheet = fs.resolve(@path, 'index', ['css', 'less']) + [indexStylesheet] + else + fs.listSync(stylesheetDirPath, ['css', 'less']) + + loadGrammars: -> + @grammars = [] + + loadGrammar = (grammarPath, callback) => + atom.syntax.readGrammar grammarPath, (error, grammar) => + if error? + console.warn("Failed to load grammar: #{grammarPath}", error.stack ? error) + else + @grammars.push(grammar) + grammar.activate() if @grammarsActivated + callback() + + deferred = Q.defer() + grammarsDirPath = path.join(@path, 'grammars') + fs.list grammarsDirPath, ['json', 'cson'], (error, grammarPaths=[]) -> + async.each grammarPaths, loadGrammar, -> deferred.resolve() + deferred.promise + + loadScopedProperties: -> + @scopedProperties = [] + + loadScopedPropertiesFile = (scopedPropertiesPath, callback) => + ScopedProperties.load scopedPropertiesPath, (error, scopedProperties) => + if error? + console.warn("Failed to load scoped properties: #{scopedPropertiesPath}", error.stack ? error) + else + @scopedProperties.push(scopedProperties) + scopedProperties.activate() if @scopedPropertiesActivated + callback() + + deferred = Q.defer() + scopedPropertiesDirPath = path.join(@path, 'scoped-properties') + fs.list scopedPropertiesDirPath, ['json', 'cson'], (error, scopedPropertiesPaths=[]) -> + async.each scopedPropertiesPaths, loadScopedPropertiesFile, -> deferred.resolve() + deferred.promise + + serialize: -> + if @mainActivated + try + @mainModule?.serialize?() + catch e + console.error "Error serializing package '#{@name}'", e.stack + + deactivate: -> + @activationDeferred?.reject() + @activationDeferred = null + @unsubscribeFromActivationEvents() + @deactivateResources() + @deactivateConfig() + @mainModule?.deactivate?() if @mainActivated + @emit('deactivated') + + deactivateConfig: -> + @mainModule?.deactivateConfig?() + @configActivated = false + + deactivateResources: -> + grammar.deactivate() for grammar in @grammars + scopedProperties.deactivate() for scopedProperties in @scopedProperties + atom.keymap.remove(keymapPath) for [keymapPath] in @keymaps + atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in @stylesheets + @stylesheetsActivated = false + @grammarsActivated = false + @scopedPropertiesActivated = false + + reloadStylesheets: -> + oldSheets = _.clone(@stylesheets) + @loadStylesheets() + atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in oldSheets + @reloadStylesheet(stylesheetPath, content) for [stylesheetPath, content] in @stylesheets + + reloadStylesheet: (stylesheetPath, content) -> + atom.themes.applyStylesheet(stylesheetPath, content, @getStylesheetType()) + + requireMainModule: -> + return @mainModule if @mainModule? + mainModulePath = @getMainModulePath() + @mainModule = require(mainModulePath) if fs.isFileSync(mainModulePath) + + getMainModulePath: -> + return @mainModulePath if @resolvedMainModulePath + @resolvedMainModulePath = true + mainModulePath = + if @metadata.main + path.join(@path, @metadata.main) + else + path.join(@path, 'index') + @mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...]) + + 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 + + handleActivationEvent: (event) => + bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event) + @activateNow() + $(event.target).trigger(event) + @restoreEventHandlersOnBubblePath(bubblePathEventHandlers) + @unsubscribeFromActivationEvents() + + 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 = -> + 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 diff --git a/src/scoped-properties.coffee b/src/scoped-properties.coffee new file mode 100644 index 000000000..57d875a3d --- /dev/null +++ b/src/scoped-properties.coffee @@ -0,0 +1,19 @@ +fs = require 'fs-plus' + +module.exports = +class ScopedProperties + @load: (scopedPropertiesPath, callback) -> + fs.readObject scopedPropertiesPath, (error, scopedProperties={}) -> + if error? + callback(error) + else + callback(null, new ScopedProperties(scopedPropertiesPath, scopedProperties)) + + constructor: (@path, @scopedProperties) -> + + activate: -> + for selector, properties of @scopedProperties + atom.syntax.addProperties(@path, selector, properties) + + deactivate: -> + atom.syntax.removeProperties(@path) diff --git a/src/text-mate-package.coffee b/src/text-mate-package.coffee deleted file mode 100644 index 92bb98bab..000000000 --- a/src/text-mate-package.coffee +++ /dev/null @@ -1,135 +0,0 @@ -Package = require './package' -path = require 'path' -_ = require 'underscore-plus' -fs = require 'fs-plus' -Q = require 'q' - -module.exports = -class TextMatePackage extends Package - @testName: (packageName) -> - packageName = path.basename(packageName) - /(^language-.+)|((\.|_|-)tmbundle$)/.test(packageName) - - @addToActivationPromise = (pack) -> - @activationPromise ?= Q() - @activationPromise = @activationPromise.then => - pack.loadGrammars() - .then -> pack.loadScopedProperties() - .fail (error) -> console.log pack.name, error.stack ? error - - constructor: -> - super - @grammars = [] - @scopedProperties = [] - @metadata = {@name} - - getType: -> 'textmate' - - load: -> - @measure 'loadTime', => - @metadata = Package.loadMetadata(@path, true) - - activate: -> - @measure 'activateTime', => - TextMatePackage.addToActivationPromise(this) - - activateConfig: -> # noop - - deactivate: -> - grammar.deactivate() for grammar in @grammars - atom.syntax.removeProperties(@path) - - loadGrammars: -> - deferred = Q.defer() - fs.isDirectory @getSyntaxesPath(), (isDirectory) => - return deferred.resolve() unless isDirectory - - fs.list @getSyntaxesPath(), ['json', 'cson'], (error, paths) => - if error? - console.log("Error loading grammars of TextMate package '#{@path}':", error.stack, error) - deferred.resolve() - else - promises = paths.map (path) => @loadGrammarAtPath(path) - Q.all(promises).then -> deferred.resolve() - - deferred.promise - - loadGrammarAtPath: (grammarPath) -> - deferred = Q.defer() - atom.syntax.readGrammar grammarPath, (error, grammar) => - if error? - console.log("Error loading grammar at path '#{grammarPath}':", error.stack ? error) - else - @addGrammar(grammar) - deferred.resolve() - - deferred.promise - - addGrammar: (grammar) -> - @grammars.push(grammar) - grammar.activate() - - getGrammars: -> @grammars - - getSyntaxesPath: -> - path.join(@path, "syntaxes") - - getPreferencesPath: -> - path.join(@path, "preferences") - - loadScopedProperties: -> - scopedProperties = [] - - for grammar in @getGrammars() - if properties = @propertiesFromTextMateSettings(grammar) - selector = atom.syntax.cssSelectorFromScopeSelector(grammar.scopeName) - scopedProperties.push({selector, properties}) - - @loadTextMatePreferenceObjects().then (preferenceObjects=[]) => - for {scope, settings} in preferenceObjects - if properties = @propertiesFromTextMateSettings(settings) - selector = atom.syntax.cssSelectorFromScopeSelector(scope) if scope? - scopedProperties.push({selector, properties}) - - @scopedProperties = scopedProperties - for {selector, properties} in @scopedProperties - atom.syntax.addProperties(@path, selector, properties) - - loadTextMatePreferenceObjects: -> - deferred = Q.defer() - fs.isDirectory @getPreferencesPath(), (isDirectory) => - return deferred.resolve() unless isDirectory - fs.list @getPreferencesPath(), (error, paths) => - if error? - console.log("Error loading preferences of TextMate package '#{@path}':", error.stack, error) - deferred.resolve() - else - promises = paths.map (path) => @loadPreferencesAtPath(path) - Q.all(promises).then (preferenceObjects) -> deferred.resolve(preferenceObjects) - - deferred.promise - - loadPreferencesAtPath: (preferencePath) -> - deferred = Q.defer() - fs.readObject preferencePath, (error, preference) -> - if error? - console.warn("Failed to parse preference at path '#{preferencePath}'", error.stack, error) - deferred.resolve(preference) - deferred.promise - - propertiesFromTextMateSettings: (textMateSettings) -> - if textMateSettings.shellVariables - shellVariables = {} - for {name, value} in textMateSettings.shellVariables - shellVariables[name] = value - textMateSettings.shellVariables = shellVariables - - editorProperties = _.compactObject( - commentStart: _.valueForKeyPath(textMateSettings, 'shellVariables.TM_COMMENT_START') - commentEnd: _.valueForKeyPath(textMateSettings, 'shellVariables.TM_COMMENT_END') - increaseIndentPattern: textMateSettings.increaseIndentPattern - decreaseIndentPattern: textMateSettings.decreaseIndentPattern - foldEndPattern: textMateSettings.foldingStopMarker - completions: textMateSettings.completions - ) - { editor: editorProperties } if _.size(editorProperties) > 0 diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee index 08ecc0d47..eb4c5a3b3 100644 --- a/src/theme-manager.coffee +++ b/src/theme-manager.coffee @@ -6,7 +6,7 @@ fs = require 'fs-plus' Q = require 'q' {$} = require './space-pen-extensions' -AtomPackage = require './atom-package' +Package = require './package' File = require './file' # Public: Handles loading and activating available themes. @@ -93,7 +93,7 @@ class ThemeManager themePaths = [] for themeName in @getEnabledThemeNames() if themePath = @packageManager.resolvePackagePath(themeName) - themePaths.push(path.join(themePath, AtomPackage.stylesheetsDir)) + themePaths.push(path.join(themePath, Package.stylesheetsDir)) themePaths.filter (themePath) -> fs.isDirectorySync(themePath) diff --git a/src/theme-package.coffee b/src/theme-package.coffee index 2d4f611a6..3c03d818f 100644 --- a/src/theme-package.coffee +++ b/src/theme-package.coffee @@ -1,24 +1,24 @@ Q = require 'q' -AtomPackage = require './atom-package' +Package = require './package' module.exports = -class ThemePackage extends AtomPackage +class ThemePackage extends Package getType: -> 'theme' getStylesheetType: -> 'theme' enable: -> - atom.config.unshiftAtKeyPath('core.themes', @metadata.name) + atom.config.unshiftAtKeyPath('core.themes', @name) disable: -> - atom.config.removeAtKeyPath('core.themes', @metadata.name) + atom.config.removeAtKeyPath('core.themes', @name) load: -> @measure 'loadTime', => try @metadata ?= Package.loadMetadata(@path) - catch e - console.warn "Failed to load theme named '#{@name}'", e.stack ? e + catch error + console.warn "Failed to load theme named '#{@name}'", error.stack ? error this activate: -> diff --git a/vendor/apm b/vendor/apm index af40a0ed5..be0c1fb77 160000 --- a/vendor/apm +++ b/vendor/apm @@ -1 +1 @@ -Subproject commit af40a0ed55a5df3e8f7a7707e17ab2493edb94a3 +Subproject commit be0c1fb771f0e9cba1708ef773c6183b292225e4