Merge branch 'master' into ns-workspace-specs-cleanup

This commit is contained in:
Nathan Sobo 2014-01-28 18:25:13 -07:00
commit 29f480661c
68 changed files with 823 additions and 798 deletions

View File

@ -1,4 +1,4 @@
# Atom — The hackable, ~~collaborative~~ editor
# Atom — The hackable editor
![Atom](http://i.imgur.com/OrTvUAD.png)

View File

@ -7,7 +7,7 @@
},
"dependencies": {
"async": "~0.2.9",
"biscotto": "0.0.17",
"biscotto": "git://github.com/atom/biscotto.git#12188bfbe5f7303fa9f1aa3c4f8662d40ce3c3be",
"first-mate": "~0.13.0",
"formidable": "~1.0.14",
"fs-plus": "1.x",
@ -19,7 +19,7 @@
"grunt-contrib-coffee": "~0.7.0",
"grunt-contrib-less": "~0.8.0",
"grunt-cson": "0.5.0",
"grunt-download-atom-shell": "git+https://atom-bot:362295be4c5258d3f7b967bbabae662a455ca2a7@github.com/atom/grunt-download-atom-shell#v0.5.0",
"grunt-download-atom-shell": "git+https://atom-bot:362295be4c5258d3f7b967bbabae662a455ca2a7@github.com/atom/grunt-download-atom-shell#v0.6.0",
"grunt-lesslint": "0.13.0",
"grunt-markdown": "~0.4.0",
"grunt-peg": "~1.1.0",
@ -30,6 +30,7 @@
"rcedit": "~0.1.2",
"request": "~2.27.0",
"rimraf": "~2.2.2",
"runas": "~0.3.0",
"unzip": "~0.1.9",
"vm-compatibility-layer": "~0.1.0",
"walkdir": "0.0.7"

View File

@ -5,12 +5,14 @@ fs = require 'fs-plus'
request = require 'request'
module.exports = (grunt) ->
{rm} = require('./task-helpers')(grunt)
cmd = path.join('node_modules', '.bin', 'coffee')
commonArgs = [path.join('build', 'node_modules', '.bin', 'biscotto'), '--']
opts =
stdio: 'inherit'
grunt.registerTask 'build-docs', 'Builds the API docs in src/app', ->
grunt.registerTask 'build-docs', 'Builds the API docs in src', ->
done = @async()
downloadFileFromRepo = ({repo, file}, callback) ->
@ -34,6 +36,7 @@ module.exports = (grunt) ->
if error?
done(error)
else
rm('docs/output/api')
args = [
commonArgs...
'--title', 'Atom API Documentation'

View File

@ -1,11 +1,25 @@
path = require 'path'
module.exports = (grunt) ->
{cp, mkdir, rm} = require('./task-helpers')(grunt)
{cp, mkdir, rm, spawn} = require('./task-helpers')(grunt)
grunt.registerTask 'install', 'Install the built application', ->
installDir = grunt.config.get('atom.installDir')
shellAppDir = grunt.config.get('atom.shellAppDir')
if process.platform is 'win32'
done = @async()
runas = require 'runas'
copyFolder = path.resolve 'script', 'copy-folder.cmd'
# cmd /c ""script" "source" "destination""
arg = "/c \"\"#{copyFolder}\" \"#{shellAppDir}\" \"#{installDir}\"\""
if runas('cmd', [arg], hide: true) isnt 0
done("Failed to copy #{shellAppDir} to #{installDir}")
createShortcut = path.resolve 'script', 'create-shortcut.cmd'
args = ['/c', createShortcut, path.join(installDir, 'atom.exe'), 'Atom']
spawn {cmd: 'cmd', args}, done
else
rm installDir
mkdir path.dirname(installDir)
cp shellAppDir, installDir

View File

@ -41,7 +41,7 @@ module.exports = (grunt) ->
strings =
CompanyName: 'GitHub, Inc.'
FileDescription: 'The hackable, collaborative editor'
FileDescription: 'The hackable editor'
LegalCopyright: 'Copyright (C) 2013 GitHub, Inc. All rights reserved'
ProductName: 'Atom'
ProductVersion: version

View File

@ -14,12 +14,26 @@ overview of the main editor API.
Check out the [Atom][Atom] class docs to see what globals are available and
what they provide.
You can also require many of these classes in your packages via:
You can also require many of these classes in your package via:
```coffee
{EditorView} = require 'atom'
```
The classes available from `require 'atom'` are:
* [BufferedProcess][BufferedProcess]
* [BufferedNodeProcess][BufferedNodeProcess]
* [Directory][Directory]
* [EditorView][EditorView]
* [File][File]
* [Git][Git]
* [Point][Point]
* [Range][Range]
* [ScrollView][ScrollView]
* [SelectList][SelectList]
* [View][View]
* [WorkspaceView][WorkspaceView]
### How do I create a package?
You probably want to read the [creating a package][creating-a-package]
@ -31,7 +45,18 @@ Atom ships with node 0.11.10 and the comprehensive node API docs are available
[here][node-docs].
[Atom]: ../classes/Atom.html
[BufferedProcess]: ../classes/BufferedProcess.html
[BufferedNodeProcess]: ../classes/BufferedNodeProcess.html
[Directory]: ../classes/Directory.html
[Editor]: ../classes/Editor.html
[EditorView]: ../classes/EditorView.html
[File]: ../classes/File.html
[Git]: ../classes/Git.html
[Point]: ../classes/Point.html
[Range]: ../classes/Range.html
[ScrollView]: ../classes/ScrollView.html
[SelectList]: ../classes/SelectList.html
[View]: ../classes/View.html
[WorkspaceView]: ../classes/WorkspaceView.html
[creating-a-package]: https://www.atom.io/docs/latest/creating-a-package
[node-docs]: http://nodejs.org/docs/v0.11.10/api

View File

@ -0,0 +1,76 @@
## Configuration API
### Reading Config Settings
If you are writing a package that you want to make configurable, you'll need to
read config settings via the `atom.config` global. You can read the current
value of a namespaced config key with `atom.config.get`:
```coffeescript
# read a value with `config.get`
@showInvisibles() if atom.config.get "editor.showInvisibles"
```
Or you can use the `::observeConfig` to track changes from any view object.
```coffeescript
class MyView extends View
initialize: ->
@observeConfig 'editor.fontSize', () =>
@adjustFontSize()
```
The `::observeConfig` method will call the given callback immediately with the
current value for the specified key path, and it will also call it in the future
whenever the value of that key path changes.
Subscriptions made with `observeConfig` are automatically canceled when the
view is removed. You can cancel config subscriptions manually via the
`unobserveConfig` method.
```coffeescript
view1.unobserveConfig() # unobserve all properties
```
You can add the ability to observe config values to non-view classes by
extending their prototype with the `ConfigObserver` mixin:
```coffeescript
{ConfigObserver} = require 'atom'
class MyClass
ConfigObserver.includeInto(this)
constructor: ->
@observeConfig 'editor.showInvisibles', -> # ...
destroy: ->
@unobserveConfig()
```
### Writing Config Settings
The `atom.config` database is populated on startup from `~/.atom/config.cson`,
but you can programmatically write to it with `atom.config.set`:
```coffeescript
# basic key update
atom.config.set("core.showInvisibles", true)
```
You should never mutate the value of a config key, because that would circumvent
the notification of observers. You can however use methods like `pushAtKeyPath`,
`unshiftAtKeyPath`, and `removeAtKeyPath` to manipulate mutable config values.
```coffeescript
atom.config.pushAtKeyPath("core.disabledPackages", "wrap-guide")
atom.config.removeAtKeyPath("core.disabledPackages", "terminal")
```
You can also use `setDefaults`, which will assign default values for keys that
are always overridden by values assigned with `set`. Defaults are not written
out to the the `config.json` file to prevent it from becoming cluttered.
```coffeescript
atom.config.setDefaults("editor", fontSize: 18, showInvisibles: true)
```

34
docs/advanced/globals.md Normal file
View File

@ -0,0 +1,34 @@
# Globals
Atom exposes several services through singleton objects accessible via the
`atom` global:
* atom
* workspace:
Manipulate and query the state of the user interface for the current
window. Open editors, manipulate panes.
* workspaceView:
Similar to workspace, but provides access to the root of all views in the
current window.
* project:
Access the directory associated with the current window. Load editors,
perform project-wide searches, register custom openers for special file
types.
* config:
Read, write, and observe user configuration settings.
* keymap:
Add and query the currently active keybindings.
* deserializers:
Deserialize instances from their state objects and register deserializers.
* packages:
Activate, deactivate, and query user packages.
* themes:
Activate, deactivate, and query user themes.
* contextMenu:
Register context menus.
* menu:
Register application menus.
* pasteboard:
Read from and write to the system pasteboard.
* syntax:
Assign and query syntactically-scoped properties.

123
docs/advanced/keymaps.md Normal file
View File

@ -0,0 +1,123 @@
# Keymaps In-Depth
## Structure of a Keymap File
Keymap files are encoded as JSON or CSON files containing nested hashes. They
work much like stylesheets, but instead of applying style properties to elements
matching the selector, they specify the meaning of keystrokes on elements
matching the selector. Here is an example of some bindings that apply when
keystrokes pass through elements with the class `.editor`:
```coffee
'.editor':
'cmd-delete': 'editor:backspace-to-beginning-of-line'
'alt-backspace': 'editor:backspace-to-beginning-of-word'
'ctrl-A': 'editor:select-to-first-character-of-line'
'ctrl-shift-e': 'editor:select-to-end-of-line'
'cmd-left': 'editor:move-to-first-character-of-line'
'.editor:not(.mini)'
'cmd-alt-[': 'editor:fold-current-row'
'cmd-alt-]': 'editor:unfold-current-row'
```
Beneath the first selector are several bindings, mapping specific *keystroke
patterns* to *commands*. When an element with the `.editor` class is focused and
`cmd-delete` is pressed, an custom DOM event called
`editor:backspace-to-beginning-of-line` is emitted on the `.editor` element.
The second selector group also targets editors, but only if they don't have the
`.mini` class. In this example, the commands for code folding don't really make
sense on mini-editors, so the selector restricts them to regular editors.
### Keystroke Patterns
Keystroke patterns express one or more keystrokes combined with optional
modifier keys. For example: `ctrl-w v`, or `cmd-shift-up`. A keystroke is
composed of the following symbols, separated by a `-`. A multi-keystroke pattern
can be expressed as keystroke patterns separated by spaces.
| Type | Examples
| --------------------|----------------------------
| Character literals | `a` `4` `$`
| Modifier keys | `cmd` `ctrl` `alt` `shift`
| Special keys | `enter` `escape` `backspace` `delete` `tab` `home` `end` `pageup` `pagedown` `left` `right` `up` `down`
### Commands
Commands are custom DOM events that are triggered when a keystroke matches a
binding. This allows user interface code to listen for named commands without
specifying the specific keybinding that triggers it. For example, the following
code sets up {EditorView} to listen for commands to move the cursor to the first
character of the current line:
```coffee
class EditorView
listenForEvents: ->
@command 'editor:move-to-first-character-of-line', =>
@editor.moveCursorToFirstCharacterOfLine()
```
The `::command` method is basically an enhanced version of jQuery's `::on`
method that listens for a custom DOM event and adds some metadata to the DOM,
which is read by the command palette.
When you are looking to bind new keys, it is often useful to use the command
palette (`ctrl-shift-p`) to discover what commands are being listened for in a
given focus context. Commands are "humanized" following a simple algorithm, so a
command like `editor:fold-current-row` would appear as "Editor: Fold Current
Row".
### Specificity and Cascade Order
As is the case with CSS applying styles, when multiple bindings match for a
single element, the conflict is resolved by choosing the most *specific*
selector. If two matching selectors have the same specificity, the binding
for the selector appearing later in the cascade takes precedence.
Currently, there's no way to specify selector ordering within a single keymap,
because JSON objects do not preserve order. We eventually plan to introduce a
custom CSS-like file format for keymaps that allows for ordering within a single
file. For now, we've opted to handle cases where selector ordering is critical
by breaking the keymap into two separate files, such as `snippets-1.cson` and
`snippets-2.cson`.
## Overloading Key Bindings
Occasionally, it makes sense to layer multiple actions on top of the same key
binding. An example of this is the snippets package. Snippets are inserted by
typing a snippet prefix such as `for` and then pressing `tab`. Every time `tab`
is pressed, we want to execute code attempting to expand a snippet if one exists
for the text preceding the cursor. If a snippet *doesn't* exist, we want `tab`
to actually insert whitespace.
To achieve this, the snippets package makes use of the `.abortKeyBinding()`
method on the event object representing the `snippets:expand` command.
```coffee-script
# pseudo-code
editor.command 'snippets:expand', (e) =>
if @cursorFollowsValidPrefix()
@expandSnippet()
else
e.abortKeyBinding()
```
When the event handler observes that the cursor does not follow a valid prefix,
it calls `e.abortKeyBinding()`, telling the keymap system to continue searching
for another matching binding.
## Step-by-Step: How Keydown Events are Mapped to Commands
* A keydown event occurs on a *focused* element.
* Starting at the focused element, the keymap walks upward towards the root of
the document, searching for the most specific CSS selector that matches the
current DOM element and also contains a keystroke pattern matching the keydown
event.
* When a matching keystroke pattern is found, the search is terminated and the
pattern's corresponding command is triggered on the current element.
* If `.abortKeyBinding()` is called on the triggered event object, the search
is resumed, triggering a binding on the next-most-specific CSS selector for
the same element or continuing upward to parent elements.
* If no bindings are found, the event is handled by Chromium normally.

View File

@ -154,7 +154,7 @@ loaded in alphabetical order. An optional `keymaps` array in your _package.json_
can specify which keymaps to load and in what order.
Keybindings are executed by determining which element the keypress occured on. In
Keybindings are executed by determining which element the keypress occurred on. In
the example above, `changer:magic` command is executed when pressing `ctrl-V` on
the `.tree-view-scroller` element.
@ -381,7 +381,7 @@ Additional libraries can be found by browsing Atom's *node_modules* folder.
[apm]: https://github.com/atom/apm
[git-tag]: http://git-scm.com/book/en/Git-Basics-Tagging
[wrap-guide]: https://github.com/atom/wrap-guide/
[keymaps]: internals/keymaps.md
[keymaps]: advanced/keymaps.md
[theme-variables]: theme-variables.md
[tm-tokens]: http://manual.macromates.com/en/language_grammars.html
[spacepen]: https://github.com/nathansobo/space-pen

View File

@ -19,13 +19,11 @@ bindings][key-bindings] section.
### Working With Files
Atom windows are scoped to the directory they're opened from. If you launch Atom
from the command line everything will be relative to the current directory. This
means that the tree view on the left will only show files contained within that
directory.
This can be a useful way to organize multiple projects, as each project will be
contained within its own window.
Atom windows are scoped to a single directory on disk. If you launch Atom from
the command line via the `atom` command and don't specify a path, Atom opens a
window for the current working directory. The current window's directory will be
visible as the root of the tree view at the left, and also serve as the context
for all file-related operations.
#### Finding Files
@ -34,20 +32,17 @@ begin typing the name of the file you're looking for. If you are looking for a
file that is already open press `cmd-b` to bring up a searchable list of open
files.
You can also use the tree view to navigate to a file. To open or move focus to
the tree view, press `cmd-\`. You can then navigate to a file using the arrow
keys and select it with `return`.
You can also use the tree view to navigate to a file. To open and focus the
the tree view, press `ctrl-0`. The tree view can be toggled open and closed with
`cmd-\`.
#### Adding, Moving, Deleting Files
Currently, all file modification is performed via the tree view. To add a file,
select a directory in the tree view and press `a`. Then type the name of the
file. Any intermediate directories you type will be created automatically if
needed.
To move or rename a file or directory, select it in the tree view and press `m`.
To delete a file, select it in the tree view and press `delete`.
Currently, all file modification is performed via the tree view. Add, move, or
delete a file by right-clicking in the tree view and selecting the desired
operation from the context menu. You can also perform these operations from the
keyboard by selecting a file or directory and using `a` to add, `m` to move, and
`delete` to delete.
### Searching
@ -58,35 +53,43 @@ To search within a buffer use `cmd-f`. To search the entire project use
#### Navigating By Symbols
If you want to jump to a method press `cmd-r`. It opens a list of all symbols
in the current file.
To jump to a symbol such as a method definition, press `cmd-r`. This opens a
list of all symbols in the current file, which you can fuzzy filter similarly to
`cmd-t`.
To search for symbols across your project use `cmd-shift-r`, but you'll need to
make sure you have a ctags installed and a tags file generated for your project.
Also, if you're editing CoffeeScript, it's a good idea to update your `~/.ctags`
file to understand the language. Here is [a good example][ctags].
To search for symbols across your project, use `cmd-shift-r`. First you'll need
to make sure you have ctags installed and a tags file generated for your
project. Also, if you're editing CoffeeScript, it's a good idea to update your
`~/.ctags` file to understand the language. Here is [a good example][ctags].
### Split Panes
You can split any editor pane horizontally or vertically by using `cmd-k right` or
`cmd-k down`. Once you have a split pane, you can move focus between them with
`cmd-k cmd-right` or `cmd-k cmd-down`. To close a pane, close all tabs inside it.
You can split any editor pane horizontally or vertically by using `cmd-k right`
or `cmd-k down`. Once you have a split pane, you can move focus between them
with `cmd-k cmd-right` or `cmd-k cmd-down`. To close a pane, close all its
editors with `meta-w`, then press `meta-w` one more time to close the pane. You
can configure panes to auto-close with empty in the preferences.
### Folding
You can fold everything with `alt-cmd-{` and unfold everything with
`alt-cmd-}`. Or, you can fold / unfold by a single level with `alt-cmd-[` and
`alt-cmd-]`.
You can fold blocks of code by clicking the arrows that appear when you hover
your mouse cursor over the gutter. You can also fold and unfold from the
keyboard with `alt-cmd-[` and `alt-cmd-]`. To fold everything, use
`alt-cmd-shift-{` and to unfold everything use `alt-cmd-shift-}`. You can also
fold at a specific indentation level with `cmd-k cmd-N` where N is the
indentation depth.
### Soft-Wrap
If you want to toggle soft wrap, trigger the command from the command palette.
Press `cmd-shift-P` to open the palette, then type "wrap" to find the correct
command.
command. By default, lines will wrap based on the size of the editor. If you
prefer to wrap at a specific line length, toggle "Wrap at preferred line length"
in preferences.
## Configuration
Press `cmd-,` to display the a settings pane. This serves as the primary
Press `cmd-,` to display the preferences pane. This serves as the primary
interface for adjusting config settings, installing packages and changing
themes.

View File

@ -7,7 +7,7 @@
### Advanced Topics
* [Configuration](internals/configuration.md)
* [Keymaps](internals/keymaps.md)
* [Serialization](internals/serialization.md)
* [View System](internals/view-system.md)
* [Configuration](advanced/configuration.md)
* [Keymaps](advanced/keymaps.md)
* [Serialization](advanced/serialization.md)
* [View System](advanced/view-system.md)

View File

@ -1,61 +0,0 @@
## Configuration API
### Reading Config Settings
If you are writing a package that you want to make configurable, you'll need to
read config settings. You can read a value from `config` with `config.get`:
```coffeescript
# read a value with `config.get`
@showInvisibles() if config.get "edtior.showInvisibles"
```
Or you can use `observeConfig` to track changes from a view object.
```coffeescript
class MyView extends View
initialize: ->
@observeConfig 'editor.fontSize', () =>
@adjustFontSize()
```
The `observeConfig` method will call the given callback immediately with the
current value for the specified key path, and it will also call it in the future
whenever the value of that key path changes.
Subscriptions made with `observeConfig` are automatically canceled when the
view is removed. You can cancel config subscriptions manually via the
`unobserveConfig` method.
```coffeescript
view1.unobserveConfig() # unobserve all properties
```
You can add the ability to observe config values to non-view classes by
extending their prototype with the `ConfigObserver` mixin:
```coffeescript
ConfigObserver = require 'config-observer'
_.extend MyClass.prototype, ConfigObserver
```
### Writing Config Settings
As discussed above, the config database is automatically populated from
`config.cson` when Atom is started, but you can programmatically write to it in
the following way:
```coffeescript
# basic key update
config.set("core.showInvisibles", true)
config.pushAtKeyPath("core.disabledPackages", "wrap-guide")
```
You can also use `setDefaults`, which will assign default values for keys that
are always overridden by values assigned with `set`. Defaults are not written out
to the the `config.json` file to prevent it from becoming cluttered.
```coffeescript
config.setDefaults("editor", fontSize: 18, showInvisibles: true)
```

View File

@ -1,69 +0,0 @@
## Keymaps In-Depth
### Structure of a Keymap File
Keymap files are encoded as JSON or CSON files containing nested hashes. The
top-level keys of a keymap are **CSS 3 selectors**, which specify a particular
context in Atom's interface. Common selectors are `.editor`, which scopes
bindings to just work when an editor is focused, and `body`, which scopes
bindings globally.
Beneath the selectors are hashes mapping **keystroke patterns** to
**semantic events**. A keystroke pattern looks like the following examples.
Note that the last example describes multiple keystrokes in succession:
- `p`
- `2`
- `ctrl-p`
- `ctrl-alt-cmd-p`
- `tab`
- `escape`
- `enter`
- `ctrl-w w`
A semantic event is the name of the custom event that will be triggered on the
target of the keydown event when a key binding matches. You can use the command
palette (bound to `cmd-shift-P`), to get a list of relevant events and their bindings
in any focused context in Atom.
### Rules for Mapping A Keydown Event to A Semantic Event
A keymap's job is to translate a physical keystroke event (like `cmd-D`) into a
semantic event (like `editor:duplicate-line`). Whenever a keydown event occurs
on a focused element, it bubbles up the DOM as usual. As soon as an element on
the bubble path matches a key binding for the keystroke, the binding's semantic
event is triggered on the original target of the keydown event. Just as with
CSS, if multiple selectors match an element, the most specific selector is
favored. If two selectors have the same specificity, the selector that occurs
latest in the cascade is favored.
Currently, there's no way to specify selector ordering within a single keymap,
because JSON hashes do not preserve order. Rather than making the format more
awkward in order to preserve order, we've opted to handle cases where order is
critical by breaking the keymap into two separate files, such as
`snippets-1.cson` and `snippets-2.cson`.
### Overloading Key Bindings
Occasionally, it makes sense to layer multiple actions on top of the same key
binding. An example of this is the snippets package. You expand a snippet by
pressing `tab` immediately following a snippet's prefix. But if the cursor is
not following a valid snippet prefix, then we want tab to perform its normal
action (probably inserting a tab character or the appropriate number of spaces).
To achieve this, the snippets package makes use of the `abortKeyBinding` method
on the event object that's triggered by the binding for `tab`.
```coffee-script
# pseudo-code
editor.command 'snippets:expand', (e) =>
if @cursorFollowsValidPrefix()
@expandSnippet()
else
e.abortKeyBinding()
```
When the event handler observes that the cursor does not follow a valid prefix,
it calls `e.abortKeyBinding()`, which tells the keymap system to continue
searching up the cascade for another matching binding. In this case, the default
implementation of `tab` ends up getting triggered.

View File

@ -1,70 +0,0 @@
**Polish the user experience**
First and foremost, Atom is a **product**. Atom needs to feel familiar and
inviting. This includes a solid introductory experience and parity with the most
important features of Sublime Text.
* First launch UI and flow (actions below should be easily discoverable)
* Create a new file
* Open a project and edit an existing file
* Install a package
* Change settings (adjust theme, change key bindings, set config options)
* How to use command P
* Use collaboration internally
* How and where to edit keyBinding should be obvious to new users
* Finish find and replace in buffer/project
* Atom should start < 300ms
* Match Sublime's multiple selection functionality (#523)
* Fix softwrap bugs
* Menus & Context menus
* Track usage/engagement of our users (make this opted in?)
* Windows support
* Reliably and securely auto-update and list what's new
* Secure access to the keychain (don't give every package access to the keychain)
* Secure access to GitHub (each package can ask to have it's own oauth token)
* Don't crash when opening/editing large (> 10Mb) files
* Send js and native crash reports to a remote server
**Lay solid groundwork for a package and theme ecosystem**
Extensibility is one of Atom's key value propositions, so a smooth experience
for creating and maintaining packages is just as important as the user
experience. The package development, dependency and publishing workflow needs to
be solid. We also want to have a mechanism for clearly communicating with
package authors about breaking API changes.
* Finish APM backend (integrate with GitHub Releases)
* Streamline Dev workflow
* `apm create` - create package scaffolding
* `apm test` - so users can run focused package tests
* `apm publish` - should integrate release best practices (ie npm version)
* Determine which classes and methods should be included in the public API
* Users can find/install/update/fork existing packages and themes
**Tighten up the view layer**
Our current approach to the view layer need some improvement. We want to
actively promote the use of the M-V-VM design pattern, provide some declarative
event binding mechanisms in the view layer, and improve the performance of the
typical package specs. We don't want the current approach to be used as an
example in a bunch of new packages, so it's important to improve it now.
* Add marker view API
**Get atom.io online with some exciting articles and documentation**
We'd love to send our private alpha candidates to a nice site with information
about what Atom is, the philosophies and technologies behind it, and guidance
for how to get started.
* Design and create www.atom.io
* Guides
* Theme & Package creation guide
* Full API per release tag
* Changelog per release
* Explanation of features
* Explain Semver and general plans for the future (reassure developers we care about them)
* General Values/Goals
* Make docs accessible from Atom
* Community/contribution guidelines
* Is all communication to be done through issues?
* When should you publish a plugin?
* Do we need to vet plugins from a security perspective?

View File

@ -1,16 +0,0 @@
## Proposed Timeline
1. **October 30st** - Internal launch - persuade as many githubbers to switch as
possible.
1. Triage bugs and identify what needs to be fixed before private alpha. Maybe
talk to @chrissiebrodigan about doing a UX study.
1. **November 22st** - Private alpha launch
1. Trickle out invites as people ask/we need more testers.
1. If our usage metrics/engagement metrics decrease, stop, identify the issue
and fix it before continuing.
1. Launch

View File

@ -1,335 +1,145 @@
# Creating Your First Package
# Create Your First Package
Let's take a look at creating your first package.
This tutorial will guide you though creating a simple command that replaces the
selected text with [ascii art](http://en.wikipedia.org/wiki/ASCII_art). When you
run our new command with the word "cool" selected, it will be replaced with:
To get started, hit `cmd-shift-P`, and start typing "Generate Package" to generate
a package. Once you select the "Generate Package" command, it'll ask you for a
name for your new package. Let's call ours _changer_.
Atom will pop open a new window, showing the _changer_ package with a default set of
folders and files created for us. Hit `cmd-shift-P` and start typing "Changer." You'll
see a new `Changer:Toggle` command which, if selected, pops up a greeting. So far,
so good!
In order to demonstrate the capabilities of Atom and its API, our Changer plugin
is going to do two things:
1. It'll show only modified files in the file tree
2. It'll append a new pane to the editor with some information about the modified
files
Let's get started!
## Changing Keybindings and Commands
Since Changer is primarily concerned with the file tree, let's write a
key binding that works only when the tree is focused. Instead of using the
default `toggle`, our keybinding executes a new command called `magic`.
_keymaps/changer.cson_ should change to look like this:
```coffeescript
'.tree-view':
'ctrl-V': 'changer:magic'
```
/\_ \
___ ___ ___\//\ \
/'___\ / __`\ / __`\\ \ \
/\ \__//\ \L\ \/\ \L\ \\_\ \_
\ \____\ \____/\ \____//\____\
\/____/\/___/ \/___/ \/____/
```
Notice that the keybinding is called `ctrl-V` &mdash; that's actually `ctrl-shift-v`.
You can use capital letters to denote using `shift` for your binding.
The final package can be viewed at
[https://github.com/atom/ascii-art](https://github.com/atom/ascii-art).
`.tree-view` represents the parent container for the tree view.
Keybindings only work within the context of where they're entered. In this case,
hitting `ctrl-V` anywhere other than tree won't do anything. Obviously, you can
bind to any part of the editor using element, id, or class names. For example,
you can map to `body` if you want to scope to anywhere in Atom, or just `.editor`
for the editor portion.
To begin, press `cmd-shift-P` to bring up the [Command
Palette](https://github.com/atom/command-palette). Type "generate package" and
select the "Package Generator: Generate Package" command. Now we need to name
the package. Let's call it _ascii-art_.
To bind keybindings to a command, we'll need to do a bit of association in our
CoffeeScript code using the `atom.workspaceView.command` method. This method takes a command
name and executes a callback function. Open up _lib/changer-view.coffee_, and
change `atom.workspaceView.command "changer:toggle"` to look like this:
Atom will open a new window with the contents of our new _ascii-art_ package
displayed in the Tree View. Because this window is opened **after** the package
is created, the ASCII Art package will be loaded and available in our new
window. To verify this, toggle the Command Palette (`cmd-shift-P`) and type
"ASCII Art" you'll see a new `ASCII Art: Toggle` command. When triggered, this
command displays a default message.
Now let's edit the package files to make our ascii art package do something
interesting. Since this package doesn't need any UI, we can remove all
view-related code. Start by opening up _lib/ascii-art.coffee_. Remove all view
code, so the file looks like this:
```coffeescript
atom.workspaceView.command "changer:magic", => @magic()
module.exports =
activate: ->
```
It's common practice to namespace your commands with your package name, separated
with a colon (`:`). Make sure to rename your `toggle` method to `magic` as well.
## Create a Command
Every time you reload the Atom editor, changes to your package code will be reevaluated,
just as if you were writing a script for the browser. Reload the editor, click on
the tree, hit your keybinding, and...nothing happens! What the heck?!
Now let's add a command. We recommend that you namespace your commands with the
package name followed by a `:`, so we'll call our command `ascii-art:convert`.
Register the command in _lib/ascii-art.coffee_:
Open up the _package.json_ file, and find the property called `activationEvents`.
Basically, this key tells Atom to not load a package until it hears a certain event.
Change the event to `changer:magic` and reload the editor:
```coffeescript
module.exports =
activate: ->
atom.workspaceView.command "ascii-art:convert", => @convert()
convert: ->
# This assumes the active pane item is an editor
editor = atom.workspace.activePaneItem
editor.insertText('Hello, World!')
```
The `atom.workspaceView.command` method takes a command name and a callback. The
callback executes when the command is triggered. In this case, when the command
is triggered the callback will call the `convert` method and insert 'Hello,
World!'.
## Reload the Package
Before we can trigger `ascii-art:convert`, we need to load the latest code for
our package by reloading the window. Run the command `window:reload` from the
command palette or by pressing `ctrl-alt-cmd-l`.
## Trigger the Command
Now open the command panel and search for the `ascii-art:convert` command. But
its not there! To fix this open _package.json_ and find the property called
`activationEvents`. Activation Events speed up load time by allowing an Atom to
delay a package's activation until it's needed. So add the `ascii-art:convert`
to the activationEvents array:
```json
"activationEvents": ["changer:magic"]
"activationEvents": ["ascii-art:convert"],
```
Hitting the key binding on the tree now works!
First, run reload the window by running the command `window:reload`. Now when
you run the `ascii-art:convert` command it will output 'Hello, World!'
## Working with Styles
## Add A Key Binding
The next step is to hide elements in the tree that aren't modified. To do that,
we'll first try and get a list of files that have not changed.
All packages are able to use jQuery in their code. In fact, there's [a list of
the bundled libraries Atom provides by default][bundled-libs].
We bring in jQuery by requiring the `atom` package and binding it to the `$` variable:
Now let's add a key binding to trigger the `ascii-art:convert` command. Open
_keymaps/ascii-art.cson_ and add a key binding linking `ctrl-alt-a` to the
`ascii-art:convert` command. When finished, the file will look like this:
```coffeescript
{$, View} = require 'atom'
'.editor':
'cmd-alt-a': 'ascii-art:convert'
```
Now, we can define the `magic` method to query the tree to get us a list of every
file that _wasn't_ modified:
Notice `.editor` on the first line. Just like CSS, keymap selectors *scope* key
bindings so they only apply to specific elements. In this case, our binding is
only active for elements matching the `.editor` selector. If the Tree View has
focus, pressing `cmd-alt-a` won't trigger the `ascii-art:convert` command. But
if the editor has focus, the `ascii-art:convert` method *will* be triggered.
More information on key bindings can be found in the
[keymaps](advanced/keymaps.html) documentation.
```coffeescript
magic: ->
$('ol.entries li').each (i, el) ->
if !$(el).hasClass("status-modified")
console.log el
```
Now reload the window and verify that the key binding works! You can also verify
that it **doesn't** work when the Tree View is focused.
You can access the dev console by hitting `alt-cmd-i`. Here, you'll see all the
statements from `console` calls. When we execute the `changer:magic` command, the
browser console lists items that are not being modified (_i.e._, those without the
`status-modified` class). Let's add a class to each of these elements called `hide-me`:
## Add the ASCII Art
```coffeescript
magic: ->
$('ol.entries li').each (i, el) ->
if !$(el).hasClass("status-modified")
$(el).addClass("hide-me")
```
Now we need to convert the selected text to ascii art. To do this we will use
the [figlet](https://npmjs.org/package/figlet) [node](http://nodejs.org/) module
from [npm](https://npmjs.org/). Open _package.json_ and add the latest version of
figlet to the dependencies:
With our newly added class, we can manipulate the visibility of the elements
with a simple stylesheet. Open up _changer.css_ in the _stylesheets_ directory,
and add a single entry:
```css
ol.entries .hide-me {
display: none;
```json
"dependencies": {
"figlet": "1.0.8"
}
```
Refresh Atom, and run the `changer` command. You'll see all the non-changed
files disappear from the tree. Success!
After saving the file run the command 'update-package-dependencies:update' from
the Command Palette. This will install the packages node module dependencies,
only figlet in this case. You will need to run
'update-package-dependencies:update' whenever you update the dependencies field
in your _package.json_ file.
![Changer_File_View]
There are a number of ways you can get the list back; let's just naively iterate
over the same elements and remove the class:
Now require the figlet node module in _lib/ascii-art.coffee_ and instead of
inserting 'Hello, World!' convert the selected text to ascii art!
```coffeescript
magic: ->
$('ol.entries li').each (i, el) ->
if !$(el).hasClass("status-modified")
if !$(el).hasClass("hide-me")
$(el).addClass("hide-me")
convert: ->
# This assumes the active pane item is an editor
editor = atom.workspace.activePaneItem
selection = editor.getSelection()
figlet = require 'figlet'
figlet selection.getText(), {font: "Larry 3D 2"}, (error, asciiArt) ->
if error
console.error(error)
else
$(el).removeClass("hide-me")
selection.insertText("\n#{asciiArt}\n")
```
## Creating a New Panel
The next goal of this package is to append a panel to the Atom editor that lists
some information about the modified files.
To do that, we're going to first open up [the style guide][styleguide]. The Style
Guide lists every type of UI element that can be created by an Atom package. Aside
from helping you avoid writing fresh code from scratch, it ensures that packages
have the same look and feel no matter how they're built.
Every package that extends from the `View` class can provide an optional class
method called `content`. The `content` method constructs the DOM that your
package uses as its UI. The principals of `content` are built entirely on
[SpacePen][space-pen], which we'll touch upon only briefly here.
Our display will simply be an unordered list of the file names, and their
modified times. We'll append this list to a panel on the bottom of the editor. A
basic `panel` element inside a `tool-panel` will work well for us. Let's start by carving out a
`div` to hold the filenames:
```coffeescript
@content: ->
@div class: "changer tool-panel panel-bottom", =>
@div class: "panel", =>
@div class: "panel-heading", "Modified Files"
@div class: "panel-body padded", outlet: 'modifiedFilesContainer', =>
@ul class: 'modified-files-list', outlet: 'modifiedFilesList', =>
@li 'Modified File Test'
@li 'Modified File Test'
```
You can add any HTML attribute you like. `outlet` names the variable your
package can use to manipulate the element directly. The fat arrow (`=>`)
indicates that the next DOM set are nested children.
Once again, you can style `li` elements using your stylesheets. Let's test that
out by adding these lines to the _changer.css_ file:
```css
ul.modified-files-list {
color: white;
}
```
We'll add one more line to the end of the `magic` method to make this pane
appear:
```coffeescript
atom.workspaceView.prependToBottom(this)
```
If you refresh Atom and hit the key command, you'll see a box appear right
underneath the editor:
![Changer_Panel_Append]
As you might have guessed, `atom.workspaceView.prependToBottom` tells Atom to
prepend `this` item (_i.e._, whatever is defined by`@content`). If we had called
`atom.workspaceView.appendToBottom`, the pane would be attached below the status
bar.
Before we populate this panel for real, let's apply some logic to toggle the
pane off and on, just like we did with the tree view. Replace the
`atom.workspaceView.prependToBottom` call with this code:
```coffeescript
# toggles the pane
if @hasParent()
@remove()
else
atom.workspaceView.prependToBottom(this)
```
There are about a hundred different ways to toggle a pane on and off, and this
might not be the most efficient one. If you know your package needs to be
toggled on and off more freely, it might be better to draw the interface during the
initialization, then immediately call `hide()` on the element to remove it from
the view. You can then swap between `show()` and `hide()`, and instead of
forcing Atom to add and remove the element as we're doing here, it'll just set a
CSS property to control your package's visibility.
Refresh Atom, hit the key combo, and watch your test list appear and disappear.
## Calling Node.js Code
Since Atom is built on top of [Node.js][node], you can call any of its libraries,
including other modules that your package requires.
We'll iterate through our resulting tree, and construct the path to our modified
file based on its depth in the tree. We'll use Node to handle path joining for
directories.
Add the following Node module to the top of your file:
```coffeescript
path = require 'path'
```
Then, add these lines to your `magic` method, _before_ your pane drawing code:
```coffeescript
modifiedFiles = []
# for each single entry...
$('ol.entries li.file.status-modified span.name').each (i, el) ->
filePath = []
# ...grab its name...
filePath.unshift($(el).text())
# ... then find its parent directories, and grab their names
parents = $(el).parents('.directory.status-modified')
parents.each (i, el) ->
filePath.unshift($(el).find('div.header span.name').eq(0).text())
modifiedFilePath = path.join(atom.project.rootDirectory.path, filePath.join(path.sep))
modifiedFiles.push modifiedFilePath
```
`modifiedFiles` is an array containing a list of our modified files. We're also
using the node.js [`path` library][path] to get the proper directory separator
for our system.
Remove the two `@li` elements we added in `@content`, so that we can
populate our `modifiedFilesList` with real information. We'll do that by
iterating over `modifiedFiles`, accessing a file's last modified time, and
appending it to `modifiedFilesList`:
```coffeescript
# toggles the pane
if @hasParent()
@remove()
else
for file in modifiedFiles
stat = fs.lstatSync(file)
mtime = stat.mtime
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
atom.workspaceView.prependToBottom(this)
```
When you toggle the modified files list, your pane is now populated with the
filenames and modified times of files in your project:
![Changer_Panel_Timestamps]
You might notice that subsequent calls to this command reduplicate information.
We could provide an elegant way of rechecking files already in the list, but for
this demonstration, we'll just clear the `modifiedFilesList` each time it's closed:
```coffeescript
# toggles the pane
if @hasParent()
@modifiedFilesList.empty() # added this to clear the list on close
@remove()
else
for file in modifiedFiles
stat = fs.lstatSync(file)
mtime = stat.mtime
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
atom.workspaceView.prependToBottom(this)
```
## Coloring UI Elements
For packages that create new UI elements, adhering to the style guide is just one
part to keeping visual consistency. Packages dealing with color, fonts, padding,
margins, and other visual cues should rely on [Theme Variables][theme-vars], instead
of developing individual styles. Theme variables are variables defined by Atom
for use in packages and themes. They're only available in [`LESS`](http://lesscss.org/)
stylesheets.
For our package, let's remove the style defined by `ul.modified-files-list` in
_changer.css_. Create a new file under the _stylesheets_ directory called _text-colors.less_.
Here, we'll import the _ui-variables.less_ file, and define some Atom-specific
styles:
```less
@import "ui-variables";
ul.modified-files-list {
color: @text-color;
background-color: @background-color-info;
}
```
Using theme variables ensures that packages look great alongside any theme.
## Further reading
For more information on the mechanics of packages, check out
[Creating a Package][creating-a-package].
[bundled-libs]: creating-a-package.html#included-libraries
[styleguide]: https://github.com/atom/styleguide
[space-pen]: https://github.com/atom/space-pen
[node]: http://nodejs.org/
[path]: http://nodejs.org/docs/latest/api/path.html
[changer_file_view]: https://f.cloud.github.com/assets/69169/1441187/d7a7cb46-41a7-11e3-8128-d93f70a5d5c1.png
[changer_panel_append]: https://f.cloud.github.com/assets/69169/1441189/db0c74da-41a7-11e3-8286-b82dd9190c34.png
[changer_panel_timestamps]: https://f.cloud.github.com/assets/69169/1441190/dcc8eeb6-41a7-11e3-830f-1f1b33072fcd.png
[theme-vars]: theme-variables.html
[creating-a-package]: creating-a-package.html
For more information on the mechanics of packages, check out [Creating a
Package](creating-a-package.html)

15
dot-atom/snippets.cson Normal file
View File

@ -0,0 +1,15 @@
# Your snippets
#
# Atom snippets allow you to enter a simple prefix in the editor and hit tab to
# expand the prefix into a larger code block with templated values.
#
# You can create a new snippet in this file by typing `snip` and then hitting
# tab.
#
# An example CoffeeScript snippet to expand log to console.log:
#
# '.source.coffee':
# 'Console log':
# 'prefix': 'log'
# 'body': 'console.log $1'
#

View File

@ -1,44 +0,0 @@
".source.coffee":
"Describe block":
prefix: "de"
body: """
describe "${1:description}", ->
${2:body}
"""
"It block":
prefix: "i"
body: """
it "$1", ->
$2
"""
"Before each":
prefix: "be"
body: """
beforeEach ->
$1
"""
"After each":
prefix: "af"
body: """
afterEach ->
$1
"""
"Expectation":
prefix: "ex"
body: "expect($1).to$2"
"Console log":
prefix: "log"
body: "console.log $1"
"Range array":
prefix: "ra"
body: "[[$1, $2], [$3, $4]]"
"Point array":
prefix: "pt"
body: "[$1, $2]"
"Key-value pair":
prefix: ":"
body: '${1:"${2:key}"}: ${3:value}'
"Create Jasmine spy":
prefix: "spy"
body: 'jasmine.createSpy("${1:description}")$2'

View File

@ -8,6 +8,7 @@ module.exports =
File: require '../src/file'
fs: require 'fs-plus'
Git: require '../src/git'
ConfigObserver: require '../src/config-observer'
Point: Point
Range: Range

View File

@ -24,9 +24,6 @@
'ctrl-shift-up': 'editor:add-selection-above'
'ctrl-shift-down': 'editor:add-selection-below'
'.tool-panel':
'escape': 'core:close'
'.tool-panel.panel-left, .tool-panel.panel-right':
'escape': 'tool-panel:unfocus'

View File

@ -1,4 +1,4 @@
'.platform-darwin':
'body':
# Apple specific
'cmd-q': 'application:quit'
'cmd-h': 'application:hide'
@ -87,7 +87,7 @@
'cmd-8': 'pane:show-item-8'
'cmd-9': 'pane:show-item-9'
'.platform-darwin .editor':
'.editor':
# Apple Specific
'cmd-backspace': 'editor:backspace-to-beginning-of-line'
'cmd-delete': 'editor:backspace-to-beginning-of-line'
@ -113,7 +113,7 @@
'cmd-k cmd-l': 'editor:lower-case'
'cmd-l': 'editor:select-line'
'body.platform-darwin .editor:not(.mini)':
'.workspace .editor:not(.mini)':
# Atom specific
'alt-cmd-z': 'editor:checkout-head-revision'
'cmd-<': 'editor:scroll-to-cursor'
@ -148,7 +148,7 @@
'cmd-k cmd-9': 'editor:fold-at-indent-level-9'
# allow standard input fields to work correctly
'body.platform-darwin .native-key-bindings':
'body .native-key-bindings':
'cmd-z': 'native!'
'cmd-Z': 'native!'
'cmd-x': 'native!'

View File

@ -1,4 +1,4 @@
'.platform-win32':
'body':
# Atom Specific
'enter': 'core:confirm'
'escape': 'core:cancel'
@ -50,7 +50,7 @@
'ctrl-k ctrl-left': 'window:focus-previous-pane'
'ctrl-k ctrl-right': 'window:focus-next-pane'
'.platform-win32 .editor':
'.workspace .editor':
# Windows specific
'ctrl-delete': 'editor:backspace-to-beginning-of-word'
@ -60,7 +60,7 @@
'ctrl-k ctrl-u': 'editor:upper-case'
'ctrl-k ctrl-l': 'editor:lower-case'
'.platform-win32 .editor:not(.mini)':
'.workspace .editor:not(.mini)':
# Atom specific
'alt-ctrl-z': 'editor:checkout-head-revision'
'ctrl-<': 'editor:scroll-to-cursor'
@ -94,7 +94,7 @@
'ctrl-k ctrl-9': 'editor:fold-at-indent-level-9'
# allow standard input fields to work correctly
'.platform-win32 input:not(.hidden-input), .platform-win32 .native-key-bindings':
'body .native-key-bindings':
'ctrl-z': 'native!'
'ctrl-Z': 'native!'
'ctrl-x': 'native!'

View File

@ -9,8 +9,11 @@
{ label: 'Preferences...', command: 'application:show-settings' }
{ label: 'Open Your Config', command: 'application:open-your-config' }
{ label: 'Open Your Keymap', command: 'application:open-your-keymap' }
{ label: 'Open Your Snippets', command: 'application:open-your-snippets' }
{ label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
{ type: 'separator' }
{ label: 'Install Shell Commands', command: 'window:install-shell-commands' }
{ type: 'separator' }
{ label: 'Hide Atom', command: 'application:hide' }
{ label: 'Hide Others', command: 'application:hide-other-applications' }
{ label: 'Show All', command: 'application:unhide-all-applications' }

View File

@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "0.48.0",
"version": "0.49.0",
"main": "./src/browser/main.js",
"repository": {
"type": "git",
@ -16,7 +16,7 @@
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
}
],
"atomShellVersion": "0.8.5",
"atomShellVersion": "0.8.7",
"dependencies": {
"async": "0.2.6",
"bootstrap": "git://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372",
@ -28,15 +28,15 @@
"first-mate": "1.x",
"fs-plus": "1.x",
"fstream": "0.1.24",
"fuzzaldrin": "0.6.0",
"git-utils": "0.33.1",
"fuzzaldrin": "0.7.0",
"git-utils": "0.34.0",
"guid": "0.0.10",
"jasmine-tagged": "0.3.0",
"mkdirp": "0.3.5",
"keytar": "0.15.1",
"less-cache": "0.11.0",
"mixto": "1.x",
"nslog": "0.3.0",
"nslog": "0.4.0",
"oniguruma": "1.x",
"optimist": "0.4.0",
"pathwatcher": "0.14.2",
@ -75,7 +75,7 @@
"editor-stats": "0.12.0",
"exception-reporting": "0.12.0",
"feedback": "0.22.0",
"find-and-replace": "0.79.0",
"find-and-replace": "0.80.0",
"fuzzy-finder": "0.31.0",
"gists": "0.15.0",
"git-diff": "0.23.0",
@ -86,27 +86,28 @@
"keybinding-resolver": "0.9.0",
"link": "0.15.0",
"markdown-preview": "0.25.1",
"metrics": "0.21.0",
"metrics": "0.24.0",
"package-generator": "0.24.0",
"release-notes": "0.17.0",
"settings-view": "0.57.0",
"snippets": "0.20.0",
"snippets": "0.22.0",
"spell-check": "0.20.0",
"status-bar": "0.32.0",
"styleguide": "0.21.0",
"symbols-view": "0.29.0",
"tabs": "0.18.0",
"terminal": "0.26.0",
"terminal": "0.27.0",
"timecop": "0.13.0",
"to-the-hubs": "0.17.0",
"to-the-hubs": "0.18.0",
"tree-view": "0.65.0",
"update-package-dependencies": "0.2.0",
"visual-bell": "0.6.0",
"welcome": "0.4.0",
"whitespace": "0.10.0",
"wrap-guide": "0.12.0",
"language-c": "0.2.0",
"language-clojure": "0.1.0",
"language-coffee-script": "0.4.0",
"language-coffee-script": "0.6.0",
"language-css": "0.2.0",
"language-gfm": "0.12.0",
"language-git": "0.3.0",

18
script/copy-folder.cmd Normal file
View File

@ -0,0 +1,18 @@
@echo off
set USAGE=Usage: %0 source destination
if [%1] == [] (
echo %USAGE%
exit 1
)
if [%2] == [] (
echo %USAGE%
exit 2
)
:: rm -rf %2
if exist %2 rmdir %2 /s /q
:: cp -rf %1 %2
xcopy %1 %2 /e /h /c /i /y /r

View File

@ -0,0 +1,23 @@
@echo off
set USAGE=Usage: %0 source name-on-desktop
if [%1] == [] (
echo %USAGE%
exit 1
)
if [%2] == [] (
echo %USAGE%
exit 2
)
set SCRIPT="%TEMP%\%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%.vbs"
echo Set oWS = WScript.CreateObject("WScript.Shell") >> %SCRIPT%
echo sLinkFile = "%USERPROFILE%\Desktop\%2.lnk" >> %SCRIPT%
echo Set oLink = oWS.CreateShortcut(sLinkFile) >> %SCRIPT%
echo oLink.TargetPath = %1 >> %SCRIPT%
echo oLink.Save >> %SCRIPT%
cscript /nologo %SCRIPT%
del %SCRIPT%

View File

@ -2,8 +2,8 @@
var cp = require('./utils/child-process-wrapper.js');
var path = require('path');
// node build/node_modules/grunt-cli/bin/grunt "$@"
var gruntPath = path.resolve(__dirname, '..', 'build', 'node_modules', 'grunt-cli', 'bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
var args = [gruntPath, '--gruntfile', path.resolve('build', 'Gruntfile.coffee')];
// node build/node_modules/.bin/grunt "$@"
var gruntPath = path.resolve(__dirname, '..', 'build', 'node_modules', '.bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
var args = ['--gruntfile', path.resolve('build', 'Gruntfile.coffee')];
args = args.concat(process.argv.slice(2));
cp.safeSpawn(process.execPath, args, process.exit);
cp.safeSpawn(gruntPath, args, process.exit);

View File

@ -3,9 +3,8 @@
path = require 'path'
CommandInstaller = require '../src/command-installer'
callback = (error, sourcePath, destinationPath) ->
unless error?
console.log "#{sourcePath} intalled to #{destinationPath}"
callback = (error) ->
console.warn error.message if error?
CommandInstaller.installAtomCommand(path.resolve(__dirname, '..'), callback)
CommandInstaller.installApmCommand(path.resolve(__dirname, '..'), callback)

View File

@ -24,7 +24,7 @@ fs.createReadStream(fontSrc).pipe(fs.createWriteStream(fontDest))
# Update Octicon UTF codes
glyphsSrc = path.join(pathToOcticons, 'data', 'glyphs.yml')
octiconUtfDest = path.join atomDir, 'static', 'octicon-utf-codes.less'
octiconUtfDest = path.join atomDir, 'static', 'variables', 'octicon-utf-codes.less'
output = []
for {css, code} in YAML.load(fs.readFileSync(glyphsSrc).toString())
output.push "@#{css}: \"\\#{code}\";"

View File

@ -4,33 +4,31 @@ temp = require 'temp'
installer = require '../src/command-installer'
describe "install(commandPath, callback)", ->
directory = path.join(temp.dir, 'install-atom-command', 'atom')
commandPath = path.join(directory, 'source')
destinationPath = path.join(directory, 'bin', 'source')
commandFilePath = temp.openSync("atom-command").path
commandName = path.basename(commandFilePath)
installationPath = temp.mkdirSync("atom-bin")
installationFilePath = path.join(installationPath, commandName)
beforeEach ->
spyOn(installer, 'findInstallDirectory').andCallFake (callback) ->
callback(directory)
fs.removeSync(directory) if fs.existsSync(directory)
spyOn(installer, 'getInstallDirectory').andReturn installationPath
describe "on #darwin", ->
it "symlinks the command and makes it executable", ->
fs.writeFileSync(commandPath, 'test')
expect(fs.isFileSync(commandPath)).toBeTruthy()
expect(fs.isExecutableSync(commandPath)).toBeFalsy()
expect(fs.isFileSync(destinationPath)).toBeFalsy()
expect(fs.isFileSync(commandFilePath)).toBeTruthy()
expect(fs.isExecutableSync(commandFilePath)).toBeFalsy()
expect(fs.isFileSync(installationFilePath)).toBeFalsy()
installDone = false
installError = null
installer.install commandPath, (error) ->
installer.install commandFilePath, (error) ->
installDone = true
installError = error
waitsFor -> installDone
waitsFor ->
installDone
runs ->
expect(installError).toBeNull()
expect(fs.isFileSync(destinationPath)).toBeTruthy()
expect(fs.realpathSync(destinationPath)).toBe fs.realpathSync(commandPath)
expect(fs.isExecutableSync(destinationPath)).toBeTruthy()
expect(fs.isFileSync(installationFilePath)).toBeTruthy()
expect(fs.realpathSync(installationFilePath)).toBe fs.realpathSync(commandFilePath)
expect(fs.isExecutableSync(installationFilePath)).toBeTruthy()

View File

@ -218,7 +218,7 @@ describe "Config", ->
runs ->
expect(fs.existsSync(atom.config.configDirPath)).toBeTruthy()
expect(fs.existsSync(path.join(atom.config.configDirPath, 'packages'))).toBeTruthy()
expect(fs.existsSync(path.join(atom.config.configDirPath, 'snippets'))).toBeTruthy()
expect(fs.isFileSync(path.join(atom.config.configDirPath, 'snippets.cson'))).toBeTruthy()
expect(fs.isFileSync(path.join(atom.config.configDirPath, 'config.cson'))).toBeTruthy()
describe ".loadUserConfig()", ->

View File

@ -63,7 +63,7 @@ describe 'File', ->
afterEach ->
if fs.existsSync(newPath)
fs.removeSync(newPath)
waitsFor "remove event", (done) -> file.on 'removed', done
waitsFor "remove event", 30000, (done) -> file.on 'removed', done
it "it updates its path", ->
jasmine.unspy(window, "setTimeout")

View File

@ -354,6 +354,27 @@ describe "Keymap", ->
bindings = keymap.keyBindingsForCommandMatchingElement('cultivate', el)
expect(bindings).toHaveLength 0
describe "loading platform specific keybindings", ->
customKeymap = null
beforeEach ->
resourcePath = temp.mkdirSync('atom')
customKeymap = new Keymap({configDirPath, resourcePath})
afterEach ->
customKeymap.destroy()
it "doesn't load keybindings from other platforms", ->
win32FilePath = path.join(resourcePath, "keymaps", "win32.cson")
darwinFilePath = path.join(resourcePath, "keymaps", "darwin.cson")
fs.writeFileSync(win32FilePath, '"body": "ctrl-l": "core:win32-move-left"')
fs.writeFileSync(darwinFilePath, '"body": "ctrl-l": "core:darwin-move-left"')
customKeymap.loadBundledKeymaps()
keyBindings = customKeymap.keyBindingsForKeystroke('ctrl-l')
expect(keyBindings).toHaveLength 1
expect(keyBindings[0].command).toBe "core:#{process.platform}-move-left"
describe "when the user keymap file is changed", ->
it "is reloaded", ->
keymapFilePath = path.join(configDirPath, "keymap.cson")

View File

@ -34,7 +34,7 @@ WindowEventHandler = require './window-event-handler'
# * `atom.themes` - A {ThemeManager} instance
module.exports =
class Atom extends Model
# Public: Load or create the Atom environment in the given mode
# Public: Load or create the Atom environment in the given mode.
#
# - mode: Pass 'editor' or 'spec' depending on the kind of environment you
# want to build.
@ -249,10 +249,12 @@ class Atom extends Model
# Private: Call this method when establishing a real application window.
startEditorWindow: ->
if process.platform is 'darwin'
CommandInstaller = require './command-installer'
CommandInstaller.installAtomCommand()
CommandInstaller.installApmCommand()
resourcePath = atom.getLoadSettings().resourcePath
CommandInstaller.installAtomCommand resourcePath, (error) ->
console.warn error.message if error?
CommandInstaller.installApmCommand resourcePath, (error) ->
console.warn error.message if error?
@restoreWindowDimensions()
@config.load()

View File

@ -15,7 +15,11 @@ url = require 'url'
{EventEmitter} = require 'events'
_ = require 'underscore-plus'
socketPath = path.join(os.tmpdir(), 'atom.sock')
socketPath =
if process.platform is 'win32'
'\\\\.\\pipe\\atom-sock'
else
path.join(os.tmpdir(), 'atom.sock')
# Private: The application's singleton class.
#
@ -35,14 +39,10 @@ class AtomApplication
# take a few seconds to trigger 'error' event, it could be a bug of node
# or atom-shell, before it's fixed we check the existence of socketPath to
# speedup startup.
if (not fs.existsSync socketPath) or options.test
if (process.platform isnt 'win32' and not fs.existsSync socketPath) or options.test
createAtomApplication()
return
# The net.connect is slow in atom-shell for now, use this workaround until
# atom/atom-shell#159 is fixed.
process.activateUvLoop()
client = net.connect {path: socketPath}, ->
client.write JSON.stringify(options), ->
client.end()
@ -103,7 +103,8 @@ class AtomApplication
# the other launches will just pass their information to this server and then
# close immediately.
listenForArgumentsFromNewProcess: ->
fs.unlinkSync socketPath if fs.existsSync(socketPath)
if process.platform isnt 'win32' and fs.existsSync(socketPath)
fs.unlinkSync socketPath
server = net.createServer (connection) =>
connection.on 'data', (data) =>
@openWithOptions(JSON.parse(data))
@ -148,15 +149,18 @@ class AtomApplication
@on 'application:report-issue', -> shell.openExternal('https://github.com/atom/atom/issues/new')
@openPathOnEvent('application:show-settings', 'atom://config')
@openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')
@openPathOnEvent('application:open-your-keymap', 'atom://.atom/keymap')
@openPathOnEvent('application:open-your-config', 'atom://.atom/config')
@openPathOnEvent('application:open-your-keymap', 'atom://.atom/keymap')
@openPathOnEvent('application:open-your-snippets', 'atom://.atom/snippets')
@openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')
app.on 'window-all-closed', ->
app.quit() if process.platform is 'win32'
app.on 'will-quit', =>
fs.unlinkSync socketPath if fs.existsSync(socketPath) # Clean the socket file when quit normally.
# Clean the socket file when quit normally.
if process.platform isnt 'win32' and fs.existsSync(socketPath)
fs.unlinkSync socketPath
app.on 'open-file', (event, pathToOpen) =>
event.preventDefault()

View File

@ -74,6 +74,8 @@ class AtomWindow
global.atomApplication.removeWindow(this)
@browserWindow.on 'unresponsive', =>
return if @isSpec
chosen = dialog.showMessageBox @browserWindow,
type: 'warning'
buttons: ['Close', 'Keep Waiting']

View File

@ -7,16 +7,14 @@ fs = require 'fs'
module = require 'module'
path = require 'path'
optimist = require 'optimist'
# TODO: NSLog is missing .lib on windows
nslog = require 'nslog' unless process.platform is 'win32'
nslog = require 'nslog'
dialog = require 'dialog'
console.log = (args...) ->
# TODO: Make NSLog work as expected
output = args.map((arg) -> JSON.stringify(arg)).join(" ")
if process.platform == 'darwin'
nslog(output)
else
if process.platform isnt 'darwin'
fs.writeFileSync('debug.log', output, flag: 'a')
process.on 'uncaughtException', (error={}) ->

View File

@ -6,6 +6,12 @@ path = require 'path'
#
# This may seem unnecessary but on Windows we have to have separate executables
# for each script without this since Windows doesn't support shebang strings.
#
# ## Requiring in packages
#
# ```coffee
# {BufferedNodeProcess} = require 'atom'
# ```
module.exports =
class BufferedNodeProcess extends BufferedProcess
# Executes the given Node script.

View File

@ -1,6 +1,12 @@
ChildProcess = require 'child_process'
# Public: A wrapper which provides line buffering for Node's ChildProcess.
#
# ## Requiring in packages
#
# ```coffee
# {BufferedProcess} = require 'atom'
# ```
module.exports =
class BufferedProcess
process: null

View File

@ -22,7 +22,10 @@ getCachedJavaScript = (cachePath) ->
fs.readFileSync(cachePath, 'utf8') if stat.isFile()
compileCoffeeScript = (coffee, filePath, cachePath) ->
js = CoffeeScript.compile(coffee, filename: filePath)
{js,v3SourceMap} = CoffeeScript.compile(coffee, filename: filePath, sourceMap: true)
# Include source map in the web page environment.
if btoa? and JSON? and unescape? and encodeURIComponent?
js = "#{js}\n//# sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//# sourceURL=#{filePath}"
try
mkdir(path.dirname(cachePath))
fs.writeFileSync(cachePath, js)

View File

@ -23,46 +23,32 @@ unlinkCommand = (destinationPath, callback) ->
callback()
module.exports =
findInstallDirectory: (callback) ->
directories = ['/opt/boxen', '/opt/github', '/usr/local']
async.detect(directories, fs.isDirectory, callback)
getInstallDirectory: ->
"/usr/local/bin"
install: (commandPath, callback) ->
return unless process.platform is 'darwin'
install: (commandPath, commandName, callback) ->
if not commandName? or _.isFunction(commandName)
callback = commandName
commandName = path.basename(commandPath, path.extname(commandPath))
installCallback = (error, sourcePath, destinationPath) ->
directory = @getInstallDirectory()
if fs.existsSync(directory)
destinationPath = path.join(directory, commandName)
unlinkCommand destinationPath, (error) =>
if error?
console.warn "Failed to install `#{commandName}` binary", error
callback?(error, sourcePath, destinationPath)
@findInstallDirectory (directory) ->
if directory?
destinationPath = path.join(directory, 'bin', commandName)
unlinkCommand destinationPath, (error) ->
if error?
installCallback(error)
error = new Error "Could not remove file at #{destinationPath}." if error
callback?(error)
else
symlinkCommand commandPath, destinationPath, (error) ->
installCallback(error, commandPath, destinationPath)
symlinkCommand commandPath, destinationPath, (error) =>
error = new Error "Failed to symlink #{commandPath} to #{destinationPath}." if error
callback?(error)
else
installCallback(new Error("No destination directory exists to install"))
error = new Error "Directory '#{directory} doesn't exist."
callback?(error)
installAtomCommand: (resourcePath, callback) ->
if _.isFunction(resourcePath)
callback = resourcePath
resourcePath = null
resourcePath ?= atom.getLoadSettings().resourcePath
commandPath = path.join(resourcePath, 'atom.sh')
@install(commandPath, callback)
@install commandPath, callback
installApmCommand: (resourcePath, callback) ->
if _.isFunction(resourcePath)
callback = resourcePath
resourcePath = null
resourcePath ?= atom.getLoadSettings().resourcePath
commandPath = path.join(resourcePath, 'apm', 'node_modules', '.bin', 'apm')
@install(commandPath, callback)
@install commandPath, callback

View File

@ -1,4 +1,7 @@
Mixin = require 'mixto'
module.exports =
class ConfigObserver extends Mixin
observeConfig: (keyPath, args...) ->
@configSubscriptions ?= {}
@configSubscriptions[keyPath] = atom.config.observe(keyPath, args...)

View File

@ -7,7 +7,13 @@ pathWatcher = require 'pathwatcher'
File = require './file'
# Public: Represents a directory using {File}s
# Public: Represents a directory using {File}s.
#
# ## Requiring in packages
#
# ```coffee
# {Directory} = require 'atom'
# ```
module.exports =
class Directory
Emitter.includeInto(this)

View File

@ -15,7 +15,7 @@ ConfigObserver = require './config-observer'
module.exports =
class DisplayBuffer extends Model
Serializable.includeInto(this)
_.extend @prototype, ConfigObserver
ConfigObserver.includeInto(this)
@properties
softWrap: null
@ -297,8 +297,12 @@ class DisplayBuffer extends Model
[startScreenRow, endScreenRow] = @rowMap.screenRowRangeForBufferRow(row)
for screenRow in [startScreenRow...endScreenRow]
unless screenLine = @screenLines[screenRow]
throw new Error("No screen line exists for screen row #{screenRow}, converted from buffer position (#{row}, #{column})")
throw new Error """
No screen line exists for screen row #{screenRow}, converted from buffer position (#{row}, #{column})
Soft wrap enabled: #{@getSoftWrap()}
Fold count: #{@findFoldMarkers().length}
Last buffer row: #{@getLastRow()}
"""
maxBufferColumn = screenLine.getMaxBufferColumn()
if screenLine.isSoftWrapped() and column > maxBufferColumn
continue

View File

@ -1,6 +1,6 @@
{View, $, $$$} = require './space-pen-extensions'
TextBuffer = require './text-buffer'
Gutter = require './gutter'
GutterView = require './gutter-view'
{Point, Range} = require 'text-buffer'
Editor = require './editor'
CursorView = require './cursor-view'
@ -16,6 +16,12 @@ LongLineLength = 1000
# Public: Represents the entire visual pane in Atom.
#
# The EditorView manages the {Editor}, which manages the file buffers.
#
# ## Requiring in packages
#
# ```coffee
# {EditorView} = require 'atom'
# ```
module.exports =
class EditorView extends View
@characterWidthCache: {}
@ -41,7 +47,7 @@ class EditorView extends View
attributes = { class: @classes(params), tabindex: -1 }
_.extend(attributes, params.attributes) if params.attributes
@div attributes, =>
@subview 'gutter', new Gutter
@subview 'gutter', new GutterView
@div class: 'scroll-view', outlet: 'scrollView', =>
@div class: 'overlayer', outlet: 'overlayer'
@div class: 'lines', outlet: 'renderedLines'
@ -405,7 +411,7 @@ class EditorView extends View
selectedText = null
@hiddenInput.on 'compositionstart', =>
selectedText = @getSelectedText()
selectedText = @editor.getSelectedText()
@hiddenInput.css('width', '100%')
@hiddenInput.on 'compositionupdate', (e) =>
@editor.insertText(e.originalEvent.data, {select: true, undo: 'skip'})

View File

@ -202,8 +202,8 @@ class Editor extends Model
# Deprecated: Use the ::scrollLeft property directly
getScrollLeft: -> @scrollLeft
# Set the number of characters that can be displayed horizontally in the
# editor that contains this edit session.
# Public: Set the number of characters that can be displayed horizontally in
# the editor.
#
# editorWidthInChars - A {Number} of characters
setEditorWidthInChars: (editorWidthInChars) ->
@ -765,27 +765,27 @@ class Editor extends Model
findMarkers: (attributes) ->
@displayBuffer.findMarkers(attributes)
# {Delegates to: DisplayBuffer.markScreenRange}
# Public: {Delegates to: DisplayBuffer.markScreenRange}
markScreenRange: (args...) ->
@displayBuffer.markScreenRange(args...)
# {Delegates to: DisplayBuffer.markBufferRange}
# Public: {Delegates to: DisplayBuffer.markBufferRange}
markBufferRange: (args...) ->
@displayBuffer.markBufferRange(args...)
# {Delegates to: DisplayBuffer.markScreenPosition}
# Public: {Delegates to: DisplayBuffer.markScreenPosition}
markScreenPosition: (args...) ->
@displayBuffer.markScreenPosition(args...)
# {Delegates to: DisplayBuffer.markBufferPosition}
# Public: {Delegates to: DisplayBuffer.markBufferPosition}
markBufferPosition: (args...) ->
@displayBuffer.markBufferPosition(args...)
# {Delegates to: DisplayBuffer.destroyMarker}
# Public: {Delegates to: DisplayBuffer.destroyMarker}
destroyMarker: (args...) ->
@displayBuffer.destroyMarker(args...)
# {Delegates to: DisplayBuffer.getMarkerCount}
# Public: {Delegates to: DisplayBuffer.getMarkerCount}
getMarkerCount: ->
@buffer.getMarkerCount()

View File

@ -10,6 +10,12 @@ fs = require 'fs-plus'
#
# You should probably create a {Directory} and access the {File} objects that
# it creates, rather than instantiating the {File} class directly.
#
# ## Requiring in packages
#
# ```coffee
# {File} = require 'atom'
# ```
module.exports =
class File
Emitter.includeInto(this)

View File

@ -7,7 +7,8 @@ GitUtils = require 'git-utils'
# Public: Represents the underlying git operations performed by Atom.
#
# This class shouldn't be instantiated directly but instead by accessing the
# `atom.project` global and calling `getRepo()`.
# `atom.project` global and calling `getRepo()`. Note that this will only be
# available when the project is backed by a Git repository.
#
# ## Example
#
@ -15,6 +16,12 @@ GitUtils = require 'git-utils'
# git = atom.project.getRepo()
# console.log git.getOriginUrl()
# ```
#
# ## Requiring in packages
#
# ```coffee
# {Git} = require 'atom'
# ```
module.exports =
class Git
Emitter.includeInto(this)
@ -250,12 +257,7 @@ class Git
# Public: Returns the upstream branch for the current HEAD, or null if there
# is no upstream branch for the current HEAD.
#
# Examples
#
# getUpstreamBranch()
# # => "refs/remotes/origin/master"
#
# Returns a String.
# Returns a String branch name such as `refs/remotes/origin/master`
getUpstreamBranch: -> @getRepo().getUpstreamBranch()
# Public: Returns the current SHA for the given reference.

View File

@ -6,7 +6,7 @@ _ = require 'underscore-plus'
#
# The gutter also indicates if rows are folded.
module.exports =
class Gutter extends View
class GutterView extends View
### Internal ###
@ -234,7 +234,7 @@ class Gutter extends View
lastBufferRow = null
for bufferRow in editor.bufferRowsForScreenRows(startScreenRow, endScreenRow) when bufferRow isnt lastBufferRow
lastBufferRow = bufferRow
lineNumberElement = @getLineNumberElement(bufferRow)[0]
if lineNumberElement = @getLineNumberElement(bufferRow)[0]
if editor.isFoldableAtBufferRow(bufferRow)
lineNumberElement.classList.add('foldable')
else

View File

@ -142,7 +142,12 @@ class Keymap
@userKeymapFile.on 'contents-changed moved removed', => @loadUserKeymap()
loadDirectory: (directoryPath) ->
@load(filePath) for filePath in fs.listSync(directoryPath, ['.cson', '.json'])
platforms = ['darwin', 'freebsd', 'linux', 'sunos', 'win32']
otherPlatforms = platforms.filter (name) -> name != process.platform
for filePath in fs.listSync(directoryPath, ['.cson', '.json'])
continue if path.basename(filePath, path.extname(filePath)) in otherPlatforms
@load(filePath)
load: (path) ->
@add(path, CSON.readFileSync(path))

View File

@ -1,7 +1,9 @@
clipboard = require 'clipboard'
crypto = require 'crypto'
# Internal: Represents the clipboard used for copying and pasting in Atom.
# Public: Represents the clipboard used for copying and pasting in Atom.
#
# A pasteboard instance is always available under the `atom.pasteboard` global.
module.exports =
class Pasteboard
signatureForMetadata: null
@ -14,18 +16,19 @@ class Pasteboard
md5: (text) ->
crypto.createHash('md5').update(text, 'utf8').digest('hex')
# Saves from the clipboard.
# Public: Write the given text to the clipboard.
#
# text - A {String} to store
# metadata - An object of additional info to associate with the text
# text - A {String} to store.
# metadata - An {Object} of additional info to associate with the text.
write: (text, metadata) ->
@signatureForMetadata = @md5(text)
@metadata = metadata
clipboard.writeText(text)
# Loads from the clipboard.
# Public: Read the text from the clipboard.
#
# Returns an {Array}. The first index is the saved text, and the second is any metadata associated with the text.
# Returns an {Array}. The first element is the saved text and the second is
# any metadata associated with the text.
read: ->
text = clipboard.readText()
value = [text]

View File

@ -16,8 +16,7 @@ Git = require './git'
# Public: Represents a project that's opened in Atom.
#
# Ultimately, a project is a git directory that's been opened. It's a collection
# of directories and files that you can operate on.
# There is always a project available under the `atom.project` global.
module.exports =
class Project extends Model
atom.deserializers.add(this)

View File

@ -5,9 +5,11 @@
# This `View` subclass listens to events such as `page-up`, `page-down`,
# `move-to-top`, and `move-to-bottom`.
#
# FIXME: I don't actually understand if this is useful or not. I think it is
# a base of package widgets but I don't really understand how the core events
# work.
# ## Requiring in packages
#
# ```coffee
# {ScrollView} = require 'atom'
# ```
module.exports =
class ScrollView extends View

View File

@ -4,6 +4,12 @@ fuzzyFilter = require('fuzzaldrin').filter
# Public: Provides a widget for users to make a selection from a list of
# choices.
#
# ## Requiring in packages
#
# ```coffee
# {SelectList} = require 'atom'
# ```
module.exports =
class SelectList extends View

View File

@ -1,9 +1,9 @@
_ = require 'underscore-plus'
spacePen = require 'space-pen'
ConfigObserver = require './config-observer'
{Subscriber} = require 'emissary'
ConfigObserver = require './config-observer'
_.extend spacePen.View.prototype, ConfigObserver
ConfigObserver.includeInto(spacePen.View)
Subscriber.includeInto(spacePen.View)
jQuery = spacePen.jQuery

View File

@ -6,7 +6,12 @@ _ = require 'underscore-plus'
{$, $$} = require './space-pen-extensions'
Token = require './token'
### Public ###
# Public: Syntax class holding the grammars used for tokenizing.
#
# The Syntax class also contains properties for things such as the
# language-specific comment regexes.
#
# There is always a syntax object available under the `atom.syntax` global.
module.exports =
class Syntax extends GrammarRegistry
Subscriber.includeInto(this)
@ -49,6 +54,18 @@ class Syntax extends GrammarRegistry
@scopedProperties = []
@scopedPropertiesIndex = 0
# Public: Get a property for the given scope and key path.
#
# ## Example
# ```coffee
# comment = atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')
# console.log(comment) # '# '
# ```
#
# * scope: An {Array} of {String} scopes.
# * keyPath: A {String} key path.
#
# Returns a {String} property value or undefined.
getProperty: (scope, keyPath) ->
for object in @propertiesForScope(scope, keyPath)
value = _.valueForKeyPath(object, keyPath)

View File

@ -12,6 +12,12 @@ child_process = require 'child_process'
# * task:warn - Emitted when console.warn is called within the task.
# * task:error - Emitted when console.error is called within the task.
# * task:completed - Emitted when the task has succeeded or failed.
#
# ## Requiring in packages
#
# ```coffee
# {Task} = require 'atom'
# ```
module.exports =
class Task
Emitter.includeInto(this)

View File

@ -1,13 +1,9 @@
### Internal ###
isHighSurrogate = (string, index) ->
0xD800 <= string.charCodeAt(index) <= 0xDBFF
isLowSurrogate = (string, index) ->
0xDC00 <= string.charCodeAt(index) <= 0xDFFF
### Public ###
# Is the character at the given index the start of a high/low surrogate pair?
#
# string - The {String} to check for a surrogate pair.

View File

@ -8,9 +8,9 @@ fs = require 'fs-plus'
AtomPackage = require './atom-package'
File = require './file'
# Private: Handles discovering and loading available themes.
# Public: Handles loading and activating available themes.
#
# Themes are a subset of packages
# A ThemeManager instance is always available under the `atom.themes` global.
module.exports =
class ThemeManager
Emitter.includeInto(this)
@ -19,27 +19,26 @@ class ThemeManager
@lessCache = null
@packageManager.registerPackageActivator(this, ['theme'])
# Internal-only:
getAvailableNames: ->
# TODO: Maybe should change to list all the available themes out there?
@getLoadedNames()
# Public: Get an array of all the loaded theme names.
getLoadedNames: ->
theme.name for theme in @getLoadedThemes()
# Internal-only:
# Public: Get an array of all the active theme names.
getActiveNames: ->
theme.name for theme in @getActiveThemes()
# Internal-only:
# Public: Get an array of all the active themes.
getActiveThemes: ->
pack for pack in @packageManager.getActivePackages() when pack.isTheme()
# Internal-only:
# Public: Get an array of all the loaded themes.
getLoadedThemes: ->
pack for pack in @packageManager.getLoadedPackages() when pack.isTheme()
# Internal-only: adhere to the PackageActivator interface
activatePackages: (themePackages) -> @activateThemes()
# Private: Get the enabled theme names from the config.
@ -53,7 +52,6 @@ class ThemeManager
# the first/top theme to override later themes in the stack.
themeNames.reverse()
# Internal-only:
activateThemes: ->
# atom.config.observe runs the callback once, then on subsequent changes.
atom.config.observe 'core.themes', =>
@ -69,13 +67,11 @@ class ThemeManager
@emit('reloaded')
# Internal-only:
deactivateThemes: ->
@unwatchUserStylesheet()
@packageManager.deactivatePackage(pack.name) for pack in @getActiveThemes()
null
# Internal-only:
refreshLessCache: ->
@lessCache?.setImportPaths(@getImportPaths())
@ -85,7 +81,6 @@ class ThemeManager
setEnabledThemes: (enabledThemeNames) ->
atom.config.set('core.themes', enabledThemeNames)
# Public:
getImportPaths: ->
activeThemes = @getActiveThemes()
if activeThemes.length > 0
@ -98,7 +93,7 @@ class ThemeManager
themePath for themePath in themePaths when fs.isDirectorySync(themePath)
# Public:
# Public: Returns the {String} path to the user's stylesheet under ~/.atom
getUserStylesheetPath: ->
stylesheetPath = fs.resolve(path.join(@configDirPath, 'user'), ['css', 'less'])
if fs.isFileSync(stylesheetPath)
@ -106,13 +101,11 @@ class ThemeManager
else
path.join(@configDirPath, 'user.less')
#Private:
unwatchUserStylesheet: ->
@userStylesheetFile?.off()
@userStylesheetFile = null
@removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
# Private:
loadUserStylesheet: ->
@unwatchUserStylesheet()
userStylesheetPath = @getUserStylesheetPath()
@ -125,34 +118,32 @@ class ThemeManager
userStylesheetContents = @loadStylesheet(userStylesheetPath)
@applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')
# Internal-only:
loadBaseStylesheets: ->
@requireStylesheet('bootstrap/less/bootstrap')
@reloadBaseStylesheets()
# Internal-only:
reloadBaseStylesheets: ->
@requireStylesheet('../static/atom')
if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less'])
@requireStylesheet(nativeStylesheetPath)
# Internal-only:
stylesheetElementForId: (id, htmlElement=$('html')) ->
htmlElement.find("""head style[id="#{id}"]""")
# Internal-only:
resolveStylesheet: (stylesheetPath) ->
if path.extname(stylesheetPath).length > 0
fs.resolveOnLoadPath(stylesheetPath)
else
fs.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
# Public: resolves and applies the stylesheet specified by the path.
# Public: Resolve and apply the stylesheet specified by the path.
#
# * stylesheetPath: String. Can be an absolute path or the name of a CSS or
# LESS file in the stylesheets path.
# This supports both CSS and LESS stylsheets.
#
# Returns the absolute path to the stylesheet
# * stylesheetPath: A {String} path to the stylesheet that can be an absolute
# path or a relative path that will be resolved against the load path.
#
# Returns the absolute path to the required stylesheet.
requireStylesheet: (stylesheetPath, ttype = 'bundled', htmlElement) ->
if fullPath = @resolveStylesheet(stylesheetPath)
content = @loadStylesheet(fullPath)
@ -162,14 +153,12 @@ class ThemeManager
fullPath
# Internal-only:
loadStylesheet: (stylesheetPath) ->
if path.extname(stylesheetPath) is '.less'
@loadLessStylesheet(stylesheetPath)
else
fs.readFileSync(stylesheetPath, 'utf8')
# Internal-only:
loadLessStylesheet: (lessStylesheetPath) ->
unless @lessCache?
LessCompileCache = require './less-compile-cache'
@ -184,16 +173,13 @@ class ThemeManager
#{e.message}
"""
# Internal-only:
stringToId: (string) ->
string.replace(/\\/g, '/')
# Internal-only:
removeStylesheet: (stylesheetPath) ->
fullPath = @resolveStylesheet(stylesheetPath) ? stylesheetPath
@stylesheetElementForId(@stringToId(fullPath)).remove()
# Internal-only:
applyStylesheet: (path, text, ttype = 'bundled', htmlElement=$('html')) ->
styleElement = @stylesheetElementForId(@stringToId(path), htmlElement)
if styleElement.length

View File

@ -1,9 +1,9 @@
# Public: Measure how long a function takes to run.
#
# * description:
# A String description that will be logged to the console.
# A {String} description that will be logged to the console.
# * fn:
# A Function to measure the duration of.
# A {Function} to measure the duration of.
#
# Returns the value returned by the given function.
window.measure = (description, fn) ->
@ -16,10 +16,10 @@ window.measure = (description, fn) ->
# Public: Create a dev tools profile for a function.
#
# * description:
# A String descrption that will be available in the Profiles tab of the dev
# A {String} descrption that will be available in the Profiles tab of the dev
# tools.
# * fn:
# A Function to profile.
# A {Function} to profile.
#
# Return the value returned by the given function.
window.profile = (description, fn) ->

View File

@ -6,6 +6,7 @@ Delegator = require 'delegato'
{$, $$, View} = require './space-pen-extensions'
fs = require 'fs-plus'
Workspace = require './workspace'
CommandInstaller = require './command-installer'
EditorView = require './editor-view'
PaneView = require './pane-view'
PaneColumnView = require './pane-column-view'
@ -37,6 +38,11 @@ Editor = require './editor'
# * `application:bring-all-windows-to-front` - Brings all {AtomWindow}s to the
# the front.
#
# ## Requiring in package specs
#
# ```coffee
# {WorkspaceView} = require 'atom'
# ```
module.exports =
class WorkspaceView extends View
Delegator.includeInto(this)
@ -101,8 +107,11 @@ class WorkspaceView extends View
@command 'application:bring-all-windows-to-front', -> ipc.sendChannel('command', 'application:bring-all-windows-to-front')
@command 'application:open-your-config', -> ipc.sendChannel('command', 'application:open-your-config')
@command 'application:open-your-keymap', -> ipc.sendChannel('command', 'application:open-your-keymap')
@command 'application:open-your-snippets', -> ipc.sendChannel('command', 'application:open-your-snippets')
@command 'application:open-your-stylesheet', -> ipc.sendChannel('command', 'application:open-your-stylesheet')
@command 'window:install-shell-commands', => @installShellCommands()
@command 'window:run-package-specs', => ipc.sendChannel('run-package-specs', path.join(atom.project.getPath(), 'spec'))
@command 'window:increase-font-size', => @increaseFontSize()
@command 'window:decrease-font-size', => @decreaseFontSize()
@ -122,6 +131,26 @@ class WorkspaceView extends View
@command 'core:save', => @saveActivePaneItem()
@command 'core:save-as', => @saveActivePaneItemAs()
installShellCommands: ->
showErrorDialog = (error) ->
installDirectory = CommandInstaller.getInstallDirectory()
atom.confirm
message: error.message
detailedMessage: "Make sure #{installDirectory} exists and is writable. Run 'sudo mkdir -p #{installDirectory} && sudo chown $USER #{installDirectory}' to fix this problem."
resourcePath = atom.getLoadSettings().resourcePath
CommandInstaller.installAtomCommand resourcePath, (error) =>
if error?
showDialog(error)
else
CommandInstaller.installApmCommand resourcePath, (error) =>
if error?
showDialog(error)
else
atom.confirm
message: "Commands installed."
detailedMessage: "The shell commands `atom` and `apm` are installed."
# Private:
handleFocus: (e) ->
if @getActivePane()

View File

@ -27,6 +27,7 @@
.make-icon(book);
.make-icon(bookmark);
.make-icon(broadcast);
.make-icon(browser);
.make-icon(bug);
.make-icon(calendar);
.make-icon(check);
@ -46,6 +47,7 @@
.make-icon(comment-add);
.make-icon(comment-discussion);
.make-icon(credit-card);
.make-icon(dash);
.make-icon(dashboard);
.make-icon(database);
.make-icon(device-camera);
@ -74,6 +76,7 @@
.make-icon(file-symlink-file);
.make-icon(file-text);
.make-icon(file-zip);
.make-icon(fold);
.make-icon(gear);
.make-icon(gift);
.make-icon(gist);
@ -92,6 +95,7 @@
.make-icon(git-pull-request-abandoned);
.make-icon(globe);
.make-icon(graph);
.make-icon(heart);
.make-icon(history);
.make-icon(home);
.make-icon(horizontal-rule);
@ -127,10 +131,14 @@
.make-icon(mail);
.make-icon(mail-read);
.make-icon(mail-reply);
.make-icon(mark-facebook);
.make-icon(mark-github);
.make-icon(mark-github-detail);
.make-icon(mark-google);
.make-icon(mark-twitter);
.make-icon(markdown);
.make-icon(megaphone);
.make-icon(mention);
.make-icon(microscope);
.make-icon(milestone);
.make-icon(mirror-private);
@ -142,6 +150,7 @@
.make-icon(mute);
.make-icon(mute-video);
.make-icon(no-newline);
.make-icon(node-js);
.make-icon(octoface);
.make-icon(organization);
.make-icon(pencil);
@ -150,11 +159,16 @@
.make-icon(person-follow);
.make-icon(person-remove);
.make-icon(pin);
.make-icon(playback-fast-forward);
.make-icon(playback-pause);
.make-icon(playback-play);
.make-icon(playback-rewind);
.make-icon(plus);
.make-icon(podium);
.make-icon(primitive-dot);
.make-icon(primitive-square);
.make-icon(pulse);
.make-icon(puzzle);
.make-icon(question);
.make-icon(quote);
.make-icon(radio-tower);
@ -175,18 +189,22 @@
.make-icon(screen-full);
.make-icon(screen-normal);
.make-icon(search);
.make-icon(search-save);
.make-icon(server);
.make-icon(settings);
.make-icon(split);
.make-icon(squirrel);
.make-icon(star);
.make-icon(star-add);
.make-icon(star-delete);
.make-icon(steps);
.make-icon(stop);
.make-icon(sync);
.make-icon(tag);
.make-icon(tag-add);
.make-icon(tag-remove);
.make-icon(telescope);
.make-icon(terminal);
.make-icon(three-bars);
.make-icon(tools);
.make-icon(triangle-down);

Binary file not shown.

View File

@ -14,6 +14,7 @@
@book: "\f007";
@bookmark: "\f07b";
@broadcast: "\f048";
@browser: "\f0c5";
@bug: "\f091";
@calendar: "\f068";
@check: "\f03a";
@ -33,6 +34,7 @@
@comment-add: "\f06f";
@comment-discussion: "\f04f";
@credit-card: "\f045";
@dash: "\f0ca";
@dashboard: "\f07d";
@database: "\f096";
@device-camera: "\f056";
@ -61,6 +63,7 @@
@file-symlink-file: "\f0b0";
@file-text: "\f011";
@file-zip: "\f013";
@fold: "\f0cc";
@gear: "\f02f";
@gift: "\f042";
@gist: "\f00e";
@ -79,6 +82,7 @@
@git-pull-request-abandoned: "\f090";
@globe: "\f0b6";
@graph: "\f043";
@heart: "\2665";
@history: "\f07e";
@home: "\f08d";
@horizontal-rule: "\f070";
@ -114,10 +118,14 @@
@mail: "\f03b";
@mail-read: "\f03c";
@mail-reply: "\f051";
@mark-facebook: "\f0ce";
@mark-github: "\f00a";
@mark-github-detail: "\f093";
@mark-google: "\f0cd";
@mark-twitter: "\f0ae";
@markdown: "\f0c9";
@megaphone: "\f077";
@mention: "\f0be";
@microscope: "\f089";
@milestone: "\f075";
@mirror-private: "\f025";
@ -129,19 +137,26 @@
@mute: "\f080";
@mute-video: "\f0b8";
@no-newline: "\f09c";
@node-js: "\f0c3";
@octoface: "\f008";
@organization: "\f037";
@package: "\f0c4";
@pencil: "\f058";
@person: "\f018";
@person-add: "\f01a";
@person-follow: "\f01c";
@person-remove: "\f01b";
@pin: "\f041";
@playback-fast-forward: "\f0bd";
@playback-pause: "\f0bb";
@playback-play: "\f0bf";
@playback-rewind: "\f0bc";
@plus: "\f05d";
@podium: "\f0af";
@primitive-dot: "\f052";
@primitive-square: "\f053";
@pulse: "\f085";
@puzzle: "\f0c0";
@question: "\f02c";
@quote: "\f063";
@radio-tower: "\f030";
@ -162,18 +177,22 @@
@screen-full: "\f066";
@screen-normal: "\f067";
@search: "\f02e";
@search-save: "\f0cb";
@server: "\f097";
@settings: "\f07c";
@split: "\f0c6";
@squirrel: "\f0b2";
@star: "\f02a";
@star-add: "\f082";
@star-delete: "\f083";
@steps: "\f0c7";
@stop: "\f08f";
@sync: "\f087";
@tag: "\f015";
@tag-add: "\f054";
@tag-remove: "\f055";
@telescope: "\f088";
@terminal: "\f0c8";
@three-bars: "\f05e";
@tools: "\f031";
@triangle-down: "\f05b";

2
vendor/apm vendored

@ -1 +1 @@
Subproject commit b80ef23ce8d2a1e8b4f40eb0f89c87f32dcc3415
Subproject commit 3f8701bfe624de844641863391c04def9cca5c86