diff --git a/.atom/bundles/Readme.md b/.atom/bundles/Readme.md deleted file mode 100644 index 8f53fde96..000000000 --- a/.atom/bundles/Readme.md +++ /dev/null @@ -1 +0,0 @@ -Put TextMate bundles in this directory diff --git a/.atom/packages/Readme.md b/.atom/packages/Readme.md new file mode 100644 index 000000000..e69de29bb diff --git a/.atom/themes/IR_Black.tmTheme b/.atom/themes/IR_Black.tmTheme new file mode 100644 index 000000000..cbc18d0b9 --- /dev/null +++ b/.atom/themes/IR_Black.tmTheme @@ -0,0 +1,810 @@ + + + + + name + IR_Black + settings + + + settings + + background + #000000 + caret + #FFFFFF + foreground + #EDEDED + invisibles + #CAE2FB3D + lineHighlight + #FFFFFF24 + selection + #333333 + + + + name + Comment + scope + comment + settings + + fontStyle + + foreground + #7C7C7C + + + + name + Entity + scope + entity + settings + + fontStyle + + foreground + #FFD2A7 + + + + name + Keyword + scope + keyword + settings + + fontStyle + + foreground + #96CBFE + + + + name + Keyword.control + scope + keyword.control + settings + + fontStyle + + foreground + #96CBFE + + + + name + Keyword.Operator + scope + keyword.operator + settings + + foreground + #EDEDED + + + + name + Class + scope + entity.name.type + settings + + fontStyle + underline + foreground + #FFFFB6 + + + + name + Support + scope + support + settings + + fontStyle + + foreground + #FFFFB6 + + + + name + Storage + scope + storage + settings + + fontStyle + + foreground + #CFCB90 + + + + name + Storage.modifier + scope + storage.modifier + settings + + fontStyle + + foreground + #96CBFE + + + + name + Constant + scope + constant + settings + + fontStyle + + foreground + #99CC99 + + + + name + String + scope + string + settings + + fontStyle + bold + foreground + #A8FF60 + + + + name + Number + scope + constant.numeric + settings + + fontStyle + bold + foreground + #FF73FD + + + + name + Punctuation + scope + punctuation + settings + + fontStyle + + + + + name + Variable + scope + variable + settings + + fontStyle + + foreground + #C6C5FE + + + + name + Invalid – Deprecated + scope + invalid.deprecated + settings + + fontStyle + italic underline + foreground + #FD5FF1 + + + + name + Invalid – Illegal + scope + invalid.illegal + settings + + background + #562D56BF + foreground + #FD5FF1 + + + + name + ----------------------------------- + settings + + + + name + ♦ Embedded Source (Bright) + scope + text source + settings + + background + #B1B3BA08 + fontStyle + + + + + name + ♦ Entity inherited-class + scope + entity.other.inherited-class + settings + + fontStyle + italic + foreground + #9B5C2E + + + + name + ♦ String embedded-variable + scope + source string source + settings + + fontStyle + + foreground + #EDEDED + + + + name + ♦ String punctuation + scope + source string source punctuation.section.embedded + settings + + fontStyle + + foreground + #00A0A0 + + + + name + ♦ String constant + scope + string constant + settings + + fontStyle + + foreground + #00A0A0 + + + + name + ♦ String.regexp + scope + string.regexp + settings + + foreground + #E9C062 + + + + name + ♦ String.regexp.«special» + scope + string.regexp constant.character.escape, string.regexp source.ruby.embedded, string.regexp string.regexp.arbitrary-repitition + settings + + fontStyle + + foreground + #FF8000 + + + + name + ♦ String.regexp.group + scope + string.regexp.group + settings + + background + #FFFFFF0F + fontStyle + + foreground + #C6A24F + + + + name + ♦ String.regexp.character-class + scope + string.regexp.character-class + settings + + fontStyle + + foreground + #B18A3D + + + + name + ♦ String variable + scope + string variable + settings + + fontStyle + + foreground + #8A9A95 + + + + name + ♦ Support.function + scope + support.function + settings + + fontStyle + + foreground + #DAD085 + + + + name + ♦ Support.constant + scope + support.constant + settings + + fontStyle + + foreground + #FFD2A7 + + + + name + c C/C++ Preprocessor Line + scope + meta.preprocessor.c + settings + + foreground + #8996A8 + + + + name + c C/C++ Preprocessor Directive + scope + meta.preprocessor.c keyword + settings + + fontStyle + + foreground + #AFC4DB + + + + name + j Cast + scope + meta.cast + settings + + fontStyle + italic + foreground + #676767 + + + + name + ✘ Doctype/XML Processing + scope + meta.sgml.html meta.doctype, meta.sgml.html meta.doctype entity, meta.sgml.html meta.doctype string, meta.xml-processing, meta.xml-processing entity, meta.xml-processing string + settings + + foreground + #494949 + + + + name + ✘ Meta.tag.«all» + scope + meta.tag, meta.tag entity + settings + + fontStyle + bold + foreground + #96CBFE + + + + name + ✘ Meta.tag.inline + scope + source entity.name.tag, source entity.other.attribute-name, meta.tag.inline, meta.tag.inline entity + settings + + fontStyle + + foreground + #96CBFE + + + + name + ✘ Meta.tag.attribute-name + scope + entity.other.attribute-name + settings + + fontStyle + + foreground + #FFD7B1 + + + + name + ✘ Namespaces + scope + entity.name.tag.namespace, entity.other.attribute-name.namespace + settings + + fontStyle + + foreground + #E18964 + + + + name + § css tag-name + scope + meta.selector.css entity.name.tag + settings + + fontStyle + underline + foreground + #96CBFE + + + + name + § css:pseudo-class + scope + meta.selector.css entity.other.attribute-name.tag.pseudo-class + settings + + fontStyle + + foreground + #8F9D6A + + + + name + § css#id + scope + meta.selector.css entity.other.attribute-name.id + settings + + fontStyle + + foreground + #8B98AB + + + + name + § css.class + scope + meta.selector.css entity.other.attribute-name.class + settings + + fontStyle + + foreground + #62B1FE + + + + name + § css property-name: + scope + support.type.property-name.css + settings + + foreground + #EDEDED + + + + name + § css property-value; + scope + meta.property-group support.constant.property-value.css, meta.property-value support.constant.property-value.css + settings + + fontStyle + + foreground + #F9EE98 + + + + name + § css @at-rule + scope + meta.preprocessor.at-rule keyword.control.at-rule + settings + + foreground + #8693A5 + + + + name + § css additional-constants + scope + meta.property-value support.constant.named-color.css, meta.property-value constant + settings + + fontStyle + + foreground + #87C38A + + + + name + § css constructor.argument + scope + meta.constructor.argument.css + settings + + foreground + #8F9D6A + + + + name + ⎇ diff.header + scope + meta.diff, meta.diff.header + settings + + background + #0E2231 + fontStyle + italic + foreground + #F8F8F8 + + + + name + ⎇ diff.deleted + scope + markup.deleted + settings + + background + #420E09 + foreground + #F8F8F8 + + + + name + ⎇ diff.changed + scope + markup.changed + settings + + background + #4A410D + foreground + #F8F8F8 + + + + name + ⎇ diff.inserted + scope + markup.inserted + settings + + background + #253B22 + foreground + #F8F8F8 + + + + name + -------------------------------- + settings + + + + name + Markup: Italic + scope + markup.italic + settings + + fontStyle + italic + foreground + #E9C062 + + + + name + Markup: Bold + scope + markup.bold + settings + + fontStyle + bold + foreground + #E9C062 + + + + name + Markup: Underline + scope + markup.underline + settings + + fontStyle + underline + foreground + #E18964 + + + + name + Markup: Quote + scope + markup.quote + settings + + background + #FEE09C12 + fontStyle + italic + foreground + #E1D4B9 + + + + name + Markup: Heading + scope + markup.heading, markup.heading entity + settings + + background + #632D04 + fontStyle + + foreground + #FEDCC5 + + + + name + Markup: List + scope + markup.list + settings + + foreground + #E1D4B9 + + + + name + Markup: Raw + scope + markup.raw + settings + + background + #B1B3BA08 + fontStyle + + foreground + #578BB3 + + + + name + Markup: Comment + scope + markup comment + settings + + fontStyle + italic + foreground + #F67B37 + + + + name + Markup: Separator + scope + meta.separator + settings + + background + #242424 + foreground + #60A633 + + + + name + Log Entry + scope + meta.line.entry.logfile, meta.line.exit.logfile + settings + + background + #EEEEEE29 + + + + name + Log Entry Error + scope + meta.line.error.logfile + settings + + background + #751012 + + + + uuid + D039AEA9-9DD2-4237-A963-E84494B0B3FB + + diff --git a/Rakefile b/Rakefile index 4f67d75f2..e85ba30a5 100644 --- a/Rakefile +++ b/Rakefile @@ -60,6 +60,20 @@ end desc "Creates .atom file if non exists" task "create-dot-atom" do + # Migration: If there is still a bundle path, rename it to packages + if File.exists?(DOT_ATOM_PATH) and File.exists?(File.join(DOT_ATOM_PATH, "bundles")) + if File.exists?(File.join(DOT_ATOM_PATH, "packages")) + `mv #{File.join(DOT_ATOM_PATH, "bundles", "*")} #{File.join(DOT_ATOM_PATH, "packages")}` + $stderr.puts "WARNING: Bundles from ~/.atom/bundles were moved to ~/.atom/packages" + else + `mv #{File.join(DOT_ATOM_PATH, "bundles")} #{File.join(DOT_ATOM_PATH, "packages")}` + $stderr.puts "WARNING: ~/.atom/bundles was moved to ~/.atom/packages" + end + end + + # Migration: remove files that are no longer needed + `rm -rf #{File.join(DOT_ATOM_PATH, 'default-config.coffee')}` + dot_atom_template_path = ATOM_SRC_PATH + "/.atom" replace_dot_atom = false next if File.exists?(DOT_ATOM_PATH) @@ -67,12 +81,8 @@ task "create-dot-atom" do `rm -rf "#{DOT_ATOM_PATH}"` `mkdir "#{DOT_ATOM_PATH}"` `cp "#{dot_atom_template_path}/atom.coffee" "#{DOT_ATOM_PATH}"` - `cp "#{dot_atom_template_path}/bundles" "#{DOT_ATOM_PATH}"` - - for path in Dir.entries(dot_atom_template_path) - next if ["..", ".", "atom.coffee", "bundles"].include? path - `ln -s "#{dot_atom_template_path}/#{path}" "#{DOT_ATOM_PATH}"` - end + `cp "#{dot_atom_template_path}/packages" "#{DOT_ATOM_PATH}"` + `cp -r "#{dot_atom_template_path}/themes" "#{DOT_ATOM_PATH}"` end desc "Clone default bundles into .atom directory" @@ -96,7 +106,7 @@ task "clone-default-bundles" => "create-dot-atom" do for bundle_url, sha in bundles bundle_dir = bundle_url[/([^\/]+?)(\.git)?$/, 1] - dest_path = File.join(DOT_ATOM_PATH, "bundles", bundle_dir) + dest_path = File.join(DOT_ATOM_PATH, "packages", bundle_dir) if File.exists? dest_path `cd #{dest_path} && git fetch --quiet` else @@ -110,6 +120,8 @@ end desc "Clean build Atom via `xcodebuild`" task :clean do output = `xcodebuild clean` + `rm -rf #{application_path()}` + `rm -rf #{BUILD_DIR}` end desc "Run Atom" @@ -135,7 +147,7 @@ task :benchmark do end task :nof do - system %{find . -name *spec.coffee | grep -v atom-build | xargs sed -E -i "" "s/f+(it|describe) +(['\\"])/\\1 \\2/g"} + system %{find . -name *spec.coffee | grep -v #{BUILD_DIR} | xargs sed -E -i "" "s/f+(it|describe) +(['\\"])/\\1 \\2/g"} end task :tags do diff --git a/benchmark/benchmark-helper.coffee b/benchmark/benchmark-helper.coffee index fea602b9b..663ebd344 100644 --- a/benchmark/benchmark-helper.coffee +++ b/benchmark/benchmark-helper.coffee @@ -11,12 +11,14 @@ TextMateTheme = require 'text-mate-theme' require 'window' requireStylesheet "jasmine.css" +# Load TextMate bundles, which specs rely on (but not other packages) +atom.loadPackages(atom.getAvailableTextMateBundles()) + beforeEach -> - # don't load user configuration + # reset config after each benchmark; don't load or save from/to `config.json` window.config = new Config() spyOn(config, 'load') spyOn(config, 'save') - config.assignDefaults() keymap = new Keymap keymap.bindDefaultKeys() diff --git a/docs/configuring-and-extending.md b/docs/configuring-and-extending.md index 8423c6fd7..033618650 100644 --- a/docs/configuring-and-extending.md +++ b/docs/configuring-and-extending.md @@ -78,58 +78,6 @@ ConfigObserver = require 'config-observer' _.extend MyClass.prototype, ConfigObserver ``` -## Scoped Config Settings (Not Yet Implemented) - -Users and extension authors can provide language-specific behavior by employing -*scoped configuration keys*. By associating key values with a specific scope, -you can make Atom behave differently in different contexts. For example, if you -want Atom to auto-indent pasted text in some languages but not others, you can -give the `autoIndentPastedText` key a different value under a scope selector: - -```coffeescript -# in config.cson -editor: - autoIndentPastedText: true -scopes: - ".source.coffee": - editor: - autoIndentPastedText: false -``` - -Scope selectors are placed under the `scope` key at the top-level of the -configuration file. The values you specify for keys under a selector will -override global values in that specific scope. Any basic CSS 3 selector is -permitted, but you should leave out element names to make your keys accessible -outside the view layer. - -### Reading Scoped Config Settings - -Use the `config.inScope` method to the read keys with the most specific selector -match: - -```coffeescript -scope = [".source.coffee", ".meta.class.instance.constructor"] -config.inScope(scope).get "editor.lineComment" -``` - -Pass `.inScope` an array of scope descriptors, which describes a specific -element. This is frequently useful when you get the nested scopes for a position -in the buffer based on its syntax. You can also pass an actual DOM element -to use its nesting within the DOM as fodder for the scope selectors (†). - -```coffeescript -config.inScope(fuzzyFinder.miniEditor).get("editor.fontSize") -``` - -`observeConfig` can take a scope as its first argument: - -``` -@observeConfig scope, "editor.autoIndentPastedText", -> # ... -``` - -†: Matching DOM elements fits cleanly into this scheme, but I can't think of a - use for it currently. Let's keep it in the back of our minds though. - # Themes (Not Yet Implemented) ## Selecting A Theme @@ -162,7 +110,7 @@ folder, which can contain multiple stylesheets along with an optional package.json: ```json { - "stylesheets": ["core", "editor", "tree-view"] + "stylesheets": ["core.css", "editor.less", "tree-view.css"] } ``` diff --git a/script/copy-files-to-bundle b/script/copy-files-to-bundle index f850405a7..659c87e04 100755 --- a/script/copy-files-to-bundle +++ b/script/copy-files-to-bundle @@ -29,4 +29,4 @@ for COFFEE_FILE in $COFFEE_FILES; do done; # Copy non-coffee files into bundle -rsync --archive --recursive --exclude="src/**/*.coffee" src static vendor spec benchmark themes "$RESOUCES_PATH" +rsync --archive --recursive --exclude="src/**/*.coffee" src static vendor spec benchmark "$RESOUCES_PATH" diff --git a/spec/app/atom-spec.coffee b/spec/app/atom-spec.coffee index 501a0fa07..e40224d61 100644 --- a/spec/app/atom-spec.coffee +++ b/spec/app/atom-spec.coffee @@ -6,9 +6,9 @@ describe "the `atom` global", -> beforeEach -> rootView = new RootView - extension = require "package-with-extension" + extension = require "package-with-module" it "requires and activates the package's main module if it exists", -> - spyOn(rootView, 'activateExtension').andCallThrough() - atom.loadPackage("package-with-extension") - expect(rootView.activateExtension).toHaveBeenCalledWith(extension) + spyOn(rootView, 'activatePackage').andCallThrough() + atom.loadPackage("package-with-module") + expect(rootView.activatePackage).toHaveBeenCalledWith(extension) diff --git a/spec/app/language-mode-spec.coffee b/spec/app/language-mode-spec.coffee index e7bdc7ea9..340f4865c 100644 --- a/spec/app/language-mode-spec.coffee +++ b/spec/app/language-mode-spec.coffee @@ -262,19 +262,19 @@ describe "LanguageMode", -> expect(buffer.lineForRow(0)).toBe "/*body {" expect(buffer.lineForRow(1)).toBe " font-size: 1234px;*/" expect(buffer.lineForRow(2)).toBe " width: 110%;" - expect(buffer.lineForRow(3)).toBe "}" + expect(buffer.lineForRow(3)).toBe " font-weight: bold !important;" languageMode.toggleLineCommentsForBufferRows(2, 2) expect(buffer.lineForRow(0)).toBe "/*body {" expect(buffer.lineForRow(1)).toBe " font-size: 1234px;*/" expect(buffer.lineForRow(2)).toBe "/* width: 110%;*/" - expect(buffer.lineForRow(3)).toBe "}" + expect(buffer.lineForRow(3)).toBe " font-weight: bold !important;" languageMode.toggleLineCommentsForBufferRows(0, 1) expect(buffer.lineForRow(0)).toBe "body {" expect(buffer.lineForRow(1)).toBe " font-size: 1234px;" expect(buffer.lineForRow(2)).toBe "/* width: 110%;*/" - expect(buffer.lineForRow(3)).toBe "}" + expect(buffer.lineForRow(3)).toBe " font-weight: bold !important;" it "uncomments lines with leading whitespace", -> buffer.replaceLines(2, 2, " /*width: 110%;*/") diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 3043f2731..cb3267598 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -125,24 +125,24 @@ describe "RootView", -> expect(rootView.getTitle()).toBe 'untitled' describe ".serialize()", -> - it "absorbs exceptions that are thrown by extension serialize methods", -> + it "absorbs exceptions that are thrown by the package module's serialize methods", -> spyOn(console, 'error') - rootView.activateExtension( + rootView.activatePackage( name: "bad-egg" activate: -> serialize: -> throw new Error("I'm broken") ) - rootView.activateExtension( + rootView.activatePackage( name: "good-egg" activate: -> serialize: -> "I still get called" ) data = rootView.serialize() - expect(data.extensionStates['good-egg']).toBe "I still get called" - expect(data.extensionStates['bad-egg']).toBeUndefined() + expect(data.packageStates['good-egg']).toBe "I still get called" + expect(data.packageStates['bad-egg']).toBeUndefined() expect(console.error).toHaveBeenCalled() describe "focus", -> @@ -393,54 +393,49 @@ describe "RootView", -> rootView.focusNextPane() expect(view1.focus).toHaveBeenCalled() - describe "extensions", -> - extension = null + describe "packages", -> + packageModule = null beforeEach -> - extension = - name: 'extension' + packageModule = + name: 'package' deactivate: -> activate: jasmine.createSpy("activate") serialize: -> "it worked" - describe ".activateExtension(extension)", -> - it "calls activate on the extension", -> - rootView.activateExtension(extension) - expect(extension.activate).toHaveBeenCalledWith(rootView, undefined, undefined) + describe ".activatePackage(packageModule)", -> + it "calls activate on the package module", -> + rootView.activatePackage(packageModule) + expect(packageModule.activate).toHaveBeenCalledWith(rootView, undefined) - it "calls activate on the extension with its previous state", -> - rootView.activateExtension(extension) - extension.activate.reset() + it "calls activate on the package module with its previous state", -> + rootView.activatePackage(packageModule) + packageModule.activate.reset() newRootView = RootView.deserialize(rootView.serialize()) - newRootView.activateExtension(extension) - expect(extension.activate).toHaveBeenCalledWith(newRootView, "it worked", undefined) + newRootView.activatePackage(packageModule) + expect(packageModule.activate).toHaveBeenCalledWith(newRootView, "it worked") newRootView.remove() - it "calls activate on the extension with the config data", -> - config = {} - rootView.activateExtension(extension, config) - expect(extension.activate).toHaveBeenCalledWith(rootView, undefined, config) + it "throws an exception if the package module has no 'name' property", -> + expect(-> rootView.activatePackage({ activate: -> })).toThrow() - it "throws an exception if the extension has no 'name' property", -> - expect(-> rootView.activateExtension({ activate: -> })).toThrow() + describe ".deactivatePackage(packageModule)", -> + it "deactivates and removes the package module from the package module map", -> + rootView.activatePackage(packageModule) + expect(rootView.packageModules[packageModule.name]).toBeTruthy() + spyOn(packageModule, "deactivate").andCallThrough() + rootView.deactivatePackage(packageModule) + expect(packageModule.deactivate).toHaveBeenCalled() + expect(rootView.packageModules[packageModule.name]).toBeFalsy() - describe ".deactivateExtension(extension)", -> - it "deactivates and removes the extension from the extension list", -> - rootView.activateExtension(extension) - expect(rootView.extensions[extension.name]).toBeTruthy() - spyOn(extension, "deactivate").andCallThrough() - rootView.deactivateExtension(extension) - expect(extension.deactivate).toHaveBeenCalled() - expect(rootView.extensions[extension.name]).toBeFalsy() - - it "is called when the rootView is deactivated to deactivate all extensions", -> - rootView.activateExtension(extension) - spyOn(rootView, "deactivateExtension").andCallThrough() - spyOn(extension, "deactivate").andCallThrough() + it "is called when the rootView is deactivated to deactivate all packages", -> + rootView.activatePackage(packageModule) + spyOn(rootView, "deactivatePackage").andCallThrough() + spyOn(packageModule, "deactivate").andCallThrough() rootView.deactivate() - expect(rootView.deactivateExtension).toHaveBeenCalled() - expect(extension.deactivate).toHaveBeenCalled() + expect(rootView.deactivatePackage).toHaveBeenCalled() + expect(packageModule.deactivate).toHaveBeenCalled() describe "keymap wiring", -> commandHandler = null diff --git a/spec/app/syntax-spec.coffee b/spec/app/syntax-spec.coffee new file mode 100644 index 000000000..c3117fb46 --- /dev/null +++ b/spec/app/syntax-spec.coffee @@ -0,0 +1,19 @@ +describe "the `syntax` global", -> + describe ".getProperty(scopeDescriptor)", -> + it "returns the property with the most specific scope selector", -> + syntax.addProperties(".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42) + syntax.addProperties(".source .string.quoted.double", foo: bar: baz: 22) + syntax.addProperties(".source", foo: bar: baz: 11) + syntax.addProperties(foo: bar: baz: 1) + + expect(syntax.getProperty([".source.coffee", ".string.quoted.double.coffee"], "foo.bar.baz")).toBe 42 + expect(syntax.getProperty([".source.js", ".string.quoted.double.js"], "foo.bar.baz")).toBe 22 + expect(syntax.getProperty([".source.js", ".variable.assignment.js"], "foo.bar.baz")).toBe 11 + expect(syntax.getProperty([".text"], "foo.bar.baz")).toBe 1 + + it "favors the most recently added properties in the event of a specificity tie", -> + syntax.addProperties(".source.coffee .string.quoted.single", foo: bar: baz: 42) + syntax.addProperties(".source.coffee .string.quoted.double", foo: bar: baz: 22) + + expect(syntax.getProperty([".source.coffee", ".string.quoted.single"], "foo.bar.baz")).toBe 42 + expect(syntax.getProperty([".source.coffee", ".string.quoted.single.double"], "foo.bar.baz")).toBe 22 diff --git a/spec/app/text-mate-bundle-spec.coffee b/spec/app/text-mate-bundle-spec.coffee index 663db783f..c67bc50cc 100644 --- a/spec/app/text-mate-bundle-spec.coffee +++ b/spec/app/text-mate-bundle-spec.coffee @@ -2,14 +2,6 @@ fs = require('fs') TextMateBundle = require 'text-mate-bundle' describe "TextMateBundle", -> - describe ".getPreferenceInScope(scope, preferenceName)", -> - it "returns the preference by the given name in the given scope or undefined if there isn't one", -> - expect(TextMateBundle.getPreferenceInScope('source.coffee', 'decreaseIndentPattern')).toBe '^\\s*(\\}|\\]|else|catch|finally)$' - expect(TextMateBundle.getPreferenceInScope('source.coffee', 'shellVariables')).toBeDefined() - - it "returns the preference by the given name in the given scope for a scope registered via a comma-separated list of scopes", -> - expect(TextMateBundle.getPreferenceInScope('source.objc++', 'shellVariables')).toBeDefined() - describe ".getPreferencesByScopeSelector()", -> it "logs warning, but does not raise errors if a preference can't be parsed", -> bundlePath = fs.join(require.resolve('fixtures'), "test.tmbundle") diff --git a/spec/app/text-mate-theme-spec.coffee b/spec/app/text-mate-theme-spec.coffee index d040a30df..0ea739f7e 100644 --- a/spec/app/text-mate-theme-spec.coffee +++ b/spec/app/text-mate-theme-spec.coffee @@ -1,29 +1,17 @@ fs = require 'fs' plist = require 'plist' TextMateTheme = require 'text-mate-theme' +Theme = require 'theme' describe "TextMateTheme", -> - theme = null + [theme, themePath] = [] + beforeEach -> - theme = TextMateTheme.getTheme('Twilight') + themePath = require.resolve(fs.join('fixtures', 'test.tmTheme')) + [theme] = Theme.load(themePath) - describe "@getNames()", -> - it "returns an array of available theme names", -> - names = TextMateTheme.getNames() - expect(names).toContain("Twilight") - expect(names).toContain("Blackboard") - - describe "@activate(name)", -> - it "activates a theme by name", -> - spyOn theme, 'activate' - TextMateTheme.activate('Twilight') - expect(theme.activate).toHaveBeenCalled() - - describe ".activate()", -> - it "applies the theme's stylesheet to the current window", -> - spyOn window, 'applyStylesheet' - theme.activate() - expect(window.applyStylesheet).toHaveBeenCalledWith(theme.name, theme.getStylesheet()) + afterEach -> + theme.deactivate() describe ".getRulesets()", -> rulesets = null diff --git a/spec/app/theme-spec.coffee b/spec/app/theme-spec.coffee new file mode 100644 index 000000000..5990aec2c --- /dev/null +++ b/spec/app/theme-spec.coffee @@ -0,0 +1,48 @@ +$ = require 'jquery' +fs = require 'fs' +Theme = require 'theme' + +describe "@load(name)", -> + themes = null + + beforeEach -> + $("#jasmine-content").append $("
") + + afterEach -> + theme.deactivate() for theme in themes + + describe "TextMateTheme", -> + it "applies the theme's stylesheet to the current window", -> + expect($(".editor").css("background-color")).not.toBe("rgb(20, 20, 20)") + + themePath = require.resolve(fs.join('fixtures', 'test.tmTheme')) + themes = Theme.load(themePath) + expect($(".editor").css("background-color")).toBe("rgb(20, 20, 20)") + + describe "AtomTheme", -> + it "Loads and applies css from package.json in the correct order", -> + expect($(".editor").css("padding-top")).not.toBe("101px") + expect($(".editor").css("padding-right")).not.toBe("102px") + expect($(".editor").css("padding-bottom")).not.toBe("103px") + + themePath = require.resolve(fs.join('fixtures', 'test-atom-theme')) + themes = Theme.load(themePath) + expect($(".editor").css("padding-top")).toBe("101px") + expect($(".editor").css("padding-right")).toBe("102px") + expect($(".editor").css("padding-bottom")).toBe("103px") + + describe "when name is an array of themes", -> + it "loads all themes in order", -> + firstThemePath = require.resolve(fs.join('fixtures', 'test.tmTheme')) + secondThemePath = require.resolve(fs.join('fixtures', 'test-atom-theme')) + + expect($(".editor").css("padding-top")).not.toBe("101px") + expect($(".editor").css("padding-right")).not.toBe("102px") + expect($(".editor").css("padding-bottom")).not.toBe("103px") + expect($(".editor").css("color")).not.toBe("rgb(0, 255, 0)") + + themes = Theme.load([firstThemePath, secondThemePath]) + expect($(".editor").css("padding-top")).toBe("101px") + expect($(".editor").css("padding-right")).toBe("102px") + expect($(".editor").css("padding-bottom")).toBe("103px") + expect($(".editor").css("color")).toBe("rgb(255, 0, 0)") diff --git a/spec/app/window-spec.coffee b/spec/app/window-spec.coffee index 05e582d34..9cd9103b2 100644 --- a/spec/app/window-spec.coffee +++ b/spec/app/window-spec.coffee @@ -48,6 +48,16 @@ describe "Window", -> requireStylesheet('atom.css') expect($('head style').length).toBe lengthBefore + 1 + describe ".disableStyleSheet(path)", -> + it "removes styling applied by given stylesheet path", -> + cssPath = require.resolve(fs.join("fixtures", "css.css")) + + expect($(document.body).css('font-weight')).not.toBe("bold") + requireStylesheet(cssPath) + expect($(document.body).css('font-weight')).toBe("bold") + removeStylesheet(cssPath) + expect($(document.body).css('font-weight')).not.toBe("bold") + describe "before the window is unloaded", -> it "saves the serialized state of the root view to the atom object so it can be rehydrated after reload", -> expect(atom.getRootViewStateForPath(window.rootView.project.getPath())).toBeUndefined() diff --git a/spec/fixtures/css.css b/spec/fixtures/css.css index 52b8f56c6..d5ae97e68 100644 --- a/spec/fixtures/css.css +++ b/spec/fixtures/css.css @@ -1,4 +1,5 @@ body { font-size: 1234px; width: 110%; + font-weight: bold !important; } diff --git a/spec/fixtures/packages/package-with-extension/index.coffee b/spec/fixtures/packages/package-with-module/index.coffee similarity index 100% rename from spec/fixtures/packages/package-with-extension/index.coffee rename to spec/fixtures/packages/package-with-module/index.coffee diff --git a/spec/fixtures/test-atom-theme/first.css b/spec/fixtures/test-atom-theme/first.css new file mode 100644 index 000000000..f9af1a345 --- /dev/null +++ b/spec/fixtures/test-atom-theme/first.css @@ -0,0 +1,7 @@ +.editor { + padding-top: 101px; + padding-right: 101px; + padding-bottom: 101px; + + color: red; +} \ No newline at end of file diff --git a/spec/fixtures/test-atom-theme/last.css b/spec/fixtures/test-atom-theme/last.css new file mode 100644 index 000000000..c0cface8c --- /dev/null +++ b/spec/fixtures/test-atom-theme/last.css @@ -0,0 +1,5 @@ +.editor { +/* padding-top: 103px; + padding-right: 103px;*/ + padding-bottom: 103px; +} \ No newline at end of file diff --git a/spec/fixtures/test-atom-theme/package.json b/spec/fixtures/test-atom-theme/package.json new file mode 100644 index 000000000..9add36774 --- /dev/null +++ b/spec/fixtures/test-atom-theme/package.json @@ -0,0 +1,3 @@ +{ + "stylesheets": ["first.css", "second.css", "last.css"] +} \ No newline at end of file diff --git a/spec/fixtures/test-atom-theme/second.css b/spec/fixtures/test-atom-theme/second.css new file mode 100644 index 000000000..3ddf03add --- /dev/null +++ b/spec/fixtures/test-atom-theme/second.css @@ -0,0 +1,5 @@ +.editor { +/* padding-top: 102px;*/ + padding-right: 102px; + padding-bottom: 102px; +} \ No newline at end of file diff --git a/spec/fixtures/test.tmTheme b/spec/fixtures/test.tmTheme new file mode 100644 index 000000000..a83f7ecbb --- /dev/null +++ b/spec/fixtures/test.tmTheme @@ -0,0 +1,514 @@ + + + + + author + Michael Sheets + name + Twilight + settings + + + settings + + background + #141414 + caret + #A7A7A7 + foreground + #F8F8F8 + invisibles + #FFFFFF40 + lineHighlight + #FFFFFF08 + selection + #DDF0FF33 + + + + name + Comment + scope + comment + settings + + fontStyle + italic + foreground + #5F5A60 + + + + name + Constant + scope + constant + settings + + foreground + #CF6A4C + + + + name + Entity + scope + entity + settings + + fontStyle + + foreground + #9B703F + + + + name + Keyword + scope + keyword + settings + + fontStyle + + foreground + #CDA869 + + + + name + Storage + scope + storage + settings + + fontStyle + + foreground + #F9EE98 + + + + name + String + scope + string + settings + + fontStyle + + foreground + #8F9D6A + + + + name + Support + scope + support + settings + + fontStyle + + foreground + #9B859D + + + + name + Variable + scope + variable + settings + + foreground + #7587A6 + + + + name + Invalid – Deprecated + scope + invalid.deprecated + settings + + fontStyle + italic underline + foreground + #D2A8A1 + + + + name + Invalid – Illegal + scope + invalid.illegal + settings + + background + #562D56BF + foreground + #F8F8F8 + + + + name + ----------------------------------- + settings + + + + name + ♦ Embedded Source + scope + text source + settings + + background + #B0B3BA14 + + + + name + ♦ Embedded Source (Bright) + scope + text.html.ruby source + settings + + background + #B1B3BA21 + + + + name + ♦ Entity inherited-class + scope + entity.other.inherited-class + settings + + fontStyle + italic + foreground + #9B5C2E + + + + name + ♦ String embedded-source + scope + string source + settings + + fontStyle + + foreground + #DAEFA3 + + + + name + ♦ String constant + scope + string constant + settings + + foreground + #DDF2A4 + + + + name + ♦ String.regexp + scope + string.regexp + settings + + fontStyle + + foreground + #E9C062 + + + + name + ♦ String.regexp.«special» + scope + string.regexp constant.character.escape, string.regexp source.ruby.embedded, string.regexp string.regexp.arbitrary-repitition + settings + + foreground + #CF7D34 + + + + name + ♦ String variable + scope + string variable + settings + + foreground + #8A9A95 + + + + name + ♦ Support.function + scope + support.function + settings + + fontStyle + + foreground + #DAD085 + + + + name + ♦ Support.constant + scope + support.constant + settings + + fontStyle + + foreground + #CF6A4C + + + + name + c C/C++ Preprocessor Line + scope + meta.preprocessor.c + settings + + foreground + #8996A8 + + + + name + c C/C++ Preprocessor Directive + scope + meta.preprocessor.c keyword + settings + + foreground + #AFC4DB + + + + name + ✘ Doctype/XML Processing + scope + meta.tag.sgml.doctype, meta.tag.sgml.doctype entity, meta.tag.sgml.doctype string, meta.tag.preprocessor.xml, meta.tag.preprocessor.xml entity, meta.tag.preprocessor.xml string + settings + + foreground + #494949 + + + + name + ✘ Meta.tag.«all» + scope + declaration.tag, declaration.tag entity, meta.tag, meta.tag entity + settings + + foreground + #AC885B + + + + name + ✘ Meta.tag.inline + scope + declaration.tag.inline, declaration.tag.inline entity, source entity.name.tag, source entity.other.attribute-name, meta.tag.inline, meta.tag.inline entity + settings + + foreground + #E0C589 + + + + name + § css tag-name + scope + meta.selector.css entity.name.tag + settings + + foreground + #CDA869 + + + + name + § css:pseudo-class + scope + meta.selector.css entity.other.attribute-name.tag.pseudo-class + settings + + foreground + #8F9D6A + + + + name + § css#id + scope + meta.selector.css entity.other.attribute-name.id + settings + + foreground + #8B98AB + + + + name + § css.class + scope + meta.selector.css entity.other.attribute-name.class + settings + + foreground + #9B703F + + + + name + § css property-name: + scope + support.type.property-name.css + settings + + foreground + #C5AF75 + + + + name + § css property-value; + scope + meta.property-group support.constant.property-value.css, meta.property-value support.constant.property-value.css + settings + + foreground + #F9EE98 + + + + name + § css @at-rule + scope + meta.preprocessor.at-rule keyword.control.at-rule + settings + + foreground + #8693A5 + + + + name + § css additional-constants + scope + meta.property-value support.constant.named-color.css, meta.property-value constant + settings + + foreground + #CA7840 + + + + name + § css constructor.argument + scope + meta.constructor.argument.css + settings + + foreground + #8F9D6A + + + + name + ⎇ diff.header + scope + meta.diff, meta.diff.header, meta.separator + settings + + background + #0E2231 + fontStyle + italic + foreground + #F8F8F8 + + + + name + ⎇ diff.deleted + scope + markup.deleted + settings + + background + #420E09 + foreground + #F8F8F8 + + + + name + ⎇ diff.changed + scope + markup.changed + settings + + background + #4A410D + foreground + #F8F8F8 + + + + name + ⎇ diff.inserted + scope + markup.inserted + settings + + background + #253B22 + foreground + #F8F8F8 + + + + name + Markup: List + scope + markup.list + settings + + foreground + #F9EE98 + + + + name + Markup: Heading + scope + markup.heading + settings + + foreground + #CF6A4C + + + + uuid + 766026CB-703D-4610-B070-8DE07D967C5F + + diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 33a372f02..5de43bccc 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -19,15 +19,17 @@ requireStylesheet "jasmine.css" require.paths.unshift(require.resolve('fixtures/packages')) +# Load TextMate bundles, which specs rely on (but not other packages) +atom.loadPackages(atom.getAvailableTextMateBundles()) + beforeEach -> window.fixturesProject = new Project(require.resolve('fixtures')) window.resetTimeouts() - # don't load or save user configuration + # reset config after each spec; don't load or save from/to `config.json` window.config = new Config() spyOn(config, 'load') spyOn(config, 'save') - config.assignDefaults() config.set "editor.fontSize", 16 # make editor display updates synchronous diff --git a/spec/stdlib/child-process-spec.coffee b/spec/stdlib/child-process-spec.coffee index 4c389f087..15b9be963 100644 --- a/spec/stdlib/child-process-spec.coffee +++ b/spec/stdlib/child-process-spec.coffee @@ -121,7 +121,7 @@ describe 'Child Processes', -> cmd = "for i in {1..20000}; do echo $RANDOM; done" options = stdout: (data) -> output.push(data) - stderr: (data) -> console.log data.length + stderr: (data) -> ChildProcess.exec(cmd, options) diff --git a/src/app/atom-package.coffee b/src/app/atom-package.coffee new file mode 100644 index 000000000..11d322461 --- /dev/null +++ b/src/app/atom-package.coffee @@ -0,0 +1,17 @@ +Package = require 'package' +fs = require 'fs' + +module.exports = +class AtomPackage extends Package + constructor: -> + super + @module = require(@path) + @module.name = @name + + load: -> + try + rootView.activatePackage(@module) + extensionKeymapPath = require.resolve(fs.join(@name, "src/keymap"), verifyExistence: false) + require extensionKeymapPath if fs.exists(extensionKeymapPath) + catch e + console.error "Failed to load package named '#{name}'", e.stack diff --git a/src/app/atom-theme.coffee b/src/app/atom-theme.coffee new file mode 100644 index 000000000..531bb34ec --- /dev/null +++ b/src/app/atom-theme.coffee @@ -0,0 +1,11 @@ +fs = require 'fs' +Theme = require 'theme' + +module.exports = +class AtomTheme extends Theme + constructor: (@path) -> + super + json = fs.read(fs.join(path, "package.json")) + for stylesheetName in JSON.parse(json).stylesheets + stylesheetPath = fs.join(@path, stylesheetName) + @stylesheets[stylesheetPath] = fs.read(stylesheetPath) diff --git a/src/app/atom.coffee b/src/app/atom.coffee index fb8a559c1..82a406bd7 100644 --- a/src/app/atom.coffee +++ b/src/app/atom.coffee @@ -1,5 +1,8 @@ +TextMateBundle = require("text-mate-bundle") fs = require 'fs' _ = require 'underscore' +Package = require 'package' +TextMatePackage = require 'text-mate-package' messageIdCounter = 1 originalSendMessageToBrowserProcess = atom.sendMessageToBrowserProcess @@ -9,18 +12,25 @@ _.extend atom, pendingBrowserProcessCallbacks: {} + getAvailablePackages: -> + allPackageNames = [] + for packageDirPath in config.packageDirPaths + packageNames = fs.list(packageDirPath) + .filter((packagePath) -> fs.isDirectory(packagePath)) + .map((packagePath) -> fs.base(packagePath)) + allPackageNames.push(packageNames...) + _.unique(allPackageNames) + + getAvailableTextMateBundles: -> + @getAvailablePackages().filter (packageName) => TextMatePackage.testName(packageName) + + loadPackages: (packageNames=@getAvailablePackages()) -> + disabledPackages = config.get("core.disabledPackages") ? [] + for packageName in packageNames + @loadPackage(packageName) unless _.contains(disabledPackages, packageName) + loadPackage: (name) -> - try - packagePath = require.resolve(name, verifyExistence: false) - throw new Error("No package found named '#{name}'") unless packagePath - packagePath = fs.directory(packagePath) - extension = require(packagePath) - extension.name = name - rootView.activateExtension(extension) - extensionKeymapPath = require.resolve(fs.join(name, "src/keymap"), verifyExistence: false) - require extensionKeymapPath if fs.exists(extensionKeymapPath) - catch e - console.error "Failed to load package named '#{name}'", e.stack + Package.forName(name).load() open: (args...) -> @sendMessageToBrowserProcess('open', args) @@ -85,4 +95,3 @@ _.extend atom, if name is 'reply' [messageId, callbackIndex] = data.shift() @pendingBrowserProcessCallbacks[messageId]?[callbackIndex]?(data...) - diff --git a/src/app/config.coffee b/src/app/config.coffee index f55987d46..0e3d0b035 100644 --- a/src/app/config.coffee +++ b/src/app/config.coffee @@ -1,11 +1,17 @@ fs = require 'fs' _ = require 'underscore' EventEmitter = require 'event-emitter' +{$$} = require 'space-pen' +jQuery = require 'jquery' +Specificity = require 'specificity' +Theme = require 'theme' configDirPath = fs.absolute("~/.atom") configJsonPath = fs.join(configDirPath, "config.json") userInitScriptPath = fs.join(configDirPath, "atom.coffee") +bundledThemesDirPath = fs.join(resourcePath, "themes") bundledPackagesDirPath = fs.join(resourcePath, "src/packages") +userThemesDirPath = fs.join(configDirPath, "themes") userPackagesDirPath = fs.join(configDirPath, "packages") require.paths.unshift userPackagesDirPath @@ -13,46 +19,31 @@ require.paths.unshift userPackagesDirPath module.exports = class Config configDirPath: configDirPath + themeDirPaths: [userThemesDirPath, bundledThemesDirPath] + packageDirPaths: [userPackagesDirPath, bundledPackagesDirPath] settings: null + constructor: -> + @settings = + core: _.clone(require('root-view').configDefaults) + editor: _.clone(require('editor').configDefaults) + load: -> - @settings = {} @loadUserConfig() - @assignDefaults() - @loadPackages() @requireUserInitScript() + atom.loadPackages() + Theme.load(config.get("core.theme") ? 'IR_Black') loadUserConfig: -> if fs.exists(configJsonPath) userConfig = JSON.parse(fs.read(configJsonPath)) _.extend(@settings, userConfig) - assignDefaults: -> - @settings ?= {} - @setDefaults "core", require('root-view').configDefaults - @setDefaults "editor", require('editor').configDefaults - - getAvailablePackages: -> - availablePackages = - fs.list(bundledPackagesDirPath) - .concat(fs.list(userPackagesDirPath)).map (path) -> fs.base(path) - _.unique(availablePackages) - - loadPackages: -> - disabledPackages = config.get("core.disabledPackages") ? [] - for packageName in @getAvailablePackages() - unless _.contains disabledPackages, packageName - atom.loadPackage(packageName) - get: (keyPath) -> - keys = @keysForKeyPath(keyPath) - value = @settings - for key in keys - break unless value = value[key] - value + _.valueForKeyPath(@settings, keyPath) set: (keyPath, value) -> - keys = @keysForKeyPath(keyPath) + keys = keyPath.split('.') hash = @settings while keys.length > 1 key = keys.shift() @@ -64,7 +55,7 @@ class Config value setDefaults: (keyPath, defaults) -> - keys = @keysForKeyPath(keyPath) + keys = keyPath.split('.') hash = @settings for key in keys hash[key] ?= {} @@ -73,12 +64,6 @@ class Config _.defaults hash, defaults @update() - keysForKeyPath: (keyPath) -> - if typeof keyPath is 'string' - keyPath.split(".") - else - new Array(keyPath...) - observe: (keyPath, callback) -> value = @get(keyPath) previousValue = _.clone(value) diff --git a/src/app/keymap.coffee b/src/app/keymap.coffee index 04ec03b8a..21710495a 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -3,7 +3,6 @@ _ = require 'underscore' fs = require 'fs' BindingSet = require 'binding-set' -Specificity = require 'specificity' module.exports = class Keymap diff --git a/src/app/language-mode.coffee b/src/app/language-mode.coffee index 1305f9191..53fcfada7 100644 --- a/src/app/language-mode.coffee +++ b/src/app/language-mode.coffee @@ -67,14 +67,14 @@ class LanguageMode toggleLineCommentsForBufferRows: (start, end) -> scopes = @editSession.scopesForBufferPosition([start, 0]) - return unless commentStartString = TextMateBundle.lineCommentStartStringForScope(scopes[0]) + return unless commentStartString = syntax.getProperty(scopes, "editor.commentStart") buffer = @editSession.buffer commentStartRegexString = _.escapeRegExp(commentStartString).replace(/(\s+)$/, '($1)?') commentStartRegex = new OnigRegExp("^(\\s*)(#{commentStartRegexString})") shouldUncomment = commentStartRegex.test(buffer.lineForRow(start)) - if commentEndString = TextMateBundle.lineCommentEndStringForScope(scopes[0]) + if commentEndString = syntax.getProperty(scopes, "editor.commentEnd") if shouldUncomment commentEndRegexString = _.escapeRegExp(commentEndString).replace(/^(\s+)/, '($1)?') commentEndRegex = new OnigRegExp("(#{commentEndRegexString})(\\s*)$") @@ -119,7 +119,7 @@ class LanguageMode continue if @editSession.isBufferRowBlank(row) indentation = @editSession.indentationForBufferRow(row) if indentation <= startIndentLevel - includeRowInFold = indentation == startIndentLevel and TextMateBundle.foldEndRegexForScope(@grammar, scopes[0]).search(@editSession.lineForBufferRow(row)) + includeRowInFold = indentation == startIndentLevel and @foldEndRegexForScopes(scopes).search(@editSession.lineForBufferRow(row)) foldEndRow = row if includeRowInFold break @@ -130,7 +130,7 @@ class LanguageMode suggestedIndentForBufferRow: (bufferRow) -> currentIndentLevel = @editSession.indentationForBufferRow(bufferRow) scopes = @editSession.scopesForBufferPosition([bufferRow, 0]) - return currentIndentLevel unless increaseIndentPattern = TextMateBundle.indentRegexForScope(scopes[0]) + return currentIndentLevel unless increaseIndentRegex = @increaseIndentRegexForScopes(scopes) currentLine = @buffer.lineForRow(bufferRow) precedingRow = @buffer.previousNonBlankRow(bufferRow) @@ -139,10 +139,10 @@ class LanguageMode precedingLine = @buffer.lineForRow(precedingRow) desiredIndentLevel = @editSession.indentationForBufferRow(precedingRow) - desiredIndentLevel += 1 if increaseIndentPattern.test(precedingLine) + desiredIndentLevel += 1 if increaseIndentRegex.test(precedingLine) - return desiredIndentLevel unless decreaseIndentPattern = TextMateBundle.outdentRegexForScope(scopes[0]) - desiredIndentLevel -= 1 if decreaseIndentPattern.test(currentLine) + return desiredIndentLevel unless decreaseIndentRegex = @decreaseIndentRegexForScopes(scopes) + desiredIndentLevel -= 1 if decreaseIndentRegex.test(currentLine) Math.max(desiredIndentLevel, currentIndentLevel) @@ -159,32 +159,44 @@ class LanguageMode precedingLine = @editSession.lineForBufferRow(precedingRow) scopes = @editSession.scopesForBufferPosition([precedingRow, Infinity]) - increaseIndentPattern = TextMateBundle.indentRegexForScope(scopes[0]) - return unless increaseIndentPattern + increaseIndentRegex = @increaseIndentRegexForScopes(scopes) + return unless increaseIndentRegex currentIndentLevel = @editSession.indentationForBufferRow(bufferRow) desiredIndentLevel = @editSession.indentationForBufferRow(precedingRow) - desiredIndentLevel += 1 if increaseIndentPattern.test(precedingLine) + desiredIndentLevel += 1 if increaseIndentRegex.test(precedingLine) if desiredIndentLevel > currentIndentLevel @editSession.setIndentationForBufferRow(bufferRow, desiredIndentLevel) autoDecreaseIndentForBufferRow: (bufferRow) -> scopes = @editSession.scopesForBufferPosition([bufferRow, 0]) - increaseIndentPattern = TextMateBundle.indentRegexForScope(scopes[0]) - decreaseIndentPattern = TextMateBundle.outdentRegexForScope(scopes[0]) - return unless increaseIndentPattern and decreaseIndentPattern + increaseIndentRegex = @increaseIndentRegexForScopes(scopes) + decreaseIndentRegex = @decreaseIndentRegexForScopes(scopes) + return unless increaseIndentRegex and decreaseIndentRegex line = @buffer.lineForRow(bufferRow) - return unless decreaseIndentPattern.test(line) + return unless decreaseIndentRegex.test(line) currentIndentLevel = @editSession.indentationForBufferRow(bufferRow) precedingRow = @buffer.previousNonBlankRow(bufferRow) precedingLine = @buffer.lineForRow(precedingRow) desiredIndentLevel = @editSession.indentationForBufferRow(precedingRow) - desiredIndentLevel -= 1 unless increaseIndentPattern.test(precedingLine) + desiredIndentLevel -= 1 unless increaseIndentRegex.test(precedingLine) if desiredIndentLevel < currentIndentLevel @editSession.setIndentationForBufferRow(bufferRow, desiredIndentLevel) tokenizeLine: (line, stack, firstLine) -> {tokens, stack} = @grammar.tokenizeLine(line, stack, firstLine) + + increaseIndentRegexForScopes: (scopes) -> + if increaseIndentPattern = syntax.getProperty(scopes, 'editor.increaseIndentPattern') + new OnigRegExp(increaseIndentPattern) + + decreaseIndentRegexForScopes: (scopes) -> + if decreaseIndentPattern = syntax.getProperty(scopes, 'editor.decreaseIndentPattern') + new OnigRegExp(decreaseIndentPattern) + + foldEndRegexForScopes: (scopes) -> + if foldEndPattern = syntax.getProperty(scopes, 'editor.foldEndPattern') + new OnigRegExp(foldEndPattern) diff --git a/src/app/package.coffee b/src/app/package.coffee new file mode 100644 index 000000000..88fcf7ef1 --- /dev/null +++ b/src/app/package.coffee @@ -0,0 +1,21 @@ +fs = require 'fs' + +module.exports = +class Package + @forName: (name) -> + AtomPackage = require 'atom-package' + TextMatePackage = require 'text-mate-package' + + if TextMatePackage.testName(name) + new TextMatePackage(name) + else + new AtomPackage(name) + + constructor: (@name) -> + @path = require.resolve(@name, verifyExistence: false) + throw new Error("No package found named '#{@name}'") unless @path + @path = fs.directory(@path) unless fs.isDirectory(@path) + + load: -> + for { selector, properties } in @getScopedProperties() + syntax.addProperties(selector, properties) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index f25e97739..2961ef76b 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -10,7 +10,6 @@ Project = require 'project' Pane = require 'pane' PaneColumn = require 'pane-column' PaneRow = require 'pane-row' -TextMateTheme = require 'text-mate-theme' module.exports = class RootView extends View @@ -24,25 +23,23 @@ class RootView extends View @div id: 'vertical', outlet: 'vertical', => @div id: 'panes', outlet: 'panes' - @deserialize: ({ projectPath, panesViewState, extensionStates }) -> - rootView = new RootView(projectPath, extensionStates: extensionStates, suppressOpen: true) + @deserialize: ({ projectPath, panesViewState, packageStates }) -> + rootView = new RootView(projectPath, packageStates: packageStates, suppressOpen: true) rootView.setRootPane(rootView.deserializeView(panesViewState)) if panesViewState rootView - extensions: null - extensionStates: null + packageModules: null + packageStates: null title: null - initialize: (pathToOpen, { @extensionStates, suppressOpen } = {}) -> + initialize: (pathToOpen, { @packageStates, suppressOpen } = {}) -> window.rootView = this - @extensionStates ?= {} - @extensions = {} + @packageStates ?= {} + @packageModules = {} @project = new Project(pathToOpen) config.load() - TextMateTheme.activate(config.get("core.theme") ? 'IR_Black') - @handleEvents() if pathToOpen @@ -53,7 +50,7 @@ class RootView extends View serialize: -> projectPath: @project?.getPath() panesViewState: @panes.children().view()?.serialize() - extensionStates: @serializeExtensions() + packageStates: @serializePackages() handleFocus: (e) -> if @getActiveEditor() @@ -99,14 +96,14 @@ class RootView extends View afterAttach: (onDom) -> @focus() if onDom - serializeExtensions: -> - extensionStates = {} - for name, extension of @extensions + serializePackages: -> + packageStates = {} + for name, packageModule of @packageModules try - extensionStates[name] = extension.serialize?() + packageStates[name] = packageModule.serialize?() catch e - console?.error("Exception serializing '#{name}' extension\n", e.stack) - extensionStates + console?.error("Exception serializing '#{name}' package's module\n", e.stack) + packageStates deserializeView: (viewState) -> switch viewState.viewClass @@ -115,18 +112,18 @@ class RootView extends View when 'PaneColumn' then PaneColumn.deserialize(viewState, this) when 'Editor' then Editor.deserialize(viewState, this) - activateExtension: (extension, config) -> - throw new Error("Trying to activate an extension with no name attribute") unless extension.name? - @extensions[extension.name] = extension - extension.activate(this, @extensionStates[extension.name], config) + activatePackage: (packageModule) -> + throw new Error("Trying to activate a package module with no name attribute") unless packageModule.name? + @packageModules[packageModule.name] = packageModule + packageModule.activate(this, @packageStates[packageModule.name]) - deactivateExtension: (extension) -> - extension.deactivate?() - delete @extensions[extension.name] + deactivatePackage: (packageModule) -> + packageModule.deactivate?() + delete @packageModules[packageModule.name] deactivate: -> atom.setRootViewStateForPath(@project.getPath(), @serialize()) - @deactivateExtension(extension) for name, extension of @extensions + @deactivatePackage(packageModule) for name, packageModule of @packageModules @remove() open: (path, options = {}) -> diff --git a/src/app/syntax.coffee b/src/app/syntax.coffee new file mode 100644 index 000000000..5a71da15e --- /dev/null +++ b/src/app/syntax.coffee @@ -0,0 +1,70 @@ +_ = require 'underscore' +jQuery = require 'jquery' +Specificity = require 'specificity' +{$$} = require 'space-pen' + +module.exports = +class Syntax + constructor: -> + @globalProperties = {} + @scopedPropertiesIndex = 0 + @scopedProperties = [] + @propertiesBySelector = {} + + addProperties: (args...) -> + selector = args.shift() if args.length > 1 + properties = args.shift() + + if selector + @scopedProperties.unshift( + selector: selector, + properties: properties, + specificity: Specificity(selector), + index: @scopedPropertiesIndex++ + ) + else + _.extend(@globalProperties, properties) + + getProperty: (scope, keyPath) -> + for object in @propertiesForScope(scope, keyPath) + value = _.valueForKeyPath(object, keyPath) + return value if value? + undefined + + propertiesForScope: (scope, keyPath) -> + matchingProperties = [] + candidates = @scopedProperties.filter ({properties}) -> _.valueForKeyPath(properties, keyPath)? + if candidates.length + element = @buildScopeElement(scope) + while element + matchingProperties.push(@matchingPropertiesForElement(element, candidates)...) + element = element.parentNode + matchingProperties.concat([@globalProperties]) + + matchingPropertiesForElement: (element, candidates) -> + matchingScopedProperties = candidates.filter ({selector}) -> + jQuery.find.matchesSelector(element, selector) + matchingScopedProperties.sort (a, b) -> + if a.specificity == b.specificity + b.index - a.index + else + b.specificity - a.specificity + _.pluck matchingScopedProperties, 'properties' + + buildScopeElement: (scope) -> + scope = new Array(scope...) + element = $$ -> + elementsForRemainingScopes = => + classString = scope.shift() + classes = classString.replace(/^\./, '').replace(/\./g, ' ') + if scope.length + @div class: classes, elementsForRemainingScopes + else + @div class: classes + elementsForRemainingScopes() + + deepestChild = element.find(":not(:has(*))") + if deepestChild.length + deepestChild[0] + else + element[0] diff --git a/src/app/text-mate-bundle.coffee b/src/app/text-mate-bundle.coffee index 6ead4ccff..5d31499eb 100644 --- a/src/app/text-mate-bundle.coffee +++ b/src/app/text-mate-bundle.coffee @@ -10,18 +10,10 @@ class TextMateBundle @grammarsByFileType: {} @grammarsByScopeName: {} @preferencesByScopeSelector: {} - @bundles: [] @grammars: [] - @loadAll: -> - localBundlePath = fs.join(config.configDirPath, "bundles") - localBundles = fs.list(localBundlePath) if fs.exists(localBundlePath) - - for bundlePath in localBundles ? [] - @registerBundle(new TextMateBundle(bundlePath)) - - @registerBundle: (bundle)-> - @bundles.push(bundle) + @load: (name)-> + bundle = new TextMateBundle(require.resolve(name)) for scopeSelector, preferences of bundle.getPreferencesByScopeSelector() if @preferencesByScopeSelector[scopeSelector]? @@ -35,6 +27,8 @@ class TextMateBundle @grammarsByFileType[fileType] = grammar @grammarsByScopeName[grammar.scopeName] = grammar + bundle + @grammarForFilePath: (filePath) -> return @grammarsByFileType["txt"] unless filePath @@ -59,34 +53,6 @@ class TextMateBundle @grammarForScopeName: (scopeName) -> @grammarsByScopeName[scopeName] - @getPreferenceInScope: (scopeSelector, preferenceName) -> - @preferencesByScopeSelector[scopeSelector]?[preferenceName] - - @getPreferenceValueInScope: (scope, preferenceName, valueName) -> - values = @getPreferenceInScope(scope, preferenceName) - (_.find values, ({name}) -> name is valueName)?['value'] - - @lineCommentStartStringForScope: (scope) -> - @getPreferenceValueInScope(scope, 'shellVariables', 'TM_COMMENT_START') - - @lineCommentEndStringForScope: (scope) -> - @getPreferenceValueInScope(scope, 'shellVariables', 'TM_COMMENT_END') - - @indentRegexForScope: (scope) -> - if source = @getPreferenceInScope(scope, 'increaseIndentPattern') - new OnigRegExp(source) - - @outdentRegexForScope: (scope) -> - if source = @getPreferenceInScope(scope, 'decreaseIndentPattern') - new OnigRegExp(source) - - @foldEndRegexForScope: (grammar, scope) -> - marker = @getPreferenceInScope(scope, 'foldingStopMarker') - if marker - new OnigRegExp(marker) - else - new OnigRegExp(grammar.foldingStopMarker) - grammars: null constructor: (@path) -> diff --git a/src/app/text-mate-package.coffee b/src/app/text-mate-package.coffee new file mode 100644 index 000000000..c28c63937 --- /dev/null +++ b/src/app/text-mate-package.coffee @@ -0,0 +1,74 @@ +Package = require 'package' +TextMateBundle = require 'text-mate-bundle' +fs = require 'fs' +plist = require 'plist' +_ = require 'underscore' + +module.exports = +class TextMatePackage extends Package + @testName: (packageName) -> + /(\.|_|-)tmbundle$/.test(packageName) + + @cssSelectorFromScopeSelector: (scopeSelector) -> + scopeSelector.split(', ').map((commaFragment) -> + commaFragment.split(' ').map((spaceFragment) -> + spaceFragment.split('.').map((dotFragment) -> + '.' + dotFragment.replace(/\+/g, '\\+') + ).join('') + ).join(' ') + ).join(', ') + + load: -> + @bundle = TextMateBundle.load(@name) + @grammars = @bundle.grammars + super + + constructor: -> + super + @preferencesPath = fs.join(@path, "Preferences") + @syntaxesPath = fs.join(@path, "Syntaxes") + + getScopedProperties: -> + scopedProperties = [] + + for grammar in @grammars + if properties = @propertiesFromTextMateSettings(grammar) + selector = @cssSelectorFromScopeSelector(grammar.scopeName) + scopedProperties.push({selector, properties}) + + for {scope, settings} in @getTextMatePreferenceObjects() + if properties = @propertiesFromTextMateSettings(settings) + selector = @cssSelectorFromScopeSelector(scope) if scope? + scopedProperties.push({selector, properties}) + + scopedProperties + + getTextMatePreferenceObjects: -> + preferenceObjects = [] + if fs.exists(@preferencesPath) + for preferencePath in fs.list(@preferencesPath) + plist.parseString fs.read(preferencePath), (e, data) => + if e + console.warn "Failed to parse preference at path '#{preferencePath}'", e.stack + else + preferenceObjects.push(data[0]) + preferenceObjects + + 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 + ) + { editor: editorProperties } if _.size(editorProperties) > 0 + + cssSelectorFromScopeSelector: (scopeSelector) -> + @constructor.cssSelectorFromScopeSelector(scopeSelector) \ No newline at end of file diff --git a/src/app/text-mate-theme.coffee b/src/app/text-mate-theme.coffee index 70f0602e2..caae44298 100644 --- a/src/app/text-mate-theme.coffee +++ b/src/app/text-mate-theme.coffee @@ -1,46 +1,16 @@ _ = require 'underscore' fs = require 'fs' -plist = require 'plist' +Theme = require 'theme' module.exports = -class TextMateTheme - @themesByName: {} - - @loadAll: -> - for themePath in fs.list(require.resolve("themes")) - @registerTheme(TextMateTheme.load(themePath)) - - @load: (path) -> - plistString = fs.read(require.resolve(path)) - theme = null - plist.parseString plistString, (err, data) -> - throw new Error("Error loading theme at '#{path}': #{err}") if err - theme = new TextMateTheme(data[0]) - theme - - @registerTheme: (theme) -> - @themesByName[theme.name] = theme - - @getNames: -> - _.keys(@themesByName) - - @getTheme: (name) -> - @themesByName[name] - - @activate: (name) -> - if theme = @getTheme(name) - theme.activate() - else - throw new Error("No theme with name '#{name}'") - - constructor: ({@name, settings}) -> +class TextMateTheme extends Theme + constructor: (@path, {settings}) -> + super @rulesets = [] globalSettings = settings[0] @buildGlobalSettingsRulesets(settings[0]) @buildScopeSelectorRulesets(settings[1..]) - - activate: -> - applyStylesheet(@name, @getStylesheet()) + @stylesheets[@path] = @getStylesheet() getStylesheet: -> lines = [] diff --git a/src/app/theme.coffee b/src/app/theme.coffee new file mode 100644 index 000000000..1f8fea54e --- /dev/null +++ b/src/app/theme.coffee @@ -0,0 +1,56 @@ +fs = require("fs") +plist = require 'plist' +_ = require 'underscore' + +module.exports = +class Theme + @stylesheets: null + + @load: (names) -> + if typeof(names) == "string" + [@loadTheme(names)] + else + names.map (name) => @loadTheme(name) + + @loadTheme: (name) -> + if fs.exists(name) + path = name + else + path = fs.resolve(config.themeDirPaths..., name) + path ?= fs.resolve(config.themeDirPaths..., name + ".tmTheme") + + if @isTextMateTheme(path) + theme = @loadTextMateTheme(path) + else + theme = @loadAtomTheme(path) + + throw new Error("Cannot activate theme named '#{name}' located at '#{path}'") unless theme + theme.activate() + theme + + @loadTextMateTheme: (path) -> + TextMateTheme = require("text-mate-theme") + plistString = fs.read(path) + theme = null + plist.parseString plistString, (err, data) -> + throw new Error("Error loading theme at '#{path}': #{err}") if err + theme = new TextMateTheme(path, data[0]) + theme + + @loadAtomTheme: (path) -> + AtomTheme = require('atom-theme') + new AtomTheme(path) + + @isTextMateTheme: (path) -> + /\.(tmTheme|plist)$/.test(path) + + constructor: (@path) -> + @stylesheets = {} + + activate: -> + for stylesheetPath, stylesheetContent of @stylesheets + applyStylesheet(stylesheetPath, stylesheetContent) + + deactivate: -> + for stylesheetPath, stylesheetContent of @stylesheets + window.removeStylesheet(stylesheetPath) \ No newline at end of file diff --git a/src/app/window.coffee b/src/app/window.coffee index 16c55e5d4..5eccba916 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -9,6 +9,7 @@ _ = require 'underscore' $ = require 'jquery' {CoffeeScript} = require 'coffee-script' Config = require 'config' +Syntax = require 'syntax' RootView = require 'root-view' Pasteboard = require 'pasteboard' require 'jquery-extensions' @@ -25,8 +26,7 @@ windowAdditions = # in all environments: spec, benchmark, and application startup: -> @config = new Config - TextMateBundle.loadAll() - TextMateTheme.loadAll() + @syntax = new Syntax @setUpKeymap() @pasteboard = new Pasteboard @@ -65,9 +65,14 @@ windowAdditions = requireStylesheet: (path) -> unless fullPath = require.resolve(path) - throw new Error("requireStylesheet could not find a file at path '#{path}'") + throw new Error("Could not find a file at path '#{path}'") window.applyStylesheet(fullPath, fs.read(fullPath)) + removeStylesheet: (path) -> + unless fullPath = require.resolve(path) + throw new Error("Could not find a file at path '#{path}'") + $("head style[id='#{fullPath}']").remove() + applyStylesheet: (id, text) -> unless $("head style[id='#{id}']").length $('head').append "" diff --git a/src/packages/command-panel/spec/command-panel-spec.coffee b/src/packages/command-panel/spec/command-panel-spec.coffee index 653642a1d..8beaed7ad 100644 --- a/src/packages/command-panel/spec/command-panel-spec.coffee +++ b/src/packages/command-panel/spec/command-panel-spec.coffee @@ -36,7 +36,7 @@ describe "CommandPanel", -> rootView.deactivate() rootView2.attachToDom() - commandPanel = rootView2.activateExtension(CommandPanel) + commandPanel = rootView2.activatePackage(CommandPanel) expect(rootView2.find('.command-panel')).toExist() expect(commandPanel.miniEditor.getText()).toBe 'abc' expect(commandPanel.miniEditor.isFocused).toBeTruthy() @@ -49,7 +49,7 @@ describe "CommandPanel", -> rootView3 = RootView.deserialize(rootView2.serialize()) rootView2.deactivate() rootView3.attachToDom() - commandPanel = rootView3.activateExtension(CommandPanel) + commandPanel = rootView3.activatePackage(CommandPanel) expect(commandPanel.miniEditor.isFocused).toBeFalsy() rootView3.deactivate() @@ -71,7 +71,7 @@ describe "CommandPanel", -> rootView.deactivate() rootView2.attachToDom() - commandPanel = rootView2.activateExtension(CommandPanel) + commandPanel = rootView2.activatePackage(CommandPanel) expect(commandPanel.history.length).toBe(2) expect(commandPanel.history[0]).toBe('/test2') expect(commandPanel.history[1]).toBe('/test3') diff --git a/src/packages/event-palette/spec/event-palette-spec.coffee b/src/packages/event-palette/spec/event-palette-spec.coffee index dbb419215..53e8448ee 100644 --- a/src/packages/event-palette/spec/event-palette-spec.coffee +++ b/src/packages/event-palette/spec/event-palette-spec.coffee @@ -8,7 +8,7 @@ describe "EventPalette", -> beforeEach -> rootView = new RootView(require.resolve('fixtures/sample.js')) - rootView.activateExtension(EventPalette) + atom.loadPackage("event-palette") palette = EventPalette.instance rootView.attachToDom().focus() rootView.trigger 'event-palette:toggle' diff --git a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee index 0c7d7e507..1306dcf1f 100644 --- a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee +++ b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee @@ -10,7 +10,7 @@ describe 'FuzzyFinder', -> beforeEach -> rootView = new RootView(require.resolve('fixtures/sample.js')) rootView.enableKeymap() - rootView.activateExtension(FuzzyFinder) + atom.loadPackage("fuzzy-finder") finder = FuzzyFinder.instance afterEach -> diff --git a/src/packages/fuzzy-finder/src/fuzzy-finder.coffee b/src/packages/fuzzy-finder/src/fuzzy-finder.coffee index cf6bc4562..d51871207 100644 --- a/src/packages/fuzzy-finder/src/fuzzy-finder.coffee +++ b/src/packages/fuzzy-finder/src/fuzzy-finder.coffee @@ -73,7 +73,8 @@ class FuzzyFinder extends SelectList else @setLoading("Indexing...") @rootView.project.getFilePaths().done (paths) => - ignoredNames = config.get("fuzzy-finder.ignoredNames") + ignoredNames = config.get("fuzzyFinder.ignoredNames") or [] + ignoredNames = ignoredNames.concat(config.get("core.ignoredNames") or []) @projectPaths = paths if ignoredNames @projectPaths = @projectPaths.filter (path) -> diff --git a/src/packages/markdown-preview/spec/markdown-preview-spec.coffee b/src/packages/markdown-preview/spec/markdown-preview-spec.coffee index 994e03884..55a364261 100644 --- a/src/packages/markdown-preview/spec/markdown-preview-spec.coffee +++ b/src/packages/markdown-preview/spec/markdown-preview-spec.coffee @@ -7,7 +7,7 @@ describe "MarkdownPreview", -> beforeEach -> rootView = new RootView(require.resolve('fixtures/markdown')) - rootView.activateExtension(MarkdownPreview) + atom.loadPackage("markdown-preview") markdownPreview = MarkdownPreview.instance rootView.attachToDom() diff --git a/src/packages/outline-view/spec/outline-view-spec.coffee b/src/packages/outline-view/spec/outline-view-spec.coffee index e133e86c5..e74981663 100644 --- a/src/packages/outline-view/spec/outline-view-spec.coffee +++ b/src/packages/outline-view/spec/outline-view-spec.coffee @@ -7,7 +7,7 @@ describe "OutlineView", -> beforeEach -> rootView = new RootView(require.resolve('fixtures')) - rootView.activateExtension(OutlineView) + atom.loadPackage("outline-view") outlineView = OutlineView.instance rootView.attachToDom() setArraySpy = spyOn(outlineView, 'setArray').andCallThrough() diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee index 4df375f29..c002ba04d 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -11,7 +11,7 @@ describe "Tabs", -> rootView = new RootView(require.resolve('fixtures/sample.js')) rootView.open('sample.txt') rootView.simulateDomAttachment() - rootView.activateExtension(Tabs) + atom.loadPackage("tabs") editor = rootView.getActiveEditor() tabs = rootView.find('.tabs').view() diff --git a/src/packages/tree-view/spec/tree-view-spec.coffee b/src/packages/tree-view/spec/tree-view-spec.coffee index 1923e8ada..7de459451 100644 --- a/src/packages/tree-view/spec/tree-view-spec.coffee +++ b/src/packages/tree-view/spec/tree-view-spec.coffee @@ -13,7 +13,7 @@ describe "TreeView", -> rootView = new RootView(require.resolve('fixtures/tree-view')) project = rootView.project - rootView.activateExtension(TreeView) + atom.loadPackage("tree-view") treeView = rootView.find(".tree-view").view() treeView.root = treeView.find('> li:first').view() sampleJs = treeView.find('.file:contains(tree-view.js)') @@ -51,7 +51,7 @@ describe "TreeView", -> rootView.deactivate() rootView = new RootView - rootView.activateExtension(TreeView) + rootView.activatePackage(TreeView) treeView = rootView.find(".tree-view").view() it "does not create a root node", -> @@ -74,7 +74,7 @@ describe "TreeView", -> describe "when the prototypes deactivate method is called", -> it "calls the deactivate on tree view instance", -> spyOn(treeView, "deactivate").andCallThrough() - rootView.deactivateExtension(TreeView) + rootView.deactivatePackage(TreeView) expect(treeView.deactivate).toHaveBeenCalled() describe "serialization", -> @@ -89,7 +89,7 @@ describe "TreeView", -> newRootView = RootView.deserialize(rootView.serialize()) rootView.deactivate() # Deactivates previous TreeView - newRootView.activateExtension(TreeView) + newRootView.activatePackage(TreeView) newTreeView = newRootView.find(".tree-view").view() @@ -106,7 +106,7 @@ describe "TreeView", -> rootView.deactivate() # Deactivates previous TreeView newRootView.attachToDom() - newRootView.activateExtension(TreeView) + newRootView.activatePackage(TreeView) newTreeView = newRootView.find(".tree-view").view() expect(newTreeView).toMatchSelector ':focus' @@ -589,7 +589,7 @@ describe "TreeView", -> rootView = new RootView(rootDirPath) project = rootView.project - rootView.activateExtension(TreeView) + rootView.activatePackage(TreeView) treeView = rootView.find(".tree-view").view() dirView = treeView.root.entries.find('.directory:contains(test-dir)').view() dirView.expand() diff --git a/src/stdlib/fs.coffee b/src/stdlib/fs.coffee index 9b9e97cda..b4470d916 100644 --- a/src/stdlib/fs.coffee +++ b/src/stdlib/fs.coffee @@ -124,6 +124,13 @@ module.exports = md5ForPath: (path) -> $native.md5ForPath(path) + resolve: (paths...) -> + to = paths.pop() + for from in paths + path = @join(from, to) + return path if @exists(path) + undefined + isCompressedExtension: (ext) -> _.contains([ '.gz' diff --git a/src/stdlib/underscore-extensions.coffee b/src/stdlib/underscore-extensions.coffee index c1e6252a6..4946bcdea 100644 --- a/src/stdlib/underscore-extensions.coffee +++ b/src/stdlib/underscore-extensions.coffee @@ -85,3 +85,16 @@ _.mixin endsWith: (string, suffix) -> string.indexOf(suffix, string.length - suffix.length) isnt -1 + + valueForKeyPath: (object, keyPath) -> + keys = keyPath.split('.') + for key in keys + object = object[key] + return unless object? + object + + compactObject: (object) -> + newObject = {} + for key, value of object + newObject[key] = value if value? + newObject