Merge remote-tracking branch 'origin/master' into cj-add-flash-error

This commit is contained in:
probablycorey 2013-10-23 10:27:49 -07:00
commit 3b525302b0
53 changed files with 1009 additions and 442 deletions

View File

@ -28,6 +28,13 @@
* Class methods (methods starting with a `@`)
* Instance variables
* Instance methods
* Be ware of platform differences
* The home directory is `process.env.USERPROFILE` on Windows, while on OS X
and Linux it's `process.env.HOME`
* Path separator is `\` on Windows, and is `/` on OS X and Linux, so use
`path.join` to concatenate filenames.
* Temporary directory is not `/tmp` on Windows, use `os.tmpdir()` when
possible
## Philosophy

View File

@ -1,5 +1,6 @@
fs = require 'fs'
path = require 'path'
os = require 'os'
fm = require 'json-front-matter'
_ = require 'underscore-plus'
@ -9,7 +10,8 @@ packageJson = require './package.json'
module.exports = (grunt) ->
appName = 'Atom.app'
[major, minor, patch] = packageJson.version.split('.')
buildDir = grunt.option('build-dir') ? '/tmp/atom-build'
tmpDir = if process.platform is 'win32' then os.tmpdir() else '/tmp'
buildDir = grunt.option('build-dir') ? path.join(tmpDir, 'atom-build')
shellAppDir = path.join(buildDir, appName)
contentsDir = path.join(shellAppDir, 'Contents')
appDir = path.join(contentsDir, 'Resources', 'app')

View File

@ -35,23 +35,30 @@ a few things before starting:
## Creating a Minimal Syntax Theme
1. Open the Command Palette (`cmd-p`)
1. Search for `Package Generator: Generate Theme` and select it.
1. Choose a name for the folder which will contain your theme.
1. Search for `Package Generator: Generate Syntax Theme` and select it.
1. Choose a name for your theme. A folder will be created with this name.
1. Press enter to create your theme. After creation, your theme will be
installed and enabled.
1. An Atom window will open with your newly created theme.
1. Open `package.json` and update the relevant parts.
1. Open `stylesheets/colors.less` to change the various colors variables which
have been already been defined.
1. Open `stylesheets/base.less` and modify the various syntax CSS selectors
that have been already been defined.
1. When you're ready update the `README.md` and include an example screenshot
1. When you're ready, update the `README.md` and include an example screenshot
of your new theme in action.
1. Reload Atom (`cmd-r`) and your theme should now be applied.
1. Reload atom (`cmd-r`) to see changes you made reflected in your Atom window.
1. Look in the theme settings, your new theme should be show in the enabled themes section
![themesettings-img]
1. Open a terminal to your new theme directory; it should be in `~/.atom/packages/<my-name>`.
1. To publish, initialize a git repository, push to GitHub, and run
`apm publish`.
Want to make edits and have them immediately show up without reloading? Open a
development window (__View > Developer > Open in Dev Mode__ menu) to the
directory of your choice &mdash; even the new theme itself. Then just edit away!
Changes will be instantly reflected in the editor without having to reload.
## Interface Themes
There are only two differences between interface and syntax themes - what
@ -65,7 +72,7 @@ To create a UI theme, do the following:
1. [atom-dark-ui]
1. [atom-light-ui]
1. Open a terminal in the forked theme's directory
1. Open your new theme in a Dev Mode Atom window (either run `atom -d .` in the terminal or use `cmd-shift-o` from atom)
1. Open your new theme in a Dev Mode Atom window (run `atom -d .` in the terminal or use the __View > Developer > Open in Dev Mode__ menu)
1. Change the name of the theme in the theme's `package.json` file
1. Run `apm link` to tell Atom about your new theme
1. Reload Atom (`cmd-r`)
@ -83,7 +90,7 @@ Reloading via `cmd-r` after you make changes to your theme less than ideal. Atom
supports [live updating][livereload] of styles on Dev Mode Atom windows.
1. Open your theme directory in a dev window by either using the
__File > Open in Dev Mode__ menu or the `cmd-shift-o` shortcut
__View > Developer > Open in Dev Mode__ menu or the `cmd-shift-o` shortcut
1. Make a change to your theme file and save &mdash; your change should be
immediately applied!

View File

@ -1,9 +1,6 @@
{Document, Point, Range, Site} = require 'telepath'
_ = require 'underscore-plus'
#TODO Remove once all packages have been updated
_.nextTick = setImmediate
module.exports =
_: _
BufferedNodeProcess: require '../src/buffered-node-process'

View File

@ -5,16 +5,24 @@
'meta-shift-down': 'core:select-to-bottom'
'.editor':
'meta-left': 'editor:move-to-first-character-of-line'
'meta-right': 'editor:move-to-end-of-line'
'meta-left': 'editor:move-to-beginning-of-line'
'alt-left': 'editor:move-to-beginning-of-word'
'alt-right': 'editor:move-to-end-of-word'
'meta-shift-left': 'editor:select-to-beginning-of-line'
'meta-shift-left': 'editor:select-to-first-character-of-line'
'meta-shift-right': 'editor:select-to-end-of-line'
'alt-shift-left': 'editor:select-to-beginning-of-word'
'alt-shift-right': 'editor:select-to-end-of-word'
'home': 'editor:move-to-first-character-of-line'
'end': 'editor:move-to-end-of-line'
'shift-home': 'editor:select-to-first-character-of-line'
'shift-end': 'editor:select-to-end-of-line'
'alt-left': 'editor:move-to-previous-word-boundary'
'alt-right': 'editor:move-to-next-word-boundary'
'alt-shift-left': 'editor:select-to-previous-word-boundary'
'alt-shift-right': 'editor:select-to-next-word-boundary'
'alt-backspace': 'editor:backspace-to-beginning-of-word'
'meta-backspace': 'editor:backspace-to-beginning-of-line'
'alt-delete': 'editor:delete-to-end-of-word'
'ctrl-t': 'editor:transpose'
'ctrl-A': 'editor:select-to-first-character-of-line'

View File

@ -30,6 +30,7 @@
'delete': 'core:delete'
'meta-z': 'core:undo'
'meta-Z': 'core:redo'
'meta-y': 'core:redo'
'meta-x': 'core:cut'
'meta-c': 'core:copy'
'meta-v': 'core:paste'

View File

@ -1,5 +1,5 @@
'.editor':
'meta-d': 'editor:delete-line'
'ctrl-K': 'editor:delete-line'
'ctrl-W': 'editor:select-word'
'meta-alt-p': 'editor:log-cursor-scope'
'meta-u': 'editor:upper-case'
@ -38,7 +38,7 @@
'ctrl-meta-up': 'editor:move-line-up'
'ctrl-meta-down': 'editor:move-line-down'
'meta-D': 'editor:duplicate-line'
'ctrl-J': 'editor:join-line'
'meta-j': 'editor:join-line'
'meta-<': 'editor:scroll-to-cursor'
'.editor.mini':

View File

@ -45,9 +45,96 @@
{ label: 'Paste', command: 'core:paste' }
{ label: 'Select All', command: 'core:select-all' }
{ type: 'separator' }
{ label: 'Toggle Comments', command: 'editor:toggle-line-comments' }
{
label: 'Lines',
submenu: [
{ label: 'Indent', command: 'editor:indent-selected-rows' }
{ label: 'Outdent', command: 'editor:outdent-selected-rows' }
{ label: 'Auto Indent', command: 'editor:auto-indent' }
{ type: 'separator' }
{ label: 'Move Line Up', command: 'editor:move-line-up' }
{ label: 'Move Line Down', command: 'editor:move-line-down' }
{ label: 'Duplicate Line', command: 'editor:duplicate-line' }
{ label: 'Delete Line', command: 'editor:delete-line' }
{ label: 'Join Lines', command: 'editor:join-line' }
]
}
{
label: 'Text',
submenu: [
{ label: 'Upper Case', command: 'editor:upper-case' }
{ label: 'Lower Case', command: 'editor:lower-case' }
{ type: 'separator' }
{ label: 'Delete to End of Word', command: 'editor:delete-to-end-of-word' }
{ label: 'Delete Line', command: 'editor:delete-line' }
{ type: 'separator' }
{ label: 'Transpose', command: 'editor:transpose' }
]
}
{
label: 'Folding',
submenu: [
{ label: 'Fold', command: 'editor:fold-current-row' }
{ label: 'Unfold', command: 'editor:unfold-current-row' }
{ label: 'Unfold All', command: 'editor:unfold-all' }
{ type: 'separator' }
{ label: 'Fold All', command: 'editor:fold-all' }
{ label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' }
{ label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' }
{ label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' }
{ label: 'Fold Level 4', command: 'editor:fold-at-indent-level-4' }
{ label: 'Fold Level 5', command: 'editor:fold-at-indent-level-5' }
{ label: 'Fold Level 6', command: 'editor:fold-at-indent-level-6' }
{ label: 'Fold Level 7', command: 'editor:fold-at-indent-level-7' }
{ label: 'Fold Level 8', command: 'editor:fold-at-indent-level-8' }
{ label: 'Fold Level 9', command: 'editor:fold-at-indent-level-9' }
]
}
]
}
{
label: 'Selection'
submenu: [
{ label: 'Add Selection Above', command: 'editor:add-selection-above' }
{ label: 'Add Selection Below', command: 'editor:add-selection-below' }
{ type: 'separator' }
{ label: 'Select to Top', command: 'core:select-to-top' }
{ label: 'Select to Bottom', command: 'core:select-to-bottom' }
{ type: 'separator' }
{ label: 'Select Line', command: 'editor:select-line' }
{ label: 'Select Word', command: 'editor:select-word' }
{ label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' }
{ label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' }
{ label: 'Select to First Character of Line', command: 'editor:select-to-first-character-of-line' }
{ label: 'Select to End of Word', command: 'editor:select-to-end-of-word' }
{ label: 'Select to End of Line', command: 'editor:select-to-end-of-line' }
]
}
{
label: 'Movement'
submenu: [
{ label: 'Move to Top', command: 'core:move-to-top' }
{ label: 'Move to Bottom', command: 'core:move-to-bottom' }
{ type: 'separator' }
{ label: 'Move to Beginning of Line', command: 'editor:move-to-beginning-of-line' }
{ label: 'Move to First Character of Line', command: 'editor:move-to-first-character-of-line' }
{ label: 'Move to End of Line', command: 'editor:move-to-end-of-line' }
{ type: 'separator' }
{ label: 'Move to Beginning of Word', command: 'editor:move-to-beginning-of-word' }
{ label: 'Move to End of Word', command: 'editor:move-to-end-of-word' }
{ label: 'Move to Next Word', command: 'editor:move-to-next-word-boundary' }
{ label: 'Move to Previous Word', command: 'editor:move-to-previous-word-boundary' }
]
}
{
label: 'Find'
submenu: []
}
{
label: 'View'
submenu: [
@ -67,6 +154,16 @@
]
}
{
label: 'Collaboration'
submenu: []
}
{
label: 'Packages'
submenu: []
}
{
label: 'Window'
submenu: [

View File

@ -9,7 +9,7 @@
"bugs": {
"url": "https://github.com/atom/atom/issues"
},
"atomShellVersion": "0.6.3",
"atomShellVersion": "0.6.4",
"dependencies": {
"async": "0.2.6",
"bootstrap": "git://github.com/twbs/bootstrap.git#v3.0.0",
@ -39,86 +39,7 @@
"space-pen": "2.0.0",
"telepath": "0.8.1",
"temp": "0.5.0",
"underscore-plus": "0.2.0",
"atom-light-ui": "0.4.0",
"atom-light-syntax": "0.4.0",
"atom-dark-ui": "0.4.0",
"atom-dark-syntax": "0.4.0",
"base16-tomorrow-dark-theme": "0.2.0",
"solarized-dark-syntax": "0.3.0",
"archive-view": "0.11.0",
"autocomplete": "0.10.0",
"autoflow": "0.5.0",
"bookmarks": "0.8.0",
"bracket-matcher": "0.7.0",
"collaboration": "0.26.0",
"command-logger": "0.6.0",
"command-palette": "0.5.0",
"dev-live-reload": "0.8.0",
"editor-stats": "0.5.0",
"exception-reporting": "0.4.0",
"find-and-replace": "0.29.0",
"fuzzy-finder": "0.15.0",
"gfm": "0.5.0",
"git-diff": "0.11.0",
"gists": "0.5.0",
"github-sign-in": "0.8.0",
"go-to-line": "0.8.0",
"grammar-selector": "0.7.0",
"image-view": "0.7.0",
"link": "0.6.0",
"markdown-preview": "0.9.0",
"metrics": "0.8.0",
"package-generator": "0.13.0",
"release-notes": "0.8.0",
"settings-view": "0.29.0",
"snippets": "0.10.0",
"spell-check": "0.7.0",
"status-bar": "0.14.0",
"styleguide": "0.9.0",
"symbols-view": "0.12.0",
"tabs": "0.7.0",
"terminal": "0.13.0",
"timecop": "0.6.0",
"to-the-hubs": "0.8.0",
"toml": "0.3.0",
"tree-view": "0.19.0",
"whitespace": "0.7.0",
"wrap-guide": "0.4.0",
"c-tmbundle": "1.0.0",
"coffee-script-tmbundle": "1.0.0",
"css-tmbundle": "1.0.0",
"git-tmbundle": "1.0.0",
"go-tmbundle": "1.0.0",
"html-tmbundle": "1.0.0",
"hyperlink-helper-tmbundle": "1.0.0",
"java-tmbundle": "1.0.0",
"javascript-tmbundle": "2.0.0",
"json-tmbundle": "1.0.0",
"less-tmbundle": "1.0.0",
"make-tmbundle": "1.0.0",
"mustache-tmbundle": "1.0.0",
"objective-c-tmbundle": "1.0.0",
"pegjs-tmbundle": "1.0.0",
"perl-tmbundle": "1.0.0",
"php-tmbundle": "1.0.0",
"property-list-tmbundle": "1.0.0",
"puppet-textmate-tmbundle": "1.0.0",
"python-tmbundle": "1.0.0",
"ruby-on-rails-tmbundle": "1.0.0",
"ruby-tmbundle": "1.0.0",
"sass-tmbundle": "1.0.0",
"shellscript-tmbundle": "1.0.0",
"source-tmbundle": "1.0.0",
"sql-tmbundle": "1.0.0",
"text-tmbundle": "1.0.0",
"textmate-clojure": "1.0.0",
"todo-tmbundle": "1.0.0",
"xml-tmbundle": "1.0.0",
"yaml-tmbundle": "1.0.0"
"underscore-plus": "0.2.0"
},
"devDependencies": {
"biscotto": "0.0.17",
@ -141,6 +62,86 @@
"jasmine-node": "git://github.com/kevinsawicki/jasmine-node.git#short-stacks",
"request": "~2.27.0"
},
"packageDependencies" : {
"atom-light-ui": "0.5.0",
"atom-light-syntax": "0.5.0",
"atom-dark-ui": "0.5.0",
"atom-dark-syntax": "0.5.0",
"base16-tomorrow-dark-theme": "0.2.0",
"solarized-dark-syntax": "0.3.0",
"archive-view": "0.11.0",
"autocomplete": "0.11.0",
"autoflow": "0.5.0",
"bookmarks": "0.8.0",
"bracket-matcher": "0.7.0",
"collaboration": "0.28.0",
"command-logger": "0.6.0",
"command-palette": "0.5.0",
"dev-live-reload": "0.11.0",
"editor-stats": "0.5.0",
"exception-reporting": "0.5.0",
"find-and-replace": "0.29.0",
"fuzzy-finder": "0.15.0",
"gfm": "0.5.0",
"git-diff": "0.12.0",
"gists": "0.5.0",
"github-sign-in": "0.8.0",
"go-to-line": "0.8.0",
"grammar-selector": "0.8.0",
"image-view": "0.7.0",
"link": "0.7.0",
"markdown-preview": "0.12.0",
"metrics": "0.8.0",
"package-generator": "0.14.0",
"release-notes": "0.8.0",
"settings-view": "0.31.1",
"snippets": "0.11.0",
"spell-check": "0.8.0",
"status-bar": "0.15.0",
"styleguide": "0.9.0",
"symbols-view": "0.13.0",
"tabs": "0.7.0",
"terminal": "0.14.0",
"timecop": "0.7.0",
"to-the-hubs": "0.8.0",
"toml": "0.3.0",
"tree-view": "0.20.0",
"whitespace": "0.7.0",
"wrap-guide": "0.4.0",
"language-c": "0.1.0",
"language-clojure": "0.1.0",
"language-coffee-script": "0.1.0",
"language-css": "0.1.0",
"language-git": "0.1.0",
"language-go": "0.2.0",
"language-html": "0.1.0",
"language-hyperlink": "0.2.0",
"language-java": "0.1.0",
"language-javascript": "0.1.0",
"language-json": "0.1.0",
"language-less": "0.1.0",
"language-make": "0.1.0",
"language-mustache": "0.1.0",
"language-objective-c": "0.1.0",
"language-pegjs": "0.1.0",
"language-perl": "0.1.0",
"language-php": "0.1.0",
"language-property-list": "0.2.0",
"language-puppet": "0.1.0",
"language-python": "0.1.0",
"language-ruby-on-rails": "0.2.0",
"language-ruby": "0.2.0",
"language-sass": "0.2.0",
"language-shellscript": "0.1.0",
"language-source": "0.1.0",
"language-sql": "0.1.0",
"language-text": "0.1.0",
"language-todo": "0.2.0",
"language-xml": "0.1.0",
"language-yaml": "0.1.0"
},
"private": true,
"scripts": {
"preinstall": "true",

View File

@ -1,22 +1,31 @@
#!/bin/sh
#!/usr/bin/env node
var safeExec = require('./utils/child-process-wrapper.js').safeExec;
var path = require('path');
# exit on subprocess errors
set -o errexit
exit_unless_npm_exists() {
if ! hash npm 2> /dev/null; then
echo "ERROR: Atom requires npm"
exit 1
fi
// Executes an array of commands one by one.
function executeCommands(commands, done, index) {
index = (index == undefined ? 0 : index);
if (index < commands.length)
safeExec(commands[index], executeCommands.bind(this, commands, done, index + 1));
else
done(null);
}
exit_unless_npm_exists
// Join multiple commands into one line.
function joinCommands() {
var commandSeparator = process.platform == 'win32' ? '&' : ';';
return Array.prototype.slice.call(arguments, 0).join(commandSeparator);
}
git submodule --quiet sync
git submodule --quiet update --recursive --init
var echoNewLine = process.platform == 'win32' ? 'echo.' : 'echo';
var commands = [
'git submodule --quiet sync',
'git submodule --quiet update --recursive --init',
joinCommands('cd vendor/apm', 'npm install --silent .'),
'npm install --silent vendor/apm',
echoNewLine,
'node vendor/apm/bin/apm install --silent',
];
(cd vendor/apm && npm install --silent .)
npm install --silent vendor/apm
echo ""
./node_modules/.bin/apm install --silent
process.chdir(path.dirname(__dirname));
executeCommands(commands, process.exit);

6
script/bootstrap.cmd Normal file
View File

@ -0,0 +1,6 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\bootstrap" %*
) ELSE (
node "%~dp0\bootstrap" %*
)

View File

@ -1,8 +1,12 @@
#!/bin/sh
#!/usr/bin/env node
var cp = require('./utils/child-process-wrapper.js');
var path = require('path');
set -e
process.chdir(path.dirname(__dirname));
cd "$(dirname "$0")/.."
./script/bootstrap
./node_modules/.bin/grunt "$@"
cp.safeExec('node script/bootstrap', function() {
// ./node_modules/.bin/grunt "$@"
var gruntPath = path.join('node_modules', '.bin', 'grunt');
var args = [gruntPath].concat(process.argv.slice(2));
cp.safeSpawn(process.execPath, args, process.exit);
});

View File

@ -1,18 +1,40 @@
#!/bin/sh
#!/usr/bin/env node
var cp = require('./utils/child-process-wrapper.js');
var fs = require('fs');
var path = require('path');
set -e
process.chdir(path.dirname(__dirname));
cd "$(dirname "$0")/.."
if (process.platform != 'darwin')
throw new Error('cibuild can not run on ' + process.platform + ' yet!');
rm -rf ~/.atom
git clean -dff
var homeDir = process.platform == 'win32' ? process.env.USERPROFILE : process.env.HOME;
ATOM_CREDENTIALS_FILE=/var/lib/jenkins/config/atomcredentials
if [ -f $ATOM_CREDENTIALS_FILE ]; then
. $ATOM_CREDENTIALS_FILE
export ATOM_ACCESS_TOKEN=$ATOM_ACCESS_TOKEN # make it visibile to grunt.
fi
function readEnvironmentVariables(callback) {
var credenticalsPath = '/var/lib/jenkins/config/atomcredentials';
fs.readFile(credenticalsPath, function(error, data) {
if (!error) {
var lines = String(data).trim().split('\n');
for (i in lines) {
var parts = lines[i].split('=');
var key = parts[0].trim();
var value = parts[1].trim().substr(1, parts[1].length - 2);
process.env[key] = value;
}
}
// Do not quit when got error.
callback(null);
});
}
./script/bootstrap
./node_modules/.bin/apm clean
./node_modules/.bin/grunt ci --stack --no-color
var async = require('async');
async.series([
readEnvironmentVariables,
cp.safeExec.bind(global, 'node script/bootstrap'),
require('rimraf').bind(global, path.join(homeDir, '.atom')),
cp.safeExec.bind(global, 'git clean -dff'),
cp.safeExec.bind(global, 'node node_modules/.bin/apm clean'),
cp.safeExec.bind(global, 'node node_modules/.bin/grunt ci --stack --no-color'),
], function(error) {
process.exit(error ? 1 : 0);
});

View File

@ -1,8 +1,9 @@
#!/bin/sh
#!/usr/bin/env node
var safeExec = require('./utils/child-process-wrapper.js').safeExec;
var path = require('path');
set -e
process.chdir(path.dirname(__dirname));
cd "$(dirname "$0")/.."
./script/bootstrap
./node_modules/.bin/grunt ci --stack --no-color
safeExec('node script/bootstrap', function() {
safeExec('node node_modules/.bin/grunt ci --stack --no-color', process.exit);
});

View File

@ -0,0 +1,35 @@
var childProcess = require('child_process');
// Exit the process if the command failed and only call the callback if the
// command succeed, output of the command would also be piped.
exports.safeExec = function(command, options, callback) {
if (!callback) {
callback = options;
options = {};
}
var child = childProcess.exec(command, options, function(error, stdout, stderr) {
if (error)
process.exit(error.code);
else
callback(null);
});
child.stderr.pipe(process.stderr);
child.stdout.pipe(process.stdout);
}
// Same with safeExec but call child_process.spawn instead.
exports.safeSpawn = function(command, args, options, callback) {
if (!callback) {
callback = options;
options = {};
}
var child = childProcess.spawn(command, args, options);
child.stderr.pipe(process.stderr);
child.stdout.pipe(process.stdout);
child.on('exit', function(code) {
if (code != 0)
process.exit(code);
else
callback(null);
});
}

View File

@ -1,6 +1,7 @@
{$, $$, fs, RootView} = require 'atom'
Exec = require('child_process').exec
path = require 'path'
ThemeManager = require '../src/theme-manager'
describe "the `atom` global", ->
beforeEach ->
@ -20,6 +21,7 @@ describe "the `atom` global", ->
expect(pack.activateStylesheets).toHaveBeenCalled()
it "continues if the package has an invalid package.json", ->
spyOn(console, 'warn')
config.set("core.disabledPackages", [])
expect(-> atom.loadPackage("package-with-broken-package-json")).not.toThrow()
@ -192,7 +194,6 @@ describe "the `atom` global", ->
expect(atom.contextMenu.definitionsForElement(element)[1].label).toBe "Menu item 1"
expect(atom.contextMenu.definitionsForElement(element)[2]).toBeUndefined()
describe "stylesheet loading", ->
describe "when the metadata contains a 'stylesheets' manifest", ->
it "loads stylesheets from the stylesheets directory as specified by the manifest", ->
@ -239,12 +240,12 @@ describe "the `atom` global", ->
describe "textmate packages", ->
it "loads the package's grammars", ->
expect(syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
atom.activatePackage('ruby-tmbundle', sync: true)
atom.activatePackage('language-ruby', sync: true)
expect(syntax.selectGrammar("file.rb").name).toBe "Ruby"
it "translates the package's scoped properties to Atom terms", ->
expect(syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
atom.activatePackage('ruby-tmbundle', sync: true)
atom.activatePackage('language-ruby', sync: true)
expect(syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBe '# '
describe "when the package has no grammars but does have preferences", ->
@ -327,12 +328,101 @@ describe "the `atom` global", ->
describe "textmate packages", ->
it "removes the package's grammars", ->
expect(syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
atom.activatePackage('ruby-tmbundle', sync: true)
atom.activatePackage('language-ruby', sync: true)
expect(syntax.selectGrammar("file.rb").name).toBe "Ruby"
atom.deactivatePackage('ruby-tmbundle')
atom.deactivatePackage('language-ruby')
expect(syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
it "removes the package's scoped properties", ->
atom.activatePackage('ruby-tmbundle', sync: true)
atom.deactivatePackage('ruby-tmbundle')
atom.activatePackage('language-ruby', sync: true)
atom.deactivatePackage('language-ruby')
expect(syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
describe ".activate()", ->
packageActivator = null
themeActivator = null
beforeEach ->
spyOn(console, 'warn')
atom.packages.loadPackages()
loadedPackages = atom.packages.getLoadedPackages()
expect(loadedPackages.length).toBeGreaterThan 0
packageActivator = spyOn(atom.packages, 'activatePackages')
themeActivator = spyOn(atom.themes, 'activatePackages')
afterEach ->
atom.packages.unloadPackages()
Syntax = require '../src/syntax'
atom.syntax = window.syntax = new Syntax()
it "activates all the packages, and none of the themes", ->
atom.packages.activate()
expect(packageActivator).toHaveBeenCalled()
expect(themeActivator).toHaveBeenCalled()
packages = packageActivator.mostRecentCall.args[0]
expect(['atom', 'textmate']).toContain(pack.getType()) for pack in packages
themes = themeActivator.mostRecentCall.args[0]
expect(['theme']).toContain(theme.getType()) for theme in themes
describe ".en/disablePackage()", ->
describe "with packages", ->
it ".enablePackage() enables a disabled package", ->
packageName = 'package-with-main'
atom.config.pushAtKeyPath('core.disabledPackages', packageName)
atom.packages.observeDisabledPackages()
expect(config.get('core.disabledPackages')).toContain packageName
pack = atom.packages.enablePackage(packageName)
loadedPackages = atom.packages.getLoadedPackages()
activatedPackages = atom.packages.getActivePackages()
expect(loadedPackages).toContain(pack)
expect(activatedPackages).toContain(pack)
expect(config.get('core.disabledPackages')).not.toContain packageName
it ".disablePackage() disables an enabled package", ->
packageName = 'package-with-main'
atom.packages.activatePackage(packageName)
atom.packages.observeDisabledPackages()
expect(config.get('core.disabledPackages')).not.toContain packageName
pack = atom.packages.disablePackage(packageName)
activatedPackages = atom.packages.getActivePackages()
expect(activatedPackages).not.toContain(pack)
expect(config.get('core.disabledPackages')).toContain packageName
describe "with themes", ->
beforeEach ->
atom.themes.activateThemes()
afterEach ->
atom.themes.deactivateThemes()
atom.config.unobserve('core.themes')
it ".enablePackage() and .disablePackage() enables and disables a theme", ->
packageName = 'theme-with-package-file'
expect(config.get('core.themes')).not.toContain packageName
expect(config.get('core.disabledPackages')).not.toContain packageName
# enabling of theme
pack = atom.packages.enablePackage(packageName)
activatedPackages = atom.packages.getActivePackages()
expect(activatedPackages).toContain(pack)
expect(config.get('core.themes')).toContain packageName
expect(config.get('core.disabledPackages')).not.toContain packageName
# disabling of theme
pack = atom.packages.disablePackage(packageName)
activatedPackages = atom.packages.getActivePackages()
expect(activatedPackages).not.toContain(pack)
expect(config.get('core.themes')).not.toContain packageName
expect(config.get('core.themes')).not.toContain packageName
expect(config.get('core.disabledPackages')).not.toContain packageName

View File

@ -1,10 +1,12 @@
{fs} = require 'atom'
path = require 'path'
temp = require 'temp'
installer = require '../src/command-installer'
describe "install(commandPath, callback)", ->
directory = '/tmp/install-atom-command/atom'
commandPath = "#{directory}/source"
destinationPath = "#{directory}/bin/source"
directory = path.join(temp.dir, 'install-atom-command', 'atom')
commandPath = path.join(directory, 'source')
destinationPath = path.join(directory, 'bin', 'source')
beforeEach ->
spyOn(installer, 'findInstallDirectory').andCallFake (callback) ->

View File

@ -1,8 +1,11 @@
{fs} = require 'atom'
path = require 'path'
temp = require 'temp'
CSON = require 'season'
describe "Config", ->
dotAtomPath = path.join(temp.dir, 'dot-atom-dir')
describe ".get(keyPath)", ->
it "allows a key path's value to be read", ->
expect(config.set("foo.bar.baz", 42)).toBe 42
@ -29,7 +32,7 @@ describe "Config", ->
config.set("foo.bar.baz", 42)
expect(config.save).toHaveBeenCalled()
expect(observeHandler).toHaveBeenCalledWith 42
expect(observeHandler).toHaveBeenCalledWith 42, {previous: undefined}
describe "when the value equals the default value", ->
it "does not store the value", ->
@ -51,7 +54,7 @@ describe "Config", ->
expect(config.pushAtKeyPath("foo.bar.baz", "b")).toBe 2
expect(config.get("foo.bar.baz")).toEqual ["a", "b"]
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz")
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz"), {previous: ['a']}
describe ".removeAtKeyPath(keyPath, value)", ->
it "removes the given value from the array at the key path and updates observers", ->
@ -62,7 +65,7 @@ describe "Config", ->
expect(config.removeAtKeyPath("foo.bar.baz", "b")).toEqual ["a", "c"]
expect(config.get("foo.bar.baz")).toEqual ["a", "c"]
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz")
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz"), {previous: ['a', 'b', 'c']}
describe ".getPositiveInt(keyPath, defaultValue)", ->
it "returns the proper current or default value", ->
@ -139,34 +142,34 @@ describe "Config", ->
it "fires the callback every time the observed value changes", ->
observeHandler.reset() # clear the initial call
config.set('foo.bar.baz', "value 2")
expect(observeHandler).toHaveBeenCalledWith("value 2")
expect(observeHandler).toHaveBeenCalledWith("value 2", {previous: 'value 1'})
observeHandler.reset()
config.set('foo.bar.baz', "value 1")
expect(observeHandler).toHaveBeenCalledWith("value 1")
expect(observeHandler).toHaveBeenCalledWith("value 1", {previous: 'value 2'})
it "fires the callback when the observed value is deleted", ->
observeHandler.reset() # clear the initial call
config.set('foo.bar.baz', undefined)
expect(observeHandler).toHaveBeenCalledWith(undefined)
expect(observeHandler).toHaveBeenCalledWith(undefined, {previous: 'value 1'})
it "fires the callback when the full key path goes into and out of existence", ->
observeHandler.reset() # clear the initial call
config.set("foo.bar", undefined)
expect(observeHandler).toHaveBeenCalledWith(undefined)
expect(observeHandler).toHaveBeenCalledWith(undefined, {previous: 'value 1'})
observeHandler.reset()
config.set("foo.bar.baz", "i'm back")
expect(observeHandler).toHaveBeenCalledWith("i'm back")
expect(observeHandler).toHaveBeenCalledWith("i'm back", {previous: undefined})
describe ".initializeConfigDirectory()", ->
beforeEach ->
config.configDirPath = '/tmp/dot-atom-dir'
config.configDirPath = dotAtomPath
expect(fs.exists(config.configDirPath)).toBeFalsy()
afterEach ->
fs.remove('/tmp/dot-atom-dir') if fs.exists('/tmp/dot-atom-dir')
fs.remove(dotAtomPath) if fs.exists(dotAtomPath)
describe "when the configDirPath doesn't exist", ->
it "copies the contents of dot-atom to ~/.atom", ->
@ -185,12 +188,12 @@ describe "Config", ->
describe ".loadUserConfig()", ->
beforeEach ->
config.configDirPath = '/tmp/dot-atom-dir'
config.configDirPath = dotAtomPath
config.configFilePath = path.join(config.configDirPath, "config.cson")
expect(fs.exists(config.configDirPath)).toBeFalsy()
afterEach ->
fs.remove('/tmp/dot-atom-dir') if fs.exists('/tmp/dot-atom-dir')
fs.remove(dotAtomPath) if fs.exists(dotAtomPath)
describe "when the config file contains valid cson", ->
beforeEach ->
@ -222,7 +225,7 @@ describe "Config", ->
updatedHandler = null
beforeEach ->
config.configDirPath = '/tmp/dot-atom-dir'
config.configDirPath = dotAtomPath
config.configFilePath = path.join(config.configDirPath, "config.cson")
expect(fs.exists(config.configDirPath)).toBeFalsy()
fs.writeSync(config.configFilePath, "foo: bar: 'baz'")
@ -233,7 +236,7 @@ describe "Config", ->
afterEach ->
config.unobserveUserConfig()
fs.remove('/tmp/dot-atom-dir') if fs.exists('/tmp/dot-atom-dir')
fs.remove(dotAtomPath) if fs.exists(dotAtomPath)
describe "when the config file changes to contain valid cson", ->
it "updates the config data", ->

View File

@ -5,7 +5,7 @@ describe "DisplayBuffer", ->
[displayBuffer, buffer, changeHandler, tabLength] = []
beforeEach ->
tabLength = 2
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('language-javascript', sync: true)
buffer = project.bufferForPathSync('sample.js')
displayBuffer = new DisplayBuffer({buffer, tabLength})
changeHandler = jasmine.createSpy 'changeHandler'

View File

@ -16,7 +16,7 @@ describe "EditSession", ->
describe "with default options", ->
beforeEach ->
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('language-javascript', sync: true)
editSession = project.openSync('sample.js', autoIndent: false)
buffer = editSession.buffer
lineLengths = buffer.getLines().map (line) -> line.length

View File

@ -1,12 +1,13 @@
{_, $, $$, fs, Editor, Range, RootView} = require 'atom'
path = require 'path'
temp = require 'temp'
describe "Editor", ->
[buffer, editor, editSession, cachedLineHeight, cachedCharWidth] = []
beforeEach ->
atom.activatePackage('text-tmbundle', sync: true)
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('language-text', sync: true)
atom.activatePackage('language-javascript', sync: true)
editSession = project.openSync('sample.js')
buffer = editSession.buffer
editor = new Editor(editSession)
@ -86,7 +87,7 @@ describe "Editor", ->
describe "when the activeEditSession's file is modified on disk", ->
it "triggers an alert", ->
filePath = "/tmp/atom-changed-file.txt"
filePath = path.join(temp.dir, 'atom-changed-file.txt')
fs.writeSync(filePath, "")
editSession = project.openSync(filePath)
editor.edit(editSession)
@ -151,7 +152,7 @@ describe "Editor", ->
expect(editor.lineElementForScreenRow(6).text()).toMatch /^ currentgoodbye/
it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
filePath = "/tmp/atom-changed-file.txt"
filePath = path.join(temp.dir, 'atom-changed-file.txt')
fs.writeSync(filePath, "")
tempEditSession = project.openSync(filePath)
editor.edit(tempEditSession)
@ -247,7 +248,7 @@ describe "Editor", ->
filePath = null
beforeEach ->
filePath = "/tmp/something.txt"
filePath = path.join(temp.dir, 'something.txt')
fs.writeSync(filePath, filePath)
afterEach ->
@ -274,11 +275,11 @@ describe "Editor", ->
expect(eventHandler).toHaveBeenCalled()
eventHandler.reset()
oldBuffer.saveAs("/tmp/atom-bad.txt")
oldBuffer.saveAs(path.join(temp.dir, 'atom-bad.txt'))
expect(eventHandler).not.toHaveBeenCalled()
eventHandler.reset()
editor.getBuffer().saveAs("/tmp/atom-new.txt")
editor.getBuffer().saveAs(path.join(temp.dir, 'atom-new.txt'))
expect(eventHandler).toHaveBeenCalled()
it "loads the grammar for the new path", ->
@ -2296,7 +2297,8 @@ describe "Editor", ->
[filePath] = []
beforeEach ->
filePath = path.join(fs.absolute("/tmp"), "grammar-change.txt")
tmpdir = fs.absolute(temp.dir)
filePath = path.join(tmpdir, "grammar-change.txt")
fs.writeSync(filePath, "var i;")
afterEach ->
@ -2648,10 +2650,11 @@ describe "Editor", ->
editor.trigger 'editor:save-debug-snapshot'
statePath = path.join(temp.dir, 'state')
expect(atom.showSaveDialog).toHaveBeenCalled()
saveDialogCallback('/tmp/state')
saveDialogCallback(statePath)
expect(fs.writeSync).toHaveBeenCalled()
expect(fs.writeSync.argsForCall[0][0]).toBe '/tmp/state'
expect(fs.writeSync.argsForCall[0][0]).toBe statePath
expect(typeof fs.writeSync.argsForCall[0][1]).toBe 'string'
describe "when the escape key is pressed on the editor", ->

View File

@ -33,12 +33,15 @@ describe "fs", ->
expect(fs.exists(null)).toBe false
describe ".makeTree(path)", ->
aPath = path.join(temp.dir, 'a')
beforeEach ->
fs.remove("/tmp/a") if fs.exists("/tmp/a")
fs.remove(aPath) if fs.exists(aPath)
it "creates all directories in path including any missing parent directories", ->
fs.makeTree("/tmp/a/b/c")
expect(fs.exists("/tmp/a/b/c")).toBeTruthy()
abcPath = path.join(aPath, 'b', 'c')
fs.makeTree(abcPath)
expect(fs.exists(abcPath)).toBeTruthy()
describe ".traverseTreeSync(path, onFile, onDirectory)", ->
it "calls fn for every path in the tree at the given path", ->
@ -131,6 +134,7 @@ describe "fs", ->
describe ".absolute(relativePath)", ->
it "converts a leading ~ segment to the HOME directory", ->
expect(fs.absolute('~')).toBe fs.realpathSync(process.env.HOME)
expect(fs.absolute(path.join('~', 'does', 'not', 'exist'))).toBe path.join(process.env.HOME, 'does', 'not', 'exist')
homeDir = atom.getHomeDirPath()
expect(fs.absolute('~')).toBe fs.realpathSync(homeDir)
expect(fs.absolute(path.join('~', 'does', 'not', 'exist'))).toBe path.join(homeDir, 'does', 'not', 'exist')
expect(fs.absolute('~test')).toBe '~test'

View File

@ -8,18 +8,19 @@ describe "Git", ->
repo = null
beforeEach ->
fs.remove('/tmp/.git') if fs.isDirectorySync('/tmp/.git')
gitPath = path.join(temp.dir, '.git')
fs.remove(gitPath) if fs.isDirectorySync(gitPath)
afterEach ->
repo.destroy() if repo?.repo?
describe "@open(path)", ->
it "returns null when no repository is found", ->
expect(Git.open('/tmp/nogit.txt')).toBeNull()
expect(Git.open(path.join(temp.dir, 'nogit.txt'))).toBeNull()
describe "new Git(path)", ->
it "throws an exception when no repository is found", ->
expect(-> new Git('/tmp/nogit.txt')).toThrow()
expect(-> new Git(path.join(temp.dir, 'nogit.txt'))).toThrow()
describe ".getPath()", ->
it "returns the repository path for a .git directory path", ->

View File

@ -6,7 +6,7 @@ describe "LanguageMode", ->
describe "javascript", ->
beforeEach ->
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('language-javascript', sync: true)
editSession = project.openSync('sample.js', autoIndent: false)
{buffer, languageMode} = editSession
@ -97,7 +97,7 @@ describe "LanguageMode", ->
describe "coffeescript", ->
beforeEach ->
atom.activatePackage('coffee-script-tmbundle', sync: true)
atom.activatePackage('language-coffee-script', sync: true)
editSession = project.openSync('coffee.coffee', autoIndent: false)
{buffer, languageMode} = editSession
@ -144,7 +144,7 @@ describe "LanguageMode", ->
describe "css", ->
beforeEach ->
atom.activatePackage('css-tmbundle', sync: true)
atom.activatePackage('language-css', sync: true)
editSession = project.openSync('css.css', autoIndent: false)
{buffer, languageMode} = editSession
@ -185,8 +185,8 @@ describe "LanguageMode", ->
describe "less", ->
beforeEach ->
atom.activatePackage('less-tmbundle', sync: true)
atom.activatePackage('css-tmbundle', sync: true)
atom.activatePackage('language-less', sync: true)
atom.activatePackage('language-css', sync: true)
editSession = project.openSync('sample.less', autoIndent: false)
{buffer, languageMode} = editSession
@ -197,7 +197,7 @@ describe "LanguageMode", ->
describe "folding", ->
beforeEach ->
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('language-javascript', sync: true)
editSession = project.openSync('sample.js', autoIndent: false)
{buffer, languageMode} = editSession
@ -287,7 +287,7 @@ describe "LanguageMode", ->
describe "folding with comments", ->
beforeEach ->
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('language-javascript', sync: true)
editSession = project.openSync('sample-with-comments.js', autoIndent: false)
{buffer, languageMode} = editSession
@ -340,8 +340,8 @@ describe "LanguageMode", ->
describe "css", ->
beforeEach ->
atom.activatePackage('source-tmbundle', sync: true)
atom.activatePackage('css-tmbundle', sync: true)
atom.activatePackage('language-source', sync: true)
atom.activatePackage('language-css', sync: true)
editSession = project.openSync('css.css', autoIndent: true)
describe "suggestedIndentForBufferRow", ->

View File

@ -1,3 +1,5 @@
path = require 'path'
temp = require 'temp'
{Site} = require 'telepath'
{View} = require 'atom'
PaneContainer = require '../src/pane-container'
@ -13,7 +15,7 @@ describe "PaneContainer replication", ->
initialize: (@name) -> @text(@name)
serialize: -> { deserializer: 'TestView', @name }
getState: -> @serialize()
getUri: -> "/tmp/#{@name}"
getUri: -> path.join(temp.dir, @name)
isEqual: (other) -> @name is other.name
beforeEach ->

View File

@ -1,3 +1,5 @@
path = require 'path'
temp = require 'temp'
PaneContainer = require '../src/pane-container'
Pane = require '../src/pane'
{_, $, View, $$} = require 'atom'
@ -12,7 +14,7 @@ describe "PaneContainer", ->
@content: -> @div tabindex: -1
initialize: (@name) -> @text(@name)
serialize: -> { deserializer: 'TestView', @name }
getUri: -> "/tmp/#{@name}"
getUri: -> path.join(temp.dir, @name)
save: -> @saved = true
isEqual: (other) -> @name is other.name

View File

@ -1,7 +1,8 @@
PaneContainer = require '../src/pane-container'
Pane = require '../src/pane'
{$, View} = require 'atom'
{dirname} = require 'path'
path = require 'path'
temp = require 'temp'
describe "Pane", ->
[container, view1, view2, editSession1, editSession2, pane] = []
@ -372,7 +373,7 @@ describe "Pane", ->
pane.trigger 'core:save-as'
expect(atom.showSaveDialogSync).toHaveBeenCalledWith(dirname(editSession2.getPath()))
expect(atom.showSaveDialogSync).toHaveBeenCalledWith(path.dirname(editSession2.getPath()))
expect(editSession2.saveAs).toHaveBeenCalledWith('/selected/path')
describe "when the current item does not have a saveAs method", ->
@ -679,7 +680,7 @@ describe "Pane", ->
expect(pane.saveItem).not.toHaveBeenCalled()
expect(pane.activeItem.save).not.toHaveBeenCalled()
initialActiveItemUri = '/tmp/hi'
initialActiveItemUri = path.join(temp.dir, 'hi')
pane.activeView.trigger 'focusout'
expect(pane.activeItem.save).toHaveBeenCalled()
@ -697,7 +698,7 @@ describe "Pane", ->
expect(initialActiveItem.save).not.toHaveBeenCalled()
pane.showItem(initialActiveItem)
initialActiveItemUri = '/tmp/hi'
initialActiveItemUri = path.join(temp.dir, 'hi')
pane.showItem(view2)
expect(initialActiveItem.save).toHaveBeenCalled()
@ -716,7 +717,7 @@ describe "Pane", ->
pane.destroyItem(view2)
expect(pane.saveItem).not.toHaveBeenCalled()
initialActiveItemUri = '/tmp/hi'
initialActiveItemUri = path.join(temp.dir, 'hi')
pane.destroyItem(initialActiveItem)
expect(initialActiveItem.save).toHaveBeenCalled()

View File

@ -1,6 +1,7 @@
{$, $$, fs, RootView, View} = require 'atom'
Q = require 'q'
path = require 'path'
temp = require 'temp'
Pane = require '../src/pane'
describe "RootView", ->
@ -162,7 +163,7 @@ describe "RootView", ->
describe "when the title of the active pane item changes", ->
it "updates the window title based on the item's new title", ->
editSession = rootView.getActivePaneItem()
editSession.buffer.setPath('/tmp/hi')
editSession.buffer.setPath(path.join(temp.dir, 'hi'))
expect(rootView.title).toBe "#{editSession.getTitle()} - #{project.getPath()}"
describe "when the active pane's item changes", ->

View File

@ -25,7 +25,7 @@ describe "SpacePen extensions", ->
config.set("foo.bar", "hello")
expect(observeHandler).toHaveBeenCalledWith("hello")
expect(observeHandler).toHaveBeenCalledWith("hello", previous: undefined)
observeHandler.reset()
view.unobserveConfig()

View File

@ -1,28 +1,30 @@
{fs} = require 'atom'
path = require 'path'
temp = require 'temp'
TextMateGrammar = require '../src/text-mate-grammar'
describe "the `syntax` global", ->
beforeEach ->
atom.activatePackage('text-tmbundle', sync: true)
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('coffee-script-tmbundle', sync: true)
atom.activatePackage('ruby-tmbundle', sync: true)
atom.activatePackage('language-text', sync: true)
atom.activatePackage('language-javascript', sync: true)
atom.activatePackage('language-coffee-script', sync: true)
atom.activatePackage('language-ruby', sync: true)
describe "serialization", ->
it "remembers grammar overrides by path", ->
path = '/foo/bar/file.js'
expect(syntax.selectGrammar(path).name).not.toBe 'Ruby'
syntax.setGrammarOverrideForPath(path, 'source.ruby')
filePath = '/foo/bar/file.js'
expect(syntax.selectGrammar(filePath).name).not.toBe 'Ruby'
syntax.setGrammarOverrideForPath(filePath, 'source.ruby')
syntax2 = deserialize(syntax.serialize())
syntax2.addGrammar(grammar) for grammar in syntax.grammars when grammar isnt syntax.nullGrammar
expect(syntax2.selectGrammar(path).name).toBe 'Ruby'
expect(syntax2.selectGrammar(filePath).name).toBe 'Ruby'
describe ".selectGrammar(filePath)", ->
it "can use the filePath to load the correct grammar based on the grammar's filetype", ->
atom.activatePackage('git-tmbundle', sync: true)
atom.activatePackage('language-git', sync: true)
expect(syntax.selectGrammar("file.js").name).toBe "JavaScript" # based on extension (.js)
expect(syntax.selectGrammar("/tmp/.git/config").name).toBe "Git Config" # based on end of the path (.git/config)
expect(syntax.selectGrammar(path.join(temp.dir, '.git', 'config')).name).toBe "Git Config" # based on end of the path (.git/config)
expect(syntax.selectGrammar("Rakefile").name).toBe "Ruby" # based on the file's basename (Rakefile)
expect(syntax.selectGrammar("curb").name).toBe "Null Grammar"
expect(syntax.selectGrammar("/hu.git/config").name).toBe "Null Grammar"
@ -32,7 +34,7 @@ describe "the `syntax` global", ->
expect(syntax.selectGrammar(filePath).name).toBe "Ruby"
it "uses the number of newlines in the first line regex to determine the number of lines to test against", ->
atom.activatePackage('property-list-tmbundle', sync: true)
atom.activatePackage('language-property-list', sync: true)
fileContent = "first-line\n<html>"
expect(syntax.selectGrammar("dummy.coffee", fileContent).name).toBe "CoffeeScript"
@ -51,12 +53,12 @@ describe "the `syntax` global", ->
expect(fs.read).not.toHaveBeenCalled()
it "allows the default grammar to be overridden for a path", ->
path = '/foo/bar/file.js'
expect(syntax.selectGrammar(path).name).not.toBe 'Ruby'
syntax.setGrammarOverrideForPath(path, 'source.ruby')
expect(syntax.selectGrammar(path).name).toBe 'Ruby'
syntax.clearGrammarOverrideForPath(path)
expect(syntax.selectGrammar(path).name).not.toBe 'Ruby'
filePath = '/foo/bar/file.js'
expect(syntax.selectGrammar(filePath).name).not.toBe 'Ruby'
syntax.setGrammarOverrideForPath(filePath, 'source.ruby')
expect(syntax.selectGrammar(filePath).name).toBe 'Ruby'
syntax.clearGrammarOverrideForPath(filePath)
expect(syntax.selectGrammar(filePath).name).not.toBe 'Ruby'
describe "when multiple grammars have matching fileTypes", ->
it "selects the grammar with the longest fileType match", ->

View File

@ -154,7 +154,7 @@ describe 'TextBuffer', ->
[filePath, bufferToDelete] = []
beforeEach ->
filePath = "/tmp/atom-file-to-delete.txt"
filePath = path.join(temp.dir, 'atom-file-to-delete.txt')
fs.writeSync(filePath, 'delete me')
bufferToDelete = project.bufferForPathSync(filePath)
filePath = bufferToDelete.getPath() # symlinks may have been converted
@ -212,7 +212,7 @@ describe 'TextBuffer', ->
it "reports the modified status changing to true after the underlying file is deleted", ->
buffer.release()
filePath = "/tmp/atom-tmp-file"
filePath = path.join(temp.dir, 'atom-tmp-file')
fs.writeSync(filePath, 'delete me')
buffer = project.bufferForPathSync(filePath)
modifiedHandler = jasmine.createSpy("modifiedHandler")
@ -224,7 +224,7 @@ describe 'TextBuffer', ->
runs -> expect(buffer.isModified()).toBe true
it "reports the modified status changing to false after a modified buffer is saved", ->
filePath = "/tmp/atom-tmp-file"
filePath = path.join(temp.dir, 'atom-tmp-file')
fs.writeSync(filePath, '')
buffer.release()
buffer = project.bufferForPathSync(filePath)
@ -248,7 +248,7 @@ describe 'TextBuffer', ->
expect(buffer.isModified()).toBe true
it "reports the modified status changing to false after a modified buffer is reloaded", ->
filePath = "/tmp/atom-tmp-file"
filePath = path.join(temp.dir, 'atom-tmp-file')
fs.writeSync(filePath, '')
buffer.release()
buffer = project.bufferForPathSync(filePath)
@ -271,7 +271,7 @@ describe 'TextBuffer', ->
expect(buffer.isModified()).toBe true
it "reports the modified status changing to false after a buffer to a non-existent file is saved", ->
filePath = "/tmp/atom-tmp-file"
filePath = path.join(temp.dir, 'atom-tmp-file')
fs.remove(filePath) if fs.exists(filePath)
expect(fs.exists(filePath)).toBeFalsy()
buffer.release()
@ -464,7 +464,7 @@ describe 'TextBuffer', ->
filePath = null
beforeEach ->
filePath = '/tmp/temp.txt'
filePath = path.join(temp.dir, 'temp.txt')
fs.writeSync(filePath, "")
saveBuffer = project.bufferForPathSync(filePath)
saveBuffer.setText("blah")
@ -521,7 +521,7 @@ describe 'TextBuffer', ->
saveAsBuffer.release()
it "saves the contents of the buffer to the path", ->
filePath = '/tmp/temp.txt'
filePath = path.join(temp.dir, 'temp.txt')
fs.remove filePath if fs.exists(filePath)
saveAsBuffer = project.bufferForPathSync(null).retain()
@ -535,8 +535,8 @@ describe 'TextBuffer', ->
expect(eventHandler).toHaveBeenCalledWith(saveAsBuffer)
it "stops listening to events on previous path and begins listening to events on new path", ->
originalPath = "/tmp/original.txt"
newPath = "/tmp/new.txt"
originalPath = path.join(temp.dir, 'original.txt')
newPath = path.join(temp.dir, 'new.txt')
fs.writeSync(originalPath, "")
saveAsBuffer = project.bufferForPathSync(originalPath).retain()

View File

@ -6,13 +6,13 @@ describe "TextMateGrammar", ->
grammar = null
beforeEach ->
atom.activatePackage('text-tmbundle', sync: true)
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('coffee-script-tmbundle', sync: true)
atom.activatePackage('ruby-tmbundle', sync: true)
atom.activatePackage('html-tmbundle', sync: true)
atom.activatePackage('php-tmbundle', sync: true)
atom.activatePackage('python-tmbundle', sync: true)
atom.activatePackage('language-text', sync: true)
atom.activatePackage('language-javascript', sync: true)
atom.activatePackage('language-coffee-script', sync: true)
atom.activatePackage('language-ruby', sync: true)
atom.activatePackage('language-html', sync: true)
atom.activatePackage('language-php', sync: true)
atom.activatePackage('language-python', sync: true)
grammar = syntax.selectGrammar("hello.coffee")
describe "@loadSync(path)", ->
@ -211,8 +211,8 @@ describe "TextMateGrammar", ->
describe "when the pattern includes rules from another grammar", ->
describe "when a grammar matching the desired scope is available", ->
it "parses tokens inside the begin/end patterns based on the included grammar's rules", ->
atom.activatePackage('html-tmbundle', sync: true)
atom.activatePackage('ruby-on-rails-tmbundle', sync: true)
atom.activatePackage('language-html', sync: true)
atom.activatePackage('language-ruby-on-rails', sync: true)
grammar = syntax.grammarForScopeName('text.html.ruby')
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
@ -242,8 +242,8 @@ describe "TextMateGrammar", ->
expect(tokens[22]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"]
it "updates the grammar if the included grammar is updated later", ->
atom.activatePackage('html-tmbundle', sync: true)
atom.activatePackage('ruby-on-rails-tmbundle', sync: true)
atom.activatePackage('language-html', sync: true)
atom.activatePackage('language-ruby-on-rails', sync: true)
grammar = syntax.selectGrammar('foo.html.erb')
grammarUpdatedHandler = jasmine.createSpy("grammarUpdatedHandler")
@ -252,7 +252,7 @@ describe "TextMateGrammar", ->
{tokens} = grammar.tokenizeLine("<div class='name'><% <<-SQL select * from users;")
expect(tokens[12].value).toBe " select * from users;"
atom.activatePackage('sql-tmbundle', sync: true)
atom.activatePackage('language-sql', sync: true)
expect(grammarUpdatedHandler).toHaveBeenCalled()
{tokens} = grammar.tokenizeLine("<div class='name'><% <<-SQL select * from users;")
expect(tokens[12].value).toBe " "
@ -260,8 +260,8 @@ describe "TextMateGrammar", ->
describe "when a grammar matching the desired scope is unavailable", ->
it "updates the grammar if a matching grammar is added later", ->
atom.deactivatePackage('html-tmbundle')
atom.activatePackage('ruby-on-rails-tmbundle', sync: true)
atom.deactivatePackage('language-html')
atom.activatePackage('language-ruby-on-rails', sync: true)
grammar = syntax.grammarForScopeName('text.html.ruby')
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
@ -270,7 +270,7 @@ describe "TextMateGrammar", ->
expect(tokens[2]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
expect(tokens[3]).toEqual value: 'User', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","support.class.ruby"]
atom.activatePackage('html-tmbundle', sync: true)
atom.activatePackage('language-html', sync: true)
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
expect(tokens[1]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"]
@ -314,7 +314,7 @@ describe "TextMateGrammar", ->
describe "when inside a C block", ->
beforeEach ->
atom.activatePackage('c-tmbundle', sync: true)
atom.activatePackage('language-c', sync: true)
it "correctly parses a method. (regression)", ->
grammar = syntax.selectGrammar("hello.c")
@ -339,7 +339,7 @@ describe "TextMateGrammar", ->
describe "when a grammar has a pattern that has back references in the match value", ->
it "does not special handle the back references and instead allows oniguruma to resolve them", ->
atom.activatePackage('sass-tmbundle', sync: true)
atom.activatePackage('language-sass', sync: true)
grammar = syntax.selectGrammar("style.scss")
{tokens} = grammar.tokenizeLine("@mixin x() { -moz-selector: whatever; }")
expect(tokens[9]).toEqual value: "-moz-selector", scopes: ["source.css.scss", "meta.property-list.scss", "meta.property-name.scss"]
@ -428,14 +428,14 @@ describe "TextMateGrammar", ->
describe "when the grammar's pattern name has a group number in it", ->
it "replaces the group number with the matched captured text", ->
atom.activatePackage('hyperlink-helper-tmbundle', sync: true)
atom.activatePackage('language-hyperlink', sync: true)
grammar = syntax.grammarForScopeName("text.hyperlink")
{tokens} = grammar.tokenizeLine("https://github.com")
expect(tokens[0].scopes).toEqual ["text.hyperlink", "markup.underline.link.https.hyperlink"]
describe "when the grammar has an injection selector", ->
it "includes the grammar's patterns when the selector matches the current scope in other grammars", ->
atom.activatePackage('hyperlink-helper-tmbundle', sync: true)
atom.activatePackage('language-hyperlink', sync: true)
grammar = syntax.selectGrammar("text.js")
{tokens} = grammar.tokenizeLine("var i; // http://github.com")
@ -454,7 +454,7 @@ describe "TextMateGrammar", ->
expect(tokens[1].value).toBe " http://github.com"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.activatePackage('hyperlink-helper-tmbundle', sync: true)
atom.activatePackage('language-hyperlink', sync: true)
{tokens} = editSession.lineForScreenRow(0)
expect(tokens[2].value).toBe "http://github.com"
@ -481,7 +481,7 @@ describe "TextMateGrammar", ->
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.activatePackage('sql-tmbundle', sync: true)
atom.activatePackage('language-sql', sync: true)
{tokens} = editSession.lineForScreenRow(0)
expect(tokens[2].value).toBe "SELECT"
@ -514,7 +514,7 @@ describe "TextMateGrammar", ->
lines = null
beforeEach ->
atom.activatePackage('todo-tmbundle', sync: true)
atom.activatePackage('language-todo', sync: true)
grammar = syntax.selectGrammar('main.rb')
lines = grammar.tokenizeLines "# TODO be nicer"
@ -528,7 +528,7 @@ describe "TextMateGrammar", ->
describe "Git commit messages", ->
beforeEach ->
atom.activatePackage('git-tmbundle', sync: true)
atom.activatePackage('language-git', sync: true)
grammar = syntax.selectGrammar('COMMIT_EDITMSG')
lines = grammar.tokenizeLines """
longggggggggggggggggggggggggggggggggggggggggggggggg
@ -547,7 +547,7 @@ describe "TextMateGrammar", ->
describe "C++", ->
beforeEach ->
atom.activatePackage('c-tmbundle', sync: true)
atom.activatePackage('language-c', sync: true)
grammar = syntax.selectGrammar('includes.cc')
lines = grammar.tokenizeLines """
#include "a.h"
@ -585,8 +585,8 @@ describe "TextMateGrammar", ->
describe "Objective-C", ->
beforeEach ->
atom.activatePackage('c-tmbundle', sync: true)
atom.activatePackage('objective-c-tmbundle', sync: true)
atom.activatePackage('language-c', sync: true)
atom.activatePackage('language-objective-c', sync: true)
grammar = syntax.selectGrammar('function.mm')
lines = grammar.tokenizeLines """
void test() {
@ -612,7 +612,7 @@ describe "TextMateGrammar", ->
describe "Java", ->
beforeEach ->
atom.activatePackage('java-tmbundle', sync: true)
atom.activatePackage('language-java', sync: true)
grammar = syntax.selectGrammar('Function.java')
it "correctly parses single line comments", ->
@ -664,7 +664,7 @@ describe "TextMateGrammar", ->
describe "when the line contains unicode characters", ->
it "correctly parses tokens starting after them", ->
atom.activatePackage('json-tmbundle', sync: true)
atom.activatePackage('language-json', sync: true)
grammar = syntax.selectGrammar('package.json')
{tokens} = grammar.tokenizeLine '{"\u2026": 1}'

View File

@ -8,10 +8,25 @@ describe "ThemeManager", ->
themeManager = null
beforeEach ->
themeManager = new ThemeManager()
themeManager = new ThemeManager(atom.packages)
afterEach ->
themeManager.unload()
themeManager.deactivateThemes()
describe "theme getters and setters", ->
beforeEach ->
atom.packages.loadPackages()
it 'getLoadedThemes get all the loaded themes', ->
themes = themeManager.getLoadedThemes()
expect(themes.length).toBeGreaterThan(2)
it 'getActiveThemes get all the active themes', ->
themeManager.activateThemes()
names = atom.config.get('core.themes')
expect(names.length).toBeGreaterThan(0)
themes = themeManager.getActiveThemes()
expect(themes).toHaveLength(names.length)
describe "getImportPaths()", ->
it "returns the theme directories before the themes are loaded", ->
@ -32,7 +47,7 @@ describe "ThemeManager", ->
it "add/removes stylesheets to reflect the new config value", ->
themeManager.on 'reloaded', reloadHandler = jasmine.createSpy()
spyOn(themeManager, 'getUserStylesheetPath').andCallFake -> null
themeManager.load()
themeManager.activateThemes()
config.set('core.themes', [])
expect($('style.theme').length).toBe 0
@ -59,21 +74,7 @@ describe "ThemeManager", ->
describe "when a theme fails to load", ->
it "logs a warning", ->
spyOn(console, 'warn')
themeManager.activateTheme('a-theme-that-will-not-be-found')
expect(console.warn).toHaveBeenCalled()
describe "theme-loaded event", ->
beforeEach ->
spyOn(themeManager, 'getUserStylesheetPath').andCallFake -> null
themeManager.load()
it "fires when a new theme has been added", ->
themeManager.on 'theme-activated', loadHandler = jasmine.createSpy()
config.set('core.themes', ['atom-dark-syntax'])
expect(loadHandler).toHaveBeenCalled()
expect(loadHandler.mostRecentCall.args[0]).toBeInstanceOf AtomPackage
expect(-> atom.packages.activatePackage('a-theme-that-will-not-be-found')).toThrow()
describe "requireStylesheet(path)", ->
it "synchronously loads css at the given path and installs a style tag for it in the head", ->
@ -140,7 +141,7 @@ describe "ThemeManager", ->
window.rootView = new RootView
rootView.append $$ -> @div class: 'editor'
rootView.attachToDom()
themeManager.load()
themeManager.activateThemes()
it "loads the correct values from the theme's ui-variables file", ->
config.set('core.themes', ['theme-with-ui-variables'])

View File

@ -5,7 +5,7 @@ describe "TokenizedBuffer", ->
[tokenizedBuffer, buffer, changeHandler] = []
beforeEach ->
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('language-javascript', sync: true)
# enable async tokenization
TokenizedBuffer.prototype.chunkSize = 5
jasmine.unspy(TokenizedBuffer.prototype, 'tokenizeInBackground')
@ -319,7 +319,7 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains hard-tabs", ->
beforeEach ->
atom.activatePackage('coffee-script-tmbundle', sync: true)
atom.activatePackage('language-coffee-script', sync: true)
buffer = project.bufferForPathSync('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer({buffer})
startTokenizing(tokenizedBuffer)
@ -349,7 +349,7 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains surrogate pairs", ->
beforeEach ->
atom.activatePackage('javascript-tmbundle', sync: true)
atom.activatePackage('language-javascript', sync: true)
buffer = project.buildBufferSync 'sample-with-pairs.js', """
'abc\uD835\uDF97def'
//\uD835\uDF97xyz
@ -386,8 +386,8 @@ describe "TokenizedBuffer", ->
describe "when the grammar is updated because a grammar it includes is activated", ->
it "retokenizes the buffer", ->
atom.activatePackage('ruby-on-rails-tmbundle', sync: true)
atom.activatePackage('ruby-tmbundle', sync: true)
atom.activatePackage('language-ruby-on-rails', sync: true)
atom.activatePackage('language-ruby', sync: true)
buffer = project.bufferForPathSync(null, "<div class='name'><%= User.find(2).full_name %></div>")
tokenizedBuffer = new TokenizedBuffer({buffer})
@ -397,7 +397,7 @@ describe "TokenizedBuffer", ->
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
atom.activatePackage('html-tmbundle', sync: true)
atom.activatePackage('language-html', sync: true)
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]

View File

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

View File

@ -14,6 +14,7 @@ shell = require 'shell'
{$$} = require 'space-pen'
crypto = require 'crypto'
path = require 'path'
os = require 'os'
dialog = remote.require 'dialog'
app = remote.require 'app'
{Document} = require 'telepath'
@ -55,7 +56,7 @@ class Atom
@__defineSetter__ 'packageStates', (packageStates) => @packages.packageStates = packageStates
@subscribe @packages, 'loaded', => @watchThemes()
@themes = new ThemeManager()
@themes = new ThemeManager(@packages)
@contextMenu = new ContextMenuManager(devMode)
@menu = new MenuManager()
@pasteboard = new Pasteboard()
@ -220,6 +221,9 @@ class Atom
inDevMode: ->
@getLoadSettings().devMode
inSpecMode: ->
@getLoadSettings().isSpec
toggleFullScreen: ->
@setFullScreen(!@isFullScreen())
@ -230,7 +234,10 @@ class Atom
@getCurrentWindow().isFullScreen()
getHomeDirPath: ->
app.getHomeDir()
process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']
getTempDirPath: ->
if process.platform is 'win32' then os.tmpdir() else '/tmp'
# Public: Get the directory path to Atom's configuration area.
getConfigDirPath: ->

View File

@ -8,13 +8,14 @@ dialog = require 'dialog'
fs = require 'fs'
ipc = require 'ipc'
path = require 'path'
os = require 'os'
net = require 'net'
shell = require 'shell'
url = require 'url'
{EventEmitter} = require 'events'
_ = require 'underscore-plus'
socketPath = '/tmp/atom.sock'
socketPath = path.join(os.tmpdir(), 'atom.sock')
# Private: The application's singleton class.
#
@ -245,7 +246,7 @@ class AtomApplication
[basename, initialLine] = path.basename(pathToOpen).split(':')
if initialLine
pathToOpen = "#{path.dirname(pathToOpen)}/#{basename}"
initialLine -= 1 if initialLine # Convert line numbers to a base of 0
initialLine -= 1 # Convert line numbers to a base of 0
unless devMode
existingWindow = @windowForPath(pathToOpen) unless pidToKillWhenClosed or newWindow

View File

@ -1,12 +1,14 @@
crypto = require 'crypto'
fs = require 'fs'
path = require 'path'
os = require 'os'
CoffeeScript = require 'coffee-script'
CSON = require 'season'
mkdir = require('mkdirp').sync
cacheDir = '/tmp/atom-compile-cache'
tmpDir = if process.platform is 'win32' then os.tmpdir() else '/tmp'
cacheDir = path.join(tmpDir, 'atom-compile-cache')
coffeeCacheDir = path.join(cacheDir, 'coffee')
CSON.setCacheDir(path.join(cacheDir, 'cson'))

View File

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

View File

@ -13,10 +13,12 @@ fsExtensions =
absolute: (relativePath) ->
return null unless relativePath?
homeDir = process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']
if relativePath is '~'
relativePath = process.env.HOME
relativePath = homeDir
else if relativePath.indexOf('~/') is 0
relativePath = "#{process.env.HOME}#{relativePath.substring(1)}"
relativePath = "#{homeDir}#{relativePath.substring(1)}"
try
fs.realpathSync(relativePath)

View File

@ -1,12 +1,15 @@
path = require 'path'
os = require 'os'
LessCache = require 'less-cache'
{Subscriber} = require 'emissary'
tmpDir = if process.platform is 'win32' then os.tmpdir() else '/tmp'
module.exports =
class LessCompileCache
Subscriber.includeInto(this)
@cacheDir: '/tmp/atom-compile-cache/less'
@cacheDir: path.join(tmpDir, 'atom-compile-cache', 'less')
constructor: ->
@cache = new LessCache

View File

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

View File

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

View File

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

View File

@ -10,7 +10,8 @@ async = require 'async'
module.exports =
class TextMatePackage extends Package
@testName: (packageName) ->
/(\.|_|-)tmbundle$/.test(packageName)
packageName = path.basename(packageName)
/(^language-.+)|((\.|_|-)tmbundle$)/.test(packageName)
@getLoadQueue: ->
return @loadQueue if @loadQueue

View File

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

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

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

View File

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

View File

@ -9,6 +9,6 @@
}
&.nav-pills > li.active > a {
background-color: @background-color-info;
background-color: @background-color-selected;
}
}

View File

@ -1,16 +1,21 @@
path = require 'path'
os = require 'os'
module.exports = (grunt) ->
{rm} = require('./task-helpers')(grunt)
grunt.registerTask 'partial-clean', 'Delete some of the build files', ->
tmpdir = if process.platform is 'win32' then os.tmpdir() else '/tmp'
rm grunt.config.get('atom.buildDir')
rm require('../src/coffee-cache').cacheDir
rm require('../src/less-compile-cache').cacheDir
rm '/tmp/atom-cached-atom-shells'
rm path.join(tmpdir, 'atom-cached-atom-shells')
rm 'atom-shell'
grunt.registerTask 'clean', 'Delete all the build files', ->
homeDir = process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']
rm 'node_modules'
rm path.join(process.env.HOME, '.atom', '.node-gyp')
rm path.join(homeDir, '.atom', '.node-gyp')
grunt.task.run('partial-clean')

View File

@ -0,0 +1,108 @@
async = require 'async'
request = require 'request'
# Configure and publish all packages in package.json to atom.io
#
# This task should be run whenever you want to be sure that atom.io contains
# all the packages and versions referenced in Atom's package.json file.
module.exports = (grunt) ->
{spawn} = require('./task-helpers')(grunt)
baseUrl = "https://www.atom.io/api/packages"
packageExists = (packageName, token, callback) ->
requestSettings =
url: "#{baseUrl}/#{packageName}"
json: true
headers:
authorization: token
request.get requestSettings, (error, response, body={}) ->
if error?
callback(error)
else
callback(null, response.statusCode is 404)
createPackage = (packageName, token, callback) ->
requestSettings =
url: baseUrl
json: true
headers:
authorization: token
method: 'POST'
body:
repository: "atom/#{packageName}"
request.get requestSettings, (error, response, body={}) ->
if error?
callback(error)
else if response.statusCode isnt 201
message = body.message ? body.error ? body
callback(new Error("Creating package failed: #{message}"))
else
callback()
createPackageVersion = (packageName, tag, token, callback) ->
requestSettings =
url: "#{baseUrl}/#{packageName}/versions"
json: true
method: 'POST'
headers:
authorization: token
body:
tag: tag
request.get requestSettings, (error, response, body={}) ->
if error?
callback(error)
else if response.statusCode isnt 201
message = body.message ? body.error ? body
if message is 'Version exists'
callback()
else
callback(new Error("Creating new version failed: #{message}"))
else
callback()
getToken = (callback) ->
if token = process.env.ATOM_ACCESS_TOKEN
callback(null, token)
else
spawn {cmd: 'security', args: ['-q', 'find-generic-password', '-ws', 'GitHub API Token']}, (error, result, code) ->
token = result.toString() unless error?
callback(error, token)
grunt.registerTask 'publish-packages', 'Publish all bundled packages', ->
done = @async()
getToken (error, token) ->
unless token
grunt.log.error('Token not found in keychain or ATOM_ACCESS_TOKEN environment variable')
done(false)
{packageDependencies} = grunt.file.readJSON('package.json') ? {}
tasks = []
for name, version of packageDependencies
do (name, version) ->
tasks.push (callback) ->
grunt.log.writeln("Publishing #{name}@#{version}")
tag = "v#{version}"
packageExists name, token, (error, exists) ->
if error?
callback(error)
return
if exists
createPackage name, token, (error) ->
if error?
callback(error)
else
createPackageVersion(name, tag, token, callback)
else
createPackageVersion(name, tag, token, callback)
async.waterfall tasks, (error) ->
if error?
grunt.log.error(error.message)
done(false)
else
done()

View File

@ -1,5 +1,6 @@
fs = require 'fs'
path = require 'path'
os = require 'os'
request = require 'request'
formidable = require 'formidable'
@ -70,7 +71,11 @@ module.exports = (grunt) ->
else
null
getCachePath = (version) -> "/tmp/atom-cached-atom-shells/#{version}"
getTempDir = ->
if process.platform is 'win32' then os.tmpdir() else '/tmp'
getCachePath = (version) ->
path.join(getTempDir(), 'atom-cached-atom-shells', version)
isAtomShellVersionCached = (version) ->
grunt.file.isFile(getCachePath(version), 'version')

2
vendor/apm vendored

@ -1 +1 @@
Subproject commit 6ef00ae8b1fc7c6ca96f6ce38859cd31db1f7430
Subproject commit 0bd38255ea0aa0fec2a932177b30a388c2d16ded