mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-11 19:17:09 +03:00
Merge branch 'master' into atom-shell
Conflicts: package.json script/bootstrap spec/app/window-spec.coffee src/app/window.coffee
This commit is contained in:
commit
61f8878e05
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -85,3 +85,6 @@
|
||||
[submodule "vendor/packages/hyperlink-helper.tmbundle"]
|
||||
path = vendor/packages/hyperlink-helper.tmbundle
|
||||
url = https://github.com/textmate/hyperlink-helper.tmbundle
|
||||
[submodule "vendor/apm"]
|
||||
path = vendor/apm
|
||||
url = https://github.com/github/apm.git
|
||||
|
@ -1,3 +1,7 @@
|
||||
* Fixed: Not being able to disable packages from configuration UI.
|
||||
* Fixed: Fuzzy finder showing poor results for entered text
|
||||
* Improved: App icon
|
||||
|
||||
* Fixed: Fuzzy finder being empty sometimes
|
||||
|
||||
* Improved: App icon
|
||||
|
3
Rakefile
3
Rakefile
@ -50,8 +50,7 @@ task :install => [:build] do
|
||||
raise "Missing directory for `atom` binary"
|
||||
end
|
||||
|
||||
FileUtils.cp("#{ATOM_SRC_PATH}/atom.sh", cli_path)
|
||||
FileUtils.chmod(0755, cli_path)
|
||||
FileUtils.ln_s "#{ATOM_SRC_PATH}/atom.sh", cli_path, :force => true
|
||||
|
||||
Rake::Task["clone-default-bundles"].invoke()
|
||||
|
||||
|
@ -106,7 +106,7 @@ describe "TokenizedBuffer.", ->
|
||||
[languageMode, buffer] = []
|
||||
|
||||
beforeEach ->
|
||||
editSession = benchmarkFixturesProject.buildEditSession('medium.coffee')
|
||||
editSession = benchmarkFixturesProject.open('medium.coffee')
|
||||
{ languageMode, buffer } = editSession
|
||||
|
||||
benchmark "construction", 20, ->
|
||||
|
@ -1,133 +0,0 @@
|
||||
# Authoring Packages
|
||||
|
||||
A package can contain a variety of different resource types to change Atom's
|
||||
behavior. The basic package layout is as follows (not every package will
|
||||
have all of these directories):
|
||||
|
||||
```text
|
||||
my-package/
|
||||
lib/
|
||||
config/
|
||||
stylesheets/
|
||||
keymaps/
|
||||
snippets/
|
||||
grammars/
|
||||
package.json
|
||||
index.coffee
|
||||
```
|
||||
|
||||
**NOTE:** NPM behavior is partially implemented until we get a working Node.js
|
||||
API built into Atom. The goal is to make Atom packages be a superset of NPM
|
||||
packages
|
||||
|
||||
## package.json
|
||||
|
||||
Similar to npm packages, Atom packages can 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.
|
||||
|
||||
## Source Code
|
||||
|
||||
If you want to extend Atom's behavior, your package should contain a single
|
||||
top-level module, which you export from `index.coffee` or another file as
|
||||
indicated by the `main` key in your `package.json` file. The remainder of your
|
||||
code should be placed in the `lib` directory, and required from your top-level
|
||||
file.
|
||||
|
||||
Your package's top-level module is a singleton object that manages the lifecycle
|
||||
of your extensions to Atom. Even if your package creates ten different views and
|
||||
appends them to different parts of the DOM, it's all managed from your top-level
|
||||
object. Your package's top-level module should implement the following methods:
|
||||
|
||||
- `activate(rootView, state)` **Required**: This method is called when your
|
||||
package is loaded. It is always passed the window's global `rootView`, and is
|
||||
sometimes passed state data if the window has been reloaded and your module
|
||||
implements the `serialize` method.
|
||||
|
||||
- `serialize()` **Optional**: This method is called when the window is shutting
|
||||
down, allowing you to return JSON to represent the state of your component. When
|
||||
the window is later restored, the data you returned will be passed to your
|
||||
module's `activate` method so you can restore your view to where the user left
|
||||
off.
|
||||
|
||||
- `deactivate()` **Optional**: This method is called when the window is shutting
|
||||
down. If your package is watching any files or holding external resources in any
|
||||
other way, release them here. If you're just subscribing to things on window
|
||||
you don't need to worry because that's getting torn down anyway.
|
||||
|
||||
## A Simple Package Layout:
|
||||
|
||||
```text
|
||||
my-package/
|
||||
package.json # optional
|
||||
index.coffee
|
||||
lib/
|
||||
my-package.coffee
|
||||
```
|
||||
|
||||
`index.coffee`:
|
||||
```coffeescript
|
||||
module.exports = require "./lib/my-package"
|
||||
```
|
||||
|
||||
`my-package/my-package.coffee`:
|
||||
```coffeescript
|
||||
module.exports =
|
||||
activate: (rootView, state) -> # ...
|
||||
deactivate: -> # ...
|
||||
serialize: -> # ...
|
||||
```
|
||||
|
||||
Beyond this simple contract, your package has full access to Atom's internal
|
||||
API. Anything we call internally, you can call as well. Be aware that since we
|
||||
are early in development, APIs are subject to change and we have not yet
|
||||
established clear boundaries between what is public and what is private. 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. See [Atom's built-in packages](https://github.com/github/atom/tree/master/src/packages)
|
||||
for examples of Atom's API in action.
|
||||
|
||||
## Stylesheets
|
||||
|
||||
Stylesheets for your package should be placed in the `stylesheets` directory.
|
||||
Any stylesheets in this directory will be loaded and attached to the DOM when
|
||||
your package is activated. An optional `stylesheets` key in your `package.json`
|
||||
can list the stylesheets by name in order to specify a load order; otherwise
|
||||
stylesheets are loaded alphabetically.
|
||||
|
||||
## Keymaps
|
||||
|
||||
Keymaps are placed in the `keymaps` subdirectory. By default, all keymaps will be
|
||||
loaded in alphabetical order unless there is a `keymaps` array in `package.json`
|
||||
specifying which keymaps to load and in what order. It's a good idea to provide
|
||||
default keymaps for your extension. They can be customized by users later. See
|
||||
the (main keymaps documentation)[#keymaps] for more information on how keymaps
|
||||
work.
|
||||
|
||||
## Snippets
|
||||
|
||||
An extension can supply snippets in a `snippets` directory as `.cson` or `.json`
|
||||
files:
|
||||
|
||||
```coffeescript
|
||||
".source.coffee .specs":
|
||||
"Expect":
|
||||
prefix: "ex"
|
||||
body: "expect($1).to$2"
|
||||
"Describe":
|
||||
prefix: "de"
|
||||
body: """
|
||||
describe "${1:description}", ->
|
||||
${2:body}
|
||||
"""
|
||||
```
|
||||
|
||||
A snippets file contains scope selectors at its top level. Each scope selector
|
||||
contains a hash of snippets keyed by their name. Each snippet specifies a
|
||||
`prefix` and a `body` key.
|
||||
|
||||
All files in the directory will be automatically loaded, unless the
|
||||
`package.json` supplies a `snippets` key as a manifest. As with all scoped
|
||||
items, snippets loaded later take precedence over earlier snippets when two
|
||||
snippets match a scope with the same specificity.
|
@ -1,22 +1,26 @@
|
||||
## Command Panel
|
||||
|
||||
A partial implementation of the [Sam command language](http://man.cat-v.org/plan_9/1/sam)
|
||||
The command panel contains a partial implementation of the [Sam command language](http://man.cat-v.org/plan_9/1/sam).
|
||||
In addition, packages are free to design and define any scoped command.
|
||||
|
||||
*Examples*
|
||||
Pop open the command line by hitting .
|
||||
You can get a list of commands available to Atom (including any keybindings) by hitting `meta-p`.
|
||||
|
||||
`,` selects entire file
|
||||
## Examples
|
||||
|
||||
`1,4` selects lines 1-4
|
||||
`,` selects the entire file
|
||||
|
||||
`1,4` selects lines 1-4 in the current file
|
||||
|
||||
`/pattern` selects the first match after the cursor/selection
|
||||
|
||||
`s/pattern/replacement` replace first text matching pattern in current selection
|
||||
`s/pattern/replacement` replaces the first text matching pattern in current selection
|
||||
|
||||
`s/pattern/replacement/g` replace all text matching pattern in current selection
|
||||
`s/pattern/replacement/g` replaces all text matching pattern in current selection
|
||||
|
||||
`,s/pattern/replacement/g` replace all text matching pattern in file
|
||||
`,s/pattern/replacement/g` replaces all text matching pattern in file
|
||||
|
||||
`1,4s/pattern/replacement` replace all text matching pattern in lines 1-4
|
||||
`1,4s/pattern/replacement` replaces all text matching pattern in lines 1-4
|
||||
|
||||
`x/pattern` selects all matches in the current selections
|
||||
|
||||
|
@ -1,17 +1,16 @@
|
||||
## Wrap Guide
|
||||
|
||||
The `wrap-guide` extension places a vertical line in each editor at a certain
|
||||
column to guide your formatting so lines do not exceed a certain width.
|
||||
column to guide your formatting, so lines do not exceed a certain width.
|
||||
|
||||
By default the wrap-guide is placed at the 80th column.
|
||||
By default, the wrap-guide is placed at the 80th column.
|
||||
|
||||
### Configuration
|
||||
|
||||
You can customize where the column is placed using the `wrapGuide.columns`
|
||||
config option.
|
||||
config option:
|
||||
|
||||
config.cson:
|
||||
```coffee-cript
|
||||
```coffeescript
|
||||
"wrap-guide":
|
||||
columns: [
|
||||
{ pattern: "\.mm$", column: 200 },
|
||||
|
138
docs/configuring-atom.md
Normal file
138
docs/configuring-atom.md
Normal file
@ -0,0 +1,138 @@
|
||||
# Configuration Settings
|
||||
|
||||
## Your .atom Directory
|
||||
|
||||
When you install Atom, an _.atom_ directory is created in your home directory.
|
||||
If you press `meta-,`, that directory is opened in a new window. For the
|
||||
time being, this serves as the primary interface for adjusting configuration
|
||||
settings, adding and changing key bindings, tweaking styles, etc.
|
||||
|
||||
Atom loads configuration settings from the `config.cson` file in your _~/.atom_
|
||||
directory, which contains CoffeeScript-style JSON:
|
||||
|
||||
```coffeescript
|
||||
core:
|
||||
hideGitIgnoredFiles: true
|
||||
editor:
|
||||
fontSize: 18
|
||||
```
|
||||
|
||||
Configuration is broken into namespaces, which are defined by the config hash's
|
||||
top-level keys. In addition to Atom's core components, each package may define
|
||||
its own namespace.
|
||||
|
||||
## Glossary of Config Keys
|
||||
|
||||
- `core`
|
||||
- `disablePackages`: An array of package names to disable
|
||||
- `hideGitIgnoredFiles`: Whether files in the _.gitignore_ should be hidden
|
||||
- `ignoredNames`: File names to ignore across all of Atom (not fully implemented)
|
||||
- `themes`: An array of theme names to load, in cascading order
|
||||
- `autosave`: Save a buffer when its view loses focus
|
||||
- `editor`
|
||||
- `autoIndent`: Enable/disable basic auto-indent (defaults to `true`)
|
||||
- `autoIndentOnPaste`: Enable/disable auto-indented pasted text (defaults to `false`)
|
||||
- `nonWordCharacters`: A string of non-word characters to define word boundaries
|
||||
- `fontSize`: The editor font size
|
||||
- `fontFamily`: The editor font family
|
||||
- `invisibles`: Specify characters that Atom renders for invisibles in this hash
|
||||
- `tab`: Hard tab characters
|
||||
- `cr`: Carriage return (for Microsoft-style line endings)
|
||||
- `eol`: `\n` characters
|
||||
- `space`: Leading and trailing space characters
|
||||
- `preferredLineLength`: Identifies the length of a line (defaults to `80`)
|
||||
- `showInvisibles`: Whether to render placeholders for invisible characters (defaults to `false`)
|
||||
- `fuzzyFinder`
|
||||
- `ignoredNames`: Files to ignore *only* in the fuzzy-finder
|
||||
- `whitespace`
|
||||
- `ensureSingleTrailingNewline`: Whether to reduce multiple newlines to one at the end of files
|
||||
- `wrapGuide`
|
||||
- `columns`: Array of hashes with a `pattern` and `column` key to match the
|
||||
the path of the current editor to a column position.
|
||||
|
||||
## Customizing Key Bindings
|
||||
|
||||
Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors
|
||||
to apply styles to elements, Atom keymaps use selectors to associate keystrokes
|
||||
with events in specific contexts. Here's a small example, excerpted from Atom's
|
||||
built-in keymaps:
|
||||
|
||||
```coffee-script
|
||||
'.editor':
|
||||
'enter': 'editor:newline'
|
||||
|
||||
".select-list .editor.mini":
|
||||
'enter': 'core:confirm',
|
||||
```
|
||||
|
||||
This keymap defines the meaning of `enter` in two different contexts. In a
|
||||
normal editor, pressing `enter` emits the `editor:newline` event, which causes
|
||||
the editor to insert a newline. But if the same keystroke occurs inside of a
|
||||
select list's mini-editor, it instead emits the `core:confirm` event based on
|
||||
the binding in the more-specific selector.
|
||||
|
||||
By default, any keymap files in your `~/.atom/keymaps` directory are loaded
|
||||
in alphabetical order when Atom is started. They will always be loaded last,
|
||||
giving you the chance to override bindings that are defined by Atom's core
|
||||
keymaps or third-party packages.
|
||||
|
||||
## Changing The Theme
|
||||
|
||||
Atom comes bundled with two themes `atom-dark-*` and `atom-light-*`.
|
||||
|
||||
Because Atom themes are based on CSS, it's possible to have multiple themes
|
||||
active at the same time. For example, you'll usually select a theme for the UI
|
||||
and another theme for syntax highlighting. You can select themes by specifying
|
||||
them in the `core.themes` array in your `config.cson`:
|
||||
|
||||
```coffee-script
|
||||
core:
|
||||
themes: ["atom-light-ui", "atom-light-syntax"]
|
||||
# or, if the sun is going down:
|
||||
# themes: ["atom-dark-ui", "atom-dark-syntax"]
|
||||
```
|
||||
|
||||
You install new themes by placing them in the _~/.atom/themes_ directory. A
|
||||
theme can be a CSS file, a directory containing multiple CSS files, or a
|
||||
TextMate theme (either _.tmTheme_ or _.plist_).
|
||||
|
||||
|
||||
## Installing Packages (Partially Implemented)
|
||||
|
||||
To install a package, clone it into the _~/.atom/packages_ directory. Atom will
|
||||
also load grammars and snippets from TextMate bundles. If you want to disable a
|
||||
package without removing it from the packages directory, insert its name into
|
||||
_config.core.disabledPackages_:
|
||||
|
||||
```coffeescript
|
||||
core:
|
||||
disabledPackages: [
|
||||
"fuzzy-finder",
|
||||
"tree-view"
|
||||
]
|
||||
```
|
||||
|
||||
## Quick Personal Hacks
|
||||
|
||||
### user.coffee
|
||||
|
||||
When Atom finishes loading, it will evaluate _user.coffee_ in your _~/.atom_
|
||||
directory, giving you a chance to run arbitrary personal CoffeeScript code to
|
||||
make customizations. You have full access to Atom's API from code in this file.
|
||||
Please refer to the [Atom Internals Guide](./internals/intro,md) for more information. If your
|
||||
customizations become extensive, consider [creating a package](./packages/creating_packages.md).
|
||||
|
||||
### user.css
|
||||
|
||||
If you want to apply quick-and-dirty personal styling changes without creating
|
||||
an entire theme that you intend to distribute, you can add styles to
|
||||
_user.css_ in your _~/.atom_ directory.
|
||||
|
||||
For example, to change the color of the highlighted line number for the line that
|
||||
contains the cursor, you could add the following style to _user.css_:
|
||||
|
||||
```css
|
||||
.editor .line-number.cursor-line {
|
||||
color: pink;
|
||||
}
|
||||
```
|
@ -1,32 +1,33 @@
|
||||
# Getting Started
|
||||
|
||||
Welcome to Atom. This documentation is intended to offer a basic introduction
|
||||
of how to get productive with this editor. Then we'll delve into more details
|
||||
about configuring, theming, and extending Atom.
|
||||
Welcome to Atom. This documentation provides a basic introduction to being
|
||||
productive with this editor. We'll then delve into more details about configuring,
|
||||
theming, and extending Atom.
|
||||
|
||||
## The Command Palette
|
||||
|
||||
If there's one key-command you learn in Atom, it should be `meta-p` (`meta` is
|
||||
If there's one key-command you must remember in Atom, it should be `meta-p` (`meta` is
|
||||
synonymous with the ⌘ key). You can always hit `meta-p` to bring up a list of
|
||||
commands that are relevant to the currently focused UI element. If there is a
|
||||
key binding for a given command, it is also displayed. This is a great way to
|
||||
explore the system and get to know the key commands interactively. If you'd like
|
||||
to add or change a binding for a command, refer to the [key
|
||||
bindings](#customizing-key-bindings) section to learn how.
|
||||
to learn about adding or changing a binding for a command, refer to the [key
|
||||
bindings](#customizing-key-bindings) section.
|
||||
|
||||
![Command Palette](http://f.cl.ly/items/32041o3w471F3C0F0V2O/Screen%20Shot%202013-02-13%20at%207.27.41%20PM.png)
|
||||
|
||||
## Basic Key Bindings
|
||||
Remember you can always use `meta-p` to explore available commands and their
|
||||
|
||||
You can always use `meta-p` to explore available commands and their
|
||||
bindings, but here's a list of a few useful commands.
|
||||
|
||||
- `meta-o` : open file/directory
|
||||
- `meta-n` : new window
|
||||
- `meta-o` : open a file or directory
|
||||
- `meta-n` : open new window
|
||||
- `meta-r` : reload the current window
|
||||
- `meta-alt-ctrl-s` : run specs
|
||||
- `meta-t` : open fuzzy file finder
|
||||
- `meta-alt-ctrl-s` : run test specs
|
||||
- `meta-t` : open file finder to navigate files in your project
|
||||
- `meta-;` : open command prompt
|
||||
- `meta-f` : open command prompt with `/`
|
||||
- `meta-f` : open command prompt with `/` for a local file search
|
||||
- `meta-g` : repeat the last local search
|
||||
- `meta-shift-f` : open command prompt with `Xx/` for a project-wide search
|
||||
- `meta-\` : focus/open tree view, or close it when it is focused
|
||||
@ -52,8 +53,8 @@ issue so you can keep working.
|
||||
|
||||
The fastest way to find a file in your project is to use the fuzzy finder. Just
|
||||
hit `meta-t` and start typing the name of the file you're looking for. If you
|
||||
already have the file open and want to jump to it, hit `meta-b` to bring up a
|
||||
searchable list of open buffers.
|
||||
already have the file open as a tab and want to jump to it, hit `meta-b` to bring
|
||||
up a searchable list of open buffers.
|
||||
|
||||
You can also use the tree view to navigate to a file. To open or move focus to
|
||||
the tree view, hit `meta-\`. You can then navigate to a file and select it with
|
||||
@ -73,28 +74,32 @@ To delete a file, select it in the tree view and hit `delete`.
|
||||
|
||||
#### Using the Command Line
|
||||
|
||||
Atom has a command line similar to editors Emacs and Vim, which is currently the
|
||||
only interface for performing searches. Hitting `meta-f` will open the command
|
||||
line prepopulated with the `/` command, which finds forward in the current
|
||||
buffer from the location of the cursor. Pressing `meta-g` will repeat the
|
||||
search. Hitting `meta-shift-f` will open the command line prepopulated with
|
||||
`Xx/`, which is a composite command that performs a global search. The results
|
||||
of the search will appear in the operation preview list, which you can focus
|
||||
Atom has a command line similar to old-school editors such as emacs and vim. Nearly
|
||||
every command has a key binding which you can discover with `meta-p`.
|
||||
|
||||
The command line is also (currently) the only place you can perform a search. Hitting
|
||||
`meta-f` opens the command line and prepopulates it with the `/` command. This finds
|
||||
text in the current buffer, starting at the location of the cursor. Pressing `meta-g`
|
||||
repeats the search. Hitting `meta-shift-f` opens the command line and prepopulates
|
||||
it with `Xx/`, which is a composite command that performs a global search. The results
|
||||
of the search appear in the operation preview list, which you can focus
|
||||
with `meta-:`.
|
||||
|
||||
Atom's command language is still under construction and is loosely based on
|
||||
Atom's command language is still under construction, and is loosely based on
|
||||
the [Sam editor](http://doc.cat-v.org/bell_labs/sam_lang_tutorial/) from the
|
||||
Plan 9 operating system. It's similar to Ex mode in Vim, but is selection-based
|
||||
Plan 9 operating system. It's similar to Ex mode in vim, but is selection-based
|
||||
rather than line-based. It allows you to compose commands together in
|
||||
interesting ways.
|
||||
|
||||
#### Navigating By Symbols
|
||||
|
||||
If you want to jump to a method, you can use the ctags-based symbols package.
|
||||
The `meta-j` binding will open a list of all symbols in the current file. The
|
||||
`meta-shift-j` binding will open a list of all symbols for the current project
|
||||
based on a tags file. And `meta-.` will jump to the tag for the word currently
|
||||
under the cursor. Make sure you have a tags file generated for the project for
|
||||
The `meta-j` binding opens a list of all symbols in the current file. The
|
||||
`meta-shift-j` binding opens a list of all symbols for the current project
|
||||
based on a tags file. `meta-.` jumps to the tag for the word currently
|
||||
under the cursor.
|
||||
|
||||
Make sure you have a tags file generated for the project for
|
||||
the latter of these two bindings to work. Also, if you're editing CoffeeScript,
|
||||
it's a good idea to update your `~/.ctags` file to understand the language. Here
|
||||
is [a good example](https://github.com/kevinsawicki/dotfiles/blob/master/.ctags).
|
||||
@ -106,8 +111,8 @@ command, as follows: `s/foo/bar/g`. Note that if you have a selection, the
|
||||
replacement will only occur inside the selected text. An empty selection will
|
||||
cause the replacement to occur across the whole buffer. If you want to run the
|
||||
command on the whole buffer even if you have a selection, precede your
|
||||
substitution with the `,` address, which specifies that the command following it
|
||||
operate on the whole buffer.
|
||||
substitution with the `,` address; this indicates that the following command should
|
||||
run on the whole buffer.
|
||||
|
||||
### Split Panes
|
||||
|
||||
@ -125,12 +130,12 @@ planning to improve it soon.
|
||||
### Soft-Wrap
|
||||
|
||||
If you want to toggle soft wrap, trigger the command from the command palette.
|
||||
Press `meta-p` to open the palette, then type "wrap" to find the correct
|
||||
Hit `meta-p` to open the palette, then type "wrap" to find the correct
|
||||
command.
|
||||
|
||||
## Your .atom Directory
|
||||
|
||||
When you install Atom, a `.atom` directory is created in your home directory.
|
||||
When you install Atom, an `.atom` directory is created in your home directory.
|
||||
If you press `meta-,`, that directory will be opened in a new window. For the
|
||||
time being, this will serve as the primary interface for adjusting configuration
|
||||
settings, adding and changing key bindings, tweaking styles, etc.
|
||||
|
@ -49,10 +49,7 @@ the following way:
|
||||
# basic key update
|
||||
config.set("core.autosave", true)
|
||||
|
||||
# if you mutate a config key, you'll need to call `config.update` to inform
|
||||
# observers of the change
|
||||
config.get("fuzzyFinder.ignoredPaths").push "vendor"
|
||||
config.update()
|
||||
config.pushAtKeyPath("core.disabledPackages", "wrap-guide")
|
||||
```
|
||||
|
||||
You can also use `setDefaults`, which will assign default values for keys that
|
||||
|
@ -1,10 +1,14 @@
|
||||
getting-started.md
|
||||
configuring-atom.md
|
||||
built-in-packages/intro.md
|
||||
built-in-packages/command-panel.md
|
||||
built-in-packages/markdown-preview.md
|
||||
built-in-packages/wrap-guide.md
|
||||
authoring-themes.md
|
||||
authoring-packages..md
|
||||
packages/authoring-packages.md
|
||||
packages/creating_a_package.md
|
||||
packages/included_libraries.md
|
||||
packages/package_json.md
|
||||
themes/authoring-themes.md
|
||||
internals/intro.md
|
||||
internals/configuration.md
|
||||
internals/keymaps.md
|
||||
|
211
docs/packages/authoring-packages.md
Normal file
211
docs/packages/authoring-packages.md
Normal file
@ -0,0 +1,211 @@
|
||||
# Authoring Packages
|
||||
|
||||
Packages are at the core of Atom. Nearly everything outside of the main editor manipulation
|
||||
is handled by a package. That includes "core" pieces like the command panel, status bar,
|
||||
file tree, 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):
|
||||
|
||||
```text
|
||||
my-package/
|
||||
lib/
|
||||
stylesheets/
|
||||
keymaps/
|
||||
snippets/
|
||||
grammars/
|
||||
spec/
|
||||
package.json
|
||||
index.coffee
|
||||
```
|
||||
|
||||
**NOTE:** NPM behavior is partially implemented until we get a working Node.js
|
||||
API built into Atom. The goal is to make Atom packages be a superset of NPM
|
||||
packages.
|
||||
|
||||
Below, we'll break down each directory. There's also [a tutorial](./creating_a_package.md)
|
||||
on creating your first package.
|
||||
|
||||
## package.json
|
||||
|
||||
Similar to [npm packages](http://en.wikipedia.org/wiki/Npm_(software)), Atom packages
|
||||
can 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.
|
||||
|
||||
In addition to the regular [npm package.json keys](https://npmjs.org/doc/json.html)
|
||||
available, Atom package.json files [have their own additions](./package_json.md).
|
||||
|
||||
## Source Code
|
||||
|
||||
If you want to extend Atom's behavior, your package should contain a single
|
||||
top-level module, which you export from _index.coffee_ (or whichever file is
|
||||
indicated by the `main` key in your _package.json_ file). The remainder of your
|
||||
code should be placed in the `lib` directory, and required from your top-level
|
||||
file.
|
||||
|
||||
Your package's top-level module is a singleton object that manages the lifecycle
|
||||
of your extensions to Atom. Even if your package creates ten different views and
|
||||
appends them to different parts of the DOM, it's all managed from your top-level
|
||||
object.
|
||||
|
||||
Your package's top-level module should implement the following methods:
|
||||
|
||||
- `activate(rootView, state)`: This **required** method is called when your
|
||||
package is loaded. It is always passed the window's global `rootView`, and is
|
||||
sometimes passed state data if the window has been reloaded and your module
|
||||
implements the `serialize` method. Use this to do initialization work when your
|
||||
package is started (like setting up DOM elements or binding events).
|
||||
|
||||
- `serialize()`: This **optional** method is called when the window is shutting
|
||||
down, allowing you to return JSON to represent the state of your component. When
|
||||
the window is later restored, the data you returned is passed to your
|
||||
module's `activate` method so you can restore your view to where the user left
|
||||
off.
|
||||
|
||||
- `deactivate()`: This **optional** method is called when the window is shutting
|
||||
down. If your package is watching any files or holding external resources in any
|
||||
other way, release them here. If you're just subscribing to things on window,
|
||||
you don't need to worry because that's getting torn down anyway.
|
||||
|
||||
### Simple Package Code
|
||||
|
||||
```text
|
||||
my-package/
|
||||
package.json # optional
|
||||
index.coffee
|
||||
lib/
|
||||
my-package.coffee
|
||||
```
|
||||
|
||||
`index.coffee`:
|
||||
```coffeescript
|
||||
module.exports = require "./lib/my-package"
|
||||
```
|
||||
|
||||
`my-package/my-package.coffee`:
|
||||
```coffeescript
|
||||
module.exports =
|
||||
activate: (rootView, state) -> # ...
|
||||
deactivate: -> # ...
|
||||
serialize: -> # ...
|
||||
```
|
||||
|
||||
Beyond this simple contract, your package has full access to Atom's internal
|
||||
API. Anything we call internally, you can call as well. Be aware that since we
|
||||
are early in development, APIs are subject to change and we have not yet
|
||||
established clear boundaries between what is public and what is private. 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.
|
||||
|
||||
See [Atom's built-in packages](https://github.com/github/atom/tree/master/src/packages)
|
||||
for examples of Atom's API in action.
|
||||
|
||||
## Stylesheets
|
||||
|
||||
Stylesheets for your package should be placed in the _stylesheets_ directory.
|
||||
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.
|
||||
|
||||
An optional `stylesheets` array in your _package.json_ can list the stylesheets by
|
||||
name to specify a loading order; otherwise, stylesheets are loaded alphabetically.
|
||||
|
||||
## Keymaps
|
||||
|
||||
Keymaps are placed in the _keymaps_ subdirectory. It's a good idea to provide
|
||||
default keymaps for your extension, especially if you're also adding a new command.
|
||||
|
||||
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.
|
||||
|
||||
See the [main keymaps documentation](../internals/keymaps.md) for more information on
|
||||
how keymaps work.
|
||||
|
||||
## Snippets
|
||||
|
||||
An extension can supply language snippets in the _snippets_ directory. These can
|
||||
be `.cson` or `.json` files. Here's an example:
|
||||
|
||||
```coffeescript
|
||||
".source.coffee .specs":
|
||||
"Expect":
|
||||
prefix: "ex"
|
||||
body: "expect($1).to$2"
|
||||
"Describe":
|
||||
prefix: "de"
|
||||
body: """
|
||||
describe "${1:description}", ->
|
||||
${2:body}
|
||||
"""
|
||||
```
|
||||
|
||||
A snippets file contains scope selectors at its top level (`.source.coffee .spec`).
|
||||
Each scope selector contains a hash of snippets keyed by their name (`Expect`, `Describe`).
|
||||
Each snippet also specifies a `prefix` and a `body` key. The `prefix` represents
|
||||
the first few letters to type before hitting the `tab` key to autocomplete. The
|
||||
`body` defines the autofilled text. You can use placeholders like `$1`, `$2`, to indicate
|
||||
regions in the body the user can navigate to every time they hit `tab`.
|
||||
|
||||
All files in the directory are automatically loaded, unless the
|
||||
_package.json_ supplies a `snippets` key. As with all scoped
|
||||
items, snippets loaded later take precedence over earlier snippets when two
|
||||
snippets match a scope with the same specificity.
|
||||
|
||||
## Language Grammars
|
||||
|
||||
If you're developing a new language grammar, you'll want to place your file in
|
||||
the _grammars_ directory. Each grammar is a pairing of two keys, `match` and
|
||||
`captures`. `match` is a regular expression identifying the pattern to highlight,
|
||||
while `captures` is a JSON representing what to do with each matching group.
|
||||
For example:
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
'match': '(?:^|\\s)(__[^_]+__)'
|
||||
'captures':
|
||||
'1': 'name': 'markup.bold.gfm'
|
||||
}
|
||||
```
|
||||
|
||||
This indicates that the first matching capture (`(__[^_]+__)`) should have the
|
||||
`markup.bold.gfm` token applied to it.
|
||||
|
||||
To capture a single group, simply use the `name` key instead:
|
||||
|
||||
```json
|
||||
{
|
||||
'match': '^#{1,6}\\s+.+$'
|
||||
'name': 'markup.heading.gfm'
|
||||
}
|
||||
```
|
||||
|
||||
This indicates that Markdown header lines (`#`, `##`, `###`) should be applied with
|
||||
the `markup.heading.gfm` token.
|
||||
|
||||
More information about the significance of these tokens can be found in
|
||||
[section 12.4 of the TextMate Manual](http://manual.macromates.com/en/language_grammars.html).
|
||||
|
||||
Your grammar should also include a `filetypes` array, which is a list of file extensions
|
||||
your grammar supports:
|
||||
|
||||
```
|
||||
'fileTypes': [
|
||||
'markdown'
|
||||
'md'
|
||||
'mkd'
|
||||
'mkdown'
|
||||
'ron'
|
||||
]
|
||||
```
|
||||
|
||||
## Writing Tests
|
||||
|
||||
Your package **should** have tests, and if they're placed in the _spec_ directory,
|
||||
they can be run by Atom.
|
||||
|
||||
Under the hood, [Jasmine](https://github.com/pivotal/jasmine) is being used to run
|
||||
to execute the tests, so you can assume that any DSL available there is available
|
||||
to your package as well.
|
254
docs/packages/creating_a_package.md
Normal file
254
docs/packages/creating_a_package.md
Normal file
@ -0,0 +1,254 @@
|
||||
# Creating Packages
|
||||
|
||||
Let's take a look at creating our first package.
|
||||
|
||||
Atom has a command you can enter that'll create a package for you:
|
||||
`package-generator:generate`. Otherwise, you can hit `meta-p`, and start typing
|
||||
"Package Generator." Once you activate this package, 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 `meta-R` to reload Atom, then hit `meta-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 keybinding
|
||||
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:
|
||||
|
||||
```cson
|
||||
'.tree-view-scroller':
|
||||
'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-scroller` 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.md).
|
||||
|
||||
Let's bring in jQuery:
|
||||
|
||||
```coffeescript
|
||||
$ = require 'jquery'
|
||||
```
|
||||
|
||||
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-meta-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 Pane
|
||||
|
||||
The next goal of this package is to append a pane 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](https://github.com/nathansobo/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. 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 UI 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.
|
||||
|
||||
You might have noticed that our two `li` elements aren't showing up. Let's set
|
||||
a color on them so that they pop. Open up `changer.css` and add this CSS:
|
||||
|
||||
```css
|
||||
ul.modified-files-list {
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
Refresh Atom, hit the key combo, and see your brilliantly white 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](http://nodejs.org/docs/latest/api/path.html) 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)
|
||||
```
|
10
docs/packages/included_libraries.md
Normal file
10
docs/packages/included_libraries.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Included Libraries
|
||||
|
||||
In addition to core node.js modules, all packages can `require` the following popular
|
||||
libraries into their packages:
|
||||
|
||||
* [SpacePen](https://github.com/nathansobo/space-pen) (as `require 'space-pen'`)
|
||||
* [jQuery](http://jquery.com/) (as `require 'jquery'`)
|
||||
* [Underscore](http://underscorejs.org/) (as `require 'underscore'`)
|
||||
|
||||
Additional libraries can be found by browsing Atom's _node_modules_ folder.
|
18
docs/packages/package_json.md
Normal file
18
docs/packages/package_json.md
Normal file
@ -0,0 +1,18 @@
|
||||
# package.json format
|
||||
|
||||
The following keys are available to your package's _package.json_ manifest file:
|
||||
|
||||
- `main` (**Required**): the path to the CoffeeScript file that's the entry point
|
||||
to your package
|
||||
- `stylesheets` (**Optional**): an Array of Strings identifying the order of the
|
||||
stylesheets your package needs to load. If not specified, stylesheets in the _stylesheets_
|
||||
directory are added alphabetically.
|
||||
- `keymaps`(**Optional**): an Array of Strings identifying the order of the
|
||||
key mappings your package needs to load. If not specified, mappings in the _keymaps_
|
||||
directory are added alphabetically.
|
||||
- `snippets` (**Optional**): an Array of Strings identifying the order of the
|
||||
snippets your package needs to load. If not specified, snippets in the _snippets_
|
||||
directory are added alphabetically.
|
||||
- `activationEvents` (**Optional**): an Array of Strings identifying events that
|
||||
trigger your package's activation. You can delay the loading of your package until
|
||||
one of these events is trigged.
|
@ -1,4 +1,4 @@
|
||||
# Authoring A Theme
|
||||
# Authoring Themes
|
||||
|
||||
If you understand CSS, you can write an Atom theme easily. Your theme can style
|
||||
Atom's user interface, specify the appearance of syntax-highlighted code, or
|
||||
@ -9,9 +9,9 @@ translate scope names to CSS classes. To theme Atom's user interface, take a
|
||||
look at the existing light and dark themes for an example. Pressing `alt-meta-i`
|
||||
and inspecting the Atom's markup directly can also be helpful.
|
||||
|
||||
The most basic theme is just a `.css` file. More complex themes occupy their own
|
||||
The most basic theme is just a _.css_ file. More complex themes occupy their own
|
||||
folder, which can contain multiple stylesheets along with an optional
|
||||
`package.cson` file containing a manifest to control their load-order:
|
||||
_package.cson_ file containing a manifest to control their load-order:
|
||||
|
||||
```text
|
||||
~/.atom/themes/
|
@ -140,7 +140,7 @@
|
||||
|
||||
- (id)initConfig {
|
||||
_isConfig = true;
|
||||
return [self initWithBootstrapScript:@"config-bootstrap" background:NO useBundleResourcePath:![self isDevMode]];
|
||||
return [self initWithBootstrapScript:@"config-bootstrap" background:NO useBundleResourcePath:![self isDevFlagSpecified]];
|
||||
}
|
||||
|
||||
- (void)addBrowserToView:(NSView *)view url:(const char *)url cefHandler:(CefRefPtr<AtomCefClient>)cefClient {
|
||||
|
29
package.json
29
package.json
@ -1,14 +1,17 @@
|
||||
{
|
||||
"name" : "atom",
|
||||
"version" : "0.0.0",
|
||||
"main" : "src/main.js",
|
||||
|
||||
"name": "atom",
|
||||
"version": "0.0.0",
|
||||
"main": "src/main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/github/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"coffee-script": "1.6.2",
|
||||
"ctags": "0.3.0",
|
||||
"oniguruma": "0.11.0",
|
||||
"mkdirp": "0.3.5",
|
||||
"git-utils": "0.15.0",
|
||||
"git-utils": "0.17.0",
|
||||
"underscore": "1.4.4",
|
||||
"d3": "3.0.8",
|
||||
"coffee-cache": "0.1.0",
|
||||
@ -17,20 +20,20 @@
|
||||
"nak": "0.2.12",
|
||||
"spellchecker": "0.3.0",
|
||||
"pathwatcher": "0.3.0",
|
||||
"keytar": "0.4.0",
|
||||
"plist": "git://github.com/nathansobo/node-plist.git",
|
||||
"space-pen": "git://github.com/nathansobo/space-pen.git",
|
||||
"space-pen": "1.0.0",
|
||||
"less": "git://github.com/nathansobo/less.js.git",
|
||||
"roaster": "0.0.3",
|
||||
"roaster": "0.0.5",
|
||||
"jqueryui-browser": "1.10.2-1",
|
||||
"optimist": "0.4.0"
|
||||
"optimist": "0.4.0",
|
||||
"season": "0.7.0",
|
||||
"humanize-plus": "1.1.0"
|
||||
},
|
||||
|
||||
"devDependencies" : {
|
||||
"biscotto" : "0.0.11"
|
||||
"devDependencies": {
|
||||
"biscotto": "0.0.11"
|
||||
},
|
||||
|
||||
"private": true,
|
||||
|
||||
"scripts": {
|
||||
"preinstall": "true"
|
||||
}
|
||||
|
@ -21,13 +21,10 @@ exit_unless_npm_exists() {
|
||||
exit_unless_xcode_exists
|
||||
exit_unless_npm_exists
|
||||
|
||||
npm install npm --silent
|
||||
|
||||
export npm_config_disturl="https://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist"
|
||||
export npm_config_target="0.10.5"
|
||||
export npm_config_arch="ia32"
|
||||
NODE_DIR="$HOME/.atom-shell-gyp"
|
||||
HOME=$NODE_DIR ./node_modules/.bin/npm install --silent
|
||||
|
||||
git submodule --quiet sync
|
||||
git submodule --quiet update --recursive --init
|
||||
|
||||
(cd vendor/apm && npm install .)
|
||||
|
||||
npm install --silent vendor/apm
|
||||
./node_modules/.bin/apm install --silent
|
||||
|
@ -18,4 +18,4 @@ node --version > /dev/null 2>&1 || {
|
||||
INPUT_FILE="${1}"
|
||||
OUTPUT_FILE="${2}"
|
||||
|
||||
node_modules/.bin/coffee script/compile-cson.coffee -- "${INPUT_FILE}" "${OUTPUT_FILE}"
|
||||
node_modules/.bin/csonc --root "${INPUT_FILE}" "${OUTPUT_FILE}"
|
||||
|
@ -1,23 +0,0 @@
|
||||
fs = require 'fs'
|
||||
{exec} = require 'child_process'
|
||||
|
||||
inputFile = process.argv[2]
|
||||
unless inputFile?.length > 0
|
||||
console.error("Input file must be first argument")
|
||||
process.exit(1)
|
||||
|
||||
outputFile = process.argv[3]
|
||||
unless outputFile?.length > 0
|
||||
console.error("Output file must be second argument")
|
||||
process.exit(1)
|
||||
|
||||
contents = fs.readFileSync(inputFile)?.toString() ? ''
|
||||
exec "node_modules/.bin/coffee -bcp #{inputFile}", (error, stdout, stderr) ->
|
||||
if error
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
json = eval(stdout.toString()) ? {}
|
||||
if json isnt Object(json)
|
||||
console.error("CSON file does not contain valid JSON")
|
||||
process.exit(1)
|
||||
fs.writeFileSync(outputFile, JSON.stringify(json, null, 2))
|
@ -1,3 +1,4 @@
|
||||
fs = require 'fs'
|
||||
fsUtils = require 'fs-utils'
|
||||
|
||||
describe "Config", ->
|
||||
@ -7,6 +8,13 @@ describe "Config", ->
|
||||
expect(config.get("foo.bar.baz")).toBe 42
|
||||
expect(config.get("bogus.key.path")).toBeUndefined()
|
||||
|
||||
it "returns a deep clone of the key path's value", ->
|
||||
config.set('value', array: [1, b: 2, 3])
|
||||
retrievedValue = config.get('value')
|
||||
retrievedValue.array[0] = 4
|
||||
retrievedValue.array[1].b = 2.1
|
||||
expect(config.get('value')).toEqual(array: [1, b: 2, 3])
|
||||
|
||||
describe ".set(keyPath, value)", ->
|
||||
it "allows a key path's value to be written", ->
|
||||
expect(config.set("foo.bar.baz", 42)).toBe 42
|
||||
@ -33,6 +41,28 @@ describe "Config", ->
|
||||
config.set('foo.changes', 1)
|
||||
expect(config.settings.foo).toEqual {}
|
||||
|
||||
describe ".pushAtKeyPath(keyPath, value)", ->
|
||||
it "pushes the given value to the array at the key path and updates observers", ->
|
||||
config.set("foo.bar.baz", ["a"])
|
||||
observeHandler = jasmine.createSpy "observeHandler"
|
||||
config.observe "foo.bar.baz", observeHandler
|
||||
observeHandler.reset()
|
||||
|
||||
expect(config.pushAtKeyPath("foo.bar.baz", "b")).toBe 2
|
||||
expect(config.get("foo.bar.baz")).toEqual ["a", "b"]
|
||||
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz")
|
||||
|
||||
describe ".removeAtKeyPath(keyPath, value)", ->
|
||||
it "removes the given value from the array at the key path and updates observers", ->
|
||||
config.set("foo.bar.baz", ["a", "b", "c"])
|
||||
observeHandler = jasmine.createSpy "observeHandler"
|
||||
config.observe "foo.bar.baz", observeHandler
|
||||
observeHandler.reset()
|
||||
|
||||
expect(config.removeAtKeyPath("foo.bar.baz", "b")).toEqual ["a", "c"]
|
||||
expect(config.get("foo.bar.baz")).toEqual ["a", "c"]
|
||||
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz")
|
||||
|
||||
describe ".getPositiveInt(keyPath, defaultValue)", ->
|
||||
it "returns the proper current or default value", ->
|
||||
config.set('editor.preferredLineLength', 0)
|
||||
@ -46,7 +76,7 @@ describe "Config", ->
|
||||
|
||||
describe ".save()", ->
|
||||
beforeEach ->
|
||||
spyOn(fsUtils, 'write')
|
||||
spyOn(fs, 'writeFileSync')
|
||||
jasmine.unspy config, 'save'
|
||||
|
||||
describe "when ~/.atom/config.json exists", ->
|
||||
@ -57,11 +87,11 @@ describe "Config", ->
|
||||
config.set("x.y.z", 3)
|
||||
config.setDefaults("a.b", e: 4, f: 5)
|
||||
|
||||
fsUtils.write.reset()
|
||||
fs.writeFileSync.reset()
|
||||
config.save()
|
||||
|
||||
expect(fsUtils.write.argsForCall[0][0]).toBe(fsUtils.join(config.configDirPath, "config.json"))
|
||||
writtenConfig = JSON.parse(fsUtils.write.argsForCall[0][1])
|
||||
expect(fs.writeFileSync.argsForCall[0][0]).toBe(fsUtils.join(config.configDirPath, "config.json"))
|
||||
writtenConfig = JSON.parse(fs.writeFileSync.argsForCall[0][1])
|
||||
expect(writtenConfig).toEqual config.settings
|
||||
|
||||
describe "when ~/.atom/config.json doesn't exist", ->
|
||||
@ -72,12 +102,12 @@ describe "Config", ->
|
||||
config.set("x.y.z", 3)
|
||||
config.setDefaults("a.b", e: 4, f: 5)
|
||||
|
||||
fsUtils.write.reset()
|
||||
fs.writeFileSync.reset()
|
||||
config.save()
|
||||
|
||||
expect(fsUtils.write.argsForCall[0][0]).toBe(fsUtils.join(config.configDirPath, "config.cson"))
|
||||
expect(fs.writeFileSync.argsForCall[0][0]).toBe(fsUtils.join(config.configDirPath, "config.cson"))
|
||||
CoffeeScript = require 'coffee-script'
|
||||
writtenConfig = CoffeeScript.eval(fsUtils.write.argsForCall[0][1], bare: true)
|
||||
writtenConfig = CoffeeScript.eval(fs.writeFileSync.argsForCall[0][1], bare: true)
|
||||
expect(writtenConfig).toEqual config.settings
|
||||
|
||||
describe ".setDefaults(keyPath, defaults)", ->
|
||||
@ -92,21 +122,6 @@ describe "Config", ->
|
||||
expect(config.get("foo.quux.x")).toBe 0
|
||||
expect(config.get("foo.quux.y")).toBe 1
|
||||
|
||||
describe ".update()", ->
|
||||
it "updates observers if a value is mutated without the use of .set", ->
|
||||
config.set("foo.bar.baz", ["a"])
|
||||
observeHandler = jasmine.createSpy "observeHandler"
|
||||
config.observe "foo.bar.baz", observeHandler
|
||||
observeHandler.reset()
|
||||
|
||||
config.get("foo.bar.baz").push("b")
|
||||
config.update()
|
||||
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz")
|
||||
observeHandler.reset()
|
||||
|
||||
config.update()
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
|
||||
describe ".observe(keyPath)", ->
|
||||
observeHandler = null
|
||||
|
||||
|
@ -10,7 +10,7 @@ describe "EditSession", ->
|
||||
|
||||
beforeEach ->
|
||||
atom.activatePackage('javascript.tmbundle', sync: true)
|
||||
editSession = project.buildEditSession('sample.js', autoIndent: false)
|
||||
editSession = project.open('sample.js', autoIndent: false)
|
||||
buffer = editSession.buffer
|
||||
lineLengths = buffer.getLines().map (line) -> line.length
|
||||
|
||||
@ -1011,194 +1011,6 @@ describe "EditSession", ->
|
||||
editSession.insertText('holy cow')
|
||||
expect(editSession.lineForScreenRow(2).fold).toBeUndefined()
|
||||
|
||||
describe "when auto-indent is enabled", ->
|
||||
describe "when a single newline is inserted", ->
|
||||
describe "when the newline is inserted on a line that starts a new level of indentation", ->
|
||||
it "auto-indents the new line to one additional level of indentation beyond the preceding line", ->
|
||||
editSession.setCursorBufferPosition([1, Infinity])
|
||||
editSession.insertText('\n', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1) + 1
|
||||
|
||||
describe "when the newline is inserted on a normal line", ->
|
||||
it "auto-indents the new line to the same level of indentation as the preceding line", ->
|
||||
editSession.setCursorBufferPosition([5, 13])
|
||||
editSession.insertText('\n', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(6)).toBe editSession.indentationForBufferRow(5)
|
||||
|
||||
describe "when text without newlines is inserted", ->
|
||||
describe "when the current line matches an auto-outdent pattern", ->
|
||||
describe "when the preceding line matches an auto-indent pattern", ->
|
||||
it "auto-decreases the indentation of the line to match that of the preceding line", ->
|
||||
editSession.setCursorBufferPosition([2, 4])
|
||||
editSession.insertText('\n', autoIndent: true)
|
||||
editSession.setCursorBufferPosition([2, 4])
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1) + 1
|
||||
editSession.insertText(' }', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1)
|
||||
|
||||
describe "when the preceding does not match an auto-indent pattern", ->
|
||||
describe "when the inserted text is whitespace", ->
|
||||
it "does not auto-decreases the indentation", ->
|
||||
editSession.setCursorBufferPosition([12, 0])
|
||||
editSession.insertText(' ', autoIndent: true)
|
||||
expect(editSession.lineForBufferRow(12)).toBe ' };'
|
||||
editSession.insertText('\t\t', autoIndent: true)
|
||||
expect(editSession.lineForBufferRow(12)).toBe ' \t\t};'
|
||||
|
||||
describe "when the inserted text is non-whitespace", ->
|
||||
it "auto-decreases the indentation of the line to be one level below that of the preceding line", ->
|
||||
editSession.setCursorBufferPosition([3, Infinity])
|
||||
editSession.insertText('\n', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(4)).toBe editSession.indentationForBufferRow(3)
|
||||
editSession.insertText(' }', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(4)).toBe editSession.indentationForBufferRow(3) - 1
|
||||
|
||||
describe "when the current line does not match an auto-outdent pattern", ->
|
||||
it "leaves the line unchanged", ->
|
||||
editSession.setCursorBufferPosition([2, 4])
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1) + 1
|
||||
editSession.insertText('foo', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1) + 1
|
||||
|
||||
describe "when the `normalizeIndent` option is true", ->
|
||||
describe "when the inserted text contains no newlines", ->
|
||||
it "does not adjust the indentation level of the text", ->
|
||||
editSession.setCursorBufferPosition([5, 2])
|
||||
editSession.insertText("foo", normalizeIndent: true)
|
||||
expect(editSession.lineForBufferRow(5)).toBe " foo current = items.shift();"
|
||||
|
||||
describe "when the inserted text contains newlines", ->
|
||||
text = null
|
||||
beforeEach ->
|
||||
editSession.setCursorBufferPosition([2, Infinity])
|
||||
text = [
|
||||
" while (true) {"
|
||||
" foo();"
|
||||
" }"
|
||||
" bar();"
|
||||
].join('\n')
|
||||
|
||||
removeLeadingWhitespace = (text) -> text.replace(/^\s*/, '')
|
||||
|
||||
describe "when the cursor is preceded only by whitespace", ->
|
||||
describe "when auto-indent is enabled", ->
|
||||
describe "when the cursor's current column is less than the suggested indent level", ->
|
||||
describe "when the indentBasis is inferred from the first line", ->
|
||||
it "indents all lines relative to the suggested indent", ->
|
||||
editSession.insertText('\n xx', autoIndent: true)
|
||||
editSession.setCursorBufferPosition([3, 1])
|
||||
editSession.insertText(text, normalizeIndent: true, autoIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(6)).toBe " bar();xx"
|
||||
|
||||
describe "when an indentBasis is provided", ->
|
||||
it "indents all lines relative to the suggested indent", ->
|
||||
editSession.insertText('\n xx')
|
||||
editSession.setCursorBufferPosition([3, 1])
|
||||
editSession.insertText(removeLeadingWhitespace(text), normalizeIndent: true, indentBasis: 2, autoIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(6)).toBe " bar();xx"
|
||||
|
||||
describe "when inserting on a line that has mixed tabs and whitespace in hard tabs mode (regression)", ->
|
||||
it "correctly indents the inserted text", ->
|
||||
editSession.softTabs = false
|
||||
buffer.setText """
|
||||
not indented
|
||||
\tmixed indented
|
||||
"""
|
||||
|
||||
editSession.setCursorBufferPosition([1, 0])
|
||||
editSession.insertText(text, normalizeIndent: true, autoIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(1)).toBe "\t\t\twhile (true) {"
|
||||
expect(editSession.lineForBufferRow(2)).toBe "\t\t\t\tfoo();"
|
||||
expect(editSession.lineForBufferRow(3)).toBe "\t\t\t}"
|
||||
expect(editSession.lineForBufferRow(4)).toBe "\t\tbar(); \tmixed indented"
|
||||
|
||||
describe "when inserting on a fractionally-indented line in hard tabs mode (regression)", ->
|
||||
it "correctly indents the inserted text", ->
|
||||
editSession.softTabs = false
|
||||
buffer.setText """
|
||||
not indented
|
||||
fractional indentation
|
||||
"""
|
||||
|
||||
editSession.setCursorBufferPosition([1, 0])
|
||||
editSession.insertText(text, normalizeIndent: true, autoIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(1)).toBe "\t\twhile (true) {"
|
||||
expect(editSession.lineForBufferRow(2)).toBe "\t\t\tfoo();"
|
||||
expect(editSession.lineForBufferRow(3)).toBe "\t\t}"
|
||||
expect(editSession.lineForBufferRow(4)).toBe "\tbar(); fractional indentation"
|
||||
|
||||
describe "when the cursor's current column is greater than the suggested indent level", ->
|
||||
describe "when the indentBasis is inferred from the first line", ->
|
||||
it "preserves the current indent level, indenting all lines relative to it", ->
|
||||
editSession.insertText('\n ')
|
||||
editSession.insertText(text, normalizeIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(6)).toBe " bar();"
|
||||
|
||||
describe "when an indentBasis is provided", ->
|
||||
it "preserves the current indent level, indenting all lines relative to it", ->
|
||||
editSession.insertText('\n ')
|
||||
editSession.insertText(removeLeadingWhitespace(text), normalizeIndent: true, indentBasis: 2)
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(6)).toBe " bar();"
|
||||
|
||||
describe "if auto-indent is disabled", ->
|
||||
describe "when the indentBasis is inferred from the first line", ->
|
||||
it "always normalizes indented lines to the cursor's current indentation level", ->
|
||||
editSession.insertText('\n ')
|
||||
editSession.insertText(text, normalizeIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(6)).toBe "bar();"
|
||||
|
||||
describe "when an indentBasis is provided", ->
|
||||
it "always normalizes indented lines to the cursor's current indentation level", ->
|
||||
editSession.insertText('\n ')
|
||||
editSession.insertText(removeLeadingWhitespace(text), normalizeIndent: true, indentBasis: 2)
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " }"
|
||||
|
||||
describe "when the cursor is preceded by non-whitespace characters", ->
|
||||
describe "when the indentBasis is inferred from the first line", ->
|
||||
it "normalizes the indentation level of all lines based on the level of the existing first line", ->
|
||||
editSession.buffer.delete([[2, 0], [2, 2]])
|
||||
editSession.insertText(text, normalizeIndent:true)
|
||||
|
||||
expect(editSession.lineForBufferRow(2)).toBe " if (items.length <= 1) return items;while (true) {"
|
||||
expect(editSession.lineForBufferRow(3)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(5)).toBe "bar();"
|
||||
|
||||
describe "when an indentBasis is provided", ->
|
||||
it "normalizes the indentation level of all lines based on the level of the existing first line", ->
|
||||
editSession.buffer.delete([[2, 0], [2, 2]])
|
||||
editSession.insertText(removeLeadingWhitespace(text), normalizeIndent:true, indentBasis: 2)
|
||||
|
||||
expect(editSession.lineForBufferRow(2)).toBe " if (items.length <= 1) return items;while (true) {"
|
||||
expect(editSession.lineForBufferRow(3)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(5)).toBe "bar();"
|
||||
|
||||
describe ".insertNewline()", ->
|
||||
describe "when there is a single cursor", ->
|
||||
describe "when the cursor is at the beginning of a line", ->
|
||||
@ -1779,17 +1591,6 @@ describe "EditSession", ->
|
||||
expect(editSession.buffer.lineForRow(0)).toBe "var first = function () {"
|
||||
expect(buffer.lineForRow(1)).toBe " var first = function(items) {"
|
||||
|
||||
it "preserves the indent level when copying and pasting multiple lines", ->
|
||||
editSession.setSelectedBufferRange([[4, 4], [7, 5]])
|
||||
editSession.copySelectedText()
|
||||
editSession.setCursorBufferPosition([10, 0])
|
||||
editSession.pasteText(autoIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(10)).toBe " while(items.length > 0) {"
|
||||
expect(editSession.lineForBufferRow(11)).toBe " current = items.shift();"
|
||||
expect(editSession.lineForBufferRow(12)).toBe " current < pivot ? left.push(current) : right.push(current);"
|
||||
expect(editSession.lineForBufferRow(13)).toBe " }"
|
||||
|
||||
describe ".indentSelectedRows()", ->
|
||||
describe "when nothing is selected", ->
|
||||
describe "when softTabs is enabled", ->
|
||||
@ -1961,7 +1762,7 @@ describe "EditSession", ->
|
||||
|
||||
it "does not explode if the current language mode has no comment regex", ->
|
||||
editSession.destroy()
|
||||
editSession = project.buildEditSession(null, autoIndent: false)
|
||||
editSession = project.open(null, autoIndent: false)
|
||||
editSession.setSelectedBufferRange([[4, 5], [4, 5]])
|
||||
editSession.toggleLineCommentsInSelection()
|
||||
expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {"
|
||||
@ -2348,13 +2149,13 @@ describe "EditSession", ->
|
||||
|
||||
describe "soft-tabs detection", ->
|
||||
it "assign soft / hard tabs based on the contents of the buffer, or uses the default if unknown", ->
|
||||
editSession = project.buildEditSession('sample.js', softTabs: false)
|
||||
editSession = project.open('sample.js', softTabs: false)
|
||||
expect(editSession.softTabs).toBeTruthy()
|
||||
|
||||
editSession = project.buildEditSession('sample-with-tabs.coffee', softTabs: true)
|
||||
editSession = project.open('sample-with-tabs.coffee', softTabs: true)
|
||||
expect(editSession.softTabs).toBeFalsy()
|
||||
|
||||
editSession = project.buildEditSession(null, softTabs: false)
|
||||
editSession = project.open(null, softTabs: false)
|
||||
expect(editSession.softTabs).toBeFalsy()
|
||||
|
||||
describe ".indentLevelForLine(line)", ->
|
||||
@ -2383,7 +2184,7 @@ describe "EditSession", ->
|
||||
jsGrammar = syntax.selectGrammar('a.js')
|
||||
syntax.removeGrammar(jsGrammar)
|
||||
|
||||
editSession = project.buildEditSession('sample.js', autoIndent: false)
|
||||
editSession = project.open('sample.js', autoIndent: false)
|
||||
expect(editSession.getGrammar()).toBe syntax.nullGrammar
|
||||
expect(editSession.lineForScreenRow(0).tokens.length).toBe 1
|
||||
|
||||
@ -2392,82 +2193,219 @@ describe "EditSession", ->
|
||||
expect(editSession.lineForScreenRow(0).tokens.length).toBeGreaterThan 1
|
||||
|
||||
describe "auto-indent", ->
|
||||
copyText = (text, {startColumn}={}) ->
|
||||
startColumn ?= 0
|
||||
editSession.setCursorBufferPosition([0, 0])
|
||||
editSession.insertText(text)
|
||||
numberOfNewlines = text.match(/\n/g)?.length
|
||||
endColumn = text.match(/[^\n]*$/)[0]?.length
|
||||
editSession.getSelection().setBufferRange([[0,startColumn], [numberOfNewlines,endColumn]])
|
||||
editSession.cutSelectedText()
|
||||
|
||||
describe "editor.autoIndent", ->
|
||||
it "auto-indents newlines if editor.autoIndent is true", ->
|
||||
config.set("editor.autoIndent", undefined)
|
||||
editSession.setCursorBufferPosition([1, 30])
|
||||
editSession.insertText("\n")
|
||||
expect(editSession.lineForBufferRow(2)).toBe " "
|
||||
describe "when editor.autoIndent is false (default)", ->
|
||||
describe "when `indent` is triggered", ->
|
||||
it "does not auto-indent the line", ->
|
||||
editSession.setCursorBufferPosition([1, 30])
|
||||
editSession.insertText("\n ")
|
||||
expect(editSession.lineForBufferRow(2)).toBe " "
|
||||
|
||||
it "does not auto-indent newlines if editor.autoIndent is false", ->
|
||||
config.set("editor.autoIndent", false)
|
||||
editSession.setCursorBufferPosition([1, 30])
|
||||
editSession.insertText("\n")
|
||||
expect(editSession.lineForBufferRow(2)).toBe ""
|
||||
config.set("editor.autoIndent", false)
|
||||
editSession.indent()
|
||||
expect(editSession.lineForBufferRow(2)).toBe " "
|
||||
|
||||
it "auto-indents calls to `indent` if editor.autoIndent is true", ->
|
||||
config.set("editor.autoIndent", true)
|
||||
editSession.setCursorBufferPosition([1, 30])
|
||||
editSession.insertText("\n ")
|
||||
expect(editSession.lineForBufferRow(2)).toBe " "
|
||||
editSession.indent()
|
||||
expect(editSession.lineForBufferRow(2)).toBe " "
|
||||
describe "when editor.autoIndent is true", ->
|
||||
beforeEach ->
|
||||
config.set("editor.autoIndent", true)
|
||||
|
||||
it "does not auto-indents calls to `indent` if editor.autoIndent is false", ->
|
||||
config.set("editor.autoIndent", false)
|
||||
editSession.setCursorBufferPosition([1, 30])
|
||||
editSession.insertText("\n ")
|
||||
expect(editSession.lineForBufferRow(2)).toBe " "
|
||||
editSession.indent()
|
||||
expect(editSession.lineForBufferRow(2)).toBe " "
|
||||
describe "when `indent` is triggered", ->
|
||||
it "auto-indents the line", ->
|
||||
editSession.setCursorBufferPosition([1, 30])
|
||||
editSession.insertText("\n ")
|
||||
expect(editSession.lineForBufferRow(2)).toBe " "
|
||||
|
||||
it "auto-indents selection when autoIndent is called", ->
|
||||
editSession.setCursorBufferPosition([2, 0])
|
||||
editSession.insertText(" 0\n 2\n4\n")
|
||||
config.set("editor.autoIndent", true)
|
||||
editSession.indent()
|
||||
expect(editSession.lineForBufferRow(2)).toBe " "
|
||||
|
||||
editSession.setSelectedBufferRange([[2, 0], [4, 0]])
|
||||
editSession.autoIndentSelectedRows()
|
||||
describe "when a newline is added", ->
|
||||
describe "when the line preceding the newline adds a new level of indentation", ->
|
||||
it "indents the newline to one additional level of indentation beyond the preceding line", ->
|
||||
editSession.setCursorBufferPosition([1, Infinity])
|
||||
editSession.insertText('\n')
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1) + 1
|
||||
|
||||
expect(editSession.lineForBufferRow(2)).toBe " 0"
|
||||
expect(editSession.lineForBufferRow(3)).toBe " 2"
|
||||
expect(editSession.lineForBufferRow(4)).toBe "4"
|
||||
describe "when the line preceding the newline does't add a level of indentation", ->
|
||||
it "indents the new line to the same level a as the preceding line", ->
|
||||
editSession.setCursorBufferPosition([5, 13])
|
||||
editSession.insertText('\n')
|
||||
expect(editSession.indentationForBufferRow(6)).toBe editSession.indentationForBufferRow(5)
|
||||
|
||||
describe "when the line preceding the newline is a comment", ->
|
||||
it "maintains the indent of the commented line", ->
|
||||
editSession.setCursorBufferPosition([0, 0])
|
||||
editSession.insertText(' //')
|
||||
editSession.setCursorBufferPosition([0, Infinity])
|
||||
editSession.insertText('\n')
|
||||
expect(editSession.indentationForBufferRow(1)).toBe 2
|
||||
|
||||
it "does not indent the line preceding the newline", ->
|
||||
editSession.setCursorBufferPosition([2, 0])
|
||||
editSession.insertText(' var this-line-should-be-indented-more\n')
|
||||
expect(editSession.indentationForBufferRow(1)).toBe 1
|
||||
|
||||
config.set("editor.autoIndent", true)
|
||||
editSession.setCursorBufferPosition([2, Infinity])
|
||||
editSession.insertText('\n')
|
||||
expect(editSession.indentationForBufferRow(1)).toBe 1
|
||||
expect(editSession.indentationForBufferRow(2)).toBe 1
|
||||
|
||||
describe "when inserted text matches a decrease indent pattern", ->
|
||||
describe "when the preceding line matches an increase indent pattern", ->
|
||||
it "decreases the indentation to match that of the preceding line", ->
|
||||
editSession.setCursorBufferPosition([1, Infinity])
|
||||
editSession.insertText('\n')
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1) + 1
|
||||
editSession.insertText('}')
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1)
|
||||
|
||||
describe "when the preceding line doesn't match an increase indent pattern", ->
|
||||
it "decreases the indentation to be one level below that of the preceding line", ->
|
||||
editSession.setCursorBufferPosition([3, Infinity])
|
||||
editSession.insertText('\n ')
|
||||
expect(editSession.indentationForBufferRow(4)).toBe editSession.indentationForBufferRow(3)
|
||||
editSession.insertText('}')
|
||||
expect(editSession.indentationForBufferRow(4)).toBe editSession.indentationForBufferRow(3) - 1
|
||||
|
||||
it "doesn't break when decreasing the indentation on a row that has no indentation", ->
|
||||
editSession.setCursorBufferPosition([12, Infinity])
|
||||
editSession.insertText("\n}; # too many closing brackets!")
|
||||
expect(editSession.lineForBufferRow(13)).toBe "}; # too many closing brackets!"
|
||||
|
||||
describe "when inserted text does not match a decrease indent pattern", ->
|
||||
it "does not the indentation", ->
|
||||
editSession.setCursorBufferPosition([12, 0])
|
||||
editSession.insertText(' ')
|
||||
expect(editSession.lineForBufferRow(12)).toBe ' };'
|
||||
editSession.insertText('\t\t')
|
||||
expect(editSession.lineForBufferRow(12)).toBe ' \t\t};'
|
||||
|
||||
describe "when the current line does not match a decrease indent pattern", ->
|
||||
it "leaves the line unchanged", ->
|
||||
editSession.setCursorBufferPosition([2, 4])
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1) + 1
|
||||
editSession.insertText('foo')
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1) + 1
|
||||
|
||||
describe "editor.autoIndentOnPaste", ->
|
||||
it "does not auto-indent pasted text by default", ->
|
||||
editSession.setCursorBufferPosition([2, 0])
|
||||
editSession.insertText("0\n 2\n 4\n")
|
||||
editSession.getSelection().setBufferRange([[2,0], [5,0]])
|
||||
editSession.cutSelectedText()
|
||||
describe "when the text contains multiple lines", ->
|
||||
beforeEach ->
|
||||
copyText("function() {\ninside=true\n}\n i=1\n")
|
||||
editSession.setCursorBufferPosition([2, 0])
|
||||
|
||||
it "does not auto-indent pasted text by default", ->
|
||||
editSession.pasteText()
|
||||
expect(editSession.lineForBufferRow(2)).toBe "function() {"
|
||||
expect(editSession.lineForBufferRow(3)).toBe "inside=true"
|
||||
expect(editSession.lineForBufferRow(4)).toBe "}"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " i=1"
|
||||
|
||||
it "auto-indents pasted text when editor.autoIndentOnPaste is true", ->
|
||||
config.set("editor.autoIndentOnPaste", true)
|
||||
editSession.pasteText()
|
||||
expect(editSession.lineForBufferRow(2)).toBe " function() {"
|
||||
expect(editSession.lineForBufferRow(3)).toBe " inside=true"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " i=1"
|
||||
|
||||
describe "when the text contains no newlines", ->
|
||||
it "increaseses indent of pasted text when editor.autoIndentOnPaste is true", ->
|
||||
copyText("var number")
|
||||
editSession.setCursorBufferPosition([10, 0])
|
||||
config.set("editor.autoIndentOnPaste", true)
|
||||
editSession.pasteText()
|
||||
expect(editSession.lineForBufferRow(10)).toBe " var number"
|
||||
|
||||
it "decreaseses indent of pasted text when editor.autoIndentOnPaste is true", ->
|
||||
copyText(" var number")
|
||||
editSession.setCursorBufferPosition([10, 0])
|
||||
config.set("editor.autoIndentOnPaste", true)
|
||||
editSession.pasteText()
|
||||
expect(editSession.lineForBufferRow(10)).toBe " var number"
|
||||
|
||||
describe "editor.normalizeIndentOnPaste", ->
|
||||
beforeEach ->
|
||||
config.set('editor.normalizeIndentOnPaste', true)
|
||||
|
||||
it "does not normalize the indentation level of the text when editor.autoIndentOnPaste is true", ->
|
||||
copyText(" function() {\nvar cool = 1;\n }\n")
|
||||
config.set('editor.autoIndentOnPaste', true)
|
||||
editSession.setCursorBufferPosition([5, ])
|
||||
editSession.pasteText()
|
||||
expect(editSession.lineForBufferRow(2)).toBe "0"
|
||||
expect(editSession.lineForBufferRow(3)).toBe " 2"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " 4"
|
||||
|
||||
it "auto-indents pasted text when editor.autoIndentOnPaste is true", ->
|
||||
config.set("editor.autoIndentOnPaste", true)
|
||||
editSession.setCursorBufferPosition([2, 0])
|
||||
editSession.insertText("0\n 2\n 4\n")
|
||||
editSession.getSelection().setBufferRange([[2,0], [5,0]])
|
||||
editSession.cutSelectedText()
|
||||
expect(editSession.lineForBufferRow(5)).toBe " function() {"
|
||||
expect(editSession.lineForBufferRow(6)).toBe " var cool = 1;"
|
||||
expect(editSession.lineForBufferRow(7)).toBe " }"
|
||||
|
||||
it "does not normalize the indentation level of the text when editor.normalizeIndentOnPaste is false", ->
|
||||
copyText(" function() {\nvar cool = 1;\n }\n")
|
||||
config.set('editor.normalizeIndentOnPaste', false)
|
||||
editSession.setCursorBufferPosition([5, 2])
|
||||
editSession.pasteText()
|
||||
expect(editSession.lineForBufferRow(2)).toBe " 0"
|
||||
expect(editSession.lineForBufferRow(3)).toBe " 2"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " 4"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " function() {"
|
||||
expect(editSession.lineForBufferRow(6)).toBe "var cool = 1;"
|
||||
expect(editSession.lineForBufferRow(7)).toBe " }"
|
||||
|
||||
describe ".autoDecreaseIndentForRow()", ->
|
||||
it "doesn't outdent the first and only row", ->
|
||||
editSession.selectAll()
|
||||
editSession.insertText("}")
|
||||
editSession.autoDecreaseIndentForRow(0)
|
||||
expect(editSession.lineForBufferRow(0)).toBe "}"
|
||||
describe "when the inserted text contains no newlines", ->
|
||||
it "does not adjust the indentation level of the text", ->
|
||||
editSession.setCursorBufferPosition([5, 2])
|
||||
editSession.insertText("foo", indentBasis: 5)
|
||||
expect(editSession.lineForBufferRow(5)).toBe " foo current = items.shift();"
|
||||
|
||||
it "doesn't outdent a row that is already fully outdented", ->
|
||||
editSession.selectAll()
|
||||
editSession.insertText("var i;\n}")
|
||||
editSession.autoDecreaseIndentForRow(1)
|
||||
expect(editSession.lineForBufferRow(1)).toBe "}"
|
||||
describe "when the inserted text contains newlines", ->
|
||||
describe "when copied text includes whitespace on first line", ->
|
||||
describe "when cursor is preceded by whitespace and followed non-whitespace", ->
|
||||
it "normalizes indented lines to the cursor's current indentation level", ->
|
||||
copyText(" while (true) {\n foo();\n }\n", {startColumn: 4})
|
||||
editSession.setCursorBufferPosition([3, 4])
|
||||
editSession.pasteText()
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(6)).toBe "var pivot = items.shift(), current, left = [], right = [];"
|
||||
|
||||
describe "when cursor is preceded by whitespace and followed by whitespace", ->
|
||||
it "normalizes indented lines to the cursor's current indentation level", ->
|
||||
copyText(" while (true) {\n foo();\n }\n", {startColumn: 0})
|
||||
editSession.setCursorBufferPosition([3, 4])
|
||||
editSession.pasteText()
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(6)).toBe "var pivot = items.shift(), current, left = [], right = [];"
|
||||
|
||||
describe "when the cursor is preceded by non-whitespace characters", ->
|
||||
it "normalizes the indentation level of all lines based on the level of the existing first line", ->
|
||||
copyText(" while (true) {\n foo();\n }\n", {startColumn: 0})
|
||||
editSession.setCursorBufferPosition([1, Infinity])
|
||||
editSession.pasteText()
|
||||
|
||||
expect(editSession.lineForBufferRow(1)).toBe " var sort = function(items) { while (true) {"
|
||||
expect(editSession.lineForBufferRow(2)).toBe " foo();"
|
||||
expect(editSession.lineForBufferRow(3)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(4)).toBe ""
|
||||
|
||||
it "autoIndentSelectedRows auto-indents the selection", ->
|
||||
editSession.setCursorBufferPosition([2, 0])
|
||||
editSession.insertText("function() {\ninside=true\n}\n i=1\n")
|
||||
editSession.getSelection().setBufferRange([[2,0], [6,0]])
|
||||
editSession.autoIndentSelectedRows()
|
||||
|
||||
expect(editSession.lineForBufferRow(2)).toBe " function() {"
|
||||
expect(editSession.lineForBufferRow(3)).toBe " inside=true"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " }"
|
||||
expect(editSession.lineForBufferRow(5)).toBe " i=1"
|
||||
|
||||
describe ".destroy()", ->
|
||||
it "destroys all markers associated with the edit session", ->
|
||||
@ -2517,7 +2455,7 @@ describe "EditSession", ->
|
||||
expect(editSession.shouldPromptToSave()).toBeFalsy()
|
||||
buffer.setText('changed')
|
||||
expect(editSession.shouldPromptToSave()).toBeTruthy()
|
||||
editSession2 = project.buildEditSession('sample.js', autoIndent: false)
|
||||
editSession2 = project.open('sample.js', autoIndent: false)
|
||||
expect(editSession.shouldPromptToSave()).toBeFalsy()
|
||||
editSession2.destroy()
|
||||
expect(editSession.shouldPromptToSave()).toBeTruthy()
|
||||
|
@ -15,7 +15,7 @@ describe "Editor", ->
|
||||
beforeEach ->
|
||||
atom.activatePackage('text.tmbundle', sync: true)
|
||||
atom.activatePackage('javascript.tmbundle', sync: true)
|
||||
editSession = project.buildEditSession('sample.js')
|
||||
editSession = project.open('sample.js')
|
||||
buffer = editSession.buffer
|
||||
editor = new Editor(editSession)
|
||||
editor.lineOverdraw = 2
|
||||
@ -38,7 +38,7 @@ describe "Editor", ->
|
||||
cachedCharWidth
|
||||
|
||||
calcDimensions = ->
|
||||
editorForMeasurement = new Editor(editSession: project.buildEditSession('sample.js'))
|
||||
editorForMeasurement = new Editor(editSession: project.open('sample.js'))
|
||||
editorForMeasurement.attachToDom()
|
||||
cachedLineHeight = editorForMeasurement.lineHeight
|
||||
cachedCharWidth = editorForMeasurement.charWidth
|
||||
@ -85,7 +85,7 @@ describe "Editor", ->
|
||||
it "triggers an alert", ->
|
||||
path = "/tmp/atom-changed-file.txt"
|
||||
fsUtils.write(path, "")
|
||||
editSession = project.buildEditSession(path)
|
||||
editSession = project.open(path)
|
||||
editor.edit(editSession)
|
||||
editor.insertText("now the buffer is modified")
|
||||
|
||||
@ -111,7 +111,7 @@ describe "Editor", ->
|
||||
[newEditSession, newBuffer] = []
|
||||
|
||||
beforeEach ->
|
||||
newEditSession = project.buildEditSession('two-hundred.txt')
|
||||
newEditSession = project.open('two-hundred.txt')
|
||||
newBuffer = newEditSession.buffer
|
||||
|
||||
it "updates the rendered lines, cursors, selections, scroll position, and event subscriptions to match the given edit session", ->
|
||||
@ -149,7 +149,7 @@ describe "Editor", ->
|
||||
it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
|
||||
path = "/tmp/atom-changed-file.txt"
|
||||
fsUtils.write(path, "")
|
||||
tempEditSession = project.buildEditSession(path)
|
||||
tempEditSession = project.open(path)
|
||||
editor.edit(tempEditSession)
|
||||
tempEditSession.insertText("a buffer change")
|
||||
|
||||
@ -243,7 +243,7 @@ describe "Editor", ->
|
||||
it "emits event when editor receives a new buffer", ->
|
||||
eventHandler = jasmine.createSpy('eventHandler')
|
||||
editor.on 'editor:path-changed', eventHandler
|
||||
editor.edit(project.buildEditSession(path))
|
||||
editor.edit(project.open(path))
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
|
||||
it "stops listening to events on previously set buffers", ->
|
||||
@ -251,7 +251,7 @@ describe "Editor", ->
|
||||
oldBuffer = editor.getBuffer()
|
||||
editor.on 'editor:path-changed', eventHandler
|
||||
|
||||
editor.edit(project.buildEditSession(path))
|
||||
editor.edit(project.open(path))
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
|
||||
eventHandler.reset()
|
||||
@ -1399,7 +1399,7 @@ describe "Editor", ->
|
||||
|
||||
describe "when autoscrolling at the end of the document", ->
|
||||
it "renders lines properly", ->
|
||||
editor.edit(project.buildEditSession('two-hundred.txt'))
|
||||
editor.edit(project.open('two-hundred.txt'))
|
||||
editor.attachToDom(heightInLines: 5.5)
|
||||
|
||||
expect(editor.renderedLines.find('.line').length).toBe 8
|
||||
@ -1624,7 +1624,7 @@ describe "Editor", ->
|
||||
expect(editor.bufferPositionForScreenPosition(editor.getCursorScreenPosition())).toEqual [3, 60]
|
||||
|
||||
it "does not wrap the lines of any newly assigned buffers", ->
|
||||
otherEditSession = project.buildEditSession()
|
||||
otherEditSession = project.open()
|
||||
otherEditSession.buffer.setText([1..100].join(''))
|
||||
editor.edit(otherEditSession)
|
||||
expect(editor.renderedLines.find('.line').length).toBe(1)
|
||||
@ -1660,7 +1660,7 @@ describe "Editor", ->
|
||||
expect(editor.getCursorScreenPosition()).toEqual [11, 0]
|
||||
|
||||
it "calls .setSoftWrapColumn() when the editor is attached because now its dimensions are available to calculate it", ->
|
||||
otherEditor = new Editor(editSession: project.buildEditSession('sample.js'))
|
||||
otherEditor = new Editor(editSession: project.open('sample.js'))
|
||||
spyOn(otherEditor, 'setSoftWrapColumn')
|
||||
|
||||
otherEditor.setSoftWrap(true)
|
||||
@ -1764,7 +1764,7 @@ describe "Editor", ->
|
||||
|
||||
describe "when the switching from an edit session for a long buffer to an edit session for a short buffer", ->
|
||||
it "updates the line numbers to reflect the shorter buffer", ->
|
||||
emptyEditSession = project.buildEditSession(null)
|
||||
emptyEditSession = project.open(null)
|
||||
editor.edit(emptyEditSession)
|
||||
expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 1
|
||||
|
||||
@ -1926,7 +1926,7 @@ describe "Editor", ->
|
||||
|
||||
describe "folding", ->
|
||||
beforeEach ->
|
||||
editSession = project.buildEditSession('two-hundred.txt')
|
||||
editSession = project.open('two-hundred.txt')
|
||||
buffer = editSession.buffer
|
||||
editor.edit(editSession)
|
||||
editor.attachToDom()
|
||||
@ -2064,7 +2064,7 @@ describe "Editor", ->
|
||||
beforeEach ->
|
||||
path = project.resolve('git/working-dir/file.txt')
|
||||
originalPathText = fsUtils.read(path)
|
||||
editor.edit(project.buildEditSession(path))
|
||||
editor.edit(project.open(path))
|
||||
|
||||
afterEach ->
|
||||
fsUtils.write(path, originalPathText)
|
||||
@ -2084,7 +2084,6 @@ describe "Editor", ->
|
||||
runs ->
|
||||
expect(editor.getText()).toBe(originalPathText)
|
||||
|
||||
|
||||
describe ".pixelPositionForBufferPosition(position)", ->
|
||||
describe "when the editor is detached", ->
|
||||
it "returns top and left values of 0", ->
|
||||
@ -2186,7 +2185,7 @@ describe "Editor", ->
|
||||
fsUtils.remove(path) if fsUtils.exists(path)
|
||||
|
||||
it "updates all the rendered lines when the grammar changes", ->
|
||||
editor.edit(project.buildEditSession(path))
|
||||
editor.edit(project.open(path))
|
||||
expect(editor.getGrammar().name).toBe 'Plain Text'
|
||||
syntax.setGrammarOverrideForPath(path, 'source.js')
|
||||
editor.reloadGrammar()
|
||||
@ -2206,7 +2205,7 @@ describe "Editor", ->
|
||||
expect(editor.getGrammar().name).toBe 'JavaScript'
|
||||
|
||||
it "emits an editor:grammar-changed event when updated", ->
|
||||
editor.edit(project.buildEditSession(path))
|
||||
editor.edit(project.open(path))
|
||||
|
||||
eventHandler = jasmine.createSpy('eventHandler')
|
||||
editor.on('editor:grammar-changed', eventHandler)
|
||||
@ -2620,7 +2619,6 @@ describe "Editor", ->
|
||||
editor.getPane().destroyActiveItem()
|
||||
expect(willBeRemovedHandler).toHaveBeenCalled()
|
||||
|
||||
|
||||
describe "when setInvisibles is toggled (regression)", ->
|
||||
it "renders inserted newlines properly", ->
|
||||
editor.setShowInvisibles(true)
|
||||
|
@ -214,7 +214,7 @@ describe "Git", ->
|
||||
expect(repo.isStatusNew(statuses[newPath])).toBeTruthy()
|
||||
expect(repo.isStatusModified(statuses[modifiedPath])).toBeTruthy()
|
||||
|
||||
it "only starts a single web worker at a time and schedules a restart if one is already running", =>
|
||||
it "only starts a single task at a time and schedules a restart if one is already running", =>
|
||||
fsUtils.write(modifiedPath, 'making this path modified')
|
||||
statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.on 'statuses-changed', statusHandler
|
||||
|
@ -11,7 +11,7 @@ describe "LanguageMode", ->
|
||||
describe "javascript", ->
|
||||
beforeEach ->
|
||||
atom.activatePackage('javascript.tmbundle', sync: true)
|
||||
editSession = project.buildEditSession('sample.js', autoIndent: false)
|
||||
editSession = project.open('sample.js', autoIndent: false)
|
||||
{ buffer, languageMode } = editSession
|
||||
|
||||
describe ".toggleLineCommentsForBufferRows(start, end)", ->
|
||||
@ -53,7 +53,7 @@ describe "LanguageMode", ->
|
||||
describe "coffeescript", ->
|
||||
beforeEach ->
|
||||
atom.activatePackage('coffee-script-tmbundle', sync: true)
|
||||
editSession = project.buildEditSession('coffee.coffee', autoIndent: false)
|
||||
editSession = project.open('coffee.coffee', autoIndent: false)
|
||||
{ buffer, languageMode } = editSession
|
||||
|
||||
describe ".toggleLineCommentsForBufferRows(start, end)", ->
|
||||
@ -89,7 +89,7 @@ describe "LanguageMode", ->
|
||||
describe "css", ->
|
||||
beforeEach ->
|
||||
atom.activatePackage('css.tmbundle', sync: true)
|
||||
editSession = project.buildEditSession('css.css', autoIndent: false)
|
||||
editSession = project.open('css.css', autoIndent: false)
|
||||
{ buffer, languageMode } = editSession
|
||||
|
||||
describe ".toggleLineCommentsForBufferRows(start, end)", ->
|
||||
|
@ -10,8 +10,8 @@ describe "Pane", ->
|
||||
container = new PaneContainer
|
||||
view1 = $$ -> @div id: 'view-1', tabindex: -1, 'View 1'
|
||||
view2 = $$ -> @div id: 'view-2', tabindex: -1, 'View 2'
|
||||
editSession1 = project.buildEditSession('sample.js')
|
||||
editSession2 = project.buildEditSession('sample.txt')
|
||||
editSession1 = project.open('sample.js')
|
||||
editSession2 = project.open('sample.txt')
|
||||
pane = new Pane(view1, editSession1, view2, editSession2)
|
||||
container.append(pane)
|
||||
|
||||
|
@ -9,8 +9,8 @@ describe "Project", ->
|
||||
|
||||
describe "when an edit session is destroyed", ->
|
||||
it "removes edit session and calls destroy on buffer (if buffer is not referenced by other edit sessions)", ->
|
||||
editSession = project.buildEditSession("a")
|
||||
anotherEditSession = project.buildEditSession("a")
|
||||
editSession = project.open("a")
|
||||
anotherEditSession = project.open("a")
|
||||
|
||||
expect(project.editSessions.length).toBe 2
|
||||
expect(editSession.buffer).toBe anotherEditSession.buffer
|
||||
@ -26,13 +26,13 @@ describe "Project", ->
|
||||
path = project.resolve('a')
|
||||
project.setPath(undefined)
|
||||
expect(project.getPath()).toBeUndefined()
|
||||
editSession = project.buildEditSession()
|
||||
editSession = project.open()
|
||||
editSession.saveAs('/tmp/atom-test-save-sets-project-path')
|
||||
expect(project.getPath()).toBe '/tmp'
|
||||
fsUtils.remove('/tmp/atom-test-save-sets-project-path')
|
||||
|
||||
describe ".buildEditSession(path)", ->
|
||||
[absolutePath, newBufferHandler, newEditSessionHandler] = []
|
||||
describe ".open(path)", ->
|
||||
[fooOpener, barOpener, absolutePath, newBufferHandler, newEditSessionHandler] = []
|
||||
beforeEach ->
|
||||
absolutePath = fsUtils.resolveOnLoadPath('fixtures/dir/a')
|
||||
newBufferHandler = jasmine.createSpy('newBufferHandler')
|
||||
@ -40,35 +40,50 @@ describe "Project", ->
|
||||
newEditSessionHandler = jasmine.createSpy('newEditSessionHandler')
|
||||
project.on 'edit-session-created', newEditSessionHandler
|
||||
|
||||
describe "when given an absolute path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = project.buildEditSession(absolutePath)
|
||||
expect(editSession.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
fooOpener = (path, options) -> { foo: path, options } if path?.match(/\.foo/)
|
||||
barOpener = (path) -> { bar: path } if path?.match(/^bar:\/\//)
|
||||
Project.registerOpener(fooOpener)
|
||||
Project.registerOpener(barOpener)
|
||||
|
||||
describe "when given a relative path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = project.buildEditSession('a')
|
||||
expect(editSession.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
afterEach ->
|
||||
Project.unregisterOpener(fooOpener)
|
||||
Project.unregisterOpener(barOpener)
|
||||
|
||||
describe "when passed the path to a buffer that has already been opened", ->
|
||||
it "returns a new edit session containing previously opened buffer and emits a 'edit-session-created' event", ->
|
||||
editSession = project.buildEditSession(absolutePath)
|
||||
newBufferHandler.reset()
|
||||
expect(project.buildEditSession(absolutePath).buffer).toBe editSession.buffer
|
||||
expect(project.buildEditSession('a').buffer).toBe editSession.buffer
|
||||
expect(newBufferHandler).not.toHaveBeenCalled()
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
describe "when passed a path that doesn't match a custom opener", ->
|
||||
describe "when given an absolute path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = project.open(absolutePath)
|
||||
expect(editSession.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when not passed a path", ->
|
||||
it "returns a new edit session and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = project.buildEditSession()
|
||||
expect(editSession.buffer.getPath()).toBeUndefined()
|
||||
expect(newBufferHandler).toHaveBeenCalledWith(editSession.buffer)
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
describe "when given a relative path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = project.open('a')
|
||||
expect(editSession.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when passed the path to a buffer that has already been opened", ->
|
||||
it "returns a new edit session containing previously opened buffer and emits a 'edit-session-created' event", ->
|
||||
editSession = project.open(absolutePath)
|
||||
newBufferHandler.reset()
|
||||
expect(project.open(absolutePath).buffer).toBe editSession.buffer
|
||||
expect(project.open('a').buffer).toBe editSession.buffer
|
||||
expect(newBufferHandler).not.toHaveBeenCalled()
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when not passed a path", ->
|
||||
it "returns a new edit session and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = project.open()
|
||||
expect(editSession.buffer.getPath()).toBeUndefined()
|
||||
expect(newBufferHandler).toHaveBeenCalledWith(editSession.buffer)
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when passed a path that matches a custom opener", ->
|
||||
it "returns the resource returned by the custom opener", ->
|
||||
expect(project.open("a.foo", hey: "there")).toEqual { foo: "a.foo", options: {hey: "there"} }
|
||||
expect(project.open("bar://baz")).toEqual { bar: "bar://baz" }
|
||||
|
||||
describe ".bufferForPath(path)", ->
|
||||
describe "when opening a previously opened path", ->
|
||||
@ -84,12 +99,17 @@ describe "Project", ->
|
||||
buffer = project.bufferForPath("a").retain().release()
|
||||
expect(project.bufferForPath("a").retain().release()).not.toBe buffer
|
||||
|
||||
describe ".resolve(path)", ->
|
||||
it "returns an absolute path based on the project's root", ->
|
||||
absolutePath = fsUtils.resolveOnLoadPath('fixtures/dir/a')
|
||||
expect(project.resolve('a')).toBe absolutePath
|
||||
expect(project.resolve(absolutePath + '/../a')).toBe absolutePath
|
||||
expect(project.resolve('a/../a')).toBe absolutePath
|
||||
describe ".resolve(uri)", ->
|
||||
describe "when passed an absolute or relative path", ->
|
||||
it "returns an absolute path based on the project's root", ->
|
||||
absolutePath = fsUtils.resolveOnLoadPath('fixtures/dir/a')
|
||||
expect(project.resolve('a')).toBe absolutePath
|
||||
expect(project.resolve(absolutePath + '/../a')).toBe absolutePath
|
||||
expect(project.resolve('a/../a')).toBe absolutePath
|
||||
|
||||
describe "when passed a uri with a scheme", ->
|
||||
it "does not modify uris that begin with a scheme", ->
|
||||
expect(project.resolve('http://zombo.com')).toBe 'http://zombo.com'
|
||||
|
||||
describe ".relativize(path)", ->
|
||||
it "returns an relative path based on the project's root", ->
|
||||
@ -160,8 +180,7 @@ describe "Project", ->
|
||||
|
||||
it "ignores ignored.txt file", ->
|
||||
paths = null
|
||||
config.get("core.ignoredNames").push("ignored.txt")
|
||||
config.update()
|
||||
config.pushAtKeyPath("core.ignoredNames", "ignored.txt")
|
||||
waitsForPromise ->
|
||||
project.getFilePaths().done (foundPaths) -> paths = foundPaths
|
||||
|
||||
@ -301,6 +320,22 @@ describe "Project", ->
|
||||
expect(paths[0]).toBe filePath
|
||||
expect(matches.length).toBe 1
|
||||
|
||||
it "excludes values in core.ignoredNames", ->
|
||||
projectPath = '/tmp/atom-tests/folder-with-dot-git/.git'
|
||||
filePath = fsUtils.join(projectPath, 'test.txt')
|
||||
fsUtils.write(filePath, 'match this')
|
||||
project.setPath(projectPath)
|
||||
paths = []
|
||||
matches = []
|
||||
waitsForPromise ->
|
||||
project.scan /match/, ({path, match, range}) ->
|
||||
paths.push(path)
|
||||
matches.push(match)
|
||||
|
||||
runs ->
|
||||
expect(paths.length).toBe 0
|
||||
expect(matches.length).toBe 0
|
||||
|
||||
describe "serialization", ->
|
||||
it "restores the project path", ->
|
||||
newProject = Project.deserialize(project.serialize())
|
||||
|
@ -46,10 +46,10 @@ describe "RootView", ->
|
||||
pane2 = pane1.splitRight()
|
||||
pane3 = pane2.splitRight()
|
||||
pane4 = pane2.splitDown()
|
||||
pane2.showItem(project.buildEditSession('b'))
|
||||
pane3.showItem(project.buildEditSession('../sample.js'))
|
||||
pane2.showItem(project.open('b'))
|
||||
pane3.showItem(project.open('../sample.js'))
|
||||
pane3.activeItem.setCursorScreenPosition([2, 4])
|
||||
pane4.showItem(project.buildEditSession('../sample.txt'))
|
||||
pane4.showItem(project.open('../sample.txt'))
|
||||
pane4.activeItem.setCursorScreenPosition([0, 2])
|
||||
pane2.focus()
|
||||
|
||||
|
@ -198,11 +198,11 @@ describe "TextMateGrammar", ->
|
||||
{tokens} = grammar.tokenizeLine('%Q+matz had some #{%Q-crazy ideas-} for ruby syntax+ # damn.')
|
||||
expect(tokens[0]).toEqual value: '%Q+', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.begin.ruby"]
|
||||
expect(tokens[1]).toEqual value: 'matz had some ', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby"]
|
||||
expect(tokens[2]).toEqual value: '#{', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","source.ruby.embedded.source","punctuation.section.embedded.ruby"]
|
||||
expect(tokens[3]).toEqual value: '%Q-', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","source.ruby.embedded.source","string.quoted.other.literal.upper.ruby","punctuation.definition.string.begin.ruby"]
|
||||
expect(tokens[4]).toEqual value: 'crazy ideas', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","source.ruby.embedded.source","string.quoted.other.literal.upper.ruby"]
|
||||
expect(tokens[5]).toEqual value: '-', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","source.ruby.embedded.source","string.quoted.other.literal.upper.ruby","punctuation.definition.string.end.ruby"]
|
||||
expect(tokens[6]).toEqual value: '}', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","source.ruby.embedded.source","punctuation.section.embedded.ruby"]
|
||||
expect(tokens[2]).toEqual value: '#{', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","punctuation.section.embedded.begin.ruby"]
|
||||
expect(tokens[3]).toEqual value: '%Q-', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.begin.ruby"]
|
||||
expect(tokens[4]).toEqual value: 'crazy ideas', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","string.quoted.other.literal.upper.ruby"]
|
||||
expect(tokens[5]).toEqual value: '-', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.end.ruby"]
|
||||
expect(tokens[6]).toEqual value: '}', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","punctuation.section.embedded.end.ruby", "source.ruby"]
|
||||
expect(tokens[7]).toEqual value: ' for ruby syntax', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby"]
|
||||
expect(tokens[8]).toEqual value: '+', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.end.ruby"]
|
||||
expect(tokens[9]).toEqual value: ' ', scopes: ["source.ruby"]
|
||||
@ -448,7 +448,7 @@ describe "TextMateGrammar", ->
|
||||
|
||||
describe "when the grammar is added", ->
|
||||
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
|
||||
editSession = project.buildEditSession('sample.js')
|
||||
editSession = project.open('sample.js')
|
||||
editSession.setText("// http://github.com")
|
||||
|
||||
{tokens} = editSession.lineForScreenRow(0)
|
||||
@ -463,7 +463,7 @@ describe "TextMateGrammar", ->
|
||||
|
||||
describe "when the grammar is updated", ->
|
||||
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
|
||||
editSession = project.buildEditSession('sample.js')
|
||||
editSession = project.open('sample.js')
|
||||
editSession.setText("// SELECT * FROM OCTOCATS")
|
||||
|
||||
{tokens} = editSession.lineForScreenRow(0)
|
||||
|
@ -343,3 +343,17 @@ describe "TokenizedBuffer", ->
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
{tokens} = tokenizedBuffer.lineForScreenRow(0)
|
||||
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
|
||||
|
||||
describe ".tokenForPosition(position)", ->
|
||||
afterEach ->
|
||||
tokenizedBuffer.destroy()
|
||||
buffer.release()
|
||||
|
||||
it "returns the correct token (regression)", ->
|
||||
buffer = project.bufferForPath('sample.js')
|
||||
tokenizedBuffer = new TokenizedBuffer(buffer)
|
||||
tokenizedBuffer.setVisible(true)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
expect(tokenizedBuffer.tokenForPosition([1,0]).scopes).toEqual ["source.js"]
|
||||
expect(tokenizedBuffer.tokenForPosition([1,1]).scopes).toEqual ["source.js"]
|
||||
expect(tokenizedBuffer.tokenForPosition([1,2]).scopes).toEqual ["source.js", "storage.modifier.js"]
|
||||
|
@ -1,18 +1,20 @@
|
||||
$ = require 'jquery'
|
||||
{$$} = require 'space-pen'
|
||||
fsUtils = require 'fs-utils'
|
||||
{less} = require 'less'
|
||||
WindowEventHandler = require 'window-event-handler'
|
||||
|
||||
describe "Window", ->
|
||||
projectPath = null
|
||||
[projectPath, windowEventHandler] = []
|
||||
|
||||
beforeEach ->
|
||||
spyOn(atom, 'getPathToOpen').andReturn(project.getPath())
|
||||
window.handleEvents()
|
||||
windowEventHandler = new WindowEventHandler()
|
||||
window.deserializeEditorWindow()
|
||||
projectPath = project.getPath()
|
||||
|
||||
afterEach ->
|
||||
window.unloadEditorWindow()
|
||||
windowEventHandler.unsubscribe()
|
||||
$(window).off 'beforeunload'
|
||||
|
||||
describe "when the window is loaded", ->
|
||||
@ -154,23 +156,6 @@ describe "Window", ->
|
||||
window.unloadEditorWindow()
|
||||
expect(atom.saveWindowState.callCount).toBe 1
|
||||
|
||||
describe ".installAtomCommand(commandPath)", ->
|
||||
commandPath = '/tmp/installed-atom-command/atom'
|
||||
|
||||
afterEach ->
|
||||
fsUtils.remove(commandPath) if fsUtils.exists(commandPath)
|
||||
|
||||
describe "when the command path doesn't exist", ->
|
||||
it "copies atom.sh to the specified path", ->
|
||||
expect(fsUtils.exists(commandPath)).toBeFalsy()
|
||||
window.installAtomCommand(commandPath)
|
||||
|
||||
waitsFor ->
|
||||
fsUtils.exists(commandPath)
|
||||
|
||||
runs ->
|
||||
expect(fsUtils.read(commandPath).length).toBeGreaterThan 1
|
||||
|
||||
describe ".deserialize(state)", ->
|
||||
class Foo
|
||||
@deserialize: ({name}) -> new Foo(name)
|
||||
@ -251,3 +236,82 @@ describe "Window", ->
|
||||
shell.openExternal.reset()
|
||||
$("<a href='#scroll-me'>link</a>").appendTo(document.body).click().remove()
|
||||
expect(shell.openExternal).not.toHaveBeenCalled()
|
||||
|
||||
describe "core:focus-next and core:focus-previous", ->
|
||||
describe "when there is no currently focused element", ->
|
||||
it "focuses the element with the lowest/highest tabindex", ->
|
||||
elements = $$ ->
|
||||
@div =>
|
||||
@button tabindex: 2
|
||||
@input tabindex: 1
|
||||
|
||||
elements.attachToDom()
|
||||
|
||||
elements.trigger "core:focus-next"
|
||||
expect(elements.find("[tabindex=1]:focus")).toExist()
|
||||
|
||||
$(":focus").blur()
|
||||
|
||||
elements.trigger "core:focus-previous"
|
||||
expect(elements.find("[tabindex=2]:focus")).toExist()
|
||||
|
||||
describe "when a tabindex is set on the currently focused element", ->
|
||||
it "focuses the element with the next highest tabindex", ->
|
||||
elements = $$ ->
|
||||
@div =>
|
||||
@input tabindex: 1
|
||||
@button tabindex: 2
|
||||
@button tabindex: 5
|
||||
@input tabindex: -1
|
||||
@input tabindex: 3
|
||||
@button tabindex: 7
|
||||
|
||||
elements.attachToDom()
|
||||
elements.find("[tabindex=1]").focus()
|
||||
|
||||
elements.trigger "core:focus-next"
|
||||
expect(elements.find("[tabindex=2]:focus")).toExist()
|
||||
|
||||
elements.trigger "core:focus-next"
|
||||
expect(elements.find("[tabindex=3]:focus")).toExist()
|
||||
|
||||
elements.focus().trigger "core:focus-next"
|
||||
expect(elements.find("[tabindex=5]:focus")).toExist()
|
||||
|
||||
elements.focus().trigger "core:focus-next"
|
||||
expect(elements.find("[tabindex=7]:focus")).toExist()
|
||||
|
||||
elements.focus().trigger "core:focus-next"
|
||||
expect(elements.find("[tabindex=1]:focus")).toExist()
|
||||
|
||||
elements.trigger "core:focus-previous"
|
||||
expect(elements.find("[tabindex=7]:focus")).toExist()
|
||||
|
||||
elements.trigger "core:focus-previous"
|
||||
expect(elements.find("[tabindex=5]:focus")).toExist()
|
||||
|
||||
elements.focus().trigger "core:focus-previous"
|
||||
expect(elements.find("[tabindex=3]:focus")).toExist()
|
||||
|
||||
elements.focus().trigger "core:focus-previous"
|
||||
expect(elements.find("[tabindex=2]:focus")).toExist()
|
||||
|
||||
elements.focus().trigger "core:focus-previous"
|
||||
expect(elements.find("[tabindex=1]:focus")).toExist()
|
||||
|
||||
it "skips disabled elements", ->
|
||||
elements = $$ ->
|
||||
@div =>
|
||||
@input tabindex: 1
|
||||
@button tabindex: 2, disabled: 'disabled'
|
||||
@input tabindex: 3
|
||||
|
||||
elements.attachToDom()
|
||||
elements.find("[tabindex=1]").focus()
|
||||
|
||||
elements.trigger "core:focus-next"
|
||||
expect(elements.find("[tabindex=3]:focus")).toExist()
|
||||
|
||||
elements.trigger "core:focus-previous"
|
||||
expect(elements.find("[tabindex=1]:focus")).toExist()
|
||||
>>>>>>> master
|
||||
|
34
spec/stdlib/command-installer-spec.coffee
Normal file
34
spec/stdlib/command-installer-spec.coffee
Normal file
@ -0,0 +1,34 @@
|
||||
fs = require 'fs'
|
||||
fsUtils = require 'fs-utils'
|
||||
installer = require 'command-installer'
|
||||
|
||||
describe "install(commandPath, callback)", ->
|
||||
directory = '/tmp/install-atom-command/atom'
|
||||
commandPath = "#{directory}/source"
|
||||
destinationPath = "#{directory}/bin/source"
|
||||
|
||||
beforeEach ->
|
||||
spyOn(installer, 'findInstallDirectory').andCallFake (callback) ->
|
||||
callback(directory)
|
||||
|
||||
fsUtils.remove(directory) if fsUtils.exists(directory)
|
||||
|
||||
it "symlinks the command and makes it executable", ->
|
||||
fsUtils.write(commandPath, 'test')
|
||||
expect(fsUtils.isFile(commandPath)).toBeTruthy()
|
||||
expect(fsUtils.isExecutable(commandPath)).toBeFalsy()
|
||||
expect(fsUtils.isFile(destinationPath)).toBeFalsy()
|
||||
|
||||
installDone = false
|
||||
installError = null
|
||||
installer.install commandPath, (error) ->
|
||||
installDone = true
|
||||
installError = error
|
||||
|
||||
waitsFor -> installDone
|
||||
|
||||
runs ->
|
||||
expect(installError).toBeNull()
|
||||
expect(fsUtils.isFile(destinationPath)).toBeTruthy()
|
||||
expect(fs.realpathSync(destinationPath)).toBe fs.realpathSync(commandPath)
|
||||
expect(fsUtils.isExecutable(destinationPath)).toBeTruthy()
|
@ -1,92 +0,0 @@
|
||||
CSON = require 'cson'
|
||||
|
||||
describe "CSON", ->
|
||||
describe "@stringify(object)", ->
|
||||
describe "when the object is undefined", ->
|
||||
it "throws an exception", ->
|
||||
expect(-> CSON.stringify()).toThrow()
|
||||
|
||||
describe "when the object is a function", ->
|
||||
it "throws an exception", ->
|
||||
expect(-> CSON.stringify(-> 'function')).toThrow()
|
||||
|
||||
describe "when the object contains a function", ->
|
||||
it "throws an exception", ->
|
||||
expect(-> CSON.stringify(a: -> 'function')).toThrow()
|
||||
|
||||
describe "when formatting an undefined key", ->
|
||||
it "does not include the key in the formatted CSON", ->
|
||||
expect(CSON.stringify(b: 1, c: undefined)).toBe "'b': 1"
|
||||
|
||||
describe "when formatting a string", ->
|
||||
it "returns formatted CSON", ->
|
||||
expect(CSON.stringify(a: 'b')).toBe "'a': 'b'"
|
||||
|
||||
it "escapes single quotes", ->
|
||||
expect(CSON.stringify(a: "'b'")).toBe "'a': '\\\'b\\\''"
|
||||
|
||||
it "doesn't escape double quotes", ->
|
||||
expect(CSON.stringify(a: '"b"')).toBe "'a': '\"b\"'"
|
||||
|
||||
it "escapes newlines", ->
|
||||
expect(CSON.stringify("a\nb")).toBe "'a\\nb'"
|
||||
|
||||
describe "when formatting a boolean", ->
|
||||
it "returns formatted CSON", ->
|
||||
expect(CSON.stringify(true)).toBe 'true'
|
||||
expect(CSON.stringify(false)).toBe 'false'
|
||||
expect(CSON.stringify(a: true)).toBe "'a': true"
|
||||
expect(CSON.stringify(a: false)).toBe "'a': false"
|
||||
|
||||
describe "when formatting a number", ->
|
||||
it "returns formatted CSON", ->
|
||||
expect(CSON.stringify(54321.012345)).toBe '54321.012345'
|
||||
expect(CSON.stringify(a: 14)).toBe "'a': 14"
|
||||
expect(CSON.stringify(a: 1.23)).toBe "'a': 1.23"
|
||||
|
||||
describe "when formatting null", ->
|
||||
it "returns formatted CSON", ->
|
||||
expect(CSON.stringify(null)).toBe 'null'
|
||||
expect(CSON.stringify(a: null)).toBe "'a': null"
|
||||
|
||||
describe "when formatting an array", ->
|
||||
describe "when the array is empty", ->
|
||||
it "puts the array on a single line", ->
|
||||
expect(CSON.stringify([])).toBe "[]"
|
||||
|
||||
it "returns formatted CSON", ->
|
||||
expect(CSON.stringify(a: ['b'])).toBe "'a': [\n 'b'\n]"
|
||||
expect(CSON.stringify(a: ['b', 4])).toBe "'a': [\n 'b'\n 4\n]"
|
||||
|
||||
describe "when the array has an undefined value", ->
|
||||
it "formats the undefined value as null", ->
|
||||
expect(CSON.stringify(['a', undefined, 'b'])).toBe "[\n 'a'\n null\n 'b'\n]"
|
||||
|
||||
describe "when the array contains an object", ->
|
||||
it "wraps the object in {}", ->
|
||||
expect(CSON.stringify([{a:'b', a1: 'b1'}, {c: 'd'}])).toBe "[\n {\n 'a': 'b'\n 'a1': 'b1'\n }\n {\n 'c': 'd'\n }\n]"
|
||||
|
||||
describe "when formatting an object", ->
|
||||
describe "when the object is empty", ->
|
||||
it "returns {}", ->
|
||||
expect(CSON.stringify({})).toBe "{}"
|
||||
|
||||
it "returns formatted CSON", ->
|
||||
expect(CSON.stringify(a: {b: 'c'})).toBe "'a':\n 'b': 'c'"
|
||||
expect(CSON.stringify(a:{})).toBe "'a': {}"
|
||||
expect(CSON.stringify(a:[])).toBe "'a': []"
|
||||
|
||||
describe "when converting back to an object", ->
|
||||
it "produces the original object", ->
|
||||
object =
|
||||
showInvisibles: true
|
||||
fontSize: 20
|
||||
core:
|
||||
themes: ['a', 'b']
|
||||
whitespace:
|
||||
ensureSingleTrailingNewline: true
|
||||
|
||||
cson = CSON.stringify(object)
|
||||
CoffeeScript = require 'coffee-script'
|
||||
evaledObject = CoffeeScript.eval(cson, bare: true)
|
||||
expect(evaledObject).toEqual object
|
@ -1,9 +1,10 @@
|
||||
Subscriber = require 'subscriber'
|
||||
EventEmitter = require 'event-emitter'
|
||||
_ = require 'underscore'
|
||||
{$$} = require 'space-pen'
|
||||
|
||||
describe "Subscriber", ->
|
||||
[emitter1, emitter2, event1Handler, event2Handler, subscriber] = []
|
||||
[emitter1, emitter2, emitter3, event1Handler, event2Handler, event3Handler, subscriber] = []
|
||||
|
||||
class TestEventEmitter
|
||||
_.extend TestEventEmitter.prototype, EventEmitter
|
||||
@ -14,11 +15,17 @@ describe "Subscriber", ->
|
||||
beforeEach ->
|
||||
emitter1 = new TestEventEmitter
|
||||
emitter2 = new TestEventEmitter
|
||||
emitter3 = $$ ->
|
||||
@div =>
|
||||
@a()
|
||||
@span()
|
||||
subscriber = new TestSubscriber
|
||||
event1Handler = jasmine.createSpy("event1Handler")
|
||||
event2Handler = jasmine.createSpy("event2Handler")
|
||||
event3Handler = jasmine.createSpy("event3Handler")
|
||||
subscriber.subscribe emitter1, 'event1', event1Handler
|
||||
subscriber.subscribe emitter2, 'event2', event2Handler
|
||||
subscriber.subscribe emitter3, 'event3', 'a', event3Handler
|
||||
|
||||
it "subscribes to events on the specified object", ->
|
||||
emitter1.trigger 'event1', 'foo'
|
||||
@ -27,6 +34,12 @@ describe "Subscriber", ->
|
||||
emitter2.trigger 'event2', 'bar'
|
||||
expect(event2Handler).toHaveBeenCalledWith('bar')
|
||||
|
||||
emitter3.find('span').trigger 'event3'
|
||||
expect(event3Handler).not.toHaveBeenCalledWith()
|
||||
|
||||
emitter3.find('a').trigger 'event3'
|
||||
expect(event3Handler).toHaveBeenCalled()
|
||||
|
||||
it "allows an object to unsubscribe en-masse", ->
|
||||
subscriber.unsubscribe()
|
||||
emitter1.trigger 'event1', 'foo'
|
||||
|
@ -3,8 +3,7 @@ Package = require 'package'
|
||||
fsUtils = require 'fs-utils'
|
||||
_ = require 'underscore'
|
||||
$ = require 'jquery'
|
||||
CSON = require 'cson'
|
||||
|
||||
CSON = require 'season'
|
||||
|
||||
### Internal: Loads and resolves packages. ###
|
||||
|
||||
@ -63,11 +62,11 @@ class AtomPackage extends Package
|
||||
|
||||
loadMetadata: ->
|
||||
if metadataPath = fsUtils.resolveExtension(fsUtils.join(@path, 'package'), ['json', 'cson'])
|
||||
@metadata = CSON.readObject(metadataPath)
|
||||
@metadata = CSON.readFileSync(metadataPath)
|
||||
@metadata ?= {}
|
||||
|
||||
loadKeymaps: ->
|
||||
@keymaps = @getKeymapPaths().map (path) -> [path, CSON.readObject(path)]
|
||||
@keymaps = @getKeymapPaths().map (path) -> [path, CSON.readFileSync(path)]
|
||||
|
||||
getKeymapPaths: ->
|
||||
keymapsDirPath = fsUtils.join(@path, 'keymaps')
|
||||
|
@ -73,7 +73,7 @@ window.atom =
|
||||
|
||||
loadPackage: (id, options) ->
|
||||
if @isPackageDisabled(id)
|
||||
return console.warn("Tried to load disabled packaged '#{id}'")
|
||||
return console.warn("Tried to load disabled package '#{id}'")
|
||||
|
||||
if path = @resolvePackagePath(id)
|
||||
return pack if pack = @getLoadedPackage(id)
|
||||
|
@ -1,7 +1,7 @@
|
||||
fsUtils = require 'fs-utils'
|
||||
_ = require 'underscore'
|
||||
EventEmitter = require 'event-emitter'
|
||||
CSON = require 'cson'
|
||||
CSON = require 'season'
|
||||
fs = require 'fs'
|
||||
async = require 'async'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
@ -72,7 +72,7 @@ class Config
|
||||
loadUserConfig: ->
|
||||
if fsUtils.exists(@configFilePath)
|
||||
try
|
||||
userConfig = CSON.readObject(@configFilePath)
|
||||
userConfig = CSON.readFileSync(@configFilePath)
|
||||
_.extend(@settings, userConfig)
|
||||
@configFileHasErrors = false
|
||||
@trigger 'updated'
|
||||
@ -108,8 +108,8 @@ class Config
|
||||
# Returns the value from Atom's default settings, the user's configuration file,
|
||||
# or `null` if the key doesn't exist in either.
|
||||
get: (keyPath) ->
|
||||
_.valueForKeyPath(@settings, keyPath) ?
|
||||
_.valueForKeyPath(@defaultSettings, keyPath)
|
||||
value = _.valueForKeyPath(@settings, keyPath) ? _.valueForKeyPath(@defaultSettings, keyPath)
|
||||
_.deepClone(value)
|
||||
|
||||
# Retrieves the setting for the given key as an integer.
|
||||
#
|
||||
@ -146,6 +146,30 @@ class Config
|
||||
@update()
|
||||
value
|
||||
|
||||
# Push the value to the array at the key path.
|
||||
#
|
||||
# keyPath - The {String} key path.
|
||||
# value - The value to push to the array.
|
||||
#
|
||||
# Returns the new array length of the setting.
|
||||
pushAtKeyPath: (keyPath, value) ->
|
||||
arrayValue = @get(keyPath) ? []
|
||||
result = arrayValue.push(value)
|
||||
@set(keyPath, arrayValue)
|
||||
result
|
||||
|
||||
# Remove the value from the array at the key path.
|
||||
#
|
||||
# keyPath - The {String} key path.
|
||||
# value - The value to remove from the array.
|
||||
#
|
||||
# Returns the new array value of the setting.
|
||||
removeAtKeyPath: (keyPath, value) ->
|
||||
arrayValue = @get(keyPath) ? []
|
||||
result = _.remove(arrayValue, value)
|
||||
@set(keyPath, arrayValue)
|
||||
result
|
||||
|
||||
# Establishes an event listener for a given key.
|
||||
#
|
||||
# `callback` is fired immediately and whenever the value of the key is changed
|
||||
@ -175,6 +199,6 @@ class Config
|
||||
@trigger 'updated'
|
||||
|
||||
save: ->
|
||||
CSON.writeObject(@configFilePath, @settings)
|
||||
CSON.writeFileSync(@configFilePath, @settings)
|
||||
|
||||
_.extend Config.prototype, EventEmitter
|
||||
|
@ -9,6 +9,7 @@ Subscriber = require 'subscriber'
|
||||
Range = require 'range'
|
||||
_ = require 'underscore'
|
||||
fsUtils = require 'fs-utils'
|
||||
TextMateScopeSelector = require 'text-mate-scope-selector'
|
||||
|
||||
# An `EditSession` manages the states between {Editor}s, {Buffer}s, and the project as a whole.
|
||||
module.exports =
|
||||
@ -23,7 +24,7 @@ class EditSession
|
||||
session = project.buildEditSessionForBuffer(Buffer.deserialize(state.buffer))
|
||||
if !session?
|
||||
console.warn "Could not build edit session for path '#{state.buffer}' because that file no longer exists" if state.buffer
|
||||
session = project.buildEditSession(null)
|
||||
session = project.open(null)
|
||||
session.setScrollTop(state.scrollTop)
|
||||
session.setScrollLeft(state.scrollLeft)
|
||||
session.setCursorScreenPosition(state.cursorScreenPosition)
|
||||
@ -284,6 +285,14 @@ class EditSession
|
||||
# {Delegates to: Buffer.isRowBlank}
|
||||
isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow)
|
||||
|
||||
# Test if an entire row is a comment
|
||||
#
|
||||
# Returns a {Boole}.
|
||||
isBufferRowCommented: (bufferRow) ->
|
||||
if match = @lineForBufferRow(bufferRow).match(/\S/)
|
||||
scopes = @tokenForBufferPosition([bufferRow, match.index]).scopes
|
||||
new TextMateScopeSelector('comment.*').matches(scopes)
|
||||
|
||||
# {Delegates to: Buffer.nextNonBlankRow}
|
||||
nextNonBlankBufferRow: (bufferRow) -> @buffer.nextNonBlankRow(bufferRow)
|
||||
|
||||
@ -361,24 +370,13 @@ class EditSession
|
||||
# Returns an {Array} of {String}s.
|
||||
getCursorScopes: -> @getCursor().getScopes()
|
||||
|
||||
# Determines whether the {Editor} will auto indent rows.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
shouldAutoIndent: ->
|
||||
config.get("editor.autoIndent")
|
||||
|
||||
# Determines whether the {Editor} will auto indent pasted text.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
shouldAutoIndentPastedText: ->
|
||||
config.get("editor.autoIndentOnPaste")
|
||||
|
||||
# Inserts text at the current cursor positions
|
||||
#
|
||||
# text - A {String} representing the text to insert.
|
||||
# options - A set of options equivalent to {Selection.insertText}
|
||||
insertText: (text, options={}) ->
|
||||
options.autoIndent ?= @shouldAutoIndent()
|
||||
options.autoIndentNewline ?= @shouldAutoIndent()
|
||||
options.autoDecreaseIndent ?= @shouldAutoIndent()
|
||||
@mutateSelectedText (selection) -> selection.insertText(text, options)
|
||||
|
||||
# Inserts a new line at the current cursor positions.
|
||||
@ -486,11 +484,11 @@ class EditSession
|
||||
#
|
||||
# options - A set of options equivalent to {Selection.insertText}.
|
||||
pasteText: (options={}) ->
|
||||
options.normalizeIndent ?= true
|
||||
options.autoIndent ?= @shouldAutoIndentPastedText()
|
||||
|
||||
[text, metadata] = pasteboard.read()
|
||||
_.extend(options, metadata) if metadata
|
||||
|
||||
options.autoIndent ?= @shouldAutoIndentPastedText()
|
||||
if config.get('editor.normalizeIndentOnPaste') and metadata
|
||||
options.indentBasis ?= metadata.indentBasis
|
||||
|
||||
@insertText(text, options)
|
||||
|
||||
@ -609,16 +607,10 @@ class EditSession
|
||||
autoIndentBufferRow: (bufferRow) ->
|
||||
@languageMode.autoIndentBufferRow(bufferRow)
|
||||
|
||||
# Given a buffer row, this increases the indentation.
|
||||
#
|
||||
# bufferRow - The row {Number}
|
||||
autoIncreaseIndentForBufferRow: (bufferRow) ->
|
||||
@languageMode.autoIncreaseIndentForBufferRow(bufferRow)
|
||||
|
||||
# Given a buffer row, this decreases the indentation.
|
||||
#
|
||||
# bufferRow - The row {Number}
|
||||
autoDecreaseIndentForRow: (bufferRow) ->
|
||||
autoDecreaseIndentForBufferRow: (bufferRow) ->
|
||||
@languageMode.autoDecreaseIndentForBufferRow(bufferRow)
|
||||
|
||||
# Wraps the lines between two rows in comments.
|
||||
@ -1247,10 +1239,6 @@ class EditSession
|
||||
@mergeIntersectingSelections(options)
|
||||
return
|
||||
|
||||
# Internal:
|
||||
inspect: ->
|
||||
JSON.stringify @serialize()
|
||||
|
||||
preserveCursorPositionOnBufferReload: ->
|
||||
cursorPosition = null
|
||||
@subscribe @buffer, "will-reload", =>
|
||||
@ -1273,12 +1261,21 @@ class EditSession
|
||||
|
||||
### Internal ###
|
||||
|
||||
shouldAutoIndent: ->
|
||||
config.get("editor.autoIndent")
|
||||
|
||||
shouldAutoIndentPastedText: ->
|
||||
config.get("editor.autoIndentOnPaste")
|
||||
|
||||
transact: (fn) -> @buffer.transact(fn)
|
||||
|
||||
commit: -> @buffer.commit()
|
||||
|
||||
abort: -> @buffer.abort()
|
||||
|
||||
inspect: ->
|
||||
JSON.stringify @serialize()
|
||||
|
||||
logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
|
||||
|
||||
handleGrammarChange: ->
|
||||
|
@ -22,6 +22,7 @@ class Editor extends View
|
||||
showLineNumbers: true
|
||||
autoIndent: true
|
||||
autoIndentOnPaste: false
|
||||
normalizeIndentOnPaste: false
|
||||
nonWordCharacters: "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?-"
|
||||
preferredLineLength: 80
|
||||
|
||||
|
@ -8,11 +8,19 @@ module.exports=
|
||||
class ImageEditSession
|
||||
registerDeserializer(this)
|
||||
|
||||
# Files with these extensions will be opened as images
|
||||
@imageExtensions: ['.gif', '.jpeg', '.jpg', '.png']
|
||||
|
||||
### Internal ###
|
||||
|
||||
Project = require 'project'
|
||||
Project.registerOpener (path) =>
|
||||
new ImageEditSession(path) if _.include(@imageExtensions, fsUtils.extension(path))
|
||||
|
||||
|
||||
@deserialize: (state) ->
|
||||
if fsUtils.exists(state.path)
|
||||
project.buildEditSession(state.path)
|
||||
project.open(state.path)
|
||||
else
|
||||
console.warn "Could not build edit session for path '#{state.path}' because that file no longer exists"
|
||||
|
||||
@ -27,19 +35,6 @@ class ImageEditSession
|
||||
|
||||
### Public ###
|
||||
|
||||
# Identifies if a path can be opened by the image viewer.
|
||||
#
|
||||
# path - The {String} name of the path to check
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
@canOpen: (path) ->
|
||||
_.indexOf([
|
||||
'.gif'
|
||||
'.jpeg'
|
||||
'.jpg'
|
||||
'.png'
|
||||
], fsUtils.extension(path), true) >= 0
|
||||
|
||||
# Retrieves the filename of the open file.
|
||||
#
|
||||
# This is `'untitled'` if the file is new and not saved to the disk.
|
||||
|
@ -1,8 +1,7 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
fsUtils = require 'fs-utils'
|
||||
CSON = require 'cson'
|
||||
|
||||
CSON = require 'season'
|
||||
BindingSet = require 'binding-set'
|
||||
|
||||
# Internal: Associates keymaps with actions.
|
||||
@ -46,7 +45,7 @@ class Keymap
|
||||
@load(filePath) for filePath in fsUtils.list(directoryPath, ['.cson', '.json'])
|
||||
|
||||
load: (path) ->
|
||||
@add(path, CSON.readObject(path))
|
||||
@add(path, CSON.readFileSync(path))
|
||||
|
||||
add: (args...) ->
|
||||
name = args.shift() if args.length > 1
|
||||
|
@ -67,10 +67,10 @@
|
||||
|
||||
# allow standard input fields to work correctly
|
||||
'input:not(.hidden-input)':
|
||||
'tab': 'core:focus-next'
|
||||
'shift-tab': 'core:focus-previous'
|
||||
'left': 'native!'
|
||||
'right': 'native!'
|
||||
'tab': 'native!'
|
||||
'shift-tab': 'native!'
|
||||
'shift-left': 'native!'
|
||||
'shift-right': 'native!'
|
||||
'backspace': 'native!'
|
||||
@ -81,3 +81,7 @@
|
||||
'meta-x': 'native!'
|
||||
'meta-c': 'native!'
|
||||
'meta-v': 'native!'
|
||||
|
||||
'button':
|
||||
'tab': 'core:focus-next'
|
||||
'shift-tab': 'core:focus-previous'
|
||||
|
@ -162,14 +162,13 @@ class LanguageMode
|
||||
return currentIndentLevel unless precedingRow?
|
||||
|
||||
precedingLine = @buffer.lineForRow(precedingRow)
|
||||
|
||||
desiredIndentLevel = @editSession.indentationForBufferRow(precedingRow)
|
||||
desiredIndentLevel += 1 if increaseIndentRegex.test(precedingLine)
|
||||
desiredIndentLevel += 1 if increaseIndentRegex.test(precedingLine) and not @editSession.isBufferRowCommented(precedingRow)
|
||||
|
||||
return desiredIndentLevel unless decreaseIndentRegex = @decreaseIndentRegexForScopes(scopes)
|
||||
desiredIndentLevel -= 1 if decreaseIndentRegex.test(currentLine)
|
||||
|
||||
Math.max(desiredIndentLevel, currentIndentLevel)
|
||||
desiredIndentLevel
|
||||
|
||||
# Indents all the rows between two buffer row numbers.
|
||||
#
|
||||
@ -182,26 +181,8 @@ class LanguageMode
|
||||
#
|
||||
# bufferRow - The row {Number}
|
||||
autoIndentBufferRow: (bufferRow) ->
|
||||
@autoIncreaseIndentForBufferRow(bufferRow)
|
||||
@autoDecreaseIndentForBufferRow(bufferRow)
|
||||
|
||||
# Given a buffer row, this increases the indentation.
|
||||
#
|
||||
# bufferRow - The row {Number}
|
||||
autoIncreaseIndentForBufferRow: (bufferRow) ->
|
||||
precedingRow = @buffer.previousNonBlankRow(bufferRow)
|
||||
return unless precedingRow?
|
||||
|
||||
precedingLine = @editSession.lineForBufferRow(precedingRow)
|
||||
scopes = @editSession.scopesForBufferPosition([precedingRow, Infinity])
|
||||
increaseIndentRegex = @increaseIndentRegexForScopes(scopes)
|
||||
return unless increaseIndentRegex
|
||||
|
||||
currentIndentLevel = @editSession.indentationForBufferRow(bufferRow)
|
||||
desiredIndentLevel = @editSession.indentationForBufferRow(precedingRow)
|
||||
desiredIndentLevel += 1 if increaseIndentRegex.test(precedingLine)
|
||||
if desiredIndentLevel > currentIndentLevel
|
||||
@editSession.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
|
||||
indentLevel = @suggestedIndentForBufferRow(bufferRow)
|
||||
@editSession.setIndentationForBufferRow(bufferRow, indentLevel)
|
||||
|
||||
# Given a buffer row, this decreases the indentation.
|
||||
#
|
||||
|
@ -30,10 +30,9 @@ class PackageConfigPanel extends ConfigPanel
|
||||
checkbox = $(e.target)
|
||||
name = checkbox.closest('tr').attr('name')
|
||||
if checkbox.attr('checked')
|
||||
_.remove(config.get('core.disabledPackages'), name)
|
||||
config.removeAtKeyPath('core.disabledPackages', name)
|
||||
else
|
||||
config.get('core.disabledPackages').push(name)
|
||||
config.update()
|
||||
config.pushAtKeyPath('core.disabledPackages', name)
|
||||
|
||||
@observeConfig 'core.disabledPackages', (disabledPackages) =>
|
||||
@packageTableBody.find("input[type='checkbox']").attr('checked', true)
|
||||
|
@ -4,7 +4,6 @@ $ = require 'jquery'
|
||||
Range = require 'range'
|
||||
Buffer = require 'text-buffer'
|
||||
EditSession = require 'edit-session'
|
||||
ImageEditSession = require 'image-edit-session'
|
||||
EventEmitter = require 'event-emitter'
|
||||
Directory = require 'directory'
|
||||
BufferedProcess = require 'buffered-process'
|
||||
@ -17,6 +16,14 @@ module.exports =
|
||||
class Project
|
||||
registerDeserializer(this)
|
||||
|
||||
@openers: []
|
||||
|
||||
@registerOpener: (opener) ->
|
||||
@openers.push(opener)
|
||||
|
||||
@unregisterOpener: (opener) ->
|
||||
_.remove(@openers, opener)
|
||||
|
||||
tabLength: 2
|
||||
softTabs: true
|
||||
softWrap: false
|
||||
@ -104,14 +111,18 @@ class Project
|
||||
ignoreRepositoryPath: (path) ->
|
||||
config.get("core.hideGitIgnoredFiles") and git?.isPathIgnored(fsUtils.join(@getPath(), path))
|
||||
|
||||
# Given a path, this resolves it relative to the project directory.
|
||||
# Given a uri, this resolves it relative to the project directory. If the path
|
||||
# is already absolute or if it is prefixed with a scheme, it is returned unchanged.
|
||||
#
|
||||
# filePath - The {String} name of the path to convert
|
||||
# uri - The {String} name of the path to convert
|
||||
#
|
||||
# Returns a {String}.
|
||||
resolve: (filePath) ->
|
||||
filePath = fsUtils.join(@getPath(), filePath) unless filePath[0] == '/'
|
||||
fsUtils.absolute filePath
|
||||
resolve: (uri) ->
|
||||
if uri?.match(/[A-Za-z0-9+-.]+:\/\//) # leave path alone if it has a scheme
|
||||
uri
|
||||
else
|
||||
uri = fsUtils.join(@getPath(), uri) unless uri[0] == '/'
|
||||
fsUtils.absolute uri
|
||||
|
||||
# Given a path, this makes it relative to the project directory.
|
||||
#
|
||||
@ -148,11 +159,11 @@ class Project
|
||||
# editSessionOptions - Options that you can pass to the `EditSession` constructor
|
||||
#
|
||||
# Returns either an {EditSession} (for text) or {ImageEditSession} (for images).
|
||||
buildEditSession: (filePath, editSessionOptions={}) ->
|
||||
if ImageEditSession.canOpen(filePath)
|
||||
new ImageEditSession(filePath)
|
||||
else
|
||||
@buildEditSessionForBuffer(@bufferForPath(filePath), editSessionOptions)
|
||||
open: (filePath, options={}) ->
|
||||
for opener in @constructor.openers
|
||||
return resource if resource = opener(filePath, options)
|
||||
|
||||
@buildEditSessionForBuffer(@bufferForPath(filePath), options)
|
||||
|
||||
# Retrieves all the {EditSession}s in the project; that is, the `EditSession`s for all open files.
|
||||
#
|
||||
@ -265,7 +276,9 @@ class Project
|
||||
|
||||
command = require.resolve('nak')
|
||||
args = ['--hidden', '--ackmate', regex.source, @getPath()]
|
||||
args.unshift("--addVCSIgnores") if config.get('core.excludeVcsIgnoredPaths')
|
||||
ignoredNames = config.get('core.ignoredNames') ? []
|
||||
args.unshift('--ignore', ignoredNames.join(',')) if ignoredNames.length > 0
|
||||
args.unshift('--addVCSIgnores') if config.get('core.excludeVcsIgnoredPaths')
|
||||
new BufferedProcess({command, args, stdout, exit})
|
||||
deferred
|
||||
|
||||
@ -300,3 +313,5 @@ class Project
|
||||
@on 'buffer-created', (buffer) -> callback(buffer)
|
||||
|
||||
_.extend Project.prototype, EventEmitter
|
||||
|
||||
require 'image-edit-session'
|
||||
|
@ -204,6 +204,12 @@ class Range
|
||||
getRowCount: ->
|
||||
@end.row - @start.row + 1
|
||||
|
||||
# Returns an array of all rows in a `Range`
|
||||
#
|
||||
# Returns an {Array}
|
||||
getRows: ->
|
||||
[@start.row..@end.row]
|
||||
|
||||
### Internal ###
|
||||
|
||||
inspect: ->
|
||||
|
@ -109,10 +109,10 @@ class RootView extends View
|
||||
changeFocus = options.changeFocus ? true
|
||||
path = project.resolve(path) if path?
|
||||
if activePane = @getActivePane()
|
||||
editSession = activePane.itemForUri(path) ? project.buildEditSession(path)
|
||||
editSession = activePane.itemForUri(path) ? project.open(path)
|
||||
activePane.showItem(editSession)
|
||||
else
|
||||
editSession = project.buildEditSession(path)
|
||||
editSession = project.open(path)
|
||||
activePane = new Pane(editSession)
|
||||
@panes.append(activePane)
|
||||
|
||||
|
@ -254,17 +254,21 @@ class Selection
|
||||
#
|
||||
# text - A {String} representing the text to add
|
||||
# options - A hash containing the following options:
|
||||
# normalizeIndent: TODO
|
||||
# select: if `true`, selects the newly added text
|
||||
# autoIndent: if `true`, indents the newly added text appropriately
|
||||
# autoIndent: if `true`, indents all inserted text appropriately
|
||||
# autoIndentNewline: if `true`, indent newline appropriately
|
||||
# autoDecreaseIndent: if `true`, decreases indent level appropriately (for example, when a closing bracket is inserted)
|
||||
|
||||
insertText: (text, options={}) ->
|
||||
oldBufferRange = @getBufferRange()
|
||||
@editSession.destroyFoldsContainingBufferRow(oldBufferRange.end.row)
|
||||
wasReversed = @isReversed()
|
||||
text = @normalizeIndent(text, options) if options.normalizeIndent
|
||||
@clear()
|
||||
@cursor.needsAutoscroll = @cursor.isLastCursor()
|
||||
|
||||
if options.indentBasis? and not options.autoIndent
|
||||
text = @normalizeIndents(text, options.indentBasis)
|
||||
|
||||
newBufferRange = @editSession.buffer.change(oldBufferRange, text)
|
||||
if options.select
|
||||
@setBufferRange(newBufferRange, reverse: wasReversed)
|
||||
@ -272,13 +276,39 @@ class Selection
|
||||
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
|
||||
|
||||
if options.autoIndent
|
||||
if text == '\n'
|
||||
@editSession.autoIndentBufferRow(newBufferRange.end.row)
|
||||
else if /\S/.test(text)
|
||||
@editSession.autoDecreaseIndentForRow(newBufferRange.start.row)
|
||||
@editSession.autoIndentBufferRow(row) for row in newBufferRange.getRows()
|
||||
else if options.autoIndentNewline and text == '\n'
|
||||
@editSession.autoIndentBufferRow(newBufferRange.end.row)
|
||||
else if options.autoDecreaseIndent and /\S/.test text
|
||||
@editSession.autoDecreaseIndentForBufferRow(newBufferRange.start.row)
|
||||
|
||||
newBufferRange
|
||||
|
||||
normalizeIndents: (text, indentBasis) ->
|
||||
textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()]
|
||||
isCursorInsideExistingLine = /\S/.test(textPrecedingCursor)
|
||||
|
||||
lines = text.split('\n')
|
||||
firstLineIndentLevel = @editSession.indentLevelForLine(lines[0])
|
||||
if isCursorInsideExistingLine
|
||||
minimumIndentLevel = @editSession.indentationForBufferRow(@cursor.getBufferRow())
|
||||
else
|
||||
minimumIndentLevel = @cursor.getIndentLevel() + firstLineIndentLevel
|
||||
normalizedLines = []
|
||||
|
||||
for line, i in lines
|
||||
if i == 0
|
||||
indentLevel = firstLineIndentLevel
|
||||
else if /$^/.test line # remove all indentation from empty lines
|
||||
indentLevel = 0
|
||||
else
|
||||
lineIndentLevel = @editSession.indentLevelForLine(lines[i])
|
||||
indentLevel = minimumIndentLevel + (lineIndentLevel - indentBasis)
|
||||
|
||||
normalizedLines.push(@setIndentationForLine(line, indentLevel))
|
||||
|
||||
normalizedLines.join('\n')
|
||||
|
||||
# Indents the selection.
|
||||
#
|
||||
# options - A hash with one key, `autoIndent`. If `true`, the indentation is
|
||||
@ -304,43 +334,8 @@ class Selection
|
||||
for row in [start..end]
|
||||
@editSession.buffer.insert([row, 0], @editSession.getTabText()) unless @editSession.buffer.lineLengthForRow(row) == 0
|
||||
|
||||
normalizeIndent: (text, options) ->
|
||||
return text unless /\n/.test(text)
|
||||
|
||||
currentBufferRow = @cursor.getBufferRow()
|
||||
currentBufferColumn = @cursor.getBufferColumn()
|
||||
lines = text.split('\n')
|
||||
currentBasis = options.indentBasis ? @editSession.indentLevelForLine(lines[0])
|
||||
lines[0] = lines[0].replace(/^\s*/, '') # strip leading space from first line
|
||||
|
||||
normalizedLines = []
|
||||
|
||||
textPrecedingCursor = @editSession.buffer.getTextInRange([[currentBufferRow, 0], [currentBufferRow, currentBufferColumn]])
|
||||
insideExistingLine = textPrecedingCursor.match(/\S/)
|
||||
|
||||
if insideExistingLine
|
||||
desiredBasis = @editSession.indentationForBufferRow(currentBufferRow)
|
||||
else if options.autoIndent
|
||||
desiredBasis = @editSession.suggestedIndentForBufferRow(currentBufferRow)
|
||||
else
|
||||
desiredBasis = @cursor.getIndentLevel()
|
||||
|
||||
for line, i in lines
|
||||
if i == 0
|
||||
if insideExistingLine
|
||||
delta = 0
|
||||
else
|
||||
delta = desiredBasis - @cursor.getIndentLevel()
|
||||
else
|
||||
delta = desiredBasis - currentBasis
|
||||
|
||||
normalizedLines.push(@adjustIndentationForLine(line, delta))
|
||||
|
||||
normalizedLines.join('\n')
|
||||
|
||||
adjustIndentationForLine: (line, delta) ->
|
||||
currentIndentLevel = @editSession.indentLevelForLine(line)
|
||||
desiredIndentLevel = Math.max(0, currentIndentLevel + delta)
|
||||
setIndentationForLine: (line, indentLevel) ->
|
||||
desiredIndentLevel = Math.max(0, indentLevel)
|
||||
desiredIndentString = @editSession.buildIndentString(desiredIndentLevel)
|
||||
line.replace(/^[\t ]*/, desiredIndentString)
|
||||
|
||||
@ -481,12 +476,6 @@ class Selection
|
||||
@editSession.createFold(range.start.row, range.end.row)
|
||||
@cursor.setBufferPosition([range.end.row + 1, 0])
|
||||
|
||||
autoIndentText: (text) ->
|
||||
@editSession.autoIndentTextAfterBufferPosition(text, @cursor.getBufferPosition())
|
||||
|
||||
autoOutdent: ->
|
||||
@editSession.autoOutdentBufferRow(@cursor.getBufferRow())
|
||||
|
||||
modifySelection: (fn) ->
|
||||
@retainSelection = true
|
||||
@placeTail()
|
||||
|
@ -93,7 +93,7 @@ class TokenizedLine
|
||||
delta = 0
|
||||
for token in @tokens
|
||||
delta += token.bufferDelta
|
||||
return token if delta >= bufferColumn
|
||||
return token if delta > bufferColumn
|
||||
token
|
||||
|
||||
breakOutAtomicTokens: (inputTokens, tabLength) ->
|
||||
|
88
src/app/window-event-handler.coffee
Normal file
88
src/app/window-event-handler.coffee
Normal file
@ -0,0 +1,88 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
ipc = require 'ipc'
|
||||
Subscriber = require 'subscriber'
|
||||
|
||||
module.exports =
|
||||
class WindowEventHandler
|
||||
constructor: ->
|
||||
@subscribe ipc, 'command', (command) -> $(window).trigger command
|
||||
|
||||
@subscribe $(window), 'focus', -> $("body").removeClass('is-blurred')
|
||||
@subscribe $(window), 'blur', -> $("body").addClass('is-blurred')
|
||||
@subscribeToCommand $(window), 'window:toggle-full-screen', => atom.toggleFullScreen()
|
||||
@subscribeToCommand $(window), 'window:close', =>
|
||||
if rootView?
|
||||
rootView.confirmClose().done -> closeWithoutConfirm()
|
||||
else
|
||||
closeWithoutConfirm()
|
||||
@subscribeToCommand $(window), 'window:reload', => atom.reload()
|
||||
|
||||
@subscribeToCommand $(document), 'core:focus-next', @focusNext
|
||||
@subscribeToCommand $(document), 'core:focus-previous', @focusPrevious
|
||||
|
||||
@subscribe $(document), 'keydown', keymap.handleKeyEvent
|
||||
|
||||
@subscribe $(document), 'drop', onDrop
|
||||
@subscribe $(document), 'dragover', (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
@subscribe $(document), 'click', 'a', @openLink
|
||||
|
||||
openLink: (event) =>
|
||||
location = $(event.target).attr('href')
|
||||
return unless location
|
||||
return if location[0] is '#'
|
||||
|
||||
if location.indexOf('https://') is 0 or location.indexOf('http://') is 0
|
||||
require('child_process').spawn('open', [location])
|
||||
false
|
||||
|
||||
eachTabIndexedElement: (callback) ->
|
||||
for element in $('[tabindex]')
|
||||
element = $(element)
|
||||
continue if element.isDisabled()
|
||||
|
||||
tabIndex = parseInt(element.attr('tabindex'))
|
||||
continue unless tabIndex >= 0
|
||||
|
||||
callback(element, tabIndex)
|
||||
|
||||
focusNext: =>
|
||||
focusedTabIndex = parseInt($(':focus').attr('tabindex')) or -Infinity
|
||||
|
||||
nextElement = null
|
||||
nextTabIndex = Infinity
|
||||
lowestElement = null
|
||||
lowestTabIndex = Infinity
|
||||
@eachTabIndexedElement (element, tabIndex) ->
|
||||
if tabIndex < lowestTabIndex
|
||||
lowestTabIndex = tabIndex
|
||||
lowestElement = element
|
||||
|
||||
if focusedTabIndex < tabIndex < nextTabIndex
|
||||
nextTabIndex = tabIndex
|
||||
nextElement = element
|
||||
|
||||
(nextElement ? lowestElement).focus()
|
||||
|
||||
focusPrevious: =>
|
||||
focusedTabIndex = parseInt($(':focus').attr('tabindex')) or Infinity
|
||||
|
||||
previousElement = null
|
||||
previousTabIndex = -Infinity
|
||||
highestElement = null
|
||||
highestTabIndex = -Infinity
|
||||
@eachTabIndexedElement (element, tabIndex) ->
|
||||
if tabIndex > highestTabIndex
|
||||
highestTabIndex = tabIndex
|
||||
highestElement = element
|
||||
|
||||
if focusedTabIndex > tabIndex > previousTabIndex
|
||||
previousTabIndex = tabIndex
|
||||
previousElement = element
|
||||
|
||||
(previousElement ? highestElement).focus()
|
||||
|
||||
_.extend WindowEventHandler.prototype, Subscriber
|
@ -1,10 +1,9 @@
|
||||
fs = require 'fs'
|
||||
fsUtils = require 'fs-utils'
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
less = require 'less'
|
||||
ipc = require 'ipc'
|
||||
remote = require 'remote'
|
||||
WindowEventHandler = require 'window-event-handler'
|
||||
require 'jquery-extensions'
|
||||
require 'underscore-extensions'
|
||||
require 'space-pen-extensions'
|
||||
@ -15,6 +14,8 @@ defaultWindowDimensions = {x: 0, y: 0, width: 800, height: 600}
|
||||
|
||||
### Internal ###
|
||||
|
||||
windowEventHandler = null
|
||||
|
||||
# This method is called in any window needing a general environment, including specs
|
||||
window.setUpEnvironment = ->
|
||||
Config = require 'config'
|
||||
@ -27,7 +28,6 @@ window.setUpEnvironment = ->
|
||||
window.syntax = deserialize(atom.getWindowState('syntax')) ? new Syntax
|
||||
window.pasteboard = new Pasteboard
|
||||
window.keymap = new Keymap()
|
||||
$(document).on 'keydown', keymap.handleKeyEvent
|
||||
|
||||
keymap.bindDefaultKeys()
|
||||
|
||||
@ -43,15 +43,11 @@ window.setUpEnvironment = ->
|
||||
|
||||
# This method is only called when opening a real application window
|
||||
window.startEditorWindow = ->
|
||||
directory = _.find ['/opt/boxen', '/opt/github', '/usr/local'], (dir) -> fsUtils.isDirectory(dir)
|
||||
if directory
|
||||
installAtomCommand(fsUtils.join(directory, 'bin/atom'))
|
||||
else
|
||||
console.warn "Failed to install `atom` binary"
|
||||
installAtomCommand()
|
||||
installApmCommand()
|
||||
|
||||
atom.windowMode = 'editor'
|
||||
handleEvents()
|
||||
handleDragDrop()
|
||||
windowEventHandler = new WindowEventHandler
|
||||
config.load()
|
||||
keymap.loadBundledKeymaps()
|
||||
atom.loadThemes()
|
||||
@ -65,7 +61,7 @@ window.startEditorWindow = ->
|
||||
|
||||
window.startConfigWindow = ->
|
||||
atom.windowMode = 'config'
|
||||
handleEvents()
|
||||
windowEventHandler = new WindowEventHandler
|
||||
config.load()
|
||||
keymap.loadBundledKeymaps()
|
||||
atom.loadThemes()
|
||||
@ -88,57 +84,25 @@ window.unloadEditorWindow = ->
|
||||
rootView.remove()
|
||||
project.destroy()
|
||||
git?.destroy()
|
||||
$(window).off('focus blur before')
|
||||
windowEventHandler?.unsubscribe()
|
||||
window.rootView = null
|
||||
window.project = null
|
||||
window.git = null
|
||||
|
||||
window.installAtomCommand = (commandPath, done) ->
|
||||
fs.exists commandPath, (exists) ->
|
||||
return if exists
|
||||
window.installAtomCommand = (callback) ->
|
||||
commandPath = fsUtils.join(window.resourcePath, 'atom.sh')
|
||||
require('command-installer').install(commandPath, callback)
|
||||
|
||||
bundledCommandPath = fsUtils.resolve(window.resourcePath, 'atom.sh')
|
||||
if bundledCommandPath?
|
||||
fs.readFile bundledCommandPath, (error, data) ->
|
||||
if error?
|
||||
console.warn "Failed to install `atom` binary", error
|
||||
else
|
||||
fsUtils.writeAsync commandPath, data, (error) ->
|
||||
if error?
|
||||
console.warn "Failed to install `atom` binary", error
|
||||
else
|
||||
fs.chmod(commandPath, 0o755, commandPath)
|
||||
window.installApmCommand = (callback) ->
|
||||
commandPath = fsUtils.join(window.resourcePath, 'node_modules', '.bin', 'apm')
|
||||
require('command-installer').install(commandPath, callback)
|
||||
|
||||
window.unloadConfigWindow = ->
|
||||
return if not configView
|
||||
atom.setWindowState('configView', configView.serialize())
|
||||
configView.remove()
|
||||
windowEventHandler?.unsubscribe()
|
||||
window.configView = null
|
||||
$(window).off('focus blur before')
|
||||
|
||||
window.handleEvents = ->
|
||||
ipc.on 'command', (command) -> console.log command; $(window).trigger command
|
||||
|
||||
$(window).command 'window:toggle-full-screen', => atom.toggleFullScreen()
|
||||
$(window).on 'focus', -> $("body").removeClass('is-blurred')
|
||||
$(window).on 'blur', -> $("body").addClass('is-blurred')
|
||||
$(window).command 'window:close', => confirmClose()
|
||||
$(window).command 'window:reload', => atom.reload()
|
||||
|
||||
$(document).on 'click', 'a', (e) ->
|
||||
location = $(e.target).attr('href')
|
||||
return unless location
|
||||
return if location[0] is '#'
|
||||
|
||||
if location.indexOf('https://') is 0 or location.indexOf('http://') is 0
|
||||
require('shell').openExternal(location)
|
||||
false
|
||||
|
||||
window.handleDragDrop = ->
|
||||
$(document).on 'dragover', (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
$(document).on 'drop', onDrop
|
||||
|
||||
window.onDrop = (e) ->
|
||||
e.preventDefault()
|
||||
@ -281,10 +245,3 @@ window.profile = (description, fn) ->
|
||||
value = fn()
|
||||
console.profileEnd(description)
|
||||
value
|
||||
|
||||
# Public: Shows a dialog asking if the window was _really_ meant to be closed.
|
||||
confirmClose = ->
|
||||
if rootView?
|
||||
rootView.confirmClose().done -> closeWithoutConfirm()
|
||||
else
|
||||
closeWithoutConfirm()
|
||||
|
@ -40,7 +40,7 @@ describe "AutocompleteView", ->
|
||||
|
||||
beforeEach ->
|
||||
window.rootView = new RootView
|
||||
editor = new Editor(editSession: project.buildEditSession('sample.js'))
|
||||
editor = new Editor(editSession: project.open('sample.js'))
|
||||
atom.activatePackage('autocomplete')
|
||||
autocomplete = new AutocompleteView(editor)
|
||||
miniEditor = autocomplete.miniEditor
|
||||
|
@ -10,7 +10,7 @@ describe "CommandInterpreter", ->
|
||||
|
||||
beforeEach ->
|
||||
interpreter = new CommandInterpreter(project)
|
||||
editSession = project.buildEditSession('sample.js')
|
||||
editSession = project.open('sample.js')
|
||||
buffer = editSession.buffer
|
||||
|
||||
afterEach ->
|
||||
@ -445,7 +445,7 @@ describe "CommandInterpreter", ->
|
||||
runs ->
|
||||
expect(operationsToPreview.length).toBeGreaterThan 3
|
||||
for operation in operationsToPreview
|
||||
editSession = project.buildEditSession(operation.getPath())
|
||||
editSession = project.open(operation.getPath())
|
||||
editSession.setSelectedBufferRange(operation.execute(editSession))
|
||||
expect(editSession.getSelectedText()).toMatch /a+/
|
||||
editSession.destroy()
|
||||
|
@ -2,6 +2,7 @@
|
||||
SelectList = require 'select-list'
|
||||
_ = require 'underscore'
|
||||
$ = require 'jquery'
|
||||
humanize = require 'humanize-plus'
|
||||
fsUtils = require 'fs-utils'
|
||||
LoadPathsTask = require './load-paths-task'
|
||||
|
||||
@ -16,6 +17,7 @@ class FuzzyFinderView extends SelectList
|
||||
maxItems: 10
|
||||
projectPaths: null
|
||||
reloadProjectPaths: true
|
||||
filterKey: 'projectRelativePath'
|
||||
|
||||
initialize: ->
|
||||
super
|
||||
@ -35,7 +37,7 @@ class FuzzyFinderView extends SelectList
|
||||
@miniEditor.command 'pane:split-up', =>
|
||||
@splitOpenPath (pane, session) -> pane.splitUp(session)
|
||||
|
||||
itemForElement: (path) ->
|
||||
itemForElement: ({path, projectRelativePath}) ->
|
||||
$$ ->
|
||||
@li class: 'two-lines', =>
|
||||
if git?
|
||||
@ -60,20 +62,20 @@ class FuzzyFinderView extends SelectList
|
||||
typeClass = 'text-name'
|
||||
|
||||
@div fsUtils.base(path), class: "primary-line file #{typeClass}"
|
||||
@div project.relativize(path), class: 'secondary-line path'
|
||||
@div projectRelativePath, class: 'secondary-line path'
|
||||
|
||||
openPath: (path) ->
|
||||
rootView.open(path, {@allowActiveEditorChange}) if path
|
||||
|
||||
splitOpenPath: (fn) ->
|
||||
path = @getSelectedElement()
|
||||
{path} = @getSelectedElement()
|
||||
return unless path
|
||||
if pane = rootView.getActivePane()
|
||||
fn(pane, project.buildEditSession(path))
|
||||
fn(pane, project.open(path))
|
||||
else
|
||||
@openPath(path)
|
||||
|
||||
confirmed : (path) ->
|
||||
confirmed : ({path}) ->
|
||||
return unless path.length
|
||||
if fsUtils.isFile(path)
|
||||
@cancel()
|
||||
@ -131,6 +133,13 @@ class FuzzyFinderView extends SelectList
|
||||
@attach()
|
||||
@miniEditor.setText(currentWord)
|
||||
|
||||
setArray: (paths) ->
|
||||
projectRelativePaths = paths.map (path) ->
|
||||
projectRelativePath = project.relativize(path)
|
||||
{path, projectRelativePath}
|
||||
|
||||
super(projectRelativePaths)
|
||||
|
||||
populateGitStatusPaths: ->
|
||||
paths = []
|
||||
paths.push(path) for path, status of git.statuses when fsUtils.isFile(path)
|
||||
@ -159,7 +168,7 @@ class FuzzyFinderView extends SelectList
|
||||
@populateProjectPaths(options)
|
||||
@loadPathsTask = new LoadPathsTask(callback)
|
||||
@loadPathsTask.on 'paths-loaded', (paths) =>
|
||||
@loadingBadge.text(paths.length)
|
||||
@loadingBadge.text(humanize.intcomma(paths.length))
|
||||
@loadPathsTask.start()
|
||||
|
||||
populateOpenBufferPaths: ->
|
||||
|
@ -35,9 +35,15 @@ class PathLoader
|
||||
|
||||
loadPath: (path) ->
|
||||
@asyncCallStarting()
|
||||
fs.stat path, (error, stats) =>
|
||||
fs.lstat path, (error, stats) =>
|
||||
unless error?
|
||||
if stats.isDirectory()
|
||||
if stats.isSymbolicLink()
|
||||
@asyncCallStarting()
|
||||
fs.stat path, (error, stats) =>
|
||||
unless error?
|
||||
@pathLoaded(path) if stats.isFile()
|
||||
@asyncCallDone()
|
||||
else if stats.isDirectory()
|
||||
@loadFolder(path) unless @isIgnored(path)
|
||||
else if stats.isFile()
|
||||
@pathLoaded(path)
|
||||
|
@ -71,6 +71,17 @@ describe 'FuzzyFinder', ->
|
||||
runs ->
|
||||
expect(finderView.list.find("li:contains(symlink-to-file)")).toExist()
|
||||
|
||||
it "excludes symlinked folder paths", ->
|
||||
rootView.attachToDom()
|
||||
finderView.maxItems = Infinity
|
||||
rootView.trigger 'fuzzy-finder:toggle-file-finder'
|
||||
|
||||
waitsFor "all project paths to load", 5000, ->
|
||||
not finderView.reloadProjectPaths
|
||||
|
||||
runs ->
|
||||
expect(finderView.list.find("li:contains(symlink-to-dir)")).not.toExist()
|
||||
|
||||
describe "when root view's project has no path", ->
|
||||
beforeEach ->
|
||||
project.setPath(null)
|
||||
@ -89,7 +100,7 @@ describe 'FuzzyFinder', ->
|
||||
rootView.trigger 'fuzzy-finder:toggle-file-finder'
|
||||
|
||||
expectedPath = project.resolve('dir/a')
|
||||
finderView.confirmed(expectedPath)
|
||||
finderView.confirmed({path: expectedPath})
|
||||
|
||||
expect(finderView.hasParent()).toBeFalsy()
|
||||
expect(editor1.getPath()).not.toBe expectedPath
|
||||
@ -101,7 +112,7 @@ describe 'FuzzyFinder', ->
|
||||
rootView.attachToDom()
|
||||
path = rootView.getActiveView().getPath()
|
||||
rootView.trigger 'fuzzy-finder:toggle-file-finder'
|
||||
finderView.confirmed('dir/this/is/not/a/file.txt')
|
||||
finderView.confirmed({path: 'dir/this/is/not/a/file.txt'})
|
||||
expect(finderView.hasParent()).toBeTruthy()
|
||||
expect(rootView.getActiveView().getPath()).toBe path
|
||||
expect(finderView.find('.error').text().length).toBeGreaterThan 0
|
||||
@ -199,7 +210,7 @@ describe 'FuzzyFinder', ->
|
||||
describe "when the active pane has an item for the selected path", ->
|
||||
it "switches to the item for the selected path", ->
|
||||
expectedPath = project.resolve('sample.txt')
|
||||
finderView.confirmed(expectedPath)
|
||||
finderView.confirmed({path: expectedPath})
|
||||
|
||||
expect(finderView.hasParent()).toBeFalsy()
|
||||
expect(editor1.getPath()).not.toBe expectedPath
|
||||
@ -215,7 +226,7 @@ describe 'FuzzyFinder', ->
|
||||
expect(rootView.getActiveView()).toBe editor1
|
||||
|
||||
expectedPath = project.resolve('sample.txt')
|
||||
finderView.confirmed(expectedPath)
|
||||
finderView.confirmed({path: expectedPath})
|
||||
|
||||
expect(finderView.hasParent()).toBeFalsy()
|
||||
expect(editor1.getPath()).toBe expectedPath
|
||||
@ -438,7 +449,7 @@ describe 'FuzzyFinder', ->
|
||||
spyOn(pane, "splitLeft").andCallThrough()
|
||||
|
||||
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
|
||||
path = finderView.getSelectedElement()
|
||||
{path} = finderView.getSelectedElement()
|
||||
finderView.miniEditor.trigger 'pane:split-left'
|
||||
|
||||
expect(rootView.getPanes().length).toBe 2
|
||||
@ -451,7 +462,7 @@ describe 'FuzzyFinder', ->
|
||||
spyOn(pane, "splitRight").andCallThrough()
|
||||
|
||||
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
|
||||
path = finderView.getSelectedElement()
|
||||
{path} = finderView.getSelectedElement()
|
||||
finderView.miniEditor.trigger 'pane:split-right'
|
||||
|
||||
expect(rootView.getPanes().length).toBe 2
|
||||
@ -464,7 +475,7 @@ describe 'FuzzyFinder', ->
|
||||
spyOn(pane, "splitUp").andCallThrough()
|
||||
|
||||
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
|
||||
path = finderView.getSelectedElement()
|
||||
{path} = finderView.getSelectedElement()
|
||||
finderView.miniEditor.trigger 'pane:split-up'
|
||||
|
||||
expect(rootView.getPanes().length).toBe 2
|
||||
@ -477,7 +488,7 @@ describe 'FuzzyFinder', ->
|
||||
spyOn(pane, "splitDown").andCallThrough()
|
||||
|
||||
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
|
||||
path = finderView.getSelectedElement()
|
||||
{path} = finderView.getSelectedElement()
|
||||
finderView.miniEditor.trigger 'pane:split-down'
|
||||
|
||||
expect(rootView.getPanes().length).toBe 2
|
||||
|
@ -54,80 +54,80 @@
|
||||
'name': 'comment.hr.gfm'
|
||||
}
|
||||
{
|
||||
'begin': '^```coffee(script)?$'
|
||||
'begin': '^`{3,}coffee(script)?$'
|
||||
'beginCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'end': '^```$'
|
||||
'end': '^`{3,}$'
|
||||
'endCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'name': 'markup.code.coffee.gfm'
|
||||
'patterns': ['include': 'source.coffee']
|
||||
}
|
||||
{
|
||||
'begin': '^```(javascript|js)$'
|
||||
'begin': '^`{3,}(javascript|js)$'
|
||||
'beginCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'end': '^```$'
|
||||
'end': '^`{3,}$'
|
||||
'endCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'name': 'markup.code.js.gfm'
|
||||
'patterns': ['include': 'source.js']
|
||||
}
|
||||
{
|
||||
'begin': '^```css$'
|
||||
'begin': '^`{3,}css$'
|
||||
'beginCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'end': '^```$'
|
||||
'end': '^`{3,}$'
|
||||
'endCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'name': 'markup.code.css.gfm'
|
||||
'patterns': ['include': 'source.css']
|
||||
}
|
||||
{
|
||||
'begin': '^```xml$'
|
||||
'begin': '^`{3,}xml$'
|
||||
'beginCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'end': '^```$'
|
||||
'end': '^`{3,}$'
|
||||
'endCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'name': 'markup.code.xml.gfm'
|
||||
'patterns': ['include': 'text.xml']
|
||||
}
|
||||
{
|
||||
'begin': '^```(ruby|rb)$'
|
||||
'begin': '^`{3,}(ruby|rb)$'
|
||||
'beginCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'end': '^```$'
|
||||
'end': '^`{3,}$'
|
||||
'endCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'name': 'markup.code.ruby.gfm'
|
||||
'patterns': ['include': 'source.ruby']
|
||||
}
|
||||
{
|
||||
'begin': '^```java$'
|
||||
'begin': '^`{3,}java$'
|
||||
'beginCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'end': '^```$'
|
||||
'end': '^`{3,}$'
|
||||
'endCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'name': 'markup.code.java.gfm'
|
||||
'patterns': ['include': 'source.java']
|
||||
}
|
||||
{
|
||||
'begin': '^```(sh|bash)$'
|
||||
'begin': '^`{3,}(sh|bash)$'
|
||||
'beginCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'end': '^```$'
|
||||
'end': '^`{3,}$'
|
||||
'endCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'name': 'markup.code.shell.gfm'
|
||||
'patterns': ['include': 'source.shell']
|
||||
}
|
||||
{
|
||||
'begin': '^```.*$'
|
||||
'begin': '^`{3,}.*$'
|
||||
'beginCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'end': '^```$'
|
||||
'end': '^`{3,}$'
|
||||
'endCaptures':
|
||||
'0': 'name': 'support.gfm'
|
||||
'name': 'markup.raw.gfm'
|
||||
|
@ -1,6 +1,5 @@
|
||||
$ = require 'jquery'
|
||||
{$$} = require 'space-pen'
|
||||
|
||||
module.exports =
|
||||
class Gists
|
||||
@activate: -> new Gists
|
||||
@ -22,6 +21,9 @@ class Gists
|
||||
dataType: 'json'
|
||||
contentType: 'application/json; charset=UTF-8'
|
||||
data: JSON.stringify(gist)
|
||||
beforeSend: (xhr) ->
|
||||
if token = require('keytar').getPassword('GitHub.com', 'github')
|
||||
xhr.setRequestHeader('Authorization', "bearer #{token}")
|
||||
success: (response) =>
|
||||
pasteboard.write(response.html_url)
|
||||
notification = $$ ->
|
||||
|
7
src/packages/github-sign-in/keymaps/github-sign-in.cson
Normal file
7
src/packages/github-sign-in/keymaps/github-sign-in.cson
Normal file
@ -0,0 +1,7 @@
|
||||
'.sign-in-view':
|
||||
'esc': 'core:cancel'
|
||||
'.sign-in-view input':
|
||||
'meta-enter': 'core:confirm'
|
||||
'.sign-in-view button':
|
||||
'enter': 'core:confirm'
|
||||
'meta-enter': 'core:confirm'
|
121
src/packages/github-sign-in/lib/sign-in-view.coffee
Normal file
121
src/packages/github-sign-in/lib/sign-in-view.coffee
Normal file
@ -0,0 +1,121 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
ScrollView = require 'scroll-view'
|
||||
keytar = require 'keytar'
|
||||
|
||||
class SignInView extends ScrollView
|
||||
@content: ->
|
||||
@div class: 'sign-in-view overlay from-top', =>
|
||||
@h4 'Sign in to GitHub'
|
||||
@p 'Your password will only be used to generate a token that will be stored in your keychain.'
|
||||
@div class: 'form-inline', =>
|
||||
@input outlet: 'username', type: 'text', placeholder: 'Username or Email', tabindex: 1
|
||||
@input outlet: 'password', type: 'password', placeholder: 'Password', tabindex: 2
|
||||
@button outlet: 'signIn', class: 'btn', disabled: 'disabled', tabindex: 3, 'Sign in'
|
||||
@button outlet: 'cancel', class: 'btn', tabindex: 4, 'Cancel'
|
||||
@div outlet: 'alert', class: 'alert alert-error'
|
||||
|
||||
initialize: ({@signedInUser}={})->
|
||||
rootView.command 'github:sign-in', => @attach()
|
||||
|
||||
@username.on 'core:confirm', => @generateOAuth2Token()
|
||||
@username.on 'input', => @validate()
|
||||
|
||||
@password.on 'core:confirm', => @generateOAuth2Token()
|
||||
@password.on 'input', => @validate()
|
||||
|
||||
@signIn.on 'core:confirm', => @generateOAuth2Token()
|
||||
@signIn.on 'click', => @generateOAuth2Token()
|
||||
|
||||
@cancel.on 'core:confirm', => @generateOAuth2Token()
|
||||
|
||||
@cancel.on 'click', => @detach()
|
||||
@on 'core:cancel', => @detach()
|
||||
|
||||
@subscribe $(document.body), 'click focusin', (e) =>
|
||||
@detach() unless $.contains(this[0], e.target)
|
||||
|
||||
serialize: -> {@signedInUser}
|
||||
|
||||
validate: ->
|
||||
if $.trim(@username.val()).length > 0 and @password.val().length > 0
|
||||
@signIn.enable()
|
||||
else
|
||||
@signIn.disable()
|
||||
|
||||
generateOAuth2Token: ->
|
||||
return if @signIn.isDisabled()
|
||||
|
||||
@alert.hide()
|
||||
@username.disable()
|
||||
@password.disable()
|
||||
@signIn.disable()
|
||||
|
||||
username = $.trim(@username.val())
|
||||
credentials = btoa("#{username}:#{@password.val()}")
|
||||
request =
|
||||
scopes: ['user', 'repo', 'gist']
|
||||
note: 'GitHub Atom'
|
||||
note_url: 'https://github.com/github/atom'
|
||||
$.ajax
|
||||
url: 'https://api.github.com/authorizations'
|
||||
type: 'POST'
|
||||
dataType: 'json'
|
||||
contentType: 'application/json; charset=UTF-8'
|
||||
data: JSON.stringify(request)
|
||||
|
||||
beforeSend: (xhr) ->
|
||||
xhr.setRequestHeader('Authorization', "Basic #{credentials}")
|
||||
|
||||
success: ({token}={}) =>
|
||||
if token?.length > 0
|
||||
@signedInUser = username
|
||||
unless keytar.replacePassword('github.com', 'github', token)
|
||||
console.warn 'Unable to save GitHub token to keychain'
|
||||
@detach()
|
||||
|
||||
error: (response={}) =>
|
||||
if _.isString(response.responseText)
|
||||
try
|
||||
message = JSON.parse(response.responseText)?.message
|
||||
else
|
||||
message = response.responseText?.message
|
||||
message ?= ''
|
||||
@alert.text(message).show()
|
||||
@username.enable()
|
||||
@password.enable()
|
||||
@signIn.enable()
|
||||
@password.focus()
|
||||
|
||||
attach: ->
|
||||
if @signedInUser?
|
||||
@username.val(@signedInUser)
|
||||
else
|
||||
@username.val('')
|
||||
@password.val('')
|
||||
@username.enable()
|
||||
@password.enable()
|
||||
@alert.hide()
|
||||
|
||||
rootView.append(this)
|
||||
|
||||
if @signedInUser?
|
||||
@password.focus()
|
||||
else
|
||||
@username.focus()
|
||||
|
||||
module.exports =
|
||||
signInView: null
|
||||
|
||||
activate: (state) ->
|
||||
@signInView = new SignInView(state)
|
||||
|
||||
deactivate: ->
|
||||
@signInView?.remove()
|
||||
@signInView = null
|
||||
|
||||
serialize: ->
|
||||
if @signInView?
|
||||
@signInView.serialize()
|
||||
else
|
||||
@state
|
4
src/packages/github-sign-in/package.cson
Normal file
4
src/packages/github-sign-in/package.cson
Normal file
@ -0,0 +1,4 @@
|
||||
'main': 'lib/sign-in-view'
|
||||
'activationEvents': [
|
||||
'github:sign-in'
|
||||
]
|
14
src/packages/github-sign-in/stylesheets/github-sign-in.less
Normal file
14
src/packages/github-sign-in/stylesheets/github-sign-in.less
Normal file
@ -0,0 +1,14 @@
|
||||
.sign-in-view {
|
||||
input, button, .alert {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.alert:before {
|
||||
content: 'Sign in failed. ';
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@ fenceNameToExtension =
|
||||
"java": "java"
|
||||
"sh": "sh"
|
||||
"bash": "sh"
|
||||
"js": "js"
|
||||
"javascript": "js"
|
||||
|
||||
module.exports =
|
||||
class MarkdownPreviewView extends ScrollView
|
||||
@ -93,7 +95,7 @@ class MarkdownPreviewView extends ScrollView
|
||||
|
||||
renderMarkdown: ->
|
||||
@setLoading()
|
||||
roaster @buffer.getText(), {}, (err, html) =>
|
||||
roaster @buffer.getText(), (err, html) =>
|
||||
if err
|
||||
@setErrorHtml(err)
|
||||
else
|
||||
|
@ -5,7 +5,7 @@ _ = require 'underscore'
|
||||
SnippetExpansion = require './snippet-expansion'
|
||||
Snippet = require './snippet'
|
||||
TextMatePackage = require 'text-mate-package'
|
||||
CSON = require 'cson'
|
||||
CSON = require 'season'
|
||||
async = require 'async'
|
||||
|
||||
module.exports =
|
||||
@ -41,7 +41,7 @@ module.exports =
|
||||
loadSnippetFile = (filename, done) =>
|
||||
return done() if filename.indexOf('.') is 0
|
||||
filepath = fsUtils.join(snippetsDirPath, filename)
|
||||
CSON.readObjectAsync filepath, (err, object) =>
|
||||
CSON.readFile filepath, (err, object) =>
|
||||
if err
|
||||
console.warn "Error reading snippets file '#{filepath}': #{err.stack}"
|
||||
else
|
||||
@ -66,7 +66,7 @@ module.exports =
|
||||
try
|
||||
readObject =
|
||||
if CSON.isObjectPath(filepath)
|
||||
CSON.readObjectAsync.bind(CSON)
|
||||
CSON.readFile.bind(CSON)
|
||||
else
|
||||
fsUtils.readPlistAsync.bind(fsUtils)
|
||||
|
||||
|
@ -6,9 +6,12 @@
|
||||
-webkit-user-select: none;
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
min-width: -webkit-min-content;
|
||||
}
|
||||
|
||||
.status-bar .git-branch {
|
||||
margin-left: 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ describe "TabBarView", ->
|
||||
registerDeserializer(TestView)
|
||||
item1 = new TestView('Item 1')
|
||||
item2 = new TestView('Item 2')
|
||||
editSession1 = project.buildEditSession('sample.js')
|
||||
editSession1 = project.open('sample.js')
|
||||
paneContainer = new PaneContainer
|
||||
pane = new Pane(item1, editSession1, item2)
|
||||
pane.showItem(item2)
|
||||
@ -77,7 +77,7 @@ describe "TabBarView", ->
|
||||
expect(tabBar.tabAtIndex(1).find('.title')).toHaveText 'Item 3'
|
||||
|
||||
it "adds the 'modified' class to the new tab if the item is initially modified", ->
|
||||
editSession2 = project.buildEditSession('sample.txt')
|
||||
editSession2 = project.open('sample.txt')
|
||||
editSession2.insertText('x')
|
||||
pane.showItem(editSession2)
|
||||
expect(tabBar.tabForItem(editSession2)).toHaveClass 'modified'
|
||||
|
@ -2,6 +2,7 @@
|
||||
FileView = require './file-view'
|
||||
Directory = require 'directory'
|
||||
$ = require 'jquery'
|
||||
fs = require 'fs'
|
||||
|
||||
module.exports =
|
||||
class DirectoryView extends View
|
||||
@ -34,7 +35,7 @@ class DirectoryView extends View
|
||||
@updateStatus()
|
||||
@updateStatus()
|
||||
else
|
||||
iconClass = 'repository-icon' if path is git.getWorkingDirectory()
|
||||
iconClass = 'repository-icon' if @isRepositoryRoot()
|
||||
|
||||
@directoryName.addClass(iconClass)
|
||||
|
||||
@ -53,6 +54,12 @@ class DirectoryView extends View
|
||||
getPath: ->
|
||||
@directory.path
|
||||
|
||||
isRepositoryRoot: ->
|
||||
try
|
||||
git? and git.getWorkingDirectory() is fs.realpathSync(@getPath())
|
||||
catch e
|
||||
false
|
||||
|
||||
isPathIgnored: (path) ->
|
||||
config.get("core.hideGitIgnoredFiles") and git?.isPathIgnored(path)
|
||||
|
||||
|
@ -22,7 +22,6 @@ describe "Whitespace", ->
|
||||
spyOn(fsUtils, 'write')
|
||||
|
||||
config.set("whitespace.ensureSingleTrailingNewline", false)
|
||||
config.update()
|
||||
|
||||
# works for buffers that are already open when extension is initialized
|
||||
editor.insertText("foo \nbar\t \n\nbaz")
|
||||
@ -45,7 +44,6 @@ describe "Whitespace", ->
|
||||
|
||||
afterEach ->
|
||||
config.set("whitespace.ensureSingleTrailingNewline", originalConfigValue)
|
||||
config.update()
|
||||
|
||||
it "adds a trailing newline when there is no trailing newline", ->
|
||||
editor.insertText "foo"
|
||||
@ -74,7 +72,6 @@ describe "Whitespace", ->
|
||||
|
||||
it "does not add trailing newline if ensureSingleTrailingNewline is false", ->
|
||||
config.set("whitespace.ensureSingleTrailingNewline", false)
|
||||
config.update()
|
||||
|
||||
editor.insertText "no trailing newline"
|
||||
editor.getBuffer().save()
|
||||
|
50
src/stdlib/command-installer.coffee
Normal file
50
src/stdlib/command-installer.coffee
Normal file
@ -0,0 +1,50 @@
|
||||
path = require 'path'
|
||||
fs = require 'fs'
|
||||
_ = require 'underscore'
|
||||
async = require 'async'
|
||||
mkdirp = require 'mkdirp'
|
||||
fsUtils = require 'fs-utils'
|
||||
|
||||
symlinkCommand = (sourcePath, destinationPath, callback) ->
|
||||
mkdirp fsUtils.directory(destinationPath), (error) ->
|
||||
if error?
|
||||
callback(error)
|
||||
else
|
||||
fs.symlink sourcePath, destinationPath, (error) ->
|
||||
if error?
|
||||
callback(error)
|
||||
else
|
||||
fs.chmod(destinationPath, 0o755, callback)
|
||||
|
||||
unlinkCommand = (destinationPath, callback) ->
|
||||
fs.exists destinationPath, (exists) ->
|
||||
if exists
|
||||
fs.unlink(destinationPath, callback)
|
||||
else
|
||||
callback()
|
||||
|
||||
module.exports =
|
||||
findInstallDirectory: (callback) ->
|
||||
directories = ['/opt/boxen','/opt/github','/usr/local']
|
||||
async.detect(directories, fsUtils.isDirectoryAsync, callback)
|
||||
|
||||
install: (commandPath, commandName, callback) ->
|
||||
if not commandName? or _.isFunction(commandName)
|
||||
callback = commandName
|
||||
commandName = path.basename(commandPath, path.extname(commandPath))
|
||||
|
||||
installCallback = (error) ->
|
||||
if error?
|
||||
console.warn "Failed to install `#{commandName}` binary", error
|
||||
callback?(error)
|
||||
|
||||
@findInstallDirectory (directory) ->
|
||||
if directory?
|
||||
destinationPath = path.join(directory, 'bin', commandName)
|
||||
unlinkCommand destinationPath, (error) ->
|
||||
if error?
|
||||
installCallback(error)
|
||||
else
|
||||
symlinkCommand(commandPath, destinationPath, installCallback)
|
||||
else
|
||||
installCallback(new Error("No destination directory exists to install"))
|
@ -1,117 +0,0 @@
|
||||
require 'underscore-extensions'
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
fsUtils = require 'fs-utils'
|
||||
|
||||
module.exports =
|
||||
isObjectPath: (path) ->
|
||||
extension = fsUtils.extension(path)
|
||||
extension is '.cson' or extension is '.json'
|
||||
|
||||
readObject: (path) ->
|
||||
@parseObject(path, fsUtils.read(path))
|
||||
|
||||
readObjectAsync: (path, done) ->
|
||||
fs.readFile path, 'utf8', (err, contents) =>
|
||||
return done(err) if err?
|
||||
try
|
||||
done(null, @parseObject(path, contents))
|
||||
catch err
|
||||
done(err)
|
||||
|
||||
parseObject: (path, contents) ->
|
||||
if fsUtils.extension(path) is '.cson'
|
||||
CoffeeScript = require 'coffee-script'
|
||||
CoffeeScript.eval(contents, bare: true)
|
||||
else
|
||||
JSON.parse(contents)
|
||||
|
||||
writeObject: (path, object) ->
|
||||
if fsUtils.extension(path) is '.cson'
|
||||
content = @stringify(object)
|
||||
else
|
||||
content = JSON.stringify(object, undefined, 2)
|
||||
fsUtils.write(path, "#{content}\n")
|
||||
|
||||
stringifyIndent: (level=0) -> _.multiplyString(' ', Math.max(level, 0))
|
||||
|
||||
stringifyString: (string) ->
|
||||
string = JSON.stringify(string)
|
||||
string = string[1...-1] # Remove surrounding double quotes
|
||||
string = string.replace(/\\"/g, '"') # Unescape escaped double quotes
|
||||
string = string.replace(/'/g, '\\\'') # Escape single quotes
|
||||
"'#{string}'" # Wrap in single quotes
|
||||
|
||||
stringifyBoolean: (boolean) -> "#{boolean}"
|
||||
|
||||
stringifyNumber: (number) -> "#{number}"
|
||||
|
||||
stringifyNull: -> 'null'
|
||||
|
||||
stringifyArray: (array, indentLevel=0) ->
|
||||
return '[]' if array.length is 0
|
||||
|
||||
cson = '[\n'
|
||||
for value in array
|
||||
indent = @stringifyIndent(indentLevel + 2)
|
||||
cson += indent
|
||||
if _.isString(value)
|
||||
cson += @stringifyString(value)
|
||||
else if _.isBoolean(value)
|
||||
cson += @stringifyBoolean(value)
|
||||
else if _.isNumber(value)
|
||||
cson += @stringifyNumber(value)
|
||||
else if _.isNull(value) or value is undefined
|
||||
cson += @stringifyNull(value)
|
||||
else if _.isArray(value)
|
||||
cson += @stringifyArray(value, indentLevel + 2)
|
||||
else if _.isObject(value)
|
||||
cson += "{\n#{@stringifyObject(value, indentLevel + 4)}\n#{indent}}"
|
||||
else
|
||||
throw new Error("Unrecognized type for array value: #{value}")
|
||||
cson += '\n'
|
||||
"#{cson}#{@stringifyIndent(indentLevel)}]"
|
||||
|
||||
stringifyObject: (object, indentLevel=0) ->
|
||||
return '{}' if _.isEmpty(object)
|
||||
|
||||
cson = ''
|
||||
prefix = ''
|
||||
for key, value of object
|
||||
continue if value is undefined
|
||||
if _.isFunction(value)
|
||||
throw new Error("Function specified as value to key: #{key}")
|
||||
|
||||
cson += "#{prefix}#{@stringifyIndent(indentLevel)}'#{key}':"
|
||||
if _.isString(value)
|
||||
cson += " #{@stringifyString(value)}"
|
||||
else if _.isBoolean(value)
|
||||
cson += " #{@stringifyBoolean(value)}"
|
||||
else if _.isNumber(value)
|
||||
cson += " #{@stringifyNumber(value)}"
|
||||
else if _.isNull(value)
|
||||
cson += " #{@stringifyNull(value)}"
|
||||
else if _.isArray(value)
|
||||
cson += " #{@stringifyArray(value, indentLevel)}"
|
||||
else if _.isObject(value)
|
||||
if _.isEmpty(value)
|
||||
cson += ' {}'
|
||||
else
|
||||
cson += "\n#{@stringifyObject(value, indentLevel + 2)}"
|
||||
else
|
||||
throw new Error("Unrecognized value type for key: #{key} with value: #{value}")
|
||||
prefix = '\n'
|
||||
cson
|
||||
|
||||
stringify: (object) ->
|
||||
throw new Error("Cannot stringify undefined object") if object is undefined
|
||||
throw new Error("Cannot stringify function: #{object}") if _.isFunction(object)
|
||||
|
||||
return @stringifyString(object) if _.isString(object)
|
||||
return @stringifyBoolean(object) if _.isBoolean(object)
|
||||
return @stringifyNumber(object) if _.isNumber(object)
|
||||
return @stringifyNull(object) if _.isNull(object)
|
||||
return @stringifyArray(object) if _.isArray(object)
|
||||
return @stringifyObject(object) if _.isObject(object)
|
||||
|
||||
throw new Error("Unrecognized type to stringify: #{object}")
|
@ -73,8 +73,11 @@ module.exports =
|
||||
return done(false) unless path?.length > 0
|
||||
fs.exists path, (exists) ->
|
||||
if exists
|
||||
fs.stat path, (err, stat) ->
|
||||
done(stat?.isDirectory() ? false)
|
||||
fs.stat path, (error, stat) ->
|
||||
if error?
|
||||
done(false)
|
||||
else
|
||||
done(stat.isDirectory())
|
||||
else
|
||||
done(false)
|
||||
|
||||
@ -87,6 +90,13 @@ module.exports =
|
||||
catch e
|
||||
false
|
||||
|
||||
# Returns true if the specified path is exectuable.
|
||||
isExecutable: (path) ->
|
||||
try
|
||||
(fs.statSync(path).mode & 0o777 & 1) isnt 0
|
||||
catch e
|
||||
false
|
||||
|
||||
# Returns an array with all the names of files contained
|
||||
# in the directory path.
|
||||
list: (rootPath, extensions) ->
|
||||
@ -334,15 +344,15 @@ module.exports =
|
||||
done(err)
|
||||
|
||||
readObject: (path) ->
|
||||
cson = require 'cson'
|
||||
if cson.isObjectPath(path)
|
||||
cson.readObject(path)
|
||||
CSON = require 'season'
|
||||
if CSON.isObjectPath(path)
|
||||
CSON.readFileSync(path)
|
||||
else
|
||||
@readPlist(path)
|
||||
|
||||
readObjectAsync: (path, done) ->
|
||||
cson = require 'cson'
|
||||
if cson.isObjectPath(path)
|
||||
cson.readObjectAsync(path, done)
|
||||
CSON = require 'season'
|
||||
if CSON.isObjectPath(path)
|
||||
CSON.readFile(path, done)
|
||||
else
|
||||
@readPlistAsync(path, done)
|
||||
|
@ -37,6 +37,15 @@ $.fn.isOnDom = ->
|
||||
$.fn.isVisible = ->
|
||||
@is(':visible')
|
||||
|
||||
$.fn.isDisabled = ->
|
||||
!!@attr('disabled')
|
||||
|
||||
$.fn.enable = ->
|
||||
@removeAttr('disabled')
|
||||
|
||||
$.fn.disable = ->
|
||||
@attr('disabled', 'disabled')
|
||||
|
||||
$.fn.containsElement = (element) ->
|
||||
(element[0].compareDocumentPosition(this[0]) & 8) == 8
|
||||
|
||||
|
@ -1,20 +1,27 @@
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
subscribe: (eventEmitter, eventName, callback) ->
|
||||
eventEmitter.on eventName, callback
|
||||
subscribeWith: (eventEmitter, methodName, args) ->
|
||||
eventEmitter[methodName](args...)
|
||||
|
||||
@subscriptions ?= []
|
||||
@subscriptionsByObject ?= new WeakMap
|
||||
@subscriptionsByObject.set(eventEmitter, []) unless @subscriptionsByObject.has(eventEmitter)
|
||||
|
||||
subscription = cancel: -> eventEmitter.off eventName, callback
|
||||
eventName = _.first(args)
|
||||
callback = _.last(args)
|
||||
subscription = cancel: ->
|
||||
# node's EventEmitter doesn't have 'off' method.
|
||||
removeListener = eventEmitter.off ? eventEmitter.removeListener
|
||||
removeListener.call eventEmitter, eventName, callback
|
||||
@subscriptions.push(subscription)
|
||||
@subscriptionsByObject.get(eventEmitter).push(subscription)
|
||||
|
||||
subscribeToCommand: (view, eventName, callback) ->
|
||||
view.command eventName, callback
|
||||
@subscriptions ?= []
|
||||
@subscriptions.push(cancel: -> view.off eventName, callback)
|
||||
subscribe: (eventEmitter, args...) ->
|
||||
@subscribeWith(eventEmitter, 'on', args)
|
||||
|
||||
subscribeToCommand: (eventEmitter, args...) ->
|
||||
@subscribeWith(eventEmitter, 'command', args)
|
||||
|
||||
unsubscribe: (object) ->
|
||||
if object?
|
||||
|
@ -34,7 +34,8 @@ global.document = window.document
|
||||
global.callTaskMethod = (method, args...) ->
|
||||
process.send(method: method, args: args)
|
||||
|
||||
# The worker's initial handler replaces itglobal when `start` is invoked
|
||||
# The worker's initial handler replaces the global hadndler when `start` is
|
||||
# invoked.
|
||||
global.handler =
|
||||
start: ({globals, handlerPath}) ->
|
||||
for key, value of globals
|
||||
|
@ -4,6 +4,7 @@ _.mixin
|
||||
remove: (array, element) ->
|
||||
index = array.indexOf(element)
|
||||
array.splice(index, 1) if index >= 0
|
||||
array
|
||||
|
||||
spliceWithArray: (originalArray, start, length, insertedArray, chunkSize=100000) ->
|
||||
if insertedArray.length < chunkSize
|
||||
@ -126,6 +127,35 @@ _.mixin
|
||||
endsWith: (string, suffix) ->
|
||||
string.indexOf(suffix, string.length - suffix.length) isnt -1
|
||||
|
||||
# Transform the given object into another object.
|
||||
#
|
||||
# `object` - The object to transform.
|
||||
# `iterator` -
|
||||
# A function that takes `(key, value)` arguments and returns a
|
||||
# `[key, value]` tuple.
|
||||
#
|
||||
# Returns a new object based with the key/values returned by the iterator.
|
||||
mapObject: (object, iterator) ->
|
||||
newObject = {}
|
||||
for key, value of object
|
||||
[key, value] = iterator(key, value)
|
||||
newObject[key] = value
|
||||
|
||||
newObject
|
||||
|
||||
# Deep clones the given JSON object.
|
||||
#
|
||||
# `object` - The JSON object to clone.
|
||||
#
|
||||
# Returns a deep clone of the JSON object.
|
||||
deepClone: (object) ->
|
||||
if _.isArray(object)
|
||||
object.map (value) -> _.deepClone(value)
|
||||
else if _.isObject(object)
|
||||
@mapObject object, (key, value) => [key, @deepClone(value)]
|
||||
else
|
||||
object
|
||||
|
||||
valueForKeyPath: (object, keyPath) ->
|
||||
keys = keyPath.split('.')
|
||||
for key in keys
|
||||
|
@ -62,7 +62,7 @@ html, body {
|
||||
|
||||
.pane .item-views > * {
|
||||
-webkit-flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
1
vendor/apm
vendored
Submodule
1
vendor/apm
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 01ded1c72cb71a0ac85fe267e3dafc370797211f
|
2
vendor/packages/ruby.tmbundle
vendored
2
vendor/packages/ruby.tmbundle
vendored
@ -1 +1 @@
|
||||
Subproject commit daad8ef03de9630e74578a046240fd9acc63b8b5
|
||||
Subproject commit 4b74231cb27d8d6e8e917947c566d21ec9d6e76a
|
Loading…
Reference in New Issue
Block a user