mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Merge remote-tracking branch 'origin/master' into cj-sublime-bindings
This commit is contained in:
@@ -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
33
docs/building-atom.md
Normal 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/
|
||||
@@ -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` — 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
|
||||
|
||||
@@ -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 — 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 — 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 — 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 — 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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
151
docs/template.jst
Normal 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
116
docs/theme-variables.md
Normal 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
334
docs/your-first-package.md
Normal 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` — 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
|
||||
@@ -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
|
||||
|
||||
31
package.json
31
package.json
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
5
script/build.cmd
Normal file
@@ -0,0 +1,5 @@
|
||||
@IF EXIST "%~dp0\node.exe" (
|
||||
"%~dp0\node.exe" "%~dp0\build" %*
|
||||
) ELSE (
|
||||
node "%~dp0\build" %*
|
||||
)
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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'
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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'
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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]]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 ? []
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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: ->
|
||||
|
||||
@@ -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: ->
|
||||
[
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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={}) ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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: ->
|
||||
|
||||
@@ -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
11
src/site-shim.coffee
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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', =>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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..])
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
2
vendor/apm
vendored
Submodule vendor/apm updated: b08ec12395...e2a09e7bb5
Reference in New Issue
Block a user