Merge remote-tracking branch 'origin/master' into cj-sublime-bindings

This commit is contained in:
probablycorey
2013-11-01 11:33:13 -07:00
76 changed files with 1320 additions and 1769 deletions

View File

@@ -3,7 +3,6 @@ require '../spec/spec-helper'
path = require 'path'
{$, _, Point, fs} = require 'atom'
Project = require '../src/project'
fsUtils = require '../src/fs-utils'
TokenizedBuffer = require '../src/tokenized-buffer'
defaultCount = 100

33
docs/building-atom.md Normal file
View File

@@ -0,0 +1,33 @@
## Building Atom
These guide is meant only for users who wish to help develop atom core,
if you're just intersted in using atom you should just [download
atom][download].
## OSX
* Use Mountain Lion
* Install the latest node 0.10.x release (32bit preferable)
* Clone [atom][atom-git] to `~/github/atom`
* Run `~/github/atom/script/bootstrap`
## Windows
* Install [Visual C++ 2010 Express][win-vs2010]
* Install the [latest 32bit Node 0.10.x][win-node]
* Install the [latest Python 2.7.x][win-python]
* Install [Github for Windows][win-github]
* Clone [atom/atom][atom-git] to `C:\Users\<user>\Documents\GitHub\atom\`
* Add `C:\Python27;C:\Program Files\nodejs;C:\Users\<user>\Documents\GitHub\atom\node_modules\`
to your PATH
* Set ATOM_ACCESS_TOKEN to your oauth2 credentials (run `security -q
find-generic-password -ws 'GitHub API Token'` on OSX to get your
credentials).
* Use the Windows GitHub shell and cd into `C:\Users\<user>\Documents\GitHub\atom`
* Run `node script/bootstrap`
[download]: http://www.atom.io
[win-node]: http://nodejs.org/download/
[win-python]: http://www.python.org/download/
[win-github]: http://windows.github.com/
[atom-git]: https://github.com/atom/atom/

View File

@@ -1,12 +1,11 @@
# Authoring Packages
# Creating Packages
Packages are at the core of Atom. Nearly everything outside of the main editor
is handled by a package. That includes "core" pieces like the file tree, status
bar and more.
is handled by a package. That includes "core" pieces like the [file tree][file-tree],
[status bar][status-bar], [syntax highlighting][cs-syntax], and more.
A package can contain a variety of different resource types to change Atom's
behavior. The basic package layout is as follows (though not every package will
have all of these directories):
behavior. The basic package layout is as follows:
```text
my-package/
@@ -21,9 +20,13 @@ my-package/
package.json
```
Not every package will have (or need) all of these directories.
We have [a tutorial on creating your first package][first-package].
## package.json
Similar to [npm packages][npm], Atom packages can contain a _package.json_ file
Similar to [npm packages][npm], Atom packages contain a _package.json_ file
in their top-level directory. This file contains metadata about the package,
such as the path to its "main" module, library dependencies, and manifests
specifying the order in which its resources should be loaded.
@@ -83,20 +86,22 @@ you don't need to worry because that's getting torn down anyway.
### Simple Package Code
Your directory would look like this:
```text
my-package/
package.json # optional
package.json
index.coffee
lib/
my-package.coffee
```
`index.coffee`:
`index.coffee` might be:
```coffeescript
module.exports = require "./lib/my-package"
```
`my-package/my-package.coffee`:
`my-package/my-package.coffee` might start:
```coffeescript
module.exports =
activate: (rootView, state) -> # ...
@@ -111,8 +116,6 @@ Also, please collaborate with us if you need an API that doesn't exist. Our goal
is to build out Atom's API organically based on the needs of package authors
like you.
Check out [wrap-guide] for a simple example of Atom's package API in action.
## Stylesheets
Stylesheets for your package should be placed in the _stylesheets_ directory.
@@ -120,14 +123,16 @@ Any stylesheets in this directory will be loaded and attached to the DOM when
your package is activated. Stylesheets can be written as CSS or [LESS] (but LESS
is recommended).
Ideally you will not need much in the way of styling. We've provided a standard
set of components. You can view all components by opening the styleguide: open
the command palette (`cmd-p`) and search for _styleguide_ or just
`cmd-ctrl-shift-g`.
Ideally, you won't need much in the way of styling. We've provided a standard
set of components which define both the colors and UI elements for any package
that fits into Atom seamlessly. You can view all of Atom's UI components by opening
the styleguide: open the command palette (`cmd-p`) and search for _styleguide_,
or just type `cmd-ctrl-G`.
If you do need styling, we try to keep only structural styles in the package
stylesheets. Colors and sizing should be taken from the active theme's
[ui-variables.less][ui-variables]. If you follow this guideline, your package
If you _do_ need special styling, try to keep only structural styles in the package
stylesheets. If you _must_ specify colors and sizing, these should be taken from
the active theme's [ui-variables.less][ui-variables]. For more information, see the
[theme variables docs][theme-variables]. If you follow this guideline, your package
will look good out of the box with any theme!
An optional `stylesheets` array in your _package.json_ can list the stylesheets
@@ -136,18 +141,23 @@ alphabetically.
## Keymaps
It's recommended that you provide key bindings for commonly used actions for
your extension, especially if you're also adding a new command:
```coffeescript
'.tree-view-scroller':
'ctrl-V': 'changer:magic'
```
It's recommended that you provide key bindings for commonly used actions for
your extension, especially if you're also adding a new command.
Keymaps are placed in the _keymaps_ subdirectory. By default, all keymaps are
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
the example above, `changer:magic` command is executed when pressing `ctrl-V` on
the `.tree-view-scroller` element.
See the [main keymaps documentation][keymaps] for more detailed information on
how keymaps work.
@@ -159,6 +169,9 @@ specify which menus to load and in what order.
### Application Menu
It's recommended that you create an application menu item for common actions
with your package that aren't tied to a specific element:
```coffee-script
'menu': [
{
@@ -178,18 +191,17 @@ specify which menus to load and in what order.
]
```
It's recommended that you create an application menu item for common actions
with your package that aren't tied to a specific element.
To add your own item to the application menu simply create a top level `menu`
key in any menu configuration file in _menus_ (since the above is [CSON] it
should end with `.cson`)
To add your own item to the application menu, simply create a top level `menu`
key in any menu configuration file in _menus_. This can be a JSON or [CSON] file.
The menu templates you specify are merged with all other templates provided
by other packages in the order which they were loaded.
### Context Menu
It's recommended to specify a context menu item for commands that are linked to
specific parts of the interface, like adding a file in the tree-view:
```coffee-script
'context-menu':
'.tree-view':
@@ -198,12 +210,9 @@ by other packages in the order which they were loaded.
'Inspect Element': 'core:inspect'
```
It's recommended to specify a context menu item for commands that are linked to
specific parts of the interface, like adding a file in the tree-view.
To add your own item to the application menu simply create a top level
`context-menu` key in any menu configuration file in _menus_ (since the above is
[CSON] it should end with `.cson`)
`context-menu` key in any menu configuration file in _menus_. This can be a
JSON or [CSON] file.
Context menus are created by determining which element was selected and
then adding all of the menu items whose selectors match that element (in
@@ -216,7 +225,7 @@ or one of its parents has the `tree-view` class applied to it.
## Snippets
An extension can supply language snippets in the _snippets_ directory which
allows the user to enter repetitive text quickly.
allows the user to enter repetitive text quickly:
```coffeescript
".source.coffee .specs":
@@ -312,8 +321,8 @@ You can also use the `atom` protocol URLs in themes.
Your package **should** have tests, and if they're placed in the _spec_
directory, they can be run by Atom.
Under the hood, [Jasmine] is being used to execute the tests, so you can
assume that any DSL available there is available to your package as well.
Under the hood, [Jasmine] executes your tests, so you can assume that any DSL
available there is available to your package as well.
**FIXME: Explain the following**
@@ -323,7 +332,9 @@ assume that any DSL available there is available to your package as well.
* setTimeout
* whatever else is different in spec-helper
## Running tests
## Running Tests
TODO: Probably use the menu option now.
Once you've got your test suite written, the recommended way to run it is `apm
test`. `apm test` prints its output to the console and returns the proper status
@@ -349,257 +360,7 @@ registry.
Run `apm help publish` to see all the available options and `apm help` to see
all the other available commands.
# Full Example
Let's take a look at creating our first package.
To get started hit `cmd-p`, and start typing "Package Generator." to generate
the package. Once you select the package generator command, it'll ask you for a
name for your new package. Let's call ours _changer_.
Now, _changer_ is going to have a default set of folders and files created for
us. Hit `cmd-r` to reload Atom, then hit `cmd-p` and start typing "Changer."
You'll see a new `Changer:Toggle` command which, if selected, pops up a new
message. 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_ can easily become this:
```coffeescript
'.tree-view':
'ctrl-V': 'changer:magic'
```
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.
`.tree-view` represents the parent container for the tree view.
Keybindings only work within the context of where they're entered. For example,
hitting `ctrl-V` anywhere other than tree won't do anything. You can map to
`body` if you want to scope to anywhere in Atom, or just `.editor` for the
editor portion.
To bind keybindings to a command, we'll use the `rootView.command` method. This
takes a command name and executes a function in the code. For example:
```coffeescript
rootView.command "changer:magic", => @magic()
```
It's common practice to namespace your commands with your package name, and
separate it with a colon (`:`). Rename the existing `toggle` method to `magic`
to get the binding to work.
Reload the editor, click on the tree, hit your keybinding, and...nothing
happens! What the heck?!
Open up the _package.json_ file, and notice the key that says
`activationEvents`. Basically, this tells Atom to not load a package until it
hears a certain event. Let's change the event to `changer:magic` and reload the
editor.
Hitting the key binding on the tree now works!
## Working with Styles
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, we have [a list of
some of the bundled libraries Atom provides by default](#included-libraries).
Let's bring in jQuery:
```coffeescript
{$} = require 'atom'
```
Now, we can query the tree to get us a list of every file that _wasn't_
modified:
```coffeescript
magic: ->
$('ol.entries li').each (i, el) ->
if !$(el).hasClass("modified")
console.log el
```
You can access the dev console by hitting `alt-cmd-i`. When we execute the
`changer:magic` command, the browser console lists the items that are not being
modified. Let's add a class to each of these elements called `hide-me`:
```coffeescript
magic: ->
$('ol.entries li').each (i, el) ->
if !$(el).hasClass("modified")
$(el).addClass("hide-me")
```
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;
}
```
Refresh atom, and run the `changer` command. You'll see all the non-changed
files disappear from the tree. 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:
```coffeescript
magic: ->
$('ol.entries li').each (i, el) ->
if !$(el).hasClass("modified")
if !$(el).hasClass("hide-me")
$(el).addClass("hide-me")
else
$(el).removeClass("hide-me")
```
## 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 create a new class method called `content`.
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], which we'll touch upon only briefly here.
Our display will simply be an unordered list of the file names, and their
modified times. Let's start by carving out a `div` to hold the filenames:
```coffeescript
@content: ->
@div class: 'modified-files-container', =>
@ul class: 'modified-files-list', outlet: 'modifiedFilesList', =>
@li 'Test'
@li 'Test2'
```
You can add any HTML5 attribute you like. `outlet` names the variable your
package can uses to manipulate the element directly. The fat pipe (`=>`)
indicates that the next set are nested children.
We'll add one more line to `magic` to make this pane appear:
```coffeescript
rootView.vertical.append(this)
```
If you hit the key command, you'll see a box appear right underneath the editor.
Success!
Before we populate this, let's apply some logic to toggle the pane off and on,
just like we did with the tree view:
```coffeescript
# toggles the pane
if @hasParent()
rootView.vertical.children().last().remove()
else
rootView.vertical.append(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 see your test list.
## Calling Node.js Code
Since Atom is built on top of Node.js, 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:
```coffeescript
path = require 'path'
# ...
modifiedFiles = []
# for each single entry...
$('ol.entries li.file.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.modified')
parents.each (i, el) ->
filePath.unshift($(el).find('div.header span.name').eq(0).text())
modifiedFilePath = path.join(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.
Let's 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()
rootView.vertical.children().last().remove()
else
for file in modifiedFiles
stat = fs.lstatSync(file)
mtime = stat.mtime
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
rootView.vertical.append(this)
```
When you toggle the modified files list, your pane is now populated with the
filenames and modified times of files in your project. 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()
rootView.vertical.children().last().remove()
else
for file in modifiedFiles
stat = fs.lstatSync(file)
mtime = stat.mtime
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
rootView.vertical.append(this)
```
# Included Libraries
## Included Libraries
FIXME: Describe `require 'atom'
@@ -612,12 +373,16 @@ popular libraries into their packages:
Additional libraries can be found by browsing Atom's *node_modules* folder.
[file-tree]: https://github.com/atom/tree-view
[status-bar]: https://github.com/atom/status-bar
[cs-syntax]: https://github.com/atom/language-coffee-script
[npm]: http://en.wikipedia.org/wiki/Npm_(software)
[npm-keys]: https://npmjs.org/doc/json.html
[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
[theme-variables]: theme-variables.md
[tm-tokens]: http://manual.macromates.com/en/language_grammars.html
[spacepen]: https://github.com/nathansobo/space-pen
[path]: http://nodejs.org/docs/latest/api/path.html
@@ -627,3 +392,4 @@ Additional libraries can be found by browsing Atom's *node_modules* folder.
[cson]: https://github.com/atom/season
[less]: http://lesscss.org
[ui-variables]: https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less
[first-package]: your-first-package.html

View File

@@ -1,72 +1,67 @@
# Creating a Theme
Atom's interface is rendered using HTML and it's styled via [LESS] (a superset
of CSS). Don't worry if you haven't heard of LESS before, it's just like CSS but
Atom's interface is rendered using HTML, and it's styled via [LESS] (a superset
of CSS). Don't worry if you haven't heard of LESS before; it's just like CSS, but
with a few handy extensions.
Since CSS is the basis of the theming system, we can load multiple themes within
Atom and they behaves just as they would on a website. Themes loaded first are overridden by
themes which are loaded later (the order is controlled from within the Settings
pane).
Atom, and the themes behave just as they would on a website. Themes loaded first
are overridden by themes which are loaded later. The order of theme loading is
controlled within the Settings/Themes pane.
This flexibility is helpful for users which prefer a light interface with a dark
syntax theme. Atom currently has only interface and syntax themes but it is
possible to create a theme to style something specific &mdash; say a changing
This flexibility is helpful for users that prefer a light interface with a dark
syntax theme. Atom currently has only interface and syntax themes, but it is
possible to create a theme to style something specific &mdash; say, changing
the colors in the tree view or creating a language specific syntax theme.
## Getting Started
To create your own theme you'll need a few things:
* A working install of [Atom], so you can work on your new theme.
* A working install of [git] to track changes.
* And a [GitHub] account, so you can distribute your themes.
Themes are pretty straight forward but it's still helpful to be familiar with
a few things before starting:
* LESS is a superset of CSS but it has some really handy features like
* LESS is a superset of CSS, but it has some really handy features like
variables. If you aren't familiar with its syntax take a few minutes
to [familiarize yourself][less-tutorial].
* Atom uses Chrome at its core, so you can use Chrome devtools to
inspect the current state of the interface. Checkout Google's
[extensive tutorial][devtools-tutorial] for a short introduction.
* You may also want to review the concept of a _[package.json]_, too. This file
is used to help distribute your theme to Atom users.
## Creating a Minimal Syntax Theme
There are two types of themes you can create: syntax themes and interface themes.
The differences between them are simply a matter of what they target and what
they provide. Syntax themes focus on the entire editor pane, while interface themes
target elements which are outside of the editor.
1. Open the Command Palette (`cmd-p`)
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
of your new theme in action.
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`.
## Creating a Syntax Theme
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.
Let's create your first theme.
## Interface Themes
To get started, hit `cmd-p`, and start typing "Generate Theme" to generate
a package. Select "Generate Theme," and you'll be asked for a theme name. Let's
call ours _motif_.
There are only two differences between interface and syntax themes - what
they target and what they provide. Interface themes only target elements which
are outside of the editor and **must** provide a `ui-variables.less` file which
contains all of the variables provided by the [core themes][ui-variables].
Atom will pop open a new window, showing the _motif_ theme, with a default set of
folders and files created for us. If you hit `cmd-,` and navigate to the Themes
menu option, you'll see the `motif` theme already available. Drag it over from
"Enabled Themes" to "Available Themes."
To create a UI theme, do the following:
Open up _stylesheets/colors.less_ to change the various colors variables which
have been already been defined. For example, turn `@red` into `#f4c2c1`.
Then, open _stylesheets/base.less_, and modify the various syntax CSS selectors
that have been already been defined. Each of these selectors represents a different
part of the Atom window. Themes that don't need to modify a particular region
can simply remove the selectors they don't need.
As an example, let's make the `.gutter` `background-color` into `@red`.
Reload Atom by hitting `cmd-r` to see the changes you made reflected in your Atom
window. Pretty neat!
## Creating an Interface Theme
Interface themes **must** provide a `ui-variables.less` file which contains all
of the variables provided by the [core themes][ui-variables].
To create an interface UI theme, do the following:
1. Fork one of the following repos
1. [atom-dark-ui]
@@ -82,32 +77,37 @@ To create a UI theme, do the following:
## Development workflow
There are a few of tools to help make theme development fast.
There are a few of tools to help make theme development faster.
### Live Reload
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.
Reloading by hitting `cmd-r` after you make changes to your theme is 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
__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
To enable a Dev Mode window:
1. Open your theme directory in a dev window by either going to the
__View > Developer > Open in Dev Mode__ menu or by hitting the `cmd-shift-o`
shortcut
1. Make a change to your theme file and save it. Your change should be
immediately applied!
If you'd like to reload all styles at any time, you can use the shortcut
`cmd-ctrl-shift-r`.
If you'd like to reload all the styles at any time, you can use the shortcut
`cmd-ctrl-R`.
### Developer Tools
Atom is based on the Chrome browser, and supports Chrome's Developer Tools. You
can open them by selecting the __View > Toggle Developer Tools__ menu or by using the
`cmd-option-i` shortcut.
can open them by selecting the __View > Toggle Developer Tools__ menu, or by using
the `cmd-option-i` shortcut.
The dev tools allow you to inspect elements and take a look at their CSS
properties.
![devtools-img]
Check out Google's [extensive tutorial][devtools-tutorial] for a short introduction.
### Atom Styleguide
If you are creating an interface theme, you'll want a way to see how your theme
@@ -115,17 +115,17 @@ changes affect all the components in the system. The [styleguide] is a page with
every component Atom supports rendered.
To open the styleguide, open the command palette (`cmd-p`) and search for
_styleguide_ or use the shortcut `cmd-ctrl-shift-g`.
_styleguide_, or use the shortcut `cmd-ctrl-shift-g`.
![styleguide-img]
[less]: http://lesscss.org/
[git]: http://git-scm.com/
[atom]: https://atom.io/
[github]: https://github.com/
[package.json]: ./creating-a-package.html#package-json
[less-tutorial]: https://speakerdeck.com/danmatthews/less-css
[devtools-tutorial]: https://developers.google.com/chrome-developer-tools/docs/elements
[ui-variables]: https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less
[ui-variables]: ./theme-variables.html
[livereload]: https://github.com/atom/dev-live-reload
[styleguide]: https://github.com/atom/styleguide
[atom-dark-ui]: https://github.com/atom/atom-dark-ui

View File

@@ -45,6 +45,10 @@ By default, `~/.atom/keymap.cson` is loaded when Atom is started. It will always
be loaded last, giving you the chance to override bindings that are defined by
Atom's core keymaps or third-party packages.
You'll want to know all the commands available to you. Open the Settings panel
(`cmd-,`) and select the _Keybindings_ tab. It will show you all the keybindings
currently in use.
## Advanced Configuration
Atom loads configuration settings from the `config.cson` file in your _~/.atom_

View File

@@ -6,6 +6,7 @@
* [Creating a Theme](creating-a-theme.md)
### Advanced Topics
* [Configuration](internals/configuration.md)
* [Keymaps](internals/keymaps.md)
* [Serialization](internals/serialization.md)

151
docs/template.jst Normal file
View File

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="../../assets/js/html5shiv.js"></script>
<script src="../../assets/js/respond.min.js"></script>
<![endif]-->
<title>Atom - <%= title %></title>
<style>
/*github.com style (c) Vasily Polovnyov <vast@whiteants.net>*/
pre code {
display: block; padding: 0.5em;
color: #333;
background: #f8f8ff
}
pre .comment,
pre .template_comment,
pre .diff .header,
pre .javadoc {
color: #998;
font-style: italic
}
pre .keyword,
pre .css .rule .keyword,
pre .winutils,
pre .javascript .title,
pre .nginx .title,
pre .subst,
pre .request,
pre .status {
color: #333;
font-weight: bold
}
pre .number,
pre .hexcolor,
pre .ruby .constant {
color: #099;
}
pre .string,
pre .tag .value,
pre .phpdoc,
pre .tex .formula {
color: #d14
}
pre .title,
pre .id {
color: #900;
font-weight: bold
}
pre .javascript .title,
pre .lisp .title,
pre .clojure .title,
pre .subst {
font-weight: normal
}
pre .class .title,
pre .haskell .type,
pre .vhdl .literal,
pre .tex .command {
color: #458;
font-weight: bold
}
pre .tag,
pre .tag .title,
pre .rules .property,
pre .django .tag .keyword {
color: #000080;
font-weight: normal
}
pre .attribute,
pre .variable,
pre .lisp .body {
color: #008080
}
pre .regexp {
color: #009926
}
pre .class {
color: #458;
font-weight: bold
}
pre .symbol,
pre .ruby .symbol .string,
pre .lisp .keyword,
pre .tex .special,
pre .prompt {
color: #990073
}
pre .built_in,
pre .lisp .title,
pre .clojure .built_in {
color: #0086b3
}
pre .preprocessor,
pre .pi,
pre .doctype,
pre .shebang,
pre .cdata {
color: #999;
font-weight: bold
}
pre .deletion {
background: #fdd
}
pre .addition {
background: #dfd
}
pre .diff .change {
background: #0086b3
}
pre .chunk {
color: #aaa
}
body {
padding-top: 50px;
}
</style>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/<%= tag %>/index.html">Atom Documentation</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="/<%= tag %>/index.html">Guides</a></li>
<li><a href="/<%= tag %>/api/index.html">API</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container">
<%= content %>
</div>
</body>
</html>

116
docs/theme-variables.md Normal file
View File

@@ -0,0 +1,116 @@
# Style variables
Atom's UI provides a set of variables you can use in your own themes and packages.
## Use in Themes
Each custom theme must specify a `ui-variables.less` file with all of the
following variables defined. The top-most theme specified in the theme settings
will be loaded and available for import.
## Use in Packages
In any of your package's `.less` files, you can access the theme variables
by importing the `ui-variables` file from Atom.
Your package should generally only specify structural styling, and these should
come from [the style guide][styleguide]. Your package shouldn't specify colors,
padding sizes, or anything in absolute pixels. You should instead use the theme
variables. If you follow this guideline, your package will look good out of the
box with any theme!
Here's an example `.less` file that a package can define using theme variables:
```css
@import "ui-variables";
.my-selector{
background-color: @base-background-color;
padding: @component-padding;
}
```
## Variables
### Text colors
* `@text-color`
* `@text-color-subtle`
* `@text-color-highlight`
* `@text-color-selected`
* `@text-color-info` - A blue
* `@text-color-success`- A green
* `@text-color-warning`- An orange or yellow
* `@text-color-error` - A red
### Background colors
* `@background-color-info` - A blue
* `@background-color-success` - A green
* `@background-color-warning` - An orange or yellow
* `@background-color-error` - A red
* `@background-color-highlight`
* `@background-color-selected`
* `@app-background-color` - The app's background under all the editor components
### Component colors
* `@base-background-color` -
* `@base-border-color` -
* `@pane-item-background-color` -
* `@pane-item-border-color` -
* `@input-background-color` -
* `@input-border-color` -
* `@tool-panel-background-color` -
* `@tool-panel-border-color` -
* `@inset-panel-background-color` -
* `@inset-panel-border-color` -
* `@panel-heading-background-color` -
* `@panel-heading-border-color` -
* `@overlay-background-color` -
* `@overlay-border-color` -
* `@button-background-color` -
* `@button-background-color-hover` -
* `@button-background-color-selected` -
* `@button-border-color` -
* `@tab-bar-background-color` -
* `@tab-bar-border-color` -
* `@tab-background-color` -
* `@tab-background-color-active` -
* `@tab-border-color` -
* `@tree-view-background-color` -
* `@tree-view-border-color` -
* `@ui-site-color-1` -
* `@ui-site-color-2` -
* `@ui-site-color-3` -
* `@ui-site-color-4` -
* `@ui-site-color-5` -
### Component sizes
* `@disclosure-arrow-size` -
* `@component-padding` -
* `@component-icon-padding` -
* `@component-icon-size` -
* `@component-line-height` -
* `@component-border-radius` -
* `@tab-height` -
### Fonts
* `@font-size` -
* `@font-family` -
[styleguide]: https://github.com/atom/styleguide

334
docs/your-first-package.md Normal file
View File

@@ -0,0 +1,334 @@
# Creating Your First Package
Let's take a look at creating your first package.
To get started, hit `cmd-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-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'
```
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.
`.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 bind keybindings to a command, we'll need to do a bit of association in our
CoffeeScript code using the `rootView.command` method. This method takes a command
name and executes a callback function. Open up _lib/changer-view.coffee_, and
change `rootView.command "changer:toggle"` to look like this:
```coffeescript
rootView.command "changer:magic", => @magic()
```
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.
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?!
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:
```json
"activationEvents": ["changer:magic"]
```
Hitting the key binding on the tree now works!
## Working with Styles
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:
```coffeescript
{$, View} = require 'atom'
```
Now, we can define the `magic` method to query the tree to get us a list of every
file that _wasn't_ modified:
```coffeescript
magic: ->
$('ol.entries li').each (i, el) ->
if !$(el).hasClass("status-modified")
console.log el
```
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`:
```coffeescript
magic: ->
$('ol.entries li').each (i, el) ->
if !$(el).hasClass("status-modified")
$(el).addClass("hide-me")
```
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;
}
```
Refresh Atom, and run the `changer` command. You'll see all the non-changed
files disappear from the tree. Success!
![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:
```coffeescript
magic: ->
$('ol.entries li').each (i, el) ->
if !$(el).hasClass("status-modified")
if !$(el).hasClass("hide-me")
$(el).addClass("hide-me")
else
$(el).removeClass("hide-me")
```
## 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
rootView.vertical.append(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, `rootView.vertical.append` tells Atom to append `this`
item (_i.e._, whatever is defined by`@content`) _vertically_ to the editor. If
we had called `rootView.horizontal.append`, the pane would be attached to the
right-hand side of the editor.
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 `rootView.vertical.append`
call with this code:
```coffeescript
# toggles the pane
if @hasParent()
rootView.vertical.children().last().remove()
else
rootView.vertical.append(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(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()
rootView.vertical.children().last().remove()
else
for file in modifiedFiles
stat = fs.lstatSync(file)
mtime = stat.mtime
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
rootView.vertical.append(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
rootView.vertical.children().last().remove()
else
for file in modifiedFiles
stat = fs.lstatSync(file)
mtime = stat.mtime
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
rootView.vertical.append(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

View File

@@ -1,14 +1,13 @@
{Document, Point, Range, Site} = require 'telepath'
_ = require 'underscore-plus'
module.exports =
_: _
_: require 'underscore-plus'
BufferedNodeProcess: require '../src/buffered-node-process'
BufferedProcess: require '../src/buffered-process'
Directory: require '../src/directory'
Document: Document
File: require '../src/file'
fs: require '../src/fs-utils'
fs: require 'fs-plus'
Git: require '../src/git'
Point: Point
Range: Range

View File

@@ -18,6 +18,7 @@
"coffeestack": "0.6.0",
"emissary": "0.9.0",
"first-mate": "0.5.0",
"fs-plus": "0.7.0",
"fuzzaldrin": "0.1.0",
"git-utils": "0.28.0",
"guid": "0.0.10",
@@ -27,16 +28,14 @@
"nslog": "0.1.0",
"oniguruma": "0.22.0",
"optimist": "0.4.0",
"pathwatcher": "0.8.0",
"pathwatcher": "0.9.0",
"pegjs": "0.7.0",
"plist": "git://github.com/nathansobo/node-plist.git",
"q": "0.9.7",
"rimraf": "2.1.4",
"scandal": "0.6.0",
"scandal": "0.6.2",
"season": "0.14.0",
"semver": "1.1.4",
"space-pen": "2.0.0",
"telepath": "0.8.1",
"telepath": "0.19.0",
"temp": "0.5.0",
"underscore-plus": "0.2.0"
},
@@ -46,7 +45,7 @@
"fstream": "0.1.24",
"grunt": "~0.4.1",
"grunt-cli": "~0.1.9",
"grunt-coffeelint": "0.0.6",
"grunt-coffeelint": "git://github.com/atom/grunt-coffeelint.git",
"grunt-lesslint": "0.13.0",
"grunt-cson": "0.5.0",
"grunt-contrib-csslint": "~0.1.2",
@@ -59,7 +58,8 @@
"json-front-matter": "~0.1.3",
"grunt-shell": "~0.3.1",
"jasmine-node": "git://github.com/kevinsawicki/jasmine-node.git#short-stacks",
"request": "~2.27.0"
"request": "~2.27.0",
"unzip": "~0.1.9"
},
"packageDependencies" : {
"atom-light-ui": "0.5.0",
@@ -72,17 +72,16 @@
"archive-view": "0.11.0",
"autocomplete": "0.11.0",
"autoflow": "0.5.0",
"autosave": "0.4.0",
"autosave": "0.6.0",
"bookmarks": "0.8.0",
"bracket-matcher": "0.7.0",
"collaboration": "0.35.0",
"command-logger": "0.6.0",
"command-palette": "0.6.0",
"dev-live-reload": "0.13.0",
"editor-stats": "0.5.0",
"exception-reporting": "0.5.0",
"find-and-replace": "0.33.0",
"fuzzy-finder": "0.15.0",
"find-and-replace": "0.35.0",
"fuzzy-finder": "0.16.0",
"gists": "0.6.0",
"git-diff": "0.13.0",
"github-sign-in": "0.9.0",
@@ -94,17 +93,17 @@
"metrics": "0.8.0",
"package-generator": "0.14.0",
"release-notes": "0.11.0",
"settings-view": "0.36.0",
"snippets": "0.12.0",
"settings-view": "0.37.0",
"snippets": "0.13.0",
"spell-check": "0.9.0",
"status-bar": "0.15.0",
"status-bar": "0.15.1",
"styleguide": "0.9.0",
"symbols-view": "0.15.0",
"tabs": "0.7.0",
"tabs": "0.7.2",
"terminal": "0.15.0",
"timecop": "0.9.0",
"to-the-hubs": "0.8.0",
"tree-view": "0.22.0",
"tree-view": "0.23.0",
"visual-bell": "0.3.0",
"whitespace": "0.8.0",
"wrap-guide": "0.4.0",

View File

@@ -24,7 +24,7 @@ var commands = [
joinCommands('cd vendor/apm', 'npm install --silent .'),
'npm install --silent vendor/apm',
echoNewLine,
'node node_modules/.bin/apm install --silent'
'node node_modules/atom-package-manager/bin/apm install --silent',
];
process.chdir(path.dirname(__dirname));

View File

@@ -5,8 +5,8 @@ var path = require('path');
process.chdir(path.dirname(__dirname));
cp.safeExec('node script/bootstrap', function() {
// ./node_modules/.bin/grunt "$@"
var gruntPath = path.join('node_modules', '.bin', 'grunt');
// node node_modules/grunt-cli/bin/grunt "$@"
var gruntPath = path.join('node_modules', 'grunt-cli', 'bin', 'grunt');
var args = [gruntPath].concat(process.argv.slice(2));
cp.safeSpawn(process.execPath, args, process.exit);
});

5
script/build.cmd Normal file
View File

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

View File

@@ -32,8 +32,8 @@ cp.safeExec.bind(global, 'node script/bootstrap', function(error) {
async.series([
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'),
cp.safeExec.bind(global, 'node node_modules/atom-package-manager/bin/apm clean'),
cp.safeExec.bind(global, 'node node_modules/grunt-cli/bin/grunt ci --stack --no-color'),
], function(error) {
process.exit(error ? 1 : 0);
});

View File

@@ -5,5 +5,5 @@ var path = require('path');
process.chdir(path.dirname(__dirname));
safeExec('node script/bootstrap', function() {
safeExec('node node_modules/.bin/grunt ci --stack --no-color', process.exit);
safeExec('node node_modules/grunt-cli/bin/grunt ci --stack --no-color', process.exit);
});

View File

@@ -12,10 +12,10 @@ describe "install(commandPath, callback)", ->
spyOn(installer, 'findInstallDirectory').andCallFake (callback) ->
callback(directory)
fs.remove(directory) if fs.exists(directory)
fs.removeSync(directory) if fs.existsSync(directory)
it "symlinks the command and makes it executable", ->
fs.writeSync(commandPath, 'test')
fs.writeFileSync(commandPath, 'test')
expect(fs.isFileSync(commandPath)).toBeTruthy()
expect(fs.isExecutableSync(commandPath)).toBeFalsy()
expect(fs.isFileSync(destinationPath)).toBeFalsy()

View File

@@ -177,10 +177,10 @@ describe "Config", ->
describe ".initializeConfigDirectory()", ->
beforeEach ->
config.configDirPath = dotAtomPath
expect(fs.exists(config.configDirPath)).toBeFalsy()
expect(fs.existsSync(config.configDirPath)).toBeFalsy()
afterEach ->
fs.remove(dotAtomPath) if fs.exists(dotAtomPath)
fs.removeSync(dotAtomPath) if fs.existsSync(dotAtomPath)
describe "when the configDirPath doesn't exist", ->
it "copies the contents of dot-atom to ~/.atom", ->
@@ -192,23 +192,23 @@ describe "Config", ->
waitsFor -> initializationDone
runs ->
expect(fs.exists(config.configDirPath)).toBeTruthy()
expect(fs.exists(path.join(config.configDirPath, 'packages'))).toBeTruthy()
expect(fs.exists(path.join(config.configDirPath, 'snippets'))).toBeTruthy()
expect(fs.existsSync(config.configDirPath)).toBeTruthy()
expect(fs.existsSync(path.join(config.configDirPath, 'packages'))).toBeTruthy()
expect(fs.existsSync(path.join(config.configDirPath, 'snippets'))).toBeTruthy()
expect(fs.isFileSync(path.join(config.configDirPath, 'config.cson'))).toBeTruthy()
describe ".loadUserConfig()", ->
beforeEach ->
config.configDirPath = dotAtomPath
config.configFilePath = path.join(config.configDirPath, "config.cson")
expect(fs.exists(config.configDirPath)).toBeFalsy()
expect(fs.existsSync(config.configDirPath)).toBeFalsy()
afterEach ->
fs.remove(dotAtomPath) if fs.exists(dotAtomPath)
fs.removeSync(dotAtomPath) if fs.existsSync(dotAtomPath)
describe "when the config file contains valid cson", ->
beforeEach ->
fs.writeSync(config.configFilePath, "foo: bar: 'baz'")
fs.writeFileSync(config.configFilePath, "foo: bar: 'baz'")
config.loadUserConfig()
it "updates the config data based on the file contents", ->
@@ -217,7 +217,7 @@ describe "Config", ->
describe "when the config file contains invalid cson", ->
beforeEach ->
spyOn(console, 'error')
fs.writeSync(config.configFilePath, "{{{{{")
fs.writeFileSync(config.configFilePath, "{{{{{")
it "logs an error to the console and does not overwrite the config file on a subsequent save", ->
config.loadUserConfig()
@@ -227,9 +227,9 @@ describe "Config", ->
describe "when the config file does not exist", ->
it "creates it with an empty object", ->
fs.makeTree(config.configDirPath)
fs.makeTreeSync(config.configDirPath)
config.loadUserConfig()
expect(fs.exists(config.configFilePath)).toBe true
expect(fs.existsSync(config.configFilePath)).toBe true
expect(CSON.readFileSync(config.configFilePath)).toEqual {}
describe ".observeUserConfig()", ->
@@ -238,8 +238,8 @@ describe "Config", ->
beforeEach ->
config.configDirPath = dotAtomPath
config.configFilePath = path.join(config.configDirPath, "config.cson")
expect(fs.exists(config.configDirPath)).toBeFalsy()
fs.writeSync(config.configFilePath, "foo: bar: 'baz'")
expect(fs.existsSync(config.configDirPath)).toBeFalsy()
fs.writeFileSync(config.configFilePath, "foo: bar: 'baz'")
config.loadUserConfig()
config.observeUserConfig()
updatedHandler = jasmine.createSpy("updatedHandler")
@@ -247,11 +247,11 @@ describe "Config", ->
afterEach ->
config.unobserveUserConfig()
fs.remove(dotAtomPath) if fs.exists(dotAtomPath)
fs.removeSync(dotAtomPath) if fs.existsSync(dotAtomPath)
describe "when the config file changes to contain valid cson", ->
it "updates the config data", ->
fs.writeSync(config.configFilePath, "foo: { bar: 'quux', baz: 'bar'}")
fs.writeFileSync(config.configFilePath, "foo: { bar: 'quux', baz: 'bar'}")
waitsFor 'update event', -> updatedHandler.callCount > 0
runs ->
expect(config.get('foo.bar')).toBe 'quux'
@@ -260,7 +260,7 @@ describe "Config", ->
describe "when the config file changes to contain invalid cson", ->
beforeEach ->
spyOn(console, 'error')
fs.writeSync(config.configFilePath, "}}}")
fs.writeFileSync(config.configFilePath, "}}}")
waitsFor "error to be logged", -> console.error.callCount > 0
it "logs a warning and does not update config data", ->
@@ -271,7 +271,7 @@ describe "Config", ->
describe "when the config file subsequently changes again to contain valid cson", ->
beforeEach ->
fs.writeSync(config.configFilePath, "foo: bar: 'baz'")
fs.writeFileSync(config.configFilePath, "foo: bar: 'baz'")
waitsFor 'update event', -> updatedHandler.callCount > 0
it "updates the config data and resumes saving", ->

View File

@@ -16,10 +16,10 @@ describe "Directory", ->
beforeEach ->
temporaryFilePath = path.join(__dirname, 'fixtures', 'temporary')
fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath)
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
afterEach ->
fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath)
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
it "triggers 'contents-changed' event handlers", ->
changeHandler = null
@@ -27,13 +27,13 @@ describe "Directory", ->
runs ->
changeHandler = jasmine.createSpy('changeHandler')
directory.on 'contents-changed', changeHandler
fs.writeSync(temporaryFilePath, '')
fs.writeFileSync(temporaryFilePath, '')
waitsFor "first change", -> changeHandler.callCount > 0
runs ->
changeHandler.reset()
fs.remove(temporaryFilePath)
fs.removeSync(temporaryFilePath)
waitsFor "second change", -> changeHandler.callCount > 0
@@ -42,10 +42,10 @@ describe "Directory", ->
beforeEach ->
temporaryFilePath = path.join(directory.path, 'temporary')
fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath)
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
afterEach ->
fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath)
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
it "no longer triggers events", ->
changeHandler = null
@@ -53,7 +53,7 @@ describe "Directory", ->
runs ->
changeHandler = jasmine.createSpy('changeHandler')
directory.on 'contents-changed', changeHandler
fs.writeSync(temporaryFilePath, '')
fs.writeFileSync(temporaryFilePath, '')
waitsFor "change event", -> changeHandler.callCount > 0
@@ -62,7 +62,7 @@ describe "Directory", ->
directory.off()
waits 20
runs -> fs.remove(temporaryFilePath)
runs -> fs.removeSync(temporaryFilePath)
waits 20
runs -> expect(changeHandler.callCount).toBe 0

View File

@@ -1,62 +0,0 @@
{Site} = require 'telepath'
Environment = require './environment'
describe "EditSession replication", ->
[env1, env2, editSession1, editSession2] = []
beforeEach ->
env1 = new Environment(siteId: 1)
env2 = env1.clone(siteId: 2)
envConnection = env1.connect(env2)
doc2 = null
env1.run ->
editSession1 = project.openSync('sample.js')
editSession1.setScrollTop(5)
editSession1.setScrollLeft(5)
editSession1.setCursorScreenPosition([0, 5])
editSession1.addSelectionForBufferRange([[1, 2], [3, 4]])
doc1 = editSession1.getState()
doc2 = doc1.clone(env2.site)
envConnection.connect(doc1, doc2)
env2.run ->
editSession2 = deserialize(doc2)
afterEach ->
env1.destroy()
env2.destroy()
it "replicates the selections of existing replicas", ->
expect(editSession2.getRemoteSelectedBufferRanges()).toEqual editSession1.getSelectedBufferRanges()
editSession1.getLastSelection().setBufferRange([[2, 3], [4, 5]])
expect(editSession2.getRemoteSelectedBufferRanges()).toEqual editSession1.getSelectedBufferRanges()
editSession1.addCursorAtBufferPosition([5, 6])
expect(editSession2.getRemoteSelectedBufferRanges()).toEqual editSession1.getSelectedBufferRanges()
editSession1.consolidateSelections()
expect(editSession2.getRemoteSelectedBufferRanges()).toEqual editSession1.getSelectedBufferRanges()
it "introduces a local cursor for a new replica at the position of the last remote cursor", ->
expect(editSession2.getCursors().length).toBe 1
expect(editSession2.getSelections().length).toBe 1
expect(editSession2.getCursorBufferPosition()).toEqual [3, 4]
expect(editSession2.getSelectedBufferRanges()).toEqual [[[3, 4], [3, 4]]]
expect(editSession1.getRemoteCursors().length).toBe 1
expect(editSession1.getRemoteSelections().length).toBe 1
[cursor] = editSession1.getRemoteCursors()
[selection] = editSession1.getRemoteSelections()
expect(cursor.getBufferPosition()).toEqual [3, 4]
expect(selection.getBufferRange()).toEqual [[3, 4], [3, 4]]
it "replicates the scroll position", ->
expect(editSession2.getScrollTop()).toBe editSession1.getScrollTop()
expect(editSession2.getScrollLeft()).toBe editSession1.getScrollLeft()
editSession1.setScrollTop(10)
expect(editSession2.getScrollTop()).toBe 10
editSession2.setScrollLeft(20)
expect(editSession1.getScrollLeft()).toBe 20

View File

@@ -1,37 +0,0 @@
{Site} = require 'telepath'
Editor = require '../src/editor'
Environment = require './environment'
describe "Editor replication", ->
[env1, env2, editSession1, editSession2, editor1, editor2] = []
beforeEach ->
env1 = new Environment(siteId: 1)
env2 = env1.clone(siteId: 2)
envConnection = env1.connect(env2)
doc2 = null
env1.run ->
editSession1 = project.openSync('sample.js')
editSession1.setSelectedBufferRange([[1, 2], [3, 4]])
doc1 = editSession1.getState()
doc2 = doc1.clone(env2.site)
envConnection.connect(doc1, doc2)
editor1 = new Editor(editSession1)
editor1.attachToDom()
env2.run ->
editSession2 = deserialize(doc2)
editor2 = new Editor(editSession2)
editor2.attachToDom()
afterEach ->
env1.destroy()
env2.destroy()
it "displays the cursors and selections from all replicas", ->
expect(editor1.getSelectionViews().length).toBe 2
expect(editor2.getSelectionViews().length).toBe 2
expect(editor1.getCursorViews().length).toBe 2
expect(editor2.getCursorViews().length).toBe 2

View File

@@ -88,7 +88,7 @@ describe "Editor", ->
describe "when the activeEditSession's file is modified on disk", ->
it "triggers an alert", ->
filePath = path.join(temp.dir, 'atom-changed-file.txt')
fs.writeSync(filePath, "")
fs.writeFileSync(filePath, "")
editSession = project.openSync(filePath)
editor.edit(editSession)
editor.insertText("now the buffer is modified")
@@ -98,7 +98,7 @@ describe "Editor", ->
spyOn(atom, "confirm")
fs.writeSync(filePath, "a file change")
fs.writeFileSync(filePath, "a file change")
waitsFor "file to trigger contents-changed event", ->
fileChangeHandler.callCount > 0
@@ -153,7 +153,7 @@ describe "Editor", ->
it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
filePath = path.join(temp.dir, 'atom-changed-file.txt')
fs.writeSync(filePath, "")
fs.writeFileSync(filePath, "")
tempEditSession = project.openSync(filePath)
editor.edit(tempEditSession)
tempEditSession.insertText("a buffer change")
@@ -162,7 +162,7 @@ describe "Editor", ->
contentsConflictedHandler = jasmine.createSpy("contentsConflictedHandler")
tempEditSession.on 'contents-conflicted', contentsConflictedHandler
fs.writeSync(filePath, "a file change")
fs.writeFileSync(filePath, "a file change")
waitsFor ->
contentsConflictedHandler.callCount > 0
@@ -249,10 +249,10 @@ describe "Editor", ->
beforeEach ->
filePath = path.join(temp.dir, 'something.txt')
fs.writeSync(filePath, filePath)
fs.writeFileSync(filePath, filePath)
afterEach ->
fs.remove(filePath) if fs.exists(filePath)
fs.removeSync(filePath) if fs.existsSync(filePath)
it "emits event when buffer's path is changed", ->
eventHandler = jasmine.createSpy('eventHandler')
@@ -1766,6 +1766,14 @@ describe "Editor", ->
expect(editor.gutter.find('.line-number:first').intValue()).toBe 2
expect(editor.gutter.find('.line-number:last').intValue()).toBe 11
it "re-renders the correct line number range when there are folds", ->
editor.activeEditSession.foldBufferRow(1)
expect(editor.gutter.find('.line-number-1')).toHaveClass 'fold'
buffer.insert([0, 0], '\n')
expect(editor.gutter.find('.line-number-2')).toHaveClass 'fold'
describe "when wrapping is on", ->
it "renders a • instead of line number for wrapped portions of lines", ->
editSession.setSoftWrap(true)
@@ -2171,11 +2179,11 @@ describe "Editor", ->
beforeEach ->
filePath = project.resolve('git/working-dir/file.txt')
originalPathText = fs.read(filePath)
originalPathText = fs.readFileSync(filePath, 'utf8')
editor.edit(project.openSync(filePath))
afterEach ->
fs.writeSync(filePath, originalPathText)
fs.writeFileSync(filePath, originalPathText)
it "restores the contents of the editor to the HEAD revision", ->
editor.setText('')
@@ -2299,10 +2307,10 @@ describe "Editor", ->
beforeEach ->
tmpdir = fs.absolute(temp.dir)
filePath = path.join(tmpdir, "grammar-change.txt")
fs.writeSync(filePath, "var i;")
fs.writeFileSync(filePath, "var i;")
afterEach ->
fs.remove(filePath) if fs.exists(filePath)
fs.removeSync(filePath) if fs.existsSync(filePath)
it "updates all the rendered lines when the grammar changes", ->
editor.edit(project.openSync(filePath))
@@ -2646,16 +2654,16 @@ describe "Editor", ->
it "saves the state of the rendered lines, the display buffer, and the buffer to a file of the user's choosing", ->
saveDialogCallback = null
spyOn(atom, 'showSaveDialog').andCallFake (callback) -> saveDialogCallback = callback
spyOn(fs, 'writeSync')
spyOn(fs, 'writeFileSync')
editor.trigger 'editor:save-debug-snapshot'
statePath = path.join(temp.dir, 'state')
expect(atom.showSaveDialog).toHaveBeenCalled()
saveDialogCallback(statePath)
expect(fs.writeSync).toHaveBeenCalled()
expect(fs.writeSync.argsForCall[0][0]).toBe statePath
expect(typeof fs.writeSync.argsForCall[0][1]).toBe 'string'
expect(fs.writeFileSync).toHaveBeenCalled()
expect(fs.writeFileSync.argsForCall[0][0]).toBe statePath
expect(typeof fs.writeFileSync.argsForCall[0][1]).toBe 'string'
describe "when the escape key is pressed on the editor", ->
it "clears multiple selections if there are any, and otherwise allows other bindings to be handled", ->

View File

@@ -1,59 +0,0 @@
path = require 'path'
{Site} = require 'telepath'
{fs} = require 'atom'
Project = require '../src/project'
module.exports =
class Environment
constructor: ({@site, @state, siteId, projectPath}={}) ->
@site ?= new Site(siteId ? 1)
if @state?
@run => @project = deserialize(@state.get('project'))
else
@state = @site.createDocument({})
@project = new Project(projectPath ? path.join(__dirname, 'fixtures'))
@state.set(project: @project.getState())
clone: (params) ->
site = new Site(params.siteId)
new Environment(site: site, state: @state.clone(site))
destroy: ->
@project.destroy()
getState: -> @state
run: (fn) ->
uninstall = @install()
fn()
uninstall()
install: ->
oldSite = window.site
oldProject = window.project
window.site = @site
window.project = @project
->
window.site = oldSite
window.project = oldProject
connect: (otherEnv) ->
new EnvironmentConnection(this, otherEnv)
connectDocuments: (docA, docB, envB) ->
class EnvironmentConnection
constructor: (@envA, @envB) ->
@envA.getState().connect(@envB.getState())
connect: (docA, docB) ->
unless docA.site is @envA.site
throw new Error("Document and environment sites do not match (doc: site #{docA.site.id}, env: site #{@envA.site.id})")
unless docB.site is @envB.site
throw new Error("Document and environment sites do not match (doc: site #{docB.site.id}, env: site #{@envB.site.id})")
connection = docA.connect(docB)
connection.abFilter = (fn) => @envB.run(fn)
connection.baFilter = (fn) => @envA.run(fn)
connection

View File

@@ -6,19 +6,19 @@ describe 'File', ->
beforeEach ->
filePath = path.join(__dirname, 'fixtures', 'atom-file-test.txt') # Don't put in /tmp because /tmp symlinks to /private/tmp and screws up the rename test
fs.remove(filePath) if fs.exists(filePath)
fs.writeSync(filePath, "this is old!")
fs.removeSync(filePath) if fs.existsSync(filePath)
fs.writeFileSync(filePath, "this is old!")
file = new File(filePath)
afterEach ->
file.off()
fs.remove(filePath) if fs.exists(filePath)
fs.removeSync(filePath) if fs.existsSync(filePath)
describe "when the file has not been read", ->
describe "when the contents of the file change", ->
it "triggers 'contents-changed' event handlers", ->
file.on 'contents-changed', changeHandler = jasmine.createSpy('changeHandler')
fs.writeSync(file.getPath(), "this is new!")
fs.writeFileSync(file.getPath(), "this is new!")
waitsFor "change event", ->
changeHandler.callCount > 0
@@ -32,14 +32,14 @@ describe 'File', ->
changeHandler = null
changeHandler = jasmine.createSpy('changeHandler')
file.on 'contents-changed', changeHandler
fs.writeSync(file.getPath(), "this is new!")
fs.writeFileSync(file.getPath(), "this is new!")
waitsFor "change event", ->
changeHandler.callCount > 0
runs ->
changeHandler.reset()
fs.writeSync(file.getPath(), "this is newer!")
fs.writeFileSync(file.getPath(), "this is newer!")
waitsFor "second change event", ->
changeHandler.callCount > 0
@@ -49,7 +49,7 @@ describe 'File', ->
removeHandler = null
removeHandler = jasmine.createSpy('removeHandler')
file.on 'removed', removeHandler
fs.remove(file.getPath())
fs.removeSync(file.getPath())
waitsFor "remove event", ->
removeHandler.callCount > 0
@@ -61,8 +61,8 @@ describe 'File', ->
newPath = path.join(path.dirname(filePath), "atom-file-was-moved-test.txt")
afterEach ->
if fs.exists(newPath)
fs.remove(newPath)
if fs.existsSync(newPath)
fs.removeSync(newPath)
waitsFor "remove event", (done) -> file.on 'removed', done
it "it updates its path", ->
@@ -71,7 +71,7 @@ describe 'File', ->
moveHandler = jasmine.createSpy('moveHandler')
file.on 'moved', moveHandler
fs.move(filePath, newPath)
fs.moveSync(filePath, newPath)
waitsFor "move event", ->
moveHandler.callCount > 0
@@ -88,14 +88,14 @@ describe 'File', ->
changeHandler = jasmine.createSpy('changeHandler')
file.on 'contents-changed', changeHandler
fs.move(filePath, newPath)
fs.moveSync(filePath, newPath)
waitsFor "move event", ->
moveHandler.callCount > 0
runs ->
expect(changeHandler).not.toHaveBeenCalled()
fs.writeSync(file.getPath(), "this is new!")
fs.writeFileSync(file.getPath(), "this is new!")
waitsFor "change event", ->
changeHandler.callCount > 0
@@ -112,12 +112,12 @@ describe 'File', ->
expect(changeHandler).not.toHaveBeenCalled()
fs.remove(filePath)
fs.removeSync(filePath)
expect(changeHandler).not.toHaveBeenCalled()
waits 20
runs ->
fs.writeSync(filePath, "HE HAS RISEN!")
fs.writeFileSync(filePath, "HE HAS RISEN!")
expect(changeHandler).not.toHaveBeenCalled()
waitsFor "resurrection change event", ->
@@ -125,7 +125,7 @@ describe 'File', ->
runs ->
expect(removeHandler).not.toHaveBeenCalled()
fs.writeSync(filePath, "Hallelujah!")
fs.writeFileSync(filePath, "Hallelujah!")
changeHandler.reset()
waitsFor "post-resurrection change event", ->

View File

@@ -1,140 +0,0 @@
{fs} = require 'atom'
path = require 'path'
temp = require 'temp'
describe "fs", ->
fixturesDir = path.join(__dirname, 'fixtures')
describe ".read(path)", ->
it "return contents of file", ->
expect(fs.read(require.resolve("./fixtures/sample.txt"))).toBe "Some text.\n"
it "does not through an exception when the path is a binary file", ->
expect(-> fs.read(require.resolve("./fixtures/binary-file.png"))).not.toThrow()
describe ".isFileSync(path)", ->
it "returns true with a file path", ->
expect(fs.isFileSync(path.join(fixturesDir, 'sample.js'))).toBe true
it "returns false with a directory path", ->
expect(fs.isFileSync(fixturesDir)).toBe false
it "returns false with a non-existent path", ->
expect(fs.isFileSync(path.join(fixturesDir, 'non-existent'))).toBe false
expect(fs.isFileSync(null)).toBe false
describe ".exists(path)", ->
it "returns true when path exsits", ->
expect(fs.exists(fixturesDir)).toBe true
it "returns false when path doesn't exsit", ->
expect(fs.exists(path.join(fixturesDir, "-nope-does-not-exist"))).toBe false
expect(fs.exists("")).toBe false
expect(fs.exists(null)).toBe false
describe ".makeTree(path)", ->
aPath = path.join(temp.dir, 'a')
beforeEach ->
fs.remove(aPath) if fs.exists(aPath)
it "creates all directories in path including any missing parent directories", ->
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", ->
paths = []
onPath = (childPath) ->
paths.push(childPath)
true
fs.traverseTreeSync fixturesDir, onPath, onPath
expect(paths).toEqual fs.listTreeSync(fixturesDir)
it "does not recurse into a directory if it is pruned", ->
paths = []
onPath = (childPath) ->
if childPath.match(/\/dir$/)
false
else
paths.push(childPath)
true
fs.traverseTreeSync fixturesDir, onPath, onPath
expect(paths.length).toBeGreaterThan 0
for filePath in paths
expect(filePath).not.toMatch /\/dir\//
it "returns entries if path is a symlink", ->
symlinkPath = path.join(fixturesDir, 'symlink-to-dir')
symlinkPaths = []
onSymlinkPath = (path) -> symlinkPaths.push(path.substring(symlinkPath.length + 1))
regularPath = path.join(fixturesDir, 'dir')
paths = []
onPath = (path) -> paths.push(path.substring(regularPath.length + 1))
fs.traverseTreeSync(symlinkPath, onSymlinkPath, onSymlinkPath)
fs.traverseTreeSync(regularPath, onPath, onPath)
expect(symlinkPaths).toEqual(paths)
it "ignores missing symlinks", ->
directory = temp.mkdirSync('symlink-in-here')
paths = []
onPath = (childPath) -> paths.push(childPath)
fs.symlinkSync(path.join(directory, 'source'), path.join(directory, 'destination'))
fs.traverseTreeSync(directory, onPath)
expect(paths.length).toBe 0
describe ".md5ForPath(path)", ->
it "returns the MD5 hash of the file at the given path", ->
expect(fs.md5ForPath(require.resolve('./fixtures/sample.js'))).toBe 'dd38087d0d7e3e4802a6d3f9b9745f2b'
describe ".list(path, extensions)", ->
it "returns the absolute paths of entries within the given directory", ->
paths = fs.listSync(project.getPath())
expect(paths).toContain project.resolve('css.css')
expect(paths).toContain project.resolve('coffee.coffee')
expect(paths).toContain project.resolve('two-hundred.txt')
it "returns an empty array for paths that aren't directories or don't exist", ->
expect(fs.listSync(project.resolve('sample.js'))).toEqual []
expect(fs.listSync('/non/existent/directory')).toEqual []
it "can filter the paths by an optional array of file extensions", ->
paths = fs.listSync(project.getPath(), ['.css', 'coffee'])
expect(paths).toContain project.resolve('css.css')
expect(paths).toContain project.resolve('coffee.coffee')
expect(listedPath).toMatch /(css|coffee)$/ for listedPath in paths
describe ".list(path, [extensions,] callback)", ->
paths = null
it "calls the callback with the absolute paths of entries within the given directory", ->
waitsFor (done) ->
fs.list project.getPath(), (err, result) ->
paths = result
done()
runs ->
expect(paths).toContain project.resolve('css.css')
expect(paths).toContain project.resolve('coffee.coffee')
expect(paths).toContain project.resolve('two-hundred.txt')
it "can filter the paths by an optional array of file extensions", ->
waitsFor (done) ->
fs.list project.getPath(), ['css', '.coffee'], (err, result) ->
paths = result
done()
runs ->
expect(paths).toContain project.resolve('css.css')
expect(paths).toContain project.resolve('coffee.coffee')
expect(listedPath).toMatch /(css|coffee)$/ for listedPath in paths
describe ".absolute(relativePath)", ->
it "converts a leading ~ segment to the HOME directory", ->
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

@@ -9,7 +9,7 @@ describe "Git", ->
beforeEach ->
gitPath = path.join(temp.dir, '.git')
fs.remove(gitPath) if fs.isDirectorySync(gitPath)
fs.removeSync(gitPath) if fs.isDirectorySync(gitPath)
afterEach ->
repo.destroy() if repo?.repo?
@@ -47,22 +47,22 @@ describe "Git", ->
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'working-dir'))
filePath = require.resolve('./fixtures/git/working-dir/file.txt')
newPath = path.join(__dirname, 'fixtures', 'git', 'working-dir', 'new-path.txt')
originalPathText = fs.read(filePath)
originalPathText = fs.readFileSync(filePath, 'utf8')
afterEach ->
fs.writeSync(filePath, originalPathText)
fs.remove(newPath) if fs.exists(newPath)
fs.writeFileSync(filePath, originalPathText)
fs.removeSync(newPath) if fs.existsSync(newPath)
describe "when the path is unstaged", ->
it "returns false if the path has not been modified", ->
expect(repo.isPathModified(filePath)).toBeFalsy()
it "returns true if the path is modified", ->
fs.writeSync(filePath, "change")
fs.writeFileSync(filePath, "change")
expect(repo.isPathModified(filePath)).toBeTruthy()
it "returns true if the path is deleted", ->
fs.remove(filePath)
fs.removeSync(filePath)
expect(repo.isPathModified(filePath)).toBeTruthy()
it "returns false if the path is new", ->
@@ -75,10 +75,10 @@ describe "Git", ->
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'working-dir'))
filePath = require.resolve('./fixtures/git/working-dir/file.txt')
newPath = path.join(__dirname, 'fixtures', 'git', 'working-dir', 'new-path.txt')
fs.writeSync(newPath, "i'm new here")
fs.writeFileSync(newPath, "i'm new here")
afterEach ->
fs.remove(newPath) if fs.exists(newPath)
fs.removeSync(newPath) if fs.existsSync(newPath)
describe "when the path is unstaged", ->
it "returns true if the path is new", ->
@@ -93,35 +93,35 @@ describe "Git", ->
beforeEach ->
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'working-dir'))
path1 = require.resolve('./fixtures/git/working-dir/file.txt')
originalPath1Text = fs.read(path1)
originalPath1Text = fs.readFileSync(path1, 'utf8')
path2 = require.resolve('./fixtures/git/working-dir/other.txt')
originalPath2Text = fs.read(path2)
originalPath2Text = fs.readFileSync(path2, 'utf8')
afterEach ->
fs.writeSync(path1, originalPath1Text)
fs.writeSync(path2, originalPath2Text)
fs.writeFileSync(path1, originalPath1Text)
fs.writeFileSync(path2, originalPath2Text)
it "no longer reports a path as modified after checkout", ->
expect(repo.isPathModified(path1)).toBeFalsy()
fs.writeSync(path1, '')
fs.writeFileSync(path1, '')
expect(repo.isPathModified(path1)).toBeTruthy()
expect(repo.checkoutHead(path1)).toBeTruthy()
expect(repo.isPathModified(path1)).toBeFalsy()
it "restores the contents of the path to the original text", ->
fs.writeSync(path1, '')
fs.writeFileSync(path1, '')
expect(repo.checkoutHead(path1)).toBeTruthy()
expect(fs.read(path1)).toBe(originalPath1Text)
expect(fs.readFileSync(path1, 'utf8')).toBe(originalPath1Text)
it "only restores the path specified", ->
fs.writeSync(path2, 'path 2 is edited')
fs.writeFileSync(path2, 'path 2 is edited')
expect(repo.isPathModified(path2)).toBeTruthy()
expect(repo.checkoutHead(path1)).toBeTruthy()
expect(fs.read(path2)).toBe('path 2 is edited')
expect(fs.readFileSync(path2, 'utf8')).toBe('path 2 is edited')
expect(repo.isPathModified(path2)).toBeTruthy()
it "fires a status-changed event if the checkout completes successfully", ->
fs.writeSync(path1, '')
fs.writeFileSync(path1, '')
repo.getPathStatus(path1)
statusHandler = jasmine.createSpy('statusHandler')
repo.on 'status-changed', statusHandler
@@ -144,14 +144,14 @@ describe "Git", ->
beforeEach ->
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'working-dir'))
filePath = require.resolve('./fixtures/git/working-dir/file.txt')
originalPathText = fs.read(filePath)
originalPathText = fs.readFileSync(filePath, 'utf8')
afterEach ->
fs.writeSync(filePath, originalPathText)
fs.writeFileSync(filePath, originalPathText)
it "returns the number of lines added and deleted", ->
expect(repo.getDiffStats(filePath)).toEqual {added: 0, deleted: 0}
fs.writeSync(filePath, "#{originalPathText} edited line")
fs.writeFileSync(filePath, "#{originalPathText} edited line")
expect(repo.getDiffStats(filePath)).toEqual {added: 1, deleted: 1}
describe ".getPathStatus(path)", ->
@@ -160,20 +160,20 @@ describe "Git", ->
beforeEach ->
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'working-dir'))
filePath = require.resolve('./fixtures/git/working-dir/file.txt')
originalPathText = fs.read(filePath)
originalPathText = fs.readFileSync(filePath, 'utf8')
afterEach ->
fs.writeSync(filePath, originalPathText)
fs.writeFileSync(filePath, originalPathText)
it "trigger a status-changed event when the new status differs from the last cached one", ->
statusHandler = jasmine.createSpy("statusHandler")
repo.on 'status-changed', statusHandler
fs.writeSync(filePath, '')
fs.writeFileSync(filePath, '')
status = repo.getPathStatus(filePath)
expect(statusHandler.callCount).toBe 1
expect(statusHandler.argsForCall[0][0..1]).toEqual [filePath, status]
fs.writeSync(filePath, 'abc')
fs.writeFileSync(filePath, 'abc')
status = repo.getPathStatus(filePath)
expect(statusHandler.callCount).toBe 1
@@ -183,17 +183,17 @@ describe "Git", ->
beforeEach ->
repo = new Git(path.join(__dirname, 'fixtures', 'git', 'working-dir'))
modifiedPath = project.resolve('git/working-dir/file.txt')
originalModifiedPathText = fs.read(modifiedPath)
originalModifiedPathText = fs.readFileSync(modifiedPath, 'utf8')
newPath = project.resolve('git/working-dir/untracked.txt')
cleanPath = project.resolve('git/working-dir/other.txt')
fs.writeSync(newPath, '')
fs.writeFileSync(newPath, '')
afterEach ->
fs.writeSync(modifiedPath, originalModifiedPathText)
fs.remove(newPath) if fs.exists(newPath)
fs.writeFileSync(modifiedPath, originalModifiedPathText)
fs.removeSync(newPath) if fs.existsSync(newPath)
it "returns status information for all new and modified files", ->
fs.writeSync(modifiedPath, 'making this path modified')
fs.writeFileSync(modifiedPath, 'making this path modified')
statusHandler = jasmine.createSpy('statusHandler')
repo.on 'statuses-changed', statusHandler
repo.refreshStatus()
@@ -207,15 +207,17 @@ describe "Git", ->
expect(repo.isStatusNew(statuses[newPath])).toBeTruthy()
expect(repo.isStatusModified(statuses[modifiedPath])).toBeTruthy()
describe "when a buffer is changed and then saved", ->
describe "buffer events", ->
[originalContent, editSession] = []
afterEach ->
fs.writeSync(editSession.getPath(), originalContent)
it "emits a status-changed event", ->
beforeEach ->
editSession = project.openSync('sample.js')
originalContent = editSession.getText()
afterEach ->
fs.writeFileSync(editSession.getPath(), originalContent)
it "emits a status-changed event when a buffer is saved", ->
editSession.insertNewline()
statusHandler = jasmine.createSpy('statusHandler')
@@ -224,16 +226,8 @@ describe "Git", ->
expect(statusHandler.callCount).toBe 1
expect(statusHandler).toHaveBeenCalledWith editSession.getPath(), 256
describe "when a buffer is reloaded and has been changed", ->
[originalContent, editSession] = []
afterEach ->
fs.writeSync(editSession.getPath(), originalContent)
it "emits a status-changed event", ->
editSession = project.openSync('sample.js')
originalContent = editSession.getText()
fs.writeSync(editSession.getPath(), 'changed')
it "emits a status-changed event when a buffer is reloaded", ->
fs.writeFileSync(editSession.getPath(), 'changed')
statusHandler = jasmine.createSpy('statusHandler')
project.getRepo().on 'status-changed', statusHandler
@@ -243,11 +237,22 @@ describe "Git", ->
editSession.getBuffer().reload()
expect(statusHandler.callCount).toBe 1
it "emits a status-changed event when a buffer's path changes", ->
fs.writeFileSync(editSession.getPath(), 'changed')
statusHandler = jasmine.createSpy('statusHandler')
project.getRepo().on 'status-changed', statusHandler
editSession.getBuffer().trigger 'path-changed'
expect(statusHandler.callCount).toBe 1
expect(statusHandler).toHaveBeenCalledWith editSession.getPath(), 256
editSession.getBuffer().trigger 'path-changed'
expect(statusHandler.callCount).toBe 1
describe "when a project is deserialized", ->
[originalContent, buffer, project2] = []
afterEach ->
fs.writeSync(buffer.getPath(), originalContent)
fs.writeFileSync(buffer.getPath(), originalContent)
project2?.destroy()
it "subscribes to all the serialized buffers in the project", ->

View File

@@ -1,110 +0,0 @@
path = require 'path'
temp = require 'temp'
{Site} = require 'telepath'
{View} = require 'atom'
PaneContainer = require '../src/pane-container'
Pane = require '../src/pane'
Environment = require './environment'
describe "PaneContainer replication", ->
[env1, env2, envConnection, container1, container2, pane1a, pane1b, pane1c] = []
class TestView extends View
@deserialize: ({name}) -> new TestView(name)
@content: -> @div tabindex: -1
initialize: (@name) -> @text(@name)
serialize: -> { deserializer: 'TestView', @name }
getState: -> @serialize()
getUri: -> path.join(temp.dir, @name)
isEqual: (other) -> @name is other.name
beforeEach ->
registerDeserializer(TestView)
env1 = new Environment(siteId: 1)
env2 = env1.clone(siteId: 2)
envConnection = env1.connect(env2)
doc2 = null
env1.run ->
container1 = new PaneContainer
pane1a = new Pane(new TestView('A'))
container1.setRoot(pane1a)
pane1b = pane1a.splitRight(new TestView('B'))
pane1c = pane1b.splitDown(new TestView('C'))
doc1 = container1.getState()
doc2 = doc1.clone(env2.site)
envConnection.connect(doc1, doc2)
env2.run ->
container2 = deserialize(doc2)
afterEach ->
env1.destroy()
env2.destroy()
unregisterDeserializer(TestView)
it "replicates the inital state of a pane container with splits", ->
expect(container1.find('.row > :eq(0):contains(A)')).toExist()
expect(container1.find('.row > :eq(1)')).toHaveClass 'column'
expect(container1.find('.row > :eq(1) > :eq(0):contains(B)')).toExist()
expect(container1.find('.row > :eq(1) > :eq(1):contains(C)')).toExist()
expect(container2.find('.row > :eq(0):contains(A)')).toExist()
expect(container2.find('.row > :eq(1)')).toHaveClass 'column'
expect(container2.find('.row > :eq(1) > :eq(0):contains(B)')).toExist()
expect(container2.find('.row > :eq(1) > :eq(1):contains(C)')).toExist()
it "replicates the splitting of panes", ->
container1.attachToDom().width(400).height(200)
container2.attachToDom().width(400).height(200)
pane1d = pane1a.splitRight(new TestView('D'))
expect(container1.find('.row > :eq(1):contains(D)')).toExist()
expect(container2.find('.row > :eq(1):contains(D)')).toExist()
expect(container2.find('.row > :eq(1):contains(D)').outerWidth()).toBe container1.find('.row > :eq(1):contains(D)').outerWidth()
pane1d.splitDown(new TestView('E'))
expect(container1.find('.row > :eq(1)')).toHaveClass('column')
expect(container1.find('.row > :eq(1) > :eq(0):contains(D)')).toExist()
expect(container1.find('.row > :eq(1) > :eq(1):contains(E)')).toExist()
expect(container2.find('.row > :eq(1)')).toHaveClass('column')
expect(container2.find('.row > :eq(1) > :eq(0):contains(D)')).toExist()
expect(container2.find('.row > :eq(1) > :eq(1):contains(E)')).toExist()
it "replicates removal of panes", ->
pane1c.remove()
expect(container1.find('.row > :eq(0):contains(A)')).toExist()
expect(container1.find('.row > :eq(1):contains(B)')).toExist()
expect(container2.find('.row > :eq(0):contains(A)')).toExist()
expect(container2.find('.row > :eq(1):contains(B)')).toExist()
pane1b.remove()
expect(container1.find('> :eq(0):contains(A)')).toExist()
expect(container2.find('> :eq(0):contains(A)')).toExist()
pane1a.remove()
expect(container1.children()).not.toExist()
expect(container2.children()).not.toExist()
# FIXME: We need to get this passing again on master
xit "replicates splitting of panes containing edit sessions", ->
env1.run ->
pane1a.showItem(project.openSync('dir/a'))
pane1a.splitDown()
expect(project.getBuffers().length).toBe 1
expect(container1.find('.row > :eq(0) > :eq(0)').view().activeItem.getRelativePath()).toBe 'dir/a'
expect(container1.find('.row > :eq(0) > :eq(1)').view().activeItem.getRelativePath()).toBe 'dir/a'
env2.run ->
expect(container2.find('.row > :eq(0) > :eq(0)').view().activeItem.getRelativePath()).toBe 'dir/a'
expect(container2.find('.row > :eq(0) > :eq(1)').view().activeItem.getRelativePath()).toBe 'dir/a'

View File

@@ -80,7 +80,7 @@ describe "PaneContainer", ->
expect(panes).toEqual [pane1, pane2, pane3]
panes = []
pane4 = pane3.splitRight()
pane4 = pane3.splitRight(pane3.copyActiveItem())
expect(panes).toEqual [pane4]
panes = []

View File

@@ -1,40 +0,0 @@
PaneContainer = require '../src/pane-container'
Pane = require '../src/pane'
{Site} = require 'telepath'
describe "Pane replication", ->
[editSession1a, editSession1b, container1, pane1, doc1] = []
[editSession2a, editSession2b, container2, pane2, doc2] = []
beforeEach ->
editSession1a = project.openSync('sample.js')
editSession1b = project.openSync('sample.txt')
container1 = new PaneContainer
pane1 = new Pane(editSession1a, editSession1b)
container1.setRoot(pane1)
doc1 = container1.getState()
doc2 = doc1.clone(new Site(2))
doc1.connect(doc2)
container2 = deserialize(doc2)
pane2 = container2.getRoot()
it "replicates the initial state of the panes", ->
expect(pane2.items).toEqual(pane1.items)
it "replicates addition and removal of pane items", ->
pane1.addItem(project.openSync('css.css'), 1)
expect(pane2.items).toEqual(pane1.items)
pane1.removeItemAtIndex(2)
expect(pane2.items).toEqual(pane1.items)
it "replicates the movement of pane items", ->
pane1.moveItem(editSession1a, 1)
expect(pane2.items).toEqual(pane1.items)
it "replicates which pane item is active", ->
pane1.showNextItem()
expect(pane2.activeItem).toEqual pane1.activeItem
pane1.showNextItem()
expect(pane2.activeItem).toEqual pane1.activeItem

View File

@@ -439,8 +439,8 @@ describe "Pane", ->
beforeEach ->
pane.showItem(editSession1)
paneToLeft = pane.splitLeft()
paneToRight = pane.splitRight()
paneToLeft = pane.splitLeft(pane.copyActiveItem())
paneToRight = pane.splitRight(pane.copyActiveItem())
container.attachToDom()
describe "when the removed pane is focused", ->
@@ -494,7 +494,7 @@ describe "Pane", ->
it "returns the next pane if one exists, wrapping around from the last pane to the first", ->
pane.showItem(editSession1)
expect(pane.getNextPane()).toBeUndefined
pane2 = pane.splitRight()
pane2 = pane.splitRight(pane.copyActiveItem())
expect(pane.getNextPane()).toBe pane2
expect(pane2.getNextPane()).toBe pane
@@ -529,7 +529,7 @@ describe "Pane", ->
expect(pane.isActive()).toBeFalsy()
pane.focusin()
expect(pane.isActive()).toBeTruthy()
pane.splitRight()
pane.splitRight(pane.copyActiveItem())
expect(pane.isActive()).toBeFalsy()
expect(becameInactiveHandler.callCount).toBe 1
@@ -545,7 +545,7 @@ describe "Pane", ->
describe "splitRight(items...)", ->
it "builds a row if needed, then appends a new pane after itself", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane1.splitRight()
pane2 = pane1.splitRight(pane1.copyActiveItem())
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(pane2.items).toEqual [editSession1]
expect(pane2.activeItem).not.toBe editSession1 # it's a copy
@@ -554,10 +554,22 @@ describe "Pane", ->
expect(pane3.getItems()).toEqual [view3, view4]
expect(container.find('.row .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
describe "splitRight(items...)", ->
it "builds a row if needed, then appends a new pane after itself ", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane1.splitRight()
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(pane2.items).toEqual []
expect(pane2.activeItem).toBe null
pane3 = pane2.splitRight()
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0], pane3[0]]
expect(pane3.items).toEqual []
expect(pane3.activeItem).toBe null
describe "splitLeft(items...)", ->
it "builds a row if needed, then appends a new pane before itself", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane.splitLeft()
pane2 = pane.splitLeft(pane1.copyActiveItem())
expect(container.find('.row .pane').toArray()).toEqual [pane2[0], pane[0]]
expect(pane2.items).toEqual [editSession1]
expect(pane2.activeItem).not.toBe editSession1 # it's a copy
@@ -569,7 +581,7 @@ describe "Pane", ->
describe "splitDown(items...)", ->
it "builds a column if needed, then appends a new pane after itself", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane.splitDown()
pane2 = pane.splitDown(pane1.copyActiveItem())
expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0]]
expect(pane2.items).toEqual [editSession1]
expect(pane2.activeItem).not.toBe editSession1 # it's a copy
@@ -581,7 +593,7 @@ describe "Pane", ->
describe "splitUp(items...)", ->
it "builds a column if needed, then appends a new pane before itself", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane.splitUp()
pane2 = pane.splitUp(pane1.copyActiveItem())
expect(container.find('.column .pane').toArray()).toEqual [pane2[0], pane[0]]
expect(pane2.items).toEqual [editSession1]
expect(pane2.activeItem).not.toBe editSession1 # it's a copy

View File

@@ -1,51 +0,0 @@
path = require 'path'
{Site} = require 'telepath'
Project = require '../src/project'
Git = require '../src/git'
describe "Project replication", ->
[doc1, doc2, project1, project2] = []
beforeEach ->
# pretend that home-1/project and home-2/project map to the same git repository url
spyOn(Git, 'open').andReturn
getOriginUrl: -> 'git://server/project.git'
destroy: ->
projectHome1 = path.join(__dirname, 'fixtures', 'replication', 'home-1')
projectHome2 = path.join(__dirname, 'fixtures', 'replication', 'home-2')
config.set('core.projectHome', projectHome1)
project1 = new Project(path.join(projectHome1, 'project'))
project1.bufferForPathSync('file-1.txt')
project1.bufferForPathSync('file-1.txt')
expect(project1.getBuffers().length).toBe 1
doc1 = project1.getState()
doc2 = doc1.clone(new Site(2))
connection = doc1.connect(doc2)
# pretend we're bootstrapping a joining window
config.set('core.projectHome', projectHome2)
project2 = deserialize(doc2)
afterEach ->
project1.destroy()
project2.destroy()
it "replicates the initial path and open buffers of the project", ->
expect(project2.getPath()).not.toBe project1.getPath()
expect(project2.getBuffers().length).toBe 1
expect(project2.getBuffers()[0].getRelativePath()).toBe project1.getBuffers()[0].getRelativePath()
expect(project2.getBuffers()[0].getPath()).not.toBe project1.getBuffers()[0].getPath()
it "replicates insertion and removal of open buffers", ->
project2.bufferForPathSync('file-2.txt')
expect(project1.getBuffers().length).toBe 2
expect(project2.getBuffers()[0].getRelativePath()).toBe project1.getBuffers()[0].getRelativePath()
expect(project2.getBuffers()[1].getRelativePath()).toBe project1.getBuffers()[1].getRelativePath()
expect(project2.getBuffers()[0].getRelativePath()).not.toBe project1.getBuffers()[0].getPath()
expect(project2.getBuffers()[1].getRelativePath()).not.toBe project1.getBuffers()[1].getPath()
project1.removeBuffer(project1.bufferForPathSync('file-2.txt'))
expect(project1.getBuffers().length).toBe 1
expect(project2.getBuffers()[0].getRelativePath()).toBe project1.getBuffers()[0].getRelativePath()

View File

@@ -365,10 +365,10 @@ describe "Project", ->
runs ->
fs.rename(path.join(projectPath, 'git.git'), path.join(projectPath, '.git'))
ignoredPath = path.join(projectPath, 'ignored.txt')
fs.writeSync(ignoredPath, 'this match should not be included')
fs.writeFileSync(ignoredPath, 'this match should not be included')
afterEach ->
fs.remove(projectPath) if fs.exists(projectPath)
fs.removeSync(projectPath) if fs.existsSync(projectPath)
it "excludes ignored files", ->
project.setPath(projectPath)
@@ -402,7 +402,7 @@ describe "Project", ->
it "includes files and folders that begin with a '.'", ->
projectPath = temp.mkdirSync()
filePath = path.join(projectPath, '.text')
fs.writeSync(filePath, 'match this')
fs.writeFileSync(filePath, 'match this')
project.setPath(projectPath)
paths = []
matches = []

View File

@@ -210,7 +210,7 @@ describe "RootView", ->
rootView.trigger 'window:decrease-font-size'
expect(config.get('editor.fontSize')).toBe 1
describe ".open(filePath, options)", ->
describe ".openSync(filePath, options)", ->
describe "when there is no active pane", ->
beforeEach ->
spyOn(Pane.prototype, 'focus')
@@ -242,6 +242,12 @@ describe "RootView", ->
editSession = rootView.openSync('b', changeFocus: false)
expect(rootView.getActivePane().focus).not.toHaveBeenCalled()
describe "when the split option is 'right'", ->
it "creates a new pane and opens the file in said pane", ->
editSession = rootView.openSync('b', split: 'right')
expect(rootView.getActivePane().activeItem).toBe editSession
expect(editSession.getPath()).toBe require.resolve('./fixtures/dir/b')
describe "when there is an active pane", ->
[activePane, initialItemCount] = []
beforeEach ->
@@ -284,7 +290,55 @@ describe "RootView", ->
editSession = rootView.openSync('b', changeFocus: false)
expect(activePane.focus).not.toHaveBeenCalled()
describe ".openAsync(filePath)", ->
describe "when the split option is 'right'", ->
it "creates a new pane and opens the file in said pane", ->
pane1 = rootView.getActivePane()
editSession = rootView.openSync('b', split: 'right')
pane2 = rootView.getActivePane()
expect(pane2[0]).not.toBe pane1[0]
expect(editSession.getPath()).toBe require.resolve('./fixtures/dir/b')
expect(rootView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
editSession = rootView.openSync('file1', split: 'right')
pane3 = rootView.getActivePane()
expect(pane3[0]).toBe pane2[0]
expect(editSession.getPath()).toBe require.resolve('./fixtures/dir/file1')
expect(rootView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
describe ".openSingletonSync(filePath, options)", ->
describe "when there is an active pane", ->
[pane1] = []
beforeEach ->
spyOn(Pane.prototype, 'focus').andCallFake -> @makeActive()
pane1 = rootView.getActivePane()
it "creates a new pane and reuses the file when already open", ->
rootView.openSingletonSync('b', split: 'right')
pane2 = rootView.getActivePane()
expect(pane2[0]).not.toBe pane1[0]
expect(pane1.itemForUri('b')).toBeFalsy()
expect(pane2.itemForUri('b')).not.toBeFalsy()
expect(rootView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
pane1.focus()
expect(rootView.getActivePane()[0]).toBe pane1[0]
rootView.openSingletonSync('b', split: 'right')
pane3 = rootView.getActivePane()
expect(pane3[0]).toBe pane2[0]
expect(pane1.itemForUri('b')).toBeFalsy()
expect(pane2.itemForUri('b')).not.toBeFalsy()
expect(rootView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
it "reuses the file when already open", ->
rootView.openSync('b')
rootView.openSingletonSync('b', split: 'right')
expect(rootView.panes.find('.pane').toArray()).toEqual [pane1[0]]
describe ".open(filePath)", ->
beforeEach ->
spyOn(Pane.prototype, 'focus')

View File

@@ -1,5 +1,5 @@
path = require 'path'
fsUtils = require '../src/fs-utils'
fs = require 'fs-plus'
{_} = require 'atom'
@@ -15,8 +15,8 @@ module.exports =
# Returns nothing.
generateEvilFiles: ->
evilFilesPath = path.join(__dirname, 'fixtures', 'evil-files')
fsUtils.remove(evilFilesPath) if fsUtils.exists(evilFilesPath)
fsUtils.mkdirSync(evilFilesPath)
fs.removeSync(evilFilesPath) if fs.existsSync(evilFilesPath)
fs.mkdirSync(evilFilesPath)
if (@isWindows())
filenames = [
@@ -34,5 +34,4 @@ module.exports =
]
for filename in filenames
fd = fsUtils.writeFileSync(path.join(evilFilesPath, filename), 'evil file!', flag: 'w')
fd = fs.writeFileSync(path.join(evilFilesPath, filename), 'evil file!', flag: 'w')

View File

@@ -59,13 +59,12 @@ beforeEach ->
atom.syntax.clearGrammarOverrides()
atom.syntax.clearProperties()
if specPackageName
spy = spyOn(atom.packages, 'resolvePackagePath').andCallFake (packageName) ->
if packageName is specPackageName
resolvePackagePath(specPackagePath)
else
resolvePackagePath(packageName)
resolvePackagePath = _.bind(spy.originalValue, atom.packages)
spy = spyOn(atom.packages, 'resolvePackagePath').andCallFake (packageName) ->
if specPackageName and packageName is specPackageName
resolvePackagePath(specPackagePath)
else
resolvePackagePath(packageName)
resolvePackagePath = _.bind(spy.originalValue, atom.packages)
# used to reset keymap after each spec
bindingSetsToRestore = _.clone(keymap.bindingSets)
@@ -166,7 +165,7 @@ addCustomMatchers = (spec) ->
toExistOnDisk: (expected) ->
notText = this.isNot and " not" or ""
@message = -> return "Expected path '" + @actual + "'" + notText + " to exist."
fs.exists(@actual)
fs.existsSync(@actual)
window.keyIdentifierForKey = (key) ->
if key.length > 1 # named key

View File

@@ -47,7 +47,7 @@ describe "the `syntax` global", ->
it "doesn't read the file when the file contents are specified", ->
filePath = require.resolve("./fixtures/shebang")
filePathContents = fs.read(filePath)
filePathContents = fs.readFileSync(filePath, 'utf8')
spyOn(fs, 'read').andCallThrough()
expect(syntax.selectGrammar(filePath, filePathContents).name).toBe "Ruby"
expect(fs.read).not.toHaveBeenCalled()

View File

@@ -1,46 +0,0 @@
{Site} = require 'telepath'
describe "TextBuffer replication", ->
[buffer1, buffer2] = []
beforeEach ->
buffer1 = project.buildBufferSync('sample.js')
doc1 = buffer1.getState()
doc2 = doc1.clone(new Site(2))
doc1.connect(doc2)
buffer2 = deserialize(doc2, {project})
waitsFor ->
buffer1.loaded and buffer2.loaded
runs ->
buffer1.insert([0, 0], 'changed\n')
afterEach ->
buffer1.destroy()
buffer2.destroy()
it "replicates the initial path and text", ->
expect(buffer2.getPath()).toBe buffer1.getPath()
expect(buffer2.getText()).toBe buffer1.getText()
it "replicates changes to the text and emits 'change' events on all replicas", ->
buffer1.on 'changed', handler1 = jasmine.createSpy("buffer1 change handler")
buffer2.on 'changed', handler2 = jasmine.createSpy("buffer2 change handler")
buffer1.change([[1, 4], [1, 6]], 'h')
expect(buffer1.lineForRow(1)).toBe 'var hicksort = function () {'
expect(buffer2.lineForRow(1)).toBe 'var hicksort = function () {'
expect(buffer1.isModified()).toBeTruthy()
expect(buffer2.isModified()).toBeTruthy()
expectedEvent =
oldRange: [[1, 4], [1, 6]]
oldText: "qu"
newRange: [[1, 4], [1, 5]]
newText: "h"
expect(handler1).toHaveBeenCalledWith(expectedEvent)
expect(handler2).toHaveBeenCalledWith(expectedEvent)
expect(handler1.callCount).toBe 1
expect(handler2.callCount).toBe 1

View File

@@ -9,7 +9,7 @@ describe 'TextBuffer', ->
beforeEach ->
filePath = require.resolve('./fixtures/sample.js')
fileContents = fs.read(filePath)
fileContents = fs.readFileSync(filePath, 'utf8')
buffer = project.bufferForPathSync(filePath)
afterEach ->
@@ -25,18 +25,18 @@ describe 'TextBuffer', ->
it "loads the contents of that file", ->
filePath = require.resolve './fixtures/sample.txt'
buffer = project.bufferForPathSync(filePath)
expect(buffer.getText()).toBe fs.read(filePath)
expect(buffer.getText()).toBe fs.readFileSync(filePath, 'utf8')
it "does not allow the initial state of the buffer to be undone", ->
filePath = require.resolve './fixtures/sample.txt'
buffer = project.bufferForPathSync(filePath)
buffer.undo()
expect(buffer.getText()).toBe fs.read(filePath)
expect(buffer.getText()).toBe fs.readFileSync(filePath, 'utf8')
describe "when no file exists for the path", ->
it "is modified and is initially empty", ->
filePath = "does-not-exist.txt"
expect(fs.exists(filePath)).toBeFalsy()
expect(fs.existsSync(filePath)).toBeFalsy()
buffer = project.bufferForPathSync(filePath)
expect(buffer.isModified()).toBeTruthy()
expect(buffer.getText()).toBe ''
@@ -52,15 +52,15 @@ describe 'TextBuffer', ->
beforeEach ->
filePath = path.join(__dirname, "fixtures", "atom-manipulate-me")
newPath = "#{filePath}-i-moved"
fs.writeSync(filePath, "")
fs.writeFileSync(filePath, "")
bufferToChange = project.bufferForPathSync(filePath)
eventHandler = jasmine.createSpy('eventHandler')
bufferToChange.on 'path-changed', eventHandler
afterEach ->
bufferToChange.destroy()
fs.remove(filePath) if fs.exists(filePath)
fs.remove(newPath) if fs.exists(newPath)
fs.removeSync(filePath) if fs.existsSync(filePath)
fs.removeSync(newPath) if fs.existsSync(newPath)
it "triggers a `path-changed` event when path is changed", ->
bufferToChange.saveAs(newPath)
@@ -69,8 +69,8 @@ describe 'TextBuffer', ->
it "triggers a `path-changed` event when the file is moved", ->
jasmine.unspy(window, "setTimeout")
fs.remove(newPath) if fs.exists(newPath)
fs.move(filePath, newPath)
fs.removeSync(newPath) if fs.existsSync(newPath)
fs.moveSync(filePath, newPath)
waitsFor "buffer path change", ->
eventHandler.callCount > 0
@@ -84,7 +84,7 @@ describe 'TextBuffer', ->
beforeEach ->
buffer.release()
filePath = temp.openSync('atom').path
fs.writeSync(filePath, "first")
fs.writeFileSync(filePath, "first")
buffer = project.bufferForPathSync(filePath).retain()
afterEach ->
@@ -105,7 +105,7 @@ describe 'TextBuffer', ->
it "changes the memory contents of the buffer to match the new disk contents and triggers a 'changed' event", ->
changeHandler = jasmine.createSpy('changeHandler')
buffer.on 'changed', changeHandler
fs.writeSync(filePath, "second")
fs.writeFileSync(filePath, "second")
expect(changeHandler.callCount).toBe 0
waitsFor "file to trigger change event", ->
@@ -125,7 +125,7 @@ describe 'TextBuffer', ->
buffer.file.on 'contents-changed', fileChangeHandler
buffer.insert([0, 0], "a change")
fs.writeSync(filePath, "second")
fs.writeFileSync(filePath, "second")
expect(fileChangeHandler.callCount).toBe 0
waitsFor "file to trigger 'contents-changed' event", ->
@@ -140,7 +140,7 @@ describe 'TextBuffer', ->
buffer.insert([0, 0], "a second change")
handler = jasmine.createSpy('fileChange')
fs.writeSync(filePath, "a disk change")
fs.writeFileSync(filePath, "a disk change")
buffer.on 'contents-conflicted', handler
expect(handler.callCount).toBe 0
@@ -155,7 +155,7 @@ describe 'TextBuffer', ->
beforeEach ->
filePath = path.join(temp.dir, 'atom-file-to-delete.txt')
fs.writeSync(filePath, 'delete me')
fs.writeFileSync(filePath, 'delete me')
bufferToDelete = project.bufferForPathSync(filePath)
filePath = bufferToDelete.getPath() # symlinks may have been converted
@@ -164,7 +164,7 @@ describe 'TextBuffer', ->
removeHandler = jasmine.createSpy('removeHandler')
bufferToDelete.file.on 'removed', removeHandler
fs.remove(filePath)
fs.removeSync(filePath)
waitsFor "file to be removed", ->
removeHandler.callCount > 0
@@ -177,10 +177,10 @@ describe 'TextBuffer', ->
it "resumes watching of the file when it is re-saved", ->
bufferToDelete.save()
expect(fs.exists(bufferToDelete.getPath())).toBeTruthy()
expect(fs.existsSync(bufferToDelete.getPath())).toBeTruthy()
expect(bufferToDelete.isInConflict()).toBeFalsy()
fs.writeSync(filePath, 'moo')
fs.writeFileSync(filePath, 'moo')
changeHandler = jasmine.createSpy('changeHandler')
bufferToDelete.on 'changed', changeHandler
@@ -213,19 +213,19 @@ describe 'TextBuffer', ->
it "reports the modified status changing to true after the underlying file is deleted", ->
buffer.release()
filePath = path.join(temp.dir, 'atom-tmp-file')
fs.writeSync(filePath, 'delete me')
fs.writeFileSync(filePath, 'delete me')
buffer = project.bufferForPathSync(filePath)
modifiedHandler = jasmine.createSpy("modifiedHandler")
buffer.on 'modified-status-changed', modifiedHandler
fs.remove(filePath)
fs.removeSync(filePath)
waitsFor "modified status to change", -> modifiedHandler.callCount
runs -> expect(buffer.isModified()).toBe true
it "reports the modified status changing to false after a modified buffer is saved", ->
filePath = path.join(temp.dir, 'atom-tmp-file')
fs.writeSync(filePath, '')
fs.writeFileSync(filePath, '')
buffer.release()
buffer = project.bufferForPathSync(filePath)
modifiedHandler = jasmine.createSpy("modifiedHandler")
@@ -249,7 +249,7 @@ describe 'TextBuffer', ->
it "reports the modified status changing to false after a modified buffer is reloaded", ->
filePath = path.join(temp.dir, 'atom-tmp-file')
fs.writeSync(filePath, '')
fs.writeFileSync(filePath, '')
buffer.release()
buffer = project.bufferForPathSync(filePath)
modifiedHandler = jasmine.createSpy("modifiedHandler")
@@ -272,8 +272,8 @@ describe 'TextBuffer', ->
it "reports the modified status changing to false after a buffer to a non-existent file is saved", ->
filePath = path.join(temp.dir, 'atom-tmp-file')
fs.remove(filePath) if fs.exists(filePath)
expect(fs.exists(filePath)).toBeFalsy()
fs.removeSync(filePath) if fs.existsSync(filePath)
expect(fs.existsSync(filePath)).toBeFalsy()
buffer.release()
buffer = project.bufferForPathSync(filePath)
modifiedHandler = jasmine.createSpy("modifiedHandler")
@@ -285,7 +285,7 @@ describe 'TextBuffer', ->
modifiedHandler.reset()
buffer.save()
expect(fs.exists(filePath)).toBeTruthy()
expect(fs.existsSync(filePath)).toBeTruthy()
expect(modifiedHandler).toHaveBeenCalledWith(false)
expect(buffer.isModified()).toBe false
@@ -465,16 +465,16 @@ describe 'TextBuffer', ->
beforeEach ->
filePath = path.join(temp.dir, 'temp.txt')
fs.writeSync(filePath, "")
fs.writeFileSync(filePath, "")
saveBuffer = project.bufferForPathSync(filePath)
saveBuffer.setText("blah")
it "saves the contents of the buffer to the path", ->
saveBuffer.setText 'Buffer contents!'
saveBuffer.save()
expect(fs.read(filePath)).toEqual 'Buffer contents!'
expect(fs.readFileSync(filePath, 'utf8')).toEqual 'Buffer contents!'
it "fires will-be-saved and saved events around the call to fs.writeSync", ->
it "fires will-be-saved and saved events around the call to fs.writeFileSync", ->
events = []
beforeSave1 = -> events.push('beforeSave1')
beforeSave2 = -> events.push('beforeSave2')
@@ -483,12 +483,12 @@ describe 'TextBuffer', ->
saveBuffer.on 'will-be-saved', beforeSave1
saveBuffer.on 'will-be-saved', beforeSave2
spyOn(fs, 'writeSync').andCallFake -> events.push 'fs.writeSync'
spyOn(fs, 'writeFileSync').andCallFake -> events.push 'fs.writeFileSync'
saveBuffer.on 'saved', afterSave1
saveBuffer.on 'saved', afterSave2
saveBuffer.save()
expect(events).toEqual ['beforeSave1', 'beforeSave2', 'fs.writeSync', 'afterSave1', 'afterSave2']
expect(events).toEqual ['beforeSave1', 'beforeSave2', 'fs.writeFileSync', 'afterSave1', 'afterSave2']
it "fires will-reload and reloaded events when reloaded", ->
events = []
@@ -522,7 +522,7 @@ describe 'TextBuffer', ->
it "saves the contents of the buffer to the path", ->
filePath = path.join(temp.dir, 'temp.txt')
fs.remove filePath if fs.exists(filePath)
fs.removeSync filePath if fs.existsSync(filePath)
saveAsBuffer = project.bufferForPathSync(null).retain()
eventHandler = jasmine.createSpy('eventHandler')
@@ -530,14 +530,14 @@ describe 'TextBuffer', ->
saveAsBuffer.setText 'Buffer contents!'
saveAsBuffer.saveAs(filePath)
expect(fs.read(filePath)).toEqual 'Buffer contents!'
expect(fs.readFileSync(filePath, 'utf8')).toEqual 'Buffer contents!'
expect(eventHandler).toHaveBeenCalledWith(saveAsBuffer)
it "stops listening to events on previous path and begins listening to events on new path", ->
originalPath = path.join(temp.dir, 'original.txt')
newPath = path.join(temp.dir, 'new.txt')
fs.writeSync(originalPath, "")
fs.writeFileSync(originalPath, "")
saveAsBuffer = project.bufferForPathSync(originalPath).retain()
changeHandler = jasmine.createSpy('changeHandler')
@@ -545,11 +545,11 @@ describe 'TextBuffer', ->
saveAsBuffer.saveAs(newPath)
expect(changeHandler).not.toHaveBeenCalled()
fs.writeSync(originalPath, "should not trigger buffer event")
fs.writeFileSync(originalPath, "should not trigger buffer event")
waits 20
runs ->
expect(changeHandler).not.toHaveBeenCalled()
fs.writeSync(newPath, "should trigger buffer event")
fs.writeFileSync(newPath, "should trigger buffer event")
waitsFor ->
changeHandler.callCount > 0
@@ -560,22 +560,22 @@ describe 'TextBuffer', ->
beforeEach ->
filePath = path.join(__dirname, "fixtures", "atom-manipulate-me")
newPath = "#{filePath}-i-moved"
fs.writeSync(filePath, "")
fs.writeFileSync(filePath, "")
bufferToChange = project.bufferForPathSync(filePath)
eventHandler = jasmine.createSpy('eventHandler')
bufferToChange.on 'path-changed', eventHandler
afterEach ->
bufferToChange.destroy()
fs.remove(filePath) if fs.exists(filePath)
fs.remove(newPath) if fs.exists(newPath)
fs.removeSync(filePath) if fs.existsSync(filePath)
fs.removeSync(newPath) if fs.existsSync(newPath)
it "updates when the text buffer's file is moved", ->
expect(bufferToChange.getRelativePath()).toBe('atom-manipulate-me')
jasmine.unspy(window, "setTimeout")
eventHandler.reset()
fs.move(filePath, newPath)
fs.moveSync(filePath, newPath)
waitsFor "buffer path change", ->
eventHandler.callCount > 0
@@ -936,7 +936,7 @@ describe 'TextBuffer', ->
expect(buffer.isModified()).toBeFalsy()
state = buffer.serialize()
state.get('text').insert([0, 0], 'simulate divergence of on-disk contents from serialized contents')
state.get('text').insertTextAtPoint([0, 0], 'simulate divergence of on-disk contents from serialized contents')
buffer2 = deserialize(state, {project})
@@ -954,13 +954,13 @@ describe 'TextBuffer', ->
buffer.release()
filePath = temp.openSync('atom').path
fs.writeSync(filePath, "words")
fs.writeFileSync(filePath, "words")
{buffer} = project.openSync(filePath)
buffer.setText("BUFFER CHANGE")
state = buffer.serialize()
expect(state.getObject('text')).toBe 'BUFFER CHANGE'
fs.writeSync(filePath, "DISK CHANGE")
fs.writeFileSync(filePath, "DISK CHANGE")
buffer2 = deserialize(state, {project})
@@ -1006,27 +1006,3 @@ describe 'TextBuffer', ->
buffer2 = deserialize(state)
expect(buffer2.getPath()).toBeUndefined()
expect(buffer2.getText()).toBe("abc")
describe "when the buffer has remote markers", ->
[buffer2, buffer3] = []
afterEach ->
buffer2.destroy()
buffer3.destroy()
it "does not include them in the serialized state", ->
doc1 = buffer.getState()
doc2 = doc1.clone(new Site(2))
doc1.connect(doc2)
buffer2 = deserialize(doc2, {project})
buffer.markPosition [1, 0]
buffer2.markPosition [2, 0]
expect(buffer.getMarkerCount()).toBe 2
expect(buffer.getMarkers()[0].isRemote()).toBe false
expect(buffer.getMarkers()[1].isRemote()).toBe true
buffer3 = deserialize(buffer.serialize(), {project})
expect(buffer3.getMarkerCount()).toBe 1
expect(buffer3.getMarkers()[0].isRemote()).toBe false
expect(buffer3.getMarkers()[0].getRange()).toEqual [[1, 0], [1, 0]]

View File

@@ -86,7 +86,7 @@ describe "ThemeManager", ->
element = $('head style[id*="css.css"]')
expect(element.attr('id')).toBe cssPath
expect(element.text()).toBe fs.read(cssPath)
expect(element.text()).toBe fs.readFileSync(cssPath, 'utf8')
# doesn't append twice
themeManager.requireStylesheet(cssPath)

View File

@@ -95,7 +95,8 @@ describe "Window", ->
it "unsubscribes from all buffers", ->
rootView.openSync('sample.js')
buffer = rootView.getActivePaneItem().buffer
rootView.getActivePane().splitRight()
pane = rootView.getActivePane()
pane.splitRight(pane.copyActiveItem())
expect(window.rootView.find('.editor').length).toBe 2
window.unloadEditorWindow()

View File

@@ -1,6 +1,6 @@
TextMateGrammar = require './text-mate-grammar'
Package = require './package'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
path = require 'path'
_ = require 'underscore-plus'
{$} = require './space-pen-extensions'
@@ -118,16 +118,16 @@ class AtomPackage extends Package
getKeymapPaths: ->
keymapsDirPath = path.join(@path, 'keymaps')
if @metadata.keymaps
@metadata.keymaps.map (name) -> fsUtils.resolve(keymapsDirPath, name, ['json', 'cson', ''])
@metadata.keymaps.map (name) -> fs.resolve(keymapsDirPath, name, ['json', 'cson', ''])
else
fsUtils.listSync(keymapsDirPath, ['cson', 'json'])
fs.listSync(keymapsDirPath, ['cson', 'json'])
getMenuPaths: ->
menusDirPath = path.join(@path, 'menus')
if @metadata.menus
@metadata.menus.map (name) -> fsUtils.resolve(menusDirPath, name, ['json', 'cson', ''])
@metadata.menus.map (name) -> fs.resolve(menusDirPath, name, ['json', 'cson', ''])
else
fsUtils.listSync(menusDirPath, ['cson', 'json'])
fs.listSync(menusDirPath, ['cson', 'json'])
loadStylesheets: ->
@stylesheets = @getStylesheetPaths().map (stylesheetPath) ->
@@ -140,25 +140,25 @@ class AtomPackage extends Package
stylesheetDirPath = @getStylesheetsPath()
if @metadata.stylesheetMain
[fsUtils.resolve(@path, @metadata.stylesheetMain)]
[fs.resolve(@path, @metadata.stylesheetMain)]
else if @metadata.stylesheets
@metadata.stylesheets.map (name) -> fsUtils.resolve(stylesheetDirPath, name, ['css', 'less', ''])
else if indexStylesheet = fsUtils.resolve(@path, 'index', ['css', 'less'])
@metadata.stylesheets.map (name) -> fs.resolve(stylesheetDirPath, name, ['css', 'less', ''])
else if indexStylesheet = fs.resolve(@path, 'index', ['css', 'less'])
[indexStylesheet]
else
fsUtils.listSync(stylesheetDirPath, ['css', 'less'])
fs.listSync(stylesheetDirPath, ['css', 'less'])
loadGrammars: ->
@grammars = []
grammarsDirPath = path.join(@path, 'grammars')
for grammarPath in fsUtils.listSync(grammarsDirPath, ['.json', '.cson'])
for grammarPath in fs.listSync(grammarsDirPath, ['.json', '.cson'])
@grammars.push(TextMateGrammar.loadSync(grammarPath))
loadScopedProperties: ->
@scopedProperties = []
scopedPropertiessDirPath = path.join(@path, 'scoped-properties')
for scopedPropertiesPath in fsUtils.listSync(scopedPropertiessDirPath, ['.json', '.cson'])
for selector, properties of fsUtils.readObjectSync(scopedPropertiesPath)
for scopedPropertiesPath in fs.listSync(scopedPropertiessDirPath, ['.json', '.cson'])
for selector, properties of fs.readObjectSync(scopedPropertiesPath)
@scopedProperties.push([scopedPropertiesPath, selector, properties])
serialize: ->
@@ -198,7 +198,7 @@ class AtomPackage extends Package
requireMainModule: ->
return @mainModule if @mainModule?
mainModulePath = @getMainModulePath()
@mainModule = require(mainModulePath) if fsUtils.isFileSync(mainModulePath)
@mainModule = require(mainModulePath) if fs.isFileSync(mainModulePath)
getMainModulePath: ->
return @mainModulePath if @resolvedMainModulePath
@@ -208,7 +208,7 @@ class AtomPackage extends Package
path.join(@path, @metadata.main)
else
path.join(@path, 'index')
@mainModulePath = fsUtils.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...])
@mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...])
registerDeferredDeserializers: ->
for deserializerName in @metadata.deferredDeserializers ? []

View File

@@ -4,7 +4,17 @@ Emitter::one = (args...) -> @once(args...)
Emitter::trigger = (args...) -> @emit(args...)
Emitter::subscriptionCount = (args...) -> @getSubscriptionCount(args...)
fsUtils = require './fs-utils'
#TODO remove once all packages have been updated
fs = require 'fs-plus'
fs.exists = fs.existsSync
fs.makeTree = fs.makeTreeSync
fs.move = fs.moveSync
fs.read = (filePath) -> fs.readFileSync(filePath, 'utf8')
fs.remove = fs.removeSync
fs.write = fs.writeFile
fs.writeSync = fs.writeFileSync
fs = require 'fs-plus'
{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
Package = require './package'
@@ -19,6 +29,7 @@ app = remote.require 'app'
{Document} = require 'telepath'
DeserializerManager = require './deserializer-manager'
{Subscriber} = require 'emissary'
SiteShim = require './site-shim'
# Public: Atom global for dealing with packages, themes, menus, and the window.
#
@@ -242,7 +253,7 @@ class Atom
# Public: Get the directory path to Atom's configuration area.
getConfigDirPath: ->
@configDirPath ?= fsUtils.absolute('~/.atom')
@configDirPath ?= fs.absolute('~/.atom')
getWindowStatePath: ->
switch @windowMode
@@ -266,9 +277,9 @@ class Atom
loadWindowState: ->
if windowStatePath = @getWindowStatePath()
if fsUtils.exists(windowStatePath)
if fs.existsSync(windowStatePath)
try
documentStateJson = fsUtils.read(windowStatePath)
documentStateJson = fs.readFileSync(windowStatePath, 'utf8')
catch error
console.warn "Error reading window state: #{windowStatePath}", error.stack, error
else
@@ -279,15 +290,19 @@ class Atom
catch error
console.warn "Error parsing window state: #{windowStatePath}", error.stack, error
doc = Document.deserialize(state: documentState) if documentState?
doc = Document.deserialize(documentState) if documentState?
doc ?= Document.create()
@site = doc.site # TODO: Remove this when everything is using telepath models
# TODO: Remove this when everything is using telepath models
if @site?
@site.setRootDocument(doc)
else
@site = new SiteShim(doc)
doc
saveWindowState: ->
windowState = @getWindowState()
if windowStatePath = @getWindowStatePath()
windowState.saveSync(path: windowStatePath)
windowState.saveSync(windowStatePath)
else
@getCurrentWindow().loadSettings.windowState = JSON.stringify(windowState.serialize())
@@ -311,7 +326,7 @@ class Atom
requireUserInitScript: ->
userInitScriptPath = path.join(@config.configDirPath, "user.coffee")
try
require userInitScriptPath if fsUtils.isFileSync(userInitScriptPath)
require userInitScriptPath if fs.isFileSync(userInitScriptPath)
catch error
console.error "Failed to load `#{userInitScriptPath}`", error.stack, error

View File

@@ -1,7 +1,6 @@
{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
{specificity} = require 'clear-cut'
PEG = require 'pegjs'
@@ -18,7 +17,8 @@ class BindingSet
name: null
constructor: (selector, commandsByKeystrokes, @index, @name) ->
BindingSet.parser ?= PEG.buildParser(fsUtils.read(require.resolve './keystroke-pattern.pegjs'))
keystrokePattern = fs.readFileSync(require.resolve('./keystroke-pattern.pegjs'), 'utf8')
BindingSet.parser ?= PEG.buildParser(keystrokePattern)
@specificity = specificity(selector)
@selector = selector.replace(/!important/g, '')
@commandsByKeystrokes = @normalizeCommandsByKeystrokes(commandsByKeystrokes)

View File

@@ -146,6 +146,9 @@ class AtomApplication
else
@openPath(pathToOpen: "atom://config")
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.
@@ -288,9 +291,9 @@ class AtomApplication
openUrl: ({urlToOpen, devMode}) ->
unless @packages?
PackageManager = require '../package-manager'
fsUtils = require '../fs-utils'
fs = require 'fs-plus'
@packages = new PackageManager
configDirPath: fsUtils.absolute('~/.atom')
configDirPath: fs.absolute('~/.atom')
devMode: devMode
resourcePath: @resourcePath

View File

@@ -1,9 +1,8 @@
path = require 'path'
fs = require 'fs'
_ = require 'underscore-plus'
async = require 'async'
fs = require 'fs-plus'
mkdirp = require 'mkdirp'
fsUtils = require './fs-utils'
symlinkCommand = (sourcePath, destinationPath, callback) ->
mkdirp path.dirname(destinationPath), (error) ->
@@ -26,7 +25,7 @@ unlinkCommand = (destinationPath, callback) ->
module.exports =
findInstallDirectory: (callback) ->
directories = ['/opt/boxen', '/opt/github', '/usr/local']
async.detect(directories, fsUtils.isDirectory, callback)
async.detect(directories, fs.isDirectory, callback)
install: (commandPath, commandName, callback) ->
if not commandName? or _.isFunction(commandName)

View File

@@ -1,8 +1,7 @@
fsUtils = require './fs-utils'
_ = require 'underscore-plus'
fs = require 'fs-plus'
{Emitter} = require 'emissary'
CSON = require 'season'
fs = require 'fs'
path = require 'path'
async = require 'async'
pathWatcher = require 'pathwatcher'
@@ -52,25 +51,25 @@ class Config
core: _.clone(require('./root-view').configDefaults)
editor: _.clone(require('./editor').configDefaults)
@settings = {}
@configFilePath = fsUtils.resolve(@configDirPath, 'config', ['json', 'cson'])
@configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= path.join(@configDirPath, 'config.cson')
# Private:
initializeConfigDirectory: (done) ->
return if fsUtils.exists(@configDirPath)
return if fs.existsSync(@configDirPath)
fsUtils.makeTree(@configDirPath)
fs.makeTreeSync(@configDirPath)
queue = async.queue ({sourcePath, destinationPath}, callback) =>
fsUtils.copy(sourcePath, destinationPath, callback)
fs.copy(sourcePath, destinationPath, callback)
queue.drain = done
templateConfigDirPath = fsUtils.resolve(@resourcePath, 'dot-atom')
templateConfigDirPath = fs.resolve(@resourcePath, 'dot-atom')
onConfigDirFile = (sourcePath) =>
relativePath = sourcePath.substring(templateConfigDirPath.length + 1)
destinationPath = path.join(@configDirPath, relativePath)
queue.push({sourcePath, destinationPath})
fsUtils.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true)
fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true)
# Private:
load: ->
@@ -80,8 +79,8 @@ class Config
# Private:
loadUserConfig: ->
if !fsUtils.exists(@configFilePath)
fsUtils.makeTree(path.dirname(@configFilePath))
unless fs.existsSync(@configFilePath)
fs.makeTreeSync(path.dirname(@configFilePath))
CSON.writeFileSync(@configFilePath, {})
try

View File

@@ -1,5 +1,5 @@
path = require 'path'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
pathWatcher = require 'pathwatcher'
File = require './file'
{Emitter} = require 'emissary'
@@ -44,7 +44,7 @@ class Directory
getRealPath: ->
unless @realPath?
try
@realPath = fsUtils.realpathSync(@path)
@realPath = fs.realpathSync(@path)
catch e
@realPath = @path
@realPath
@@ -84,11 +84,11 @@ class Directory
getEntries: ->
directories = []
files = []
for entryPath in fsUtils.listSync(@path)
for entryPath in fs.listSync(@path)
try
stat = fsUtils.lstatSync(entryPath)
stat = fs.lstatSync(entryPath)
symlink = stat.isSymbolicLink()
stat = fsUtils.statSync(entryPath) if symlink
stat = fs.statSync(entryPath) if symlink
catch e
continue
if stat.isDirectory()

View File

@@ -50,11 +50,10 @@ class DisplayBuffer
@subscribe @buffer, 'markers-updated', @handleBufferMarkersUpdated
@subscribe @buffer, 'marker-created', @handleBufferMarkerCreated
@subscribe @state, 'changed', ({key, newValue}) =>
switch key
when 'softWrap'
@emit 'soft-wrap-changed', newValue
@updateWrappedScreenLines()
@subscribe @state, 'changed', ({newValues}) =>
if newValues.softWrap?
@emit 'soft-wrap-changed', newValues.softWrap
@updateWrappedScreenLines()
@observeConfig 'editor.preferredLineLength', callNow: false, =>
@updateWrappedScreenLines() if @getSoftWrap() and config.get('editor.softWrapAtPreferredLineLength')

View File

@@ -1,5 +1,4 @@
_ = require 'underscore-plus'
fsUtils = require './fs-utils'
path = require 'path'
telepath = require 'telepath'
guid = require 'guid'
@@ -100,12 +99,13 @@ class EditSession
@addCursorAtBufferPosition(position)
@languageMode = new LanguageMode(this, @buffer.getExtension())
@subscribe @state, 'changed', ({key, newValue}) =>
switch key
when 'scrollTop'
@emit 'scroll-top-changed', newValue
when 'scrollLeft'
@emit 'scroll-left-changed', newValue
@subscribe @state, 'changed', ({newValues}) =>
for key, newValue of newValues
switch key
when 'scrollTop'
@emit 'scroll-top-changed', newValue
when 'scrollLeft'
@emit 'scroll-left-changed', newValue
project.addEditSession(this) if registerEditSession
@@ -138,8 +138,8 @@ class EditSession
return if @destroyed
@destroyed = true
@unsubscribe()
@buffer.release()
selection.destroy() for selection in @getSelections()
@buffer.release()
@displayBuffer.destroy()
@languageMode.destroy()
project?.removeEditSession(this)
@@ -1187,12 +1187,12 @@ class EditSession
@expandSelectionsBackward (selection) => selection.selectLeft()
# Public: Selects all the text one position above all local cursors.
selectUp: ->
@expandSelectionsBackward (selection) => selection.selectUp()
selectUp: (rowCount) ->
@expandSelectionsBackward (selection) => selection.selectUp(rowCount)
# Public: Selects all the text one position below all local cursors.
selectDown: ->
@expandSelectionsForward (selection) => selection.selectDown()
selectDown: (rowCount) ->
@expandSelectionsForward (selection) => selection.selectDown(rowCount)
# Public: Selects all the text from all local cursors to the top of the
# buffer.
@@ -1432,7 +1432,7 @@ class EditSession
# Private:
getSelectionMarkerAttributes: ->
type: 'selection', editSessionId: @id, invalidation: 'never'
type: 'selection', editSessionId: @id, invalidate: 'never'
# Private:
getDebugSnapshot: ->

View File

@@ -5,7 +5,7 @@ Gutter = require './gutter'
EditSession = require './edit-session'
CursorView = require './cursor-view'
SelectionView = require './selection-view'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
_ = require 'underscore-plus'
MeasureRange = document.createRange()
@@ -1014,17 +1014,21 @@ class Editor extends View
@updateLayerDimensions()
@requestDisplayUpdate()
splitLeft: (items...) ->
@getPane()?.splitLeft(items...).activeView
splitLeft: ->
pane = @getPane()
pane?.splitLeft(pane?.copyActiveItem()).activeView
splitRight: (items...) ->
@getPane()?.splitRight(items...).activeView
splitRight: ->
pane = @getPane()
pane?.splitRight(pane?.copyActiveItem()).activeView
splitUp: (items...) ->
@getPane()?.splitUp(items...).activeView
splitUp: ->
pane = @getPane()
pane?.splitUp(pane?.copyActiveItem()).activeView
splitDown: (items...) ->
@getPane()?.splitDown(items...).activeView
splitDown: ->
pane = @getPane()
pane?.splitDown(pane?.copyActiveItem()).activeView
# Retrieve's the `Editor`'s pane.
#
@@ -1648,12 +1652,13 @@ class Editor extends View
screenPositionFromMouseEvent: (e) ->
{ pageX, pageY } = e
offset = @scrollView.offset()
editorRelativeTop = pageY - @scrollView.offset().top + @scrollTop()
editorRelativeTop = pageY - offset.top + @scrollTop()
row = Math.floor(editorRelativeTop / @lineHeight)
column = 0
if lineElement = @lineElementForScreenRow(row)[0]
if pageX > offset.left and lineElement = @lineElementForScreenRow(row)[0]
range = document.createRange()
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, acceptNode: -> NodeFilter.FILTER_ACCEPT)
while node = iterator.nextNode()
@@ -1819,7 +1824,7 @@ class Editor extends View
saveDebugSnapshot: ->
atom.showSaveDialog (path) =>
fsUtils.writeSync(path, @getDebugSnapshot()) if path
fs.writeFileSync(path, @getDebugSnapshot()) if path
getDebugSnapshot: ->
[

View File

@@ -4,7 +4,7 @@ pathWatcher = require 'pathwatcher'
Q = require 'q'
{Emitter} = require 'emissary'
_ = require 'underscore-plus'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
# Public: Represents an individual file.
#
@@ -24,7 +24,7 @@ class File
# * symlink:
# A Boolean indicating if the path is a symlink (default: false)
constructor: (@path, @symlink=false) ->
throw new Error("#{@path} is a directory") if fsUtils.isDirectorySync(@path)
throw new Error("#{@path} is a directory") if fs.isDirectorySync(@path)
@handleEventSubscriptions()
@@ -57,7 +57,7 @@ class File
write: (text) ->
previouslyExisted = @exists()
@cachedContents = text
fsUtils.writeSync(@getPath(), text)
fs.writeFileSync(@getPath(), text)
@subscribeToNativeChangeEvents() if not previouslyExisted and @subscriptionCount() > 0
# Private: Deprecated
@@ -65,7 +65,7 @@ class File
if not @exists()
@cachedContents = null
else if not @cachedContents? or flushCache
@cachedContents = fsUtils.read(@getPath())
@cachedContents = fs.readFileSync(@getPath(), 'utf8')
else
@cachedContents
@@ -83,14 +83,14 @@ class File
if not @exists()
promise = Q(null)
else if not @cachedContents? or flushCache
if fsUtils.statSyncNoException(@getPath()).size >= 1048576 # 1MB
if fs.getSizeSync(@getPath()) >= 1048576 # 1MB
throw new Error("Atom can only handle files < 1MB, for now.")
deferred = Q.defer()
promise = deferred.promise
content = []
bytesRead = 0
readStream = fsUtils.createReadStream @getPath(), encoding: 'utf8'
readStream = fs.createReadStream @getPath(), encoding: 'utf8'
readStream.on 'data', (chunk) ->
content.push(chunk)
bytesRead += chunk.length
@@ -110,7 +110,7 @@ class File
# Public: Returns whether the file exists.
exists: ->
fsUtils.exists(@getPath())
fs.existsSync(@getPath())
setDigest: (contents) ->
@digest = crypto.createHash('sha1').update(contents ? '').digest('hex')

View File

@@ -1,431 +0,0 @@
_ = require 'underscore-plus'
fs = require 'fs'
mkdirp = require 'mkdirp'
Module = require 'module'
async = require 'async'
rimraf = require 'rimraf'
path = require 'path'
# Public: Useful extensions to node's built-in fs module
#
# Important, this extends Node's builtin in ['fs' module][fs], which means that you
# can do anything that you can do with Node's 'fs' module plus a few extra
# functions that we've found to be helpful.
#
# [fs]: http://nodejs.org/api/fs.html
fsExtensions =
# Public: Make the given path absolute by resolving it against the current
# working directory.
#
# * relativePath:
# The String containing the relative path. If the path is prefixed with
# '~', it will be expanded to the current user's home directory.
#
# Returns the absolute path or the relative path if it's unable to determine
# it's realpath.
absolute: (relativePath) ->
return null unless relativePath?
homeDir = process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']
if relativePath is '~'
relativePath = homeDir
else if relativePath.indexOf('~/') is 0
relativePath = "#{homeDir}#{relativePath.substring(1)}"
try
fs.realpathSync(relativePath)
catch e
relativePath
# Public: Is the given path absolute?
#
# * pathToCheck:
# The relative or absolute path to check.
#
# Returns true if the path is absolute, false otherwise.
isAbsolute: (pathToCheck='') ->
if process.platform is 'win32'
pathToCheck[1] is ':' # C:\ style
else
pathToCheck[0] is '/' # /usr style
# Public: Returns true if a file or folder at the specified path exists.
exists: (pathToCheck) ->
# TODO: rename to existsSync
pathToCheck? and fs.statSyncNoException(pathToCheck) isnt false
# Public: Returns true if the given path exists and is a directory.
isDirectorySync: (directoryPath) ->
return false unless directoryPath?.length > 0
if stat = fs.statSyncNoException(directoryPath)
stat.isDirectory()
else
false
# Public: Asynchronously checks that the given path exists and is a directory.
isDirectory: (directoryPath, done) ->
return done(false) unless directoryPath?.length > 0
fs.exists directoryPath, (exists) ->
if exists
fs.stat directoryPath, (error, stat) ->
if error?
done(false)
else
done(stat.isDirectory())
else
done(false)
# Public: Returns true if the specified path exists and is a file.
isFileSync: (filePath) ->
return false unless filePath?.length > 0
if stat = fs.statSyncNoException(filePath)
stat.isFile()
else
false
# Public: Returns true if the specified path is executable.
isExecutableSync: (pathToCheck) ->
return false unless pathToCheck?.length > 0
if stat = fs.statSyncNoException(pathToCheck)
(stat.mode & 0o777 & 1) isnt 0
else
false
# Public: Returns an Array with the paths of the files and directories
# contained within the directory path. It is not recursive.
#
# * rootPath:
# The absolute path to the directory to list.
# * extensions:
# An array of extensions to filter the results by. If none are given, none
# are filtered (optional).
listSync: (rootPath, extensions) ->
return [] unless @isDirectorySync(rootPath)
paths = fs.readdirSync(rootPath)
paths = @filterExtensions(paths, extensions) if extensions
paths = paths.map (childPath) -> path.join(rootPath, childPath)
paths
# Public: Asynchronously lists the files and directories in the given path.
# The listing is not recursive.
#
# * rootPath:
# The absolute path to the directory to list.
# * extensions:
# An array of extensions to filter the results by. If none are given, none
# are filtered (optional)
# * callback:
# The function to call
list: (rootPath, rest...) ->
extensions = rest.shift() if rest.length > 1
done = rest.shift()
fs.readdir rootPath, (error, paths) =>
if error?
done(error)
else
paths = @filterExtensions(paths, extensions) if extensions
paths = paths.map (childPath) -> path.join(rootPath, childPath)
done(null, paths)
# Private: Returns only the paths which end with one of the given extensions.
filterExtensions: (paths, extensions) ->
extensions = extensions.map (ext) ->
if ext is ''
ext
else
'.' + ext.replace(/^\./, '')
paths.filter (pathToCheck) -> _.include(extensions, path.extname(pathToCheck))
# Deprecated: No one currently uses this.
listTreeSync: (rootPath) ->
paths = []
onPath = (childPath) ->
paths.push(childPath)
true
@traverseTreeSync(rootPath, onPath, onPath)
paths
# Public: Moves the file or directory to the target synchronously.
move: (source, target) ->
# TODO: This should be renamed to moveSync
fs.renameSync(source, target)
# Public: Removes the file or directory at the given path synchronously.
remove: (pathToRemove) ->
# TODO: This should be renamed to removeSync
rimraf.sync(pathToRemove)
# Public: Open, read, and close a file, returning the file's contents
# synchronously.
read: (filePath) ->
# TODO: This should be renamed to readSync
fs.readFileSync(filePath, 'utf8')
# Public: Open, write, flush, and close a file, writing the given content
# synchronously.
#
# It also creates the necessary parent directories.
writeSync: (filePath, content) ->
mkdirp.sync(path.dirname(filePath))
fs.writeFileSync(filePath, content)
# Public: Open, write, flush, and close a file, writing the given content
# asynchronously.
#
# It also creates the necessary parent directories.
write: (filePath, content, callback) ->
mkdirp path.dirname(filePath), (error) ->
if error?
callback?(error)
else
fs.writeFile(filePath, content, callback)
# Public: Copies the given path asynchronously.
copy: (sourcePath, destinationPath, done) ->
mkdirp path.dirname(destinationPath), (error) ->
if error?
done?(error)
return
sourceStream = fs.createReadStream(sourcePath)
sourceStream.on 'error', (error) ->
done?(error)
done = null
destinationStream = fs.createWriteStream(destinationPath)
destinationStream.on 'error', (error) ->
done?(error)
done = null
destinationStream.on 'close', ->
done?()
done = null
sourceStream.pipe(destinationStream)
# Public: Create a directory at the specified path including any missing
# parent directories synchronously.
makeTree: (directoryPath) ->
# TODO: rename to makeTreeSync
mkdirp.sync(directoryPath) if directoryPath and not @exists(directoryPath)
# Public: Recursively walk the given path and execute the given functions
# synchronously.
#
# * rootPath:
# The String containing the directory to recurse into.
# * onFile:
# The function to execute on each file, receives a single argument the
# absolute path.
# * onDirectory:
# The function to execute on each directory, receives a single argument the
# absolute path (defaults to onFile)
traverseTreeSync: (rootPath, onFile, onDirectory=onFile) ->
return unless @isDirectorySync(rootPath)
traverse = (directoryPath, onFile, onDirectory) ->
for file in fs.readdirSync(directoryPath)
childPath = path.join(directoryPath, file)
stats = fs.lstatSync(childPath)
if stats.isSymbolicLink()
if linkStats = fs.statSyncNoException(childPath)
stats = linkStats
if stats.isDirectory()
traverse(childPath, onFile, onDirectory) if onDirectory(childPath)
else if stats.isFile()
onFile(childPath)
traverse(rootPath, onFile, onDirectory)
# Public: Recursively walk the given path and execute the given functions
# asynchronously.
#
# * rootPath:
# The String containing the directory to recurse into.
# * onFile:
# The function to execute on each file, receives a single argument the
# absolute path.
# * onDirectory:
# The function to execute on each directory, receives a single argument the
# absolute path (defaults to onFile)
traverseTree: (rootPath, onFile, onDirectory, onDone) ->
fs.readdir rootPath, (error, files) ->
if error
onDone?()
else
queue = async.queue (childPath, callback) ->
fs.stat childPath, (error, stats) ->
if error
callback(error)
else if stats.isFile()
onFile(childPath)
callback()
else if stats.isDirectory()
if onDirectory(childPath)
fs.readdir childPath, (error, files) ->
if error
callback(error)
else
for file in files
queue.unshift(path.join(childPath, file))
callback()
else
callback()
queue.concurrency = 1
queue.drain = onDone
queue.push(path.join(rootPath, file)) for file in files
# Public: Hashes the contents of the given file.
#
# * pathToDigest:
# The String containing the absolute path.
#
# Returns a String containing the MD5 hexadecimal hash.
md5ForPath: (pathToDigest) ->
contents = fs.readFileSync(pathToDigest)
require('crypto').createHash('md5').update(contents).digest('hex')
# Public: Finds a relative path among the given array of paths.
#
# * loadPaths:
# An Array of absolute and relative paths to search.
# * pathToResolve:
# The string containing the path to resolve.
# * extensions:
# An array of extensions to pass to {resolveExtensions} in which case
# pathToResolve should not contain an extension (optional).
#
# Returns the absolute path of the file to be resolved if it's found and
# undefined otherwise.
resolve: (args...) ->
extensions = args.pop() if _.isArray(_.last(args))
pathToResolve = args.pop()
loadPaths = args
if @isAbsolute(pathToResolve)
if extensions and resolvedPath = @resolveExtension(pathToResolve, extensions)
return resolvedPath
else
return pathToResolve if @exists(pathToResolve)
for loadPath in loadPaths
candidatePath = path.join(loadPath, pathToResolve)
if extensions
if resolvedPath = @resolveExtension(candidatePath, extensions)
return resolvedPath
else
return @absolute(candidatePath) if @exists(candidatePath)
undefined
# Deprecated:
resolveOnLoadPath: (args...) ->
loadPaths = Module.globalPaths.concat(module.paths)
@resolve(loadPaths..., args...)
# Public: Finds the first file in the given path which matches the extension
# in the order given.
#
# * pathToResolve:
# The String containing relative or absolute path of the file in question
# without the extension or '.'.
# * extensions:
# The ordered Array of extensions to try.
#
# Returns the absolute path of the file if it exists with any of the given
# extensions, otherwise it's undefined.
resolveExtension: (pathToResolve, extensions) ->
for extension in extensions
if extension == ""
return @absolute(pathToResolve) if @exists(pathToResolve)
else
pathWithExtension = pathToResolve + "." + extension.replace(/^\./, "")
return @absolute(pathWithExtension) if @exists(pathWithExtension)
undefined
# Public: Returns true for extensions associated with compressed files.
isCompressedExtension: (ext) ->
_.indexOf([
'.gz'
'.jar'
'.tar'
'.tgz'
'.zip'
], ext, true) >= 0
# Public: Returns true for extensions associated with image files.
isImageExtension: (ext) ->
_.indexOf([
'.gif'
'.jpeg'
'.jpg'
'.png'
'.tiff'
], ext, true) >= 0
# Public: Returns true for extensions associated with pdf files.
isPdfExtension: (ext) ->
ext is '.pdf'
# Public: Returns true for extensions associated with binary files.
isBinaryExtension: (ext) ->
_.indexOf([
'.DS_Store'
'.a'
'.o'
'.so'
'.woff'
], ext, true) >= 0
# Public: Returns true for files named similarily to 'README'
isReadmePath: (readmePath) ->
extension = path.extname(readmePath)
base = path.basename(readmePath, extension).toLowerCase()
base is 'readme' and (extension is '' or @isMarkdownExtension(extension))
# Private: Used by isReadmePath.
isMarkdownExtension: (ext) ->
_.indexOf([
'.markdown'
'.md'
'.mdown'
'.mkd'
'.mkdown'
'.ron'
], ext, true) >= 0
# Public: Reads and returns CSON, JSON or Plist files and returns the
# corresponding Object.
readObjectSync: (objectPath) ->
CSON = require 'season'
if CSON.isObjectPath(objectPath)
CSON.readFileSync(objectPath)
else
@readPlistSync(objectPath)
# Public: Reads and returns CSON, JSON or Plist files and calls the specified
# callback with the corresponding Object.
readObject: (objectPath, done) ->
CSON = require 'season'
if CSON.isObjectPath(objectPath)
CSON.readFile(objectPath, done)
else
@readPlist(objectPath, done)
# Private: Used by readObjectSync.
readPlistSync: (plistPath) ->
plist = require 'plist'
plist.parseStringSync(@read(plistPath))
# Private: Used by readObject.
readPlist: (plistPath, done) ->
plist = require 'plist'
fs.readFile plistPath, 'utf8', (error, contents) ->
if error?
done(error)
else
try
done(null, plist.parseStringSync(contents))
catch parseError
done(parseError)
module.exports = _.extend({}, fs, fsExtensions)

View File

@@ -1,5 +1,5 @@
_ = require 'underscore-plus'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
Task = require './task'
{Emitter, Subscriber} = require 'emissary'
GitUtils = require 'git-utils'
@@ -76,11 +76,9 @@ class Git
# Private: Subscribes to buffer events.
subscribeToBuffer: (buffer) ->
bufferStatusHandler = =>
@subscribe buffer, 'saved reloaded path-changed', =>
if path = buffer.getPath()
@getPathStatus(path)
@subscribe buffer, 'saved', bufferStatusHandler
@subscribe buffer, 'reloaded', bufferStatusHandler
@subscribe buffer, 'destroyed', => @unsubscribe(buffer)
# Public: Destroy this `Git` object. This destroys any tasks and subscriptions
@@ -108,7 +106,7 @@ class Git
# Public: Returns the path of the repository.
getPath: ->
@path ?= fsUtils.absolute(@getRepo().getPath())
@path ?= fs.absolute(@getRepo().getPath())
# Public: Returns the working directory of the repository.
getWorkingDirectory: -> @getRepo().getWorkingDirectory()

View File

@@ -141,12 +141,11 @@ class Gutter extends View
updateAllLines = not (startScreenRow? and endScreenRow?)
updateAllLines |= endScreenRow <= @firstScreenRow or startScreenRow >= @lastScreenRow
for change in changes
# When there is a change to the bufferRow -> screenRow map (i.e. a fold),
# then rerender everything.
if (change.screenDelta or change.bufferDelta) and change.screenDelta != change.bufferDelta
updateAllLines = true
break
unless updateAllLines
for change in changes
if change.screenDelta or change.bufferDelta
updateAllLines = true
break
if updateAllLines
@lineNumbers[0].innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)

View File

@@ -1,6 +1,6 @@
{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
path = require 'path'
CSON = require 'season'
BindingSet = require './binding-set'
@@ -43,7 +43,7 @@ class Keymap
@load(userKeymapPath) if userKeymapPath
loadDirectory: (directoryPath) ->
@load(filePath) for filePath in fsUtils.listSync(directoryPath, ['.cson', '.json'])
@load(filePath) for filePath in fs.listSync(directoryPath, ['.cson', '.json'])
load: (path) ->
@add(path, CSON.readFileSync(path))

View File

@@ -3,8 +3,7 @@ path = require 'path'
_ = require 'underscore-plus'
ipc = require 'ipc'
CSON = require 'season'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
# Public: Provides a registry for menu items that you'd like to appear in the
# application menu.
@@ -37,7 +36,7 @@ class MenuManager
# Private
loadCoreItems: ->
menuPaths = fsUtils.listSync(atom.config.bundledMenusDirPath, ['cson', 'json'])
menuPaths = fs.listSync(atom.config.bundledMenusDirPath, ['cson', 'json'])
for menuPath in menuPaths
data = CSON.readFileSync(menuPath)
@add(data.menu)

View File

@@ -1,5 +1,5 @@
{Emitter} = require 'emissary'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
_ = require 'underscore-plus'
Package = require './package'
path = require 'path'
@@ -173,10 +173,10 @@ class PackageManager
pack for pack in @getLoadedPackages() when pack.getType() in types
resolvePackagePath: (name) ->
return name if fsUtils.isDirectorySync(name)
return name if fs.isDirectorySync(name)
packagePath = fsUtils.resolve(@packageDirPaths..., name)
return packagePath if fsUtils.isDirectorySync(packagePath)
packagePath = fs.resolve(@packageDirPaths..., name)
return packagePath if fs.isDirectorySync(packagePath)
packagePath = path.join(@resourcePath, 'node_modules', name)
return packagePath if @isInternalPackage(packagePath)
@@ -192,16 +192,16 @@ class PackageManager
packagePaths = []
for packageDirPath in @packageDirPaths
for packagePath in fsUtils.listSync(packageDirPath)
packagePaths.push(packagePath) if fsUtils.isDirectorySync(packagePath)
for packagePath in fs.listSync(packageDirPath)
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
try
metadataPath = path.join(@resourcePath, 'package.json')
{packageDependencies} = JSON.parse(fsUtils.read(metadataPath)) ? {}
{packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {}
packagesPath = path.join(@resourcePath, 'node_modules')
for packageName, packageVersion of packageDependencies ? {}
packagePath = path.join(packagesPath, packageName)
packagePaths.push(packagePath) if fsUtils.isDirectorySync(packagePath)
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
_.uniq(packagePaths)

View File

@@ -17,16 +17,17 @@ class PaneAxis extends View
@state = site.createDocument(deserializer: @className(), children: [])
@addChild(child) for child in args
@state.get('children').on 'changed', ({index, inserted, removed, site}) =>
return if site is @state.site.id
for childState in removed
@state.get('children').on 'changed', ({index, insertedValues, removedValues, siteId}) =>
return if siteId is @state.siteId
for childState in removedValues
@removeChild(@children(":eq(#{index})").view(), updateState: false)
for childState, i in inserted
for childState, i in insertedValues
@addChild(deserialize(childState), index + i, updateState: false)
addChild: (child, index=@children().length, options={}) ->
@insertAt(index, child)
@state.get('children').insert(index, child.getState()) if options.updateState ? true
state = child.getState()
@state.get('children').insert(index, state) if options.updateState ? true
@getContainer()?.adjustPaneDimensions()
removeChild: (child, options={}) ->

View File

@@ -27,11 +27,11 @@ class PaneContainer extends View
else
@state = site.createDocument(deserializer: 'PaneContainer')
@subscribe @state, 'changed', ({key, newValue, site}) =>
return if site is @state.site.id
if key is 'root'
if newValue?
@setRoot(deserialize(newValue))
@subscribe @state, 'changed', ({newValues, siteId}) =>
return if siteId is @state.siteId
if newValues.hasOwnProperty('root')
if rootState = newValues.root
@setRoot(deserialize(rootState))
else
@setRoot(null)
@@ -161,6 +161,12 @@ class PaneContainer extends View
getActiveView: ->
@getActivePane()?.activeView
paneForUri: (uri) ->
for pane in @getPanes()
view = pane.itemForUri(uri)
return pane if view?
null
adjustPaneDimensions: ->
if root = @getRoot()
root.css(width: '100%', height: '100%', top: 0, left: 0)

View File

@@ -40,16 +40,17 @@ class Pane extends View
deserializer: 'Pane'
items: @items.map (item) -> item.getState?() ? item.serialize()
@subscribe @state.get('items'), 'changed', ({index, removed, inserted, site}) =>
return if site is @state.site.id
for itemState in removed
@subscribe @state.get('items'), 'changed', ({index, removedValues, insertedValues, siteId}) =>
return if siteId is @state.siteId
for itemState in removedValues
@removeItemAtIndex(index, updateState: false)
for itemState, i in inserted
for itemState, i in insertedValues
@addItem(deserialize(itemState), index + i, updateState: false)
@subscribe @state, 'changed', ({key, newValue, site}) =>
return if site is @state.site.id
@showItemForUri(newValue) if key is 'activeItemUri'
@subscribe @state, 'changed', ({newValues, siteId}) =>
return if site is @state.siteId
if newValues.activeItemUri
@showItemForUri(newValues.activeItemUri)
@viewsByItem = new WeakMap()
activeItemUri = @state.get('activeItemUri')
@@ -73,10 +74,10 @@ class Pane extends View
@command 'pane:show-item-8', => @showItemAtIndex(7)
@command 'pane:show-item-9', => @showItemAtIndex(8)
@command 'pane:split-left', => @splitLeft()
@command 'pane:split-right', => @splitRight()
@command 'pane:split-up', => @splitUp()
@command 'pane:split-down', => @splitDown()
@command 'pane:split-left', => @splitLeft(@copyActiveItem())
@command 'pane:split-right', => @splitRight(@copyActiveItem())
@command 'pane:split-up', => @splitUp(@copyActiveItem())
@command 'pane:split-down', => @splitDown(@copyActiveItem())
@command 'pane:close', => @destroyItems()
@command 'pane:close-other-items', => @destroyInactiveItems()
@on 'focus', => @activeView?.focus(); false
@@ -198,8 +199,8 @@ class Pane extends View
container = @getContainer()
if @promptToSaveItem(item)
@removeItem(item)
container.itemDestroyed(item)
@removeItem(item)
item.destroy?()
true
else
@@ -262,9 +263,9 @@ class Pane extends View
@saveItem(item) for item in @getItems()
# Public:
removeItem: (item) ->
removeItem: (item, options) ->
index = @items.indexOf(item)
@removeItemAtIndex(index) if index >= 0
@removeItemAtIndex(index, options) if index >= 0
# Public: Just remove the item at the given index.
removeItemAtIndex: (index, options={}) ->
@@ -281,16 +282,15 @@ class Pane extends View
oldIndex = @items.indexOf(item)
@items.splice(oldIndex, 1)
@items.splice(newIndex, 0, item)
@state.get('items').splice(oldIndex, 1)
@state.get('items').splice(newIndex, 0, item.getState?() ? item.serialize())
@state.get('items').insert(newIndex, item.getState?() ? item.serialize())
@trigger 'pane:item-moved', [item, newIndex]
# Public: Moves the given item to another pane.
moveItemToPane: (item, pane, index) ->
@isMovingItem = true
@removeItem(item)
@isMovingItem = false
pane.addItem(item, index)
@removeItem(item, updateState: false)
@isMovingItem = false
# Public: Finds the first item that matches the given uri.
itemForUri: (uri) ->
@@ -387,15 +387,13 @@ class Pane extends View
axis = @buildPaneAxis(axis)
if parent instanceof PaneContainer
@detach()
axis.addChild(this)
parent.setRoot(axis)
else
parent.insertChildBefore(this, axis)
parent.detachChild(this)
axis.addChild(this)
axis.addChild(this)
parent = axis
items = [@copyActiveItem()] unless items.length
newPane = new Pane(items...)
switch side

View File

@@ -1,11 +1,12 @@
fsUtils = require './fs-utils'
path = require 'path'
url = require 'url'
Q = require 'q'
_ = require 'underscore-plus'
fs = require 'fs-plus'
Q = require 'q'
telepath = require 'telepath'
{Range} = telepath
TextBuffer = require './text-buffer'
EditSession = require './edit-session'
{Emitter} = require 'emissary'
@@ -80,12 +81,12 @@ class Project
@state = site.createDocument(deserializer: @constructor.name, version: @constructor.version, buffers: [])
@setPath(pathOrState)
@state.get('buffers').on 'changed', ({inserted, removed, index, site}) =>
return if site is @state.site.id
@state.get('buffers').on 'changed', ({index, insertedValues, removedValues, siteId}) =>
return if siteId is @state.siteId
for removedBuffer in removed
for removedBuffer in removedValues
@removeBufferAtIndex(index, updateState: false)
for insertedBuffer, i in inserted
for insertedBuffer, i in insertedValues
@addBufferAtIndex(deserialize(insertedBuffer, project: this), index + i, updateState: false)
# Private:
@@ -116,9 +117,11 @@ class Project
@destroyRepo()
if projectPath?
directory = if fsUtils.isDirectorySync(projectPath) then projectPath else path.dirname(projectPath)
directory = if fs.isDirectorySync(projectPath) then projectPath else path.dirname(projectPath)
@rootDirectory = new Directory(directory)
@repo = Git.open(projectPath, project: this)
if @repo = Git.open(projectPath, project: this)
@repo.refreshIndex()
@repo.refreshStatus()
else
@rootDirectory = null
@@ -157,8 +160,8 @@ class Project
if uri?.match(/[A-Za-z0-9+-.]+:\/\//) # leave path alone if it has a scheme
uri
else
uri = path.join(@getPath(), uri) unless fsUtils.isAbsolute(uri)
fsUtils.absolute uri
uri = path.join(@getPath(), uri) unless fs.isAbsolute(uri)
fs.absolute uri
# Public: Make the given path relative to the project directory.
relativize: (fullPath) ->
@@ -290,7 +293,7 @@ class Project
# Private:
removeBufferAtIndex: (index, options={}) ->
[buffer] = @buffers.splice(index, 1)
@state.get('buffers').remove(index) if options.updateState ? true
@state.get('buffers')?.remove(index) if options.updateState ? true
buffer?.destroy()
# Public: Performs a search across all the files in the project.

View File

@@ -1,10 +1,9 @@
fs = require 'fs'
ipc = require 'ipc'
path = require 'path'
Q = require 'q'
{$, $$, View} = require './space-pen-extensions'
fsUtils = require './fs-utils'
_ = require 'underscore-plus'
fs = require 'fs-plus'
telepath = require 'telepath'
Editor = require './editor'
Pane = require './pane'
@@ -180,9 +179,6 @@ class RootView extends View
editSession = activePane.itemForUri(project.relativize(filePath)) if activePane and filePath
promise = project.open(filePath, {initialLine}) if not editSession
fileSize = 0
fileSize = fs.statSync(filePath).size if fsUtils.exists(filePath)
Q(editSession ? promise).then (editSession) =>
if not activePane
activePane = new Pane(editSession)
@@ -194,23 +190,45 @@ class RootView extends View
editSession
# Private: Only used in specs
openSync: (filePath, options = {}) ->
changeFocus = options.changeFocus ? true
initialLine = options.initialLine
filePath = project.relativize(filePath)
if activePane = @getActivePane()
if filePath
editSession = activePane.itemForUri(filePath) ? project.openSync(filePath, {initialLine})
else
editSession = project.openSync()
activePane.showItem(editSession)
else
editSession = project.openSync(filePath, {initialLine})
activePane = new Pane(editSession)
@panes.setRoot(activePane)
openSync: (uri, {changeFocus, initialLine, pane, split}={}) ->
changeFocus ?= true
pane ?= @getActivePane()
uri = project.relativize(uri)
activePane.focus() if changeFocus
editSession
if pane
if uri
paneItem = pane.itemForUri(uri) ? project.openSync(uri, {initialLine})
else
paneItem = project.openSync()
if split
panes = @getPanes()
if panes.length == 1
pane = panes[0].splitRight()
else
pane = _.last(panes)
pane.showItem(paneItem)
else
paneItem = project.openSync(uri, {initialLine})
pane = new Pane(paneItem)
@panes.setRoot(pane)
pane.focus() if changeFocus
paneItem
openSingletonSync: (uri, {changeFocus, initialLine, split}={}) ->
changeFocus ?= true
uri = project.relativize(uri)
pane = @panes.paneForUri(uri)
if pane
paneItem = pane.itemForUri(uri)
pane.showItem(paneItem)
pane.focus() if changeFocus
paneItem
else
@openSync(uri, {changeFocus, initialLine, split})
# Public: Updates the application's title, based on whichever file is open.
updateTitle: ->

View File

@@ -182,12 +182,12 @@ class Selection
@modifySelection => @cursor.moveLeft()
# Public: Selects all the text one position above the cursor.
selectUp: ->
@modifySelection => @cursor.moveUp()
selectUp: (rowCount) ->
@modifySelection => @cursor.moveUp(rowCount)
# Public: Selects all the text one position below the cursor.
selectDown: ->
@modifySelection => @cursor.moveDown()
selectDown: (rowCount) ->
@modifySelection => @cursor.moveDown(rowCount)
# Public: Selects all the text from the current cursor position to the top of
# the buffer.

11
src/site-shim.coffee Normal file
View File

@@ -0,0 +1,11 @@
module.exports =
class SiteShim
constructor: (document) ->
@setRootDocument(document)
setRootDocument: (@document) ->
@id = @document.siteId
@document.set('looseDocuments', [])
createDocument: (values) ->
@document.get('looseDocuments').push(values)

View File

@@ -1,7 +1,6 @@
_ = require 'underscore-plus'
{specificity} = require 'clear-cut'
{$, $$} = require './space-pen-extensions'
fsUtils = require './fs-utils'
{Emitter} = require 'emissary'
NullGrammar = require './null-grammar'
TextMateScopeSelector = require('first-mate').ScopeSelector

View File

@@ -51,8 +51,10 @@ class Task
#{coffeeCacheRequire}
#{taskBootstrapRequire}
"""
bootstrap = bootstrap.replace(/\\/g, "\\\\")
taskPath = require.resolve(taskPath)
taskPath = taskPath.replace(/\\/g, "\\\\")
env = _.extend({}, process.env, {taskPath, userAgent: navigator.userAgent})
args = [bootstrap, '--harmony_collections']

View File

@@ -6,7 +6,6 @@ Q = require 'q'
telepath = require 'telepath'
_ = require 'underscore-plus'
fsUtils = require './fs-utils'
File = require './file'
{Point, Range} = telepath
@@ -51,7 +50,7 @@ class TextBuffer
@useSerializedText = @state.get('isModified') != false
else
{@project, filePath} = optionsOrState
@text = site.createDocument(initialText ? '', shareStrings: true)
@text = new telepath.MutableString(initialText ? '')
@id = guid.create().toString()
@state = site.createDocument
id: @id
@@ -654,7 +653,7 @@ class TextBuffer
change: (oldRange, newText, options={}) ->
oldRange = @clipRange(oldRange)
newText = @normalizeLineEndings(oldRange.start.row, newText) if options.normalizeLineEndings ? true
@text.change(oldRange, newText, options)
@text.setTextInRange(oldRange, newText, options)
normalizeLineEndings: (startRow, text) ->
if lineEnding = @suggestedLineEndingForRow(startRow)

View File

@@ -1,6 +1,5 @@
_ = require 'underscore-plus'
fsUtils = require './fs-utils'
plist = require 'plist'
fs = require 'fs-plus'
Token = require './token'
{OnigRegExp, OnigScanner} = require 'oniguruma'
path = require 'path'
@@ -16,14 +15,14 @@ class TextMateGrammar
Emitter.includeInto(this)
@load: (grammarPath, done) ->
fsUtils.readObject grammarPath, (error, object) ->
fs.readObject grammarPath, (error, object) ->
if error?
done(error)
else
done(null, new TextMateGrammar(object))
@loadSync: (grammarPath) ->
new TextMateGrammar(fsUtils.readObjectSync(grammarPath))
new TextMateGrammar(fs.readObjectSync(grammarPath))
name: null
rawPatterns: null
@@ -74,7 +73,7 @@ class TextMateGrammar
true
getScore: (filePath, contents) ->
contents = fsUtils.read(filePath) if not contents? and fsUtils.isFileSync(filePath)
contents = fs.readFileSync(filePath, 'utf8') if not contents? and fs.isFileSync(filePath)
if syntax.grammarOverrideForPath(filePath) is @scopeName
2 + (filePath?.length ? 0)

View File

@@ -1,7 +1,7 @@
Package = require './package'
fsUtils = require './fs-utils'
path = require 'path'
_ = require 'underscore-plus'
fs = require 'fs-plus'
TextMateGrammar = require './text-mate-grammar'
async = require 'async'
@@ -54,9 +54,9 @@ class TextMatePackage extends Package
legalGrammarExtensions: ['plist', 'tmLanguage', 'tmlanguage', 'json', 'cson']
loadGrammars: (done) ->
fsUtils.isDirectory @getSyntaxesPath(), (isDirectory) =>
fs.isDirectory @getSyntaxesPath(), (isDirectory) =>
if isDirectory
fsUtils.list @getSyntaxesPath(), @legalGrammarExtensions, (error, paths) =>
fs.list @getSyntaxesPath(), @legalGrammarExtensions, (error, paths) =>
if error?
console.log("Error loading grammars of TextMate package '#{@path}':", error.stack, error)
done()
@@ -72,7 +72,7 @@ class TextMatePackage extends Package
done()
loadGrammarsSync: ->
for grammarPath in fsUtils.listSync(@getSyntaxesPath(), @legalGrammarExtensions)
for grammarPath in fs.listSync(@getSyntaxesPath(), @legalGrammarExtensions)
@addGrammar(TextMateGrammar.loadSync(grammarPath))
addGrammar: (grammar) ->
@@ -83,14 +83,14 @@ class TextMatePackage extends Package
getSyntaxesPath: ->
syntaxesPath = path.join(@path, "syntaxes")
if fsUtils.isDirectorySync(syntaxesPath)
if fs.isDirectorySync(syntaxesPath)
syntaxesPath
else
path.join(@path, "Syntaxes")
getPreferencesPath: ->
preferencesPath = path.join(@path, "preferences")
if fsUtils.isDirectorySync(preferencesPath)
if fs.isDirectorySync(preferencesPath)
preferencesPath
else
path.join(@path, "Preferences")
@@ -101,8 +101,8 @@ class TextMatePackage extends Package
selector = syntax.cssSelectorFromScopeSelector(grammar.scopeName)
@scopedProperties.push({selector, properties})
for preferencePath in fsUtils.listSync(@getPreferencesPath())
{scope, settings} = fsUtils.readObjectSync(preferencePath)
for preferencePath in fs.listSync(@getPreferencesPath())
{scope, settings} = fs.readObjectSync(preferencePath)
if properties = @propertiesFromTextMateSettings(settings)
selector = syntax.cssSelectorFromScopeSelector(scope) if scope?
@scopedProperties.push({selector, properties})
@@ -134,17 +134,17 @@ class TextMatePackage extends Package
@loadTextMatePreferenceObjects(preferenceObjects, done)
loadTextMatePreferenceObjects: (preferenceObjects, done) ->
fsUtils.isDirectory @getPreferencesPath(), (isDirectory) =>
fs.isDirectory @getPreferencesPath(), (isDirectory) =>
return done() unless isDirectory
fsUtils.list @getPreferencesPath(), (error, paths) =>
fs.list @getPreferencesPath(), (error, paths) =>
if error?
console.log("Error loading preferences of TextMate package '#{@path}':", error.stack, error)
done()
return
loadPreferencesAtPath = (preferencePath, done) ->
fsUtils.readObject preferencePath, (error, preferences) =>
fs.readObject preferencePath, (error, preferences) =>
if error?
console.warn("Failed to parse preference at path '#{preferencePath}'", error.stack, error)
else

View File

@@ -1,11 +1,13 @@
path = require 'path'
{Emitter} = require 'emissary'
Package = require './package'
AtomPackage = require './atom-package'
_ = require 'underscore-plus'
{Emitter} = require 'emissary'
fs = require 'fs-plus'
Package = require './package'
AtomPackage = require './atom-package'
{$} = require './space-pen-extensions'
fsUtils = require './fs-utils'
# Private: Handles discovering and loading available themes.
###
@@ -81,12 +83,12 @@ class ThemeManager
if themePath = @packageManager.resolvePackagePath(themeName)
themePaths.push(path.join(themePath, AtomPackage.stylesheetsDir))
themePath for themePath in themePaths when fsUtils.isDirectorySync(themePath)
themePath for themePath in themePaths when fs.isDirectorySync(themePath)
# Public:
getUserStylesheetPath: ->
stylesheetPath = fsUtils.resolve(path.join(atom.config.configDirPath, 'user'), ['css', 'less'])
if fsUtils.isFileSync(stylesheetPath)
stylesheetPath = fs.resolve(path.join(atom.config.configDirPath, 'user'), ['css', 'less'])
if fs.isFileSync(stylesheetPath)
stylesheetPath
else
null
@@ -106,7 +108,7 @@ class ThemeManager
# Internal-only:
reloadBaseStylesheets: ->
@requireStylesheet('../static/atom')
if nativeStylesheetPath = fsUtils.resolveOnLoadPath(process.platform, ['css', 'less'])
if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less'])
@requireStylesheet(nativeStylesheetPath)
# Internal-only:
@@ -116,9 +118,9 @@ class ThemeManager
# Internal-only:
resolveStylesheet: (stylesheetPath) ->
if path.extname(stylesheetPath).length > 0
fsUtils.resolveOnLoadPath(stylesheetPath)
fs.resolveOnLoadPath(stylesheetPath)
else
fsUtils.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
fs.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
# Public: resolves and applies the stylesheet specified by the path.
#
@@ -140,7 +142,7 @@ class ThemeManager
if path.extname(stylesheetPath) is '.less'
@loadLessStylesheet(stylesheetPath)
else
fsUtils.read(stylesheetPath)
fs.readFileSync(stylesheetPath, 'utf8')
# Internal-only:
loadLessStylesheet: (lessStylesheetPath) ->

View File

@@ -3,7 +3,7 @@ _ = require 'underscore-plus'
ipc = require 'ipc'
shell = require 'shell'
{Subscriber} = require 'emissary'
fsUtils = require './fs-utils'
fs = require 'fs-plus'
# Private: Handles low-level events related to the window.
module.exports =
@@ -28,7 +28,7 @@ class WindowEventHandler
@subscribe $(window), 'blur', -> $("body").addClass('is-blurred')
@subscribe $(window), 'window:open-path', (event, {pathToOpen, initialLine}) ->
unless fsUtils.isDirectorySync(pathToOpen)
unless fs.isDirectorySync(pathToOpen)
atom.rootView?.open(pathToOpen, {initialLine})
@subscribe $(window), 'beforeunload', =>

View File

@@ -54,9 +54,9 @@
// Handle indentation of the tree. Assume disclosure arrows.
.list-tree {
.list-nested-item > .list-tree,
.list-nested-item > .list-group {
margin-left: @component-icon-size + @component-icon-padding;
.list-nested-item > .list-tree > li,
.list-nested-item > .list-group > li {
padding-left: @component-icon-size + @component-icon-padding;
}
&.has-collapsable-children {
@@ -86,9 +86,9 @@
}
}
.list-nested-item > .list-tree,
.list-nested-item > .list-group {
margin-left: @disclosure-arrow-padding;
.list-nested-item > .list-tree > li,
.list-nested-item > .list-group > li {
padding-left: @disclosure-arrow-padding;
}
// You want a subtree to be flat -- no collapsable children

View File

@@ -1,7 +1,7 @@
path = require 'path'
_ = require 'underscore-plus'
plist = require 'plist'
fs = require 'fs-plus'
{ScopeSelector} = require 'first-mate'
module.exports = (grunt) ->
@@ -27,7 +27,7 @@ class TextMateTheme
@buildRulesets()
buildRulesets: ->
{settings} = plist.parseFileSync(@path)
{settings} = fs.readPlistSync(@path)
@buildGlobalSettingsRulesets(settings[0])
@buildScopeSelectorRulesets(settings[1..])

View File

@@ -26,7 +26,7 @@ module.exports = (grunt) ->
grunt.log.writeln("Launching #{path.basename(packagePath)} specs.")
spawn options, (error, results, code) ->
grunt.log.writeln()
passed = passed and code is 0
passed = passed and not error and code is 0
callback()
modulesDirectory = path.resolve('node_modules')
@@ -51,7 +51,7 @@ module.exports = (grunt) ->
spawn options, (error, results, code) ->
grunt.log.writeln()
packageSpecQueue.concurrency = 2
callback(null, code is 0)
callback(null, not error and code is 0)
grunt.registerTask 'run-specs', 'Run the specs', ->
done = @async()

View File

@@ -4,6 +4,7 @@ os = require 'os'
request = require 'request'
formidable = require 'formidable'
unzip = require 'unzip'
module.exports = (grunt) ->
{spawn, mkdir, rm, cp} = require('./task-helpers')(grunt)
@@ -148,9 +149,20 @@ module.exports = (grunt) ->
grunt.log.writeln('Unzipping atom-shell')
directoryPath = path.dirname(zipPath)
spawn {cmd: 'unzip', args: [zipPath, '-d', directoryPath]}, (error) ->
rm(zipPath)
callback(error)
if process.platform is 'darwin'
# The zip archive of darwin build contains symbol links, only the "unzip"
# command can handle it correctly.
spawn {cmd: 'unzip', args: [zipPath, '-d', directoryPath]}, (error) ->
rm(zipPath)
callback(error)
else
fileStream = fs.createReadStream(zipPath)
fileStream.on('error', callback)
zipStream = fileStream.pipe(unzip.Extract(path: directoryPath))
zipStream.on('error', callback)
zipStream.on 'close', ->
rm(zipPath)
callback(null)
rebuildNativeModules = (previousVersion, callback) ->
newVersion = getAtomShellVersion()

2
vendor/apm vendored

Submodule vendor/apm updated: b08ec12395...e2a09e7bb5